From 209e92dcc264f824243348f442e2f15a9f165314 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Sun, 6 Jul 2025 07:48:09 +0800 Subject: [PATCH 1/2] fix: make create's typing consistent with Prisma regarding to relations --- packages/runtime/src/client/crud-types.ts | 38 ++++++++++++++----- packages/runtime/src/client/crud/validator.ts | 25 +++++++----- .../runtime/test/client-api/create.test.ts | 29 ++++++++++++++ 3 files changed, 73 insertions(+), 19 deletions(-) diff --git a/packages/runtime/src/client/crud-types.ts b/packages/runtime/src/client/crud-types.ts index ada75549..4643991d 100644 --- a/packages/runtime/src/client/crud-types.ts +++ b/packages/runtime/src/client/crud-types.ts @@ -528,12 +528,9 @@ export type CreateArgs omit?: OmitFields; }; -export type CreateManyArgs> = CreateManyPayload< - Schema, - Model ->; +export type CreateManyArgs> = CreateManyInput; -export type CreateManyAndReturnArgs> = CreateManyPayload< +export type CreateManyAndReturnArgs> = CreateManyInput< Schema, Model > & { @@ -597,8 +594,13 @@ type CreateRelationPayload; -type CreateWithFKInput> = CreateScalarPayload & - CreateFKPayload; +type CreateWithFKInput> = + // scalar fields + CreateScalarPayload & + // fk fields + CreateFKPayload & + // non-owned relations + CreateWithNonOwnedRelationPayload; type CreateWithRelationInput> = CreateScalarPayload< Schema, @@ -606,6 +608,14 @@ type CreateWithRelationInput & CreateRelationPayload; +type CreateWithNonOwnedRelationPayload> = OptionalWrap< + Schema, + Model, + { + [Key in NonOwnedRelationFields]: CreateRelationFieldPayload; + } +>; + type ConnectOrCreatePayload< Schema extends SchemaDef, Model extends GetModels, @@ -615,7 +625,7 @@ type ConnectOrCreatePayload< create: CreateInput; }; -export type CreateManyPayload< +export type CreateManyInput< Schema extends SchemaDef, Model extends GetModels, Without extends string = never, @@ -643,7 +653,7 @@ type NestedCreateManyInput< Schema extends SchemaDef, Model extends GetModels, Field extends RelationFields, -> = CreateManyPayload, OppositeRelationAndFK>; +> = CreateManyInput, OppositeRelationAndFK>; //#endregion @@ -1078,3 +1088,13 @@ type NestedDeleteManyInput< > = OrArray, true>>; // #endregion + +// #region Utilities + +type NonOwnedRelationFields> = keyof { + [Key in RelationFields as GetField['relation'] extends { references: unknown[] } + ? never + : Key]: Key; +}; + +// #endregion diff --git a/packages/runtime/src/client/crud/validator.ts b/packages/runtime/src/client/crud/validator.ts index 46a7d7d1..f41fe072 100644 --- a/packages/runtime/src/client/crud/validator.ts +++ b/packages/runtime/src/client/crud/validator.ts @@ -633,8 +633,8 @@ export class InputValidator { withoutFields: string[] = [], withoutRelationFields = false, ) { - const regularAndFkFields: any = {}; - const regularAndRelationFields: any = {}; + const uncheckedVariantFields: any = {}; + const checkedVariantFields: any = {}; const modelDef = requireModel(this.schema, model); const hasRelation = !withoutRelationFields && @@ -688,7 +688,11 @@ export class InputValidator { if (fieldDef.optional && !fieldDef.array) { fieldSchema = fieldSchema.nullable(); } - regularAndRelationFields[field] = fieldSchema; + checkedVariantFields[field] = fieldSchema; + if (fieldDef.array || !fieldDef.relation.references) { + // non-owned relation + uncheckedVariantFields[field] = fieldSchema; + } } else { let fieldSchema: ZodType = this.makePrimitiveSchema(fieldDef.type); @@ -711,21 +715,22 @@ export class InputValidator { fieldSchema = fieldSchema.nullable(); } - regularAndFkFields[field] = fieldSchema; + uncheckedVariantFields[field] = fieldSchema; if (!fieldDef.foreignKeyFor) { - regularAndRelationFields[field] = fieldSchema; + // non-fk field + checkedVariantFields[field] = fieldSchema; } } }); if (!hasRelation) { - return this.orArray(z.object(regularAndFkFields).strict(), canBeArray); + return this.orArray(z.object(uncheckedVariantFields).strict(), canBeArray); } else { return z.union([ - z.object(regularAndFkFields).strict(), - z.object(regularAndRelationFields).strict(), - ...(canBeArray ? [z.array(z.object(regularAndFkFields).strict())] : []), - ...(canBeArray ? [z.array(z.object(regularAndRelationFields).strict())] : []), + z.object(uncheckedVariantFields).strict(), + z.object(checkedVariantFields).strict(), + ...(canBeArray ? [z.array(z.object(uncheckedVariantFields).strict())] : []), + ...(canBeArray ? [z.array(z.object(checkedVariantFields).strict())] : []), ]); } } diff --git a/packages/runtime/test/client-api/create.test.ts b/packages/runtime/test/client-api/create.test.ts index 4004d7cc..85b27793 100644 --- a/packages/runtime/test/client-api/create.test.ts +++ b/packages/runtime/test/client-api/create.test.ts @@ -306,4 +306,33 @@ describe.each(createClientSpecs(PG_DB_NAME))('Client create tests', ({ createCli }), ).rejects.toThrow(QueryError); }); + + it('complies with Prisma checked/unchecked typing', async () => { + const user = await client.user.create({ + data: { email: 'u1@test.com' }, + }); + + // fk and owned-relation are mutually exclusive + client.post.create({ + // @ts-expect-error + data: { + authorId: user.id, + title: 'title', + author: { connect: { id: user.id } }, + }, + }); + + // fk can work with non-owned relation + await expect( + client.post.create({ + data: { + authorId: user.id, + title: 'title', + comments: { + create: { content: 'comment' }, + }, + }, + }), + ).toResolveTruthy(); + }); }); From e332f85866052534eab88ed872f1928ce138f7cf Mon Sep 17 00:00:00 2001 From: Yiming Cao Date: Sun, 6 Jul 2025 07:52:08 +0800 Subject: [PATCH 2/2] Update packages/runtime/src/client/crud/validator.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/runtime/src/client/crud/validator.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/runtime/src/client/crud/validator.ts b/packages/runtime/src/client/crud/validator.ts index f41fe072..38826b7c 100644 --- a/packages/runtime/src/client/crud/validator.ts +++ b/packages/runtime/src/client/crud/validator.ts @@ -633,8 +633,8 @@ export class InputValidator { withoutFields: string[] = [], withoutRelationFields = false, ) { - const uncheckedVariantFields: any = {}; - const checkedVariantFields: any = {}; + const uncheckedVariantFields: Record = {}; + const checkedVariantFields: Record = {}; const modelDef = requireModel(this.schema, model); const hasRelation = !withoutRelationFields &&