prisma-generator-express 1.59.0 → 1.60.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 (64) hide show
  1. package/dist/client/encodeQueryParams.js +19 -23
  2. package/dist/client/encodeQueryParams.js.map +1 -1
  3. package/dist/copy/operationDefinitions.d.ts +37 -0
  4. package/dist/copy/operationDefinitions.js +412 -0
  5. package/dist/copy/operationDefinitions.js.map +1 -0
  6. package/dist/generators/generateFastifyHandler.js +10 -41
  7. package/dist/generators/generateFastifyHandler.js.map +1 -1
  8. package/dist/generators/generateHonoHandler.js +10 -41
  9. package/dist/generators/generateHonoHandler.js.map +1 -1
  10. package/dist/generators/generateModelMetadata.d.ts +8 -0
  11. package/dist/generators/generateModelMetadata.js +77 -0
  12. package/dist/generators/generateModelMetadata.js.map +1 -0
  13. package/dist/generators/generateOperationCore.js +20 -30
  14. package/dist/generators/generateOperationCore.js.map +1 -1
  15. package/dist/generators/generateQueryBuilderHelper.js +1 -1
  16. package/dist/generators/generateQueryBuilderHelper.js.map +1 -1
  17. package/dist/generators/generateRouteConfigType.js +11 -57
  18. package/dist/generators/generateRouteConfigType.js.map +1 -1
  19. package/dist/generators/generateRouter.js +64 -177
  20. package/dist/generators/generateRouter.js.map +1 -1
  21. package/dist/generators/generateRouterFastify.js +59 -169
  22. package/dist/generators/generateRouterFastify.js.map +1 -1
  23. package/dist/generators/generateRouterHono.js +52 -152
  24. package/dist/generators/generateRouterHono.js.map +1 -1
  25. package/dist/generators/generateUnifiedHandler.js +7 -30
  26. package/dist/generators/generateUnifiedHandler.js.map +1 -1
  27. package/dist/generators/generateUnifiedScalarUI.js +9 -74
  28. package/dist/generators/generateUnifiedScalarUI.js.map +1 -1
  29. package/dist/index.js +11 -0
  30. package/dist/index.js.map +1 -1
  31. package/dist/utils/copyFiles.js +7 -0
  32. package/dist/utils/copyFiles.js.map +1 -1
  33. package/dist/utils/writeFileSafely.js +5 -12
  34. package/dist/utils/writeFileSafely.js.map +1 -1
  35. package/package.json +12 -2
  36. package/src/client/encodeQueryParams.ts +23 -36
  37. package/src/copy/autoIncludePlanner.ts +14 -23
  38. package/src/copy/autoIncludeRuntime.ts +117 -228
  39. package/src/copy/buildModelOpenApi.ts +248 -628
  40. package/src/copy/concurrency.ts +20 -0
  41. package/src/copy/docsRenderer.ts +63 -333
  42. package/src/copy/errorMapper.ts +126 -0
  43. package/src/copy/guardHelpers.ts +56 -0
  44. package/src/copy/materializedCount.ts +68 -0
  45. package/src/copy/materializedRouter.ts +33 -29
  46. package/src/copy/operationDefinitions.ts +359 -35
  47. package/src/copy/operationRuntime.ts +11 -605
  48. package/src/copy/pagination.ts +151 -0
  49. package/src/copy/scalarTypes.ts +2 -0
  50. package/src/copy/sse.ts +296 -0
  51. package/src/generators/generateFastifyHandler.ts +13 -47
  52. package/src/generators/generateHonoHandler.ts +13 -47
  53. package/src/generators/generateModelMetadata.ts +92 -0
  54. package/src/generators/generateOperationCore.ts +19 -32
  55. package/src/generators/generateQueryBuilderHelper.ts +1 -1
  56. package/src/generators/generateRouteConfigType.ts +9 -60
  57. package/src/generators/generateRouter.ts +88 -180
  58. package/src/generators/generateRouterFastify.ts +65 -172
  59. package/src/generators/generateRouterHono.ts +58 -155
  60. package/src/generators/generateUnifiedHandler.ts +8 -33
  61. package/src/generators/generateUnifiedScalarUI.ts +9 -91
  62. package/src/index.ts +13 -1
  63. package/src/utils/copyFiles.ts +7 -0
  64. package/src/utils/writeFileSafely.ts +5 -11
@@ -3,6 +3,46 @@ import { generateRouteConfigType } from './generateRouteConfigType'
3
3
  import { ImportStyle } from '../utils/resolveImportStyle'
4
4
  import { importExt } from '../utils/importExt'
5
5
  import { WriteStrategy, FindManyPaginatedMode } from '../constants'
