prisma-generator-express 1.66.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 (130) hide show
  1. package/README.md +188 -3432
  2. package/dist/client/encodeQueryParams.js +3 -1
  3. package/dist/client/encodeQueryParams.js.map +1 -1
  4. package/dist/copy/misc.d.ts +1 -0
  5. package/dist/copy/misc.js +14 -0
  6. package/dist/copy/misc.js.map +1 -1
  7. package/dist/copy/operationDefinitions.d.ts +4 -1
  8. package/dist/copy/operationDefinitions.js +133 -9
  9. package/dist/copy/operationDefinitions.js.map +1 -1
  10. package/dist/generators/generateFastifyHandler.d.ts +2 -2
  11. package/dist/generators/generateFastifyHandler.js +68 -12
  12. package/dist/generators/generateFastifyHandler.js.map +1 -1
  13. package/dist/generators/generateHonoHandler.d.ts +2 -2
  14. package/dist/generators/generateHonoHandler.js +53 -6
  15. package/dist/generators/generateHonoHandler.js.map +1 -1
  16. package/dist/generators/generateHonoOpenApi.d.ts +13 -0
  17. package/dist/generators/generateHonoOpenApi.js +70 -0
  18. package/dist/generators/generateHonoOpenApi.js.map +1 -0
  19. package/dist/generators/generateImportPrismaStatement.d.ts +1 -0
  20. package/dist/generators/generateImportPrismaStatement.js +6 -0
  21. package/dist/generators/generateImportPrismaStatement.js.map +1 -1
  22. package/dist/generators/generateMcpApp.d.ts +10 -0
  23. package/dist/generators/generateMcpApp.js +113 -0
  24. package/dist/generators/generateMcpApp.js.map +1 -0
  25. package/dist/generators/generateModelMcp.d.ts +9 -0
  26. package/dist/generators/generateModelMcp.js +145 -0
  27. package/dist/generators/generateModelMcp.js.map +1 -0
  28. package/dist/generators/generateModelMetadata.js +29 -15
  29. package/dist/generators/generateModelMetadata.js.map +1 -1
  30. package/dist/generators/generateOperationCore.d.ts +3 -3
  31. package/dist/generators/generateOperationCore.js +51 -16
  32. package/dist/generators/generateOperationCore.js.map +1 -1
  33. package/dist/generators/generateRelationMeta.js +2 -2
  34. package/dist/generators/generateRelationMeta.js.map +1 -1
  35. package/dist/generators/generateRouteConfigType.d.ts +2 -2
  36. package/dist/generators/generateRouteConfigType.js +29 -11
  37. package/dist/generators/generateRouteConfigType.js.map +1 -1
  38. package/dist/generators/generateRouter.d.ts +7 -4
  39. package/dist/generators/generateRouter.js +164 -65
  40. package/dist/generators/generateRouter.js.map +1 -1
  41. package/dist/generators/generateRouterFastify.d.ts +7 -4
  42. package/dist/generators/generateRouterFastify.js +106 -86
  43. package/dist/generators/generateRouterFastify.js.map +1 -1
  44. package/dist/generators/generateRouterHono.d.ts +7 -4
  45. package/dist/generators/generateRouterHono.js +59 -414
  46. package/dist/generators/generateRouterHono.js.map +1 -1
  47. package/dist/generators/generateRouterPartsHono.d.ts +14 -0
  48. package/dist/generators/generateRouterPartsHono.js +180 -0
  49. package/dist/generators/generateRouterPartsHono.js.map +1 -0
  50. package/dist/generators/generateUnifiedDocs.d.ts +1 -1
  51. package/dist/generators/generateUnifiedDocs.js +15 -8
  52. package/dist/generators/generateUnifiedDocs.js.map +1 -1
  53. package/dist/generators/generateUnifiedHandler.d.ts +2 -2
  54. package/dist/generators/generateUnifiedHandler.js +42 -18
  55. package/dist/generators/generateUnifiedHandler.js.map +1 -1
  56. package/dist/guardOptions.js +12 -7
  57. package/dist/guardOptions.js.map +1 -1
  58. package/dist/index.d.ts +3 -1
  59. package/dist/index.js +123 -4
  60. package/dist/index.js.map +1 -1
  61. package/dist/utils/copyFiles.d.ts +1 -1
  62. package/dist/utils/copyFiles.js +42 -7
  63. package/dist/utils/copyFiles.js.map +1 -1
  64. package/dist/utils/importExt.d.ts +1 -1
  65. package/dist/utils/mcpGate.d.ts +10 -0
  66. package/dist/utils/mcpGate.js +118 -0
  67. package/dist/utils/mcpGate.js.map +1 -0
  68. package/dist/utils/pathCasing.d.ts +4 -0
  69. package/dist/utils/pathCasing.js +40 -0
  70. package/dist/utils/pathCasing.js.map +1 -0
  71. package/dist/utils/resolveImportStyle.js +13 -5
  72. package/dist/utils/resolveImportStyle.js.map +1 -1
  73. package/dist/utils/writeFileSafely.js +6 -0
  74. package/dist/utils/writeFileSafely.js.map +1 -1
  75. package/package.json +22 -5
  76. package/src/bin.ts +1 -1
  77. package/src/client/encodeQueryParams.ts +39 -35
  78. package/src/constants.ts +1 -1
  79. package/src/copy/autoIncludePlanner.ts +93 -27
  80. package/src/copy/autoIncludePlannerGuarded.ts +171 -48
  81. package/src/copy/autoIncludeRuntimeGuarded.ts +25 -7
  82. package/src/copy/buildModelOpenApi.ts +229 -176
  83. package/src/copy/concurrency.ts +10 -8
  84. package/src/copy/docsRenderer.ts +14 -15
  85. package/src/copy/errorMapper.ts +68 -19
  86. package/src/copy/guardHelpers.ts +30 -9
  87. package/src/copy/materializedCount.ts +17 -5
  88. package/src/copy/mcpRuntime.ts +1017 -0
  89. package/src/copy/misc.ts +88 -68
  90. package/src/copy/ndjson.ts +72 -0
  91. package/src/copy/operationDefinitions.ts +177 -25
  92. package/src/copy/operationPipeline.ts +267 -0
  93. package/src/copy/operationRuntime.ts +124 -26
  94. package/src/copy/operationSchemas.ts +2773 -0
  95. package/src/copy/pagination.ts +35 -13
  96. package/src/copy/parseQueryParams.ts +47 -48
  97. package/src/copy/projectionDefaults.ts +6 -4
  98. package/src/copy/routeConfig.express.ts +16 -4
  99. package/src/copy/routeConfig.fastify.ts +16 -2
  100. package/src/copy/routeConfig.hono.ts +46 -14
  101. package/src/copy/routeConfig.ts +175 -40
  102. package/src/copy/routerParts.ts +524 -0
  103. package/src/copy/scalarTypes.ts +7 -2
  104. package/src/copy/sse.ts +72 -19
  105. package/src/generators/generateFastifyHandler.ts +77 -20
  106. package/src/generators/generateHonoHandler.ts +62 -14
  107. package/src/generators/generateHonoOpenApi.ts +93 -0
  108. package/src/generators/generateImportPrismaStatement.ts +93 -81
  109. package/src/generators/generateMcpApp.ts +133 -0
  110. package/src/generators/generateModelMcp.ts +208 -0
  111. package/src/generators/generateModelMetadata.ts +40 -22
  112. package/src/generators/generateOperationCore.ts +81 -33
  113. package/src/generators/generateRelationMeta.ts +23 -11
  114. package/src/generators/generateRouteConfigType.ts +39 -13
  115. package/src/generators/generateRouter.ts +171 -68
  116. package/src/generators/generateRouterFastify.ts +117 -89
  117. package/src/generators/generateRouterHono.ts +84 -464
  118. package/src/generators/generateRouterPartsHono.ts +220 -0
  119. package/src/generators/generateUnifiedDocs.ts +29 -8
  120. package/src/generators/generateUnifiedHandler.ts +47 -24
  121. package/src/generators/generateUnifiedScalarUI.ts +1 -1
  122. package/src/guardOptions.ts +105 -92
  123. package/src/index.ts +161 -6
  124. package/src/utils/copyFiles.ts +235 -168
  125. package/src/utils/importExt.ts +2 -2
  126. package/src/utils/mcpGate.ts +157 -0
  127. package/src/utils/pathCasing.ts +42 -0
  128. package/src/utils/resolveImportStyle.ts +37 -15
  129. package/src/utils/strings.ts +2 -2
  130. package/src/utils/writeFileSafely.ts +12 -2
