Skip to content

Commit 9c37d47

Browse files
authored
fix(delegate): resolve from short name back to full name when processing delegate types (#2051)
1 parent 1369f9e commit 9c37d47

File tree

2 files changed

+142
-7
lines changed

2 files changed

+142
-7
lines changed

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

+31-7
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ export class EnhancerGenerator {
7070
// Regex patterns for matching input/output types for models with JSON type fields
7171
private readonly modelsWithJsonTypeFieldsInputOutputPattern: RegExp[];
7272

73+
// a mapping from shortened names to full names
74+
private reversedShortNameMap = new Map<string, string>();
75+
7376
constructor(
7477
private readonly model: Model,
7578
private readonly options: PluginOptions,
@@ -322,7 +325,7 @@ export type Enhanced<Client> =
322325

323326
// calculate a relative output path to output the logical prisma client into enhancer's output dir
324327
const prismaClientOutDir = path.join(path.relative(zmodelDir, this.outDir), LOGICAL_CLIENT_GENERATION_PATH);
325-
await prismaGenerator.generate({
328+
const generateResult = await prismaGenerator.generate({
326329
provider: '@internal', // doesn't matter
327330
schemaPath: this.options.schemaPath,
328331
output: logicalPrismaFile,
@@ -331,6 +334,11 @@ export type Enhanced<Client> =
331334
customAttributesAsComments: true,
332335
});
333336

337+
// reverse direction of shortNameMap and store for future lookup
338+
this.reversedShortNameMap = new Map<string, string>(
339+
Array.from(generateResult.shortNameMap.entries()).map(([key, value]) => [value, key])
340+
);
341+
334342
// generate the prisma client
335343

336344
// only run prisma client generator for the logical schema
@@ -390,7 +398,7 @@ export type Enhanced<Client> =
390398
const createInputPattern = new RegExp(`^(.+?)(Unchecked)?Create.*Input$`);
391399
for (const inputType of dmmf.schema.inputObjectTypes.prisma) {
392400
const match = inputType.name.match(createInputPattern);
393-
const modelName = match?.[1];
401+
const modelName = this.resolveName(match?.[1]);
394402
if (modelName) {
395403
const dataModel = this.model.declarations.find(
396404
(d): d is DataModel => isDataModel(d) && d.name === modelName
@@ -673,7 +681,7 @@ export type Enhanced<Client> =
673681

674682
const match = typeName.match(concreteCreateUpdateInputRegex);
675683
if (match) {
676-
const modelName = match[1];
684+
const modelName = this.resolveName(match[1]);
677685
const dataModel = this.model.declarations.find(
678686
(d): d is DataModel => isDataModel(d) && d.name === modelName
679687
);
@@ -724,8 +732,9 @@ export type Enhanced<Client> =
724732
return source;
725733
}
726734

727-
const nameTuple = match[3]; // [modelName]_[relationFieldName]_[concreteModelName]
728-
const [modelName, relationFieldName, _] = nameTuple.split('_');
735+
// [modelName]_[relationFieldName]_[concreteModelName]
736+
const nameTuple = this.resolveName(match[3], true);
737+
const [modelName, relationFieldName, _] = nameTuple!.split('_');
729738

730739
const fieldDef = this.findNamedProperty(typeAlias, relationFieldName);
731740
if (fieldDef) {
@@ -769,13 +778,28 @@ export type Enhanced<Client> =
769778
return source;
770779
}
771780

781+
// resolves a potentially shortened name back to the original
782+
private resolveName(name: string | undefined, withDelegateAuxPrefix = false) {
783+
if (!name) {
784+
return name;
785+
}
786+
const shortNameLookupKey = withDelegateAuxPrefix ? `${DELEGATE_AUX_RELATION_PREFIX}_${name}` : name;
787+
if (this.reversedShortNameMap.has(shortNameLookupKey)) {
788+
name = this.reversedShortNameMap.get(shortNameLookupKey)!;
789+
if (withDelegateAuxPrefix) {
790+
name = name.substring(DELEGATE_AUX_RELATION_PREFIX.length + 1);
791+
}
792+
}
793+
return name;
794+
}
795+
772796
private fixDefaultAuthType(typeAlias: TypeAliasDeclaration, source: string) {
773797
const match = typeAlias.getName().match(this.modelsWithAuthInDefaultCreateInputPattern);
774798
if (!match) {
775799
return source;
776800
}
777801

778-
const modelName = match[1];
802+
const modelName = this.resolveName(match[1]);
779803
const dataModel = this.model.declarations.find((d): d is DataModel => isDataModel(d) && d.name === modelName);
780804
if (dataModel) {
781805
for (const fkField of dataModel.fields.filter((f) => f.attributes.some(isDefaultWithAuth))) {
@@ -831,7 +855,7 @@ export type Enhanced<Client> =
831855
continue;
832856
}
833857
// first capture group is the model name
834-
const modelName = match[1];
858+
const modelName = this.resolveName(match[1]);
835859
const model = this.modelsWithJsonTypeFields.find((m) => m.name === modelName);
836860
const fieldsToFix = getTypedJsonFields(model!);
837861
for (const field of fieldsToFix) {
+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { loadSchema } from '@zenstackhq/testtools';
2+
3+
describe('issue 1994', () => {
4+
it('regression', async () => {
5+
const { enhance } = await loadSchema(
6+
`
7+
model OrganizationRole {
8+
id Int @id @default(autoincrement())
9+
rolePrivileges OrganizationRolePrivilege[]
10+
type String
11+
@@delegate(type)
12+
}
13+
14+
model Organization {
15+
id Int @id @default(autoincrement())
16+
customRoles CustomOrganizationRole[]
17+
}
18+
19+
// roles common to all orgs, defined once
20+
model SystemDefinedRole extends OrganizationRole {
21+
name String @unique
22+
}
23+
24+
// roles specific to each org
25+
model CustomOrganizationRole extends OrganizationRole {
26+
name String
27+
organizationId Int
28+
organization Organization @relation(fields: [organizationId], references: [id])
29+
30+
@@unique([organizationId, name])
31+
@@index([organizationId])
32+
}
33+
34+
model OrganizationRolePrivilege {
35+
organizationRoleId Int
36+
privilegeId Int
37+
38+
organizationRole OrganizationRole @relation(fields: [organizationRoleId], references: [id])
39+
privilege Privilege @relation(fields: [privilegeId], references: [id])
40+
41+
@@id([organizationRoleId, privilegeId])
42+
}
43+
44+
model Privilege {
45+
id Int @id @default(autoincrement())
46+
name String // e.g. "org:manage"
47+
48+
orgRolePrivileges OrganizationRolePrivilege[]
49+
@@unique([name])
50+
}
51+
`,
52+
{
53+
enhancements: ['delegate'],
54+
compile: true,
55+
extraSourceFiles: [
56+
{
57+
name: 'main.ts',
58+
content: `
59+
import { PrismaClient } from '@prisma/client';
60+
import { enhance } from '.zenstack/enhance';
61+
62+
const prisma = new PrismaClient();
63+
64+
async function main() {
65+
const db = enhance(prisma);
66+
const privilege = await db.privilege.create({
67+
data: { name: 'org:manage' },
68+
});
69+
70+
await db.systemDefinedRole.create({
71+
data: {
72+
name: 'Admin',
73+
rolePrivileges: {
74+
create: [
75+
{
76+
privilegeId: privilege.id,
77+
},
78+
],
79+
},
80+
},
81+
});
82+
}
83+
main()
84+
`,
85+
},
86+
],
87+
}
88+
);
89+
90+
const db = enhance();
91+
92+
const privilege = await db.privilege.create({
93+
data: { name: 'org:manage' },
94+
});
95+
96+
await expect(
97+
db.systemDefinedRole.create({
98+
data: {
99+
name: 'Admin',
100+
rolePrivileges: {
101+
create: [
102+
{
103+
privilegeId: privilege.id,
104+
},
105+
],
106+
},
107+
},
108+
})
109+
).toResolveTruthy();
110+
});
111+
});

0 commit comments

Comments
 (0)