@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 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**: Parse contract-marker rows from the database (`marker.ts`) and gate execution on hash equality
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
- - `RuntimeVerifyOptions` - Verification mode configuration
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
- const storageTypes = storage.types;
545
- if (!storageTypes) return helpers;
548
+ if (!documentTypes) return helpers;
546
549
  const typeRefSites = collectTypeRefSites(storage);
547
- for (const [typeName, typeInstance] of Object.entries(storageTypes)) {
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.storage.types ?? {})) {
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 { contract, stack } = options;
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
- let parsed;
994
- if (typeof wireValue === "string") parsed = JSON.parse(wireValue);
995
- else if (Array.isArray(wireValue)) parsed = wireValue;
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
- verify;
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, verify, middleware, mode, log } = options;
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.verify = verify;
1366
+ this.verifyMarkerOption = verifyMarker ?? "onFirstUse";
1356
1367
  this.codecRegistryValidated = false;
1357
- this.verified = verify.mode === "startup" ? false : verify.mode === "always";
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 (!this.startupVerified && this.verify.mode === "startup") await this.verifyMarker();
1446
- if (!this.verified && this.verify.mode === "onFirstUse") await this.verifyMarker();
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
- if (this.verify.requireMarker) throw runtimeError("CONTRACT.MARKER_MISSING", "Contract marker not found in database");
1648
- this.verified = true;
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 contract = this.contract;
1653
- if (marker.storageHash !== contract.storage.storageHash) throw runtimeError("CONTRACT.MARKER_MISMATCH", "Database storage hash does not match contract", {
1654
- expected: contract.storage.storageHash,
1655
- actual: marker.storageHash
1656
- });
1657
- const expectedProfile = contract.profileHash ?? null;
1658
- if (expectedProfile !== null && marker.profileHash !== expectedProfile) throw runtimeError("CONTRACT.MARKER_MISMATCH", "Database profile hash does not match contract", {
1659
- expectedProfile,
1660
- actualProfile: marker.profileHash
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, verify, middleware, mode, log } = options;
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
- verify,
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-BsRNNJxU.mjs.map
1783
+ //# sourceMappingURL=exports-CXYd2w6k.mjs.map