package/src/copy/sse.ts CHANGED
@@ -5,6 +5,7 @@ import type {
5
5
  ProgressiveStage,
6
6
  ProgressiveStopResult,
7
7
  ProgressivePatch,
8
+ PrismaClientLike,
8
9
  } from './routeConfig'
9
10
 
10
11
  export function acceptsEventStream(accept: string | undefined): boolean {
@@ -17,7 +18,11 @@ export function acceptsEventStream(accept: string | undefined): boolean {
17
18
 
18
19
  const UNSAFE_PATH_SEGMENTS = new Set(['__proto__', 'constructor', 'prototype'])
19
20
 
20
- export function setByPath(target: Record<string, unknown>, path: string, value: unknown): boolean {
21
+ export function setByPath(
22
+ target: Record<string, unknown>,
23
+ path: string,
24
+ value: unknown,
25
+ ): boolean {
21
26
  const parts = path.split('.')
22
27
  if (parts.length === 0) return false
23
28
  for (const p of parts) {
@@ -31,8 +36,11 @@ export function setByPath(target: Record<string, unknown>, path: string, value:
31
36
  if (process.env.NODE_ENV !== 'production') {
32
37
  console.warn(
33
38
  LOG_PREFIX,
34
- 'Dropping patch for "' + path +
35
- '": cannot traverse non-plain-object at segment "' + part + '"',
39
+ 'Dropping patch for "' +
40
+ path +
41
+ '": cannot traverse non-plain-object at segment "' +
42
+ part +
43
+ '"',
36
44
  )
37
45
  }
38
46
  return false
@@ -61,7 +69,10 @@ export type SseWritable = {
61
69
  destroyed: boolean
62
70
  }
63
71
 
64
- export function removeReqCloseListener(req: EventEmitterLike, listener: () => void): void {
72
+ export function removeReqCloseListener(
73
+ req: EventEmitterLike,
74
+ listener: () => void,
75
+ ): void {
65
76
  if (typeof req.off === 'function') {
66
77
  req.off('close', listener)
67
78
  } else if (typeof req.removeListener === 'function') {
@@ -75,12 +86,15 @@ export function initSSE(res: SseWritable): void {
75
86
  res.setHeader('Cache-Control', 'no-cache, no-transform')
76
87
  res.setHeader('Connection', 'keep-alive')
77
88
  res.setHeader('X-Accel-Buffering', 'no')
89
+ res.setHeader('X-Stream-Protocol-Version', '1')
78
90
  if (typeof res.flushHeaders === 'function') res.flushHeaders()
79
91
  }
80
92
 
81
93
  export function flushSSE(res: SseWritable): void {
82
94
  if (typeof res.flush === 'function') {
83
- try { res.flush() } catch {}
95
+ try {
96
+ res.flush()
97
+ } catch {}
84
98
  }
85
99
  }
86
100
 
@@ -96,11 +110,20 @@ export function sendSSE(res: SseWritable, payload: unknown): boolean {
96
110
  }
97
111
  }
98
112
 
99
- export function sendSSEProgress(res: SseWritable, stage: string, completed: number, total: number): boolean {
113
+ export function sendSSEProgress(
114
+ res: SseWritable,
115
+ stage: string,
116
+ completed: number,
117
+ total: number,
118
+ ): boolean {
100
119
  return sendSSE(res, { type: 'progress', stage, completed, total })
101
120
  }
102
121
 
103
- export function sendSSEField(res: SseWritable, key: string, value: unknown): boolean {
122
+ export function sendSSEField(
123
+ res: SseWritable,
124
+ key: string,
125
+ value: unknown,
126
+ ): boolean {
104
127
  return sendSSE(res, { type: 'field', key, value })
105
128
  }
106
129
 
@@ -126,7 +149,12 @@ export function sendSSENestedRelationBatch(
126
149
  depth: number,
127
150
  attachments: Array<{ locator: Array<number | string>; value: unknown }>,
128
151
  ): boolean {
129
- return sendSSE(res, { type: 'nestedRelationBatch', relationPath, depth, attachments })
152
+ return sendSSE(res, {
153
+ type: 'nestedRelationBatch',
154
+ relationPath,
155
+ depth,
156
+ attachments,
157
+ })
130
158
  }
131
159
 
132
160
  export function sendSSEPageMeta(
@@ -157,7 +185,10 @@ export function safeSendError(res: SseWritable, message: string): void {
157
185
 
158
186
  type IntervalHandle = ReturnType<typeof setInterval>
159
187
 
160
- export function startSSEKeepalive(res: SseWritable, intervalMs: number = 15000): IntervalHandle {
188
+ export function startSSEKeepalive(
189
+ res: SseWritable,
190
+ intervalMs: number = 15000,
191
+ ): IntervalHandle {
161
192
  const handle = setInterval(() => {
162
193
  if (res.writableEnded || res.destroyed) return
163
194
  try {
@@ -170,12 +201,17 @@ export function startSSEKeepalive(res: SseWritable, intervalMs: number = 15000):
170
201
  return handle
171
202
  }
172
203
 
173
- export function endSSE(res: SseWritable, keepaliveHandle: IntervalHandle | null): void {
204
+ export function endSSE(
205
+ res: SseWritable,
206
+ keepaliveHandle: IntervalHandle | null,
207
+ ): void {
174
208
  if (keepaliveHandle) {
175
209
  clearInterval(keepaliveHandle)
176
210
  }
177
211
  if (!res.writableEnded && !res.destroyed) {
178
- try { res.end() } catch {}
212
+ try {
213
+ res.end()
214
+ } catch {}
179
215
  }
180
216
  }
181
217
 
@@ -225,7 +261,9 @@ export interface RunSingleResultSSEOptions {
225
261
  coreQueryFn: () => Promise<unknown>
226
262
  }
227
263
 
228
- export async function runSingleResultSSE(options: RunSingleResultSSEOptions): Promise<void> {
264
+ export async function runSingleResultSSE(
265
+ options: RunSingleResultSSEOptions,
266
+ ): Promise<void> {
229
267
  const { req, res, coreQueryFn } = options
230
268
  await withSSE({ res, label: 'single-result' }, async () => {
231
269
  if (req.destroyed) return
@@ -236,20 +274,26 @@ export async function runSingleResultSSE(options: RunSingleResultSSEOptions): Pr
236
274
  }
237
275
 
238
276
  function isStopResult(value: unknown): value is ProgressiveStopResult<unknown> {
239
- return typeof value === 'object' && value !== null && (value as { stop?: unknown }).stop === true
277
+ return (
278
+ typeof value === 'object' &&
279
+ value !== null &&
280
+ (value as { stop?: unknown }).stop === true
281
+ )
240
282
  }
241
283
 
242
284
  export interface RunProgressiveOptions {
243
285
  req: EventEmitterLike
244
286
  res: SseWritable
245
287
  ctx: unknown
246
- prisma: unknown
288
+ prisma: PrismaClientLike
247
289
  variant: string
248
290
  stages: string[]
249
291
  stageRegistry: Record<string, ProgressiveStage>
250
292
  }
251
293
 
252
- export async function runProgressiveEndpoint(options: RunProgressiveOptions): Promise<void> {
294
+ export async function runProgressiveEndpoint(
295
+ options: RunProgressiveOptions,
296
+ ): Promise<void> {
253
297
  const { req, res, ctx, prisma, variant, stages, stageRegistry } = options
254
298
  const controller = new AbortController()
255
299
  const onClose = () => controller.abort()
@@ -266,9 +310,18 @@ export async function runProgressiveEndpoint(options: RunProgressiveOptions): Pr
266
310
  if (res.writableEnded || res.destroyed || signal.aborted) return
267
311
  const stageName = stages[i]
268
312
  const stage = stageRegistry[stageName]
269
- if (!stage) throw new HttpError(500, 'Missing progressive stage: ' + stageName)
270
-
271
- const result = await stage({ ctx, req, res, prisma, variant, accumulated, signal })
313
+ if (!stage)
314
+ throw new HttpError(500, 'Missing progressive stage: ' + stageName)
315
+
316
+ const result = await stage({
317
+ ctx,
318
+ req,
319
+ res,
320
+ prisma,
321
+ variant,
322
+ accumulated,
323
+ signal,
324
+ })
272
325
  if (res.writableEnded || res.destroyed) return
273
326
 
274
327
  if (isStopResult(result)) {
@@ -293,4 +346,4 @@ export async function runProgressiveEndpoint(options: RunProgressiveOptions): Pr
293
346
  } finally {
294
347
  removeReqCloseListener(req, onClose)
295
348
  }
296
- }
349
+ }
@@ -1,5 +1,5 @@
1
- import { DMMF } from '@prisma/generator-helper'
2
- import { ImportStyle } from '../utils/resolveImportStyle'
1
+ import type { DMMF } from '@prisma/generator-helper'
2
+ import type { ImportStyle } from '../utils/resolveImportStyle'
3
3
  import { importExt } from '../utils/importExt'
4
4
  import { OPERATION_METADATA } from '../copy/operationDefinitions'
5
5
 
@@ -12,34 +12,69 @@ export function generateFastifyHandler(options: {
12
12
 
13
13
  const readOps = OPERATION_METADATA.filter((m) => m.kind === 'read')
14
14
  const writeOps = OPERATION_METADATA.filter(
15
- (m) => (m.kind === 'write' || m.kind === 'batch') && m.name !== 'updateEach',
15
+ (m) =>
16
+ (m.kind === 'write' || m.kind === 'batch') && m.name !== 'updateEach',
16
17
  )
17
18
 
18
- const readHandlers = readOps.map((meta) => {
19
- const exportName = `${modelName}${meta.name.charAt(0).toUpperCase() + meta.name.slice(1)}`
20
- return `
19
+ const readHandlers = readOps
20
+ .map((meta) => {
21
+ const exportName = `${modelName}${meta.name.charAt(0).toUpperCase() + meta.name.slice(1)}`
22
+ return `
21
23
  export async function ${exportName}(
22
24
  request: FastifyRequest,
23
25
  _reply: FastifyReply,
24
26
  ): Promise<void> {
25
- const data = await core.${meta.coreName}(buildContext(request))
26
- ;(request as FastifyExtended).resultData = data
27
+ const fx = request as FastifyExtended
28
+ const data = await executeOperation(
29
+ { variantKey: fx.guardVariantKey, caller: fx.guardCaller },
30
+ { guardShape: fx.guardShape },
31
+ {
32
+ core: core.${meta.coreName},
33
+ args: queryChannel(request),
34
+ body: request.body,
35
+ prisma: requirePrisma(fx.prisma),
36
+ postgres: fx.postgres,
37
+ sqlite: fx.sqlite,
38
+ pagination: fx.routeConfig?.pagination,
39
+ override: fx.operationOverride,
40
+ getContext: fx.getContext,
41
+ findManyPaginatedMode: fx.findManyPaginatedMode,
42
+ },
43
+ )
44
+ fx.resultData = data
27
45
  }`
28
- }).join('\n')
46
+ })
47
+ .join('\n')
29
48
 
30
- const writeHandlers = writeOps.map((meta) => {
31
- const exportName = `${modelName}${meta.name.charAt(0).toUpperCase() + meta.name.slice(1)}`
32
- return `
49
+ const writeHandlers = writeOps
50
+ .map((meta) => {
51
+ const exportName = `${modelName}${meta.name.charAt(0).toUpperCase() + meta.name.slice(1)}`
52
+ return `
33
53
  export async function ${exportName}(
34
54
  request: FastifyRequest,
35
55
  _reply: FastifyReply,
36
56
  ): Promise<void> {
37
- const data = await core.${meta.coreName}(buildContext(request))
38
- const ext = request as FastifyExtended
39
- ext.resultData = data
40
- ext.resultStatus = ${meta.successStatus}
57
+ const fx = request as FastifyExtended
58
+ const data = await executeOperation(
59
+ { variantKey: fx.guardVariantKey, caller: fx.guardCaller },
60
+ { guardShape: fx.guardShape },
61
+ {
62
+ core: core.${meta.coreName},
63
+ args: queryChannel(request),
64
+ body: request.body,
65
+ prisma: requirePrisma(fx.prisma),
66
+ postgres: fx.postgres,
67
+ sqlite: fx.sqlite,
68
+ pagination: fx.routeConfig?.pagination,
69
+ override: fx.operationOverride,
70
+ getContext: fx.getContext,
71
+ },
72
+ )
73
+ fx.resultData = data
74
+ fx.resultStatus = ${meta.successStatus}
41
75
  }`
42
- }).join('\n')
76
+ })
77
+ .join('\n')
43
78
 
44
79
  const updateEachExportName = `${modelName}UpdateEach`
45
80
  const updateEachHandler = `
@@ -54,24 +89,45 @@ export async function ${updateEachExportName}(
54
89
 
55
90
  return `import type { FastifyRequest, FastifyReply } from 'fastify'
56
91
  import * as core from './${modelName}Core${ext}'
57
- import type { OperationContext, FindManyPaginatedMode } from '../operationRuntime${ext}'
92
+ import type { RuntimeOperationOverride, OperationContext, FindManyPaginatedMode } from '../operationRuntime${ext}'
93
+ import type { PaginationConfig } from '../routeConfig${ext}'
94
+ import {
95
+ executeOperation,
96
+ requirePrisma,
97
+ type ArgsChannel,
98
+ } from '../operationPipeline${ext}'
58
99
 
59
100
  type FastifyExtended = FastifyRequest & {
60
101
  prisma?: unknown
61
102
  postgres?: unknown
62
103
  sqlite?: unknown
63
104
  parsedQuery?: Record<string, unknown>
64
- routeConfig?: { pagination?: OperationContext['paginationConfig'] }
105
+ routeConfig?: { pagination?: PaginationConfig }
65
106
  guardShape?: Record<string, unknown>
66
107
  guardCaller?: string
108
+ guardVariantKey?: string
109
+ operationOverride?: RuntimeOperationOverride
110
+ getContext?: () => Promise<unknown>
67
111
  findManyPaginatedMode?: FindManyPaginatedMode
68
112
  resultData?: unknown
69
113
  resultStatus?: number
70
114
  }
71
115
 
116
+ function queryChannel(request: FastifyRequest): ArgsChannel {
117
+ const fx = request as FastifyExtended
118
+ return {
119
+ read: () => fx.parsedQuery,
120
+ write: (next) => {
121
+ fx.parsedQuery = next
122
+ },
123
+ }
124
+ }
125
+
72
126
  function buildContext(request: FastifyRequest): OperationContext {
73
127
  const req = request as FastifyExtended
74
128
  return {
129
+ operationOverride: req.operationOverride,
130
+ resolveOperationContext: req.getContext,
75
131
  prisma: req.prisma,
76
132
  postgres: req.postgres,
77
133
  sqlite: req.sqlite,
@@ -79,6 +135,7 @@ function buildContext(request: FastifyRequest): OperationContext {
79
135
  body: request.body,
80
136
  guardShape: req.guardShape,
81
137
  guardCaller: req.guardCaller,
138
+ guardVariantKey: req.guardVariantKey,
82
139
  paginationConfig: req.routeConfig?.pagination,
83
140
  findManyPaginatedMode: req.findManyPaginatedMode,
84
141
  }
@@ -87,4 +144,4 @@ ${readHandlers}
87
144
  ${writeHandlers}
88
145
  ${updateEachHandler}
89
146
  `
90
- }
147
+ }
@@ -1,5 +1,5 @@
1
- import { DMMF } from '@prisma/generator-helper'
2
- import { ImportStyle } from '../utils/resolveImportStyle'
1
+ import type { DMMF } from '@prisma/generator-helper'
2
+ import type { ImportStyle } from '../utils/resolveImportStyle'
3
3
  import { importExt } from '../utils/importExt'
4
4
  import { OPERATION_METADATA } from '../copy/operationDefinitions'
5
5
 
@@ -12,27 +12,60 @@ export function generateHonoHandler(options: {
12
12
 
13
13
  const readOps = OPERATION_METADATA.filter((m) => m.kind === 'read')
14
14
  const writeOps = OPERATION_METADATA.filter(
15
- (m) => (m.kind === 'write' || m.kind === 'batch') && m.name !== 'updateEach',
15
+ (m) =>
16
+ (m.kind === 'write' || m.kind === 'batch') && m.name !== 'updateEach',
16
17
  )
17
18
 
18
- const readHandlers = readOps.map((meta) => {
19
- const exportName = `${modelName}${meta.name.charAt(0).toUpperCase() + meta.name.slice(1)}`
20
- return `
19
+ const readHandlers = readOps
20
+ .map((meta) => {
21
+ const exportName = `${modelName}${meta.name.charAt(0).toUpperCase() + meta.name.slice(1)}`
22
+ return `
21
23
  export async function ${exportName}(c: HandlerContext): Promise<void> {
22
- const data = await core.${meta.coreName}(buildContext(c))
24
+ const data = await executeOperation(
25
+ { variantKey: c.get('guardVariantKey'), caller: c.get('guardCaller') },
26
+ { guardShape: c.get('guardShape') },
27
+ {
28
+ core: core.${meta.coreName},
29
+ args: queryChannel(c),
30
+ body: c.get('body'),
31
+ prisma: requirePrisma(c.get('prisma')),
32
+ postgres: c.get('postgres'),
33
+ sqlite: c.get('sqlite'),
34
+ pagination: c.get('routeConfig')?.pagination,
35
+ override: c.get('operationOverride'),
36
+ getContext: c.get('getContext'),
37
+ },
38
+ )
23
39
  c.set('resultData', data)
24
40
  }`
25
- }).join('\n')
41
+ })
42
+ .join('\n')
26
43
 
27
- const writeHandlers = writeOps.map((meta) => {
28
- const exportName = `${modelName}${meta.name.charAt(0).toUpperCase() + meta.name.slice(1)}`
29
- return `
44
+ const writeHandlers = writeOps
45
+ .map((meta) => {
46
+ const exportName = `${modelName}${meta.name.charAt(0).toUpperCase() + meta.name.slice(1)}`
47
+ return `
30
48
  export async function ${exportName}(c: HandlerContext): Promise<void> {
31
- const data = await core.${meta.coreName}(buildContext(c))
49
+ const data = await executeOperation(
50
+ { variantKey: c.get('guardVariantKey'), caller: c.get('guardCaller') },
51
+ { guardShape: c.get('guardShape') },
52
+ {
53
+ core: core.${meta.coreName},
54
+ args: queryChannel(c),
55
+ body: c.get('body'),
56
+ prisma: requirePrisma(c.get('prisma')),
57
+ postgres: c.get('postgres'),
58
+ sqlite: c.get('sqlite'),
59
+ pagination: c.get('routeConfig')?.pagination,
60
+ override: c.get('operationOverride'),
61
+ getContext: c.get('getContext'),
62
+ },
63
+ )
32
64
  c.set('resultData', data)
33
65
  c.set('resultStatus', ${meta.successStatus})
34
66
  }`
35
- }).join('\n')
67
+ })
68
+ .join('\n')
36
69
 
37
70
  const updateEachExportName = `${modelName}UpdateEach`
38
71
  const updateEachHandler = `
@@ -45,12 +78,26 @@ export async function ${updateEachExportName}(c: HandlerContext): Promise<void>
45
78
  return `import type { Context } from 'hono'
46
79
  import * as core from './${modelName}Core${ext}'
47
80
  import type { OperationContext } from '../operationRuntime${ext}'
81
+ import {
82
+ executeOperation,
83
+ requirePrisma,
84
+ type ArgsChannel,
85
+ } from '../operationPipeline${ext}'
48
86
  import type { HonoInternalVariables } from '../routeConfig.target${ext}'
49
87
 
50
88
  type HandlerContext = Context<{ Variables: HonoInternalVariables }>
51
89
 
90
+ function queryChannel(c: HandlerContext): ArgsChannel {
91
+ return {
92
+ read: () => c.get('parsedQuery'),
93
+ write: (next) => c.set('parsedQuery', next),
94
+ }
95
+ }
96
+
52
97
  function buildContext(c: HandlerContext): OperationContext {
53
98
  return {
99
+ operationOverride: c.get('operationOverride'),
100
+ resolveOperationContext: c.get('getContext'),
54
101
  prisma: c.get('prisma'),
55
102
  postgres: c.get('postgres'),
56
103
  sqlite: c.get('sqlite'),
@@ -58,6 +105,7 @@ function buildContext(c: HandlerContext): OperationContext {
58
105
  body: c.get('body'),
59
106
  guardShape: c.get('guardShape'),
60
107
  guardCaller: c.get('guardCaller'),
108
+ guardVariantKey: c.get('guardVariantKey'),
61
109
  paginationConfig: c.get('routeConfig')?.pagination,
62
110
  }
63
111
  }
@@ -65,4 +113,4 @@ ${readHandlers}
65
113
  ${writeHandlers}
66
114
  ${updateEachHandler}
67
115
  `
68
- }
116
+ }
@@ -0,0 +1,93 @@
1
+ import type { DMMF } from '@prisma/generator-helper'
2
+ import { generateRouteConfigType } from './generateRouteConfigType'
3
+ import type { ImportStyle } from '../utils/resolveImportStyle'
4
+ import { importExt } from '../utils/importExt'
5
+ import type { WriteStrategy } from '../constants'
6
+ import { modelPathSegment, type PathCase } from '../utils/pathCasing'
7
+
8
+ /**
9
+ * The per-model OpenAPI part, in a module of its own so the phase 9 boundary
10
+ * holds by construction: a bundle that does not import this file has no path
11
+ * to `buildModelOpenApi` or `yaml`, regardless of bundler side-effect
12
+ * heuristics.
13
+ */
14
+ export function generateHonoOpenApiRoutes({
15
+ model,
16
+ enums,
17
+ guardShapesImport,
18
+ clientImport,
19
+ importStyle,
20
+ writeStrategy,
21
+ pathCase,
22
+ }: {
23
+ model: DMMF.Model
24
+ enums: DMMF.DatamodelEnum[]
25
+ guardShapesImport: string | null
26
+ clientImport?: string
27
+ importStyle: ImportStyle
28
+ writeStrategy: WriteStrategy
29
+ pathCase: PathCase
30
+ }): string {
31
+ const ext = importExt(importStyle)
32
+ const modelName = model.name
33
+ const modelSegment = modelPathSegment(modelName, pathCase)
34
+ const lower = modelName.charAt(0).toLowerCase() + modelName.slice(1)
35
+
36
+ return `import { Hono } from 'hono'
37
+ import type {
38
+ RouteConfig,
39
+ HonoEnvBase,
40
+ GeneratedHonoEnv,
41
+ PrismaClientLike,
42
+ } from '../routeConfig.target${ext}'
43
+ import { normalizePrefix, getEnv } from '../misc${ext}'
44
+ import { registerOpenApiRoutes } from '../routerParts${ext}'
45
+ import { buildModelOpenApi } from '../buildModelOpenApi${ext}'
46
+ import { MODEL_FIELDS, MODEL_ENUMS } from './${modelName}Metadata${ext}'
47
+
48
+ ${generateRouteConfigType(modelName, 'HonoBeforeHook', guardShapesImport, importStyle, 'hono', clientImport)}
49
+
50
+ export function ${lower}OpenApi<TCtx = unknown, TPrisma extends PrismaClientLike = PrismaClientLike, TEnv extends HonoEnvBase = HonoEnvBase>(config: ${modelName}RouteConfig<TCtx, TPrisma, TEnv> = {}): Hono<GeneratedHonoEnv<TEnv>> {
51
+ const _env = getEnv()
52
+ const customPrefix = normalizePrefix(config.customUrlPrefix || '')
53
+ const modelPrefix = config.addModelPrefix !== false ? '/${modelSegment}' : ''
54
+ const basePath = customPrefix + modelPrefix
55
+
56
+ const openApiDisabled = config.disableOpenApi === true
57
+ || (config.disableOpenApi !== false && (
58
+ _env.NODE_ENV === 'production'
59
+ || _env.DISABLE_OPENAPI === 'true'
60
+ ))
61
+
62
+ const app = new Hono<GeneratedHonoEnv<TEnv>>()
63
+ if (!openApiDisabled) {
64
+ let jsonCache: unknown = undefined
65
+ let yamlCache: string | undefined = undefined
66
+ registerOpenApiRoutes(app, basePath, () => {
67
+ if (jsonCache === undefined) {
68
+ jsonCache = buildModelOpenApi(
69
+ '${modelName}',
70
+ MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
71
+ MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
72
+ config as unknown as Parameters<typeof buildModelOpenApi>[3],
73
+ { format: 'json', writeStrategy: '${writeStrategy}', pathSegment: '${modelSegment}' },
74
+ )
75
+ }
76
+ return jsonCache
77
+ }, () => {
78
+ if (yamlCache === undefined) {
79
+ yamlCache = buildModelOpenApi(
80
+ '${modelName}',
81
+ MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
82
+ MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
83
+ config as unknown as Parameters<typeof buildModelOpenApi>[3],
84
+ { format: 'yaml', writeStrategy: '${writeStrategy}', pathSegment: '${modelSegment}' },
85
+ ) as string
86
+ }
87
+ return yamlCache
88
+ })
89
+ }
90
+ return app
91
+ }
92
+ `
93
+ }