prisma-generator-express 1.66.0 → 1.68.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/README.md +188 -3432
  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.d.ts +4 -1
  8. package/dist/copy/operationDefinitions.js +133 -9
  9. package/dist/copy/operationDefinitions.js.map +1 -1
  10. package/dist/generators/generateFastifyHandler.d.ts +2 -2
  11. package/dist/generators/generateFastifyHandler.js +68 -12
  12. package/dist/generators/generateFastifyHandler.js.map +1 -1
  13. package/dist/generators/generateHonoHandler.d.ts +2 -2
  14. package/dist/generators/generateHonoHandler.js +53 -6
  15. package/dist/generators/generateHonoHandler.js.map +1 -1
  16. package/dist/generators/generateHonoOpenApi.d.ts +13 -0
  17. package/dist/generators/generateHonoOpenApi.js +70 -0
  18. package/dist/generators/generateHonoOpenApi.js.map +1 -0
  19. package/dist/generators/generateImportPrismaStatement.d.ts +1 -0
  20. package/dist/generators/generateImportPrismaStatement.js +6 -0
  21. package/dist/generators/generateImportPrismaStatement.js.map +1 -1
  22. package/dist/generators/generateMcpApp.d.ts +10 -0
  23. package/dist/generators/generateMcpApp.js +113 -0
  24. package/dist/generators/generateMcpApp.js.map +1 -0
  25. package/dist/generators/generateModelMcp.d.ts +9 -0
  26. package/dist/generators/generateModelMcp.js +145 -0
  27. package/dist/generators/generateModelMcp.js.map +1 -0
  28. package/dist/generators/generateModelMetadata.js +29 -15
  29. package/dist/generators/generateModelMetadata.js.map +1 -1
  30. package/dist/generators/generateOperationCore.d.ts +3 -3
  31. package/dist/generators/generateOperationCore.js +51 -16
  32. package/dist/generators/generateOperationCore.js.map +1 -1
  33. package/dist/generators/generateRelationMeta.js +2 -2
  34. package/dist/generators/generateRelationMeta.js.map +1 -1
  35. package/dist/generators/generateRouteConfigType.d.ts +2 -2
  36. package/dist/generators/generateRouteConfigType.js +29 -11
  37. package/dist/generators/generateRouteConfigType.js.map +1 -1
  38. package/dist/generators/generateRouter.d.ts +7 -4
  39. package/dist/generators/generateRouter.js +164 -65
  40. package/dist/generators/generateRouter.js.map +1 -1
  41. package/dist/generators/generateRouterFastify.d.ts +7 -4
  42. package/dist/generators/generateRouterFastify.js +106 -86
  43. package/dist/generators/generateRouterFastify.js.map +1 -1
  44. package/dist/generators/generateRouterHono.d.ts +7 -4
  45. package/dist/generators/generateRouterHono.js +59 -414
  46. package/dist/generators/generateRouterHono.js.map +1 -1
  47. package/dist/generators/generateRouterPartsHono.d.ts +14 -0
  48. package/dist/generators/generateRouterPartsHono.js +180 -0
  49. package/dist/generators/generateRouterPartsHono.js.map +1 -0
  50. package/dist/generators/generateUnifiedDocs.d.ts +1 -1
  51. package/dist/generators/generateUnifiedDocs.js +15 -8
  52. package/dist/generators/generateUnifiedDocs.js.map +1 -1
  53. package/dist/generators/generateUnifiedHandler.d.ts +2 -2
  54. package/dist/generators/generateUnifiedHandler.js +42 -18
  55. package/dist/generators/generateUnifiedHandler.js.map +1 -1
  56. package/dist/guardOptions.js +12 -7
  57. package/dist/guardOptions.js.map +1 -1
  58. package/dist/index.d.ts +3 -1
  59. package/dist/index.js +123 -4
  60. package/dist/index.js.map +1 -1
  61. package/dist/utils/copyFiles.d.ts +1 -1
  62. package/dist/utils/copyFiles.js +42 -7
  63. package/dist/utils/copyFiles.js.map +1 -1
  64. package/dist/utils/importExt.d.ts +1 -1
  65. package/dist/utils/mcpGate.d.ts +10 -0
  66. package/dist/utils/mcpGate.js +118 -0
  67. package/dist/utils/mcpGate.js.map +1 -0
  68. package/dist/utils/pathCasing.d.ts +4 -0
  69. package/dist/utils/pathCasing.js +40 -0
  70. package/dist/utils/pathCasing.js.map +1 -0
  71. package/dist/utils/resolveImportStyle.js +13 -5
  72. package/dist/utils/resolveImportStyle.js.map +1 -1
  73. package/dist/utils/writeFileSafely.js +6 -0
  74. package/dist/utils/writeFileSafely.js.map +1 -1
  75. package/package.json +22 -5
  76. package/src/bin.ts +1 -1
  77. package/src/client/encodeQueryParams.ts +39 -35
  78. package/src/constants.ts +1 -1
  79. package/src/copy/autoIncludePlanner.ts +93 -27
  80. package/src/copy/autoIncludePlannerGuarded.ts +171 -48
  81. package/src/copy/autoIncludeRuntimeGuarded.ts +25 -7
  82. package/src/copy/buildModelOpenApi.ts +229 -176
  83. package/src/copy/concurrency.ts +10 -8
  84. package/src/copy/docsRenderer.ts +14 -15
  85. package/src/copy/errorMapper.ts +68 -19
  86. package/src/copy/guardHelpers.ts +30 -9
  87. package/src/copy/materializedCount.ts +17 -5
  88. package/src/copy/mcpRuntime.ts +1017 -0
  89. package/src/copy/misc.ts +88 -68
  90. package/src/copy/ndjson.ts +72 -0
  91. package/src/copy/operationDefinitions.ts +177 -25
  92. package/src/copy/operationPipeline.ts +267 -0
  93. package/src/copy/operationRuntime.ts +124 -26
  94. package/src/copy/operationSchemas.ts +2773 -0
  95. package/src/copy/pagination.ts +35 -13
  96. package/src/copy/parseQueryParams.ts +47 -48
  97. package/src/copy/projectionDefaults.ts +6 -4
  98. package/src/copy/routeConfig.express.ts +16 -4
  99. package/src/copy/routeConfig.fastify.ts +16 -2
  100. package/src/copy/routeConfig.hono.ts +46 -14
  101. package/src/copy/routeConfig.ts +175 -40
  102. package/src/copy/routerParts.ts +524 -0
  103. package/src/copy/scalarTypes.ts +7 -2
  104. package/src/copy/sse.ts +72 -19
  105. package/src/generators/generateFastifyHandler.ts +77 -20
  106. package/src/generators/generateHonoHandler.ts +62 -14
  107. package/src/generators/generateHonoOpenApi.ts +93 -0
  108. package/src/generators/generateImportPrismaStatement.ts +93 -81
  109. package/src/generators/generateMcpApp.ts +133 -0
  110. package/src/generators/generateModelMcp.ts +208 -0
  111. package/src/generators/generateModelMetadata.ts +40 -22
  112. package/src/generators/generateOperationCore.ts +81 -33
  113. package/src/generators/generateRelationMeta.ts +23 -11
  114. package/src/generators/generateRouteConfigType.ts +39 -13
  115. package/src/generators/generateRouter.ts +171 -68
  116. package/src/generators/generateRouterFastify.ts +117 -89
  117. package/src/generators/generateRouterHono.ts +84 -464
  118. package/src/generators/generateRouterPartsHono.ts +220 -0
  119. package/src/generators/generateUnifiedDocs.ts +29 -8
  120. package/src/generators/generateUnifiedHandler.ts +47 -24
  121. package/src/generators/generateUnifiedScalarUI.ts +1 -1
  122. package/src/guardOptions.ts +105 -92
  123. package/src/index.ts +161 -6
  124. package/src/utils/copyFiles.ts +235 -168
  125. package/src/utils/importExt.ts +2 -2
  126. package/src/utils/mcpGate.ts +157 -0
  127. package/src/utils/pathCasing.ts +42 -0
  128. package/src/utils/resolveImportStyle.ts +37 -15
  129. package/src/utils/strings.ts +2 -2
  130. package/src/utils/writeFileSafely.ts +12 -2
