prisma-generator-express 1.67.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 (68) hide show
  1. package/README.md +223 -28
  2. package/dist/copy/operationDefinitions.d.ts +4 -1
  3. package/dist/copy/operationDefinitions.js +64 -3
  4. package/dist/copy/operationDefinitions.js.map +1 -1
  5. package/dist/generators/generateFastifyHandler.js +55 -9
  6. package/dist/generators/generateFastifyHandler.js.map +1 -1
  7. package/dist/generators/generateHonoHandler.js +44 -4
  8. package/dist/generators/generateHonoHandler.js.map +1 -1
  9. package/dist/generators/generateHonoOpenApi.d.ts +13 -0
  10. package/dist/generators/generateHonoOpenApi.js +70 -0
  11. package/dist/generators/generateHonoOpenApi.js.map +1 -0
  12. package/dist/generators/generateMcpApp.d.ts +10 -0
  13. package/dist/generators/generateMcpApp.js +113 -0
  14. package/dist/generators/generateMcpApp.js.map +1 -0
  15. package/dist/generators/generateModelMcp.d.ts +9 -0
  16. package/dist/generators/generateModelMcp.js +145 -0
  17. package/dist/generators/generateModelMcp.js.map +1 -0
  18. package/dist/generators/generateModelMetadata.js +9 -5
  19. package/dist/generators/generateModelMetadata.js.map +1 -1
  20. package/dist/generators/generateRouter.js +62 -56
  21. package/dist/generators/generateRouter.js.map +1 -1
  22. package/dist/generators/generateRouterFastify.js +56 -64
  23. package/dist/generators/generateRouterFastify.js.map +1 -1
  24. package/dist/generators/generateRouterHono.js +43 -411
  25. package/dist/generators/generateRouterHono.js.map +1 -1
  26. package/dist/generators/generateRouterPartsHono.d.ts +14 -0
  27. package/dist/generators/generateRouterPartsHono.js +180 -0
  28. package/dist/generators/generateRouterPartsHono.js.map +1 -0
  29. package/dist/generators/generateUnifiedHandler.js +36 -20
  30. package/dist/generators/generateUnifiedHandler.js.map +1 -1
  31. package/dist/index.d.ts +2 -0
  32. package/dist/index.js +101 -3
  33. package/dist/index.js.map +1 -1
  34. package/dist/utils/copyFiles.d.ts +1 -1
  35. package/dist/utils/copyFiles.js +20 -1
  36. package/dist/utils/copyFiles.js.map +1 -1
  37. package/dist/utils/mcpGate.d.ts +10 -0
  38. package/dist/utils/mcpGate.js +118 -0
  39. package/dist/utils/mcpGate.js.map +1 -0
  40. package/dist/utils/writeFileSafely.js +6 -0
  41. package/dist/utils/writeFileSafely.js.map +1 -1
  42. package/package.json +18 -3
  43. package/src/copy/buildModelOpenApi.ts +76 -126
  44. package/src/copy/docsRenderer.ts +14 -15
  45. package/src/copy/mcpRuntime.ts +1017 -0
  46. package/src/copy/operationDefinitions.ts +78 -4
  47. package/src/copy/operationPipeline.ts +267 -0
  48. package/src/copy/operationRuntime.ts +24 -14
  49. package/src/copy/operationSchemas.ts +2773 -0
  50. package/src/copy/pagination.ts +10 -2
  51. package/src/copy/routeConfig.hono.ts +34 -5
  52. package/src/copy/routeConfig.ts +1 -1
  53. package/src/copy/routerParts.ts +524 -0
  54. package/src/generators/generateFastifyHandler.ts +55 -9
  55. package/src/generators/generateHonoHandler.ts +44 -4
  56. package/src/generators/generateHonoOpenApi.ts +93 -0
  57. package/src/generators/generateMcpApp.ts +133 -0
  58. package/src/generators/generateModelMcp.ts +208 -0
  59. package/src/generators/generateModelMetadata.ts +16 -10
  60. package/src/generators/generateRouter.ts +62 -56
  61. package/src/generators/generateRouterFastify.ts +56 -64
  62. package/src/generators/generateRouterHono.ts +49 -459
  63. package/src/generators/generateRouterPartsHono.ts +220 -0
  64. package/src/generators/generateUnifiedHandler.ts +36 -21
  65. package/src/index.ts +116 -3
  66. package/src/utils/copyFiles.ts +36 -0
  67. package/src/utils/mcpGate.ts +157 -0
  68. package/src/utils/writeFileSafely.ts +6 -0
