prisma-generator-express 1.67.0 → 1.68.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 (68) hide show
  1. package/README.md +223 -28
  2. package/dist/copy/operationDefinitions.d.ts +4 -1
  3. package/dist/copy/operationDefinitions.js +64 -3
  4. package/dist/copy/operationDefinitions.js.map +1 -1
  5. package/dist/generators/generateFastifyHandler.js +55 -9
  6. package/dist/generators/generateFastifyHandler.js.map +1 -1
  7. package/dist/generators/generateHonoHandler.js +44 -4
  8. package/dist/generators/generateHonoHandler.js.map +1 -1
  9. package/dist/generators/generateHonoOpenApi.d.ts +13 -0
  10. package/dist/generators/generateHonoOpenApi.js +70 -0
  11. package/dist/generators/generateHonoOpenApi.js.map +1 -0
  12. package/dist/generators/generateMcpApp.d.ts +10 -0
  13. package/dist/generators/generateMcpApp.js +113 -0
  14. package/dist/generators/generateMcpApp.js.map +1 -0
  15. package/dist/generators/generateModelMcp.d.ts +9 -0
  16. package/dist/generators/generateModelMcp.js +145 -0
  17. package/dist/generators/generateModelMcp.js.map +1 -0
  18. package/dist/generators/generateModelMetadata.js +9 -5
  19. package/dist/generators/generateModelMetadata.js.map +1 -1
  20. package/dist/generators/generateRouter.js +62 -56
  21. package/dist/generators/generateRouter.js.map +1 -1
  22. package/dist/generators/generateRouterFastify.js +56 -64
  23. package/dist/generators/generateRouterFastify.js.map +1 -1
  24. package/dist/generators/generateRouterHono.js +43 -411
  25. package/dist/generators/generateRouterHono.js.map +1 -1
  26. package/dist/generators/generateRouterPartsHono.d.ts +14 -0
  27. package/dist/generators/generateRouterPartsHono.js +180 -0
  28. package/dist/generators/generateRouterPartsHono.js.map +1 -0
  29. package/dist/generators/generateUnifiedHandler.js +36 -20
  30. package/dist/generators/generateUnifiedHandler.js.map +1 -1
  31. package/dist/index.d.ts +2 -0
  32. package/dist/index.js +101 -3
  33. package/dist/index.js.map +1 -1
  34. package/dist/utils/copyFiles.d.ts +1 -1
  35. package/dist/utils/copyFiles.js +20 -1
  36. package/dist/utils/copyFiles.js.map +1 -1
  37. package/dist/utils/mcpGate.d.ts +10 -0
  38. package/dist/utils/mcpGate.js +118 -0
  39. package/dist/utils/mcpGate.js.map +1 -0
  40. package/dist/utils/writeFileSafely.js +6 -0
  41. package/dist/utils/writeFileSafely.js.map +1 -1
  42. package/package.json +18 -3
  43. package/src/copy/buildModelOpenApi.ts +76 -126
  44. package/src/copy/docsRenderer.ts +14 -15
  45. package/src/copy/mcpRuntime.ts +1017 -0
  46. package/src/copy/operationDefinitions.ts +78 -4
  47. package/src/copy/operationPipeline.ts +267 -0
  48. package/src/copy/operationRuntime.ts +24 -14
  49. package/src/copy/operationSchemas.ts +2773 -0
  50. package/src/copy/pagination.ts +10 -2
  51. package/src/copy/routeConfig.hono.ts +34 -5
  52. package/src/copy/routeConfig.ts +1 -1
  53. package/src/copy/routerParts.ts +524 -0
  54. package/src/generators/generateFastifyHandler.ts +55 -9
  55. package/src/generators/generateHonoHandler.ts +44 -4
  56. package/src/generators/generateHonoOpenApi.ts +93 -0
  57. package/src/generators/generateMcpApp.ts +133 -0
  58. package/src/generators/generateModelMcp.ts +208 -0
  59. package/src/generators/generateModelMetadata.ts +16 -10
  60. package/src/generators/generateRouter.ts +62 -56
  61. package/src/generators/generateRouterFastify.ts +56 -64
  62. package/src/generators/generateRouterHono.ts +49 -459
  63. package/src/generators/generateRouterPartsHono.ts +220 -0
  64. package/src/generators/generateUnifiedHandler.ts +36 -21
  65. package/src/index.ts +116 -3
  66. package/src/utils/copyFiles.ts +36 -0
  67. package/src/utils/mcpGate.ts +157 -0
  68. package/src/utils/writeFileSafely.ts +6 -0
