@prisma-next/sql-runtime 0.5.0-dev.5 → 0.5.0-dev.50
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-Boy5W7kg.mjs +1608 -0
- package/dist/exports-Boy5W7kg.mjs.map +1 -0
- package/dist/{index-yb51L_1h.d.mts → index-_dXSGeho.d.mts} +78 -25
- package/dist/index-_dXSGeho.d.mts.map +1 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/test/utils.d.mts +6 -5
- package/dist/test/utils.d.mts.map +1 -1
- package/dist/test/utils.mjs +11 -5
- package/dist/test/utils.mjs.map +1 -1
- package/package.json +10 -12
- package/src/codecs/decoding.ts +294 -173
- package/src/codecs/encoding.ts +162 -37
- package/src/codecs/validation.ts +22 -3
- package/src/content-hash.ts +44 -0
- package/src/exports/index.ts +11 -7
- package/src/fingerprint.ts +22 -0
- package/src/guardrails/raw.ts +165 -0
- package/src/lower-sql-plan.ts +3 -3
- package/src/marker.ts +75 -0
- package/src/middleware/before-compile-chain.ts +1 -0
- package/src/middleware/budgets.ts +26 -96
- package/src/middleware/lints.ts +3 -3
- package/src/middleware/sql-middleware.ts +6 -5
- package/src/runtime-spi.ts +44 -0
- package/src/sql-context.ts +332 -78
- package/src/sql-family-adapter.ts +3 -2
- package/src/sql-marker.ts +62 -47
- package/src/sql-runtime.ts +336 -113
- package/dist/exports-BQZSVXXt.mjs +0 -981
- 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,39 +1,51 @@
|
|
|
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
|
+
checkAborted,
|
|
9
|
+
checkMiddlewareCompatibility,
|
|
10
|
+
RuntimeCore,
|
|
11
|
+
type RuntimeExecuteOptions,
|
|
12
|
+
type RuntimeLog,
|
|
18
13
|
runtimeError,
|
|
19
|
-
|
|
14
|
+
runWithMiddleware,
|
|
15
|
+
} from '@prisma-next/framework-components/runtime';
|
|
20
16
|
import type { SqlStorage } from '@prisma-next/sql-contract/types';
|
|
21
17
|
import type {
|
|
22
18
|
Adapter,
|
|
23
19
|
AnyQueryAst,
|
|
24
20
|
CodecRegistry,
|
|
21
|
+
ContractCodecRegistry,
|
|
25
22
|
LoweredStatement,
|
|
23
|
+
SqlCodecCallContext,
|
|
26
24
|
SqlDriver,
|
|
25
|
+
SqlQueryable,
|
|
26
|
+
SqlTransaction,
|
|
27
27
|
} from '@prisma-next/sql-relational-core/ast';
|
|
28
|
-
import type { SqlQueryPlan } from '@prisma-next/sql-relational-core/plan';
|
|
29
|
-
import type {
|
|
28
|
+
import type { SqlExecutionPlan, SqlQueryPlan } from '@prisma-next/sql-relational-core/plan';
|
|
29
|
+
import type {
|
|
30
|
+
CodecDescriptorRegistry,
|
|
31
|
+
JsonSchemaValidatorRegistry,
|
|
32
|
+
} from '@prisma-next/sql-relational-core/query-lane-context';
|
|
33
|
+
import type { RuntimeScope } from '@prisma-next/sql-relational-core/types';
|
|
30
34
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
31
35
|
import { decodeRow } from './codecs/decoding';
|
|
32
36
|
import { encodeParams } from './codecs/encoding';
|
|
33
37
|
import { validateCodecRegistryCompleteness } from './codecs/validation';
|
|
38
|
+
import { computeSqlContentHash } from './content-hash';
|
|
39
|
+
import { computeSqlFingerprint } from './fingerprint';
|
|
34
40
|
import { lowerSqlPlan } from './lower-sql-plan';
|
|
35
41
|
import { runBeforeCompileChain } from './middleware/before-compile-chain';
|
|
36
|
-
import type { SqlMiddleware } from './middleware/sql-middleware';
|
|
42
|
+
import type { SqlMiddleware, SqlMiddlewareContext } from './middleware/sql-middleware';
|
|
43
|
+
import type {
|
|
44
|
+
RuntimeFamilyAdapter,
|
|
45
|
+
RuntimeTelemetryEvent,
|
|
46
|
+
RuntimeVerifyOptions,
|
|
47
|
+
TelemetryOutcome,
|
|
48
|
+
} from './runtime-spi';
|
|
37
49
|
import type {
|
|
38
50
|
ExecutionContext,
|
|
39
51
|
SqlRuntimeAdapterInstance,
|
|
@@ -41,6 +53,8 @@ import type {
|
|
|
41
53
|
} from './sql-context';
|
|
42
54
|
import { SqlFamilyAdapter } from './sql-family-adapter';
|
|
43
55
|
|
|
56
|
+
export type Log = RuntimeLog;
|
|
57
|
+
|
|
44
58
|
export interface RuntimeOptions<TContract extends Contract<SqlStorage> = Contract<SqlStorage>> {
|
|
45
59
|
readonly context: ExecutionContext<TContract>;
|
|
46
60
|
readonly adapter: Adapter<AnyQueryAst, Contract<SqlStorage>, LoweredStatement>;
|
|
@@ -107,39 +121,39 @@ export interface RuntimeTransaction extends RuntimeQueryable {
|
|
|
107
121
|
rollback(): Promise<void>;
|
|
108
122
|
}
|
|
109
123
|
|
|
110
|
-
export interface RuntimeQueryable {
|
|
111
|
-
execute<Row = Record<string, unknown>>(
|
|
112
|
-
plan: ExecutionPlan<Row> | SqlQueryPlan<Row>,
|
|
113
|
-
): AsyncIterableResult<Row>;
|
|
114
|
-
}
|
|
124
|
+
export interface RuntimeQueryable extends RuntimeScope {}
|
|
115
125
|
|
|
116
126
|
export interface TransactionContext extends RuntimeQueryable {
|
|
117
127
|
readonly invalidated: boolean;
|
|
118
128
|
}
|
|
119
129
|
|
|
120
|
-
interface CoreQueryable {
|
|
121
|
-
execute<Row = Record<string, unknown>>(plan: ExecutionPlan<Row>): AsyncIterableResult<Row>;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
130
|
export type { RuntimeTelemetryEvent, RuntimeVerifyOptions, TelemetryOutcome };
|
|
125
131
|
|
|
132
|
+
function isExecutionPlan(plan: SqlExecutionPlan | SqlQueryPlan): plan is SqlExecutionPlan {
|
|
133
|
+
return 'sql' in plan;
|
|
134
|
+
}
|
|
135
|
+
|
|
126
136
|
class SqlRuntimeImpl<TContract extends Contract<SqlStorage> = Contract<SqlStorage>>
|
|
137
|
+
extends RuntimeCore<SqlQueryPlan, SqlExecutionPlan, SqlMiddleware>
|
|
127
138
|
implements Runtime
|
|
128
139
|
{
|
|
129
|
-
private readonly core: RuntimeCore<TContract, SqlDriver<unknown>, SqlMiddleware>;
|
|
130
140
|
private readonly contract: TContract;
|
|
131
141
|
private readonly adapter: Adapter<AnyQueryAst, Contract<SqlStorage>, LoweredStatement>;
|
|
142
|
+
private readonly driver: SqlDriver<unknown>;
|
|
143
|
+
private readonly familyAdapter: RuntimeFamilyAdapter<Contract<SqlStorage>>;
|
|
132
144
|
private readonly codecRegistry: CodecRegistry;
|
|
145
|
+
private readonly contractCodecs: ContractCodecRegistry;
|
|
146
|
+
private readonly codecDescriptors: CodecDescriptorRegistry;
|
|
133
147
|
private readonly jsonSchemaValidators: JsonSchemaValidatorRegistry | undefined;
|
|
148
|
+
private readonly sqlCtx: SqlMiddlewareContext;
|
|
149
|
+
private readonly verify: RuntimeVerifyOptions;
|
|
134
150
|
private codecRegistryValidated: boolean;
|
|
151
|
+
private verified: boolean;
|
|
152
|
+
private startupVerified: boolean;
|
|
153
|
+
private _telemetry: RuntimeTelemetryEvent | null;
|
|
135
154
|
|
|
136
155
|
constructor(options: RuntimeOptions<TContract>) {
|
|
137
156
|
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
157
|
|
|
144
158
|
if (middleware) {
|
|
145
159
|
for (const mw of middleware) {
|
|
@@ -147,126 +161,334 @@ class SqlRuntimeImpl<TContract extends Contract<SqlStorage> = Contract<SqlStorag
|
|
|
147
161
|
}
|
|
148
162
|
}
|
|
149
163
|
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
164
|
+
const sqlCtx: SqlMiddlewareContext = {
|
|
165
|
+
contract: context.contract,
|
|
166
|
+
mode: mode ?? 'strict',
|
|
167
|
+
now: () => Date.now(),
|
|
168
|
+
log: log ?? {
|
|
169
|
+
info: () => {},
|
|
170
|
+
warn: () => {},
|
|
171
|
+
error: () => {},
|
|
172
|
+
},
|
|
173
|
+
// ctx is only invoked by runWithMiddleware with execs this runtime lowered;
|
|
174
|
+
// the framework parameter type is the cross-family base.
|
|
175
|
+
contentHash: (exec) => computeSqlContentHash(exec as SqlExecutionPlan),
|
|
159
176
|
};
|
|
160
177
|
|
|
161
|
-
|
|
178
|
+
super({ middleware: middleware ?? [], ctx: sqlCtx });
|
|
179
|
+
|
|
180
|
+
this.contract = context.contract;
|
|
181
|
+
this.adapter = adapter;
|
|
182
|
+
this.driver = driver;
|
|
183
|
+
this.familyAdapter = new SqlFamilyAdapter(context.contract, adapter.profile);
|
|
184
|
+
this.codecRegistry = context.codecs;
|
|
185
|
+
this.contractCodecs = context.contractCodecs;
|
|
186
|
+
this.codecDescriptors = context.codecDescriptors;
|
|
187
|
+
this.jsonSchemaValidators = context.jsonSchemaValidators;
|
|
188
|
+
this.sqlCtx = sqlCtx;
|
|
189
|
+
this.verify = verify;
|
|
190
|
+
this.codecRegistryValidated = false;
|
|
191
|
+
this.verified = verify.mode === 'startup' ? false : verify.mode === 'always';
|
|
192
|
+
this.startupVerified = false;
|
|
193
|
+
this._telemetry = null;
|
|
162
194
|
|
|
163
195
|
if (verify.mode === 'startup') {
|
|
164
|
-
validateCodecRegistryCompleteness(this.
|
|
196
|
+
validateCodecRegistryCompleteness(this.codecDescriptors, context.contract);
|
|
165
197
|
this.codecRegistryValidated = true;
|
|
166
198
|
}
|
|
167
199
|
}
|
|
168
200
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
201
|
+
/**
|
|
202
|
+
* Lower a `SqlQueryPlan` (AST + meta) into a `SqlExecutionPlan` with
|
|
203
|
+
* encoded parameters ready for the driver. This is the single point at
|
|
204
|
+
* which params transition from app-layer values to driver wire-format.
|
|
205
|
+
*
|
|
206
|
+
* `ctx: SqlCodecCallContext` is forwarded to `encodeParams` so per-query
|
|
207
|
+
* cancellation reaches every codec body during parameter encoding. The
|
|
208
|
+
* framework abstract typed this as `CodecCallContext`; the SQL family
|
|
209
|
+
* narrows it to the SQL-specific extension. SQL params do not populate
|
|
210
|
+
* `ctx.column` — encode-side column metadata is the middleware's domain.
|
|
211
|
+
*/
|
|
212
|
+
protected override async lower(
|
|
213
|
+
plan: SqlQueryPlan,
|
|
214
|
+
ctx: SqlCodecCallContext,
|
|
215
|
+
): Promise<SqlExecutionPlan> {
|
|
216
|
+
const lowered = lowerSqlPlan(this.adapter, this.contract, plan);
|
|
217
|
+
return Object.freeze({
|
|
218
|
+
...lowered,
|
|
219
|
+
params: await encodeParams(lowered, this.codecRegistry, ctx, this.contractCodecs),
|
|
220
|
+
});
|
|
174
221
|
}
|
|
175
222
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
223
|
+
/**
|
|
224
|
+
* Default driver invocation. Production execution paths override the
|
|
225
|
+
* queryable target (e.g. transaction or connection) by going through
|
|
226
|
+
* `executeAgainstQueryable`; this implementation supports any caller of
|
|
227
|
+
* `super.execute(plan)` and the abstract-base contract.
|
|
228
|
+
*/
|
|
229
|
+
protected override runDriver(exec: SqlExecutionPlan): AsyncIterable<Record<string, unknown>> {
|
|
230
|
+
return this.driver.execute<Record<string, unknown>>({
|
|
231
|
+
sql: exec.sql,
|
|
232
|
+
params: exec.params,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
186
235
|
|
|
236
|
+
/**
|
|
237
|
+
* SQL pre-compile hook. Runs the registered middleware `beforeCompile`
|
|
238
|
+
* chain over the plan's draft (AST + meta). Returns the original plan
|
|
239
|
+
* unchanged when no middleware rewrote the AST; otherwise returns a new
|
|
240
|
+
* plan carrying the rewritten AST and meta. The AST is the authoritative
|
|
241
|
+
* source of execution metadata, so a rewrite needs no sidecar
|
|
242
|
+
* reconciliation here — the lowering adapter and the encoder both walk
|
|
243
|
+
* the rewritten AST directly.
|
|
244
|
+
*/
|
|
245
|
+
protected override async runBeforeCompile(plan: SqlQueryPlan): Promise<SqlQueryPlan> {
|
|
187
246
|
const rewrittenDraft = await runBeforeCompileChain(
|
|
188
|
-
this.
|
|
247
|
+
this.middleware,
|
|
189
248
|
{ ast: plan.ast, meta: plan.meta },
|
|
190
|
-
this.
|
|
249
|
+
this.sqlCtx,
|
|
191
250
|
);
|
|
251
|
+
return rewrittenDraft.ast === plan.ast
|
|
252
|
+
? plan
|
|
253
|
+
: { ...plan, ast: rewrittenDraft.ast, meta: rewrittenDraft.meta };
|
|
254
|
+
}
|
|
192
255
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
256
|
+
override execute<Row>(
|
|
257
|
+
plan: (SqlExecutionPlan<unknown> | SqlQueryPlan<unknown>) & { readonly _row?: Row },
|
|
258
|
+
options?: RuntimeExecuteOptions,
|
|
259
|
+
): AsyncIterableResult<Row> {
|
|
260
|
+
return this.executeAgainstQueryable<Row>(plan, this.driver, options);
|
|
197
261
|
}
|
|
198
262
|
|
|
199
|
-
private executeAgainstQueryable<Row
|
|
200
|
-
plan:
|
|
201
|
-
queryable:
|
|
263
|
+
private executeAgainstQueryable<Row>(
|
|
264
|
+
plan: SqlExecutionPlan<unknown> | SqlQueryPlan<unknown>,
|
|
265
|
+
queryable: SqlQueryable,
|
|
266
|
+
options?: RuntimeExecuteOptions,
|
|
202
267
|
): AsyncIterableResult<Row> {
|
|
203
|
-
this.ensureCodecRegistryValidated(
|
|
204
|
-
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
)
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
268
|
+
this.ensureCodecRegistryValidated();
|
|
269
|
+
|
|
270
|
+
const self = this;
|
|
271
|
+
const signal = options?.signal;
|
|
272
|
+
// One ctx per execute() call — the same reference is shared by
|
|
273
|
+
// encodeParams (lower), decodeRow (per-row), and the stream loop's
|
|
274
|
+
// between-row checks. Per-cell ctx allocations inside decodeField add
|
|
275
|
+
// `column` for resolvable cells without re-wrapping the signal. The
|
|
276
|
+
// ctx object is always allocated; the `signal` field is only included
|
|
277
|
+
// when a signal was supplied (exactOptionalPropertyTypes).
|
|
278
|
+
const codecCtx: SqlCodecCallContext = signal === undefined ? {} : { signal };
|
|
279
|
+
|
|
280
|
+
const generator = async function* (): AsyncGenerator<Row, void, unknown> {
|
|
281
|
+
checkAborted(codecCtx, 'stream');
|
|
282
|
+
|
|
283
|
+
const exec: SqlExecutionPlan = isExecutionPlan(plan)
|
|
284
|
+
? Object.freeze({
|
|
285
|
+
...plan,
|
|
286
|
+
params: await encodeParams(plan, self.codecRegistry, codecCtx, self.contractCodecs),
|
|
287
|
+
})
|
|
288
|
+
: await self.lower(await self.runBeforeCompile(plan), codecCtx);
|
|
289
|
+
|
|
290
|
+
self.familyAdapter.validatePlan(exec, self.contract);
|
|
291
|
+
self._telemetry = null;
|
|
292
|
+
|
|
293
|
+
if (!self.startupVerified && self.verify.mode === 'startup') {
|
|
294
|
+
await self.verifyMarker();
|
|
295
|
+
}
|
|
214
296
|
|
|
215
|
-
|
|
297
|
+
if (!self.verified && self.verify.mode === 'onFirstUse') {
|
|
298
|
+
await self.verifyMarker();
|
|
299
|
+
}
|
|
216
300
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
self.
|
|
301
|
+
const startedAt = Date.now();
|
|
302
|
+
let outcome: TelemetryOutcome | null = null;
|
|
303
|
+
|
|
304
|
+
try {
|
|
305
|
+
if (self.verify.mode === 'always') {
|
|
306
|
+
await self.verifyMarker();
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const stream = runWithMiddleware<SqlExecutionPlan, Record<string, unknown>>(
|
|
310
|
+
exec,
|
|
311
|
+
self.middleware,
|
|
312
|
+
self.ctx,
|
|
313
|
+
() =>
|
|
314
|
+
queryable.execute<Record<string, unknown>>({
|
|
315
|
+
sql: exec.sql,
|
|
316
|
+
params: exec.params,
|
|
317
|
+
}),
|
|
223
318
|
);
|
|
224
|
-
|
|
319
|
+
|
|
320
|
+
// Manually drive the driver's async iterator so the between-row
|
|
321
|
+
// abort check fires *before* requesting the next row. With a
|
|
322
|
+
// `for await...of` loop the runtime would await `iterator.next()`
|
|
323
|
+
// first, leaving a window where one extra row is pulled through
|
|
324
|
+
// the driver after the signal aborted.
|
|
325
|
+
const iterator = stream[Symbol.asyncIterator]();
|
|
326
|
+
try {
|
|
327
|
+
while (true) {
|
|
328
|
+
checkAborted(codecCtx, 'stream');
|
|
329
|
+
const next = await iterator.next();
|
|
330
|
+
if (next.done) {
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
const decodedRow = await decodeRow(
|
|
334
|
+
next.value,
|
|
335
|
+
exec,
|
|
336
|
+
self.codecRegistry,
|
|
337
|
+
self.jsonSchemaValidators,
|
|
338
|
+
codecCtx,
|
|
339
|
+
self.contractCodecs,
|
|
340
|
+
);
|
|
341
|
+
yield decodedRow as Row;
|
|
342
|
+
}
|
|
343
|
+
} finally {
|
|
344
|
+
// Best-effort iterator cleanup so the driver can release its
|
|
345
|
+
// resources whether the stream finished normally, threw, or was
|
|
346
|
+
// abandoned by the consumer.
|
|
347
|
+
await iterator.return?.();
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
outcome = 'success';
|
|
351
|
+
} catch (error) {
|
|
352
|
+
outcome = 'runtime-error';
|
|
353
|
+
throw error;
|
|
354
|
+
} finally {
|
|
355
|
+
if (outcome !== null) {
|
|
356
|
+
self.recordTelemetry(exec, outcome, Date.now() - startedAt);
|
|
357
|
+
}
|
|
225
358
|
}
|
|
226
359
|
};
|
|
227
360
|
|
|
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);
|
|
361
|
+
return new AsyncIterableResult(generator());
|
|
235
362
|
}
|
|
236
363
|
|
|
237
364
|
async connection(): Promise<RuntimeConnection> {
|
|
238
|
-
const
|
|
365
|
+
const driverConn = await this.driver.acquireConnection();
|
|
239
366
|
const self = this;
|
|
367
|
+
|
|
240
368
|
const wrappedConnection: RuntimeConnection = {
|
|
241
369
|
async transaction(): Promise<RuntimeTransaction> {
|
|
242
|
-
const
|
|
243
|
-
return
|
|
244
|
-
commit: coreTx.commit.bind(coreTx),
|
|
245
|
-
rollback: coreTx.rollback.bind(coreTx),
|
|
246
|
-
execute<Row = Record<string, unknown>>(
|
|
247
|
-
plan: ExecutionPlan<Row> | SqlQueryPlan<Row>,
|
|
248
|
-
): AsyncIterableResult<Row> {
|
|
249
|
-
return self.executeAgainstQueryable(plan, coreTx);
|
|
250
|
-
},
|
|
251
|
-
};
|
|
370
|
+
const driverTx = await driverConn.beginTransaction();
|
|
371
|
+
return self.wrapTransaction(driverTx);
|
|
252
372
|
},
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
373
|
+
async release(): Promise<void> {
|
|
374
|
+
await driverConn.release();
|
|
375
|
+
},
|
|
376
|
+
async destroy(reason?: unknown): Promise<void> {
|
|
377
|
+
await driverConn.destroy(reason);
|
|
378
|
+
},
|
|
379
|
+
execute<Row>(
|
|
380
|
+
plan: (SqlExecutionPlan<unknown> | SqlQueryPlan<unknown>) & { readonly _row?: Row },
|
|
381
|
+
options?: RuntimeExecuteOptions,
|
|
257
382
|
): AsyncIterableResult<Row> {
|
|
258
|
-
return self.executeAgainstQueryable(plan,
|
|
383
|
+
return self.executeAgainstQueryable<Row>(plan, driverConn, options);
|
|
259
384
|
},
|
|
260
385
|
};
|
|
386
|
+
|
|
261
387
|
return wrappedConnection;
|
|
262
388
|
}
|
|
263
389
|
|
|
390
|
+
private wrapTransaction(driverTx: SqlTransaction): RuntimeTransaction {
|
|
391
|
+
const self = this;
|
|
392
|
+
return {
|
|
393
|
+
async commit(): Promise<void> {
|
|
394
|
+
await driverTx.commit();
|
|
395
|
+
},
|
|
396
|
+
async rollback(): Promise<void> {
|
|
397
|
+
await driverTx.rollback();
|
|
398
|
+
},
|
|
399
|
+
execute<Row>(
|
|
400
|
+
plan: (SqlExecutionPlan<unknown> | SqlQueryPlan<unknown>) & { readonly _row?: Row },
|
|
401
|
+
options?: RuntimeExecuteOptions,
|
|
402
|
+
): AsyncIterableResult<Row> {
|
|
403
|
+
return self.executeAgainstQueryable<Row>(plan, driverTx, options);
|
|
404
|
+
},
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
264
408
|
telemetry(): RuntimeTelemetryEvent | null {
|
|
265
|
-
return this.
|
|
409
|
+
return this._telemetry;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
async close(): Promise<void> {
|
|
413
|
+
await this.driver.close();
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
private ensureCodecRegistryValidated(): void {
|
|
417
|
+
if (!this.codecRegistryValidated) {
|
|
418
|
+
validateCodecRegistryCompleteness(this.codecDescriptors, this.contract);
|
|
419
|
+
this.codecRegistryValidated = true;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
private async verifyMarker(): Promise<void> {
|
|
424
|
+
if (this.verify.mode === 'always') {
|
|
425
|
+
this.verified = false;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (this.verified) {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const readStatement = this.familyAdapter.markerReader.readMarkerStatement();
|
|
433
|
+
const result = await this.driver.query(readStatement.sql, readStatement.params);
|
|
434
|
+
|
|
435
|
+
if (result.rows.length === 0) {
|
|
436
|
+
if (this.verify.requireMarker) {
|
|
437
|
+
throw runtimeError('CONTRACT.MARKER_MISSING', 'Contract marker not found in database');
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
this.verified = true;
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const marker = this.familyAdapter.markerReader.parseMarkerRow(result.rows[0]);
|
|
445
|
+
|
|
446
|
+
const contract = this.contract as {
|
|
447
|
+
storage: { storageHash: string };
|
|
448
|
+
execution?: { executionHash?: string | null };
|
|
449
|
+
profileHash?: string | null;
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
if (marker.storageHash !== contract.storage.storageHash) {
|
|
453
|
+
throw runtimeError(
|
|
454
|
+
'CONTRACT.MARKER_MISMATCH',
|
|
455
|
+
'Database storage hash does not match contract',
|
|
456
|
+
{
|
|
457
|
+
expected: contract.storage.storageHash,
|
|
458
|
+
actual: marker.storageHash,
|
|
459
|
+
},
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const expectedProfile = contract.profileHash ?? null;
|
|
464
|
+
if (expectedProfile !== null && marker.profileHash !== expectedProfile) {
|
|
465
|
+
throw runtimeError(
|
|
466
|
+
'CONTRACT.MARKER_MISMATCH',
|
|
467
|
+
'Database profile hash does not match contract',
|
|
468
|
+
{
|
|
469
|
+
expectedProfile,
|
|
470
|
+
actualProfile: marker.profileHash,
|
|
471
|
+
},
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
this.verified = true;
|
|
476
|
+
this.startupVerified = true;
|
|
266
477
|
}
|
|
267
478
|
|
|
268
|
-
|
|
269
|
-
|
|
479
|
+
private recordTelemetry(
|
|
480
|
+
plan: SqlExecutionPlan,
|
|
481
|
+
outcome: TelemetryOutcome,
|
|
482
|
+
durationMs?: number,
|
|
483
|
+
): void {
|
|
484
|
+
const contract = this.contract as { target: string };
|
|
485
|
+
this._telemetry = Object.freeze({
|
|
486
|
+
lane: plan.meta.lane,
|
|
487
|
+
target: contract.target,
|
|
488
|
+
fingerprint: computeSqlFingerprint(plan.sql),
|
|
489
|
+
outcome,
|
|
490
|
+
...(durationMs !== undefined ? { durationMs } : {}),
|
|
491
|
+
});
|
|
270
492
|
}
|
|
271
493
|
}
|
|
272
494
|
|
|
@@ -290,13 +512,14 @@ export async function withTransaction<R>(
|
|
|
290
512
|
get invalidated() {
|
|
291
513
|
return invalidated;
|
|
292
514
|
},
|
|
293
|
-
execute<Row
|
|
294
|
-
plan:
|
|
515
|
+
execute<Row>(
|
|
516
|
+
plan: (SqlExecutionPlan<unknown> | SqlQueryPlan<unknown>) & { readonly _row?: Row },
|
|
517
|
+
options?: RuntimeExecuteOptions,
|
|
295
518
|
): AsyncIterableResult<Row> {
|
|
296
519
|
if (invalidated) {
|
|
297
520
|
throw transactionClosedError();
|
|
298
521
|
}
|
|
299
|
-
const inner = transaction.execute(plan);
|
|
522
|
+
const inner = transaction.execute(plan, options);
|
|
300
523
|
const guarded = async function* (): AsyncGenerator<Row, void, unknown> {
|
|
301
524
|
for await (const row of inner) {
|
|
302
525
|
if (invalidated) {
|