6
+ import { OPERATION_METADATA } from '../copy/operationDefinitions'
7
+
8
+ function pathExpr(suffix: string): string {
9
+ if (!suffix) return `basePath || '/'`
10
+ return `\`\${basePath}${suffix}\``
11
+ }
12
+
13
+ function emitReadOp(meta: (typeof OPERATION_METADATA)[number], modelName: string): string {
14
+ const c = meta.name.charAt(0).toUpperCase() + meta.name.slice(1)
15
+ const handlerName = `${modelName}${c}`
16
+ const pathValue = pathExpr(meta.pathSuffix)
17
+
18
+ const postReadLine = meta.supportsPostRead
19
+ ? meta.name === 'findMany'
20
+ ? ` if (postReadsEnabled) {
21
+ const postPath = basePath ? \`\${basePath}/read\` : '/read'
22
+ instance.post(postPath, handleRead(opConfig, ${handlerName}, parseBodyAsQueryHook))
23
+ }`
24
+ : ` if (postReadsEnabled) instance.post(path, handleRead(opConfig, ${handlerName}, parseBodyAsQueryHook))`
25
+ : ''
26
+
27
+ return ` if (isEnabled(config.${meta.configKey})) {
28
+ const opConfig: OperationConfigLike = (config.${meta.configKey} as OperationConfigLike | undefined) ?? defaultOpConfig
29
+ const path = ${pathValue}
30
+ instance.get(path, handleRead(opConfig, ${handlerName}, parseQueryHook))
31
+ ${postReadLine}
32
+ }`
33
+ }
34
+
35
+ function emitWriteOp(meta: (typeof OPERATION_METADATA)[number], modelName: string): string {
36
+ const c = meta.name.charAt(0).toUpperCase() + meta.name.slice(1)
37
+ const handlerName = `${modelName}${c}`
38
+ const pathValue = pathExpr(meta.pathSuffix)
39
+
40
+ return ` if (isEnabled(config.${meta.configKey})) {
41
+ const opConfig: OperationConfigLike = (config.${meta.configKey} as OperationConfigLike | undefined) ?? defaultOpConfig
42
+ const path = ${pathValue}
43
+ instance.${meta.method}(path, handleWrite(opConfig, ${handlerName}))
44
+ }`
45
+ }
6
46
 