@@ -4,8 +4,10 @@ import {
4
4
  buildCountShape,
5
5
  type PrismaDelegateLike,
6
6
  } from './guardHelpers'
7
- import { countFromMaterializedView } from './materializedCount'
8
7
  import { isPlainObject } from './misc'
8
+ // legacy self-contained behavior restored: the materialized-view module is
9
+ // small and statically owned by pagination (phase 9 budget re-measured)
10
+ import { countFromMaterializedView } from './materializedCount'
9
11
  import type { PaginationConfig, PaginationCountSource } from './routeConfig'
10
12
 
11
13
  export const DISTINCT_COUNT_LIMIT = 100000
@@ -119,7 +121,13 @@ export async function countForPagination(
119
121
  whereIsEmpty &&
120
122
  distinctIsEmpty
121
123
  ) {
122
- return countFromMaterializedView(rawClient ?? delegate, countSource)
124
+ return countFromMaterializedView(
125
+ rawClient ?? delegate,
126
+ countSource as Extract<
127
+ PaginationCountSource,
128
+ { type: 'materializedView' }
129
+ >,
130
+ )
123
131
  }
124
132
 
125
133
  const hasDistinct = normalizedDistinct.length > 0
@@ -1,6 +1,6 @@
1
1
  import type { RuntimeOperationOverride } from './operationRuntime'
2
+ import type { HttpError } from './errorMapper'
2
3
  import type { Context } from 'hono'
