Skip to content

Cannot type extended client in function arguments #20326

@franky47

Description

@franky47

Bug description

Some context

I maintain a middleware that provides field-level encryption, which I'm porting to the client extension mechanism (PR 47ng/prisma-field-encryption#66).

It can also be used as a generator to generate data migration files to rotate encryption keys. Those generated files look like the following: a function that takes a Prisma client as an argument to perform the work.

import { PrismaClient } from '.location/to/generated/prisma/client/to/allow/custom/locations'

export async function migrate(client: PrismaClient) {
  // generated migration code
}

The reason why it's accepting a client rather than creating one itself is because of configuration of the middleware/extension, and to potentially include other middleware and extensions. The idea is to make the data migration process as transparent as possible: reading & updating records in an iterative manner, just as it would be done in application code.

The problem

The issue I encounter is with defining the type of client and connecting it to extended client type definitions. I'd like to maintain both a middleware and extension API for this library, for retrocompatibility, until Prisma drops middleware support entirely.

In integration tests, it seems like the only way to have a client type be either middleware-based or extension-based is to explicitly cast the extended client as PrismaClient, see 47ng/prisma-field-encryption#63 (comment).

There are also some discrepancies between using the PrismaClient type imported from @prisma/client vs .custom/client/location. The former doesn't seem to support interactive transaction types (the client argument of the transaction callback is typed as any), but the latter seems to fit the bill for all model operations, which is what we're using to allow supporting custom client locations anyway.

That being said, this PrismaClient type is not type-compatible with extended clients. Runtime compatibility is fine, all model operations work if using directives like explicit type casts or // @ts-ignore.

TL;DR: There doesn't seem to be a way at the moment to type extended clients as arguments of standalone functions.

cc @millsp

How to reproduce

Reproduction repository: https://github.com/franky47/prisma-field-encryption-sandbox

Steps to reproduce:

  1. Clone the repository
  2. Install dependencies with yarn install
  3. Generate migration files with prisma generate
  4. Open the migrate.ts file, and remove the as PrismaClient type cast
  5. Observe an error

Expected behavior

There should be a type definition for extended clients that allows them to be passed to external functions.

While the extended API can include anything added by extensions, having at least the ability to get a working type for the basic functionality (model operations, but not allowing $use or $on) would already be a great start. Those types could then be augmented by users if need be at specific call site locations with added methods from extensions.

Prisma information

datasource db {
  provider = "sqlite"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
  output   = "../prisma-client" // needs to support custom client locations
}

generator migrations {
  provider = "prisma-field-encryption"
  output   = "./data-migrations"
}

model Post {
  id        Int     @id @default(autoincrement())
  title     String
  content   String? /// @encrypted <- annotate fields to encrypt (must be a String)
  published Boolean @default(false)
  author    User?   @relation(fields: [authorId], references: [id], onDelete: Cascade, onUpdate: Cascade)
  authorId  Int?
}

model User {
  id    Int     @id @default(autoincrement())
  email String  @unique
  name  String? /// @encrypted
  posts Post[]
}
import { fieldEncryptionExtension } from 'prisma-field-encryption'
import { PrismaClient } from './prisma-client'
import { migrate } from './prisma/data-migrations'

async function main() {
  const prisma = new PrismaClient().$extends(fieldEncryptionExtension())
  await migrate(prisma)
}

main()

Environment & setup

  • OS: macOS 12.6.7
  • Database: SQLite (but irrelevant to the issue)
  • Node.js version: 18.12.0

Prisma Version

5.0.0

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions