prisma-generator-express 1.66.0 → 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 +37 -3476
- 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/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 +36 -23
- 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 +5 -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 +12 -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/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 +60 -30
- 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, FindManyPaginatedMode } from '../constants'
|
|
5
|
+
import type { WriteStrategy, FindManyPaginatedMode } 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 {
|
|
@@ -55,11 +56,11 @@ function emitReadOp(
|
|
|
55
56
|
|
|
56
57
|
const postReadLine = meta.supportsPostRead
|
|
57
58
|
? meta.name === 'findMany'
|
|
58
|
-
? ` if (
|
|
59
|
+
? ` if (resolvePostReadsEnabled(config.disablePostReads, opConfig.disablePostReads)) {
|
|
59
60
|
const postPath = basePath ? \`\${basePath}/read\` : '/read'
|
|
60
61
|
instance.post(postPath, handleRead(opConfig, ${handlerName}, parseBodyAsQueryHook, '${opKind}'))
|
|
61
62
|
}`
|
|
62
|
-
: ` if (
|
|
63
|
+
: ` if (resolvePostReadsEnabled(config.disablePostReads, opConfig.disablePostReads)) instance.post(path, handleRead(opConfig, ${handlerName}, parseBodyAsQueryHook, '${opKind}'))`
|
|
63
64
|
: ''
|
|
64
65
|
|
|
65
66
|
return ` if (isEnabled(config.${meta.configKey})) {
|
|
@@ -90,34 +91,41 @@ export function generateFastifyRouterFunction({
|
|
|
90
91
|
model,
|
|
91
92
|
enums,
|
|
92
93
|
guardShapesImport,
|
|
94
|
+
clientImport,
|
|
93
95
|
importStyle,
|
|
94
96
|
writeStrategy,
|
|
95
97
|
findManyPaginatedMode,
|
|
96
98
|
dropGuard,
|
|
99
|
+
pathCase,
|
|
97
100
|
}: {
|
|
98
101
|
model: DMMF.Model
|
|
99
102
|
enums: DMMF.DatamodelEnum[]
|
|
100
103
|
guardShapesImport: string | null
|
|
104
|
+
clientImport?: string
|
|
101
105
|
importStyle: ImportStyle
|
|
102
106
|
writeStrategy: WriteStrategy
|
|
103
107
|
findManyPaginatedMode: FindManyPaginatedMode
|
|
104
108
|
dropGuard: boolean
|
|
109
|
+
pathCase: PathCase
|
|
105
110
|
}): string {
|
|
106
111
|
const ext = importExt(importStyle)
|
|
107
112
|
const modelName = model.name
|
|
108
|
-
const
|
|
109
|
-
const routerFunctionName = `${
|
|
113
|
+
const modelSegment = modelPathSegment(model.name, pathCase)
|
|
114
|
+
const routerFunctionName = `${model.name}Router`
|
|
110
115
|
|
|
111
|
-
const handlerImports = OPERATION_METADATA
|
|
112
|
-
|
|
113
|
-
|
|
116
|
+
const handlerImports = OPERATION_METADATA.map(
|
|
117
|
+
(m) => ` ${modelName}${m.name.charAt(0).toUpperCase() + m.name.slice(1)},`,
|
|
118
|
+
).join('\n')
|
|
114
119
|
|
|
115
120
|
const readOps = OPERATION_METADATA.filter((m) => m.kind === 'read')
|
|
116
|
-
const writeOps = OPERATION_METADATA.filter(
|
|
117
|
-
|
|
121
|
+
const writeOps = OPERATION_METADATA.filter(
|
|
122
|
+
(m) => m.kind === 'write' || m.kind === 'batch',
|
|
123
|
+
).filter((m) => m.name !== 'updateEach')
|
|
118
124
|
|
|
119
125
|
const readOpBlocks = readOps.map((m) => emitReadOp(m, modelName)).join('\n\n')
|
|
120
|
-
const writeOpBlocks = writeOps
|
|
126
|
+
const writeOpBlocks = writeOps
|
|
127
|
+
.map((m) => emitWriteOp(m, modelName))
|
|
128
|
+
.join('\n\n')
|
|
121
129
|
|
|
122
130
|
return `import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyError } from 'fastify'
|
|
123
131
|
import { startQueryBuilder } from '../queryBuilder${ext}'
|
|
@@ -129,9 +137,11 @@ import type {
|
|
|
129
137
|
FastifyHookHandler,
|
|
130
138
|
FindManyPaginatedMode,
|
|
131
139
|
PaginationConfig,
|
|
140
|
+
PrismaClientLike,
|
|
141
|
+
QueryBuilderConfig,
|
|
132
142
|
} from '../routeConfig.target${ext}'
|
|
133
143
|
import { parseQueryParams } from '../parseQueryParams${ext}'
|
|
134
|
-
import { sanitizeKeys, normalizePrefix, getEnv, isPlainObject } from '../misc${ext}'
|
|
144
|
+
import { sanitizeKeys, normalizePrefix, getEnv, isPlainObject, resolveDropGuardEnv } from '../misc${ext}'
|
|
135
145
|
import { buildModelOpenApi } from '../buildModelOpenApi${ext}'
|
|
136
146
|
import {
|
|
137
147
|
normalizeOperation,
|
|
@@ -139,7 +149,10 @@ import {
|
|
|
139
149
|
validateCountSourceWhere,
|
|
140
150
|
validateOperationConfig,
|
|
141
151
|
validateUpdateEachConfig,
|
|
152
|
+
resolvePostReadsEnabled,
|
|
153
|
+
warnIfUnguardedRoutes,
|
|
142
154
|
} from '../routeConfig${ext}'
|
|
155
|
+
import type { RuntimeOperationOverride } from '../operationRuntime${ext}'
|
|
143
156
|
import type { NormalizedOperationConfig } from '../routeConfig${ext}'
|
|
144
157
|
import type { OperationContext } from '../operationRuntime${ext}'
|
|
145
158
|
import { transformResult } from '../operationRuntime${ext}'
|
|
@@ -151,13 +164,18 @@ import { applyDroppedGuard } from '../projectionDefaults${ext}'
|
|
|
151
164
|
import type { OpKind } from '../projectionDefaults${ext}'
|
|
152
165
|
import { MODEL_FIELDS, MODEL_ENUMS } from './${modelName}Metadata${ext}'
|
|
153
166
|
|
|
154
|
-
${generateRouteConfigType(modelName, 'FastifyHookHandler', guardShapesImport, importStyle, 'fastify')}
|
|
167
|
+
${generateRouteConfigType(modelName, 'FastifyHookHandler', guardShapesImport, importStyle, 'fastify', clientImport)}
|
|
155
168
|
const _env = getEnv()
|
|
156
169
|
|
|
157
170
|
const FIND_MANY_PAGINATED_MODE: FindManyPaginatedMode = '${findManyPaginatedMode}'
|
|
158
|
-
|
|
171
|
+
// Fixed at generation time. The environment bypass is resolved per hook,
|
|
172
|
+
// because it requires the configuration's explicit \`allowE2EGuardBypass: true\`:
|
|
173
|
+
// false or omitted means the environment cannot drop guards for this target.
|
|
174
|
+
const DROP_GUARD = ${dropGuard}
|
|
159
175
|
|
|
160
176
|
type OperationConfigLike = {
|
|
177
|
+
authorize?: FastifyHookHandler
|
|
178
|
+
override?: RuntimeOperationOverride
|
|
161
179
|
before?: FastifyHookHandler[]
|
|
162
180
|
after?: FastifyHookHandler[]
|
|
163
181
|
shape?: unknown
|
|
@@ -185,6 +203,8 @@ type FastifyExtended = FastifyRequest & {
|
|
|
185
203
|
routeConfig?: { pagination?: PaginationConfig }
|
|
186
204
|
guardShape?: Record<string, unknown>
|
|
187
205
|
guardCaller?: string
|
|
206
|
+
operationOverride?: RuntimeOperationOverride
|
|
207
|
+
resolveOperationContext?: () => unknown | Promise<unknown>
|
|
188
208
|
guardVariantKey?: string
|
|
189
209
|
guardVariantFailure?: Extract<GuardVariantResolution, { ok: false }>
|
|
190
210
|
resultData?: unknown
|
|
@@ -197,14 +217,14 @@ function normalizeFastifyOperation(
|
|
|
197
217
|
return normalizeOperation<FastifyHookHandler, FastifyHookHandler>(config)
|
|
198
218
|
}
|
|
199
219
|
|
|
200
|
-
function isQueryBuilderEnabled(config:
|
|
220
|
+
function isQueryBuilderEnabled(config: { queryBuilder?: QueryBuilderConfig | false }): boolean {
|
|
201
221
|
if (config.queryBuilder === false) return false
|
|
202
222
|
if (typeof config.queryBuilder === 'object' && config.queryBuilder.enabled === false) return false
|
|
203
223
|
if (_env.NODE_ENV === 'production') return false
|
|
204
224
|
return true
|
|
205
225
|
}
|
|
206
226
|
|
|
207
|
-
function getQueryBuilderConfig(config:
|
|
227
|
+
function getQueryBuilderConfig(config: { queryBuilder?: QueryBuilderConfig | false }) {
|
|
208
228
|
if (config.queryBuilder === false) return null
|
|
209
229
|
if (typeof config.queryBuilder === 'object') return config.queryBuilder
|
|
210
230
|
return {}
|
|
@@ -225,21 +245,35 @@ function parseBodyAsQueryHook(request: FastifyRequest): void {
|
|
|
225
245
|
(request as FastifyExtended).parsedQuery = sanitizeKeys(body as Record<string, unknown>)
|
|
226
246
|
}
|
|
227
247
|
|
|
228
|
-
function buildResolveContext(
|
|
229
|
-
config: ${modelName}RouteConfig,
|
|
248
|
+
function buildResolveContext<TCtx, TPrisma extends PrismaClientLike>(
|
|
249
|
+
config: ${modelName}RouteConfig<TCtx, TPrisma>,
|
|
230
250
|
request: FastifyRequest,
|
|
231
251
|
): (() => unknown | Promise<unknown>) | undefined {
|
|
232
252
|
if (typeof config.resolveContext !== 'function') return undefined
|
|
233
253
|
return () => (config.resolveContext as (r: FastifyRequest) => unknown | Promise<unknown>)(request)
|
|
234
254
|
}
|
|
235
255
|
|
|
236
|
-
function makeShapeHook(
|
|
237
|
-
config: ${modelName}RouteConfig,
|
|
256
|
+
function makeShapeHook<TCtx, TPrisma extends PrismaClientLike>(
|
|
257
|
+
config: ${modelName}RouteConfig<TCtx, TPrisma>,
|
|
238
258
|
opConfig: NormalizedOp,
|
|
239
259
|
opKind: OpKind,
|
|
240
260
|
): (request: FastifyRequest) => Promise<void> {
|
|
261
|
+
/**
|
|
262
|
+
* The environment bypass, and it is OPT-IN for this target.
|
|
263
|
+
*
|
|
264
|
+
* \`PGE_DROP_GUARD=true\` (deprecated alias \`E2E=true\`) downgrading enforcement
|
|
265
|
+
* in a deployed environment is a real hazard: on most platforms it is an
|
|
266
|
+
* ordinary config var, set on staging, copied forward, flagged as
|
|
267
|
+
* security-relevant nowhere. It is honoured only when this configuration
|
|
268
|
+
* states \`allowE2EGuardBypass: true\` — false or omitted means the environment
|
|
269
|
+
* cannot drop guards. The generation-time \`DROP_GUARD\` literal is unchanged.
|
|
270
|
+
*/
|
|
271
|
+
const dropGuard = DROP_GUARD || (config.allowE2EGuardBypass === true && resolveDropGuardEnv(_env))
|
|
241
272
|
return async (request: FastifyRequest) => {
|
|
242
273
|
const fx = request as FastifyExtended
|
|
274
|
+
fx.operationOverride = opConfig.override
|
|
275
|
+
let context: Promise<unknown> | undefined
|
|
276
|
+
fx.resolveOperationContext = () => context ??= Promise.resolve(config.resolveContext?.(request))
|
|
243
277
|
const merged = mergePaginationConfig(config.pagination, opConfig.pagination)
|
|
244
278
|
if (merged) fx.routeConfig = { pagination: merged }
|
|
245
279
|
|
|
@@ -263,7 +297,7 @@ function makeShapeHook(
|
|
|
263
297
|
if (resolvedKey !== undefined) fx.guardVariantKey = resolvedKey
|
|
264
298
|
|
|
265
299
|
if (opConfig.guardShape) {
|
|
266
|
-
if (!
|
|
300
|
+
if (!dropGuard) {
|
|
267
301
|
fx.guardShape = opConfig.guardShape
|
|
268
302
|
} else {
|
|
269
303
|
await applyDroppedGuard(
|
|
@@ -322,7 +356,7 @@ function sendError(reply: FastifyReply, error: unknown): void {
|
|
|
322
356
|
reply.code(httpError.status).send({ message: httpError.message })
|
|
323
357
|
}
|
|
324
358
|
|
|
325
|
-
export async function ${routerFunctionName}<TCtx = unknown, TPrisma =
|
|
359
|
+
export async function ${routerFunctionName}<TCtx = unknown, TPrisma extends PrismaClientLike = PrismaClientLike>(
|
|
326
360
|
fastify: FastifyInstance,
|
|
327
361
|
config: ${modelName}RouteConfig<TCtx, TPrisma> = {},
|
|
328
362
|
) {
|
|
@@ -334,6 +368,7 @@ export async function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(
|
|
|
334
368
|
|
|
335
369
|
await fastify.register(async (instance) => {
|
|
336
370
|
const isEnabled = (value: unknown): boolean => value !== false && !!(config.enableAll || value)
|
|
371
|
+
warnIfUnguardedRoutes('${modelName}', ['findMany', 'findUnique', 'findUniqueOrThrow', 'findFirst', 'findFirstOrThrow', 'findManyPaginated', 'count', 'aggregate', 'groupBy', 'create', 'createMany', 'createManyAndReturn', 'update', 'updateMany', 'updateManyAndReturn', 'upsert', 'delete', 'deleteMany'], config, isEnabled)
|
|
337
372
|
|
|
338
373
|
const opFor = (key: string): NormalizedOp => {
|
|
339
374
|
const raw = (config as unknown as Record<string, unknown>)[key] as OperationConfigLike | undefined
|
|
@@ -342,7 +377,7 @@ export async function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(
|
|
|
342
377
|
}
|
|
343
378
|
|
|
344
379
|
const customPrefix = normalizePrefix(config.customUrlPrefix || '')
|
|
345
|
-
const modelPrefix = config.addModelPrefix !== false ? '/${
|
|
380
|
+
const modelPrefix = config.addModelPrefix !== false ? '/${modelSegment}' : ''
|
|
346
381
|
const basePath = customPrefix + modelPrefix
|
|
347
382
|
|
|
348
383
|
const openApiDisabled = config.disableOpenApi === true
|
|
@@ -351,7 +386,6 @@ export async function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(
|
|
|
351
386
|
|| _env.DISABLE_OPENAPI === 'true'
|
|
352
387
|
))
|
|
353
388
|
|
|
354
|
-
const postReadsEnabled = !config.disablePostReads
|
|
355
389
|
|
|
356
390
|
let _openApiJsonCache: unknown = undefined
|
|
357
391
|
const getOpenApiJson = (): unknown => {
|
|
@@ -360,8 +394,8 @@ export async function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(
|
|
|
360
394
|
'${modelName}',
|
|
361
395
|
MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
|
|
362
396
|
MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
|
|
363
|
-
config,
|
|
364
|
-
{ format: 'json', writeStrategy: '${writeStrategy}' },
|
|
397
|
+
config as unknown as Parameters<typeof buildModelOpenApi>[3],
|
|
398
|
+
{ format: 'json', writeStrategy: '${writeStrategy}', pathSegment: '${modelSegment}' },
|
|
365
399
|
)
|
|
366
400
|
}
|
|
367
401
|
return _openApiJsonCache
|
|
@@ -373,8 +407,8 @@ export async function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(
|
|
|
373
407
|
'${modelName}',
|
|
374
408
|
MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
|
|
375
409
|
MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
|
|
376
|
-
config,
|
|
377
|
-
{ format: 'yaml', writeStrategy: '${writeStrategy}' },
|
|
410
|
+
config as unknown as Parameters<typeof buildModelOpenApi>[3],
|
|
411
|
+
{ format: 'yaml', writeStrategy: '${writeStrategy}', pathSegment: '${modelSegment}' },
|
|
378
412
|
) as string
|
|
379
413
|
}
|
|
380
414
|
return _openApiYamlCache
|
|
@@ -424,6 +458,7 @@ export async function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(
|
|
|
424
458
|
opKind: OpKind,
|
|
425
459
|
) => async (request: FastifyRequest, reply: FastifyReply) => {
|
|
426
460
|
try {
|
|
461
|
+
if (await runHooks(opConfig.authorize ? [opConfig.authorize] : [], request, reply)) return
|
|
427
462
|
parseFn(request)
|
|
428
463
|
await makeShapeHook(config, opConfig, opKind)(request)
|
|
429
464
|
if (await runHooks(opConfig.operationBefore, request, reply)) return
|
|
@@ -456,6 +491,7 @@ export async function ${routerFunctionName}<TCtx = unknown, TPrisma = any>(
|
|
|
456
491
|
opKind: OpKind,
|
|
457
492
|
) => async (request: FastifyRequest, reply: FastifyReply) => {
|
|
458
493
|
try {
|
|
494
|
+
if (await runHooks(opConfig.authorize ? [opConfig.authorize] : [], request, reply)) return
|
|
459
495
|
await makeShapeHook(config, opConfig, opKind)(request)
|
|
460
496
|
if (await runHooks(opConfig.operationBefore, request, reply)) return
|
|
461
497
|
|
|
@@ -514,4 +550,4 @@ ${writeOpBlocks}
|
|
|
514
550
|
})
|
|
515
551
|
}
|
|
516
552
|
`
|
|
517
|
-
}
|
|
553
|
+
}
|
|
@@ -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
|
|
@@ -216,6 +237,8 @@ const DROP_GUARD = ${dropGuard}
|
|
|
216
237
|
type JsonLike = string | number | boolean | null | unknown[] | Record<string, unknown>
|
|
217
238
|
|
|
218
239
|
type OperationConfigLike<TEnv extends HonoEnvBase> = {
|
|
240
|
+
authorize?: HonoBeforeHook<TEnv>
|
|
241
|
+
override?: RuntimeOperationOverride
|
|
219
242
|
before?: HonoBeforeHook<TEnv>[]
|
|
220
243
|
after?: HonoAfterHook<TEnv>[]
|
|
221
244
|
shape?: unknown
|
|
@@ -289,7 +312,7 @@ async function parseWriteBodyMiddleware(c: HandlerContext): Promise<void> {
|
|
|
289
312
|
c.set('body', body)
|
|
290
313
|
}
|
|
291
314
|
|
|
292
|
-
function makeShapeMiddleware<TCtx, TPrisma, TEnv extends HonoEnvBase>(
|
|
315
|
+
function makeShapeMiddleware<TCtx, TPrisma extends PrismaClientLike, TEnv extends HonoEnvBase>(
|
|
293
316
|
config: ${modelName}RouteConfig<TCtx, TPrisma, TEnv>,
|
|
294
317
|
opConfig: NormalizedOp<TEnv>,
|
|
295
318
|
opKind: OpKind,
|
|
@@ -299,11 +322,11 @@ function makeShapeMiddleware<TCtx, TPrisma, TEnv extends HonoEnvBase>(
|
|
|
299
322
|
/**
|
|
300
323
|
* The environment bypass, honoured unless the consumer turned it off.
|
|
301
324
|
*
|
|
302
|
-
* \`
|
|
303
|
-
* hazard, and it is also upstream behaviour
|
|
304
|
-
* 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.
|
|
305
328
|
*/
|
|
306
|
-
const dropGuard = DROP_GUARD || (policy.allowE2EGuardBypass && _env
|
|
329
|
+
const dropGuard = DROP_GUARD || (policy.allowE2EGuardBypass && resolveDropGuardEnv(_env))
|
|
307
330
|
|
|
308
331
|
return async (c: Context<GeneratedHonoEnv<TEnv>>): Promise<void> => {
|
|
309
332
|
/**
|
|
@@ -318,6 +341,9 @@ function makeShapeMiddleware<TCtx, TPrisma, TEnv extends HonoEnvBase>(
|
|
|
318
341
|
* in.
|
|
319
342
|
*/
|
|
320
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)))
|
|
321
347
|
|
|
322
348
|
const merged = mergePaginationConfig(config.pagination, opConfig.pagination)
|
|
323
349
|
if (merged) vars.set('routeConfig', { pagination: merged })
|
|
@@ -341,7 +367,7 @@ function makeShapeMiddleware<TCtx, TPrisma, TEnv extends HonoEnvBase>(
|
|
|
341
367
|
|
|
342
368
|
if (opConfig.guardShape) {
|
|
343
369
|
const resolveCtx = typeof config.resolveContext === 'function'
|
|
344
|
-
?
|
|
370
|
+
? vars.get('resolveOperationContext')
|
|
345
371
|
: undefined
|
|
346
372
|
|
|
347
373
|
/**
|
|
@@ -441,7 +467,7 @@ function sendError(c: HandlerContext, error: unknown): Response {
|
|
|
441
467
|
return c.json({ message: httpError.message }, httpError.status as ContentfulStatusCode)
|
|
442
468
|
}
|
|
443
469
|
|
|
444
|
-
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>> {
|
|
445
471
|
validateCountSourceWhere(config.pagination?.countSource, '${modelName} pagination')
|
|
446
472
|
validateCountSourceWhere(
|
|
447
473
|
(config.findManyPaginated && typeof config.findManyPaginated === 'object' ? config.findManyPaginated : undefined)?.pagination?.countSource,
|
|
@@ -451,9 +477,10 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
|
|
|
451
477
|
const app = new Hono<GeneratedHonoEnv<TEnv>>()
|
|
452
478
|
|
|
453
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)
|
|
454
481
|
|
|
455
482
|
const customPrefix = normalizePrefix(config.customUrlPrefix || '')
|
|
456
|
-
|
|
483
|
+
const modelPrefix = config.addModelPrefix !== false ? '/${modelSegment}' : ''
|
|
457
484
|
const basePath = customPrefix + modelPrefix
|
|
458
485
|
|
|
459
486
|
const openApiDisabled = config.disableOpenApi === true
|
|
@@ -462,7 +489,6 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
|
|
|
462
489
|
|| _env.DISABLE_OPENAPI === 'true'
|
|
463
490
|
))
|
|
464
491
|
|
|
465
|
-
const postReadsEnabled = !config.disablePostReads
|
|
466
492
|
|
|
467
493
|
let _openApiJsonCache: unknown = undefined
|
|
468
494
|
const getOpenApiJson = (): unknown => {
|
|
@@ -472,7 +498,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
|
|
|
472
498
|
MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
|
|
473
499
|
MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
|
|
474
500
|
config as unknown as Parameters<typeof buildModelOpenApi>[3],
|
|
475
|
-
{ format: 'json', writeStrategy: '${writeStrategy}' },
|
|
501
|
+
{ format: 'json', writeStrategy: '${writeStrategy}', pathSegment: '${modelSegment}' },
|
|
476
502
|
)
|
|
477
503
|
}
|
|
478
504
|
return _openApiJsonCache
|
|
@@ -485,7 +511,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
|
|
|
485
511
|
MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
|
|
486
512
|
MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
|
|
487
513
|
config as unknown as Parameters<typeof buildModelOpenApi>[3],
|
|
488
|
-
{ format: 'yaml', writeStrategy: '${writeStrategy}' },
|
|
514
|
+
{ format: 'yaml', writeStrategy: '${writeStrategy}', pathSegment: '${modelSegment}' },
|
|
489
515
|
) as string
|
|
490
516
|
}
|
|
491
517
|
return _openApiYamlCache
|
|
@@ -552,6 +578,8 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
|
|
|
552
578
|
opKind: OpKind,
|
|
553
579
|
) => async (c: Context<GeneratedHonoEnv<TEnv>>): Promise<Response> => {
|
|
554
580
|
try {
|
|
581
|
+
const authorized = await runBeforeHooks<TEnv>(opConfig.authorize ? [opConfig.authorize] : [], c)
|
|
582
|
+
if (authorized) return authorized
|
|
555
583
|
await parseFn(c as unknown as HandlerContext)
|
|
556
584
|
await makeShapeMiddleware<TCtx, TPrisma, TEnv>(config, opConfig, opKind)(c)
|
|
557
585
|
|
|
@@ -587,6 +615,8 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
|
|
|
587
615
|
opKind: OpKind,
|
|
588
616
|
) => async (c: Context<GeneratedHonoEnv<TEnv>>): Promise<Response> => {
|
|
589
617
|
try {
|
|
618
|
+
const authorized = await runBeforeHooks<TEnv>(opConfig.authorize ? [opConfig.authorize] : [], c)
|
|
619
|
+
if (authorized) return authorized
|
|
590
620
|
await parseWriteBodyMiddleware(c as unknown as HandlerContext)
|
|
591
621
|
await makeShapeMiddleware<TCtx, TPrisma, TEnv>(config, opConfig, opKind)(c)
|
|
592
622
|
|
|
@@ -677,4 +707,4 @@ ${writeOpBlocks}
|
|
|
677
707
|
return app
|
|
678
708
|
}
|
|
679
709
|
`
|
|
680
|
-
}
|
|
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
|
+
}
|