prisma-generator-express 1.42.0 → 1.44.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 (53) hide show
  1. package/dist/generators/generateFastifyHandler.d.ts +2 -0
  2. package/dist/generators/generateFastifyHandler.js +21 -6
  3. package/dist/generators/generateFastifyHandler.js.map +1 -1
  4. package/dist/generators/generateHonoHandler.d.ts +2 -0
  5. package/dist/generators/generateHonoHandler.js +8 -6
  6. package/dist/generators/generateHonoHandler.js.map +1 -1
  7. package/dist/generators/generateOperationCore.d.ts +0 -1
  8. package/dist/generators/generateOperationCore.js +34 -546
  9. package/dist/generators/generateOperationCore.js.map +1 -1
  10. package/dist/generators/generateRelationMeta.d.ts +13 -0
  11. package/dist/generators/generateRelationMeta.js +110 -0
  12. package/dist/generators/generateRelationMeta.js.map +1 -0
  13. package/dist/generators/generateRouteConfigType.js +13 -5
  14. package/dist/generators/generateRouteConfigType.js.map +1 -1
  15. package/dist/generators/generateRouter.js +83 -19
  16. package/dist/generators/generateRouter.js.map +1 -1
  17. package/dist/generators/generateRouterFastify.js +127 -384
  18. package/dist/generators/generateRouterFastify.js.map +1 -1
  19. package/dist/generators/generateRouterHono.js +48 -36
  20. package/dist/generators/generateRouterHono.js.map +1 -1
  21. package/dist/generators/generateUnifiedHandler.d.ts +2 -0
  22. package/dist/generators/generateUnifiedHandler.js +28 -10
  23. package/dist/generators/generateUnifiedHandler.js.map +1 -1
  24. package/dist/generators/generateUnifiedScalarUI.d.ts +2 -0
  25. package/dist/generators/generateUnifiedScalarUI.js +19 -16
  26. package/dist/generators/generateUnifiedScalarUI.js.map +1 -1
  27. package/dist/index.js +21 -5
  28. package/dist/index.js.map +1 -1
  29. package/dist/utils/copyFiles.js +12 -0
  30. package/dist/utils/copyFiles.js.map +1 -1
  31. package/dist/utils/writeFileSafely.js +2 -2
  32. package/dist/utils/writeFileSafely.js.map +1 -1
  33. package/package.json +1 -1
  34. package/src/copy/autoIncludePlanner.ts +354 -0
  35. package/src/copy/autoIncludeRuntime.ts +362 -0
  36. package/src/copy/operationRuntime.ts +614 -0
  37. package/src/copy/routeConfig.express.ts +5 -3
  38. package/src/copy/routeConfig.fastify.ts +2 -2
  39. package/src/copy/routeConfig.hono.ts +3 -3
  40. package/src/copy/routeConfig.ts +20 -9
  41. package/src/generators/generateFastifyHandler.ts +23 -6
  42. package/src/generators/generateHonoHandler.ts +10 -6
  43. package/src/generators/generateOperationCore.ts +34 -546
  44. package/src/generators/generateRelationMeta.ts +160 -0
  45. package/src/generators/generateRouteConfigType.ts +13 -6
  46. package/src/generators/generateRouter.ts +83 -19
  47. package/src/generators/generateRouterFastify.ts +127 -384
  48. package/src/generators/generateRouterHono.ts +48 -36
  49. package/src/generators/generateUnifiedHandler.ts +30 -10
  50. package/src/generators/generateUnifiedScalarUI.ts +21 -16
  51. package/src/index.ts +31 -13
  52. package/src/utils/copyFiles.ts +13 -0
  53. package/src/utils/writeFileSafely.ts +2 -2
