prisma-generator-express 1.56.4 → 1.58.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 (44) hide show
  1. package/README.md +286 -29
  2. package/dist/copy/misc.js +25 -6
  3. package/dist/copy/misc.js.map +1 -1
  4. package/dist/generators/generateFastifyHandler.js +11 -0
  5. package/dist/generators/generateFastifyHandler.js.map +1 -1
  6. package/dist/generators/generateHonoHandler.js +14 -20
  7. package/dist/generators/generateHonoHandler.js.map +1 -1
  8. package/dist/generators/generateImportPrismaStatement.js +43 -0
  9. package/dist/generators/generateImportPrismaStatement.js.map +1 -1
  10. package/dist/generators/generateOperationCore.js +58 -17
  11. package/dist/generators/generateOperationCore.js.map +1 -1
  12. package/dist/generators/generateRouteConfigType.js +44 -15
  13. package/dist/generators/generateRouteConfigType.js.map +1 -1
  14. package/dist/generators/generateRouter.d.ts +2 -1
  15. package/dist/generators/generateRouter.js +60 -34
  16. package/dist/generators/generateRouter.js.map +1 -1
  17. package/dist/generators/generateRouterFastify.d.ts +2 -1
  18. package/dist/generators/generateRouterFastify.js +238 -193
  19. package/dist/generators/generateRouterFastify.js.map +1 -1
  20. package/dist/generators/generateRouterHono.d.ts +2 -1
  21. package/dist/generators/generateRouterHono.js +124 -89
  22. package/dist/generators/generateRouterHono.js.map +1 -1
  23. package/dist/index.js +22 -4
  24. package/dist/index.js.map +1 -1
  25. package/package.json +1 -1
  26. package/src/copy/autoIncludeRuntime.ts +9 -5
  27. package/src/copy/buildModelOpenApi.ts +96 -0
  28. package/src/copy/docsRenderer.ts +577 -174
  29. package/src/copy/materializedRouter.ts +40 -1
  30. package/src/copy/misc.ts +23 -6
  31. package/src/copy/operationDefinitions.ts +10 -0
  32. package/src/copy/operationRuntime.ts +28 -9
  33. package/src/copy/routeConfig.express.ts +9 -9
  34. package/src/copy/routeConfig.hono.ts +63 -5
  35. package/src/copy/routeConfig.ts +44 -20
  36. package/src/generators/generateFastifyHandler.ts +12 -0
  37. package/src/generators/generateHonoHandler.ts +15 -20
  38. package/src/generators/generateImportPrismaStatement.ts +13 -0
  39. package/src/generators/generateOperationCore.ts +58 -17
  40. package/src/generators/generateRouteConfigType.ts +52 -17
  41. package/src/generators/generateRouter.ts +61 -33
  42. package/src/generators/generateRouterFastify.ts +239 -192
  43. package/src/generators/generateRouterHono.ts +125 -88
  44. package/src/index.ts +25 -5
