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
@@ -0,0 +1,92 @@
1
+ import { DMMF } from '@prisma/generator-helper'
2
+ import { ImportStyle } from '../utils/resolveImportStyle'
3
+ import { importExt } from '../utils/importExt'
4
+
5
+ function exampleValueForType(fieldType: string): unknown {
6
+ switch (fieldType) {
7
+ case 'String': return 'example'
8
+ case 'Int': return 1
9
+ case 'BigInt': return '1'
10
+ case 'Float': return 1.0
11
+ case 'Decimal': return '1.0'
12
+ case 'Boolean': return true
13
+ case 'DateTime': return '2025-01-01T00:00:00.000Z'
14
+ case 'Json': return {}
15
+ case 'Bytes': return 'base64data'
16
+ default: return 'example'
17
+ }
18
+ }
19
+
20
+ export interface GenerateModelMetadataOptions {
21
+ model: DMMF.Model
22
+ enums: DMMF.DatamodelEnum[]
23
+ importStyle: ImportStyle
24
+ }
25
+
26
+ export function generateModelMetadata(options: GenerateModelMetadataOptions): string {
27
+ const ext = importExt(options.importStyle)
28
+ const { model, enums } = options
29
+ const modelName = model.name
30
+
31
+ const fieldsMeta = model.fields.map((f: any) => ({
32
+ name: f.name,
33
+ kind: f.kind,
34
+ type: f.type,
35
+ isList: f.isList,
36
+ isRequired: f.isRequired,
37
+ hasDefaultValue: f.hasDefaultValue,
38
+ isUpdatedAt: Boolean(f.isUpdatedAt),
39
+ documentation: f.documentation ?? null,
40
+ isId: Boolean(f.isId),
41
+ isUnique: Boolean(f.isUnique),
42
+ relationFromFields: f.relationFromFields,
43
+ }))
44
+
45
+ const referencedEnumTypes = new Set(
46
+ model.fields.filter((f: any) => f.kind === 'enum').map((f: any) => f.type),
47
+ )
48
+
49
+ const enumsMeta = enums
50
+ .filter((e) => referencedEnumTypes.has(e.name))
51
+ .map((e) => ({
52
+ name: e.name,
53
+ values: e.values.map((v) => ({ name: v.name })),
54
+ }))
55
+
56
+ const exampleValues: Record<string, unknown> = {}
57
+ for (const f of model.fields as any[]) {
58
+ if (f.isId || f.isUnique || f.kind === 'scalar' || f.kind === 'enum') {
59
+ if (f.kind === 'enum') {
60
+ const enumDef = enums.find((e) => e.name === f.type)
61
+ exampleValues[f.name] = enumDef?.values[0]?.name || 'VALUE'
62
+ } else {
63
+ exampleValues[f.name] = exampleValueForType(f.type)
64
+ }
65
+ }
66
+ }
67
+
68
+ const compoundId =
69
+ model.primaryKey && model.primaryKey.fields.length > 1
70
+ ? { fields: model.primaryKey.fields }
71
+ : null
72
+
73
+ const compoundUniques = ((model as any).uniqueIndexes || [])
74
+ .filter((idx: any) => idx.fields && idx.fields.length > 1)
75
+ .map((idx: any) => ({
76
+ name: idx.name || idx.fields.join('_'),
77
+ fields: idx.fields,
78
+ }))
79
+
80
+ return `import type { FieldMeta, EnumMeta } from '../docsRenderer${ext}'
81
+
82
+ export const MODEL_FIELDS: FieldMeta[] = ${JSON.stringify(fieldsMeta, null, 2)}
83
+
84
+ export const MODEL_ENUMS: EnumMeta[] = ${JSON.stringify(enumsMeta, null, 2)}
85
+
86
+ export const COMPOUND_ID: { fields: string[] } | null = ${JSON.stringify(compoundId)}
87
+
88
+ export const COMPOUND_UNIQUES: { name: string; fields: string[] }[] = ${JSON.stringify(compoundUniques)}
89
+
90
+ export const EXAMPLE_VALUES: Record<string, unknown> = ${JSON.stringify(exampleValues, null, 2)}
91
+ `
92
+ }
@@ -10,28 +10,21 @@ export interface ModelCoreOptions {
10
10
  findManyPaginatedMode: FindManyPaginatedMode
11
11
  }
