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

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.
@@ -1,11 +1,11 @@
1
1
  import { AsyncIterableResult, RuntimeCore, checkAborted, checkMiddlewareCompatibility, isRuntimeError, raceAgainstAbort, runWithMiddleware, runtimeError } from "@prisma-next/framework-components/runtime";
2
2
  import { type } from "arktype";
3
- import { collectOrderedParamRefs, createCodecRegistry, isQueryAst } from "@prisma-next/sql-relational-core/ast";
3
+ import { collectOrderedParamRefs, isQueryAst, validateParamRefRefs } from "@prisma-next/sql-relational-core/ast";
4
4
  import { ifDefined } from "@prisma-next/utils/defined";
5
- import { synthesizeNonParameterizedDescriptor } from "@prisma-next/framework-components/codec";
6
5
  import { checkContractComponentRequirements } from "@prisma-next/framework-components/components";
7
6
  import { createExecutionStack } from "@prisma-next/framework-components/execution";
8
7
  import { createSqlOperationRegistry } from "@prisma-next/sql-operations";
8
+ import { buildCodecDescriptorRegistry } from "@prisma-next/sql-relational-core/codec-descriptor-registry";
9
9
  import { canonicalStringify } from "@prisma-next/utils/canonical-stringify";
10
10
  import { hashContent } from "@prisma-next/utils/hash-content";
11
11
  import { createHash } from "node:crypto";
@@ -28,17 +28,10 @@ function extractCodecIdsFromColumns(contract) {
28
28
  }
29
29
  return codecIds;
30
30
  }
