prisma-generator-express 1.66.0 → 1.68.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 (130) hide show
  1. package/README.md +188 -3432
  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.d.ts +4 -1
  8. package/dist/copy/operationDefinitions.js +133 -9
  9. package/dist/copy/operationDefinitions.js.map +1 -1
  10. package/dist/generators/generateFastifyHandler.d.ts +2 -2
  11. package/dist/generators/generateFastifyHandler.js +68 -12
  12. package/dist/generators/generateFastifyHandler.js.map +1 -1
  13. package/dist/generators/generateHonoHandler.d.ts +2 -2
  14. package/dist/generators/generateHonoHandler.js +53 -6
  15. package/dist/generators/generateHonoHandler.js.map +1 -1
  16. package/dist/generators/generateHonoOpenApi.d.ts +13 -0
  17. package/dist/generators/generateHonoOpenApi.js +70 -0
  18. package/dist/generators/generateHonoOpenApi.js.map +1 -0
  19. package/dist/generators/generateImportPrismaStatement.d.ts +1 -0
  20. package/dist/generators/generateImportPrismaStatement.js +6 -0
  21. package/dist/generators/generateImportPrismaStatement.js.map +1 -1
  22. package/dist/generators/generateMcpApp.d.ts +10 -0
  23. package/dist/generators/generateMcpApp.js +113 -0
  24. package/dist/generators/generateMcpApp.js.map +1 -0
  25. package/dist/generators/generateModelMcp.d.ts +9 -0
  26. package/dist/generators/generateModelMcp.js +145 -0
  27. package/dist/generators/generateModelMcp.js.map +1 -0
  28. package/dist/generators/generateModelMetadata.js +29 -15
  29. package/dist/generators/generateModelMetadata.js.map +1 -1
  30. package/dist/generators/generateOperationCore.d.ts +3 -3
  31. package/dist/generators/generateOperationCore.js +51 -16
  32. package/dist/generators/generateOperationCore.js.map +1 -1
  33. package/dist/generators/generateRelationMeta.js +2 -2
  34. package/dist/generators/generateRelationMeta.js.map +1 -1
  35. package/dist/generators/generateRouteConfigType.d.ts +2 -2
  36. package/dist/generators/generateRouteConfigType.js +29 -11
  37. package/dist/generators/generateRouteConfigType.js.map +1 -1
  38. package/dist/generators/generateRouter.d.ts +7 -4
  39. package/dist/generators/generateRouter.js +164 -65
  40. package/dist/generators/generateRouter.js.map +1 -1
  41. package/dist/generators/generateRouterFastify.d.ts +7 -4
  42. package/dist/generators/generateRouterFastify.js +106 -86
  43. package/dist/generators/generateRouterFastify.js.map +1 -1
  44. package/dist/generators/generateRouterHono.d.ts +7 -4
  45. package/dist/generators/generateRouterHono.js +59 -414
  46. package/dist/generators/generateRouterHono.js.map +1 -1
  47. package/dist/generators/generateRouterPartsHono.d.ts +14 -0
  48. package/dist/generators/generateRouterPartsHono.js +180 -0
  49. package/dist/generators/generateRouterPartsHono.js.map +1 -0
  50. package/dist/generators/generateUnifiedDocs.d.ts +1 -1
  51. package/dist/generators/generateUnifiedDocs.js +15 -8
  52. package/dist/generators/generateUnifiedDocs.js.map +1 -1
  53. package/dist/generators/generateUnifiedHandler.d.ts +2 -2
  54. package/dist/generators/generateUnifiedHandler.js +42 -18
  55. package/dist/generators/generateUnifiedHandler.js.map +1 -1
  56. package/dist/guardOptions.js +12 -7
  57. package/dist/guardOptions.js.map +1 -1
  58. package/dist/index.d.ts +3 -1
  59. package/dist/index.js +123 -4
  60. package/dist/index.js.map +1 -1
  61. package/dist/utils/copyFiles.d.ts +1 -1
  62. package/dist/utils/copyFiles.js +42 -7
  63. package/dist/utils/copyFiles.js.map +1 -1
  64. package/dist/utils/importExt.d.ts +1 -1
  65. package/dist/utils/mcpGate.d.ts +10 -0
  66. package/dist/utils/mcpGate.js +118 -0
  67. package/dist/utils/mcpGate.js.map +1 -0
  68. package/dist/utils/pathCasing.d.ts +4 -0
  69. package/dist/utils/pathCasing.js +40 -0
  70. package/dist/utils/pathCasing.js.map +1 -0
  71. package/dist/utils/resolveImportStyle.js +13 -5
  72. package/dist/utils/resolveImportStyle.js.map +1 -1
  73. package/dist/utils/writeFileSafely.js +6 -0
  74. package/dist/utils/writeFileSafely.js.map +1 -1
  75. package/package.json +22 -5
  76. package/src/bin.ts +1 -1
  77. package/src/client/encodeQueryParams.ts +39 -35
  78. package/src/constants.ts +1 -1
  79. package/src/copy/autoIncludePlanner.ts +93 -27
  80. package/src/copy/autoIncludePlannerGuarded.ts +171 -48
  81. package/src/copy/autoIncludeRuntimeGuarded.ts +25 -7
  82. package/src/copy/buildModelOpenApi.ts +229 -176
  83. package/src/copy/concurrency.ts +10 -8
  84. package/src/copy/docsRenderer.ts +14 -15
  85. package/src/copy/errorMapper.ts +68 -19
  86. package/src/copy/guardHelpers.ts +30 -9
  87. package/src/copy/materializedCount.ts +17 -5
  88. package/src/copy/mcpRuntime.ts +1017 -0
  89. package/src/copy/misc.ts +88 -68
  90. package/src/copy/ndjson.ts +72 -0
  91. package/src/copy/operationDefinitions.ts +177 -25
  92. package/src/copy/operationPipeline.ts +267 -0
  93. package/src/copy/operationRuntime.ts +124 -26
  94. package/src/copy/operationSchemas.ts +2773 -0
  95. package/src/copy/pagination.ts +35 -13
  96. package/src/copy/parseQueryParams.ts +47 -48
  97. package/src/copy/projectionDefaults.ts +6 -4
  98. package/src/copy/routeConfig.express.ts +16 -4
  99. package/src/copy/routeConfig.fastify.ts +16 -2
  100. package/src/copy/routeConfig.hono.ts +46 -14
  101. package/src/copy/routeConfig.ts +175 -40
  102. package/src/copy/routerParts.ts +524 -0
  103. package/src/copy/scalarTypes.ts +7 -2
  104. package/src/copy/sse.ts +72 -19
  105. package/src/generators/generateFastifyHandler.ts +77 -20
  106. package/src/generators/generateHonoHandler.ts +62 -14
  107. package/src/generators/generateHonoOpenApi.ts +93 -0
  108. package/src/generators/generateImportPrismaStatement.ts +93 -81
  109. package/src/generators/generateMcpApp.ts +133 -0
  110. package/src/generators/generateModelMcp.ts +208 -0
  111. package/src/generators/generateModelMetadata.ts +40 -22
  112. package/src/generators/generateOperationCore.ts +81 -33
  113. package/src/generators/generateRelationMeta.ts +23 -11
  114. package/src/generators/generateRouteConfigType.ts +39 -13
  115. package/src/generators/generateRouter.ts +171 -68
  116. package/src/generators/generateRouterFastify.ts +117 -89
  117. package/src/generators/generateRouterHono.ts +84 -464
  118. package/src/generators/generateRouterPartsHono.ts +220 -0
  119. package/src/generators/generateUnifiedDocs.ts +29 -8
  120. package/src/generators/generateUnifiedHandler.ts +47 -24
  121. package/src/generators/generateUnifiedScalarUI.ts +1 -1
  122. package/src/guardOptions.ts +105 -92
  123. package/src/index.ts +161 -6
  124. package/src/utils/copyFiles.ts +235 -168
  125. package/src/utils/importExt.ts +2 -2
  126. package/src/utils/mcpGate.ts +157 -0
  127. package/src/utils/pathCasing.ts +42 -0
  128. package/src/utils/resolveImportStyle.ts +37 -15
  129. package/src/utils/strings.ts +2 -2
  130. package/src/utils/writeFileSafely.ts +12 -2
