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.
Files changed (106) hide show
  1. package/README.md +51 -3480
  2. package/dist/client/encodeQueryParams.js +3 -1
  3. package/dist/client/encodeQueryParams.js.map +1 -1
  4. package/dist/copy/misc.d.ts +1 -0
  5. package/dist/copy/misc.js +14 -0
  6. package/dist/copy/misc.js.map +1 -1
  7. package/dist/copy/operationDefinitions.js +69 -6
  8. package/dist/copy/operationDefinitions.js.map +1 -1
  9. package/dist/generators/generateFastifyHandler.d.ts +2 -2
  10. package/dist/generators/generateFastifyHandler.js +15 -5
  11. package/dist/generators/generateFastifyHandler.js.map +1 -1
  12. package/dist/generators/generateHonoHandler.d.ts +2 -2
  13. package/dist/generators/generateHonoHandler.js +12 -5
  14. package/dist/generators/generateHonoHandler.js.map +1 -1
  15. package/dist/generators/generateImportPrismaStatement.d.ts +1 -0
  16. package/dist/generators/generateImportPrismaStatement.js +6 -0
  17. package/dist/generators/generateImportPrismaStatement.js.map +1 -1
  18. package/dist/generators/generateModelMetadata.js +20 -10
  19. package/dist/generators/generateModelMetadata.js.map +1 -1
  20. package/dist/generators/generateOperationCore.d.ts +3 -3
  21. package/dist/generators/generateOperationCore.js +51 -16
  22. package/dist/generators/generateOperationCore.js.map +1 -1
  23. package/dist/generators/generateQueryBuilderHelper.d.ts +1 -1
  24. package/dist/generators/generateQueryBuilderHelper.js +2 -4
  25. package/dist/generators/generateQueryBuilderHelper.js.map +1 -1
  26. package/dist/generators/generateRelationMeta.js +2 -2
  27. package/dist/generators/generateRelationMeta.js.map +1 -1
  28. package/dist/generators/generateRouteConfigType.d.ts +2 -2
  29. package/dist/generators/generateRouteConfigType.js +29 -11
  30. package/dist/generators/generateRouteConfigType.js.map +1 -1
  31. package/dist/generators/generateRouter.d.ts +7 -4
  32. package/dist/generators/generateRouter.js +107 -14
  33. package/dist/generators/generateRouter.js.map +1 -1
  34. package/dist/generators/generateRouterFastify.d.ts +7 -4
  35. package/dist/generators/generateRouterFastify.js +56 -28
  36. package/dist/generators/generateRouterFastify.js.map +1 -1
  37. package/dist/generators/generateRouterHono.d.ts +7 -4
  38. package/dist/generators/generateRouterHono.js +84 -53
  39. package/dist/generators/generateRouterHono.js.map +1 -1
  40. package/dist/generators/generateUnifiedDocs.d.ts +1 -1
  41. package/dist/generators/generateUnifiedDocs.js +15 -8
  42. package/dist/generators/generateUnifiedDocs.js.map +1 -1
  43. package/dist/generators/generateUnifiedHandler.d.ts +2 -2
  44. package/dist/generators/generateUnifiedHandler.js +11 -3
  45. package/dist/generators/generateUnifiedHandler.js.map +1 -1
  46. package/dist/guardOptions.js +12 -7
  47. package/dist/guardOptions.js.map +1 -1
  48. package/dist/index.d.ts +1 -1
  49. package/dist/index.js +22 -1
  50. package/dist/index.js.map +1 -1
  51. package/dist/utils/copyFiles.js +22 -6
  52. package/dist/utils/copyFiles.js.map +1 -1
  53. package/dist/utils/importExt.d.ts +1 -1
  54. package/dist/utils/pathCasing.d.ts +4 -0
  55. package/dist/utils/pathCasing.js +40 -0
  56. package/dist/utils/pathCasing.js.map +1 -0
  57. package/dist/utils/resolveImportStyle.js +13 -5
  58. package/dist/utils/resolveImportStyle.js.map +1 -1
  59. package/dist/utils/writeFileSafely.js.map +1 -1
  60. package/package.json +16 -3
  61. package/src/bin.ts +1 -1
  62. package/src/client/encodeQueryParams.ts +39 -35
  63. package/src/constants.ts +1 -1
  64. package/src/copy/autoIncludePlanner.ts +93 -27
  65. package/src/copy/autoIncludePlannerGuarded.ts +171 -48
  66. package/src/copy/autoIncludeRuntimeGuarded.ts +25 -7
  67. package/src/copy/buildModelOpenApi.ts +153 -50
  68. package/src/copy/concurrency.ts +10 -8
  69. package/src/copy/errorMapper.ts +68 -19
  70. package/src/copy/guardHelpers.ts +30 -9
  71. package/src/copy/materializedCount.ts +17 -5
  72. package/src/copy/misc.ts +88 -68
  73. package/src/copy/ndjson.ts +72 -0
  74. package/src/copy/operationDefinitions.ts +99 -21
  75. package/src/copy/operationRuntime.ts +100 -12
  76. package/src/copy/pagination.ts +25 -11
  77. package/src/copy/parseQueryParams.ts +47 -48
  78. package/src/copy/projectionDefaults.ts +6 -4
  79. package/src/copy/routeConfig.express.ts +16 -4
  80. package/src/copy/routeConfig.fastify.ts +16 -2
  81. package/src/copy/routeConfig.hono.ts +19 -9
  82. package/src/copy/routeConfig.ts +174 -39
  83. package/src/copy/scalarTypes.ts +7 -2
  84. package/src/copy/sse.ts +72 -19
  85. package/src/generators/generateFastifyHandler.ts +24 -13
  86. package/src/generators/generateHonoHandler.ts +21 -13
  87. package/src/generators/generateImportPrismaStatement.ts +93 -81
  88. package/src/generators/generateModelMetadata.ts +24 -12
  89. package/src/generators/generateOperationCore.ts +81 -33
  90. package/src/generators/generateQueryBuilderHelper.ts +20 -4
  91. package/src/generators/generateRelationMeta.ts +23 -11
  92. package/src/generators/generateRouteConfigType.ts +39 -13
  93. package/src/generators/generateRouter.ts +114 -17
  94. package/src/generators/generateRouterFastify.ts +67 -31
  95. package/src/generators/generateRouterHono.ts +108 -60
  96. package/src/generators/generateUnifiedDocs.ts +29 -8
  97. package/src/generators/generateUnifiedHandler.ts +16 -8
  98. package/src/generators/generateUnifiedScalarUI.ts +1 -1
  99. package/src/guardOptions.ts +105 -92
  100. package/src/index.ts +46 -4
  101. package/src/utils/copyFiles.ts +199 -168
  102. package/src/utils/importExt.ts +2 -2
  103. package/src/utils/pathCasing.ts +42 -0
  104. package/src/utils/resolveImportStyle.ts +37 -15
  105. package/src/utils/strings.ts +2 -2
  106. package/src/utils/writeFileSafely.ts +6 -2
