Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(client): query extensions type and runtime issues #16563

Merged
merged 1 commit into from
Dec 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 12 additions & 4 deletions packages/client/src/generation/TSClient/PrismaClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,28 +81,36 @@ function clientExtensionsQueryDefinition(this: PrismaClientClass) {
return `${acc}
${action}: {
args: Prisma.${getModelArgName(modelName, action)}<ExtArgs>,
result: Promise<runtime.Types.Utils.OptionalFlat<${modelName}>>
queryExtCbArgs: { model: '${modelName}', operation: '${action}', args: runtime.Types.Extensions.ReadonlySelector<TypeMap<ExtArgs>['${modelName}']['${action}']['args']>, query: (args: object) => PrismaPromise<runtime.Types.Utils.OptionalFlat<${modelName}>> },
result: runtime.Types.Utils.OptionalFlat<${modelName}>
}`
}, '')}
}`
},
'',
)}
}`
const queryCbDefinition = (modelName: string, operationName: string) => {
const queryArgs = `runtime.Types.Extensions.ReadonlySelector<Prisma.TypeMap<ExtArgs>[${modelName}][${operationName}]['args']>`
const queryResult = `Prisma.TypeMap<ExtArgs>[${modelName}][${operationName}]['result']`
const inputQuery = `{ model: ${modelName}, operation: ${operationName}, args: ${queryArgs} }`
const inputQueryCb = `${inputQuery} & { query: (args: ${queryArgs}) => PrismaPromise<${queryResult}> }`

return `(args: ${inputQueryCb}) => Promise<${queryResult}>`
}

const allOperationsParam = (modelNames: string[], indent: string) => {
const key = modelNames.length > 1 ? `keyof Prisma.TypeMap<ExtArgs>` : `'${modelNames[0]}'`

return `{
${indent}$allOperations?: (args: Prisma.TypeMap<ExtArgs>[${key}][keyof Prisma.TypeMap<ExtArgs>[${key}]]['queryExtCbArgs']) => Prisma.TypeMap<ExtArgs>[${key}][keyof Prisma.TypeMap<ExtArgs>[${key}]]['result']
${indent}$allOperations?: ${queryCbDefinition(key, `keyof Prisma.TypeMap<ExtArgs>[${key}]`)}
${indent}}`
}

