prisma-generator-express 1.64.0 → 1.64.2
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/dist/bin.js +0 -0
- package/dist/generators/generateRouterHono.js +74 -49
- package/dist/generators/generateRouterHono.js.map +1 -1
- package/package.json +2 -2
- package/src/bin.ts +0 -0
- package/src/copy/projectionDefaults.ts +6 -1
- package/src/copy/routeConfig.ts +572 -361
- package/src/generators/generateRouterHono.ts +101 -49
|
@@ -80,6 +80,33 @@ function emitWriteOp(meta: (typeof OPERATION_METADATA)[number], modelName: strin
|
|
|
80
80
|
}`
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Guard dropping is a GENERATION-TIME decision, never a runtime one.
|
|
85
|
+
*
|
|
86
|
+
* The emitted router used to compute `DROP_GUARD = <flag> || _env.E2E === 'true'`,
|
|
87
|
+
* so setting `E2E=true` in a deployed environment downgraded enforcement even
|
|
88
|
+
* when the generator had been told to keep the guard. The two modes are not
|
|
89
|
+
* equivalent: with the guard, the shape goes to prisma-guard; with it dropped,
|
|
90
|
+
* `applyDroppedGuard` applies projection defaults and forced `where` clauses and
|
|
91
|
+
* nothing else is validated against the shape.
|
|
92
|
+
*
|
|
93
|
+
* On an edge runtime that variable is an ordinary config var — set on a staging
|
|
94
|
+
* deployment, copied forward, flagged as security-relevant nowhere. A
|
|
95
|
+
* deployment's guard behaviour has to be a property of the artifact, not of the
|
|
96
|
+
* environment it happens to land in.
|
|
97
|
+
*
|
|
98
|
+
* The rationale lives here rather than in the emitted file on purpose: generated
|
|
99
|
+
* output is an artifact, and a paragraph about a bypass that no longer exists
|
|
100
|
+
* would be copied into every router this generator writes.
|
|
101
|
+
*
|
|
102
|
+
* `updateEach` is refused for the same reason, one step further on. It bypasses
|
|
103
|
+
* guard shapes by design — the endpoint is a batch of `{ where, data }` items
|
|
104
|
+
* applied directly — and the only thing between it and an unguarded mass
|
|
105
|
+
* mutation was a `console.warn` suppressed in production. A warning is not a
|
|
106
|
+
* security boundary; it is advice to whoever happens to be reading a development
|
|
107
|
+
* log. Refused at construction rather than silently dropped, so a deployment
|
|
108
|
+
* expecting the route learns at boot instead of at the first 404.
|
|
109
|
+
*/
|
|
83
110
|
export function generateHonoRouterFunction({
|
|
84
111
|
model,
|
|
85
112
|
enums,
|
|
@@ -133,9 +160,9 @@ import { buildModelOpenApi } from '../buildModelOpenApi${ext}'
|
|
|
133
160
|
import {
|
|
134
161
|
normalizeOperation,
|
|
135
162
|
resolveOperationVariantKey,
|
|
163
|
+
resolveGuardShapeOnce,
|
|
136
164
|
validateCountSourceWhere,
|
|
137
165
|
validateOperationConfig,
|
|
138
|
-
validateUpdateEachConfig,
|
|
139
166
|
} from '../routeConfig${ext}'
|
|
140
167
|
import type { NormalizedOperationConfig } from '../routeConfig${ext}'
|
|
141
168
|
import { transformResult } from '../operationRuntime${ext}'
|
|
@@ -149,7 +176,9 @@ import { MODEL_FIELDS, MODEL_ENUMS } from './${modelName}Metadata${ext}'
|
|
|
149
176
|
${generateRouteConfigType(modelName, 'HonoBeforeHook', guardShapesImport, importStyle, 'hono')}
|
|
150
177
|
const _env = getEnv()
|
|
151
178
|
|
|
152
|
-
|
|
179
|
+
// Fixed at generation time. Never read from the environment — see
|
|
180
|
+
// generateRouterHono.ts.
|
|
181
|
+
const DROP_GUARD = ${dropGuard}
|
|
153
182
|
|
|
154
183
|
type JsonLike =
|
|
155
184
|
| string
|
|
@@ -220,19 +249,6 @@ async function parseWriteBodyMiddleware(c: HandlerContext): Promise<void> {
|
|
|
220
249
|
c.set('body', body)
|
|
221
250
|
}
|
|
222
251
|
|
|
223
|
-
async function parseUpdateEachBodyMiddleware(c: HandlerContext): Promise<void> {
|
|
224
|
-
let body: unknown
|
|
225
|
-
try {
|
|
226
|
-
body = await c.req.json()
|
|
227
|
-
} catch {
|
|
228
|
-
throw new HTTPException(400, { message: 'updateEach body must be an array of { where, data } items' })
|
|
229
|
-
}
|
|
230
|
-
if (!Array.isArray(body)) {
|
|
231
|
-
throw new HTTPException(400, { message: 'updateEach body must be an array of { where, data } items' })
|
|
232
|
-
}
|
|
233
|
-
c.set('body', body)
|
|
234
|
-
}
|
|
235
|
-
|
|
236
252
|
function makeShapeMiddleware<TCtx, TPrisma, TEnv extends HonoEnvBase>(
|
|
237
253
|
config: ${modelName}RouteConfig<TCtx, TPrisma, TEnv>,
|
|
238
254
|
opConfig: NormalizedOp<TEnv>,
|
|
@@ -260,15 +276,27 @@ function makeShapeMiddleware<TCtx, TPrisma, TEnv extends HonoEnvBase>(
|
|
|
260
276
|
if (resolvedKey !== undefined) c.set('guardVariantKey', resolvedKey)
|
|
261
277
|
|
|
262
278
|
if (opConfig.guardShape) {
|
|
279
|
+
const resolveCtx = typeof config.resolveContext === 'function'
|
|
280
|
+
? () => (config.resolveContext as (ctx: Context<GeneratedHonoEnv<TEnv>>) => unknown | Promise<unknown>)(c)
|
|
281
|
+
: undefined
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Resolved ONCE, here, and the validated value is what travels onward —
|
|
285
|
+
* see resolveGuardShapeOnce. Passing the function on would let it return a
|
|
286
|
+
* different shape when it is enforced than when it was checked.
|
|
287
|
+
*/
|
|
288
|
+
const resolution = await resolveGuardShapeOnce(opConfig.guardShape, resolvedKey, resolveCtx)
|
|
289
|
+
if (!resolution.ok) {
|
|
290
|
+
c.set('guardShapeFailure', resolution.problem)
|
|
291
|
+
return
|
|
292
|
+
}
|
|
293
|
+
const effectiveShape = resolution.shape
|
|
294
|
+
|
|
263
295
|
if (!DROP_GUARD) {
|
|
264
|
-
c.set('guardShape',
|
|
296
|
+
c.set('guardShape', effectiveShape)
|
|
265
297
|
} else {
|
|
266
|
-
const resolveCtx = typeof config.resolveContext === 'function'
|
|
267
|
-
? () => (config.resolveContext as (ctx: Context<GeneratedHonoEnv<TEnv>>) => unknown | Promise<unknown>)(c)
|
|
268
|
-
: undefined
|
|
269
|
-
|
|
270
298
|
await applyDroppedGuard(
|
|
271
|
-
|
|
299
|
+
effectiveShape,
|
|
272
300
|
resolvedKey,
|
|
273
301
|
resolveCtx,
|
|
274
302
|
opKind,
|
|
@@ -419,9 +447,19 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
|
|
|
419
447
|
try {
|
|
420
448
|
await parseFn(c as unknown as HandlerContext)
|
|
421
449
|
await makeShapeMiddleware<TCtx, TPrisma, TEnv>(config, opConfig, opKind)(c)
|
|
422
|
-
const operationBefore = await runBeforeHooks<TEnv>(opConfig.operationBefore, c)
|
|
423
|
-
if (operationBefore) return operationBefore
|
|
424
450
|
|
|
451
|
+
/**
|
|
452
|
+
* Variant resolution is settled BEFORE any operation hook runs.
|
|
453
|
+
*
|
|
454
|
+
* The check used to sit after \`operationBefore\`, so a hook that returned a
|
|
455
|
+
* Response — an auth gate, a cache, a short-circuit for a known caller —
|
|
456
|
+
* answered the request before anyone established which guard applied to
|
|
457
|
+
* it. A cached response served for a request whose variant could not be
|
|
458
|
+
* resolved is a response served under a guard nobody chose.
|
|
459
|
+
*
|
|
460
|
+
* Hooks that must run first belong outside the generated router, where
|
|
461
|
+
* they are visibly not part of guard resolution.
|
|
462
|
+
*/
|
|
425
463
|
const failure = c.get('guardVariantFailure')
|
|
426
464
|
if (failure) {
|
|
427
465
|
throw new HTTPException(400, {
|
|
@@ -429,6 +467,22 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
|
|
|
429
467
|
})
|
|
430
468
|
}
|
|
431
469
|
|
|
470
|
+
/**
|
|
471
|
+
* A shape function that returned something unusable is a DEPLOYMENT fault,
|
|
472
|
+
* not a caller fault — 500, and refused here, before any hook can answer
|
|
473
|
+
* the request and before Prisma is reached. Running unguarded because the
|
|
474
|
+
* guard failed to produce a guard is the outcome this exists to prevent.
|
|
475
|
+
*/
|
|
476
|
+
const shapeFailure = c.get('guardShapeFailure')
|
|
477
|
+
if (shapeFailure) {
|
|
478
|
+
throw new HTTPException(500, {
|
|
479
|
+
message: 'guard shape could not be resolved: ' + shapeFailure,
|
|
480
|
+
})
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const operationBefore = await runBeforeHooks<TEnv>(opConfig.operationBefore, c)
|
|
484
|
+
if (operationBefore) return operationBefore
|
|
485
|
+
|
|
432
486
|
const key = c.get('guardVariantKey')
|
|
433
487
|
const variantHooks =
|
|
434
488
|
key !== undefined ? opConfig.variantHooks[key] : undefined
|
|
@@ -454,9 +508,8 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
|
|
|
454
508
|
try {
|
|
455
509
|
await parseWriteBodyMiddleware(c as unknown as HandlerContext)
|
|
456
510
|
await makeShapeMiddleware<TCtx, TPrisma, TEnv>(config, opConfig, opKind)(c)
|
|
457
|
-
const operationBefore = await runBeforeHooks<TEnv>(opConfig.operationBefore, c)
|
|
458
|
-
if (operationBefore) return operationBefore
|
|
459
511
|
|
|
512
|
+
// Settled before any operation hook — see the read handler above.
|
|
460
513
|
const failure = c.get('guardVariantFailure')
|
|
461
514
|
if (failure) {
|
|
462
515
|
throw new HTTPException(400, {
|
|
@@ -464,6 +517,22 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
|
|
|
464
517
|
})
|
|
465
518
|
}
|
|
466
519
|
|
|
520
|
+
/**
|
|
521
|
+
* A shape function that returned something unusable is a DEPLOYMENT fault,
|
|
522
|
+
* not a caller fault — 500, and refused here, before any hook can answer
|
|
523
|
+
* the request and before Prisma is reached. Running unguarded because the
|
|
524
|
+
* guard failed to produce a guard is the outcome this exists to prevent.
|
|
525
|
+
*/
|
|
526
|
+
const shapeFailure = c.get('guardShapeFailure')
|
|
527
|
+
if (shapeFailure) {
|
|
528
|
+
throw new HTTPException(500, {
|
|
529
|
+
message: 'guard shape could not be resolved: ' + shapeFailure,
|
|
530
|
+
})
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const operationBefore = await runBeforeHooks<TEnv>(opConfig.operationBefore, c)
|
|
534
|
+
if (operationBefore) return operationBefore
|
|
535
|
+
|
|
467
536
|
const key = c.get('guardVariantKey')
|
|
468
537
|
const variantHooks =
|
|
469
538
|
key !== undefined ? opConfig.variantHooks[key] : undefined
|
|
@@ -493,31 +562,14 @@ ${readOpBlocks}
|
|
|
493
562
|
|
|
494
563
|
${writeOpBlocks}
|
|
495
564
|
|
|
565
|
+
// Not registered on this target — see generateRouterHono.ts for why.
|
|
496
566
|
if (config.updateEach) {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
'This endpoint bypasses guard shapes and should be protected by authentication middleware.',
|
|
504
|
-
)
|
|
505
|
-
}
|
|
506
|
-
const path = basePath ? \`\${basePath}/each\` : '/each'
|
|
507
|
-
app.post(path, async (c: Context<GeneratedHonoEnv<TEnv>>): Promise<Response> => {
|
|
508
|
-
try {
|
|
509
|
-
await parseUpdateEachBodyMiddleware(c as unknown as HandlerContext)
|
|
510
|
-
await makeShapeMiddleware<TCtx, TPrisma, TEnv>(config, opConfig, 'noop')(c)
|
|
511
|
-
const beforeResponse = await runBeforeHooks<TEnv>(opConfig.operationBefore, c)
|
|
512
|
-
if (beforeResponse) return beforeResponse
|
|
513
|
-
await ${modelName}UpdateEach(c as unknown as HandlerContext)
|
|
514
|
-
const afterResponse = await runAfterHooks<TEnv>(opConfig.operationAfter, c)
|
|
515
|
-
if (afterResponse) return afterResponse
|
|
516
|
-
return sendResult(c as unknown as HandlerContext)
|
|
517
|
-
} catch (error: unknown) {
|
|
518
|
-
return sendError(c as unknown as HandlerContext, error)
|
|
519
|
-
}
|
|
520
|
-
})
|
|
567
|
+
throw new Error(
|
|
568
|
+
'${modelName}.updateEach: this target does not register updateEach. It bypasses ' +
|
|
569
|
+
'guard shapes entirely, so there is no configuration that makes it safe to ' +
|
|
570
|
+
'expose. Perform batch updates through a guarded operation, or behind your own ' +
|
|
571
|
+
'authenticated route outside the generated router.',
|
|
572
|
+
)
|
|
521
573
|
}
|
|
522
574
|
|
|
523
575
|
return app
|