@@ -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
- export type ProgressiveStageContext<TContext = unknown, TPrisma = any> = {
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<TContext = unknown, TPrisma = any, T = unknown> = (
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(config: Partial<GuardPolicy> | undefined): GuardPolicy {
325
+ export function resolveGuardPolicy(
326
+ config: Partial<GuardPolicy> | undefined,
327
+ ): GuardPolicy {
283
328
  return {
284
- requireGuardShape: config?.requireGuardShape ?? UPSTREAM_GUARD_DEFAULTS.requireGuardShape,
285
- validateGuardShapes: config?.validateGuardShapes ?? UPSTREAM_GUARD_DEFAULTS.validateGuardShapes,
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 ?? UPSTREAM_GUARD_DEFAULTS.requireDefaultVariantOptIn,
288
- enableUpdateEach: config?.enableUpdateEach ?? UPSTREAM_GUARD_DEFAULTS.enableUpdateEach,
335
+ config?.requireDefaultVariantOptIn ??
336
+ UPSTREAM_GUARD_DEFAULTS.requireDefaultVariantOptIn,
337
+ enableUpdateEach:
338
+ config?.enableUpdateEach ?? UPSTREAM_GUARD_DEFAULTS.enableUpdateEach,
289
339
  guardResolutionOrder:
290
- config?.guardResolutionOrder ?? UPSTREAM_GUARD_DEFAULTS.guardResolutionOrder,
340
+ config?.guardResolutionOrder ??
341
+ UPSTREAM_GUARD_DEFAULTS.guardResolutionOrder,
291
342
  allowE2EGuardBypass:
292
- config?.allowE2EGuardBypass ?? UPSTREAM_GUARD_DEFAULTS.allowE2EGuardBypass,
343
+ config?.allowE2EGuardBypass ??
344
+ UPSTREAM_GUARD_DEFAULTS.allowE2EGuardBypass,
293
345
  validateResolvedShapes:
294
- config?.validateResolvedShapes ?? UPSTREAM_GUARD_DEFAULTS.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 + ': shape must be an object or a function, got ' +
334
- (shape === null ? 'null' : Array.isArray(shape) ? 'array' : typeof shape),
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 + ': shape is empty, so it constrains nothing. Give it at least ' +
342
- 'one guard key, or use variants.',
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 + ': shape mixes guard keys (' + reserved.join(', ') + ') with ' +
351
- 'non-guard keys (' + stray.join(', ') + '). A shape whose keys are not all ' +
352
- 'guard keys is read as a VARIANT MAP, which is rarely intended — check for ' +
353
- 'a typo, or split them into "variants".',
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: { shape?: unknown; variants?: unknown; allowDefaultVariant?: unknown } | undefined,
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 + ': 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.',
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 + ': 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.',
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 + ': allowDefaultVariant is set but no "default" variant exists.',
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 + ': variant name "' + key +
490
- '" collides with a reserved guard shape key',
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 'the shape function returned ' +
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 'the shape function returned non-guard keys (' + stray.join(', ') + ')'
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 + ': countSource.where["' + key + '"] must be scalar or null; got ' +
697
- (Array.isArray(value) ? 'array' : 'object'),
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
+ }
@@ -1,2 +1,7 @@
1
- export const NUMERIC_SCALAR_TYPES = new Set(['Int', 'BigInt', 'Float', 'Decimal'])
2
- export const STRING_NUMERIC_TYPES = new Set(['BigInt', 'Decimal'])
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(target: Record<string, unknown>, path: string, value: unknown): boolean {
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 "' + path +
35
- '": cannot traverse non-plain-object at segment "' + part + '"',
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(req: EventEmitterLike, listener: () => void): void {
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 { res.flush() } catch {}
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(res: SseWritable, stage: string, completed: number, total: number): boolean {
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(res: SseWritable, key: string, value: unknown): boolean {
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, { type: 'nestedRelationBatch', relationPath, depth, attachments })
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(res: SseWritable, intervalMs: number = 15000): IntervalHandle {
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(res: SseWritable, keepaliveHandle: IntervalHandle | null): void {
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 { res.end() } catch {}
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(options: RunSingleResultSSEOptions): Promise<void> {
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 typeof value === 'object' && value !== null && (value as { stop?: unknown }).stop === true
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: unknown
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(options: RunProgressiveOptions): Promise<void> {
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) throw new HttpError(500, 'Missing progressive stage: ' + stageName)
270
-
271
- const result = await stage({ ctx, req, res, prisma, variant, accumulated, signal })
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
+ }