const modelParam = (propName: string, modelNames: string[]) => {
const key = modelNames.length > 1 ? `keyof Prisma.TypeMap<ExtArgs>` : `'${modelNames[0]}'`

return `${propName}?: {
[K in keyof Prisma.TypeMap<ExtArgs>[${key}]]?: (args: Prisma.TypeMap<ExtArgs>[${key}][K]['queryExtCbArgs']) => Prisma.TypeMap<ExtArgs>[${key}][K]['result']
[K in keyof Prisma.TypeMap<ExtArgs>[${key}]]?: ${queryCbDefinition(key, `K`)}
} & ${allOperationsParam(modelNames, ' ')}`
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ function iterateAndCallQueryCallbacks(
return queryCbs[i]({
model: params.model,
operation: params.action,
args: klona(params.args),
args: klona(params.args ?? {}),
SevInf marked this conversation as resolved.
Show resolved Hide resolved
query: (args) => {
params.args = args
return iterateAndCallQueryCallbacks(client, params, queryCbs, i + 1)
Expand Down
67 changes: 54 additions & 13 deletions packages/client/tests/functional/extensions/enabled/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ testMatrix.setupTestSuite(
await prisma.$disconnect()
})

testIf(process.platform !== 'win32')('extending a specific model query', async () => {
test('extending a specific model query', async () => {
const fnUser = jest.fn()
const fnPost = jest.fn()
const fnEmitter = jest.fn()
Expand Down Expand Up @@ -94,7 +94,7 @@ testMatrix.setupTestSuite(
await waitFor(() => expect(fnEmitter).toHaveBeenCalledTimes(1))
})

testIf(process.platform !== 'win32')('top to bottom execution order', async () => {
test('top to bottom execution order', async () => {
let i = 0
const fnUser1 = jest.fn()
const fnUser2 = jest.fn()
Expand Down Expand Up @@ -136,7 +136,7 @@ testMatrix.setupTestSuite(
await waitFor(() => expect(fnEmitter).toHaveBeenCalledTimes(1))
})

testIf(process.platform !== 'win32')('args mutation isolation', async () => {
test('args mutation isolation', async () => {
const fnEmitter = jest.fn()
const fnUser = jest.fn()

Expand Down Expand Up @@ -195,7 +195,7 @@ testMatrix.setupTestSuite(
await waitFor(() => expect(fnEmitter).toHaveBeenCalledTimes(1))
})

testIf(process.platform !== 'win32')('args mutation accumulation', async () => {
test('args mutation accumulation', async () => {
const fnUser = jest.fn()
const fnEmitter = jest.fn()

Expand Down Expand Up @@ -243,7 +243,7 @@ testMatrix.setupTestSuite(
await waitFor(() => expect(fnEmitter).toHaveBeenCalledTimes(1))
})

testIf(process.platform !== 'win32')('query result override with a simple call', async () => {
test('query result override with a simple call', async () => {
const fnEmitter = jest.fn()

prisma.$on('query', fnEmitter)
Expand Down Expand Up @@ -301,7 +301,7 @@ testMatrix.setupTestSuite(
await wait(() => expect(fnEmitter).not.toHaveBeenCalled())
})

testIf(process.platform !== 'win32')('query result override with extra extension before', async () => {
test('query result override with extra extension before', async () => {
const fnEmitter = jest.fn()
const fnUser = jest.fn()

Expand Down Expand Up @@ -337,7 +337,7 @@ testMatrix.setupTestSuite(
await wait(() => expect(fnEmitter).not.toHaveBeenCalled())
})

testIf(process.platform !== 'win32')('query result mutation with a simple call', async () => {
test('query result mutation with a simple call', async () => {
const fnEmitter = jest.fn()

prisma.$on('query', fnEmitter)
Expand Down Expand Up @@ -369,7 +369,7 @@ testMatrix.setupTestSuite(
await waitFor(() => expect(fnEmitter).toHaveBeenCalledTimes(1))
})

testIf(process.platform !== 'win32')('query result mutation with multiple calls', async () => {
test('query result mutation with multiple calls', async () => {
const fnEmitter = jest.fn()

prisma.$on('query', fnEmitter)
Expand Down Expand Up @@ -662,7 +662,7 @@ testMatrix.setupTestSuite(
},
)

testIf(process.platform !== 'win32')('extending with $allModels and a specific query', async () => {
test('extending with $allModels and a specific query', async () => {
const fnModel = jest.fn()
const fnEmitter = jest.fn()

Expand Down Expand Up @@ -705,7 +705,7 @@ testMatrix.setupTestSuite(
await waitFor(() => expect(fnEmitter).toHaveBeenCalledTimes(2))
})

testIf(process.platform !== 'win32')('extending with $allModels and $allOperations', async () => {
test('extending with $allModels and $allOperations', async () => {
const fnModel = jest.fn()
const fnEmitter = jest.fn()

Expand Down Expand Up @@ -769,7 +769,7 @@ testMatrix.setupTestSuite(
await waitFor(() => expect(fnEmitter).toHaveBeenCalledTimes(3))
})

testIf(process.platform !== 'win32')('extending with specific model and $allOperations', async () => {
test('extending with specific model and $allOperations', async () => {
const fnModel = jest.fn()
const fnEmitter = jest.fn()

Expand Down Expand Up @@ -830,7 +830,7 @@ testMatrix.setupTestSuite(
await waitFor(() => expect(fnEmitter).toHaveBeenCalledTimes(2))
})

testIf(process.platform !== 'win32')('errors in callback', async () => {
test('errors in callback', async () => {
const xprisma = prisma.$extends({
name: 'Faulty query ext',
query: {
Expand All @@ -847,7 +847,7 @@ testMatrix.setupTestSuite(
)
})

testIf(process.platform !== 'win32')('errors in with no extension name', async () => {
test('errors in with no extension name', async () => {
const xprisma = prisma.$extends({
query: {
user: {
Expand All @@ -862,6 +862,47 @@ testMatrix.setupTestSuite(
`Error caused by an extension: All is lost!`,
)
})

test('empty args becomes an empty object', async () => {
const fnUser1 = jest.fn()
const fnEmitter = jest.fn()

prisma.$on('query', fnEmitter)

const xprisma = prisma.$extends({
query: {
user: {
findFirst({ args, query, operation, model }) {
fnUser1(args)
return query(args)
},
},
},
})

const args = undefined

const data = await xprisma.user.findFirst(args)

expect(data).not.toBe(null)
expect(fnUser1).toHaveBeenCalledWith({})
expect(fnUser1).toHaveBeenCalledTimes(1)
await waitFor(() => expect(fnEmitter).toHaveBeenCalledTimes(1))
})

test('passing incorrect argument errors', () => {
prisma.$extends({
query: {
user: {
async findFirst({ args, query, operation, model }) {
const user = await query(args)
// @ts-expect-error
return query(user)
SevInf marked this conversation as resolved.
Show resolved Hide resolved
},
},
},
})
})
},
{
skipDefaultClientInstance: true,
Expand Down