@@ -0,0 +1,267 @@
1
+ import { HttpError, mapError } from './errorMapper'
2
+ import { formatGuardVariantResolutionError } from './guardVariantError'
3
+ import { isPlainObject } from './misc'
4
+ import {
5
+ applyDroppedGuard,
6
+ type ContextResolver,
7
+ type OpKind,
8
+ } from './projectionDefaults'
9
+ import {
10
+ resolveOperationVariantKey,
11
+ resolveGuardShapeOnce,
12
+ type NormalizedGuardRouting,
13
+ type PaginationConfig,
14
+ type FindManyPaginatedMode,
15
+ } from './routeConfig'
16
+ import type { GuardVariantResolution } from './guardVariantRouting'
17
+ import type {
18
+ OperationContext,
19
+ PrismaClientLike,
20
+ RuntimeOperationOverride,
21
+ } from './operationRuntime'
22
+
23
+ /**
24
+ * Transport-neutral orchestration shared by the Express, Fastify, Hono and MCP
25
+ * emitters. Five stages: route, prepareGuard, settle, execute, classifyError.
26
+ *
27
+ * The pipeline never reads a request, a header or an authentication. Adapters
28
+ * resolve the caller themselves and pass it in; `caller` is a routing key for
29
+ * variant selection only, never an identity.
30
+ */
31
+
32
+ /** Resolves application context at most once per request. */
33
+ export type GetContext<T = unknown> = () => Promise<T>
34
+
35
+ export function memoizeContext<T>(): GetContext<T>
36
+ export function memoizeContext<T>(resolver: () => T | undefined): GetContext<T>
37
+ export function memoizeContext(resolver?: () => unknown): GetContext {
38
+ let cached: Promise<unknown> | undefined
39
+ return () => (cached ??= Promise.resolve(resolver?.()))
40
+ }
41
+
42
+ /**
43
+ * One request-scoped argument channel. Backed by adapter request storage
44
+ * (Express `res.locals.parsedQuery` / `req.body`, the Fastify request, Hono
45
+ * context vars). Dropped-guard transformations, framework hooks, overrides and
46
+ * execution all observe the same storage through it, so guard transforms
47
+ * cannot be dropped between stages.
48
+ *
49
+ * `read()` returns the STORED value — `undefined` when nothing was stored —
50
+ * exactly like the storage it fronts; mutation of a stored object is visible
51
+ * to every later stage.
52
+ */
53
+ export type ArgsChannel = {
54
+ read(): Record<string, unknown> | undefined
55
+ write(next: Record<string, unknown>): void
56
+ }
57
+
58
+ /**
59
+ * Runtime narrowing for the adapter boundary: request bags store the client
60
+ * as `unknown` (Express `req.prisma`, the Fastify request, Hono vars), and
61
+ * this is the ONE place that narrows it to the structural client contract the
62
+ * pipeline types require. Missing client is the classic deployment fault —
63
+ * classified 500, same message the operation core raises later.
64
+ */
65
+ export function requirePrisma(value: unknown): PrismaClientLike {
66
+ if (value === null || typeof value !== 'object') {
67
+ throw new HttpError(
68
+ 500,
69
+ 'PrismaClient not found on request. Set req.prisma in middleware.',
70
+ )
71
+ }
72
+ return value as PrismaClientLike
73
+ }
74
+
75
+ /** Ensure-style accessor over a channel: materialises and stores empties. */
76
+ export function ensureArgs(channel: ArgsChannel): Record<string, unknown> {
77
+ const current = channel.read()
78
+ if (current) return current
79
+ const fresh: Record<string, unknown> = {}
80
+ channel.write(fresh)
81
+ return fresh
82
+ }
83
+
84
+ /** Minimal execution policy; `GuardPolicy` itself carries target defaults. */
85
+ export type PipelinePolicy = {
86
+ dropGuard: boolean
87
+ validateResolvedShapes: boolean
88
+ }
89
+
90
+ export type RouteStageOk = {
91
+ ok: true
92
+ variantKey: string | undefined
93
+ caller: string | undefined
94
+ }
95
+
96
+ export type RouteStageResult = RouteStageOk | { ok: false; failure: HttpError }
97
+
98
+ export type GuardStageOk = {
99
+ ok: true
100
+ guardShape: Record<string, unknown> | undefined
101
+ }
102
+
103
+ export type GuardStageResult = GuardStageOk | { ok: false; failure: HttpError }
104
+
105
+ /**
106
+ * Variant routing. Synchronous; no application context, no Prisma. A variant
107
+ * failure is stored already classified as 400, so `settleStage` rethrows the
108
+ * same classified error wherever the adapter chooses to settle it.
109
+ */
110
+ export function routeOperation(input: {
111
+ guardRouting: NormalizedGuardRouting
112
+ caller: string | undefined
113
+ }): RouteStageResult {
114
+ const resolution: GuardVariantResolution = resolveOperationVariantKey(
115
+ input.guardRouting,
116
+ input.caller,
117
+ )
118
+ if (!resolution.ok) {
119
+ return {
120
+ ok: false,
121
+ failure: new HttpError(
122
+ 400,
123
+ formatGuardVariantResolutionError(resolution),
124
+ ),
125
+ }
126
+ }
127
+ const variantKey =
128
+ input.guardRouting.kind === 'named' ? resolution.key : undefined
129
+ return {
130
+ ok: true,
131
+ variantKey,
132
+ caller: typeof input.caller === 'string' ? input.caller : undefined,
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Guard-shape preparation. Accepts only a successful route result at the type
138
+ * level. Shape handling preserves upstream behaviour: default passes the raw
139
+ * shape through unresolved; resolution happens only when the policy asks for
140
+ * it (`validateResolvedShapes`, always on for MCP) or the guard is dropped
141
+ * (local apply, written through the argument channels).
142
+ *
143
+ * Resolved-shape failures are stored classified as 500; `settleStage`
144
+ * rethrows them unchanged.
145
+ */
146
+ export async function prepareGuardOperation(
147
+ routed: RouteStageOk,
148
+ input: {
149
+ guardShape: Record<string, unknown> | undefined
150
+ opKind: OpKind
151
+ policy: PipelinePolicy
152
+ getContext: ContextResolver | undefined
153
+ args: ArgsChannel
154
+ writeArgs: ArgsChannel | undefined
155
+ },
156
+ ): Promise<GuardStageResult> {
157
+ const { guardShape } = input
158
+ if (!guardShape) return { ok: true, guardShape: undefined }
159
+
160
+ try {
161
+ if (input.policy.dropGuard) {
162
+ await applyDroppedGuard(
163
+ guardShape,
164
+ routed.variantKey,
165
+ input.getContext,
166
+ input.opKind,
167
+ {
168
+ readQuery: input.args.read(),
169
+ writeBody: input.writeArgs?.read(),
170
+ },
171
+ () => ensureArgs(input.args),
172
+ input.writeArgs
173
+ ? () => ensureArgs(input.writeArgs as ArgsChannel)
174
+ : () => {
175
+ throw new HttpError(
176
+ 500,
177
+ 'dropped guard requires a write argument channel for this operation',
178
+ )
179
+ },
180
+ )
181
+ return { ok: true, guardShape: undefined }
182
+ }
183
+
184
+ let effectiveShape: Record<string, unknown> = guardShape
185
+ if (input.policy.validateResolvedShapes) {
186
+ const resolution = await resolveGuardShapeOnce(
187
+ guardShape,
188
+ routed.variantKey,
189
+ input.getContext,
190
+ )
191
+ if (!resolution.ok) {
192
+ return {
193
+ ok: false,
194
+ failure: new HttpError(
195
+ 500,
196
+ 'guard shape could not be resolved: ' + resolution.problem,
197
+ ),
198
+ }
199
+ }
200
+ effectiveShape = isPlainObject(resolution.shape)
201
+ ? resolution.shape
202
+ : guardShape
203
+ }
204
+ return { ok: true, guardShape: effectiveShape }
205
+ } catch (error) {
206
+ return { ok: false, failure: mapError(error) }
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Rethrows the stored classified failure without reclassification. Placement
212
+ * is the adapter's behavioural choice (default order, Hono `before-hooks`,
213
+ * MCP before authorize continuation).
214
+ */
215
+ export function settleStage(
216
+ result: RouteStageResult | GuardStageResult,
217
+ ): asserts result is RouteStageOk | GuardStageOk {
218
+ if (!result.ok) throw result.failure
219
+ }
220
+
221
+ /** Shared error classification; encoding stays per transport. */
222
+ export function classifyError(error: unknown): HttpError {
223
+ return mapError(error)
224
+ }
225
+
226
+ export type ExecuteDeps<TPrisma extends PrismaClientLike = PrismaClientLike> = {
227
+ core: (ctx: OperationContext) => Promise<unknown>
228
+ args: ArgsChannel
229
+ body?: unknown
230
+ /** The caller's client; typed so `TPrisma` flows through the shared stage. */
231
+ prisma: TPrisma
232
+ postgres?: unknown
233
+ sqlite?: unknown
234
+ pagination?: PaginationConfig
235
+ override?: RuntimeOperationOverride
236
+ getContext: (() => unknown | Promise<unknown>) | undefined
237
+ findManyPaginatedMode?: FindManyPaginatedMode
238
+ }
239
+
240
+ /**
241
+ * Execution. Reads the argument channel at call time, so every write the
242
+ * dropped-guard transformation made is what reaches the operation core — the
243
+ * same core, pagination, override and transformResult path REST uses.
244
+ */
245
+ export async function executeOperation<
246
+ TPrisma extends PrismaClientLike = PrismaClientLike,
247
+ >(
248
+ routed: { variantKey?: string; caller?: string },
249
+ guard: { guardShape?: Record<string, unknown> },
250
+ deps: ExecuteDeps<TPrisma>,
251
+ ): Promise<unknown> {
252
+ const ctx: OperationContext = {
253
+ prisma: deps.prisma,
254
+ postgres: deps.postgres,
255
+ sqlite: deps.sqlite,
256
+ parsedQuery: deps.args.read() ?? {},
257
+ body: deps.body,
258
+ guardShape: guard.guardShape,
259
+ guardCaller: routed.caller,
260
+ guardVariantKey: routed.variantKey,
261
+ paginationConfig: deps.pagination,
262
+ operationOverride: deps.override,
263
+ resolveOperationContext: deps.getContext,
264
+ findManyPaginatedMode: deps.findManyPaginatedMode,
265
+ }
266
+ return deps.core(ctx)
267
+ }
@@ -1,3 +1,5 @@
1
+ import { resolveGuardShapeOnce } from './routeConfig'
2
+ import { assertGuard } from './guardHelpers'
1
3
  import { HttpError, LOG_PREFIX } from './errorMapper'
2
4
  import { sanitizeKeys, isPlainObject } from './misc'
3
5
  import type {
@@ -24,12 +26,15 @@ export type {
24
26
 
25
27
  export interface OperationContext {
26
28
  prisma: unknown
29
+ operationOverride?: RuntimeOperationOverride
30
+ resolveOperationContext?: () => unknown | Promise<unknown>
27
31
  postgres?: unknown
28
32
  sqlite?: unknown
29
33
  parsedQuery?: Record<string, unknown>
30
34
  body?: unknown
31
35
  guardShape?: Record<string, unknown>
32
36
  guardCaller?: string
37
+ guardVariantKey?: string
33
38
  paginationConfig?: PaginationConfig
34
39
  findManyPaginatedMode?: FindManyPaginatedMode
35
40
  }
@@ -60,34 +65,53 @@ export type PrismaClientLike = {
60
65
  $transaction?: <T>(fn: (tx: PrismaClientLike) => Promise<T>) => Promise<T>
61
66
  }
62
67
 
63
- type SpeedExtensionFactory = (opts: { postgres?: unknown; sqlite?: unknown; debug?: boolean }) => unknown
68
+ type SpeedExtensionFactory = (opts: {
69
+ postgres?: unknown
70
+ sqlite?: unknown
71
+ debug?: boolean
72
+ }) => unknown
64
73
 
65
74
  let _speedExtension: SpeedExtensionFactory | null = null
75
+ let _prismasqlReady: Promise<void> | undefined
66
76
 
67
77
  const _prismasqlModule = 'prisma-' + 'sql'
68
- const _prismasqlReady = (async () => {
69
- try {
70
- const mod = (await import(_prismasqlModule)) as {
71
- speedExtension?: SpeedExtensionFactory
72
- default?: { speedExtension?: SpeedExtensionFactory }
73
- }
74
- _speedExtension = mod.speedExtension ?? mod.default?.speedExtension ?? null
75
- } catch (err) {
76
- const code = (err as { code?: string } | null)?.code
77
- if (code !== 'MODULE_NOT_FOUND' && code !== 'ERR_MODULE_NOT_FOUND') {
78
- console.warn(LOG_PREFIX, 'prisma-sql initialization failed:', err)
78
+
79
+ /**
80
+ * Loaded on FIRST USE, not at module load: the copied runtime stays free of
81
+ * top-level side effects, so a bundler can evaluate the import graph without
82
+ * triggering the optional `prisma-sql` lookup.
83
+ */
84
+ function ensurePrismaSqlReady(): Promise<void> {
85
+ return (_prismasqlReady ??= (async () => {
86
+ try {
87
+ const mod = (await import(_prismasqlModule)) as {
88
+ speedExtension?: SpeedExtensionFactory
89
+ default?: { speedExtension?: SpeedExtensionFactory }
90
+ }
91
+ _speedExtension =
92
+ mod.speedExtension ?? mod.default?.speedExtension ?? null
93
+ } catch (err) {
94
+ const code = (err as { code?: string } | null)?.code
95
+ if (code !== 'MODULE_NOT_FOUND' && code !== 'ERR_MODULE_NOT_FOUND') {
96
+ console.warn(LOG_PREFIX, 'prisma-sql initialization failed:', err)
97
+ }
79
98
  }
80
- }
81
- })()
99
+ })())
100
+ }
82
101
 
83
102
  const _extendedClients = new WeakMap<object, WeakMap<object, unknown>>()
84
103
 
85
- export async function getExtendedClient(ctx: OperationContext): Promise<unknown> {
104
+ export async function getExtendedClient(
105
+ ctx: OperationContext,
106
+ ): Promise<unknown> {
86
107
  const base = ctx.prisma as PrismaClientLike | null | undefined
87
108
  if (!base) {
88
- throw new HttpError(500, 'PrismaClient not found on request. Set req.prisma in middleware.')
109
+ throw new HttpError(
110
+ 500,
111
+ 'PrismaClient not found on request. Set req.prisma in middleware.',
112
+ )
89
113
  }
90
- await _prismasqlReady
114
+ await ensurePrismaSqlReady()
91
115
  if (!_speedExtension) return base
92
116
  const connector = (ctx.postgres ?? ctx.sqlite) as object | undefined
93
117
  if (!connector) return base
@@ -98,11 +122,13 @@ export async function getExtendedClient(ctx: OperationContext): Promise<unknown>
98
122
  }
99
123
  try {
100
124
  if (typeof base.$extends !== 'function') return base
101
- const extended = base.$extends(_speedExtension({
102
- postgres: ctx.postgres,
103
- sqlite: ctx.sqlite,
104
- debug: process.env.DEBUG === 'true',
105
- }))
125
+ const extended = base.$extends(
126
+ _speedExtension({
127
+ postgres: ctx.postgres,
128
+ sqlite: ctx.sqlite,
129
+ debug: process.env.DEBUG === 'true',
130
+ }),
131
+ )
106
132
  let map = _extendedClients.get(connector)
107
133
  if (!map) {
108
134
  map = new WeakMap<object, unknown>()
@@ -111,7 +137,10 @@ export async function getExtendedClient(ctx: OperationContext): Promise<unknown>
111
137
  map.set(base as unknown as object, extended)
112
138
  return extended
113
139
  } catch (error) {
114
- console.warn('[speedExtension] Failed to initialize, using base client:', error)
140
+ console.warn(
141
+ '[speedExtension] Failed to initialize, using base client:',
142
+ error,
143
+ )
115
144
  return base
116
145
  }
117
146
  }
@@ -134,7 +163,10 @@ export function validateBody(body: unknown): Record<string, unknown> {
134
163
  return sanitizeKeys(body as Record<string, unknown>)
135
164
  }
136
165
 
137
- export function requireBodyField(body: Record<string, unknown>, field: string): void {
166
+ export function requireBodyField(
167
+ body: Record<string, unknown>,
168
+ field: string,
169
+ ): void {
138
170
  if (!(field in body) || body[field] === undefined) {
139
171
  throw new HttpError(400, 'Missing required field: ' + field)
140
172
  }
@@ -143,7 +175,8 @@ export function requireBodyField(body: Record<string, unknown>, field: string):
143
175
  export function transformResult(value: unknown): unknown {
144
176
  if (value === null || value === undefined) return value
145
177
  if (typeof value === 'bigint') return value.toString()
146
- if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) return value.toString('base64')
178
+ if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value))
179
+ return value.toString('base64')
147
180
  if (typeof Buffer !== 'undefined' && value instanceof Uint8Array) {
148
181
  return Buffer.from(value).toString('base64')
149
182
  }
@@ -169,4 +202,69 @@ export function transformResult(value: unknown): unknown {
169
202
  return changed ? out : value
170
203
  }
171
204
  return value
172
- }
205
+ }
206
+
207
+ export const OPERATION_OVERRIDE_VERSION = 1
208
+
209
+ export type OperationOverride<TInput, TResult, TContext, TPrisma> = {
210
+ invoke(options: {
211
+ input: TInput
212
+ args: TInput
213
+ context: TContext
214
+ prisma: TPrisma
215
+ core: () => Promise<TResult>
216
+ }): TResult | Promise<TResult>
217
+ }['invoke']
218
+
219
+ export type RuntimeOperationOverride = OperationOverride<
220
+ Record<string, unknown>,
221
+ unknown,
222
+ unknown,
223
+ Readonly<
224
+ Record<
225
+ string,
226
+ Readonly<Record<string, (...args: never[]) => Promise<unknown>>>
227
+ >
228
+ >
229
+ >
230
+
231
+ export async function executeOperationOverride<TResult>(
232
+ ctx: OperationContext,
233
+ model: string,
234
+ operation: keyof Omit<PrismaDelegate, 'guard'> | 'findManyPaginated',
235
+ input: Record<string, unknown>,
236
+ core: () => Promise<TResult>,
237
+ ): Promise<TResult> {
238
+ if (!ctx.operationOverride) return core()
239
+ if (!ctx.guardShape)
240
+ throw new HttpError(500, 'An operation override requires a guard shape')
241
+ const context = await ctx.resolveOperationContext?.()
242
+ const resolved = await resolveGuardShapeOnce(
243
+ ctx.guardShape,
244
+ ctx.guardVariantKey ?? ctx.guardCaller,
245
+ () => context,
246
+ )
247
+ if (!resolved.ok || !isPlainObject(resolved.shape)) {
248
+ throw new HttpError(
249
+ 500,
250
+ 'An operation override requires a resolved guard shape',
251
+ )
252
+ }
253
+ ctx.guardShape = resolved.shape
254
+ const delegate = getDelegate(await getExtendedClient(ctx), model)
255
+ assertGuard(delegate)
256
+ const guarded = delegate.guard(resolved.shape, ctx.guardCaller)
257
+ const methods =
258
+ operation === 'findManyPaginated'
259
+ ? (['findMany', 'count'] as const)
260
+ : [operation]
261
+ const exposed: Record<string, (...args: never[]) => Promise<unknown>> = {}
262
+ for (const method of methods) exposed[method] = guarded[method].bind(guarded)
263
+ return (await ctx.operationOverride({
264
+ input,
265
+ args: input,
266
+ context,
267
+ prisma: Object.freeze({ [model]: Object.freeze(exposed) }),
268
+ core,
269
+ })) as TResult
270
+ }