3
- import type { GuardVariantResolution } from './guardVariantRouting'
4
4
  import type {
5
5
  BaseOperationConfig,
6
6
  BaseRouteConfig,
@@ -30,9 +30,22 @@ export type HonoEnvBase = {
30
30
  Bindings?: Record<string, unknown>
31
31
  }
32
32
 
33
+ /**
34
+ * A stored, already-classified stage failure. `settleStage` rethrows the
35
+ * `HttpError` unchanged; where that happens is the adapter's ordering choice.
36
+ */
37
+ export type StoredStageFailure = { ok: false; failure: HttpError }
38
+
33
39
  export type HonoInternalVariables = {
34
40
  operationOverride?: RuntimeOperationOverride
41
+ /**
42
+ * Memoized application-context resolver (new key).
43
+ *
44
+ * @deprecated keep-populated alias of the pre-1.x key below; existing typed
45
+ * hooks read `resolveOperationContext`, so the router writes BOTH.
46
+ */
35
47
  resolveOperationContext?: () => unknown | Promise<unknown>
48
+ getContext?: () => Promise<unknown>
36
49
  prisma?: unknown
37
50
  postgres?: unknown
38
51
  sqlite?: unknown
@@ -42,12 +55,28 @@ export type HonoInternalVariables = {
42
55
  guardShape?: Record<string, unknown>
43
56
  guardCaller?: string
44
57
  guardVariantKey?: string
45
- guardVariantFailure?: Extract<GuardVariantResolution, { ok: false }>
46
58
  /**
47
- * Set when `validateResolvedShapes` refuses what a shape function returned.
59
+ * Stored, already-classified variant failure (new internal key).
60
+ */
61
+ guardVariantStageFailure?: StoredStageFailure
62
+ /**
63
+ * Raw variant resolution — the pre-1.x public shape. Still populated so
64
+ * existing typed hooks reading `.code` keep compiling and running.
65
+ *
66
+ * @deprecated read `guardVariantStageFailure` for the classified failure.
67
+ */
68
+ guardVariantFailure?: Extract<
69
+ import('./guardVariantRouting').GuardVariantResolution,
70
+ { ok: false }
71
+ >
72
+ /**
73
+ * Stored, already-classified shape failure (new internal key).
74
+ */
75
+ guardShapeStageFailure?: StoredStageFailure
76
+ /**
77
+ * Pre-1.x string form of the shape-resolution problem. Still populated.
48
78
  *
49
- * It was written by the router and never declared here, so the emitted router
50
- * failed to typecheck at exactly the line that implements the control.
79
+ * @deprecated read `guardShapeStageFailure` for the classified failure.
51
80
  */
52
81
  guardShapeFailure?: string
53
82
  resultData?: unknown
@@ -249,7 +249,7 @@ export interface NormalizedOperationConfig<TBefore, TAfter> {
249
249
  disablePostReads?: boolean
250
250
  }
251
251
 
252
- type OperationConfigInput<TBefore, TAfter> = {
252
+ export type OperationConfigInput<TBefore, TAfter> = {
253
253
  authorize?: TBefore
254
254
  override?: RuntimeOperationOverride
255
255
  before?: TBefore[]
@@ -0,0 +1,524 @@
1
+ import type { Context } from 'hono'
2
+ import type { ContentfulStatusCode } from 'hono/utils/http-status'
3
+ import { HTTPException } from 'hono/http-exception'
4
+ import { parseQueryParams } from './parseQueryParams'
5
+ import { sanitizeKeys, isPlainObject } from './misc'
6
+ import {
7
+ normalizeOperation,
8
+ resolveGuardPolicy,
9
+ resolveOperationVariantKey,
10
+ validateOperationConfig,
11
+ type BaseRouteConfig,
12
+ type GuardPolicy,
13
+ type NormalizedOperationConfig,
14
+ type PaginationConfig,
15
+ } from './routeConfig'
16
+ import type {
17
+ HonoBeforeHook,
18
+ HonoAfterHook,
19
+ HonoEnvBase,
20
+ GeneratedHonoEnv,
21
+ HonoInternalVariables,
22
+ } from './routeConfig.hono'
23
+ import { mergePaginationConfig } from './pagination'
24
+ import { mapError } from './errorMapper'
25
+ import {
26
+ routeOperation,
27
+ prepareGuardOperation,
28
+ settleStage,
29
+ memoizeContext,
30
+ type ArgsChannel,
31
+ } from './operationPipeline'
32
+ import { transformResult } from './operationRuntime'
33
+ import type { OpKind } from './projectionDefaults'
34
+
35
+ /**
36
+ * Phase 9 static assembly boundary, Hono side.
37
+ *
38
+ * Every operation route is created here, one function per operation, reachable
39
+ * only through the per-op named exports of a model's `*RouterParts.ts`. The
40
+ * legacy router factory registers all of them; a bundle that imports only the
41
+ * parts it serves omits the rest, and heavy features (OpenAPI renderer, the
42
+ * query-builder UI, SSE/ndjson — Express-only — and materialized-view counting,
43
+ * which pagination loads lazily) are unreachable unless a used part references
44
+ * them.
45
+ */
46
+
47
+ export type HandlerContext = Context<{ Variables: HonoInternalVariables }>
48
+
49
+ type JsonLike =
50
+ | string
51
+ | number
52
+ | boolean
53
+ | null
54
+ | unknown[]
55
+ | Record<string, unknown>
56
+
57
+ /**
58
+ * The config surface a route needs, picked from the real BaseRouteConfig so
59
+ * every emitted `*RouteConfig` is compatible by construction.
60
+ */
61
+ export type HonoRouteConfigLike<TCtx, TEnv extends HonoEnvBase> = Pick<
62
+ BaseRouteConfig<
63
+ HonoBeforeHook<TEnv>,
64
+ Context<GeneratedHonoEnv<TEnv>>,
65
+ Record<string, unknown>,
66
+ TCtx
67
+ >,
68
+ 'resolveContext' | 'pagination' | 'guard' | 'disablePostReads'
69
+ > & {
70
+ allowE2EGuardBypass?: boolean
71
+ }
72
+
73
+ export type HonoOpConfig<TEnv extends HonoEnvBase> = NormalizedOperationConfig<
74
+ HonoBeforeHook<TEnv>,
75
+ HonoAfterHook<TEnv>
76
+ >
77
+
78
+ export async function parseQueryMiddleware(c: HandlerContext): Promise<void> {
79
+ const raw = c.req.query() as Record<string, unknown>
80
+ if (raw && Object.keys(raw).length > 0) {
81
+ c.set('parsedQuery', parseQueryParams(raw) as Record<string, unknown>)
82
+ }
83
+ }
84
+
85
+ export async function parseBodyAsQueryMiddleware(
86
+ c: HandlerContext,
87
+ ): Promise<void> {
88
+ let body: unknown
89
+ try {
90
+ body = await c.req.json()
91
+ } catch {
92
+ throw new HTTPException(400, {
93
+ message: 'Request body must be a JSON object',
94
+ })
95
+ }
96
+ if (!body || typeof body !== 'object' || Array.isArray(body)) {
97
+ throw new HTTPException(400, {
98
+ message: 'Request body must be a JSON object',
99
+ })
100
+ }
101
+ c.set('parsedQuery', sanitizeKeys(body as Record<string, unknown>))
102
+ }
103
+
104
+ export async function parseWriteBodyMiddleware(
105
+ c: HandlerContext,
106
+ ): Promise<void> {
107
+ let body: unknown
108
+ try {
109
+ body = await c.req.json()
110
+ } catch {
111
+ throw new HTTPException(400, {
112
+ message: 'Request body must be a JSON object',
113
+ })
114
+ }
115
+ if (!body || typeof body !== 'object' || Array.isArray(body)) {
116
+ throw new HTTPException(400, {
117
+ message: 'Request body must be a JSON object',
118
+ })
119
+ }
120
+ c.set('body', body)
121
+ }
122
+
123
+ export async function parseUpdateEachBodyMiddleware(
124
+ c: HandlerContext,
125
+ ): Promise<void> {
126
+ let body: unknown
127
+ try {
128
+ body = await c.req.json()
129
+ } catch {
130
+ throw new HTTPException(400, {
131
+ message: 'updateEach body must be an array of { where, data } items',
132
+ })
133
+ }
134
+ if (!Array.isArray(body)) {
135
+ throw new HTTPException(400, {
136
+ message: 'updateEach body must be an array of { where, data } items',
137
+ })
138
+ }
139
+ c.set('body', body)
140
+ }
141
+
142
+ function queryChannel(c: HandlerContext): ArgsChannel {
143
+ return {
144
+ read: () => c.get('parsedQuery'),
145
+ write: (next) => c.set('parsedQuery', next),
146
+ }
147
+ }
148
+
149
+ function bodyChannel(c: HandlerContext): ArgsChannel {
150
+ return {
151
+ read: () => {
152
+ const body = c.get('body')
153
+ return isPlainObject(body) ? body : undefined
154
+ },
155
+ write: (next) => c.set('body', next),
156
+ }
157
+ }
158
+
159
+ export type HonoOperationConfigInput<TEnv extends HonoEnvBase> = {
160
+ authorize?: HonoBeforeHook<TEnv>
161
+ override?: import('./operationRuntime').RuntimeOperationOverride
162
+ before?: HonoBeforeHook<TEnv>[]
163
+ after?: HonoAfterHook<TEnv>[]
164
+ shape?: unknown
165
+ variants?: Record<
166
+ string,
167
+ {
168
+ shape?: unknown
169
+ before?: HonoBeforeHook<TEnv>[]
170
+ after?: HonoAfterHook<TEnv>[]
171
+ }
172
+ >
173
+ pagination?: Partial<PaginationConfig>
174
+ disablePostReads?: boolean
175
+ }
176
+
177
+ export function normalizeHonoOperation<TEnv extends HonoEnvBase>(
178
+ config: HonoOperationConfigInput<TEnv> | undefined,
179
+ ): HonoOpConfig<TEnv> {
180
+ return normalizeOperation<HonoBeforeHook<TEnv>, HonoAfterHook<TEnv>>(config)
181
+ }
182
+
183
+ /**
184
+ * Validates and normalizes one operation config exactly as the legacy router
185
+ * does, through the same shared code path.
186
+ */
187
+ export function opConfigFor<TEnv extends HonoEnvBase>(
188
+ raw: HonoOperationConfigInput<TEnv> | undefined,
189
+ location: string,
190
+ policy: Partial<GuardPolicy>,
191
+ ): HonoOpConfig<TEnv> {
192
+ validateOperationConfig(raw, location, policy)
193
+ return normalizeHonoOperation<TEnv>(raw)
194
+ }
195
+
196
+ export function createShapeMiddleware<
197
+ TCtx,
198
+ TPrisma,
199
+ TEnv extends HonoEnvBase,
200
+ >(input: {
201
+ config: HonoRouteConfigLike<TCtx, TEnv>
202
+ opConfig: HonoOpConfig<TEnv>
203
+ opKind: OpKind
204
+ dropGuard: boolean
205
+ }): (c: Context<GeneratedHonoEnv<TEnv>>) => Promise<void> {
206
+ const policy = {
207
+ dropGuard: input.dropGuard,
208
+ validateResolvedShapes: resolveGuardPolicy(
209
+ input.config as Partial<GuardPolicy>,
210
+ ).validateResolvedShapes,
211
+ }
212
+ return async (c: Context<GeneratedHonoEnv<TEnv>>): Promise<void> => {
213
+ const vars = c as unknown as HandlerContext
214
+ vars.set('operationOverride', input.opConfig.override)
215
+ const getContext = memoizeContext(() => input.config.resolveContext?.(c))
216
+ vars.set('getContext', getContext)
217
+ // legacy key, still populated for pre-existing typed hooks
218
+ vars.set('resolveOperationContext', getContext)
219
+
220
+ const merged = mergePaginationConfig(
221
+ input.config.pagination,
222
+ input.opConfig.pagination,
223
+ )
224
+ if (merged) vars.set('routeConfig', { pagination: merged })
225
+
226
+ const headerName = input.config.guard?.variantHeader || 'x-api-variant'
227
+ const headerValue = c.req.header(headerName)
228
+ const caller =
229
+ input.config.guard?.resolveVariant?.(c) ?? headerValue ?? undefined
230
+ if (typeof caller === 'string') vars.set('guardCaller', caller)
231
+
232
+ const resolution = resolveOperationVariantKey(
233
+ input.opConfig.guardRouting,
234
+ caller,
235
+ )
236
+ const routed = routeOperation({
237
+ guardRouting: input.opConfig.guardRouting,
238
+ caller,
239
+ })
240
+ if (!routed.ok) {
241
+ // legacy key keeps the pre-1.x public shape for existing typed hooks
242
+ if (!resolution.ok) vars.set('guardVariantFailure', resolution)
243
+ vars.set('guardVariantStageFailure', routed)
244
+ return
245
+ }
246
+ vars.set('guardVariantStageFailure', undefined)
247
+ vars.set('guardVariantFailure', undefined)
248
+
249
+ if (routed.variantKey !== undefined)
250
+ vars.set('guardVariantKey', routed.variantKey)
251
+
252
+ const guard = await prepareGuardOperation(routed, {
253
+ guardShape: input.opConfig.guardShape,
254
+ opKind: input.opKind,
255
+ policy,
256
+ getContext:
257
+ typeof input.config.resolveContext === 'function'
258
+ ? vars.get('getContext')
259
+ : undefined,
260
+ args: queryChannel(c as unknown as HandlerContext),
261
+ writeArgs: bodyChannel(c as unknown as HandlerContext),
262
+ })
263
+ if (!guard.ok) {
264
+ // legacy string key, still populated for pre-existing typed hooks
265
+ vars.set(
266
+ 'guardShapeFailure',
267
+ guard.failure.message.replace(
268
+ 'guard shape could not be resolved: ',
269
+ '',
270
+ ),
271
+ )
272
+ vars.set('guardShapeStageFailure', guard)
273
+ return
274
+ }
275
+ vars.set('guardShapeFailure', undefined)
276
+ vars.set('guardShapeStageFailure', undefined)
277
+
278
+ vars.set('guardShape', guard.guardShape)
279
+ }
280
+ }
281
+
282
+ export function createSettleGuard<TEnv extends HonoEnvBase>(): (
283
+ c: Context<GeneratedHonoEnv<TEnv>>,
284
+ ) => void {
285
+ return (c: Context<GeneratedHonoEnv<TEnv>>): void => {
286
+ const vars = c as unknown as HandlerContext
287
+ const failure = vars.get('guardVariantStageFailure')
288
+ if (failure) settleStage(failure)
289
+
290
+ const shapeFailure = vars.get('guardShapeStageFailure')
291
+ if (shapeFailure) settleStage(shapeFailure)
292
+ }
293
+ }
294
+
295
+ async function runBeforeHooks<TEnv extends HonoEnvBase>(
296
+ hooks: readonly HonoBeforeHook<TEnv>[],
297
+ c: Context<GeneratedHonoEnv<TEnv>>,
298
+ ): Promise<Response | undefined> {
299
+ for (const hook of hooks) {
300
+ const result = await hook(c)
301
+ if (result instanceof Response) return result
302
+ }
303
+ return undefined
304
+ }
305
+
306
+ async function runAfterHooks<TEnv extends HonoEnvBase>(
307
+ hooks: readonly HonoAfterHook<TEnv>[],
308
+ c: Context<GeneratedHonoEnv<TEnv>>,
309
+ ): Promise<Response | undefined> {
310
+ for (const hook of hooks) {
311
+ const result = await hook(c)
312
+ if (result instanceof Response) return result
313
+ }
314
+ return undefined
315
+ }
316
+
317
+ export function sendResult(c: HandlerContext): Response {
318
+ const data = c.get('resultData')
319
+ const status = (c.get('resultStatus') as number | undefined) ?? 200
320
+ if (data === undefined) {
321
+ throw new HTTPException(500, { message: 'No data set by handler' })
322
+ }
323
+ return c.json(
324
+ transformResult(data) as JsonLike,
325
+ status as ContentfulStatusCode,
326
+ )
327
+ }
328
+
329
+ export function sendError(c: HandlerContext, error: unknown): Response {
330
+ if (error instanceof HTTPException) {
331
+ return c.json(
332
+ { message: error.message },
333
+ error.status as ContentfulStatusCode,
334
+ )
335
+ }
336
+ const httpError = mapError(error)
337
+ return c.json(
338
+ { message: httpError.message },
339
+ httpError.status as ContentfulStatusCode,
340
+ )
341
+ }
342
+
343
+ export function createReadRoute<
344
+ TCtx,
345
+ TPrisma,
346
+ TEnv extends HonoEnvBase,
347
+ >(input: {
348
+ config: HonoRouteConfigLike<TCtx, TEnv>
349
+ opConfig: HonoOpConfig<TEnv>
350
+ opKind: OpKind
351
+ handler: (c: HandlerContext) => Promise<void>
352
+ parse: 'query' | 'body'
353
+ dropGuard: boolean
354
+ settleBeforeHooks: boolean
355
+ }): (c: Context<GeneratedHonoEnv<TEnv>>) => Promise<Response> {
356
+ const shape = createShapeMiddleware<TCtx, TPrisma, TEnv>(input)
357
+ const settleGuard = createSettleGuard<TEnv>()
358
+ const parseFn =
359
+ input.parse === 'query' ? parseQueryMiddleware : parseBodyAsQueryMiddleware
360
+ return async (c: Context<GeneratedHonoEnv<TEnv>>): Promise<Response> => {
361
+ try {
362
+ const authorized = await runBeforeHooks<TEnv>(
363
+ input.opConfig.authorize ? [input.opConfig.authorize] : [],
364
+ c,
365
+ )
366
+ if (authorized) return authorized
367
+ await parseFn(c as unknown as HandlerContext)
368
+ await shape(c)
369
+
370
+ if (input.settleBeforeHooks) settleGuard(c)
371
+
372
+ const operationBefore = await runBeforeHooks<TEnv>(
373
+ input.opConfig.operationBefore,
374
+ c,
375
+ )
376
+ if (operationBefore) return operationBefore
377
+
378
+ if (!input.settleBeforeHooks) settleGuard(c)
379
+
380
+ const key = (c as unknown as HandlerContext).get('guardVariantKey')
381
+ const variantHooks =
382
+ key !== undefined ? input.opConfig.variantHooks[key] : undefined
383
+
384
+ const variantBefore = await runBeforeHooks<TEnv>(
385
+ variantHooks?.before ?? [],
386
+ c,
387
+ )
388
+ if (variantBefore) return variantBefore
389
+ await input.handler(c as unknown as HandlerContext)
390
+ const variantAfter = await runAfterHooks<TEnv>(
391
+ variantHooks?.after ?? [],
392
+ c,
393
+ )
394
+ if (variantAfter) return variantAfter
395
+ const operationAfter = await runAfterHooks<TEnv>(
396
+ input.opConfig.operationAfter,
397
+ c,
398
+ )
399
+ if (operationAfter) return operationAfter
400
+ return sendResult(c as unknown as HandlerContext)
401
+ } catch (error: unknown) {
402
+ return sendError(c as unknown as HandlerContext, error)
403
+ }
404
+ }
405
+ }
406
+
407
+ export function createWriteRoute<
408
+ TCtx,
409
+ TPrisma,
410
+ TEnv extends HonoEnvBase,
411
+ >(input: {
412
+ config: HonoRouteConfigLike<TCtx, TEnv>
413
+ opConfig: HonoOpConfig<TEnv>
414
+ opKind: OpKind
415
+ handler: (c: HandlerContext) => Promise<void>
416
+ dropGuard: boolean
417
+ settleBeforeHooks: boolean
418
+ }): (c: Context<GeneratedHonoEnv<TEnv>>) => Promise<Response> {
419
+ const shape = createShapeMiddleware<TCtx, TPrisma, TEnv>(input)
420
+ const settleGuard = createSettleGuard<TEnv>()
421
+ return async (c: Context<GeneratedHonoEnv<TEnv>>): Promise<Response> => {
422
+ try {
423
+ const authorized = await runBeforeHooks<TEnv>(
424
+ input.opConfig.authorize ? [input.opConfig.authorize] : [],
425
+ c,
426
+ )
427
+ if (authorized) return authorized
428
+ await parseWriteBodyMiddleware(c as unknown as HandlerContext)
429
+ await shape(c)
430
+
431
+ if (input.settleBeforeHooks) settleGuard(c)
432
+
433
+ const operationBefore = await runBeforeHooks<TEnv>(
434
+ input.opConfig.operationBefore,
435
+ c,
436
+ )
437
+ if (operationBefore) return operationBefore
438
+
439
+ if (!input.settleBeforeHooks) settleGuard(c)
440
+
441
+ const key = (c as unknown as HandlerContext).get('guardVariantKey')
442
+ const variantHooks =
443
+ key !== undefined ? input.opConfig.variantHooks[key] : undefined
444
+
445
+ const variantBefore = await runBeforeHooks<TEnv>(
446
+ variantHooks?.before ?? [],
447
+ c,
448
+ )
449
+ if (variantBefore) return variantBefore
450
+ await input.handler(c as unknown as HandlerContext)
451
+ const variantAfter = await runAfterHooks<TEnv>(
452
+ variantHooks?.after ?? [],
453
+ c,
454
+ )
455
+ if (variantAfter) return variantAfter
456
+ const operationAfter = await runAfterHooks<TEnv>(
457
+ input.opConfig.operationAfter,
458
+ c,
459
+ )
460
+ if (operationAfter) return operationAfter
461
+ return sendResult(c as unknown as HandlerContext)
462
+ } catch (error: unknown) {
463
+ return sendError(c as unknown as HandlerContext, error)
464
+ }
465
+ }
466
+ }
467
+
468
+ export function createUpdateEachRoute<
469
+ TCtx,
470
+ TPrisma,
471
+ TEnv extends HonoEnvBase,
472
+ >(input: {
473
+ config: HonoRouteConfigLike<TCtx, TEnv>
474
+ opConfig: HonoOpConfig<TEnv>
475
+ handler: (c: HandlerContext) => Promise<void>
476
+ dropGuard: boolean
477
+ }): (c: Context<GeneratedHonoEnv<TEnv>>) => Promise<Response> {
478
+ const shape = createShapeMiddleware<TCtx, TPrisma, TEnv>({
479
+ ...input,
480
+ opKind: 'noop',
481
+ })
482
+ return async (c: Context<GeneratedHonoEnv<TEnv>>): Promise<Response> => {
483
+ try {
484
+ await parseUpdateEachBodyMiddleware(c as unknown as HandlerContext)
485
+ await shape(c)
486
+ const beforeResponse = await runBeforeHooks<TEnv>(
487
+ input.opConfig.operationBefore,
488
+ c,
489
+ )
490
+ if (beforeResponse) return beforeResponse
491
+ await input.handler(c as unknown as HandlerContext)
492
+ const afterResponse = await runAfterHooks<TEnv>(
493
+ input.opConfig.operationAfter,
494
+ c,
495
+ )
496
+ if (afterResponse) return afterResponse
497
+ return sendResult(c as unknown as HandlerContext)
498
+ } catch (error: unknown) {
499
+ return sendError(c as unknown as HandlerContext, error)
500
+ }
501
+ }
502
+ }
503
+
504
+ /**
505
+ * OpenAPI endpoints for one model. Takes prebuilt document getters so this
506
+ * module never imports the renderer: a bundle that registers no OpenAPI part
507
+ * keeps `yaml` and `buildModelOpenApi` out of its graph entirely.
508
+ */
509
+ export function registerOpenApiRoutes(
510
+ app: {
511
+ get: (path: string, handler: (c: HandlerContext) => Response) => void
512
+ },
513
+ basePath: string,
514
+ getJson: () => unknown,
515
+ getYaml: () => string,
516
+ ): void {
517
+ const jsonPath = basePath ? `${basePath}/openapi.json` : '/openapi.json'
518
+ const yamlPath = basePath ? `${basePath}/openapi.yaml` : '/openapi.yaml'
519
+ app.get(jsonPath, (c) => c.json(getJson() as JsonLike))
520
+ app.get(yamlPath, (c) => {
521
+ c.header('Content-Type', 'application/yaml')
522
+ return c.body(getYaml())
523
+ })
524
+ }