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
@@ -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(suffix: string): string {
@@ -55,11 +56,11 @@ function emitReadOp(
55
56
 
56
57
  const postReadLine = meta.supportsPostRead
57
58
  ? meta.name === 'findMany'
58
- ? ` if (postReadsEnabled) {
59
+ ? ` if (resolvePostReadsEnabled(config.disablePostReads, opConfig.disablePostReads)) {
59
60
  const postPath = basePath ? \`\${basePath}/read\` : '/read'
60
61
  instance.post(postPath, handleRead(opConfig, ${handlerName}, parseBodyAsQueryHook, '${opKind}'))
61
62
  }`
62
- : ` if (postReadsEnabled) instance.post(path, handleRead(opConfig, ${handlerName}, parseBodyAsQueryHook, '${opKind}'))`
63
+ : ` if (resolvePostReadsEnabled(config.disablePostReads, opConfig.disablePostReads)) instance.post(path, handleRead(opConfig, ${handlerName}, parseBodyAsQueryHook, '${opKind}'))`
63
64
  : ''
64
65
 
65
66
  return ` if (isEnabled(config.${meta.configKey})) {
@@ -90,34 +91,41 @@ export function generateFastifyRouterFunction({
90
91
  model,
91
92
  enums,
92
93
  guardShapesImport,
94
+ clientImport,
93
95
  importStyle,
94
96
  writeStrategy,
95
97
  findManyPaginatedMode,
96
98
  dropGuard,
99
+ pathCase,
97
100
  }: {
98
101
  model: DMMF.Model
99
102
  enums: DMMF.DatamodelEnum[]
100
103
  guardShapesImport: string | null
104
+ clientImport?: string
101
105
  importStyle: ImportStyle
102
106
  writeStrategy: WriteStrategy
103
107
  findManyPaginatedMode: FindManyPaginatedMode
104
108
  dropGuard: boolean
109
+ pathCase: PathCase
105
110
  }): string {
106
111
  const ext = importExt(importStyle)
107
112
  const modelName = model.name
108
- const modelNameLower = modelName.toLowerCase()
109
- const routerFunctionName = `${modelName}Router`
113
+ const modelSegment = modelPathSegment(model.name, pathCase)
114
+ const routerFunctionName = `${model.name}Router`
110
115
 
111
- const handlerImports = OPERATION_METADATA
112
- .map((m) => ` ${modelName}${m.name.charAt(0).toUpperCase() + m.name.slice(1)},`)
113
- .join('\n')
116
+ const handlerImports = OPERATION_METADATA.map(
117
+ (m) => ` ${modelName}${m.name.charAt(0).toUpperCase() + m.name.slice(1)},`,
118
+ ).join('\n')
114
119
 
115
120
  const readOps = OPERATION_METADATA.filter((m) => m.kind === 'read')
116
- const writeOps = OPERATION_METADATA.filter((m) => m.kind === 'write' || m.kind === 'batch')
117
- .filter((m) => m.name !== 'updateEach')
121
+ const writeOps = OPERATION_METADATA.filter(
122
+ (m) => m.kind === 'write' || m.kind === 'batch',
123
+ ).filter((m) => m.name !== 'updateEach')
118
124
 
119
125
  const readOpBlocks = readOps.map((m) => emitReadOp(m, modelName)).join('\n\n')
120
- const writeOpBlocks = writeOps.map((m) => emitWriteOp(m, modelName)).join('\n\n')
126
+ const writeOpBlocks = writeOps
127
+ .map((m) => emitWriteOp(m, modelName))
128
+ .join('\n\n')
121
129
 
122
130
  return `import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyError } from 'fastify'
123
131
  import { startQueryBuilder } from '../queryBuilder${ext}'
@@ -129,9 +137,11 @@ import type {
129
137
  FastifyHookHandler,
130
138
  FindManyPaginatedMode,
131
139
  PaginationConfig,
140
+ PrismaClientLike,
141
+ QueryBuilderConfig,
132
142
  } from '../routeConfig.target${ext}'
133
143
  import { parseQueryParams } from '../parseQueryParams${ext}'
134
- import { sanitizeKeys, normalizePrefix, getEnv, isPlainObject } from '../misc${ext}'
144
+ import { sanitizeKeys, normalizePrefix, getEnv, isPlainObject, resolveDropGuardEnv } from '../misc${ext}'
135
145
  import { buildModelOpenApi } from '../buildModelOpenApi${ext}'
136
146
  import {
137
147
  normalizeOperation,
@@ -139,7 +149,10 @@ import {
139
149
  validateCountSourceWhere,
140
150
  validateOperationConfig,
141
151
  validateUpdateEachConfig,
152
+ resolvePostReadsEnabled,
153
+ warnIfUnguardedRoutes,
142
154
  } from '../routeConfig${ext}'
155
+ import type { RuntimeOperationOverride } from '../operationRuntime${ext}'
143
156
  import type { NormalizedOperationConfig } from '../routeConfig${ext}'
144
157
  import type { OperationContext } from '../operationRuntime${ext}'
145
158
  import { transformResult } from '../operationRuntime${ext}'
@@ -151,13 +164,18 @@ import { applyDroppedGuard } from '../projectionDefaults${ext}'
151
164
  import type { OpKind } from '../projectionDefaults${ext}'
152
165
  import { MODEL_FIELDS, MODEL_ENUMS } from './${modelName}Metadata${ext}'
153
166
 
154
- ${generateRouteConfigType(modelName, 'FastifyHookHandler', guardShapesImport, importStyle, 'fastify')}
167
+ ${generateRouteConfigType(modelName, 'FastifyHookHandler', guardShapesImport, importStyle, 'fastify', clientImport)}
155
168
  const _env = getEnv()
156
169
 
157
170
  const FIND_MANY_PAGINATED_MODE: FindManyPaginatedMode = '${findManyPaginatedMode}'
158
- const DROP_GUARD = ${dropGuard} || _env.E2E === 'true'
171
+ // Fixed at generation time. The environment bypass is resolved per hook,
172
+ // because it requires the configuration's explicit \`allowE2EGuardBypass: true\`:
173
+ // false or omitted means the environment cannot drop guards for this target.
174
+ const DROP_GUARD = ${dropGuard}
159
175
 
160
176
  type OperationConfigLike = {
177
+ authorize?: FastifyHookHandler
178
+ override?: RuntimeOperationOverride
161
179
  before?: FastifyHookHandler[]
162
180
  after?: FastifyHookHandler[]
163
181
  shape?: unknown
@@ -185,6 +203,8 @@ type FastifyExtended = FastifyRequest & {
185
203
  routeConfig?: { pagination?: PaginationConfig }
186
204
  guardShape?: Record<string, unknown>
187
205
  guardCaller?: string
206
+ operationOverride?: RuntimeOperationOverride
207
+ resolveOperationContext?: () => unknown | Promise<unknown>
188
208
  guardVariantKey?: string
189
209
  guardVariantFailure?: Extract<GuardVariantResolution, { ok: false }>
190
210
  resultData?: unknown
@@ -197,14 +217,14 @@ function normalizeFastifyOperation(
197
217
  return normalizeOperation<FastifyHookHandler, FastifyHookHandler>(config)
198
218
  }
199
219
 
200
- function isQueryBuilderEnabled(config: RouteConfig): boolean {
220
+ function isQueryBuilderEnabled(config: { queryBuilder?: QueryBuilderConfig | false }): boolean {
201
221
  if (config.queryBuilder === false) return false
202
222
  if (typeof config.queryBuilder === 'object' && config.queryBuilder.enabled === false) return false
203
223
  if (_env.NODE_ENV === 'production') return false
204
224
  return true
205
225
  }
206
226
 
207
- function getQueryBuilderConfig(config: RouteConfig) {
227
+ function getQueryBuilderConfig(config: { queryBuilder?: QueryBuilderConfig | false }) {
208
228
  if (config.queryBuilder === false) return null
209
229
  if (typeof config.queryBuilder === 'object') return config.queryBuilder
210
230
  return {}
@@ -225,21 +245,35 @@ function parseBodyAsQueryHook(request: FastifyRequest): void {
225
245
  (request as FastifyExtended).parsedQuery = sanitizeKeys(body as Record<string, unknown>)
226
246
  }
227
247
 
228
- function buildResolveContext(
229
- config: ${modelName}RouteConfig,
248
+ function buildResolveContext<TCtx, TPrisma extends PrismaClientLike>(
249
+ config: ${modelName}RouteConfig<TCtx, TPrisma>,
230
250
  request: FastifyRequest,
231
251
  ): (() => unknown | Promise<unknown>) | undefined {
232
252
  if (typeof config.resolveContext !== 'function') return undefined
233
253
  return () => (config.resolveContext as (r: FastifyRequest) => unknown | Promise<unknown>)(request)
234
254
  }
235
255
 
236
- function makeShapeHook(
237
- config: ${modelName}RouteConfig,
256
+ function makeShapeHook<TCtx, TPrisma extends PrismaClientLike>(
257
+ config: ${modelName}RouteConfig<TCtx, TPrisma>,
238
258
  opConfig: NormalizedOp,
239
259
  opKind: OpKind,
240
260
  ): (request: FastifyRequest) => Promise<void> {
261
+ /**
262
+ * The environment bypass, and it is OPT-IN for this target.
263
+ *
264
+ * \`PGE_DROP_GUARD=true\` (deprecated alias \`E2E=true\`) downgrading enforcement
265
+ * in a deployed environment is a real hazard: on most platforms it is an
266
+ * ordinary config var, set on staging, copied forward, flagged as
267
+ * security-relevant nowhere. It is honoured only when this configuration
268
+ * states \`allowE2EGuardBypass: true\` — false or omitted means the environment
269
+ * cannot drop guards. The generation-time \`DROP_GUARD\` literal is unchanged.
270
+ */
271
+ const dropGuard = DROP_GUARD || (config.allowE2EGuardBypass === true && resolveDropGuardEnv(_env))
241
272
  return async (request: FastifyRequest) => {
242
273
  const fx = request as FastifyExtended
274
+ fx.operationOverride = opConfig.override
275
+ let context: Promise<unknown> | undefined
276
+ fx.resolveOperationContext = () => context ??= Promise.resolve(config.resolveContext?.(request))
243
277
  const merged = mergePaginationConfig(config.pagination, opConfig.pagination)
244
278
  if (merged) fx.routeConfig = { pagination: merged }
245
279
 
@@ -263,7 +297,7 @@ function makeShapeHook(
263
297
  if (resolvedKey !== undefined) fx.guardVariantKey = resolvedKey
264
298
 
265
299
  if (opConfig.guardShape) {
266
- if (!DROP_GUARD) {
300
+ if (!dropGuard) {
267
301
  fx.guardShape = opConfig.guardShape
268
302
  } else {
269
303
  await applyDroppedGuard(
@@ -322,7 +356,7 @@ function sendError(reply: FastifyReply, error: unknown): void {
322
356
  reply.code(httpError.status).send({ message: httpError.message })
323
357
  }
324
358
 
325
- export async function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(
359
+ export async function ${routerFunctionName}<TCtx = unknown, TPrisma extends PrismaClientLike = PrismaClientLike>(
326
360
  fastify: FastifyInstance,
327
361
  config: ${modelName}RouteConfig<TCtx, TPrisma> = {},
328
362
  ) {
@@ -334,6 +368,7 @@ export async function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(
334
368
 
335
369
  await fastify.register(async (instance) => {
336
370
  const isEnabled = (value: unknown): boolean => value !== false && !!(config.enableAll || value)
371
+ warnIfUnguardedRoutes('${modelName}', ['findMany', 'findUnique', 'findUniqueOrThrow', 'findFirst', 'findFirstOrThrow', 'findManyPaginated', 'count', 'aggregate', 'groupBy', 'create', 'createMany', 'createManyAndReturn', 'update', 'updateMany', 'updateManyAndReturn', 'upsert', 'delete', 'deleteMany'], config, isEnabled)
337
372
 
338
373
  const opFor = (key: string): NormalizedOp => {
339
374
  const raw = (config as unknown as Record<string, unknown>)[key] as OperationConfigLike | undefined
@@ -342,7 +377,7 @@ export async function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(
342
377
  }
343
378
 
344
379
  const customPrefix = normalizePrefix(config.customUrlPrefix || '')
345
- const modelPrefix = config.addModelPrefix !== false ? '/${modelNameLower}' : ''
380
+ const modelPrefix = config.addModelPrefix !== false ? '/${modelSegment}' : ''
346
381
  const basePath = customPrefix + modelPrefix
347
382
 
348
383
  const openApiDisabled = config.disableOpenApi === true
@@ -351,7 +386,6 @@ export async function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(
351
386
  || _env.DISABLE_OPENAPI === 'true'
352
387
  ))
353
388
 
354
- const postReadsEnabled = !config.disablePostReads
355
389
 
356
390
  let _openApiJsonCache: unknown = undefined
357
391
  const getOpenApiJson = (): unknown => {
@@ -360,8 +394,8 @@ export async function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(
360
394
  '${modelName}',
361
395
  MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
362
396
  MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
363
- config,
364
- { format: 'json', writeStrategy: '${writeStrategy}' },
397
+ config as unknown as Parameters<typeof buildModelOpenApi>[3],
398
+ { format: 'json', writeStrategy: '${writeStrategy}', pathSegment: '${modelSegment}' },
365
399
  )
366
400
  }
367
401
  return _openApiJsonCache
@@ -373,8 +407,8 @@ export async function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(
373
407
  '${modelName}',
374
408
  MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
375
409
  MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
376
- config,
377
- { format: 'yaml', writeStrategy: '${writeStrategy}' },
410
+ config as unknown as Parameters<typeof buildModelOpenApi>[3],
411
+ { format: 'yaml', writeStrategy: '${writeStrategy}', pathSegment: '${modelSegment}' },
378
412
  ) as string
379
413
  }
380
414
  return _openApiYamlCache
@@ -424,6 +458,7 @@ export async function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(
424
458
  opKind: OpKind,
425
459
  ) => async (request: FastifyRequest, reply: FastifyReply) => {
426
460
  try {
461
+ if (await runHooks(opConfig.authorize ? [opConfig.authorize] : [], request, reply)) return
427
462
  parseFn(request)
428
463
  await makeShapeHook(config, opConfig, opKind)(request)
429
464
  if (await runHooks(opConfig.operationBefore, request, reply)) return
@@ -456,6 +491,7 @@ export async function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(
456
491
  opKind: OpKind,
457
492
  ) => async (request: FastifyRequest, reply: FastifyReply) => {
458
493
  try {
494
+ if (await runHooks(opConfig.authorize ? [opConfig.authorize] : [], request, reply)) return
459
495
  await makeShapeHook(config, opConfig, opKind)(request)
460
496
  if (await runHooks(opConfig.operationBefore, request, reply)) return
461
497
 
@@ -514,4 +550,4 @@ ${writeOpBlocks}
514
550
  })
515
551
  }
516
552
  `
517
- }
553
+ }