@prisma-next/sql-runtime 0.5.0-dev.1 → 0.5.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 +29 -21
- package/dist/{exports-TJ70Qw3r.mjs → exports-BOHa3Emo.mjs} +502 -121
- package/dist/exports-BOHa3Emo.mjs.map +1 -0
- package/dist/{index-DyDQ4fyK.d.mts → index-CZmC2kD3.d.mts} +87 -23
- 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 +59 -0
- package/src/middleware/budgets.ts +25 -33
- package/src/middleware/lints.ts +5 -5
- package/src/middleware/sql-middleware.ts +36 -6
- 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 +279 -101
- package/dist/exports-TJ70Qw3r.mjs.map +0 -1
- package/dist/index-DyDQ4fyK.d.mts.map +0 -1
- package/test/async-iterable-result.test.ts +0 -141
- 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 -159
- 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 -634
- package/test/utils.ts +0 -297
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type { Contract
|
|
1
|
+
import type { Contract } from '@prisma-next/contract/types';
|
|
2
|
+
import type { ExecutionPlan } from '@prisma-next/framework-components/runtime';
|
|
2
3
|
import { runtimeError } from '@prisma-next/framework-components/runtime';
|
|
3
|
-
import type { MarkerReader, RuntimeFamilyAdapter } from '@prisma-next/runtime-executor';
|
|
4
4
|
import type { SqlStorage } from '@prisma-next/sql-contract/types';
|
|
5
5
|
import type { AdapterProfile } from '@prisma-next/sql-relational-core/ast';
|
|
6
|
+
import type { MarkerReader, RuntimeFamilyAdapter } from './runtime-spi';
|
|
6
7
|
|
|
7
8
|
export class SqlFamilyAdapter<TContract extends Contract<SqlStorage>>
|
|
8
9
|
implements RuntimeFamilyAdapter<TContract>
|
package/src/sql-marker.ts
CHANGED
package/src/sql-runtime.ts
CHANGED
|
@@ -1,23 +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
|
-
Middleware,
|
|
10
|
-
RuntimeCore,
|
|
11
|
-
RuntimeCoreOptions,
|
|
12
|
-
RuntimeTelemetryEvent,
|
|
13
|
-
RuntimeVerifyOptions,
|
|
14
|
-
TelemetryOutcome,
|
|
15
|
-
} from '@prisma-next/runtime-executor';
|
|
16
6
|
import {
|
|
17
7
|
AsyncIterableResult,
|
|
18
|
-
|
|
8
|
+
checkMiddlewareCompatibility,
|
|
9
|
+
RuntimeCore,
|
|
10
|
+
type RuntimeLog,
|
|
19
11
|
runtimeError,
|
|
20
|
-
|
|
12
|
+
runWithMiddleware,
|
|
13
|
+
} from '@prisma-next/framework-components/runtime';
|
|
21
14
|
import type { SqlStorage } from '@prisma-next/sql-contract/types';
|
|
22
15
|
import type {
|
|
23
16
|
Adapter,
|
|
@@ -25,14 +18,27 @@ import type {
|
|
|
25
18
|
CodecRegistry,
|
|
26
19
|
LoweredStatement,
|
|
27
20
|
SqlDriver,
|
|
21
|
+
SqlQueryable,
|
|
22
|
+
SqlTransaction,
|
|
28
23
|
} from '@prisma-next/sql-relational-core/ast';
|
|
29
|
-
import type { SqlQueryPlan } from '@prisma-next/sql-relational-core/plan';
|
|
24
|
+
import type { SqlExecutionPlan, SqlQueryPlan } from '@prisma-next/sql-relational-core/plan';
|
|
30
25
|
import type { JsonSchemaValidatorRegistry } from '@prisma-next/sql-relational-core/query-lane-context';
|
|
26
|
+
import type { RuntimeScope } from '@prisma-next/sql-relational-core/types';
|
|
31
27
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
32
28
|
import { decodeRow } from './codecs/decoding';
|
|
33
29
|
import { encodeParams } from './codecs/encoding';
|
|
34
30
|
import { validateCodecRegistryCompleteness } from './codecs/validation';
|
|
31
|
+
import { computeSqlFingerprint } from './fingerprint';
|
|
35
32
|
import { lowerSqlPlan } from './lower-sql-plan';
|
|
33
|
+
import { parseContractMarkerRow } from './marker';
|
|
34
|
+
import { runBeforeCompileChain } from './middleware/before-compile-chain';
|
|
35
|
+
import type { SqlMiddleware, SqlMiddlewareContext } from './middleware/sql-middleware';
|
|
36
|
+
import type {
|
|
37
|
+
RuntimeFamilyAdapter,
|
|
38
|
+
RuntimeTelemetryEvent,
|
|
39
|
+
RuntimeVerifyOptions,
|
|
40
|
+
TelemetryOutcome,
|
|
41
|
+
} from './runtime-spi';
|
|
36
42
|
import type {
|
|
37
43
|
ExecutionContext,
|
|
38
44
|
SqlRuntimeAdapterInstance,
|
|
@@ -40,12 +46,14 @@ import type {
|
|
|
40
46
|
} from './sql-context';
|
|
41
47
|
import { SqlFamilyAdapter } from './sql-family-adapter';
|
|
42
48
|
|
|
49
|
+
export type Log = RuntimeLog;
|
|
50
|
+
|
|
43
51
|
export interface RuntimeOptions<TContract extends Contract<SqlStorage> = Contract<SqlStorage>> {
|
|
44
52
|
readonly context: ExecutionContext<TContract>;
|
|
45
53
|
readonly adapter: Adapter<AnyQueryAst, Contract<SqlStorage>, LoweredStatement>;
|
|
46
54
|
readonly driver: SqlDriver<unknown>;
|
|
47
55
|
readonly verify: RuntimeVerifyOptions;
|
|
48
|
-
readonly middleware?: readonly
|
|
56
|
+
readonly middleware?: readonly SqlMiddleware[];
|
|
49
57
|
readonly mode?: 'strict' | 'permissive';
|
|
50
58
|
readonly log?: Log;
|
|
51
59
|
}
|
|
@@ -64,7 +72,7 @@ export interface CreateRuntimeOptions<
|
|
|
64
72
|
readonly context: ExecutionContext<TContract>;
|
|
65
73
|
readonly driver: SqlDriver<unknown>;
|
|
66
74
|
readonly verify: RuntimeVerifyOptions;
|
|
67
|
-
readonly middleware?: readonly
|
|
75
|
+
readonly middleware?: readonly SqlMiddleware[];
|
|
68
76
|
readonly mode?: 'strict' | 'permissive';
|
|
69
77
|
readonly log?: Log;
|
|
70
78
|
}
|
|
@@ -106,39 +114,37 @@ export interface RuntimeTransaction extends RuntimeQueryable {
|
|
|
106
114
|
rollback(): Promise<void>;
|
|
107
115
|
}
|
|
108
116
|
|
|
109
|
-
export interface RuntimeQueryable {
|
|
110
|
-
execute<Row = Record<string, unknown>>(
|
|
111
|
-
plan: ExecutionPlan<Row> | SqlQueryPlan<Row>,
|
|
112
|
-
): AsyncIterableResult<Row>;
|
|
113
|
-
}
|
|
117
|
+
export interface RuntimeQueryable extends RuntimeScope {}
|
|
114
118
|
|
|
115
119
|
export interface TransactionContext extends RuntimeQueryable {
|
|
116
120
|
readonly invalidated: boolean;
|
|
117
121
|
}
|
|
118
122
|
|
|
119
|
-
interface CoreQueryable {
|
|
120
|
-
execute<Row = Record<string, unknown>>(plan: ExecutionPlan<Row>): AsyncIterableResult<Row>;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
123
|
export type { RuntimeTelemetryEvent, RuntimeVerifyOptions, TelemetryOutcome };
|
|
124
124
|
|
|
125
|
+
function isExecutionPlan(plan: SqlExecutionPlan | SqlQueryPlan): plan is SqlExecutionPlan {
|
|
126
|
+
return 'sql' in plan;
|
|
127
|
+
}
|
|
128
|
+
|
|
125
129
|
class SqlRuntimeImpl<TContract extends Contract<SqlStorage> = Contract<SqlStorage>>
|
|
130
|
+
extends RuntimeCore<SqlQueryPlan, SqlExecutionPlan, SqlMiddleware>
|
|
126
131
|
implements Runtime
|
|
127
132
|
{
|
|
128
|
-
private readonly core: RuntimeCore<TContract, SqlDriver<unknown>>;
|
|
129
133
|
private readonly contract: TContract;
|
|
130
134
|
private readonly adapter: Adapter<AnyQueryAst, Contract<SqlStorage>, LoweredStatement>;
|
|
135
|
+
private readonly driver: SqlDriver<unknown>;
|
|
136
|
+
private readonly familyAdapter: RuntimeFamilyAdapter<Contract<SqlStorage>>;
|
|
131
137
|
private readonly codecRegistry: CodecRegistry;
|
|
132
138
|
private readonly jsonSchemaValidators: JsonSchemaValidatorRegistry | undefined;
|
|
139
|
+
private readonly sqlCtx: SqlMiddlewareContext;
|
|
140
|
+
private readonly verify: RuntimeVerifyOptions;
|
|
133
141
|
private codecRegistryValidated: boolean;
|
|
142
|
+
private verified: boolean;
|
|
143
|
+
private startupVerified: boolean;
|
|
144
|
+
private _telemetry: RuntimeTelemetryEvent | null;
|
|
134
145
|
|
|
135
146
|
constructor(options: RuntimeOptions<TContract>) {
|
|
136
147
|
const { context, adapter, driver, verify, middleware, mode, log } = options;
|
|
137
|
-
this.contract = context.contract;
|
|
138
|
-
this.adapter = adapter;
|
|
139
|
-
this.codecRegistry = context.codecs;
|
|
140
|
-
this.jsonSchemaValidators = context.jsonSchemaValidators;
|
|
141
|
-
this.codecRegistryValidated = false;
|
|
142
148
|
|
|
143
149
|
if (middleware) {
|
|
144
150
|
for (const mw of middleware) {
|
|
@@ -146,18 +152,31 @@ class SqlRuntimeImpl<TContract extends Contract<SqlStorage> = Contract<SqlStorag
|
|
|
146
152
|
}
|
|
147
153
|
}
|
|
148
154
|
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
+
},
|
|
158
164
|
};
|
|
159
165
|
|
|
160
|
-
|
|
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;
|
|
161
180
|
|
|
162
181
|
if (verify.mode === 'startup') {
|
|
163
182
|
validateCodecRegistryCompleteness(this.codecRegistry, context.contract);
|
|
@@ -165,92 +184,251 @@ class SqlRuntimeImpl<TContract extends Contract<SqlStorage> = Contract<SqlStorag
|
|
|
165
184
|
}
|
|
166
185
|
}
|
|
167
186
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
+
});
|
|
173
198
|
}
|
|
174
199
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
+
}
|
|
179
212
|
|
|
180
|
-
|
|
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> {
|
|
221
|
+
const rewrittenDraft = await runBeforeCompileChain(
|
|
222
|
+
this.middleware,
|
|
223
|
+
{ ast: plan.ast, meta: plan.meta },
|
|
224
|
+
this.sqlCtx,
|
|
225
|
+
);
|
|
226
|
+
return rewrittenDraft.ast === plan.ast
|
|
227
|
+
? plan
|
|
228
|
+
: { ...plan, ast: rewrittenDraft.ast, meta: rewrittenDraft.meta };
|
|
181
229
|
}
|
|
182
230
|
|
|
183
|
-
|
|
184
|
-
plan:
|
|
185
|
-
queryable: CoreQueryable,
|
|
231
|
+
override execute<Row>(
|
|
232
|
+
plan: (SqlExecutionPlan<unknown> | SqlQueryPlan<unknown>) & { readonly _row?: Row },
|
|
186
233
|
): AsyncIterableResult<Row> {
|
|
187
|
-
this.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
234
|
+
return this.executeAgainstQueryable<Row>(plan, this.driver);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private executeAgainstQueryable<Row>(
|
|
238
|
+
plan: SqlExecutionPlan<unknown> | SqlQueryPlan<unknown>,
|
|
239
|
+
queryable: SqlQueryable,
|
|
240
|
+
): AsyncIterableResult<Row> {
|
|
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
|
+
}
|
|
198
262
|
|
|
199
|
-
const
|
|
263
|
+
const startedAt = Date.now();
|
|
264
|
+
let outcome: TelemetryOutcome | null = null;
|
|
200
265
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
266
|
+
try {
|
|
267
|
+
if (self.verify.mode === 'always') {
|
|
268
|
+
await self.verifyMarker();
|
|
269
|
+
}
|
|
270
|
+
|
|
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
|
+
}),
|
|
207
280
|
);
|
|
208
|
-
|
|
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
|
+
}
|
|
209
300
|
}
|
|
210
301
|
};
|
|
211
302
|
|
|
212
|
-
return new AsyncIterableResult(
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
execute<Row = Record<string, unknown>>(
|
|
216
|
-
plan: ExecutionPlan<Row> | SqlQueryPlan<Row>,
|
|
217
|
-
): AsyncIterableResult<Row> {
|
|
218
|
-
return this.executeAgainstQueryable(plan, this.core);
|
|
303
|
+
return new AsyncIterableResult(generator());
|
|
219
304
|
}
|
|
220
305
|
|
|
221
306
|
async connection(): Promise<RuntimeConnection> {
|
|
222
|
-
const
|
|
307
|
+
const driverConn = await this.driver.acquireConnection();
|
|
223
308
|
const self = this;
|
|
309
|
+
|
|
224
310
|
const wrappedConnection: RuntimeConnection = {
|
|
225
311
|
async transaction(): Promise<RuntimeTransaction> {
|
|
226
|
-
const
|
|
227
|
-
return
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
},
|
|
235
|
-
};
|
|
312
|
+
const driverTx = await driverConn.beginTransaction();
|
|
313
|
+
return self.wrapTransaction(driverTx);
|
|
314
|
+
},
|
|
315
|
+
async release(): Promise<void> {
|
|
316
|
+
await driverConn.release();
|
|
317
|
+
},
|
|
318
|
+
async destroy(reason?: unknown): Promise<void> {
|
|
319
|
+
await driverConn.destroy(reason);
|
|
236
320
|
},
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
execute<Row = Record<string, unknown>>(
|
|
240
|
-
plan: ExecutionPlan<Row> | SqlQueryPlan<Row>,
|
|
321
|
+
execute<Row>(
|
|
322
|
+
plan: (SqlExecutionPlan<unknown> | SqlQueryPlan<unknown>) & { readonly _row?: Row },
|
|
241
323
|
): AsyncIterableResult<Row> {
|
|
242
|
-
return self.executeAgainstQueryable(plan,
|
|
324
|
+
return self.executeAgainstQueryable<Row>(plan, driverConn);
|
|
243
325
|
},
|
|
244
326
|
};
|
|
327
|
+
|
|
245
328
|
return wrappedConnection;
|
|
246
329
|
}
|
|
247
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
|
+
|
|
248
348
|
telemetry(): RuntimeTelemetryEvent | null {
|
|
249
|
-
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;
|
|
250
417
|
}
|
|
251
418
|
|
|
252
|
-
|
|
253
|
-
|
|
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
|
+
});
|
|
254
432
|
}
|
|
255
433
|
}
|
|
256
434
|
|
|
@@ -274,8 +452,8 @@ export async function withTransaction<R>(
|
|
|
274
452
|
get invalidated() {
|
|
275
453
|
return invalidated;
|
|
276
454
|
},
|
|
277
|
-
execute<Row
|
|
278
|
-
plan:
|
|
455
|
+
execute<Row>(
|
|
456
|
+
plan: (SqlExecutionPlan<unknown> | SqlQueryPlan<unknown>) & { readonly _row?: Row },
|
|
279
457
|
): AsyncIterableResult<Row> {
|
|
280
458
|
if (invalidated) {
|
|
281
459
|
throw transactionClosedError();
|