@prisma/orm-family-sql 8.0.0-rc.18-dev.17 → 8.0.0-rc.18-dev.18

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,2371 @@
1
+ import { t as createSqlOperationRegistry } from "./dist-C_q1_QR9.mjs";
2
+ import { K as isQueryAst, j as PreparedParamRef } from "./types-NTQsD2MS-DAP2yVDn.mjs";
3
+ import { t as collectOrderedParamRefs } from "./util-BBgcP3gI-XJBkxo1K.mjs";
4
+ import { t as createSqlParamRefMutator } from "./middleware-yBAfiVWn-CzNCthJT.mjs";
5
+ import { t as buildSqlAggregateDescriptorRegistry } from "./aggregate-descriptor-registry-Dd7Uj7DJ.mjs";
6
+ import { t as buildCodecDescriptorRegistry } from "./codec-descriptor-registry-4kw8FPpo.mjs";
7
+ import { a as runPreparedQuery, c as transactionStart, i as runPreparedExecute, l as wasRefusedBeforeBegin, n as preparedStatementExecute, o as settlementOf, r as preparedStatementQuery, s as transactionSettlement, t as createTransactionStart } from "./transaction-state-BfXhs7D7-3k10jZ8I.mjs";
8
+ import { isStructuredError, structuredError } from "@prisma/orm-framework/utils/structured-error";
9
+ import { ifDefined } from "@prisma/orm-framework/utils/defined";
10
+ import { createHash } from "node:crypto";
11
+ import { materializeCodec, resolveCodecDescriptorOrThrow } from "@prisma/orm-framework/components/codec";
12
+ import { blindCast, castAs } from "@prisma/orm-framework/utils/casts";
13
+ import { assertNever, isInternalError } from "@prisma/orm-framework/utils/internal-error";
14
+ import { canonicalStringify } from "@prisma/orm-framework/utils/canonical-stringify";
15
+ import { checkContractComponentRequirements, mergeCapabilityMatrices } from "@prisma/orm-framework/components/components";
16
+ import { AsyncIterableResult, RuntimeCore, applyMutationDefaults, assertMutationDefaultGeneratorsAvailable, checkAborted, checkMiddlewareCompatibility, collectMutationDefaultGenerators, createContextBeneath, executeStatement, raceAgainstAbort, runtimeError as runtimeError$1, startClientActivity, startTransactionActivity, streamStatement } from "@prisma/orm-framework/components/runtime";
17
+ import { canonicalizeJson } from "@prisma/orm-framework/components/utils";
18
+ import { invariant } from "@prisma/orm-framework/utils/assertions";
19
+ import { createExecutionStack } from "@prisma/orm-framework/components/execution";
20
+ import { hashContent } from "@prisma/orm-framework/utils/hash-content";
21
+ //#region ../../../2-sql/5-runtime/dist/exports-DVsHk8HP.mjs
22
+ /** The symbol-keyed member through which a runtime, connection, transaction, `withTransaction` context or activity handle starts activities; user-facing types do not show it. */
23
+ const activitySource = Symbol("activitySource");
24
+ function hasActivitySourceMember(target) {
25
+ return activitySource in target;
26
+ }
27
+ /** The `activitySource` member of a queryable; `undefined` for a queryable that cannot start activities, such as a test stub. */
28
+ function findActivitySource(target) {
29
+ return hasActivitySourceMember(target) ? target[activitySource] : void 0;
30
+ }
31
+ function activitySourceOf(target) {
32
+ const source = findActivitySource(target);
33
+ invariant(source !== void 0, "The queryable cannot start activities");
34
+ return source;
35
+ }
36
+ /** An activity source that starts activities on whatever queryable `target()` returns when called, for an object that sends its statements to a queryable it looks up each time. */
37
+ function delegatingActivitySource(target) {
38
+ return {
39
+ startActivity: (activity) => startActivityOn(target(), activity),
40
+ planContentHash: (plan) => planContentHashOn(target(), plan)
41
+ };
42
+ }
43
+ /** Starts an activity on a queryable through its `activitySource` member. */
44
+ function startActivityOn(target, activity) {
45
+ return activitySourceOf(target).startActivity(activity);
46
+ }
47
+ function planContentHashOn(target, plan) {
48
+ return activitySourceOf(target).planContentHash(plan);
49
+ }
50
+ /**
51
+ * Build an {@link AstCodecResolver} bound to a descriptor registry and a per-call instance-context factory.
52
+ *
53
+ * The instance-context factory lets callers control `name` / `usedAt` for refs the AST supplies (e.g. AST-embedded migration ops where the materialisation site is the AST node, not a contract column). The contract-walk pre-population path constructs its own contexts and invokes the resolver with those refs to seed the cache.
54
+ */
55
+ function createAstCodecResolver(descriptors, instanceContextFor) {
56
+ const cache = /* @__PURE__ */ new Map();
57
+ return { forCodecRef(ref) {
58
+ const key = `${ref.codecId}:${canonicalizeJson(ref.typeParams)}`;
59
+ const cached = cache.get(key);
60
+ if (cached) return cached;
61
+ const codec = materializeCodec(resolveCodecDescriptorOrThrow((id) => descriptors.descriptorFor(id), ref, "RUNTIME.CODEC_DESCRIPTOR_MISSING"), ref, instanceContextFor(ref));
62
+ cache.set(key, codec);
63
+ return codec;
64
+ } };
65
+ }
66
+ /**
67
+ * Build a contract-free {@link ContractCodecRegistry} that resolves codecs
68
+ * purely from AST-supplied {@link import('@internal/framework-components/codec').CodecRef}s
69
+ * against a target's descriptor registry.
70
+ *
71
+ * Dispatch is driven entirely by `CodecRef`s embedded in AST nodes; no
72
+ * contract walk is needed. `forColumn` always returns `undefined` — this
73
+ * registry carries no column-to-codec mappings.
74
+ */
75
+ function createAstCodecRegistry(descriptors) {
76
+ const resolver = createAstCodecResolver(descriptors, (ref) => ({
77
+ name: ref.codecId,
78
+ usedAt: []
79
+ }));
80
+ return {
81
+ forColumn: () => void 0,
82
+ forCodecRef: (ref) => resolver.forCodecRef(ref)
83
+ };
84
+ }
85
+ const NO_METADATA = Object.freeze({
86
+ codec: void 0,
87
+ name: void 0
88
+ });
89
+ function deriveParamMetadata(ast) {
90
+ return collectOrderedParamRefs(ast).map((ref) => {
91
+ return {
92
+ codec: ref.codec,
93
+ name: ref.name
94
+ };
95
+ });
96
+ }
97
+ function resolveParamCodec(metadata, contractCodecs) {
98
+ if (metadata.codec && contractCodecs) return contractCodecs.forCodecRef(metadata.codec);
99
+ }
100
+ function paramLabel(metadata, paramIndex) {
101
+ return metadata.name ?? `param[${paramIndex}]`;
102
+ }
103
+ function wrapEncodeFailure(error, metadata, paramIndex, codecId) {
104
+ if (isInternalError(error)) throw error;
105
+ const label = paramLabel(metadata, paramIndex);
106
+ const wrapped = runtimeError$1("RUNTIME.ENCODE_FAILED", `Failed to encode parameter ${label} with codec '${codecId}': ${error instanceof Error ? error.message : String(error)}`, {
107
+ label,
108
+ codec: codecId,
109
+ paramIndex
110
+ });
111
+ wrapped.cause = error;
112
+ throw wrapped;
113
+ }
114
+ async function encodeParamValue(value, metadata, paramIndex, ctx, contractCodecs) {
115
+ if (value === null || value === void 0) return null;
116
+ const codec = resolveParamCodec(metadata, contractCodecs);
117
+ if (!codec) return value;
118
+ if (metadata.codec?.many) {
119
+ if (!Array.isArray(value)) wrapEncodeFailure(/* @__PURE__ */ new TypeError(`expected an array for many-typed parameter, got ${typeof value}`), metadata, paramIndex, codec.id);
120
+ const encoded = [];
121
+ for (let i = 0; i < value.length; i++) {
122
+ const elem = value[i];
123
+ if (elem === null || elem === void 0) {
124
+ encoded.push(null);
125
+ continue;
126
+ }
127
+ try {
128
+ encoded.push(await codec.toWire(elem, ctx));
129
+ } catch (error) {
130
+ if (isStructuredError(error)) throw error;
131
+ wrapEncodeFailure(error, metadata, paramIndex, codec.id);
132
+ }
133
+ }
134
+ return encoded;
135
+ }
136
+ try {
137
+ return await codec.toWire(value, ctx);
138
+ } catch (error) {
139
+ if (isStructuredError(error)) throw error;
140
+ wrapEncodeFailure(error, metadata, paramIndex, codec.id);
141
+ }
142
+ }
143
+ /**
144
+ * 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`.
145
+ *
146
+ * When `ctx.signal` is provided:
147
+ *
148
+ * - **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.
149
+ * - **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).
150
+ * - Existing structured envelopes (any dotted-code error passing `isStructuredError`, e.g. `RUNTIME.ENCODE_FAILED`) that surface from a codec body before the runtime observes the abort pass through unchanged (no double wrap).
151
+ */
152
+ async function encodeParams(plan, ctx, contractCodecs) {
153
+ return encodeParamsWithMetadata(plan.params, deriveParamMetadata(plan.ast), ctx, contractCodecs);
154
+ }
155
+ async function encodeParamsWithMetadata(values, metadata, ctx, contractCodecs) {
156
+ checkAborted(ctx, "encode");
157
+ const signal = ctx.signal;
158
+ if (values.length === 0) return values;
159
+ const tasks = values.map((value, i) => encodeParamValue(value, metadata[i] ?? NO_METADATA, i, ctx, contractCodecs));
160
+ const settled = await raceAgainstAbort(Promise.all(tasks), signal, "encode");
161
+ return Object.freeze(settled);
162
+ }
163
+ function extractCodecIds(contract) {
164
+ const codecIds = /* @__PURE__ */ new Set();
165
+ for (const ns of Object.values(contract.storage.namespaces)) for (const table of Object.values(ns.entries.table ?? {})) for (const column of Object.values(table.columns)) {
166
+ const codecId = column.codecId;
167
+ codecIds.add(codecId);
168
+ }
169
+ return codecIds;
170
+ }
171
+ function extractColumnCodecRefs(contract) {
172
+ const refs = [];
173
+ for (const [namespaceId, ns] of Object.entries(contract.storage.namespaces)) for (const [tableName, table] of Object.entries(ns.entries.table ?? {})) for (const [columnName, column] of Object.entries(table.columns)) refs.push({
174
+ namespaceId,
175
+ table: tableName,
176
+ column: columnName,
177
+ codecId: column.codecId
178
+ });
179
+ return refs;
180
+ }
181
+ function validateContractCodecMappings(registry, contract) {
182
+ const invalidCodecs = extractColumnCodecRefs(contract).filter((ref) => registry.descriptorFor(ref.codecId) === void 0);
183
+ if (invalidCodecs.length > 0) {
184
+ const details = {
185
+ contractTarget: contract.target,
186
+ invalidCodecs
187
+ };
188
+ throw runtimeError$1("RUNTIME.CODEC_MISSING", `Missing codec implementations for column codecIds: ${invalidCodecs.map((c) => `${c.namespaceId}.${c.table}.${c.column} (${c.codecId})`).join(", ")}`, details);
189
+ }
190
+ }
191
+ function validateCodecRegistryCompleteness(registry, contract) {
192
+ validateContractCodecMappings(registry, contract);
193
+ }
194
+ /**
195
+ * Lowers a SQL query plan to an executable Plan by calling the adapter's lower method.
196
+ *
197
+ * Ad-hoc lowerings produce only `{kind: 'literal'}` slots; this helper
198
+ * unwraps them into the bare-value array `SqlExecutionPlan` exposes.
199
+ * Encountering a `{kind: 'bind'}` slot here means the caller passed an
200
+ * AST containing `PreparedParamRef` to the ad-hoc execute path — that's a
201
+ * caller error, surfaced as `RUNTIME.PREPARE_BIND_ON_ADHOC`.
202
+ */
203
+ function lowerSqlPlan(adapter, contract, queryPlan) {
204
+ const lowered = adapter.lower(queryPlan.ast, {
205
+ contract,
206
+ params: queryPlan.params
207
+ });
208
+ const params = lowered.params.map((slot) => {
209
+ if (slot.kind === "literal") return slot.value;
210
+ throw runtimeError$1("RUNTIME.PREPARE_BIND_ON_ADHOC", `Ad-hoc execute received a bind-site slot for '${slot.name}' — bind-site references are only valid inside runtime.prepare(...).`, { name: slot.name });
211
+ });
212
+ return Object.freeze({
213
+ sql: lowered.sql,
214
+ params,
215
+ ast: queryPlan.ast,
216
+ meta: queryPlan.meta
217
+ });
218
+ }
219
+ function hasAggregateWithoutGroupBy(ast) {
220
+ if (ast.groupBy !== void 0) return false;
221
+ return ast.projection.some((item) => item.expr.kind === "aggregate");
222
+ }
223
+ function primaryTableFromAst(ast) {
224
+ if (ast.from === void 0) return void 0;
225
+ switch (ast.from.kind) {
226
+ case "table-source": return ast.from.name;
227
+ case "derived-table-source": return ast.from.alias;
228
+ case "function-source": return ast.from.fn;
229
+ // v8 ignore next 4
230
+ default: return assertNever(ast.from, `Unsupported source kind: ${ast.from.kind}`);
231
+ }
232
+ }
233
+ function estimateRowsFromAst(ast, tableRows, defaultTableRows, hasAggregateWithoutGroup) {
234
+ if (hasAggregateWithoutGroup) return 1;
235
+ const primaryTable = primaryTableFromAst(ast);
236
+ const tableEstimate = (primaryTable !== void 0 ? tableRows[primaryTable] : void 0) ?? defaultTableRows;
237
+ if (typeof ast.limit === "number") return Math.min(ast.limit, tableEstimate);
238
+ return tableEstimate;
239
+ }
240
+ function emitBudgetViolation(error, shouldBlock, ctx) {
241
+ if (shouldBlock) throw error;
242
+ ctx.log.warn({
243
+ code: error.code,
244
+ message: error.message,
245
+ details: error.details
246
+ });
247
+ }
248
+ const observedRows = Symbol("budgets.observedRows");
249
+ function budgets(options) {
250
+ const maxRows = options?.maxRows ?? 1e4;
251
+ const defaultTableRows = options?.defaultTableRows ?? 1e4;
252
+ const tableRows = options?.tableRows ?? {};
253
+ const maxLatencyMs = options?.maxLatencyMs ?? 1e3;
254
+ const rowSeverity = options?.severities?.rowCount ?? "error";
255
+ const latencySeverity = options?.severities?.latency ?? "warn";
256
+ return Object.freeze({
257
+ name: "budgets",
258
+ familyId: "sql",
259
+ async beforeEncode(plan, ctx) {
260
+ if (isQueryAst(plan.ast) && plan.ast.kind === "select") evaluateSelectAst(plan.ast, ctx);
261
+ },
262
+ async onRow(_row, _plan, ctx) {
263
+ const observed = ctx.state(observedRows);
264
+ const count = (typeof observed["count"] === "number" ? observed["count"] : 0) + 1;
265
+ observed["count"] = count;
266
+ if (count <= maxRows || observed["warned"] === true) return;
267
+ const shouldBlock = rowSeverity === "error" || ctx.mode === "strict";
268
+ if (!shouldBlock) observed["warned"] = true;
269
+ emitBudgetViolation(runtimeError$1("BUDGET.ROWS_EXCEEDED", "Observed row count exceeds budget", {
270
+ source: "observed",
271
+ observedRows: count,
272
+ maxRows
273
+ }), shouldBlock, ctx);
274
+ },
275
+ async activityEnded(_activity, result, ctx) {
276
+ if (result.kind === "statement") checkLatency(result.latencyMs, ctx);
277
+ }
278
+ });
279
+ function checkLatency(latencyMs, ctx) {
280
+ if (latencyMs > maxLatencyMs) {
281
+ const shouldBlock = latencySeverity === "error" || ctx.mode === "strict";
282
+ emitBudgetViolation(runtimeError$1("BUDGET.TIME_EXCEEDED", "Query latency exceeds budget", {
283
+ latencyMs,
284
+ maxLatencyMs
285
+ }), shouldBlock, ctx);
286
+ }
287
+ }
288
+ function evaluateSelectAst(ast, ctx) {
289
+ const hasAggNoGroup = hasAggregateWithoutGroupBy(ast);
290
+ const estimated = estimateRowsFromAst(ast, tableRows, defaultTableRows, hasAggNoGroup);
291
+ const isUnbounded = ast.limit === void 0 && !hasAggNoGroup;
292
+ const shouldBlock = rowSeverity === "error" || ctx.mode === "strict";
293
+ if (isUnbounded) {
294
+ emitBudgetViolation(runtimeError$1("BUDGET.ROWS_EXCEEDED", "Unbounded SELECT query exceeds budget", estimated >= maxRows ? {
295
+ source: "ast",
296
+ estimatedRows: estimated,
297
+ maxRows
298
+ } : {
299
+ source: "ast",
300
+ maxRows
301
+ }), shouldBlock, ctx);
302
+ return;
303
+ }
304
+ if (estimated > maxRows) emitBudgetViolation(runtimeError$1("BUDGET.ROWS_EXCEEDED", "Estimated row count exceeds budget", {
305
+ source: "ast",
306
+ estimatedRows: estimated,
307
+ maxRows
308
+ }), shouldBlock, ctx);
309
+ }
310
+ }
311
+ const SELECT_STAR_REGEX = /select\s+\*/i;
312
+ const LIMIT_REGEX = /\blimit\b/i;
313
+ const MUTATION_PREFIX_REGEX = /^(insert|update|delete|create|alter|drop|truncate)\b/i;
314
+ const READ_ONLY_INTENTS = /* @__PURE__ */ new Set([
315
+ "read",
316
+ "report",
317
+ "readonly"
318
+ ]);
319
+ function evaluateRawGuardrails(plan, config) {
320
+ const lints = [];
321
+ const budgets = [];
322
+ const normalized = normalizeWhitespace(plan.sql);
323
+ const statementType = classifyStatement(normalized);
324
+ if (statementType === "select") {
325
+ if (SELECT_STAR_REGEX.test(normalized)) lints.push(createLint("LINT.SELECT_STAR", "error", "Raw SQL plan selects all columns via *", { sql: snippet(plan.sql) }));
326
+ if (!LIMIT_REGEX.test(normalized)) {
327
+ const severity = config?.budgets?.unboundedSelectSeverity ?? "error";
328
+ lints.push(createLint("LINT.NO_LIMIT", "warn", "Raw SQL plan omits LIMIT clause", { sql: snippet(plan.sql) }));
329
+ budgets.push(createBudget("BUDGET.ROWS_EXCEEDED", severity, "Raw SQL plan is unbounded and may exceed row budget", {
330
+ sql: snippet(plan.sql),
331
+ ...config?.budgets?.estimatedRows !== void 0 ? { estimatedRows: config.budgets.estimatedRows } : {}
332
+ }));
333
+ }
334
+ }
335
+ if (isMutationStatement(statementType) && isReadOnlyIntent(plan.meta)) lints.push(createLint("LINT.READ_ONLY_MUTATION", "error", "Raw SQL plan mutates data despite read-only intent", {
336
+ sql: snippet(plan.sql),
337
+ intent: plan.meta.annotations?.["intent"]
338
+ }));
339
+ return {
340
+ lints,
341
+ budgets,
342
+ statement: statementType
343
+ };
344
+ }
345
+ function classifyStatement(sql) {
346
+ const trimmed = sql.trim();
347
+ const lower = trimmed.toLowerCase();
348
+ if (lower.startsWith("with")) {
349
+ if (lower.includes("select")) return "select";
350
+ }
351
+ if (lower.startsWith("select")) return "select";
352
+ if (MUTATION_PREFIX_REGEX.test(trimmed)) return "mutation";
353
+ return "other";
354
+ }
355
+ function isMutationStatement(statement) {
356
+ return statement === "mutation";
357
+ }
358
+ function isReadOnlyIntent(meta) {
359
+ const annotations = meta.annotations;
360
+ const intent = typeof annotations?.intent === "string" ? annotations.intent.toLowerCase() : void 0;
361
+ return intent !== void 0 && READ_ONLY_INTENTS.has(intent);
362
+ }
363
+ function normalizeWhitespace(value) {
364
+ return value.replace(/\s+/g, " ").trim();
365
+ }
366
+ function snippet(sql) {
367
+ return normalizeWhitespace(sql).slice(0, 200);
368
+ }
369
+ function createLint(code, severity, message, details) {
370
+ return {
371
+ code,
372
+ severity,
373
+ message,
374
+ ...details ? { details } : {}
375
+ };
376
+ }
377
+ function createBudget(code, severity, message, details) {
378
+ return {
379
+ code,
380
+ severity,
381
+ message,
382
+ ...details ? { details } : {}
383
+ };
384
+ }
385
+ function getFromSourceTableDetail(source) {
386
+ switch (source.kind) {
387
+ case "table-source": return source.name;
388
+ case "derived-table-source": return source.alias;
389
+ case "function-source": return source.fn;
390
+ // v8 ignore next 4
391
+ default: return assertNever(source, `Unsupported source kind: ${source.kind}`);
392
+ }
393
+ }
394
+ function evaluateAstLints(ast) {
395
+ const findings = [];
396
+ switch (ast.kind) {
397
+ case "delete":
398
+ if (ast.where === void 0) findings.push({
399
+ code: "LINT.DELETE_WITHOUT_WHERE",
400
+ severity: "error",
401
+ message: "DELETE without WHERE clause blocks execution to prevent accidental full-table deletion",
402
+ details: { table: ast.table.name }
403
+ });
404
+ break;
405
+ case "update":
406
+ if (ast.where === void 0) findings.push({
407
+ code: "LINT.UPDATE_WITHOUT_WHERE",
408
+ severity: "error",
409
+ message: "UPDATE without WHERE clause blocks execution to prevent accidental full-table update",
410
+ details: { table: ast.table.name }
411
+ });
412
+ break;
413
+ case "select":
414
+ if (ast.limit === void 0) {
415
+ const table = ast.from !== void 0 ? getFromSourceTableDetail(ast.from) : void 0;
416
+ findings.push({
417
+ code: "LINT.NO_LIMIT",
418
+ severity: "warn",
419
+ message: "Unbounded SELECT may return large result sets",
420
+ ...ifDefined("details", table !== void 0 ? { table } : void 0)
421
+ });
422
+ }
423
+ if (ast.selectAllIntent !== void 0) {
424
+ const table = ast.selectAllIntent.table;
425
+ findings.push({
426
+ code: "LINT.SELECT_STAR",
427
+ severity: "warn",
428
+ message: "Query selects all columns via selectAll intent",
429
+ ...ifDefined("details", table !== void 0 ? { table } : void 0)
430
+ });
431
+ }
432
+ break;
433
+ case "insert": break;
434
+ case "raw-query": break;
435
+ // v8 ignore next 2
436
+ default: assertNever(ast, `Unsupported AST kind: ${ast.kind}`);
437
+ }
438
+ return findings;
439
+ }
440
+ function getConfiguredSeverity(code, options) {
441
+ const severities = options?.severities;
442
+ if (!severities) return void 0;
443
+ switch (code) {
444
+ case "LINT.SELECT_STAR": return severities.selectStar;
445
+ case "LINT.NO_LIMIT": return severities.noLimit;
446
+ case "LINT.DELETE_WITHOUT_WHERE": return severities.deleteWithoutWhere;
447
+ case "LINT.UPDATE_WITHOUT_WHERE": return severities.updateWithoutWhere;
448
+ case "LINT.READ_ONLY_MUTATION": return severities.readOnlyMutation;
449
+ default: return;
450
+ }
451
+ }
452
+ /**
453
+ * AST-first lint middleware for SQL plans. When `plan.ast` is a SQL QueryAst, inspects
454
+ * the AST structurally. When `plan.ast` is missing, falls back to raw heuristic
455
+ * guardrails or skips linting depending on `fallbackWhenAstMissing`.
456
+ *
457
+ * Rules (AST-based):
458
+ * - DELETE without WHERE: blocks execution (configurable severity, default error)
459
+ * - UPDATE without WHERE: blocks execution (configurable severity, default error)
460
+ * - Unbounded SELECT: warn/error (severity from noLimit)
461
+ * - SELECT * intent: warn/error (severity from selectStar)
462
+ *
463
+ * Fallback: When ast is missing, `fallbackWhenAstMissing: 'raw'` uses heuristic
464
+ * SQL parsing; `'skip'` skips all lints. Default is `'raw'`.
465
+ */
466
+ function lints(options) {
467
+ const fallback = options?.fallbackWhenAstMissing ?? "raw";
468
+ const beforeOperation = async (plan, ctx) => {
469
+ const findings = [];
470
+ if (isQueryAst(plan.ast)) {
471
+ findings.push(...evaluateAstLints(plan.ast));
472
+ if (plan.ast.kind === "raw-query") findings.push(...evaluateRawGuardrails(plan).lints);
473
+ } else if (fallback !== "skip") findings.push(...evaluateRawGuardrails(plan).lints);
474
+ for (const lint of findings) {
475
+ const effectiveSeverity = getConfiguredSeverity(lint.code, options) ?? lint.severity;
476
+ if (effectiveSeverity === "error") throw runtimeError$1(lint.code, lint.message, lint.details);
477
+ if (effectiveSeverity === "warn") ctx.log.warn({
478
+ code: lint.code,
479
+ message: lint.message,
480
+ details: lint.details
481
+ });
482
+ }
483
+ };
484
+ return Object.freeze({
485
+ name: "lints",
486
+ familyId: "sql",
487
+ beforeEncode: beforeOperation
488
+ });
489
+ }
490
+ var PreparedStatementImpl = class {
491
+ sql;
492
+ ast;
493
+ meta;
494
+ slots;
495
+ decodeContext;
496
+ paramMetadata;
497
+ constructor(internals) {
498
+ this.sql = internals.sql;
499
+ this.ast = internals.ast;
500
+ this.meta = internals.meta;
501
+ this.slots = internals.slots;
502
+ this.decodeContext = internals.decodeContext;
503
+ this.paramMetadata = internals.paramMetadata;
504
+ Object.freeze(this);
505
+ }
506
+ query(target, params, options) {
507
+ return runPreparedQuery(target, this, params, options);
508
+ }
509
+ };
510
+ /**
511
+ * The statistics-reporting sibling of {@link PreparedStatementImpl}. A plan
512
+ * declaring an affected-row count prepares into one of these, which carries
513
+ * the same lowered statement and bind slots and is consumed by executing it.
514
+ *
515
+ * The two classes are separate so the consumption a plan did not declare is
516
+ * absent from the value, not merely from its type.
517
+ */
518
+ var PreparedExecutionImpl = class {
519
+ sql;
520
+ ast;
521
+ meta;
522
+ slots;
523
+ decodeContext;
524
+ paramMetadata;
525
+ constructor(internals) {
526
+ this.sql = internals.sql;
527
+ this.ast = internals.ast;
528
+ this.meta = internals.meta;
529
+ this.slots = internals.slots;
530
+ this.decodeContext = internals.decodeContext;
531
+ this.paramMetadata = internals.paramMetadata;
532
+ Object.freeze(this);
533
+ }
534
+ execute(target, params, options) {
535
+ return runPreparedExecute(target, this, params, options);
536
+ }
537
+ };
538
+ function documentScopedCodecTypes(contract) {
539
+ return blindCast(contract.storage.types);
540
+ }
541
+ function createSqlExecutionStack(options) {
542
+ return createExecutionStack({
543
+ target: options.target,
544
+ adapter: options.adapter,
545
+ driver: options.driver,
546
+ extensions: options.extensions
547
+ });
548
+ }
549
+ function assertExecutionStackContractRequirements(contract, stack) {
550
+ const providedComponentIds = /* @__PURE__ */ new Set([
551
+ stack.target.id,
552
+ stack.adapter.id,
553
+ ...stack.extensions.map((pack) => pack.id)
554
+ ]);
555
+ const result = checkContractComponentRequirements({
556
+ contract,
557
+ expectedTargetFamily: "sql",
558
+ expectedTargetId: stack.target.targetId,
559
+ providedComponentIds
560
+ });
561
+ if (result.familyMismatch) throw runtimeError$1("RUNTIME.CONTRACT_FAMILY_MISMATCH", `Contract target family '${result.familyMismatch.actual}' does not match runtime family '${result.familyMismatch.expected}'.`, {
562
+ actual: result.familyMismatch.actual,
563
+ expected: result.familyMismatch.expected
564
+ });
565
+ if (result.targetMismatch) throw runtimeError$1("RUNTIME.CONTRACT_TARGET_MISMATCH", `Contract target '${result.targetMismatch.actual}' does not match runtime target descriptor '${result.targetMismatch.expected}'.`, {
566
+ actual: result.targetMismatch.actual,
567
+ expected: result.targetMismatch.expected
568
+ });
569
+ if (result.missingExtensionPackIds.length > 0) {
570
+ const packIds = result.missingExtensionPackIds;
571
+ throw runtimeError$1("RUNTIME.MISSING_EXTENSION_PACK", `Contract requires extension pack(s) ${packIds.map((id) => `'${id}'`).join(", ")}, but runtime descriptors do not provide matching component(s).`, { packIds });
572
+ }
573
+ }
574
+ function validateTypeParams(typeParams, descriptor, context) {
575
+ const result = descriptor.paramsSchema["~standard"].validate(typeParams);
576
+ if (result instanceof Promise) throw runtimeError$1("RUNTIME.TYPE_PARAMS_INVALID", `paramsSchema for codec '${descriptor.codecId}' returned a Promise; runtime validation requires a synchronous Standard Schema validator.`, {
577
+ ...context,
578
+ codecId: descriptor.codecId,
579
+ typeParams
580
+ });
581
+ if (result.issues) {
582
+ const messages = result.issues.map((issue) => issue.message).join("; ");
583
+ throw runtimeError$1("RUNTIME.TYPE_PARAMS_INVALID", `Invalid typeParams for ${context.typeName ? `type '${context.typeName}'` : `column '${context.tableName}.${context.columnName}'`} (codecId: ${descriptor.codecId}): ${messages}`, {
584
+ ...context,
585
+ codecId: descriptor.codecId,
586
+ typeParams
587
+ });
588
+ }
589
+ return blindCast(result.value);
590
+ }
591
+ /**
592
+ * 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: a descriptor is parameterized when it has a `paramsSchema`.
593
+ *
594
+ * 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).
595
+ */
596
+ function collectCodecDescriptors(contributors) {
597
+ const all = [];
598
+ const parameterized = /* @__PURE__ */ new Map();
599
+ const seen = /* @__PURE__ */ new Set();
600
+ for (const contributor of contributors) for (const descriptor of contributor.codecs()) {
601
+ if (seen.has(descriptor.codecId)) throw runtimeError$1("RUNTIME.DUPLICATE_CODEC", `Duplicate codec descriptor for codecId '${descriptor.codecId}'.`, { codecId: descriptor.codecId });
602
+ seen.add(descriptor.codecId);
603
+ all.push(descriptor);
604
+ if (descriptor.isParameterized) parameterized.set(descriptor.codecId, blindCast(descriptor));
605
+ }
606
+ return {
607
+ all,
608
+ parameterized
609
+ };
610
+ }
611
+ /**
612
+ * Flatten every aggregate descriptor the SQL stack contributes. Shape validation, single-ownership of each `(operation, input)` pair, and trait-overlap resolution all happen inside {@link buildSqlAggregateDescriptorRegistry}, so contributions reach it unchecked and fail there — at composition, never mid-query.
613
+ */
614
+ function collectAggregateDescriptorContributions(contributors) {
615
+ return contributors.flatMap((contributor) => contributor.types?.aggregateDescriptors ?? []);
616
+ }
617
+ function collectTypeRefSites(storage) {
618
+ const sites = /* @__PURE__ */ new Map();
619
+ for (const ns of Object.values(storage.namespaces)) for (const [tableName, table] of Object.entries(ns.entries.table ?? {})) for (const [columnName, column] of Object.entries(table.columns)) {
620
+ if (typeof column.typeRef !== "string") continue;
621
+ const list = sites.get(column.typeRef);
622
+ const entry = {
623
+ table: tableName,
624
+ column: columnName
625
+ };
626
+ if (list) list.push(entry);
627
+ else sites.set(column.typeRef, [entry]);
628
+ }
629
+ return sites;
630
+ }
631
+ function initializeTypeHelpers(storage, documentTypes, codecDescriptors) {
632
+ const helpers = {};
633
+ if (!documentTypes) return helpers;
634
+ const typeRefSites = collectTypeRefSites(storage);
635
+ for (const [typeName, typeInstance] of Object.entries(documentTypes)) {
636
+ const codecId = typeInstance.codecId;
637
+ const typeParams = typeInstance.typeParams;
638
+ const descriptor = codecDescriptors.get(codecId);
639
+ if (!descriptor) {
640
+ helpers[typeName] = typeInstance;
641
+ continue;
642
+ }
643
+ const validatedParams = validateTypeParams(typeParams ?? {}, descriptor, { typeName });
644
+ const ctx = {
645
+ name: typeName,
646
+ usedAt: typeRefSites.get(typeName) ?? []
647
+ };
648
+ helpers[typeName] = descriptor.factory(validatedParams)(ctx);
649
+ }
650
+ return helpers;
651
+ }
652
+ function validateColumnTypeParams(storage, codecDescriptors) {
653
+ for (const ns of Object.values(storage.namespaces)) for (const [tableName, table] of Object.entries(ns.entries.table ?? {})) for (const [columnName, column] of Object.entries(table.columns)) if (column.typeParams) {
654
+ const descriptor = codecDescriptors.get(column.codecId);
655
+ if (descriptor) validateTypeParams(column.typeParams, descriptor, {
656
+ tableName,
657
+ columnName
658
+ });
659
+ }
660
+ }
661
+ /**
662
+ * Build-time contract-integrity check: every `(table, column)` resolves to a {@link CodecRef} whose `codecId` is registered and whose `typeParams` presence matches the descriptor's `isParameterized` flag.
663
+ *
664
+ * Surfaces three classes of malformed contract that AST-bound codec resolution would otherwise mask silently:
665
+ *
666
+ * - column references a codecId no contributor registered → `RUNTIME.CODEC_DESCRIPTOR_MISSING`.
667
+ * - parameterized codec, no `typeParams` (legacy "tolerate refs without params" shape) → `RUNTIME.CODEC_PARAMETERIZATION_MISMATCH`.
668
+ * - non-parameterized codec, `typeParams` supplied → `RUNTIME.CODEC_PARAMETERIZATION_MISMATCH`.
669
+ *
670
+ * Runs unconditionally from `createExecutionContext` so contract bugs fail fast at construction time instead of silently skipping affected columns in the codec registry's pre-population walk.
671
+ */
672
+ function assertColumnCodecIntegrity(storage, codecDescriptors) {
673
+ for (const [namespaceId, ns] of Object.entries(storage.namespaces)) for (const [tableName, table] of Object.entries(ns.entries.table ?? {})) for (const columnName of Object.keys(table.columns)) {
674
+ const ref = codecDescriptors.codecRefForColumn(namespaceId, tableName, columnName);
675
+ if (!ref) continue;
676
+ const descriptor = codecDescriptors.descriptorFor(ref.codecId);
677
+ if (!descriptor) throw runtimeError$1("RUNTIME.CODEC_DESCRIPTOR_MISSING", `Column '${tableName}.${columnName}' references codec '${ref.codecId}' but no contributor registered a codec descriptor for that codecId. Add the extension pack that owns the codec to the runtime stack.`, {
678
+ table: tableName,
679
+ column: columnName,
680
+ codecId: ref.codecId
681
+ });
682
+ const paramsSchema = descriptor.paramsSchema;
683
+ if (paramsSchema !== void 0 && ref.typeParams === void 0) {
684
+ const probe = paramsSchema["~standard"].validate({});
685
+ if (probe instanceof Promise) {
686
+ probe.catch(() => {});
687
+ throw runtimeError$1("RUNTIME.TYPE_PARAMS_INVALID", `Column '${tableName}.${columnName}' uses parameterized codec '${ref.codecId}' whose paramsSchema returned a Promise; paramsSchema must be a synchronous Standard Schema validator. Return a value/issues result directly instead of a Promise.`, {
688
+ table: tableName,
689
+ column: columnName,
690
+ codecId: ref.codecId
691
+ });
692
+ }
693
+ if ("issues" in probe && !!probe.issues) throw runtimeError$1("RUNTIME.CODEC_PARAMETERIZATION_MISMATCH", `Column '${tableName}.${columnName}' uses parameterized codec '${ref.codecId}' but no typeParams are supplied. Provide typeParams on the column, or use a typeRef pointing at a storage.types entry that carries them.`, {
694
+ table: tableName,
695
+ column: columnName,
696
+ codecId: ref.codecId,
697
+ expected: "parameterized",
698
+ actual: "no typeParams"
699
+ });
700
+ }
701
+ const refTypeParams = ref.typeParams;
702
+ const refHasTypeParamKeys = refTypeParams !== void 0 && refTypeParams !== null && typeof refTypeParams === "object" && !Array.isArray(refTypeParams) && Object.keys(refTypeParams).length > 0;
703
+ if (!descriptor.isParameterized && refHasTypeParamKeys) throw runtimeError$1("RUNTIME.CODEC_PARAMETERIZATION_MISMATCH", `Column '${tableName}.${columnName}' supplies typeParams to non-parameterized codec '${ref.codecId}'. Remove the typeParams or switch to a parameterized codec id.`, {
704
+ table: tableName,
705
+ column: columnName,
706
+ codecId: ref.codecId,
707
+ expected: "non-parameterized",
708
+ actual: "has typeParams"
709
+ });
710
+ }
711
+ }
712
+ /**
713
+ * Build a {@link ContractCodecRegistry} that resolves codecs exclusively through the `forCodecRef` content-keyed cache.
714
+ *
715
+ * One pre-population pass walks `storage.types` and `storage.tables[].columns[]` to seed the resolver's per-ref instance context with the *aggregated* `usedAt` set for each canonical `(codecId, typeParams)` key. The same codec materialised through `forColumn` or `forCodecRef` is therefore one instance with one `SqlCodecInstanceContext` — stateful codecs reading `usedAt` see the full column set regardless of which surface the caller used.
716
+ *
717
+ * Per-key instance-name policy:
718
+ *
719
+ * - typeRef-shared columns use the `storage.types[name]` name.
720
+ * - inline-`typeParams` columns use `<col:Table.column>` (the first column observed at that key; additional columns sharing the key extend `usedAt`).
721
+ * - non-parameterized codec ids use `<codec:codecId>`, aggregating every column on that codec id into one `usedAt` set.
722
+ * - ad-hoc refs the contract walk did not pre-populate (e.g. AST-supplied refs from deserialised migration ops) fall back to the canonical cache key `${codecId}:${canonicalizeJson(typeParams)}` — the only structurally honest identity for an ad-hoc ref, distinct per `(codecId, typeParams)`.
723
+ *
724
+ * Contract integrity is enforced upstream by {@link assertColumnCodecIntegrity}: every column must reference a registered `codecId` whose `descriptor.isParameterized` flag matches the presence of `typeParams` (via `codecRefForColumn`). The pre-population walk and `forColumn` therefore make no defensive checks — malformed columns fail fast at `createExecutionContext` construction with `RUNTIME.CODEC_DESCRIPTOR_MISSING` or `RUNTIME.CODEC_PARAMETERIZATION_MISMATCH` rather than being silently skipped here.
725
+ *
726
+ * `forColumn(ns, t, c)` is a thin delegate over `forCodecRef(codecRefForColumn(ns, t, c))`; encode/decode hot paths read the resolver directly via `forCodecRef`. The only `undefined` `forColumn` returns is the legitimate "no such column in the contract" case.
727
+ */
728
+ function buildContractCodecRegistry(contract, codecDescriptors) {
729
+ const refKeyOf = (ref) => `${ref.codecId}:${canonicalizeJson(ref.typeParams)}`;
730
+ const usedAtByKey = /* @__PURE__ */ new Map();
731
+ const nameByKey = /* @__PURE__ */ new Map();
732
+ const typeRefSites = collectTypeRefSites(contract.storage);
733
+ for (const [typeName, typeInstance] of Object.entries(documentScopedCodecTypes(contract) ?? {})) {
734
+ const instanceTypeParams = typeInstance.typeParams;
735
+ const key = refKeyOf(instanceTypeParams !== void 0 && Object.keys(instanceTypeParams).length > 0 ? {
736
+ codecId: typeInstance.codecId,
737
+ typeParams: blindCast(instanceTypeParams)
738
+ } : { codecId: typeInstance.codecId });
739
+ const sites = typeRefSites.get(typeName) ?? [];
740
+ const existing = usedAtByKey.get(key);
741
+ if (existing) existing.push(...sites);
742
+ else {
743
+ usedAtByKey.set(key, [...sites]);
744
+ nameByKey.set(key, typeName);
745
+ }
746
+ }
747
+ for (const [namespaceId, ns] of Object.entries(contract.storage.namespaces)) for (const [tableName, table] of Object.entries(ns.entries.table ?? {})) for (const [columnName, column] of Object.entries(table.columns)) {
748
+ if (column.typeRef !== void 0) continue;
749
+ const ref = codecDescriptors.codecRefForColumn(namespaceId, tableName, columnName);
750
+ if (!ref) continue;
751
+ const key = refKeyOf(ref);
752
+ const site = {
753
+ table: tableName,
754
+ column: columnName
755
+ };
756
+ const existing = usedAtByKey.get(key);
757
+ if (existing) existing.push(site);
758
+ else {
759
+ usedAtByKey.set(key, [site]);
760
+ const name = ref.typeParams !== void 0 ? `<col:${tableName}.${columnName}>` : `<codec:${ref.codecId}>`;
761
+ nameByKey.set(key, name);
762
+ }
763
+ }
764
+ const resolver = createAstCodecResolver(codecDescriptors, (ref) => {
765
+ const key = refKeyOf(ref);
766
+ return {
767
+ name: nameByKey.get(key) ?? key,
768
+ usedAt: usedAtByKey.get(key) ?? []
769
+ };
770
+ });
771
+ for (const [namespaceId, ns] of Object.entries(contract.storage.namespaces)) for (const [tableName, table] of Object.entries(ns.entries.table ?? {})) for (const columnName of Object.keys(table.columns)) {
772
+ const ref = codecDescriptors.codecRefForColumn(namespaceId, tableName, columnName);
773
+ if (!ref) continue;
774
+ resolver.forCodecRef(ref);
775
+ }
776
+ return {
777
+ forColumn(namespaceId, table, column) {
778
+ const ref = codecDescriptors.codecRefForColumn(namespaceId, table, column);
779
+ return ref ? resolver.forCodecRef(ref) : void 0;
780
+ },
781
+ forCodecRef(ref) {
782
+ return resolver.forCodecRef(ref);
783
+ }
784
+ };
785
+ }
786
+ function createExecutionContext(options) {
787
+ const { stack, driver } = options;
788
+ assertExecutionStackContractRequirements(options.contract, stack);
789
+ const capabilityContributors = [
790
+ stack.target,
791
+ stack.adapter,
792
+ ...driver ? [driver] : [],
793
+ ...stack.extensions
794
+ ];
795
+ const mergedCapabilities = mergeCapabilityMatrices(options.contract.capabilities, capabilityContributors);
796
+ const contract = {
797
+ ...options.contract,
798
+ capabilities: mergedCapabilities
799
+ };
800
+ const contributors = [
801
+ stack.target,
802
+ stack.adapter,
803
+ ...stack.extensions
804
+ ];
805
+ const { all: allCodecDescriptors, parameterized: parameterizedCodecDescriptors } = collectCodecDescriptors(contributors);
806
+ const queryOperationRegistry = createSqlOperationRegistry();
807
+ for (const contributor of contributors) {
808
+ const ops = contributor.queryOperations?.() ?? {};
809
+ for (const [name, op] of Object.entries(ops)) queryOperationRegistry.register(name, op);
810
+ }
811
+ const codecDescriptors = buildCodecDescriptorRegistry(allCodecDescriptors, contract.storage);
812
+ assertColumnCodecIntegrity(contract.storage, codecDescriptors);
813
+ const aggregateDescriptors = buildSqlAggregateDescriptorRegistry(collectAggregateDescriptorContributions(contributors), codecDescriptors);
814
+ const mutationDefaultGeneratorRegistry = collectMutationDefaultGenerators(contributors);
815
+ assertMutationDefaultGeneratorsAvailable(contract.execution, mutationDefaultGeneratorRegistry);
816
+ if (parameterizedCodecDescriptors.size > 0) validateColumnTypeParams(contract.storage, parameterizedCodecDescriptors);
817
+ const types = initializeTypeHelpers(contract.storage, documentScopedCodecTypes(contract), parameterizedCodecDescriptors);
818
+ return {
819
+ contract,
820
+ contractCodecs: buildContractCodecRegistry(contract, codecDescriptors),
821
+ codecDescriptors,
822
+ aggregateDescriptors,
823
+ queryOperations: queryOperationRegistry,
824
+ types,
825
+ rawCodecInferer: stack.adapter.rawCodecInferer,
826
+ collectionMethods: contributors.flatMap((contributor) => contributor.collectionMethods?.() ?? []),
827
+ applyMutationDefaults: (options) => applyMutationDefaults(contract.execution, mutationDefaultGeneratorRegistry, options)
828
+ };
829
+ }
830
+ const sqlNativeArrayListDecoder = async (wireValue, decodeElement) => {
831
+ if (!Array.isArray(wireValue)) throw new TypeError(`expected an array from the driver for many-typed column, got ${typeof wireValue}`);
832
+ const decoded = [];
833
+ for (const elem of wireValue) decoded.push(await decodeElement(elem));
834
+ return decoded;
835
+ };
836
+ const WIRE_PREVIEW_LIMIT = 100;
837
+ const EMPTY_INCLUDE_ALIASES = /* @__PURE__ */ new Set();
838
+ function projectionListFromAst(ast) {
839
+ if (ast.kind === "select") return ast.projection;
840
+ return ast.returning;
841
+ }
842
+ function resolveProjectionCodec(item, contractCodecs) {
843
+ if (item.codec && contractCodecs) return contractCodecs.forCodecRef(item.codec);
844
+ }
845
+ const EMPTY_MANY_ALIASES = /* @__PURE__ */ new Set();
846
+ function undecodedContext() {
847
+ return {
848
+ aliases: void 0,
849
+ codecs: /* @__PURE__ */ new Map(),
850
+ columnRefs: /* @__PURE__ */ new Map(),
851
+ includeAliases: EMPTY_INCLUDE_ALIASES,
852
+ manyAliases: EMPTY_MANY_ALIASES,
853
+ aliasSource: "projection"
854
+ };
855
+ }
856
+ /**
857
+ * Decode context for a raw statement: the columns come from the row spec its
858
+ * author declared at the terminator, and each carries the codec that decodes
859
+ * it. A statement that reports an affected-row count declares no columns, so
860
+ * its single stats row passes through undecoded.
861
+ *
862
+ * The spec is the only description of the result — the runtime never parses
863
+ * the SQL — so it is also what a mismatched result set is measured against.
864
+ */
865
+ function rawQueryDecodeContext(ast, contractCodecs) {
866
+ if (ast.result.kind === "affected-count") return undecodedContext();
867
+ const aliases = [];
868
+ const codecs = /* @__PURE__ */ new Map();
869
+ for (const [name, column] of Object.entries(ast.result.columns)) {
870
+ aliases.push(name);
871
+ if (contractCodecs) codecs.set(name, contractCodecs.forCodecRef({ codecId: column.codecId }));
872
+ }
873
+ return {
874
+ aliases,
875
+ codecs,
876
+ columnRefs: /* @__PURE__ */ new Map(),
877
+ includeAliases: EMPTY_INCLUDE_ALIASES,
878
+ manyAliases: EMPTY_MANY_ALIASES,
879
+ aliasSource: "row-spec"
880
+ };
881
+ }
882
+ function buildDecodeContext(ast, contractCodecs) {
883
+ if (ast.kind === "raw-query") return rawQueryDecodeContext(ast, contractCodecs);
884
+ const projection = projectionListFromAst(ast);
885
+ if (!projection || projection.length === 0) return undecodedContext();
886
+ const aliases = [];
887
+ const codecs = /* @__PURE__ */ new Map();
888
+ const columnRefs = /* @__PURE__ */ new Map();
889
+ const includeAliases = /* @__PURE__ */ new Set();
890
+ const manyAliases = /* @__PURE__ */ new Set();
891
+ for (const item of projection) {
892
+ aliases.push(item.alias);
893
+ const codec = resolveProjectionCodec(item, contractCodecs);
894
+ if (codec) codecs.set(item.alias, codec);
895
+ if (item.codec?.many) manyAliases.add(item.alias);
896
+ if (item.expr.kind === "column-ref") columnRefs.set(item.alias, {
897
+ table: item.expr.table,
898
+ column: item.expr.column
899
+ });
900
+ else if (item.expr.kind === "subquery" || item.expr.kind === "json-array-agg") includeAliases.add(item.alias);
901
+ }
902
+ return {
903
+ aliases,
904
+ codecs,
905
+ columnRefs,
906
+ includeAliases,
907
+ manyAliases,
908
+ aliasSource: "projection"
909
+ };
910
+ }
911
+ function previewWireValue(wireValue) {
912
+ if (typeof wireValue === "string") return wireValue.length > WIRE_PREVIEW_LIMIT ? `${wireValue.substring(0, WIRE_PREVIEW_LIMIT)}...` : wireValue;
913
+ return String(wireValue).substring(0, WIRE_PREVIEW_LIMIT);
914
+ }
915
+ function wrapDecodeFailure(error, alias, ref, codec, wireValue) {
916
+ if (isInternalError(error)) throw error;
917
+ const message = error instanceof Error ? error.message : String(error);
918
+ const wrapped = runtimeError$1("RUNTIME.DECODE_FAILED", `Failed to decode column ${ref ? `${ref.table}.${ref.column}` : alias} with codec '${codec.id}': ${message}`, {
919
+ ...ref ? {
920
+ table: ref.table,
921
+ column: ref.column
922
+ } : { alias },
923
+ codec: codec.id,
924
+ wirePreview: previewWireValue(wireValue)
925
+ });
926
+ wrapped.cause = error;
927
+ throw wrapped;
928
+ }
929
+ function wrapIncludeAggregateFailure(error, alias, wireValue) {
930
+ const wrapped = runtimeError$1("RUNTIME.DECODE_FAILED", `Failed to parse JSON array for include alias '${alias}': ${error instanceof Error ? error.message : String(error)}`, {
931
+ alias,
932
+ wirePreview: previewWireValue(wireValue)
933
+ });
934
+ wrapped.cause = error;
935
+ throw wrapped;
936
+ }
937
+ function decodeIncludeAggregate(alias, wireValue) {
938
+ if (wireValue === null || wireValue === void 0) return [];
939
+ try {
940
+ if (typeof wireValue === "string") return JSON.parse(wireValue);
941
+ if (typeof wireValue === "object") return blindCast(wireValue);
942
+ return JSON.parse(String(wireValue));
943
+ } catch (error) {
944
+ wrapIncludeAggregateFailure(error, alias, wireValue);
945
+ }
946
+ }
947
+ /**
948
+ * 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
949
+ * no separate validator-registry pass.
950
+ *
951
+ * 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
952
+ * aliases, computed projections without a simple ref) get `column: undefined`, matching the spec contract that the runtime never silently defaults this field.
953
+ *
954
+ * For `many`-flagged aliases this function delegates frame traversal to the selected `ListDecoder`, passing `null` elements through unchanged and mapping the same element codec over every non-null element. SQL runtimes without a target-owned contribution select `sqlNativeArrayListDecoder` explicitly before row decoding. Element-level failures surface through the existing `RUNTIME.DECODE_FAILED` envelope with the column/codec context from the parent cell.
955
+ */
956
+ async function decodeField(alias, wireValue, decodeCtx, rowCtx, listDecoder) {
957
+ if (wireValue === null) return null;
958
+ const codec = decodeCtx.codecs.get(alias);
959
+ if (!codec) return wireValue;
960
+ const ref = decodeCtx.columnRefs.get(alias);
961
+ let cellCtx;
962
+ if (ref) cellCtx = {
963
+ ...rowCtx,
964
+ column: {
965
+ table: ref.table,
966
+ name: ref.column
967
+ }
968
+ };
969
+ else {
970
+ const { column: _drop, ...rowCtxWithoutColumn } = rowCtx;
971
+ cellCtx = rowCtxWithoutColumn;
972
+ }
973
+ const decodeElement = async (elem) => {
974
+ if (elem === null || elem === void 0) return null;
975
+ try {
976
+ return await codec.fromWire(elem, cellCtx);
977
+ } catch (error) {
978
+ if (isStructuredError(error)) throw error;
979
+ wrapDecodeFailure(error, alias, ref, codec, elem);
980
+ }
981
+ };
982
+ if (decodeCtx.manyAliases.has(alias)) try {
983
+ return await listDecoder(wireValue, decodeElement);
984
+ } catch (error) {
985
+ if (isStructuredError(error)) throw error;
986
+ wrapDecodeFailure(error, alias, ref, codec, wireValue);
987
+ }
988
+ try {
989
+ return await codec.fromWire(wireValue, cellCtx);
990
+ } catch (error) {
991
+ if (isStructuredError(error)) throw error;
992
+ wrapDecodeFailure(error, alias, ref, codec, wireValue);
993
+ }
994
+ }
995
+ /**
996
+ * Decodes a row by dispatching all per-cell codec calls concurrently via `Promise.all`. Each cell follows the single-armed `decodeField` path. Structured envelopes thrown by codec bodies (anything passing `isStructuredError`) and an `InternalError` pass through unchanged; all other 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`.
997
+ *
998
+ * When `rowCtx.signal` is provided:
999
+ *
1000
+ * - **Already-aborted at entry** short-circuits with `RUNTIME.ABORTED` (`{ phase: 'decode' }`) before any `codec.decode` call is made.
1001
+ * - **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).
1002
+ * - Existing structured envelopes (any dotted-code error passing `isStructuredError`, e.g. `RUNTIME.DECODE_FAILED`) from codec bodies pass through unchanged (no double wrap).
1003
+ */
1004
+ async function decodeRow(row, decodeCtx, rowCtx, listDecoder) {
1005
+ checkAborted(rowCtx, "decode");
1006
+ const signal = rowCtx.signal;
1007
+ const aliases = decodeCtx.aliases ?? Object.keys(row);
1008
+ if (decodeCtx.aliases !== void 0) {
1009
+ for (const alias of decodeCtx.aliases) if (!Object.hasOwn(row, alias)) throw decodeCtx.aliasSource === "row-spec" ? runtimeError$1("RUNTIME.RAW_ROW_COLUMN_MISSING", `Raw statement result has no column "${alias}", which its row spec declares`, {
1010
+ column: alias,
1011
+ declaredColumns: decodeCtx.aliases,
1012
+ resultColumns: Object.keys(row)
1013
+ }) : runtimeError$1("RUNTIME.DECODE_FAILED", `Row missing projection alias "${alias}"`, {
1014
+ alias,
1015
+ expectedAliases: decodeCtx.aliases,
1016
+ presentKeys: Object.keys(row)
1017
+ });
1018
+ }
1019
+ const tasks = [];
1020
+ const includeIndices = [];
1021
+ for (let i = 0; i < aliases.length; i++) {
1022
+ const alias = blindCast(aliases[i]);
1023
+ const wireValue = row[alias];
1024
+ if (decodeCtx.includeAliases.has(alias)) {
1025
+ includeIndices.push({
1026
+ index: i,
1027
+ alias,
1028
+ value: wireValue
1029
+ });
1030
+ tasks.push(Promise.resolve(void 0));
1031
+ continue;
1032
+ }
1033
+ tasks.push(decodeField(alias, wireValue, decodeCtx, rowCtx, listDecoder));
1034
+ }
1035
+ const settled = await raceAgainstAbort(Promise.all(tasks), signal, "decode");
1036
+ for (const entry of includeIndices) settled[entry.index] = decodeIncludeAggregate(entry.alias, entry.value);
1037
+ const decoded = {};
1038
+ for (let i = 0; i < aliases.length; i++) {
1039
+ const alias = blindCast(aliases[i]);
1040
+ decoded[alias] = settled[i];
1041
+ }
1042
+ return decoded;
1043
+ }
1044
+ /**
1045
+ * Computes a stable content hash for a lowered SQL execution plan.
1046
+ *
1047
+ * Internally builds an unambiguous canonical-stringified preimage from
1048
+ * three components:
1049
+ *
1050
+ * 1. `meta.storageHash` — discriminates by schema. A migration changes the
1051
+ * storage hash, which invalidates cached entries automatically.
1052
+ * 2. `exec.sql` — the raw lowered SQL text. Two queries with different
1053
+ * structure produce different keys. Note that we deliberately do **not**
1054
+ * use `computeSqlFingerprint` here: that helper strips literals to group
1055
+ * executions by statement shape (used by telemetry), which is the
1056
+ * opposite of what a content hash needs — we want per-execution
1057
+ * discrimination, not per-statement-shape grouping.
1058
+ * 3. `exec.params` — the bound parameters. `canonicalStringify` produces a
1059
+ * deterministic serialization that is stable across object key
1060
+ * insertion order and that distinguishes types JSON would otherwise
1061
+ * conflate (e.g. `BigInt(1)` vs `1`).
1062
+ *
1063
+ * The components are wrapped in an object and canonicalized as a single
1064
+ * unit (rather than concatenated with a delimiter) so component
1065
+ * boundaries are unambiguous: any character appearing inside `sql` or
1066
+ * `storageHash` cannot bleed across components and produce a collision
1067
+ * with a different split of the same characters.
1068
+ *
1069
+ * The canonical string is then piped through `hashContent` to produce a
1070
+ * bounded, opaque digest. See `@internal/utils/hash-content` for the
1071
+ * rationale.
1072
+ *
1073
+ * @internal
1074
+ */
1075
+ function computeSqlContentHash(exec) {
1076
+ return hashContent(canonicalStringify({
1077
+ storageHash: exec.meta.storageHash,
1078
+ sql: exec.sql,
1079
+ params: exec.params
1080
+ }));
1081
+ }
1082
+ const STRING_LITERAL_REGEX = /'(?:''|[^'])*'/g;
1083
+ const NUMERIC_LITERAL_REGEX = /\b\d+(?:\.\d+)?\b/g;
1084
+ const WHITESPACE_REGEX = /\s+/g;
1085
+ /**
1086
+ * Computes a literal-stripped, normalized fingerprint of a SQL statement.
1087
+ *
1088
+ * The function strips string and numeric literals, collapses whitespace, and
1089
+ * lowercases the result before hashing — so two structurally equivalent
1090
+ * statements (with different parameter values) produce the same fingerprint.
1091
+ * Used by SQL telemetry to group queries.
1092
+ */
1093
+ function computeSqlFingerprint(sql) {
1094
+ const normalized = sql.replace(STRING_LITERAL_REGEX, "?").replace(NUMERIC_LITERAL_REGEX, "?").replace(WHITESPACE_REGEX, " ").trim().toLowerCase();
1095
+ return createHash("sha256").update(normalized).digest("hex");
1096
+ }
1097
+ var InFlightOperations = class {
1098
+ #count = 0;
1099
+ #started = 0;
1100
+ #drainWaiters = [];
1101
+ /** Records one operation as in flight; the returned callback ends it and ignores later calls. */
1102
+ begin() {
1103
+ this.#count += 1;
1104
+ this.#started += 1;
1105
+ let ended = false;
1106
+ return () => {
1107
+ if (ended) return;
1108
+ ended = true;
1109
+ this.#count -= 1;
1110
+ if (this.#count === 0) for (const wake of this.#drainWaiters.splice(0)) wake();
1111
+ };
1112
+ }
1113
+ /** How many operations have begun so far; a change between two reads means work started in between. */
1114
+ get started() {
1115
+ return this.#started;
1116
+ }
1117
+ /** How many operations are in flight. */
1118
+ get count() {
1119
+ return this.#count;
1120
+ }
1121
+ get active() {
1122
+ return this.#count > 0;
1123
+ }
1124
+ drained() {
1125
+ if (this.#count === 0) return Promise.resolve();
1126
+ return new Promise((resolve) => this.#drainWaiters.push(resolve));
1127
+ }
1128
+ /** Records `work` as in flight until it settles. */
1129
+ async track(work) {
1130
+ const end = this.begin();
1131
+ try {
1132
+ return await work();
1133
+ } finally {
1134
+ end();
1135
+ }
1136
+ }
1137
+ };
1138
+ async function runBeforeCompileChain(middleware, initial, ctx) {
1139
+ let current = initial;
1140
+ for (const mw of middleware) {
1141
+ if (!mw.beforeCompile) continue;
1142
+ const result = await mw.beforeCompile(current, ctx);
1143
+ if (result === void 0) continue;
1144
+ if (result.ast === current.ast) continue;
1145
+ ctx.log.debug?.({
1146
+ event: "middleware.rewrite",
1147
+ middleware: mw.name,
1148
+ lane: current.meta.lane
1149
+ });
1150
+ current = result;
1151
+ }
1152
+ return current;
1153
+ }
1154
+ /**
1155
+ * Begins a transaction on a connection the transaction owns. The connection is released once the transaction has committed or rolled back, and destroyed after a failed rollback or a failed start other than a refusal before `BEGIN`. After a failed commit it stays held for the rollback that follows. A release that fails after the transaction settled is passed to `log.error`, and `commit()` or `rollback()` keeps its own outcome.
1156
+ */
1157
+ async function beginOwnedTransaction(connection, begin, log) {
1158
+ let transaction;
1159
+ try {
1160
+ transaction = await begin();
1161
+ } catch (error) {
1162
+ if (wasRefusedBeforeBegin(connection)) await connection.release().catch(() => void 0);
1163
+ else await connection.destroy(error).catch(() => void 0);
1164
+ throw error;
1165
+ }
1166
+ let disposed = false;
1167
+ const dispose = async (destroyReason) => {
1168
+ if (disposed) return;
1169
+ disposed = true;
1170
+ if (destroyReason !== void 0) {
1171
+ await connection.destroy(destroyReason.reason).catch(() => void 0);
1172
+ return;
1173
+ }
1174
+ try {
1175
+ await connection.release();
1176
+ } catch (error) {
1177
+ try {
1178
+ log.error({
1179
+ event: "runtime.connection.release.error",
1180
+ error
1181
+ });
1182
+ } catch {}
1183
+ }
1184
+ };
1185
+ return Object.assign(castAs(Object.create(transaction)), {
1186
+ async commit() {
1187
+ try {
1188
+ await transaction.commit();
1189
+ } catch (error) {
1190
+ if (settlementOf(transaction)?.committed === true) await dispose();
1191
+ throw error;
1192
+ }
1193
+ await dispose();
1194
+ },
1195
+ async rollback() {
1196
+ try {
1197
+ await transaction.rollback();
1198
+ } catch (error) {
1199
+ const settlement = settlementOf(transaction);
1200
+ const settled = settlement?.rolledBack === true || settlement?.committed === true;
1201
+ await dispose(settled ? void 0 : { reason: error });
1202
+ throw error;
1203
+ }
1204
+ await dispose();
1205
+ }
1206
+ });
1207
+ }
1208
+ function normalizeSpec(spec) {
1209
+ if (typeof spec === "string") return {
1210
+ codec: { codecId: spec },
1211
+ nullable: false
1212
+ };
1213
+ return {
1214
+ codec: spec.typeParams !== void 0 ? {
1215
+ codecId: spec.codecId,
1216
+ typeParams: spec.typeParams
1217
+ } : { codecId: spec.codecId },
1218
+ nullable: spec.nullable === true
1219
+ };
1220
+ }
1221
+ var BindSiteExpression = class {
1222
+ returnType;
1223
+ #ast;
1224
+ constructor(ref, returnType) {
1225
+ this.#ast = ref;
1226
+ this.returnType = returnType;
1227
+ }
1228
+ buildAst() {
1229
+ return this.#ast;
1230
+ }
1231
+ };
1232
+ function buildBindSiteParams(declaration) {
1233
+ const params = {};
1234
+ for (const [name, spec] of Object.entries(declaration)) {
1235
+ const { codec, nullable } = normalizeSpec(spec);
1236
+ params[name] = new BindSiteExpression(PreparedParamRef.of(name, codec, nullable), {
1237
+ codecId: codec.codecId,
1238
+ nullable
1239
+ });
1240
+ }
1241
+ return blindCast(Object.freeze(params));
1242
+ }
1243
+ /**
1244
+ * Resolve a PreparedStatement's slot order to the unencoded values it
1245
+ * will carry into encode. Literal slots come from the lowered AST;
1246
+ * bind slots are looked up by name on `userParams`. Missing user params
1247
+ * surface `RUNTIME.PREPARE_MISSING_PARAM` so the caller cannot silently
1248
+ * bind `undefined`.
1249
+ */
1250
+ function isRecord(value) {
1251
+ return typeof value === "object" && value !== null;
1252
+ }
1253
+ function resolvePreparedSlotValues(ps, userParams) {
1254
+ if (!isRecord(userParams)) throw runtimeError$1("RUNTIME.PREPARE_MISSING_PARAM", "Prepared statement parameters must be an object");
1255
+ return ps.slots.map((slot) => {
1256
+ if (slot.kind === "literal") return slot.value;
1257
+ if (!Object.hasOwn(userParams, slot.name)) throw runtimeError$1("RUNTIME.PREPARE_MISSING_PARAM", `Prepared statement query is missing parameter '${slot.name}'`, { name: slot.name });
1258
+ return userParams[slot.name];
1259
+ });
1260
+ }
1261
+ async function encodePreparedParams(ps, userParams, ctx, contractCodecs) {
1262
+ return encodeParamsWithMetadata(resolvePreparedSlotValues(ps, userParams), ps.paramMetadata, ctx, contractCodecs);
1263
+ }
1264
+ var SqlFamilyAdapter = class {
1265
+ contract;
1266
+ markerReader;
1267
+ constructor(contract, adapterProfile) {
1268
+ this.contract = contract;
1269
+ this.markerReader = adapterProfile;
1270
+ }
1271
+ validatePlan(plan, contract) {
1272
+ if (plan.meta.target !== contract.target) throw runtimeError$1("PLAN.TARGET_MISMATCH", "Plan target does not match runtime target", {
1273
+ planTarget: plan.meta.target,
1274
+ runtimeTarget: contract.target
1275
+ });
1276
+ if (plan.meta.storageHash !== contract.storage.storageHash) throw runtimeError$1("PLAN.HASH_MISMATCH", "Plan storage hash does not match runtime contract", {
1277
+ planStorageHash: plan.meta.storageHash,
1278
+ runtimeStorageHash: contract.storage.storageHash
1279
+ });
1280
+ }
1281
+ };
1282
+ function isExecutionPlan(plan) {
1283
+ return "sql" in plan;
1284
+ }
1285
+ function isBoundPreparedStatement(subject) {
1286
+ return "statement" in subject;
1287
+ }
1288
+ function isPreparedStatementInternals(statement) {
1289
+ return statement instanceof PreparedStatementImpl || statement instanceof PreparedExecutionImpl;
1290
+ }
1291
+ // v8 ignore next 2
1292
+ const noopLogSink = () => {};
1293
+ const noopLog = {
1294
+ info: noopLogSink,
1295
+ warn: noopLogSink,
1296
+ error: noopLogSink
1297
+ };
1298
+ const scheduleTimer = globalThis.setTimeout;
1299
+ function runtimeClosedError() {
1300
+ return structuredError("DRIVER.NOT_CONNECTED", "Runtime is closed", {
1301
+ why: "close() was called on this runtime, or on the client or connection that owns it. An await using scope calls close() when it ends.",
1302
+ fix: "Await every query, transaction and prepared statement before close(). The usual cause is a query returned without await from an await using scope."
1303
+ });
1304
+ }
1305
+ /**
1306
+ * Abstract family-layer base for SQL runtimes. Subclass to build a target runtime
1307
+ * (e.g. `PostgresRuntimeImpl`); app code should consume the `Runtime` interface returned
1308
+ * by the target factories, never this class directly.
1309
+ */
1310
+ var SqlRuntimeBase = class extends RuntimeCore {
1311
+ contract;
1312
+ adapter;
1313
+ driver;
1314
+ familyAdapter;
1315
+ contractCodecs;
1316
+ codecDescriptors;
1317
+ sqlCtx;
1318
+ verifyMarkerOption;
1319
+ verifyMarkerPromise;
1320
+ closePromise;
1321
+ closeRefusal;
1322
+ #inFlight = new InFlightOperations();
1323
+ #openActivityHandles = 0;
1324
+ #refusing = false;
1325
+ #heldQueryables = /* @__PURE__ */ new WeakSet();
1326
+ #preparedStatementHandles = /* @__PURE__ */ new WeakMap();
1327
+ #holdFinishedChildren;
1328
+ #openTransactions = /* @__PURE__ */ new WeakMap();
1329
+ codecRegistryValidated;
1330
+ _telemetry;
1331
+ constructor(options) {
1332
+ const { context, adapter, driver, verifyMarker, middleware, mode, log, closeRefusal } = options;
1333
+ if (middleware) for (const mw of middleware) checkMiddlewareCompatibility(mw, "sql", context.contract.target);
1334
+ const sqlCtx = {
1335
+ contract: context.contract,
1336
+ mode: mode ?? "strict",
1337
+ now: () => Date.now(),
1338
+ log: log ?? noopLog,
1339
+ contentHash: (exec) => computeSqlContentHash(blindCast(exec)),
1340
+ scope: "runtime"
1341
+ };
1342
+ super({
1343
+ middleware: middleware ?? [],
1344
+ ctx: sqlCtx
1345
+ });
1346
+ this.#holdFinishedChildren = this.middleware.some((mw) => mw.activityFinalized !== void 0);
1347
+ this.contract = context.contract;
1348
+ this.adapter = adapter;
1349
+ this.driver = driver;
1350
+ this.familyAdapter = new SqlFamilyAdapter(context.contract, adapter.profile);
1351
+ this.contractCodecs = context.contractCodecs;
1352
+ this.codecDescriptors = context.codecDescriptors;
1353
+ this.sqlCtx = sqlCtx;
1354
+ this.verifyMarkerOption = verifyMarker ?? "onFirstUse";
1355
+ this.closeRefusal = closeRefusal ?? "when-idle";
1356
+ this.codecRegistryValidated = false;
1357
+ this.verifyMarkerPromise = this.verifyMarkerOption === false ? Promise.resolve() : null;
1358
+ this.closePromise = null;
1359
+ this._telemetry = null;
1360
+ }
1361
+ /**
1362
+ * Lower a `SqlQueryPlan` (AST + meta) into a `SqlExecutionPlan`
1363
+ * with encoded parameters ready for the driver.
1364
+ *
1365
+ * Implementation note: SQL splits lower-then-encode across
1366
+ * {@link lowerToDraft} + {@link encodeDraftParams} so the selected
1367
+ * operation's middleware chain can run between them:
1368
+ * {@link prepareOperation} runs the statement's `beforeEncode` chain
1369
+ * between them (cipherstash bulk-encrypt, for example, mutates pre-encode
1370
+ * `ParamRef.value` slots). This protected hook composes the two back into
1371
+ * the cross-family `lower()` shape `RuntimeCore` expects. The production
1372
+ * operation methods use the matching split form before driver execution.
1373
+ *
1374
+ * `ctx: SqlCodecCallContext` is forwarded to `encodeParams` so
1375
+ * per-query cancellation reaches every codec body during parameter
1376
+ * encoding. SQL params do not populate `ctx.column` — encode-side
1377
+ * column metadata is the middleware's domain.
1378
+ */
1379
+ async lower(plan, ctx) {
1380
+ const draft = this.lowerToDraft(plan);
1381
+ return await this.encodeDraftParams(draft, ctx);
1382
+ }
1383
+ /**
1384
+ * AST → pre-encode draft for the selected `query()` or `execute()` operation.
1385
+ * The returned plan has `sql` rendered and `params` populated with the
1386
+ * user-domain values the lowering site collected from `ParamRef` nodes. No
1387
+ * codec encode has happened yet; consumers can mutate `params` via the
1388
+ * `SqlParamRefMutator` before {@link encodeDraftParams} runs.
1389
+ */
1390
+ lowerToDraft(plan) {
1391
+ return lowerSqlPlan(this.adapter, this.contract, plan);
1392
+ }
1393
+ /**
1394
+ * Encode a draft plan's params for the selected `query()` or `execute()`
1395
+ * operation through the per-column codecs and freeze the result into the
1396
+ * final `SqlExecutionPlan` the driver sees. Errors surface as
1397
+ * `RUNTIME.ENCODE_FAILED` envelopes from {@link encodeParams}.
1398
+ */
1399
+ async encodeDraftParams(draft, ctx) {
1400
+ return Object.freeze({
1401
+ ...draft,
1402
+ params: await encodeParams(draft, ctx, this.contractCodecs)
1403
+ });
1404
+ }
1405
+ /** Default query invocation required by the abstract `RuntimeCore` contract. */
1406
+ // v8 ignore next 6
1407
+ runDriver(exec) {
1408
+ return this.driver.query({
1409
+ sql: exec.sql,
1410
+ params: exec.params
1411
+ });
1412
+ }
1413
+ runExecute(exec) {
1414
+ return this.driver.execute({
1415
+ sql: exec.sql,
1416
+ params: exec.params
1417
+ });
1418
+ }
1419
+ /**
1420
+ * 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 lowering adapter and the encoder both walk the rewritten AST directly.
1421
+ */
1422
+ async compilePlan(plan, middlewareCtx) {
1423
+ const rewrittenDraft = await runBeforeCompileChain(this.middleware, {
1424
+ ast: plan.ast,
1425
+ meta: plan.meta
1426
+ }, middlewareCtx);
1427
+ return rewrittenDraft.ast === plan.ast ? plan : {
1428
+ ...plan,
1429
+ ast: rewrittenDraft.ast,
1430
+ meta: rewrittenDraft.meta
1431
+ };
1432
+ }
1433
+ query(plan, options) {
1434
+ return this.queryAgainstQueryable(plan, this.driver, options);
1435
+ }
1436
+ execute(plan, options) {
1437
+ return this.executeStatisticsAgainstQueryable(plan, this.driver, options);
1438
+ }
1439
+ [preparedStatementQuery](ps, params, options) {
1440
+ return this.runPreparedQueryAgainstQueryable(blindCast(ps), params, this.driver, options);
1441
+ }
1442
+ [preparedStatementExecute](ps, params, options) {
1443
+ return this.runPreparedExecuteAgainstQueryable(blindCast(ps), params, this.driver, options);
1444
+ }
1445
+ #runtimeRoute;
1446
+ [activitySource] = {
1447
+ startActivity: (activity) => {
1448
+ this.#runtimeRoute ??= this.queryableRoute(this.driver, "runtime", () => void 0, {
1449
+ transaction: (parent) => this.beginOwnedTransactionBeneath(parent),
1450
+ runtimeWork: true
1451
+ });
1452
+ return this.startActivityOnRoute(this.#runtimeRoute, activity, void 0);
1453
+ },
1454
+ planContentHash: (plan) => this.planContentHashFor(plan, "runtime", void 0)
1455
+ };
1456
+ /**
1457
+ * Starts a client activity whose statements and transaction run on `route`, beneath `parent` or, without one, beneath `route.parent()`.
1458
+ */
1459
+ startActivityOnRoute(route, activity, parent) {
1460
+ let endWork = noWork;
1461
+ let started;
1462
+ try {
1463
+ endWork = route.runtimeWork ? this.beginActivityWork(parent) : noWork;
1464
+ started = startClientActivity(activity, {
1465
+ middleware: this.middleware,
1466
+ context: route.scope === "runtime" ? this.sqlCtx : {
1467
+ ...this.sqlCtx,
1468
+ scope: route.scope
1469
+ },
1470
+ parent: parent ?? route.parent(),
1471
+ holdFinishedChildren: this.#holdFinishedChildren
1472
+ });
1473
+ } catch (error) {
1474
+ endWork();
1475
+ return Promise.reject(error);
1476
+ }
1477
+ return started.then((run) => this.activityHandle(run, route, endWork), (error) => {
1478
+ endWork();
1479
+ throw error;
1480
+ });
1481
+ }
1482
+ /** The content hash of the statement `plan` would send on a queryable of `scope` beneath `parent`. */
1483
+ async planContentHashFor(plan, scope, parent) {
1484
+ this.ensureCodecRegistryValidated();
1485
+ if (isBoundPreparedStatement(plan)) {
1486
+ const { statement, params } = plan;
1487
+ invariant(isPreparedStatementInternals(statement), "The prepared statement was not prepared by a SQL runtime");
1488
+ return computeSqlContentHash({
1489
+ sql: statement.sql,
1490
+ ast: statement.ast,
1491
+ meta: statement.meta,
1492
+ params: await encodePreparedParams(statement, params, {}, this.contractCodecs)
1493
+ });
1494
+ }
1495
+ if (isExecutionPlan(plan)) return computeSqlContentHash(Object.freeze({
1496
+ ...plan,
1497
+ params: await encodeParams(plan, {}, this.contractCodecs)
1498
+ }));
1499
+ const compiled = await this.compilePlan(plan, createContextBeneath({
1500
+ ...this.sqlCtx,
1501
+ scope
1502
+ }, parent));
1503
+ return computeSqlContentHash(await this.encodeDraftParams(this.lowerToDraft(compiled), {}));
1504
+ }
1505
+ /** An activity route whose statements run on `queryable`. */
1506
+ queryableRoute(queryable, scope, parent, options) {
1507
+ return {
1508
+ scope,
1509
+ parent,
1510
+ ...options,
1511
+ query: (plan, executeOptions) => this.queryAgainstQueryable(plan, queryable, {
1512
+ ...executeOptions,
1513
+ scope
1514
+ }),
1515
+ execute: (plan, executeOptions) => this.executeStatisticsAgainstQueryable(plan, queryable, {
1516
+ ...executeOptions,
1517
+ scope
1518
+ }),
1519
+ preparedQuery: (statement, params, executeOptions) => this.runPreparedQueryAgainstQueryable(statement, params, queryable, {
1520
+ ...executeOptions,
1521
+ scope
1522
+ }),
1523
+ preparedExecute: (statement, params, executeOptions) => this.runPreparedExecuteAgainstQueryable(statement, params, queryable, {
1524
+ ...executeOptions,
1525
+ scope
1526
+ })
1527
+ };
1528
+ }
1529
+ /** The activity route of a held connection that `owner` wraps: its statements run on `connection`, and its transactions begin there. */
1530
+ connectionRoute(connection, owner) {
1531
+ const openTransaction = this.#openTransactions.get(connection);
1532
+ return this.queryableRoute(connection, "connection", () => openTransaction, {
1533
+ transaction: openTransaction === void 0 ? (parent) => this.beginTransactionBeneathOn(connection, owner, parent) : void 0,
1534
+ runtimeWork: false
1535
+ });
1536
+ }
1537
+ beginTransactionBeneathOn(connection, owner, parent) {
1538
+ const openTransaction = this.#openTransactions.get(connection);
1539
+ if (openTransaction !== void 0) return Promise.reject(transactionAlreadyOpenError(parent, openTransaction));
1540
+ return this.beginTransactionOn(connection, owner, parent);
1541
+ }
1542
+ /** The `activitySource` member of a held connection that `owner()` wraps. */
1543
+ connectionActivitySource(connection, owner) {
1544
+ return {
1545
+ startActivity: (activity) => this.startActivityOnRoute(this.connectionRoute(connection, owner()), activity, void 0),
1546
+ planContentHash: (plan) => this.planContentHashFor(plan, "connection", this.#openTransactions.get(connection))
1547
+ };
1548
+ }
1549
+ activityHandle(run, route, endWork) {
1550
+ const beneath = (options) => ({
1551
+ ...options,
1552
+ activityParent: run
1553
+ });
1554
+ const source = {
1555
+ startActivity: (activity) => {
1556
+ try {
1557
+ run.assertAcceptsChildren();
1558
+ } catch (error) {
1559
+ return Promise.reject(error);
1560
+ }
1561
+ return this.startActivityOnRoute(route, activity, run);
1562
+ },
1563
+ planContentHash: (plan) => this.planContentHashFor(plan, route.scope, run)
1564
+ };
1565
+ const handle = {
1566
+ [activitySource]: source,
1567
+ startActivity: source.startActivity,
1568
+ planContentHash: source.planContentHash,
1569
+ activity: run.activity,
1570
+ get intercepted() {
1571
+ return run.intercepted;
1572
+ },
1573
+ query(plan, options) {
1574
+ return rowsBeneath(run, () => route.query(plan, beneath(options)));
1575
+ },
1576
+ async execute(plan, options) {
1577
+ run.assertAcceptsChildren();
1578
+ return route.execute(plan, beneath(options));
1579
+ },
1580
+ [preparedStatementQuery](ps, params, options) {
1581
+ return rowsBeneath(run, () => route.preparedQuery(blindCast(ps), params, beneath(options)));
1582
+ },
1583
+ async [preparedStatementExecute](ps, params, options) {
1584
+ run.assertAcceptsChildren();
1585
+ return route.preparedExecute(blindCast(ps), params, beneath(options));
1586
+ },
1587
+ end(end) {
1588
+ let ending;
1589
+ try {
1590
+ ending = run.end(end);
1591
+ } catch (error) {
1592
+ endWork();
1593
+ return Promise.reject(error);
1594
+ }
1595
+ return ending.then(endWork, (error) => {
1596
+ endWork();
1597
+ throw error;
1598
+ });
1599
+ }
1600
+ };
1601
+ const beginTransaction = route.transaction;
1602
+ if (beginTransaction === void 0) return handle;
1603
+ return Object.assign(handle, { transaction: async () => {
1604
+ run.assertAcceptsChildren();
1605
+ return beginTransaction(run);
1606
+ } });
1607
+ }
1608
+ async beginOwnedTransactionBeneath(parent) {
1609
+ const { driverConnection, connection } = await this.openConnection({ activityParent: parent });
1610
+ return this.ownTransaction(connection, () => this.beginTransactionOn(driverConnection, connection, parent));
1611
+ }
1612
+ /** Begins a transaction with `begin` on a connection the transaction owns; see `beginOwnedTransaction`. */
1613
+ ownTransaction(connection, begin) {
1614
+ return beginOwnedTransaction(connection, begin, this.sqlCtx.log);
1615
+ }
1616
+ /**
1617
+ * Returns the raw driver connection. The connection is a `SqlQueryable` — SQL
1618
+ * issued on it runs below the middleware/codec/telemetry pipeline. It carries
1619
+ * its own lifecycle (`release`/`destroy`/`beginTransaction`); the caller owns
1620
+ * disposal.
1621
+ */
1622
+ acquireRawConnection() {
1623
+ return this.driver.acquireConnection();
1624
+ }
1625
+ async setupDriverExecution(exec, queryable) {
1626
+ this.familyAdapter.validatePlan(exec, this.contract);
1627
+ this._telemetry = null;
1628
+ if (this.verifyMarkerPromise === null) {
1629
+ const markerQueryable = this.#heldQueryables.has(queryable) ? queryable : this.driver;
1630
+ this.verifyMarkerPromise = this.verifyMarker(markerQueryable).catch((error) => {
1631
+ this.verifyMarkerPromise = null;
1632
+ throw error;
1633
+ });
1634
+ }
1635
+ await this.verifyMarkerPromise;
1636
+ }
1637
+ beginActivityWork(parent) {
1638
+ const endOperation = this.beginOperation(this.driver, { activityParent: parent });
1639
+ this.#openActivityHandles++;
1640
+ let ended = false;
1641
+ return () => {
1642
+ if (ended) return;
1643
+ ended = true;
1644
+ this.#openActivityHandles--;
1645
+ endOperation();
1646
+ };
1647
+ }
1648
+ warnWhenWaitingOnlyForActivities(warned) {
1649
+ if (warned.done || this.#openActivityHandles === 0) return;
1650
+ if (this.#inFlight.count !== this.#openActivityHandles) return;
1651
+ warned.done = true;
1652
+ this.sqlCtx.log.warn({
1653
+ event: "runtime.close.waiting-for-activities",
1654
+ openActivities: this.#openActivityHandles
1655
+ });
1656
+ }
1657
+ beginOperation(queryable, options) {
1658
+ if (queryable === this.driver && this.#refusing && options?.activityParent === void 0) throw runtimeClosedError();
1659
+ return this.#inFlight.begin();
1660
+ }
1661
+ parentOf(queryable, options) {
1662
+ const openTransaction = this.#openTransactions.get(queryable);
1663
+ const activityParent = options?.activityParent;
1664
+ if (activityParent === void 0) return openTransaction;
1665
+ if (openTransaction !== void 0 && !activityParent.ctx.inTransaction()) throw activityOutsideTransactionError(activityParent, openTransaction);
1666
+ return activityParent;
1667
+ }
1668
+ getListDecoder() {
1669
+ return sqlNativeArrayListDecoder;
1670
+ }
1671
+ async *streamDecodedRows(run, exec, createDecodeContext, driverCall, codecCtx, queryable, onDriverAnswered) {
1672
+ const rowDecodeContext = createDecodeContext();
1673
+ await this.setupDriverExecution(exec, queryable);
1674
+ const startedAt = Date.now();
1675
+ let telemetryOutcome = null;
1676
+ try {
1677
+ const iterator = run.runQuery(exec, driverCall)[Symbol.asyncIterator]();
1678
+ let answered = false;
1679
+ try {
1680
+ while (true) {
1681
+ checkAborted(codecCtx, "stream");
1682
+ const next = answered ? await iterator.next() : await iterator.next().finally(onDriverAnswered);
1683
+ answered = true;
1684
+ if (next.done) break;
1685
+ yield blindCast(await decodeRow(next.value, rowDecodeContext, codecCtx, this.getListDecoder()));
1686
+ }
1687
+ } finally {
1688
+ await iterator.return?.();
1689
+ }
1690
+ telemetryOutcome = "success";
1691
+ } catch (error) {
1692
+ telemetryOutcome = "runtime-error";
1693
+ throw error;
1694
+ } finally {
1695
+ if (telemetryOutcome !== null) this.recordTelemetry(exec, telemetryOutcome, Date.now() - startedAt);
1696
+ }
1697
+ }
1698
+ createQueryContexts(options) {
1699
+ const signal = options?.signal;
1700
+ const scope = options?.scope ?? "runtime";
1701
+ return {
1702
+ codecCtx: signal === void 0 ? {} : { signal },
1703
+ contextFields: {
1704
+ ...this.sqlCtx,
1705
+ ...ifDefined("signal", signal),
1706
+ ...scope !== "runtime" ? { scope } : {}
1707
+ }
1708
+ };
1709
+ }
1710
+ async prepareOperation(plan, codecCtx, run) {
1711
+ checkAborted(codecCtx, "stream");
1712
+ if (isExecutionPlan(plan)) {
1713
+ const mutator = createSqlParamRefMutator(plan);
1714
+ await run.beforeEncode(plan, mutator);
1715
+ return Object.freeze({
1716
+ ...plan,
1717
+ params: await encodeParams({
1718
+ ...plan,
1719
+ params: mutator.currentParams()
1720
+ }, codecCtx, this.contractCodecs)
1721
+ });
1722
+ }
1723
+ const compiled = await this.compilePlan(plan, run.ctx);
1724
+ const draft = this.lowerToDraft(compiled);
1725
+ const mutator = createSqlParamRefMutator(draft);
1726
+ await run.beforeEncode(draft, mutator);
1727
+ const draftWithMutations = Object.freeze({
1728
+ ...draft,
1729
+ params: mutator.currentParams()
1730
+ });
1731
+ return this.encodeDraftParams(draftWithMutations, codecCtx);
1732
+ }
1733
+ /** Query rows against a caller-supplied queryable through the shared preparation pipeline. */
1734
+ queryAgainstQueryable(plan, queryable, options) {
1735
+ this.ensureCodecRegistryValidated();
1736
+ const self = this;
1737
+ const { codecCtx, contextFields } = this.createQueryContexts(options);
1738
+ const generator = async function* () {
1739
+ const endOperation = self.beginOperation(queryable, options);
1740
+ try {
1741
+ yield* streamStatement({
1742
+ middleware: self.middleware,
1743
+ plan,
1744
+ context: contextFields,
1745
+ parent: self.parentOf(queryable, options)
1746
+ }, async function* (run) {
1747
+ const exec = await self.prepareOperation(plan, codecCtx, run);
1748
+ yield* self.streamDecodedRows(run, exec, () => buildDecodeContext(exec.ast, self.contractCodecs), () => queryable.query({
1749
+ sql: exec.sql,
1750
+ params: exec.params
1751
+ }), codecCtx, queryable, endOperation);
1752
+ });
1753
+ } finally {
1754
+ endOperation();
1755
+ }
1756
+ };
1757
+ return new AsyncIterableResult(generator());
1758
+ }
1759
+ /** Execute statistics against a caller-supplied queryable through the shared preparation pipeline. */
1760
+ async executeStatisticsAgainstQueryable(plan, queryable, options) {
1761
+ const endOperation = this.beginOperation(queryable, options);
1762
+ try {
1763
+ return await this.executeStatisticsInFlight(plan, queryable, options);
1764
+ } finally {
1765
+ endOperation();
1766
+ }
1767
+ }
1768
+ async executeStatisticsInFlight(plan, queryable, options) {
1769
+ this.ensureCodecRegistryValidated();
1770
+ const { codecCtx, contextFields } = this.createQueryContexts(options);
1771
+ return this.executeOn(queryable, options, plan, contextFields, async (run) => {
1772
+ const exec = await this.prepareOperation(plan, codecCtx, run);
1773
+ return this.executeEncodedPlan(run, exec, queryable, codecCtx, () => queryable.execute({
1774
+ sql: exec.sql,
1775
+ params: exec.params
1776
+ }));
1777
+ });
1778
+ }
1779
+ executeOn(queryable, options, plan, context, body) {
1780
+ return executeStatement({
1781
+ middleware: this.middleware,
1782
+ plan,
1783
+ context,
1784
+ parent: this.parentOf(queryable, options)
1785
+ }, body);
1786
+ }
1787
+ async executeEncodedPlan(run, exec, queryable, codecCtx, driverCall) {
1788
+ await this.setupDriverExecution(exec, queryable);
1789
+ checkAborted(codecCtx, "stream");
1790
+ const startedAt = Date.now();
1791
+ let telemetryOutcome = "success";
1792
+ try {
1793
+ return await run.runExecute(exec, driverCall);
1794
+ } catch (error) {
1795
+ telemetryOutcome = "runtime-error";
1796
+ throw error;
1797
+ } finally {
1798
+ this.recordTelemetry(exec, telemetryOutcome, Date.now() - startedAt);
1799
+ }
1800
+ }
1801
+ async prepare(declaration, callback) {
1802
+ this.ensureCodecRegistryValidated();
1803
+ const userPlan = callback(buildBindSiteParams(declaration));
1804
+ const finalPlan = await this.compilePlan(userPlan, createContextBeneath(this.sqlCtx, void 0));
1805
+ const orderedRefs = collectOrderedParamRefs(finalPlan.ast);
1806
+ const referencedNames = /* @__PURE__ */ new Set();
1807
+ for (const ref of orderedRefs) if (ref.kind === "prepared-param-ref") referencedNames.add(ref.name);
1808
+ const missing = Object.keys(declaration).filter((name) => !referencedNames.has(name));
1809
+ if (missing.length > 0) throw runtimeError$1("RUNTIME.PREPARE_UNUSED_PARAM", `Prepared statement declaration includes parameter${missing.length === 1 ? "" : "s"} not referenced by the callback's plan: ${missing.join(", ")}`, { unused: missing });
1810
+ const lowered = this.adapter.lower(finalPlan.ast, {
1811
+ contract: this.contract,
1812
+ params: orderedRefs.map((r) => r.kind === "param-ref" ? r.value : void 0)
1813
+ });
1814
+ const decodeContext = buildDecodeContext(finalPlan.ast, this.contractCodecs);
1815
+ const paramMetadata = deriveParamMetadata(finalPlan.ast);
1816
+ const internals = Object.freeze({
1817
+ sql: lowered.sql,
1818
+ ast: finalPlan.ast,
1819
+ meta: finalPlan.meta,
1820
+ slots: lowered.params,
1821
+ decodeContext,
1822
+ paramMetadata
1823
+ });
1824
+ return blindCast(finalPlan.ast.kind === "raw-query" && finalPlan.ast.result.kind === "affected-count" ? new PreparedExecutionImpl(internals) : new PreparedStatementImpl(internals));
1825
+ }
1826
+ /** Query prepared rows against a caller-supplied queryable through the full pipeline. */
1827
+ runPreparedQueryAgainstQueryable(ps, userParams, queryable, options) {
1828
+ this.ensureCodecRegistryValidated();
1829
+ const self = this;
1830
+ const { codecCtx, contextFields } = this.createQueryContexts(options);
1831
+ const generator = async function* () {
1832
+ const endOperation = self.beginOperation(queryable, options);
1833
+ try {
1834
+ yield* streamStatement({
1835
+ middleware: self.middleware,
1836
+ plan: ps,
1837
+ context: contextFields,
1838
+ parent: self.parentOf(queryable, options)
1839
+ }, (run) => self.streamPreparedRows(run, ps, userParams, queryable, codecCtx, endOperation));
1840
+ } finally {
1841
+ endOperation();
1842
+ }
1843
+ };
1844
+ return new AsyncIterableResult(generator());
1845
+ }
1846
+ async *streamPreparedRows(run, ps, userParams, queryable, codecCtx, onDriverAnswered) {
1847
+ checkAborted(codecCtx, "stream");
1848
+ const preEncodeValues = resolvePreparedSlotValues(ps, userParams);
1849
+ const preEncodeExec = {
1850
+ sql: ps.sql,
1851
+ params: preEncodeValues,
1852
+ ast: ps.ast,
1853
+ meta: ps.meta
1854
+ };
1855
+ const mutator = createSqlParamRefMutator(preEncodeExec);
1856
+ await run.beforeEncode(preEncodeExec, mutator);
1857
+ const encodedParams = await encodeParamsWithMetadata(mutator.currentParams(), ps.paramMetadata, codecCtx, this.contractCodecs);
1858
+ const exec = {
1859
+ sql: ps.sql,
1860
+ params: encodedParams,
1861
+ ast: ps.ast,
1862
+ meta: ps.meta
1863
+ };
1864
+ const handles = this.#preparedStatementHandles;
1865
+ const request = {
1866
+ sql: exec.sql,
1867
+ params: exec.params,
1868
+ preparedStatementHandle: {
1869
+ get: () => handles.get(ps),
1870
+ set: (value) => {
1871
+ handles.set(ps, value);
1872
+ }
1873
+ }
1874
+ };
1875
+ yield* this.streamDecodedRows(run, exec, () => ps.decodeContext, () => queryable.query(request), codecCtx, queryable, onDriverAnswered);
1876
+ }
1877
+ /** Execute a prepared statement's statistics against a caller-supplied queryable through the full pipeline. */
1878
+ async runPreparedExecuteAgainstQueryable(ps, userParams, queryable, options) {
1879
+ const endOperation = this.beginOperation(queryable, options);
1880
+ try {
1881
+ return await this.runPreparedExecuteInFlight(ps, userParams, queryable, options);
1882
+ } finally {
1883
+ endOperation();
1884
+ }
1885
+ }
1886
+ async runPreparedExecuteInFlight(ps, userParams, queryable, options) {
1887
+ this.ensureCodecRegistryValidated();
1888
+ const { codecCtx, contextFields } = this.createQueryContexts(options);
1889
+ return this.executeOn(queryable, options, ps, contextFields, (run) => this.executePrepared(run, ps, userParams, queryable, codecCtx));
1890
+ }
1891
+ async executePrepared(run, ps, userParams, queryable, codecCtx) {
1892
+ checkAborted(codecCtx, "stream");
1893
+ const preEncodeValues = resolvePreparedSlotValues(ps, userParams);
1894
+ const preEncodeExec = {
1895
+ sql: ps.sql,
1896
+ params: preEncodeValues,
1897
+ ast: ps.ast,
1898
+ meta: ps.meta
1899
+ };
1900
+ const mutator = createSqlParamRefMutator(preEncodeExec);
1901
+ await run.beforeEncode(preEncodeExec, mutator);
1902
+ const exec = {
1903
+ sql: ps.sql,
1904
+ params: await encodeParamsWithMetadata(mutator.currentParams(), ps.paramMetadata, codecCtx, this.contractCodecs),
1905
+ ast: ps.ast,
1906
+ meta: ps.meta
1907
+ };
1908
+ const handles = this.#preparedStatementHandles;
1909
+ const request = {
1910
+ sql: exec.sql,
1911
+ params: exec.params,
1912
+ preparedStatementHandle: {
1913
+ get: () => handles.get(ps),
1914
+ set: (value) => {
1915
+ handles.set(ps, value);
1916
+ }
1917
+ }
1918
+ };
1919
+ return this.executeEncodedPlan(run, exec, queryable, codecCtx, () => queryable.execute(request));
1920
+ }
1921
+ async connection() {
1922
+ return (await this.openConnection()).connection;
1923
+ }
1924
+ async openConnection(options) {
1925
+ const endOperation = this.beginOperation(this.driver, options);
1926
+ let driverConn;
1927
+ try {
1928
+ driverConn = await this.driver.acquireConnection();
1929
+ } finally {
1930
+ endOperation();
1931
+ }
1932
+ this.#heldQueryables.add(driverConn);
1933
+ const self = this;
1934
+ const wrappedConnection = {
1935
+ [transactionStart]: createTransactionStart(),
1936
+ transaction() {
1937
+ return self.beginTransactionOn(driverConn, wrappedConnection, void 0);
1938
+ },
1939
+ [activitySource]: self.connectionActivitySource(driverConn, () => wrappedConnection),
1940
+ async release() {
1941
+ await self.#inFlight.track(() => driverConn.release());
1942
+ },
1943
+ async destroy(reason) {
1944
+ await self.#inFlight.track(() => driverConn.destroy(reason));
1945
+ },
1946
+ query(plan, options) {
1947
+ return self.queryAgainstQueryable(plan, driverConn, {
1948
+ ...options,
1949
+ scope: "connection"
1950
+ });
1951
+ },
1952
+ execute(plan, options) {
1953
+ return self.executeStatisticsAgainstQueryable(plan, driverConn, {
1954
+ ...options,
1955
+ scope: "connection"
1956
+ });
1957
+ },
1958
+ [preparedStatementQuery](ps, params, options) {
1959
+ return self.runPreparedQueryAgainstQueryable(blindCast(ps), params, driverConn, {
1960
+ ...options,
1961
+ scope: "connection"
1962
+ });
1963
+ },
1964
+ [preparedStatementExecute](ps, params, options) {
1965
+ return self.runPreparedExecuteAgainstQueryable(blindCast(ps), params, driverConn, {
1966
+ ...options,
1967
+ scope: "connection"
1968
+ });
1969
+ }
1970
+ };
1971
+ return {
1972
+ driverConnection: driverConn,
1973
+ connection: wrappedConnection
1974
+ };
1975
+ }
1976
+ /**
1977
+ * Begins a transaction on `connection`, which `owner` wraps, as a transaction activity. When the transaction's `activityStarted` hooks refuse the start, no `BEGIN` is sent, `owner` is recorded as clean, and `withTransaction` releases it; after any other start failure it destroys it. The transaction's `activityStarted` hooks run before `BEGIN`. Every statement sent on the transaction, or on `connection`, from the end of `BEGIN` until `commit()` or `rollback()` is called is the transaction's child, and is finalized when the transaction ends, with its outcome. `commit()` and `rollback()` resolve after every finalization hook has run. A subclass that holds its own connection begins its transactions here.
1978
+ */
1979
+ async beginTransactionOn(connection, owner, parent) {
1980
+ owner[transactionStart].refusedBeforeBegin = false;
1981
+ let activity;
1982
+ try {
1983
+ activity = await startTransactionActivity({
1984
+ middleware: this.middleware,
1985
+ context: {
1986
+ ...this.sqlCtx,
1987
+ scope: "transaction"
1988
+ },
1989
+ parent,
1990
+ holdFinishedChildren: this.#holdFinishedChildren
1991
+ });
1992
+ } catch (error) {
1993
+ owner[transactionStart].refusedBeforeBegin = true;
1994
+ throw error;
1995
+ }
1996
+ let driverTx;
1997
+ try {
1998
+ driverTx = await this.#inFlight.track(() => connection.beginTransaction());
1999
+ } catch (error) {
2000
+ await activity.end({
2001
+ by: "rollback",
2002
+ outcome: "failed",
2003
+ commitAttempted: false
2004
+ });
2005
+ throw error;
2006
+ }
2007
+ if (this.#heldQueryables.has(connection)) this.#heldQueryables.add(driverTx);
2008
+ return this.wrapTransaction(driverTx, connection, activity);
2009
+ }
2010
+ wrapTransaction(driverTx, connection, activity) {
2011
+ this.#openTransactions.set(driverTx, activity);
2012
+ this.#openTransactions.set(connection, activity);
2013
+ const stopAcceptingChildren = () => {
2014
+ activity.stopAcceptingChildren();
2015
+ for (const queryable of [driverTx, connection]) if (this.#openTransactions.get(queryable) === activity) this.#openTransactions.delete(queryable);
2016
+ };
2017
+ let commitAttempted = false;
2018
+ const settlement = {
2019
+ committed: false,
2020
+ rolledBack: false,
2021
+ logDroppedError(error) {
2022
+ try {
2023
+ activity.ctx.log.error({
2024
+ event: "middleware.activityEnded.error",
2025
+ error
2026
+ });
2027
+ } catch {}
2028
+ }
2029
+ };
2030
+ let commitEnding;
2031
+ const self = this;
2032
+ return {
2033
+ [transactionSettlement]: settlement,
2034
+ async commit() {
2035
+ commitAttempted = true;
2036
+ stopAcceptingChildren();
2037
+ try {
2038
+ await self.#inFlight.track(() => driverTx.commit());
2039
+ } catch (error) {
2040
+ await activity.end({
2041
+ by: "commit",
2042
+ outcome: "failed"
2043
+ });
2044
+ throw error;
2045
+ }
2046
+ settlement.committed = true;
2047
+ commitEnding = activity.end({
2048
+ by: "commit",
2049
+ outcome: "succeeded"
2050
+ });
2051
+ await commitEnding;
2052
+ },
2053
+ async rollback() {
2054
+ stopAcceptingChildren();
2055
+ if (settlement.committed) {
2056
+ await commitEnding?.catch(() => void 0);
2057
+ return;
2058
+ }
2059
+ if (settlement.rolledBack) return;
2060
+ try {
2061
+ await self.#inFlight.track(() => driverTx.rollback());
2062
+ } catch (error) {
2063
+ await activity.end({
2064
+ by: "rollback",
2065
+ outcome: "failed",
2066
+ commitAttempted
2067
+ });
2068
+ throw error;
2069
+ }
2070
+ settlement.rolledBack = true;
2071
+ await activity.end({
2072
+ by: "rollback",
2073
+ outcome: "succeeded",
2074
+ commitAttempted
2075
+ });
2076
+ },
2077
+ [activitySource]: {
2078
+ startActivity: (clientActivity) => self.startActivityOnRoute(self.queryableRoute(driverTx, "transaction", () => activity, {
2079
+ transaction: void 0,
2080
+ runtimeWork: false
2081
+ }), clientActivity, void 0),
2082
+ planContentHash: (plan) => self.planContentHashFor(plan, "transaction", activity)
2083
+ },
2084
+ query(plan, options) {
2085
+ return self.queryAgainstQueryable(plan, driverTx, {
2086
+ ...options,
2087
+ scope: "transaction"
2088
+ });
2089
+ },
2090
+ execute(plan, options) {
2091
+ return self.executeStatisticsAgainstQueryable(plan, driverTx, {
2092
+ ...options,
2093
+ scope: "transaction"
2094
+ });
2095
+ },
2096
+ [preparedStatementQuery](ps, params, options) {
2097
+ return self.runPreparedQueryAgainstQueryable(blindCast(ps), params, driverTx, {
2098
+ ...options,
2099
+ scope: "transaction"
2100
+ });
2101
+ },
2102
+ [preparedStatementExecute](ps, params, options) {
2103
+ return self.runPreparedExecuteAgainstQueryable(blindCast(ps), params, driverTx, {
2104
+ ...options,
2105
+ scope: "transaction"
2106
+ });
2107
+ }
2108
+ };
2109
+ }
2110
+ telemetry() {
2111
+ return this._telemetry;
2112
+ }
2113
+ close() {
2114
+ this.closePromise ??= this.closeDriverAfterWork();
2115
+ return this.closePromise;
2116
+ }
2117
+ async closeDriverAfterWork() {
2118
+ const warned = { done: false };
2119
+ if (this.closeRefusal === "when-idle") await this.waitForIdleTurn(warned);
2120
+ this.#refusing = true;
2121
+ this.warnWhenWaitingOnlyForActivities(warned);
2122
+ await this.#inFlight.drained();
2123
+ await this.driver.close();
2124
+ }
2125
+ async waitForIdleTurn(warned) {
2126
+ for (;;) {
2127
+ const startedBefore = this.#inFlight.started;
2128
+ await new Promise((resolve) => scheduleTimer(resolve, 0));
2129
+ if (!this.#inFlight.active && this.#inFlight.started === startedBefore) return;
2130
+ this.warnWhenWaitingOnlyForActivities(warned);
2131
+ await this.#inFlight.drained();
2132
+ }
2133
+ }
2134
+ ensureCodecRegistryValidated() {
2135
+ if (!this.codecRegistryValidated) {
2136
+ validateCodecRegistryCompleteness(this.codecDescriptors, this.contract);
2137
+ this.codecRegistryValidated = true;
2138
+ }
2139
+ }
2140
+ async verifyMarker(queryable) {
2141
+ const readResult = await this.familyAdapter.markerReader.readMarker(queryable);
2142
+ const expectedStorageHash = this.contract.storage.storageHash;
2143
+ const expectedProfileHash = this.contract.profileHash ?? null;
2144
+ const expected = {
2145
+ storageHash: expectedStorageHash,
2146
+ profileHash: expectedProfileHash
2147
+ };
2148
+ if (readResult.kind !== "present") {
2149
+ this.sqlCtx.log.warn({
2150
+ code: "CONTRACT.MARKER_MISSING",
2151
+ scope: "marker-verification",
2152
+ expected,
2153
+ actual: null,
2154
+ message: "Contract marker not found in database"
2155
+ });
2156
+ return;
2157
+ }
2158
+ const marker = readResult.record;
2159
+ const storageHashMatch = marker.storageHash === expectedStorageHash;
2160
+ const profileHashMatch = expectedProfileHash === null || marker.profileHash === expectedProfileHash;
2161
+ if (!storageHashMatch || !profileHashMatch) this.sqlCtx.log.warn({
2162
+ code: "CONTRACT.MARKER_MISMATCH",
2163
+ scope: "marker-verification",
2164
+ expected,
2165
+ actual: {
2166
+ storageHash: marker.storageHash,
2167
+ profileHash: marker.profileHash ?? null
2168
+ },
2169
+ message: "Contract marker hash does not match runtime contract"
2170
+ });
2171
+ }
2172
+ recordTelemetry(plan, outcome, durationMs) {
2173
+ const contract = this.contract;
2174
+ this._telemetry = Object.freeze({
2175
+ lane: plan.meta.lane,
2176
+ target: contract.target,
2177
+ fingerprint: computeSqlFingerprint(plan.sql),
2178
+ outcome,
2179
+ ...durationMs !== void 0 ? { durationMs } : {}
2180
+ });
2181
+ }
2182
+ };
2183
+ function activityOutsideTransactionError(parent, openTransaction) {
2184
+ return runtimeError$1("RUNTIME.ACTIVITY_OUTSIDE_TRANSACTION", `Work was sent through a '${parent.activity.kind}' activity onto a connection whose open transaction the activity is not part of. Start the activity on the transaction instead.`, {
2185
+ kind: parent.activity.kind,
2186
+ id: parent.activity.id,
2187
+ transactionId: openTransaction.activity.id
2188
+ });
2189
+ }
2190
+ function transactionAlreadyOpenError(parent, openTransaction) {
2191
+ return runtimeError$1("RUNTIME.TRANSACTION_ALREADY_OPEN", `A transaction is already open on this connection; a '${parent.activity.kind}' activity cannot begin another until it commits or rolls back.`, {
2192
+ kind: parent.activity.kind,
2193
+ id: parent.activity.id,
2194
+ transactionId: openTransaction.activity.id
2195
+ });
2196
+ }
2197
+ function noWork() {}
2198
+ function rowsBeneath(run, rows) {
2199
+ return new AsyncIterableResult(new RowsBeneath(run, rows));
2200
+ }
2201
+ /** The rows of a statement started beneath `run`, checked against `run` on the first `next()`. It forwards to the statement's own iterator rather than adding an async generator, so each row costs no extra turn. */
2202
+ var RowsBeneath = class {
2203
+ #run;
2204
+ #rows;
2205
+ #inner;
2206
+ #done = false;
2207
+ constructor(run, rows) {
2208
+ this.#run = run;
2209
+ this.#rows = rows;
2210
+ }
2211
+ next() {
2212
+ if (this.#done) return Promise.resolve({
2213
+ done: true,
2214
+ value: void 0
2215
+ });
2216
+ if (this.#inner === void 0) try {
2217
+ this.#run.assertAcceptsChildren();
2218
+ this.#inner = this.#rows()[Symbol.asyncIterator]();
2219
+ } catch (error) {
2220
+ this.#done = true;
2221
+ return Promise.reject(error);
2222
+ }
2223
+ return this.#inner.next();
2224
+ }
2225
+ return() {
2226
+ const inner = this.#inner;
2227
+ this.#done = true;
2228
+ return inner?.return === void 0 ? Promise.resolve({
2229
+ done: true,
2230
+ value: void 0
2231
+ }) : inner.return();
2232
+ }
2233
+ throw(error) {
2234
+ const inner = this.#inner;
2235
+ if (inner?.throw === void 0) {
2236
+ this.#done = true;
2237
+ return Promise.reject(error);
2238
+ }
2239
+ return inner.throw(error);
2240
+ }
2241
+ [Symbol.asyncIterator]() {
2242
+ return this;
2243
+ }
2244
+ async [Symbol.asyncDispose]() {
2245
+ await this.return();
2246
+ }
2247
+ };
2248
+ function transactionClosedError() {
2249
+ return runtimeError$1("RUNTIME.TRANSACTION_CLOSED", "Cannot use a transaction operation after the transaction has ended. Consume query results and await execute calls inside the transaction callback.", {});
2250
+ }
2251
+ async function withTransaction(runtime, fn) {
2252
+ const connection = await runtime.connection();
2253
+ let transaction;
2254
+ try {
2255
+ transaction = await connection.transaction();
2256
+ } catch (error) {
2257
+ if (wasRefusedBeforeBegin(connection)) await connection.release().catch(() => void 0);
2258
+ else await connection.destroy(error).catch(() => void 0);
2259
+ throw error;
2260
+ }
2261
+ let invalidated = false;
2262
+ async function* guardedStream(inner) {
2263
+ if (invalidated) throw transactionClosedError();
2264
+ for await (const row of inner) {
2265
+ yield row;
2266
+ if (invalidated) throw transactionClosedError();
2267
+ }
2268
+ }
2269
+ function guardedQueryable(target) {
2270
+ return {
2271
+ query(plan, options) {
2272
+ if (invalidated) throw transactionClosedError();
2273
+ return new AsyncIterableResult(guardedStream(target.query(plan, options)));
2274
+ },
2275
+ async execute(plan, options) {
2276
+ if (invalidated) throw transactionClosedError();
2277
+ return target.execute(plan, options);
2278
+ },
2279
+ [preparedStatementQuery](ps, params, options) {
2280
+ if (invalidated) throw transactionClosedError();
2281
+ return new AsyncIterableResult(guardedStream(runPreparedQuery(target, ps, params, options)));
2282
+ },
2283
+ [preparedStatementExecute](ps, params, options) {
2284
+ if (invalidated) throw transactionClosedError();
2285
+ return runPreparedExecute(target, ps, params, options);
2286
+ }
2287
+ };
2288
+ }
2289
+ function guardedSource(target) {
2290
+ return {
2291
+ async startActivity(activity) {
2292
+ if (invalidated) throw transactionClosedError();
2293
+ return guardedHandle(await startActivityOn(target, activity));
2294
+ },
2295
+ planContentHash(plan) {
2296
+ return planContentHashOn(target, plan);
2297
+ }
2298
+ };
2299
+ }
2300
+ function guardedHandle(handle) {
2301
+ const source = guardedSource(handle);
2302
+ return {
2303
+ ...source,
2304
+ ...guardedQueryable(handle),
2305
+ [activitySource]: source,
2306
+ activity: handle.activity,
2307
+ get intercepted() {
2308
+ return handle.intercepted;
2309
+ },
2310
+ end: (end) => handle.end(end)
2311
+ };
2312
+ }
2313
+ const txContext = {
2314
+ ...guardedQueryable(transaction),
2315
+ get invalidated() {
2316
+ return invalidated;
2317
+ },
2318
+ [activitySource]: guardedSource(transaction)
2319
+ };
2320
+ let connectionDisposed = false;
2321
+ const destroyConnection = async (reason) => {
2322
+ if (connectionDisposed) return;
2323
+ connectionDisposed = true;
2324
+ await connection.destroy(reason).catch(() => void 0);
2325
+ };
2326
+ try {
2327
+ let result;
2328
+ try {
2329
+ result = await fn(txContext);
2330
+ } catch (error) {
2331
+ try {
2332
+ await transaction.rollback();
2333
+ } catch (rollbackError) {
2334
+ const settlement = settlementOf(transaction);
2335
+ if (settlement?.rolledBack === true) {
2336
+ settlement.logDroppedError(rollbackError);
2337
+ throw error;
2338
+ }
2339
+ await destroyConnection(rollbackError);
2340
+ const wrapped = runtimeError$1("RUNTIME.TRANSACTION_ROLLBACK_FAILED", "Transaction rollback failed after callback error", { rollbackError });
2341
+ wrapped.cause = error;
2342
+ throw wrapped;
2343
+ }
2344
+ throw error;
2345
+ } finally {
2346
+ invalidated = true;
2347
+ }
2348
+ try {
2349
+ await transaction.commit();
2350
+ } catch (commitError) {
2351
+ if (settlementOf(transaction)?.committed === true) throw commitError;
2352
+ try {
2353
+ await transaction.rollback();
2354
+ } catch (rollbackError) {
2355
+ const settlement = settlementOf(transaction);
2356
+ if (settlement?.rolledBack === true) settlement.logDroppedError(rollbackError);
2357
+ else await destroyConnection(commitError);
2358
+ }
2359
+ const wrapped = runtimeError$1("RUNTIME.TRANSACTION_COMMIT_FAILED", "Transaction commit failed", { commitError });
2360
+ wrapped.cause = commitError;
2361
+ throw wrapped;
2362
+ }
2363
+ return result;
2364
+ } finally {
2365
+ if (!connectionDisposed) await connection.release();
2366
+ }
2367
+ }
2368
+ //#endregion
2369
+ export { startActivityOn as _, budgets as a, withTransaction as b, createSqlExecutionStack as c, encodeParamsWithMetadata as d, extractCodecIds as f, planContentHashOn as g, lowerSqlPlan as h, activitySource as i, delegatingActivitySource as l, lints as m, PreparedStatementImpl as n, createAstCodecRegistry as o, findActivitySource as p, SqlRuntimeBase as r, createExecutionContext as s, PreparedExecutionImpl as t, deriveParamMetadata as u, validateCodecRegistryCompleteness as v, validateContractCodecMappings as y };
2370
+
2371
+ //# sourceMappingURL=exports-DVsHk8HP-DrH_2a-H.mjs.map