31
- function adaptDescriptorRegistry(registry) {
32
- return { has: (id) => registry.descriptorFor(id) !== void 0 };
33
- }
34
- function isDescriptorRegistry(registry) {
35
- return "descriptorFor" in registry;
36
- }
37
31
  function validateContractCodecMappings(registry, contract) {
38
- const lookup = isDescriptorRegistry(registry) ? adaptDescriptorRegistry(registry) : registry;
39
32
  const codecIds = extractCodecIdsFromColumns(contract);
40
33
  const invalidCodecs = [];
41
- for (const [key, codecId] of codecIds.entries()) if (!lookup.has(codecId)) {
34
+ for (const [key, codecId] of codecIds.entries()) if (registry.descriptorFor(codecId) === void 0) {
42
35
  const parts = key.split(".");
43
36
  const table = parts[0] ?? "";
44
37
  const column = parts[1] ?? "";
@@ -138,14 +131,12 @@ function primaryTableFromAst(ast) {
138
131
  switch (ast.from.kind) {
139
132
  case "table-source": return ast.from.name;
140
133
  case "derived-table-source": return ast.from.alias;
141
- default: return;
134
+ default: throw new Error(`Unsupported source kind: ${ast.from.kind}`);
142
135
  }
143
136
  }
144
137
  function estimateRowsFromAst(ast, tableRows, defaultTableRows, hasAggregateWithoutGroup) {
145
138
  if (hasAggregateWithoutGroup) return 1;
146
- const table = primaryTableFromAst(ast);
147
- if (!table) return null;
148
- const tableEstimate = tableRows[table] ?? defaultTableRows;
139
+ const tableEstimate = tableRows[primaryTableFromAst(ast)] ?? defaultTableRows;
149
140
  if (ast.limit !== void 0) return Math.min(ast.limit, tableEstimate);
150
141
  return tableEstimate;
151
142
  }
@@ -198,21 +189,17 @@ function budgets(options) {
198
189
  const isUnbounded = ast.limit === void 0 && !hasAggNoGroup;
199
190
  const shouldBlock = rowSeverity === "error" || ctx.mode === "strict";
200
191
  if (isUnbounded) {
201
- if (estimated !== null && estimated >= maxRows) {
202
- emitBudgetViolation(runtimeError("BUDGET.ROWS_EXCEEDED", "Unbounded SELECT query exceeds budget", {
203
- source: "ast",
204
- estimatedRows: estimated,
205
- maxRows
206
- }), shouldBlock, ctx);
207
- return;
208
- }
209
- emitBudgetViolation(runtimeError("BUDGET.ROWS_EXCEEDED", "Unbounded SELECT query exceeds budget", {
192
+ emitBudgetViolation(runtimeError("BUDGET.ROWS_EXCEEDED", "Unbounded SELECT query exceeds budget", estimated >= maxRows ? {
193
+ source: "ast",
194
+ estimatedRows: estimated,
195
+ maxRows
196
+ } : {
210
197
  source: "ast",
211
198
  maxRows
212
199
  }), shouldBlock, ctx);
213
200
  return;
214
201
  }
215
- if (estimated !== null && estimated > maxRows) emitBudgetViolation(runtimeError("BUDGET.ROWS_EXCEEDED", "Estimated row count exceeds budget", {
202
+ if (estimated > maxRows) emitBudgetViolation(runtimeError("BUDGET.ROWS_EXCEEDED", "Estimated row count exceeds budget", {
216
203
  source: "ast",
217
204
  estimatedRows: estimated,
218
205
  maxRows
@@ -446,68 +433,41 @@ function assertExecutionStackContractRequirements(contract, stack) {
446
433
  throw runtimeError("RUNTIME.MISSING_EXTENSION_PACK", `Contract requires extension pack(s) ${packIds.map((id) => `'${id}'`).join(", ")}, but runtime descriptors do not provide matching component(s).`, { packIds });
447
434
  }
448
435
  }
449
- function validateTypeParams(typeParams, codecDescriptor, context) {
450
- const result = codecDescriptor.paramsSchema["~standard"].validate(typeParams);
451
- if (result instanceof Promise) throw runtimeError("RUNTIME.TYPE_PARAMS_INVALID", `paramsSchema for codec '${codecDescriptor.codecId}' returned a Promise; runtime validation requires a synchronous Standard Schema validator.`, {
436
+ function validateTypeParams(typeParams, descriptor, context) {
437
+ const result = descriptor.paramsSchema["~standard"].validate(typeParams);
438
+ if (result instanceof Promise) throw runtimeError("RUNTIME.TYPE_PARAMS_INVALID", `paramsSchema for codec '${descriptor.codecId}' returned a Promise; runtime validation requires a synchronous Standard Schema validator.`, {
452
439
  ...context,
453
- codecId: codecDescriptor.codecId,
440
+ codecId: descriptor.codecId,
454
441
  typeParams
455
442
  });
456
443
  if (result.issues) {
457
444
  const messages = result.issues.map((issue) => issue.message).join("; ");
458
- throw runtimeError("RUNTIME.TYPE_PARAMS_INVALID", `Invalid typeParams for ${context.typeName ? `type '${context.typeName}'` : `column '${context.tableName}.${context.columnName}'`} (codecId: ${codecDescriptor.codecId}): ${messages}`, {
445
+ throw runtimeError("RUNTIME.TYPE_PARAMS_INVALID", `Invalid typeParams for ${context.typeName ? `type '${context.typeName}'` : `column '${context.tableName}.${context.columnName}'`} (codecId: ${descriptor.codecId}): ${messages}`, {
459
446
  ...context,
460
- codecId: codecDescriptor.codecId,
447
+ codecId: descriptor.codecId,
461
448
  typeParams
462
449
  });
463
450
  }
464
451
  return result.value;
465
452
  }
466
- function collectParameterizedCodecDescriptors(contributors) {
467
- const descriptors = /* @__PURE__ */ new Map();
468
- for (const contributor of contributors) for (const descriptor of contributor.parameterizedCodecs()) {
469
- if (descriptors.has(descriptor.codecId)) throw runtimeError("RUNTIME.DUPLICATE_PARAMETERIZED_CODEC", `Duplicate parameterized codec descriptor for codecId '${descriptor.codecId}'.`, { codecId: descriptor.codecId });
470
- descriptors.set(descriptor.codecId, descriptor);
471
- }
472
- return descriptors;
473
- }
474
453
  /**
475
- * Build the unified descriptor map. Combines parameterized descriptors
476
- * (which already ship as `CodecDescriptor`s) with synthesized descriptors
477
- * for non-parameterized codecs registered through the legacy `codecs:`
478
- * slot. Codec ids that ship a parameterized descriptor take precedence —
479
- * even when the legacy registry registers a representative codec under
480
- * the same id, the parameterized descriptor is the authoritative source.
454
+ * Collect every {@link CodecDescriptor} contributed by the SQL stack and partition into "parameterized" vs "non-parameterized" via the descriptor's own {@link CodecDescriptorImpl.isParameterized} getter. The getter is the canonical discriminator — a `paramsSchema` identity check would misroute any descriptor that doesn't reuse the exact `voidParamsSchema` singleton (e.g. a non-parameterized codec authoring its own no-op schema).
481
455
  *
482
- * Codec-registry-unification spec § Decision: every codec resolves
483
- * through one descriptor map; reads are non-branching.
456
+ * 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).
484
457
  */
485
- function buildCodecDescriptorRegistry(codecRegistry, parameterizedDescriptors) {
486
- const byId = /* @__PURE__ */ new Map();
487
- const byTargetType = /* @__PURE__ */ new Map();
488
- function registerInIndices(descriptor) {
489
- byId.set(descriptor.codecId, descriptor);
490
- for (const targetType of descriptor.targetTypes) {
491
- const list = byTargetType.get(targetType);
492
- if (list) list.push(descriptor);
493
- else byTargetType.set(targetType, [descriptor]);
494
- }
495
- }
496
- for (const descriptor of parameterizedDescriptors.values()) registerInIndices(descriptor);
497
- for (const codec$1 of codecRegistry.values()) {
498
- if (byId.has(codec$1.id)) continue;
499
- registerInIndices(synthesizeNonParameterizedDescriptor(codec$1));
458
+ function collectCodecDescriptors(contributors) {
459
+ const all = [];
460
+ const parameterized = /* @__PURE__ */ new Map();
461
+ const seen = /* @__PURE__ */ new Set();
462
+ for (const contributor of contributors) for (const descriptor of contributor.codecs()) {
463
+ if (seen.has(descriptor.codecId)) throw runtimeError("RUNTIME.DUPLICATE_CODEC", `Duplicate codec descriptor for codecId '${descriptor.codecId}'.`, { codecId: descriptor.codecId });
464
+ seen.add(descriptor.codecId);
465
+ all.push(descriptor);
466
+ if (descriptor.isParameterized) parameterized.set(descriptor.codecId, descriptor);
500
467
  }
501
468
  return {
502
- descriptorFor(codecId) {
503
- return byId.get(codecId);
504
- },
505
- *values() {
506
- yield* byId.values();
507
- },
508
- byTargetType(targetType) {
509
- return byTargetType.get(targetType) ?? Object.freeze([]);
510
- }
469
+ all,
470
+ parameterized
511
471
  };
512
472
  }
513
473
  function collectTypeRefSites(storage) {
@@ -553,50 +513,43 @@ function validateColumnTypeParams(storage, codecDescriptors) {
553
513
  });
554
514
  }
555
515
  }
556
- function hasJsonValidatorTrait(candidate) {
557
- if (candidate === null || typeof candidate !== "object") return false;
558
- const traits = candidate.traits;
559
- if (!Array.isArray(traits)) return false;
560
- if (!traits.includes("json-validator")) return false;
561
- return typeof candidate.validate === "function";
562
- }
563
- function extractValidator(candidate) {
564
- return hasJsonValidatorTrait(candidate) ? candidate.validate : void 0;
565
- }
566
516
  function isResolvedCodec(candidate) {
567
517
  return candidate !== null && typeof candidate === "object" && "id" in candidate && "decode" in candidate;
568
518
  }
569
519
  /**
570
- * Walk the contract's `storage.tables[].columns[]` and resolve each
571
- * column to a `Codec` through the unified descriptor map. Per-instance
572
- * behavior:
573
- *
574
- * - **typeRef columns**: reuse the resolved codec materialized once by
575
- * `initializeTypeHelpers` for the `storage.types` entry. Multiple
576
- * columns sharing one typeRef share one codec instance.
577
- * - **inline-typeParams columns**: call `descriptor.factory(typeParams)
578
- * (ctx)` once per column (per-column anonymous instance).
579
- * - **non-parameterized columns**: call `descriptor.factory()(ctx)`
580
- * once. The synthesized descriptor's factory is constant — every call
581
- * returns the same shared codec instance — so columns sharing a non-
582
- * parameterized codec id share one resolved codec without explicit
583
- * caching.
520
+ * Walk the contract's `storage.tables[].columns[]` and resolve each column to a `Codec` through the unified descriptor map. Per-instance behavior:
584
521
  *
585
- * Combines what `initializeTypeHelpers` (named-instance walk) and the
586
- * old `buildJsonSchemaValidatorRegistry` (per-column walk) used to do
587
- * separately: one walk over all columns, one resolved codec per column,
588
- * one trait-gated validator extraction per column. The result drives
589
- * both the dispatch registry (`ContractCodecRegistry.forColumn`) and the
590
- * validator registry.
522
+ * - **typeRef columns**: reuse the resolved codec materialized once by `initializeTypeHelpers` for the `storage.types` entry. Multiple columns sharing one typeRef share one codec instance.
523
+ * - **inline-typeParams columns**: call `descriptor.factory(typeParams) (ctx)` once per column (per-column anonymous instance).
524
+ * - **non-parameterized columns**: call `descriptor.factory()(ctx)` once. The synthesized descriptor's factory is constant — every call returns the same shared codec instance — so columns sharing a non-parameterized codec id share one resolved codec without explicit caching.
591
525
  *
592
- * Codec-registry-unification spec § AC-4: every column resolves through
593
- * one descriptor map without branching on parameterization.
526
+ * Codec-registry-unification spec § AC-4: every column resolves through one descriptor map without branching on parameterization. JSON-Schema validation, when required, lives inside the resolved codec's `decode` body (see `arktype-json`'s `ArktypeJsonCodecClass`); the framework no longer maintains a parallel validator registry.
594
527
  */
595
- function buildContractCodecRegistry(contract, codecDescriptors, legacyCodecRegistry, types, parameterizedDescriptors) {
528
+ function buildContractCodecRegistry(contract, codecDescriptors, types, parameterizedDescriptors) {
596
529
  const byColumn = /* @__PURE__ */ new Map();
597
530
  const byCodecId = /* @__PURE__ */ new Map();
598
531
  const ambiguousCodecIds = /* @__PURE__ */ new Set();
599
- const validators = /* @__PURE__ */ new Map();
532
+ for (const descriptor of codecDescriptors.values()) {
533
+ if (descriptor.isParameterized) continue;
534
+ const ctx = {
535
+ name: `<shared:${descriptor.codecId}>`,
536
+ usedAt: []
537
+ };
538
+ const voidFactory = descriptor.factory;
539
+ byCodecId.set(descriptor.codecId, voidFactory(void 0)(ctx));
540
+ }
541
+ const parameterizedRepresentatives = /* @__PURE__ */ new Map();
542
+ for (const descriptor of codecDescriptors.values()) {
543
+ if (!descriptor.isParameterized) continue;
544
+ const ctx = {
545
+ name: `<shared:${descriptor.codecId}>`,
546
+ usedAt: []
547
+ };
548
+ const factory = descriptor.factory.bind(descriptor);
549
+ try {
550
+ parameterizedRepresentatives.set(descriptor.codecId, factory(void 0)(ctx));
551
+ } catch {}
552
+ }
600
553
  for (const [tableName, table] of Object.entries(contract.storage.tables)) for (const [columnName, column] of Object.entries(table.columns)) {
601
554
  const columnKey = `${tableName}.${columnName}`;
602
555
  const descriptor = codecDescriptors.descriptorFor(column.codecId);
@@ -614,7 +567,7 @@ function buildContractCodecRegistry(contract, codecDescriptors, legacyCodecRegis
614
567
  columnName
615
568
  });
616
569
  const ctx = {
617
- name: `<anon:${tableName}.${columnName}>`,
570
+ name: `<col:${tableName}.${columnName}>`,
618
571
  usedAt: [{
619
572
  table: tableName,
620
573
  column: columnName
@@ -623,45 +576,31 @@ function buildContractCodecRegistry(contract, codecDescriptors, legacyCodecRegis
623
576
  resolvedCodec = parameterizedDescriptor.factory(validatedParams)(ctx);
624
577
  }
625
578
  } else if (!isParameterized) {
626
- let cached = byCodecId.get(column.codecId);
627
- if (!cached) {
628
- const ctx = {
629
- name: `<shared:${column.codecId}>`,
630
- usedAt: [{
631
- table: tableName,
632
- column: columnName
633
- }]
634
- };
635
- const voidFactory = descriptor.factory;
636
- cached = voidFactory(void 0)(ctx);
637
- byCodecId.set(column.codecId, cached);
638
- }
639
- resolvedCodec = cached;
579
+ const ctx = {
580
+ name: `<col:${tableName}.${columnName}>`,
581
+ usedAt: [{
582
+ table: tableName,
583
+ column: columnName
584
+ }]
585
+ };
586
+ resolvedCodec = descriptor.factory.bind(descriptor)(void 0)(ctx);
640
587
  }
641
588
  }
642
589
  if (resolvedCodec) {
643
590
  byColumn.set(columnKey, resolvedCodec);
644
- const validate = extractValidator(resolvedCodec);
645
- if (validate) validators.set(columnKey, validate);
646
591
  const existing = byCodecId.get(column.codecId);
647
592
  if (existing === void 0) byCodecId.set(column.codecId, resolvedCodec);
648
593
  else if (existing !== resolvedCodec && parameterizedDescriptors.has(column.codecId)) ambiguousCodecIds.add(column.codecId);
649
594
  }
650
595
  }
651
596
  return {
652
- registry: {
653
- forColumn(table, column) {
654
- return byColumn.get(`${table}.${column}`);
655
- },
656
- forCodecId(codecId) {
657
- if (ambiguousCodecIds.has(codecId)) throw runtimeError("RUNTIME.TYPE_PARAMS_INVALID", `Codec '${codecId}' resolves to multiple parameterized instances; column-aware dispatch is required.`, { codecId });
658
- return byCodecId.get(codecId) ?? legacyCodecRegistry.get(codecId);
659
- }
597
+ forColumn(table, column) {
598
+ return byColumn.get(`${table}.${column}`);
660
599
  },
661
- jsonValidators: validators.size > 0 ? {
662
- get: (key) => validators.get(key),
663
- size: validators.size
664
- } : void 0
600
+ forCodecId(codecId) {
601
+ if (ambiguousCodecIds.has(codecId)) throw runtimeError("RUNTIME.TYPE_PARAMS_INVALID", `Codec '${codecId}' resolves to multiple parameterized instances; column-aware dispatch is required.`, { codecId });
602
+ return byCodecId.get(codecId) ?? parameterizedRepresentatives.get(codecId);
603
+ }
665
604
  };
666
605
  }
667
606
  function assertMutationDefaultGeneratorsAvailable(contract, generatorRegistry) {
@@ -744,30 +683,25 @@ function scopedCache(stability, rowCache, queryCache) {
744
683
  function createExecutionContext(options) {
745
684
  const { contract, stack } = options;
746
685
  assertExecutionStackContractRequirements(contract, stack);
747
- const codecRegistry = createCodecRegistry();
748
686
  const contributors = [
749
687
  stack.target,
750
688
  stack.adapter,
751
689
  ...stack.extensionPacks
752
690
  ];
753
- for (const contributor of contributors) for (const c of contributor.codecs().values()) codecRegistry.register(c);
691
+ const { all: allCodecDescriptors, parameterized: parameterizedCodecDescriptors } = collectCodecDescriptors(contributors);
754
692
  const queryOperationRegistry = createSqlOperationRegistry();
755
693
  for (const contributor of contributors) for (const op of contributor.queryOperations?.() ?? []) queryOperationRegistry.register(op);
756
- const parameterizedCodecDescriptors = collectParameterizedCodecDescriptors(contributors);
757
- const codecDescriptors = buildCodecDescriptorRegistry(codecRegistry, parameterizedCodecDescriptors);
694
+ const codecDescriptors = buildCodecDescriptorRegistry(allCodecDescriptors);
758
695
  const mutationDefaultGeneratorRegistry = collectMutationDefaultGenerators(contributors);
759
696
  assertMutationDefaultGeneratorsAvailable(contract, mutationDefaultGeneratorRegistry);
760
697
  if (parameterizedCodecDescriptors.size > 0) validateColumnTypeParams(contract.storage, parameterizedCodecDescriptors);
761
698
  const types = initializeTypeHelpers(contract.storage, parameterizedCodecDescriptors);
762
- const { registry: contractCodecs, jsonValidators: jsonSchemaValidators } = buildContractCodecRegistry(contract, codecDescriptors, codecRegistry, types, parameterizedCodecDescriptors);
763
699
  return {
764
700
  contract,
765
- codecs: codecRegistry,
766
- contractCodecs,
701
+ contractCodecs: buildContractCodecRegistry(contract, codecDescriptors, types, parameterizedCodecDescriptors),
767
702
  codecDescriptors,
768
703
  queryOperations: queryOperationRegistry,
769
704
  types,
770
- ...jsonSchemaValidators ? { jsonSchemaValidators } : {},
771
705
  applyMutationDefaults: (options$1) => applyMutationDefaults(contract, mutationDefaultGeneratorRegistry, options$1)
772
706
  };
773
707
  }
@@ -882,37 +816,31 @@ function writeContractMarker(input) {
882
816
  }
883
817
 
884
818
  //#endregion
885
- //#region src/codecs/json-schema-validation.ts
819
+ //#region src/codecs/alias-resolver.ts
886
820
  /**
887
- * Validates a JSON value against its column's JSON Schema, if a validator exists.
821
+ * Build a map from query-local table aliases to their underlying source table names.
888
822
  *
889
- * Throws `RUNTIME.JSON_SCHEMA_VALIDATION_FAILED` on validation failure.
890
- * No-ops if no validator is registered for the column.
823
+ * Self-joins like `db.sql.post.as('p1').innerJoin(db.sql.post.as('p2'), …)` produce `ColumnRef`s whose `table` is the alias (`p1`, `p2`) — the SQL renderer needs the alias for `SELECT p1.id, …`. Codec dispatch keys `byColumn` by the underlying source table, so aliases must be resolved back to the source name for `forColumn(...)` to hit. Tables that already use their canonical name (no alias) are also entered so a single
824
+ * lookup works for both shapes.
891
825
  */
892
- function validateJsonValue(registry, table, column, value, direction, codecId) {
893
- const key = `${table}.${column}`;
894
- const validate = registry.get(key);
895
- if (!validate) return;
896
- const result = validate(value);
897
- if (result.valid) return;
898
- throw createJsonSchemaValidationError(table, column, direction, result.errors, codecId);
899
- }
900
- function createJsonSchemaValidationError(table, column, direction, errors, codecId) {
901
- return runtimeError("RUNTIME.JSON_SCHEMA_VALIDATION_FAILED", `JSON schema validation failed for column '${table}.${column}' (${direction}): ${formatErrorSummary(errors)}`, {
902
- table,
903
- column,
904
- codecId,
905
- direction,
906
- errors: [...errors]
907
- });
826
+ function buildAliasMap(ast) {
827
+ const aliases = /* @__PURE__ */ new Map();
828
+ const recordSource = (source) => {
829
+ if (source.kind === "table-source") {
830
+ const key = source.alias ?? source.name;
831
+ aliases.set(key, source.name);
832
+ } else aliases.set(source.alias, source.alias);
833
+ };
834
+ if (ast.kind === "select") {
835
+ recordSource(ast.from);
836
+ for (const join of ast.joins ?? []) recordSource(join.source);
837
+ } else recordSource(ast.table);
838
+ return aliases;
908
839
  }
909
- function formatErrorSummary(errors) {
910
- if (errors.length === 0) return "unknown validation error";
911
- if (errors.length === 1) {
912
- const err = errors[0];
913
- return err.path === "/" ? err.message : `${err.path}: ${err.message}`;
914
- }
915
- return errors.map((err) => err.path === "/" ? err.message : `${err.path}: ${err.message}`).join("; ");
840
+ function makeAliasResolver(ast) {
841
+ if (!ast) return (alias) => alias;
842
+ const map = buildAliasMap(ast);
843
+ return (alias) => map.get(alias) ?? alias;
916
844
  }
917
845
 
918
846
  //#endregion
@@ -929,32 +857,29 @@ function projectionListFromAst(ast) {
929
857
  /**
930
858
  * Resolve the per-cell codec for a projection item.
931
859
  *
932
- * Phase B: when a `(table, column)` ref is available for the projection,
933
- * prefer `contractCodecs.forColumn(table, column)` — that's the per-
934
- * instance resolved codec materialized from the codec descriptor's
935
- * factory at context-construction time (carries any per-instance state
936
- * such as the compiled JSON-Schema validator). When the projection
937
- * resolves to a non-`column-ref` expression (computed projections, raw
938
- * SQL aliases) but still carries a codec id (ADR 205 stamps every
939
- * `ProjectionItem` with the producer's codec id), fall back to the
940
- * codec-id-keyed `forCodecId(codecId)` lookup, which itself falls back
941
- * to the legacy `CodecRegistry` for codec ids the contract walk
942
- * couldn't resolve.
860
+ * When a `(table, column)` ref is available — either implicit on a `column-ref` expression or carried explicitly via `item.refs` for column-bound non-`column-ref` projections — prefer `contractCodecs.forColumn(table, column)`: that returns the per-instance codec materialized from the descriptor's factory for that column, encoding any per-instance state (typeParams like vector length, schema validators, etc.).
861
+ *
862
+ * The wrong-instance risk for parameterized codecs is closed off structurally:
863
+ *
864
+ * 1. `buildContractCodecRegistry` pre-populates `byCodecId` with one canonical instance per non-parameterized descriptor; parameterized descriptors are intentionally absent. 2. `forCodecId` rejects ambiguous parameterized fallbacks (`ambiguousCodecIds`). 3. The non-ambiguous parameterized case stores the column-correct per-instance codec under `byCodecId`, so the fall-through still resolves to the right instance.
943
865
  *
944
- * Codec-registry-unification spec § AC-4.
866
+ * The `forCodecId` fallback otherwise covers projections that are *not* column-bound (computed projections, raw SQL aliases) but still carry a `codecId` (ADR 205 stamps every `ProjectionItem` with the producer's codec id).
867
+ *
868
+ * Codec-registry-unification spec § AC-4 / AC-5.
945
869
  */
946
- function resolveProjectionCodec(item, registry, contractCodecs) {
947
- if (item.expr.kind === "column-ref" && contractCodecs) {
948
- const byColumn = contractCodecs.forColumn(item.expr.table, item.expr.column);
949
- if (byColumn) return byColumn;
950
- }
951
- if (item.codecId) {
952
- const fromContract = contractCodecs?.forCodecId(item.codecId);
953
- if (fromContract) return fromContract;
954
- return registry.get(item.codecId);
870
+ function resolveProjectionCodec(item, contractCodecs, aliasResolver) {
871
+ if (contractCodecs) {
872
+ if (item.expr.kind === "column-ref") {
873
+ const byColumn = contractCodecs.forColumn(aliasResolver(item.expr.table), item.expr.column);
874
+ if (byColumn && (item.codecId === void 0 || byColumn.id === item.codecId)) return byColumn;
875
+ } else if (item.refs) {
876
+ const byColumn = contractCodecs.forColumn(aliasResolver(item.refs.table), item.refs.column);
877
+ if (byColumn && (item.codecId === void 0 || byColumn.id === item.codecId)) return byColumn;
878
+ }
955
879
  }
880
+ if (item.codecId) return contractCodecs?.forCodecId(item.codecId);
956
881
  }
957
- function buildDecodeContext(plan, registry, contractCodecs) {
882
+ function buildDecodeContext(plan, contractCodecs) {
958
883
  if (!isAstBackedPlan(plan)) return {
959
884
  aliases: void 0,
960
885
  codecs: /* @__PURE__ */ new Map(),
@@ -972,14 +897,19 @@ function buildDecodeContext(plan, registry, contractCodecs) {
972
897
  const codecs = /* @__PURE__ */ new Map();
973
898
  const columnRefs = /* @__PURE__ */ new Map();
974
899
  const includeAliases = /* @__PURE__ */ new Set();
900
+ const aliasResolver = makeAliasResolver(plan.ast);
975
901
  for (const item of projection) {
976
902
  aliases.push(item.alias);
977
- const codec$1 = resolveProjectionCodec(item, registry, contractCodecs);
978
- if (codec$1) codecs.set(item.alias, codec$1);
903
+ const codec = resolveProjectionCodec(item, contractCodecs, aliasResolver);
904
+ if (codec) codecs.set(item.alias, codec);
979
905
  if (item.expr.kind === "column-ref") columnRefs.set(item.alias, {
980
- table: item.expr.table,
906
+ table: aliasResolver(item.expr.table),
981
907
  column: item.expr.column
982
908
  });
909
+ else if (item.refs) columnRefs.set(item.alias, {
910
+ table: aliasResolver(item.refs.table),
911
+ column: item.refs.column
912
+ });
983
913
  else if (item.expr.kind === "subquery" || item.expr.kind === "json-array-agg") includeAliases.add(item.alias);
984
914
  }
985
915
  return {
@@ -993,17 +923,14 @@ function previewWireValue(wireValue) {
993
923
  if (typeof wireValue === "string") return wireValue.length > WIRE_PREVIEW_LIMIT ? `${wireValue.substring(0, WIRE_PREVIEW_LIMIT)}...` : wireValue;
994
924
  return String(wireValue).substring(0, WIRE_PREVIEW_LIMIT);
995
925
  }
996
- function isJsonSchemaValidationError(error) {
997
- return isRuntimeError(error) && error.code === "RUNTIME.JSON_SCHEMA_VALIDATION_FAILED";
998
- }
999
- function wrapDecodeFailure(error, alias, ref, codec$1, wireValue) {
926
+ function wrapDecodeFailure(error, alias, ref, codec, wireValue) {
1000
927
  const message = error instanceof Error ? error.message : String(error);
1001
- const wrapped = runtimeError("RUNTIME.DECODE_FAILED", `Failed to decode column ${ref ? `${ref.table}.${ref.column}` : alias} with codec '${codec$1.id}': ${message}`, {
928
+ const wrapped = runtimeError("RUNTIME.DECODE_FAILED", `Failed to decode column ${ref ? `${ref.table}.${ref.column}` : alias} with codec '${codec.id}': ${message}`, {
1002
929
  ...ref ? {
1003
930
  table: ref.table,
1004
931
  column: ref.column
1005
932
  } : { alias },
1006
- codec: codec$1.id,
933
+ codec: codec.id,
1007
934
  wirePreview: previewWireValue(wireValue)
1008
935
  });
1009
936
  wrapped.cause = error;
@@ -1031,24 +958,16 @@ function decodeIncludeAggregate(alias, wireValue) {
1031
958
  }
1032
959
  }
1033
960
  /**
1034
- * Decodes a single field. Single-armed: every cell takes the same path —
1035
- * `codec.decode await → JSON-Schema validate → return plain value` — so
1036
- * sync- and async-authored codecs are indistinguishable to callers.
961
+ * 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
962
+ * no separate validator-registry pass.
1037
963
  *
1038
- * The row-level `rowCtx` is repackaged into a per-cell
1039
- * `SqlCodecCallContext` whose `column = { table, name }` is a structural
1040
- * projection of the per-cell `ColumnRef = { table, column }` resolved from
1041
- * the AST-backed `DecodeContext` (the same resolution `wrapDecodeFailure`
1042
- * uses for envelope construction — one resolution per cell, two consumers).
1043
- * Cells the runtime cannot resolve to a single underlying column (aggregate
1044
- * aliases, computed projections without a simple ref) get
1045
- * `column: undefined`, matching the spec contract that the runtime never
1046
- * silently defaults this field.
964
+ * 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
965
+ * aliases, computed projections without a simple ref) get `column: undefined`, matching the spec contract that the runtime never silently defaults this field.
1047
966
  */
1048
- async function decodeField(alias, wireValue, decodeCtx, jsonValidators, rowCtx) {
967
+ async function decodeField(alias, wireValue, decodeCtx, rowCtx) {
1049
968
  if (wireValue === null) return null;
1050
- const codec$1 = decodeCtx.codecs.get(alias);
1051
- if (!codec$1) return wireValue;
969
+ const codec = decodeCtx.codecs.get(alias);
970
+ if (!codec) return wireValue;
1052
971
  const ref = decodeCtx.columnRefs.get(alias);
1053
972
  let cellCtx;
1054
973
  if (ref) cellCtx = {
@@ -1062,42 +981,26 @@ async function decodeField(alias, wireValue, decodeCtx, jsonValidators, rowCtx)
1062
981
  const { column: _drop, ...rowCtxWithoutColumn } = rowCtx;
1063
982
  cellCtx = rowCtxWithoutColumn;
1064
983
  }
1065
- let decoded;
1066
984
  try {
1067
- decoded = await codec$1.decode(wireValue, cellCtx);
1068
- } catch (error) {
1069
- wrapDecodeFailure(error, alias, ref, codec$1, wireValue);
1070
- }
1071
- if (jsonValidators && ref) try {
1072
- validateJsonValue(jsonValidators, ref.table, ref.column, decoded, "decode", codec$1.id);
985
+ return await codec.decode(wireValue, cellCtx);
1073
986
  } catch (error) {
1074
- if (isJsonSchemaValidationError(error)) throw error;
1075
- wrapDecodeFailure(error, alias, ref, codec$1, wireValue);
987
+ if (isRuntimeError(error)) throw error;
988
+ wrapDecodeFailure(error, alias, ref, codec, wireValue);
1076
989
  }
1077
- return decoded;
1078
990
  }
1079
991
  /**
1080
- * Decodes a row by dispatching all per-cell codec calls concurrently via
1081
- * `Promise.all`. Each cell follows the single-armed `decodeField` path.
1082
- * Failures are wrapped in `RUNTIME.DECODE_FAILED` with `{ table, column,
1083
- * codec }` (or `{ alias, codec }` when no column ref is resolvable) and the
1084
- * original error attached on `cause`.
992
+ * Decodes a row by dispatching all per-cell codec calls concurrently via `Promise.all`. Each cell follows the single-armed `decodeField` path. Failures are wrapped in `RUNTIME.DECODE_FAILED` with `{ table, column, codec }` (or `{ alias, codec }` when no column ref is resolvable) and the original error attached on `cause`.
1085
993
  *
1086
994
  * When `rowCtx.signal` is provided:
1087
995
  *
1088
- * - **Already-aborted at entry** short-circuits with `RUNTIME.ABORTED`
1089
- * (`{ phase: 'decode' }`) before any `codec.decode` call is made.
1090
- * - **Mid-flight aborts** race the per-cell `Promise.all` against the
1091
- * signal so the runtime returns promptly even when codec bodies ignore
1092
- * it. In-flight bodies that ignore the signal complete in the
1093
- * background (cooperative cancellation).
1094
- * - Existing `RUNTIME.DECODE_FAILED` envelopes from codec bodies pass
1095
- * through unchanged (no double wrap).
996
+ * - **Already-aborted at entry** short-circuits with `RUNTIME.ABORTED` (`{ phase: 'decode' }`) before any `codec.decode` call is made.
997
+ * - **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).
998
+ * - Existing `RUNTIME.DECODE_FAILED` envelopes from codec bodies pass through unchanged (no double wrap).
1096
999
  */
1097
- async function decodeRow(row, plan, registry, jsonValidators, rowCtx, contractCodecs) {
1000
+ async function decodeRow(row, plan, rowCtx, contractCodecs) {
1098
1001
  checkAborted(rowCtx, "decode");
1099
1002
  const signal = rowCtx.signal;
1100
- const decodeCtx = buildDecodeContext(plan, registry, contractCodecs);
1003
+ const decodeCtx = buildDecodeContext(plan, contractCodecs);
1101
1004
  const aliases = decodeCtx.aliases ?? Object.keys(row);
1102
1005
  if (decodeCtx.aliases !== void 0) {
1103
1006
  for (const alias of decodeCtx.aliases) if (!Object.hasOwn(row, alias)) throw runtimeError("RUNTIME.DECODE_FAILED", `Row missing projection alias "${alias}"`, {
@@ -1120,7 +1023,7 @@ async function decodeRow(row, plan, registry, jsonValidators, rowCtx, contractCo
1120
1023
  tasks.push(Promise.resolve(void 0));
1121
1024
  continue;
1122
1025
  }
1123
- tasks.push(decodeField(alias, wireValue, decodeCtx, jsonValidators, rowCtx));
1026
+ tasks.push(decodeField(alias, wireValue, decodeCtx, rowCtx));
1124
1027
  }
1125
1028
  const settled = await raceAgainstAbort(Promise.all(tasks), signal, "decode");
1126
1029
  for (const entry of includeIndices) settled[entry.index] = decodeIncludeAggregate(entry.alias, entry.value);
@@ -1133,35 +1036,28 @@ async function decodeRow(row, plan, registry, jsonValidators, rowCtx, contractCo
1133
1036
  //#region src/codecs/encoding.ts
1134
1037
  const NO_METADATA = Object.freeze({
1135
1038
  codecId: void 0,
1136
- name: void 0
1039
+ name: void 0,
1040
+ refs: void 0
1137
1041
  });
1138
1042
  /**
1139
1043
  * Resolve the codec for an outgoing param.
1140
1044
  *
1141
- * Phase B (and AC-5-deferred carve-out): `ParamRef` does not carry a
1142
- * `(table, column)` ref today — every `ParamRef` carries `codecId` but
1143
- * not the column it relates to. Encode-side dispatch therefore consults
1144
- * `contractCodecs.forCodecId(codecId)` (which itself prefers the
1145
- * contract-walk-derived shared codec, falling back to the legacy
1146
- * `CodecRegistry.get` for parameterized codec ids whose contracts don't
1147
- * have a column the walk could resolve through).
1045
+ * Column-aware dispatch: when `metadata.refs` is populated by a column-bound construction site, prefer `contractCodecs.forColumn(refs.table, refs.column)` — that returns the per-instance codec the contract walk materialized for the `(table, column)` pair, encoding the column's typeParams (e.g. `vector(1024)` vs. `vector(1536)`).
1046
+ *
1047
+ * On a column-lookup miss the resolver falls through to `forCodecId`. The wrong-instance risk for parameterized codecs is closed off structurally:
1148
1048
  *
1149
- * For the parameterized codecs shipped at Phase B (pgvector, postgres
1150
- * json/jsonb), encode is per-instance-stateless w.r.t. params:
1151
- * - pgvector formats `[v1,v2,...]` regardless of declared length;
1152
- * - postgres json/jsonb encode is `JSON.stringify` regardless of schema.
1049
+ * 1. `buildContractCodecRegistry` pre-populates `byCodecId` with one canonical instance per non-parameterized descriptor; parameterized descriptors are intentionally absent from this pre-population. 2. `forCodecId` rejects ambiguous parameterized fallbacks (`ambiguousCodecIds`) — if the contract walk resolved more than one distinct instance under a single parameterized id, the call throws rather than binding to
1050
+ * whichever landed first. 3. For the non-ambiguous parameterized case (a single column with that id), `byCodecId` stores the column-correct per-instance codec, so the fall-through still resolves to the right instance.
1153
1051
  *
1154
- * So the codec-id-keyed lookup yields a structurally equivalent encoder
1155
- * even when the resolved per-instance codec carries extra state (e.g. a
1156
- * compiled JSON-Schema validator used only by `decode`). TML-2357 retires
1157
- * the fallback by threading `ParamRef.refs` through column-bound
1158
- * construction sites.
1052
+ * Refs-less fallback: ParamRefs constructed outside a column-bound site (literals, transient builder state) carry a non-parameterized `codecId` whose dispatch is ambiguity-free. The validator pass (`validateParamRefRefs`) already enforced refs on every parameterized ParamRef before encode runs.
1159
1053
  */
1160
- function resolveParamCodec(metadata, registry, contractCodecs) {
1054
+ function resolveParamCodec(metadata, contractCodecs, aliasResolver) {
1161
1055
  if (!metadata.codecId) return void 0;
1162
- const fromContract = contractCodecs?.forCodecId(metadata.codecId);
1163
- if (fromContract) return fromContract;
1164
- return registry.get(metadata.codecId);
1056
+ if (metadata.refs && contractCodecs) {
1057
+ const byColumn = contractCodecs.forColumn(aliasResolver(metadata.refs.table), metadata.refs.column);
1058
+ if (byColumn && byColumn.id === metadata.codecId) return byColumn;
1059
+ }
1060
+ return contractCodecs?.forCodecId(metadata.codecId);
1165
1061
  }
1166
1062
  function paramLabel(metadata, paramIndex) {
1167
1063
  return metadata.name ?? `param[${paramIndex}]`;
@@ -1176,36 +1072,26 @@ function wrapEncodeFailure(error, metadata, paramIndex, codecId) {
1176
1072
  wrapped.cause = error;
1177
1073
  throw wrapped;
1178
1074
  }
1179
- async function encodeParamValue(value, metadata, paramIndex, registry, ctx, contractCodecs) {
1075
+ async function encodeParamValue(value, metadata, paramIndex, ctx, contractCodecs, aliasResolver) {
1180
1076
  if (value === null || value === void 0) return null;
1181
- const codec$1 = resolveParamCodec(metadata, registry, contractCodecs);
1182
- if (!codec$1) return value;
1077
+ const codec = resolveParamCodec(metadata, contractCodecs, aliasResolver);
1078
+ if (!codec) return value;
1183
1079
  try {
1184
- return await codec$1.encode(value, ctx);
1080
+ return await codec.encode(value, ctx);
1185
1081
  } catch (error) {
1186
- wrapEncodeFailure(error, metadata, paramIndex, codec$1.id);
1082
+ wrapEncodeFailure(error, metadata, paramIndex, codec.id);
1187
1083
  }
1188
1084
  }
1189
1085
  /**
1190
- * Encodes all parameters concurrently via `Promise.all`. Per parameter, sync-
1191
- * and async-authored codecs share the same path: `codec.encode → await →
1192
- * return`. Param-level failures are wrapped in `RUNTIME.ENCODE_FAILED`.
1086
+ * Encodes all parameters concurrently via `Promise.all`. Per parameter, sync-and async-authored codecs share the same path: `codec.encode → await → return`. Param-level failures are wrapped in `RUNTIME.ENCODE_FAILED`.
1193
1087
  *
1194
1088
  * When `ctx.signal` is provided:
1195
1089
  *
1196
- * - **Already-aborted at entry** short-circuits with `RUNTIME.ABORTED`
1197
- * (`{ phase: 'encode' }`) before any `codec.encode` call is made codecs
1198
- * can pin this with a per-call counter that stays at zero.
1199
- * - **Mid-flight abort** races the per-param `Promise.all` against
1200
- * `abortable(ctx.signal)`. The runtime returns `RUNTIME.ABORTED` promptly
1201
- * even if codec bodies ignore the signal; the in-flight bodies are
1202
- * abandoned and run to completion in the background (cooperative
1203
- * cancellation, see ADR 204).
1204
- * - Existing `RUNTIME.ENCODE_FAILED` envelopes that surface from a codec
1205
- * body before the runtime observes the abort pass through unchanged
1206
- * (no double wrap).
1090
+ * - **Already-aborted at entry** short-circuits with `RUNTIME.ABORTED` (`{ phase: 'encode' }`) before any `codec.encode` call is made — codecs can pin this with a per-call counter that stays at zero.
1091
+ * - **Mid-flight abort** races the per-param `Promise.all` against `abortable(ctx.signal)`. The runtime returns `RUNTIME.ABORTED` promptly even if codec bodies ignore the signal; the in-flight bodies are abandoned and run to completion in the background (cooperative cancellation, see ADR 204).
1092
+ * - Existing `RUNTIME.ENCODE_FAILED` envelopes that surface from a codec body before the runtime observes the abort pass through unchanged (no double wrap).
1207
1093
  */
1208
- async function encodeParams(plan, registry, ctx, contractCodecs) {
1094
+ async function encodeParams(plan, ctx, contractCodecs) {
1209
1095
  checkAborted(ctx, "encode");
1210
1096
  const signal = ctx.signal;
1211
1097
  if (plan.params.length === 0) return plan.params;
@@ -1217,12 +1103,14 @@ async function encodeParams(plan, registry, ctx, contractCodecs) {
1217
1103
  const ref = refs[i];
1218
1104
  if (ref) metadata[i] = {
1219
1105
  codecId: ref.codecId,
1220
- name: ref.name
1106
+ name: ref.name,
1107
+ refs: ref.refs
1221
1108
  };
1222
1109
  }
1223
1110
  }
1111
+ const aliasResolver = makeAliasResolver(plan.ast);
1224
1112
  const tasks = new Array(paramCount);
1225
- for (let i = 0; i < paramCount; i++) tasks[i] = encodeParamValue(plan.params[i], metadata[i] ?? NO_METADATA, i, registry, ctx, contractCodecs);
1113
+ for (let i = 0; i < paramCount; i++) tasks[i] = encodeParamValue(plan.params[i], metadata[i] ?? NO_METADATA, i, ctx, contractCodecs, aliasResolver);
1226
1114
  const settled = await raceAgainstAbort(Promise.all(tasks), signal, "encode");
1227
1115
  return Object.freeze(settled);
1228
1116
  }
@@ -1331,15 +1219,20 @@ var SqlFamilyAdapter = class {
1331
1219
  function isExecutionPlan(plan) {
1332
1220
  return "sql" in plan;
1333
1221
  }
1222
+ // v8 ignore next 2
1223
+ const noopLogSink = () => {};
1224
+ const noopLog = {
1225
+ info: noopLogSink,
1226
+ warn: noopLogSink,
1227
+ error: noopLogSink
1228
+ };
1334
1229
  var SqlRuntimeImpl = class extends RuntimeCore {
1335
1230
  contract;
1336
1231
  adapter;
1337
1232
  driver;
1338
1233
  familyAdapter;
1339
- codecRegistry;
1340
1234
  contractCodecs;
1341
1235
  codecDescriptors;
1342
- jsonSchemaValidators;
1343
1236
  sqlCtx;
1344
1237
  verify;
1345
1238
  codecRegistryValidated;
@@ -1353,11 +1246,7 @@ var SqlRuntimeImpl = class extends RuntimeCore {
1353
1246
  contract: context.contract,
1354
1247
  mode: mode ?? "strict",
1355
1248
  now: () => Date.now(),
1356
- log: log ?? {
1357
- info: () => {},
1358
- warn: () => {},
1359
- error: () => {}
1360
- },
1249
+ log: log ?? noopLog,
1361
1250
  contentHash: (exec) => computeSqlContentHash(exec)
1362
1251
  };
1363
1252
  super({
@@ -1368,10 +1257,8 @@ var SqlRuntimeImpl = class extends RuntimeCore {
1368
1257
  this.adapter = adapter;
1369
1258
  this.driver = driver;
1370
1259
  this.familyAdapter = new SqlFamilyAdapter(context.contract, adapter.profile);
1371
- this.codecRegistry = context.codecs;
1372
1260
  this.contractCodecs = context.contractCodecs;
1373
1261
  this.codecDescriptors = context.codecDescriptors;
1374
- this.jsonSchemaValidators = context.jsonSchemaValidators;
1375
1262
  this.sqlCtx = sqlCtx;
1376
1263
  this.verify = verify;
1377
1264
  this.codecRegistryValidated = false;
@@ -1384,29 +1271,22 @@ var SqlRuntimeImpl = class extends RuntimeCore {
1384
1271
  }
1385
1272
  }
1386
1273
  /**
1387
- * Lower a `SqlQueryPlan` (AST + meta) into a `SqlExecutionPlan` with
1388
- * encoded parameters ready for the driver. This is the single point at
1389
- * which params transition from app-layer values to driver wire-format.
1274
+ * Lower a `SqlQueryPlan` (AST + meta) into a `SqlExecutionPlan` with encoded parameters ready for the driver. This is the single point at which params transition from app-layer values to driver wire-format.
1390
1275
  *
1391
- * `ctx: SqlCodecCallContext` is forwarded to `encodeParams` so per-query
1392
- * cancellation reaches every codec body during parameter encoding. The
1393
- * framework abstract typed this as `CodecCallContext`; the SQL family
1394
- * narrows it to the SQL-specific extension. SQL params do not populate
1395
- * `ctx.column` — encode-side column metadata is the middleware's domain.
1276
+ * `ctx: SqlCodecCallContext` is forwarded to `encodeParams` so per-query cancellation reaches every codec body during parameter encoding. The framework abstract typed this as `CodecCallContext`; the SQL family narrows it to the SQL-specific extension. SQL params do not populate `ctx.column` — encode-side column metadata is the middleware's domain.
1396
1277
  */
1397
1278
  async lower(plan, ctx) {
1279
+ validateParamRefRefs(plan.ast, this.codecDescriptors);
1398
1280
  const lowered = lowerSqlPlan(this.adapter, this.contract, plan);
1399
1281
  return Object.freeze({
1400
1282
  ...lowered,
1401
- params: await encodeParams(lowered, this.codecRegistry, ctx, this.contractCodecs)
1283
+ params: await encodeParams(lowered, ctx, this.contractCodecs)
1402
1284
  });
1403
1285
  }
1404
1286
  /**
1405
- * Default driver invocation. Production execution paths override the
1406
- * queryable target (e.g. transaction or connection) by going through
1407
- * `executeAgainstQueryable`; this implementation supports any caller of
1408
- * `super.execute(plan)` and the abstract-base contract.
1287
+ * Default driver invocation required by the abstract `RuntimeCore` contract. Every production path overrides `execute()` and routes through `executeAgainstQueryable`, so this hook is defensive only — subclasses that delegate back to `super.execute()` would land here.
1409
1288
  */
1289
+ // v8 ignore next 6
1410
1290
  runDriver(exec) {
1411
1291
  return this.driver.execute({
1412
1292
  sql: exec.sql,
@@ -1414,13 +1294,8 @@ var SqlRuntimeImpl = class extends RuntimeCore {
1414
1294
  });
1415
1295
  }
1416
1296
  /**
1417
- * SQL pre-compile hook. Runs the registered middleware `beforeCompile`
1418
- * chain over the plan's draft (AST + meta). Returns the original plan
1419
- * unchanged when no middleware rewrote the AST; otherwise returns a new
1420
- * plan carrying the rewritten AST and meta. The AST is the authoritative
1421
- * source of execution metadata, so a rewrite needs no sidecar
1422
- * reconciliation here — the lowering adapter and the encoder both walk
1423
- * the rewritten AST directly.
1297
+ * SQL pre-compile hook. Runs the registered middleware `beforeCompile` chain over the plan's draft (AST + meta). Returns the original plan unchanged when no middleware rewrote the AST; otherwise returns a new plan carrying the rewritten AST and meta. The AST is the authoritative source of execution metadata, so a rewrite needs no sidecar reconciliation here — the lowering adapter and the encoder both walk the rewritten
1298
+ * AST directly.
1424
1299
  */
1425
1300
  async runBeforeCompile(plan) {
1426
1301
  const rewrittenDraft = await runBeforeCompileChain(this.middleware, {
@@ -1443,10 +1318,14 @@ var SqlRuntimeImpl = class extends RuntimeCore {
1443
1318
  const codecCtx = signal === void 0 ? {} : { signal };
1444
1319
  const generator = async function* () {
1445
1320
  checkAborted(codecCtx, "stream");
1446
- const exec = isExecutionPlan(plan) ? Object.freeze({
1447
- ...plan,
1448
- params: await encodeParams(plan, self.codecRegistry, codecCtx, self.contractCodecs)
1449
- }) : await self.lower(await self.runBeforeCompile(plan), codecCtx);
1321
+ let exec;
1322
+ if (isExecutionPlan(plan)) {
1323
+ if (plan.ast) validateParamRefRefs(plan.ast, self.codecDescriptors);
1324
+ exec = Object.freeze({
1325
+ ...plan,
1326
+ params: await encodeParams(plan, codecCtx, self.contractCodecs)
1327
+ });
1328
+ } else exec = await self.lower(await self.runBeforeCompile(plan), codecCtx);
1450
1329
  self.familyAdapter.validatePlan(exec, self.contract);
1451
1330
  self._telemetry = null;
1452
1331
  if (!self.startupVerified && self.verify.mode === "startup") await self.verifyMarker();
@@ -1464,7 +1343,7 @@ var SqlRuntimeImpl = class extends RuntimeCore {
1464
1343
  checkAborted(codecCtx, "stream");
1465
1344
  const next = await iterator.next();
1466
1345
  if (next.done) break;
1467
- yield await decodeRow(next.value, exec, self.codecRegistry, self.jsonSchemaValidators, codecCtx, self.contractCodecs);
1346
+ yield await decodeRow(next.value, exec, codecCtx, self.contractCodecs);
1468
1347
  }
1469
1348
  } finally {
1470
1349
  await iterator.return?.();
@@ -1525,8 +1404,6 @@ var SqlRuntimeImpl = class extends RuntimeCore {
1525
1404
  }
1526
1405
  }
1527
1406
  async verifyMarker() {
1528
- if (this.verify.mode === "always") this.verified = false;
1529
- if (this.verified) return;
1530
1407
  const readStatement = this.familyAdapter.markerReader.readMarkerStatement();
1531
1408
  const result = await this.driver.query(readStatement.sql, readStatement.params);
1532
1409
  if (result.rows.length === 0) {
@@ -1637,4 +1514,4 @@ function createRuntime(options) {
1637
1514
 
1638
1515
  //#endregion
1639
1516
  export { readContractMarker as a, createSqlExecutionStack as c, parseContractMarkerRow as d, lowerSqlPlan as f, validateContractCodecMappings as h, ensureTableStatement as i, lints as l, validateCodecRegistryCompleteness as m, withTransaction as n, writeContractMarker as o, extractCodecIds as p, ensureSchemaStatement as r, createExecutionContext as s, createRuntime as t, budgets as u };
1640
- //# sourceMappingURL=exports-BcX9wp4z.mjs.map
1517
+ //# sourceMappingURL=exports-CZIUsCRE.mjs.map