Skip to content

Commit ff5d94f

Browse files
committed
additional fixes
1 parent e23f5c9 commit ff5d94f

File tree

4 files changed

+98
-58
lines changed

4 files changed

+98
-58
lines changed

packages/schema/src/plugins/zod/transformer.ts

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { DELEGATE_AUX_RELATION_PREFIX } from '@zenstackhq/runtime';
33
import {
44
getForeignKeyFields,
5+
getRelationBackLink,
56
hasAttribute,
67
indentString,
78
isDelegateModel,
@@ -251,7 +252,10 @@ export default class Transformer {
251252
) {
252253
// reduce concrete input types to their delegate base types
253254
// e.g.: "UserCreateNestedOneWithoutDelegate_aux_PostInput" => "UserCreateWithoutAssetInput"
254-
const mappedInputType = this.mapDelegateInputType(inputType, contextDataModel);
255+
let mappedInputType = inputType;
256+
if (contextDataModel) {
257+
mappedInputType = this.mapDelegateInputType(inputType, contextDataModel, field.name);
258+
}
255259

256260
if (mappedInputType.type !== this.originalName && typeof mappedInputType.type === 'string') {
257261
this.addSchemaImport(mappedInputType.type);
@@ -299,29 +303,42 @@ export default class Transformer {
299303
return [[` ${fieldName} ${resString} `, field, true]];
300304
}
301305

302-
private mapDelegateInputType(inputType: PrismaDMMF.InputTypeRef, contextDataModel: DataModel | undefined) {
306+
private mapDelegateInputType(
307+
inputType: PrismaDMMF.InputTypeRef,
308+
contextDataModel: DataModel,
309+
contextFieldName: string
310+
) {
311+
// input type mapping is only relevant for relation inherited from delegate models
312+
const contextField = contextDataModel.fields.find((f) => f.name === contextFieldName);
313+
if (!contextField || !isDataModel(contextField.type.reference?.ref)) {
314+
return inputType;
315+
}
316+
317+
if (!contextField.$inheritedFrom || !isDelegateModel(contextField.$inheritedFrom)) {
318+
return inputType;
319+
}
320+
303321
let processedInputType = inputType;
322+
304323
// captures: model name and operation, "Without" part that references a concrete model,
305324
// and the "Input" or "NestedInput" suffix
306325
const match = inputType.type.match(/^(\S+?)((NestedOne)?WithoutDelegate_aux\S+?)((Nested)?Input)$/);
307326
if (match) {
308327
let mappedInputTypeName = match[1];
309328

310329
if (contextDataModel) {
311-
// find the parent delegate model and replace the "Without" part with it
312-
const delegateBase = contextDataModel.superTypes
313-
.map((t) => t.ref)
314-
.filter((t) => t && isDelegateModel(t))?.[0];
315-
if (delegateBase) {
316-
mappedInputTypeName += `Without${upperCaseFirst(delegateBase.name)}`;
330+
// get the opposite side of the relation field, which should be of the proper
331+
// delegate base type
332+
const oppositeRelationField = getRelationBackLink(contextField);
333+
if (oppositeRelationField) {
334+
mappedInputTypeName += `Without${upperCaseFirst(oppositeRelationField.name)}`;
317335
}
318336
}
319337

320338
// "Input" or "NestedInput" suffix
321339
mappedInputTypeName += match[4];
322340

323341
processedInputType = { ...inputType, type: mappedInputTypeName };
324-
// console.log('Replacing type', inputTyp.type, 'with', processedInputType.type);
325342
}
326343
return processedInputType;
327344
}

packages/sdk/src/model-meta-generator.ts

Lines changed: 2 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,15 @@ import {
2424
ExpressionContext,
2525
getAttribute,
2626
getAttributeArg,
27-
getAttributeArgLiteral,
2827
getAttributeArgs,
2928
getAuthDecl,
3029
getDataModels,
3130
getInheritedFromDelegate,
3231
getLiteral,
32+
getRelationBackLink,
3333
getRelationField,
3434
hasAttribute,
3535
isAuthInvocation,
36-
isDelegateModel,
3736
isEnumFieldReference,
3837
isForeignKeyField,
3938
isIdField,
@@ -289,7 +288,7 @@ function writeFields(
289288
if (dmField) {
290289
// metadata specific to DataModelField
291290

292-
const backlink = getBackLink(dmField);
291+
const backlink = getRelationBackLink(dmField);
293292
const fkMapping = generateForeignKeyMapping(dmField);
294293

295294
if (backlink) {
@@ -336,51 +335,6 @@ function writeFields(
336335
writer.write(',');
337336
}
338337

339-
function getBackLink(field: DataModelField) {
340-
if (!field.type.reference?.ref || !isDataModel(field.type.reference?.ref)) {
341-
return undefined;
342-
}
343-
344-
const relName = getRelationName(field);
345-
346-
let sourceModel: DataModel;
347-
if (field.$inheritedFrom && isDelegateModel(field.$inheritedFrom)) {
348-
// field is inherited from a delegate model, use it as the source
349-
sourceModel = field.$inheritedFrom;
350-
} else {
351-
// otherwise use the field's container model as the source
352-
sourceModel = field.$container as DataModel;
353-
}
354-
355-
const targetModel = field.type.reference.ref as DataModel;
356-
357-
for (const otherField of targetModel.fields) {
358-
if (otherField === field) {
359-
// backlink field is never self
360-
continue;
361-
}
362-
if (otherField.type.reference?.ref === sourceModel) {
363-
if (relName) {
364-
const otherRelName = getRelationName(otherField);
365-
if (relName === otherRelName) {
366-
return otherField;
367-
}
368-
} else {
369-
return otherField;
370-
}
371-
}
372-
}
373-
return undefined;
374-
}
375-
376-
function getRelationName(field: DataModelField) {
377-
const relAttr = getAttribute(field, '@relation');
378-
if (!relAttr) {
379-
return undefined;
380-
}
381-
return getAttributeArgLiteral(relAttr, 'name');
382-
}
383-
384338
function getAttributes(target: DataModelField | DataModel | TypeDefField): RuntimeAttribute[] {
385339
return target.attributes
386340
.map((attr) => {

packages/sdk/src/utils.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,3 +632,54 @@ export function getInheritanceChain(from: DataModel, to: DataModel): DataModel[]
632632

633633
return undefined;
634634
}
635+
636+
/**
637+
* Get the opposite side of a relation field.
638+
*/
639+
export function getRelationBackLink(field: DataModelField) {
640+
if (!field.type.reference?.ref || !isDataModel(field.type.reference?.ref)) {
641+
return undefined;
642+
}
643+
644+
const relName = getRelationName(field);
645+
646+
let sourceModel: DataModel;
647+
if (field.$inheritedFrom && isDelegateModel(field.$inheritedFrom)) {
648+
// field is inherited from a delegate model, use it as the source
649+
sourceModel = field.$inheritedFrom;
650+
} else {
651+
// otherwise use the field's container model as the source
652+
sourceModel = field.$container as DataModel;
653+
}
654+
655+
const targetModel = field.type.reference.ref as DataModel;
656+
657+
for (const otherField of targetModel.fields) {
658+
if (otherField === field) {
659+
// backlink field is never self
660+
continue;
661+
}
662+
if (otherField.type.reference?.ref === sourceModel) {
663+
if (relName) {
664+
const otherRelName = getRelationName(otherField);
665+
if (relName === otherRelName) {
666+
return otherField;
667+
}
668+
} else {
669+
return otherField;
670+
}
671+
}
672+
}
673+
return undefined;
674+
}
675+
676+
/**
677+
* Get the relation name of a relation field.
678+
*/
679+
export function getRelationName(field: DataModelField) {
680+
const relAttr = getAttribute(field, '@relation');
681+
if (!relAttr) {
682+
return undefined;
683+
}
684+
return getAttributeArgLiteral(relAttr, 'name');
685+
}

tests/regression/tests/issue-1993.test.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { loadSchema } from '@zenstackhq/testtools';
22

33
describe('issue 1993', () => {
44
it('regression', async () => {
5-
await loadSchema(
5+
const { zodSchemas } = await loadSchema(
66
`
77
enum UserType {
88
UserLocal
@@ -41,5 +41,23 @@ model UserFolder {
4141
} `,
4242
{ pushDb: false, fullZod: true, compile: true, output: 'lib/zenstack' }
4343
);
44+
45+
expect(
46+
zodSchemas.input.UserLocalInputSchema.create.safeParse({
47+
data: {
48+
49+
password: 'password',
50+
},
51+
})
52+
).toMatchObject({ success: true });
53+
54+
expect(
55+
zodSchemas.input.UserFolderInputSchema.create.safeParse({
56+
data: {
57+
path: '/',
58+
userId: '1',
59+
},
60+
})
61+
).toMatchObject({ success: true });
4462
});
4563
});

0 commit comments

Comments
 (0)