prisma-generator-express 1.65.2 → 1.67.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 (106) hide show
  1. package/README.md +51 -3480
  2. package/dist/client/encodeQueryParams.js +3 -1
  3. package/dist/client/encodeQueryParams.js.map +1 -1
  4. package/dist/copy/misc.d.ts +1 -0
  5. package/dist/copy/misc.js +14 -0
  6. package/dist/copy/misc.js.map +1 -1
  7. package/dist/copy/operationDefinitions.js +69 -6
  8. package/dist/copy/operationDefinitions.js.map +1 -1
  9. package/dist/generators/generateFastifyHandler.d.ts +2 -2
  10. package/dist/generators/generateFastifyHandler.js +15 -5
  11. package/dist/generators/generateFastifyHandler.js.map +1 -1
  12. package/dist/generators/generateHonoHandler.d.ts +2 -2
  13. package/dist/generators/generateHonoHandler.js +12 -5
  14. package/dist/generators/generateHonoHandler.js.map +1 -1
  15. package/dist/generators/generateImportPrismaStatement.d.ts +1 -0
  16. package/dist/generators/generateImportPrismaStatement.js +6 -0
  17. package/dist/generators/generateImportPrismaStatement.js.map +1 -1
  18. package/dist/generators/generateModelMetadata.js +20 -10
  19. package/dist/generators/generateModelMetadata.js.map +1 -1
  20. package/dist/generators/generateOperationCore.d.ts +3 -3
  21. package/dist/generators/generateOperationCore.js +51 -16
  22. package/dist/generators/generateOperationCore.js.map +1 -1
  23. package/dist/generators/generateQueryBuilderHelper.d.ts +1 -1
  24. package/dist/generators/generateQueryBuilderHelper.js +2 -4
  25. package/dist/generators/generateQueryBuilderHelper.js.map +1 -1
  26. package/dist/generators/generateRelationMeta.js +2 -2
  27. package/dist/generators/generateRelationMeta.js.map +1 -1
  28. package/dist/generators/generateRouteConfigType.d.ts +2 -2
  29. package/dist/generators/generateRouteConfigType.js +29 -11
  30. package/dist/generators/generateRouteConfigType.js.map +1 -1
  31. package/dist/generators/generateRouter.d.ts +7 -4
  32. package/dist/generators/generateRouter.js +107 -14
  33. package/dist/generators/generateRouter.js.map +1 -1
  34. package/dist/generators/generateRouterFastify.d.ts +7 -4
  35. package/dist/generators/generateRouterFastify.js +56 -28
  36. package/dist/generators/generateRouterFastify.js.map +1 -1
  37. package/dist/generators/generateRouterHono.d.ts +7 -4
  38. package/dist/generators/generateRouterHono.js +84 -53
  39. package/dist/generators/generateRouterHono.js.map +1 -1
  40. package/dist/generators/generateUnifiedDocs.d.ts +1 -1
  41. package/dist/generators/generateUnifiedDocs.js +15 -8
  42. package/dist/generators/generateUnifiedDocs.js.map +1 -1
  43. package/dist/generators/generateUnifiedHandler.d.ts +2 -2
  44. package/dist/generators/generateUnifiedHandler.js +11 -3
  45. package/dist/generators/generateUnifiedHandler.js.map +1 -1
  46. package/dist/guardOptions.js +12 -7
  47. package/dist/guardOptions.js.map +1 -1
  48. package/dist/index.d.ts +1 -1
  49. package/dist/index.js +22 -1
  50. package/dist/index.js.map +1 -1
  51. package/dist/utils/copyFiles.js +22 -6
  52. package/dist/utils/copyFiles.js.map +1 -1
  53. package/dist/utils/importExt.d.ts +1 -1
  54. package/dist/utils/pathCasing.d.ts +4 -0
  55. package/dist/utils/pathCasing.js +40 -0
  56. package/dist/utils/pathCasing.js.map +1 -0
  57. package/dist/utils/resolveImportStyle.js +13 -5
  58. package/dist/utils/resolveImportStyle.js.map +1 -1
  59. package/dist/utils/writeFileSafely.js.map +1 -1
  60. package/package.json +16 -3
  61. package/src/bin.ts +1 -1
  62. package/src/client/encodeQueryParams.ts +39 -35
  63. package/src/constants.ts +1 -1
  64. package/src/copy/autoIncludePlanner.ts +93 -27
  65. package/src/copy/autoIncludePlannerGuarded.ts +171 -48
  66. package/src/copy/autoIncludeRuntimeGuarded.ts +25 -7
  67. package/src/copy/buildModelOpenApi.ts +153 -50
  68. package/src/copy/concurrency.ts +10 -8
  69. package/src/copy/errorMapper.ts +68 -19
  70. package/src/copy/guardHelpers.ts +30 -9
  71. package/src/copy/materializedCount.ts +17 -5
  72. package/src/copy/misc.ts +88 -68
  73. package/src/copy/ndjson.ts +72 -0
  74. package/src/copy/operationDefinitions.ts +99 -21
  75. package/src/copy/operationRuntime.ts +100 -12
  76. package/src/copy/pagination.ts +25 -11
  77. package/src/copy/parseQueryParams.ts +47 -48
  78. package/src/copy/projectionDefaults.ts +6 -4
  79. package/src/copy/routeConfig.express.ts +16 -4
  80. package/src/copy/routeConfig.fastify.ts +16 -2
  81. package/src/copy/routeConfig.hono.ts +19 -9
  82. package/src/copy/routeConfig.ts +174 -39
  83. package/src/copy/scalarTypes.ts +7 -2
  84. package/src/copy/sse.ts +72 -19
  85. package/src/generators/generateFastifyHandler.ts +24 -13
  86. package/src/generators/generateHonoHandler.ts +21 -13
  87. package/src/generators/generateImportPrismaStatement.ts +93 -81
  88. package/src/generators/generateModelMetadata.ts +24 -12
  89. package/src/generators/generateOperationCore.ts +81 -33
  90. package/src/generators/generateQueryBuilderHelper.ts +20 -4
  91. package/src/generators/generateRelationMeta.ts +23 -11
  92. package/src/generators/generateRouteConfigType.ts +39 -13
  93. package/src/generators/generateRouter.ts +114 -17
  94. package/src/generators/generateRouterFastify.ts +67 -31
  95. package/src/generators/generateRouterHono.ts +108 -60
  96. package/src/generators/generateUnifiedDocs.ts +29 -8
  97. package/src/generators/generateUnifiedHandler.ts +16 -8
  98. package/src/generators/generateUnifiedScalarUI.ts +1 -1
  99. package/src/guardOptions.ts +105 -92
  100. package/src/index.ts +46 -4
  101. package/src/utils/copyFiles.ts +199 -168
  102. package/src/utils/importExt.ts +2 -2
  103. package/src/utils/pathCasing.ts +42 -0
  104. package/src/utils/resolveImportStyle.ts +37 -15
  105. package/src/utils/strings.ts +2 -2
  106. package/src/utils/writeFileSafely.ts +6 -2