@@ -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,24 +182,34 @@ 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,
182
- resolveOperationVariantKey,
183
192
  validateCountSourceWhere,
184
193
  validateOperationConfig,
185
194
  validateUpdateEachConfig,
195
+ resolvePostReadsEnabled,
196
+ warnIfUnguardedRoutes,
186
197
  } from '../routeConfig${ext}'
198
+ import type { RuntimeOperationOverride } from '../operationRuntime${ext}'
187
199
  import type { NormalizedOperationConfig } from '../routeConfig${ext}'
188
200
  import type { OperationContext } from '../operationRuntime${ext}'
189
201
  import { transformResult } from '../operationRuntime${ext}'
190
202
  import { HttpError, mapError } from '../errorMapper${ext}'
191
- import { formatGuardVariantResolutionError } from '../guardVariantError${ext}'
192
- import type { GuardVariantResolution } from '../guardVariantRouting${ext}'
203
+ import {
204
+ routeOperation,
205
+ prepareGuardOperation,
206
+ settleStage,
207
+ classifyError,
208
+ memoizeContext,
209
+ type ArgsChannel,
210
+ } from '../operationPipeline${ext}'
193
211
  import { mergePaginationConfig } from '../pagination${ext}'
212
+ import { acceptsNdjson, runNdjsonFindMany } from '../ndjson${ext}'
194
213
  import {
195
214
  acceptsEventStream,
196
215
  runProgressiveEndpoint,
@@ -200,17 +219,21 @@ import {
200
219
  } from '../sse${ext}'
201
220
  import { relationModels } from '../relationModels${ext}'
202
221
  import { runAutoIncludeProgressive } from '../autoIncludeRuntime${ext}'
203
- import { applyDroppedGuard } from '../projectionDefaults${ext}'
204
222
  import type { OpKind } from '../projectionDefaults${ext}'
205
223
  import { MODEL_FIELDS, MODEL_ENUMS } from './${modelName}Metadata${ext}'
206
224
 
207
- ${generateRouteConfigType(modelName, 'RequestHandler', guardShapesImport, importStyle, 'express')}
225
+ ${generateRouteConfigType(modelName, 'RequestHandler', guardShapesImport, importStyle, 'express', clientImport)}
208
226
  const _env = getEnv()
209
227
 
210
228
  const FIND_MANY_PAGINATED_MODE: FindManyPaginatedMode = '${findManyPaginatedMode}'
211
- const DROP_GUARD = ${dropGuard} || _env.E2E === 'true'
229
+ // Fixed at generation time. The environment bypass is resolved per router,
230
+ // because it requires the configuration's explicit \`allowE2EGuardBypass: true\`:
231
+ // false or omitted means the environment cannot drop guards for this target.
232
+ const DROP_GUARD = ${dropGuard}
212
233
 
213
234
  type OperationConfigLike = {
235
+ authorize?: RequestHandler
236
+ override?: RuntimeOperationOverride
214
237
  before?: RequestHandler[]
215
238
  after?: RequestHandler[]
216
239
  shape?: unknown
@@ -233,7 +256,7 @@ type NormalizedOp = NormalizedOperationConfig<RequestHandler, RequestHandler> &
233
256
  }
234
257
 
235
258
  type ExtendedRequest = Request & {
236
- prisma?: unknown
259
+ prisma?: PrismaClientLike
237
260
  postgres?: unknown
238
261
  sqlite?: unknown
239
262
  }
@@ -243,11 +266,35 @@ type LocalsBag = {
243
266
  routeConfig?: { pagination?: PaginationConfig }
244
267
  guardShape?: Record<string, unknown>
245
268
  guardCaller?: string
269
+ operationOverride?: RuntimeOperationOverride
270
+ getContext?: () => Promise<unknown>
246
271
  guardVariantKey?: string
247
- guardVariantFailure?: Extract<GuardVariantResolution, { ok: false }>
272
+ routeFailure?: { ok: false; failure: HttpError }
273
+ guardFailure?: { ok: false; failure: HttpError }
248
274
  data?: unknown
249
275
  }
250
276
 
277
+ function queryChannel(res: Response): ArgsChannel {
278
+ return {
279
+ read: () => readLocals(res).parsedQuery,
280
+ write: (next) => {
281
+ readLocals(res).parsedQuery = next
282
+ },
283
+ }
284
+ }
285
+
286
+ function bodyChannel(req: Request): ArgsChannel {
287
+ return {
288
+ read: () =>
289
+ isPlainObject(req.body)
290
+ ? (req.body as Record<string, unknown>)
291
+ : undefined,
292
+ write: (next) => {
293
+ req.body = next
294
+ },
295
+ }
296
+ }
297
+
251
298
  function normalizeExpressOperation(
252
299
  config: OperationConfigLike | undefined,
253
300
  ): NormalizedOp {
@@ -351,16 +398,19 @@ function variantAfterDispatcher(opConfig: NormalizedOp): RequestHandler {
351
398
 
352
399
  function requireVariantKey(): RequestHandler {
353
400
  return (_req, res, next) => {
354
- const failure = readLocals(res).guardVariantFailure
355
- if (!failure) {
356
- next()
401
+ const locals = readLocals(res)
402
+ try {
403
+ if (locals.routeFailure) settleStage(locals.routeFailure)
404
+ if (locals.guardFailure) settleStage(locals.guardFailure)
405
+ } catch (err) {
406
+ next(err)
357
407
  return
358
408
  }
359
- next(new HttpError(400, formatGuardVariantResolutionError(failure)))
409
+ next()
360
410
  }
361
411
  }
362
412
 
363
- export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${modelName}RouteConfig<TCtx, TPrisma> = {}) {
413
+ export function ${routerFunctionName}<TCtx = unknown, TPrisma extends PrismaClientLike = PrismaClientLike>(config: ${modelName}RouteConfig<TCtx, TPrisma> = {}) {
364
414
  validateCountSourceWhere(config.pagination?.countSource, '${modelName} pagination')
365
415
  validateCountSourceWhere(
366
416
  (config.findManyPaginated && typeof config.findManyPaginated === 'object' ? config.findManyPaginated : undefined)?.pagination?.countSource,
@@ -370,6 +420,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
370
420
  const router = express.Router()
371
421
 
372
422
  const isEnabled = (value: unknown): boolean => value !== false && !!(config.enableAll || value)
423
+ warnIfUnguardedRoutes('${modelName}', ['findMany', 'findUnique', 'findUniqueOrThrow', 'findFirst', 'findFirstOrThrow', 'findManyPaginated', 'count', 'aggregate', 'groupBy', 'create', 'createMany', 'createManyAndReturn', 'update', 'updateMany', 'updateManyAndReturn', 'upsert', 'delete', 'deleteMany'], config, isEnabled)
373
424
 
374
425
  const opFor = (key: string): NormalizedOp => {
375
426
  const raw = (config as unknown as Record<string, unknown>)[key] as OperationConfigLike | undefined
@@ -378,13 +429,12 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
378
429
  }
379
430
 
380
431
  const customPrefix = normalizePrefix(config.customUrlPrefix || '')
381
- const modelPrefix = config.addModelPrefix !== false ? '/${modelNameLower}' : ''
432
+ const modelPrefix = config.addModelPrefix !== false ? '/${modelSegment}' : ''
382
433
  const basePath = customPrefix + modelPrefix
383
434
 
384
435
  const openApiDisabled = config.disableOpenApi === true
385
436
  || (config.disableOpenApi !== false && (_env.DISABLE_OPENAPI === 'true' || _env.NODE_ENV === 'production'))
386
437
 
387
- const postReadsEnabled = !config.disablePostReads
388
438
 
389
439
  let _openApiJsonCache: unknown = undefined
390
440
  const getOpenApiJson = (): unknown => {
@@ -394,7 +444,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
394
444
  MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
395
445
  MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
396
446
  config as unknown as Parameters<typeof buildModelOpenApi>[3],
397
- { format: 'json', writeStrategy: '${writeStrategy}' },
447
+ { format: 'json', writeStrategy: '${writeStrategy}', pathSegment: '${modelSegment}' },
398
448
  )
399
449
  }
400
450
  return _openApiJsonCache
@@ -407,7 +457,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
407
457
  MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
408
458
  MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
409
459
  config as unknown as Parameters<typeof buildModelOpenApi>[3],
410
- { format: 'yaml', writeStrategy: '${writeStrategy}' },
460
+ { format: 'yaml', writeStrategy: '${writeStrategy}', pathSegment: '${modelSegment}' },
411
461
  ) as string
412
462
  }
413
463
  return _openApiYamlCache
@@ -429,6 +479,8 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
429
479
  const extReq = req as ExtendedRequest
430
480
  const locals = readLocals(res)
431
481
  return {
482
+ operationOverride: locals.operationOverride,
483
+ resolveOperationContext: locals.getContext,
432
484
  prisma: extReq.prisma,
433
485
  postgres: extReq.postgres,
434
486
  sqlite: extReq.sqlite,
@@ -436,16 +488,12 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
436
488
  body: req.body,
437
489
  guardShape: locals.guardShape,
438
490
  guardCaller: locals.guardCaller,
491
+ guardVariantKey: locals.guardVariantKey,
439
492
  paginationConfig: locals.routeConfig?.pagination,
440
493
  findManyPaginatedMode: FIND_MANY_PAGINATED_MODE,
441
494
  }
442
495
  }
443
496
 
444
- const buildResolveContext = (req: Request): (() => unknown | Promise<unknown>) | undefined => {
445
- if (typeof config.resolveContext !== 'function') return undefined
446
- return () => (config.resolveContext as (r: Request) => unknown | Promise<unknown>)(req)
447
- }
448
-
449
497
  const parseQuery: RequestHandler = (req, res, next) => {
450
498
  const rawQuery = req.query
451
499
  if (rawQuery && Object.keys(rawQuery).length > 0) {
@@ -463,10 +511,24 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
463
511
  next()
464
512
  }
465
513
 
514
+ /**
515
+ * The environment bypass, and it is OPT-IN for this target.
516
+ *
517
+ * \`PGE_DROP_GUARD=true\` (deprecated alias \`E2E=true\`) downgrading enforcement
518
+ * in a deployed environment is a real hazard: on most platforms it is an
519
+ * ordinary config var, set on staging, copied forward, flagged as
520
+ * security-relevant nowhere. It is honoured only when this configuration
521
+ * states \`allowE2EGuardBypass: true\` — false or omitted means the environment
522
+ * cannot drop guards. The generation-time \`DROP_GUARD\` literal is unchanged.
523
+ */
524
+ const dropGuard = DROP_GUARD || (config.allowE2EGuardBypass === true && resolveDropGuardEnv(_env))
525
+
466
526
  const setShape = (opConfig: NormalizedOp, opKind: OpKind): RequestHandler => {
467
527
  return async (req, res, next) => {
528
+ const locals = readLocals(res)
468
529
  try {
469
- const locals = readLocals(res)
530
+ locals.operationOverride = opConfig.override
531
+ locals.getContext = memoizeContext(() => config.resolveContext?.(req))
470
532
  const merged = mergePaginationConfig(config.pagination, opConfig.pagination)
471
533
  if (merged) {
472
534
  locals.routeConfig = { pagination: merged }
@@ -477,51 +539,69 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
477
539
  const caller = config.guard?.resolveVariant?.(req) ?? headerValue ?? undefined
478
540
  if (typeof caller === 'string') locals.guardCaller = caller
479
541
 
480
- const resolution = resolveOperationVariantKey(opConfig.guardRouting, caller)
481
- if (!resolution.ok) {
482
- locals.guardVariantFailure = resolution
542
+ const routed = routeOperation({ guardRouting: opConfig.guardRouting, caller })
543
+ if (!routed.ok) {
544
+ locals.routeFailure = routed
483
545
  next()
484
546
  return
485
547
  }
486
548
 
487
- const resolvedKey =
488
- opConfig.guardRouting.kind === 'named'
489
- ? resolution.key
490
- : undefined
491
- if (resolvedKey !== undefined) locals.guardVariantKey = resolvedKey
492
-
493
- if (opConfig.guardShape) {
494
- if (!DROP_GUARD) {
495
- locals.guardShape = opConfig.guardShape
496
- } else {
497
- await applyDroppedGuard(
498
- opConfig.guardShape,
499
- resolvedKey,
500
- buildResolveContext(req),
501
- opKind,
502
- {
503
- readQuery: locals.parsedQuery,
504
- writeBody: isPlainObject(req.body)
505
- ? (req.body as Record<string, unknown>)
506
- : undefined,
507
- },
508
- () => {
509
- if (!locals.parsedQuery) locals.parsedQuery = {}
510
- return locals.parsedQuery
511
- },
512
- () => {
513
- if (!isPlainObject(req.body)) {
514
- req.body = {}
515
- }
516
- return req.body as Record<string, unknown>
517
- },
518
- )
519
- }
549
+ if (routed.variantKey !== undefined) locals.guardVariantKey = routed.variantKey
550
+
551
+ const guard = await prepareGuardOperation(routed, {
552
+ guardShape: opConfig.guardShape,
553
+ opKind,
554
+ policy: { dropGuard, validateResolvedShapes: false },
555
+ getContext: typeof config.resolveContext === 'function' ? locals.getContext : undefined,
556
+ args: queryChannel(res),
557
+ writeArgs: bodyChannel(req),
558
+ })
559
+ if (!guard.ok) {
560
+ locals.guardFailure = guard
561
+ next()
562
+ return
520
563
  }
521
564
 
565
+ locals.guardShape = guard.guardShape
566
+
522
567
  next()
523
568
  } catch (err) {
524
- next(mapError(err))
569
+ next(classifyError(err))
570
+ }
571
+ }
572
+ }
573
+
574
+ const maybeNdjsonFindMany = (
575
+ coreFn: (ctx: OperationContext) => Promise<unknown>,
576
+ ): RequestHandler => {
577
+ return async (req, res, next) => {
578
+ if (res.headersSent || res.writableEnded) return next()
579
+ if (req.method !== 'GET') return next()
580
+ if (!acceptsNdjson(req.headers.accept)) return next()
581
+
582
+ const locals = readLocals(res)
583
+ const rawArgs = (locals.parsedQuery ?? {}) as Record<string, unknown>
584
+ const args = { ...rawArgs }
585
+ const paginationCfg = mergePaginationConfig(config.pagination, undefined)
586
+ const requestedTake = typeof rawArgs.take === 'number' ? rawArgs.take : undefined
587
+ const initialSkip = typeof rawArgs.skip === 'number' ? rawArgs.skip : 0
588
+
589
+ try {
590
+ await runNdjsonFindMany({
591
+ res,
592
+ isClosed: () => req.destroyed,
593
+ fetchPage: (skip, pageTake) =>
594
+ coreFn({
595
+ ...buildContext(req, res),
596
+ parsedQuery: { ...args, skip, take: pageTake },
597
+ }),
598
+ initialSkip,
599
+ requestedTake,
600
+ defaultLimit: paginationCfg?.defaultLimit,
601
+ maxLimit: paginationCfg?.maxLimit ?? 1000,
602
+ })
603
+ } catch (err) {
604
+ next(err)
525
605
  }
526
606
  }
527
607
  }
@@ -552,6 +632,23 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
552
632
  }
553
633
 
554
634
  if (progressiveConfig.mode === 'autoInclude') {
635
+ if (progressiveConfig.experimental !== true) {
636
+ if (_env.NODE_ENV !== 'production') {
637
+ console.warn(
638
+ '[prisma-generator-express] auto-include progressive is experimental; set \`experimental: true\` on the progressive config to enable it. Falling back.'
639
+ )
640
+ }
641
+ if (progressiveConfig.fallback === 'error') {
642
+ emitTerminalSSEError(res, 'auto-include requires experimental: true')
643
+ return
644
+ }
645
+ await runSingleResultSSE({
646
+ req,
647
+ res,
648
+ coreQueryFn: () => coreFn(buildContext(req, res)),
649
+ })
650
+ return
651
+ }
555
652
  const isAutoIncludeReadable =
556
653
  baseOp === 'findUnique' || baseOp === 'findUniqueOrThrow' ||
557
654
  baseOp === 'findFirst' || baseOp === 'findFirstOrThrow' ||
@@ -619,11 +716,16 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(config: ${m
619
716
  }
620
717
 
621
718
  const ctx = await config.resolveContext(req)
719
+ const prismaClient = (req as ExtendedRequest).prisma
720
+ if (!prismaClient) {
721
+ emitTerminalSSEError(res, 'Progressive endpoint requires req.prisma to be set')
722
+ return
723
+ }
622
724
  await runProgressiveEndpoint({
623
725
  req,
624
726
  res,
625
727
  ctx,
626
- prisma: (req as ExtendedRequest).prisma,
728
+ prisma: prismaClient,
627
729
  variant: variant as string,
628
730
  stages: progressiveConfig.stages,
629
731
  stageRegistry,
@@ -679,6 +781,7 @@ ${writeOpBlocks}
679
781
  const path = basePath ? \`\${basePath}/each\` : '/each'
680
782
  router.post(
681
783
  path,
784
+ ...(opConfig.authorize ? [opConfig.authorize] : []),
682
785
  setShape(opConfig, 'noop'),
683
786
  ...opConfig.operationBefore,
684
787
  async (req: Request, res: Response, next: NextFunction) => {
@@ -707,4 +810,4 @@ ${writeOpBlocks}
707
810
  return router
708
811
  }
709
812
  `
710
- }
813
+ }