@@ -0,0 +1,160 @@
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
+ selfFieldName: string,
31
+ ): DMMF.Field | null {
32
+ if (!relationName) return null
33
+ const target = models.find((m) => m.name === targetModelName)
34
+ if (!target) return null
35
+ const isSelfRelation = targetModelName === selfModelName
36
+ return target.fields.find(
37
+ (f) =>
38
+ f.kind === 'object' &&
39
+ f.relationName === relationName &&
40
+ f.type === selfModelName &&
41
+ !(isSelfRelation && f.name === selfFieldName),
42
+ ) || null
43
+ }
44
+
45
+ function computeRelation(
46
+ field: DMMF.Field,
47
+ selfModelName: string,
48
+ models: ReadonlyArray<DMMF.Model>,
49
+ ): RelationFieldMeta {
50
+ const selfFrom = (field.relationFromFields ?? []) as string[]
51
+ const selfTo = (field.relationToFields ?? []) as string[]
52
+
53
+ if (selfFrom.length > 0) {
54
+ return {
55
+ name: field.name,
56
+ type: field.type,
57
+ isList: field.isList,
58
+ isRequired: field.isRequired,
59
+ direction: 'parentOwnsFk',
60
+ parentLinkFields: selfFrom,
61
+ childLinkFields: selfTo,
62
+ }
63
+ }
64
+
65
+ const opposite = findOppositeField(models, field.type, field.relationName, selfModelName, field.name)
66
+ if (opposite) {
67
+ const oppFrom = (opposite.relationFromFields ?? []) as string[]
68
+ const oppTo = (opposite.relationToFields ?? []) as string[]
69
+ if (oppFrom.length > 0) {
70
+ return {
71
+ name: field.name,
72
+ type: field.type,
73
+ isList: field.isList,
74
+ isRequired: field.isRequired,
75
+ direction: 'childOwnsFk',
76
+ parentLinkFields: oppTo,
77
+ childLinkFields: oppFrom,
78
+ }
79
+ }
80
+ }
81
+
82
+ return {
83
+ name: field.name,
84
+ type: field.type,
85
+ isList: field.isList,
86
+ isRequired: field.isRequired,
87
+ direction: 'implicitM2M',
88
+ parentLinkFields: [],
89
+ childLinkFields: [],
90
+ }
91
+ }
92
+
93
+ function buildModelMeta(
94
+ model: DMMF.Model,
95
+ models: ReadonlyArray<DMMF.Model>,
96
+ ): ModelMeta {
97
+ const scalarFields: string[] = []
98
+ const idFields: string[] = []
99
+ const relations: Record<string, RelationFieldMeta> = {}
100
+
101
+ for (const field of model.fields) {
102
+ if (field.kind === 'object') {
103
+ relations[field.name] = computeRelation(field, model.name, models)
104
+ } else if (field.kind === 'scalar' || field.kind === 'enum') {
105
+ scalarFields.push(field.name)
106
+ if (field.isId) idFields.push(field.name)
107
+ }
108
+ }
109
+
110
+ if (model.primaryKey && Array.isArray(model.primaryKey.fields)) {
111
+ for (const f of model.primaryKey.fields) {
112
+ if (!idFields.includes(f)) idFields.push(f)
113
+ }
114
+ }
115
+
116
+ return {
117
+ name: model.name,
118
+ delegateKey: model.name.charAt(0).toLowerCase() + model.name.slice(1),
119
+ scalarFields,
120
+ idFields,
121
+ relations,
122
+ }
123
+ }
124
+
125
+ export interface GenerateRelationMetaOptions {
126
+ model: DMMF.Model
127
+ allModels: ReadonlyArray<DMMF.Model>
128
+ importStyle: ImportStyle
129
+ }
130
+
131
+ export function generateRelationMeta(options: GenerateRelationMetaOptions): string {
132
+ const ext = importExt(options.importStyle)
133
+ const meta = buildModelMeta(options.model, options.allModels)
134
+ return `import type { ModelRelationMap } from '../autoIncludePlanner${ext}'
135
+
136
+ export const ${options.model.name}Relations: ModelRelationMap = ${JSON.stringify(meta, null, 2)}
137
+ `
138
+ }
139
+
140
+ export interface GenerateRelationModelsIndexOptions {
141
+ modelNames: ReadonlyArray<string>
142
+ importStyle: ImportStyle
143
+ }
144
+
145
+ export function generateRelationModelsIndex(options: GenerateRelationModelsIndexOptions): string {
146
+ const ext = importExt(options.importStyle)
147
+ const imports = options.modelNames
148
+ .map((n) => `import { ${n}Relations } from './${n}/${n}Relations${ext}'`)
149
+ .join('\n')
150
+ const entries = options.modelNames
151
+ .map((n) => ` ${n}: ${n}Relations,`)
152
+ .join('\n')
153
+ return `import type { ModelRelationMap } from './autoIncludePlanner${ext}'
154
+ ${imports}
155
+
156
+ export const relationModels: Record<string, ModelRelationMap> = {
157
+ ${entries}
158
+ }
159
+ `
160
+ }
@@ -42,6 +42,12 @@ function capitalize(s: string): string {
42
42
  return s.charAt(0).toUpperCase() + s.slice(1)
43
43
  }