7
47
  export function generateFastifyRouterFunction({
8
48
  model,
@@ -26,56 +66,25 @@ export function generateFastifyRouterFunction({
26
66
  const modelNameLower = modelName.toLowerCase()
27
67
  const routerFunctionName = `${modelName}Router`
28
68
 
29
- const fieldsMeta = model.fields.map((f) => ({
30
- name: f.name,
31
- kind: f.kind,
32
- type: f.type,
33
- isList: f.isList,
34
- isRequired: f.isRequired,
35
- hasDefaultValue: f.hasDefaultValue,
36
- isUpdatedAt: f.isUpdatedAt ?? false,
37
- documentation: f.documentation,
38
- relationFromFields: f.relationFromFields,
39
- }))
40
-
41
- const referencedEnumTypes = new Set(
42
- model.fields.filter((f) => f.kind === 'enum').map((f) => f.type),
43
- )
69
+ const handlerImports = OPERATION_METADATA
70
+ .map((m) => ` ${modelName}${m.name.charAt(0).toUpperCase() + m.name.slice(1)},`)
71
+ .join('\n')
44
72
 
45
- const enumsMeta = enums
46
- .filter((e) => referencedEnumTypes.has(e.name))
47
- .map((e) => ({
48
- name: e.name,
49
- values: e.values.map((v) => ({ name: v.name })),
50
- }))
73
+ const readOps = OPERATION_METADATA.filter((m) => m.kind === 'read')
74
+ const writeOps = OPERATION_METADATA.filter((m) => m.kind === 'write' || m.kind === 'batch')
75
+ .filter((m) => m.name !== 'updateEach')
76
+
77
+ const readOpBlocks = readOps.map((m) => emitReadOp(m, modelName)).join('\n\n')
78
+ const writeOpBlocks = writeOps.map((m) => emitWriteOp(m, modelName)).join('\n\n')
51
79
 
52
80
  return `import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyError } from 'fastify'
53
81
  import { startQueryBuilder } from '../queryBuilder${ext}'
54
82
  import {
55
- ${modelName}FindUnique,
56
- ${modelName}FindUniqueOrThrow,
57
- ${modelName}FindFirst,
58
- ${modelName}FindFirstOrThrow,
59
- ${modelName}FindMany,
60
- ${modelName}FindManyPaginated,
61
- ${modelName}Create,
62
- ${modelName}CreateMany,
63
- ${modelName}CreateManyAndReturn,
64
- ${modelName}Update,
65
- ${modelName}UpdateMany,
66
- ${modelName}UpdateManyAndReturn,
67
- ${modelName}Upsert,
68
- ${modelName}Delete,
69
- ${modelName}DeleteMany,
70
- ${modelName}Aggregate,
71
- ${modelName}Count,
72
- ${modelName}GroupBy,
73
- ${modelName}UpdateEach,
83
+ ${handlerImports}
74
84
  } from './${modelName}Handlers${ext}'
75
85
  import type {
76
86
  RouteConfig,
77
87
  FastifyHookHandler,
78
- WriteStrategy,
79
88
  FindManyPaginatedMode,
80
89
  PaginationConfig,
81
90
  } from '../routeConfig.target${ext}'
@@ -83,19 +92,18 @@ import { parseQueryParams } from '../parseQueryParams${ext}'
83
92
  import { sanitizeKeys, normalizePrefix, getEnv } from '../misc${ext}'
84
93
  import { buildModelOpenApi } from '../buildModelOpenApi${ext}'
85
94
  import { validateCountSourceWhere } from '../routeConfig${ext}'
86
- import { mapError, transformResult, mergePaginationConfig, HttpError, type OperationContext } from '../operationRuntime${ext}'
95
+ import type { OperationContext } from '../operationRuntime${ext}'
96
+ import { transformResult } from '../operationRuntime${ext}'
97
+ import { HttpError, mapError } from '../errorMapper${ext}'
98
+ import { mergePaginationConfig } from '../pagination${ext}'
99
+ import { MODEL_FIELDS, MODEL_ENUMS } from './${modelName}Metadata${ext}'
87
100
 
88
101
  ${generateRouteConfigType(modelName, 'FastifyHookHandler', guardShapesImport, importStyle, 'fastify')}
89
102
  const _env = getEnv()
90
103
 
91
- const WRITE_STRATEGY: WriteStrategy = '${writeStrategy}'
92
104
  const FIND_MANY_PAGINATED_MODE: FindManyPaginatedMode = '${findManyPaginatedMode}'
93
105
  const DROP_GUARD = ${dropGuard} || _env.E2E === 'true'
94
106
 
95
- const MODEL_FIELDS = ${JSON.stringify(fieldsMeta, null, 2)} as const
96
-
97
- const MODEL_ENUMS = ${JSON.stringify(enumsMeta, null, 2)} as const
98
-
99
107
  type OperationConfigLike = {
100
108
  before?: FastifyHookHandler[]
101
109
  after?: FastifyHookHandler[]
@@ -234,7 +242,7 @@ export async function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(
234
242
  MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
235
243
  MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
236
244
  config,
237
- { format: 'json', writeStrategy: WRITE_STRATEGY },
245
+ { format: 'json', writeStrategy: '${writeStrategy}' },
238
246
  )
239
247
  }
240
248
  return _openApiJsonCache
@@ -247,7 +255,7 @@ export async function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(
247
255
  MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
248
256
  MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
249
257
  config,
250
- { format: 'yaml', writeStrategy: WRITE_STRATEGY },
258
+ { format: 'yaml', writeStrategy: '${writeStrategy}' },
251
259
  ) as string
252
260
  }
253
261
  return _openApiYamlCache
@@ -271,11 +279,9 @@ export async function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(
271
279
  })
272
280
 
273
281
  instance.setErrorHandler((error: FastifyError, _request: FastifyRequest, reply: FastifyReply) => {
274
- const e = error as { status?: number; statusCode?: number; message?: string }
275
- const status = e.status ?? e.statusCode ?? 500
276
- const message = error.message || 'Internal server error'
282
+ const httpError = mapError(error)
277
283
  if (!reply.sent) {
278
- reply.code(status).send({ message })
284
+ reply.code(httpError.status).send({ message: httpError.message })
279
285
  }
280
286
  })
281
287
 
@@ -292,7 +298,7 @@ export async function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(
292
298
  })
293
299
  }
294
300
 
295
- const handleGet = (
301
+ const handleRead = (
296
302
  opConfig: OperationConfigLike,
297
303
  handlerFn: (req: FastifyRequest, reply: FastifyReply) => Promise<void>,
298
304
  parseFn: (req: FastifyRequest) => void,
@@ -326,125 +332,9 @@ export async function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(
326
332
  }
327
333
  }
328
334
 
329
- if (isEnabled(config.findFirst)) {
330
- const opConfig: OperationConfigLike = (config.findFirst as OperationConfigLike | undefined) ?? defaultOpConfig
331
- const path = basePath ? \`\${basePath}/first\` : '/first'
332
- instance.get(path, handleGet(opConfig, ${modelName}FindFirst, parseQueryHook))
333
- if (postReadsEnabled) instance.post(path, handleGet(opConfig, ${modelName}FindFirst, parseBodyAsQueryHook))
334
- }
335
-
336
- if (isEnabled(config.findFirstOrThrow)) {
337
- const opConfig: OperationConfigLike = (config.findFirstOrThrow as OperationConfigLike | undefined) ?? defaultOpConfig
338
- const path = basePath ? \`\${basePath}/first/strict\` : '/first/strict'
339
- instance.get(path, handleGet(opConfig, ${modelName}FindFirstOrThrow, parseQueryHook))
340
- if (postReadsEnabled) instance.post(path, handleGet(opConfig, ${modelName}FindFirstOrThrow, parseBodyAsQueryHook))
341
- }
342
-
343
- if (isEnabled(config.findManyPaginated)) {
344
- const opConfig: OperationConfigLike = (config.findManyPaginated as OperationConfigLike | undefined) ?? defaultOpConfig
345
- const path = basePath ? \`\${basePath}/paginated\` : '/paginated'
346
- instance.get(path, handleGet(opConfig, ${modelName}FindManyPaginated, parseQueryHook))
347
- if (postReadsEnabled) instance.post(path, handleGet(opConfig, ${modelName}FindManyPaginated, parseBodyAsQueryHook))
348
- }
349
-
350
- if (isEnabled(config.aggregate)) {
351
- const opConfig: OperationConfigLike = (config.aggregate as OperationConfigLike | undefined) ?? defaultOpConfig
352
- const path = basePath ? \`\${basePath}/aggregate\` : '/aggregate'
353
- instance.get(path, handleGet(opConfig, ${modelName}Aggregate, parseQueryHook))
354
- if (postReadsEnabled) instance.post(path, handleGet(opConfig, ${modelName}Aggregate, parseBodyAsQueryHook))
355
- }
356
-
357
- if (isEnabled(config.count)) {
358
- const opConfig: OperationConfigLike = (config.count as OperationConfigLike | undefined) ?? defaultOpConfig
359
- const path = basePath ? \`\${basePath}/count\` : '/count'
360
- instance.get(path, handleGet(opConfig, ${modelName}Count, parseQueryHook))
361
- if (postReadsEnabled) instance.post(path, handleGet(opConfig, ${modelName}Count, parseBodyAsQueryHook))
362
- }
335
+ ${readOpBlocks}
363
336
 
364
- if (isEnabled(config.groupBy)) {
365
- const opConfig: OperationConfigLike = (config.groupBy as OperationConfigLike | undefined) ?? defaultOpConfig
366
- const path = basePath ? \`\${basePath}/groupby\` : '/groupby'
367
- instance.get(path, handleGet(opConfig, ${modelName}GroupBy, parseQueryHook))
368
- if (postReadsEnabled) instance.post(path, handleGet(opConfig, ${modelName}GroupBy, parseBodyAsQueryHook))
369
- }
370
-
371
- if (isEnabled(config.findUniqueOrThrow)) {
372
- const opConfig: OperationConfigLike = (config.findUniqueOrThrow as OperationConfigLike | undefined) ?? defaultOpConfig
373
- const path = basePath ? \`\${basePath}/unique/strict\` : '/unique/strict'
374
- instance.get(path, handleGet(opConfig, ${modelName}FindUniqueOrThrow, parseQueryHook))
375
- if (postReadsEnabled) instance.post(path, handleGet(opConfig, ${modelName}FindUniqueOrThrow, parseBodyAsQueryHook))
376
- }
377
-
378
- if (isEnabled(config.findUnique)) {
379
- const opConfig: OperationConfigLike = (config.findUnique as OperationConfigLike | undefined) ?? defaultOpConfig
380
- const path = basePath ? \`\${basePath}/unique\` : '/unique'
381
- instance.get(path, handleGet(opConfig, ${modelName}FindUnique, parseQueryHook))
382
- if (postReadsEnabled) instance.post(path, handleGet(opConfig, ${modelName}FindUnique, parseBodyAsQueryHook))
383
- }
384
-
385
- if (isEnabled(config.findMany)) {
386
- const opConfig: OperationConfigLike = (config.findMany as OperationConfigLike | undefined) ?? defaultOpConfig
387
- const path = basePath || '/'
388
- instance.get(path, handleGet(opConfig, ${modelName}FindMany, parseQueryHook))
389
- if (postReadsEnabled) {
390
- const postPath = basePath ? \`\${basePath}/read\` : '/read'
391
- instance.post(postPath, handleGet(opConfig, ${modelName}FindMany, parseBodyAsQueryHook))
392
- }
393
- }
394
-
395
- if (isEnabled(config.createManyAndReturn)) {
396
- const opConfig: OperationConfigLike = (config.createManyAndReturn as OperationConfigLike | undefined) ?? defaultOpConfig
397
- const path = basePath ? \`\${basePath}/many/return\` : '/many/return'
398
- instance.post(path, handleWrite(opConfig, ${modelName}CreateManyAndReturn))
399
- }
400
-
401
- if (isEnabled(config.createMany)) {
402
- const opConfig: OperationConfigLike = (config.createMany as OperationConfigLike | undefined) ?? defaultOpConfig
403
- const path = basePath ? \`\${basePath}/many\` : '/many'
404
- instance.post(path, handleWrite(opConfig, ${modelName}CreateMany))
405
- }
406
-
407
- if (isEnabled(config.create)) {
408
- const opConfig: OperationConfigLike = (config.create as OperationConfigLike | undefined) ?? defaultOpConfig
409
- const path = basePath || '/'
410
- instance.post(path, handleWrite(opConfig, ${modelName}Create))
411
- }
412
-
413
- if (isEnabled(config.updateManyAndReturn)) {
414
- const opConfig: OperationConfigLike = (config.updateManyAndReturn as OperationConfigLike | undefined) ?? defaultOpConfig
415
- const path = basePath ? \`\${basePath}/many/return\` : '/many/return'
416
- instance.put(path, handleWrite(opConfig, ${modelName}UpdateManyAndReturn))
417
- }
418
-
419
- if (isEnabled(config.updateMany)) {
420
- const opConfig: OperationConfigLike = (config.updateMany as OperationConfigLike | undefined) ?? defaultOpConfig
421
- const path = basePath ? \`\${basePath}/many\` : '/many'
422
- instance.put(path, handleWrite(opConfig, ${modelName}UpdateMany))
423
- }
424
-
425
- if (isEnabled(config.update)) {
426
- const opConfig: OperationConfigLike = (config.update as OperationConfigLike | undefined) ?? defaultOpConfig
427
- const path = basePath || '/'
428
- instance.put(path, handleWrite(opConfig, ${modelName}Update))
429
- }
430
-
431
- if (isEnabled(config.upsert)) {
432
- const opConfig: OperationConfigLike = (config.upsert as OperationConfigLike | undefined) ?? defaultOpConfig
433
- const path = basePath || '/'
434
- instance.patch(path, handleWrite(opConfig, ${modelName}Upsert))
435
- }
436
-
437
- if (isEnabled(config.deleteMany)) {
438
- const opConfig: OperationConfigLike = (config.deleteMany as OperationConfigLike | undefined) ?? defaultOpConfig
439
- const path = basePath ? \`\${basePath}/many\` : '/many'
440
- instance.delete(path, handleWrite(opConfig, ${modelName}DeleteMany))
441
- }
442
-
443
- if (isEnabled(config.delete)) {
444
- const opConfig: OperationConfigLike = (config.delete as OperationConfigLike | undefined) ?? defaultOpConfig
445
- const path = basePath || '/'
446
- instance.delete(path, handleWrite(opConfig, ${modelName}Delete))
447
- }
337
+ ${writeOpBlocks}
448
338
 
449
339
  if (config.updateEach) {
450
340
  const opConfig: OperationConfigLike = (config.updateEach as OperationConfigLike | undefined) ?? defaultOpConfig
@@ -457,6 +347,9 @@ export async function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(
457
347
  const path = basePath ? \`\${basePath}/each\` : '/each'
458
348
  instance.post(path, async (request: FastifyRequest, reply: FastifyReply) => {
459
349
  try {
350
+ if (!Array.isArray(request.body)) {
351
+ throw new HttpError(400, 'updateEach body must be an array of { where, data } items')
352
+ }
460
353
  makeShapeHook(config, opConfig)(request)
461
354
  const { before = [], after = [] } = opConfig
462
355
  if (await runHooks(before, request, reply)) return
@@ -3,6 +3,46 @@ import { generateRouteConfigType } from './generateRouteConfigType'
3
3
  import { ImportStyle } from '../utils/resolveImportStyle'
4
4
  import { importExt } from '../utils/importExt'
5
5
  import { WriteStrategy } from '../constants'
6
+ import { OPERATION_METADATA } from '../copy/operationDefinitions'
7
+
8
+ function pathExpr(suffix: string): string {
9
+ if (!suffix) return `basePath || '/'`
10
+ return `\`\${basePath}${suffix}\``
11
+ }
12
+
13
+ function emitReadOp(meta: (typeof OPERATION_METADATA)[number], modelName: string): string {
14
+ const c = meta.name.charAt(0).toUpperCase() + meta.name.slice(1)
15
+ const handlerName = `${modelName}${c}`
16
+ const pathValue = pathExpr(meta.pathSuffix)
17
+
18
+ const postReadLine = meta.supportsPostRead
19
+ ? meta.name === 'findMany'
20
+ ? ` if (postReadsEnabled) {
21
+ const postPath = basePath ? \`\${basePath}/read\` : '/read'
22
+ app.post(postPath, handleRead(opConfig, ${handlerName}, parseBodyAsQueryMiddleware))
23
+ }`
24
+ : ` if (postReadsEnabled) app.post(path, handleRead(opConfig, ${handlerName}, parseBodyAsQueryMiddleware))`
25
+ : ''
26
+
27
+ return ` if (isEnabled(config.${meta.configKey})) {
28
+ const opConfig = opFor('${meta.configKey}')
29
+ const path = ${pathValue}
30
+ app.get(path, handleRead(opConfig, ${handlerName}, parseQueryMiddleware))
31
+ ${postReadLine}
32
+ }`
33
+ }
34
+
35
+ function emitWriteOp(meta: (typeof OPERATION_METADATA)[number], modelName: string): string {
36
+ const c = meta.name.charAt(0).toUpperCase() + meta.name.slice(1)
37
+ const handlerName = `${modelName}${c}`
38
+ const pathValue = pathExpr(meta.pathSuffix)
39
+
40
+ return ` if (isEnabled(config.${meta.configKey})) {
41
+ const opConfig = opFor('${meta.configKey}')
42
+ const path = ${pathValue}
43
+ app.${meta.method}(path, handleWrite(opConfig, ${handlerName}))
44
+ }`
45
+ }
6
46
 
7
47
  export function generateHonoRouterFunction({
8
48
  model,
@@ -24,53 +64,23 @@ export function generateHonoRouterFunction({
24
64
  const modelNameLower = modelName.toLowerCase()
25
65
  const routerFunctionName = `${modelName}Router`
26
66
 
27
- const fieldsMeta = model.fields.map((f) => ({
28
- name: f.name,
29
- kind: f.kind,
30
- type: f.type,
31
- isList: f.isList,
32
- isRequired: f.isRequired,
33
- hasDefaultValue: f.hasDefaultValue,
34
- isUpdatedAt: f.isUpdatedAt ?? false,
35
- documentation: f.documentation,
36
- relationFromFields: f.relationFromFields,
37
- }))
38
-
39
- const referencedEnumTypes = new Set(
40
- model.fields.filter((f) => f.kind === 'enum').map((f) => f.type),
41
- )
67
+ const handlerImports = OPERATION_METADATA
68
+ .map((m) => ` ${modelName}${m.name.charAt(0).toUpperCase() + m.name.slice(1)},`)
69
+ .join('\n')
70
+
71
+ const readOps = OPERATION_METADATA.filter((m) => m.kind === 'read')
72
+ const writeOps = OPERATION_METADATA.filter((m) => m.kind === 'write' || m.kind === 'batch')
73
+ .filter((m) => m.name !== 'updateEach')
42
74
 
43
- const enumsMeta = enums
44
- .filter((e) => referencedEnumTypes.has(e.name))
45
- .map((e) => ({
46
- name: e.name,
47
- values: e.values.map((v) => ({ name: v.name })),
48
- }))
75
+ const readOpBlocks = readOps.map((m) => emitReadOp(m, modelName)).join('\n\n')
76
+ const writeOpBlocks = writeOps.map((m) => emitWriteOp(m, modelName)).join('\n\n')
49
77
 
50
78
  return `import { Hono } from 'hono'
51
79
  import type { Context } from 'hono'
52
80
  import type { ContentfulStatusCode } from 'hono/utils/http-status'
53
81
  import { HTTPException } from 'hono/http-exception'
54
82
  import {
55
- ${modelName}FindUnique,
56
- ${modelName}FindUniqueOrThrow,
57
- ${modelName}FindFirst,
58
- ${modelName}FindFirstOrThrow,
59
- ${modelName}FindMany,
60
- ${modelName}FindManyPaginated,
61
- ${modelName}Create,
62
- ${modelName}CreateMany,
63
- ${modelName}CreateManyAndReturn,
64
- ${modelName}Update,
65
- ${modelName}UpdateMany,
66
- ${modelName}UpdateManyAndReturn,
67
- ${modelName}Upsert,
68
- ${modelName}Delete,
69
- ${modelName}DeleteMany,
70
- ${modelName}Aggregate,
71
- ${modelName}Count,
72
- ${modelName}GroupBy,
73
- ${modelName}UpdateEach,
83
+ ${handlerImports}
74
84
  } from './${modelName}Handlers${ext}'
75
85
  import type {
76
86
  RouteConfig,
@@ -79,23 +89,20 @@ import type {
79
89
  HonoEnvBase,
80
90
  HonoInternalVariables,
81
91
  GeneratedHonoEnv,
82
- WriteStrategy,
83
92
  PaginationConfig,
84
93
  } from '../routeConfig.target${ext}'
85
94
  import { parseQueryParams } from '../parseQueryParams${ext}'
86
95
  import { normalizePrefix, getEnv, sanitizeKeys } from '../misc${ext}'
87
96
  import { buildModelOpenApi } from '../buildModelOpenApi${ext}'
88
97
  import { validateCountSourceWhere } from '../routeConfig${ext}'
89
- import {
90
- mapError,
91
- transformResult,
92
- mergePaginationConfig,
93
- } from '../operationRuntime${ext}'
98
+ import { transformResult } from '../operationRuntime${ext}'
99
+ import { mapError } from '../errorMapper${ext}'
100
+ import { mergePaginationConfig } from '../pagination${ext}'
101
+ import { MODEL_FIELDS, MODEL_ENUMS } from './${modelName}Metadata${ext}'
94
102
 
95
103
  ${generateRouteConfigType(modelName, 'HonoBeforeHook', guardShapesImport, importStyle, 'hono')}
96
104
  const _env = getEnv()
97
105
 
98
- const WRITE_STRATEGY: WriteStrategy = '${writeStrategy}'
99
106
  const DROP_GUARD = ${dropGuard} || _env.E2E === 'true'
100
107
 
101
108
  type JsonLike =
@@ -106,10 +113,6 @@ type JsonLike =
106
113
  | JsonLike[]
107
114
  | { [k: string]: JsonLike }
108
115
 
109
- const MODEL_FIELDS = ${JSON.stringify(fieldsMeta, null, 2)} as const
110
-
111
- const MODEL_ENUMS = ${JSON.stringify(enumsMeta, null, 2)} as const
112
-
113
116
  type OperationConfigLike<TEnv extends HonoEnvBase> = {
114
117
  before?: HonoBeforeHook<TEnv>[]
115
118
  after?: HonoAfterHook<TEnv>[]
@@ -259,7 +262,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
259
262
  MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
260
263
  MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
261
264
  config as RouteConfig,
262
- { format: 'json', writeStrategy: WRITE_STRATEGY },
265
+ { format: 'json', writeStrategy: '${writeStrategy}' },
263
266
  )
264
267
  }
265
268
  return _openApiJsonCache
@@ -272,7 +275,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
272
275
  MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
273
276
  MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
274
277
  config as RouteConfig,
275
- { format: 'yaml', writeStrategy: WRITE_STRATEGY },
278
+ { format: 'yaml', writeStrategy: '${writeStrategy}' },
276
279
  ) as string
277
280
  }
278
281
  return _openApiYamlCache
@@ -343,109 +346,9 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
343
346
  ?? (defaultOpConfig as OperationConfigLike<TEnv>)
344
347
  }
345
348
 
346
- if (isEnabled(config.findFirst)) {
347
- const opConfig = opFor('findFirst')
348
- const path = basePath ? \`\${basePath}/first\` : '/first'
349
- app.get(path, handleRead(opConfig, ${modelName}FindFirst, parseQueryMiddleware))
350
- if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}FindFirst, parseBodyAsQueryMiddleware))
351
- }
352
- if (isEnabled(config.findFirstOrThrow)) {
353
- const opConfig = opFor('findFirstOrThrow')
354
- const path = basePath ? \`\${basePath}/first/strict\` : '/first/strict'
355
- app.get(path, handleRead(opConfig, ${modelName}FindFirstOrThrow, parseQueryMiddleware))
356
- if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}FindFirstOrThrow, parseBodyAsQueryMiddleware))
357
- }
358
- if (isEnabled(config.findManyPaginated)) {
359
- const opConfig = opFor('findManyPaginated')
360
- const path = basePath ? \`\${basePath}/paginated\` : '/paginated'
361
- app.get(path, handleRead(opConfig, ${modelName}FindManyPaginated, parseQueryMiddleware))
362
- if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}FindManyPaginated, parseBodyAsQueryMiddleware))
363
- }
364
- if (isEnabled(config.aggregate)) {
365
- const opConfig = opFor('aggregate')
366
- const path = basePath ? \`\${basePath}/aggregate\` : '/aggregate'
367
- app.get(path, handleRead(opConfig, ${modelName}Aggregate, parseQueryMiddleware))
368
- if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}Aggregate, parseBodyAsQueryMiddleware))
369
- }
370
- if (isEnabled(config.count)) {
371
- const opConfig = opFor('count')
372
- const path = basePath ? \`\${basePath}/count\` : '/count'
373
- app.get(path, handleRead(opConfig, ${modelName}Count, parseQueryMiddleware))
374
- if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}Count, parseBodyAsQueryMiddleware))
375
- }
376
- if (isEnabled(config.groupBy)) {
377
- const opConfig = opFor('groupBy')
378
- const path = basePath ? \`\${basePath}/groupby\` : '/groupby'
379
- app.get(path, handleRead(opConfig, ${modelName}GroupBy, parseQueryMiddleware))
380
- if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}GroupBy, parseBodyAsQueryMiddleware))
381
- }
382
- if (isEnabled(config.findUniqueOrThrow)) {
383
- const opConfig = opFor('findUniqueOrThrow')
384
- const path = basePath ? \`\${basePath}/unique/strict\` : '/unique/strict'
385
- app.get(path, handleRead(opConfig, ${modelName}FindUniqueOrThrow, parseQueryMiddleware))
386
- if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}FindUniqueOrThrow, parseBodyAsQueryMiddleware))
387
- }
388
- if (isEnabled(config.findUnique)) {
389
- const opConfig = opFor('findUnique')
390
- const path = basePath ? \`\${basePath}/unique\` : '/unique'
391
- app.get(path, handleRead(opConfig, ${modelName}FindUnique, parseQueryMiddleware))
392
- if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}FindUnique, parseBodyAsQueryMiddleware))
393
- }
394
- if (isEnabled(config.findMany)) {
395
- const opConfig = opFor('findMany')
396
- const path = basePath || '/'
397
- app.get(path, handleRead(opConfig, ${modelName}FindMany, parseQueryMiddleware))
398
- if (postReadsEnabled) {
399
- const postPath = basePath ? \`\${basePath}/read\` : '/read'
400
- app.post(postPath, handleRead(opConfig, ${modelName}FindMany, parseBodyAsQueryMiddleware))
401
- }
402
- }
349
+ ${readOpBlocks}
403
350
 
404
- if (isEnabled(config.createManyAndReturn)) {
405
- const opConfig = opFor('createManyAndReturn')
406
- const path = basePath ? \`\${basePath}/many/return\` : '/many/return'
407
- app.post(path, handleWrite(opConfig, ${modelName}CreateManyAndReturn))
408
- }
409
- if (isEnabled(config.createMany)) {
410
- const opConfig = opFor('createMany')
411
- const path = basePath ? \`\${basePath}/many\` : '/many'
412
- app.post(path, handleWrite(opConfig, ${modelName}CreateMany))
413
- }
414
- if (isEnabled(config.create)) {
415
- const opConfig = opFor('create')
416
- const path = basePath || '/'
417
- app.post(path, handleWrite(opConfig, ${modelName}Create))
418
- }
419
- if (isEnabled(config.updateManyAndReturn)) {
420
- const opConfig = opFor('updateManyAndReturn')
421
- const path = basePath ? \`\${basePath}/many/return\` : '/many/return'
422
- app.put(path, handleWrite(opConfig, ${modelName}UpdateManyAndReturn))
423
- }
424
- if (isEnabled(config.updateMany)) {
425
- const opConfig = opFor('updateMany')
426
- const path = basePath ? \`\${basePath}/many\` : '/many'
427
- app.put(path, handleWrite(opConfig, ${modelName}UpdateMany))
428
- }
429
- if (isEnabled(config.update)) {
430
- const opConfig = opFor('update')
431
- const path = basePath || '/'
432
- app.put(path, handleWrite(opConfig, ${modelName}Update))
433
- }
434
- if (isEnabled(config.upsert)) {
435
- const opConfig = opFor('upsert')
436
- const path = basePath || '/'
437
- app.patch(path, handleWrite(opConfig, ${modelName}Upsert))
438
- }
439
- if (isEnabled(config.deleteMany)) {
440
- const opConfig = opFor('deleteMany')
441
- const path = basePath ? \`\${basePath}/many\` : '/many'
442
- app.delete(path, handleWrite(opConfig, ${modelName}DeleteMany))
443
- }
444
- if (isEnabled(config.delete)) {
445
- const opConfig = opFor('delete')
446
- const path = basePath || '/'
447
- app.delete(path, handleWrite(opConfig, ${modelName}Delete))
448
- }
351
+ ${writeOpBlocks}
449
352
 
450
353
  if (config.updateEach) {
451
354
  const opConfig = (config.updateEach as unknown as OperationConfigLike<TEnv> | undefined) ?? (defaultOpConfig as OperationConfigLike<TEnv>)
@@ -1,47 +1,21 @@
1
1
  import { DMMF } from '@prisma/generator-helper'
2
2
  import { ImportStyle } from '../utils/resolveImportStyle'
3
3
  import { importExt } from '../utils/importExt'
4
+ import { OPERATION_METADATA } from '../copy/operationDefinitions'
4
5
 
5
6
  export interface UnifiedHandlerOptions {
6
7
  model: DMMF.Model
7
8
  importStyle: ImportStyle
8
9
  }
9
10
 
10
- const CORE_NAME_MAP: Record<string, string> = {
11
- delete: 'deleteUnique',
12
- }
13
-
14
- function coreFnName(op: string): string {
15
- return CORE_NAME_MAP[op] || op
16
- }
17
-
18
- const ALL_OPS = [
19
- 'findMany',
20
- 'findFirst',
21
- 'findFirstOrThrow',
22
- 'findUnique',
23
- 'findUniqueOrThrow',
24
- 'findManyPaginated',
25
- 'create',
26
- 'createMany',
27
- 'createManyAndReturn',
28
- 'update',
29
- 'updateMany',
30
- 'updateManyAndReturn',
31
- 'upsert',
32
- 'delete',
33
- 'deleteMany',
34
- 'aggregate',
35
- 'count',
36
- 'groupBy',
37
- ]
38
-
39
11
  export function generateUnifiedHandler(options: UnifiedHandlerOptions): string {
40
12
  const ext = importExt(options.importStyle)
41
13
  const modelName = options.model.name
42
14
 
43
- const handlers = ALL_OPS.map((op) => {
44
- const exportName = `${modelName}${op.charAt(0).toUpperCase() + op.slice(1)}`
15
+ const dispatchOps = OPERATION_METADATA.filter((m) => m.name !== 'updateEach')
16
+
17
+ const handlers = dispatchOps.map((meta) => {
18
+ const exportName = `${modelName}${meta.name.charAt(0).toUpperCase() + meta.name.slice(1)}`
45
19
 
46
20
  return `
47
21
  export async function ${exportName}(
@@ -50,7 +24,7 @@ export async function ${exportName}(
50
24
  next: NextFunction,
51
25
  ) {
52
26
  try {
53
- ;(res.locals as LocalsBag).data = await core.${coreFnName(op)}(buildContext(req, res))
27
+ ;(res.locals as LocalsBag).data = await core.${meta.coreName}(buildContext(req, res))
54
28
  next()
55
29
  } catch (error: unknown) {
56
30
  next(mapError(error))
@@ -60,7 +34,8 @@ export async function ${exportName}(
60
34
 
61
35
  return `import type { Request, Response, NextFunction } from 'express'
62
36
  import * as core from './${modelName}Core${ext}'
63
- import { OperationContext, mapError } from '../operationRuntime${ext}'
37
+ import { OperationContext } from '../operationRuntime${ext}'
38
+ import { mapError } from '../errorMapper${ext}'
64
39
 
65
40
  type ExtendedRequest = Request & {
66
41
  prisma?: unknown