prisma-generator-express 1.57.0 → 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 (41) hide show
  1. package/README.md +111 -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 +7 -1
  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 +12 -7
  13. package/dist/generators/generateRouteConfigType.js.map +1 -1
  14. package/dist/generators/generateRouter.js +57 -32
  15. package/dist/generators/generateRouter.js.map +1 -1
  16. package/dist/generators/generateRouterFastify.js +235 -191
  17. package/dist/generators/generateRouterFastify.js.map +1 -1
  18. package/dist/generators/generateRouterHono.js +121 -87
  19. package/dist/generators/generateRouterHono.js.map +1 -1
  20. package/dist/index.js +4 -4
  21. package/dist/index.js.map +1 -1
  22. package/package.json +1 -1
  23. package/src/copy/autoIncludeRuntime.ts +9 -5
  24. package/src/copy/buildModelOpenApi.ts +96 -0
  25. package/src/copy/docsRenderer.ts +577 -174
  26. package/src/copy/materializedRouter.ts +40 -1
  27. package/src/copy/misc.ts +23 -6
  28. package/src/copy/operationDefinitions.ts +10 -0
  29. package/src/copy/operationRuntime.ts +28 -9
  30. package/src/copy/routeConfig.express.ts +9 -9
  31. package/src/copy/routeConfig.hono.ts +63 -5
  32. package/src/copy/routeConfig.ts +44 -22
  33. package/src/generators/generateFastifyHandler.ts +12 -0
  34. package/src/generators/generateHonoHandler.ts +15 -20
  35. package/src/generators/generateImportPrismaStatement.ts +10 -1
  36. package/src/generators/generateOperationCore.ts +58 -17
  37. package/src/generators/generateRouteConfigType.ts +13 -8
  38. package/src/generators/generateRouter.ts +57 -32
  39. package/src/generators/generateRouterFastify.ts +235 -191
  40. package/src/generators/generateRouterHono.ts +121 -87
  41. package/src/index.ts +6 -4
@@ -50,10 +50,9 @@ export function generateHonoRouterFunction({
50
50
  }))
51
51
 
