prisma-generator-express 1.42.0 → 1.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/generators/generateFastifyHandler.js +17 -4
  2. package/dist/generators/generateFastifyHandler.js.map +1 -1
  3. package/dist/generators/generateHonoHandler.js +4 -4
  4. package/dist/generators/generateOperationCore.d.ts +0 -1
  5. package/dist/generators/generateOperationCore.js +34 -546
  6. package/dist/generators/generateOperationCore.js.map +1 -1
  7. package/dist/generators/generateRelationMeta.d.ts +13 -0
  8. package/dist/generators/generateRelationMeta.js +106 -0
  9. package/dist/generators/generateRelationMeta.js.map +1 -0
  10. package/dist/generators/generateRouteConfigType.js +4 -4
  11. package/dist/generators/generateRouteConfigType.js.map +1 -1
  12. package/dist/generators/generateRouter.js +51 -13
  13. package/dist/generators/generateRouter.js.map +1 -1
  14. package/dist/generators/generateRouterFastify.js +127 -384
  15. package/dist/generators/generateRouterFastify.js.map +1 -1
  16. package/dist/generators/generateRouterHono.js +48 -36
  17. package/dist/generators/generateRouterHono.js.map +1 -1
  18. package/dist/generators/generateUnifiedHandler.js +24 -8
  19. package/dist/generators/generateUnifiedHandler.js.map +1 -1
  20. package/dist/index.js +21 -5
  21. package/dist/index.js.map +1 -1
  22. package/dist/utils/copyFiles.js +12 -0
  23. package/dist/utils/copyFiles.js.map +1 -1
  24. package/dist/utils/writeFileSafely.js +3 -0
  25. package/dist/utils/writeFileSafely.js.map +1 -1
  26. package/package.json +1 -1
  27. package/src/copy/autoIncludePlanner.ts +299 -0
  28. package/src/copy/autoIncludeRuntime.ts +307 -0
  29. package/src/copy/operationRuntime.ts +603 -0
  30. package/src/copy/routeConfig.express.ts +5 -3
  31. package/src/copy/routeConfig.fastify.ts +2 -2
  32. package/src/copy/routeConfig.hono.ts +3 -3
  33. package/src/copy/routeConfig.ts +20 -9
  34. package/src/generators/generateFastifyHandler.ts +17 -4
  35. package/src/generators/generateHonoHandler.ts +4 -4
  36. package/src/generators/generateOperationCore.ts +34 -546
  37. package/src/generators/generateRelationMeta.ts +154 -0
  38. package/src/generators/generateRouteConfigType.ts +5 -5
  39. package/src/generators/generateRouter.ts +51 -13
  40. package/src/generators/generateRouterFastify.ts +127 -384
  41. package/src/generators/generateRouterHono.ts +48 -36
  42. package/src/generators/generateUnifiedHandler.ts +24 -8
  43. package/src/index.ts +25 -7
  44. package/src/utils/copyFiles.ts +13 -0
  45. package/src/utils/writeFileSafely.ts +3 -0