44
44
 
45
+ function requestTypeFor(target: Target): string {
46
+ if (target === 'fastify') return `import('fastify').FastifyRequest`
47
+ if (target === 'hono') return `import('hono').Context`
48
+ return `import('express').Request`
49
+ }
50
+
45
51
  export function generateRouteConfigType(
46
52
  modelName: string,
47
53
  hookHandlerType: string,
@@ -52,9 +58,14 @@ export function generateRouteConfigType(
52
58
  const ext = importExt(importStyle)
53
59
  const m = modelName
54
60
  const supportsProgressive = target === 'express'
61
+ const requestType = requestTypeFor(target)
62
+
63
+ const progressiveTypeImport = supportsProgressive
64
+ ? `import type { ProgressiveVariantConfig, ProgressiveStage } from '../routeConfig.target${ext}'\n\n`
65
+ : ''
55
66
 
56
67
  if (!guardShapesImport) {
57
- return `export type ${m}RouteConfig<TCtx = unknown> = RouteConfig<Record<string, unknown>, TCtx>\n`
68
+ return progressiveTypeImport + `export type ${m}RouteConfig<TCtx = unknown> = RouteConfig<Record<string, unknown>, TCtx>\n`
58
69
  }
59
70
 
60
71
  const shapeOps = Object.values(ROUTER_OP_TO_SHAPE_OP).filter((v, i, a) => a.indexOf(v) === i)
@@ -78,10 +89,6 @@ export function generateRouteConfigType(
78
89
 
79
90
  const omitKeys = ROUTER_OPERATIONS.map((k) => `'${k}'`).join('\n | ')
80
91
 
81
- const progressiveTypeImport = supportsProgressive
82
- ? `import type { ProgressiveVariantConfig, ProgressiveStage } from '../routeConfig.target${ext}'\n\n`
83
- : ''
84
-
85
92
  return (
86
93
  progressiveTypeImport +
87
94
  `import type {\n ${opShapeImports}\n} from '${guardShapesImport}${ext}'\n\n` +
@@ -90,7 +97,7 @@ export function generateRouteConfigType(
90
97
  ` | ${omitKeys}\n` +
91
98
  ` | 'resolveContext'\n` +
92
99
  `> & {\n` +
93
- ` resolveContext?: (request: import('express').Request) => TCtx | Promise<TCtx>\n` +
100
+ ` resolveContext?: (request: ${requestType}) => TCtx | Promise<TCtx>\n` +
94
101
  `${overrides}\n}\n`
95
102
  )
96
103
  }
@@ -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}'
@@ -79,7 +80,13 @@ import {
79
80
  acceptsEventStream,
80
81
  runProgressiveEndpoint,
81
82
  runSingleResultSSE,
83
+ emitTerminalSSEError,
84
+ removeReqCloseListener,
85
+ mapError,
86
+ HttpError,
82
87
  } from '../operationRuntime${ext}'
88
+ import { relationModels } from '../relationModels${ext}'
89
+ import { runAutoIncludeProgressive } from '../autoIncludeRuntime${ext}'
83
90
 
84
91
  ${generateRouteConfigType(modelName, 'RequestHandler', guardShapesImport, importStyle, 'express')}
85
92
  const _env = typeof process !== 'undefined' && process.env ? process.env : {} as Record<string, string | undefined>
@@ -103,7 +110,7 @@ type ExtendedRequest = Request & {
103
110
 
104
111
  type LocalsBag = {
105
112
  parsedQuery?: Record<string, unknown>
106
- routeConfig?: ${modelName}RouteConfig
113
+ routeConfig?: { pagination?: OperationContext['paginationConfig'] }
107
114
  guardShape?: Record<string, unknown>
108
115
  guardCaller?: string
109
116
  data?: unknown
@@ -200,7 +207,9 @@ export function ${routerFunctionName}<TCtx = unknown>(config: ${modelName}RouteC
200
207
  const setShape = (opConfig: OperationConfigLike): RequestHandler => {
201
208
  return (req, res, next) => {
202
209
  const locals = readLocals(res)
203
- locals.routeConfig = config
210
+ if (config.pagination) {
211
+ locals.routeConfig = { pagination: config.pagination }
212
+ }
204
213
  const headerName = config.guard?.variantHeader || 'x-api-variant'
205
214
  const headerValue = req.get(headerName)
206
215
  const caller = config.guard?.resolveVariant?.(req) ?? headerValue ?? undefined
@@ -210,9 +219,10 @@ export function ${routerFunctionName}<TCtx = unknown>(config: ${modelName}RouteC
210
219
  }
211
220
  }
212
221
 
213
- const maybeProgressiveSSE = (
222
+ const maybeProgressiveSSE = (
214
223
  opConfig: OperationConfigLike,
215
224
  coreFn: (ctx: OperationContext) => Promise<unknown>,
225
+ baseOp: string,
216
226
  ): RequestHandler => {
217
227
  return async (req, res, next) => {
218
228
  if (res.headersSent || res.writableEnded) return next()
@@ -233,13 +243,61 @@ export function ${routerFunctionName}<TCtx = unknown>(config: ${modelName}RouteC
233
243
  return
234
244
  }
235
245
 
246
+ if (progressiveConfig.mode === 'autoInclude') {
247
+ const isSingleRecordRead =
248
+ baseOp === 'findUnique' || baseOp === 'findUniqueOrThrow' ||
249
+ baseOp === 'findFirst' || baseOp === 'findFirstOrThrow'
250
+
251
+ if (!isSingleRecordRead) {
252
+ if (progressiveConfig.fallback === 'error') {
253
+ emitTerminalSSEError(res, 'auto-progressive fallback: operation not single-record')
254
+ return
255
+ }
256
+ await runSingleResultSSE({
257
+ req,
258
+ res,
259
+ coreQueryFn: () => coreFn(buildContext(req, res)),
260
+ })
261
+ return
262
+ }
263
+
264
+ const ctx = buildContext(req, res)
265
+ const args = (locals.parsedQuery ?? {}) as Record<string, unknown>
266
+ const controller = new AbortController()
267
+ const onClose = () => controller.abort()
268
+ req.on('close', onClose)
269
+ try {
270
+ await runAutoIncludeProgressive({
271
+ req,
272
+ res,
273
+ ctx,
274
+ args,
275
+ baseOp: baseOp as 'findUnique' | 'findUniqueOrThrow' | 'findFirst' | 'findFirstOrThrow',
276
+ modelName: '${modelName}',
277
+ delegateKey: '${delegateKey}',
278
+ models: relationModels,
279
+ variantConfig: progressiveConfig,
280
+ coreQueryFn: () => coreFn(ctx),
281
+ signal: controller.signal,
282
+ })
283
+ } finally {
284
+ removeReqCloseListener(req, onClose)
285
+ }
286
+ return
287
+ }
288
+
236
289
  if (!Array.isArray(progressiveConfig.stages)) {
237
- return next({ status: 500, message: 'Progressive endpoint requires stages array' })
290
+ await runSingleResultSSE({
291
+ req,
292
+ res,
293
+ coreQueryFn: () => coreFn(buildContext(req, res)),
294
+ })
295
+ return
238
296
  }
239
297
 
240
298
  const stageRegistry = opConfig.progressiveStages ?? {}
241
299
  const missingStage = progressiveConfig.stages.find(
242
- (name) => typeof stageRegistry[name] !== 'function',
300
+ (name: string) => typeof stageRegistry[name] !== 'function',
243
301
  )
244
302
  if (missingStage) {
245
303
  return next({ status: 500, message: 'Missing progressive stage: ' + missingStage })
@@ -297,63 +355,63 @@ export function ${routerFunctionName}<TCtx = unknown>(config: ${modelName}RouteC
297
355
  const opConfig: OperationConfigLike = (config.findFirst as OperationConfigLike | undefined) ?? defaultOpConfig
298
356
  const { before = [], after = [] } = opConfig
299
357
  const path = basePath ? \`\${basePath}/first\` : '/first'
300
- router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findFirst), ${prefix}FindFirst as RequestHandler, ...after, respond)
358
+ router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findFirst, 'findFirst'), ${prefix}FindFirst as RequestHandler, ...after, respond)
301
359
  if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${prefix}FindFirst as RequestHandler, ...after, respond)
302
360
  }
303
361
  if (config.enableAll || config.findFirstOrThrow) {
304
362
  const opConfig: OperationConfigLike = (config.findFirstOrThrow as OperationConfigLike | undefined) ?? defaultOpConfig
305
363
  const { before = [], after = [] } = opConfig
306
364
  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)
365
+ router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findFirstOrThrow, 'findFirstOrThrow'), ${prefix}FindFirstOrThrow as RequestHandler, ...after, respond)
308
366
  if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${prefix}FindFirstOrThrow as RequestHandler, ...after, respond)
309
367
  }
310
368
  if (config.enableAll || config.findManyPaginated) {
311
369
  const opConfig: OperationConfigLike = (config.findManyPaginated as OperationConfigLike | undefined) ?? defaultOpConfig
312
370
  const { before = [], after = [] } = opConfig
313
371
  const path = basePath ? \`\${basePath}/paginated\` : '/paginated'
314
- router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findManyPaginated), ${prefix}FindManyPaginated as RequestHandler, ...after, respond)
372
+ router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findManyPaginated, 'findManyPaginated'), ${prefix}FindManyPaginated as RequestHandler, ...after, respond)
315
373
  if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${prefix}FindManyPaginated as RequestHandler, ...after, respond)
316
374
  }
317
375
  if (config.enableAll || config.aggregate) {
318
376
  const opConfig: OperationConfigLike = (config.aggregate as OperationConfigLike | undefined) ?? defaultOpConfig
319
377
  const { before = [], after = [] } = opConfig
320
378
  const path = basePath ? \`\${basePath}/aggregate\` : '/aggregate'
321
- router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.aggregate), ${prefix}Aggregate as RequestHandler, ...after, respond)
379
+ router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.aggregate, 'aggregate'), ${prefix}Aggregate as RequestHandler, ...after, respond)
322
380
  if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${prefix}Aggregate as RequestHandler, ...after, respond)
323
381
  }
324
382
  if (config.enableAll || config.count) {
325
383
  const opConfig: OperationConfigLike = (config.count as OperationConfigLike | undefined) ?? defaultOpConfig
326
384
  const { before = [], after = [] } = opConfig
327
385
  const path = basePath ? \`\${basePath}/count\` : '/count'
328
- router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.count), ${prefix}Count as RequestHandler, ...after, respond)
386
+ router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.count, 'count'), ${prefix}Count as RequestHandler, ...after, respond)
329
387
  if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${prefix}Count as RequestHandler, ...after, respond)
330
388
  }
331
389
  if (config.enableAll || config.groupBy) {
332
390
  const opConfig: OperationConfigLike = (config.groupBy as OperationConfigLike | undefined) ?? defaultOpConfig
333
391
  const { before = [], after = [] } = opConfig
334
392
  const path = basePath ? \`\${basePath}/groupby\` : '/groupby'
335
- router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.groupBy), ${prefix}GroupBy as RequestHandler, ...after, respond)
393
+ router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.groupBy, 'groupBy'), ${prefix}GroupBy as RequestHandler, ...after, respond)
336
394
  if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${prefix}GroupBy as RequestHandler, ...after, respond)
337
395
  }
338
396
  if (config.enableAll || config.findUniqueOrThrow) {
339
397
  const opConfig: OperationConfigLike = (config.findUniqueOrThrow as OperationConfigLike | undefined) ?? defaultOpConfig
340
398
  const { before = [], after = [] } = opConfig
341
399
  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)
400
+ router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findUniqueOrThrow, 'findUniqueOrThrow'), ${prefix}FindUniqueOrThrow as RequestHandler, ...after, respond)
343
401
  if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${prefix}FindUniqueOrThrow as RequestHandler, ...after, respond)
344
402
  }
345
403
  if (config.enableAll || config.findUnique) {
346
404
  const opConfig: OperationConfigLike = (config.findUnique as OperationConfigLike | undefined) ?? defaultOpConfig
347
405
  const { before = [], after = [] } = opConfig
348
406
  const path = basePath ? \`\${basePath}/unique\` : '/unique'
349
- router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findUnique), ${prefix}FindUnique as RequestHandler, ...after, respond)
407
+ router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findUnique, 'findUnique'), ${prefix}FindUnique as RequestHandler, ...after, respond)
350
408
  if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${prefix}FindUnique as RequestHandler, ...after, respond)
351
409
  }
352
410
  if (config.enableAll || config.findMany) {
353
411
  const opConfig: OperationConfigLike = (config.findMany as OperationConfigLike | undefined) ?? defaultOpConfig
354
412
  const { before = [], after = [] } = opConfig
355
413
  const path = basePath || '/'
356
- router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findMany), ${prefix}FindMany as RequestHandler, ...after, respond)
414
+ router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findMany, 'findMany'), ${prefix}FindMany as RequestHandler, ...after, respond)
357
415
  if (postReadsEnabled) {
358
416
  const postPath = basePath ? \`\${basePath}/read\` : '/read'
359
417
  router.post(postPath, parseBodyAsQuery, setShape(opConfig), ...before, ${prefix}FindMany as RequestHandler, ...after, respond)
@@ -416,10 +474,16 @@ export function ${routerFunctionName}<TCtx = unknown>(config: ${modelName}RouteC
416
474
  }
417
475
 
418
476
  router.use((err: unknown, _req: Request, res: Response, next: NextFunction) => {
419
- const e = err as { status?: number; message?: string }
420
- const status = typeof e.status === 'number' ? e.status : 500
421
- const message = e.message || 'Internal server error'
422
- if (!res.headersSent) return res.status(status).json({ message })
477
+ let httpError: HttpError
478
+ if (err instanceof HttpError) {
479
+ httpError = err
480
+ } else if (err && typeof err === 'object' && typeof (err as { status?: number }).status === 'number') {
481
+ const e = err as { status: number; message?: string }
482
+ httpError = new HttpError(e.status, e.message || 'Internal server error')
483
+ } else {
484
+ httpError = mapError(err)
485
+ }
486
+ if (!res.headersSent) return res.status(httpError.status).json({ message: httpError.message })
423
487
  next(err)
424
488
  })
425
489