52
52
  return `import { Hono } from 'hono'
53
- import type { Context, Next } from 'hono'
53
+ import type { Context } from 'hono'
54
54
  import type { ContentfulStatusCode } from 'hono/utils/http-status'
55
55
  import { HTTPException } from 'hono/http-exception'
56
- import { startQueryBuilder } from '../queryBuilder${ext}'
57
56
  import {
58
57
  ${modelName}FindUnique,
59
58
  ${modelName}FindUniqueOrThrow,
@@ -73,32 +72,32 @@ import {
73
72
  ${modelName}Aggregate,
74
73
  ${modelName}Count,
75
74
  ${modelName}GroupBy,
75
+ ${modelName}UpdateEach,
76
76
  } from './${modelName}Handlers${ext}'
77
77
  import type {
78
78
  RouteConfig,
79
- HonoHookHandler,
79
+ HonoBeforeHook,
80
+ HonoAfterHook,
80
81
  HonoEnvBase,
81
82
  HonoInternalVariables,
82
83
  GeneratedHonoEnv,
83
84
  WriteStrategy,
84
- FindManyPaginatedMode,
85
85
  PaginationConfig,
86
86
  } from '../routeConfig.target${ext}'
87
87
  import { parseQueryParams } from '../parseQueryParams${ext}'
88
- import { sanitizeKeys, normalizePrefix, getEnv } from '../misc${ext}'
88
+ import { normalizePrefix, getEnv, sanitizeKeys } from '../misc${ext}'
89
89
  import { buildModelOpenApi } from '../buildModelOpenApi${ext}'
90
+ import { validateCountSourceWhere } from '../routeConfig${ext}'
90
91
  import {
91
92
  mapError,
92
93
  transformResult,
93
94
  mergePaginationConfig,
94
- type OperationContext,
95
95
  } from '../operationRuntime${ext}'
96
96
 
97
- ${generateRouteConfigType(modelName, 'HonoHookHandler', guardShapesImport, importStyle, 'hono')}
97
+ ${generateRouteConfigType(modelName, 'HonoBeforeHook', guardShapesImport, importStyle, 'hono')}
98
98
  const _env = getEnv()
99
99
 
100
100
  const WRITE_STRATEGY: WriteStrategy = '${writeStrategy}'
101
- const FIND_MANY_PAGINATED_MODE: FindManyPaginatedMode = '${findManyPaginatedMode}'
102
101
  const DROP_GUARD = ${dropGuard} || _env.E2E === 'true'
103
102
 
104
103
  const MODEL_FIELDS = ${JSON.stringify(fieldsMeta, null, 2)} as const
@@ -106,8 +105,8 @@ const MODEL_FIELDS = ${JSON.stringify(fieldsMeta, null, 2)} as const
106
105
  const MODEL_ENUMS = ${JSON.stringify(enumsMeta, null, 2)} as const
107
106
 
108
107
  type OperationConfigLike<TEnv extends HonoEnvBase> = {
109
- before?: HonoHookHandler<TEnv>[]
110
- after?: HonoHookHandler<TEnv>[]
108
+ before?: HonoBeforeHook<TEnv>[]
109
+ after?: HonoAfterHook<TEnv>[]
111
110
  shape?: Record<string, unknown>
112
111
  pagination?: Partial<PaginationConfig>
113
112
  }
@@ -117,20 +116,7 @@ const defaultOpConfig = Object.freeze({
117
116
  after: Object.freeze([]),
118
117
  }) as unknown as OperationConfigLike<HonoEnvBase>
119
118
 
120
- type HandlerContext = Context<{ Variables: HonoInternalVariables & { findManyPaginatedMode?: FindManyPaginatedMode } }>
121
-
122
- function isQueryBuilderEnabled(config: RouteConfig): boolean {
123
- if (config.queryBuilder === false) return false
124
- if (typeof config.queryBuilder === 'object' && config.queryBuilder.enabled === false) return false
125
- if (_env.NODE_ENV === 'production') return false
126
- return true
127
- }
128
-
129
- function getQueryBuilderConfig(config: RouteConfig) {
130
- if (config.queryBuilder === false) return null
131
- if (typeof config.queryBuilder === 'object') return config.queryBuilder
132
- return {}
133
- }
119
+ type HandlerContext = Context<{ Variables: HonoInternalVariables }>
134
120
 
135
121
  async function parseQueryMiddleware(c: HandlerContext): Promise<void> {
136
122
  const raw = c.req.query() as Record<string, unknown>
@@ -162,7 +148,20 @@ async function parseWriteBodyMiddleware(c: HandlerContext): Promise<void> {
162
148
  if (!body || typeof body !== 'object' || Array.isArray(body)) {
163
149
  throw new HTTPException(400, { message: 'Request body must be a JSON object' })
164
150
  }
165
- 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)
166
165
  }
167
166
 
168
167
  function makeShapeMiddleware<TCtx, TPrisma, TEnv extends HonoEnvBase>(
@@ -174,7 +173,6 @@ function makeShapeMiddleware<TCtx, TPrisma, TEnv extends HonoEnvBase>(
174
173
  if (merged) {
175
174
  c.set('routeConfig', { pagination: merged })
176
175
  }
177
- ;(c as unknown as HandlerContext).set('findManyPaginatedMode', FIND_MANY_PAGINATED_MODE)
178
176
  const headerName = config.guard?.variantHeader || 'x-api-variant'
179
177
  const headerValue = c.req.header(headerName)
180
178
  const caller = config.guard?.resolveVariant?.(c) ?? headerValue ?? undefined
@@ -185,26 +183,24 @@ function makeShapeMiddleware<TCtx, TPrisma, TEnv extends HonoEnvBase>(
185
183
  }
186
184
  }
187
185
 
188
- async function runHooks<TEnv extends HonoEnvBase>(
189
- hooks: HonoHookHandler<TEnv>[],
186
+ async function runBeforeHooks<TEnv extends HonoEnvBase>(
187
+ hooks: HonoBeforeHook<TEnv>[],
190
188
  c: Context<GeneratedHonoEnv<TEnv>>,
191
189
  ): Promise<Response | undefined> {
192
190
  for (const hook of hooks) {
193
- let advanced = false
194
- const next: Next = async () => {
195
- advanced = true
196
- }
197
- 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)
198
203
  if (result instanceof Response) return result
199
- if (!advanced) {
200
- if (_env.NODE_ENV !== 'production') {
201
- console.warn(
202
- '[hono-router] Hook returned without calling next() or returning a Response. ' +
203
- 'Use \`return c.json(...)\` to short-circuit, or \`await next()\` to continue.',
204
- )
205
- }
206
- return c.body(null) ?? undefined
207
- }
208
204
  }
209
205
  return undefined
210
206
  }
@@ -219,69 +215,81 @@ function sendResult(c: HandlerContext): Response {
219
215
  }
220
216
 
221
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
+ }
222
221
  const httpError = mapError(error)
223
222
  return c.json({ message: httpError.message }, httpError.status as ContentfulStatusCode)
224
223
  }
225
224
 
226
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
+
227
232
  const app = new Hono<GeneratedHonoEnv<TEnv>>()
228
233
 
234
+ const isEnabled = (value: unknown): boolean => value !== false && !!(config.enableAll || value)
235
+
229
236
  const customPrefix = normalizePrefix(config.customUrlPrefix || '')
230
237
  const modelPrefix = config.addModelPrefix !== false ? '/${modelNameLower}' : ''
231
238
  const basePath = customPrefix + modelPrefix
232
239
 
233
240
  const openApiDisabled = config.disableOpenApi === true
234
241
  || (config.disableOpenApi !== false && (
235
- _env.DISABLE_OPENAPI === 'true'
236
- || _env.NODE_ENV === 'production'
242
+ _env.NODE_ENV === 'production'
243
+ || _env.DISABLE_OPENAPI === 'true'
237
244
  ))
238
245
 
239
246
  const postReadsEnabled = !config.disablePostReads
240
247
 
241
- const openApiJsonSpec = openApiDisabled
242
- ? null
243
- : buildModelOpenApi(
248
+ let _openApiJsonCache: unknown = undefined
249
+ const getOpenApiJson = (): unknown => {
250
+ if (_openApiJsonCache === undefined) {
251
+ _openApiJsonCache = buildModelOpenApi(
244
252
  '${modelName}',
245
253
  MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
246
254
  MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
247
255
  config as RouteConfig,
248
256
  { format: 'json', writeStrategy: WRITE_STRATEGY },
249
257
  )
250
- const openApiYamlSpec = openApiDisabled
251
- ? null
252
- : buildModelOpenApi(
258
+ }
259
+ return _openApiJsonCache
260
+ }
261
+ let _openApiYamlCache: string | undefined = undefined
262
+ const getOpenApiYaml = (): string => {
263
+ if (_openApiYamlCache === undefined) {
264
+ _openApiYamlCache = buildModelOpenApi(
253
265
  '${modelName}',
254
266
  MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
255
267
  MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
256
268
  config as RouteConfig,
257
269
  { format: 'yaml', writeStrategy: WRITE_STRATEGY },
258
- )
259
-
260
- if (isQueryBuilderEnabled(config as RouteConfig)) {
261
- const qbConfig = getQueryBuilderConfig(config as RouteConfig)
262
- if (qbConfig) {
263
- try {
264
- startQueryBuilder(qbConfig)
265
- } catch (err) {
266
- if (_env.NODE_ENV !== 'production') console.warn('[query-builder]', err)
267
- }
270
+ ) as string
268
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
+ )
269
280
  }
270
281
 
271
282
  app.onError((err, c) => {
272
- if (err instanceof HTTPException) {
273
- return c.json({ message: err.message }, err.status as ContentfulStatusCode)
274
- }
275
283
  return sendError(c as HandlerContext, err)
276
284
  })
277
285
 
278
286
  if (!openApiDisabled) {
279
287
  const openapiJsonPath = basePath ? \`\${basePath}/openapi.json\` : '/openapi.json'
280
288
  const openapiYamlPath = basePath ? \`\${basePath}/openapi.yaml\` : '/openapi.yaml'
281
- app.get(openapiJsonPath, (c) => c.json(openApiJsonSpec as Record<string, unknown>))
289
+ app.get(openapiJsonPath, (c) => c.json(getOpenApiJson() as Record<string, unknown>))
282
290
  app.get(openapiYamlPath, (c) => {
283
291
  c.header('Content-Type', 'application/yaml')
284
- return c.body(openApiYamlSpec as string)
292
+ return c.body(getOpenApiYaml())
285
293
  })
286
294
  }
287
295
 
@@ -294,10 +302,10 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
294
302
  await parseFn(c as unknown as HandlerContext)
295
303
  makeShapeMiddleware<TCtx, TPrisma, TEnv>(config, opConfig)(c)
296
304
  const { before = [], after = [] } = opConfig
297
- const beforeResp = await runHooks<TEnv>(before, c)
305
+ const beforeResp = await runBeforeHooks<TEnv>(before, c)
298
306
  if (beforeResp) return beforeResp
299
307
  await handlerFn(c as unknown as HandlerContext)
300
- const afterResp = await runHooks<TEnv>(after, c)
308
+ const afterResp = await runAfterHooks<TEnv>(after, c)
301
309
  if (afterResp) return afterResp
302
310
  return sendResult(c as unknown as HandlerContext)
303
311
  } catch (error: unknown) {
@@ -313,10 +321,10 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
313
321
  await parseWriteBodyMiddleware(c as unknown as HandlerContext)
314
322
  makeShapeMiddleware<TCtx, TPrisma, TEnv>(config, opConfig)(c)
315
323
  const { before = [], after = [] } = opConfig
316
- const beforeResp = await runHooks<TEnv>(before, c)
324
+ const beforeResp = await runBeforeHooks<TEnv>(before, c)
317
325
  if (beforeResp) return beforeResp
318
326
  await handlerFn(c as unknown as HandlerContext)
319
- const afterResp = await runHooks<TEnv>(after, c)
327
+ const afterResp = await runAfterHooks<TEnv>(after, c)
320
328
  if (afterResp) return afterResp
321
329
  return sendResult(c as unknown as HandlerContext)
322
330
  } catch (error: unknown) {
@@ -329,55 +337,55 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
329
337
  ?? (defaultOpConfig as OperationConfigLike<TEnv>)
330
338
  }
331
339
 
332
- if (config.enableAll || config.findFirst) {
340
+ if (isEnabled(config.findFirst)) {
333
341
  const opConfig = opFor('findFirst')
334
342
  const path = basePath ? \`\${basePath}/first\` : '/first'
335
343
  app.get(path, handleRead(opConfig, ${modelName}FindFirst, parseQueryMiddleware))
336
344
  if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}FindFirst, parseBodyAsQueryMiddleware))
337
345
  }
338
- if (config.enableAll || config.findFirstOrThrow) {
346
+ if (isEnabled(config.findFirstOrThrow)) {
339
347
  const opConfig = opFor('findFirstOrThrow')
340
348
  const path = basePath ? \`\${basePath}/first/strict\` : '/first/strict'
341
349
  app.get(path, handleRead(opConfig, ${modelName}FindFirstOrThrow, parseQueryMiddleware))
342
350
  if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}FindFirstOrThrow, parseBodyAsQueryMiddleware))
343
351
  }
344
- if (config.enableAll || config.findManyPaginated) {
352
+ if (isEnabled(config.findManyPaginated)) {
345
353
  const opConfig = opFor('findManyPaginated')
346
354
  const path = basePath ? \`\${basePath}/paginated\` : '/paginated'
347
355
  app.get(path, handleRead(opConfig, ${modelName}FindManyPaginated, parseQueryMiddleware))
348
356
  if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}FindManyPaginated, parseBodyAsQueryMiddleware))
349
357
  }
350
- if (config.enableAll || config.aggregate) {
358
+ if (isEnabled(config.aggregate)) {
351
359
  const opConfig = opFor('aggregate')
352
360
  const path = basePath ? \`\${basePath}/aggregate\` : '/aggregate'
353
361
  app.get(path, handleRead(opConfig, ${modelName}Aggregate, parseQueryMiddleware))
354
362
  if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}Aggregate, parseBodyAsQueryMiddleware))
355
363
  }
356
- if (config.enableAll || config.count) {
364
+ if (isEnabled(config.count)) {
357
365
  const opConfig = opFor('count')
358
366
  const path = basePath ? \`\${basePath}/count\` : '/count'
359
367
  app.get(path, handleRead(opConfig, ${modelName}Count, parseQueryMiddleware))
360
368
  if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}Count, parseBodyAsQueryMiddleware))
361
369
  }
362
- if (config.enableAll || config.groupBy) {
370
+ if (isEnabled(config.groupBy)) {
363
371
  const opConfig = opFor('groupBy')
364
372
  const path = basePath ? \`\${basePath}/groupby\` : '/groupby'
365
373
  app.get(path, handleRead(opConfig, ${modelName}GroupBy, parseQueryMiddleware))
366
374
  if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}GroupBy, parseBodyAsQueryMiddleware))
367
375
  }
368
- if (config.enableAll || config.findUniqueOrThrow) {
376
+ if (isEnabled(config.findUniqueOrThrow)) {
369
377
  const opConfig = opFor('findUniqueOrThrow')
370
378
  const path = basePath ? \`\${basePath}/unique/strict\` : '/unique/strict'
371
379
  app.get(path, handleRead(opConfig, ${modelName}FindUniqueOrThrow, parseQueryMiddleware))
372
380
  if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}FindUniqueOrThrow, parseBodyAsQueryMiddleware))
373
381
  }
374
- if (config.enableAll || config.findUnique) {
382
+ if (isEnabled(config.findUnique)) {
375
383
  const opConfig = opFor('findUnique')
376
384
  const path = basePath ? \`\${basePath}/unique\` : '/unique'
377
385
  app.get(path, handleRead(opConfig, ${modelName}FindUnique, parseQueryMiddleware))
378
386
  if (postReadsEnabled) app.post(path, handleRead(opConfig, ${modelName}FindUnique, parseBodyAsQueryMiddleware))
379
387
  }
380
- if (config.enableAll || config.findMany) {
388
+ if (isEnabled(config.findMany)) {
381
389
  const opConfig = opFor('findMany')
382
390
  const path = basePath || '/'
383
391
  app.get(path, handleRead(opConfig, ${modelName}FindMany, parseQueryMiddleware))
@@ -387,52 +395,78 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
387
395
  }
388
396
  }
389
397
 
390
- if (config.enableAll || config.createManyAndReturn) {
398
+ if (isEnabled(config.createManyAndReturn)) {
391
399
  const opConfig = opFor('createManyAndReturn')
392
400
  const path = basePath ? \`\${basePath}/many/return\` : '/many/return'
393
401
  app.post(path, handleWrite(opConfig, ${modelName}CreateManyAndReturn))
394
402
  }
395
- if (config.enableAll || config.createMany) {
403
+ if (isEnabled(config.createMany)) {
396
404
  const opConfig = opFor('createMany')
397
405
  const path = basePath ? \`\${basePath}/many\` : '/many'
398
406
  app.post(path, handleWrite(opConfig, ${modelName}CreateMany))
399
407
  }
400
- if (config.enableAll || config.create) {
408
+ if (isEnabled(config.create)) {
401
409
  const opConfig = opFor('create')
402
410
  const path = basePath || '/'
403
411
  app.post(path, handleWrite(opConfig, ${modelName}Create))
404
412
  }
405
- if (config.enableAll || config.updateManyAndReturn) {
413
+ if (isEnabled(config.updateManyAndReturn)) {
406
414
  const opConfig = opFor('updateManyAndReturn')
407
415
  const path = basePath ? \`\${basePath}/many/return\` : '/many/return'
408
416
  app.put(path, handleWrite(opConfig, ${modelName}UpdateManyAndReturn))
409
417
  }
410
- if (config.enableAll || config.updateMany) {
418
+ if (isEnabled(config.updateMany)) {
411
419
  const opConfig = opFor('updateMany')
412
420
  const path = basePath ? \`\${basePath}/many\` : '/many'
413
421
  app.put(path, handleWrite(opConfig, ${modelName}UpdateMany))
414
422
  }
415
- if (config.enableAll || config.update) {
423
+ if (isEnabled(config.update)) {
416
424
  const opConfig = opFor('update')
417
425
  const path = basePath || '/'
418
426
  app.put(path, handleWrite(opConfig, ${modelName}Update))
419
427
  }
420
- if (config.enableAll || config.upsert) {
428
+ if (isEnabled(config.upsert)) {
421
429
  const opConfig = opFor('upsert')
422
430
  const path = basePath || '/'
423
431
  app.patch(path, handleWrite(opConfig, ${modelName}Upsert))
424
432
  }
425
- if (config.enableAll || config.deleteMany) {
433
+ if (isEnabled(config.deleteMany)) {
426
434
  const opConfig = opFor('deleteMany')
427
435
  const path = basePath ? \`\${basePath}/many\` : '/many'
428
436
  app.delete(path, handleWrite(opConfig, ${modelName}DeleteMany))
429
437
  }
430
- if (config.enableAll || config.delete) {
438
+ if (isEnabled(config.delete)) {
431
439
  const opConfig = opFor('delete')
432
440
  const path = basePath || '/'
433
441
  app.delete(path, handleWrite(opConfig, ${modelName}Delete))
434
442
  }
435
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
+
436
470
  return app
437
471
  }
438
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(
@@ -101,7 +101,7 @@ generatorHandler({
101
101
  onManifest() {
102
102
  return {
103
103
  version: require('../package.json').version,
104
- defaultOutput: '../generated/express',
104
+ defaultOutput: '../generated/output',
105
105
  prettyName: GENERATOR_NAME,
106
106
  }
107
107
  },
@@ -116,7 +116,7 @@ generatorHandler({
116
116
  __dirname,
117
117
  '..',
118
118
  'generated',
119
- target,
119
+ 'output',
120
120
  )
121
121
  const currentOutput = options.generator.output?.value
122
122
  const isUnsetOrManifestDefault =
@@ -136,7 +136,9 @@ generatorHandler({
136
136
  console.log(` Import style: ${importStyle}`)
137
137
  console.log(` Write strategy: ${writeStrategy}`)
138
138
  console.log(` findManyPaginated mode: ${findManyPaginatedMode}`)
139
- console.log(` Drop guard: ${dropGuard}`)
139
+ console.log(
140
+ ` Drop guard (generator): ${dropGuard}${dropGuard ? '' : ' (runtime E2E=true will also drop guard)'}`,
141
+ )
140
142
 
141
143
  if (options.dmmf.datamodel.models.length > 0) {
142
144
  validateClientGeneratorPresent(options)