@prisma-next/sql-runtime 0.11.0 → 0.12.0
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 +14 -3
- package/dist/{exports-BsRNNJxU.mjs → exports-CXYd2w6k.mjs} +66 -48
- package/dist/exports-CXYd2w6k.mjs.map +1 -0
- package/dist/{index-B2sP_QGE.d.mts → index-DTr7KoUs.d.mts} +34 -11
- package/dist/index-DTr7KoUs.d.mts.map +1 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +1 -1
- package/dist/test/utils.d.mts +16 -6
- package/dist/test/utils.d.mts.map +1 -1
- package/dist/test/utils.mjs +22 -7
- package/dist/test/utils.mjs.map +1 -1
- package/package.json +26 -15
- package/src/codecs/decoding.ts +9 -11
- package/src/exports/index.ts +1 -1
- package/src/runtime-spi.ts +19 -5
- package/src/sql-context.ts +56 -9
- package/src/sql-marker.ts +1 -1
- package/src/sql-runtime.ts +52 -64
- package/dist/exports-BsRNNJxU.mjs.map +0 -1
- package/dist/index-B2sP_QGE.d.mts.map +0 -1
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ Execute SQL query Plans with deterministic verification, guardrails, and feedbac
|
|
|
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`)
|
|
30
|
-
- **Marker Verification**:
|
|
30
|
+
- **Marker Verification**: Read contract-marker rows from the database (`marker.ts`) on first execute and compare storage/profile hashes against the contract. On the first `execute()` call, any drift — hash mismatch, absent row, or missing marker table — is reported via a single `warn`-level structured log line through the runtime's `Log` interface (payload includes `code`, `scope`, `expected`, `actual`, `message`) and the query proceeds normally. The check runs once per runtime lifetime; subsequent queries skip the marker read. Pass `verifyMarker: false` to skip the marker read entirely.
|
|
31
31
|
- **Telemetry Fingerprinting**: Compute SQL fingerprints for telemetry events (`fingerprint.ts`)
|
|
32
32
|
- **Raw-SQL Guardrails**: Heuristic safety checks for raw SQL plans (`guardrails/raw.ts`)
|
|
33
33
|
- **`beforeCompile` Chain**: AST-rewrite middleware chain run pre-lowering (`middleware/before-compile-chain.ts`)
|
|
@@ -72,7 +72,6 @@ const runtime = createRuntime({
|
|
|
72
72
|
stackInstance,
|
|
73
73
|
context,
|
|
74
74
|
driver,
|
|
75
|
-
verify: { mode: 'onFirstUse', requireMarker: false },
|
|
76
75
|
middleware: [budgets()],
|
|
77
76
|
});
|
|
78
77
|
|
|
@@ -81,6 +80,18 @@ for await (const row of runtime.execute(plan)) {
|
|
|
81
80
|
}
|
|
82
81
|
```
|
|
83
82
|
|
|
83
|
+
Use `verifyMarker: false` to skip the marker read entirely — e.g. during a known-skewed deploy window where contract drift is expected and tolerated.
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
const runtime = createRuntime({
|
|
87
|
+
stackInstance,
|
|
88
|
+
context,
|
|
89
|
+
driver,
|
|
90
|
+
verifyMarker: false,
|
|
91
|
+
middleware: [budgets()],
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
84
95
|
## Exports
|
|
85
96
|
|
|
86
97
|
### Runtime
|
|
@@ -88,7 +99,7 @@ for await (const row of runtime.execute(plan)) {
|
|
|
88
99
|
- `createRuntime` - Create a SQL runtime instance
|
|
89
100
|
- `Runtime` - Runtime instance type
|
|
90
101
|
- `CreateRuntimeOptions` - Options for `createRuntime`
|
|
91
|
-
- `
|
|
102
|
+
- `VerifyMarkerOption` - Marker-verification option (`'onFirstUse'` default; `false` to skip)
|
|
92
103
|
- `RuntimeTelemetryEvent`, `TelemetryOutcome` - Telemetry event types
|
|
93
104
|
|
|
94
105
|
### Context
|
|
@@ -2,10 +2,11 @@ import { AsyncIterableResult, RuntimeCore, checkAborted, checkMiddlewareCompatib
|
|
|
2
2
|
import { type } from "arktype";
|
|
3
3
|
import { PreparedParamRef, collectOrderedParamRefs, isQueryAst } from "@prisma-next/sql-relational-core/ast";
|
|
4
4
|
import { ifDefined } from "@prisma-next/utils/defined";
|
|
5
|
-
import { checkContractComponentRequirements } from "@prisma-next/framework-components/components";
|
|
5
|
+
import { checkContractComponentRequirements, mergeCapabilityMatrices } from "@prisma-next/framework-components/components";
|
|
6
6
|
import { createExecutionStack } from "@prisma-next/framework-components/execution";
|
|
7
7
|
import { canonicalizeJson } from "@prisma-next/framework-components/utils";
|
|
8
8
|
import { isPostgresEnumStorageEntry } from "@prisma-next/sql-contract/types";
|
|
9
|
+
import { blindCast } from "@prisma-next/utils/casts";
|
|
9
10
|
import { createSqlOperationRegistry } from "@prisma-next/sql-operations";
|
|
10
11
|
import { buildCodecDescriptorRegistry } from "@prisma-next/sql-relational-core/codec-descriptor-registry";
|
|
11
12
|
import { APP_SPACE_ID } from "@prisma-next/framework-components/control";
|
|
@@ -436,6 +437,9 @@ function validateTypeParams$1(paramsSchema, ref) {
|
|
|
436
437
|
}
|
|
437
438
|
//#endregion
|
|
438
439
|
//#region src/sql-context.ts
|
|
440
|
+
function documentScopedCodecTypes(contract) {
|
|
441
|
+
return blindCast(contract.storage.types);
|
|
442
|
+
}
|
|
439
443
|
function createSqlExecutionStack(options) {
|
|
440
444
|
return createExecutionStack({
|
|
441
445
|
target: options.target,
|
|
@@ -539,12 +543,11 @@ function collectTypeRefSites(storage) {
|
|
|
539
543
|
}
|
|
540
544
|
return sites;
|
|
541
545
|
}
|
|
542
|
-
function initializeTypeHelpers(storage, codecDescriptors) {
|
|
546
|
+
function initializeTypeHelpers(storage, documentTypes, codecDescriptors) {
|
|
543
547
|
const helpers = {};
|
|
544
|
-
|
|
545
|
-
if (!storageTypes) return helpers;
|
|
548
|
+
if (!documentTypes) return helpers;
|
|
546
549
|
const typeRefSites = collectTypeRefSites(storage);
|
|
547
|
-
for (const [typeName, typeInstance] of Object.entries(
|
|
550
|
+
for (const [typeName, typeInstance] of Object.entries(documentTypes)) {
|
|
548
551
|
const enumView = readEnumViewIfApplicable(typeInstance);
|
|
549
552
|
const codecId = enumView ? enumView.codecId : typeInstance.codecId;
|
|
550
553
|
const typeParams = enumView ? enumView.typeParams : typeInstance.typeParams;
|
|
@@ -640,7 +643,7 @@ function buildContractCodecRegistry(contract, codecDescriptors) {
|
|
|
640
643
|
const usedAtByKey = /* @__PURE__ */ new Map();
|
|
641
644
|
const nameByKey = /* @__PURE__ */ new Map();
|
|
642
645
|
const typeRefSites = collectTypeRefSites(contract.storage);
|
|
643
|
-
for (const [typeName, typeInstance] of Object.entries(contract
|
|
646
|
+
for (const [typeName, typeInstance] of Object.entries(documentScopedCodecTypes(contract) ?? {})) {
|
|
644
647
|
const enumView = readEnumViewIfApplicable(typeInstance);
|
|
645
648
|
const key = refKeyOf(enumView ? {
|
|
646
649
|
codecId: enumView.codecId,
|
|
@@ -774,8 +777,19 @@ function scopedCache(stability, rowCache, queryCache) {
|
|
|
774
777
|
}
|
|
775
778
|
}
|
|
776
779
|
function createExecutionContext(options) {
|
|
777
|
-
const {
|
|
778
|
-
assertExecutionStackContractRequirements(contract, stack);
|
|
780
|
+
const { stack, driver } = options;
|
|
781
|
+
assertExecutionStackContractRequirements(options.contract, stack);
|
|
782
|
+
const capabilityContributors = [
|
|
783
|
+
stack.target,
|
|
784
|
+
stack.adapter,
|
|
785
|
+
...driver ? [driver] : [],
|
|
786
|
+
...stack.extensionPacks
|
|
787
|
+
];
|
|
788
|
+
const mergedCapabilities = mergeCapabilityMatrices(options.contract.capabilities, capabilityContributors);
|
|
789
|
+
const contract = {
|
|
790
|
+
...options.contract,
|
|
791
|
+
capabilities: mergedCapabilities
|
|
792
|
+
};
|
|
779
793
|
const contributors = [
|
|
780
794
|
stack.target,
|
|
781
795
|
stack.adapter,
|
|
@@ -792,7 +806,7 @@ function createExecutionContext(options) {
|
|
|
792
806
|
const mutationDefaultGeneratorRegistry = collectMutationDefaultGenerators(contributors);
|
|
793
807
|
assertMutationDefaultGeneratorsAvailable(contract, mutationDefaultGeneratorRegistry);
|
|
794
808
|
if (parameterizedCodecDescriptors.size > 0) validateColumnTypeParams(contract.storage, parameterizedCodecDescriptors);
|
|
795
|
-
const types = initializeTypeHelpers(contract.storage, parameterizedCodecDescriptors);
|
|
809
|
+
const types = initializeTypeHelpers(contract.storage, documentScopedCodecTypes(contract), parameterizedCodecDescriptors);
|
|
796
810
|
return {
|
|
797
811
|
contract,
|
|
798
812
|
contractCodecs: buildContractCodecRegistry(contract, codecDescriptors),
|
|
@@ -990,12 +1004,9 @@ function wrapIncludeAggregateFailure(error, alias, wireValue) {
|
|
|
990
1004
|
function decodeIncludeAggregate(alias, wireValue) {
|
|
991
1005
|
if (wireValue === null || wireValue === void 0) return [];
|
|
992
1006
|
try {
|
|
993
|
-
|
|
994
|
-
if (typeof wireValue === "
|
|
995
|
-
|
|
996
|
-
else parsed = JSON.parse(String(wireValue));
|
|
997
|
-
if (!Array.isArray(parsed)) throw new Error(`Expected array for include alias '${alias}', got ${typeof parsed}`);
|
|
998
|
-
return parsed;
|
|
1007
|
+
if (typeof wireValue === "string") return JSON.parse(wireValue);
|
|
1008
|
+
if (typeof wireValue === "object") return wireValue;
|
|
1009
|
+
return JSON.parse(String(wireValue));
|
|
999
1010
|
} catch (error) {
|
|
1000
1011
|
wrapIncludeAggregateFailure(error, alias, wireValue);
|
|
1001
1012
|
}
|
|
@@ -1324,14 +1335,13 @@ var SqlRuntimeImpl = class extends RuntimeCore {
|
|
|
1324
1335
|
contractCodecs;
|
|
1325
1336
|
codecDescriptors;
|
|
1326
1337
|
sqlCtx;
|
|
1327
|
-
|
|
1338
|
+
verifyMarkerOption;
|
|
1339
|
+
verifyMarkerPromise;
|
|
1328
1340
|
#preparedStatementHandles = /* @__PURE__ */ new WeakMap();
|
|
1329
1341
|
codecRegistryValidated;
|
|
1330
|
-
verified;
|
|
1331
|
-
startupVerified;
|
|
1332
1342
|
_telemetry;
|
|
1333
1343
|
constructor(options) {
|
|
1334
|
-
const { context, adapter, driver,
|
|
1344
|
+
const { context, adapter, driver, verifyMarker, middleware, mode, log } = options;
|
|
1335
1345
|
if (middleware) for (const mw of middleware) checkMiddlewareCompatibility(mw, "sql", context.contract.target);
|
|
1336
1346
|
const sqlCtx = {
|
|
1337
1347
|
contract: context.contract,
|
|
@@ -1339,7 +1349,8 @@ var SqlRuntimeImpl = class extends RuntimeCore {
|
|
|
1339
1349
|
now: () => Date.now(),
|
|
1340
1350
|
log: log ?? noopLog,
|
|
1341
1351
|
contentHash: (exec) => computeSqlContentHash(exec),
|
|
1342
|
-
scope: "runtime"
|
|
1352
|
+
scope: "runtime",
|
|
1353
|
+
planExecutionId: ""
|
|
1343
1354
|
};
|
|
1344
1355
|
super({
|
|
1345
1356
|
middleware: middleware ?? [],
|
|
@@ -1352,15 +1363,10 @@ var SqlRuntimeImpl = class extends RuntimeCore {
|
|
|
1352
1363
|
this.contractCodecs = context.contractCodecs;
|
|
1353
1364
|
this.codecDescriptors = context.codecDescriptors;
|
|
1354
1365
|
this.sqlCtx = sqlCtx;
|
|
1355
|
-
this.
|
|
1366
|
+
this.verifyMarkerOption = verifyMarker ?? "onFirstUse";
|
|
1356
1367
|
this.codecRegistryValidated = false;
|
|
1357
|
-
this.
|
|
1358
|
-
this.startupVerified = false;
|
|
1368
|
+
this.verifyMarkerPromise = this.verifyMarkerOption === false ? Promise.resolve() : null;
|
|
1359
1369
|
this._telemetry = null;
|
|
1360
|
-
if (verify.mode === "startup") {
|
|
1361
|
-
validateCodecRegistryCompleteness(this.codecDescriptors, context.contract);
|
|
1362
|
-
this.codecRegistryValidated = true;
|
|
1363
|
-
}
|
|
1364
1370
|
}
|
|
1365
1371
|
/**
|
|
1366
1372
|
* Lower a `SqlQueryPlan` (AST + meta) into a `SqlExecutionPlan`
|
|
@@ -1442,12 +1448,11 @@ var SqlRuntimeImpl = class extends RuntimeCore {
|
|
|
1442
1448
|
async *streamRows(exec, decodeContext, driverCall, codecCtx, execMiddlewareCtx) {
|
|
1443
1449
|
this.familyAdapter.validatePlan(exec, this.contract);
|
|
1444
1450
|
this._telemetry = null;
|
|
1445
|
-
if (
|
|
1446
|
-
|
|
1451
|
+
if (this.verifyMarkerPromise === null) this.verifyMarkerPromise = this.verifyMarker();
|
|
1452
|
+
await this.verifyMarkerPromise;
|
|
1447
1453
|
const startedAt = Date.now();
|
|
1448
1454
|
let outcome = null;
|
|
1449
1455
|
try {
|
|
1450
|
-
if (this.verify.mode === "always") await this.verifyMarker();
|
|
1451
1456
|
const iterator = runWithMiddleware(exec, this.middleware, execMiddlewareCtx, driverCall)[Symbol.asyncIterator]();
|
|
1452
1457
|
try {
|
|
1453
1458
|
while (true) {
|
|
@@ -1476,7 +1481,8 @@ var SqlRuntimeImpl = class extends RuntimeCore {
|
|
|
1476
1481
|
const execMiddlewareCtx = {
|
|
1477
1482
|
...self.ctx,
|
|
1478
1483
|
...ifDefined("signal", signal),
|
|
1479
|
-
...scope !== "runtime" ? { scope } : {}
|
|
1484
|
+
...scope !== "runtime" ? { scope } : {},
|
|
1485
|
+
planExecutionId: crypto.randomUUID()
|
|
1480
1486
|
};
|
|
1481
1487
|
const generator = async function* () {
|
|
1482
1488
|
checkAborted(codecCtx, "stream");
|
|
@@ -1543,7 +1549,8 @@ var SqlRuntimeImpl = class extends RuntimeCore {
|
|
|
1543
1549
|
const execMiddlewareCtx = {
|
|
1544
1550
|
...self.ctx,
|
|
1545
1551
|
...ifDefined("signal", signal),
|
|
1546
|
-
...scope !== "runtime" ? { scope } : {}
|
|
1552
|
+
...scope !== "runtime" ? { scope } : {},
|
|
1553
|
+
planExecutionId: crypto.randomUUID()
|
|
1547
1554
|
};
|
|
1548
1555
|
const generator = async function* () {
|
|
1549
1556
|
checkAborted(codecCtx, "stream");
|
|
@@ -1643,24 +1650,35 @@ var SqlRuntimeImpl = class extends RuntimeCore {
|
|
|
1643
1650
|
}
|
|
1644
1651
|
async verifyMarker() {
|
|
1645
1652
|
const readResult = await this.familyAdapter.markerReader.readMarker(this.driver);
|
|
1653
|
+
const expectedStorageHash = this.contract.storage.storageHash;
|
|
1654
|
+
const expectedProfileHash = this.contract.profileHash ?? null;
|
|
1655
|
+
const expected = {
|
|
1656
|
+
storageHash: expectedStorageHash,
|
|
1657
|
+
profileHash: expectedProfileHash
|
|
1658
|
+
};
|
|
1646
1659
|
if (readResult.kind !== "present") {
|
|
1647
|
-
|
|
1648
|
-
|
|
1660
|
+
this.sqlCtx.log.warn({
|
|
1661
|
+
code: "CONTRACT.MARKER_MISSING",
|
|
1662
|
+
scope: "marker-verification",
|
|
1663
|
+
expected,
|
|
1664
|
+
actual: null,
|
|
1665
|
+
message: "Contract marker not found in database"
|
|
1666
|
+
});
|
|
1649
1667
|
return;
|
|
1650
1668
|
}
|
|
1651
1669
|
const marker = readResult.record;
|
|
1652
|
-
const
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1670
|
+
const storageHashMatch = marker.storageHash === expectedStorageHash;
|
|
1671
|
+
const profileHashMatch = expectedProfileHash === null || marker.profileHash === expectedProfileHash;
|
|
1672
|
+
if (!storageHashMatch || !profileHashMatch) this.sqlCtx.log.warn({
|
|
1673
|
+
code: "CONTRACT.MARKER_MISMATCH",
|
|
1674
|
+
scope: "marker-verification",
|
|
1675
|
+
expected,
|
|
1676
|
+
actual: {
|
|
1677
|
+
storageHash: marker.storageHash,
|
|
1678
|
+
profileHash: marker.profileHash ?? null
|
|
1679
|
+
},
|
|
1680
|
+
message: "Contract marker hash does not match runtime contract"
|
|
1661
1681
|
});
|
|
1662
|
-
this.verified = true;
|
|
1663
|
-
this.startupVerified = true;
|
|
1664
1682
|
}
|
|
1665
1683
|
recordTelemetry(plan, outcome, durationMs) {
|
|
1666
1684
|
const contract = this.contract;
|
|
@@ -1748,12 +1766,12 @@ async function withTransaction(runtime, fn) {
|
|
|
1748
1766
|
}
|
|
1749
1767
|
}
|
|
1750
1768
|
function createRuntime(options) {
|
|
1751
|
-
const { stackInstance, context, driver,
|
|
1769
|
+
const { stackInstance, context, driver, verifyMarker, middleware, mode, log } = options;
|
|
1752
1770
|
return new SqlRuntimeImpl({
|
|
1753
1771
|
context,
|
|
1754
1772
|
adapter: stackInstance.adapter,
|
|
1755
1773
|
driver,
|
|
1756
|
-
|
|
1774
|
+
...ifDefined("verifyMarker", verifyMarker),
|
|
1757
1775
|
...ifDefined("middleware", middleware),
|
|
1758
1776
|
...ifDefined("mode", mode),
|
|
1759
1777
|
...ifDefined("log", log)
|
|
@@ -1762,4 +1780,4 @@ function createRuntime(options) {
|
|
|
1762
1780
|
//#endregion
|
|
1763
1781
|
export { ensureTableStatement as a, createExecutionContext as c, budgets as d, parseContractMarkerRow as f, validateContractCodecMappings as g, validateCodecRegistryCompleteness as h, ensureSchemaStatement as i, createSqlExecutionStack as l, extractCodecIds as m, withTransaction as n, readContractMarker as o, lowerSqlPlan as p, APP_SPACE_ID as r, writeContractMarker as s, createRuntime as t, lints as u };
|
|
1764
1782
|
|
|
1765
|
-
//# sourceMappingURL=exports-
|
|
1783
|
+
//# sourceMappingURL=exports-CXYd2w6k.mjs.map
|