@prisma-next/sql-runtime 0.12.0 → 0.13.0-dev.10
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/README.md +7 -4
- package/dist/{exports-CXYd2w6k.mjs → exports-DDqF-xmg.mjs} +158 -305
- package/dist/exports-DDqF-xmg.mjs.map +1 -0
- package/dist/{index-DTr7KoUs.d.mts → index-JOQlRa75.d.mts} +24 -59
- package/dist/index-JOQlRa75.d.mts.map +1 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/test/utils.d.mts +28 -18
- package/dist/test/utils.d.mts.map +1 -1
- package/dist/test/utils.mjs +34 -28
- package/dist/test/utils.mjs.map +1 -1
- package/package.json +15 -15
- package/src/codecs/ast-codec-registry.ts +25 -0
- package/src/codecs/validation.ts +2 -2
- package/src/exports/index.ts +3 -10
- package/src/sql-context.ts +50 -33
- package/src/sql-runtime.ts +19 -20
- package/dist/exports-CXYd2w6k.mjs.map +0 -1
- package/dist/index-DTr7KoUs.d.mts.map +0 -1
- package/src/marker.ts +0 -75
- package/src/sql-marker.ts +0 -143
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ Execute SQL query Plans with deterministic verification, guardrails, and feedbac
|
|
|
23
23
|
- **Mutation Default Generator Composition**: Assemble execution default generators from composed target/adapter/extension contributors
|
|
24
24
|
- **No Implicit Generator Baseline**: Runtime resolves generator ids only from composed contributors (no built-in runtime fallback table)
|
|
25
25
|
- **SQL Context Creation**: Create runtime contexts with SQL contracts, adapters, and codecs
|
|
26
|
-
- **SQL Marker
|
|
26
|
+
- **SQL Marker Bootstrap**: Provide test-utility helpers for bootstrapping the marker table; marker reads and writes both go through the control adapter SPI
|
|
27
27
|
- **Codec Encoding/Decoding**: Encode parameters and decode rows using SQL codec registries
|
|
28
28
|
- **Codec Validation**: Validate that codec registries contain all required codecs
|
|
29
29
|
- **SQL Family Adapter**: Implement `RuntimeFamilyAdapter` for SQL contracts (defined in `runtime-spi.ts`)
|
|
@@ -124,12 +124,15 @@ const runtime = createRuntime({
|
|
|
124
124
|
- `validateCodecRegistryCompleteness` - Codec validation
|
|
125
125
|
- `extractCodecIds` - Extract codec IDs from a contract
|
|
126
126
|
- `validateContractCodecMappings` - Validate contract codec mappings against registry
|
|
127
|
+
- `createAstCodecRegistry` - Contract-free codec registry that resolves codecs from AST-supplied `CodecRef`s
|
|
128
|
+
- `deriveParamMetadata` - Walk a control DML AST and collect per-value codec metadata for encoding
|
|
129
|
+
- `encodeParamsWithMetadata` - Encode lowered control DML parameters through their codecs
|
|
127
130
|
|
|
128
131
|
### SQL Marker
|
|
129
132
|
|
|
130
|
-
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
+
- Test helpers in `./test/utils` bootstrap marker tables via contract-free DDL lowering
|
|
134
|
+
|
|
135
|
+
Marker reads and writes go through the control adapter SPI (`adapter.readMarker`, `adapter.initMarker`, `adapter.updateMarker`, `adapter.writeLedgerEntry`).
|
|
133
136
|
|
|
134
137
|
### Plan Lowering
|
|
135
138
|
|
|
@@ -1,23 +1,143 @@
|
|
|
1
1
|
import { AsyncIterableResult, RuntimeCore, checkAborted, checkMiddlewareCompatibility, isRuntimeError, raceAgainstAbort, runBeforeExecuteChain, runWithMiddleware, runtimeError } from "@prisma-next/framework-components/runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { canonicalizeJson } from "@prisma-next/framework-components/utils";
|
|
3
3
|
import { PreparedParamRef, collectOrderedParamRefs, isQueryAst } from "@prisma-next/sql-relational-core/ast";
|
|
4
4
|
import { ifDefined } from "@prisma-next/utils/defined";
|
|
5
5
|
import { checkContractComponentRequirements, mergeCapabilityMatrices } from "@prisma-next/framework-components/components";
|
|
6
6
|
import { createExecutionStack } from "@prisma-next/framework-components/execution";
|
|
7
|
-
import { canonicalizeJson } from "@prisma-next/framework-components/utils";
|
|
8
7
|
import { isPostgresEnumStorageEntry } from "@prisma-next/sql-contract/types";
|
|
9
8
|
import { blindCast } from "@prisma-next/utils/casts";
|
|
10
9
|
import { createSqlOperationRegistry } from "@prisma-next/sql-operations";
|
|
11
10
|
import { buildCodecDescriptorRegistry } from "@prisma-next/sql-relational-core/codec-descriptor-registry";
|
|
12
|
-
import { APP_SPACE_ID } from "@prisma-next/framework-components/control";
|
|
13
11
|
import { createSqlParamRefMutator } from "@prisma-next/sql-relational-core/middleware";
|
|
14
12
|
import { canonicalStringify } from "@prisma-next/utils/canonical-stringify";
|
|
15
13
|
import { hashContent } from "@prisma-next/utils/hash-content";
|
|
16
14
|
import { createHash } from "node:crypto";
|
|
15
|
+
//#region src/codecs/ast-codec-resolver.ts
|
|
16
|
+
/**
|
|
17
|
+
* Build an {@link AstCodecResolver} bound to a descriptor registry and a per-call instance-context factory.
|
|
18
|
+
*
|
|
19
|
+
* The instance-context factory lets callers control `name` / `usedAt` for refs the AST supplies (e.g. AST-embedded migration ops where the materialisation site is the AST node, not a contract column). The contract-walk pre-population path constructs its own contexts and invokes the resolver with those refs to seed the cache.
|
|
20
|
+
*/
|
|
21
|
+
function createAstCodecResolver(descriptors, instanceContextFor) {
|
|
22
|
+
const cache = /* @__PURE__ */ new Map();
|
|
23
|
+
return { forCodecRef(ref) {
|
|
24
|
+
const key = `${ref.codecId}:${canonicalizeJson(ref.typeParams)}`;
|
|
25
|
+
const cached = cache.get(key);
|
|
26
|
+
if (cached) return cached;
|
|
27
|
+
const descriptor = descriptors.descriptorFor(ref.codecId);
|
|
28
|
+
if (!descriptor) throw runtimeError("RUNTIME.CODEC_DESCRIPTOR_MISSING", `No codec descriptor registered for codecId '${ref.codecId}'.`, { codecId: ref.codecId });
|
|
29
|
+
const validated = validateTypeParams$1(descriptor.paramsSchema, descriptor.isParameterized && ref.typeParams === void 0 ? {
|
|
30
|
+
...ref,
|
|
31
|
+
typeParams: {}
|
|
32
|
+
} : ref);
|
|
33
|
+
const ctx = instanceContextFor(ref);
|
|
34
|
+
const codec = descriptor.factory(validated)(ctx);
|
|
35
|
+
cache.set(key, codec);
|
|
36
|
+
return codec;
|
|
37
|
+
} };
|
|
38
|
+
}
|
|
39
|
+
function validateTypeParams$1(paramsSchema, ref) {
|
|
40
|
+
const result = paramsSchema["~standard"].validate(ref.typeParams);
|
|
41
|
+
if (result instanceof Promise) throw runtimeError("RUNTIME.TYPE_PARAMS_INVALID", `paramsSchema for codec '${ref.codecId}' returned a Promise; runtime validation requires a synchronous Standard Schema validator.`, {
|
|
42
|
+
codecId: ref.codecId,
|
|
43
|
+
typeParams: ref.typeParams
|
|
44
|
+
});
|
|
45
|
+
if ("issues" in result && result.issues) {
|
|
46
|
+
const messages = result.issues.map((issue) => issue.message).join("; ");
|
|
47
|
+
throw runtimeError("RUNTIME.TYPE_PARAMS_INVALID", `Invalid typeParams for codec '${ref.codecId}': ${messages}`, {
|
|
48
|
+
codecId: ref.codecId,
|
|
49
|
+
typeParams: ref.typeParams
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return result.value;
|
|
53
|
+
}
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/codecs/ast-codec-registry.ts
|
|
56
|
+
/**
|
|
57
|
+
* Build a contract-free {@link ContractCodecRegistry} that resolves codecs
|
|
58
|
+
* purely from AST-supplied {@link import('@prisma-next/framework-components/codec').CodecRef}s
|
|
59
|
+
* against a target's descriptor registry.
|
|
60
|
+
*
|
|
61
|
+
* Dispatch is driven entirely by `CodecRef`s embedded in AST nodes; no
|
|
62
|
+
* contract walk is needed. `forColumn` always returns `undefined` — this
|
|
63
|
+
* registry carries no column-to-codec mappings.
|
|
64
|
+
*/
|
|
65
|
+
function createAstCodecRegistry(descriptors) {
|
|
66
|
+
const resolver = createAstCodecResolver(descriptors, (ref) => ({
|
|
67
|
+
name: ref.codecId,
|
|
68
|
+
usedAt: []
|
|
69
|
+
}));
|
|
70
|
+
return {
|
|
71
|
+
forColumn: () => void 0,
|
|
72
|
+
forCodecRef: (ref) => resolver.forCodecRef(ref)
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region src/codecs/encoding.ts
|
|
77
|
+
const NO_METADATA = Object.freeze({
|
|
78
|
+
codec: void 0,
|
|
79
|
+
name: void 0
|
|
80
|
+
});
|
|
81
|
+
function deriveParamMetadata(ast) {
|
|
82
|
+
return collectOrderedParamRefs(ast).map((ref) => {
|
|
83
|
+
return {
|
|
84
|
+
codec: ref.codec,
|
|
85
|
+
name: ref.name
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
function resolveParamCodec(metadata, contractCodecs) {
|
|
90
|
+
if (metadata.codec && contractCodecs) return contractCodecs.forCodecRef(metadata.codec);
|
|
91
|
+
}
|
|
92
|
+
function paramLabel(metadata, paramIndex) {
|
|
93
|
+
return metadata.name ?? `param[${paramIndex}]`;
|
|
94
|
+
}
|
|
95
|
+
function wrapEncodeFailure(error, metadata, paramIndex, codecId) {
|
|
96
|
+
const label = paramLabel(metadata, paramIndex);
|
|
97
|
+
const wrapped = runtimeError("RUNTIME.ENCODE_FAILED", `Failed to encode parameter ${label} with codec '${codecId}': ${error instanceof Error ? error.message : String(error)}`, {
|
|
98
|
+
label,
|
|
99
|
+
codec: codecId,
|
|
100
|
+
paramIndex
|
|
101
|
+
});
|
|
102
|
+
wrapped.cause = error;
|
|
103
|
+
throw wrapped;
|
|
104
|
+
}
|
|
105
|
+
async function encodeParamValue(value, metadata, paramIndex, ctx, contractCodecs) {
|
|
106
|
+
if (value === null || value === void 0) return null;
|
|
107
|
+
const codec = resolveParamCodec(metadata, contractCodecs);
|
|
108
|
+
if (!codec) return value;
|
|
109
|
+
try {
|
|
110
|
+
return await codec.encode(value, ctx);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
if (isRuntimeError(error)) throw error;
|
|
113
|
+
wrapEncodeFailure(error, metadata, paramIndex, codec.id);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* 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`.
|
|
118
|
+
*
|
|
119
|
+
* When `ctx.signal` is provided:
|
|
120
|
+
*
|
|
121
|
+
* - **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.
|
|
122
|
+
* - **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).
|
|
123
|
+
* - Existing `RUNTIME.ENCODE_FAILED` envelopes that surface from a codec body before the runtime observes the abort pass through unchanged (no double wrap).
|
|
124
|
+
*/
|
|
125
|
+
async function encodeParams(plan, ctx, contractCodecs) {
|
|
126
|
+
return encodeParamsWithMetadata(plan.params, deriveParamMetadata(plan.ast), ctx, contractCodecs);
|
|
127
|
+
}
|
|
128
|
+
async function encodeParamsWithMetadata(values, metadata, ctx, contractCodecs) {
|
|
129
|
+
checkAborted(ctx, "encode");
|
|
130
|
+
const signal = ctx.signal;
|
|
131
|
+
if (values.length === 0) return values;
|
|
132
|
+
const tasks = values.map((value, i) => encodeParamValue(value, metadata[i] ?? NO_METADATA, i, ctx, contractCodecs));
|
|
133
|
+
const settled = await raceAgainstAbort(Promise.all(tasks), signal, "encode");
|
|
134
|
+
return Object.freeze(settled);
|
|
135
|
+
}
|
|
136
|
+
//#endregion
|
|
17
137
|
//#region src/codecs/validation.ts
|
|
18
138
|
function extractCodecIds(contract) {
|
|
19
139
|
const codecIds = /* @__PURE__ */ new Set();
|
|
20
|
-
for (const ns of Object.values(contract.storage.namespaces)) for (const table of Object.values(ns.
|
|
140
|
+
for (const ns of Object.values(contract.storage.namespaces)) for (const table of Object.values(ns.entries.table)) for (const column of Object.values(table.columns)) {
|
|
21
141
|
const codecId = column.codecId;
|
|
22
142
|
codecIds.add(codecId);
|
|
23
143
|
}
|
|
@@ -25,7 +145,7 @@ function extractCodecIds(contract) {
|
|
|
25
145
|
}
|
|
26
146
|
function extractCodecIdsFromColumns(contract) {
|
|
27
147
|
const codecIds = /* @__PURE__ */ new Map();
|
|
28
|
-
for (const ns of Object.values(contract.storage.namespaces)) for (const [tableName, table] of Object.entries(ns.
|
|
148
|
+
for (const ns of Object.values(contract.storage.namespaces)) for (const [tableName, table] of Object.entries(ns.entries.table)) for (const [columnName, column] of Object.entries(table.columns)) {
|
|
29
149
|
const codecId = column.codecId;
|
|
30
150
|
const key = `${tableName}.${columnName}`;
|
|
31
151
|
codecIds.set(key, codecId);
|
|
@@ -84,50 +204,6 @@ function lowerSqlPlan(adapter, contract, queryPlan) {
|
|
|
84
204
|
});
|
|
85
205
|
}
|
|
86
206
|
//#endregion
|
|
87
|
-
//#region src/marker.ts
|
|
88
|
-
const MetaSchema = type({ "[string]": "unknown" });
|
|
89
|
-
function parseMeta(meta) {
|
|
90
|
-
if (meta === null || meta === void 0) return {};
|
|
91
|
-
let parsed;
|
|
92
|
-
if (typeof meta === "string") try {
|
|
93
|
-
parsed = JSON.parse(meta);
|
|
94
|
-
} catch {
|
|
95
|
-
return {};
|
|
96
|
-
}
|
|
97
|
-
else parsed = meta;
|
|
98
|
-
const result = MetaSchema(parsed);
|
|
99
|
-
if (result instanceof type.errors) return {};
|
|
100
|
-
return result;
|
|
101
|
-
}
|
|
102
|
-
const ContractMarkerRowSchema = type({
|
|
103
|
-
core_hash: "string",
|
|
104
|
-
profile_hash: "string",
|
|
105
|
-
"contract_json?": "unknown | null",
|
|
106
|
-
"canonical_version?": "number | null",
|
|
107
|
-
"updated_at?": "Date | string",
|
|
108
|
-
"app_tag?": "string | null",
|
|
109
|
-
"meta?": "unknown | null",
|
|
110
|
-
invariants: type("string").array()
|
|
111
|
-
});
|
|
112
|
-
function parseContractMarkerRow(row) {
|
|
113
|
-
const result = ContractMarkerRowSchema(row);
|
|
114
|
-
if (result instanceof type.errors) {
|
|
115
|
-
const messages = result.map((p) => p.message).join("; ");
|
|
116
|
-
throw new Error(`Invalid contract marker row: ${messages}`);
|
|
117
|
-
}
|
|
118
|
-
const updatedAt = result.updated_at ? result.updated_at instanceof Date ? result.updated_at : new Date(result.updated_at) : /* @__PURE__ */ new Date();
|
|
119
|
-
return {
|
|
120
|
-
storageHash: result.core_hash,
|
|
121
|
-
profileHash: result.profile_hash,
|
|
122
|
-
contractJson: result.contract_json ?? null,
|
|
123
|
-
canonicalVersion: result.canonical_version ?? null,
|
|
124
|
-
updatedAt,
|
|
125
|
-
appTag: result.app_tag ?? null,
|
|
126
|
-
meta: parseMeta(result.meta),
|
|
127
|
-
invariants: result.invariants
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
//#endregion
|
|
131
207
|
//#region src/middleware/budgets.ts
|
|
132
208
|
function hasAggregateWithoutGroupBy(ast) {
|
|
133
209
|
if (ast.groupBy !== void 0) return false;
|
|
@@ -396,46 +472,6 @@ function lints(options) {
|
|
|
396
472
|
});
|
|
397
473
|
}
|
|
398
474
|
//#endregion
|
|
399
|
-
//#region src/codecs/ast-codec-resolver.ts
|
|
400
|
-
/**
|
|
401
|
-
* Build an {@link AstCodecResolver} bound to a descriptor registry and a per-call instance-context factory.
|
|
402
|
-
*
|
|
403
|
-
* The instance-context factory lets callers control `name` / `usedAt` for refs the AST supplies (e.g. AST-embedded migration ops where the materialisation site is the AST node, not a contract column). The contract-walk pre-population path constructs its own contexts and invokes the resolver with those refs to seed the cache.
|
|
404
|
-
*/
|
|
405
|
-
function createAstCodecResolver(descriptors, instanceContextFor) {
|
|
406
|
-
const cache = /* @__PURE__ */ new Map();
|
|
407
|
-
return { forCodecRef(ref) {
|
|
408
|
-
const key = `${ref.codecId}:${canonicalizeJson(ref.typeParams)}`;
|
|
409
|
-
const cached = cache.get(key);
|
|
410
|
-
if (cached) return cached;
|
|
411
|
-
const descriptor = descriptors.descriptorFor(ref.codecId);
|
|
412
|
-
if (!descriptor) throw runtimeError("RUNTIME.CODEC_DESCRIPTOR_MISSING", `No codec descriptor registered for codecId '${ref.codecId}'.`, { codecId: ref.codecId });
|
|
413
|
-
const validated = validateTypeParams$1(descriptor.paramsSchema, descriptor.isParameterized && ref.typeParams === void 0 ? {
|
|
414
|
-
...ref,
|
|
415
|
-
typeParams: {}
|
|
416
|
-
} : ref);
|
|
417
|
-
const ctx = instanceContextFor(ref);
|
|
418
|
-
const codec = descriptor.factory(validated)(ctx);
|
|
419
|
-
cache.set(key, codec);
|
|
420
|
-
return codec;
|
|
421
|
-
} };
|
|
422
|
-
}
|
|
423
|
-
function validateTypeParams$1(paramsSchema, ref) {
|
|
424
|
-
const result = paramsSchema["~standard"].validate(ref.typeParams);
|
|
425
|
-
if (result instanceof Promise) throw runtimeError("RUNTIME.TYPE_PARAMS_INVALID", `paramsSchema for codec '${ref.codecId}' returned a Promise; runtime validation requires a synchronous Standard Schema validator.`, {
|
|
426
|
-
codecId: ref.codecId,
|
|
427
|
-
typeParams: ref.typeParams
|
|
428
|
-
});
|
|
429
|
-
if ("issues" in result && result.issues) {
|
|
430
|
-
const messages = result.issues.map((issue) => issue.message).join("; ");
|
|
431
|
-
throw runtimeError("RUNTIME.TYPE_PARAMS_INVALID", `Invalid typeParams for codec '${ref.codecId}': ${messages}`, {
|
|
432
|
-
codecId: ref.codecId,
|
|
433
|
-
typeParams: ref.typeParams
|
|
434
|
-
});
|
|
435
|
-
}
|
|
436
|
-
return result.value;
|
|
437
|
-
}
|
|
438
|
-
//#endregion
|
|
439
475
|
//#region src/sql-context.ts
|
|
440
476
|
function documentScopedCodecTypes(contract) {
|
|
441
477
|
return blindCast(contract.storage.types);
|
|
@@ -531,7 +567,7 @@ function collectCodecDescriptors(contributors) {
|
|
|
531
567
|
}
|
|
532
568
|
function collectTypeRefSites(storage) {
|
|
533
569
|
const sites = /* @__PURE__ */ new Map();
|
|
534
|
-
for (const ns of Object.values(storage.namespaces)) for (const [tableName, table] of Object.entries(ns.
|
|
570
|
+
for (const ns of Object.values(storage.namespaces)) for (const [tableName, table] of Object.entries(ns.entries.table)) for (const [columnName, column] of Object.entries(table.columns)) {
|
|
535
571
|
if (typeof column.typeRef !== "string") continue;
|
|
536
572
|
const list = sites.get(column.typeRef);
|
|
537
573
|
const entry = {
|
|
@@ -556,7 +592,7 @@ function initializeTypeHelpers(storage, documentTypes, codecDescriptors) {
|
|
|
556
592
|
helpers[typeName] = typeInstance;
|
|
557
593
|
continue;
|
|
558
594
|
}
|
|
559
|
-
const validatedParams = validateTypeParams(typeParams, descriptor, { typeName });
|
|
595
|
+
const validatedParams = validateTypeParams(typeParams ?? {}, descriptor, { typeName });
|
|
560
596
|
const ctx = {
|
|
561
597
|
name: typeName,
|
|
562
598
|
usedAt: typeRefSites.get(typeName) ?? []
|
|
@@ -566,7 +602,7 @@ function initializeTypeHelpers(storage, documentTypes, codecDescriptors) {
|
|
|
566
602
|
return helpers;
|
|
567
603
|
}
|
|
568
604
|
function validateColumnTypeParams(storage, codecDescriptors) {
|
|
569
|
-
for (const ns of Object.values(storage.namespaces)) for (const [tableName, table] of Object.entries(ns.
|
|
605
|
+
for (const ns of Object.values(storage.namespaces)) for (const [tableName, table] of Object.entries(ns.entries.table)) for (const [columnName, column] of Object.entries(table.columns)) if (column.typeParams) {
|
|
570
606
|
const descriptor = codecDescriptors.get(column.codecId);
|
|
571
607
|
if (descriptor) validateTypeParams(column.typeParams, descriptor, {
|
|
572
608
|
tableName,
|
|
@@ -586,8 +622,8 @@ function validateColumnTypeParams(storage, codecDescriptors) {
|
|
|
586
622
|
* Runs unconditionally from `createExecutionContext` so contract bugs fail fast at construction time instead of silently skipping affected columns in the codec registry's pre-population walk.
|
|
587
623
|
*/
|
|
588
624
|
function assertColumnCodecIntegrity(storage, codecDescriptors) {
|
|
589
|
-
for (const ns of Object.
|
|
590
|
-
const ref = codecDescriptors.codecRefForColumn(tableName, columnName);
|
|
625
|
+
for (const [namespaceId, ns] of Object.entries(storage.namespaces)) for (const [tableName, table] of Object.entries(ns.entries.table)) for (const columnName of Object.keys(table.columns)) {
|
|
626
|
+
const ref = codecDescriptors.codecRefForColumn(namespaceId, tableName, columnName);
|
|
591
627
|
if (!ref) continue;
|
|
592
628
|
const descriptor = codecDescriptors.descriptorFor(ref.codecId);
|
|
593
629
|
if (!descriptor) throw runtimeError("RUNTIME.CODEC_DESCRIPTOR_MISSING", `Column '${tableName}.${columnName}' references codec '${ref.codecId}' but no contributor registered a codec descriptor for that codecId. Add the extension pack that owns the codec to the runtime stack.`, {
|
|
@@ -613,7 +649,9 @@ function assertColumnCodecIntegrity(storage, codecDescriptors) {
|
|
|
613
649
|
actual: "no typeParams"
|
|
614
650
|
});
|
|
615
651
|
}
|
|
616
|
-
|
|
652
|
+
const refTypeParams = ref.typeParams;
|
|
653
|
+
const refHasTypeParamKeys = refTypeParams !== void 0 && refTypeParams !== null && typeof refTypeParams === "object" && !Array.isArray(refTypeParams) && Object.keys(refTypeParams).length > 0;
|
|
654
|
+
if (!descriptor.isParameterized && refHasTypeParamKeys) throw runtimeError("RUNTIME.CODEC_PARAMETERIZATION_MISMATCH", `Column '${tableName}.${columnName}' supplies typeParams to non-parameterized codec '${ref.codecId}'. Remove the typeParams or switch to a parameterized codec id.`, {
|
|
617
655
|
table: tableName,
|
|
618
656
|
column: columnName,
|
|
619
657
|
codecId: ref.codecId,
|
|
@@ -636,7 +674,7 @@ function assertColumnCodecIntegrity(storage, codecDescriptors) {
|
|
|
636
674
|
*
|
|
637
675
|
* Contract integrity is enforced upstream by {@link assertColumnCodecIntegrity}: every column must reference a registered `codecId` whose `descriptor.isParameterized` flag matches the presence of `typeParams` (via `codecRefForColumn`). The pre-population walk and `forColumn` therefore make no defensive checks — malformed columns fail fast at `createExecutionContext` construction with `RUNTIME.CODEC_DESCRIPTOR_MISSING` or `RUNTIME.CODEC_PARAMETERIZATION_MISMATCH` rather than being silently skipped here.
|
|
638
676
|
*
|
|
639
|
-
* `forColumn(t, c)` is a thin delegate over `forCodecRef(codecRefForColumn(t, c))`; encode/decode hot paths read the resolver directly via `forCodecRef`. The only `undefined` `forColumn` returns is the legitimate "no such column in the contract" case.
|
|
677
|
+
* `forColumn(ns, t, c)` is a thin delegate over `forCodecRef(codecRefForColumn(ns, t, c))`; encode/decode hot paths read the resolver directly via `forCodecRef`. The only `undefined` `forColumn` returns is the legitimate "no such column in the contract" case.
|
|
640
678
|
*/
|
|
641
679
|
function buildContractCodecRegistry(contract, codecDescriptors) {
|
|
642
680
|
const refKeyOf = (ref) => `${ref.codecId}:${canonicalizeJson(ref.typeParams)}`;
|
|
@@ -645,13 +683,15 @@ function buildContractCodecRegistry(contract, codecDescriptors) {
|
|
|
645
683
|
const typeRefSites = collectTypeRefSites(contract.storage);
|
|
646
684
|
for (const [typeName, typeInstance] of Object.entries(documentScopedCodecTypes(contract) ?? {})) {
|
|
647
685
|
const enumView = readEnumViewIfApplicable(typeInstance);
|
|
648
|
-
const
|
|
686
|
+
const instanceTypeParams = enumView ? enumView.typeParams : typeInstance.typeParams;
|
|
687
|
+
const hasParamKeys = instanceTypeParams !== void 0 && Object.keys(instanceTypeParams).length > 0;
|
|
688
|
+
const key = refKeyOf(enumView ? hasParamKeys ? {
|
|
649
689
|
codecId: enumView.codecId,
|
|
650
|
-
typeParams:
|
|
651
|
-
} : {
|
|
690
|
+
typeParams: instanceTypeParams
|
|
691
|
+
} : { codecId: enumView.codecId } : hasParamKeys ? {
|
|
652
692
|
codecId: typeInstance.codecId,
|
|
653
|
-
typeParams:
|
|
654
|
-
});
|
|
693
|
+
typeParams: instanceTypeParams
|
|
694
|
+
} : { codecId: typeInstance.codecId });
|
|
655
695
|
const sites = typeRefSites.get(typeName) ?? [];
|
|
656
696
|
const existing = usedAtByKey.get(key);
|
|
657
697
|
if (existing) existing.push(...sites);
|
|
@@ -660,9 +700,9 @@ function buildContractCodecRegistry(contract, codecDescriptors) {
|
|
|
660
700
|
nameByKey.set(key, typeName);
|
|
661
701
|
}
|
|
662
702
|
}
|
|
663
|
-
for (const ns of Object.
|
|
703
|
+
for (const [namespaceId, ns] of Object.entries(contract.storage.namespaces)) for (const [tableName, table] of Object.entries(ns.entries.table)) for (const [columnName, column] of Object.entries(table.columns)) {
|
|
664
704
|
if (column.typeRef !== void 0) continue;
|
|
665
|
-
const ref = codecDescriptors.codecRefForColumn(tableName, columnName);
|
|
705
|
+
const ref = codecDescriptors.codecRefForColumn(namespaceId, tableName, columnName);
|
|
666
706
|
if (!ref) continue;
|
|
667
707
|
const key = refKeyOf(ref);
|
|
668
708
|
const site = {
|
|
@@ -684,14 +724,14 @@ function buildContractCodecRegistry(contract, codecDescriptors) {
|
|
|
684
724
|
usedAt: usedAtByKey.get(key) ?? []
|
|
685
725
|
};
|
|
686
726
|
});
|
|
687
|
-
for (const ns of Object.
|
|
688
|
-
const ref = codecDescriptors.codecRefForColumn(tableName, columnName);
|
|
727
|
+
for (const [namespaceId, ns] of Object.entries(contract.storage.namespaces)) for (const [tableName, table] of Object.entries(ns.entries.table)) for (const columnName of Object.keys(table.columns)) {
|
|
728
|
+
const ref = codecDescriptors.codecRefForColumn(namespaceId, tableName, columnName);
|
|
689
729
|
if (!ref) continue;
|
|
690
730
|
resolver.forCodecRef(ref);
|
|
691
731
|
}
|
|
692
732
|
return {
|
|
693
|
-
forColumn(table, column) {
|
|
694
|
-
const ref = codecDescriptors.codecRefForColumn(table, column);
|
|
733
|
+
forColumn(namespaceId, table, column) {
|
|
734
|
+
const ref = codecDescriptors.codecRefForColumn(namespaceId, table, column);
|
|
695
735
|
return ref ? resolver.forCodecRef(ref) : void 0;
|
|
696
736
|
},
|
|
697
737
|
forCodecRef(ref) {
|
|
@@ -817,125 +857,6 @@ function createExecutionContext(options) {
|
|
|
817
857
|
};
|
|
818
858
|
}
|
|
819
859
|
//#endregion
|
|
820
|
-
//#region src/sql-marker.ts
|
|
821
|
-
const ensureSchemaStatement = {
|
|
822
|
-
sql: "create schema if not exists prisma_contract",
|
|
823
|
-
params: []
|
|
824
|
-
};
|
|
825
|
-
/**
|
|
826
|
-
* Schema for `prisma_contract.marker`. The `space text` primary key
|
|
827
|
-
* supports one row per loaded contract space (`'app'`,
|
|
828
|
-
* `'<extension-id>'`, …); brand-new databases create this shape
|
|
829
|
-
* directly. Pre-1.0 single-row markers (no `space` column) are not
|
|
830
|
-
* auto-migrated — the target-specific migration runner detects the
|
|
831
|
-
* legacy shape at boot and surfaces a structured `LEGACY_MARKER_SHAPE`
|
|
832
|
-
* failure pointing the operator at re-running `dbInit`.
|
|
833
|
-
*
|
|
834
|
-
* @see specs/framework-mechanism.spec.md § 2.
|
|
835
|
-
*/
|
|
836
|
-
const ensureTableStatement = {
|
|
837
|
-
sql: `create table if not exists prisma_contract.marker (
|
|
838
|
-
space text not null primary key default '${APP_SPACE_ID}',
|
|
839
|
-
core_hash text not null,
|
|
840
|
-
profile_hash text not null,
|
|
841
|
-
contract_json jsonb,
|
|
842
|
-
canonical_version int,
|
|
843
|
-
updated_at timestamptz not null default now(),
|
|
844
|
-
app_tag text,
|
|
845
|
-
meta jsonb not null default '{}',
|
|
846
|
-
invariants text[] not null default '{}'
|
|
847
|
-
)`,
|
|
848
|
-
params: []
|
|
849
|
-
};
|
|
850
|
-
function readContractMarker(space) {
|
|
851
|
-
return {
|
|
852
|
-
sql: `select
|
|
853
|
-
core_hash,
|
|
854
|
-
profile_hash,
|
|
855
|
-
contract_json,
|
|
856
|
-
canonical_version,
|
|
857
|
-
updated_at,
|
|
858
|
-
app_tag,
|
|
859
|
-
meta,
|
|
860
|
-
invariants
|
|
861
|
-
from prisma_contract.marker
|
|
862
|
-
where space = $1`,
|
|
863
|
-
params: [space]
|
|
864
|
-
};
|
|
865
|
-
}
|
|
866
|
-
/**
|
|
867
|
-
* Variable columns that participate in INSERT/UPDATE alongside the
|
|
868
|
-
* always-on `space = $1` and `updated_at = now()`. Each column declares
|
|
869
|
-
* its name, optional cast type, and parameter value; the placeholder
|
|
870
|
-
* (`$N`) is computed positionally below — adding or reordering a
|
|
871
|
-
* column doesn't desync indices. `invariants` only appears when the
|
|
872
|
-
* caller supplies it — see `WriteMarkerInput.invariants`.
|
|
873
|
-
*/
|
|
874
|
-
function markerColumns(input) {
|
|
875
|
-
return [
|
|
876
|
-
{
|
|
877
|
-
name: "core_hash",
|
|
878
|
-
param: input.storageHash
|
|
879
|
-
},
|
|
880
|
-
{
|
|
881
|
-
name: "profile_hash",
|
|
882
|
-
param: input.profileHash
|
|
883
|
-
},
|
|
884
|
-
{
|
|
885
|
-
name: "contract_json",
|
|
886
|
-
type: "jsonb",
|
|
887
|
-
param: input.contractJson ?? null
|
|
888
|
-
},
|
|
889
|
-
{
|
|
890
|
-
name: "canonical_version",
|
|
891
|
-
param: input.canonicalVersion ?? null
|
|
892
|
-
},
|
|
893
|
-
{
|
|
894
|
-
name: "app_tag",
|
|
895
|
-
param: input.appTag ?? null
|
|
896
|
-
},
|
|
897
|
-
{
|
|
898
|
-
name: "meta",
|
|
899
|
-
type: "jsonb",
|
|
900
|
-
param: JSON.stringify(input.meta ?? {})
|
|
901
|
-
},
|
|
902
|
-
...input.invariants !== void 0 ? [{
|
|
903
|
-
name: "invariants",
|
|
904
|
-
type: "text[]",
|
|
905
|
-
param: input.invariants
|
|
906
|
-
}] : []
|
|
907
|
-
];
|
|
908
|
-
}
|
|
909
|
-
function writeContractMarker(input) {
|
|
910
|
-
const placed = markerColumns(input).map((c, i) => ({
|
|
911
|
-
name: c.name,
|
|
912
|
-
expr: c.type ? `$${i + 2}::${c.type}` : `$${i + 2}`,
|
|
913
|
-
param: c.param
|
|
914
|
-
}));
|
|
915
|
-
const params = [input.space, ...placed.map((c) => c.param)];
|
|
916
|
-
const insertColumns = [
|
|
917
|
-
"space",
|
|
918
|
-
...placed.map((c) => c.name),
|
|
919
|
-
"updated_at"
|
|
920
|
-
].join(", ");
|
|
921
|
-
const insertValues = [
|
|
922
|
-
"$1",
|
|
923
|
-
...placed.map((c) => c.expr),
|
|
924
|
-
"now()"
|
|
925
|
-
].join(", ");
|
|
926
|
-
const setClauses = [...placed.map((c) => `${c.name} = ${c.expr}`), "updated_at = now()"].join(", ");
|
|
927
|
-
return {
|
|
928
|
-
insert: {
|
|
929
|
-
sql: `insert into prisma_contract.marker (${insertColumns}) values (${insertValues})`,
|
|
930
|
-
params
|
|
931
|
-
},
|
|
932
|
-
update: {
|
|
933
|
-
sql: `update prisma_contract.marker set ${setClauses} where space = $1`,
|
|
934
|
-
params
|
|
935
|
-
}
|
|
936
|
-
};
|
|
937
|
-
}
|
|
938
|
-
//#endregion
|
|
939
860
|
//#region src/codecs/decoding.ts
|
|
940
861
|
const WIRE_PREVIEW_LIMIT = 100;
|
|
941
862
|
const EMPTY_INCLUDE_ALIASES = /* @__PURE__ */ new Set();
|
|
@@ -1085,67 +1006,6 @@ async function decodeRow(row, decodeCtx, rowCtx) {
|
|
|
1085
1006
|
return decoded;
|
|
1086
1007
|
}
|
|
1087
1008
|
//#endregion
|
|
1088
|
-
//#region src/codecs/encoding.ts
|
|
1089
|
-
const NO_METADATA = Object.freeze({
|
|
1090
|
-
codec: void 0,
|
|
1091
|
-
name: void 0
|
|
1092
|
-
});
|
|
1093
|
-
function deriveParamMetadata(ast) {
|
|
1094
|
-
return collectOrderedParamRefs(ast).map((ref) => {
|
|
1095
|
-
return {
|
|
1096
|
-
codec: ref.codec,
|
|
1097
|
-
name: ref.name
|
|
1098
|
-
};
|
|
1099
|
-
});
|
|
1100
|
-
}
|
|
1101
|
-
function resolveParamCodec(metadata, contractCodecs) {
|
|
1102
|
-
if (metadata.codec && contractCodecs) return contractCodecs.forCodecRef(metadata.codec);
|
|
1103
|
-
}
|
|
1104
|
-
function paramLabel(metadata, paramIndex) {
|
|
1105
|
-
return metadata.name ?? `param[${paramIndex}]`;
|
|
1106
|
-
}
|
|
1107
|
-
function wrapEncodeFailure(error, metadata, paramIndex, codecId) {
|
|
1108
|
-
const label = paramLabel(metadata, paramIndex);
|
|
1109
|
-
const wrapped = runtimeError("RUNTIME.ENCODE_FAILED", `Failed to encode parameter ${label} with codec '${codecId}': ${error instanceof Error ? error.message : String(error)}`, {
|
|
1110
|
-
label,
|
|
1111
|
-
codec: codecId,
|
|
1112
|
-
paramIndex
|
|
1113
|
-
});
|
|
1114
|
-
wrapped.cause = error;
|
|
1115
|
-
throw wrapped;
|
|
1116
|
-
}
|
|
1117
|
-
async function encodeParamValue(value, metadata, paramIndex, ctx, contractCodecs) {
|
|
1118
|
-
if (value === null || value === void 0) return null;
|
|
1119
|
-
const codec = resolveParamCodec(metadata, contractCodecs);
|
|
1120
|
-
if (!codec) return value;
|
|
1121
|
-
try {
|
|
1122
|
-
return await codec.encode(value, ctx);
|
|
1123
|
-
} catch (error) {
|
|
1124
|
-
if (isRuntimeError(error)) throw error;
|
|
1125
|
-
wrapEncodeFailure(error, metadata, paramIndex, codec.id);
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
/**
|
|
1129
|
-
* 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`.
|
|
1130
|
-
*
|
|
1131
|
-
* When `ctx.signal` is provided:
|
|
1132
|
-
*
|
|
1133
|
-
* - **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.
|
|
1134
|
-
* - **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).
|
|
1135
|
-
* - Existing `RUNTIME.ENCODE_FAILED` envelopes that surface from a codec body before the runtime observes the abort pass through unchanged (no double wrap).
|
|
1136
|
-
*/
|
|
1137
|
-
async function encodeParams(plan, ctx, contractCodecs) {
|
|
1138
|
-
return encodeParamsWithMetadata(plan.params, deriveParamMetadata(plan.ast), ctx, contractCodecs);
|
|
1139
|
-
}
|
|
1140
|
-
async function encodeParamsWithMetadata(values, metadata, ctx, contractCodecs) {
|
|
1141
|
-
checkAborted(ctx, "encode");
|
|
1142
|
-
const signal = ctx.signal;
|
|
1143
|
-
if (values.length === 0) return values;
|
|
1144
|
-
const tasks = values.map((value, i) => encodeParamValue(value, metadata[i] ?? NO_METADATA, i, ctx, contractCodecs));
|
|
1145
|
-
const settled = await raceAgainstAbort(Promise.all(tasks), signal, "encode");
|
|
1146
|
-
return Object.freeze(settled);
|
|
1147
|
-
}
|
|
1148
|
-
//#endregion
|
|
1149
1009
|
//#region src/content-hash.ts
|
|
1150
1010
|
/**
|
|
1151
1011
|
* Computes a stable content hash for a lowered SQL execution plan.
|
|
@@ -1698,31 +1558,24 @@ async function withTransaction(runtime, fn) {
|
|
|
1698
1558
|
const connection = await runtime.connection();
|
|
1699
1559
|
const transaction = await connection.transaction();
|
|
1700
1560
|
let invalidated = false;
|
|
1561
|
+
async function* guardedStream(inner) {
|
|
1562
|
+
if (invalidated) throw transactionClosedError();
|
|
1563
|
+
for await (const row of inner) {
|
|
1564
|
+
yield row;
|
|
1565
|
+
if (invalidated) throw transactionClosedError();
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1701
1568
|
const txContext = {
|
|
1702
1569
|
get invalidated() {
|
|
1703
1570
|
return invalidated;
|
|
1704
1571
|
},
|
|
1705
1572
|
execute(plan, options) {
|
|
1706
1573
|
if (invalidated) throw transactionClosedError();
|
|
1707
|
-
|
|
1708
|
-
const guarded = async function* () {
|
|
1709
|
-
for await (const row of inner) {
|
|
1710
|
-
if (invalidated) throw transactionClosedError();
|
|
1711
|
-
yield row;
|
|
1712
|
-
}
|
|
1713
|
-
};
|
|
1714
|
-
return new AsyncIterableResult(guarded());
|
|
1574
|
+
return new AsyncIterableResult(guardedStream(transaction.execute(plan, options)));
|
|
1715
1575
|
},
|
|
1716
1576
|
executePrepared(ps, params, options) {
|
|
1717
1577
|
if (invalidated) throw transactionClosedError();
|
|
1718
|
-
|
|
1719
|
-
const guarded = async function* () {
|
|
1720
|
-
for await (const row of inner) {
|
|
1721
|
-
if (invalidated) throw transactionClosedError();
|
|
1722
|
-
yield row;
|
|
1723
|
-
}
|
|
1724
|
-
};
|
|
1725
|
-
return new AsyncIterableResult(guarded());
|
|
1578
|
+
return new AsyncIterableResult(guardedStream(transaction.executePrepared(ps, params, options)));
|
|
1726
1579
|
}
|
|
1727
1580
|
};
|
|
1728
1581
|
let connectionDisposed = false;
|
|
@@ -1778,6 +1631,6 @@ function createRuntime(options) {
|
|
|
1778
1631
|
});
|
|
1779
1632
|
}
|
|
1780
1633
|
//#endregion
|
|
1781
|
-
export {
|
|
1634
|
+
export { lints as a, extractCodecIds as c, deriveParamMetadata as d, encodeParamsWithMetadata as f, createSqlExecutionStack as i, validateCodecRegistryCompleteness as l, withTransaction as n, budgets as o, createAstCodecRegistry as p, createExecutionContext as r, lowerSqlPlan as s, createRuntime as t, validateContractCodecMappings as u };
|
|
1782
1635
|
|
|
1783
|
-
//# sourceMappingURL=exports-
|
|
1636
|
+
//# sourceMappingURL=exports-DDqF-xmg.mjs.map
|