prisma-generator-express 1.64.2 → 1.64.4
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/generators/generateQueryBuilderHelper.d.ts +1 -1
- package/dist/generators/generateQueryBuilderHelper.js +5 -4
- package/dist/generators/generateQueryBuilderHelper.js.map +1 -1
- package/dist/generators/generateRouteConfigType.js +2 -1
- package/dist/generators/generateRouteConfigType.js.map +1 -1
- package/dist/generators/generateRouterHono.js +132 -70
- package/dist/generators/generateRouterHono.js.map +1 -1
- package/dist/guardOptions.d.ts +30 -0
- package/dist/guardOptions.js +123 -0
- package/dist/guardOptions.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/copy/routeConfig.hono.ts +85 -0
- package/src/copy/routeConfig.ts +148 -20
- package/src/generators/generateQueryBuilderHelper.ts +9 -6
- package/src/generators/generateRouteConfigType.ts +6 -1
- package/src/generators/generateRouterHono.ts +162 -78
- package/src/guardOptions.ts +226 -0
- package/src/index.ts +19 -0
|
@@ -107,6 +107,91 @@ export type RouteConfig<
|
|
|
107
107
|
>,
|
|
108
108
|
HonoOpKeys
|
|
109
109
|
> & {
|
|
110
|
+
/**
|
|
111
|
+
* SEVEN INDEPENDENT GUARD CONTROLS. Every default reproduces the public
|
|
112
|
+
* package's pre-1.64.2 behaviour exactly.
|
|
113
|
+
*
|
|
114
|
+
* 1.64.2 shipped these behaviours ON by default, which was a breaking change
|
|
115
|
+
* to a published package. The first correction put all seven behind one
|
|
116
|
+
* `requireGuard` switch, which was also wrong: they are unrelated decisions,
|
|
117
|
+
* and bundling them means a consumer who wants a missing-guard refusal is also
|
|
118
|
+
* forced into a hook-ordering change and the removal of a route they may use.
|
|
119
|
+
*
|
|
120
|
+
* So each is its own option. An unset option means the upstream default — never
|
|
121
|
+
* an opinionated value chosen on the consumer's behalf. `HARDENED_GUARD_PROFILE`
|
|
122
|
+
* selects all seven together for those who want them, but it is a set of values
|
|
123
|
+
* to spread, not a mode: what gets stored is the individual settings.
|
|
124
|
+
*
|
|
125
|
+
* `GUARD_OPTION_METADATA` describes these machine-readably, so a UI can render
|
|
126
|
+
* them without a hand-maintained second copy of the defaults.
|
|
127
|
+
*/
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Refuse an operation that configures no guard at all. **Default `false`.**
|
|
131
|
+
*
|
|
132
|
+
* With it off — the upstream default — an unconfigured operation is emitted and
|
|
133
|
+
* passes the caller's own where/select/include straight to Prisma. On, router
|
|
134
|
+
* construction throws, which on a Worker is boot.
|
|
135
|
+
*/
|
|
136
|
+
requireGuardShape?: boolean
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Validate that a shape constrains something. **Default `false`.**
|
|
140
|
+
*
|
|
141
|
+
* On, `shape: {}` and a shape mixing guard keys with non-guard keys (usually a
|
|
142
|
+
* typo: `wheer`, `iclude`) are refused, at the top level and inside variants.
|
|
143
|
+
*/
|
|
144
|
+
validateGuardShapes?: boolean
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Require `allowDefaultVariant: true` on an operation carrying a variant named
|
|
148
|
+
* `default`. **Default `false`.**
|
|
149
|
+
*
|
|
150
|
+
* A `default` variant answers every unrecognised, blank and missing caller.
|
|
151
|
+
* That is safe when it is the most restrictive variant and dangerous when it is
|
|
152
|
+
* the most permissive, and nothing can tell which from the shape.
|
|
153
|
+
*/
|
|
154
|
+
requireDefaultVariantOptIn?: boolean
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Register the `updateEach` batch route. **Default `true`.**
|
|
158
|
+
*
|
|
159
|
+
* It bypasses guard shapes by design — a batch of `{ where, data }` applied
|
|
160
|
+
* directly — and is protected only by a development-only warning. Set `false`
|
|
161
|
+
* to refuse it at router construction.
|
|
162
|
+
*/
|
|
163
|
+
enableUpdateEach?: boolean
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* When guard failures are raised relative to operation `before` hooks.
|
|
167
|
+
* **Default `'after-hooks'`.**
|
|
168
|
+
*
|
|
169
|
+
* `'after-hooks'` is upstream behaviour: a hook may answer the request before
|
|
170
|
+
* variant resolution is settled. `'before-hooks'` settles the guard first, so a
|
|
171
|
+
* hook cannot answer a request whose guard was never established.
|
|
172
|
+
*/
|
|
173
|
+
guardResolutionOrder?: 'after-hooks' | 'before-hooks'
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Honour `E2E=true` in the environment as a guard bypass. **Default `true`.**
|
|
177
|
+
*
|
|
178
|
+
* Upstream behaviour, and a real hazard on an edge runtime where that variable
|
|
179
|
+
* is an ordinary config var — set on staging, copied forward, flagged as
|
|
180
|
+
* security-relevant nowhere. `false` makes guard behaviour a property of the
|
|
181
|
+
* artifact alone.
|
|
182
|
+
*/
|
|
183
|
+
allowE2EGuardBypass?: boolean
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Check what a shape FUNCTION returned before using it. **Default `false`.**
|
|
187
|
+
*
|
|
188
|
+
* A function shape is opaque at construction time. On, a resolved value that is
|
|
189
|
+
* empty, not an object, or carrying non-guard keys fails the request with 500 —
|
|
190
|
+
* a deployment fault, not a caller fault — instead of running unguarded. It
|
|
191
|
+
* also resolves the function exactly once and passes the validated value on.
|
|
192
|
+
*/
|
|
193
|
+
validateResolvedShapes?: boolean
|
|
194
|
+
|
|
110
195
|
findUnique?: OperationConfig<TShape, TEnv> | false
|
|
111
196
|
findUniqueOrThrow?: OperationConfig<TShape, TEnv> | false
|
|
112
197
|
findFirst?: OperationConfig<TShape, TEnv> | false
|
package/src/copy/routeConfig.ts
CHANGED
|
@@ -119,6 +119,14 @@ export type BaseOperationConfig<
|
|
|
119
119
|
before?: TBefore[]
|
|
120
120
|
after?: TAfter[]
|
|
121
121
|
pagination?: Partial<PaginationConfig>
|
|
122
|
+
/**
|
|
123
|
+
* Confirms that a variant literally named `default` is meant to answer every
|
|
124
|
+
* unrecognised, blank and missing caller.
|
|
125
|
+
*
|
|
126
|
+
* Only consulted when `requireDefaultVariantOptIn` is on. Otherwise the key
|
|
127
|
+
* behaves as it always has, so this is inert for existing configurations.
|
|
128
|
+
*/
|
|
129
|
+
allowDefaultVariant?: boolean
|
|
122
130
|
}
|
|
123
131
|
|
|
124
132
|
export interface BaseUpdateEachConfig<TBefore, TAfter = TBefore> {
|
|
@@ -216,6 +224,77 @@ type OperationConfigInput<TBefore, TAfter> = {
|
|
|
216
224
|
pagination?: Partial<PaginationConfig>
|
|
217
225
|
}
|
|
218
226
|
|
|
227
|
+
/**
|
|
228
|
+
* The seven guard controls, resolved — the RUNTIME half.
|
|
229
|
+
*
|
|
230
|
+
* This lives here, not in the compiled `src/guardOptions.ts`, because a generated
|
|
231
|
+
* project gets these files copied in and must run with no dependency on this
|
|
232
|
+
* package. The two cannot import each other across that boundary, so the values
|
|
233
|
+
* appear twice and `guardOptionIndependence.test.ts` asserts they agree — against
|
|
234
|
+
* this resolver, which is the one the router actually runs.
|
|
235
|
+
*
|
|
236
|
+
* The machine-readable METADATA is compiled and public: see src/guardOptions.ts.
|
|
237
|
+
* Defining it here would put it in a file that never reaches `dist`.
|
|
238
|
+
*/
|
|
239
|
+
export type GuardPolicy = {
|
|
240
|
+
requireGuardShape: boolean
|
|
241
|
+
validateGuardShapes: boolean
|
|
242
|
+
requireDefaultVariantOptIn: boolean
|
|
243
|
+
enableUpdateEach: boolean
|
|
244
|
+
guardResolutionOrder: 'after-hooks' | 'before-hooks'
|
|
245
|
+
allowE2EGuardBypass: boolean
|
|
246
|
+
validateResolvedShapes: boolean
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* PRE-1.64.2 BEHAVIOUR, exactly. Changing any value here changes what an
|
|
251
|
+
* unconfigured consumer gets on upgrade, which is the thing that must not happen.
|
|
252
|
+
*/
|
|
253
|
+
export const UPSTREAM_GUARD_DEFAULTS: Readonly<GuardPolicy> = Object.freeze({
|
|
254
|
+
requireGuardShape: false,
|
|
255
|
+
validateGuardShapes: false,
|
|
256
|
+
requireDefaultVariantOptIn: false,
|
|
257
|
+
enableUpdateEach: true,
|
|
258
|
+
guardResolutionOrder: 'after-hooks',
|
|
259
|
+
allowE2EGuardBypass: true,
|
|
260
|
+
validateResolvedShapes: false,
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Every control at its most restrictive — a SHORTCUT, not a mode.
|
|
265
|
+
*
|
|
266
|
+
* Spread it, edit what you disagree with, and store the result. There is
|
|
267
|
+
* deliberately no `preset: 'hardened'` option: a stored preset name would mean a
|
|
268
|
+
* future version of this package could silently change what a consumer's
|
|
269
|
+
* configuration does, which is the same class of mistake as 1.64.2 itself.
|
|
270
|
+
*/
|
|
271
|
+
export const HARDENED_GUARD_PROFILE: Readonly<GuardPolicy> = Object.freeze({
|
|
272
|
+
requireGuardShape: true,
|
|
273
|
+
validateGuardShapes: true,
|
|
274
|
+
requireDefaultVariantOptIn: true,
|
|
275
|
+
enableUpdateEach: false,
|
|
276
|
+
guardResolutionOrder: 'before-hooks',
|
|
277
|
+
allowE2EGuardBypass: false,
|
|
278
|
+
validateResolvedShapes: true,
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
/** Fill in the defaults. Absent means upstream, never an opinion. */
|
|
282
|
+
export function resolveGuardPolicy(config: Partial<GuardPolicy> | undefined): GuardPolicy {
|
|
283
|
+
return {
|
|
284
|
+
requireGuardShape: config?.requireGuardShape ?? UPSTREAM_GUARD_DEFAULTS.requireGuardShape,
|
|
285
|
+
validateGuardShapes: config?.validateGuardShapes ?? UPSTREAM_GUARD_DEFAULTS.validateGuardShapes,
|
|
286
|
+
requireDefaultVariantOptIn:
|
|
287
|
+
config?.requireDefaultVariantOptIn ?? UPSTREAM_GUARD_DEFAULTS.requireDefaultVariantOptIn,
|
|
288
|
+
enableUpdateEach: config?.enableUpdateEach ?? UPSTREAM_GUARD_DEFAULTS.enableUpdateEach,
|
|
289
|
+
guardResolutionOrder:
|
|
290
|
+
config?.guardResolutionOrder ?? UPSTREAM_GUARD_DEFAULTS.guardResolutionOrder,
|
|
291
|
+
allowE2EGuardBypass:
|
|
292
|
+
config?.allowE2EGuardBypass ?? UPSTREAM_GUARD_DEFAULTS.allowE2EGuardBypass,
|
|
293
|
+
validateResolvedShapes:
|
|
294
|
+
config?.validateResolvedShapes ?? UPSTREAM_GUARD_DEFAULTS.validateResolvedShapes,
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
219
298
|
function classifyGuardRouting(shape: unknown): NormalizedGuardRouting {
|
|
220
299
|
if (shape === undefined) return { kind: 'none' }
|
|
221
300
|
if (typeof shape === 'function') return { kind: 'single' }
|
|
@@ -295,34 +374,79 @@ function validateShapeConfig(shape: unknown, location: string): void {
|
|
|
295
374
|
export function validateOperationConfig(
|
|
296
375
|
config: { shape?: unknown; variants?: unknown; allowDefaultVariant?: unknown } | undefined,
|
|
297
376
|
location: string,
|
|
377
|
+
policy: Partial<GuardPolicy> = {},
|
|
298
378
|
): void {
|
|
379
|
+
const { requireGuardShape, validateGuardShapes, requireDefaultVariantOptIn } =
|
|
380
|
+
resolveGuardPolicy(policy)
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* THE CHECKS THAT PREDATE 1.64.2 RUN UNCONDITIONALLY.
|
|
384
|
+
*
|
|
385
|
+
* Everything below them is gated per option. Note each gate stands alone: a
|
|
386
|
+
* consumer who enables `validateGuardShapes` gets shape validation and NOT a
|
|
387
|
+
* missing-guard refusal, and vice versa. That independence is the point, and
|
|
388
|
+
* `guardOptionIndependence.test.ts` asserts it option by option.
|
|
389
|
+
*/
|
|
299
390
|
if (!config) {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
391
|
+
if (requireGuardShape) {
|
|
392
|
+
throw new Error(
|
|
393
|
+
location + ': no guard configured. Every generated operation must define ' +
|
|
394
|
+
'"shape" or "variants"; an unguarded operation would pass the caller\'s ' +
|
|
395
|
+
'own where/select/include straight to Prisma.',
|
|
396
|
+
)
|
|
397
|
+
}
|
|
398
|
+
return
|
|
305
399
|
}
|
|
306
400
|
|
|
307
401
|
const hasShape = config.shape !== undefined
|
|
308
402
|
const hasVariants = config.variants !== undefined
|
|
309
403
|
|
|
404
|
+
// Pre-1.64.2. Not gated.
|
|
310
405
|
if (hasShape && hasVariants) {
|
|
311
406
|
throw new Error(location + ': shape and variants cannot both be defined')
|
|
312
407
|
}
|
|
313
408
|
|
|
314
409
|
if (!hasShape && !hasVariants) {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
410
|
+
if (requireGuardShape) {
|
|
411
|
+
throw new Error(
|
|
412
|
+
location + ': no guard configured. Define "shape" or "variants".',
|
|
413
|
+
)
|
|
414
|
+
}
|
|
415
|
+
return
|
|
318
416
|
}
|
|
319
417
|
|
|
320
418
|
if (hasShape) {
|
|
321
|
-
validateShapeConfig(config.shape, location)
|
|
419
|
+
if (validateGuardShapes) validateShapeConfig(config.shape, location)
|
|
322
420
|
return
|
|
323
421
|
}
|
|
324
422
|
|
|
325
|
-
|
|
423
|
+
validateVariantMap(
|
|
424
|
+
config.variants,
|
|
425
|
+
location,
|
|
426
|
+
{ validateGuardShapes, requireDefaultVariantOptIn },
|
|
427
|
+
config.allowDefaultVariant,
|
|
428
|
+
)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* The variant-map checks.
|
|
433
|
+
*
|
|
434
|
+
* The four that predate 1.64.2 — non-array object, at least one entry, no
|
|
435
|
+
* reserved-key collision, every entry an object carrying a `shape` — run
|
|
436
|
+
* ALWAYS, because they are not new and removing them would be its own breaking
|
|
437
|
+
* change.
|
|
438
|
+
*
|
|
439
|
+
* The two the hardening added are gated on their OWN options, independently:
|
|
440
|
+
* `requireDefaultVariantOptIn` confirms a `default` variant,
|
|
441
|
+
* `validateGuardShapes` validates each variant's shape the way a top-level
|
|
442
|
+
* `shape` is validated. Enabling either does not enable the other.
|
|
443
|
+
*/
|
|
444
|
+
function validateVariantMap(
|
|
445
|
+
variants: unknown,
|
|
446
|
+
location: string,
|
|
447
|
+
flags: { validateGuardShapes: boolean; requireDefaultVariantOptIn: boolean },
|
|
448
|
+
allowDefaultVariant?: unknown,
|
|
449
|
+
): void {
|
|
326
450
|
if (!isPlainObject(variants)) {
|
|
327
451
|
throw new Error(location + ': variants must be a non-array object')
|
|
328
452
|
}
|
|
@@ -343,18 +467,20 @@ export function validateOperationConfig(
|
|
|
343
467
|
* writes on purpose; a key called `default` is one they may have typed without
|
|
344
468
|
* noticing what it catches.
|
|
345
469
|
*/
|
|
346
|
-
if (
|
|
347
|
-
if (
|
|
470
|
+
if (flags.requireDefaultVariantOptIn) {
|
|
471
|
+
if (Object.prototype.hasOwnProperty.call(variants, 'default')) {
|
|
472
|
+
if (allowDefaultVariant !== true) {
|
|
473
|
+
throw new Error(
|
|
474
|
+
location + ': a variant named "default" answers every unrecognised, ' +
|
|
475
|
+
'blank and missing caller. Set allowDefaultVariant: true to confirm ' +
|
|
476
|
+
'that is intended, and make sure it is the most restrictive variant.',
|
|
477
|
+
)
|
|
478
|
+
}
|
|
479
|
+
} else if (allowDefaultVariant !== undefined) {
|
|
348
480
|
throw new Error(
|
|
349
|
-
location + ':
|
|
350
|
-
'blank and missing caller. Set allowDefaultVariant: true to confirm ' +
|
|
351
|
-
'that is intended, and make sure it is the most restrictive variant.',
|
|
481
|
+
location + ': allowDefaultVariant is set but no "default" variant exists.',
|
|
352
482
|
)
|
|
353
483
|
}
|
|
354
|
-
} else if (config.allowDefaultVariant !== undefined) {
|
|
355
|
-
throw new Error(
|
|
356
|
-
location + ': allowDefaultVariant is set but no "default" variant exists.',
|
|
357
|
-
)
|
|
358
484
|
}
|
|
359
485
|
|
|
360
486
|
for (const [key, rawEntry] of entries) {
|
|
@@ -384,7 +510,9 @@ export function validateOperationConfig(
|
|
|
384
510
|
* different key. A guard is not more trustworthy for being written inside a
|
|
385
511
|
* variant.
|
|
386
512
|
*/
|
|
387
|
-
|
|
513
|
+
if (flags.validateGuardShapes) {
|
|
514
|
+
validateShapeConfig(rawEntry.shape, location + ' variant "' + key + '"')
|
|
515
|
+
}
|
|
388
516
|
}
|
|
389
517
|
}
|
|
390
518
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { GeneratorOptions } from '@prisma/generator-helper'
|
|
2
2
|
|
|
3
|
-
export function generateQueryBuilderHelper(
|
|
3
|
+
export function generateQueryBuilderHelper(
|
|
4
|
+
options: Pick<GeneratorOptions, 'schemaPath'>,
|
|
5
|
+
): string {
|
|
4
6
|
const schemaPath = options.schemaPath
|
|
5
7
|
? JSON.stringify(options.schemaPath)
|
|
6
8
|
: "resolve(process.cwd(), 'prisma/schema.prisma')"
|
|
@@ -52,7 +54,7 @@ export function startQueryBuilder(options: QueryBuilderOptions = {}): void {
|
|
|
52
54
|
|
|
53
55
|
function doStart(options: QueryBuilderOptions): Promise<void> {
|
|
54
56
|
return new Promise<void>((resolvePromise) => {
|
|
55
|
-
const env =
|
|
57
|
+
const env = process.env
|
|
56
58
|
|
|
57
59
|
if (env.NODE_ENV === 'production') {
|
|
58
60
|
resolvePromise()
|
|
@@ -97,7 +99,7 @@ function doStart(options: QueryBuilderOptions): Promise<void> {
|
|
|
97
99
|
|
|
98
100
|
const schemaCwd = dirname(resolve(schemaPath))
|
|
99
101
|
|
|
100
|
-
|
|
102
|
+
const child = spawn(process.execPath, [cliPath], {
|
|
101
103
|
stdio: 'inherit',
|
|
102
104
|
env: {
|
|
103
105
|
...env,
|
|
@@ -110,14 +112,15 @@ function doStart(options: QueryBuilderOptions): Promise<void> {
|
|
|
110
112
|
DATABASE_URL: databaseUrl,
|
|
111
113
|
},
|
|
112
114
|
})
|
|
115
|
+
_process = child
|
|
113
116
|
|
|
114
|
-
|
|
117
|
+
child.on('error', (err) => {
|
|
115
118
|
console.error('[query-builder] Failed to start:', err.message)
|
|
116
119
|
_process = null
|
|
117
120
|
resolvePromise()
|
|
118
121
|
})
|
|
119
122
|
|
|
120
|
-
|
|
123
|
+
child.on('exit', (code) => {
|
|
121
124
|
const wasStopping = _stopping
|
|
122
125
|
_stopping = false
|
|
123
126
|
_process = null
|
|
@@ -160,4 +163,4 @@ export function stopQueryBuilder(): void {
|
|
|
160
163
|
_process.kill()
|
|
161
164
|
}
|
|
162
165
|
`
|
|
163
|
-
}
|
|
166
|
+
}
|
|
@@ -117,10 +117,15 @@ export function generateRouteConfigType(
|
|
|
117
117
|
` after?: ${afterRef}[]\n` +
|
|
118
118
|
` }>`
|
|
119
119
|
|
|
120
|
+
// Only consulted when `requireDefaultVariantOptIn` is on; inert otherwise.
|
|
121
|
+
// Emitted so a TypeScript consumer who opts in can confirm a `default`
|
|
122
|
+
// variant without a cast — 1.64.2 demanded it and never added it to the type.
|
|
123
|
+
const allowDefault = ` allowDefaultVariant?: boolean`
|
|
124
|
+
|
|
120
125
|
return (
|
|
121
126
|
` ${routerOp}?: (${commonConfig} & (\n` +
|
|
122
127
|
` | { shape?: ${m}${c}ShapeInput<TCtx>; variants?: never }\n` +
|
|
123
|
-
` | { shape?: never; variants: ${variantsConfig} }\n` +
|
|
128
|
+
` | { shape?: never; variants: ${variantsConfig}\n${allowDefault} }\n` +
|
|
124
129
|
` )) | false`
|
|
125
130
|
)
|
|
126
131
|
}).join('\n')
|