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