@@ -0,0 +1,154 @@
1
+ import { DMMF } from '@prisma/generator-helper'
2
+ import { ImportStyle } from '../utils/resolveImportStyle'
3
+ import { importExt } from '../utils/importExt'
4
+
5
+ type RelationDirection = 'parentOwnsFk' | 'childOwnsFk' | 'implicitM2M'
6
+
7
+ type RelationFieldMeta = {
8
+ name: string
9
+ type: string
10
+ isList: boolean
11
+ isRequired: boolean
12
+ direction: RelationDirection
13
+ parentLinkFields: string[]
14
+ childLinkFields: string[]
15
+ }
16
+
17
+ type ModelMeta = {
18
+ name: string
19
+ delegateKey: string
20
+ scalarFields: string[]
21
+ idFields: string[]
22
+ relations: Record<string, RelationFieldMeta>
23
+ }
24
+
25
+ function findOppositeField(
26
+ models: ReadonlyArray<DMMF.Model>,
27
+ targetModelName: string,
28
+ relationName: string | undefined,
29
+ selfModelName: string,
30
+ ): DMMF.Field | null {
31
+ if (!relationName) return null
32
+ const target = models.find((m) => m.name === targetModelName)
33
+ if (!target) return null
34
+ return target.fields.find(
35
+ (f) => f.kind === 'object' && f.relationName === relationName && f.type === selfModelName,
36
+ ) || null
37
+ }
38
+
39
+ function computeRelation(
40
+ field: DMMF.Field,
41
+ selfModelName: string,
42
+ models: ReadonlyArray<DMMF.Model>,
43
+ ): RelationFieldMeta {
44
+ const selfFrom = (field.relationFromFields ?? []) as string[]
45
+ const selfTo = (field.relationToFields ?? []) as string[]
46
+
47
+ if (selfFrom.length > 0) {
48
+ return {
49
+ name: field.name,
50
+ type: field.type,
51
+ isList: field.isList,
52
+ isRequired: field.isRequired,
53
+ direction: 'parentOwnsFk',
54
+ parentLinkFields: selfFrom,
55
+ childLinkFields: selfTo,
56
+ }
57
+ }
58
+
59
+ const opposite = findOppositeField(models, field.type, field.relationName, selfModelName)
60
+ if (opposite) {
61
+ const oppFrom = (opposite.relationFromFields ?? []) as string[]
62
+ const oppTo = (opposite.relationToFields ?? []) as string[]
63
+ if (oppFrom.length > 0) {
64
+ return {
65
+ name: field.name,
66
+ type: field.type,
67
+ isList: field.isList,
68
+ isRequired: field.isRequired,
69
+ direction: 'childOwnsFk',
70
+ parentLinkFields: oppTo,
71
+ childLinkFields: oppFrom,
72
+ }
73
+ }
74
+ }
75
+
76
+ return {
77
+ name: field.name,
78
+ type: field.type,
79
+ isList: field.isList,
80
+ isRequired: field.isRequired,
81
+ direction: 'implicitM2M',
82
+ parentLinkFields: [],
83
+ childLinkFields: [],
84
+ }
85
+ }
86
+
87
+ function buildModelMeta(
88
+ model: DMMF.Model,
89
+ models: ReadonlyArray<DMMF.Model>,
90
+ ): ModelMeta {
91
+ const scalarFields: string[] = []
92
+ const idFields: string[] = []
93
+ const relations: Record<string, RelationFieldMeta> = {}
94
+
95
+ for (const field of model.fields) {
96
+ if (field.kind === 'object') {
97
+ relations[field.name] = computeRelation(field, model.name, models)
98
+ } else if (field.kind === 'scalar' || field.kind === 'enum') {
99
+ scalarFields.push(field.name)
100
+ if (field.isId) idFields.push(field.name)
101
+ }
102
+ }
103
+
104
+ if (model.primaryKey && Array.isArray(model.primaryKey.fields)) {
105
+ for (const f of model.primaryKey.fields) {
106
+ if (!idFields.includes(f)) idFields.push(f)
107
+ }
108
+ }
109
+
110
+ return {
111
+ name: model.name,
112
+ delegateKey: model.name.charAt(0).toLowerCase() + model.name.slice(1),
113
+ scalarFields,
114
+ idFields,
115
+ relations,
116
+ }
117
+ }
118
+
119
+ export interface GenerateRelationMetaOptions {
120
+ model: DMMF.Model
121
+ allModels: ReadonlyArray<DMMF.Model>
122
+ importStyle: ImportStyle
123
+ }
124
+
125
+ export function generateRelationMeta(options: GenerateRelationMetaOptions): string {
126
+ const ext = importExt(options.importStyle)
127
+ const meta = buildModelMeta(options.model, options.allModels)
128
+ return `import type { ModelRelationMap } from '../autoIncludePlanner${ext}'
129
+
130
+ export const ${options.model.name}Relations: ModelRelationMap = ${JSON.stringify(meta, null, 2)}
131
+ `
132
+ }
133
+
134
+ export interface GenerateRelationModelsIndexOptions {
135
+ modelNames: ReadonlyArray<string>
136
+ importStyle: ImportStyle
137
+ }
138
+
139
+ export function generateRelationModelsIndex(options: GenerateRelationModelsIndexOptions): string {
140
+ const ext = importExt(options.importStyle)
141
+ const imports = options.modelNames
142
+ .map((n) => `import { ${n}Relations } from './${n}/${n}Relations${ext}'`)
143
+ .join('\n')
144
+ const entries = options.modelNames
145
+ .map((n) => ` ${n}: ${n}Relations,`)
146
+ .join('\n')
147
+ return `import type { ModelRelationMap } from './autoIncludePlanner${ext}'
148
+ ${imports}
149
+
150
+ export const relationModels: Record<string, ModelRelationMap> = {
151
+ ${entries}
152
+ }
153
+ `
154
+ }
@@ -53,8 +53,12 @@ export function generateRouteConfigType(
53
53
  const m = modelName
54
54
  const supportsProgressive = target === 'express'
55
55
 
56
+ const progressiveTypeImport = supportsProgressive
57
+ ? `import type { ProgressiveVariantConfig, ProgressiveStage } from '../routeConfig.target${ext}'\n\n`
58
+ : ''
59
+
56
60
  if (!guardShapesImport) {
57
- return `export type ${m}RouteConfig<TCtx = unknown> = RouteConfig<Record<string, unknown>, TCtx>\n`
61
+ return progressiveTypeImport + `export type ${m}RouteConfig<TCtx = unknown> = RouteConfig<Record<string, unknown>, TCtx>\n`
58
62
  }
59
63
 
60
64
  const shapeOps = Object.values(ROUTER_OP_TO_SHAPE_OP).filter((v, i, a) => a.indexOf(v) === i)
@@ -78,10 +82,6 @@ export function generateRouteConfigType(
78
82
 
79
83
  const omitKeys = ROUTER_OPERATIONS.map((k) => `'${k}'`).join('\n | ')
80
84
 
81
- const progressiveTypeImport = supportsProgressive
82
- ? `import type { ProgressiveVariantConfig, ProgressiveStage } from '../routeConfig.target${ext}'\n\n`
83
- : ''
84
-
85
85
  return (
86
86
  progressiveTypeImport +
87
87
  `import type {\n ${opShapeImports}\n} from '${guardShapesImport}${ext}'\n\n` +
@@ -20,6 +20,7 @@ export function generateRouterFunction({
20
20
  const modelName = model.name
21
21
  const prefix = toCamelCase(modelName)
22
22
  const modelNameLower = modelName.toLowerCase()
23
+ const delegateKey = modelName.charAt(0).toLowerCase() + modelName.slice(1)
23
24
  const routerFunctionName = `${prefix}Router`
24
25
 
25
26
  const fieldsMeta = model.fields.map((f) => ({
@@ -69,7 +70,7 @@ import {
69
70
  ${prefix}GroupBy,
70
71
  } from './${modelName}Handlers${ext}'
71
72
  import * as core from './${modelName}Core${ext}'
72
- import type { RouteConfig, ProgressiveVariantConfig, ProgressiveStage } from '../routeConfig.target${ext}'
73
+ import type { RouteConfig } from '../routeConfig.target${ext}'
73
74
  import { parseQueryParams } from '../parseQueryParams${ext}'
74
75
  import { sanitizeKeys } from '../misc${ext}'
75
76
  import { buildModelOpenApi } from '../buildModelOpenApi${ext}'
@@ -80,6 +81,8 @@ import {
80
81
  runProgressiveEndpoint,
81
82
  runSingleResultSSE,
82
83
  } from '../operationRuntime${ext}'
84
+ import { relationModels } from '../relationModels${ext}'
85
+ import { runAutoIncludeProgressive } from '../autoIncludeRuntime${ext}'
83
86
 
84
87
  ${generateRouteConfigType(modelName, 'RequestHandler', guardShapesImport, importStyle, 'express')}
85
88
  const _env = typeof process !== 'undefined' && process.env ? process.env : {} as Record<string, string | undefined>
@@ -103,7 +106,7 @@ type ExtendedRequest = Request & {
103
106
 
104
107
  type LocalsBag = {
105
108
  parsedQuery?: Record<string, unknown>
106
- routeConfig?: ${modelName}RouteConfig
109
+ routeConfig?: { pagination?: OperationContext['paginationConfig'] }
107
110
  guardShape?: Record<string, unknown>
108
111
  guardCaller?: string
109
112
  data?: unknown
@@ -200,7 +203,9 @@ export function ${routerFunctionName}<TCtx = unknown>(config: ${modelName}RouteC
200
203
  const setShape = (opConfig: OperationConfigLike): RequestHandler => {
201
204
  return (req, res, next) => {
202
205
  const locals = readLocals(res)
203
- locals.routeConfig = config
206
+ if (config.pagination) {
207
+ locals.routeConfig = { pagination: config.pagination }
208
+ }
204
209
  const headerName = config.guard?.variantHeader || 'x-api-variant'
205
210
  const headerValue = req.get(headerName)
206
211
  const caller = config.guard?.resolveVariant?.(req) ?? headerValue ?? undefined
@@ -213,6 +218,7 @@ export function ${routerFunctionName}<TCtx = unknown>(config: ${modelName}RouteC
213
218
  const maybeProgressiveSSE = (
214
219
  opConfig: OperationConfigLike,
215
220
  coreFn: (ctx: OperationContext) => Promise<unknown>,
221
+ baseOp: string,
216
222
  ): RequestHandler => {
217
223
  return async (req, res, next) => {
218
224
  if (res.headersSent || res.writableEnded) return next()
@@ -233,13 +239,45 @@ export function ${routerFunctionName}<TCtx = unknown>(config: ${modelName}RouteC
233
239
  return
234
240
  }
235
241
 
242
+ if (progressiveConfig.mode === 'autoInclude') {
243
+ const isSingleRecordRead =
244
+ baseOp === 'findUnique' || baseOp === 'findUniqueOrThrow' ||
245
+ baseOp === 'findFirst' || baseOp === 'findFirstOrThrow'
246
+
247
+ if (!isSingleRecordRead) {
248
+ if (progressiveConfig.fallback === 'error') {
249
+ return next({ status: 400, message: 'autoInclude mode supports only single-record reads' })
250
+ }
251
+ await runSingleResultSSE({
252
+ req,
253
+ res,
254
+ coreQueryFn: () => coreFn(buildContext(req, res)),
255
+ })
256
+ return
257
+ }
258
+
259
+ await runAutoIncludeProgressive({
260
+ req,
261
+ res,
262
+ ctx: buildContext(req, res),
263
+ args: locals.parsedQuery ?? {},
264
+ baseOp: baseOp as 'findUnique' | 'findUniqueOrThrow' | 'findFirst' | 'findFirstOrThrow',
265
+ modelName: '${modelName}',
266
+ delegateKey: '${delegateKey}',
267
+ models: relationModels,
268
+ variantConfig: progressiveConfig,
269
+ coreQueryFn: () => coreFn(buildContext(req, res)),
270
+ })
271
+ return
272
+ }
273
+
236
274
  if (!Array.isArray(progressiveConfig.stages)) {
237
275
  return next({ status: 500, message: 'Progressive endpoint requires stages array' })
238
276
  }
239
277
 
240
278
  const stageRegistry = opConfig.progressiveStages ?? {}
241
279
  const missingStage = progressiveConfig.stages.find(
242
- (name) => typeof stageRegistry[name] !== 'function',
280
+ (name: string) => typeof stageRegistry[name] !== 'function',
243
281
  )
244
282
  if (missingStage) {
245
283
  return next({ status: 500, message: 'Missing progressive stage: ' + missingStage })
@@ -297,63 +335,63 @@ export function ${routerFunctionName}<TCtx = unknown>(config: ${modelName}RouteC
297
335
  const opConfig: OperationConfigLike = (config.findFirst as OperationConfigLike | undefined) ?? defaultOpConfig
298
336
  const { before = [], after = [] } = opConfig
299
337
  const path = basePath ? \`\${basePath}/first\` : '/first'
300
- router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findFirst), ${prefix}FindFirst as RequestHandler, ...after, respond)
338
+ router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findFirst, 'findFirst'), ${prefix}FindFirst as RequestHandler, ...after, respond)
301
339
  if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${prefix}FindFirst as RequestHandler, ...after, respond)
302
340
  }
303
341
  if (config.enableAll || config.findFirstOrThrow) {
304
342
  const opConfig: OperationConfigLike = (config.findFirstOrThrow as OperationConfigLike | undefined) ?? defaultOpConfig
305
343
  const { before = [], after = [] } = opConfig
306
344
  const path = basePath ? \`\${basePath}/first/strict\` : '/first/strict'
307
- router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findFirstOrThrow), ${prefix}FindFirstOrThrow as RequestHandler, ...after, respond)
345
+ router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findFirstOrThrow, 'findFirstOrThrow'), ${prefix}FindFirstOrThrow as RequestHandler, ...after, respond)
308
346
  if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${prefix}FindFirstOrThrow as RequestHandler, ...after, respond)
309
347
  }
310
348
  if (config.enableAll || config.findManyPaginated) {
311
349
  const opConfig: OperationConfigLike = (config.findManyPaginated as OperationConfigLike | undefined) ?? defaultOpConfig
312
350
  const { before = [], after = [] } = opConfig
313
351
  const path = basePath ? \`\${basePath}/paginated\` : '/paginated'
314
- router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findManyPaginated), ${prefix}FindManyPaginated as RequestHandler, ...after, respond)
352
+ router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findManyPaginated, 'findManyPaginated'), ${prefix}FindManyPaginated as RequestHandler, ...after, respond)
315
353
  if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${prefix}FindManyPaginated as RequestHandler, ...after, respond)
316
354
  }
317
355
  if (config.enableAll || config.aggregate) {
318
356
  const opConfig: OperationConfigLike = (config.aggregate as OperationConfigLike | undefined) ?? defaultOpConfig
319
357
  const { before = [], after = [] } = opConfig
320
358
  const path = basePath ? \`\${basePath}/aggregate\` : '/aggregate'
321
- router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.aggregate), ${prefix}Aggregate as RequestHandler, ...after, respond)
359
+ router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.aggregate, 'aggregate'), ${prefix}Aggregate as RequestHandler, ...after, respond)
322
360
  if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${prefix}Aggregate as RequestHandler, ...after, respond)
323
361
  }
324
362
  if (config.enableAll || config.count) {
325
363
  const opConfig: OperationConfigLike = (config.count as OperationConfigLike | undefined) ?? defaultOpConfig
326
364
  const { before = [], after = [] } = opConfig
327
365
  const path = basePath ? \`\${basePath}/count\` : '/count'
328
- router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.count), ${prefix}Count as RequestHandler, ...after, respond)
366
+ router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.count, 'count'), ${prefix}Count as RequestHandler, ...after, respond)
329
367
  if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${prefix}Count as RequestHandler, ...after, respond)
330
368
  }
331
369
  if (config.enableAll || config.groupBy) {
332
370
  const opConfig: OperationConfigLike = (config.groupBy as OperationConfigLike | undefined) ?? defaultOpConfig
333
371
  const { before = [], after = [] } = opConfig
334
372
  const path = basePath ? \`\${basePath}/groupby\` : '/groupby'
335
- router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.groupBy), ${prefix}GroupBy as RequestHandler, ...after, respond)
373
+ router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.groupBy, 'groupBy'), ${prefix}GroupBy as RequestHandler, ...after, respond)
336
374
  if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${prefix}GroupBy as RequestHandler, ...after, respond)
337
375
  }
338
376
  if (config.enableAll || config.findUniqueOrThrow) {
339
377
  const opConfig: OperationConfigLike = (config.findUniqueOrThrow as OperationConfigLike | undefined) ?? defaultOpConfig
340
378
  const { before = [], after = [] } = opConfig
341
379
  const path = basePath ? \`\${basePath}/unique/strict\` : '/unique/strict'
342
- router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findUniqueOrThrow), ${prefix}FindUniqueOrThrow as RequestHandler, ...after, respond)
380
+ router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findUniqueOrThrow, 'findUniqueOrThrow'), ${prefix}FindUniqueOrThrow as RequestHandler, ...after, respond)
343
381
  if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${prefix}FindUniqueOrThrow as RequestHandler, ...after, respond)
344
382
  }
345
383
  if (config.enableAll || config.findUnique) {
346
384
  const opConfig: OperationConfigLike = (config.findUnique as OperationConfigLike | undefined) ?? defaultOpConfig
347
385
  const { before = [], after = [] } = opConfig
348
386
  const path = basePath ? \`\${basePath}/unique\` : '/unique'
349
- router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findUnique), ${prefix}FindUnique as RequestHandler, ...after, respond)
387
+ router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findUnique, 'findUnique'), ${prefix}FindUnique as RequestHandler, ...after, respond)
350
388
  if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${prefix}FindUnique as RequestHandler, ...after, respond)
351
389
  }
352
390
  if (config.enableAll || config.findMany) {
353
391
  const opConfig: OperationConfigLike = (config.findMany as OperationConfigLike | undefined) ?? defaultOpConfig
354
392
  const { before = [], after = [] } = opConfig
355
393
  const path = basePath || '/'
356
- router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findMany), ${prefix}FindMany as RequestHandler, ...after, respond)
394
+ router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findMany, 'findMany'), ${prefix}FindMany as RequestHandler, ...after, respond)
357
395
  if (postReadsEnabled) {
358
396
  const postPath = basePath ? \`\${basePath}/read\` : '/read'
359
397
  router.post(postPath, parseBodyAsQuery, setShape(opConfig), ...before, ${prefix}FindMany as RequestHandler, ...after, respond)