diff --git a/src/packages/client/src/__tests__/integration/happy/middlewares-transaction/.gitignore b/src/packages/client/src/__tests__/integration/happy/middlewares-transaction/.gitignore new file mode 100644 index 000000000000..97952752a72b --- /dev/null +++ b/src/packages/client/src/__tests__/integration/happy/middlewares-transaction/.gitignore @@ -0,0 +1 @@ +!dev.db \ No newline at end of file diff --git a/src/packages/client/src/__tests__/integration/happy/middlewares-transaction/dev.db b/src/packages/client/src/__tests__/integration/happy/middlewares-transaction/dev.db new file mode 100644 index 000000000000..ea3cf06fb5a6 Binary files /dev/null and b/src/packages/client/src/__tests__/integration/happy/middlewares-transaction/dev.db differ diff --git a/src/packages/client/src/__tests__/integration/happy/middlewares-transaction/package.json b/src/packages/client/src/__tests__/integration/happy/middlewares-transaction/package.json new file mode 100644 index 000000000000..1b535ed921ff --- /dev/null +++ b/src/packages/client/src/__tests__/integration/happy/middlewares-transaction/package.json @@ -0,0 +1,12 @@ +{ + "name": "my-prisma-project", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/src/packages/client/src/__tests__/integration/happy/middlewares-transaction/schema.prisma b/src/packages/client/src/__tests__/integration/happy/middlewares-transaction/schema.prisma new file mode 100644 index 000000000000..2b0879c1f46c --- /dev/null +++ b/src/packages/client/src/__tests__/integration/happy/middlewares-transaction/schema.prisma @@ -0,0 +1,28 @@ +datasource db { + provider = "sqlite" + url = "file:dev.db" +} + +generator client { + provider = "prisma-client-js" +} + +// / User model comment +model User { + id String @id @default(uuid()) + email String @unique + // / name comment + name String? + posts Post[] +} + +model Post { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + published Boolean + title String + content String? + authorId String? + author User? @relation(fields: [authorId], references: [id]) +} diff --git a/src/packages/client/src/__tests__/integration/happy/middlewares-transaction/test.ts b/src/packages/client/src/__tests__/integration/happy/middlewares-transaction/test.ts new file mode 100644 index 000000000000..098853c095fb --- /dev/null +++ b/src/packages/client/src/__tests__/integration/happy/middlewares-transaction/test.ts @@ -0,0 +1,35 @@ +import { getTestClient } from '../../../../utils/getTestClient' + +describe('middleware and transaction', () => { + test('typeof await next()', async () => { + const PrismaClient = await getTestClient() + + const prisma = new PrismaClient() + await prisma.user.deleteMany() + + const responses: any[] = [] + prisma.$use(async (params, next) => { + const response = await next(params) + responses.push(response) + return response + }) + + await prisma.$transaction([ + prisma.user.create({ + data: { + email: 'test@test.com', + name: 'test', + }, + }), + ]) + expect(typeof responses[0]).toEqual(`object`) + expect(responses[0].email).toMatchInlineSnapshot(`test@test.com`) + + const users = await prisma.user.findMany() + expect(users[0].email).toMatchInlineSnapshot(`test@test.com`) + + await prisma.user.deleteMany() + + prisma.$disconnect() + }) +}) diff --git a/src/packages/client/src/runtime/getPrismaClient.ts b/src/packages/client/src/runtime/getPrismaClient.ts index eb784432a7d4..e9c79851af3d 100644 --- a/src/packages/client/src/runtime/getPrismaClient.ts +++ b/src/packages/client/src/runtime/getPrismaClient.ts @@ -435,16 +435,16 @@ export function getPrismaClient(config: GetPrismaClientOptions): any { * Hook a middleware into the client * @param middleware to hook */ - $use(middleware: QueryMiddleware) - $use(namespace: 'all', cb: QueryMiddleware) - $use(namespace: 'engine', cb: EngineMiddleware) - $use( - arg0: Namespace | QueryMiddleware, - arg1?: QueryMiddleware | EngineMiddleware, + $use(middleware: QueryMiddleware) + $use(namespace: 'all', cb: QueryMiddleware) + $use(namespace: 'engine', cb: EngineMiddleware) + $use( + arg0: Namespace | QueryMiddleware, + arg1?: QueryMiddleware | EngineMiddleware, ) { // TODO use a mixin and move this into MiddlewareHandler if (typeof arg0 === 'function') { - this._middlewares.query.use(arg0) + this._middlewares.query.use(arg0 as QueryMiddleware) } else if (arg0 === 'all') { this._middlewares.query.use(arg1 as QueryMiddleware) } else if (arg0 === 'engine') { @@ -915,45 +915,40 @@ new PrismaClient({ * @param middlewareIndex * @returns */ - private _request( - internalParams: InternalRequestParams, - middlewareIndex = 0, - ): Promise { + private _request(internalParams: InternalRequestParams): Promise { try { - // in this recursion, we check for our terminating condition - const middleware = this._middlewares.query.get(middlewareIndex) + let index = -1 // async scope https://github.com/prisma/prisma/issues/3148 const resource = new AsyncResource('prisma-client-request') + // make sure that we don't leak extra properties to users + const params: QueryMiddlewareParams = { + args: internalParams.args, + dataPath: internalParams.dataPath, + runInTransaction: internalParams.runInTransaction, + action: internalParams.action, + model: internalParams.model, + } + + // prepare recursive fn that will pipe params through middlewares + const consumer = (changedParams: QueryMiddlewareParams) => { + // if this `next` was called and there's some more middlewares + const nextMiddleware = this._middlewares.query.get(++index) - if (middleware) { - // make sure that we don't leak extra properties to users - const params: QueryMiddlewareParams = { - args: internalParams.args, - dataPath: internalParams.dataPath, - runInTransaction: internalParams.runInTransaction, - action: internalParams.action, - model: internalParams.model, + if (nextMiddleware) { + // we pass the modfied params down to the next one, & repeat + return nextMiddleware(changedParams, consumer) } - return resource.runInAsyncScope(() => { - // call the middleware of the user & get their changes - return middleware(params, (changedParams) => { - // this middleware returns the value of the next one 🐛 - return this._request( - { - ...internalParams, - ...changedParams, - }, - ++middlewareIndex, - ) // recursion happens over here - }) - }) + const changedInternalParams = { ...internalParams, ...params } + + // TODO remove this, because transactionId should be passed? + if (index > 0) delete changedInternalParams['transactionId'] + + // no middleware? then we just proceed with request execution + return this._executeRequest(changedInternalParams) } - // they're finished, or there's none, then execute request - return resource.runInAsyncScope(() => { - return this._executeRequest(internalParams) - }) + return resource.runInAsyncScope(() => consumer(params)) } catch (e) { e.clientVersion = this._clientVersion