Skip to main content
Glama

Prisma MCP Server

Official
by prisma
Apache 2.0
4
44,213
  • Linux
  • Apple
model.ts26.4 kB
/* eslint-disable @typescript-eslint/no-redundant-type-constituents */ import { expectTypeOf } from 'expect-type' import { Providers } from '../_utils/providers' import { waitFor } from '../_utils/tests/waitFor' import { NewPrismaClient } from '../_utils/types' import { providersSupportingRelationJoins } from '../relation-load-strategy/_common' import testMatrix from './_matrix' // @ts-ignore import type { Prisma as PrismaNamespace, PrismaClient } from './generated/prisma/client' declare let Prisma: typeof PrismaNamespace let prisma: PrismaClient declare const newPrismaClient: NewPrismaClient<PrismaClient, typeof PrismaClient> testMatrix.setupTestSuite( ({ provider, driverAdapter, clientEngineExecutor }, _suiteMeta, _clientMeta, cliMeta) => { const isSqlServer = provider === Providers.SQLSERVER beforeEach(() => { prisma = newPrismaClient({ log: [{ emit: 'event', level: 'query' }], }) }) afterEach(async () => { await prisma.$disconnect() }) test('extend specific model', () => { const extMethod = jest.fn() const xprisma = prisma.$extends({ model: { user: { extMethod, }, }, }) xprisma.user.extMethod() expect(extMethod).toHaveBeenCalledTimes(1) expect((xprisma.post as any).extMethod).toBeUndefined() }) test('chain $on with $extends', () => { const fnEmitter = jest.fn() const extMethod = jest.fn() const xprisma = newPrismaClient({ log: [{ emit: 'event', level: 'query' }], }) // @ts-expect-error - client not typed for log opts for cross generator compatibility - can be improved once we drop the prisma-client-js generator .$on('query', fnEmitter) .$extends({ model: { user: { extMethod, }, }, }) xprisma.user.extMethod() expect(extMethod).toHaveBeenCalledTimes(1) }) test('extend all models', () => { const extMethod = jest.fn() const xprisma = prisma.$extends({ model: { $allModels: { extMethod, }, }, }) xprisma.user.extMethod() xprisma.post.extMethod() expect(extMethod).toHaveBeenCalledTimes(2) }) test('pass arguments to ext method', () => { const extMethod = jest.fn() const xprisma = prisma.$extends({ model: { user: { extMethod, }, }, }) xprisma.user.extMethod('hello', 'world') expect(extMethod).toHaveBeenCalledWith('hello', 'world') }) test('return value to ext method', () => { const extMethod = jest.fn().mockReturnValue('hi!') const xprisma = prisma.$extends({ model: { user: { extMethod, }, }, }) expect(xprisma.user.extMethod()).toBe('hi!') }) test('specific model extension has precedence over $allModels', () => { const genericMethod = jest.fn() const specificMethod = jest.fn() const xprisma = prisma.$extends({ model: { $allModels: { extMethod: genericMethod, }, user: { extMethod: specificMethod, }, }, }) xprisma.user.extMethod() expect(specificMethod).toHaveBeenCalled() expect(genericMethod).not.toHaveBeenCalled() }) test('last extension takes precedence over earlier ones', () => { const firstMethod = jest.fn() const secondMethod = jest.fn() const xprisma = prisma .$extends({ model: { user: { extMethod: firstMethod, }, }, }) .$extends({ model: { user: { extMethod: secondMethod, }, }, }) xprisma.user.extMethod() expect(secondMethod).toHaveBeenCalled() expect(firstMethod).not.toHaveBeenCalled() }) test('allows to override built-in methods', () => { const extMethod = jest.fn() const xprisma = prisma.$extends({ model: { user: { findFirst() { extMethod() return undefined }, }, }, }) const findFirstData = xprisma.user.findFirst() // @ts-expect-error void xprisma.user.findFirst({}) expect(findFirstData).toBeUndefined() expectTypeOf(findFirstData).toEqualTypeOf<undefined>() expect(extMethod).toHaveBeenCalled() }) test('non-conflicting extensions can co-exist', () => { const firstMethod = jest.fn() const secondMethod = jest.fn() const xprisma = prisma .$extends({ model: { user: { firstMethod, }, }, }) .$extends({ model: { user: { secondMethod, }, }, }) xprisma.user.firstMethod() xprisma.user.secondMethod() expect(firstMethod).toHaveBeenCalled() expect(secondMethod).toHaveBeenCalled() }) test('extension methods can call each other', () => { const helper = jest.fn() const xprisma = prisma.$extends({ model: { user: { helper, extMethod() { this.helper() }, }, }, }) xprisma.user.extMethod() expect(helper).toHaveBeenCalled() }) test('extension methods can call model methods', async () => { const xprisma = prisma.$extends({ model: { user: { myFind() { const ctx = Prisma.getExtensionContext(this) return ctx.findMany({}) }, }, }, }) const users = await xprisma.user.myFind() expect(users).toEqual([]) }) test('extension methods can call methods of other extensions', () => { const firstMethod = jest.fn() const xprisma = prisma .$extends({ model: { user: { firstMethod, }, }, }) .$extends({ model: { user: { secondMethod() { const ctx = Prisma.getExtensionContext(this) ctx.firstMethod() }, }, }, }) xprisma.user.secondMethod() expect(firstMethod).toHaveBeenCalled() }) test('empty extension does nothing', async () => { const xprisma = prisma .$extends({ model: { user: { myFind() { const ctx = Prisma.getExtensionContext(this) return ctx.findMany({}) }, }, }, }) .$extends({}) .$extends({ model: { user: {}, }, }) const users = await xprisma.user.myFind() expect(users).toEqual([]) }) test('only accepts methods', () => { prisma.$extends({ // TODO -@-ts-expect-error model: { badInput: 1, }, }) }) test('error in extension methods', () => { const xprisma = prisma.$extends({ name: 'Faulty model', model: { user: { fail() { throw new Error('Fail!') }, }, }, }) expect(() => xprisma.user.fail()).toThrowErrorMatchingInlineSnapshot(`"Fail!"`) }) test('error in async methods', async () => { const xprisma = prisma.$extends({ name: 'Faulty model', model: { user: { fail() { return Promise.reject(new Error('Fail!')) }, }, }, }) await expect(xprisma.user.fail()).rejects.toThrowErrorMatchingInlineSnapshot(`"Fail!"`) }) test('error in async PrismaPromise methods', async () => { const xprisma = prisma.$extends((client) => { return client.$extends({ name: 'Faulty model', model: { user: { fail() { const ctx = Prisma.getExtensionContext(this) return ctx.findUnique({ // @ts-expect-error badInput: true, }) }, }, }, }) }) if (cliMeta.previewFeatures.includes('relationJoins') && providersSupportingRelationJoins.includes(provider)) { await expect(xprisma.user.fail()).rejects.toThrowErrorMatchingInlineSnapshot(` " Invalid \`prisma.user.findUnique()\` invocation: { badInput: true, ~~~~~~~~ ? where?: UserWhereUniqueInput, ? relationLoadStrategy?: RelationLoadStrategy } Unknown argument \`badInput\`. Available options are marked with ?." `) } else { await expect(xprisma.user.fail()).rejects.toThrowErrorMatchingInlineSnapshot(` " Invalid \`prisma.user.findUnique()\` invocation: { badInput: true, ~~~~~~~~ ? where?: UserWhereUniqueInput } Unknown argument \`badInput\`. Available options are marked with ?." `) } }) testIf(provider !== Providers.MONGODB && process.platform !== 'win32')( 'batching of PrismaPromise returning custom model methods', async () => { const fnEmitter = jest.fn() // @ts-expect-error - client not typed for log opts for cross generator compatibility - can be improved once we drop the prisma-client-js generator prisma.$on('query', fnEmitter) const xprisma = prisma.$extends({ model: { user: { fn() { const ctx = Prisma.getExtensionContext(this) return Object.assign(ctx.findFirst(), { prop: 'value' }) }, }, }, }) const data = await xprisma.$transaction([xprisma.user.fn(), xprisma.user.fn()]) expect(data).toMatchInlineSnapshot(` [ null, null, ] `) await waitFor(() => { const expectation = [ [{ query: expect.stringContaining('SELECT') }], [{ query: expect.stringContaining('SELECT') }], [{ query: expect.stringContaining('COMMIT') }], ] if (driverAdapter === undefined && clientEngineExecutor !== 'remote') { // Driver adapters do not issue BEGIN through the query engine. expectation.unshift([{ query: expect.stringContaining('BEGIN') }]) if (isSqlServer) { expectation.unshift([{ query: expect.stringContaining('SET TRANSACTION') }]) } } expect(fnEmitter).toHaveBeenCalledTimes(expectation.length) expect(fnEmitter.mock.calls).toMatchObject(expectation) }) }, ) testIf(provider !== Providers.MONGODB && process.platform !== 'win32')( 'batching of PrismaPromise returning custom model methods and query', async () => { const fnEmitter = jest.fn() // @ts-expect-error - client not typed for log opts for cross generator compatibility - can be improved once we drop the prisma-client-js generator prisma.$on('query', fnEmitter) const xprisma = prisma .$extends({ model: { user: { fn() { const ctx = Prisma.getExtensionContext(this) return Object.assign(ctx.findFirst(), { prop: 'value' }) }, }, }, }) .$extends({ query: { $allModels: { async $allOperations({ query, args }) { // test if await has any side effects const data = await query(args) return data }, }, }, }) const data = await xprisma.$transaction([xprisma.user.fn(), xprisma.user.fn()]) expect(data).toMatchInlineSnapshot(` [ null, null, ] `) await waitFor(() => { const expectation = [ [{ query: expect.stringContaining('SELECT') }], [{ query: expect.stringContaining('SELECT') }], [{ query: expect.stringContaining('COMMIT') }], ] if (driverAdapter === undefined && clientEngineExecutor !== 'remote') { // Driver adapters do not issue BEGIN through the query engine. expectation.unshift([{ query: expect.stringContaining('BEGIN') }]) if (isSqlServer) { expectation.unshift([{ query: expect.stringContaining('SET TRANSACTION') }]) } } expect(fnEmitter).toHaveBeenCalledTimes(expectation.length) expect(fnEmitter.mock.calls).toMatchObject(expectation) }) }, ) test('error in extension methods without name', () => { const xprisma = prisma.$extends({ model: { user: { fail() { throw new Error('Fail!') }, }, }, }) expect(() => xprisma.user.fail()).toThrowErrorMatchingInlineSnapshot(`"Fail!"`) }) test('custom method re-using input types to augment them via intersection', () => { const xprisma = prisma.$extends({ model: { $allModels: { findFirstOrCreate<T, A>( this: T, args: PrismaNamespace.Exact< A, PrismaNamespace.Args<T, 'findUniqueOrThrow'> & { cache: boolean } >, ): A { return args as any as A }, }, }, }) const args = xprisma.user.findFirstOrCreate({ cache: true, where: { id: '1', }, }) expectTypeOf(args).toHaveProperty('cache').toEqualTypeOf<true>() expectTypeOf(args).toHaveProperty('where').toEqualTypeOf<{ id: '1' }>() }) test('custom method re-using input types to augment them via mapped type', () => { type Nullable<T> = { [K in keyof T]: T[K] | null } const xprisma = prisma.$extends({ model: { $allModels: { findFirstOrCreate<T, A>( this: T, args: PrismaNamespace.Exact<A, Nullable<PrismaNamespace.Args<T, 'findUniqueOrThrow'>>>, ): A { return args as any }, }, }, }) const args = xprisma.user.findFirstOrCreate({ include: null, where: { id: '1', }, }) expectTypeOf(args).toHaveProperty('include').toEqualTypeOf<null>() expectTypeOf(args).toHaveProperty('where').toEqualTypeOf<{ id: '1' }>() }) test('custom method re-using output to augment it via intersection', () => { const xprisma = prisma.$extends({ model: { $allModels: { findFirstOrCreate<T, A>( this: T, _args: PrismaNamespace.Exact<A, PrismaNamespace.Args<T, 'findUniqueOrThrow'>>, ): PrismaNamespace.Result<T, A, 'findUniqueOrThrow'> & { extra: boolean } { return {} as any }, }, }, }) const data = xprisma.user.findFirstOrCreate({ where: { id: '1', }, }) expectTypeOf(data).toHaveProperty('extra').toEqualTypeOf<boolean>() expectTypeOf(data).toHaveProperty('id').toEqualTypeOf<string>() expectTypeOf(data).toHaveProperty('email').toEqualTypeOf<string>() expectTypeOf(data).toHaveProperty('firstName').toEqualTypeOf<string>() expectTypeOf(data).toHaveProperty('lastName').toEqualTypeOf<string>() }) test('custom method re-using payload output types', () => { const xprisma = prisma.$extends({ model: { $allModels: { findFirstOrCreate<T>(this: T) { return {} as PrismaNamespace.Payload<T, 'findUniqueOrThrow'> }, }, }, }) const data = xprisma.user.findFirstOrCreate() expectTypeOf<typeof data>().toHaveProperty('scalars').toMatchTypeOf<object>() expectTypeOf<typeof data>().toHaveProperty('objects').toMatchTypeOf<object>() expectTypeOf<(typeof data)['scalars']>().toHaveProperty('id').toMatchTypeOf<string>() expectTypeOf<(typeof data)['objects']>().toHaveProperty('posts').toMatchTypeOf<object>() expectTypeOf<(typeof data)['objects']['posts']>().toMatchTypeOf<object[]>() expectTypeOf<(typeof data)['objects']['posts'][0]>().toMatchTypeOf<object>() expectTypeOf<(typeof data)['objects']['posts'][0]>().toHaveProperty('scalars').toMatchTypeOf<object>() expectTypeOf<(typeof data)['objects']['posts'][0]>().toHaveProperty('objects').toMatchTypeOf<object>() }) test('custom method that uses exact for narrowing inputs', () => { const xprisma = prisma.$extends({ model: { $allModels: { findFirstOrCreate<T, A>( this: T, _args: PrismaNamespace.Exact< A, { guestName: string foodChoice: ('starters' | 'main' | 'desert' | 'drink')[] allergies: string[] vegan: boolean } >, ): A { return {} as any }, }, }, }) const data = xprisma.user.findFirstOrCreate({ allergies: ['nuts'], foodChoice: ['starters', 'main'], guestName: 'John', vegan: false, }) expectTypeOf(data).toEqualTypeOf<{ allergies: ['nuts'] foodChoice: ['starters', 'main'] guestName: 'John' vegan: false }>() void xprisma.user.findFirstOrCreate({ // @ts-expect-error allergies: 'invalid', // @ts-expect-error foodChoice: ['starters', 'invalid'], // @ts-expect-error guestName: null, // @ts-expect-error vegan: 'invalid', }) }) test('custom method that uses exact for narrowing generic inputs', () => { type Pick<T, K extends string | number | symbol> = { [P in keyof T as P extends K ? P : never]: T[P] } type Input<T> = { where: Pick< PrismaNamespace.Args<T, 'findMany'>['where'], keyof PrismaNamespace.Payload<T, 'findMany'>['scalars'] > include: PrismaNamespace.Args<T, 'findMany'>['include'] } const xprisma = prisma.$extends({ model: { $allModels: { findFirstOrCreate<T, A>(this: T, _args: PrismaNamespace.Exact<A, Input<T>>): A { return {} as any }, }, }, }) const data = xprisma.user.findFirstOrCreate({ where: { email: 'test', // @ts-expect-error posts: { none: { id: '1', }, }, }, include: {}, }) expectTypeOf(data).toEqualTypeOf<{ where: { email: 'test' posts: { none: { id: '1' } } } include: {} }>() }) test('getExtension context on specific model and non-generic this', () => { const xprisma = prisma.$extends({ model: { user: { myCustomCallB() {}, myCustomCallA() { const ctx = Prisma.getExtensionContext(this) expect(ctx.name).toEqual('User') expect(ctx.$name).toEqual('User') return ctx }, }, }, }) const ctx = xprisma.user.myCustomCallA() expectTypeOf(ctx).toHaveProperty('name').toEqualTypeOf<'User'>() expectTypeOf(ctx).toHaveProperty('myCustomCallB').toEqualTypeOf<() => void>() expectTypeOf(ctx).toHaveProperty('update').toMatchTypeOf<Function>() }) test('getExtension context on generic model and non-generic this', () => { const xprisma = prisma.$extends({ model: { $allModels: { myCustomCallB() {}, myCustomCallA() { const ctx = Prisma.getExtensionContext(this) expect(ctx.name).toEqual('User') expect(ctx.$name).toEqual('User') return ctx }, }, }, }) const ctx = xprisma.user.myCustomCallA() expectTypeOf(ctx).toHaveProperty('name').toEqualTypeOf<string | undefined>() expectTypeOf(ctx).toHaveProperty('$name').toEqualTypeOf<string | undefined>() expectTypeOf(ctx).toHaveProperty('myCustomCallB').toEqualTypeOf<() => void>() expectTypeOf(ctx).not.toHaveProperty('update') }) test('getExtension context on specific model and generic this', () => { const xprisma = prisma.$extends({ model: { user: { myCustomCallB() {}, myCustomCallA<T>(this: T) { const ctx = Prisma.getExtensionContext(this) expect(ctx.name).toEqual('User') expect(ctx.$name).toEqual('User') return ctx }, }, }, }) const ctx = xprisma.user.myCustomCallA() expectTypeOf(ctx).toHaveProperty('name').toEqualTypeOf<string | undefined>() expectTypeOf(ctx).toHaveProperty('$name').toEqualTypeOf<string | undefined>() expectTypeOf(ctx).toHaveProperty('myCustomCallB').toEqualTypeOf<() => void>() expectTypeOf(ctx).toHaveProperty('update').toMatchTypeOf<Function>() }) test('getExtension context on generic model and generic this', () => { const xprisma = prisma.$extends({ model: { $allModels: { myCustomCallB() {}, myCustomCallA<T>(this: T) { const ctx = Prisma.getExtensionContext(this) expect(ctx.name).toEqual('User') expect(ctx.$name).toEqual('User') return ctx }, }, }, }) const ctx = xprisma.user.myCustomCallA() expectTypeOf(ctx).toHaveProperty('name').toEqualTypeOf<string | undefined>() expectTypeOf(ctx).toHaveProperty('$name').toEqualTypeOf<string | undefined>() expectTypeOf(ctx).toHaveProperty('myCustomCallB').toEqualTypeOf<() => void>() expectTypeOf(ctx).toHaveProperty('update').toMatchTypeOf<Function>() }) test('one specific user extension along a generic $allModels model extension', () => { const myCustomCallA = jest.fn() const myCustomCallB = jest.fn() const xprisma = prisma.$extends({ model: { user: { myCustomCallB(input: string) { myCustomCallB(input) return input }, }, $allModels: { myCustomCallA(input: number) { myCustomCallA(input) return input }, }, }, }) const results = [ xprisma.user.myCustomCallA(42), xprisma.user.myCustomCallA(42), xprisma.user.myCustomCallB('Hello'), ] as const // @ts-expect-error expect(() => xprisma.post.myCustomCallB('Hello')).toThrow() expect(results).toEqual([42, 42, 'Hello']) expectTypeOf(results).toEqualTypeOf<readonly [number, number, string]>() expect(myCustomCallA).toHaveBeenCalledTimes(2) expect(myCustomCallA).toHaveBeenCalledWith(42) expect(myCustomCallB).toHaveBeenCalledTimes(1) expect(myCustomCallB).toHaveBeenCalledWith('Hello') }) test('does not allow to pass invalid properties', async () => { const xprisma = prisma.$extends({}) await expect( xprisma.user.findFirst({ // @ts-expect-error invalid: true, }), ).rejects.toThrow() }) test('input type should be able to be passed to method accepting same input types', () => { const xprisma = prisma.$extends({}) const args: PrismaNamespace.UserUpsertArgs = { where: { id: '1', }, create: { email: 'test', firstName: 'test', lastName: 'test', }, update: {}, } void prisma.user.upsert(args) void xprisma.user.upsert(args) }) test('an extension can also reference a previous one via parent on a specific model', async () => { const xprisma = prisma .$extends({ model: { user: { async findFirst(a: 'SomeString') { return Promise.resolve(a) }, }, }, }) .$extends({ model: { user: { async findFirst() { const ctx = Prisma.getExtensionContext(this) const data = await ctx.$parent.user.findFirst('SomeString') expect(data).toEqual('SomeString') expectTypeOf(data).toEqualTypeOf<'SomeString'>() }, }, }, }) await xprisma.user.findFirst() expect.assertions(1) }) test('an extension can also reference a previous one via parent on $allModels', async () => { const xprisma = prisma .$extends({ model: { user: { async findFirst(a: 'SomeString') { return Promise.resolve(a) }, }, }, }) .$extends({ model: { $allModels: { async findFirst() { const ctx = Prisma.getExtensionContext(this) const data = await ctx.$parent!['user'].findFirst('SomeString') expect(data).toEqual('SomeString') expectTypeOf(data).toEqualTypeOf<any>() }, }, }, }) await xprisma.user.findFirst() expect.assertions(1) }) }, { skipDefaultClientInstance: true, }, )

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/prisma/prisma'

If you have feedback or need assistance with the MCP directory API, please join our Discord server