12
12
 
13
- type WriteOpDecision =
14
- | { mode: 'normal'; method: string }
15
- | { mode: 'redirect'; method: string }
16
- | { mode: 'throw' }
13
+ type WriteOpDecision = { throw?: true; method: string }
17
14
 
18
15
  function decideWriteOp(
19
16
  name: string,
20
17
  defaultMethod: string,
21
18
  strategy: WriteStrategy,
22
19
  ): WriteOpDecision {
23
- if (strategy === 'regular') {
24
- return { mode: 'normal', method: defaultMethod }
20
+ if (strategy === 'throwOnNonReturning' && (name === 'createMany' || name === 'updateMany')) {
21
+ return { throw: true, method: defaultMethod }
25
22
  }
26
- if (strategy === 'throwOnNonReturning') {
27
- if (name === 'createMany' || name === 'updateMany') {
28
- return { mode: 'throw' }
29
- }
30
- return { mode: 'normal', method: defaultMethod }
23
+ if (strategy === 'forceReturn') {
24
+ if (name === 'createMany') return { method: 'createManyAndReturn' }
25
+ if (name === 'updateMany') return { method: 'updateManyAndReturn' }
31
26
  }
32
- if (name === 'createMany') return { mode: 'redirect', method: 'createManyAndReturn' }
33
- if (name === 'updateMany') return { mode: 'redirect', method: 'updateManyAndReturn' }
34
- return { mode: 'normal', method: defaultMethod }
27
+ return { method: defaultMethod }
35
28
  }
36
29
 
37
30
  function renderPaginatedBody(modelNameLower: string, mode: FindManyPaginatedMode): string {
@@ -112,7 +105,7 @@ export async function ${op}(ctx: OperationContext): Promise<unknown> {
112
105
  const writeHandlers = writeOps.map((op) => {
113
106
  const decision = decideWriteOp(op.name, op.method, writeStrategy)
114
107
 
115
- if (decision.mode === 'throw') {
108
+ if (decision.throw) {
116
109
  return `
117
110
  export async function ${op.name}(_ctx: OperationContext): Promise<unknown> {
118
111
  throw new HttpError(501, '${op.name} is disabled by writeStrategy="${writeStrategy}"')
@@ -141,16 +134,15 @@ ${validationLines}
141
134
  return `import {
142
135
  OperationContext,
143
136
  PrismaClientLike,
144
- HttpError,
145
137
  getExtendedClient,
146
138
  getDelegate,
147
139
  validateBody,
148
140
  requireBodyField,
149
- applyPaginationLimits,
150
- assertGuard,
151
- countForPagination,
152
- mapError,
153
141
  } from '../operationRuntime${ext}'
142
+ import { HttpError, mapError } from '../errorMapper${ext}'
143
+ import { applyPaginationLimits, countForPagination } from '../pagination${ext}'
144
+ import { assertGuard } from '../guardHelpers${ext}'
145
+ import { mapLimited } from '../concurrency${ext}'
154
146
 
155
147
  export async function findMany(ctx: OperationContext): Promise<unknown> {
156
148
  const rawQuery = ctx.parsedQuery || {}
@@ -248,20 +240,15 @@ export async function updateEach(
248
240
  const CONCURRENCY = 8
249
241
  const results: Array<{ status: 'ok'; data: unknown } | { status: 'error'; error: string }> =
250
242
  new Array(items.length)
251
- let cursor = 0
252
- const workerCount = Math.min(CONCURRENCY, items.length)
253
- const workers = Array.from({ length: workerCount }, async () => {
254
- for (;;) {
255
- const i = cursor++
256
- if (i >= items.length) return
257
- try {
258
- results[i] = { status: 'ok', data: await delegate.update(items[i]) }
259
- } catch (err) {
260
- results[i] = { status: 'error', error: mapError(err).message }
261
- }
243
+
244
+ await mapLimited(items, CONCURRENCY, async (item, i) => {
245
+ try {
246
+ results[i] = { status: 'ok', data: await delegate.update(item) }
247
+ } catch (err) {
248
+ results[i] = { status: 'error', error: mapError(err).message }
262
249
  }
263
250
  })
264
- await Promise.all(workers)
251
+
265
252
  return results
266
253
  }
267
254
  `
@@ -3,7 +3,7 @@ import { GeneratorOptions } from '@prisma/generator-helper'
3
3
  export function generateQueryBuilderHelper(options: GeneratorOptions): string {
4
4
  const schemaPath = options.schemaPath
5
5
  ? JSON.stringify(options.schemaPath)
6
- : "require('path').resolve(process.cwd(), 'prisma/schema.prisma')"
6
+ : "resolve(process.cwd(), 'prisma/schema.prisma')"
7
7
 
8
8
  return `import { spawn } from 'child_process'
9
9
  import { resolve, join, dirname } from 'path'
@@ -1,62 +1,14 @@
1
1
  import { ImportStyle } from '../utils/resolveImportStyle'
2
2
  import { importExt } from '../utils/importExt'
3
3
  import type { Target } from '../constants'
4
- import { capitalize } from '../utils/strings'
4
+ import { OPERATION_METADATA, READ_OPERATION_NAMES } from '../copy/operationDefinitions'
5
5
 
6
- const ROUTER_OPERATIONS = [
7
- 'findUnique',
8
- 'findUniqueOrThrow',
9
- 'findFirst',
10
- 'findFirstOrThrow',
11
- 'findMany',
12
- 'findManyPaginated',
13
- 'count',
14
- 'aggregate',
15
- 'groupBy',
16
- 'create',
17
- 'createMany',
18
- 'createManyAndReturn',
19
- 'update',
20
- 'updateMany',
21
- 'updateManyAndReturn',
22
- 'upsert',
23
- 'delete',
24
- 'deleteMany',
25
- ] as const
6
+ const ROUTER_OPERATIONS = OPERATION_METADATA
7
+ .filter((m) => m.name !== 'updateEach')
8
+ .map((m) => m.name)
26
9
 
27
- type RouterOperation = (typeof ROUTER_OPERATIONS)[number]
28
-
29
- const READ_OPERATIONS: ReadonlySet<RouterOperation> = new Set<RouterOperation>([
30
- 'findUnique',
31
- 'findUniqueOrThrow',
32
- 'findFirst',
33
- 'findFirstOrThrow',
34
- 'findMany',
35
- 'findManyPaginated',
36
- 'count',
37
- 'aggregate',
38
- 'groupBy',
39
- ])
40
-
41
- const ROUTER_OP_TO_SHAPE_OP: Record<RouterOperation, string> = {
42
- findUnique: 'findUnique',
43
- findUniqueOrThrow: 'findUniqueOrThrow',
44
- findFirst: 'findFirst',
45
- findFirstOrThrow: 'findFirstOrThrow',
46
- findMany: 'findMany',
47
- findManyPaginated: 'findManyPaginated',
48
- count: 'count',
49
- aggregate: 'aggregate',
50
- groupBy: 'groupBy',
51
- create: 'create',
52
- createMany: 'createMany',
53
- createManyAndReturn: 'createManyAndReturn',
54
- update: 'update',
55
- updateMany: 'updateMany',
56
- updateManyAndReturn: 'updateManyAndReturn',
57
- upsert: 'upsert',
58
- delete: 'delete',
59
- deleteMany: 'deleteMany',
10
+ function capitalize(str: string): string {
11
+ return str.charAt(0).toUpperCase() + str.slice(1)
60
12
  }
61
13
 
62
14
  function requestTypeFor(target: Target): string {
@@ -120,17 +72,14 @@ export function generateRouteConfigType(
120
72
  )
121
73
  }
122
74
 
123
- const shapeOps = Object.values(ROUTER_OP_TO_SHAPE_OP).filter(
124
- (v, i, a) => a.indexOf(v) === i,
125
- )
75
+ const shapeOps = Array.from(new Set(ROUTER_OPERATIONS))
126
76
  const opShapeImports = shapeOps
127
77
  .map((op) => `${m}${capitalize(op)}ShapeInput`)
128
78
  .join(',\n ')
129
79
 
130
80
  const overrides = ROUTER_OPERATIONS.map((routerOp) => {
131
- const shapeOp = ROUTER_OP_TO_SHAPE_OP[routerOp]
132
- const c = capitalize(shapeOp)
133
- const isRead = READ_OPERATIONS.has(routerOp)
81
+ const c = capitalize(routerOp)
82
+ const isRead = READ_OPERATION_NAMES.has(routerOp)
134
83
  const lines = [
135
84
  ` before?: ${beforeRef}[]`,
136
85
  ` after?: ${afterRef}[]`,
@@ -3,6 +3,60 @@ 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
+ interface OpEmitContext {
9
+ modelName: string
10
+ basePath: string
11
+ postReadsEnabled: string
12
+ }
13
+
14
+ function pathExpr(basePath: string, suffix: string): string {
15
+ if (!suffix) return basePath || '/'
16
+ if (!basePath) return `'${suffix}'`
17
+ return `\`\${basePath}${suffix}\``
18
+ }
19
+
20
+ function emitReadOp(
21
+ meta: (typeof OPERATION_METADATA)[number],
22
+ modelName: string,
23
+ ): string {
24
+ const c = meta.name.charAt(0).toUpperCase() + meta.name.slice(1)
25
+ const handlerName = `${modelName}${c}`
26
+ const pathValue = pathExpr('basePath', meta.pathSuffix)
27
+
28
+ const postReadBlock = meta.supportsPostRead
29
+ ? ` if (postReadsEnabled) {
30
+ const postPath = ${meta.name === 'findMany' ? "basePath ? `${basePath}/read` : '/read'" : `path`}
31
+ router.post(postPath, parseBodyAsQuery, setShape(opConfig), ...before, ${handlerName} as RequestHandler, ...after, respond)
32
+ }`
33
+ : ''
34
+
35
+ return ` if (isEnabled(config.${meta.configKey})) {
36
+ const opConfig: OperationConfigLike = (config.${meta.configKey} as OperationConfigLike | undefined) ?? defaultOpConfig
37
+ const { before = [], after = [] } = opConfig
38
+ const path = ${pathValue}
39
+ router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.${meta.coreName}, '${meta.name}'), ${handlerName} as RequestHandler, ...after, respond)
40
+ ${postReadBlock}
41
+ }`
42
+ }
43
+
44
+ function emitWriteOp(
45
+ meta: (typeof OPERATION_METADATA)[number],
46
+ modelName: string,
47
+ ): string {
48
+ const c = meta.name.charAt(0).toUpperCase() + meta.name.slice(1)
49
+ const handlerName = `${modelName}${c}`
50
+ const pathValue = pathExpr('basePath', meta.pathSuffix)
51
+ const respondFn = meta.successStatus === 201 ? 'respondCreated' : 'respond'
52
+
53
+ return ` if (isEnabled(config.${meta.configKey})) {
54
+ const opConfig: OperationConfigLike = (config.${meta.configKey} as OperationConfigLike | undefined) ?? defaultOpConfig
55
+ const { before = [], after = [] } = opConfig
56
+ const path = ${pathValue}
57
+ router.${meta.method}(path, setShape(opConfig), ...before, ${handlerName} as RequestHandler, ...after, ${respondFn})
58
+ }`
59
+ }
6
60
 
7
61
  export function generateRouterFunction({
8
62
  model,
@@ -27,57 +81,35 @@ export function generateRouterFunction({
27
81
  const delegateKey = modelName.charAt(0).toLowerCase() + modelName.slice(1)
28
82
  const routerFunctionName = `${modelName}Router`
29
83
 
30
- const fieldsMeta = model.fields.map((f) => ({
31
- name: f.name,
32
- kind: f.kind,
33
- type: f.type,
34
- isList: f.isList,
35
- isRequired: f.isRequired,
36
- hasDefaultValue: f.hasDefaultValue,
37
- isUpdatedAt: f.isUpdatedAt ?? false,
38
- documentation: f.documentation,
39
- relationFromFields: f.relationFromFields,
40
- }))
41
-
42
- const referencedEnumTypes = new Set(
43
- model.fields.filter((f) => f.kind === 'enum').map((f) => f.type),
84
+ const handlerImports = OPERATION_METADATA.filter(
85
+ (m) => m.name !== 'updateEach',
44
86
  )
87
+ .map(
88
+ (m) =>
89
+ ` ${modelName}${m.name.charAt(0).toUpperCase() + m.name.slice(1)},`,
90
+ )
91
+ .join('\n')
45
92
 
46
- const enumsMeta = enums
47
- .filter((e) => referencedEnumTypes.has(e.name))
48
- .map((e) => ({
49
- name: e.name,
50
- values: e.values.map((v) => ({ name: v.name })),
51
- }))
93
+ const readOps = OPERATION_METADATA.filter((m) => m.kind === 'read')
94
+ const writeOps = OPERATION_METADATA.filter(
95
+ (m) => m.kind === 'write' || m.kind === 'batch',
96
+ ).filter((m) => m.name !== 'updateEach')
97
+
98
+ const readOpBlocks = readOps.map((m) => emitReadOp(m, modelName)).join('\n\n')
99
+ const writeOpBlocks = writeOps
100
+ .map((m) => emitWriteOp(m, modelName))
101
+ .join('\n\n')
52
102
 
53
103
  return `import express from 'express'
54
104
  import type { Request, Response, NextFunction, RequestHandler } from 'express'
55
105
  import { startQueryBuilder } from '../queryBuilder${ext}'
56
106
  import {
57
- ${modelName}FindUnique,
58
- ${modelName}FindUniqueOrThrow,
59
- ${modelName}FindFirst,
60
- ${modelName}FindFirstOrThrow,
61
- ${modelName}FindMany,
62
- ${modelName}FindManyPaginated,
63
- ${modelName}Create,
64
- ${modelName}CreateMany,
65
- ${modelName}CreateManyAndReturn,
66
- ${modelName}Update,
67
- ${modelName}UpdateMany,
68
- ${modelName}UpdateManyAndReturn,
69
- ${modelName}Upsert,
70
- ${modelName}Delete,
71
- ${modelName}DeleteMany,
72
- ${modelName}Aggregate,
73
- ${modelName}Count,
74
- ${modelName}GroupBy,
107
+ ${handlerImports}
75
108
  } from './${modelName}Handlers${ext}'
76
109
  import * as core from './${modelName}Core${ext}'
77
110
  import type {
78
111
  RouteConfig,
79
112
  QueryBuilderConfig,
80
- WriteStrategy,
81
113
  FindManyPaginatedMode,
82
114
  PaginationConfig,
83
115
  } from '../routeConfig.target${ext}'
@@ -86,30 +118,26 @@ import { sanitizeKeys, normalizePrefix, getEnv } from '../misc${ext}'
86
118
  import { buildModelOpenApi } from '../buildModelOpenApi${ext}'
87
119
  import { validateCountSourceWhere } from '../routeConfig${ext}'
88
120
  import type { OperationContext } from '../operationRuntime${ext}'
121
+ import { transformResult } from '../operationRuntime${ext}'
122
+ import { HttpError, mapError } from '../errorMapper${ext}'
123
+ import { mergePaginationConfig } from '../pagination${ext}'
89
124
  import {
90
- transformResult,
91
125
  acceptsEventStream,
92
126
  runProgressiveEndpoint,
93
127
  runSingleResultSSE,
94
128
  emitTerminalSSEError,
95
129
  removeReqCloseListener,
96
- mergePaginationConfig,
97
- mapError,
98
- HttpError,
99
- } from '../operationRuntime${ext}'
130
+ } from '../sse${ext}'
100
131
  import { relationModels } from '../relationModels${ext}'
101
132
  import { runAutoIncludeProgressive } from '../autoIncludeRuntime${ext}'
133
+ import { MODEL_FIELDS, MODEL_ENUMS } from './${modelName}Metadata${ext}'
102
134
 
103
135
  ${generateRouteConfigType(modelName, 'RequestHandler', guardShapesImport, importStyle, 'express')}
104
136
  const _env = getEnv()
105
137
 
106
- const WRITE_STRATEGY: WriteStrategy = '${writeStrategy}'
107
138
  const FIND_MANY_PAGINATED_MODE: FindManyPaginatedMode = '${findManyPaginatedMode}'
108
139
  const DROP_GUARD = ${dropGuard} || _env.E2E === 'true'
109
140
 
110
- const MODEL_FIELDS = ${JSON.stringify(fieldsMeta, null, 2)} as const
111
- const MODEL_ENUMS = ${JSON.stringify(enumsMeta, null, 2)} as const
112
-
113
141
  type OperationConfigLike = {
114
142
  before?: RequestHandler[]
115
143
  after?: RequestHandler[]
@@ -183,7 +211,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
183
211
  MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
184
212
  MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
185
213
  config as unknown as Parameters<typeof buildModelOpenApi>[3],
186
- { format: 'json', writeStrategy: WRITE_STRATEGY },
214
+ { format: 'json', writeStrategy: '${writeStrategy}' },
187
215
  )
188
216
  }
189
217
  return _openApiJsonCache
@@ -196,7 +224,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
196
224
  MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
197
225
  MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
198
226
  config as unknown as Parameters<typeof buildModelOpenApi>[3],
199
- { format: 'yaml', writeStrategy: WRITE_STRATEGY },
227
+ { format: 'yaml', writeStrategy: '${writeStrategy}' },
200
228
  ) as string
201
229
  }
202
230
  return _openApiYamlCache
@@ -374,12 +402,14 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
374
402
  }
375
403
 
376
404
  const respond: RequestHandler = (_req, res) => {
405
+ if (res.headersSent || res.writableEnded) return
377
406
  const data = readLocals(res).data
378
407
  if (data === undefined) return res.status(500).json({ message: 'No data set by handler' })
379
408
  return res.json(transformResult(data))
380
409
  }
381
410
 
382
411
  const respondCreated: RequestHandler = (_req, res) => {
412
+ if (res.headersSent || res.writableEnded) return
383
413
  const data = readLocals(res).data
384
414
  if (data === undefined) return res.status(500).json({ message: 'No data set by handler' })
385
415
  return res.status(201).json(transformResult(data))
@@ -396,127 +426,10 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
396
426
  })
397
427
  }
398
428
 
399
- if (isEnabled(config.findFirst)) {
400
- const opConfig: OperationConfigLike = (config.findFirst as OperationConfigLike | undefined) ?? defaultOpConfig
401
- const { before = [], after = [] } = opConfig
402
- const path = basePath ? \`\${basePath}/first\` : '/first'
403
- router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findFirst, 'findFirst'), ${modelName}FindFirst as RequestHandler, ...after, respond)
404
- if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${modelName}FindFirst as RequestHandler, ...after, respond)
405
- }
406
- if (isEnabled(config.findFirstOrThrow)) {
407
- const opConfig: OperationConfigLike = (config.findFirstOrThrow as OperationConfigLike | undefined) ?? defaultOpConfig
408
- const { before = [], after = [] } = opConfig
409
- const path = basePath ? \`\${basePath}/first/strict\` : '/first/strict'
410
- router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findFirstOrThrow, 'findFirstOrThrow'), ${modelName}FindFirstOrThrow as RequestHandler, ...after, respond)
411
- if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${modelName}FindFirstOrThrow as RequestHandler, ...after, respond)
412
- }
413
- if (isEnabled(config.findManyPaginated)) {
414
- const opConfig: OperationConfigLike = (config.findManyPaginated as OperationConfigLike | undefined) ?? defaultOpConfig
415
- const { before = [], after = [] } = opConfig
416
- const path = basePath ? \`\${basePath}/paginated\` : '/paginated'
417
- router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findManyPaginated, 'findManyPaginated'), ${modelName}FindManyPaginated as RequestHandler, ...after, respond)
418
- if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${modelName}FindManyPaginated as RequestHandler, ...after, respond)
419
- }
420
- if (isEnabled(config.aggregate)) {
421
- const opConfig: OperationConfigLike = (config.aggregate as OperationConfigLike | undefined) ?? defaultOpConfig
422
- const { before = [], after = [] } = opConfig
423
- const path = basePath ? \`\${basePath}/aggregate\` : '/aggregate'
424
- router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.aggregate, 'aggregate'), ${modelName}Aggregate as RequestHandler, ...after, respond)
425
- if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${modelName}Aggregate as RequestHandler, ...after, respond)
426
- }
427
- if (isEnabled(config.count)) {
428
- const opConfig: OperationConfigLike = (config.count as OperationConfigLike | undefined) ?? defaultOpConfig
429
- const { before = [], after = [] } = opConfig
430
- const path = basePath ? \`\${basePath}/count\` : '/count'
431
- router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.count, 'count'), ${modelName}Count as RequestHandler, ...after, respond)
432
- if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${modelName}Count as RequestHandler, ...after, respond)
433
- }
434
- if (isEnabled(config.groupBy)) {
435
- const opConfig: OperationConfigLike = (config.groupBy as OperationConfigLike | undefined) ?? defaultOpConfig
436
- const { before = [], after = [] } = opConfig
437
- const path = basePath ? \`\${basePath}/groupby\` : '/groupby'
438
- router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.groupBy, 'groupBy'), ${modelName}GroupBy as RequestHandler, ...after, respond)
439
- if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${modelName}GroupBy as RequestHandler, ...after, respond)
440
- }
441
- if (isEnabled(config.findUniqueOrThrow)) {
442
- const opConfig: OperationConfigLike = (config.findUniqueOrThrow as OperationConfigLike | undefined) ?? defaultOpConfig
443
- const { before = [], after = [] } = opConfig
444
- const path = basePath ? \`\${basePath}/unique/strict\` : '/unique/strict'
445
- router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findUniqueOrThrow, 'findUniqueOrThrow'), ${modelName}FindUniqueOrThrow as RequestHandler, ...after, respond)
446
- if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${modelName}FindUniqueOrThrow as RequestHandler, ...after, respond)
447
- }
448
- if (isEnabled(config.findUnique)) {
449
- const opConfig: OperationConfigLike = (config.findUnique as OperationConfigLike | undefined) ?? defaultOpConfig
450
- const { before = [], after = [] } = opConfig
451
- const path = basePath ? \`\${basePath}/unique\` : '/unique'
452
- router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findUnique, 'findUnique'), ${modelName}FindUnique as RequestHandler, ...after, respond)
453
- if (postReadsEnabled) router.post(path, parseBodyAsQuery, setShape(opConfig), ...before, ${modelName}FindUnique as RequestHandler, ...after, respond)
454
- }
455
- if (isEnabled(config.findMany)) {
456
- const opConfig: OperationConfigLike = (config.findMany as OperationConfigLike | undefined) ?? defaultOpConfig
457
- const { before = [], after = [] } = opConfig
458
- const path = basePath || '/'
459
- router.get(path, parseQuery, setShape(opConfig), ...before, maybeProgressiveSSE(opConfig, core.findMany, 'findMany'), ${modelName}FindMany as RequestHandler, ...after, respond)
460
- if (postReadsEnabled) {
461
- const postPath = basePath ? \`\${basePath}/read\` : '/read'
462
- router.post(postPath, parseBodyAsQuery, setShape(opConfig), ...before, ${modelName}FindMany as RequestHandler, ...after, respond)
463
- }
464
- }
429
+ ${readOpBlocks}
430
+
431
+ ${writeOpBlocks}
465
432
 
466
- if (isEnabled(config.createManyAndReturn)) {
467
- const opConfig: OperationConfigLike = (config.createManyAndReturn as OperationConfigLike | undefined) ?? defaultOpConfig
468
- const { before = [], after = [] } = opConfig
469
- const path = basePath ? \`\${basePath}/many/return\` : '/many/return'
470
- router.post(path, setShape(opConfig), ...before, ${modelName}CreateManyAndReturn as RequestHandler, ...after, respondCreated)
471
- }
472
- if (isEnabled(config.createMany)) {
473
- const opConfig: OperationConfigLike = (config.createMany as OperationConfigLike | undefined) ?? defaultOpConfig
474
- const { before = [], after = [] } = opConfig
475
- const path = basePath ? \`\${basePath}/many\` : '/many'
476
- router.post(path, setShape(opConfig), ...before, ${modelName}CreateMany as RequestHandler, ...after, respondCreated)
477
- }
478
- if (isEnabled(config.create)) {
479
- const opConfig: OperationConfigLike = (config.create as OperationConfigLike | undefined) ?? defaultOpConfig
480
- const { before = [], after = [] } = opConfig
481
- const path = basePath || '/'
482
- router.post(path, setShape(opConfig), ...before, ${modelName}Create as RequestHandler, ...after, respondCreated)
483
- }
484
- if (isEnabled(config.updateManyAndReturn)) {
485
- const opConfig: OperationConfigLike = (config.updateManyAndReturn as OperationConfigLike | undefined) ?? defaultOpConfig
486
- const { before = [], after = [] } = opConfig
487
- const path = basePath ? \`\${basePath}/many/return\` : '/many/return'
488
- router.put(path, setShape(opConfig), ...before, ${modelName}UpdateManyAndReturn as RequestHandler, ...after, respond)
489
- }
490
- if (isEnabled(config.updateMany)) {
491
- const opConfig: OperationConfigLike = (config.updateMany as OperationConfigLike | undefined) ?? defaultOpConfig
492
- const { before = [], after = [] } = opConfig
493
- const path = basePath ? \`\${basePath}/many\` : '/many'
494
- router.put(path, setShape(opConfig), ...before, ${modelName}UpdateMany as RequestHandler, ...after, respond)
495
- }
496
- if (isEnabled(config.update)) {
497
- const opConfig: OperationConfigLike = (config.update as OperationConfigLike | undefined) ?? defaultOpConfig
498
- const { before = [], after = [] } = opConfig
499
- const path = basePath || '/'
500
- router.put(path, setShape(opConfig), ...before, ${modelName}Update as RequestHandler, ...after, respond)
501
- }
502
- if (isEnabled(config.upsert)) {
503
- const opConfig: OperationConfigLike = (config.upsert as OperationConfigLike | undefined) ?? defaultOpConfig
504
- const { before = [], after = [] } = opConfig
505
- const path = basePath || '/'
506
- router.patch(path, setShape(opConfig), ...before, ${modelName}Upsert as RequestHandler, ...after, respond)
507
- }
508
- if (isEnabled(config.deleteMany)) {
509
- const opConfig: OperationConfigLike = (config.deleteMany as OperationConfigLike | undefined) ?? defaultOpConfig
510
- const { before = [], after = [] } = opConfig
511
- const path = basePath ? \`\${basePath}/many\` : '/many'
512
- router.delete(path, setShape(opConfig), ...before, ${modelName}DeleteMany as RequestHandler, ...after, respond)
513
- }
514
- if (isEnabled(config.delete)) {
515
- const opConfig: OperationConfigLike = (config.delete as OperationConfigLike | undefined) ?? defaultOpConfig
516
- const { before = [], after = [] } = opConfig
517
- const path = basePath || '/'
518
- router.delete(path, setShape(opConfig), ...before, ${modelName}Delete as RequestHandler, ...after, respond)
519
- }
520
433
  if (config.updateEach) {
521
434
  const opConfig: OperationConfigLike = (config.updateEach as OperationConfigLike | undefined) ?? defaultOpConfig
522
435
  if ((!opConfig.before || opConfig.before.length === 0) && _env.NODE_ENV !== 'production') {
@@ -533,6 +446,9 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
533
446
  ...before,
534
447
  async (req: Request, res: Response, next: NextFunction) => {
535
448
  try {
449
+ if (!Array.isArray(req.body)) {
450
+ throw new HttpError(400, 'updateEach body must be an array of { where, data } items')
451
+ }
536
452
  const atomic = req.get('x-batch-atomic') === 'true'
537
453
  readLocals(res).data = await core.updateEach(buildContext(req, res), atomic)
538
454
  next()
@@ -546,15 +462,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
546
462
  }
547
463
 
548
464
  router.use((err: unknown, _req: Request, res: Response, next: NextFunction) => {
549
- let httpError: HttpError
550
- if (err instanceof HttpError) {
551
- httpError = err
552
- } else if (err && typeof err === 'object' && typeof (err as { status?: number }).status === 'number') {
553
- const e = err as { status: number; message?: string }
554
- httpError = new HttpError(e.status, e.message || 'Internal server error')
555
- } else {
556
- httpError = mapError(err)
557
- }
465
+ const httpError = mapError(err)
558
466
  if (!res.headersSent) return res.status(httpError.status).json({ message: httpError.message })
559
467
  next(err)
560
468
  })
@@ -562,4 +470,4 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
562
470
  return router
563
471
  }
564
472
  `
565
- }
473
+ }