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 generateHonoRouterFunction({
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 generateHonoRouterFunction({
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
@@ -48,10 +50,9 @@ export function generateHonoRouterFunction({
48
50
  }))
49
51
 
50
52
  return `import { Hono } from 'hono'
51
- import type { Context, Next } from 'hono'
53
+ import type { Context } from 'hono'
52
54
  import type { ContentfulStatusCode } from 'hono/utils/http-status'
53
55
  import { HTTPException } from 'hono/http-exception'
54
- import { startQueryBuilder } from '../queryBuilder${ext}'
55
56
  import {
56
57
  ${modelName}FindUnique,
57
58
  ${modelName}FindUniqueOrThrow,
@@ -71,40 +72,41 @@ import {
71
72
  ${modelName}Aggregate,
72
73
  ${modelName}Count,
73
74
  ${modelName}GroupBy,
75
+ ${modelName}UpdateEach,
74
76
  } from './${modelName}Handlers${ext}'
75
77
  import type {
76
78
  RouteConfig,
77
- HonoHookHandler,
79
+ HonoBeforeHook,
80
+ HonoAfterHook,
78
81
  HonoEnvBase,
79
82
  HonoInternalVariables,
80
83
  GeneratedHonoEnv,
81
84
  WriteStrategy,
82
- FindManyPaginatedMode,
83
85
  PaginationConfig,
84
86
  } from '../routeConfig.target${ext}'
85
87
  import { parseQueryParams } from '../parseQueryParams${ext}'
86
- import { sanitizeKeys, normalizePrefix, getEnv } from '../misc${ext}'
88
+ import { normalizePrefix, getEnv, sanitizeKeys } from '../misc${ext}'
87
89
  import { buildModelOpenApi } from '../buildModelOpenApi${ext}'
90
+ import { validateCountSourceWhere } from '../routeConfig${ext}'
88
91
  import {
89
92
  mapError,
90
93
  transformResult,
91
94
  mergePaginationConfig,
92
- type OperationContext,
93
95
  } from '../operationRuntime${ext}'
94
96
 
95
- ${generateRouteConfigType(modelName, 'HonoHookHandler', guardShapesImport, importStyle, 'hono')}
97
+ ${generateRouteConfigType(modelName, 'HonoBeforeHook', guardShapesImport, importStyle, 'hono')}
96
98
  const _env = getEnv()
97
99
 
98
100
  const WRITE_STRATEGY: WriteStrategy = '${writeStrategy}'
99
- const FIND_MANY_PAGINATED_MODE: FindManyPaginatedMode = '${findManyPaginatedMode}'
101
+ const DROP_GUARD = ${dropGuard} || _env.E2E === 'true'
100
102
 
101
103
  const MODEL_FIELDS = ${JSON.stringify(fieldsMeta, null, 2)} as const
102
104
 
103
105
  const MODEL_ENUMS = ${JSON.stringify(enumsMeta, null, 2)} as const
104
106
 
105
107
  type OperationConfigLike<TEnv extends HonoEnvBase> = {
106
- before?: HonoHookHandler<TEnv>[]
107
- after?: HonoHookHandler<TEnv>[]
108
+ before?: HonoBeforeHook<TEnv>[]
109
+ after?: HonoAfterHook<TEnv>[]
108
110
  shape?: Record<string, unknown>
109
111
  pagination?: Partial<PaginationConfig>
110
112
  }
@@ -114,20 +116,7 @@ const defaultOpConfig = Object.freeze({
114
116
  after: Object.freeze([]),
115
117
  }) as unknown as OperationConfigLike<HonoEnvBase>
116
118
 
117
- type HandlerContext = Context<{ Variables: HonoInternalVariables & { findManyPaginatedMode?: FindManyPaginatedMode } }>
118
-
119
- function isQueryBuilderEnabled(config: RouteConfig): boolean {
120
- if (config.queryBuilder === false) return false
121
- if (typeof config.queryBuilder === 'object' && config.queryBuilder.enabled === false) return false
122
- if (_env.NODE_ENV === 'production') return false
123
- return true
124
- }
125
-
126
- function getQueryBuilderConfig(config: RouteConfig) {
127
- if (config.queryBuilder === false) return null
128
- if (typeof config.queryBuilder === 'object') return config.queryBuilder
129
- return {}
130
- }
119
+ type HandlerContext = Context<{ Variables: HonoInternalVariables }>
131
120
 
132
121
  async function parseQueryMiddleware(c: HandlerContext): Promise<void> {
133
122
  const raw = c.req.query() as Record<string, unknown>
@@ -159,7 +148,20 @@ async function parseWriteBodyMiddleware(c: HandlerContext): Promise<void> {
159
148
  if (!body || typeof body !== 'object' || Array.isArray(body)) {
160
149
  throw new HTTPException(400, { message: 'Request body must be a JSON object' })
161
150
  }
162
- c.set('body', sanitizeKeys(body as Record<string, unknown>))
151
+ c.set('body', body)
152
+ }
153
+
154
+ async function parseUpdateEachBodyMiddleware(c: HandlerContext): Promise<void> {
155
+ let body: unknown
156
+ try {
157
+ body = await c.req.json()
158
+ } catch {
159
+ throw new HTTPException(400, { message: 'updateEach body must be an array of { where, data } items' })
160
+ }
161
+ if (!Array.isArray(body)) {
162
+ throw new HTTPException(400, { message: 'updateEach body must be an array of { where, data } items' })
163
+ }
164
+ c.set('body', body)
163
165
  }
164
166
 
165
167
  function makeShapeMiddleware<TCtx, TPrisma, TEnv extends HonoEnvBase>(
@@ -171,37 +173,34 @@ function makeShapeMiddleware<TCtx, TPrisma, TEnv extends HonoEnvBase>(
171
173
  if (merged) {
172
174
  c.set('routeConfig', { pagination: merged })
173
175
  }
174
- ;(c as unknown as HandlerContext).set('findManyPaginatedMode', FIND_MANY_PAGINATED_MODE)
175
176
  const headerName = config.guard?.variantHeader || 'x-api-variant'
176
177
  const headerValue = c.req.header(headerName)
177
178
  const caller = config.guard?.resolveVariant?.(c) ?? headerValue ?? undefined
178
179
  if (caller) c.set('guardCaller', caller)
179
- if (opConfig.shape) {
180
+ if (opConfig.shape && !DROP_GUARD) {
180
181
  c.set('guardShape', opConfig.shape)
181
182
  }
182
183
  }
183
184
  }
184
185
 
185
- async function runHooks<TEnv extends HonoEnvBase>(
186
- hooks: HonoHookHandler<TEnv>[],
186
+ async function runBeforeHooks<TEnv extends HonoEnvBase>(
187
+ hooks: HonoBeforeHook<TEnv>[],
187
188
  c: Context<GeneratedHonoEnv<TEnv>>,
188
189
  ): Promise<Response | undefined> {
189
190
  for (const hook of hooks) {
190
- let advanced = false
191
- const next: Next = async () => {
192
- advanced = true
193
- }
194
- const result = await hook(c, next)
191
+ const result = await hook(c)
192
+ if (result instanceof Response) return result
193
+ }
194
+ return undefined
195
+ }
196
+
197
+ async function runAfterHooks<TEnv extends HonoEnvBase>(
198
+ hooks: HonoAfterHook<TEnv>[],
199
+ c: Context<GeneratedHonoEnv<TEnv>>,
200
+ ): Promise<Response | undefined> {
201
+ for (const hook of hooks) {
202
+ const result = await hook(c)
195
203
  if (result instanceof Response) return result
196
- if (!advanced) {
197
- if (_env.NODE_ENV !== 'production') {
198
- console.warn(
199
- '[hono-router] Hook returned without calling next() or returning a Response. ' +
200
- 'Use \`return c.json(...)\` to short-circuit, or \`await next()\` to continue.',
201
- )
202
- }
203
- return c.body(null) ?? undefined
204
- }
205
204
  }
206
205
  return undefined
207
206
  }
@@ -216,69 +215,81 @@ function sendResult(c: HandlerContext): Response {
216
215
  }
217
216
 
218
217
  function sendError(c: HandlerContext, error: unknown): Response {
218
+ if (error instanceof HTTPException) {
219
+ return c.json({ message: error.message }, error.status as ContentfulStatusCode)
220
+ }
219
221
  const httpError = mapError(error)
220
222
  return c.json({ message: httpError.message }, httpError.status as ContentfulStatusCode)
221
223
  }
222
224
 
223
225
  export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extends HonoEnvBase = HonoEnvBase>(config: ${modelName}RouteConfig<TCtx, TPrisma, TEnv> = {}): Hono<GeneratedHonoEnv<TEnv>> {
226
+ validateCountSourceWhere(config.pagination?.countSource, '${modelName} pagination')
227
+ validateCountSourceWhere(
228
+ (config.findManyPaginated && typeof config.findManyPaginated === 'object' ? config.findManyPaginated : undefined)?.pagination?.countSource,
229
+ '${modelName} findManyPaginated pagination',
230
+ )
231
+
224
232
  const app = new Hono<GeneratedHonoEnv<TEnv>>()
225
233
 
234
+ const isEnabled = (value: unknown): boolean => value !== false && !!(config.enableAll || value)
235
+
226
236
  const customPrefix = normalizePrefix(config.customUrlPrefix || '')
227
237
  const modelPrefix = config.addModelPrefix !== false ? '/${modelNameLower}' : ''
228
238
  const basePath = customPrefix + modelPrefix
229
239
 
230
240
  const openApiDisabled = config.disableOpenApi === true
231
241
  || (config.disableOpenApi !== false && (
232
- _env.DISABLE_OPENAPI === 'true'
233
- || _env.NODE_ENV === 'production'
242
+ _env.NODE_ENV === 'production'
243
+ || _env.DISABLE_OPENAPI === 'true'
234
244
  ))
235
245
 
236
246
  const postReadsEnabled = !config.disablePostReads
237
247
 
238
- const openApiJsonSpec = openApiDisabled
239
- ? null
240
- : buildModelOpenApi(
248
+ let _openApiJsonCache: unknown = undefined
249
+ const getOpenApiJson = (): unknown => {
250
+ if (_openApiJsonCache === undefined) {
251
+ _openApiJsonCache = buildModelOpenApi(
241
252
  '${modelName}',
242
253
  MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
243
254
  MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
244
255
  config as RouteConfig,
245
256
  { format: 'json', writeStrategy: WRITE_STRATEGY },
246
257
  )
247
- const openApiYamlSpec = openApiDisabled
248
- ? null
249
- : buildModelOpenApi(
258
+ }
259
+ return _openApiJsonCache
260
+ }
261
+ let _openApiYamlCache: string | undefined = undefined
262
+ const getOpenApiYaml = (): string => {
263
+ if (_openApiYamlCache === undefined) {
264
+ _openApiYamlCache = buildModelOpenApi(
250
265
  '${modelName}',
251
266
  MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
252
267
  MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
253
268
  config as RouteConfig,
254
269
  { format: 'yaml', writeStrategy: WRITE_STRATEGY },
255
- )
256
-
257
- if (isQueryBuilderEnabled(config as RouteConfig)) {
258
- const qbConfig = getQueryBuilderConfig(config as RouteConfig)
259
- if (qbConfig) {
260
- try {
261
- startQueryBuilder(qbConfig)
262
- } catch (err) {
263
- if (_env.NODE_ENV !== 'production') console.warn('[query-builder]', err)
264
- }
270
+ ) as string
265
271
  }
272
+ return _openApiYamlCache
273
+ }
274
+
275
+ if (config.queryBuilder && config.queryBuilder !== false && _env.NODE_ENV !== 'production') {
276
+ console.warn(
277
+ '[${modelName}Router] queryBuilder config is present but Hono target does not auto-start it. ' +
278
+ 'Run \`npx prisma-query-builder-ui\` in a separate process.',
279
+ )
266
280
  }
267
281
 
268
282
  app.onError((err, c) => {
269
- if (err instanceof HTTPException) {
270
- return c.json({ message: err.message }, err.status as ContentfulStatusCode)
271
- }
272
283
  return sendError(c as HandlerContext, err)
273
284
  })
274
285
 
275
286
  if (!openApiDisabled) {
276
287
  const openapiJsonPath = basePath ? \`\${basePath}/openapi.json\` : '/openapi.json'
277
288
  const openapiYamlPath = basePath ? \`\${basePath}/openapi.yaml\` : '/openapi.yaml'
278
- app.get(openapiJsonPath, (c) => c.json(openApiJsonSpec as Record<string, unknown>))
289
+ app.get(openapiJsonPath, (c) => c.json(getOpenApiJson() as Record<string, unknown>))
279
290
  app.get(openapiYamlPath, (c) => {
280
291
  c.header('Content-Type', 'application/yaml')
281
- return c.body(openApiYamlSpec as string)
292
+ return c.body(getOpenApiYaml())
282
293
  })
283
294
  }
284
295
 
@@ -291,10 +302,10 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
291
302
  await parseFn(c as unknown as HandlerContext)
292
303
  makeShapeMiddleware<TCtx, TPrisma, TEnv>(config, opConfig)(c)
293
304
  const { before = [], after = [] } = opConfig
294
- const beforeResp = await runHooks<TEnv>(before, c)
305
+ const beforeResp = await runBeforeHooks<TEnv>(before, c)
295
306
  if (beforeResp) return beforeResp
296
307
  await handlerFn(c as unknown as HandlerContext)
297
- const afterResp = await runHooks<TEnv>(after, c)
308
+ const afterResp = await runAfterHooks<TEnv>(after, c)
298
309
  if (afterResp) return afterResp
299
310
  return sendResult(c as unknown as HandlerContext)
300
311
  } catch (error: unknown) {
@@ -310,10 +321,10 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
310
321
  await parseWriteBodyMiddleware(c as unknown as HandlerContext)
311
322
  makeShapeMiddleware<TCtx, TPrisma, TEnv>(config, opConfig)(c)
312
323
  const { before = [], after = [] } = opConfig
313
- const beforeResp = await runHooks<TEnv>(before, c)
324
+ const beforeResp = await runBeforeHooks<TEnv>(before, c)
314
325
  if (beforeResp) return beforeResp
315
326
  await handlerFn(c as unknown as HandlerContext)
316
- const afterResp = await runHooks<TEnv>(after, c)
327
+ const afterResp = await runAfterHooks<TEnv>(after, c)
317
328
  if (afterResp) return afterResp
318
329
  return sendResult(c as unknown as HandlerContext)
319
330
  } catch (error: unknown) {
@@ -326,55 +337,55 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
326
337
  ?? (defaultOpConfig as OperationConfigLike<TEnv>)
327
338
  }
328
339
 
329
- if (config.enableAll || config.findFirst) {
340
+ if (isEnabled(config.findFirst)) {
330
341
  const opConfig = opFor('findFirst')
331
342
  const path = basePath ? \`\${basePath}/first\` : '/first'
332
343
  app.get(path, handleRead(opConfig, ${modelName}FindFirst, parseQueryMiddleware))
333
344
  if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}FindFirst, parseBodyAsQueryMiddleware))
334
345
  }
335
- if (config.enableAll || config.findFirstOrThrow) {
346
+ if (isEnabled(config.findFirstOrThrow)) {
336
347
  const opConfig = opFor('findFirstOrThrow')
337
348
  const path = basePath ? \`\${basePath}/first/strict\` : '/first/strict'
338
349
  app.get(path, handleRead(opConfig, ${modelName}FindFirstOrThrow, parseQueryMiddleware))
339
350
  if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}FindFirstOrThrow, parseBodyAsQueryMiddleware))
340
351
  }
341
- if (config.enableAll || config.findManyPaginated) {
352
+ if (isEnabled(config.findManyPaginated)) {
342
353
  const opConfig = opFor('findManyPaginated')
343
354
  const path = basePath ? \`\${basePath}/paginated\` : '/paginated'
344
355
  app.get(path, handleRead(opConfig, ${modelName}FindManyPaginated, parseQueryMiddleware))
345
356
  if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}FindManyPaginated, parseBodyAsQueryMiddleware))
346
357
  }
347
- if (config.enableAll || config.aggregate) {
358
+ if (isEnabled(config.aggregate)) {
348
359
  const opConfig = opFor('aggregate')
349
360
  const path = basePath ? \`\${basePath}/aggregate\` : '/aggregate'
350
361
  app.get(path, handleRead(opConfig, ${modelName}Aggregate, parseQueryMiddleware))
351
362
  if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}Aggregate, parseBodyAsQueryMiddleware))
352
363
  }
353
- if (config.enableAll || config.count) {
364
+ if (isEnabled(config.count)) {
354
365
  const opConfig = opFor('count')
355
366
  const path = basePath ? \`\${basePath}/count\` : '/count'
356
367
  app.get(path, handleRead(opConfig, ${modelName}Count, parseQueryMiddleware))
357
368
  if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}Count, parseBodyAsQueryMiddleware))
358
369
  }
359
- if (config.enableAll || config.groupBy) {
370
+ if (isEnabled(config.groupBy)) {
360
371
  const opConfig = opFor('groupBy')
361
372
  const path = basePath ? \`\${basePath}/groupby\` : '/groupby'
362
373
  app.get(path, handleRead(opConfig, ${modelName}GroupBy, parseQueryMiddleware))
363
374
  if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}GroupBy, parseBodyAsQueryMiddleware))
364
375
  }
365
- if (config.enableAll || config.findUniqueOrThrow) {
376
+ if (isEnabled(config.findUniqueOrThrow)) {
366
377
  const opConfig = opFor('findUniqueOrThrow')
367
378
  const path = basePath ? \`\${basePath}/unique/strict\` : '/unique/strict'
368
379
  app.get(path, handleRead(opConfig, ${modelName}FindUniqueOrThrow, parseQueryMiddleware))
369
380
  if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}FindUniqueOrThrow, parseBodyAsQueryMiddleware))
370
381
  }
371
- if (config.enableAll || config.findUnique) {
382
+ if (isEnabled(config.findUnique)) {
372
383
  const opConfig = opFor('findUnique')
373
384
  const path = basePath ? \`\${basePath}/unique\` : '/unique'
374
385
  app.get(path, handleRead(opConfig, ${modelName}FindUnique, parseQueryMiddleware))
375
386
  if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}FindUnique, parseBodyAsQueryMiddleware))
376
387
  }
377
- if (config.enableAll || config.findMany) {
388
+ if (isEnabled(config.findMany)) {
378
389
  const opConfig = opFor('findMany')
379
390
  const path = basePath || '/'
380
391
  app.get(path, handleRead(opConfig, ${modelName}FindMany, parseQueryMiddleware))
@@ -384,52 +395,78 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
384
395
  }
385
396
  }
386
397
 
387
- if (config.enableAll || config.createManyAndReturn) {
398
+ if (isEnabled(config.createManyAndReturn)) {
388
399
  const opConfig = opFor('createManyAndReturn')
389
400
  const path = basePath ? \`\${basePath}/many/return\` : '/many/return'
390
401
  app.post(path, handleWrite(opConfig, ${modelName}CreateManyAndReturn))
391
402
  }
392
- if (config.enableAll || config.createMany) {
403
+ if (isEnabled(config.createMany)) {
393
404
  const opConfig = opFor('createMany')
394
405
  const path = basePath ? \`\${basePath}/many\` : '/many'
395
406
  app.post(path, handleWrite(opConfig, ${modelName}CreateMany))
396
407
  }
397
- if (config.enableAll || config.create) {
408
+ if (isEnabled(config.create)) {
398
409
  const opConfig = opFor('create')
399
410
  const path = basePath || '/'
400
411
  app.post(path, handleWrite(opConfig, ${modelName}Create))
401
412
  }
402
- if (config.enableAll || config.updateManyAndReturn) {
413
+ if (isEnabled(config.updateManyAndReturn)) {
403
414
  const opConfig = opFor('updateManyAndReturn')
404
415
  const path = basePath ? \`\${basePath}/many/return\` : '/many/return'
405
416
  app.put(path, handleWrite(opConfig, ${modelName}UpdateManyAndReturn))
406
417
  }
407
- if (config.enableAll || config.updateMany) {
418
+ if (isEnabled(config.updateMany)) {
408
419
  const opConfig = opFor('updateMany')
409
420
  const path = basePath ? \`\${basePath}/many\` : '/many'
410
421
  app.put(path, handleWrite(opConfig, ${modelName}UpdateMany))
411
422
  }
412
- if (config.enableAll || config.update) {
423
+ if (isEnabled(config.update)) {
413
424
  const opConfig = opFor('update')
414
425
  const path = basePath || '/'
415
426
  app.put(path, handleWrite(opConfig, ${modelName}Update))
416
427
  }
417
- if (config.enableAll || config.upsert) {
428
+ if (isEnabled(config.upsert)) {
418
429
  const opConfig = opFor('upsert')
419
430
  const path = basePath || '/'
420
431
  app.patch(path, handleWrite(opConfig, ${modelName}Upsert))
421
432
  }
422
- if (config.enableAll || config.deleteMany) {
433
+ if (isEnabled(config.deleteMany)) {
423
434
  const opConfig = opFor('deleteMany')
424
435
  const path = basePath ? \`\${basePath}/many\` : '/many'
425
436
  app.delete(path, handleWrite(opConfig, ${modelName}DeleteMany))
426
437
  }
427
- if (config.enableAll || config.delete) {
438
+ if (isEnabled(config.delete)) {
428
439
  const opConfig = opFor('delete')
429
440
  const path = basePath || '/'
430
441
  app.delete(path, handleWrite(opConfig, ${modelName}Delete))
431
442
  }
432
443
 
444
+ if (config.updateEach) {
445
+ const opConfig = (config.updateEach as unknown as OperationConfigLike<TEnv> | undefined) ?? (defaultOpConfig as OperationConfigLike<TEnv>)
446
+ if ((!opConfig.before || opConfig.before.length === 0) && _env.NODE_ENV !== 'production') {
447
+ console.warn(
448
+ '[${modelName}Router] updateEach is enabled without a before hook. ' +
449
+ 'This endpoint bypasses guard shapes and should be protected by authentication middleware.',
450
+ )
451
+ }
452
+ const path = basePath ? \`\${basePath}/each\` : '/each'
453
+ app.post(path, async (c: Context<GeneratedHonoEnv<TEnv>>): Promise<Response> => {
454
+ try {
455
+ await parseUpdateEachBodyMiddleware(c as unknown as HandlerContext)
456
+ makeShapeMiddleware<TCtx, TPrisma, TEnv>(config, opConfig)(c)
457
+ const { before = [], after = [] } = opConfig
458
+ const beforeResp = await runBeforeHooks<TEnv>(before, c)
459
+ if (beforeResp) return beforeResp
460
+ await ${modelName}UpdateEach(c as unknown as HandlerContext)
461
+ const afterResp = await runAfterHooks<TEnv>(after, c)
462
+ if (afterResp) return afterResp
463
+ return sendResult(c as unknown as HandlerContext)
464
+ } catch (error: unknown) {
465
+ return sendError(c as unknown as HandlerContext, error)
466
+ }
467
+ })
468
+ }
469
+
433
470
  return app
434
471
  }
435
472
  `
package/src/index.ts CHANGED
@@ -32,7 +32,7 @@ import {
32
32
  FindManyPaginatedMode,
33
33
  } from './constants'
34
34
 
35
- const GENERATOR_OFF_RE = /\bgenerator off\b/
35
+ const GENERATOR_OFF_RE = /^\s*generator off\s*$/m
36
36
 
37
37
  function getTarget(options: GeneratorOptions): Target {
38
38
  const raw = String(
@@ -79,6 +79,17 @@ function getFindManyPaginatedMode(
79
79
  )
80
80
  }
81
81
 
82
+ function getDropGuard(options: GeneratorOptions): boolean {
83
+ const raw = (options.generator.config as Record<string, unknown>).dropGuard
84
+ if (raw === undefined || raw === null || raw === '') return false
85
+ const lower = String(raw).toLowerCase()
86
+ if (lower === 'true' || lower === '1') return true
87
+ if (lower === 'false' || lower === '0') return false
88
+ throw new Error(
89
+ `Invalid dropGuard "${raw}". Expected "true" or "false".`,
90
+ )
91
+ }
92
+
82
93
  function validateClientGeneratorPresent(options: GeneratorOptions): void {
83
94
  getRelativeClientPath(
84
95
  options,
@@ -90,7 +101,7 @@ generatorHandler({
90
101
  onManifest() {
91
102
  return {
92
103
  version: require('../package.json').version,
93
- defaultOutput: '../generated/express',
104
+ defaultOutput: '../generated/output',
94
105
  prettyName: GENERATOR_NAME,
95
106
  }
96
107
  },
@@ -99,12 +110,13 @@ generatorHandler({
99
110
  const target = getTarget(options)
100
111
  const writeStrategy = getWriteStrategy(options)
101
112
  const findManyPaginatedMode = getFindManyPaginatedMode(options)
113
+ const dropGuard = getDropGuard(options)
102
114
 
103
115
  const manifestDefaultAbs = path.resolve(
104
116
  __dirname,
105
117
  '..',
106
118
  'generated',
107
- target,
119
+ 'output',
108
120
  )
109
121
  const currentOutput = options.generator.output?.value
110
122
  const isUnsetOrManifestDefault =
@@ -124,6 +136,9 @@ generatorHandler({
124
136
  console.log(` Import style: ${importStyle}`)
125
137
  console.log(` Write strategy: ${writeStrategy}`)
126
138
  console.log(` findManyPaginated mode: ${findManyPaginatedMode}`)
139
+ console.log(
140
+ ` Drop guard (generator): ${dropGuard}${dropGuard ? '' : ' (runtime E2E=true will also drop guard)'}`,
141
+ )
127
142
 
128
143
  if (options.dmmf.datamodel.models.length > 0) {
129
144
  validateClientGeneratorPresent(options)
@@ -151,7 +166,9 @@ generatorHandler({
151
166
  }
152
167
  modelNames.push(model.name)
153
168
 
154
- const guardShapesImport = getGuardShapesImport(options, model.name)
169
+ const guardShapesImport = dropGuard
170
+ ? null
171
+ : getGuardShapesImport(options, model.name)
155
172
 
156
173
  await writeFileSafely({
157
174
  content: generateModelCore({
@@ -181,6 +198,7 @@ generatorHandler({
181
198
  importStyle,
182
199
  writeStrategy,
183
200
  findManyPaginatedMode,
201
+ dropGuard,
184
202
  })
185
203
  : target === 'hono'
186
204
  ? generateHonoRouterFunction({
@@ -190,6 +208,7 @@ generatorHandler({
190
208
  importStyle,
191
209
  writeStrategy,
192
210
  findManyPaginatedMode,
211
+ dropGuard,
193
212
  })
194
213
  : generateRouterFunction({
195
214
  model: model as DMMF.Model,
@@ -198,6 +217,7 @@ generatorHandler({
198
217
  importStyle,
199
218
  writeStrategy,
200
219
  findManyPaginatedMode,
220
+ dropGuard,
201
221
  })
202
222
 
203
223
  await writeFileSafely({
@@ -258,4 +278,4 @@ generatorHandler({
258
278
  console.log(`✓ ${modelNames.length} models (${target})`)
259
279
  console.log('')
260
280
  },
261
- })
281
+ })