Skip to content

Commit 317f535

Browse files
authored
fix(json): incorrect JSON field type generated for inputs (#1996)
1 parent 94dd30f commit 317f535

File tree

2 files changed

+85
-21
lines changed

2 files changed

+85
-21
lines changed

packages/schema/src/plugins/enhancer/enhance/index.ts

+37-21
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ export class EnhancerGenerator {
6464
// names for models that use `auth()` in `@default` attribute
6565
private readonly modelsWithAuthInDefaultCreateInputPattern: RegExp;
6666

67+
// models with JSON type fields
68+
private readonly modelsWithJsonTypeFields: DataModel[];
69+
70+
// Regex patterns for matching input/output types for models with JSON type fields
71+
private readonly modelsWithJsonTypeFieldsInputOutputPattern: RegExp[];
72+
6773
constructor(
6874
private readonly model: Model,
6975
private readonly options: PluginOptions,
@@ -73,9 +79,27 @@ export class EnhancerGenerator {
7379
const modelsWithAuthInDefault = this.model.declarations.filter(
7480
(d): d is DataModel => isDataModel(d) && d.fields.some((f) => f.attributes.some(isDefaultWithAuth))
7581
);
82+
7683
this.modelsWithAuthInDefaultCreateInputPattern = new RegExp(
7784
`^(${modelsWithAuthInDefault.map((m) => m.name).join('|')})(Unchecked)?Create.*?Input$`
7885
);
86+
87+
this.modelsWithJsonTypeFields = this.model.declarations.filter(
88+
(d): d is DataModel => isDataModel(d) && d.fields.some((f) => isTypeDef(f.type.reference?.ref))
89+
);
90+
91+
// input/output patterns for models with json type fields
92+
const relevantTypePatterns = [
93+
'GroupByOutputType',
94+
'(Unchecked)?Create(\\S+?)?Input',
95+
'(Unchecked)?Update(\\S+?)?Input',
96+
'CreateManyInput',
97+
'(Unchecked)?UpdateMany(Mutation)?Input',
98+
];
99+
// build combination regex with all models with JSON types and the above suffixes
100+
this.modelsWithJsonTypeFieldsInputOutputPattern = this.modelsWithJsonTypeFields.map(
101+
(m) => new RegExp(`^(${m.name})(${relevantTypePatterns.join('|')})$`)
102+
);
79103
}
80104

81105
async generate(): Promise<{ dmmf: DMMF.Document | undefined; newPrismaClientDtsPath: string | undefined }> {
@@ -748,9 +772,6 @@ export function enhance(prisma: any, context?: EnhancementContext<${authTypePara
748772
}
749773

750774
private fixJsonFieldType(typeAlias: TypeAliasDeclaration, source: string) {
751-
const modelsWithTypeField = this.model.declarations.filter(
752-
(d): d is DataModel => isDataModel(d) && d.fields.some((f) => isTypeDef(f.type.reference?.ref))
753-
);
754775
const typeName = typeAlias.getName();
755776

756777
const getTypedJsonFields = (model: DataModel) => {
@@ -767,7 +788,7 @@ export function enhance(prisma: any, context?: EnhancementContext<${authTypePara
767788
};
768789

769790
// fix "$[Model]Payload" type
770-
const payloadModelMatch = modelsWithTypeField.find((m) => `$${m.name}Payload` === typeName);
791+
const payloadModelMatch = this.modelsWithJsonTypeFields.find((m) => `$${m.name}Payload` === typeName);
771792
if (payloadModelMatch) {
772793
const scalars = typeAlias
773794
.getDescendantsOfKind(SyntaxKind.PropertySignature)
@@ -783,24 +804,19 @@ export function enhance(prisma: any, context?: EnhancementContext<${authTypePara
783804
}
784805

785806
// fix input/output types, "[Model]CreateInput", etc.
786-
const inputOutputModelMatch = modelsWithTypeField.find((m) => typeName.startsWith(m.name));
787-
if (inputOutputModelMatch) {
788-
const relevantTypePatterns = [
789-
'GroupByOutputType',
790-
'(Unchecked)?Create(\\S+?)?Input',
791-
'(Unchecked)?Update(\\S+?)?Input',
792-
'CreateManyInput',
793-
'(Unchecked)?UpdateMany(Mutation)?Input',
794-
];
795-
const typeRegex = modelsWithTypeField.map(
796-
(m) => new RegExp(`^(${m.name})(${relevantTypePatterns.join('|')})$`)
797-
);
798-
if (typeRegex.some((r) => r.test(typeName))) {
799-
const fieldsToFix = getTypedJsonFields(inputOutputModelMatch);
800-
for (const field of fieldsToFix) {
801-
source = replacePrismaJson(source, field);
802-
}
807+
for (const pattern of this.modelsWithJsonTypeFieldsInputOutputPattern) {
808+
const match = typeName.match(pattern);
809+
if (!match) {
810+
continue;
811+
}
812+
// first capture group is the model name
813+
const modelName = match[1];
814+
const model = this.modelsWithJsonTypeFields.find((m) => m.name === modelName);
815+
const fieldsToFix = getTypedJsonFields(model!);
816+
for (const field of fieldsToFix) {
817+
source = replacePrismaJson(source, field);
803818
}
819+
break;
804820
}
805821

806822
return source;
+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { loadSchema } from '@zenstackhq/testtools';
2+
3+
describe('issue 1991', () => {
4+
it('regression', async () => {
5+
await loadSchema(
6+
`
7+
type FooMetadata {
8+
isLocked Boolean
9+
}
10+
11+
type FooOptionMetadata {
12+
color String
13+
}
14+
15+
model Foo {
16+
id String @id @db.Uuid @default(uuid())
17+
meta FooMetadata @json
18+
}
19+
20+
model FooOption {
21+
id String @id @db.Uuid @default(uuid())
22+
meta FooOptionMetadata @json
23+
}
24+
`,
25+
{
26+
provider: 'postgresql',
27+
pushDb: false,
28+
compile: true,
29+
extraSourceFiles: [
30+
{
31+
name: 'main.ts',
32+
content: `
33+
import { PrismaClient } from '@prisma/client';
34+
import { enhance } from '.zenstack/enhance';
35+
36+
const prisma = new PrismaClient();
37+
const db = enhance(prisma);
38+
39+
db.fooOption.create({
40+
data: { meta: { color: 'red' } }
41+
})
42+
`,
43+
},
44+
],
45+
}
46+
);
47+
});
48+
});

0 commit comments

Comments
 (0)