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.
- package/README.md +188 -3432
- package/dist/client/encodeQueryParams.js +3 -1
- package/dist/client/encodeQueryParams.js.map +1 -1
- package/dist/copy/misc.d.ts +1 -0
- package/dist/copy/misc.js +14 -0
- package/dist/copy/misc.js.map +1 -1
- package/dist/copy/operationDefinitions.d.ts +4 -1
- package/dist/copy/operationDefinitions.js +133 -9
- package/dist/copy/operationDefinitions.js.map +1 -1
- package/dist/generators/generateFastifyHandler.d.ts +2 -2
- package/dist/generators/generateFastifyHandler.js +68 -12
- package/dist/generators/generateFastifyHandler.js.map +1 -1
- package/dist/generators/generateHonoHandler.d.ts +2 -2
- package/dist/generators/generateHonoHandler.js +53 -6
- package/dist/generators/generateHonoHandler.js.map +1 -1
- package/dist/generators/generateHonoOpenApi.d.ts +13 -0
- package/dist/generators/generateHonoOpenApi.js +70 -0
- package/dist/generators/generateHonoOpenApi.js.map +1 -0
- package/dist/generators/generateImportPrismaStatement.d.ts +1 -0
- package/dist/generators/generateImportPrismaStatement.js +6 -0
- package/dist/generators/generateImportPrismaStatement.js.map +1 -1
- package/dist/generators/generateMcpApp.d.ts +10 -0
- package/dist/generators/generateMcpApp.js +113 -0
- package/dist/generators/generateMcpApp.js.map +1 -0
- package/dist/generators/generateModelMcp.d.ts +9 -0
- package/dist/generators/generateModelMcp.js +145 -0
- package/dist/generators/generateModelMcp.js.map +1 -0
- package/dist/generators/generateModelMetadata.js +29 -15
- package/dist/generators/generateModelMetadata.js.map +1 -1
- package/dist/generators/generateOperationCore.d.ts +3 -3
- package/dist/generators/generateOperationCore.js +51 -16
- package/dist/generators/generateOperationCore.js.map +1 -1
- package/dist/generators/generateRelationMeta.js +2 -2
- package/dist/generators/generateRelationMeta.js.map +1 -1
- package/dist/generators/generateRouteConfigType.d.ts +2 -2
- package/dist/generators/generateRouteConfigType.js +29 -11
- package/dist/generators/generateRouteConfigType.js.map +1 -1
- package/dist/generators/generateRouter.d.ts +7 -4
- package/dist/generators/generateRouter.js +164 -65
- package/dist/generators/generateRouter.js.map +1 -1
- package/dist/generators/generateRouterFastify.d.ts +7 -4
- package/dist/generators/generateRouterFastify.js +106 -86
- package/dist/generators/generateRouterFastify.js.map +1 -1
- package/dist/generators/generateRouterHono.d.ts +7 -4
- package/dist/generators/generateRouterHono.js +59 -414
- package/dist/generators/generateRouterHono.js.map +1 -1
- package/dist/generators/generateRouterPartsHono.d.ts +14 -0
- package/dist/generators/generateRouterPartsHono.js +180 -0
- package/dist/generators/generateRouterPartsHono.js.map +1 -0
- package/dist/generators/generateUnifiedDocs.d.ts +1 -1
- package/dist/generators/generateUnifiedDocs.js +15 -8
- package/dist/generators/generateUnifiedDocs.js.map +1 -1
- package/dist/generators/generateUnifiedHandler.d.ts +2 -2
- package/dist/generators/generateUnifiedHandler.js +42 -18
- package/dist/generators/generateUnifiedHandler.js.map +1 -1
- package/dist/guardOptions.js +12 -7
- package/dist/guardOptions.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +123 -4
- package/dist/index.js.map +1 -1
- package/dist/utils/copyFiles.d.ts +1 -1
- package/dist/utils/copyFiles.js +42 -7
- package/dist/utils/copyFiles.js.map +1 -1
- package/dist/utils/importExt.d.ts +1 -1
- package/dist/utils/mcpGate.d.ts +10 -0
- package/dist/utils/mcpGate.js +118 -0
- package/dist/utils/mcpGate.js.map +1 -0
- package/dist/utils/pathCasing.d.ts +4 -0
- package/dist/utils/pathCasing.js +40 -0
- package/dist/utils/pathCasing.js.map +1 -0
- package/dist/utils/resolveImportStyle.js +13 -5
- package/dist/utils/resolveImportStyle.js.map +1 -1
- package/dist/utils/writeFileSafely.js +6 -0
- package/dist/utils/writeFileSafely.js.map +1 -1
- package/package.json +22 -5
- package/src/bin.ts +1 -1
- package/src/client/encodeQueryParams.ts +39 -35
- package/src/constants.ts +1 -1
- package/src/copy/autoIncludePlanner.ts +93 -27
- package/src/copy/autoIncludePlannerGuarded.ts +171 -48
- package/src/copy/autoIncludeRuntimeGuarded.ts +25 -7
- package/src/copy/buildModelOpenApi.ts +229 -176
- package/src/copy/concurrency.ts +10 -8
- package/src/copy/docsRenderer.ts +14 -15
- package/src/copy/errorMapper.ts +68 -19
- package/src/copy/guardHelpers.ts +30 -9
- package/src/copy/materializedCount.ts +17 -5
- package/src/copy/mcpRuntime.ts +1017 -0
- package/src/copy/misc.ts +88 -68
- package/src/copy/ndjson.ts +72 -0
- package/src/copy/operationDefinitions.ts +177 -25
- package/src/copy/operationPipeline.ts +267 -0
- package/src/copy/operationRuntime.ts +124 -26
- package/src/copy/operationSchemas.ts +2773 -0
- package/src/copy/pagination.ts +35 -13
- package/src/copy/parseQueryParams.ts +47 -48
- package/src/copy/projectionDefaults.ts +6 -4
- package/src/copy/routeConfig.express.ts +16 -4
- package/src/copy/routeConfig.fastify.ts +16 -2
- package/src/copy/routeConfig.hono.ts +46 -14
- package/src/copy/routeConfig.ts +175 -40
- package/src/copy/routerParts.ts +524 -0
- package/src/copy/scalarTypes.ts +7 -2
- package/src/copy/sse.ts +72 -19
- package/src/generators/generateFastifyHandler.ts +77 -20
- package/src/generators/generateHonoHandler.ts +62 -14
- package/src/generators/generateHonoOpenApi.ts +93 -0
- package/src/generators/generateImportPrismaStatement.ts +93 -81
- package/src/generators/generateMcpApp.ts +133 -0
- package/src/generators/generateModelMcp.ts +208 -0
- package/src/generators/generateModelMetadata.ts +40 -22
- package/src/generators/generateOperationCore.ts +81 -33
- package/src/generators/generateRelationMeta.ts +23 -11
- package/src/generators/generateRouteConfigType.ts +39 -13
- package/src/generators/generateRouter.ts +171 -68
- package/src/generators/generateRouterFastify.ts +117 -89
- package/src/generators/generateRouterHono.ts +84 -464
- package/src/generators/generateRouterPartsHono.ts +220 -0
- package/src/generators/generateUnifiedDocs.ts +29 -8
- package/src/generators/generateUnifiedHandler.ts +47 -24
- package/src/generators/generateUnifiedScalarUI.ts +1 -1
- package/src/guardOptions.ts +105 -92
- package/src/index.ts +161 -6
- package/src/utils/copyFiles.ts +235 -168
- package/src/utils/importExt.ts +2 -2
- package/src/utils/mcpGate.ts +157 -0
- package/src/utils/pathCasing.ts +42 -0
- package/src/utils/resolveImportStyle.ts +37 -15
- package/src/utils/strings.ts +2 -2
- package/src/utils/writeFileSafely.ts +12 -2
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { DMMF } from '@prisma/generator-helper'
|
|
1
|
+
import type { DMMF } from '@prisma/generator-helper'
|
|
2
2
|
import { generateRouteConfigType } from './generateRouteConfigType'
|
|
3
|
-
import { ImportStyle } from '../utils/resolveImportStyle'
|
|
3
|
+
import type { ImportStyle } from '../utils/resolveImportStyle'
|
|
4
4
|
import { importExt } from '../utils/importExt'
|
|
5
|
-
import { WriteStrategy } from '../constants'
|
|
5
|
+
import type { WriteStrategy } from '../constants'
|
|
6
|
+
import { modelPathSegment, type PathCase } from '../utils/pathCasing'
|
|
6
7
|
import { OPERATION_METADATA } from '../copy/operationDefinitions'
|
|
7
8
|
|
|
8
9
|
function pathExpr(suffix: string): string {
|
|
@@ -44,7 +45,10 @@ function opKindFor(opName: string): string {
|
|
|
44
45
|
}
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
function emitReadOp(
|
|
48
|
+
function emitReadOp(
|
|
49
|
+
meta: (typeof OPERATION_METADATA)[number],
|
|
50
|
+
modelName: string,
|
|
51
|
+
): string {
|
|
48
52
|
const c = meta.name.charAt(0).toUpperCase() + meta.name.slice(1)
|
|
49
53
|
const handlerName = `${modelName}${c}`
|
|
50
54
|
const pathValue = pathExpr(meta.pathSuffix)
|
|
@@ -52,22 +56,25 @@ function emitReadOp(meta: (typeof OPERATION_METADATA)[number], modelName: string
|
|
|
52
56
|
|
|
53
57
|
const postReadLine = meta.supportsPostRead
|
|
54
58
|
? meta.name === 'findMany'
|
|
55
|
-
? ` if (
|
|
59
|
+
? ` if (resolvePostReadsEnabled(config.disablePostReads, opConfig.disablePostReads)) {
|
|
56
60
|
const postPath = basePath ? \`\${basePath}/read\` : '/read'
|
|
57
|
-
app.post(postPath,
|
|
61
|
+
app.post(postPath, createReadRoute<TCtx, TPrisma, TEnv>({ config, opConfig, opKind: '${opKind}', handler: ${handlerName}, parse: 'body', dropGuard, settleBeforeHooks: SETTLE_BEFORE_HOOKS }))
|
|
58
62
|
}`
|
|
59
|
-
: ` if (
|
|
63
|
+
: ` if (resolvePostReadsEnabled(config.disablePostReads, opConfig.disablePostReads)) app.post(path, createReadRoute<TCtx, TPrisma, TEnv>({ config, opConfig, opKind: '${opKind}', handler: ${handlerName}, parse: 'body', dropGuard, settleBeforeHooks: SETTLE_BEFORE_HOOKS }))`
|
|
60
64
|
: ''
|
|
61
65
|
|
|
62
66
|
return ` if (isEnabled(config.${meta.configKey})) {
|
|
63
67
|
const opConfig = opFor('${meta.configKey}')
|
|
64
68
|
const path = ${pathValue}
|
|
65
|
-
app.get(path,
|
|
69
|
+
app.get(path, createReadRoute<TCtx, TPrisma, TEnv>({ config, opConfig, opKind: '${opKind}', handler: ${handlerName}, parse: 'query', dropGuard, settleBeforeHooks: SETTLE_BEFORE_HOOKS }))
|
|
66
70
|
${postReadLine}
|
|
67
71
|
}`
|
|
68
72
|
}
|
|
69
73
|
|
|
70
|
-
function emitWriteOp(
|
|
74
|
+
function emitWriteOp(
|
|
75
|
+
meta: (typeof OPERATION_METADATA)[number],
|
|
76
|
+
modelName: string,
|
|
77
|
+
): string {
|
|
71
78
|
const c = meta.name.charAt(0).toUpperCase() + meta.name.slice(1)
|
|
72
79
|
const handlerName = `${modelName}${c}`
|
|
73
80
|
const pathValue = pathExpr(meta.pathSuffix)
|
|
@@ -76,128 +83,94 @@ function emitWriteOp(meta: (typeof OPERATION_METADATA)[number], modelName: strin
|
|
|
76
83
|
return ` if (isEnabled(config.${meta.configKey})) {
|
|
77
84
|
const opConfig = opFor('${meta.configKey}')
|
|
78
85
|
const path = ${pathValue}
|
|
79
|
-
app.${meta.method}(path,
|
|
86
|
+
app.${meta.method}(path, createWriteRoute<TCtx, TPrisma, TEnv>({ config, opConfig, opKind: '${opKind}', handler: ${handlerName}, dropGuard, settleBeforeHooks: SETTLE_BEFORE_HOOKS }))
|
|
80
87
|
}`
|
|
81
88
|
}
|
|
82
89
|
|
|
83
90
|
/**
|
|
84
91
|
* Guard behaviour is SEVEN INDEPENDENT OPT-IN CONTROLS on the route config.
|
|
92
|
+
* See routeConfig.ts; the long rationale lives with the controls.
|
|
85
93
|
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* Each is now its own option, each defaulting to the pre-1.64.2 behaviour:
|
|
93
|
-
*
|
|
94
|
-
* - `requireGuardShape` refuse an operation with no guard
|
|
95
|
-
* - `validateGuardShapes` refuse an empty or key-mixing shape
|
|
96
|
-
* - `requireDefaultVariantOptIn` confirm a `default` variant
|
|
97
|
-
* - `enableUpdateEach` register the batch route (default true)
|
|
98
|
-
* - `guardResolutionOrder` settle the guard before or after hooks
|
|
99
|
-
* - `allowE2EGuardBypass` honour E2E=true (default true)
|
|
100
|
-
* - `validateResolvedShapes` check what a shape function returned
|
|
101
|
-
*
|
|
102
|
-
* `HARDENED_GUARD_PROFILE` selects all seven, as values to spread rather than a
|
|
103
|
-
* mode to store. See routeConfig.ts.
|
|
104
|
-
*
|
|
105
|
-
* Guard dropping is then a GENERATION-TIME decision, never a runtime one.
|
|
106
|
-
*
|
|
107
|
-
* The emitted router used to compute `DROP_GUARD = <flag> || _env.E2E === 'true'`,
|
|
108
|
-
* so setting `E2E=true` in a deployed environment downgraded enforcement even
|
|
109
|
-
* when the generator had been told to keep the guard. The two modes are not
|
|
110
|
-
* equivalent: with the guard, the shape goes to prisma-guard; with it dropped,
|
|
111
|
-
* `applyDroppedGuard` applies projection defaults and forced `where` clauses and
|
|
112
|
-
* nothing else is validated against the shape.
|
|
113
|
-
*
|
|
114
|
-
* On an edge runtime that variable is an ordinary config var — set on a staging
|
|
115
|
-
* deployment, copied forward, flagged as security-relevant nowhere. A
|
|
116
|
-
* deployment's guard behaviour has to be a property of the artifact, not of the
|
|
117
|
-
* environment it happens to land in.
|
|
118
|
-
*
|
|
119
|
-
* The rationale lives here rather than in the emitted file on purpose: generated
|
|
120
|
-
* output is an artifact, and a paragraph about a bypass that no longer exists
|
|
121
|
-
* would be copied into every router this generator writes.
|
|
122
|
-
*
|
|
123
|
-
* `updateEach` is refused for the same reason, one step further on — and, again,
|
|
124
|
-
* only under the flag. It bypasses guard shapes by design — the endpoint is a
|
|
125
|
-
* batch of `{ where, data }` items applied directly — and the only thing between
|
|
126
|
-
* it and an unguarded mass mutation is a `console.warn` suppressed in
|
|
127
|
-
* production. A warning is not a security boundary; it is advice to whoever
|
|
128
|
-
* happens to be reading a development log. Refused at construction rather than
|
|
129
|
-
* silently dropped, so a deployment expecting the route learns at boot instead
|
|
130
|
-
* of at the first 404.
|
|
94
|
+
* Since phase S the orchestration itself is shared: every route is created by
|
|
95
|
+
* `../routerParts` through the same pipeline stages the other targets and MCP
|
|
96
|
+
* use, and this factory only decides WHICH routes exist. That module is also
|
|
97
|
+
* the phase 9 static boundary — per-op route factories live there, so a
|
|
98
|
+
* bundle importing only selected parts omits the rest.
|
|
131
99
|
*/
|
|
132
100
|
export function generateHonoRouterFunction({
|
|
133
101
|
model,
|
|
134
102
|
enums,
|
|
135
103
|
guardShapesImport,
|
|
104
|
+
clientImport,
|
|
136
105
|
importStyle,
|
|
137
106
|
writeStrategy,
|
|
138
107
|
dropGuard,
|
|
108
|
+
pathCase,
|
|
139
109
|
}: {
|
|
140
110
|
model: DMMF.Model
|
|
141
111
|
enums: DMMF.DatamodelEnum[]
|
|
142
112
|
guardShapesImport: string | null
|
|
113
|
+
clientImport?: string
|
|
143
114
|
importStyle: ImportStyle
|
|
144
115
|
writeStrategy: WriteStrategy
|
|
145
116
|
dropGuard: boolean
|
|
117
|
+
pathCase: PathCase
|
|
146
118
|
}): string {
|
|
147
119
|
const ext = importExt(importStyle)
|
|
148
120
|
const modelName = model.name
|
|
149
|
-
const
|
|
121
|
+
const modelSegment = modelPathSegment(modelName, pathCase)
|
|
150
122
|
const routerFunctionName = `${modelName}Router`
|
|
151
123
|
|
|
152
|
-
const handlerImports = OPERATION_METADATA
|
|
153
|
-
|
|
154
|
-
|
|
124
|
+
const handlerImports = OPERATION_METADATA.map(
|
|
125
|
+
(m) => ` ${modelName}${m.name.charAt(0).toUpperCase() + m.name.slice(1)},`,
|
|
126
|
+
).join('\n')
|
|
155
127
|
|
|
156
128
|
const readOps = OPERATION_METADATA.filter((m) => m.kind === 'read')
|
|
157
|
-
const writeOps = OPERATION_METADATA.filter(
|
|
158
|
-
|
|
129
|
+
const writeOps = OPERATION_METADATA.filter(
|
|
130
|
+
(m) => m.kind === 'write' || m.kind === 'batch',
|
|
131
|
+
).filter((m) => m.name !== 'updateEach')
|
|
159
132
|
|
|
160
133
|
const readOpBlocks = readOps.map((m) => emitReadOp(m, modelName)).join('\n\n')
|
|
161
|
-
const writeOpBlocks = writeOps
|
|
134
|
+
const writeOpBlocks = writeOps
|
|
135
|
+
.map((m) => emitWriteOp(m, modelName))
|
|
136
|
+
.join('\n\n')
|
|
162
137
|
|
|
163
138
|
return `import { Hono } from 'hono'
|
|
164
|
-
import type { Context } from 'hono'
|
|
165
|
-
import type { ContentfulStatusCode } from 'hono/utils/http-status'
|
|
166
|
-
import { HTTPException } from 'hono/http-exception'
|
|
167
139
|
import {
|
|
168
140
|
${handlerImports}
|
|
169
141
|
} from './${modelName}Handlers${ext}'
|
|
170
142
|
import type {
|
|
171
143
|
RouteConfig,
|
|
172
|
-
HonoBeforeHook,
|
|
173
|
-
HonoAfterHook,
|
|
174
144
|
HonoEnvBase,
|
|
175
|
-
HonoInternalVariables,
|
|
176
145
|
GeneratedHonoEnv,
|
|
177
|
-
|
|
146
|
+
PrismaClientLike,
|
|
178
147
|
} from '../routeConfig.target${ext}'
|
|
179
|
-
import {
|
|
180
|
-
import { normalizePrefix, getEnv, sanitizeKeys, isPlainObject } from '../misc${ext}'
|
|
148
|
+
import { normalizePrefix, getEnv, resolveDropGuardEnv } from '../misc${ext}'
|
|
181
149
|
import { buildModelOpenApi } from '../buildModelOpenApi${ext}'
|
|
182
150
|
import {
|
|
183
|
-
normalizeOperation,
|
|
184
|
-
resolveOperationVariantKey,
|
|
185
151
|
resolveGuardPolicy,
|
|
186
|
-
|
|
152
|
+
resolvePostReadsEnabled,
|
|
187
153
|
validateCountSourceWhere,
|
|
188
|
-
validateOperationConfig,
|
|
189
154
|
validateUpdateEachConfig,
|
|
155
|
+
warnIfUnguardedRoutes,
|
|
190
156
|
} from '../routeConfig${ext}'
|
|
191
|
-
import
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
157
|
+
import {
|
|
158
|
+
createReadRoute,
|
|
159
|
+
createWriteRoute,
|
|
160
|
+
createUpdateEachRoute,
|
|
161
|
+
opConfigFor,
|
|
162
|
+
normalizeHonoOperation,
|
|
163
|
+
registerOpenApiRoutes,
|
|
164
|
+
sendError,
|
|
165
|
+
type HandlerContext,
|
|
166
|
+
type HonoOpConfig,
|
|
167
|
+
} from '../routerParts${ext}'
|
|
168
|
+
// The legacy router factory preserves pre-parts behavior: materialized-view
|
|
169
|
+
// counting is registered on import. The per-op parts boundary keeps this
|
|
170
|
+
// module out of opt-in small bundles.
|
|
198
171
|
import { MODEL_FIELDS, MODEL_ENUMS } from './${modelName}Metadata${ext}'
|
|
199
172
|
|
|
200
|
-
${generateRouteConfigType(modelName, 'HonoBeforeHook', guardShapesImport, importStyle, 'hono')}
|
|
173
|
+
${generateRouteConfigType(modelName, 'HonoBeforeHook', guardShapesImport, importStyle, 'hono', clientImport)}
|
|
201
174
|
const _env = getEnv()
|
|
202
175
|
|
|
203
176
|
// Fixed at generation time. The \`allowE2EGuardBypass\` control decides whether the
|
|
@@ -205,243 +178,7 @@ const _env = getEnv()
|
|
|
205
178
|
// which is upstream behaviour. See generateRouterHono.ts.
|
|
206
179
|
const DROP_GUARD = ${dropGuard}
|
|
207
180
|
|
|
208
|
-
|
|
209
|
-
* DELIBERATELY NOT RECURSIVE.
|
|
210
|
-
*
|
|
211
|
-
* A self-referential JSON type sent through \`c.json()\` makes Hono's response
|
|
212
|
-
* inference instantiate without a fixed point, and TypeScript answers
|
|
213
|
-
* "type instantiation is excessively deep" — in the emitted file, for every
|
|
214
|
-
* consumer. One level is all this needs: the value is serialised, not walked.
|
|
215
|
-
*/
|
|
216
|
-
type JsonLike = string | number | boolean | null | unknown[] | Record<string, unknown>
|
|
217
|
-
|
|
218
|
-
type OperationConfigLike<TEnv extends HonoEnvBase> = {
|
|
219
|
-
before?: HonoBeforeHook<TEnv>[]
|
|
220
|
-
after?: HonoAfterHook<TEnv>[]
|
|
221
|
-
shape?: unknown
|
|
222
|
-
variants?: Record<
|
|
223
|
-
string,
|
|
224
|
-
{
|
|
225
|
-
shape?: unknown
|
|
226
|
-
before?: HonoBeforeHook<TEnv>[]
|
|
227
|
-
after?: HonoAfterHook<TEnv>[]
|
|
228
|
-
}
|
|
229
|
-
>
|
|
230
|
-
pagination?: Partial<PaginationConfig>
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
type NormalizedOp<TEnv extends HonoEnvBase> = NormalizedOperationConfig<
|
|
234
|
-
HonoBeforeHook<TEnv>,
|
|
235
|
-
HonoAfterHook<TEnv>
|
|
236
|
-
>
|
|
237
|
-
|
|
238
|
-
function normalizeHonoOperation<TEnv extends HonoEnvBase>(
|
|
239
|
-
config: OperationConfigLike<TEnv> | undefined,
|
|
240
|
-
): NormalizedOp<TEnv> {
|
|
241
|
-
return normalizeOperation<HonoBeforeHook<TEnv>, HonoAfterHook<TEnv>>(config)
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
type HandlerContext = Context<{ Variables: HonoInternalVariables }>
|
|
245
|
-
|
|
246
|
-
async function parseQueryMiddleware(c: HandlerContext): Promise<void> {
|
|
247
|
-
const raw = c.req.query() as Record<string, unknown>
|
|
248
|
-
if (raw && Object.keys(raw).length > 0) {
|
|
249
|
-
c.set('parsedQuery', parseQueryParams(raw) as Record<string, unknown>)
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
async function parseBodyAsQueryMiddleware(c: HandlerContext): Promise<void> {
|
|
254
|
-
let body: unknown
|
|
255
|
-
try {
|
|
256
|
-
body = await c.req.json()
|
|
257
|
-
} catch {
|
|
258
|
-
throw new HTTPException(400, { message: 'Request body must be a JSON object' })
|
|
259
|
-
}
|
|
260
|
-
if (!body || typeof body !== 'object' || Array.isArray(body)) {
|
|
261
|
-
throw new HTTPException(400, { message: 'Request body must be a JSON object' })
|
|
262
|
-
}
|
|
263
|
-
c.set('parsedQuery', sanitizeKeys(body as Record<string, unknown>))
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
async function parseUpdateEachBodyMiddleware(c: HandlerContext): Promise<void> {
|
|
267
|
-
let body: unknown
|
|
268
|
-
try {
|
|
269
|
-
body = await c.req.json()
|
|
270
|
-
} catch {
|
|
271
|
-
throw new HTTPException(400, { message: 'updateEach body must be an array of { where, data } items' })
|
|
272
|
-
}
|
|
273
|
-
if (!Array.isArray(body)) {
|
|
274
|
-
throw new HTTPException(400, { message: 'updateEach body must be an array of { where, data } items' })
|
|
275
|
-
}
|
|
276
|
-
c.set('body', body)
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
async function parseWriteBodyMiddleware(c: HandlerContext): Promise<void> {
|
|
280
|
-
let body: unknown
|
|
281
|
-
try {
|
|
282
|
-
body = await c.req.json()
|
|
283
|
-
} catch {
|
|
284
|
-
throw new HTTPException(400, { message: 'Request body must be a JSON object' })
|
|
285
|
-
}
|
|
286
|
-
if (!body || typeof body !== 'object' || Array.isArray(body)) {
|
|
287
|
-
throw new HTTPException(400, { message: 'Request body must be a JSON object' })
|
|
288
|
-
}
|
|
289
|
-
c.set('body', body)
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
function makeShapeMiddleware<TCtx, TPrisma, TEnv extends HonoEnvBase>(
|
|
293
|
-
config: ${modelName}RouteConfig<TCtx, TPrisma, TEnv>,
|
|
294
|
-
opConfig: NormalizedOp<TEnv>,
|
|
295
|
-
opKind: OpKind,
|
|
296
|
-
) {
|
|
297
|
-
const policy = resolveGuardPolicy(config)
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* The environment bypass, honoured unless the consumer turned it off.
|
|
301
|
-
*
|
|
302
|
-
* \`E2E=true\` downgrading enforcement in a deployed environment is a real
|
|
303
|
-
* hazard, and it is also upstream behaviour — so it is a control, not a
|
|
304
|
-
* decision made here.
|
|
305
|
-
*/
|
|
306
|
-
const dropGuard = DROP_GUARD || (policy.allowE2EGuardBypass && _env.E2E === 'true')
|
|
307
|
-
|
|
308
|
-
return async (c: Context<GeneratedHonoEnv<TEnv>>): Promise<void> => {
|
|
309
|
-
/**
|
|
310
|
-
* INTERNAL REQUEST STATE IS SET THROUGH A CONCRETE CONTEXT.
|
|
311
|
-
*
|
|
312
|
-
* \`GeneratedHonoEnv<TEnv>['Variables']\` is \`HonoInternalVariables & TEnv['Variables']\`,
|
|
313
|
-
* and inside this generic function TypeScript cannot know that the consumer's
|
|
314
|
-
* half does not also declare \`routeConfig\` — so setting that key straight
|
|
315
|
-
* onto \`c\` is unassignable to the intersection and the emitted router does not
|
|
316
|
-
* typecheck under \`strict\` for ANY consumer. These keys belong to this
|
|
317
|
-
* router, so they are written through the internal shape they were declared
|
|
318
|
-
* in.
|
|
319
|
-
*/
|
|
320
|
-
const vars = c as unknown as HandlerContext
|
|
321
|
-
|
|
322
|
-
const merged = mergePaginationConfig(config.pagination, opConfig.pagination)
|
|
323
|
-
if (merged) vars.set('routeConfig', { pagination: merged })
|
|
324
|
-
|
|
325
|
-
const headerName = config.guard?.variantHeader || 'x-api-variant'
|
|
326
|
-
const headerValue = c.req.header(headerName)
|
|
327
|
-
const caller = config.guard?.resolveVariant?.(c) ?? headerValue ?? undefined
|
|
328
|
-
if (typeof caller === 'string') vars.set('guardCaller', caller)
|
|
329
|
-
|
|
330
|
-
const resolution = resolveOperationVariantKey(opConfig.guardRouting, caller)
|
|
331
|
-
if (!resolution.ok) {
|
|
332
|
-
vars.set('guardVariantFailure', resolution)
|
|
333
|
-
return
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
const resolvedKey =
|
|
337
|
-
opConfig.guardRouting.kind === 'named'
|
|
338
|
-
? resolution.key
|
|
339
|
-
: undefined
|
|
340
|
-
if (resolvedKey !== undefined) vars.set('guardVariantKey', resolvedKey)
|
|
341
|
-
|
|
342
|
-
if (opConfig.guardShape) {
|
|
343
|
-
const resolveCtx = typeof config.resolveContext === 'function'
|
|
344
|
-
? () => (config.resolveContext as unknown as (ctx: Context<GeneratedHonoEnv<TEnv>>) => unknown | Promise<unknown>)(c)
|
|
345
|
-
: undefined
|
|
346
|
-
|
|
347
|
-
/**
|
|
348
|
-
* With \`validateResolvedShapes\`, resolved ONCE here and the validated value
|
|
349
|
-
* is what travels onward — see resolveGuardShapeOnce. Passing the function
|
|
350
|
-
* on would let it return a different shape when it is enforced than when it
|
|
351
|
-
* was checked.
|
|
352
|
-
*
|
|
353
|
-
* Without it the raw shape is passed through untouched, which is upstream
|
|
354
|
-
* behaviour: it is resolved at the point of use, and a function returning
|
|
355
|
-
* something unusable is not this router's business to refuse.
|
|
356
|
-
*/
|
|
357
|
-
let effectiveShape: Record<string, unknown> | undefined = opConfig.guardShape
|
|
358
|
-
if (policy.validateResolvedShapes) {
|
|
359
|
-
const resolution = await resolveGuardShapeOnce(opConfig.guardShape, resolvedKey, resolveCtx)
|
|
360
|
-
if (!resolution.ok) {
|
|
361
|
-
vars.set('guardShapeFailure', resolution.problem)
|
|
362
|
-
return
|
|
363
|
-
}
|
|
364
|
-
// \`ok\` means the resolved value is a shape object; the fallback keeps the
|
|
365
|
-
// declared shape rather than storing something the context cannot hold.
|
|
366
|
-
effectiveShape = isPlainObject(resolution.shape) ? resolution.shape : opConfig.guardShape
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
if (!dropGuard) {
|
|
370
|
-
vars.set('guardShape', effectiveShape)
|
|
371
|
-
} else {
|
|
372
|
-
await applyDroppedGuard(
|
|
373
|
-
effectiveShape,
|
|
374
|
-
resolvedKey,
|
|
375
|
-
resolveCtx,
|
|
376
|
-
opKind,
|
|
377
|
-
{
|
|
378
|
-
readQuery: vars.get('parsedQuery'),
|
|
379
|
-
writeBody: isPlainObject(vars.get('body'))
|
|
380
|
-
? (vars.get('body') as Record<string, unknown>)
|
|
381
|
-
: undefined,
|
|
382
|
-
},
|
|
383
|
-
() => {
|
|
384
|
-
let target = vars.get('parsedQuery')
|
|
385
|
-
if (!target) {
|
|
386
|
-
target = {}
|
|
387
|
-
vars.set('parsedQuery', target)
|
|
388
|
-
}
|
|
389
|
-
return target
|
|
390
|
-
},
|
|
391
|
-
() => {
|
|
392
|
-
let target = vars.get('body')
|
|
393
|
-
if (!isPlainObject(target)) {
|
|
394
|
-
target = {}
|
|
395
|
-
vars.set('body', target)
|
|
396
|
-
}
|
|
397
|
-
return target as Record<string, unknown>
|
|
398
|
-
},
|
|
399
|
-
)
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
async function runBeforeHooks<TEnv extends HonoEnvBase>(
|
|
406
|
-
hooks: readonly HonoBeforeHook<TEnv>[],
|
|
407
|
-
c: Context<GeneratedHonoEnv<TEnv>>,
|
|
408
|
-
): Promise<Response | undefined> {
|
|
409
|
-
for (const hook of hooks) {
|
|
410
|
-
const result = await hook(c)
|
|
411
|
-
if (result instanceof Response) return result
|
|
412
|
-
}
|
|
413
|
-
return undefined
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
async function runAfterHooks<TEnv extends HonoEnvBase>(
|
|
417
|
-
hooks: readonly HonoAfterHook<TEnv>[],
|
|
418
|
-
c: Context<GeneratedHonoEnv<TEnv>>,
|
|
419
|
-
): Promise<Response | undefined> {
|
|
420
|
-
for (const hook of hooks) {
|
|
421
|
-
const result = await hook(c)
|
|
422
|
-
if (result instanceof Response) return result
|
|
423
|
-
}
|
|
424
|
-
return undefined
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
function sendResult(c: HandlerContext): Response {
|
|
428
|
-
const data = c.get('resultData')
|
|
429
|
-
const status = (c.get('resultStatus') as number | undefined) ?? 200
|
|
430
|
-
if (data === undefined) {
|
|
431
|
-
throw new HTTPException(500, { message: 'No data set by handler' })
|
|
432
|
-
}
|
|
433
|
-
return c.json(transformResult(data) as JsonLike, status as ContentfulStatusCode)
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
function sendError(c: HandlerContext, error: unknown): Response {
|
|
437
|
-
if (error instanceof HTTPException) {
|
|
438
|
-
return c.json({ message: error.message }, error.status as ContentfulStatusCode)
|
|
439
|
-
}
|
|
440
|
-
const httpError = mapError(error)
|
|
441
|
-
return c.json({ message: httpError.message }, httpError.status as ContentfulStatusCode)
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extends HonoEnvBase = HonoEnvBase>(config: ${modelName}RouteConfig<TCtx, TPrisma, TEnv> = {}): Hono<GeneratedHonoEnv<TEnv>> {
|
|
181
|
+
export function ${routerFunctionName}<TCtx = unknown, TPrisma extends PrismaClientLike = PrismaClientLike, TEnv extends HonoEnvBase = HonoEnvBase>(config: ${modelName}RouteConfig<TCtx, TPrisma, TEnv> = {}): Hono<GeneratedHonoEnv<TEnv>> {
|
|
445
182
|
validateCountSourceWhere(config.pagination?.countSource, '${modelName} pagination')
|
|
446
183
|
validateCountSourceWhere(
|
|
447
184
|
(config.findManyPaginated && typeof config.findManyPaginated === 'object' ? config.findManyPaginated : undefined)?.pagination?.countSource,
|
|
@@ -451,19 +188,36 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
|
|
|
451
188
|
const app = new Hono<GeneratedHonoEnv<TEnv>>()
|
|
452
189
|
|
|
453
190
|
const isEnabled = (value: unknown): boolean => value !== false && !!(config.enableAll || value)
|
|
191
|
+
warnIfUnguardedRoutes('${modelName}', ['findMany', 'findUnique', 'findUniqueOrThrow', 'findFirst', 'findFirstOrThrow', 'findManyPaginated', 'count', 'aggregate', 'groupBy', 'create', 'createMany', 'createManyAndReturn', 'update', 'updateMany', 'updateManyAndReturn', 'upsert', 'delete', 'deleteMany'], config, isEnabled)
|
|
454
192
|
|
|
455
193
|
const customPrefix = normalizePrefix(config.customUrlPrefix || '')
|
|
456
|
-
const modelPrefix = config.addModelPrefix !== false ? '/${
|
|
194
|
+
const modelPrefix = config.addModelPrefix !== false ? '/${modelSegment}' : ''
|
|
457
195
|
const basePath = customPrefix + modelPrefix
|
|
458
196
|
|
|
197
|
+
const POLICY = resolveGuardPolicy(config)
|
|
198
|
+
const SETTLE_BEFORE_HOOKS = POLICY.guardResolutionOrder === 'before-hooks'
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* The environment bypass, honoured unless the consumer turned it off.
|
|
202
|
+
* \`PGE_DROP_GUARD=true\` downgrading enforcement in a deployed environment is
|
|
203
|
+
* a real hazard, and it is also upstream behaviour (under its deprecated
|
|
204
|
+
* \`E2E=true\` spelling) — so it is a control, not a decision made here.
|
|
205
|
+
*/
|
|
206
|
+
const dropGuard = DROP_GUARD || (POLICY.allowE2EGuardBypass && resolveDropGuardEnv(_env))
|
|
207
|
+
|
|
208
|
+
const opFor = <K extends keyof ${modelName}RouteConfig<TCtx, TPrisma, TEnv>>(
|
|
209
|
+
key: K,
|
|
210
|
+
): HonoOpConfig<TEnv> => {
|
|
211
|
+
const raw = config[key] as unknown as Parameters<typeof normalizeHonoOperation<TEnv>>[0]
|
|
212
|
+
return opConfigFor<TEnv>(raw, '${modelName}.' + String(key), POLICY)
|
|
213
|
+
}
|
|
214
|
+
|
|
459
215
|
const openApiDisabled = config.disableOpenApi === true
|
|
460
216
|
|| (config.disableOpenApi !== false && (
|
|
461
217
|
_env.NODE_ENV === 'production'
|
|
462
218
|
|| _env.DISABLE_OPENAPI === 'true'
|
|
463
219
|
))
|
|
464
220
|
|
|
465
|
-
const postReadsEnabled = !config.disablePostReads
|
|
466
|
-
|
|
467
221
|
let _openApiJsonCache: unknown = undefined
|
|
468
222
|
const getOpenApiJson = (): unknown => {
|
|
469
223
|
if (_openApiJsonCache === undefined) {
|
|
@@ -472,7 +226,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
|
|
|
472
226
|
MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
|
|
473
227
|
MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
|
|
474
228
|
config as unknown as Parameters<typeof buildModelOpenApi>[3],
|
|
475
|
-
{ format: 'json', writeStrategy: '${writeStrategy}' },
|
|
229
|
+
{ format: 'json', writeStrategy: '${writeStrategy}', pathSegment: '${modelSegment}' },
|
|
476
230
|
)
|
|
477
231
|
}
|
|
478
232
|
return _openApiJsonCache
|
|
@@ -485,7 +239,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
|
|
|
485
239
|
MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
|
|
486
240
|
MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
|
|
487
241
|
config as unknown as Parameters<typeof buildModelOpenApi>[3],
|
|
488
|
-
{ format: 'yaml', writeStrategy: '${writeStrategy}' },
|
|
242
|
+
{ format: 'yaml', writeStrategy: '${writeStrategy}', pathSegment: '${modelSegment}' },
|
|
489
243
|
) as string
|
|
490
244
|
}
|
|
491
245
|
return _openApiYamlCache
|
|
@@ -503,125 +257,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
|
|
|
503
257
|
})
|
|
504
258
|
|
|
505
259
|
if (!openApiDisabled) {
|
|
506
|
-
|
|
507
|
-
const openapiYamlPath = basePath ? \`\${basePath}/openapi.yaml\` : '/openapi.yaml'
|
|
508
|
-
app.get(openapiJsonPath, (c) => c.json(getOpenApiJson() as JsonLike))
|
|
509
|
-
app.get(openapiYamlPath, (c) => {
|
|
510
|
-
c.header('Content-Type', 'application/yaml')
|
|
511
|
-
return c.body(getOpenApiYaml())
|
|
512
|
-
})
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
const POLICY = resolveGuardPolicy(config)
|
|
516
|
-
const SETTLE_BEFORE_HOOKS = POLICY.guardResolutionOrder === 'before-hooks'
|
|
517
|
-
|
|
518
|
-
/**
|
|
519
|
-
* The guard-resolution failures, raised in one place so both handlers and both
|
|
520
|
-
* orderings share exactly one definition of what a failure is.
|
|
521
|
-
*
|
|
522
|
-
* WHERE it is called is the behavioural difference, and it is the
|
|
523
|
-
* \`guardResolutionOrder\` control. \`'before-hooks'\` runs it before any operation
|
|
524
|
-
* hook, because a hook that returns a Response — an auth gate, a cache, a
|
|
525
|
-
* short-circuit for a known caller — would otherwise answer a request whose
|
|
526
|
-
* guard was never established. \`'after-hooks'\` is upstream behaviour.
|
|
527
|
-
*
|
|
528
|
-
* \`guardShapeFailure\` is only ever set when \`validateResolvedShapes\` is on, so
|
|
529
|
-
* that branch is inert rather than merely unreached when it is off.
|
|
530
|
-
*/
|
|
531
|
-
const settleGuard = (c: Context<GeneratedHonoEnv<TEnv>>): void => {
|
|
532
|
-
const vars = c as unknown as HandlerContext
|
|
533
|
-
const failure = vars.get('guardVariantFailure')
|
|
534
|
-
if (failure) {
|
|
535
|
-
throw new HTTPException(400, {
|
|
536
|
-
message: formatGuardVariantResolutionError(failure),
|
|
537
|
-
})
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
const shapeFailure = vars.get('guardShapeFailure')
|
|
541
|
-
if (shapeFailure) {
|
|
542
|
-
throw new HTTPException(500, {
|
|
543
|
-
message: 'guard shape could not be resolved: ' + shapeFailure,
|
|
544
|
-
})
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
const handleRead = (
|
|
549
|
-
opConfig: NormalizedOp<TEnv>,
|
|
550
|
-
handlerFn: (c: HandlerContext) => Promise<void>,
|
|
551
|
-
parseFn: (c: HandlerContext) => Promise<void>,
|
|
552
|
-
opKind: OpKind,
|
|
553
|
-
) => async (c: Context<GeneratedHonoEnv<TEnv>>): Promise<Response> => {
|
|
554
|
-
try {
|
|
555
|
-
await parseFn(c as unknown as HandlerContext)
|
|
556
|
-
await makeShapeMiddleware<TCtx, TPrisma, TEnv>(config, opConfig, opKind)(c)
|
|
557
|
-
|
|
558
|
-
// Settled BEFORE any hook can answer the request, when asked for.
|
|
559
|
-
if (SETTLE_BEFORE_HOOKS) settleGuard(c)
|
|
560
|
-
|
|
561
|
-
const operationBefore = await runBeforeHooks<TEnv>(opConfig.operationBefore, c)
|
|
562
|
-
if (operationBefore) return operationBefore
|
|
563
|
-
|
|
564
|
-
// Upstream order: hooks first, guard failure after.
|
|
565
|
-
if (!SETTLE_BEFORE_HOOKS) settleGuard(c)
|
|
566
|
-
|
|
567
|
-
const key = (c as unknown as HandlerContext).get('guardVariantKey')
|
|
568
|
-
const variantHooks =
|
|
569
|
-
key !== undefined ? opConfig.variantHooks[key] : undefined
|
|
570
|
-
|
|
571
|
-
const variantBefore = await runBeforeHooks<TEnv>(variantHooks?.before ?? [], c)
|
|
572
|
-
if (variantBefore) return variantBefore
|
|
573
|
-
await handlerFn(c as unknown as HandlerContext)
|
|
574
|
-
const variantAfter = await runAfterHooks<TEnv>(variantHooks?.after ?? [], c)
|
|
575
|
-
if (variantAfter) return variantAfter
|
|
576
|
-
const operationAfter = await runAfterHooks<TEnv>(opConfig.operationAfter, c)
|
|
577
|
-
if (operationAfter) return operationAfter
|
|
578
|
-
return sendResult(c as unknown as HandlerContext)
|
|
579
|
-
} catch (error: unknown) {
|
|
580
|
-
return sendError(c as unknown as HandlerContext, error)
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
const handleWrite = (
|
|
585
|
-
opConfig: NormalizedOp<TEnv>,
|
|
586
|
-
handlerFn: (c: HandlerContext) => Promise<void>,
|
|
587
|
-
opKind: OpKind,
|
|
588
|
-
) => async (c: Context<GeneratedHonoEnv<TEnv>>): Promise<Response> => {
|
|
589
|
-
try {
|
|
590
|
-
await parseWriteBodyMiddleware(c as unknown as HandlerContext)
|
|
591
|
-
await makeShapeMiddleware<TCtx, TPrisma, TEnv>(config, opConfig, opKind)(c)
|
|
592
|
-
|
|
593
|
-
// Settled BEFORE any hook can answer the request, when asked for.
|
|
594
|
-
if (SETTLE_BEFORE_HOOKS) settleGuard(c)
|
|
595
|
-
|
|
596
|
-
const operationBefore = await runBeforeHooks<TEnv>(opConfig.operationBefore, c)
|
|
597
|
-
if (operationBefore) return operationBefore
|
|
598
|
-
|
|
599
|
-
// Upstream order: hooks first, guard failure after.
|
|
600
|
-
if (!SETTLE_BEFORE_HOOKS) settleGuard(c)
|
|
601
|
-
|
|
602
|
-
const key = (c as unknown as HandlerContext).get('guardVariantKey')
|
|
603
|
-
const variantHooks =
|
|
604
|
-
key !== undefined ? opConfig.variantHooks[key] : undefined
|
|
605
|
-
|
|
606
|
-
const variantBefore = await runBeforeHooks<TEnv>(variantHooks?.before ?? [], c)
|
|
607
|
-
if (variantBefore) return variantBefore
|
|
608
|
-
await handlerFn(c as unknown as HandlerContext)
|
|
609
|
-
const variantAfter = await runAfterHooks<TEnv>(variantHooks?.after ?? [], c)
|
|
610
|
-
if (variantAfter) return variantAfter
|
|
611
|
-
const operationAfter = await runAfterHooks<TEnv>(opConfig.operationAfter, c)
|
|
612
|
-
if (operationAfter) return operationAfter
|
|
613
|
-
return sendResult(c as unknown as HandlerContext)
|
|
614
|
-
} catch (error: unknown) {
|
|
615
|
-
return sendError(c as unknown as HandlerContext, error)
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
const opFor = <K extends keyof ${modelName}RouteConfig<TCtx, TPrisma, TEnv>>(
|
|
620
|
-
key: K,
|
|
621
|
-
): NormalizedOp<TEnv> => {
|
|
622
|
-
const raw = config[key] as unknown as OperationConfigLike<TEnv> | undefined
|
|
623
|
-
validateOperationConfig(raw, '${modelName}.' + String(key), POLICY)
|
|
624
|
-
return normalizeHonoOperation(raw)
|
|
260
|
+
registerOpenApiRoutes(app, basePath, getOpenApiJson, getOpenApiYaml)
|
|
625
261
|
}
|
|
626
262
|
|
|
627
263
|
${readOpBlocks}
|
|
@@ -634,9 +270,6 @@ ${writeOpBlocks}
|
|
|
634
270
|
* endpoint is a batch of { where, data } applied directly — and the only thing
|
|
635
271
|
* between it and an unguarded mass mutation is a console.warn suppressed in
|
|
636
272
|
* production. A warning is not a security boundary.
|
|
637
|
-
*
|
|
638
|
-
* Removing the route outright was a breaking change to a published package,
|
|
639
|
-
* so by default it registers exactly as it did, warning and all.
|
|
640
273
|
*/
|
|
641
274
|
if (!POLICY.enableUpdateEach) {
|
|
642
275
|
throw new Error(
|
|
@@ -648,9 +281,9 @@ ${writeOpBlocks}
|
|
|
648
281
|
)
|
|
649
282
|
}
|
|
650
283
|
|
|
651
|
-
const rawUpdateEach = config.updateEach as unknown as
|
|
284
|
+
const rawUpdateEach = config.updateEach as unknown as Parameters<typeof normalizeHonoOperation<TEnv>>[0]
|
|
652
285
|
validateUpdateEachConfig(rawUpdateEach, '${modelName}.updateEach')
|
|
653
|
-
const opConfig = normalizeHonoOperation(rawUpdateEach)
|
|
286
|
+
const opConfig = normalizeHonoOperation<TEnv>(rawUpdateEach)
|
|
654
287
|
if (opConfig.operationBefore.length === 0 && _env.NODE_ENV !== 'production') {
|
|
655
288
|
console.warn(
|
|
656
289
|
'[${modelName}Router] updateEach is enabled without a before hook. ' +
|
|
@@ -658,23 +291,10 @@ ${writeOpBlocks}
|
|
|
658
291
|
)
|
|
659
292
|
}
|
|
660
293
|
const path = basePath ? \`\${basePath}/each\` : '/each'
|
|
661
|
-
app.post(path,
|
|
662
|
-
try {
|
|
663
|
-
await parseUpdateEachBodyMiddleware(c as unknown as HandlerContext)
|
|
664
|
-
await makeShapeMiddleware<TCtx, TPrisma, TEnv>(config, opConfig, 'noop')(c)
|
|
665
|
-
const beforeResponse = await runBeforeHooks<TEnv>(opConfig.operationBefore, c)
|
|
666
|
-
if (beforeResponse) return beforeResponse
|
|
667
|
-
await ${modelName}UpdateEach(c as unknown as HandlerContext)
|
|
668
|
-
const afterResponse = await runAfterHooks<TEnv>(opConfig.operationAfter, c)
|
|
669
|
-
if (afterResponse) return afterResponse
|
|
670
|
-
return sendResult(c as unknown as HandlerContext)
|
|
671
|
-
} catch (error: unknown) {
|
|
672
|
-
return sendError(c as unknown as HandlerContext, error)
|
|
673
|
-
}
|
|
674
|
-
})
|
|
294
|
+
app.post(path, createUpdateEachRoute<TCtx, TPrisma, TEnv>({ config, opConfig, handler: ${modelName}UpdateEach, dropGuard }))
|
|
675
295
|
}
|
|
676
296
|
|
|
677
297
|
return app
|
|
678
298
|
}
|
|
679
299
|
`
|
|
680
|
-
}
|
|
300
|
+
}
|