@prisma-next/sql-runtime 0.5.0-dev.6 → 0.5.0-dev.60

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 (46) hide show
  1. package/README.md +31 -22
  2. package/dist/exports-BcX9wp4z.mjs +1640 -0
  3. package/dist/exports-BcX9wp4z.mjs.map +1 -0
  4. package/dist/{index-yb51L_1h.d.mts → index-DkthtnOX.d.mts} +100 -25
  5. package/dist/index-DkthtnOX.d.mts.map +1 -0
  6. package/dist/index.d.mts +2 -2
  7. package/dist/index.mjs +2 -2
  8. package/dist/test/utils.d.mts +6 -5
  9. package/dist/test/utils.d.mts.map +1 -1
  10. package/dist/test/utils.mjs +13 -6
  11. package/dist/test/utils.mjs.map +1 -1
  12. package/package.json +13 -14
  13. package/src/codecs/decoding.ts +294 -173
  14. package/src/codecs/encoding.ts +162 -37
  15. package/src/codecs/validation.ts +22 -3
  16. package/src/content-hash.ts +44 -0
  17. package/src/exports/index.ts +12 -7
  18. package/src/fingerprint.ts +22 -0
  19. package/src/guardrails/raw.ts +165 -0
  20. package/src/lower-sql-plan.ts +3 -3
  21. package/src/marker.ts +75 -0
  22. package/src/middleware/before-compile-chain.ts +1 -0
  23. package/src/middleware/budgets.ts +26 -96
  24. package/src/middleware/lints.ts +3 -3
  25. package/src/middleware/sql-middleware.ts +6 -5
  26. package/src/runtime-spi.ts +44 -0
  27. package/src/sql-context.ts +438 -79
  28. package/src/sql-family-adapter.ts +3 -2
  29. package/src/sql-marker.ts +62 -47
  30. package/src/sql-runtime.ts +336 -113
  31. package/dist/exports-BQZSVXXt.mjs +0 -981
  32. package/dist/exports-BQZSVXXt.mjs.map +0 -1
  33. package/dist/index-yb51L_1h.d.mts.map +0 -1
  34. package/test/async-iterable-result.test.ts +0 -141
  35. package/test/before-compile-chain.test.ts +0 -223
  36. package/test/budgets.test.ts +0 -431
  37. package/test/context.types.test-d.ts +0 -68
  38. package/test/execution-stack.test.ts +0 -161
  39. package/test/json-schema-validation.test.ts +0 -571
  40. package/test/lints.test.ts +0 -160
  41. package/test/mutation-default-generators.test.ts +0 -254
  42. package/test/parameterized-types.test.ts +0 -529
  43. package/test/sql-context.test.ts +0 -384
  44. package/test/sql-family-adapter.test.ts +0 -103
  45. package/test/sql-runtime.test.ts +0 -792
  46. package/test/utils.ts +0 -297