@@ -11,6 +11,7 @@ export function generateFastifyRouterFunction({
11
11
  importStyle,
12
12
  writeStrategy,
13
13
  findManyPaginatedMode,
14
+ dropGuard,
14
15
  }: {
15
16
  model: DMMF.Model
16
17
  enums: DMMF.DatamodelEnum[]
@@ -18,6 +19,7 @@ export function generateFastifyRouterFunction({
18
19
  importStyle: ImportStyle
19
20
  writeStrategy: WriteStrategy
20
21
  findManyPaginatedMode: FindManyPaginatedMode
22
+ dropGuard: boolean
21
23
  }): string {
22
24
  const ext = importExt(importStyle)
23
25
  const modelName = model.name
@@ -68,6 +70,7 @@ import {
68
70
  ${modelName}Aggregate,
69
71
  ${modelName}Count,
70
72
  ${modelName}GroupBy,
73
+ ${modelName}UpdateEach,
71
74
  } from './${modelName}Handlers${ext}'
72
75
  import type {
73
76
  RouteConfig,
@@ -79,6 +82,7 @@ import type {
79
82
  import { parseQueryParams } from '../parseQueryParams${ext}'
80
83
  import { sanitizeKeys, normalizePrefix, getEnv } from '../misc${ext}'
81
84
  import { buildModelOpenApi } from '../buildModelOpenApi${ext}'
85
+ import { validateCountSourceWhere } from '../routeConfig${ext}'
82
86
  import { mapError, transformResult, mergePaginationConfig, HttpError, type OperationContext } from '../operationRuntime${ext}'
83
87
 
84
88
  ${generateRouteConfigType(modelName, 'FastifyHookHandler', guardShapesImport, importStyle, 'fastify')}
@@ -86,6 +90,7 @@ const _env = getEnv()
86
90
 
87
91
  const WRITE_STRATEGY: WriteStrategy = '${writeStrategy}'
88
92
  const FIND_MANY_PAGINATED_MODE: FindManyPaginatedMode = '${findManyPaginatedMode}'
93
+ const DROP_GUARD = ${dropGuard} || _env.E2E === 'true'
89
94
 
90
95
  const MODEL_FIELDS = ${JSON.stringify(fieldsMeta, null, 2)} as const
91
96
 
@@ -161,7 +166,7 @@ function makeShapeHook(
161
166
  if (caller) {
162
167
  fx.guardCaller = caller
163
168
  }
164
- if (opConfig.shape) {
169
+ if (opConfig.shape && !DROP_GUARD) {
165
170
  fx.guardShape = opConfig.shape
166
171
  }
167
172
  }
@@ -191,6 +196,7 @@ function sendResult(request: FastifyRequest, reply: FastifyReply): void {
191
196
  }
192
197
 
193
198
  function sendError(reply: FastifyReply, error: unknown): void {
199
+ if (reply.sent) return
194
200
  const httpError = mapError(error)
195
201
  reply.code(httpError.status).send({ message: httpError.message })
196
202
  }
@@ -199,229 +205,270 @@ export async function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(
199
205
  fastify: FastifyInstance,
200
206
  config: ${modelName}RouteConfig<TCtx, TPrisma> = {},
201
207
  ) {
202
- const customPrefix = normalizePrefix(config.customUrlPrefix || '')
203
- const modelPrefix = config.addModelPrefix !== false ? '/${modelNameLower}' : ''
204
- const basePath = customPrefix + modelPrefix
205
-
206
- const openApiDisabled = config.disableOpenApi === true
207
- || (config.disableOpenApi !== false && (
208
- _env.DISABLE_OPENAPI === 'true'
209
- || _env.NODE_ENV === 'production'
210
- ))
211
-
212
- const postReadsEnabled = !config.disablePostReads
213
-
214
- const openApiJsonSpec = openApiDisabled
215
- ? null
216
- : buildModelOpenApi(
217
- '${modelName}',
218
- MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
219
- MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
220
- config,
221
- { format: 'json', writeStrategy: WRITE_STRATEGY },
222
- )
223
- const openApiYamlSpec = openApiDisabled
224
- ? null
225
- : buildModelOpenApi(
226
- '${modelName}',
227
- MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
228
- MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
229
- config,
230
- { format: 'yaml', writeStrategy: WRITE_STRATEGY },
231
- )
232
-
233
- const qbEnabled = isQueryBuilderEnabled(config)
234
-
235
- if (qbEnabled) {
236
- const qbConfig = getQueryBuilderConfig(config)
237
- if (qbConfig) {
238
- try {
239
- startQueryBuilder(qbConfig)
240
- } catch (err) {
241
- if (_env.NODE_ENV !== 'production') console.warn('[query-builder]', err)
208
+ validateCountSourceWhere(config.pagination?.countSource, '${modelName} pagination')
209
+ validateCountSourceWhere(
210
+ (config.findManyPaginated && typeof config.findManyPaginated === 'object' ? config.findManyPaginated : undefined)?.pagination?.countSource,
211
+ '${modelName} findManyPaginated pagination',
212
+ )
213
+
214
+ await fastify.register(async (instance) => {
215
+ const isEnabled = (value: unknown): boolean => value !== false && !!(config.enableAll || value)
216
+
217
+ const customPrefix = normalizePrefix(config.customUrlPrefix || '')
218
+ const modelPrefix = config.addModelPrefix !== false ? '/${modelNameLower}' : ''
219
+ const basePath = customPrefix + modelPrefix
220
+
221
+ const openApiDisabled = config.disableOpenApi === true
222
+ || (config.disableOpenApi !== false && (
223
+ _env.NODE_ENV === 'production'
224
+ || _env.DISABLE_OPENAPI === 'true'
225
+ ))
226
+
227
+ const postReadsEnabled = !config.disablePostReads
228
+
229
+ let _openApiJsonCache: unknown = undefined
230
+ const getOpenApiJson = (): unknown => {
231
+ if (_openApiJsonCache === undefined) {
232
+ _openApiJsonCache = buildModelOpenApi(
233
+ '${modelName}',
234
+ MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
235
+ MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
236
+ config,
237
+ { format: 'json', writeStrategy: WRITE_STRATEGY },
238
+ )
242
239
  }
240
+ return _openApiJsonCache
241
+ }
242
+ let _openApiYamlCache: string | undefined = undefined
243
+ const getOpenApiYaml = (): string => {
244
+ if (_openApiYamlCache === undefined) {
245
+ _openApiYamlCache = buildModelOpenApi(
246
+ '${modelName}',
247
+ MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
248
+ MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
249
+ config,
250
+ { format: 'yaml', writeStrategy: WRITE_STRATEGY },
251
+ ) as string
252
+ }
253
+ return _openApiYamlCache
243
254
  }
244
- }
245
255
 
246
- fastify.addHook('onRequest', async (request: FastifyRequest) => {
247
- (request as FastifyExtended & { findManyPaginatedMode?: FindManyPaginatedMode }).findManyPaginatedMode = FIND_MANY_PAGINATED_MODE
248
- })
256
+ const qbEnabled = isQueryBuilderEnabled(config)
249
257
 
250
- fastify.setErrorHandler((error: FastifyError, _request: FastifyRequest, reply: FastifyReply) => {
251
- const e = error as { status?: number; statusCode?: number; message?: string }
252
- const status = e.status ?? e.statusCode ?? 500
253
- const message = error.message || 'Internal server error'
254
- if (!reply.sent) {
255
- reply.code(status).send({ message })
258
+ if (qbEnabled) {
259
+ const qbConfig = getQueryBuilderConfig(config)
260
+ if (qbConfig) {
261
+ try {
262
+ startQueryBuilder(qbConfig)
263
+ } catch (err) {
264
+ if (_env.NODE_ENV !== 'production') console.warn('[query-builder]', err)
265
+ }
266
+ }
256
267
  }
257
- })
258
-
259
- if (!openApiDisabled) {
260
- const openapiJsonPath = basePath ? \`\${basePath}/openapi.json\` : '/openapi.json'
261
- const openapiYamlPath = basePath ? \`\${basePath}/openapi.yaml\` : '/openapi.yaml'
262
268
 
263
- fastify.get(openapiJsonPath, async (_request, reply) => {
264
- return reply.send(openApiJsonSpec)
269
+ instance.addHook('onRequest', async (request: FastifyRequest) => {
270
+ (request as FastifyExtended & { findManyPaginatedMode?: FindManyPaginatedMode }).findManyPaginatedMode = FIND_MANY_PAGINATED_MODE
265
271
  })
266
272
 
267
- fastify.get(openapiYamlPath, async (_request, reply) => {
268
- return reply.type('application/yaml').send(openApiYamlSpec as string)
273
+ instance.setErrorHandler((error: FastifyError, _request: FastifyRequest, reply: FastifyReply) => {
274
+ const e = error as { status?: number; statusCode?: number; message?: string }
275
+ const status = e.status ?? e.statusCode ?? 500
276
+ const message = error.message || 'Internal server error'
277
+ if (!reply.sent) {
278
+ reply.code(status).send({ message })
279
+ }
269
280
  })
270
- }
271
281
 
272
- const handleGet = (
273
- opConfig: OperationConfigLike,
274
- handlerFn: (req: FastifyRequest, reply: FastifyReply) => Promise<void>,
275
- parseFn: (req: FastifyRequest) => void,
276
- ) => async (request: FastifyRequest, reply: FastifyReply) => {
277
- try {
278
- parseFn(request)
279
- makeShapeHook(config, opConfig)(request)
280
- const { before = [], after = [] } = opConfig
281
- if (await runHooks(before, request, reply)) return
282
- await handlerFn(request, reply)
283
- if (await runHooks(after, request, reply)) return
284
- sendResult(request, reply)
285
- } catch (error: unknown) {
286
- sendError(reply, error)
282
+ if (!openApiDisabled) {
283
+ const openapiJsonPath = basePath ? \`\${basePath}/openapi.json\` : '/openapi.json'
284
+ const openapiYamlPath = basePath ? \`\${basePath}/openapi.yaml\` : '/openapi.yaml'
285
+
286
+ instance.get(openapiJsonPath, async (_request, reply) => {
287
+ return reply.send(getOpenApiJson())
288
+ })
289
+
290
+ instance.get(openapiYamlPath, async (_request, reply) => {
291
+ return reply.type('application/yaml').send(getOpenApiYaml())
292
+ })
287
293
  }
288
- }
289
294
 
290
- const handleWrite = (
291
- opConfig: OperationConfigLike,
292
- handlerFn: (req: FastifyRequest, reply: FastifyReply) => Promise<void>,
293
- ) => async (request: FastifyRequest, reply: FastifyReply) => {
294
- try {
295
- makeShapeHook(config, opConfig)(request)
296
- const { before = [], after = [] } = opConfig
297
- if (await runHooks(before, request, reply)) return
298
- await handlerFn(request, reply)
299
- if (await runHooks(after, request, reply)) return
300
- sendResult(request, reply)
301
- } catch (error: unknown) {
302
- sendError(reply, error)
295
+ const handleGet = (
296
+ opConfig: OperationConfigLike,
297
+ handlerFn: (req: FastifyRequest, reply: FastifyReply) => Promise<void>,
298
+ parseFn: (req: FastifyRequest) => void,
299
+ ) => async (request: FastifyRequest, reply: FastifyReply) => {
300
+ try {
301
+ parseFn(request)
302
+ makeShapeHook(config, opConfig)(request)
303
+ const { before = [], after = [] } = opConfig
304
+ if (await runHooks(before, request, reply)) return
305
+ await handlerFn(request, reply)
306
+ if (await runHooks(after, request, reply)) return
307
+ sendResult(request, reply)
308
+ } catch (error: unknown) {
309
+ sendError(reply, error)
310
+ }
303
311
  }
304
- }
305
312
 
306
- if (config.enableAll || config.findFirst) {
307
- const opConfig: OperationConfigLike = (config.findFirst as OperationConfigLike | undefined) ?? defaultOpConfig
308
- const path = basePath ? \`\${basePath}/first\` : '/first'
309
- fastify.get(path, handleGet(opConfig, ${modelName}FindFirst, parseQueryHook))
310
- if (postReadsEnabled) fastify.post(path, handleGet(opConfig, ${modelName}FindFirst, parseBodyAsQueryHook))
311
- }
313
+ const handleWrite = (
314
+ opConfig: OperationConfigLike,
315
+ handlerFn: (req: FastifyRequest, reply: FastifyReply) => Promise<void>,
316
+ ) => async (request: FastifyRequest, reply: FastifyReply) => {
317
+ try {
318
+ makeShapeHook(config, opConfig)(request)
319
+ const { before = [], after = [] } = opConfig
320
+ if (await runHooks(before, request, reply)) return
321
+ await handlerFn(request, reply)
322
+ if (await runHooks(after, request, reply)) return
323
+ sendResult(request, reply)
324
+ } catch (error: unknown) {
325
+ sendError(reply, error)
326
+ }
327
+ }
312
328
 
313
- if (config.enableAll || config.findFirstOrThrow) {
314
- const opConfig: OperationConfigLike = (config.findFirstOrThrow as OperationConfigLike | undefined) ?? defaultOpConfig
315
- const path = basePath ? \`\${basePath}/first/strict\` : '/first/strict'
316
- fastify.get(path, handleGet(opConfig, ${modelName}FindFirstOrThrow, parseQueryHook))
317
- if (postReadsEnabled) fastify.post(path, handleGet(opConfig, ${modelName}FindFirstOrThrow, parseBodyAsQueryHook))
318
- }
329
+ if (isEnabled(config.findFirst)) {
330
+ const opConfig: OperationConfigLike = (config.findFirst as OperationConfigLike | undefined) ?? defaultOpConfig
331
+ const path = basePath ? \`\${basePath}/first\` : '/first'
332
+ instance.get(path, handleGet(opConfig, ${modelName}FindFirst, parseQueryHook))
333
+ if (postReadsEnabled) instance.post(path, handleGet(opConfig, ${modelName}FindFirst, parseBodyAsQueryHook))
334
+ }
319
335
 
320
- if (config.enableAll || config.findManyPaginated) {
321
- const opConfig: OperationConfigLike = (config.findManyPaginated as OperationConfigLike | undefined) ?? defaultOpConfig
322
- const path = basePath ? \`\${basePath}/paginated\` : '/paginated'
323
- fastify.get(path, handleGet(opConfig, ${modelName}FindManyPaginated, parseQueryHook))
324
- if (postReadsEnabled) fastify.post(path, handleGet(opConfig, ${modelName}FindManyPaginated, parseBodyAsQueryHook))
325
- }
336
+ if (isEnabled(config.findFirstOrThrow)) {
337
+ const opConfig: OperationConfigLike = (config.findFirstOrThrow as OperationConfigLike | undefined) ?? defaultOpConfig
338
+ const path = basePath ? \`\${basePath}/first/strict\` : '/first/strict'
339
+ instance.get(path, handleGet(opConfig, ${modelName}FindFirstOrThrow, parseQueryHook))
340
+ if (postReadsEnabled) instance.post(path, handleGet(opConfig, ${modelName}FindFirstOrThrow, parseBodyAsQueryHook))
341
+ }
326
342
 
327
- if (config.enableAll || config.aggregate) {
328
- const opConfig: OperationConfigLike = (config.aggregate as OperationConfigLike | undefined) ?? defaultOpConfig
329
- const path = basePath ? \`\${basePath}/aggregate\` : '/aggregate'
330
- fastify.get(path, handleGet(opConfig, ${modelName}Aggregate, parseQueryHook))
331
- if (postReadsEnabled) fastify.post(path, handleGet(opConfig, ${modelName}Aggregate, parseBodyAsQueryHook))
332
- }
343
+ if (isEnabled(config.findManyPaginated)) {
344
+ const opConfig: OperationConfigLike = (config.findManyPaginated as OperationConfigLike | undefined) ?? defaultOpConfig
345
+ const path = basePath ? \`\${basePath}/paginated\` : '/paginated'
346
+ instance.get(path, handleGet(opConfig, ${modelName}FindManyPaginated, parseQueryHook))
347
+ if (postReadsEnabled) instance.post(path, handleGet(opConfig, ${modelName}FindManyPaginated, parseBodyAsQueryHook))
348
+ }
333
349
 
334
- if (config.enableAll || config.count) {
335
- const opConfig: OperationConfigLike = (config.count as OperationConfigLike | undefined) ?? defaultOpConfig
336
- const path = basePath ? \`\${basePath}/count\` : '/count'
337
- fastify.get(path, handleGet(opConfig, ${modelName}Count, parseQueryHook))
338
- if (postReadsEnabled) fastify.post(path, handleGet(opConfig, ${modelName}Count, parseBodyAsQueryHook))
339
- }
350
+ if (isEnabled(config.aggregate)) {
351
+ const opConfig: OperationConfigLike = (config.aggregate as OperationConfigLike | undefined) ?? defaultOpConfig
352
+ const path = basePath ? \`\${basePath}/aggregate\` : '/aggregate'
353
+ instance.get(path, handleGet(opConfig, ${modelName}Aggregate, parseQueryHook))
354
+ if (postReadsEnabled) instance.post(path, handleGet(opConfig, ${modelName}Aggregate, parseBodyAsQueryHook))
355
+ }
340
356
 
341
- if (config.enableAll || config.groupBy) {
342
- const opConfig: OperationConfigLike = (config.groupBy as OperationConfigLike | undefined) ?? defaultOpConfig
343
- const path = basePath ? \`\${basePath}/groupby\` : '/groupby'
344
- fastify.get(path, handleGet(opConfig, ${modelName}GroupBy, parseQueryHook))
345
- if (postReadsEnabled) fastify.post(path, handleGet(opConfig, ${modelName}GroupBy, parseBodyAsQueryHook))
346
- }
357
+ if (isEnabled(config.count)) {
358
+ const opConfig: OperationConfigLike = (config.count as OperationConfigLike | undefined) ?? defaultOpConfig
359
+ const path = basePath ? \`\${basePath}/count\` : '/count'
360
+ instance.get(path, handleGet(opConfig, ${modelName}Count, parseQueryHook))
361
+ if (postReadsEnabled) instance.post(path, handleGet(opConfig, ${modelName}Count, parseBodyAsQueryHook))
362
+ }
347
363
 
348
- if (config.enableAll || config.findUniqueOrThrow) {
349
- const opConfig: OperationConfigLike = (config.findUniqueOrThrow as OperationConfigLike | undefined) ?? defaultOpConfig
350
- const path = basePath ? \`\${basePath}/unique/strict\` : '/unique/strict'
351
- fastify.get(path, handleGet(opConfig, ${modelName}FindUniqueOrThrow, parseQueryHook))
352
- if (postReadsEnabled) fastify.post(path, handleGet(opConfig, ${modelName}FindUniqueOrThrow, parseBodyAsQueryHook))
353
- }
364
+ if (isEnabled(config.groupBy)) {
365
+ const opConfig: OperationConfigLike = (config.groupBy as OperationConfigLike | undefined) ?? defaultOpConfig
366
+ const path = basePath ? \`\${basePath}/groupby\` : '/groupby'
367
+ instance.get(path, handleGet(opConfig, ${modelName}GroupBy, parseQueryHook))
368
+ if (postReadsEnabled) instance.post(path, handleGet(opConfig, ${modelName}GroupBy, parseBodyAsQueryHook))
369
+ }
354
370
 
355
- if (config.enableAll || config.findUnique) {
356
- const opConfig: OperationConfigLike = (config.findUnique as OperationConfigLike | undefined) ?? defaultOpConfig
357
- const path = basePath ? \`\${basePath}/unique\` : '/unique'
358
- fastify.get(path, handleGet(opConfig, ${modelName}FindUnique, parseQueryHook))
359
- if (postReadsEnabled) fastify.post(path, handleGet(opConfig, ${modelName}FindUnique, parseBodyAsQueryHook))
360
- }
371
+ if (isEnabled(config.findUniqueOrThrow)) {
372
+ const opConfig: OperationConfigLike = (config.findUniqueOrThrow as OperationConfigLike | undefined) ?? defaultOpConfig
373
+ const path = basePath ? \`\${basePath}/unique/strict\` : '/unique/strict'
374
+ instance.get(path, handleGet(opConfig, ${modelName}FindUniqueOrThrow, parseQueryHook))
375
+ if (postReadsEnabled) instance.post(path, handleGet(opConfig, ${modelName}FindUniqueOrThrow, parseBodyAsQueryHook))
376
+ }
361
377
 
362
- if (config.enableAll || config.findMany) {
363
- const opConfig: OperationConfigLike = (config.findMany as OperationConfigLike | undefined) ?? defaultOpConfig
364
- const path = basePath || '/'
365
- fastify.get(path, handleGet(opConfig, ${modelName}FindMany, parseQueryHook))
366
- if (postReadsEnabled) {
367
- const postPath = basePath ? \`\${basePath}/read\` : '/read'
368
- fastify.post(postPath, handleGet(opConfig, ${modelName}FindMany, parseBodyAsQueryHook))
378
+ if (isEnabled(config.findUnique)) {
379
+ const opConfig: OperationConfigLike = (config.findUnique as OperationConfigLike | undefined) ?? defaultOpConfig
380
+ const path = basePath ? \`\${basePath}/unique\` : '/unique'
381
+ instance.get(path, handleGet(opConfig, ${modelName}FindUnique, parseQueryHook))
382
+ if (postReadsEnabled) instance.post(path, handleGet(opConfig, ${modelName}FindUnique, parseBodyAsQueryHook))
369
383
  }
370
- }
371
384
 
372
- if (config.enableAll || config.createManyAndReturn) {
373
- const opConfig: OperationConfigLike = (config.createManyAndReturn as OperationConfigLike | undefined) ?? defaultOpConfig
374
- const path = basePath ? \`\${basePath}/many/return\` : '/many/return'
375
- fastify.post(path, handleWrite(opConfig, ${modelName}CreateManyAndReturn))
376
- }
385
+ if (isEnabled(config.findMany)) {
386
+ const opConfig: OperationConfigLike = (config.findMany as OperationConfigLike | undefined) ?? defaultOpConfig
387
+ const path = basePath || '/'
388
+ instance.get(path, handleGet(opConfig, ${modelName}FindMany, parseQueryHook))
389
+ if (postReadsEnabled) {
390
+ const postPath = basePath ? \`\${basePath}/read\` : '/read'
391
+ instance.post(postPath, handleGet(opConfig, ${modelName}FindMany, parseBodyAsQueryHook))
392
+ }
393
+ }
377
394
 
378
- if (config.enableAll || config.createMany) {
379
- const opConfig: OperationConfigLike = (config.createMany as OperationConfigLike | undefined) ?? defaultOpConfig
380
- const path = basePath ? \`\${basePath}/many\` : '/many'
381
- fastify.post(path, handleWrite(opConfig, ${modelName}CreateMany))
382
- }
395
+ if (isEnabled(config.createManyAndReturn)) {
396
+ const opConfig: OperationConfigLike = (config.createManyAndReturn as OperationConfigLike | undefined) ?? defaultOpConfig
397
+ const path = basePath ? \`\${basePath}/many/return\` : '/many/return'
398
+ instance.post(path, handleWrite(opConfig, ${modelName}CreateManyAndReturn))
399
+ }
383
400
 
384
- if (config.enableAll || config.create) {
385
- const opConfig: OperationConfigLike = (config.create as OperationConfigLike | undefined) ?? defaultOpConfig
386
- const path = basePath || '/'
387
- fastify.post(path, handleWrite(opConfig, ${modelName}Create))
388
- }
401
+ if (isEnabled(config.createMany)) {
402
+ const opConfig: OperationConfigLike = (config.createMany as OperationConfigLike | undefined) ?? defaultOpConfig
403
+ const path = basePath ? \`\${basePath}/many\` : '/many'
404
+ instance.post(path, handleWrite(opConfig, ${modelName}CreateMany))
405
+ }
389
406
 
390
- if (config.enableAll || config.updateManyAndReturn) {
391
- const opConfig: OperationConfigLike = (config.updateManyAndReturn as OperationConfigLike | undefined) ?? defaultOpConfig
392
- const path = basePath ? \`\${basePath}/many/return\` : '/many/return'
393
- fastify.put(path, handleWrite(opConfig, ${modelName}UpdateManyAndReturn))
394
- }
407
+ if (isEnabled(config.create)) {
408
+ const opConfig: OperationConfigLike = (config.create as OperationConfigLike | undefined) ?? defaultOpConfig
409
+ const path = basePath || '/'
410
+ instance.post(path, handleWrite(opConfig, ${modelName}Create))
411
+ }
395
412
 
396
- if (config.enableAll || config.updateMany) {
397
- const opConfig: OperationConfigLike = (config.updateMany as OperationConfigLike | undefined) ?? defaultOpConfig
398
- const path = basePath ? \`\${basePath}/many\` : '/many'
399
- fastify.put(path, handleWrite(opConfig, ${modelName}UpdateMany))
400
- }
413
+ if (isEnabled(config.updateManyAndReturn)) {
414
+ const opConfig: OperationConfigLike = (config.updateManyAndReturn as OperationConfigLike | undefined) ?? defaultOpConfig
415
+ const path = basePath ? \`\${basePath}/many/return\` : '/many/return'
416
+ instance.put(path, handleWrite(opConfig, ${modelName}UpdateManyAndReturn))
417
+ }
401
418
 
402
- if (config.enableAll || config.update) {
403
- const opConfig: OperationConfigLike = (config.update as OperationConfigLike | undefined) ?? defaultOpConfig
404
- const path = basePath || '/'
405
- fastify.put(path, handleWrite(opConfig, ${modelName}Update))
406
- }
419
+ if (isEnabled(config.updateMany)) {
420
+ const opConfig: OperationConfigLike = (config.updateMany as OperationConfigLike | undefined) ?? defaultOpConfig
421
+ const path = basePath ? \`\${basePath}/many\` : '/many'
422
+ instance.put(path, handleWrite(opConfig, ${modelName}UpdateMany))
423
+ }
407
424
 
408
- if (config.enableAll || config.upsert) {
409
- const opConfig: OperationConfigLike = (config.upsert as OperationConfigLike | undefined) ?? defaultOpConfig
410
- const path = basePath || '/'
411
- fastify.patch(path, handleWrite(opConfig, ${modelName}Upsert))
412
- }
425
+ if (isEnabled(config.update)) {
426
+ const opConfig: OperationConfigLike = (config.update as OperationConfigLike | undefined) ?? defaultOpConfig
427
+ const path = basePath || '/'
428
+ instance.put(path, handleWrite(opConfig, ${modelName}Update))
429
+ }
413
430
 
414
- if (config.enableAll || config.deleteMany) {
415
- const opConfig: OperationConfigLike = (config.deleteMany as OperationConfigLike | undefined) ?? defaultOpConfig
416
- const path = basePath ? \`\${basePath}/many\` : '/many'
417
- fastify.delete(path, handleWrite(opConfig, ${modelName}DeleteMany))
418
- }
431
+ if (isEnabled(config.upsert)) {
432
+ const opConfig: OperationConfigLike = (config.upsert as OperationConfigLike | undefined) ?? defaultOpConfig
433
+ const path = basePath || '/'
434
+ instance.patch(path, handleWrite(opConfig, ${modelName}Upsert))
435
+ }
419
436
 
420
- if (config.enableAll || config.delete) {
421
- const opConfig: OperationConfigLike = (config.delete as OperationConfigLike | undefined) ?? defaultOpConfig
422
- const path = basePath || '/'
423
- fastify.delete(path, handleWrite(opConfig, ${modelName}Delete))
424
- }
437
+ if (isEnabled(config.deleteMany)) {
438
+ const opConfig: OperationConfigLike = (config.deleteMany as OperationConfigLike | undefined) ?? defaultOpConfig
439
+ const path = basePath ? \`\${basePath}/many\` : '/many'
440
+ instance.delete(path, handleWrite(opConfig, ${modelName}DeleteMany))
441
+ }
442
+
443
+ if (isEnabled(config.delete)) {
444
+ const opConfig: OperationConfigLike = (config.delete as OperationConfigLike | undefined) ?? defaultOpConfig
445
+ const path = basePath || '/'
446
+ instance.delete(path, handleWrite(opConfig, ${modelName}Delete))
447
+ }
448
+
449
+ if (config.updateEach) {
450
+ const opConfig: OperationConfigLike = (config.updateEach as OperationConfigLike | undefined) ?? defaultOpConfig
451
+ if ((!opConfig.before || opConfig.before.length === 0) && _env.NODE_ENV !== 'production') {
452
+ console.warn(
453
+ '[${modelName}Router] updateEach is enabled without a before hook. ' +
454
+ 'This endpoint bypasses guard shapes and should be protected by authentication middleware.',
455
+ )
456
+ }
457
+ const path = basePath ? \`\${basePath}/each\` : '/each'
458
+ instance.post(path, async (request: FastifyRequest, reply: FastifyReply) => {
459
+ try {
460
+ makeShapeHook(config, opConfig)(request)
461
+ const { before = [], after = [] } = opConfig
462
+ if (await runHooks(before, request, reply)) return
463
+ await ${modelName}UpdateEach(request, reply)
464
+ if (await runHooks(after, request, reply)) return
465
+ sendResult(request, reply)
466
+ } catch (error: unknown) {
467
+ sendError(reply, error)
468
+ }
469
+ })
470
+ }
471
+ })
425
472
  }
426
473
  `
427
474
  }