prisma-generator-express 1.65.2 → 1.67.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -3480
- package/dist/client/encodeQueryParams.js +3 -1
- package/dist/client/encodeQueryParams.js.map +1 -1
- package/dist/copy/misc.d.ts +1 -0
- package/dist/copy/misc.js +14 -0
- package/dist/copy/misc.js.map +1 -1
- package/dist/copy/operationDefinitions.js +69 -6
- package/dist/copy/operationDefinitions.js.map +1 -1
- package/dist/generators/generateFastifyHandler.d.ts +2 -2
- package/dist/generators/generateFastifyHandler.js +15 -5
- package/dist/generators/generateFastifyHandler.js.map +1 -1
- package/dist/generators/generateHonoHandler.d.ts +2 -2
- package/dist/generators/generateHonoHandler.js +12 -5
- package/dist/generators/generateHonoHandler.js.map +1 -1
- package/dist/generators/generateImportPrismaStatement.d.ts +1 -0
- package/dist/generators/generateImportPrismaStatement.js +6 -0
- package/dist/generators/generateImportPrismaStatement.js.map +1 -1
- package/dist/generators/generateModelMetadata.js +20 -10
- package/dist/generators/generateModelMetadata.js.map +1 -1
- package/dist/generators/generateOperationCore.d.ts +3 -3
- package/dist/generators/generateOperationCore.js +51 -16
- package/dist/generators/generateOperationCore.js.map +1 -1
- package/dist/generators/generateQueryBuilderHelper.d.ts +1 -1
- package/dist/generators/generateQueryBuilderHelper.js +2 -4
- package/dist/generators/generateQueryBuilderHelper.js.map +1 -1
- package/dist/generators/generateRelationMeta.js +2 -2
- package/dist/generators/generateRelationMeta.js.map +1 -1
- package/dist/generators/generateRouteConfigType.d.ts +2 -2
- package/dist/generators/generateRouteConfigType.js +29 -11
- package/dist/generators/generateRouteConfigType.js.map +1 -1
- package/dist/generators/generateRouter.d.ts +7 -4
- package/dist/generators/generateRouter.js +107 -14
- package/dist/generators/generateRouter.js.map +1 -1
- package/dist/generators/generateRouterFastify.d.ts +7 -4
- package/dist/generators/generateRouterFastify.js +56 -28
- package/dist/generators/generateRouterFastify.js.map +1 -1
- package/dist/generators/generateRouterHono.d.ts +7 -4
- package/dist/generators/generateRouterHono.js +84 -53
- package/dist/generators/generateRouterHono.js.map +1 -1
- package/dist/generators/generateUnifiedDocs.d.ts +1 -1
- package/dist/generators/generateUnifiedDocs.js +15 -8
- package/dist/generators/generateUnifiedDocs.js.map +1 -1
- package/dist/generators/generateUnifiedHandler.d.ts +2 -2
- package/dist/generators/generateUnifiedHandler.js +11 -3
- package/dist/generators/generateUnifiedHandler.js.map +1 -1
- package/dist/guardOptions.js +12 -7
- package/dist/guardOptions.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +22 -1
- package/dist/index.js.map +1 -1
- package/dist/utils/copyFiles.js +22 -6
- package/dist/utils/copyFiles.js.map +1 -1
- package/dist/utils/importExt.d.ts +1 -1
- package/dist/utils/pathCasing.d.ts +4 -0
- package/dist/utils/pathCasing.js +40 -0
- package/dist/utils/pathCasing.js.map +1 -0
- package/dist/utils/resolveImportStyle.js +13 -5
- package/dist/utils/resolveImportStyle.js.map +1 -1
- package/dist/utils/writeFileSafely.js.map +1 -1
- package/package.json +16 -3
- package/src/bin.ts +1 -1
- package/src/client/encodeQueryParams.ts +39 -35
- package/src/constants.ts +1 -1
- package/src/copy/autoIncludePlanner.ts +93 -27
- package/src/copy/autoIncludePlannerGuarded.ts +171 -48
- package/src/copy/autoIncludeRuntimeGuarded.ts +25 -7
- package/src/copy/buildModelOpenApi.ts +153 -50
- package/src/copy/concurrency.ts +10 -8
- package/src/copy/errorMapper.ts +68 -19
- package/src/copy/guardHelpers.ts +30 -9
- package/src/copy/materializedCount.ts +17 -5
- package/src/copy/misc.ts +88 -68
- package/src/copy/ndjson.ts +72 -0
- package/src/copy/operationDefinitions.ts +99 -21
- package/src/copy/operationRuntime.ts +100 -12
- package/src/copy/pagination.ts +25 -11
- package/src/copy/parseQueryParams.ts +47 -48
- package/src/copy/projectionDefaults.ts +6 -4
- package/src/copy/routeConfig.express.ts +16 -4
- package/src/copy/routeConfig.fastify.ts +16 -2
- package/src/copy/routeConfig.hono.ts +19 -9
- package/src/copy/routeConfig.ts +174 -39
- package/src/copy/scalarTypes.ts +7 -2
- package/src/copy/sse.ts +72 -19
- package/src/generators/generateFastifyHandler.ts +24 -13
- package/src/generators/generateHonoHandler.ts +21 -13
- package/src/generators/generateImportPrismaStatement.ts +93 -81
- package/src/generators/generateModelMetadata.ts +24 -12
- package/src/generators/generateOperationCore.ts +81 -33
- package/src/generators/generateQueryBuilderHelper.ts +20 -4
- package/src/generators/generateRelationMeta.ts +23 -11
- package/src/generators/generateRouteConfigType.ts +39 -13
- package/src/generators/generateRouter.ts +114 -17
- package/src/generators/generateRouterFastify.ts +67 -31
- package/src/generators/generateRouterHono.ts +108 -60
- package/src/generators/generateUnifiedDocs.ts +29 -8
- package/src/generators/generateUnifiedHandler.ts +16 -8
- package/src/generators/generateUnifiedScalarUI.ts +1 -1
- package/src/guardOptions.ts +105 -92
- package/src/index.ts +46 -4
- package/src/utils/copyFiles.ts +199 -168
- package/src/utils/importExt.ts +2 -2
- package/src/utils/pathCasing.ts +42 -0
- package/src/utils/resolveImportStyle.ts +37 -15
- package/src/utils/strings.ts +2 -2
- package/src/utils/writeFileSafely.ts +6 -2
package/src/copy/routeConfig.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import type { RuntimeOperationOverride } from './operationRuntime'
|
|
1
2
|
import { GUARD_SHAPE_CONFIG_KEYS } from './guardHelpers'
|
|
2
|
-
import { isPlainObject } from './misc'
|
|
3
|
+
import { getEnv, isPlainObject } from './misc'
|
|
3
4
|
import { resolveShape, type ContextResolver } from './projectionDefaults'
|
|
4
5
|
import {
|
|
5
6
|
resolveGuardVariantKey,
|
|
@@ -65,7 +66,29 @@ export type ProgressiveStageResult<T = unknown> =
|
|
|
65
66
|
| ProgressivePatch[]
|
|
66
67
|
| ProgressiveStopResult<T>
|
|
67
68
|
|
|
68
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Minimal structural view of a Prisma-compatible client as the generated
|
|
71
|
+
* runtime uses it: dynamic model delegates called with method args. Real
|
|
72
|
+
* PrismaClient and $extends results are intended to be usable here; pass
|
|
73
|
+
* your client's type explicitly for precise inference.
|
|
74
|
+
*/
|
|
75
|
+
/**
|
|
76
|
+
* DELIBERATELY LOOSE. A real `PrismaClient` — and every `$extends` result —
|
|
77
|
+
* must satisfy this, and neither is a plain map of method maps: a model
|
|
78
|
+
* delegate carries `fields` refs, and the client carries `$`-prefixed members.
|
|
79
|
+
* The f6411dc shape (`Record<string, Record<string, fn>>`) refused the actual
|
|
80
|
+
* client in every emitted `app.ts` (TS2344). Delegates are resolved at runtime
|
|
81
|
+
* by `getDelegate`, which owns the narrow contract.
|
|
82
|
+
*/
|
|
83
|
+
export type PrismaClientLike = {
|
|
84
|
+
$extends?: (extension: never) => unknown
|
|
85
|
+
$transaction?: (fn: never) => Promise<unknown>
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export type ProgressiveStageContext<
|
|
89
|
+
TContext = unknown,
|
|
90
|
+
TPrisma extends PrismaClientLike = PrismaClientLike,
|
|
91
|
+
> = {
|
|
69
92
|
ctx: TContext
|
|
70
93
|
req: unknown
|
|
71
94
|
res: unknown
|
|
@@ -75,7 +98,11 @@ export type ProgressiveStageContext<TContext = unknown, TPrisma = any> = {
|
|
|
75
98
|
signal: AbortSignal
|
|
76
99
|
}
|
|
77
100
|
|
|
78
|
-
export type ProgressiveStage<
|
|
101
|
+
export type ProgressiveStage<
|
|
102
|
+
TContext = unknown,
|
|
103
|
+
TPrisma extends PrismaClientLike = PrismaClientLike,
|
|
104
|
+
T = unknown,
|
|
105
|
+
> = (
|
|
79
106
|
context: ProgressiveStageContext<TContext, TPrisma>,
|
|
80
107
|
) => Promise<ProgressiveStageResult<T>>
|
|
81
108
|
|
|
@@ -88,6 +115,8 @@ export type ManualProgressiveVariantConfig = {
|
|
|
88
115
|
export type AutoIncludeProgressiveVariantConfig = {
|
|
89
116
|
enabled?: boolean
|
|
90
117
|
mode: 'autoInclude'
|
|
118
|
+
/** Auto-include streaming is experimental; routers refuse it unless this is set. */
|
|
119
|
+
experimental?: boolean
|
|
91
120
|
fallback?: 'singleResult' | 'error'
|
|
92
121
|
}
|
|
93
122
|
|
|
@@ -116,9 +145,17 @@ export type BaseOperationConfig<
|
|
|
116
145
|
TShape = Record<string, unknown>,
|
|
117
146
|
TAfter = TBefore,
|
|
118
147
|
> = OperationShapeConfig<TShape, TBefore, TAfter> & {
|
|
148
|
+
authorize?: TBefore
|
|
119
149
|
before?: TBefore[]
|
|
120
150
|
after?: TAfter[]
|
|
151
|
+
override?: RuntimeOperationOverride
|
|
121
152
|
pagination?: Partial<PaginationConfig>
|
|
153
|
+
/**
|
|
154
|
+
* Disables the POST read variant for this operation only. Overrides the
|
|
155
|
+
* router-level `disablePostReads` (operation value wins, matching the
|
|
156
|
+
* pagination shallow-merge order).
|
|
157
|
+
*/
|
|
158
|
+
disablePostReads?: boolean
|
|
122
159
|
/**
|
|
123
160
|
* Confirms that a variant literally named `default` is meant to answer every
|
|
124
161
|
* unrecognised, blank and missing caller.
|
|
@@ -201,15 +238,20 @@ export type NormalizedVariantHooks<TBefore, TAfter> = Readonly<
|
|
|
201
238
|
>
|
|
202
239
|
|
|
203
240
|
export interface NormalizedOperationConfig<TBefore, TAfter> {
|
|
241
|
+
authorize?: TBefore
|
|
242
|
+
override?: RuntimeOperationOverride
|
|
204
243
|
guardShape?: Record<string, unknown>
|
|
205
244
|
guardRouting: NormalizedGuardRouting
|
|
206
245
|
operationBefore: readonly TBefore[]
|
|
207
246
|
operationAfter: readonly TAfter[]
|
|
208
247
|
variantHooks: NormalizedVariantHooks<TBefore, TAfter>
|
|
209
248
|
pagination?: Readonly<Partial<PaginationConfig>>
|
|
249
|
+
disablePostReads?: boolean
|
|
210
250
|
}
|
|
211
251
|
|
|
212
252
|
type OperationConfigInput<TBefore, TAfter> = {
|
|
253
|
+
authorize?: TBefore
|
|
254
|
+
override?: RuntimeOperationOverride
|
|
213
255
|
before?: TBefore[]
|
|
214
256
|
after?: TAfter[]
|
|
215
257
|
shape?: unknown
|
|
@@ -222,6 +264,7 @@ type OperationConfigInput<TBefore, TAfter> = {
|
|
|
222
264
|
}
|
|
223
265
|
>
|
|
224
266
|
pagination?: Partial<PaginationConfig>
|
|
267
|
+
disablePostReads?: boolean
|
|
225
268
|
}
|
|
226
269
|
|
|
227
270
|
/**
|
|
@@ -279,19 +322,29 @@ export const HARDENED_GUARD_PROFILE: Readonly<GuardPolicy> = Object.freeze({
|
|
|
279
322
|
})
|
|
280
323
|
|
|
281
324
|
/** Fill in the defaults. Absent means upstream, never an opinion. */
|
|
282
|
-
export function resolveGuardPolicy(
|
|
325
|
+
export function resolveGuardPolicy(
|
|
326
|
+
config: Partial<GuardPolicy> | undefined,
|
|
327
|
+
): GuardPolicy {
|
|
283
328
|
return {
|
|
284
|
-
requireGuardShape:
|
|
285
|
-
|
|
329
|
+
requireGuardShape:
|
|
330
|
+
config?.requireGuardShape ?? UPSTREAM_GUARD_DEFAULTS.requireGuardShape,
|
|
331
|
+
validateGuardShapes:
|
|
332
|
+
config?.validateGuardShapes ??
|
|
333
|
+
UPSTREAM_GUARD_DEFAULTS.validateGuardShapes,
|
|
286
334
|
requireDefaultVariantOptIn:
|
|
287
|
-
config?.requireDefaultVariantOptIn ??
|
|
288
|
-
|
|
335
|
+
config?.requireDefaultVariantOptIn ??
|
|
336
|
+
UPSTREAM_GUARD_DEFAULTS.requireDefaultVariantOptIn,
|
|
337
|
+
enableUpdateEach:
|
|
338
|
+
config?.enableUpdateEach ?? UPSTREAM_GUARD_DEFAULTS.enableUpdateEach,
|
|
289
339
|
guardResolutionOrder:
|
|
290
|
-
config?.guardResolutionOrder ??
|
|
340
|
+
config?.guardResolutionOrder ??
|
|
341
|
+
UPSTREAM_GUARD_DEFAULTS.guardResolutionOrder,
|
|
291
342
|
allowE2EGuardBypass:
|
|
292
|
-
config?.allowE2EGuardBypass ??
|
|
343
|
+
config?.allowE2EGuardBypass ??
|
|
344
|
+
UPSTREAM_GUARD_DEFAULTS.allowE2EGuardBypass,
|
|
293
345
|
validateResolvedShapes:
|
|
294
|
-
config?.validateResolvedShapes ??
|
|
346
|
+
config?.validateResolvedShapes ??
|
|
347
|
+
UPSTREAM_GUARD_DEFAULTS.validateResolvedShapes,
|
|
295
348
|
}
|
|
296
349
|
}
|
|
297
350
|
|
|
@@ -302,8 +355,7 @@ function classifyGuardRouting(shape: unknown): NormalizedGuardRouting {
|
|
|
302
355
|
|
|
303
356
|
const keys = Object.keys(shape)
|
|
304
357
|
const isSingle =
|
|
305
|
-
keys.length === 0 ||
|
|
306
|
-
keys.every((key) => GUARD_SHAPE_CONFIG_KEYS.has(key))
|
|
358
|
+
keys.length === 0 || keys.every((key) => GUARD_SHAPE_CONFIG_KEYS.has(key))
|
|
307
359
|
|
|
308
360
|
return isSingle ? { kind: 'single' } : { kind: 'named', keys }
|
|
309
361
|
}
|
|
@@ -330,16 +382,22 @@ function validateShapeConfig(shape: unknown, location: string): void {
|
|
|
330
382
|
|
|
331
383
|
if (!isPlainObject(shape)) {
|
|
332
384
|
throw new Error(
|
|
333
|
-
location +
|
|
334
|
-
|
|
385
|
+
location +
|
|
386
|
+
': shape must be an object or a function, got ' +
|
|
387
|
+
(shape === null
|
|
388
|
+
? 'null'
|
|
389
|
+
: Array.isArray(shape)
|
|
390
|
+
? 'array'
|
|
391
|
+
: typeof shape),
|
|
335
392
|
)
|
|
336
393
|
}
|
|
337
394
|
|
|
338
395
|
const keys = Object.keys(shape)
|
|
339
396
|
if (keys.length === 0) {
|
|
340
397
|
throw new Error(
|
|
341
|
-
location +
|
|
342
|
-
|
|
398
|
+
location +
|
|
399
|
+
': shape is empty, so it constrains nothing. Give it at least ' +
|
|
400
|
+
'one guard key, or use variants.',
|
|
343
401
|
)
|
|
344
402
|
}
|
|
345
403
|
|
|
@@ -347,10 +405,15 @@ function validateShapeConfig(shape: unknown, location: string): void {
|
|
|
347
405
|
if (reserved.length > 0 && reserved.length !== keys.length) {
|
|
348
406
|
const stray = keys.filter((key) => !GUARD_SHAPE_CONFIG_KEYS.has(key))
|
|
349
407
|
throw new Error(
|
|
350
|
-
location +
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
408
|
+
location +
|
|
409
|
+
': shape mixes guard keys (' +
|
|
410
|
+
reserved.join(', ') +
|
|
411
|
+
') with ' +
|
|
412
|
+
'non-guard keys (' +
|
|
413
|
+
stray.join(', ') +
|
|
414
|
+
'). A shape whose keys are not all ' +
|
|
415
|
+
'guard keys is read as a VARIANT MAP, which is rarely intended — check for ' +
|
|
416
|
+
'a typo, or split them into "variants".',
|
|
354
417
|
)
|
|
355
418
|
}
|
|
356
419
|
}
|
|
@@ -372,7 +435,9 @@ function validateShapeConfig(shape: unknown, location: string): void {
|
|
|
372
435
|
* check exists to make.
|
|
373
436
|
*/
|
|
374
437
|
export function validateOperationConfig(
|
|
375
|
-
config:
|
|
438
|
+
config:
|
|
439
|
+
| { shape?: unknown; variants?: unknown; allowDefaultVariant?: unknown }
|
|
440
|
+
| undefined,
|
|
376
441
|
location: string,
|
|
377
442
|
policy: Partial<GuardPolicy> = {},
|
|
378
443
|
): void {
|
|
@@ -390,9 +455,10 @@ export function validateOperationConfig(
|
|
|
390
455
|
if (!config) {
|
|
391
456
|
if (requireGuardShape) {
|
|
392
457
|
throw new Error(
|
|
393
|
-
location +
|
|
394
|
-
|
|
395
|
-
|
|
458
|
+
location +
|
|
459
|
+
': no guard configured. Every generated operation must define ' +
|
|
460
|
+
'"shape" or "variants"; an unguarded operation would pass the caller\'s ' +
|
|
461
|
+
'own where/select/include straight to Prisma.',
|
|
396
462
|
)
|
|
397
463
|
}
|
|
398
464
|
return
|
|
@@ -471,14 +537,16 @@ function validateVariantMap(
|
|
|
471
537
|
if (Object.prototype.hasOwnProperty.call(variants, 'default')) {
|
|
472
538
|
if (allowDefaultVariant !== true) {
|
|
473
539
|
throw new Error(
|
|
474
|
-
location +
|
|
475
|
-
|
|
476
|
-
|
|
540
|
+
location +
|
|
541
|
+
': a variant named "default" answers every unrecognised, ' +
|
|
542
|
+
'blank and missing caller. Set allowDefaultVariant: true to confirm ' +
|
|
543
|
+
'that is intended, and make sure it is the most restrictive variant.',
|
|
477
544
|
)
|
|
478
545
|
}
|
|
479
546
|
} else if (allowDefaultVariant !== undefined) {
|
|
480
547
|
throw new Error(
|
|
481
|
-
location +
|
|
548
|
+
location +
|
|
549
|
+
': allowDefaultVariant is set but no "default" variant exists.',
|
|
482
550
|
)
|
|
483
551
|
}
|
|
484
552
|
}
|
|
@@ -486,8 +554,10 @@ function validateVariantMap(
|
|
|
486
554
|
for (const [key, rawEntry] of entries) {
|
|
487
555
|
if (GUARD_SHAPE_CONFIG_KEYS.has(key)) {
|
|
488
556
|
throw new Error(
|
|
489
|
-
location +
|
|
490
|
-
|
|
557
|
+
location +
|
|
558
|
+
': variant name "' +
|
|
559
|
+
key +
|
|
560
|
+
'" collides with a reserved guard shape key',
|
|
491
561
|
)
|
|
492
562
|
}
|
|
493
563
|
|
|
@@ -498,9 +568,7 @@ function validateVariantMap(
|
|
|
498
568
|
}
|
|
499
569
|
|
|
500
570
|
if (rawEntry.shape === undefined) {
|
|
501
|
-
throw new Error(
|
|
502
|
-
location + ': variant "' + key + '" is missing "shape"',
|
|
503
|
-
)
|
|
571
|
+
throw new Error(location + ': variant "' + key + '" is missing "shape"')
|
|
504
572
|
}
|
|
505
573
|
|
|
506
574
|
/**
|
|
@@ -535,9 +603,11 @@ export function describeResolvedGuardShape(resolved: unknown): string | null {
|
|
|
535
603
|
}
|
|
536
604
|
|
|
537
605
|
if (!isPlainObject(resolved)) {
|
|
538
|
-
return
|
|
606
|
+
return (
|
|
607
|
+
'the shape function returned ' +
|
|
539
608
|
(Array.isArray(resolved) ? 'an array' : typeof resolved) +
|
|
540
609
|
', which is not a guard shape'
|
|
610
|
+
)
|
|
541
611
|
}
|
|
542
612
|
|
|
543
613
|
const keys = Object.keys(resolved)
|
|
@@ -547,7 +617,9 @@ export function describeResolvedGuardShape(resolved: unknown): string | null {
|
|
|
547
617
|
|
|
548
618
|
const stray = keys.filter((key) => !GUARD_SHAPE_CONFIG_KEYS.has(key))
|
|
549
619
|
if (stray.length > 0) {
|
|
550
|
-
return
|
|
620
|
+
return (
|
|
621
|
+
'the shape function returned non-guard keys (' + stray.join(', ') + ')'
|
|
622
|
+
)
|
|
551
623
|
}
|
|
552
624
|
|
|
553
625
|
return null
|
|
@@ -565,6 +637,8 @@ export function validateUpdateEachConfig(
|
|
|
565
637
|
export function normalizeOperation<TBefore, TAfter>(
|
|
566
638
|
config: OperationConfigInput<TBefore, TAfter> | undefined,
|
|
567
639
|
): NormalizedOperationConfig<TBefore, TAfter> {
|
|
640
|
+
if (config?.override !== undefined && typeof config.override !== 'function')
|
|
641
|
+
throw new Error('An operation accepts exactly one override function')
|
|
568
642
|
const operationBefore = config?.before ?? []
|
|
569
643
|
const operationAfter = config?.after ?? []
|
|
570
644
|
|
|
@@ -579,6 +653,8 @@ export function normalizeOperation<TBefore, TAfter>(
|
|
|
579
653
|
kind: 'named',
|
|
580
654
|
keys: entries.map(([key]) => key),
|
|
581
655
|
},
|
|
656
|
+
override: config.override,
|
|
657
|
+
authorize: config.authorize,
|
|
582
658
|
operationBefore,
|
|
583
659
|
operationAfter,
|
|
584
660
|
variantHooks: Object.fromEntries(
|
|
@@ -591,19 +667,75 @@ export function normalizeOperation<TBefore, TAfter>(
|
|
|
591
667
|
]),
|
|
592
668
|
),
|
|
593
669
|
pagination: config.pagination,
|
|
670
|
+
disablePostReads: config.disablePostReads,
|
|
594
671
|
}
|
|
595
672
|
}
|
|
596
673
|
|
|
597
674
|
return {
|
|
598
675
|
guardShape: config?.shape as Record<string, unknown> | undefined,
|
|
599
676
|
guardRouting: classifyGuardRouting(config?.shape),
|
|
677
|
+
override: config?.override,
|
|
678
|
+
authorize: config?.authorize,
|
|
600
679
|
operationBefore,
|
|
601
680
|
operationAfter,
|
|
602
681
|
variantHooks: {},
|
|
603
682
|
pagination: config?.pagination,
|
|
683
|
+
disablePostReads: config?.disablePostReads,
|
|
604
684
|
}
|
|
605
685
|
}
|
|
606
686
|
|
|
687
|
+
const UNGUARDED_MODELS_WARNED = new Set<string>()
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Construction-time safety notice. The generator cannot see route configs —
|
|
691
|
+
* they are application code, evaluated long after `prisma generate` — so the
|
|
692
|
+
* emitted router inspects its own config at construction: when operations are
|
|
693
|
+
* enabled and none carries a guard shape or variants, every column of the
|
|
694
|
+
* table is reachable by any client.
|
|
695
|
+
*/
|
|
696
|
+
export function warnIfUnguardedRoutes(
|
|
697
|
+
modelName: string,
|
|
698
|
+
opKeys: readonly string[],
|
|
699
|
+
config: unknown,
|
|
700
|
+
isEnabled: (value: unknown) => boolean,
|
|
701
|
+
): void {
|
|
702
|
+
// Route configs are interfaces without index signatures, so dynamic key
|
|
703
|
+
// access needs one narrowing here at the single point that does it.
|
|
704
|
+
const cfg = (config ?? {}) as Record<string, unknown>
|
|
705
|
+
const env = getEnv()
|
|
706
|
+
if (env.PGE_SUPPRESS_WARNINGS === 'true' || env.NODE_ENV === 'production')
|
|
707
|
+
return
|
|
708
|
+
let anyEnabled = false
|
|
709
|
+
for (const key of opKeys) {
|
|
710
|
+
if (!isEnabled(cfg[key])) continue
|
|
711
|
+
anyEnabled = true
|
|
712
|
+
const raw = cfg[key]
|
|
713
|
+
if (raw && typeof raw === 'object') {
|
|
714
|
+
const rec = raw as { shape?: unknown; variants?: unknown }
|
|
715
|
+
if (rec.shape !== undefined || rec.variants !== undefined) return
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
if (!anyEnabled || UNGUARDED_MODELS_WARNED.has(modelName)) return
|
|
719
|
+
UNGUARDED_MODELS_WARNED.add(modelName)
|
|
720
|
+
console.warn(
|
|
721
|
+
'[prisma-generator-express] ' +
|
|
722
|
+
modelName +
|
|
723
|
+
': routes enabled without guard shapes - every column of this table is readable/writable by any client. ' +
|
|
724
|
+
'Configure operation shapes/variants, or set PGE_SUPPRESS_WARNINGS=true to silence.',
|
|
725
|
+
)
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* Effective POST-read availability for one operation.
|
|
730
|
+
* Operation-level value overrides the router-level global; absent means enabled.
|
|
731
|
+
*/
|
|
732
|
+
export function resolvePostReadsEnabled(
|
|
733
|
+
globalValue: boolean | undefined,
|
|
734
|
+
opValue: boolean | undefined,
|
|
735
|
+
): boolean {
|
|
736
|
+
return !(opValue ?? globalValue ?? false)
|
|
737
|
+
}
|
|
738
|
+
|
|
607
739
|
export function resolveOperationVariantKey(
|
|
608
740
|
routing: NormalizedGuardRouting,
|
|
609
741
|
caller: string | undefined,
|
|
@@ -693,9 +825,12 @@ export function validateCountSourceWhere(
|
|
|
693
825
|
if (value === null) continue
|
|
694
826
|
if (typeof value === 'object') {
|
|
695
827
|
throw new Error(
|
|
696
|
-
location +
|
|
697
|
-
|
|
828
|
+
location +
|
|
829
|
+
': countSource.where["' +
|
|
830
|
+
key +
|
|
831
|
+
'"] must be scalar or null; got ' +
|
|
832
|
+
(Array.isArray(value) ? 'array' : 'object'),
|
|
698
833
|
)
|
|
699
834
|
}
|
|
700
835
|
}
|
|
701
|
-
}
|
|
836
|
+
}
|
package/src/copy/scalarTypes.ts
CHANGED
|
@@ -1,2 +1,7 @@
|
|
|
1
|
-
export const NUMERIC_SCALAR_TYPES = new Set([
|
|
2
|
-
|
|
1
|
+
export const NUMERIC_SCALAR_TYPES = new Set([
|
|
2
|
+
'Int',
|
|
3
|
+
'BigInt',
|
|
4
|
+
'Float',
|
|
5
|
+
'Decimal',
|
|
6
|
+
])
|
|
7
|
+
export const STRING_NUMERIC_TYPES = new Set(['BigInt', 'Decimal'])
|
package/src/copy/sse.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
ProgressiveStage,
|
|
6
6
|
ProgressiveStopResult,
|
|
7
7
|
ProgressivePatch,
|
|
8
|
+
PrismaClientLike,
|
|
8
9
|
} from './routeConfig'
|
|
9
10
|
|
|
10
11
|
export function acceptsEventStream(accept: string | undefined): boolean {
|
|
@@ -17,7 +18,11 @@ export function acceptsEventStream(accept: string | undefined): boolean {
|
|
|
17
18
|
|
|
18
19
|
const UNSAFE_PATH_SEGMENTS = new Set(['__proto__', 'constructor', 'prototype'])
|
|
19
20
|
|
|
20
|
-
export function setByPath(
|
|
21
|
+
export function setByPath(
|
|
22
|
+
target: Record<string, unknown>,
|
|
23
|
+
path: string,
|
|
24
|
+
value: unknown,
|
|
25
|
+
): boolean {
|
|
21
26
|
const parts = path.split('.')
|
|
22
27
|
if (parts.length === 0) return false
|
|
23
28
|
for (const p of parts) {
|
|
@@ -31,8 +36,11 @@ export function setByPath(target: Record<string, unknown>, path: string, value:
|
|
|
31
36
|
if (process.env.NODE_ENV !== 'production') {
|
|
32
37
|
console.warn(
|
|
33
38
|
LOG_PREFIX,
|
|
34
|
-
'Dropping patch for "' +
|
|
35
|
-
|
|
39
|
+
'Dropping patch for "' +
|
|
40
|
+
path +
|
|
41
|
+
'": cannot traverse non-plain-object at segment "' +
|
|
42
|
+
part +
|
|
43
|
+
'"',
|
|
36
44
|
)
|
|
37
45
|
}
|
|
38
46
|
return false
|
|
@@ -61,7 +69,10 @@ export type SseWritable = {
|
|
|
61
69
|
destroyed: boolean
|
|
62
70
|
}
|
|
63
71
|
|
|
64
|
-
export function removeReqCloseListener(
|
|
72
|
+
export function removeReqCloseListener(
|
|
73
|
+
req: EventEmitterLike,
|
|
74
|
+
listener: () => void,
|
|
75
|
+
): void {
|
|
65
76
|
if (typeof req.off === 'function') {
|
|
66
77
|
req.off('close', listener)
|
|
67
78
|
} else if (typeof req.removeListener === 'function') {
|
|
@@ -75,12 +86,15 @@ export function initSSE(res: SseWritable): void {
|
|
|
75
86
|
res.setHeader('Cache-Control', 'no-cache, no-transform')
|
|
76
87
|
res.setHeader('Connection', 'keep-alive')
|
|
77
88
|
res.setHeader('X-Accel-Buffering', 'no')
|
|
89
|
+
res.setHeader('X-Stream-Protocol-Version', '1')
|
|
78
90
|
if (typeof res.flushHeaders === 'function') res.flushHeaders()
|
|
79
91
|
}
|
|
80
92
|
|
|
81
93
|
export function flushSSE(res: SseWritable): void {
|
|
82
94
|
if (typeof res.flush === 'function') {
|
|
83
|
-
try {
|
|
95
|
+
try {
|
|
96
|
+
res.flush()
|
|
97
|
+
} catch {}
|
|
84
98
|
}
|
|
85
99
|
}
|
|
86
100
|
|
|
@@ -96,11 +110,20 @@ export function sendSSE(res: SseWritable, payload: unknown): boolean {
|
|
|
96
110
|
}
|
|
97
111
|
}
|
|
98
112
|
|
|
99
|
-
export function sendSSEProgress(
|
|
113
|
+
export function sendSSEProgress(
|
|
114
|
+
res: SseWritable,
|
|
115
|
+
stage: string,
|
|
116
|
+
completed: number,
|
|
117
|
+
total: number,
|
|
118
|
+
): boolean {
|
|
100
119
|
return sendSSE(res, { type: 'progress', stage, completed, total })
|
|
101
120
|
}
|
|
102
121
|
|
|
103
|
-
export function sendSSEField(
|
|
122
|
+
export function sendSSEField(
|
|
123
|
+
res: SseWritable,
|
|
124
|
+
key: string,
|
|
125
|
+
value: unknown,
|
|
126
|
+
): boolean {
|
|
104
127
|
return sendSSE(res, { type: 'field', key, value })
|
|
105
128
|
}
|
|
106
129
|
|
|
@@ -126,7 +149,12 @@ export function sendSSENestedRelationBatch(
|
|
|
126
149
|
depth: number,
|
|
127
150
|
attachments: Array<{ locator: Array<number | string>; value: unknown }>,
|
|
128
151
|
): boolean {
|
|
129
|
-
return sendSSE(res, {
|
|
152
|
+
return sendSSE(res, {
|
|
153
|
+
type: 'nestedRelationBatch',
|
|
154
|
+
relationPath,
|
|
155
|
+
depth,
|
|
156
|
+
attachments,
|
|
157
|
+
})
|
|
130
158
|
}
|
|
131
159
|
|
|
132
160
|
export function sendSSEPageMeta(
|
|
@@ -157,7 +185,10 @@ export function safeSendError(res: SseWritable, message: string): void {
|
|
|
157
185
|
|
|
158
186
|
type IntervalHandle = ReturnType<typeof setInterval>
|
|
159
187
|
|
|
160
|
-
export function startSSEKeepalive(
|
|
188
|
+
export function startSSEKeepalive(
|
|
189
|
+
res: SseWritable,
|
|
190
|
+
intervalMs: number = 15000,
|
|
191
|
+
): IntervalHandle {
|
|
161
192
|
const handle = setInterval(() => {
|
|
162
193
|
if (res.writableEnded || res.destroyed) return
|
|
163
194
|
try {
|
|
@@ -170,12 +201,17 @@ export function startSSEKeepalive(res: SseWritable, intervalMs: number = 15000):
|
|
|
170
201
|
return handle
|
|
171
202
|
}
|
|
172
203
|
|
|
173
|
-
export function endSSE(
|
|
204
|
+
export function endSSE(
|
|
205
|
+
res: SseWritable,
|
|
206
|
+
keepaliveHandle: IntervalHandle | null,
|
|
207
|
+
): void {
|
|
174
208
|
if (keepaliveHandle) {
|
|
175
209
|
clearInterval(keepaliveHandle)
|
|
176
210
|
}
|
|
177
211
|
if (!res.writableEnded && !res.destroyed) {
|
|
178
|
-
try {
|
|
212
|
+
try {
|
|
213
|
+
res.end()
|
|
214
|
+
} catch {}
|
|
179
215
|
}
|
|
180
216
|
}
|
|
181
217
|
|
|
@@ -225,7 +261,9 @@ export interface RunSingleResultSSEOptions {
|
|
|
225
261
|
coreQueryFn: () => Promise<unknown>
|
|
226
262
|
}
|
|
227
263
|
|
|
228
|
-
export async function runSingleResultSSE(
|
|
264
|
+
export async function runSingleResultSSE(
|
|
265
|
+
options: RunSingleResultSSEOptions,
|
|
266
|
+
): Promise<void> {
|
|
229
267
|
const { req, res, coreQueryFn } = options
|
|
230
268
|
await withSSE({ res, label: 'single-result' }, async () => {
|
|
231
269
|
if (req.destroyed) return
|
|
@@ -236,20 +274,26 @@ export async function runSingleResultSSE(options: RunSingleResultSSEOptions): Pr
|
|
|
236
274
|
}
|
|
237
275
|
|
|
238
276
|
function isStopResult(value: unknown): value is ProgressiveStopResult<unknown> {
|
|
239
|
-
return
|
|
277
|
+
return (
|
|
278
|
+
typeof value === 'object' &&
|
|
279
|
+
value !== null &&
|
|
280
|
+
(value as { stop?: unknown }).stop === true
|
|
281
|
+
)
|
|
240
282
|
}
|
|
241
283
|
|
|
242
284
|
export interface RunProgressiveOptions {
|
|
243
285
|
req: EventEmitterLike
|
|
244
286
|
res: SseWritable
|
|
245
287
|
ctx: unknown
|
|
246
|
-
prisma:
|
|
288
|
+
prisma: PrismaClientLike
|
|
247
289
|
variant: string
|
|
248
290
|
stages: string[]
|
|
249
291
|
stageRegistry: Record<string, ProgressiveStage>
|
|
250
292
|
}
|
|
251
293
|
|
|
252
|
-
export async function runProgressiveEndpoint(
|
|
294
|
+
export async function runProgressiveEndpoint(
|
|
295
|
+
options: RunProgressiveOptions,
|
|
296
|
+
): Promise<void> {
|
|
253
297
|
const { req, res, ctx, prisma, variant, stages, stageRegistry } = options
|
|
254
298
|
const controller = new AbortController()
|
|
255
299
|
const onClose = () => controller.abort()
|
|
@@ -266,9 +310,18 @@ export async function runProgressiveEndpoint(options: RunProgressiveOptions): Pr
|
|
|
266
310
|
if (res.writableEnded || res.destroyed || signal.aborted) return
|
|
267
311
|
const stageName = stages[i]
|
|
268
312
|
const stage = stageRegistry[stageName]
|
|
269
|
-
if (!stage)
|
|
270
|
-
|
|
271
|
-
|
|
313
|
+
if (!stage)
|
|
314
|
+
throw new HttpError(500, 'Missing progressive stage: ' + stageName)
|
|
315
|
+
|
|
316
|
+
const result = await stage({
|
|
317
|
+
ctx,
|
|
318
|
+
req,
|
|
319
|
+
res,
|
|
320
|
+
prisma,
|
|
321
|
+
variant,
|
|
322
|
+
accumulated,
|
|
323
|
+
signal,
|
|
324
|
+
})
|
|
272
325
|
if (res.writableEnded || res.destroyed) return
|
|
273
326
|
|
|
274
327
|
if (isStopResult(result)) {
|
|
@@ -293,4 +346,4 @@ export async function runProgressiveEndpoint(options: RunProgressiveOptions): Pr
|
|
|
293
346
|
} finally {
|
|
294
347
|
removeReqCloseListener(req, onClose)
|
|
295
348
|
}
|
|
296
|
-
}
|
|
349
|
+
}
|