prisma-generator-express 1.57.0 → 1.58.1

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 (42) hide show
  1. package/README.md +111 -29
  2. package/dist/copy/misc.js +25 -6
  3. package/dist/copy/misc.js.map +1 -1
  4. package/dist/generators/generateFastifyHandler.js +11 -0
  5. package/dist/generators/generateFastifyHandler.js.map +1 -1
  6. package/dist/generators/generateHonoHandler.js +14 -20
  7. package/dist/generators/generateHonoHandler.js.map +1 -1
  8. package/dist/generators/generateImportPrismaStatement.js +7 -1
  9. package/dist/generators/generateImportPrismaStatement.js.map +1 -1
  10. package/dist/generators/generateOperationCore.js +58 -17
  11. package/dist/generators/generateOperationCore.js.map +1 -1
  12. package/dist/generators/generateRouteConfigType.js +12 -7
  13. package/dist/generators/generateRouteConfigType.js.map +1 -1
  14. package/dist/generators/generateRouter.js +57 -32
  15. package/dist/generators/generateRouter.js.map +1 -1
  16. package/dist/generators/generateRouterFastify.js +235 -191
  17. package/dist/generators/generateRouterFastify.js.map +1 -1
  18. package/dist/generators/generateRouterHono.d.ts +2 -3
  19. package/dist/generators/generateRouterHono.js +131 -89
  20. package/dist/generators/generateRouterHono.js.map +1 -1
  21. package/dist/index.js +8 -6
  22. package/dist/index.js.map +1 -1
  23. package/package.json +1 -1
  24. package/src/copy/autoIncludeRuntime.ts +9 -5
  25. package/src/copy/buildModelOpenApi.ts +96 -0
  26. package/src/copy/docsRenderer.ts +577 -174
  27. package/src/copy/materializedRouter.ts +40 -1
  28. package/src/copy/misc.ts +23 -6
  29. package/src/copy/operationDefinitions.ts +10 -0
  30. package/src/copy/operationRuntime.ts +28 -9
  31. package/src/copy/routeConfig.express.ts +9 -9
  32. package/src/copy/routeConfig.hono.ts +63 -5
  33. package/src/copy/routeConfig.ts +44 -22
  34. package/src/generators/generateFastifyHandler.ts +12 -0
  35. package/src/generators/generateHonoHandler.ts +15 -20
  36. package/src/generators/generateImportPrismaStatement.ts +10 -1
  37. package/src/generators/generateOperationCore.ts +58 -17
  38. package/src/generators/generateRouteConfigType.ts +13 -8
  39. package/src/generators/generateRouter.ts +57 -32
  40. package/src/generators/generateRouterFastify.ts +235 -191
  41. package/src/generators/generateRouterHono.ts +131 -91
  42. package/src/index.ts +11 -7
@@ -82,8 +82,13 @@ function routeConfigBaseFor(target: Target): string {
82
82
  return `RouteConfig<Record<string, unknown>, TCtx>`
83
83
  }
84
84
 
