@prisma-next/sql-runtime 0.5.0-dev.9 → 0.6.0-dev.1

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