@prisma-next/sql-runtime 0.5.0-dev.60 → 0.5.0-dev.61
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.
- package/dist/{exports-BcX9wp4z.mjs → exports-CZIUsCRE.mjs} +197 -320
- package/dist/exports-CZIUsCRE.mjs.map +1 -0
- package/dist/{index-DkthtnOX.d.mts → index-Ba3eysL3.d.mts} +19 -44
- package/dist/index-Ba3eysL3.d.mts.map +1 -0
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/test/utils.d.mts +31 -26
- package/dist/test/utils.d.mts.map +1 -1
- package/dist/test/utils.mjs +99 -55
- package/dist/test/utils.mjs.map +1 -1
- package/package.json +11 -11
- package/src/codecs/alias-resolver.ts +34 -0
- package/src/codecs/decoding.ts +49 -83
- package/src/codecs/encoding.ts +44 -56
- package/src/codecs/validation.ts +3 -22
- package/src/middleware/budgets.ts +13 -27
- package/src/sql-context.ts +119 -268
- package/src/sql-runtime.ts +39 -101
- package/dist/exports-BcX9wp4z.mjs.map +0 -1
- package/dist/index-DkthtnOX.d.mts.map +0 -1
- package/src/codecs/json-schema-validation.ts +0 -61
|
@@ -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,
|
|
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 (
|
|
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:
|
|
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
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
|
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,
|
|
450
|
-
const result =
|
|
451
|
-
if (result instanceof Promise) throw runtimeError("RUNTIME.TYPE_PARAMS_INVALID", `paramsSchema for codec '${
|
|
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:
|
|
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: ${
|
|
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:
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
486
|
-
const
|
|
487
|
-
const
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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
|
-
|
|
503
|
-
|
|
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
|
-
*
|
|
586
|
-
*
|
|
587
|
-
*
|
|
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,
|
|
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
|
|
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: `<
|
|
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
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
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
|
-
|
|
653
|
-
|
|
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
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
}
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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/
|
|
819
|
+
//#region src/codecs/alias-resolver.ts
|
|
886
820
|
/**
|
|
887
|
-
*
|
|
821
|
+
* Build a map from query-local table aliases to their underlying source table names.
|
|
888
822
|
*
|
|
889
|
-
*
|
|
890
|
-
*
|
|
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
|
|
893
|
-
const
|
|
894
|
-
const
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
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
|
|
910
|
-
if (
|
|
911
|
-
|
|
912
|
-
|
|
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
|
-
*
|
|
933
|
-
*
|
|
934
|
-
* instance
|
|
935
|
-
*
|
|
936
|
-
*
|
|
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
|
-
*
|
|
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,
|
|
947
|
-
if (
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
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,
|
|
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
|
|
978
|
-
if (codec
|
|
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
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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,
|
|
967
|
+
async function decodeField(alias, wireValue, decodeCtx, rowCtx) {
|
|
1049
968
|
if (wireValue === null) return null;
|
|
1050
|
-
const codec
|
|
1051
|
-
if (!codec
|
|
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
|
-
|
|
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 (
|
|
1075
|
-
wrapDecodeFailure(error, alias, ref, codec
|
|
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
|
-
*
|
|
1090
|
-
* -
|
|
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,
|
|
1000
|
+
async function decodeRow(row, plan, rowCtx, contractCodecs) {
|
|
1098
1001
|
checkAborted(rowCtx, "decode");
|
|
1099
1002
|
const signal = rowCtx.signal;
|
|
1100
|
-
const decodeCtx = buildDecodeContext(plan,
|
|
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,
|
|
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
|
-
*
|
|
1142
|
-
*
|
|
1143
|
-
*
|
|
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
|
-
*
|
|
1150
|
-
*
|
|
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
|
-
*
|
|
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,
|
|
1054
|
+
function resolveParamCodec(metadata, contractCodecs, aliasResolver) {
|
|
1161
1055
|
if (!metadata.codecId) return void 0;
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
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,
|
|
1075
|
+
async function encodeParamValue(value, metadata, paramIndex, ctx, contractCodecs, aliasResolver) {
|
|
1180
1076
|
if (value === null || value === void 0) return null;
|
|
1181
|
-
const codec
|
|
1182
|
-
if (!codec
|
|
1077
|
+
const codec = resolveParamCodec(metadata, contractCodecs, aliasResolver);
|
|
1078
|
+
if (!codec) return value;
|
|
1183
1079
|
try {
|
|
1184
|
-
return await codec
|
|
1080
|
+
return await codec.encode(value, ctx);
|
|
1185
1081
|
} catch (error) {
|
|
1186
|
-
wrapEncodeFailure(error, metadata, paramIndex, codec
|
|
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
|
-
*
|
|
1198
|
-
*
|
|
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,
|
|
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,
|
|
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,
|
|
1283
|
+
params: await encodeParams(lowered, ctx, this.contractCodecs)
|
|
1402
1284
|
});
|
|
1403
1285
|
}
|
|
1404
1286
|
/**
|
|
1405
|
-
* Default driver invocation.
|
|
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
|
-
*
|
|
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
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
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,
|
|
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-
|
|
1517
|
+
//# sourceMappingURL=exports-CZIUsCRE.mjs.map
|