Skip to content

Commit 828a056

Browse files
mhodgsonymc9
andauthored
fix: Update re-exports for new prisma-client generator (#2184)
Co-authored-by: ymc9 <[email protected]>
1 parent 9f5db3b commit 828a056

File tree

3 files changed

+467
-183
lines changed

3 files changed

+467
-183
lines changed

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

Lines changed: 109 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { DELEGATE_AUX_RELATION_PREFIX } from '@zenstackhq/runtime';
2+
import { invariant, upperCaseFirst } from '@zenstackhq/runtime/local-helpers';
23
import {
34
PluginError,
45
getAttribute,
@@ -26,7 +27,6 @@ import {
2627
type Model,
2728
} from '@zenstackhq/sdk/ast';
2829
import { getDMMF, getPrismaClientImportSpec, getPrismaVersion, type DMMF } from '@zenstackhq/sdk/prisma';
29-
import { invariant, upperCaseFirst } from '@zenstackhq/runtime/local-helpers';
3030
import fs from 'fs';
3131
import path from 'path';
3232
import semver from 'semver';
@@ -105,44 +105,122 @@ export class EnhancerGenerator {
105105
}
106106

107107
async generate(): Promise<{ dmmf: DMMF.Document | undefined; newPrismaClientDtsPath: string | undefined }> {
108-
let dmmf: DMMF.Document | undefined;
108+
if (this.isNewPrismaClientGenerator) {
109+
// "prisma-client" generator
110+
return this.generateForNewClientGenerator();
111+
} else {
112+
// "prisma-client-js" generator
113+
return this.generateForOldClientGenerator();
114+
}
115+
}
109116

117+
// logic for "prisma-client" generator
118+
private async generateForNewClientGenerator() {
119+
const needsLogicalClient = this.needsLogicalClient;
110120
const prismaImport = getPrismaClientImportSpec(this.outDir, this.options);
111-
let prismaTypesFixed = false;
112-
let resultPrismaTypeImport = prismaImport;
113-
114-
if (this.needsLogicalClient) {
115-
prismaTypesFixed = true;
116-
resultPrismaTypeImport = LOGICAL_CLIENT_GENERATION_PATH;
117-
if (this.isNewPrismaClientGenerator) {
118-
resultPrismaTypeImport += '/client';
119-
}
121+
let resultPrismaBaseImport = path.dirname(prismaImport); // get to the parent folder of "client"
122+
let dmmf: DMMF.Document | undefined;
123+
124+
if (needsLogicalClient) {
125+
// use logical client, note we use the parent of "client" folder here too
126+
resultPrismaBaseImport = LOGICAL_CLIENT_GENERATION_PATH;
120127
const result = await this.generateLogicalPrisma();
121128
dmmf = result.dmmf;
122129
}
123130

124-
// reexport PrismaClient types (original or fixed)
125-
const modelsTsContent = `export * from '${resultPrismaTypeImport}';${
126-
this.isNewPrismaClientGenerator ? "\nexport * from './json-types';" : ''
127-
}`;
131+
// `models.ts` for exporting model types
132+
const modelsTsContent = [
133+
`export * from '${resultPrismaBaseImport}/models';`,
134+
`export * from './json-types';`,
135+
].join('\n');
136+
const modelsTs = this.project.createSourceFile(path.join(this.outDir, 'models.ts'), modelsTsContent, {
137+
overwrite: true,
138+
});
139+
this.saveSourceFile(modelsTs);
140+
141+
// `enums.ts` for exporting enums
142+
const enumsTs = this.project.createSourceFile(
143+
path.join(this.outDir, 'enums.ts'),
144+
`export * from '${resultPrismaBaseImport}/enums';`,
145+
{
146+
overwrite: true,
147+
}
148+
);
149+
this.saveSourceFile(enumsTs);
150+
151+
// `client.ts` for exporting `PrismaClient` and `Prisma` namespace
152+
const clientTs = this.project.createSourceFile(
153+
path.join(this.outDir, 'client.ts'),
154+
`export * from '${resultPrismaBaseImport}/client';`,
155+
{
156+
overwrite: true,
157+
}
158+
);
159+
this.saveSourceFile(clientTs);
160+
161+
// `enhance.ts` and `enhance-edge.ts`
162+
for (const target of ['node', 'edge'] as const) {
163+
this.generateEnhance(prismaImport, `${resultPrismaBaseImport}/client`, needsLogicalClient, target);
164+
}
165+
166+
return {
167+
// logical dmmf if there is one
168+
dmmf,
169+
// new client generator doesn't have a barrel .d.ts file
170+
newPrismaClientDtsPath: undefined,
171+
};
172+
}
173+
174+
// logic for "prisma-client-js" generator
175+
private async generateForOldClientGenerator() {
176+
const needsLogicalClient = this.needsLogicalClient;
177+
const prismaImport = getPrismaClientImportSpec(this.outDir, this.options);
178+
let resultPrismaClientImport = prismaImport;
179+
let dmmf: DMMF.Document | undefined;
180+
181+
if (needsLogicalClient) {
182+
// redirect `PrismaClient` import to the logical client
183+
resultPrismaClientImport = LOGICAL_CLIENT_GENERATION_PATH;
184+
const result = await this.generateLogicalPrisma();
185+
dmmf = result.dmmf;
186+
}
128187

188+
// `models.ts` for exporting model types
189+
const modelsTsContent = `export * from '${resultPrismaClientImport}';`;
129190
const modelsTs = this.project.createSourceFile(path.join(this.outDir, 'models.ts'), modelsTsContent, {
130191
overwrite: true,
131192
});
132193
this.saveSourceFile(modelsTs);
133194

195+
// `enhance.ts` and `enhance-edge.ts`
196+
for (const target of ['node', 'edge'] as const) {
197+
this.generateEnhance(prismaImport, resultPrismaClientImport, needsLogicalClient, target);
198+
}
199+
200+
return {
201+
// logical dmmf if there is one
202+
dmmf,
203+
newPrismaClientDtsPath: needsLogicalClient
204+
? path.resolve(this.outDir, LOGICAL_CLIENT_GENERATION_PATH, 'index.d.ts')
205+
: undefined,
206+
};
207+
}
208+
209+
private generateEnhance(
210+
prismaImport: string,
211+
prismaClientImport: string,
212+
needsLogicalClient: boolean,
213+
target: 'node' | 'edge'
214+
) {
134215
const authDecl = getAuthDecl(getDataModelAndTypeDefs(this.model));
135216
const authTypes = authDecl ? generateAuthType(this.model, authDecl) : '';
136217
const authTypeParam = authDecl ? `auth.${authDecl.name}` : 'AuthUser';
137-
138218
const checkerTypes = this.generatePermissionChecker ? generateCheckerType(this.model) : '';
139219

140-
for (const target of ['node', 'edge']) {
141-
// generate separate `enhance()` for node and edge runtime
142-
const outFile = target === 'node' ? 'enhance.ts' : 'enhance-edge.ts';
143-
const enhanceTs = this.project.createSourceFile(
144-
path.join(this.outDir, outFile),
145-
`/* eslint-disable */
220+
const outFile = target === 'node' ? 'enhance.ts' : 'enhance-edge.ts';
221+
const enhanceTs = this.project.createSourceFile(
222+
path.join(this.outDir, outFile),
223+
`/* eslint-disable */
146224
import { type EnhancementContext, type EnhancementOptions, type ZodSchemas, type AuthUser } from '@zenstackhq/runtime';
147225
import { createEnhancement } from '@zenstackhq/runtime/enhancements/${target}';
148226
import modelMeta from './model-meta';
@@ -154,8 +232,8 @@ ${
154232
}
155233
156234
${
157-
prismaTypesFixed
158-
? this.createLogicalPrismaImports(prismaImport, resultPrismaTypeImport, target)
235+
needsLogicalClient
236+
? this.createLogicalPrismaImports(prismaImport, prismaClientImport, target)
159237
: this.createSimplePrismaImports(prismaImport, target)
160238
}
161239
@@ -164,23 +242,15 @@ ${authTypes}
164242
${checkerTypes}
165243
166244
${
167-
prismaTypesFixed
245+
needsLogicalClient
168246
? this.createLogicalPrismaEnhanceFunction(authTypeParam)
169247
: this.createSimplePrismaEnhanceFunction(authTypeParam)
170248
}
171249
`,
172-
{ overwrite: true }
173-
);
174-
175-
this.saveSourceFile(enhanceTs);
176-
}
250+
{ overwrite: true }
251+
);
177252

178-
return {
179-
dmmf,
180-
newPrismaClientDtsPath: prismaTypesFixed
181-
? path.resolve(this.outDir, LOGICAL_CLIENT_GENERATION_PATH, 'index.d.ts')
182-
: undefined,
183-
};
253+
this.saveSourceFile(enhanceTs);
184254
}
185255

186256
private getZodImport() {
@@ -210,7 +280,7 @@ ${
210280
return normalizedRelative(this.outDir, zodAbsPath);
211281
}
212282

213-
private createSimplePrismaImports(prismaImport: string, target: string) {
283+
private createSimplePrismaImports(prismaImport: string, target: string | undefined) {
214284
const prismaTargetImport = target === 'edge' ? `${prismaImport}/edge` : prismaImport;
215285

216286
return `import { Prisma, type PrismaClient } from '${prismaTargetImport}';
@@ -241,10 +311,10 @@ export function enhance<DbClient extends object>(prisma: DbClient, context?: Enh
241311
`;
242312
}
243313

244-
private createLogicalPrismaImports(prismaImport: string, prismaClientImport: string, target: string) {
314+
private createLogicalPrismaImports(prismaImport: string, prismaClientImport: string, target: string | undefined) {
245315
const prismaTargetImport = target === 'edge' ? `${prismaImport}/edge` : prismaImport;
246316
const runtimeLibraryImport = this.isNewPrismaClientGenerator
247-
? // new generator has these typed only in "@prisma/client"
317+
? // new generator has these types only in "@prisma/client"
248318
'@prisma/client/runtime/library'
249319
: // old generator has these types generated with the client
250320
`${prismaImport}/runtime/library`;

packages/testtools/src/schema.ts

Lines changed: 80 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,84 @@ export async function loadSchemaFromFile(schemaFile: string, options?: SchemaLoa
171171
}
172172

173173
export async function loadSchema(schema: string, options?: SchemaLoadOptions) {
174+
const { projectDir, options: mergedOptions } = createProjectAndCompile(schema, options);
175+
176+
const prismaLoadPath =
177+
mergedOptions?.prismaLoadPath && mergedOptions.prismaLoadPath !== '@prisma/client'
178+
? path.isAbsolute(mergedOptions.prismaLoadPath)
179+
? mergedOptions.prismaLoadPath
180+
: path.join(projectDir, mergedOptions.prismaLoadPath)
181+
: path.join(projectDir, 'node_modules/.prisma/client');
182+
const prismaModule = require(prismaLoadPath);
183+
const PrismaClient = prismaModule.PrismaClient;
184+
185+
let clientOptions: object = { log: ['info', 'warn', 'error'] };
186+
if (mergedOptions?.prismaClientOptions) {
187+
clientOptions = { ...clientOptions, ...mergedOptions.prismaClientOptions };
188+
}
189+
let prisma = new PrismaClient(clientOptions);
190+
// https://github.com/prisma/prisma/issues/18292
191+
prisma[Symbol.for('nodejs.util.inspect.custom')] = 'PrismaClient';
192+
193+
if (mergedOptions.pulseApiKey) {
194+
const withPulse = loadModule('@prisma/extension-pulse/node', projectDir).withPulse;
195+
prisma = prisma.$extends(withPulse({ apiKey: mergedOptions.pulseApiKey }));
196+
}
197+
198+
if (mergedOptions?.getPrismaOnly) {
199+
return {
200+
prisma,
201+
prismaModule,
202+
projectDir,
203+
enhance: undefined as any,
204+
enhanceRaw: undefined as any,
205+
policy: undefined as unknown as PolicyDef,
206+
modelMeta: undefined as any,
207+
zodSchemas: undefined as any,
208+
};
209+
}
210+
211+
const outputPath = mergedOptions.output
212+
? path.isAbsolute(mergedOptions.output)
213+
? mergedOptions.output
214+
: path.join(projectDir, mergedOptions.output)
215+
: path.join(projectDir, 'node_modules', DEFAULT_RUNTIME_LOAD_PATH);
216+
217+
const policy: PolicyDef = require(path.join(outputPath, 'policy')).default;
218+
const modelMeta = require(path.join(outputPath, 'model-meta')).default;
219+
220+
let zodSchemas: any;
221+
try {
222+
zodSchemas = require(path.join(outputPath, 'zod'));
223+
} catch {
224+
/* noop */
225+
}
226+
227+
const enhance = require(path.join(outputPath, 'enhance')).enhance;
228+
229+
return {
230+
projectDir: projectDir,
231+
prisma,
232+
enhance: (user?: AuthUser, options?: EnhancementOptions): FullDbClientContract =>
233+
enhance(
234+
prisma,
235+
{ user },
236+
{
237+
logPrismaQuery: mergedOptions.logPrismaQuery,
238+
transactionTimeout: 1000000,
239+
kinds: mergedOptions.enhancements,
240+
...(options ?? mergedOptions.enhanceOptions),
241+
}
242+
),
243+
enhanceRaw: enhance,
244+
policy,
245+
modelMeta,
246+
zodSchemas,
247+
prismaModule,
248+
};
249+
}
250+
251+
export function createProjectAndCompile(schema: string, options: SchemaLoadOptions | undefined) {
174252
const opt = { ...defaultOptions, ...options };
175253

176254
let projectDir = opt.projectDir;
@@ -282,11 +360,7 @@ export async function loadSchema(schema: string, options?: SchemaLoadOptions) {
282360
fs.writeFileSync(path.join(projectDir, name), content);
283361
});
284362

285-
if (opt.extraSourceFiles && opt.extraSourceFiles.length > 0 && !opt.compile) {
286-
console.warn('`extraSourceFiles` is true but `compile` is false.');
287-
}
288-
289-
if (opt.compile) {
363+
if (opt.compile || opt.extraSourceFiles) {
290364
console.log('Compiling...');
291365

292366
run('npx tsc --init');
@@ -303,79 +377,7 @@ export async function loadSchema(schema: string, options?: SchemaLoadOptions) {
303377
fs.writeFileSync(path.join(projectDir, './tsconfig.json'), JSON.stringify(tsconfig, null, 2));
304378
run('npx tsc --project tsconfig.json');
305379
}
306-
307-
const prismaLoadPath = options?.prismaLoadPath
308-
? path.isAbsolute(options.prismaLoadPath)
309-
? options.prismaLoadPath
310-
: path.join(projectDir, options.prismaLoadPath)
311-
: path.join(projectDir, 'node_modules/.prisma/client');
312-
const prismaModule = require(prismaLoadPath);
313-
const PrismaClient = prismaModule.PrismaClient;
314-
315-
let clientOptions: object = { log: ['info', 'warn', 'error'] };
316-
if (options?.prismaClientOptions) {
317-
clientOptions = { ...clientOptions, ...options.prismaClientOptions };
318-
}
319-
let prisma = new PrismaClient(clientOptions);
320-
// https://github.com/prisma/prisma/issues/18292
321-
prisma[Symbol.for('nodejs.util.inspect.custom')] = 'PrismaClient';
322-
323-
if (opt.pulseApiKey) {
324-
const withPulse = loadModule('@prisma/extension-pulse/node', projectDir).withPulse;
325-
prisma = prisma.$extends(withPulse({ apiKey: opt.pulseApiKey }));
326-
}
327-
328-
if (options?.getPrismaOnly) {
329-
return {
330-
prisma,
331-
prismaModule,
332-
projectDir,
333-
enhance: undefined as any,
334-
enhanceRaw: undefined as any,
335-
policy: undefined as unknown as PolicyDef,
336-
modelMeta: undefined as any,
337-
zodSchemas: undefined as any,
338-
};
339-
}
340-
341-
const outputPath = opt.output
342-
? path.isAbsolute(opt.output)
343-
? opt.output
344-
: path.join(projectDir, opt.output)
345-
: path.join(projectDir, 'node_modules', DEFAULT_RUNTIME_LOAD_PATH);
346-
347-
const policy: PolicyDef = require(path.join(outputPath, 'policy')).default;
348-
const modelMeta = require(path.join(outputPath, 'model-meta')).default;
349-
350-
let zodSchemas: any;
351-
try {
352-
zodSchemas = require(path.join(outputPath, 'zod'));
353-
} catch {
354-
/* noop */
355-
}
356-
357-
const enhance = require(path.join(outputPath, 'enhance')).enhance;
358-
359-
return {
360-
projectDir: projectDir,
361-
prisma,
362-
enhance: (user?: AuthUser, options?: EnhancementOptions): FullDbClientContract =>
363-
enhance(
364-
prisma,
365-
{ user },
366-
{
367-
logPrismaQuery: opt.logPrismaQuery,
368-
transactionTimeout: 1000000,
369-
kinds: opt.enhancements,
370-
...(options ?? opt.enhanceOptions),
371-
}
372-
),
373-
enhanceRaw: enhance,
374-
policy,
375-
modelMeta,
376-
zodSchemas,
377-
prismaModule,
378-
};
380+
return { projectDir, options: opt };
379381
}
380382

381383
/**

0 commit comments

Comments
 (0)