From bb2b4dcc98efadcbccf5e814cd53d3c5df9c1415 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 18 Jan 2026 03:38:28 +0000 Subject: [PATCH 1/3] Initial plan From d31632a3ce9e6d6afc7f217859cd8ad9b5684a3d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 18 Jan 2026 03:51:19 +0000 Subject: [PATCH 2/3] fix: merge $create and $update schemas for upsert validation - Handle upsert operation specially to match TypeScript type behavior - When both $create and $update schemas exist, merge them for upsert - Add test case to verify the fix works correctly Co-authored-by: ymc9 <104139426+ymc9@users.noreply.github.com> --- .../orm/src/client/crud/validator/index.ts | 28 ++++++ .../orm/plugin-infra/ext-query-args.test.ts | 88 +++++++++++++++++++ 2 files changed, 116 insertions(+) diff --git a/packages/orm/src/client/crud/validator/index.ts b/packages/orm/src/client/crud/validator/index.ts index 158b308e0..37c2e6e2f 100644 --- a/packages/orm/src/client/crud/validator/index.ts +++ b/packages/orm/src/client/crud/validator/index.ts @@ -392,6 +392,34 @@ export class InputValidator { if (operation in plugin.queryArgs && plugin.queryArgs[operation]) { // most specific operation takes highest precedence result = plugin.queryArgs[operation]; + } else if (operation === 'upsert') { + // upsert is special: it's in both CoreCreateOperations and CoreUpdateOperations + // so we need to merge both $create and $update schemas to match the type system + const createSchema = + '$create' in plugin.queryArgs && plugin.queryArgs['$create'] + ? plugin.queryArgs['$create'] + : undefined; + const updateSchema = + '$update' in plugin.queryArgs && plugin.queryArgs['$update'] + ? plugin.queryArgs['$update'] + : undefined; + + if (createSchema && updateSchema) { + invariant( + createSchema instanceof z.ZodObject, + 'Plugin extended query args schema must be a Zod object', + ); + invariant( + updateSchema instanceof z.ZodObject, + 'Plugin extended query args schema must be a Zod object', + ); + // merge both schemas using intersection + result = createSchema.merge(updateSchema); + } else if (createSchema) { + result = createSchema; + } else if (updateSchema) { + result = updateSchema; + } } else if ( // then comes grouped operations: $create, $read, $update, $delete CoreCreateOperations.includes(operation as CoreCreateOperations) && diff --git a/tests/e2e/orm/plugin-infra/ext-query-args.test.ts b/tests/e2e/orm/plugin-infra/ext-query-args.test.ts index 33e3d3fd5..d65f2f829 100644 --- a/tests/e2e/orm/plugin-infra/ext-query-args.test.ts +++ b/tests/e2e/orm/plugin-infra/ext-query-args.test.ts @@ -230,4 +230,92 @@ describe('Plugin extended query args', () => { await expect(db.user.findMany({ cache: { ttl: 2000 } })).rejects.toThrow('Unrecognized key'); await expect(extDb.user.findMany({ cache: { ttl: 2000 } })).toResolveWithLength(0); }); + + it('should merge $create and $update schemas for upsert operation', async () => { + // Define different schemas for $create and $update + const createOnlySchema = z.object({ + tracking: z + .strictObject({ + source: z.string().optional(), + }) + .optional(), + }); + + const updateOnlySchema = z.object({ + audit: z + .strictObject({ + reason: z.string().optional(), + }) + .optional(), + }); + + const extDb = db.$use( + definePlugin({ + id: 'test', + queryArgs: { + $create: createOnlySchema, + $update: updateOnlySchema, + }, + }), + ); + + // upsert should accept both tracking (from $create) and audit (from $update) + await expect( + extDb.user.upsert({ + where: { id: 999 }, + create: { name: 'Alice' }, + update: { name: 'Alice Updated' }, + tracking: { source: 'test' }, + audit: { reason: 'testing merge' }, + }), + ).resolves.toMatchObject({ name: 'Alice' }); + + // upsert should reject tracking-only in update operations + await expect( + extDb.user.update({ + where: { id: 1 }, + data: { name: 'Test' }, + // @ts-expect-error - tracking is only for $create + tracking: { source: 'test' }, + }), + ).rejects.toThrow('Unrecognized key'); + + // upsert should reject audit-only in create operations + await expect( + extDb.user.create({ + data: { name: 'Bob' }, + // @ts-expect-error - audit is only for $update + audit: { reason: 'test' }, + }), + ).rejects.toThrow('Unrecognized key'); + + // verify that upsert without both is fine + await expect( + extDb.user.upsert({ + where: { id: 888 }, + create: { name: 'Charlie' }, + update: { name: 'Charlie Updated' }, + }), + ).resolves.toMatchObject({ name: 'Charlie' }); + + // verify that upsert with only tracking is fine + await expect( + extDb.user.upsert({ + where: { id: 777 }, + create: { name: 'David' }, + update: { name: 'David Updated' }, + tracking: { source: 'test' }, + }), + ).resolves.toMatchObject({ name: 'David' }); + + // verify that upsert with only audit is fine + await expect( + extDb.user.upsert({ + where: { id: 666 }, + create: { name: 'Eve' }, + update: { name: 'Eve Updated' }, + audit: { reason: 'testing' }, + }), + ).resolves.toMatchObject({ name: 'Eve' }); + }); }); From 556cfc47986e9862e47ef0f3ebd89aeb2f8d1b58 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 18 Jan 2026 03:52:25 +0000 Subject: [PATCH 3/3] fix: improve comment accuracy about Zod merge behavior Co-authored-by: ymc9 <104139426+ymc9@users.noreply.github.com> --- packages/orm/src/client/crud/validator/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/orm/src/client/crud/validator/index.ts b/packages/orm/src/client/crud/validator/index.ts index 37c2e6e2f..77bc39197 100644 --- a/packages/orm/src/client/crud/validator/index.ts +++ b/packages/orm/src/client/crud/validator/index.ts @@ -413,7 +413,7 @@ export class InputValidator { updateSchema instanceof z.ZodObject, 'Plugin extended query args schema must be a Zod object', ); - // merge both schemas using intersection + // merge both schemas (combines their properties) result = createSchema.merge(updateSchema); } else if (createSchema) { result = createSchema;