@@ -0,0 +1,1640 @@
1
+ import { AsyncIterableResult, RuntimeCore, checkAborted, checkMiddlewareCompatibility, isRuntimeError, raceAgainstAbort, runWithMiddleware, runtimeError } from "@prisma-next/framework-components/runtime";
2
+ import { type } from "arktype";
3
+ import { collectOrderedParamRefs, createCodecRegistry, isQueryAst } from "@prisma-next/sql-relational-core/ast";
4
+ import { ifDefined } from "@prisma-next/utils/defined";
5
+ import { synthesizeNonParameterizedDescriptor } from "@prisma-next/framework-components/codec";
6
+ import { checkContractComponentRequirements } from "@prisma-next/framework-components/components";
7
+ import { createExecutionStack } from "@prisma-next/framework-components/execution";
8
+ import { createSqlOperationRegistry } from "@prisma-next/sql-operations";
9
+ import { canonicalStringify } from "@prisma-next/utils/canonical-stringify";
10
+ import { hashContent } from "@prisma-next/utils/hash-content";
11
+ import { createHash } from "node:crypto";
12
+
13
+ //#region src/codecs/validation.ts
14
+ function extractCodecIds(contract) {
15
+ const codecIds = /* @__PURE__ */ new Set();
16
+ for (const table of Object.values(contract.storage.tables)) for (const column of Object.values(table.columns)) {
17
+ const codecId = column.codecId;
18
+ codecIds.add(codecId);
19
+ }
20
+ return codecIds;
21
+ }
22
+ function extractCodecIdsFromColumns(contract) {
23
+ const codecIds = /* @__PURE__ */ new Map();
24
+ for (const [tableName, table] of Object.entries(contract.storage.tables)) for (const [columnName, column] of Object.entries(table.columns)) {
25
+ const codecId = column.codecId;
26
+ const key = `${tableName}.${columnName}`;
27
+ codecIds.set(key, codecId);
28
+ }
29
+ return codecIds;
30
+ }
31
+ function adaptDescriptorRegistry(registry) {
32
+ return { has: (id) => registry.descriptorFor(id) !== void 0 };
33
+ }
34
+ function isDescriptorRegistry(registry) {
35
+ return "descriptorFor" in registry;
36
+ }
37
+ function validateContractCodecMappings(registry, contract) {
38
+ const lookup = isDescriptorRegistry(registry) ? adaptDescriptorRegistry(registry) : registry;
39
+ const codecIds = extractCodecIdsFromColumns(contract);
40
+ const invalidCodecs = [];
41
+ for (const [key, codecId] of codecIds.entries()) if (!lookup.has(codecId)) {
42
+ const parts = key.split(".");
43
+ const table = parts[0] ?? "";
44
+ const column = parts[1] ?? "";
45
+ invalidCodecs.push({
46
+ table,
47
+ column,
48
+ codecId
49
+ });
50
+ }
51
+ if (invalidCodecs.length > 0) {
52
+ const details = {
53
+ contractTarget: contract.target,
54
+ invalidCodecs
55
+ };
56
+ throw runtimeError("RUNTIME.CODEC_MISSING", `Missing codec implementations for column codecIds: ${invalidCodecs.map((c) => `${c.table}.${c.column} (${c.codecId})`).join(", ")}`, details);
57
+ }
58
+ }
59
+ function validateCodecRegistryCompleteness(registry, contract) {
60
+ validateContractCodecMappings(registry, contract);
61
+ }
62
+
63
+ //#endregion
64
+ //#region src/lower-sql-plan.ts
65
+ /**
66
+ * Lowers a SQL query plan to an executable Plan by calling the adapter's lower method.
67
+ *
68
+ * @param adapter - Adapter to lower AST to SQL
69
+ * @param contract - Contract for lowering context
70
+ * @param queryPlan - SQL query plan from a lane (contains AST, params, meta, but no SQL)
71
+ * @returns Fully executable Plan with SQL string
72
+ */
73
+ function lowerSqlPlan(adapter, contract, queryPlan) {
74
+ const lowered = adapter.lower(queryPlan.ast, {
75
+ contract,
76
+ params: queryPlan.params
77
+ });
78
+ return Object.freeze({
79
+ sql: lowered.sql,
80
+ params: lowered.params ?? queryPlan.params,
81
+ ast: queryPlan.ast,
82
+ meta: queryPlan.meta
83
+ });
84
+ }
85
+
86
+ //#endregion
87
+ //#region src/marker.ts
88
+ const MetaSchema = type({ "[string]": "unknown" });
89
+ function parseMeta(meta) {
90
+ if (meta === null || meta === void 0) return {};
91
+ let parsed;
92
+ if (typeof meta === "string") try {
93
+ parsed = JSON.parse(meta);
94
+ } catch {
95
+ return {};
96
+ }
97
+ else parsed = meta;
98
+ const result = MetaSchema(parsed);
99
+ if (result instanceof type.errors) return {};
100
+ return result;
101
+ }
102
+ const ContractMarkerRowSchema = type({
103
+ core_hash: "string",
104
+ profile_hash: "string",
105
+ "contract_json?": "unknown | null",
106
+ "canonical_version?": "number | null",
107
+ "updated_at?": "Date | string",
108
+ "app_tag?": "string | null",
109
+ "meta?": "unknown | null",
110
+ invariants: type("string").array()
111
+ });
112
+ function parseContractMarkerRow(row) {
113
+ const result = ContractMarkerRowSchema(row);
114
+ if (result instanceof type.errors) {
115
+ const messages = result.map((p) => p.message).join("; ");
116
+ throw new Error(`Invalid contract marker row: ${messages}`);
117
+ }
118
+ const updatedAt = result.updated_at ? result.updated_at instanceof Date ? result.updated_at : new Date(result.updated_at) : /* @__PURE__ */ new Date();
119
+ return {
120
+ storageHash: result.core_hash,
121
+ profileHash: result.profile_hash,
122
+ contractJson: result.contract_json ?? null,
123
+ canonicalVersion: result.canonical_version ?? null,
124
+ updatedAt,
125
+ appTag: result.app_tag ?? null,
126
+ meta: parseMeta(result.meta),
127
+ invariants: result.invariants
128
+ };
129
+ }
130
+
131
+ //#endregion
132
+ //#region src/middleware/budgets.ts
133
+ function hasAggregateWithoutGroupBy(ast) {
134
+ if (ast.groupBy !== void 0) return false;
135
+ return ast.projection.some((item) => item.expr.kind === "aggregate");
136
+ }
137
+ function primaryTableFromAst(ast) {
138
+ switch (ast.from.kind) {
139
+ case "table-source": return ast.from.name;
140
+ case "derived-table-source": return ast.from.alias;
141
+ default: return;
142
+ }
143
+ }
144
+ function estimateRowsFromAst(ast, tableRows, defaultTableRows, hasAggregateWithoutGroup) {
145
+ if (hasAggregateWithoutGroup) return 1;
146
+ const table = primaryTableFromAst(ast);
147
+ if (!table) return null;
148
+ const tableEstimate = tableRows[table] ?? defaultTableRows;
149
+ if (ast.limit !== void 0) return Math.min(ast.limit, tableEstimate);
150
+ return tableEstimate;
151
+ }
152
+ function emitBudgetViolation(error, shouldBlock, ctx) {
153
+ if (shouldBlock) throw error;
154
+ ctx.log.warn({
155
+ code: error.code,
156
+ message: error.message,
157
+ details: error.details
158
+ });
159
+ }
160
+ function budgets(options) {
161
+ const maxRows = options?.maxRows ?? 1e4;
162
+ const defaultTableRows = options?.defaultTableRows ?? 1e4;
163
+ const tableRows = options?.tableRows ?? {};
164
+ const maxLatencyMs = options?.maxLatencyMs ?? 1e3;
165
+ const rowSeverity = options?.severities?.rowCount ?? "error";
166
+ const observedRowsByPlan = /* @__PURE__ */ new WeakMap();
167
+ return Object.freeze({
168
+ name: "budgets",
169
+ familyId: "sql",
170
+ async beforeExecute(plan, ctx) {
171
+ observedRowsByPlan.set(plan, { count: 0 });
172
+ if (isQueryAst(plan.ast) && plan.ast.kind === "select") return evaluateSelectAst(plan.ast, ctx);
173
+ },
174
+ async onRow(_row, plan, _ctx) {
175
+ const state = observedRowsByPlan.get(plan);
176
+ if (!state) return;
177
+ state.count += 1;
178
+ if (state.count > maxRows) throw runtimeError("BUDGET.ROWS_EXCEEDED", "Observed row count exceeds budget", {
179
+ source: "observed",
180
+ observedRows: state.count,
181
+ maxRows
182
+ });
183
+ },
184
+ async afterExecute(_plan, result, ctx) {
185
+ const latencyMs = result.latencyMs;
186
+ if (latencyMs > maxLatencyMs) {
187
+ const shouldBlock = ctx.mode === "strict";
188
+ emitBudgetViolation(runtimeError("BUDGET.TIME_EXCEEDED", "Query latency exceeds budget", {
189
+ latencyMs,
190
+ maxLatencyMs
191
+ }), shouldBlock, ctx);
192
+ }
193
+ }
194
+ });
195
+ function evaluateSelectAst(ast, ctx) {
196
+ const hasAggNoGroup = hasAggregateWithoutGroupBy(ast);
197
+ const estimated = estimateRowsFromAst(ast, tableRows, defaultTableRows, hasAggNoGroup);
198
+ const isUnbounded = ast.limit === void 0 && !hasAggNoGroup;
199
+ const shouldBlock = rowSeverity === "error" || ctx.mode === "strict";
200
+ if (isUnbounded) {
201
+ if (estimated !== null && estimated >= maxRows) {
202
+ emitBudgetViolation(runtimeError("BUDGET.ROWS_EXCEEDED", "Unbounded SELECT query exceeds budget", {
203
+ source: "ast",
204
+ estimatedRows: estimated,
205
+ maxRows
206
+ }), shouldBlock, ctx);
207
+ return;
208
+ }
209
+ emitBudgetViolation(runtimeError("BUDGET.ROWS_EXCEEDED", "Unbounded SELECT query exceeds budget", {
210
+ source: "ast",
211
+ maxRows
212
+ }), shouldBlock, ctx);
213
+ return;
214
+ }
215
+ if (estimated !== null && estimated > maxRows) emitBudgetViolation(runtimeError("BUDGET.ROWS_EXCEEDED", "Estimated row count exceeds budget", {
216
+ source: "ast",
217
+ estimatedRows: estimated,
218
+ maxRows
219
+ }), shouldBlock, ctx);
220
+ }
221
+ }
222
+
223
+ //#endregion
224
+ //#region src/guardrails/raw.ts
225
+ const SELECT_STAR_REGEX = /select\s+\*/i;
226
+ const LIMIT_REGEX = /\blimit\b/i;
227
+ const MUTATION_PREFIX_REGEX = /^(insert|update|delete|create|alter|drop|truncate)\b/i;
228
+ const READ_ONLY_INTENTS = new Set([
229
+ "read",
230
+ "report",
231
+ "readonly"
232
+ ]);
233
+ function evaluateRawGuardrails(plan, config) {
234
+ const lints$1 = [];
235
+ const budgets$1 = [];
236
+ const normalized = normalizeWhitespace(plan.sql);
237
+ const statementType = classifyStatement(normalized);
238
+ if (statementType === "select") {
239
+ if (SELECT_STAR_REGEX.test(normalized)) lints$1.push(createLint("LINT.SELECT_STAR", "error", "Raw SQL plan selects all columns via *", { sql: snippet(plan.sql) }));
240
+ if (!LIMIT_REGEX.test(normalized)) {
241
+ const severity = config?.budgets?.unboundedSelectSeverity ?? "error";
242
+ lints$1.push(createLint("LINT.NO_LIMIT", "warn", "Raw SQL plan omits LIMIT clause", { sql: snippet(plan.sql) }));
243
+ budgets$1.push(createBudget("BUDGET.ROWS_EXCEEDED", severity, "Raw SQL plan is unbounded and may exceed row budget", {
244
+ sql: snippet(plan.sql),
245
+ ...config?.budgets?.estimatedRows !== void 0 ? { estimatedRows: config.budgets.estimatedRows } : {}
246
+ }));
247
+ }
248
+ }
249
+ if (isMutationStatement(statementType) && isReadOnlyIntent(plan.meta)) lints$1.push(createLint("LINT.READ_ONLY_MUTATION", "error", "Raw SQL plan mutates data despite read-only intent", {
250
+ sql: snippet(plan.sql),
251
+ intent: plan.meta.annotations?.["intent"]
252
+ }));
253
+ return {
254
+ lints: lints$1,
255
+ budgets: budgets$1,
256
+ statement: statementType
257
+ };
258
+ }
259
+ function classifyStatement(sql) {
260
+ const trimmed = sql.trim();
261
+ const lower = trimmed.toLowerCase();
262
+ if (lower.startsWith("with")) {
263
+ if (lower.includes("select")) return "select";
264
+ }
265
+ if (lower.startsWith("select")) return "select";
266
+ if (MUTATION_PREFIX_REGEX.test(trimmed)) return "mutation";
267
+ return "other";
268
+ }
269
+ function isMutationStatement(statement) {
270
+ return statement === "mutation";
271
+ }
272
+ function isReadOnlyIntent(meta) {
273
+ const annotations = meta.annotations;
274
+ const intent = typeof annotations?.intent === "string" ? annotations.intent.toLowerCase() : void 0;
275
+ return intent !== void 0 && READ_ONLY_INTENTS.has(intent);
276
+ }
277
+ function normalizeWhitespace(value) {
278
+ return value.replace(/\s+/g, " ").trim();
279
+ }
280
+ function snippet(sql) {
281
+ return normalizeWhitespace(sql).slice(0, 200);
282
+ }
283
+ function createLint(code, severity, message, details) {
284
+ return {
285
+ code,
286
+ severity,
287
+ message,
288
+ ...details ? { details } : {}
289
+ };
290
+ }
291
+ function createBudget(code, severity, message, details) {
292
+ return {
293
+ code,
294
+ severity,
295
+ message,
296
+ ...details ? { details } : {}
297
+ };
298
+ }
299
+
300
+ //#endregion
301
+ //#region src/middleware/lints.ts
302
+ function getFromSourceTableDetail(source) {
303
+ switch (source.kind) {
304
+ case "table-source": return source.name;
305
+ case "derived-table-source": return source.alias;
306
+ default: throw new Error(`Unsupported source kind: ${source.kind}`);
307
+ }
308
+ }
309
+ function evaluateAstLints(ast) {
310
+ const findings = [];
311
+ switch (ast.kind) {
312
+ case "delete":
313
+ if (ast.where === void 0) findings.push({
314
+ code: "LINT.DELETE_WITHOUT_WHERE",
315
+ severity: "error",
316
+ message: "DELETE without WHERE clause blocks execution to prevent accidental full-table deletion",
317
+ details: { table: ast.table.name }
318
+ });
319
+ break;
320
+ case "update":
321
+ if (ast.where === void 0) findings.push({
322
+ code: "LINT.UPDATE_WITHOUT_WHERE",
323
+ severity: "error",
324
+ message: "UPDATE without WHERE clause blocks execution to prevent accidental full-table update",
325
+ details: { table: ast.table.name }
326
+ });
327
+ break;
328
+ case "select":
329
+ if (ast.limit === void 0) {
330
+ const table = getFromSourceTableDetail(ast.from);
331
+ findings.push({
332
+ code: "LINT.NO_LIMIT",
333
+ severity: "warn",
334
+ message: "Unbounded SELECT may return large result sets",
335
+ ...ifDefined("details", table !== void 0 ? { table } : void 0)
336
+ });
337
+ }
338
+ if (ast.selectAllIntent !== void 0) {
339
+ const table = ast.selectAllIntent.table;
340
+ findings.push({
341
+ code: "LINT.SELECT_STAR",
342
+ severity: "warn",
343
+ message: "Query selects all columns via selectAll intent",
344
+ ...ifDefined("details", table !== void 0 ? { table } : void 0)
345
+ });
346
+ }
347
+ break;
348
+ case "insert": break;
349
+ default: throw new Error(`Unsupported AST kind: ${ast.kind}`);
350
+ }
351
+ return findings;
352
+ }
353
+ function getConfiguredSeverity(code, options) {
354
+ const severities = options?.severities;
355
+ if (!severities) return void 0;
356
+ switch (code) {
357
+ case "LINT.SELECT_STAR": return severities.selectStar;
358
+ case "LINT.NO_LIMIT": return severities.noLimit;
359
+ case "LINT.DELETE_WITHOUT_WHERE": return severities.deleteWithoutWhere;
360
+ case "LINT.UPDATE_WITHOUT_WHERE": return severities.updateWithoutWhere;
361
+ case "LINT.READ_ONLY_MUTATION": return severities.readOnlyMutation;
362
+ case "LINT.UNINDEXED_PREDICATE": return severities.unindexedPredicate;
363
+ default: return;
364
+ }
365
+ }
366
+ /**
367
+ * AST-first lint middleware for SQL plans. When `plan.ast` is a SQL QueryAst, inspects
368
+ * the AST structurally. When `plan.ast` is missing, falls back to raw heuristic
369
+ * guardrails or skips linting depending on `fallbackWhenAstMissing`.
370
+ *
371
+ * Rules (AST-based):
372
+ * - DELETE without WHERE: blocks execution (configurable severity, default error)
373
+ * - UPDATE without WHERE: blocks execution (configurable severity, default error)
374
+ * - Unbounded SELECT: warn/error (severity from noLimit)
375
+ * - SELECT * intent: warn/error (severity from selectStar)
376
+ *
377
+ * Fallback: When ast is missing, `fallbackWhenAstMissing: 'raw'` uses heuristic
378
+ * SQL parsing; `'skip'` skips all lints. Default is `'raw'`.
379
+ */
380
+ function lints(options) {
381
+ const fallback = options?.fallbackWhenAstMissing ?? "raw";
382
+ return Object.freeze({
383
+ name: "lints",
384
+ familyId: "sql",
385
+ async beforeExecute(plan, ctx) {
386
+ if (isQueryAst(plan.ast)) {
387
+ const findings = evaluateAstLints(plan.ast);
388
+ for (const lint of findings) {
389
+ const effectiveSeverity = getConfiguredSeverity(lint.code, options) ?? lint.severity;
390
+ if (effectiveSeverity === "error") throw runtimeError(lint.code, lint.message, lint.details);
391
+ if (effectiveSeverity === "warn") ctx.log.warn({
392
+ code: lint.code,
393
+ message: lint.message,
394
+ details: lint.details
395
+ });
396
+ }
397
+ return;
398
+ }
399
+ if (fallback === "skip") return;
400
+ const evaluation = evaluateRawGuardrails(plan);
401
+ for (const lint of evaluation.lints) {
402
+ const effectiveSeverity = getConfiguredSeverity(lint.code, options) ?? lint.severity;
403
+ if (effectiveSeverity === "error") throw runtimeError(lint.code, lint.message, lint.details);
404
+ if (effectiveSeverity === "warn") ctx.log.warn({
405
+ code: lint.code,
406
+ message: lint.message,
407
+ details: lint.details
408
+ });
409
+ }
410
+ }
411
+ });
412
+ }
413
+
414
+ //#endregion
415
+ //#region src/sql-context.ts
416
+ function createSqlExecutionStack(options) {
417
+ return createExecutionStack({
418
+ target: options.target,
419
+ adapter: options.adapter,
420
+ driver: options.driver,
421
+ extensionPacks: options.extensionPacks
422
+ });
423
+ }
424
+ function assertExecutionStackContractRequirements(contract, stack) {
425
+ const providedComponentIds = new Set([
426
+ stack.target.id,
427
+ stack.adapter.id,
428
+ ...stack.extensionPacks.map((pack) => pack.id)
429
+ ]);
430
+ const result = checkContractComponentRequirements({
431
+ contract,
432
+ expectedTargetFamily: "sql",
433
+ expectedTargetId: stack.target.targetId,
434
+ providedComponentIds
435
+ });
436
+ if (result.familyMismatch) throw runtimeError("RUNTIME.CONTRACT_FAMILY_MISMATCH", `Contract target family '${result.familyMismatch.actual}' does not match runtime family '${result.familyMismatch.expected}'.`, {
437
+ actual: result.familyMismatch.actual,
438
+ expected: result.familyMismatch.expected
439
+ });
440
+ if (result.targetMismatch) throw runtimeError("RUNTIME.CONTRACT_TARGET_MISMATCH", `Contract target '${result.targetMismatch.actual}' does not match runtime target descriptor '${result.targetMismatch.expected}'.`, {
441
+ actual: result.targetMismatch.actual,
442
+ expected: result.targetMismatch.expected
443
+ });
444
+ if (result.missingExtensionPackIds.length > 0) {
445
+ const packIds = result.missingExtensionPackIds;
446
+ throw runtimeError("RUNTIME.MISSING_EXTENSION_PACK", `Contract requires extension pack(s) ${packIds.map((id) => `'${id}'`).join(", ")}, but runtime descriptors do not provide matching component(s).`, { packIds });
447
+ }
448
+ }
449
+ function validateTypeParams(typeParams, codecDescriptor, context) {
450
+ const result = codecDescriptor.paramsSchema["~standard"].validate(typeParams);
451
+ if (result instanceof Promise) throw runtimeError("RUNTIME.TYPE_PARAMS_INVALID", `paramsSchema for codec '${codecDescriptor.codecId}' returned a Promise; runtime validation requires a synchronous Standard Schema validator.`, {
452
+ ...context,
453
+ codecId: codecDescriptor.codecId,
454
+ typeParams
455
+ });
456
+ if (result.issues) {
457
+ const messages = result.issues.map((issue) => issue.message).join("; ");
458
+ throw runtimeError("RUNTIME.TYPE_PARAMS_INVALID", `Invalid typeParams for ${context.typeName ? `type '${context.typeName}'` : `column '${context.tableName}.${context.columnName}'`} (codecId: ${codecDescriptor.codecId}): ${messages}`, {
459
+ ...context,
460
+ codecId: codecDescriptor.codecId,
461
+ typeParams
462
+ });
463
+ }
464
+ return result.value;
465
+ }
466
+ function collectParameterizedCodecDescriptors(contributors) {
467
+ const descriptors = /* @__PURE__ */ new Map();
468
+ for (const contributor of contributors) for (const descriptor of contributor.parameterizedCodecs()) {
469
+ if (descriptors.has(descriptor.codecId)) throw runtimeError("RUNTIME.DUPLICATE_PARAMETERIZED_CODEC", `Duplicate parameterized codec descriptor for codecId '${descriptor.codecId}'.`, { codecId: descriptor.codecId });
470
+ descriptors.set(descriptor.codecId, descriptor);
471
+ }
472
+ return descriptors;
473
+ }
474
+ /**
475
+ * Build the unified descriptor map. Combines parameterized descriptors
476
+ * (which already ship as `CodecDescriptor`s) with synthesized descriptors
477
+ * for non-parameterized codecs registered through the legacy `codecs:`
478
+ * slot. Codec ids that ship a parameterized descriptor take precedence —
479
+ * even when the legacy registry registers a representative codec under
480
+ * the same id, the parameterized descriptor is the authoritative source.
481
+ *
482
+ * Codec-registry-unification spec § Decision: every codec resolves
483
+ * through one descriptor map; reads are non-branching.
484
+ */
485
+ function buildCodecDescriptorRegistry(codecRegistry, parameterizedDescriptors) {
486
+ const byId = /* @__PURE__ */ new Map();
487
+ const byTargetType = /* @__PURE__ */ new Map();
488
+ function registerInIndices(descriptor) {
489
+ byId.set(descriptor.codecId, descriptor);
490
+ for (const targetType of descriptor.targetTypes) {
491
+ const list = byTargetType.get(targetType);
492
+ if (list) list.push(descriptor);
493
+ else byTargetType.set(targetType, [descriptor]);
494
+ }
495
+ }
496
+ for (const descriptor of parameterizedDescriptors.values()) registerInIndices(descriptor);
497
+ for (const codec$1 of codecRegistry.values()) {
498
+ if (byId.has(codec$1.id)) continue;
499
+ registerInIndices(synthesizeNonParameterizedDescriptor(codec$1));
500
+ }
501
+ return {
502
+ descriptorFor(codecId) {
503
+ return byId.get(codecId);
504
+ },
505
+ *values() {
506
+ yield* byId.values();
507
+ },
508
+ byTargetType(targetType) {
509
+ return byTargetType.get(targetType) ?? Object.freeze([]);
510
+ }
511
+ };
512
+ }
513
+ function collectTypeRefSites(storage) {
514
+ const sites = /* @__PURE__ */ new Map();
515
+ for (const [tableName, table] of Object.entries(storage.tables)) for (const [columnName, column] of Object.entries(table.columns)) {
516
+ if (typeof column.typeRef !== "string") continue;
517
+ const list = sites.get(column.typeRef);
518
+ const entry = {
519
+ table: tableName,
520
+ column: columnName
521
+ };
522
+ if (list) list.push(entry);
523
+ else sites.set(column.typeRef, [entry]);
524
+ }
525
+ return sites;
526
+ }
527
+ function initializeTypeHelpers(storage, codecDescriptors) {
528
+ const helpers = {};
529
+ const storageTypes = storage.types;
530
+ if (!storageTypes) return helpers;
531
+ const typeRefSites = collectTypeRefSites(storage);
532
+ for (const [typeName, typeInstance] of Object.entries(storageTypes)) {
533
+ const descriptor = codecDescriptors.get(typeInstance.codecId);
534
+ if (!descriptor) {
535
+ helpers[typeName] = typeInstance;
536
+ continue;
537
+ }
538
+ const validatedParams = validateTypeParams(typeInstance.typeParams, descriptor, { typeName });
539
+ const ctx = {
540
+ name: typeName,
541
+ usedAt: typeRefSites.get(typeName) ?? []
542
+ };
543
+ helpers[typeName] = descriptor.factory(validatedParams)(ctx);
544
+ }
545
+ return helpers;
546
+ }
547
+ function validateColumnTypeParams(storage, codecDescriptors) {
548
+ for (const [tableName, table] of Object.entries(storage.tables)) for (const [columnName, column] of Object.entries(table.columns)) if (column.typeParams) {
549
+ const descriptor = codecDescriptors.get(column.codecId);
550
+ if (descriptor) validateTypeParams(column.typeParams, descriptor, {
551
+ tableName,
552
+ columnName
553
+ });
554
+ }
555
+ }
556
+ function hasJsonValidatorTrait(candidate) {
557
+ if (candidate === null || typeof candidate !== "object") return false;
558
+ const traits = candidate.traits;
559
+ if (!Array.isArray(traits)) return false;
560
+ if (!traits.includes("json-validator")) return false;
561
+ return typeof candidate.validate === "function";
562
+ }
563
+ function extractValidator(candidate) {
564
+ return hasJsonValidatorTrait(candidate) ? candidate.validate : void 0;
565
+ }
566
+ function isResolvedCodec(candidate) {
567
+ return candidate !== null && typeof candidate === "object" && "id" in candidate && "decode" in candidate;
568
+ }
569
+ /**
570
+ * Walk the contract's `storage.tables[].columns[]` and resolve each
571
+ * column to a `Codec` through the unified descriptor map. Per-instance
572
+ * behavior:
573
+ *
574
+ * - **typeRef columns**: reuse the resolved codec materialized once by
575
+ * `initializeTypeHelpers` for the `storage.types` entry. Multiple
576
+ * columns sharing one typeRef share one codec instance.
577
+ * - **inline-typeParams columns**: call `descriptor.factory(typeParams)
578
+ * (ctx)` once per column (per-column anonymous instance).
579
+ * - **non-parameterized columns**: call `descriptor.factory()(ctx)`
580
+ * once. The synthesized descriptor's factory is constant — every call
581
+ * returns the same shared codec instance — so columns sharing a non-
582
+ * parameterized codec id share one resolved codec without explicit
583
+ * caching.
584
+ *
585
+ * Combines what `initializeTypeHelpers` (named-instance walk) and the
586
+ * old `buildJsonSchemaValidatorRegistry` (per-column walk) used to do
587
+ * separately: one walk over all columns, one resolved codec per column,
588
+ * one trait-gated validator extraction per column. The result drives
589
+ * both the dispatch registry (`ContractCodecRegistry.forColumn`) and the
590
+ * validator registry.
591
+ *
592
+ * Codec-registry-unification spec § AC-4: every column resolves through
593
+ * one descriptor map without branching on parameterization.
594
+ */
595
+ function buildContractCodecRegistry(contract, codecDescriptors, legacyCodecRegistry, types, parameterizedDescriptors) {
596
+ const byColumn = /* @__PURE__ */ new Map();
597
+ const byCodecId = /* @__PURE__ */ new Map();
598
+ const ambiguousCodecIds = /* @__PURE__ */ new Set();
599
+ const validators = /* @__PURE__ */ new Map();
600
+ for (const [tableName, table] of Object.entries(contract.storage.tables)) for (const [columnName, column] of Object.entries(table.columns)) {
601
+ const columnKey = `${tableName}.${columnName}`;
602
+ const descriptor = codecDescriptors.descriptorFor(column.codecId);
603
+ let resolvedCodec;
604
+ if (descriptor) {
605
+ const isParameterized = parameterizedDescriptors.has(column.codecId);
606
+ if (column.typeRef) {
607
+ const helper = types[column.typeRef];
608
+ if (isResolvedCodec(helper)) resolvedCodec = helper;
609
+ } else if (column.typeParams && isParameterized) {
610
+ const parameterizedDescriptor = parameterizedDescriptors.get(column.codecId);
611
+ if (parameterizedDescriptor) {
612
+ const validatedParams = validateTypeParams(column.typeParams, parameterizedDescriptor, {
613
+ tableName,
614
+ columnName
615
+ });
616
+ const ctx = {
617
+ name: `<anon:${tableName}.${columnName}>`,
618
+ usedAt: [{
619
+ table: tableName,
620
+ column: columnName
621
+ }]
622
+ };
623
+ resolvedCodec = parameterizedDescriptor.factory(validatedParams)(ctx);
624
+ }
625
+ } else if (!isParameterized) {
626
+ let cached = byCodecId.get(column.codecId);
627
+ if (!cached) {
628
+ const ctx = {
629
+ name: `<shared:${column.codecId}>`,
630
+ usedAt: [{
631
+ table: tableName,
632
+ column: columnName
633
+ }]
634
+ };
635
+ const voidFactory = descriptor.factory;
636
+ cached = voidFactory(void 0)(ctx);
637
+ byCodecId.set(column.codecId, cached);
638
+ }
639
+ resolvedCodec = cached;
640
+ }
641
+ }
642
+ if (resolvedCodec) {
643
+ byColumn.set(columnKey, resolvedCodec);
644
+ const validate = extractValidator(resolvedCodec);
645
+ if (validate) validators.set(columnKey, validate);
646
+ const existing = byCodecId.get(column.codecId);
647
+ if (existing === void 0) byCodecId.set(column.codecId, resolvedCodec);
648
+ else if (existing !== resolvedCodec && parameterizedDescriptors.has(column.codecId)) ambiguousCodecIds.add(column.codecId);
649
+ }
650
+ }
651
+ return {
652
+ registry: {
653
+ forColumn(table, column) {
654
+ return byColumn.get(`${table}.${column}`);
655
+ },
656
+ forCodecId(codecId) {
657
+ if (ambiguousCodecIds.has(codecId)) throw runtimeError("RUNTIME.TYPE_PARAMS_INVALID", `Codec '${codecId}' resolves to multiple parameterized instances; column-aware dispatch is required.`, { codecId });
658
+ return byCodecId.get(codecId) ?? legacyCodecRegistry.get(codecId);
659
+ }
660
+ },
661
+ jsonValidators: validators.size > 0 ? {
662
+ get: (key) => validators.get(key),
663
+ size: validators.size
664
+ } : void 0
665
+ };
666
+ }
667
+ function assertMutationDefaultGeneratorsAvailable(contract, generatorRegistry) {
668
+ const defaults = contract.execution?.mutations.defaults ?? [];
669
+ if (defaults.length === 0) return;
670
+ const missing = /* @__PURE__ */ new Set();
671
+ for (const mutationDefault of defaults) for (const phase of [mutationDefault.onCreate, mutationDefault.onUpdate]) {
672
+ if (!phase) continue;
673
+ if (phase.kind === "generator" && !generatorRegistry.has(phase.id)) missing.add(phase.id);
674
+ }
675
+ if (missing.size === 0) return;
676
+ const ids = Array.from(missing);
677
+ throw runtimeError("RUNTIME.MISSING_MUTATION_DEFAULT_GENERATOR", `Contract requires mutation default generator(s) ${ids.map((id) => `'${id}'`).join(", ")}, but no runtime component provides them.`, { ids });
678
+ }
679
+ function collectMutationDefaultGenerators(contributors) {
680
+ const generators = /* @__PURE__ */ new Map();
681
+ const owners = /* @__PURE__ */ new Map();
682
+ for (const contributor of contributors) {
683
+ const nextGenerators = contributor.mutationDefaultGenerators?.() ?? [];
684
+ for (const generator of nextGenerators) {
685
+ const existingOwner = owners.get(generator.id);
686
+ if (existingOwner !== void 0) throw runtimeError("RUNTIME.DUPLICATE_MUTATION_DEFAULT_GENERATOR", `Duplicate mutation default generator '${generator.id}'.`, {
687
+ id: generator.id,
688
+ existingOwner,
689
+ incomingOwner: contributor.id
690
+ });
691
+ generators.set(generator.id, generator);
692
+ owners.set(generator.id, contributor.id);
693
+ }
694
+ }
695
+ return generators;
696
+ }
697
+ function computeExecutionDefaultValue(spec, generatorRegistry) {
698
+ switch (spec.kind) {
699
+ case "generator": {
700
+ const generator = generatorRegistry.get(spec.id);
701
+ if (!generator) throw runtimeError("RUNTIME.MUTATION_DEFAULT_GENERATOR_MISSING", `Contract references mutation default generator '${spec.id}' but no runtime component provides it.`, { id: spec.id });
702
+ return generator.generate(spec.params);
703
+ }
704
+ }
705
+ }
706
+ function applyMutationDefaults(contract, generatorRegistry, options) {
707
+ const defaults = contract.execution?.mutations.defaults ?? [];
708
+ if (defaults.length === 0) return [];
709
+ const isEmptyUpdate = options.op === "update" && Object.keys(options.values).length === 0;
710
+ const applied = [];
711
+ const appliedColumns = /* @__PURE__ */ new Set();
712
+ const rowCache = /* @__PURE__ */ new Map();
713
+ for (const mutationDefault of defaults) {
714
+ if (mutationDefault.ref.table !== options.table) continue;
715
+ const defaultSpec = options.op === "create" ? mutationDefault.onCreate : mutationDefault.onUpdate;
716
+ if (!defaultSpec) continue;
717
+ if (isEmptyUpdate) continue;
718
+ const columnName = mutationDefault.ref.column;
719
+ if (Object.hasOwn(options.values, columnName) || appliedColumns.has(columnName)) continue;
720
+ applied.push({
721
+ column: columnName,
722
+ value: resolveScopedValue(defaultSpec, generatorRegistry, rowCache, options.defaultValueCache)
723
+ });
724
+ appliedColumns.add(columnName);
725
+ }
726
+ return applied;
727
+ }
728
+ function resolveScopedValue(spec, generatorRegistry, rowCache, queryCache) {
729
+ if (spec.kind !== "generator") return computeExecutionDefaultValue(spec, generatorRegistry);
730
+ const cache = scopedCache(generatorRegistry.get(spec.id)?.stability, rowCache, queryCache);
731
+ if (!cache) return computeExecutionDefaultValue(spec, generatorRegistry);
732
+ if (cache.has(spec.id)) return cache.get(spec.id);
733
+ const value = computeExecutionDefaultValue(spec, generatorRegistry);
734
+ cache.set(spec.id, value);
735
+ return value;
736
+ }
737
+ function scopedCache(stability, rowCache, queryCache) {
738
+ switch (stability) {
739
+ case "row": return rowCache;
740
+ case "query": return queryCache;
741
+ default: return;
742
+ }
743
+ }
744
+ function createExecutionContext(options) {
745
+ const { contract, stack } = options;
746
+ assertExecutionStackContractRequirements(contract, stack);
747
+ const codecRegistry = createCodecRegistry();
748
+ const contributors = [
749
+ stack.target,
750
+ stack.adapter,
751
+ ...stack.extensionPacks
752
+ ];
753
+ for (const contributor of contributors) for (const c of contributor.codecs().values()) codecRegistry.register(c);
754
+ const queryOperationRegistry = createSqlOperationRegistry();
755
+ for (const contributor of contributors) for (const op of contributor.queryOperations?.() ?? []) queryOperationRegistry.register(op);
756
+ const parameterizedCodecDescriptors = collectParameterizedCodecDescriptors(contributors);
757
+ const codecDescriptors = buildCodecDescriptorRegistry(codecRegistry, parameterizedCodecDescriptors);
758
+ const mutationDefaultGeneratorRegistry = collectMutationDefaultGenerators(contributors);
759
+ assertMutationDefaultGeneratorsAvailable(contract, mutationDefaultGeneratorRegistry);
760
+ if (parameterizedCodecDescriptors.size > 0) validateColumnTypeParams(contract.storage, parameterizedCodecDescriptors);
761
+ const types = initializeTypeHelpers(contract.storage, parameterizedCodecDescriptors);
762
+ const { registry: contractCodecs, jsonValidators: jsonSchemaValidators } = buildContractCodecRegistry(contract, codecDescriptors, codecRegistry, types, parameterizedCodecDescriptors);
763
+ return {
764
+ contract,
765
+ codecs: codecRegistry,
766
+ contractCodecs,
767
+ codecDescriptors,
768
+ queryOperations: queryOperationRegistry,
769
+ types,
770
+ ...jsonSchemaValidators ? { jsonSchemaValidators } : {},
771
+ applyMutationDefaults: (options$1) => applyMutationDefaults(contract, mutationDefaultGeneratorRegistry, options$1)
772
+ };
773
+ }
774
+
775
+ //#endregion
776
+ //#region src/sql-marker.ts
777
+ const ensureSchemaStatement = {
778
+ sql: "create schema if not exists prisma_contract",
779
+ params: []
780
+ };
781
+ const ensureTableStatement = {
782
+ sql: `create table if not exists prisma_contract.marker (
783
+ id smallint primary key default 1,
784
+ core_hash text not null,
785
+ profile_hash text not null,
786
+ contract_json jsonb,
787
+ canonical_version int,
788
+ updated_at timestamptz not null default now(),
789
+ app_tag text,
790
+ meta jsonb not null default '{}',
791
+ invariants text[] not null default '{}'
792
+ )`,
793
+ params: []
794
+ };
795
+ function readContractMarker() {
796
+ return {
797
+ sql: `select
798
+ core_hash,
799
+ profile_hash,
800
+ contract_json,
801
+ canonical_version,
802
+ updated_at,
803
+ app_tag,
804
+ meta,
805
+ invariants
806
+ from prisma_contract.marker
807
+ where id = $1`,
808
+ params: [1]
809
+ };
810
+ }
811
+ /**
812
+ * Variable columns that participate in INSERT/UPDATE alongside the
813
+ * always-on `id = $1` and `updated_at = now()`. Each column declares
814
+ * its name, optional cast type, and parameter value; the placeholder
815
+ * (`$N`) is computed positionally below — adding or reordering a
816
+ * column doesn't desync indices. `invariants` only appears when the
817
+ * caller supplies it — see `WriteMarkerInput.invariants`.
818
+ */
819
+ function markerColumns(input) {
820
+ return [
821
+ {
822
+ name: "core_hash",
823
+ param: input.storageHash
824
+ },
825
+ {
826
+ name: "profile_hash",
827
+ param: input.profileHash
828
+ },
829
+ {
830
+ name: "contract_json",
831
+ type: "jsonb",
832
+ param: input.contractJson ?? null
833
+ },
834
+ {
835
+ name: "canonical_version",
836
+ param: input.canonicalVersion ?? null
837
+ },
838
+ {
839
+ name: "app_tag",
840
+ param: input.appTag ?? null
841
+ },
842
+ {
843
+ name: "meta",
844
+ type: "jsonb",
845
+ param: JSON.stringify(input.meta ?? {})
846
+ },
847
+ ...input.invariants !== void 0 ? [{
848
+ name: "invariants",
849
+ type: "text[]",
850
+ param: input.invariants
851
+ }] : []
852
+ ];
853
+ }
854
+ function writeContractMarker(input) {
855
+ const placed = markerColumns(input).map((c, i) => ({
856
+ name: c.name,
857
+ expr: c.type ? `$${i + 2}::${c.type}` : `$${i + 2}`,
858
+ param: c.param
859
+ }));
860
+ const params = [1, ...placed.map((c) => c.param)];
861
+ const insertColumns = [
862
+ "id",
863
+ ...placed.map((c) => c.name),
864
+ "updated_at"
865
+ ].join(", ");
866
+ const insertValues = [
867
+ "$1",
868
+ ...placed.map((c) => c.expr),
869
+ "now()"
870
+ ].join(", ");
871
+ const setClauses = [...placed.map((c) => `${c.name} = ${c.expr}`), "updated_at = now()"].join(", ");
872
+ return {
873
+ insert: {
874
+ sql: `insert into prisma_contract.marker (${insertColumns}) values (${insertValues})`,
875
+ params
876
+ },
877
+ update: {
878
+ sql: `update prisma_contract.marker set ${setClauses} where id = $1`,
879
+ params
880
+ }
881
+ };
882
+ }
883
+
884
+ //#endregion
885
+ //#region src/codecs/json-schema-validation.ts
886
+ /**
887
+ * Validates a JSON value against its column's JSON Schema, if a validator exists.
888
+ *
889
+ * Throws `RUNTIME.JSON_SCHEMA_VALIDATION_FAILED` on validation failure.
890
+ * No-ops if no validator is registered for the column.
891
+ */
892
+ function validateJsonValue(registry, table, column, value, direction, codecId) {
893
+ const key = `${table}.${column}`;
894
+ const validate = registry.get(key);
895
+ if (!validate) return;
896
+ const result = validate(value);
897
+ if (result.valid) return;
898
+ throw createJsonSchemaValidationError(table, column, direction, result.errors, codecId);
899
+ }
900
+ function createJsonSchemaValidationError(table, column, direction, errors, codecId) {
901
+ return runtimeError("RUNTIME.JSON_SCHEMA_VALIDATION_FAILED", `JSON schema validation failed for column '${table}.${column}' (${direction}): ${formatErrorSummary(errors)}`, {
902
+ table,
903
+ column,
904
+ codecId,
905
+ direction,
906
+ errors: [...errors]
907
+ });
908
+ }
909
+ function formatErrorSummary(errors) {
910
+ if (errors.length === 0) return "unknown validation error";
911
+ if (errors.length === 1) {
912
+ const err = errors[0];
913
+ return err.path === "/" ? err.message : `${err.path}: ${err.message}`;
914
+ }
915
+ return errors.map((err) => err.path === "/" ? err.message : `${err.path}: ${err.message}`).join("; ");
916
+ }
917
+
918
+ //#endregion
919
+ //#region src/codecs/decoding.ts
920
+ const WIRE_PREVIEW_LIMIT = 100;
921
+ const EMPTY_INCLUDE_ALIASES = /* @__PURE__ */ new Set();
922
+ function isAstBackedPlan(plan) {
923
+ return plan.ast !== void 0;
924
+ }
925
+ function projectionListFromAst(ast) {
926
+ if (ast.kind === "select") return ast.projection;
927
+ return ast.returning;
928
+ }
929
+ /**
930
+ * Resolve the per-cell codec for a projection item.
931
+ *
932
+ * Phase B: when a `(table, column)` ref is available for the projection,
933
+ * prefer `contractCodecs.forColumn(table, column)` — that's the per-
934
+ * instance resolved codec materialized from the codec descriptor's
935
+ * factory at context-construction time (carries any per-instance state
936
+ * such as the compiled JSON-Schema validator). When the projection
937
+ * resolves to a non-`column-ref` expression (computed projections, raw
938
+ * SQL aliases) but still carries a codec id (ADR 205 stamps every
939
+ * `ProjectionItem` with the producer's codec id), fall back to the
940
+ * codec-id-keyed `forCodecId(codecId)` lookup, which itself falls back
941
+ * to the legacy `CodecRegistry` for codec ids the contract walk
942
+ * couldn't resolve.
943
+ *
944
+ * Codec-registry-unification spec § AC-4.
945
+ */
946
+ function resolveProjectionCodec(item, registry, contractCodecs) {
947
+ if (item.expr.kind === "column-ref" && contractCodecs) {
948
+ const byColumn = contractCodecs.forColumn(item.expr.table, item.expr.column);
949
+ if (byColumn) return byColumn;
950
+ }
951
+ if (item.codecId) {
952
+ const fromContract = contractCodecs?.forCodecId(item.codecId);
953
+ if (fromContract) return fromContract;
954
+ return registry.get(item.codecId);
955
+ }
956
+ }
957
+ function buildDecodeContext(plan, registry, contractCodecs) {
958
+ if (!isAstBackedPlan(plan)) return {
959
+ aliases: void 0,
960
+ codecs: /* @__PURE__ */ new Map(),
961
+ columnRefs: /* @__PURE__ */ new Map(),
962
+ includeAliases: EMPTY_INCLUDE_ALIASES
963
+ };
964
+ const projection = projectionListFromAst(plan.ast);
965
+ if (!projection) return {
966
+ aliases: void 0,
967
+ codecs: /* @__PURE__ */ new Map(),
968
+ columnRefs: /* @__PURE__ */ new Map(),
969
+ includeAliases: EMPTY_INCLUDE_ALIASES
970
+ };
971
+ const aliases = [];
972
+ const codecs = /* @__PURE__ */ new Map();
973
+ const columnRefs = /* @__PURE__ */ new Map();
974
+ const includeAliases = /* @__PURE__ */ new Set();
975
+ for (const item of projection) {
976
+ aliases.push(item.alias);
977
+ const codec$1 = resolveProjectionCodec(item, registry, contractCodecs);
978
+ if (codec$1) codecs.set(item.alias, codec$1);
979
+ if (item.expr.kind === "column-ref") columnRefs.set(item.alias, {
980
+ table: item.expr.table,
981
+ column: item.expr.column
982
+ });
983
+ else if (item.expr.kind === "subquery" || item.expr.kind === "json-array-agg") includeAliases.add(item.alias);
984
+ }
985
+ return {
986
+ aliases,
987
+ codecs,
988
+ columnRefs,
989
+ includeAliases
990
+ };
991
+ }
992
+ function previewWireValue(wireValue) {
993
+ if (typeof wireValue === "string") return wireValue.length > WIRE_PREVIEW_LIMIT ? `${wireValue.substring(0, WIRE_PREVIEW_LIMIT)}...` : wireValue;
994
+ return String(wireValue).substring(0, WIRE_PREVIEW_LIMIT);
995
+ }
996
+ function isJsonSchemaValidationError(error) {
997
+ return isRuntimeError(error) && error.code === "RUNTIME.JSON_SCHEMA_VALIDATION_FAILED";
998
+ }
999
+ function wrapDecodeFailure(error, alias, ref, codec$1, wireValue) {
1000
+ const message = error instanceof Error ? error.message : String(error);
1001
+ const wrapped = runtimeError("RUNTIME.DECODE_FAILED", `Failed to decode column ${ref ? `${ref.table}.${ref.column}` : alias} with codec '${codec$1.id}': ${message}`, {
1002
+ ...ref ? {
1003
+ table: ref.table,
1004
+ column: ref.column
1005
+ } : { alias },
1006
+ codec: codec$1.id,
1007
+ wirePreview: previewWireValue(wireValue)
1008
+ });
1009
+ wrapped.cause = error;
1010
+ throw wrapped;
1011
+ }
1012
+ function wrapIncludeAggregateFailure(error, alias, wireValue) {
1013
+ const wrapped = runtimeError("RUNTIME.DECODE_FAILED", `Failed to parse JSON array for include alias '${alias}': ${error instanceof Error ? error.message : String(error)}`, {
1014
+ alias,
1015
+ wirePreview: previewWireValue(wireValue)
1016
+ });
1017
+ wrapped.cause = error;
1018
+ throw wrapped;
1019
+ }
1020
+ function decodeIncludeAggregate(alias, wireValue) {
1021
+ if (wireValue === null || wireValue === void 0) return [];
1022
+ try {
1023
+ let parsed;
1024
+ if (typeof wireValue === "string") parsed = JSON.parse(wireValue);
1025
+ else if (Array.isArray(wireValue)) parsed = wireValue;
1026
+ else parsed = JSON.parse(String(wireValue));
1027
+ if (!Array.isArray(parsed)) throw new Error(`Expected array for include alias '${alias}', got ${typeof parsed}`);
1028
+ return parsed;
1029
+ } catch (error) {
1030
+ wrapIncludeAggregateFailure(error, alias, wireValue);
1031
+ }
1032
+ }
1033
+ /**
1034
+ * Decodes a single field. Single-armed: every cell takes the same path —
1035
+ * `codec.decode → await → JSON-Schema validate → return plain value` — so
1036
+ * sync- and async-authored codecs are indistinguishable to callers.
1037
+ *
1038
+ * The row-level `rowCtx` is repackaged into a per-cell
1039
+ * `SqlCodecCallContext` whose `column = { table, name }` is a structural
1040
+ * projection of the per-cell `ColumnRef = { table, column }` resolved from
1041
+ * the AST-backed `DecodeContext` (the same resolution `wrapDecodeFailure`
1042
+ * uses for envelope construction — one resolution per cell, two consumers).
1043
+ * Cells the runtime cannot resolve to a single underlying column (aggregate
1044
+ * aliases, computed projections without a simple ref) get
1045
+ * `column: undefined`, matching the spec contract that the runtime never
1046
+ * silently defaults this field.
1047
+ */
1048
+ async function decodeField(alias, wireValue, decodeCtx, jsonValidators, rowCtx) {
1049
+ if (wireValue === null) return null;
1050
+ const codec$1 = decodeCtx.codecs.get(alias);
1051
+ if (!codec$1) return wireValue;
1052
+ const ref = decodeCtx.columnRefs.get(alias);
1053
+ let cellCtx;
1054
+ if (ref) cellCtx = {
1055
+ ...rowCtx,
1056
+ column: {
1057
+ table: ref.table,
1058
+ name: ref.column
1059
+ }
1060
+ };
1061
+ else {
1062
+ const { column: _drop, ...rowCtxWithoutColumn } = rowCtx;
1063
+ cellCtx = rowCtxWithoutColumn;
1064
+ }
1065
+ let decoded;
1066
+ try {
1067
+ decoded = await codec$1.decode(wireValue, cellCtx);
1068
+ } catch (error) {
1069
+ wrapDecodeFailure(error, alias, ref, codec$1, wireValue);
1070
+ }
1071
+ if (jsonValidators && ref) try {
1072
+ validateJsonValue(jsonValidators, ref.table, ref.column, decoded, "decode", codec$1.id);
1073
+ } catch (error) {
1074
+ if (isJsonSchemaValidationError(error)) throw error;
1075
+ wrapDecodeFailure(error, alias, ref, codec$1, wireValue);
1076
+ }
1077
+ return decoded;
1078
+ }
1079
+ /**
1080
+ * Decodes a row by dispatching all per-cell codec calls concurrently via
1081
+ * `Promise.all`. Each cell follows the single-armed `decodeField` path.
1082
+ * Failures are wrapped in `RUNTIME.DECODE_FAILED` with `{ table, column,
1083
+ * codec }` (or `{ alias, codec }` when no column ref is resolvable) and the
1084
+ * original error attached on `cause`.
1085
+ *
1086
+ * When `rowCtx.signal` is provided:
1087
+ *
1088
+ * - **Already-aborted at entry** short-circuits with `RUNTIME.ABORTED`
1089
+ * (`{ phase: 'decode' }`) before any `codec.decode` call is made.
1090
+ * - **Mid-flight aborts** race the per-cell `Promise.all` against the
1091
+ * signal so the runtime returns promptly even when codec bodies ignore
1092
+ * it. In-flight bodies that ignore the signal complete in the
1093
+ * background (cooperative cancellation).
1094
+ * - Existing `RUNTIME.DECODE_FAILED` envelopes from codec bodies pass
1095
+ * through unchanged (no double wrap).
1096
+ */
1097
+ async function decodeRow(row, plan, registry, jsonValidators, rowCtx, contractCodecs) {
1098
+ checkAborted(rowCtx, "decode");
1099
+ const signal = rowCtx.signal;
1100
+ const decodeCtx = buildDecodeContext(plan, registry, contractCodecs);
1101
+ const aliases = decodeCtx.aliases ?? Object.keys(row);
1102
+ if (decodeCtx.aliases !== void 0) {
1103
+ for (const alias of decodeCtx.aliases) if (!Object.hasOwn(row, alias)) throw runtimeError("RUNTIME.DECODE_FAILED", `Row missing projection alias "${alias}"`, {
1104
+ alias,
1105
+ expectedAliases: decodeCtx.aliases,
1106
+ presentKeys: Object.keys(row)
1107
+ });
1108
+ }
1109
+ const tasks = [];
1110
+ const includeIndices = [];
1111
+ for (let i = 0; i < aliases.length; i++) {
1112
+ const alias = aliases[i];
1113
+ const wireValue = row[alias];
1114
+ if (decodeCtx.includeAliases.has(alias)) {
1115
+ includeIndices.push({
1116
+ index: i,
1117
+ alias,
1118
+ value: wireValue
1119
+ });
1120
+ tasks.push(Promise.resolve(void 0));
1121
+ continue;
1122
+ }
1123
+ tasks.push(decodeField(alias, wireValue, decodeCtx, jsonValidators, rowCtx));
1124
+ }
1125
+ const settled = await raceAgainstAbort(Promise.all(tasks), signal, "decode");
1126
+ for (const entry of includeIndices) settled[entry.index] = decodeIncludeAggregate(entry.alias, entry.value);
1127
+ const decoded = {};
1128
+ for (let i = 0; i < aliases.length; i++) decoded[aliases[i]] = settled[i];
1129
+ return decoded;
1130
+ }
1131
+
1132
+ //#endregion
1133
+ //#region src/codecs/encoding.ts
1134
+ const NO_METADATA = Object.freeze({
1135
+ codecId: void 0,
1136
+ name: void 0
1137
+ });
1138
+ /**
1139
+ * Resolve the codec for an outgoing param.
1140
+ *
1141
+ * Phase B (and AC-5-deferred carve-out): `ParamRef` does not carry a
1142
+ * `(table, column)` ref today — every `ParamRef` carries `codecId` but
1143
+ * not the column it relates to. Encode-side dispatch therefore consults
1144
+ * `contractCodecs.forCodecId(codecId)` (which itself prefers the
1145
+ * contract-walk-derived shared codec, falling back to the legacy
1146
+ * `CodecRegistry.get` for parameterized codec ids whose contracts don't
1147
+ * have a column the walk could resolve through).
1148
+ *
1149
+ * For the parameterized codecs shipped at Phase B (pgvector, postgres
1150
+ * json/jsonb), encode is per-instance-stateless w.r.t. params:
1151
+ * - pgvector formats `[v1,v2,...]` regardless of declared length;
1152
+ * - postgres json/jsonb encode is `JSON.stringify` regardless of schema.
1153
+ *
1154
+ * So the codec-id-keyed lookup yields a structurally equivalent encoder
1155
+ * even when the resolved per-instance codec carries extra state (e.g. a
1156
+ * compiled JSON-Schema validator used only by `decode`). TML-2357 retires
1157
+ * the fallback by threading `ParamRef.refs` through column-bound
1158
+ * construction sites.
1159
+ */
1160
+ function resolveParamCodec(metadata, registry, contractCodecs) {
1161
+ if (!metadata.codecId) return void 0;
1162
+ const fromContract = contractCodecs?.forCodecId(metadata.codecId);
1163
+ if (fromContract) return fromContract;
1164
+ return registry.get(metadata.codecId);
1165
+ }
1166
+ function paramLabel(metadata, paramIndex) {
1167
+ return metadata.name ?? `param[${paramIndex}]`;
1168
+ }
1169
+ function wrapEncodeFailure(error, metadata, paramIndex, codecId) {
1170
+ const label = paramLabel(metadata, paramIndex);
1171
+ const wrapped = runtimeError("RUNTIME.ENCODE_FAILED", `Failed to encode parameter ${label} with codec '${codecId}': ${error instanceof Error ? error.message : String(error)}`, {
1172
+ label,
1173
+ codec: codecId,
1174
+ paramIndex
1175
+ });
1176
+ wrapped.cause = error;
1177
+ throw wrapped;
1178
+ }
1179
+ async function encodeParamValue(value, metadata, paramIndex, registry, ctx, contractCodecs) {
1180
+ if (value === null || value === void 0) return null;
1181
+ const codec$1 = resolveParamCodec(metadata, registry, contractCodecs);
1182
+ if (!codec$1) return value;
1183
+ try {
1184
+ return await codec$1.encode(value, ctx);
1185
+ } catch (error) {
1186
+ wrapEncodeFailure(error, metadata, paramIndex, codec$1.id);
1187
+ }
1188
+ }
1189
+ /**
1190
+ * Encodes all parameters concurrently via `Promise.all`. Per parameter, sync-
1191
+ * and async-authored codecs share the same path: `codec.encode → await →
1192
+ * return`. Param-level failures are wrapped in `RUNTIME.ENCODE_FAILED`.
1193
+ *
1194
+ * When `ctx.signal` is provided:
1195
+ *
1196
+ * - **Already-aborted at entry** short-circuits with `RUNTIME.ABORTED`
1197
+ * (`{ phase: 'encode' }`) before any `codec.encode` call is made — codecs
1198
+ * can pin this with a per-call counter that stays at zero.
1199
+ * - **Mid-flight abort** races the per-param `Promise.all` against
1200
+ * `abortable(ctx.signal)`. The runtime returns `RUNTIME.ABORTED` promptly
1201
+ * even if codec bodies ignore the signal; the in-flight bodies are
1202
+ * abandoned and run to completion in the background (cooperative
1203
+ * cancellation, see ADR 204).
1204
+ * - Existing `RUNTIME.ENCODE_FAILED` envelopes that surface from a codec
1205
+ * body before the runtime observes the abort pass through unchanged
1206
+ * (no double wrap).
1207
+ */
1208
+ async function encodeParams(plan, registry, ctx, contractCodecs) {
1209
+ checkAborted(ctx, "encode");
1210
+ const signal = ctx.signal;
1211
+ if (plan.params.length === 0) return plan.params;
1212
+ const paramCount = plan.params.length;
1213
+ const metadata = new Array(paramCount).fill(NO_METADATA);
1214
+ if (plan.ast) {
1215
+ const refs = collectOrderedParamRefs(plan.ast);
1216
+ for (let i = 0; i < paramCount && i < refs.length; i++) {
1217
+ const ref = refs[i];
1218
+ if (ref) metadata[i] = {
1219
+ codecId: ref.codecId,
1220
+ name: ref.name
1221
+ };
1222
+ }
1223
+ }
1224
+ const tasks = new Array(paramCount);
1225
+ for (let i = 0; i < paramCount; i++) tasks[i] = encodeParamValue(plan.params[i], metadata[i] ?? NO_METADATA, i, registry, ctx, contractCodecs);
1226
+ const settled = await raceAgainstAbort(Promise.all(tasks), signal, "encode");
1227
+ return Object.freeze(settled);
1228
+ }
1229
+
1230
+ //#endregion
1231
+ //#region src/content-hash.ts
1232
+ /**
1233
+ * Computes a stable content hash for a lowered SQL execution plan.
1234
+ *
1235
+ * Internally builds an unambiguous canonical-stringified preimage from
1236
+ * three components:
1237
+ *
1238
+ * 1. `meta.storageHash` — discriminates by schema. A migration changes the
1239
+ * storage hash, which invalidates cached entries automatically.
1240
+ * 2. `exec.sql` — the raw lowered SQL text. Two queries with different
1241
+ * structure produce different keys. Note that we deliberately do **not**
1242
+ * use `computeSqlFingerprint` here: that helper strips literals to group
1243
+ * executions by statement shape (used by telemetry), which is the
1244
+ * opposite of what a content hash needs — we want per-execution
1245
+ * discrimination, not per-statement-shape grouping.
1246
+ * 3. `exec.params` — the bound parameters. `canonicalStringify` produces a
1247
+ * deterministic serialization that is stable across object key
1248
+ * insertion order and that distinguishes types JSON would otherwise
1249
+ * conflate (e.g. `BigInt(1)` vs `1`).
1250
+ *
1251
+ * The components are wrapped in an object and canonicalized as a single
1252
+ * unit (rather than concatenated with a delimiter) so component
1253
+ * boundaries are unambiguous: any character appearing inside `sql` or
1254
+ * `storageHash` cannot bleed across components and produce a collision
1255
+ * with a different split of the same characters.
1256
+ *
1257
+ * The canonical string is then piped through `hashContent` to produce a
1258
+ * bounded, opaque digest. See `@prisma-next/utils/hash-content` for the
1259
+ * rationale.
1260
+ *
1261
+ * @internal
1262
+ */
1263
+ function computeSqlContentHash(exec) {
1264
+ return hashContent(canonicalStringify({
1265
+ storageHash: exec.meta.storageHash,
1266
+ sql: exec.sql,
1267
+ params: exec.params
1268
+ }));
1269
+ }
1270
+
1271
+ //#endregion
1272
+ //#region src/fingerprint.ts
1273
+ const STRING_LITERAL_REGEX = /'(?:''|[^'])*'/g;
1274
+ const NUMERIC_LITERAL_REGEX = /\b\d+(?:\.\d+)?\b/g;
1275
+ const WHITESPACE_REGEX = /\s+/g;
1276
+ /**
1277
+ * Computes a literal-stripped, normalized fingerprint of a SQL statement.
1278
+ *
1279
+ * The function strips string and numeric literals, collapses whitespace, and
1280
+ * lowercases the result before hashing — so two structurally equivalent
1281
+ * statements (with different parameter values) produce the same fingerprint.
1282
+ * Used by SQL telemetry to group queries.
1283
+ */
1284
+ function computeSqlFingerprint(sql) {
1285
+ const normalized = sql.replace(STRING_LITERAL_REGEX, "?").replace(NUMERIC_LITERAL_REGEX, "?").replace(WHITESPACE_REGEX, " ").trim().toLowerCase();
1286
+ return `sha256:${createHash("sha256").update(normalized).digest("hex")}`;
1287
+ }
1288
+
1289
+ //#endregion
1290
+ //#region src/middleware/before-compile-chain.ts
1291
+ async function runBeforeCompileChain(middleware, initial, ctx) {
1292
+ let current = initial;
1293
+ for (const mw of middleware) {
1294
+ if (!mw.beforeCompile) continue;
1295
+ const result = await mw.beforeCompile(current, ctx);
1296
+ if (result === void 0) continue;
1297
+ if (result.ast === current.ast) continue;
1298
+ ctx.log.debug?.({
1299
+ event: "middleware.rewrite",
1300
+ middleware: mw.name,
1301
+ lane: current.meta.lane
1302
+ });
1303
+ current = result;
1304
+ }
1305
+ return current;
1306
+ }
1307
+
1308
+ //#endregion
1309
+ //#region src/sql-family-adapter.ts
1310
+ var SqlFamilyAdapter = class {
1311
+ contract;
1312
+ markerReader;
1313
+ constructor(contract, adapterProfile) {
1314
+ this.contract = contract;
1315
+ this.markerReader = adapterProfile;
1316
+ }
1317
+ validatePlan(plan, contract) {
1318
+ if (plan.meta.target !== contract.target) throw runtimeError("PLAN.TARGET_MISMATCH", "Plan target does not match runtime target", {
1319
+ planTarget: plan.meta.target,
1320
+ runtimeTarget: contract.target
1321
+ });
1322
+ if (plan.meta.storageHash !== contract.storage.storageHash) throw runtimeError("PLAN.HASH_MISMATCH", "Plan storage hash does not match runtime contract", {
1323
+ planStorageHash: plan.meta.storageHash,
1324
+ runtimeStorageHash: contract.storage.storageHash
1325
+ });
1326
+ }
1327
+ };
1328
+
1329
+ //#endregion
1330
+ //#region src/sql-runtime.ts
1331
+ function isExecutionPlan(plan) {
1332
+ return "sql" in plan;
1333
+ }
1334
+ var SqlRuntimeImpl = class extends RuntimeCore {
1335
+ contract;
1336
+ adapter;
1337
+ driver;
1338
+ familyAdapter;
1339
+ codecRegistry;
1340
+ contractCodecs;
1341
+ codecDescriptors;
1342
+ jsonSchemaValidators;
1343
+ sqlCtx;
1344
+ verify;
1345
+ codecRegistryValidated;
1346
+ verified;
1347
+ startupVerified;
1348
+ _telemetry;
1349
+ constructor(options) {
1350
+ const { context, adapter, driver, verify, middleware, mode, log } = options;
1351
+ if (middleware) for (const mw of middleware) checkMiddlewareCompatibility(mw, "sql", context.contract.target);
1352
+ const sqlCtx = {
1353
+ contract: context.contract,
1354
+ mode: mode ?? "strict",
1355
+ now: () => Date.now(),
1356
+ log: log ?? {
1357
+ info: () => {},
1358
+ warn: () => {},
1359
+ error: () => {}
1360
+ },
1361
+ contentHash: (exec) => computeSqlContentHash(exec)
1362
+ };
1363
+ super({
1364
+ middleware: middleware ?? [],
1365
+ ctx: sqlCtx
1366
+ });
1367
+ this.contract = context.contract;
1368
+ this.adapter = adapter;
1369
+ this.driver = driver;
1370
+ this.familyAdapter = new SqlFamilyAdapter(context.contract, adapter.profile);
1371
+ this.codecRegistry = context.codecs;
1372
+ this.contractCodecs = context.contractCodecs;
1373
+ this.codecDescriptors = context.codecDescriptors;
1374
+ this.jsonSchemaValidators = context.jsonSchemaValidators;
1375
+ this.sqlCtx = sqlCtx;
1376
+ this.verify = verify;
1377
+ this.codecRegistryValidated = false;
1378
+ this.verified = verify.mode === "startup" ? false : verify.mode === "always";
1379
+ this.startupVerified = false;
1380
+ this._telemetry = null;
1381
+ if (verify.mode === "startup") {
1382
+ validateCodecRegistryCompleteness(this.codecDescriptors, context.contract);
1383
+ this.codecRegistryValidated = true;
1384
+ }
1385
+ }
1386
+ /**
1387
+ * Lower a `SqlQueryPlan` (AST + meta) into a `SqlExecutionPlan` with
1388
+ * encoded parameters ready for the driver. This is the single point at
1389
+ * which params transition from app-layer values to driver wire-format.
1390
+ *
1391
+ * `ctx: SqlCodecCallContext` is forwarded to `encodeParams` so per-query
1392
+ * cancellation reaches every codec body during parameter encoding. The
1393
+ * framework abstract typed this as `CodecCallContext`; the SQL family
1394
+ * narrows it to the SQL-specific extension. SQL params do not populate
1395
+ * `ctx.column` — encode-side column metadata is the middleware's domain.
1396
+ */
1397
+ async lower(plan, ctx) {
1398
+ const lowered = lowerSqlPlan(this.adapter, this.contract, plan);
1399
+ return Object.freeze({
1400
+ ...lowered,
1401
+ params: await encodeParams(lowered, this.codecRegistry, ctx, this.contractCodecs)
1402
+ });
1403
+ }
1404
+ /**
1405
+ * Default driver invocation. Production execution paths override the
1406
+ * queryable target (e.g. transaction or connection) by going through
1407
+ * `executeAgainstQueryable`; this implementation supports any caller of
1408
+ * `super.execute(plan)` and the abstract-base contract.
1409
+ */
1410
+ runDriver(exec) {
1411
+ return this.driver.execute({
1412
+ sql: exec.sql,
1413
+ params: exec.params
1414
+ });
1415
+ }
1416
+ /**
1417
+ * SQL pre-compile hook. Runs the registered middleware `beforeCompile`
1418
+ * chain over the plan's draft (AST + meta). Returns the original plan
1419
+ * unchanged when no middleware rewrote the AST; otherwise returns a new
1420
+ * plan carrying the rewritten AST and meta. The AST is the authoritative
1421
+ * source of execution metadata, so a rewrite needs no sidecar
1422
+ * reconciliation here — the lowering adapter and the encoder both walk
1423
+ * the rewritten AST directly.
1424
+ */
1425
+ async runBeforeCompile(plan) {
1426
+ const rewrittenDraft = await runBeforeCompileChain(this.middleware, {
1427
+ ast: plan.ast,
1428
+ meta: plan.meta
1429
+ }, this.sqlCtx);
1430
+ return rewrittenDraft.ast === plan.ast ? plan : {
1431
+ ...plan,
1432
+ ast: rewrittenDraft.ast,
1433
+ meta: rewrittenDraft.meta
1434
+ };
1435
+ }
1436
+ execute(plan, options) {
1437
+ return this.executeAgainstQueryable(plan, this.driver, options);
1438
+ }
1439
+ executeAgainstQueryable(plan, queryable, options) {
1440
+ this.ensureCodecRegistryValidated();
1441
+ const self = this;
1442
+ const signal = options?.signal;
1443
+ const codecCtx = signal === void 0 ? {} : { signal };
1444
+ const generator = async function* () {
1445
+ checkAborted(codecCtx, "stream");
1446
+ const exec = isExecutionPlan(plan) ? Object.freeze({
1447
+ ...plan,
1448
+ params: await encodeParams(plan, self.codecRegistry, codecCtx, self.contractCodecs)
1449
+ }) : await self.lower(await self.runBeforeCompile(plan), codecCtx);
1450
+ self.familyAdapter.validatePlan(exec, self.contract);
1451
+ self._telemetry = null;
1452
+ if (!self.startupVerified && self.verify.mode === "startup") await self.verifyMarker();
1453
+ if (!self.verified && self.verify.mode === "onFirstUse") await self.verifyMarker();
1454
+ const startedAt = Date.now();
1455
+ let outcome = null;
1456
+ try {
1457
+ if (self.verify.mode === "always") await self.verifyMarker();
1458
+ const iterator = runWithMiddleware(exec, self.middleware, self.ctx, () => queryable.execute({
1459
+ sql: exec.sql,
1460
+ params: exec.params
1461
+ }))[Symbol.asyncIterator]();
1462
+ try {
1463
+ while (true) {
1464
+ checkAborted(codecCtx, "stream");
1465
+ const next = await iterator.next();
1466
+ if (next.done) break;
1467
+ yield await decodeRow(next.value, exec, self.codecRegistry, self.jsonSchemaValidators, codecCtx, self.contractCodecs);
1468
+ }
1469
+ } finally {
1470
+ await iterator.return?.();
1471
+ }
1472
+ outcome = "success";
1473
+ } catch (error) {
1474
+ outcome = "runtime-error";
1475
+ throw error;
1476
+ } finally {
1477
+ if (outcome !== null) self.recordTelemetry(exec, outcome, Date.now() - startedAt);
1478
+ }
1479
+ };
1480
+ return new AsyncIterableResult(generator());
1481
+ }
1482
+ async connection() {
1483
+ const driverConn = await this.driver.acquireConnection();
1484
+ const self = this;
1485
+ return {
1486
+ async transaction() {
1487
+ const driverTx = await driverConn.beginTransaction();
1488
+ return self.wrapTransaction(driverTx);
1489
+ },
1490
+ async release() {
1491
+ await driverConn.release();
1492
+ },
1493
+ async destroy(reason) {
1494
+ await driverConn.destroy(reason);
1495
+ },
1496
+ execute(plan, options) {
1497
+ return self.executeAgainstQueryable(plan, driverConn, options);
1498
+ }
1499
+ };
1500
+ }
1501
+ wrapTransaction(driverTx) {
1502
+ const self = this;
1503
+ return {
1504
+ async commit() {
1505
+ await driverTx.commit();
1506
+ },
1507
+ async rollback() {
1508
+ await driverTx.rollback();
1509
+ },
1510
+ execute(plan, options) {
1511
+ return self.executeAgainstQueryable(plan, driverTx, options);
1512
+ }
1513
+ };
1514
+ }
1515
+ telemetry() {
1516
+ return this._telemetry;
1517
+ }
1518
+ async close() {
1519
+ await this.driver.close();
1520
+ }
1521
+ ensureCodecRegistryValidated() {
1522
+ if (!this.codecRegistryValidated) {
1523
+ validateCodecRegistryCompleteness(this.codecDescriptors, this.contract);
1524
+ this.codecRegistryValidated = true;
1525
+ }
1526
+ }
1527
+ async verifyMarker() {
1528
+ if (this.verify.mode === "always") this.verified = false;
1529
+ if (this.verified) return;
1530
+ const readStatement = this.familyAdapter.markerReader.readMarkerStatement();
1531
+ const result = await this.driver.query(readStatement.sql, readStatement.params);
1532
+ if (result.rows.length === 0) {
1533
+ if (this.verify.requireMarker) throw runtimeError("CONTRACT.MARKER_MISSING", "Contract marker not found in database");
1534
+ this.verified = true;
1535
+ return;
1536
+ }
1537
+ const marker = this.familyAdapter.markerReader.parseMarkerRow(result.rows[0]);
1538
+ const contract = this.contract;
1539
+ if (marker.storageHash !== contract.storage.storageHash) throw runtimeError("CONTRACT.MARKER_MISMATCH", "Database storage hash does not match contract", {
1540
+ expected: contract.storage.storageHash,
1541
+ actual: marker.storageHash
1542
+ });
1543
+ const expectedProfile = contract.profileHash ?? null;
1544
+ if (expectedProfile !== null && marker.profileHash !== expectedProfile) throw runtimeError("CONTRACT.MARKER_MISMATCH", "Database profile hash does not match contract", {
1545
+ expectedProfile,
1546
+ actualProfile: marker.profileHash
1547
+ });
1548
+ this.verified = true;
1549
+ this.startupVerified = true;
1550
+ }
1551
+ recordTelemetry(plan, outcome, durationMs) {
1552
+ const contract = this.contract;
1553
+ this._telemetry = Object.freeze({
1554
+ lane: plan.meta.lane,
1555
+ target: contract.target,
1556
+ fingerprint: computeSqlFingerprint(plan.sql),
1557
+ outcome,
1558
+ ...durationMs !== void 0 ? { durationMs } : {}
1559
+ });
1560
+ }
1561
+ };
1562
+ function transactionClosedError() {
1563
+ return runtimeError("RUNTIME.TRANSACTION_CLOSED", "Cannot read from a query result after the transaction has ended. Await the result or call .toArray() inside the transaction callback.", {});
1564
+ }
1565
+ async function withTransaction(runtime, fn) {
1566
+ const connection = await runtime.connection();
1567
+ const transaction = await connection.transaction();
1568
+ let invalidated = false;
1569
+ const txContext = {
1570
+ get invalidated() {
1571
+ return invalidated;
1572
+ },
1573
+ execute(plan, options) {
1574
+ if (invalidated) throw transactionClosedError();
1575
+ const inner = transaction.execute(plan, options);
1576
+ const guarded = async function* () {
1577
+ for await (const row of inner) {
1578
+ if (invalidated) throw transactionClosedError();
1579
+ yield row;
1580
+ }
1581
+ };
1582
+ return new AsyncIterableResult(guarded());
1583
+ }
1584
+ };
1585
+ let connectionDisposed = false;
1586
+ const destroyConnection = async (reason) => {
1587
+ if (connectionDisposed) return;
1588
+ connectionDisposed = true;
1589
+ await connection.destroy(reason).catch(() => void 0);
1590
+ };
1591
+ try {
1592
+ let result;
1593
+ try {
1594
+ result = await fn(txContext);
1595
+ } catch (error) {
1596
+ try {
1597
+ await transaction.rollback();
1598
+ } catch (rollbackError) {
1599
+ await destroyConnection(rollbackError);
1600
+ const wrapped = runtimeError("RUNTIME.TRANSACTION_ROLLBACK_FAILED", "Transaction rollback failed after callback error", { rollbackError });
1601
+ wrapped.cause = error;
1602
+ throw wrapped;
1603
+ }
1604
+ throw error;
1605
+ } finally {
1606
+ invalidated = true;
1607
+ }
1608
+ try {
1609
+ await transaction.commit();
1610
+ } catch (commitError) {
1611
+ try {
1612
+ await transaction.rollback();
1613
+ } catch {
1614
+ await destroyConnection(commitError);
1615
+ }
1616
+ const wrapped = runtimeError("RUNTIME.TRANSACTION_COMMIT_FAILED", "Transaction commit failed", { commitError });
1617
+ wrapped.cause = commitError;
1618
+ throw wrapped;
1619
+ }
1620
+ return result;
1621
+ } finally {
1622
+ if (!connectionDisposed) await connection.release();
1623
+ }
1624
+ }
1625
+ function createRuntime(options) {
1626
+ const { stackInstance, context, driver, verify, middleware, mode, log } = options;
1627
+ return new SqlRuntimeImpl({
1628
+ context,
1629
+ adapter: stackInstance.adapter,
1630
+ driver,
1631
+ verify,
1632
+ ...ifDefined("middleware", middleware),
1633
+ ...ifDefined("mode", mode),
1634
+ ...ifDefined("log", log)
1635
+ });
1636
+ }
1637
+
1638
+ //#endregion
1639
+ export { readContractMarker as a, createSqlExecutionStack as c, parseContractMarkerRow as d, lowerSqlPlan as f, validateContractCodecMappings as h, ensureTableStatement as i, lints as l, validateCodecRegistryCompleteness as m, withTransaction as n, writeContractMarker as o, extractCodecIds as p, ensureSchemaStatement as r, createExecutionContext as s, createRuntime as t, budgets as u };
1640
+ //# sourceMappingURL=exports-BcX9wp4z.mjs.map