prisma-generator-express 1.65.2 → 1.67.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 +51 -3480
- 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.js +69 -6
- package/dist/copy/operationDefinitions.js.map +1 -1
- package/dist/generators/generateFastifyHandler.d.ts +2 -2
- package/dist/generators/generateFastifyHandler.js +15 -5
- package/dist/generators/generateFastifyHandler.js.map +1 -1
- package/dist/generators/generateHonoHandler.d.ts +2 -2
- package/dist/generators/generateHonoHandler.js +12 -5
- package/dist/generators/generateHonoHandler.js.map +1 -1
- 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/generateModelMetadata.js +20 -10
- 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/generateQueryBuilderHelper.d.ts +1 -1
- package/dist/generators/generateQueryBuilderHelper.js +2 -4
- package/dist/generators/generateQueryBuilderHelper.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 +107 -14
- package/dist/generators/generateRouter.js.map +1 -1
- package/dist/generators/generateRouterFastify.d.ts +7 -4
- package/dist/generators/generateRouterFastify.js +56 -28
- package/dist/generators/generateRouterFastify.js.map +1 -1
- package/dist/generators/generateRouterHono.d.ts +7 -4
- package/dist/generators/generateRouterHono.js +84 -53
- package/dist/generators/generateRouterHono.js.map +1 -1
- 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 +11 -3
- 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 +1 -1
- package/dist/index.js +22 -1
- package/dist/index.js.map +1 -1
- package/dist/utils/copyFiles.js +22 -6
- package/dist/utils/copyFiles.js.map +1 -1
- package/dist/utils/importExt.d.ts +1 -1
- 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.map +1 -1
- package/package.json +16 -3
- 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 +153 -50
- package/src/copy/concurrency.ts +10 -8
- package/src/copy/errorMapper.ts +68 -19
- package/src/copy/guardHelpers.ts +30 -9
- package/src/copy/materializedCount.ts +17 -5
- package/src/copy/misc.ts +88 -68
- package/src/copy/ndjson.ts +72 -0
- package/src/copy/operationDefinitions.ts +99 -21
- package/src/copy/operationRuntime.ts +100 -12
- package/src/copy/pagination.ts +25 -11
- 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 +19 -9
- package/src/copy/routeConfig.ts +174 -39
- package/src/copy/scalarTypes.ts +7 -2
- package/src/copy/sse.ts +72 -19
- package/src/generators/generateFastifyHandler.ts +24 -13
- package/src/generators/generateHonoHandler.ts +21 -13
- package/src/generators/generateImportPrismaStatement.ts +93 -81
- package/src/generators/generateModelMetadata.ts +24 -12
- package/src/generators/generateOperationCore.ts +81 -33
- package/src/generators/generateQueryBuilderHelper.ts +20 -4
- package/src/generators/generateRelationMeta.ts +23 -11
- package/src/generators/generateRouteConfigType.ts +39 -13
- package/src/generators/generateRouter.ts +114 -17
- package/src/generators/generateRouterFastify.ts +67 -31
- package/src/generators/generateRouterHono.ts +108 -60
- package/src/generators/generateUnifiedDocs.ts +29 -8
- package/src/generators/generateUnifiedHandler.ts +16 -8
- package/src/generators/generateUnifiedScalarUI.ts +1 -1
- package/src/guardOptions.ts +105 -92
- package/src/index.ts +46 -4
- package/src/utils/copyFiles.ts +199 -168
- package/src/utils/importExt.ts +2 -2
- 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 +6 -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,11 +56,11 @@ 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
61
|
app.post(postPath, handleRead(opConfig, ${handlerName}, parseBodyAsQueryMiddleware, '${opKind}'))
|
|
58
62
|
}`
|
|
59
|
-
: ` if (
|
|
63
|
+
: ` if (resolvePostReadsEnabled(config.disablePostReads, opConfig.disablePostReads)) app.post(path, handleRead(opConfig, ${handlerName}, parseBodyAsQueryMiddleware, '${opKind}'))`
|
|
60
64
|
: ''
|
|
61
65
|
|
|
62
66
|
return ` if (isEnabled(config.${meta.configKey})) {
|
|
@@ -67,7 +71,10 @@ ${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)
|
|
@@ -96,7 +103,7 @@ function emitWriteOp(meta: (typeof OPERATION_METADATA)[number], modelName: strin
|
|
|
96
103
|
* - `requireDefaultVariantOptIn` confirm a `default` variant
|
|
97
104
|
* - `enableUpdateEach` register the batch route (default true)
|
|
98
105
|
* - `guardResolutionOrder` settle the guard before or after hooks
|
|
99
|
-
* - `allowE2EGuardBypass` honour
|
|
106
|
+
* - `allowE2EGuardBypass` honour PGE_DROP_GUARD=true (default true)
|
|
100
107
|
* - `validateResolvedShapes` check what a shape function returned
|
|
101
108
|
*
|
|
102
109
|
* `HARDENED_GUARD_PROFILE` selects all seven, as values to spread rather than a
|
|
@@ -106,7 +113,10 @@ function emitWriteOp(meta: (typeof OPERATION_METADATA)[number], modelName: strin
|
|
|
106
113
|
*
|
|
107
114
|
* The emitted router used to compute `DROP_GUARD = <flag> || _env.E2E === 'true'`,
|
|
108
115
|
* so setting `E2E=true` in a deployed environment downgraded enforcement even
|
|
109
|
-
* when the generator had been told to keep the guard. The
|
|
116
|
+
* when the generator had been told to keep the guard. The bypass now lives once
|
|
117
|
+
* in the shared runtime as `resolveDropGuardEnv`: it honours `PGE_DROP_GUARD=true`
|
|
118
|
+
* and keeps `E2E=true` working as a deprecated alias with a one-time warning.
|
|
119
|
+
* The two modes are not
|
|
110
120
|
* equivalent: with the guard, the shape goes to prisma-guard; with it dropped,
|
|
111
121
|
* `applyDroppedGuard` applies projection defaults and forced `where` clauses and
|
|
112
122
|
* nothing else is validated against the shape.
|
|
@@ -133,32 +143,39 @@ export function generateHonoRouterFunction({
|
|
|
133
143
|
model,
|
|
134
144
|
enums,
|
|
135
145
|
guardShapesImport,
|
|
146
|
+
clientImport,
|
|
136
147
|
importStyle,
|
|
137
148
|
writeStrategy,
|
|
138
149
|
dropGuard,
|
|
150
|
+
pathCase,
|
|
139
151
|
}: {
|
|
140
152
|
model: DMMF.Model
|
|
141
153
|
enums: DMMF.DatamodelEnum[]
|
|
142
154
|
guardShapesImport: string | null
|
|
155
|
+
clientImport?: string
|
|
143
156
|
importStyle: ImportStyle
|
|
144
157
|
writeStrategy: WriteStrategy
|
|
145
158
|
dropGuard: boolean
|
|
159
|
+
pathCase: PathCase
|
|
146
160
|
}): string {
|
|
147
161
|
const ext = importExt(importStyle)
|
|
148
162
|
const modelName = model.name
|
|
149
|
-
const
|
|
163
|
+
const modelSegment = modelPathSegment(modelName, pathCase)
|
|
150
164
|
const routerFunctionName = `${modelName}Router`
|
|
151
165
|
|
|
152
|
-
const handlerImports = OPERATION_METADATA
|
|
153
|
-
|
|
154
|
-
|
|
166
|
+
const handlerImports = OPERATION_METADATA.map(
|
|
167
|
+
(m) => ` ${modelName}${m.name.charAt(0).toUpperCase() + m.name.slice(1)},`,
|
|
168
|
+
).join('\n')
|
|
155
169
|
|
|
156
170
|
const readOps = OPERATION_METADATA.filter((m) => m.kind === 'read')
|
|
157
|
-
const writeOps = OPERATION_METADATA.filter(
|
|
158
|
-
|
|
171
|
+
const writeOps = OPERATION_METADATA.filter(
|
|
172
|
+
(m) => m.kind === 'write' || m.kind === 'batch',
|
|
173
|
+
).filter((m) => m.name !== 'updateEach')
|
|
159
174
|
|
|
160
175
|
const readOpBlocks = readOps.map((m) => emitReadOp(m, modelName)).join('\n\n')
|
|
161
|
-
const writeOpBlocks = writeOps
|
|
176
|
+
const writeOpBlocks = writeOps
|
|
177
|
+
.map((m) => emitWriteOp(m, modelName))
|
|
178
|
+
.join('\n\n')
|
|
162
179
|
|
|
163
180
|
return `import { Hono } from 'hono'
|
|
164
181
|
import type { Context } from 'hono'
|
|
@@ -175,9 +192,10 @@ import type {
|
|
|
175
192
|
HonoInternalVariables,
|
|
176
193
|
GeneratedHonoEnv,
|
|
177
194
|
PaginationConfig,
|
|
195
|
+
PrismaClientLike,
|
|
178
196
|
} from '../routeConfig.target${ext}'
|
|
179
197
|
import { parseQueryParams } from '../parseQueryParams${ext}'
|
|
180
|
-
import { normalizePrefix, getEnv, sanitizeKeys, isPlainObject } from '../misc${ext}'
|
|
198
|
+
import { normalizePrefix, getEnv, sanitizeKeys, isPlainObject, resolveDropGuardEnv } from '../misc${ext}'
|
|
181
199
|
import { buildModelOpenApi } from '../buildModelOpenApi${ext}'
|
|
182
200
|
import {
|
|
183
201
|
normalizeOperation,
|
|
@@ -187,7 +205,10 @@ import {
|
|
|
187
205
|
validateCountSourceWhere,
|
|
188
206
|
validateOperationConfig,
|
|
189
207
|
validateUpdateEachConfig,
|
|
208
|
+
resolvePostReadsEnabled,
|
|
209
|
+
warnIfUnguardedRoutes,
|
|
190
210
|
} from '../routeConfig${ext}'
|
|
211
|
+
import type { RuntimeOperationOverride } from '../operationRuntime${ext}'
|
|
191
212
|
import type { NormalizedOperationConfig } from '../routeConfig${ext}'
|
|
192
213
|
import { transformResult } from '../operationRuntime${ext}'
|
|
193
214
|
import { mapError } from '../errorMapper${ext}'
|
|
@@ -197,7 +218,7 @@ import { applyDroppedGuard } from '../projectionDefaults${ext}'
|
|
|
197
218
|
import type { OpKind } from '../projectionDefaults${ext}'
|
|
198
219
|
import { MODEL_FIELDS, MODEL_ENUMS } from './${modelName}Metadata${ext}'
|
|
199
220
|
|
|
200
|
-
${generateRouteConfigType(modelName, 'HonoBeforeHook', guardShapesImport, importStyle, 'hono')}
|
|
221
|
+
${generateRouteConfigType(modelName, 'HonoBeforeHook', guardShapesImport, importStyle, 'hono', clientImport)}
|
|
201
222
|
const _env = getEnv()
|
|
202
223
|
|
|
203
224
|
// Fixed at generation time. The \`allowE2EGuardBypass\` control decides whether the
|
|
@@ -205,15 +226,19 @@ const _env = getEnv()
|
|
|
205
226
|
// which is upstream behaviour. See generateRouterHono.ts.
|
|
206
227
|
const DROP_GUARD = ${dropGuard}
|
|
207
228
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
229
|
+
/**
|
|
230
|
+
* DELIBERATELY NOT RECURSIVE.
|
|
231
|
+
*
|
|
232
|
+
* A self-referential JSON type sent through \`c.json()\` makes Hono's response
|
|
233
|
+
* inference instantiate without a fixed point, and TypeScript answers
|
|
234
|
+
* "type instantiation is excessively deep" — in the emitted file, for every
|
|
235
|
+
* consumer. One level is all this needs: the value is serialised, not walked.
|
|
236
|
+
*/
|
|
237
|
+
type JsonLike = string | number | boolean | null | unknown[] | Record<string, unknown>
|
|
215
238
|
|
|
216
239
|
type OperationConfigLike<TEnv extends HonoEnvBase> = {
|
|
240
|
+
authorize?: HonoBeforeHook<TEnv>
|
|
241
|
+
override?: RuntimeOperationOverride
|
|
217
242
|
before?: HonoBeforeHook<TEnv>[]
|
|
218
243
|
after?: HonoAfterHook<TEnv>[]
|
|
219
244
|
shape?: unknown
|
|
@@ -287,7 +312,7 @@ async function parseWriteBodyMiddleware(c: HandlerContext): Promise<void> {
|
|
|
287
312
|
c.set('body', body)
|
|
288
313
|
}
|
|
289
314
|
|
|
290
|
-
function makeShapeMiddleware<TCtx, TPrisma, TEnv extends HonoEnvBase>(
|
|
315
|
+
function makeShapeMiddleware<TCtx, TPrisma extends PrismaClientLike, TEnv extends HonoEnvBase>(
|
|
291
316
|
config: ${modelName}RouteConfig<TCtx, TPrisma, TEnv>,
|
|
292
317
|
opConfig: NormalizedOp<TEnv>,
|
|
293
318
|
opKind: OpKind,
|
|
@@ -297,24 +322,40 @@ function makeShapeMiddleware<TCtx, TPrisma, TEnv extends HonoEnvBase>(
|
|
|
297
322
|
/**
|
|
298
323
|
* The environment bypass, honoured unless the consumer turned it off.
|
|
299
324
|
*
|
|
300
|
-
* \`
|
|
301
|
-
* hazard, and it is also upstream behaviour
|
|
302
|
-
* decision made here.
|
|
325
|
+
* \`PGE_DROP_GUARD=true\` downgrading enforcement in a deployed environment is
|
|
326
|
+
* a real hazard, and it is also upstream behaviour (under its deprecated
|
|
327
|
+
* \`E2E=true\` spelling) — so it is a control, not a decision made here.
|
|
303
328
|
*/
|
|
304
|
-
const dropGuard = DROP_GUARD || (policy.allowE2EGuardBypass && _env
|
|
329
|
+
const dropGuard = DROP_GUARD || (policy.allowE2EGuardBypass && resolveDropGuardEnv(_env))
|
|
305
330
|
|
|
306
331
|
return async (c: Context<GeneratedHonoEnv<TEnv>>): Promise<void> => {
|
|
332
|
+
/**
|
|
333
|
+
* INTERNAL REQUEST STATE IS SET THROUGH A CONCRETE CONTEXT.
|
|
334
|
+
*
|
|
335
|
+
* \`GeneratedHonoEnv<TEnv>['Variables']\` is \`HonoInternalVariables & TEnv['Variables']\`,
|
|
336
|
+
* and inside this generic function TypeScript cannot know that the consumer's
|
|
337
|
+
* half does not also declare \`routeConfig\` — so setting that key straight
|
|
338
|
+
* onto \`c\` is unassignable to the intersection and the emitted router does not
|
|
339
|
+
* typecheck under \`strict\` for ANY consumer. These keys belong to this
|
|
340
|
+
* router, so they are written through the internal shape they were declared
|
|
341
|
+
* in.
|
|
342
|
+
*/
|
|
343
|
+
const vars = c as unknown as HandlerContext
|
|
344
|
+
vars.set('operationOverride', opConfig.override)
|
|
345
|
+
let context: Promise<unknown> | undefined
|
|
346
|
+
vars.set('resolveOperationContext', () => context ??= Promise.resolve(config.resolveContext?.(c)))
|
|
347
|
+
|
|
307
348
|
const merged = mergePaginationConfig(config.pagination, opConfig.pagination)
|
|
308
|
-
if (merged)
|
|
349
|
+
if (merged) vars.set('routeConfig', { pagination: merged })
|
|
309
350
|
|
|
310
351
|
const headerName = config.guard?.variantHeader || 'x-api-variant'
|
|
311
352
|
const headerValue = c.req.header(headerName)
|
|
312
353
|
const caller = config.guard?.resolveVariant?.(c) ?? headerValue ?? undefined
|
|
313
|
-
if (typeof caller === 'string')
|
|
354
|
+
if (typeof caller === 'string') vars.set('guardCaller', caller)
|
|
314
355
|
|
|
315
356
|
const resolution = resolveOperationVariantKey(opConfig.guardRouting, caller)
|
|
316
357
|
if (!resolution.ok) {
|
|
317
|
-
|
|
358
|
+
vars.set('guardVariantFailure', resolution)
|
|
318
359
|
return
|
|
319
360
|
}
|
|
320
361
|
|
|
@@ -322,11 +363,11 @@ function makeShapeMiddleware<TCtx, TPrisma, TEnv extends HonoEnvBase>(
|
|
|
322
363
|
opConfig.guardRouting.kind === 'named'
|
|
323
364
|
? resolution.key
|
|
324
365
|
: undefined
|
|
325
|
-
if (resolvedKey !== undefined)
|
|
366
|
+
if (resolvedKey !== undefined) vars.set('guardVariantKey', resolvedKey)
|
|
326
367
|
|
|
327
368
|
if (opConfig.guardShape) {
|
|
328
369
|
const resolveCtx = typeof config.resolveContext === 'function'
|
|
329
|
-
?
|
|
370
|
+
? vars.get('resolveOperationContext')
|
|
330
371
|
: undefined
|
|
331
372
|
|
|
332
373
|
/**
|
|
@@ -339,18 +380,20 @@ function makeShapeMiddleware<TCtx, TPrisma, TEnv extends HonoEnvBase>(
|
|
|
339
380
|
* behaviour: it is resolved at the point of use, and a function returning
|
|
340
381
|
* something unusable is not this router's business to refuse.
|
|
341
382
|
*/
|
|
342
|
-
let effectiveShape: unknown = opConfig.guardShape
|
|
383
|
+
let effectiveShape: Record<string, unknown> | undefined = opConfig.guardShape
|
|
343
384
|
if (policy.validateResolvedShapes) {
|
|
344
385
|
const resolution = await resolveGuardShapeOnce(opConfig.guardShape, resolvedKey, resolveCtx)
|
|
345
386
|
if (!resolution.ok) {
|
|
346
|
-
|
|
387
|
+
vars.set('guardShapeFailure', resolution.problem)
|
|
347
388
|
return
|
|
348
389
|
}
|
|
349
|
-
|
|
390
|
+
// \`ok\` means the resolved value is a shape object; the fallback keeps the
|
|
391
|
+
// declared shape rather than storing something the context cannot hold.
|
|
392
|
+
effectiveShape = isPlainObject(resolution.shape) ? resolution.shape : opConfig.guardShape
|
|
350
393
|
}
|
|
351
394
|
|
|
352
395
|
if (!dropGuard) {
|
|
353
|
-
|
|
396
|
+
vars.set('guardShape', effectiveShape)
|
|
354
397
|
} else {
|
|
355
398
|
await applyDroppedGuard(
|
|
356
399
|
effectiveShape,
|
|
@@ -358,24 +401,24 @@ function makeShapeMiddleware<TCtx, TPrisma, TEnv extends HonoEnvBase>(
|
|
|
358
401
|
resolveCtx,
|
|
359
402
|
opKind,
|
|
360
403
|
{
|
|
361
|
-
readQuery:
|
|
362
|
-
writeBody: isPlainObject(
|
|
363
|
-
? (
|
|
404
|
+
readQuery: vars.get('parsedQuery'),
|
|
405
|
+
writeBody: isPlainObject(vars.get('body'))
|
|
406
|
+
? (vars.get('body') as Record<string, unknown>)
|
|
364
407
|
: undefined,
|
|
365
408
|
},
|
|
366
409
|
() => {
|
|
367
|
-
let target =
|
|
410
|
+
let target = vars.get('parsedQuery')
|
|
368
411
|
if (!target) {
|
|
369
412
|
target = {}
|
|
370
|
-
|
|
413
|
+
vars.set('parsedQuery', target)
|
|
371
414
|
}
|
|
372
415
|
return target
|
|
373
416
|
},
|
|
374
417
|
() => {
|
|
375
|
-
let target =
|
|
418
|
+
let target = vars.get('body')
|
|
376
419
|
if (!isPlainObject(target)) {
|
|
377
420
|
target = {}
|
|
378
|
-
|
|
421
|
+
vars.set('body', target)
|
|
379
422
|
}
|
|
380
423
|
return target as Record<string, unknown>
|
|
381
424
|
},
|
|
@@ -424,7 +467,7 @@ function sendError(c: HandlerContext, error: unknown): Response {
|
|
|
424
467
|
return c.json({ message: httpError.message }, httpError.status as ContentfulStatusCode)
|
|
425
468
|
}
|
|
426
469
|
|
|
427
|
-
export function ${routerFunctionName}<TCtx = unknown, TPrisma =
|
|
470
|
+
export function ${routerFunctionName}<TCtx = unknown, TPrisma extends PrismaClientLike = PrismaClientLike, TEnv extends HonoEnvBase = HonoEnvBase>(config: ${modelName}RouteConfig<TCtx, TPrisma, TEnv> = {}): Hono<GeneratedHonoEnv<TEnv>> {
|
|
428
471
|
validateCountSourceWhere(config.pagination?.countSource, '${modelName} pagination')
|
|
429
472
|
validateCountSourceWhere(
|
|
430
473
|
(config.findManyPaginated && typeof config.findManyPaginated === 'object' ? config.findManyPaginated : undefined)?.pagination?.countSource,
|
|
@@ -434,9 +477,10 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
|
|
|
434
477
|
const app = new Hono<GeneratedHonoEnv<TEnv>>()
|
|
435
478
|
|
|
436
479
|
const isEnabled = (value: unknown): boolean => value !== false && !!(config.enableAll || value)
|
|
480
|
+
warnIfUnguardedRoutes('${modelName}', ['findMany', 'findUnique', 'findUniqueOrThrow', 'findFirst', 'findFirstOrThrow', 'findManyPaginated', 'count', 'aggregate', 'groupBy', 'create', 'createMany', 'createManyAndReturn', 'update', 'updateMany', 'updateManyAndReturn', 'upsert', 'delete', 'deleteMany'], config, isEnabled)
|
|
437
481
|
|
|
438
482
|
const customPrefix = normalizePrefix(config.customUrlPrefix || '')
|
|
439
|
-
|
|
483
|
+
const modelPrefix = config.addModelPrefix !== false ? '/${modelSegment}' : ''
|
|
440
484
|
const basePath = customPrefix + modelPrefix
|
|
441
485
|
|
|
442
486
|
const openApiDisabled = config.disableOpenApi === true
|
|
@@ -445,7 +489,6 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
|
|
|
445
489
|
|| _env.DISABLE_OPENAPI === 'true'
|
|
446
490
|
))
|
|
447
491
|
|
|
448
|
-
const postReadsEnabled = !config.disablePostReads
|
|
449
492
|
|
|
450
493
|
let _openApiJsonCache: unknown = undefined
|
|
451
494
|
const getOpenApiJson = (): unknown => {
|
|
@@ -454,8 +497,8 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
|
|
|
454
497
|
'${modelName}',
|
|
455
498
|
MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
|
|
456
499
|
MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
|
|
457
|
-
config as
|
|
458
|
-
{ format: 'json', writeStrategy: '${writeStrategy}' },
|
|
500
|
+
config as unknown as Parameters<typeof buildModelOpenApi>[3],
|
|
501
|
+
{ format: 'json', writeStrategy: '${writeStrategy}', pathSegment: '${modelSegment}' },
|
|
459
502
|
)
|
|
460
503
|
}
|
|
461
504
|
return _openApiJsonCache
|
|
@@ -467,14 +510,14 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
|
|
|
467
510
|
'${modelName}',
|
|
468
511
|
MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
|
|
469
512
|
MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
|
|
470
|
-
config as
|
|
471
|
-
{ format: 'yaml', writeStrategy: '${writeStrategy}' },
|
|
513
|
+
config as unknown as Parameters<typeof buildModelOpenApi>[3],
|
|
514
|
+
{ format: 'yaml', writeStrategy: '${writeStrategy}', pathSegment: '${modelSegment}' },
|
|
472
515
|
) as string
|
|
473
516
|
}
|
|
474
517
|
return _openApiYamlCache
|
|
475
518
|
}
|
|
476
519
|
|
|
477
|
-
if (config.queryBuilder &&
|
|
520
|
+
if (config.queryBuilder && _env.NODE_ENV !== 'production') {
|
|
478
521
|
console.warn(
|
|
479
522
|
'[${modelName}Router] queryBuilder config is present but Hono target does not auto-start it. ' +
|
|
480
523
|
'Run \`npx prisma-query-builder-ui\` in a separate process.',
|
|
@@ -482,7 +525,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
|
|
|
482
525
|
}
|
|
483
526
|
|
|
484
527
|
app.onError((err, c) => {
|
|
485
|
-
return sendError(c as HandlerContext, err)
|
|
528
|
+
return sendError(c as unknown as HandlerContext, err)
|
|
486
529
|
})
|
|
487
530
|
|
|
488
531
|
if (!openApiDisabled) {
|
|
@@ -512,14 +555,15 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
|
|
|
512
555
|
* that branch is inert rather than merely unreached when it is off.
|
|
513
556
|
*/
|
|
514
557
|
const settleGuard = (c: Context<GeneratedHonoEnv<TEnv>>): void => {
|
|
515
|
-
const
|
|
558
|
+
const vars = c as unknown as HandlerContext
|
|
559
|
+
const failure = vars.get('guardVariantFailure')
|
|
516
560
|
if (failure) {
|
|
517
561
|
throw new HTTPException(400, {
|
|
518
562
|
message: formatGuardVariantResolutionError(failure),
|
|
519
563
|
})
|
|
520
564
|
}
|
|
521
565
|
|
|
522
|
-
const shapeFailure =
|
|
566
|
+
const shapeFailure = vars.get('guardShapeFailure')
|
|
523
567
|
if (shapeFailure) {
|
|
524
568
|
throw new HTTPException(500, {
|
|
525
569
|
message: 'guard shape could not be resolved: ' + shapeFailure,
|
|
@@ -534,6 +578,8 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
|
|
|
534
578
|
opKind: OpKind,
|
|
535
579
|
) => async (c: Context<GeneratedHonoEnv<TEnv>>): Promise<Response> => {
|
|
536
580
|
try {
|
|
581
|
+
const authorized = await runBeforeHooks<TEnv>(opConfig.authorize ? [opConfig.authorize] : [], c)
|
|
582
|
+
if (authorized) return authorized
|
|
537
583
|
await parseFn(c as unknown as HandlerContext)
|
|
538
584
|
await makeShapeMiddleware<TCtx, TPrisma, TEnv>(config, opConfig, opKind)(c)
|
|
539
585
|
|
|
@@ -546,7 +592,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
|
|
|
546
592
|
// Upstream order: hooks first, guard failure after.
|
|
547
593
|
if (!SETTLE_BEFORE_HOOKS) settleGuard(c)
|
|
548
594
|
|
|
549
|
-
const key = c.get('guardVariantKey')
|
|
595
|
+
const key = (c as unknown as HandlerContext).get('guardVariantKey')
|
|
550
596
|
const variantHooks =
|
|
551
597
|
key !== undefined ? opConfig.variantHooks[key] : undefined
|
|
552
598
|
|
|
@@ -569,6 +615,8 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
|
|
|
569
615
|
opKind: OpKind,
|
|
570
616
|
) => async (c: Context<GeneratedHonoEnv<TEnv>>): Promise<Response> => {
|
|
571
617
|
try {
|
|
618
|
+
const authorized = await runBeforeHooks<TEnv>(opConfig.authorize ? [opConfig.authorize] : [], c)
|
|
619
|
+
if (authorized) return authorized
|
|
572
620
|
await parseWriteBodyMiddleware(c as unknown as HandlerContext)
|
|
573
621
|
await makeShapeMiddleware<TCtx, TPrisma, TEnv>(config, opConfig, opKind)(c)
|
|
574
622
|
|
|
@@ -581,7 +629,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
|
|
|
581
629
|
// Upstream order: hooks first, guard failure after.
|
|
582
630
|
if (!SETTLE_BEFORE_HOOKS) settleGuard(c)
|
|
583
631
|
|
|
584
|
-
const key = c.get('guardVariantKey')
|
|
632
|
+
const key = (c as unknown as HandlerContext).get('guardVariantKey')
|
|
585
633
|
const variantHooks =
|
|
586
634
|
key !== undefined ? opConfig.variantHooks[key] : undefined
|
|
587
635
|
|
|
@@ -659,4 +707,4 @@ ${writeOpBlocks}
|
|
|
659
707
|
return app
|
|
660
708
|
}
|
|
661
709
|
`
|
|
662
|
-
}
|
|
710
|
+
}
|
|
@@ -4,18 +4,21 @@ import type { Target } from '../constants'
|
|
|
4
4
|
|
|
5
5
|
export function generateUnifiedDocs(
|
|
6
6
|
modelNames: string[],
|
|
7
|
+
pathSegments: Record<string, string>,
|
|
7
8
|
target: Target,
|
|
8
9
|
importStyle: ImportStyle,
|
|
9
10
|
): string {
|
|
10
11
|
const ext = importExt(importStyle)
|
|
11
|
-
if (target === 'fastify')
|
|
12
|
-
|
|
13
|
-
return
|
|
12
|
+
if (target === 'fastify')
|
|
13
|
+
return generateFastify(modelNames, pathSegments, ext)
|
|
14
|
+
if (target === 'hono') return generateHono(modelNames, pathSegments, ext)
|
|
15
|
+
return generateExpress(modelNames, pathSegments, ext)
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
const renderIndexFn = `function renderIndex(title: string, modelNames: string[], basePath: string): string {
|
|
19
|
+
const segments = MODEL_PATH_SEGMENTS as Record<string, string>
|
|
17
20
|
const links = modelNames
|
|
18
|
-
.map((n) => '<li><a href="' + basePath + '/' + n.toLowerCase() + '">' + n + '</a></li>')
|
|
21
|
+
.map((n) => '<li><a href="' + basePath + '/' + (segments[n] ?? n.toLowerCase()) + '">' + n + '</a></li>')
|
|
19
22
|
.join('')
|
|
20
23
|
|
|
21
24
|
return '<!DOCTYPE html><html><head><meta charset="utf-8" /><title>' + title +
|
|
@@ -27,7 +30,11 @@ const renderIndexFn = `function renderIndex(title: string, modelNames: string[],
|
|
|
27
30
|
'</div></body></html>'
|
|
28
31
|
}`
|
|
29
32
|
|
|
30
|
-
function generateExpress(
|
|
33
|
+
function generateExpress(
|
|
34
|
+
modelNames: string[],
|
|
35
|
+
pathSegments: Record<string, string>,
|
|
36
|
+
ext: string,
|
|
37
|
+
): string {
|
|
31
38
|
return `import type { Router } from 'express'
|
|
32
39
|
import { getEnv, removeTrailingSlash } from './misc${ext}'
|
|
33
40
|
|
|
@@ -35,6 +42,8 @@ const _env = getEnv()
|
|
|
35
42
|
|
|
36
43
|
const MODELS = ${JSON.stringify(modelNames)} as const
|
|
37
44
|
|
|
45
|
+
const MODEL_PATH_SEGMENTS = ${JSON.stringify(pathSegments)} as const
|
|
46
|
+
|
|
38
47
|
interface UnifiedDocsConfig {
|
|
39
48
|
title?: string
|
|
40
49
|
basePath?: string
|
|
@@ -62,7 +71,11 @@ export default registerUnifiedDocs
|
|
|
62
71
|
`
|
|
63
72
|
}
|
|
64
73
|
|
|
65
|
-
function generateFastify(
|
|
74
|
+
function generateFastify(
|
|
75
|
+
modelNames: string[],
|
|
76
|
+
pathSegments: Record<string, string>,
|
|
77
|
+
ext: string,
|
|
78
|
+
): string {
|
|
66
79
|
return `import type { FastifyInstance } from 'fastify'
|
|
67
80
|
import { getEnv, removeTrailingSlash } from './misc${ext}'
|
|
68
81
|
|
|
@@ -70,6 +83,8 @@ const _env = getEnv()
|
|
|
70
83
|
|
|
71
84
|
const MODELS = ${JSON.stringify(modelNames)} as const
|
|
72
85
|
|
|
86
|
+
const MODEL_PATH_SEGMENTS = ${JSON.stringify(pathSegments)} as const
|
|
87
|
+
|
|
73
88
|
interface UnifiedDocsConfig {
|
|
74
89
|
title?: string
|
|
75
90
|
basePath?: string
|
|
@@ -100,7 +115,11 @@ export default registerUnifiedDocs
|
|
|
100
115
|
`
|
|
101
116
|
}
|
|
102
117
|
|
|
103
|
-
function generateHono(
|
|
118
|
+
function generateHono(
|
|
119
|
+
modelNames: string[],
|
|
120
|
+
pathSegments: Record<string, string>,
|
|
121
|
+
ext: string,
|
|
122
|
+
): string {
|
|
104
123
|
return `import type { Hono } from 'hono'
|
|
105
124
|
import { getEnv, removeTrailingSlash } from './misc${ext}'
|
|
106
125
|
|
|
@@ -108,6 +127,8 @@ const _env = getEnv()
|
|
|
108
127
|
|
|
109
128
|
const MODELS = ${JSON.stringify(modelNames)} as const
|
|
110
129
|
|
|
130
|
+
const MODEL_PATH_SEGMENTS = ${JSON.stringify(pathSegments)} as const
|
|
131
|
+
|
|
111
132
|
interface UnifiedDocsConfig {
|
|
112
133
|
title?: string
|
|
113
134
|
basePath?: string
|
|
@@ -133,4 +154,4 @@ ${renderIndexFn}
|
|
|
133
154
|
|
|
134
155
|
export default registerUnifiedDocs
|
|
135
156
|
`
|
|
136
|
-
}
|
|
157
|
+
}
|
|
@@ -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
|
|
|
@@ -14,10 +14,11 @@ export function generateUnifiedHandler(options: UnifiedHandlerOptions): string {
|
|
|
14
14
|
|
|
15
15
|
const dispatchOps = OPERATION_METADATA.filter((m) => m.name !== 'updateEach')
|
|
16
16
|
|
|
17
|
-
const handlers = dispatchOps
|
|
18
|
-
|
|
17
|
+
const handlers = dispatchOps
|
|
18
|
+
.map((meta) => {
|
|
19
|
+
const exportName = `${modelName}${meta.name.charAt(0).toUpperCase() + meta.name.slice(1)}`
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
return `
|
|
21
22
|
export async function ${exportName}(
|
|
22
23
|
req: Request,
|
|
23
24
|
res: Response,
|
|
@@ -30,11 +31,12 @@ export async function ${exportName}(
|
|
|
30
31
|
next(mapError(error))
|
|
31
32
|
}
|
|
32
33
|
}`
|
|
33
|
-
|
|
34
|
+
})
|
|
35
|
+
.join('\n')
|
|
34
36
|
|
|
35
37
|
return `import type { Request, Response, NextFunction } from 'express'
|
|
36
38
|
import * as core from './${modelName}Core${ext}'
|
|
37
|
-
import { OperationContext } from '../operationRuntime${ext}'
|
|
39
|
+
import type { OperationContext, RuntimeOperationOverride } from '../operationRuntime${ext}'
|
|
38
40
|
import { mapError } from '../errorMapper${ext}'
|
|
39
41
|
|
|
40
42
|
type ExtendedRequest = Request & {
|
|
@@ -48,6 +50,9 @@ type LocalsBag = {
|
|
|
48
50
|
routeConfig?: { pagination?: unknown }
|
|
49
51
|
guardShape?: Record<string, unknown>
|
|
50
52
|
guardCaller?: string
|
|
53
|
+
guardVariantKey?: string
|
|
54
|
+
operationOverride?: RuntimeOperationOverride
|
|
55
|
+
resolveOperationContext?: () => unknown | Promise<unknown>
|
|
51
56
|
data?: unknown
|
|
52
57
|
}
|
|
53
58
|
|
|
@@ -55,6 +60,8 @@ function buildContext(req: Request, res: Response): OperationContext {
|
|
|
55
60
|
const extReq = req as ExtendedRequest
|
|
56
61
|
const locals = res.locals as LocalsBag
|
|
57
62
|
return {
|
|
63
|
+
operationOverride: locals.operationOverride,
|
|
64
|
+
resolveOperationContext: locals.resolveOperationContext,
|
|
58
65
|
prisma: extReq.prisma,
|
|
59
66
|
postgres: extReq.postgres,
|
|
60
67
|
sqlite: extReq.sqlite,
|
|
@@ -62,9 +69,10 @@ function buildContext(req: Request, res: Response): OperationContext {
|
|
|
62
69
|
body: req.body,
|
|
63
70
|
guardShape: locals.guardShape,
|
|
64
71
|
guardCaller: locals.guardCaller,
|
|
72
|
+
guardVariantKey: locals.guardVariantKey,
|
|
65
73
|
paginationConfig: (locals.routeConfig?.pagination) as OperationContext['paginationConfig'],
|
|
66
74
|
}
|
|
67
75
|
}
|
|
68
76
|
${handlers}
|
|
69
77
|
`
|
|
70
|
-
}
|
|
78
|
+
}
|