prisma-generator-express 1.57.0 → 1.58.1

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