@@ -21,11 +21,25 @@ export interface OpMetadata {
21
21
  argsSchema: readonly string[]
22
22
  requiredArgs: readonly string[]
23
23
  notes: string
24
+ /**
25
+ * EXPLICIT behaviour annotations, never inferred from `kind`. Mapped to MCP
26
+ * tool hints (readOnlyHint / destructiveHint / idempotentHint) and available
27
+ * for any other transport that wants the same vocabulary. MCP semantics:
28
+ * `destructive: false` means ONLY additive updates — anything that can
29
+ * overwrite or remove existing data (update, upsert, delete families) is
30
+ * destructive.
31
+ */
32
+ readOnly: boolean
33
+ destructive: boolean
34
+ idempotent: boolean
24
35
  }
25
36
 
26
37
  export const OPERATION_METADATA: readonly OpMetadata[] = [
27
38
  {
28
39
  name: 'findMany',
40
+ readOnly: true,
41
+ destructive: false,
42
+ idempotent: true,
29
43
  method: 'get',
30
44
  pathSuffix: '',
31
45
  configKey: 'findMany',
@@ -56,6 +70,9 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
56
70
  },
57
71
  {
58
72
  name: 'findUnique',
73
+ readOnly: true,
74
+ destructive: false,
75
+ idempotent: true,
59
76
  method: 'get',
60
77
  pathSuffix: '/unique',
61
78
  configKey: 'findUnique',
@@ -76,6 +93,9 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
76
93
  },
77
94
  {
78
95
  name: 'findUniqueOrThrow',
96
+ readOnly: true,
97
+ destructive: false,
98
+ idempotent: true,
79
99
  method: 'get',
80
100
  pathSuffix: '/unique/strict',
81
101
  configKey: 'findUniqueOrThrow',
@@ -96,6 +116,9 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
96
116
  },
97
117
  {
98
118
  name: 'findFirst',
119
+ readOnly: true,
120
+ destructive: false,
121
+ idempotent: true,
99
122
  method: 'get',
100
123
  pathSuffix: '/first',
101
124
  configKey: 'findFirst',
@@ -126,6 +149,9 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
126
149
  },
127
150
  {
128
151
  name: 'findFirstOrThrow',
152
+ readOnly: true,
153
+ destructive: false,
154
+ idempotent: true,
129
155
  method: 'get',
130
156
  pathSuffix: '/first/strict',
131
157
  configKey: 'findFirstOrThrow',
@@ -156,6 +182,9 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
156
182
  },
157
183
  {
158
184
  name: 'findManyPaginated',
185
+ readOnly: true,
186
+ destructive: false,
187
+ idempotent: true,
159
188
  method: 'get',
160
189
  pathSuffix: '/paginated',
161
190
  configKey: 'findManyPaginated',
@@ -187,6 +216,9 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
187
216
  },
188
217
  {
189
218
  name: 'count',
219
+ readOnly: true,
220
+ destructive: false,
221
+ idempotent: true,
190
222
  method: 'get',
191
223
  pathSuffix: '/count',
192
224
  configKey: 'count',
@@ -208,6 +240,9 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
208
240
  },
209
241
  {
210
242
  name: 'aggregate',
243
+ readOnly: true,
244
+ destructive: false,
245
+ idempotent: true,
211
246
  method: 'get',
212
247
  pathSuffix: '/aggregate',
213
248
  configKey: 'aggregate',
@@ -240,6 +275,9 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
240
275
  },
241
276
  {
242
277
  name: 'groupBy',
278
+ readOnly: true,
279
+ destructive: false,
280
+ idempotent: true,
243
281
  method: 'get',
244
282
  pathSuffix: '/groupby',
245
283
  configKey: 'groupBy',
@@ -274,6 +312,9 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
274
312
  },
275
313
  {
276
314
  name: 'create',
315
+ readOnly: false,
316
+ destructive: false,
317
+ idempotent: false,
277
318
  method: 'post',
278
319
  pathSuffix: '',
279
320
  configKey: 'create',
@@ -294,6 +335,9 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
294
335
  },
295
336
  {
296
337
  name: 'createMany',
338
+ readOnly: false,
339
+ destructive: false,
340
+ idempotent: false,
297
341
  method: 'post',
298
342
  pathSuffix: '/many',
299
343
  configKey: 'createMany',
@@ -315,6 +359,9 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
315
359
  },
316
360
  {
317
361
  name: 'createManyAndReturn',
362
+ readOnly: false,
363
+ destructive: false,
364
+ idempotent: false,
318
365
  method: 'post',
319
366
  pathSuffix: '/many/return',
320
367
  configKey: 'createManyAndReturn',
@@ -336,6 +383,9 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
336
383
  },
337
384
  {
338
385
  name: 'update',
386
+ readOnly: false,
387
+ destructive: true,
388
+ idempotent: false,
339
389
  method: 'put',
340
390
  pathSuffix: '',
341
391
  configKey: 'update',
@@ -357,6 +407,9 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
357
407
  },
358
408
  {
359
409
  name: 'updateMany',
410
+ readOnly: false,
411
+ destructive: true,
412
+ idempotent: false,
360
413
  method: 'put',
361
414
  pathSuffix: '/many',
362
415
  configKey: 'updateMany',
@@ -378,6 +431,9 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
378
431
  },
379
432
  {
380
433
  name: 'updateManyAndReturn',
434
+ readOnly: false,
435
+ destructive: true,
436
+ idempotent: false,
381
437
  method: 'put',
382
438
  pathSuffix: '/many/return',
383
439
  configKey: 'updateManyAndReturn',
@@ -399,6 +455,9 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
399
455
  },
400
456
  {
401
457
  name: 'upsert',
458
+ readOnly: false,
459
+ destructive: true,
460
+ idempotent: true,
402
461
  method: 'patch',
403
462
  pathSuffix: '',
404
463
  configKey: 'upsert',
@@ -419,6 +478,9 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
419
478
  },
420
479
  {
421
480
  name: 'delete',
481
+ readOnly: false,
482
+ destructive: true,
483
+ idempotent: true,
422
484
  method: 'delete',
423
485
  pathSuffix: '',
424
486
  configKey: 'delete',
@@ -439,6 +501,9 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
439
501
  },
440
502
  {
441
503
  name: 'deleteMany',
504
+ readOnly: false,
505
+ destructive: true,
506
+ idempotent: true,
442
507
  method: 'delete',
443
508
  pathSuffix: '/many',
444
509
  configKey: 'deleteMany',
@@ -459,6 +524,9 @@ export const OPERATION_METADATA: readonly OpMetadata[] = [
459
524
  },
460
525
  {
461
526
  name: 'updateEach',
527
+ readOnly: false,
528
+ destructive: true,
529
+ idempotent: false,
462
530
  method: 'post',
463
531
  pathSuffix: '/each',
464
532
  configKey: 'updateEach',
@@ -517,10 +585,16 @@ export function getPostReadPathSuffix(opName: string): string {
517
585
  }
518
586
 
519
587
  export function isOperationEnabled(
520
- config: Record<string, any>,
588
+ config: object,
521
589
  def: OperationDef | OpMetadata,
522
590
  ): boolean {
523
- if (config[def.configKey] === false) return false
524
- if (def.excludeFromEnableAll) return !!config[def.configKey]
525
- return !!(config.enableAll || config[def.configKey])
591
+ // Route configs are interfaces without index signatures, so the value is
592
+ // reached through one runtime narrowing here at the single point that reads it.
593
+ if (!config || typeof config !== 'object') {
594
+ return false
595
+ }
596
+ const cfg = config as Record<string, unknown>
597
+ if (cfg[def.configKey] === false) return false
598
+ if (def.excludeFromEnableAll) return !!cfg[def.configKey]
599
+ return !!(cfg.enableAll || cfg[def.configKey])
526
600
  }
@@ -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
+ }
@@ -72,22 +72,32 @@ type SpeedExtensionFactory = (opts: {
72
72
  }) => unknown
73
73
 
74
74
  let _speedExtension: SpeedExtensionFactory | null = null
75
+ let _prismasqlReady: Promise<void> | undefined
75
76
 
76
77
  const _prismasqlModule = 'prisma-' + 'sql'
77
- const _prismasqlReady = (async () => {
78
- try {
79
- const mod = (await import(_prismasqlModule)) as {
80
- speedExtension?: SpeedExtensionFactory
81
- default?: { speedExtension?: SpeedExtensionFactory }
82
- }
83
- _speedExtension = mod.speedExtension ?? mod.default?.speedExtension ?? null
84
- } catch (err) {
85
- const code = (err as { code?: string } | null)?.code
86
- if (code !== 'MODULE_NOT_FOUND' && code !== 'ERR_MODULE_NOT_FOUND') {
87
- 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
+ }
88
98
  }
89
- }
90
- })()
99
+ })())
100
+ }
91
101
 
92
102
  const _extendedClients = new WeakMap<object, WeakMap<object, unknown>>()
93
103
 
@@ -101,7 +111,7 @@ export async function getExtendedClient(
101
111
  'PrismaClient not found on request. Set req.prisma in middleware.',
102
112
  )
103
113
  }
104
- await _prismasqlReady
114
+ await ensurePrismaSqlReady()
105
115
  if (!_speedExtension) return base
106
116
  const connector = (ctx.postgres ?? ctx.sqlite) as object | undefined
107
117
  if (!connector) return base