@@ -32,13 +32,15 @@ function findOppositeField(
32
32
  const target = models.find((m) => m.name === targetModelName)
33
33
  if (!target) return null
34
34
  const isSelfRelation = targetModelName === selfModelName
35
- return target.fields.find(
36
- (f) =>
37
- f.kind === 'object' &&
38
- f.relationName === relationName &&
39
- f.type === selfModelName &&
40
- !(isSelfRelation && f.name === selfFieldName),
41
- ) || null
35
+ return (
36
+ 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
+ )
42
44
  }
43
45
 
44
46
  function computeRelation(
@@ -61,7 +63,13 @@ function computeRelation(
61
63
  }
62
64
  }
63
65
 
64
- const opposite = findOppositeField(models, field.type, field.relationName, selfModelName, field.name)
66
+ const opposite = findOppositeField(
67
+ models,
68
+ field.type,
69
+ field.relationName,
70
+ selfModelName,
71
+ field.name,
72
+ )
65
73
  if (opposite) {
66
74
  const oppFrom = (opposite.relationFromFields ?? []) as string[]
67
75
  const oppTo = (opposite.relationToFields ?? []) as string[]
@@ -118,7 +126,9 @@ export interface GenerateRelationMetaOptions {
118
126
  importStyle: ImportStyle
119
127
  }
120
128
 
121
- export function generateRelationMeta(options: GenerateRelationMetaOptions): string {
129
+ export function generateRelationMeta(
130
+ options: GenerateRelationMetaOptions,
131
+ ): string {
122
132
  const ext = importExt(options.importStyle)
123
133
  const meta = buildModelMeta(options.model, options.allModels)
124
134
  return `import type { ModelRelationMap } from '../autoIncludePlanner${ext}'
@@ -132,7 +142,9 @@ export interface GenerateRelationModelsIndexOptions {
132
142
  importStyle: ImportStyle
133
143
  }
134
144
 
135
- export function generateRelationModelsIndex(options: GenerateRelationModelsIndexOptions): string {
145
+ export function generateRelationModelsIndex(
146
+ options: GenerateRelationModelsIndexOptions,
147
+ ): string {
136
148
  const ext = importExt(options.importStyle)
137
149
  const imports = options.modelNames
138
150
  .map((n) => `import { ${n}Relations } from './${n}/${n}Relations${ext}'`)
@@ -147,4 +159,4 @@ export const relationModels: Record<string, ModelRelationMap> = {
147
159
  ${entries}
148
160
  }
149
161
  `
150
- }
162
+ }
@@ -1,11 +1,14 @@
1
- import { ImportStyle } from '../utils/resolveImportStyle'
1
+ import type { ImportStyle } from '../utils/resolveImportStyle'
2
2
  import { importExt } from '../utils/importExt'
3
3
  import type { Target } from '../constants'
4
- import { OPERATION_METADATA, READ_OPERATION_NAMES } from '../copy/operationDefinitions'
4
+ import {
5
+ OPERATION_METADATA,
6
+ READ_OPERATION_NAMES,
7
+ } from '../copy/operationDefinitions'
5
8
 
6
- const ROUTER_OPERATIONS = OPERATION_METADATA
7
- .filter((m) => m.name !== 'updateEach')
8
- .map((m) => m.name)
9
+ const ROUTER_OPERATIONS = OPERATION_METADATA.filter(
10
+ (m) => m.name !== 'updateEach',
11
+ ).map((m) => m.name)
9
12
 
10
13
  function capitalize(str: string): string {
11
14
  return str.charAt(0).toUpperCase() + str.slice(1)
@@ -13,15 +16,15 @@ function capitalize(str: string): string {
13
16
 
14
17
  function requestTypeFor(target: Target): string {
15
18
  if (target === 'fastify') return `import('fastify').FastifyRequest`
16
- if (target === 'hono') return `import('hono').Context<TEnv>`
19
+ if (target === 'hono') return `import('hono').Context<GeneratedHonoEnv<TEnv>>`
17
20
  return `import('express').Request`
18
21
  }
19
22
 
20
23
  function configGenericsFor(target: Target): string {
21
24
  if (target === 'hono') {
22
- return `<TCtx = unknown, TPrisma = any, TEnv extends { Variables: Record<string, unknown> } = { Variables: Record<string, unknown> }>`
25
+ return `<TCtx = unknown, TPrisma extends PrismaClientLike = PrismaClientLike, TEnv extends { Variables: Record<string, unknown> } = { Variables: Record<string, unknown> }>`
23
26
  }
24
- return `<TCtx = unknown, TPrisma = any>`
27
+ return `<TCtx = unknown, TPrisma extends PrismaClientLike = PrismaClientLike>`
25
28
  }
26
29
 
27
30
  function routeConfigBaseFor(target: Target): string {
@@ -50,6 +53,7 @@ export function generateRouteConfigType(
50
53
  guardShapesImport: string | null,
51
54
  importStyle: ImportStyle,
52
55
  target: Target,
56
+ clientImport?: string,
53
57
  ): string {
54
58
  const ext = importExt(importStyle)
55
59
  const m = modelName
@@ -61,14 +65,33 @@ export function generateRouteConfigType(
61
65
  const afterRef = afterHookRef(target, hookHandlerType)
62
66
  const requestType = requestTypeFor(target)
63
67
 
64
- const progressiveTypeImport = supportsProgressive
68
+ const clientPath = clientImport ? `${clientImport}${ext}` : '@prisma/client'
69
+ const delegate = `${m.charAt(0).toLowerCase() + m.slice(1)}`
70
+ const modelDelegate = clientImport
71
+ ? `PrismaClient['${delegate}']`
72
+ : `(TPrisma extends Record<'${delegate}', infer D> ? D : never)`
73
+ const overrideImports = `import type { Prisma${clientImport ? ', PrismaClient' : ''} } from '${clientPath}'\nimport type { OperationOverride } from '../operationRuntime${ext}'\n`
74
+ const overrideType = (op: string) => {
75
+ const method = op === 'findManyPaginated' ? 'findMany' : op
76
+ const args = `Prisma.Args<${modelDelegate}, '${method}'>`
77
+ const result = `Prisma.Result<${modelDelegate}, ${args}, '${method}'>`
78
+ const output =
79
+ op === 'findManyPaginated'
80
+ ? `{ data: ${result}; total: number; hasMore: boolean }`
81
+ : result
82
+ const methods =
83
+ op === 'findManyPaginated' ? "'findMany' | 'count'" : `'${method}'`
84
+ return `OperationOverride<${args}, ${output}, TCtx, Readonly<{ ${delegate}: Pick<${modelDelegate}, Extract<keyof ${modelDelegate}, ${methods}>> }>>`
85
+ }
86
+ const typeImports = supportsProgressive
65
87
  ? `import type { ProgressiveVariantConfig, ProgressiveStage } from '../routeConfig.target${ext}'\n`
66
88
  : ''
67
89
 
68
90
  if (!guardShapesImport) {
69
91
  return (
70
- progressiveTypeImport +
71
- `export type ${m}RouteConfig${generics} = ${baseConfig}\n`
92
+ overrideImports +
93
+ typeImports +
94
+ `export type ${m}RouteConfig${generics} = Omit<${baseConfig}, ${ROUTER_OPERATIONS.map((op) => `'${op}'`).join(' | ')}> & {\n${ROUTER_OPERATIONS.map((op) => ` ${op}?: (Omit<Exclude<${baseConfig}['${op}'], false | undefined>, 'override'> & { override?: ${overrideType(op)} }) | false`).join('\n')}\n}\n`
72
95
  )
73
96
  }
74
97
 
@@ -95,6 +118,8 @@ export function generateRouteConfigType(
95
118
  const c = capitalize(routerOp)
96
119
  const isRead = READ_OPERATION_NAMES.has(routerOp)
97
120
  const commonLines = [
121
+ ` override?: ${overrideType(routerOp)}`,
122
+ ` authorize?: ${beforeRef}`,
98
123
  ` before?: ${beforeRef}[]`,
99
124
  ` after?: ${afterRef}[]`,
100
125
  ` pagination?: Partial<PaginationConfig>`,
@@ -133,7 +158,8 @@ export function generateRouteConfigType(
133
158
  const omitKeys = ROUTER_OPERATIONS.map((k) => `'${k}'`).join('\n | ')
134
159
 
135
160
  return (
136
- progressiveTypeImport +
161
+ overrideImports +
162
+ typeImports +
137
163
  `import type {\n ${opShapeImports}\n} from '${guardShapesImport}${ext}'\n\n` +
138
164
  `${shapeOrFnAliases}\n\n` +
139
165
  `export type ${m}RouteConfig${generics} = Omit<\n` +
@@ -144,4 +170,4 @@ export function generateRouteConfigType(
144
170
  ` resolveContext?: (request: ${requestType}) => TCtx | Promise<TCtx>\n` +
145
171
  `${overrides}\n}\n`
146
172
  )
147
- }
173
+ }
@@ -1,8 +1,9 @@
1
- import { DMMF } from '@prisma/generator-helper'
1
+ import type { DMMF } from '@prisma/generator-helper'
2
2
  import { generateRouteConfigType } from './generateRouteConfigType'
3
- import { ImportStyle } from '../utils/resolveImportStyle'
3
+ import type { ImportStyle } from '../utils/resolveImportStyle'
4
4
  import { importExt } from '../utils/importExt'
5
- import { WriteStrategy, FindManyPaginatedMode } from '../constants'
5
+ import type { WriteStrategy, FindManyPaginatedMode } from '../constants'
6
+ import { modelPathSegment, type PathCase } from '../utils/pathCasing'
6
7
  import { OPERATION_METADATA } from '../copy/operationDefinitions'
7
8
 
8
9
  function pathExpr(basePath: string, suffix: string): string {
@@ -55,10 +56,11 @@ function emitReadOp(
55
56
  const opKind = opKindFor(meta.name)
56
57
 
57
58
  const postReadBlock = meta.supportsPostRead
58
- ? ` if (postReadsEnabled) {
59
+ ? ` if (resolvePostReadsEnabled(config.disablePostReads, opConfig.disablePostReads)) {
59
60
  const postPath = ${meta.name === 'findMany' ? "basePath ? `${basePath}/read` : '/read'" : `path`}
60
61
  router.post(
61
62
  postPath,
63
+ ...(opConfig.authorize ? [opConfig.authorize] : []),
62
64
  parseBodyAsQuery,
63
65
  setShape(opConfig, '${opKind}'),
64
66
  ...opConfig.operationBefore,
@@ -77,12 +79,14 @@ function emitReadOp(
77
79
  const path = ${pathValue}
78
80
  router.get(
79
81
  path,
82
+ ...(opConfig.authorize ? [opConfig.authorize] : []),
80
83
  parseQuery,
81
84
  setShape(opConfig, '${opKind}'),
82
85
  ...opConfig.operationBefore,
83
86
  requireVariantKey(),
84
87
  variantBeforeDispatcher(opConfig),
85
88
  maybeProgressiveSSE(opConfig, core.${meta.coreName}, '${meta.name}'),
89
+ ${meta.name === 'findMany' ? 'maybeNdjsonFindMany(core.findMany),' : ''}
86
90
  ${handlerName} as RequestHandler,
87
91
  variantAfterDispatcher(opConfig),
88
92
  ...opConfig.operationAfter,
@@ -107,6 +111,7 @@ function emitWriteOp(
107
111
  const path = ${pathValue}
108
112
  router.${meta.method}(
109
113
  path,
114
+ ...(opConfig.authorize ? [opConfig.authorize] : []),
110
115
  setShape(opConfig, '${opKind}'),
111
116
  ...opConfig.operationBefore,
112
117
  requireVariantKey(),
@@ -123,22 +128,26 @@ export function generateRouterFunction({
123
128
  model,
124
129
  enums,
125
130
  guardShapesImport,
131
+ clientImport,
126
132
  importStyle,
127
133
  writeStrategy,
128
134
  findManyPaginatedMode,
129
135
  dropGuard,
136
+ pathCase,
130
137
  }: {
131
138
  model: DMMF.Model
132
139
  enums: DMMF.DatamodelEnum[]
133
140
  guardShapesImport: string | null
141
+ clientImport?: string
134
142
  importStyle: ImportStyle
135
143
  writeStrategy: WriteStrategy
136
144
  findManyPaginatedMode: FindManyPaginatedMode
137
145
  dropGuard: boolean
146
+ pathCase: PathCase
138
147
  }): string {
139
148
  const ext = importExt(importStyle)
140
149
  const modelName = model.name
141
- const modelNameLower = modelName.toLowerCase()
150
+ const modelSegment = modelPathSegment(modelName, pathCase)
142
151
  const delegateKey = modelName.charAt(0).toLowerCase() + modelName.slice(1)
143
152
  const routerFunctionName = `${modelName}Router`
144
153
 
@@ -173,9 +182,10 @@ import type {
173
182
  QueryBuilderConfig,
174
183
  FindManyPaginatedMode,
175
184
  PaginationConfig,
185
+ PrismaClientLike,
176
186
  } from '../routeConfig.target${ext}'
177
187
  import { parseQueryParams } from '../parseQueryParams${ext}'
178
- import { sanitizeKeys, normalizePrefix, getEnv, isPlainObject } from '../misc${ext}'
188
+ import { sanitizeKeys, normalizePrefix, getEnv, isPlainObject, resolveDropGuardEnv } from '../misc${ext}'
179
189
  import { buildModelOpenApi } from '../buildModelOpenApi${ext}'
180
190
  import {
181
191
  normalizeOperation,
@@ -183,7 +193,10 @@ import {
183
193
  validateCountSourceWhere,
184
194
  validateOperationConfig,
185
195
  validateUpdateEachConfig,
196
+ resolvePostReadsEnabled,
197
+ warnIfUnguardedRoutes,
186
198
  } from '../routeConfig${ext}'
199
+ import type { RuntimeOperationOverride } from '../operationRuntime${ext}'
187
200
  import type { NormalizedOperationConfig } from '../routeConfig${ext}'
188
201
  import type { OperationContext } from '../operationRuntime${ext}'
189
202
  import { transformResult } from '../operationRuntime${ext}'
@@ -191,6 +204,7 @@ import { HttpError, mapError } from '../errorMapper${ext}'
191
204
  import { formatGuardVariantResolutionError } from '../guardVariantError${ext}'
192
205
  import type { GuardVariantResolution } from '../guardVariantRouting${ext}'
193
206
  import { mergePaginationConfig } from '../pagination${ext}'
207
+ import { acceptsNdjson, runNdjsonFindMany } from '../ndjson${ext}'
194
208
  import {
195
209
  acceptsEventStream,
196
210
  runProgressiveEndpoint,
@@ -204,13 +218,18 @@ import { applyDroppedGuard } from '../projectionDefaults${ext}'
204
218
  import type { OpKind } from '../projectionDefaults${ext}'
205
219
  import { MODEL_FIELDS, MODEL_ENUMS } from './${modelName}Metadata${ext}'
206
220
 
207
- ${generateRouteConfigType(modelName, 'RequestHandler', guardShapesImport, importStyle, 'express')}
221
+ ${generateRouteConfigType(modelName, 'RequestHandler', guardShapesImport, importStyle, 'express', clientImport)}
208
222
  const _env = getEnv()
209
223
 
210
224
  const FIND_MANY_PAGINATED_MODE: FindManyPaginatedMode = '${findManyPaginatedMode}'
211
- const DROP_GUARD = ${dropGuard} || _env.E2E === 'true'
225
+ // Fixed at generation time. The environment bypass is resolved per router,
226
+ // because it requires the configuration's explicit \`allowE2EGuardBypass: true\`:
227
+ // false or omitted means the environment cannot drop guards for this target.
228
+ const DROP_GUARD = ${dropGuard}
212
229
 
213
230
  type OperationConfigLike = {
231
+ authorize?: RequestHandler
232
+ override?: RuntimeOperationOverride
214
233
  before?: RequestHandler[]
215
234
  after?: RequestHandler[]
216
235
  shape?: unknown
@@ -233,7 +252,7 @@ type NormalizedOp = NormalizedOperationConfig<RequestHandler, RequestHandler> &
233
252
  }
234
253
 
235
254
  type ExtendedRequest = Request & {
236
- prisma?: unknown
255
+ prisma?: PrismaClientLike
237
256
  postgres?: unknown
238
257
  sqlite?: unknown
239
258
  }
@@ -243,6 +262,8 @@ type LocalsBag = {
243
262
  routeConfig?: { pagination?: PaginationConfig }
244
263
  guardShape?: Record<string, unknown>
245
264
  guardCaller?: string
265
+ operationOverride?: RuntimeOperationOverride
266
+ resolveOperationContext?: () => unknown | Promise<unknown>
246
267
  guardVariantKey?: string
247
268
  guardVariantFailure?: Extract<GuardVariantResolution, { ok: false }>
248
269
  data?: unknown
@@ -360,7 +381,7 @@ function requireVariantKey(): RequestHandler {
360
381
  }
361
382
  }
362
383
 
363
- export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${modelName}RouteConfig<TCtx, TPrisma> = {}) {
384
+ export function ${routerFunctionName}<TCtx = unknown, TPrisma extends PrismaClientLike = PrismaClientLike>(config: ${modelName}RouteConfig<TCtx, TPrisma> = {}) {
364
385
  validateCountSourceWhere(config.pagination?.countSource, '${modelName} pagination')
365
386
  validateCountSourceWhere(
366
387
  (config.findManyPaginated && typeof config.findManyPaginated === 'object' ? config.findManyPaginated : undefined)?.pagination?.countSource,
@@ -370,6 +391,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
370
391
  const router = express.Router()
371
392
 
372
393
  const isEnabled = (value: unknown): boolean => value !== false && !!(config.enableAll || value)
394
+ warnIfUnguardedRoutes('${modelName}', ['findMany', 'findUnique', 'findUniqueOrThrow', 'findFirst', 'findFirstOrThrow', 'findManyPaginated', 'count', 'aggregate', 'groupBy', 'create', 'createMany', 'createManyAndReturn', 'update', 'updateMany', 'updateManyAndReturn', 'upsert', 'delete', 'deleteMany'], config, isEnabled)
373
395
 
374
396
  const opFor = (key: string): NormalizedOp => {
375
397
  const raw = (config as unknown as Record<string, unknown>)[key] as OperationConfigLike | undefined
@@ -378,13 +400,12 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
378
400
  }
379
401
 
380
402
  const customPrefix = normalizePrefix(config.customUrlPrefix || '')
381
- const modelPrefix = config.addModelPrefix !== false ? '/${modelNameLower}' : ''
403
+ const modelPrefix = config.addModelPrefix !== false ? '/${modelSegment}' : ''
382
404
  const basePath = customPrefix + modelPrefix
383
405
 
384
406
  const openApiDisabled = config.disableOpenApi === true
385
407
  || (config.disableOpenApi !== false && (_env.DISABLE_OPENAPI === 'true' || _env.NODE_ENV === 'production'))
386
408
 
387
- const postReadsEnabled = !config.disablePostReads
388
409
 
389
410
  let _openApiJsonCache: unknown = undefined
390
411
  const getOpenApiJson = (): unknown => {
@@ -394,7 +415,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
394
415
  MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
395
416
  MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
396
417
  config as unknown as Parameters<typeof buildModelOpenApi>[3],
397
- { format: 'json', writeStrategy: '${writeStrategy}' },
418
+ { format: 'json', writeStrategy: '${writeStrategy}', pathSegment: '${modelSegment}' },
398
419
  )
399
420
  }
400
421
  return _openApiJsonCache
@@ -407,7 +428,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
407
428
  MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
408
429
  MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
409
430
  config as unknown as Parameters<typeof buildModelOpenApi>[3],
410
- { format: 'yaml', writeStrategy: '${writeStrategy}' },
431
+ { format: 'yaml', writeStrategy: '${writeStrategy}', pathSegment: '${modelSegment}' },
411
432
  ) as string
412
433
  }
413
434
  return _openApiYamlCache
@@ -429,6 +450,8 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
429
450
  const extReq = req as ExtendedRequest
430
451
  const locals = readLocals(res)
431
452
  return {
453
+ operationOverride: locals.operationOverride,
454
+ resolveOperationContext: locals.resolveOperationContext,
432
455
  prisma: extReq.prisma,
433
456
  postgres: extReq.postgres,
434
457
  sqlite: extReq.sqlite,
@@ -436,6 +459,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
436
459
  body: req.body,
437
460
  guardShape: locals.guardShape,
438
461
  guardCaller: locals.guardCaller,
462
+ guardVariantKey: locals.guardVariantKey,
439
463
  paginationConfig: locals.routeConfig?.pagination,
440
464
  findManyPaginatedMode: FIND_MANY_PAGINATED_MODE,
441
465
  }
@@ -463,10 +487,25 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
463
487
  next()
464
488
  }
465
489
 
490
+ /**
491
+ * The environment bypass, and it is OPT-IN for this target.
492
+ *
493
+ * \`PGE_DROP_GUARD=true\` (deprecated alias \`E2E=true\`) downgrading enforcement
494
+ * in a deployed environment is a real hazard: on most platforms it is an
495
+ * ordinary config var, set on staging, copied forward, flagged as
496
+ * security-relevant nowhere. It is honoured only when this configuration
497
+ * states \`allowE2EGuardBypass: true\` — false or omitted means the environment
498
+ * cannot drop guards. The generation-time \`DROP_GUARD\` literal is unchanged.
499
+ */
500
+ const dropGuard = DROP_GUARD || (config.allowE2EGuardBypass === true && resolveDropGuardEnv(_env))
501
+
466
502
  const setShape = (opConfig: NormalizedOp, opKind: OpKind): RequestHandler => {
467
503
  return async (req, res, next) => {
468
504
  try {
469
505
  const locals = readLocals(res)
506
+ locals.operationOverride = opConfig.override
507
+ let context: Promise<unknown> | undefined
508
+ locals.resolveOperationContext = () => context ??= Promise.resolve(config.resolveContext?.(req))
470
509
  const merged = mergePaginationConfig(config.pagination, opConfig.pagination)
471
510
  if (merged) {
472
511
  locals.routeConfig = { pagination: merged }
@@ -491,7 +530,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
491
530
  if (resolvedKey !== undefined) locals.guardVariantKey = resolvedKey
492
531
 
493
532
  if (opConfig.guardShape) {
494
- if (!DROP_GUARD) {
533
+ if (!dropGuard) {
495
534
  locals.guardShape = opConfig.guardShape
496
535
  } else {
497
536
  await applyDroppedGuard(
@@ -526,6 +565,41 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
526
565
  }
527
566
  }
528
567
 
568
+ const maybeNdjsonFindMany = (
569
+ coreFn: (ctx: OperationContext) => Promise<unknown>,
570
+ ): RequestHandler => {
571
+ return async (req, res, next) => {
572
+ if (res.headersSent || res.writableEnded) return next()
573
+ if (req.method !== 'GET') return next()
574
+ if (!acceptsNdjson(req.headers.accept)) return next()
575
+
576
+ const locals = readLocals(res)
577
+ const rawArgs = (locals.parsedQuery ?? {}) as Record<string, unknown>
578
+ const args = { ...rawArgs }
579
+ const paginationCfg = mergePaginationConfig(config.pagination, undefined)
580
+ const requestedTake = typeof rawArgs.take === 'number' ? rawArgs.take : undefined
581
+ const initialSkip = typeof rawArgs.skip === 'number' ? rawArgs.skip : 0
582
+
583
+ try {
584
+ await runNdjsonFindMany({
585
+ res,
586
+ isClosed: () => req.destroyed,
587
+ fetchPage: (skip, pageTake) =>
588
+ coreFn({
589
+ ...buildContext(req, res),
590
+ parsedQuery: { ...args, skip, take: pageTake },
591
+ }),
592
+ initialSkip,
593
+ requestedTake,
594
+ defaultLimit: paginationCfg?.defaultLimit,
595
+ maxLimit: paginationCfg?.maxLimit ?? 1000,
596
+ })
597
+ } catch (err) {
598
+ next(err)
599
+ }
600
+ }
601
+ }
602
+
529
603
  const maybeProgressiveSSE = (
530
604
  opConfig: NormalizedOp,
531
605
  coreFn: (ctx: OperationContext) => Promise<unknown>,
@@ -552,6 +626,23 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
552
626
  }
553
627
 
554
628
  if (progressiveConfig.mode === 'autoInclude') {
629
+ if (progressiveConfig.experimental !== true) {
630
+ if (_env.NODE_ENV !== 'production') {
631
+ console.warn(
632
+ '[prisma-generator-express] auto-include progressive is experimental; set \`experimental: true\` on the progressive config to enable it. Falling back.'
633
+ )
634
+ }
635
+ if (progressiveConfig.fallback === 'error') {
636
+ emitTerminalSSEError(res, 'auto-include requires experimental: true')
637
+ return
638
+ }
639
+ await runSingleResultSSE({
640
+ req,
641
+ res,
642
+ coreQueryFn: () => coreFn(buildContext(req, res)),
643
+ })
644
+ return
645
+ }
555
646
  const isAutoIncludeReadable =
556
647
  baseOp === 'findUnique' || baseOp === 'findUniqueOrThrow' ||
557
648
  baseOp === 'findFirst' || baseOp === 'findFirstOrThrow' ||
@@ -619,11 +710,16 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
619
710
  }
620
711
 
621
712
  const ctx = await config.resolveContext(req)
713
+ const prismaClient = (req as ExtendedRequest).prisma
714
+ if (!prismaClient) {
715
+ emitTerminalSSEError(res, 'Progressive endpoint requires req.prisma to be set')
716
+ return
717
+ }
622
718
  await runProgressiveEndpoint({
623
719
  req,
624
720
  res,
625
721
  ctx,
626
- prisma: (req as ExtendedRequest).prisma,
722
+ prisma: prismaClient,
627
723
  variant: variant as string,
628
724
  stages: progressiveConfig.stages,
629
725
  stageRegistry,
@@ -679,6 +775,7 @@ ${writeOpBlocks}
679
775
  const path = basePath ? \`\${basePath}/each\` : '/each'
680
776
  router.post(
681
777
  path,
778
+ ...(opConfig.authorize ? [opConfig.authorize] : []),
682
779
  setShape(opConfig, 'noop'),
683
780
  ...opConfig.operationBefore,
684
781
  async (req: Request, res: Response, next: NextFunction) => {
@@ -707,4 +804,4 @@ ${writeOpBlocks}
707
804
  return router
708
805
  }
709
806
  `
710
- }
807
+ }