85
- function hookHandlerTypeRef(target: Target, hookHandlerType: string): string {
86
- if (target === 'hono') return `${hookHandlerType}<TEnv>`
85
+ function beforeHookRef(target: Target, hookHandlerType: string): string {
86
+ if (target === 'hono') return `HonoBeforeHook<TEnv>`
87
+ return hookHandlerType
88
+ }
89
+
90
+ function afterHookRef(target: Target, hookHandlerType: string): string {
91
+ if (target === 'hono') return `HonoAfterHook<TEnv>`
87
92
  return hookHandlerType
88
93
  }
89
94
 
@@ -100,7 +105,8 @@ export function generateRouteConfigType(
100
105
 
101
106
  const generics = configGenericsFor(target)
102
107
  const baseConfig = routeConfigBaseFor(target)
103
- const hookRef = hookHandlerTypeRef(target, hookHandlerType)
108
+ const beforeRef = beforeHookRef(target, hookHandlerType)
109
+ const afterRef = afterHookRef(target, hookHandlerType)
104
110
  const requestType = requestTypeFor(target)
105
111
 
106
112
  const progressiveTypeImport = supportsProgressive
@@ -126,11 +132,10 @@ export function generateRouteConfigType(
126
132
  const c = capitalize(shapeOp)
127
133
  const isRead = READ_OPERATIONS.has(routerOp)
128
134
  const lines = [
129
- ` before?: ${hookRef}[]`,
130
- ` after?: ${hookRef}[]`,
135
+ ` before?: ${beforeRef}[]`,
136
+ ` after?: ${afterRef}[]`,
131
137
  ` shape?: ${m}${c}ShapeInput<TCtx>`,
132
138
  ` pagination?: Partial<PaginationConfig>`,
133
- ` dropGuard?: boolean`,
134
139
  ]
135
140
  if (isRead && supportsProgressive) {
136
141
  lines.push(` progressive?: Record<string, ProgressiveVariantConfig>`)
@@ -138,7 +143,7 @@ export function generateRouteConfigType(
138
143
  ` progressiveStages?: Record<string, ProgressiveStage<TCtx, TPrisma>>`,
139
144
  )
140
145
  }
141
- return ` ${routerOp}?: {\n${lines.join('\n')}\n }`
146
+ return ` ${routerOp}?: {\n${lines.join('\n')}\n } | false`
142
147
  }).join('\n')
143
148
 
144
149
  const omitKeys = ROUTER_OPERATIONS.map((k) => `'${k}'`).join('\n | ')
@@ -154,4 +159,4 @@ export function generateRouteConfigType(
154
159
  ` resolveContext?: (request: ${requestType}) => TCtx | Promise<TCtx>\n` +
155
160
  `${overrides}\n}\n`
156
161
  )
157
- }
162
+ }
@@ -84,6 +84,7 @@ import type {
84
84
  import { parseQueryParams } from '../parseQueryParams${ext}'
85
85
  import { sanitizeKeys, normalizePrefix, getEnv } from '../misc${ext}'
86
86
  import { buildModelOpenApi } from '../buildModelOpenApi${ext}'
87
+ import { validateCountSourceWhere } from '../routeConfig${ext}'
87
88
  import type { OperationContext } from '../operationRuntime${ext}'
88
89
  import {
89
90
  transformResult,
@@ -155,8 +156,16 @@ function readLocals(res: Response): LocalsBag {
155
156
  }
156
157
 
157
158
  export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${modelName}RouteConfig<TCtx, TPrisma> = {}) {
159
+ validateCountSourceWhere(config.pagination?.countSource, '${modelName} pagination')
160
+ validateCountSourceWhere(
161
+ (config.findManyPaginated && typeof config.findManyPaginated === 'object' ? config.findManyPaginated : undefined)?.pagination?.countSource,
162
+ '${modelName} findManyPaginated pagination',
163
+ )
164
+
158
165
  const router = express.Router()
159
166
 
167
+ const isEnabled = (value: unknown): boolean => value !== false && !!(config.enableAll || value)
168
+
160
169
  const customPrefix = normalizePrefix(config.customUrlPrefix || '')
161
170
  const modelPrefix = config.addModelPrefix !== false ? '/${modelNameLower}' : ''
162
171
  const basePath = customPrefix + modelPrefix
@@ -166,24 +175,32 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
166
175
 
167
176
  const postReadsEnabled = !config.disablePostReads
168
177
 
169
- const openApiJsonSpec = openApiDisabled
170
- ? null
171
- : buildModelOpenApi(
178
+ let _openApiJsonCache: unknown = undefined
179
+ const getOpenApiJson = (): unknown => {
180
+ if (_openApiJsonCache === undefined) {
181
+ _openApiJsonCache = buildModelOpenApi(
172
182
  '${modelName}',
173
183
  MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
174
184
  MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
175
185
  config as unknown as Parameters<typeof buildModelOpenApi>[3],
176
186
  { format: 'json', writeStrategy: WRITE_STRATEGY },
177
187
  )
178
- const openApiYamlSpec = openApiDisabled
179
- ? null
180
- : buildModelOpenApi(
188
+ }
189
+ return _openApiJsonCache
190
+ }
191
+ let _openApiYamlCache: string | undefined = undefined
192
+ const getOpenApiYaml = (): string => {
193
+ if (_openApiYamlCache === undefined) {
194
+ _openApiYamlCache = buildModelOpenApi(
181
195
  '${modelName}',
182
196
  MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
183
197
  MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
184
198
  config as unknown as Parameters<typeof buildModelOpenApi>[3],
185
199
  { format: 'yaml', writeStrategy: WRITE_STRATEGY },
186
- )
200
+ ) as string
201
+ }
202
+ return _openApiYamlCache
203
+ }
187
204
 
188
205
  const qbEnabled = isQueryBuilderEnabled(config)
189
206
  if (qbEnabled) {
@@ -224,7 +241,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
224
241
 
225
242
  const parseBodyAsQuery: RequestHandler = (req, res, next) => {
226
243
  if (!req.body || typeof req.body !== 'object' || Array.isArray(req.body)) {
227
- return next({ status: 400, message: 'Request body must be a JSON object' })
244
+ return next(new HttpError(400, 'Request body must be a JSON object'))
228
245
  }
229
246
  readLocals(res).parsedQuery = sanitizeKeys(req.body as Record<string, unknown>)
230
247
  next()
@@ -328,11 +345,13 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
328
345
  (name: string) => typeof stageRegistry[name] !== 'function',
329
346
  )
330
347
  if (missingStage) {
331
- return next({ status: 500, message: 'Missing progressive stage: ' + missingStage })
348
+ emitTerminalSSEError(res, 'Missing progressive stage: ' + missingStage)
349
+ return
332
350
  }
333
351
 
334
352
  if (typeof config.resolveContext !== 'function') {
335
- return next({ status: 500, message: 'Progressive endpoint requires config.resolveContext' })
353
+ emitTerminalSSEError(res, 'Progressive endpoint requires config.resolveContext')
354
+ return
336
355
  }
337
356
 
338
357
  const ctx = await config.resolveContext(req)
@@ -347,8 +366,8 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
347
366
  })
348
367
  } catch (err) {
349
368
  console.error('[progressive] dispatch error:', err)
350
- if (!res.headersSent) {
351
- return next({ status: 500, message: 'Internal server error' })
369
+ if (!res.headersSent && !res.writableEnded) {
370
+ emitTerminalSSEError(res, 'Internal server error')
352
371
  }
353
372
  }
354
373
  }
@@ -370,70 +389,70 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
370
389
  const openapiJsonPath = basePath ? \`\${basePath}/openapi.json\` : '/openapi.json'
371
390
  const openapiYamlPath = basePath ? \`\${basePath}/openapi.yaml\` : '/openapi.yaml'
372
391
  router.get(openapiJsonPath, (_req, res) => {
373
- res.json(openApiJsonSpec)
392
+ res.json(getOpenApiJson())
374
393
  })
375
394
  router.get(openapiYamlPath, (_req, res) => {
376
- res.type('application/yaml').send(openApiYamlSpec as string)
395
+ res.type('application/yaml').send(getOpenApiYaml())
377
396
  })
378
397
  }
379
398
 
380
- if (config.enableAll || config.findFirst) {
399
+ if (isEnabled(config.findFirst)) {
381
400
  const opConfig: OperationConfigLike = (config.findFirst as OperationConfigLike | undefined) ?? defaultOpConfig
382
401
  const { before = [], after = [] } = opConfig
383
402
  const path = basePath ? \`\${basePath}/first\` : '/first'
384
403
  router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findFirst, 'findFirst'), ${modelName}FindFirst as RequestHandler, ...after, respond)
385
404
  if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${modelName}FindFirst as RequestHandler, ...after, respond)
386
405
  }
387
- if (config.enableAll || config.findFirstOrThrow) {
406
+ if (isEnabled(config.findFirstOrThrow)) {
388
407
  const opConfig: OperationConfigLike = (config.findFirstOrThrow as OperationConfigLike | undefined) ?? defaultOpConfig
389
408
  const { before = [], after = [] } = opConfig
390
409
  const path = basePath ? \`\${basePath}/first/strict\` : '/first/strict'
391
410
  router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findFirstOrThrow, 'findFirstOrThrow'), ${modelName}FindFirstOrThrow as RequestHandler, ...after, respond)
392
411
  if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${modelName}FindFirstOrThrow as RequestHandler, ...after, respond)
393
412
  }
394
- if (config.enableAll || config.findManyPaginated) {
413
+ if (isEnabled(config.findManyPaginated)) {
395
414
  const opConfig: OperationConfigLike = (config.findManyPaginated as OperationConfigLike | undefined) ?? defaultOpConfig
396
415
  const { before = [], after = [] } = opConfig
397
416
  const path = basePath ? \`\${basePath}/paginated\` : '/paginated'
398
417
  router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findManyPaginated, 'findManyPaginated'), ${modelName}FindManyPaginated as RequestHandler, ...after, respond)
399
418
  if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${modelName}FindManyPaginated as RequestHandler, ...after, respond)
400
419
  }
401
- if (config.enableAll || config.aggregate) {
420
+ if (isEnabled(config.aggregate)) {
402
421
  const opConfig: OperationConfigLike = (config.aggregate as OperationConfigLike | undefined) ?? defaultOpConfig
403
422
  const { before = [], after = [] } = opConfig
404
423
  const path = basePath ? \`\${basePath}/aggregate\` : '/aggregate'
405
424
  router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.aggregate, 'aggregate'), ${modelName}Aggregate as RequestHandler, ...after, respond)
406
425
  if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${modelName}Aggregate as RequestHandler, ...after, respond)
407
426
  }
408
- if (config.enableAll || config.count) {
427
+ if (isEnabled(config.count)) {
409
428
  const opConfig: OperationConfigLike = (config.count as OperationConfigLike | undefined) ?? defaultOpConfig
410
429
  const { before = [], after = [] } = opConfig
411
430
  const path = basePath ? \`\${basePath}/count\` : '/count'
412
431
  router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.count, 'count'), ${modelName}Count as RequestHandler, ...after, respond)
413
432
  if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${modelName}Count as RequestHandler, ...after, respond)
414
433
  }
415
- if (config.enableAll || config.groupBy) {
434
+ if (isEnabled(config.groupBy)) {
416
435
  const opConfig: OperationConfigLike = (config.groupBy as OperationConfigLike | undefined) ?? defaultOpConfig
417
436
  const { before = [], after = [] } = opConfig
418
437
  const path = basePath ? \`\${basePath}/groupby\` : '/groupby'
419
438
  router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.groupBy, 'groupBy'), ${modelName}GroupBy as RequestHandler, ...after, respond)
420
439
  if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${modelName}GroupBy as RequestHandler, ...after, respond)
421
440
  }
422
- if (config.enableAll || config.findUniqueOrThrow) {
441
+ if (isEnabled(config.findUniqueOrThrow)) {
423
442
  const opConfig: OperationConfigLike = (config.findUniqueOrThrow as OperationConfigLike | undefined) ?? defaultOpConfig
424
443
  const { before = [], after = [] } = opConfig
425
444
  const path = basePath ? \`\${basePath}/unique/strict\` : '/unique/strict'
426
445
  router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findUniqueOrThrow, 'findUniqueOrThrow'), ${modelName}FindUniqueOrThrow as RequestHandler, ...after, respond)
427
446
  if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${modelName}FindUniqueOrThrow as RequestHandler, ...after, respond)
428
447
  }
429
- if (config.enableAll || config.findUnique) {
448
+ if (isEnabled(config.findUnique)) {
430
449
  const opConfig: OperationConfigLike = (config.findUnique as OperationConfigLike | undefined) ?? defaultOpConfig
431
450
  const { before = [], after = [] } = opConfig
432
451
  const path = basePath ? \`\${basePath}/unique\` : '/unique'
433
452
  router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findUnique, 'findUnique'), ${modelName}FindUnique as RequestHandler, ...after, respond)
434
453
  if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${modelName}FindUnique as RequestHandler, ...after, respond)
435
454
  }
436
- if (config.enableAll || config.findMany) {
455
+ if (isEnabled(config.findMany)) {
437
456
  const opConfig: OperationConfigLike = (config.findMany as OperationConfigLike | undefined) ?? defaultOpConfig
438
457
  const { before = [], after = [] } = opConfig
439
458
  const path = basePath || '/'
@@ -444,55 +463,55 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
444
463
  }
445
464
  }
446
465
 
447
- if (config.enableAll || config.createManyAndReturn) {
466
+ if (isEnabled(config.createManyAndReturn)) {
448
467
  const opConfig: OperationConfigLike = (config.createManyAndReturn as OperationConfigLike | undefined) ?? defaultOpConfig
449
468
  const { before = [], after = [] } = opConfig
450
469
  const path = basePath ? \`\${basePath}/many/return\` : '/many/return'
451
470
  router.post(path, setShape(opConfig), ...before, ${modelName}CreateManyAndReturn as RequestHandler, ...after, respondCreated)
452
471
  }
453
- if (config.enableAll || config.createMany) {
472
+ if (isEnabled(config.createMany)) {
454
473
  const opConfig: OperationConfigLike = (config.createMany as OperationConfigLike | undefined) ?? defaultOpConfig
455
474
  const { before = [], after = [] } = opConfig
456
475
  const path = basePath ? \`\${basePath}/many\` : '/many'
457
476
  router.post(path, setShape(opConfig), ...before, ${modelName}CreateMany as RequestHandler, ...after, respondCreated)
458
477
  }
459
- if (config.enableAll || config.create) {
478
+ if (isEnabled(config.create)) {
460
479
  const opConfig: OperationConfigLike = (config.create as OperationConfigLike | undefined) ?? defaultOpConfig
461
480
  const { before = [], after = [] } = opConfig
462
481
  const path = basePath || '/'
463
482
  router.post(path, setShape(opConfig), ...before, ${modelName}Create as RequestHandler, ...after, respondCreated)
464
483
  }
465
- if (config.enableAll || config.updateManyAndReturn) {
484
+ if (isEnabled(config.updateManyAndReturn)) {
466
485
  const opConfig: OperationConfigLike = (config.updateManyAndReturn as OperationConfigLike | undefined) ?? defaultOpConfig
467
486
  const { before = [], after = [] } = opConfig
468
487
  const path = basePath ? \`\${basePath}/many/return\` : '/many/return'
469
488
  router.put(path, setShape(opConfig), ...before, ${modelName}UpdateManyAndReturn as RequestHandler, ...after, respond)
470
489
  }
471
- if (config.enableAll || config.updateMany) {
490
+ if (isEnabled(config.updateMany)) {
472
491
  const opConfig: OperationConfigLike = (config.updateMany as OperationConfigLike | undefined) ?? defaultOpConfig
473
492
  const { before = [], after = [] } = opConfig
474
493
  const path = basePath ? \`\${basePath}/many\` : '/many'
475
494
  router.put(path, setShape(opConfig), ...before, ${modelName}UpdateMany as RequestHandler, ...after, respond)
476
495
  }
477
- if (config.enableAll || config.update) {
496
+ if (isEnabled(config.update)) {
478
497
  const opConfig: OperationConfigLike = (config.update as OperationConfigLike | undefined) ?? defaultOpConfig
479
498
  const { before = [], after = [] } = opConfig
480
499
  const path = basePath || '/'
481
500
  router.put(path, setShape(opConfig), ...before, ${modelName}Update as RequestHandler, ...after, respond)
482
501
  }
483
- if (config.enableAll || config.upsert) {
502
+ if (isEnabled(config.upsert)) {
484
503
  const opConfig: OperationConfigLike = (config.upsert as OperationConfigLike | undefined) ?? defaultOpConfig
485
504
  const { before = [], after = [] } = opConfig
486
505
  const path = basePath || '/'
487
506
  router.patch(path, setShape(opConfig), ...before, ${modelName}Upsert as RequestHandler, ...after, respond)
488
507
  }
489
- if (config.enableAll || config.deleteMany) {
508
+ if (isEnabled(config.deleteMany)) {
490
509
  const opConfig: OperationConfigLike = (config.deleteMany as OperationConfigLike | undefined) ?? defaultOpConfig
491
510
  const { before = [], after = [] } = opConfig
492
511
  const path = basePath ? \`\${basePath}/many\` : '/many'
493
512
  router.delete(path, setShape(opConfig), ...before, ${modelName}DeleteMany as RequestHandler, ...after, respond)
494
513
  }
495
- if (config.enableAll || config.delete) {
514
+ if (isEnabled(config.delete)) {
496
515
  const opConfig: OperationConfigLike = (config.delete as OperationConfigLike | undefined) ?? defaultOpConfig
497
516
  const { before = [], after = [] } = opConfig
498
517
  const path = basePath || '/'
@@ -500,6 +519,12 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
500
519
  }
501
520
  if (config.updateEach) {
502
521
  const opConfig: OperationConfigLike = (config.updateEach as OperationConfigLike | undefined) ?? defaultOpConfig
522
+ if ((!opConfig.before || opConfig.before.length === 0) && _env.NODE_ENV !== 'production') {
523
+ console.warn(
524
+ '[${modelName}Router] updateEach is enabled without a before hook. ' +
525
+ 'This endpoint bypasses guard shapes and should be protected by authentication middleware.',
526
+ )
527
+ }
503
528
  const { before = [], after = [] } = opConfig
504
529
  const path = basePath ? \`\${basePath}/each\` : '/each'
505
530
  router.post(