@prisma-next/sql-runtime 0.5.0-dev.7 → 0.5.0-dev.9
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 +29 -21
- package/dist/{exports-BQZSVXXt.mjs → exports-BOHa3Emo.mjs} +481 -128
- package/dist/exports-BOHa3Emo.mjs.map +1 -0
- package/dist/{index-yb51L_1h.d.mts → index-CZmC2kD3.d.mts} +53 -16
- package/dist/index-CZmC2kD3.d.mts.map +1 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +1 -1
- package/dist/test/utils.d.mts +6 -5
- package/dist/test/utils.d.mts.map +1 -1
- package/dist/test/utils.mjs +7 -2
- package/dist/test/utils.mjs.map +1 -1
- package/package.json +12 -14
- package/src/codecs/decoding.ts +172 -116
- package/src/codecs/encoding.ts +59 -21
- package/src/exports/index.ts +10 -7
- package/src/fingerprint.ts +22 -0
- package/src/guardrails/raw.ts +214 -0
- package/src/lower-sql-plan.ts +3 -3
- package/src/marker.ts +82 -0
- package/src/middleware/before-compile-chain.ts +32 -1
- package/src/middleware/budgets.ts +14 -11
- package/src/middleware/lints.ts +3 -3
- package/src/middleware/sql-middleware.ts +6 -5
- package/src/runtime-spi.ts +43 -0
- package/src/sql-family-adapter.ts +3 -2
- package/src/sql-marker.ts +1 -1
- package/src/sql-runtime.ts +272 -110
- package/dist/exports-BQZSVXXt.mjs.map +0 -1
- package/dist/index-yb51L_1h.d.mts.map +0 -1
- package/test/async-iterable-result.test.ts +0 -141
- package/test/before-compile-chain.test.ts +0 -223
- package/test/budgets.test.ts +0 -431
- package/test/context.types.test-d.ts +0 -68
- package/test/execution-stack.test.ts +0 -161
- package/test/json-schema-validation.test.ts +0 -571
- package/test/lints.test.ts +0 -160
- package/test/mutation-default-generators.test.ts +0 -254
- package/test/parameterized-types.test.ts +0 -529
- package/test/sql-context.test.ts +0 -384
- package/test/sql-family-adapter.test.ts +0 -103
- package/test/sql-runtime.test.ts +0 -792
- package/test/utils.ts +0 -297
package/src/sql-runtime.ts
CHANGED
|
@@ -1,22 +1,16 @@
|
|
|
1
|
-
import type { Contract
|
|
1
|
+
import type { Contract } from '@prisma-next/contract/types';
|
|
2
2
|
import type {
|
|
3
3
|
ExecutionStackInstance,
|
|
4
4
|
RuntimeDriverInstance,
|
|
5
5
|
} from '@prisma-next/framework-components/execution';
|
|
6
|
-
import { checkMiddlewareCompatibility } from '@prisma-next/framework-components/runtime';
|
|
7
|
-
import type {
|
|
8
|
-
Log,
|
|
9
|
-
RuntimeCore,
|
|
10
|
-
RuntimeCoreOptions,
|
|
11
|
-
RuntimeTelemetryEvent,
|
|
12
|
-
RuntimeVerifyOptions,
|
|
13
|
-
TelemetryOutcome,
|
|
14
|
-
} from '@prisma-next/runtime-executor';
|
|
15
6
|
import {
|
|
16
7
|
AsyncIterableResult,
|
|
17
|
-
|
|
8
|
+
checkMiddlewareCompatibility,
|
|
9
|
+
RuntimeCore,
|
|
10
|
+
type RuntimeLog,
|
|
18
11
|
runtimeError,
|
|
19
|
-
|
|
12
|
+
runWithMiddleware,
|
|
13
|
+
} from '@prisma-next/framework-components/runtime';
|
|
20
14
|
import type { SqlStorage } from '@prisma-next/sql-contract/types';
|
|
21
15
|
import type {
|
|
22
16
|
Adapter,
|
|
@@ -24,16 +18,27 @@ import type {
|
|
|
24
18
|
CodecRegistry,
|
|
25
19
|
LoweredStatement,
|
|
26
20
|
SqlDriver,
|
|
21
|
+
SqlQueryable,
|
|
22
|
+
SqlTransaction,
|
|
27
23
|
} from '@prisma-next/sql-relational-core/ast';
|
|
28
|
-
import type { SqlQueryPlan } from '@prisma-next/sql-relational-core/plan';
|
|
24
|
+
import type { SqlExecutionPlan, SqlQueryPlan } from '@prisma-next/sql-relational-core/plan';
|
|
29
25
|
import type { JsonSchemaValidatorRegistry } from '@prisma-next/sql-relational-core/query-lane-context';
|
|
26
|
+
import type { RuntimeScope } from '@prisma-next/sql-relational-core/types';
|
|
30
27
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
31
28
|
import { decodeRow } from './codecs/decoding';
|
|
32
29
|
import { encodeParams } from './codecs/encoding';
|
|
33
30
|
import { validateCodecRegistryCompleteness } from './codecs/validation';
|
|
31
|
+
import { computeSqlFingerprint } from './fingerprint';
|
|
34
32
|
import { lowerSqlPlan } from './lower-sql-plan';
|
|
33
|
+
import { parseContractMarkerRow } from './marker';
|
|
35
34
|
import { runBeforeCompileChain } from './middleware/before-compile-chain';
|
|
36
|
-
import type { SqlMiddleware } from './middleware/sql-middleware';
|
|
35
|
+
import type { SqlMiddleware, SqlMiddlewareContext } from './middleware/sql-middleware';
|
|
36
|
+
import type {
|
|
37
|
+
RuntimeFamilyAdapter,
|
|
38
|
+
RuntimeTelemetryEvent,
|
|
39
|
+
RuntimeVerifyOptions,
|
|
40
|
+
TelemetryOutcome,
|
|
41
|
+
} from './runtime-spi';
|
|
37
42
|
import type {
|
|
38
43
|
ExecutionContext,
|
|
39
44
|
SqlRuntimeAdapterInstance,
|
|
@@ -41,6 +46,8 @@ import type {
|
|
|
41
46
|
} from './sql-context';
|
|
42
47
|
import { SqlFamilyAdapter } from './sql-family-adapter';
|
|
43
48
|
|
|
49
|
+
export type Log = RuntimeLog;
|
|
50
|
+
|
|
44
51
|
export interface RuntimeOptions<TContract extends Contract<SqlStorage> = Contract<SqlStorage>> {
|
|
45
52
|
readonly context: ExecutionContext<TContract>;
|
|
46
53
|
readonly adapter: Adapter<AnyQueryAst, Contract<SqlStorage>, LoweredStatement>;
|
|
@@ -107,39 +114,37 @@ export interface RuntimeTransaction extends RuntimeQueryable {
|
|
|
107
114
|
rollback(): Promise<void>;
|
|
108
115
|
}
|
|
109
116
|
|
|
110
|
-
export interface RuntimeQueryable {
|
|
111
|
-
execute<Row = Record<string, unknown>>(
|
|
112
|
-
plan: ExecutionPlan<Row> | SqlQueryPlan<Row>,
|
|
113
|
-
): AsyncIterableResult<Row>;
|
|
114
|
-
}
|
|
117
|
+
export interface RuntimeQueryable extends RuntimeScope {}
|
|
115
118
|
|
|
116
119
|
export interface TransactionContext extends RuntimeQueryable {
|
|
117
120
|
readonly invalidated: boolean;
|
|
118
121
|
}
|
|
119
122
|
|
|
120
|
-
interface CoreQueryable {
|
|
121
|
-
execute<Row = Record<string, unknown>>(plan: ExecutionPlan<Row>): AsyncIterableResult<Row>;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
123
|
export type { RuntimeTelemetryEvent, RuntimeVerifyOptions, TelemetryOutcome };
|
|
125
124
|
|
|
125
|
+
function isExecutionPlan(plan: SqlExecutionPlan | SqlQueryPlan): plan is SqlExecutionPlan {
|
|
126
|
+
return 'sql' in plan;
|
|
127
|
+
}
|
|
128
|
+
|
|
126
129
|
class SqlRuntimeImpl<TContract extends Contract<SqlStorage> = Contract<SqlStorage>>
|
|
130
|
+
extends RuntimeCore<SqlQueryPlan, SqlExecutionPlan, SqlMiddleware>
|
|
127
131
|
implements Runtime
|
|
128
132
|
{
|
|
129
|
-
private readonly core: RuntimeCore<TContract, SqlDriver<unknown>, SqlMiddleware>;
|
|
130
133
|
private readonly contract: TContract;
|
|
131
134
|
private readonly adapter: Adapter<AnyQueryAst, Contract<SqlStorage>, LoweredStatement>;
|
|
135
|
+
private readonly driver: SqlDriver<unknown>;
|
|
136
|
+
private readonly familyAdapter: RuntimeFamilyAdapter<Contract<SqlStorage>>;
|
|
132
137
|
private readonly codecRegistry: CodecRegistry;
|
|
133
138
|
private readonly jsonSchemaValidators: JsonSchemaValidatorRegistry | undefined;
|
|
139
|
+
private readonly sqlCtx: SqlMiddlewareContext;
|
|
140
|
+
private readonly verify: RuntimeVerifyOptions;
|
|
134
141
|
private codecRegistryValidated: boolean;
|
|
142
|
+
private verified: boolean;
|
|
143
|
+
private startupVerified: boolean;
|
|
144
|
+
private _telemetry: RuntimeTelemetryEvent | null;
|
|
135
145
|
|
|
136
146
|
constructor(options: RuntimeOptions<TContract>) {
|
|
137
147
|
const { context, adapter, driver, verify, middleware, mode, log } = options;
|
|
138
|
-
this.contract = context.contract;
|
|
139
|
-
this.adapter = adapter;
|
|
140
|
-
this.codecRegistry = context.codecs;
|
|
141
|
-
this.jsonSchemaValidators = context.jsonSchemaValidators;
|
|
142
|
-
this.codecRegistryValidated = false;
|
|
143
148
|
|
|
144
149
|
if (middleware) {
|
|
145
150
|
for (const mw of middleware) {
|
|
@@ -147,18 +152,31 @@ class SqlRuntimeImpl<TContract extends Contract<SqlStorage> = Contract<SqlStorag
|
|
|
147
152
|
}
|
|
148
153
|
}
|
|
149
154
|
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
155
|
+
const sqlCtx: SqlMiddlewareContext = {
|
|
156
|
+
contract: context.contract,
|
|
157
|
+
mode: mode ?? 'strict',
|
|
158
|
+
now: () => Date.now(),
|
|
159
|
+
log: log ?? {
|
|
160
|
+
info: () => {},
|
|
161
|
+
warn: () => {},
|
|
162
|
+
error: () => {},
|
|
163
|
+
},
|
|
159
164
|
};
|
|
160
165
|
|
|
161
|
-
|
|
166
|
+
super({ middleware: middleware ?? [], ctx: sqlCtx });
|
|
167
|
+
|
|
168
|
+
this.contract = context.contract;
|
|
169
|
+
this.adapter = adapter;
|
|
170
|
+
this.driver = driver;
|
|
171
|
+
this.familyAdapter = new SqlFamilyAdapter(context.contract, adapter.profile);
|
|
172
|
+
this.codecRegistry = context.codecs;
|
|
173
|
+
this.jsonSchemaValidators = context.jsonSchemaValidators;
|
|
174
|
+
this.sqlCtx = sqlCtx;
|
|
175
|
+
this.verify = verify;
|
|
176
|
+
this.codecRegistryValidated = false;
|
|
177
|
+
this.verified = verify.mode === 'startup' ? false : verify.mode === 'always';
|
|
178
|
+
this.startupVerified = false;
|
|
179
|
+
this._telemetry = null;
|
|
162
180
|
|
|
163
181
|
if (verify.mode === 'startup') {
|
|
164
182
|
validateCodecRegistryCompleteness(this.codecRegistry, context.contract);
|
|
@@ -166,107 +184,251 @@ class SqlRuntimeImpl<TContract extends Contract<SqlStorage> = Contract<SqlStorag
|
|
|
166
184
|
}
|
|
167
185
|
}
|
|
168
186
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
187
|
+
/**
|
|
188
|
+
* Lower a `SqlQueryPlan` (AST + meta) into a `SqlExecutionPlan` with
|
|
189
|
+
* encoded parameters ready for the driver. This is the single point at
|
|
190
|
+
* which params transition from app-layer values to driver wire-format.
|
|
191
|
+
*/
|
|
192
|
+
protected override async lower(plan: SqlQueryPlan): Promise<SqlExecutionPlan> {
|
|
193
|
+
const lowered = lowerSqlPlan(this.adapter, this.contract, plan);
|
|
194
|
+
return Object.freeze({
|
|
195
|
+
...lowered,
|
|
196
|
+
params: await encodeParams(lowered, this.codecRegistry),
|
|
197
|
+
});
|
|
174
198
|
}
|
|
175
199
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
200
|
+
/**
|
|
201
|
+
* Default driver invocation. Production execution paths override the
|
|
202
|
+
* queryable target (e.g. transaction or connection) by going through
|
|
203
|
+
* `executeAgainstQueryable`; this implementation supports any caller of
|
|
204
|
+
* `super.execute(plan)` and the abstract-base contract.
|
|
205
|
+
*/
|
|
206
|
+
protected override runDriver(exec: SqlExecutionPlan): AsyncIterable<Record<string, unknown>> {
|
|
207
|
+
return this.driver.execute<Record<string, unknown>>({
|
|
208
|
+
sql: exec.sql,
|
|
209
|
+
params: exec.params,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
186
212
|
|
|
213
|
+
/**
|
|
214
|
+
* SQL pre-compile hook. Runs the registered middleware `beforeCompile`
|
|
215
|
+
* chain over the plan's draft (AST + meta) and returns a `SqlQueryPlan`
|
|
216
|
+
* with the rewritten AST and meta when the chain mutates them. The chain
|
|
217
|
+
* re-derives `meta.paramDescriptors` from the rewritten AST so descriptors
|
|
218
|
+
* stay in lockstep with the params the adapter will emit during lowering.
|
|
219
|
+
*/
|
|
220
|
+
protected override async runBeforeCompile(plan: SqlQueryPlan): Promise<SqlQueryPlan> {
|
|
187
221
|
const rewrittenDraft = await runBeforeCompileChain(
|
|
188
|
-
this.
|
|
222
|
+
this.middleware,
|
|
189
223
|
{ ast: plan.ast, meta: plan.meta },
|
|
190
|
-
this.
|
|
224
|
+
this.sqlCtx,
|
|
191
225
|
);
|
|
226
|
+
return rewrittenDraft.ast === plan.ast
|
|
227
|
+
? plan
|
|
228
|
+
: { ...plan, ast: rewrittenDraft.ast, meta: rewrittenDraft.meta };
|
|
229
|
+
}
|
|
192
230
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
return
|
|
231
|
+
override execute<Row>(
|
|
232
|
+
plan: (SqlExecutionPlan<unknown> | SqlQueryPlan<unknown>) & { readonly _row?: Row },
|
|
233
|
+
): AsyncIterableResult<Row> {
|
|
234
|
+
return this.executeAgainstQueryable<Row>(plan, this.driver);
|
|
197
235
|
}
|
|
198
236
|
|
|
199
|
-
private executeAgainstQueryable<Row
|
|
200
|
-
plan:
|
|
201
|
-
queryable:
|
|
237
|
+
private executeAgainstQueryable<Row>(
|
|
238
|
+
plan: SqlExecutionPlan<unknown> | SqlQueryPlan<unknown>,
|
|
239
|
+
queryable: SqlQueryable,
|
|
202
240
|
): AsyncIterableResult<Row> {
|
|
203
|
-
this.ensureCodecRegistryValidated(
|
|
204
|
-
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
241
|
+
this.ensureCodecRegistryValidated();
|
|
242
|
+
|
|
243
|
+
const self = this;
|
|
244
|
+
const generator = async function* (): AsyncGenerator<Row, void, unknown> {
|
|
245
|
+
const exec: SqlExecutionPlan = isExecutionPlan(plan)
|
|
246
|
+
? Object.freeze({
|
|
247
|
+
...plan,
|
|
248
|
+
params: await encodeParams(plan, self.codecRegistry),
|
|
249
|
+
})
|
|
250
|
+
: await self.lower(await self.runBeforeCompile(plan));
|
|
251
|
+
|
|
252
|
+
self.familyAdapter.validatePlan(exec, self.contract);
|
|
253
|
+
self._telemetry = null;
|
|
254
|
+
|
|
255
|
+
if (!self.startupVerified && self.verify.mode === 'startup') {
|
|
256
|
+
await self.verifyMarker();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (!self.verified && self.verify.mode === 'onFirstUse') {
|
|
260
|
+
await self.verifyMarker();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const startedAt = Date.now();
|
|
264
|
+
let outcome: TelemetryOutcome | null = null;
|
|
214
265
|
|
|
215
|
-
|
|
266
|
+
try {
|
|
267
|
+
if (self.verify.mode === 'always') {
|
|
268
|
+
await self.verifyMarker();
|
|
269
|
+
}
|
|
216
270
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
271
|
+
const stream = runWithMiddleware<SqlExecutionPlan, Record<string, unknown>>(
|
|
272
|
+
exec,
|
|
273
|
+
self.middleware,
|
|
274
|
+
self.ctx,
|
|
275
|
+
() =>
|
|
276
|
+
queryable.execute<Record<string, unknown>>({
|
|
277
|
+
sql: exec.sql,
|
|
278
|
+
params: exec.params,
|
|
279
|
+
}),
|
|
223
280
|
);
|
|
224
|
-
|
|
281
|
+
|
|
282
|
+
for await (const rawRow of stream) {
|
|
283
|
+
const decodedRow = await decodeRow(
|
|
284
|
+
rawRow,
|
|
285
|
+
exec,
|
|
286
|
+
self.codecRegistry,
|
|
287
|
+
self.jsonSchemaValidators,
|
|
288
|
+
);
|
|
289
|
+
yield decodedRow as Row;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
outcome = 'success';
|
|
293
|
+
} catch (error) {
|
|
294
|
+
outcome = 'runtime-error';
|
|
295
|
+
throw error;
|
|
296
|
+
} finally {
|
|
297
|
+
if (outcome !== null) {
|
|
298
|
+
self.recordTelemetry(exec, outcome, Date.now() - startedAt);
|
|
299
|
+
}
|
|
225
300
|
}
|
|
226
301
|
};
|
|
227
302
|
|
|
228
|
-
return new AsyncIterableResult(
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
execute<Row = Record<string, unknown>>(
|
|
232
|
-
plan: ExecutionPlan<Row> | SqlQueryPlan<Row>,
|
|
233
|
-
): AsyncIterableResult<Row> {
|
|
234
|
-
return this.executeAgainstQueryable(plan, this.core);
|
|
303
|
+
return new AsyncIterableResult(generator());
|
|
235
304
|
}
|
|
236
305
|
|
|
237
306
|
async connection(): Promise<RuntimeConnection> {
|
|
238
|
-
const
|
|
307
|
+
const driverConn = await this.driver.acquireConnection();
|
|
239
308
|
const self = this;
|
|
309
|
+
|
|
240
310
|
const wrappedConnection: RuntimeConnection = {
|
|
241
311
|
async transaction(): Promise<RuntimeTransaction> {
|
|
242
|
-
const
|
|
243
|
-
return
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
plan: ExecutionPlan<Row> | SqlQueryPlan<Row>,
|
|
248
|
-
): AsyncIterableResult<Row> {
|
|
249
|
-
return self.executeAgainstQueryable(plan, coreTx);
|
|
250
|
-
},
|
|
251
|
-
};
|
|
312
|
+
const driverTx = await driverConn.beginTransaction();
|
|
313
|
+
return self.wrapTransaction(driverTx);
|
|
314
|
+
},
|
|
315
|
+
async release(): Promise<void> {
|
|
316
|
+
await driverConn.release();
|
|
252
317
|
},
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
318
|
+
async destroy(reason?: unknown): Promise<void> {
|
|
319
|
+
await driverConn.destroy(reason);
|
|
320
|
+
},
|
|
321
|
+
execute<Row>(
|
|
322
|
+
plan: (SqlExecutionPlan<unknown> | SqlQueryPlan<unknown>) & { readonly _row?: Row },
|
|
257
323
|
): AsyncIterableResult<Row> {
|
|
258
|
-
return self.executeAgainstQueryable(plan,
|
|
324
|
+
return self.executeAgainstQueryable<Row>(plan, driverConn);
|
|
259
325
|
},
|
|
260
326
|
};
|
|
327
|
+
|
|
261
328
|
return wrappedConnection;
|
|
262
329
|
}
|
|
263
330
|
|
|
331
|
+
private wrapTransaction(driverTx: SqlTransaction): RuntimeTransaction {
|
|
332
|
+
const self = this;
|
|
333
|
+
return {
|
|
334
|
+
async commit(): Promise<void> {
|
|
335
|
+
await driverTx.commit();
|
|
336
|
+
},
|
|
337
|
+
async rollback(): Promise<void> {
|
|
338
|
+
await driverTx.rollback();
|
|
339
|
+
},
|
|
340
|
+
execute<Row>(
|
|
341
|
+
plan: (SqlExecutionPlan<unknown> | SqlQueryPlan<unknown>) & { readonly _row?: Row },
|
|
342
|
+
): AsyncIterableResult<Row> {
|
|
343
|
+
return self.executeAgainstQueryable<Row>(plan, driverTx);
|
|
344
|
+
},
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
264
348
|
telemetry(): RuntimeTelemetryEvent | null {
|
|
265
|
-
return this.
|
|
349
|
+
return this._telemetry;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async close(): Promise<void> {
|
|
353
|
+
await this.driver.close();
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
private ensureCodecRegistryValidated(): void {
|
|
357
|
+
if (!this.codecRegistryValidated) {
|
|
358
|
+
validateCodecRegistryCompleteness(this.codecRegistry, this.contract);
|
|
359
|
+
this.codecRegistryValidated = true;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
private async verifyMarker(): Promise<void> {
|
|
364
|
+
if (this.verify.mode === 'always') {
|
|
365
|
+
this.verified = false;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (this.verified) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const readStatement = this.familyAdapter.markerReader.readMarkerStatement();
|
|
373
|
+
const result = await this.driver.query(readStatement.sql, readStatement.params);
|
|
374
|
+
|
|
375
|
+
if (result.rows.length === 0) {
|
|
376
|
+
if (this.verify.requireMarker) {
|
|
377
|
+
throw runtimeError('CONTRACT.MARKER_MISSING', 'Contract marker not found in database');
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
this.verified = true;
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const marker = parseContractMarkerRow(result.rows[0]);
|
|
385
|
+
|
|
386
|
+
const contract = this.contract as {
|
|
387
|
+
storage: { storageHash: string };
|
|
388
|
+
execution?: { executionHash?: string | null };
|
|
389
|
+
profileHash?: string | null;
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
if (marker.storageHash !== contract.storage.storageHash) {
|
|
393
|
+
throw runtimeError(
|
|
394
|
+
'CONTRACT.MARKER_MISMATCH',
|
|
395
|
+
'Database storage hash does not match contract',
|
|
396
|
+
{
|
|
397
|
+
expected: contract.storage.storageHash,
|
|
398
|
+
actual: marker.storageHash,
|
|
399
|
+
},
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const expectedProfile = contract.profileHash ?? null;
|
|
404
|
+
if (expectedProfile !== null && marker.profileHash !== expectedProfile) {
|
|
405
|
+
throw runtimeError(
|
|
406
|
+
'CONTRACT.MARKER_MISMATCH',
|
|
407
|
+
'Database profile hash does not match contract',
|
|
408
|
+
{
|
|
409
|
+
expectedProfile,
|
|
410
|
+
actualProfile: marker.profileHash,
|
|
411
|
+
},
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
this.verified = true;
|
|
416
|
+
this.startupVerified = true;
|
|
266
417
|
}
|
|
267
418
|
|
|
268
|
-
|
|
269
|
-
|
|
419
|
+
private recordTelemetry(
|
|
420
|
+
plan: SqlExecutionPlan,
|
|
421
|
+
outcome: TelemetryOutcome,
|
|
422
|
+
durationMs?: number,
|
|
423
|
+
): void {
|
|
424
|
+
const contract = this.contract as { target: string };
|
|
425
|
+
this._telemetry = Object.freeze({
|
|
426
|
+
lane: plan.meta.lane,
|
|
427
|
+
target: contract.target,
|
|
428
|
+
fingerprint: computeSqlFingerprint(plan.sql),
|
|
429
|
+
outcome,
|
|
430
|
+
...(durationMs !== undefined ? { durationMs } : {}),
|
|
431
|
+
});
|
|
270
432
|
}
|
|
271
433
|
}
|
|
272
434
|
|
|
@@ -290,8 +452,8 @@ export async function withTransaction<R>(
|
|
|
290
452
|
get invalidated() {
|
|
291
453
|
return invalidated;
|
|
292
454
|
},
|
|
293
|
-
execute<Row
|
|
294
|
-
plan:
|
|
455
|
+
execute<Row>(
|
|
456
|
+
plan: (SqlExecutionPlan<unknown> | SqlQueryPlan<unknown>) & { readonly _row?: Row },
|
|
295
457
|
): AsyncIterableResult<Row> {
|
|
296
458
|
if (invalidated) {
|
|
297
459
|
throw transactionClosedError();
|