@prisma-next/runtime-executor 0.3.0-dev.158 → 0.3.0-dev.159
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/dist/index.d.mts +27 -68
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +16 -106
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -4
- package/src/exports/index.ts +5 -6
- package/src/middleware/types.ts +29 -0
- package/src/runtime-core.ts +47 -46
- package/src/async-iterable-result.ts +0 -100
- package/src/errors.ts +0 -39
- package/src/plugins/types.ts +0 -42
package/dist/index.d.mts
CHANGED
|
@@ -1,43 +1,6 @@
|
|
|
1
|
+
import { AfterExecuteResult, AsyncIterableResult, AsyncIterableResult as AsyncIterableResult$1, RuntimeErrorEnvelope, RuntimeExecutor, RuntimeLog, RuntimeMiddleware, RuntimeMiddlewareContext, runtimeError } from "@prisma-next/framework-components/runtime";
|
|
1
2
|
import { ContractMarkerRecord, ExecutionPlan } from "@prisma-next/contract/types";
|
|
2
3
|
|
|
3
|
-
//#region src/async-iterable-result.d.ts
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Custom async iterable result that extends AsyncIterable with a toArray() method.
|
|
7
|
-
* This provides a convenient way to collect all results from an async iterator.
|
|
8
|
-
*/
|
|
9
|
-
declare class AsyncIterableResult<Row> implements AsyncIterable<Row>, PromiseLike<Row[]> {
|
|
10
|
-
private readonly generator;
|
|
11
|
-
private consumed;
|
|
12
|
-
private consumedBy;
|
|
13
|
-
private bufferedArrayPromise;
|
|
14
|
-
constructor(generator: AsyncGenerator<Row, void, unknown>);
|
|
15
|
-
[Symbol.asyncIterator](): AsyncIterator<Row>;
|
|
16
|
-
/**
|
|
17
|
-
* Collects all values from the async iterator into an array.
|
|
18
|
-
* Once called, the iterator is consumed and cannot be reused.
|
|
19
|
-
*/
|
|
20
|
-
toArray(): Promise<Row[]>;
|
|
21
|
-
/**
|
|
22
|
-
* Returns the first row, or null if the result set is empty.
|
|
23
|
-
*/
|
|
24
|
-
first(): Promise<Row | null>;
|
|
25
|
-
/**
|
|
26
|
-
* Returns the first row, or throws if the result set is empty.
|
|
27
|
-
*/
|
|
28
|
-
firstOrThrow(): Promise<Row>;
|
|
29
|
-
then<TResult1 = Row[], TResult2 = never>(onfulfilled?: ((value: Row[]) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | undefined | null): PromiseLike<TResult1 | TResult2>;
|
|
30
|
-
}
|
|
31
|
-
//#endregion
|
|
32
|
-
//#region src/errors.d.ts
|
|
33
|
-
interface RuntimeErrorEnvelope extends Error {
|
|
34
|
-
readonly code: string;
|
|
35
|
-
readonly category: 'PLAN' | 'CONTRACT' | 'LINT' | 'BUDGET' | 'RUNTIME';
|
|
36
|
-
readonly severity: 'error';
|
|
37
|
-
readonly details?: Record<string, unknown>;
|
|
38
|
-
}
|
|
39
|
-
declare function runtimeError(code: string, message: string, details?: Record<string, unknown>): RuntimeErrorEnvelope;
|
|
40
|
-
//#endregion
|
|
41
4
|
//#region src/fingerprint.d.ts
|
|
42
5
|
declare function computeSqlFingerprint(sql: string): string;
|
|
43
6
|
//#endregion
|
|
@@ -72,31 +35,15 @@ declare function evaluateRawGuardrails(plan: ExecutionPlan, config?: RawGuardrai
|
|
|
72
35
|
//#region src/marker.d.ts
|
|
73
36
|
declare function parseContractMarkerRow(row: unknown): ContractMarkerRecord;
|
|
74
37
|
//#endregion
|
|
75
|
-
//#region src/
|
|
38
|
+
//#region src/middleware/types.d.ts
|
|
76
39
|
type Severity = 'error' | 'warn' | 'info';
|
|
77
|
-
interface
|
|
78
|
-
info(event: unknown): void;
|
|
79
|
-
warn(event: unknown): void;
|
|
80
|
-
error(event: unknown): void;
|
|
81
|
-
}
|
|
82
|
-
interface PluginContext<TContract = unknown, TAdapter = unknown, TDriver = unknown> {
|
|
40
|
+
interface MiddlewareContext<TContract = unknown> extends RuntimeMiddlewareContext {
|
|
83
41
|
readonly contract: TContract;
|
|
84
|
-
readonly adapter: TAdapter;
|
|
85
|
-
readonly driver: TDriver;
|
|
86
|
-
readonly mode: 'strict' | 'permissive';
|
|
87
|
-
readonly now: () => number;
|
|
88
|
-
readonly log: Log;
|
|
89
42
|
}
|
|
90
|
-
interface
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
interface Plugin<TContract = unknown, TAdapter = unknown, TDriver = unknown> {
|
|
96
|
-
readonly name: string;
|
|
97
|
-
beforeExecute?(plan: ExecutionPlan, ctx: PluginContext<TContract, TAdapter, TDriver>): Promise<void>;
|
|
98
|
-
onRow?(row: Record<string, unknown>, plan: ExecutionPlan, ctx: PluginContext<TContract, TAdapter, TDriver>): Promise<void>;
|
|
99
|
-
afterExecute?(plan: ExecutionPlan, result: AfterExecuteResult, ctx: PluginContext<TContract, TAdapter, TDriver>): Promise<void>;
|
|
43
|
+
interface Middleware<TContract = unknown> extends RuntimeMiddleware {
|
|
44
|
+
beforeExecute?(plan: ExecutionPlan, ctx: MiddlewareContext<TContract>): Promise<void>;
|
|
45
|
+
onRow?(row: Record<string, unknown>, plan: ExecutionPlan, ctx: MiddlewareContext<TContract>): Promise<void>;
|
|
46
|
+
afterExecute?(plan: ExecutionPlan, result: AfterExecuteResult, ctx: MiddlewareContext<TContract>): Promise<void>;
|
|
100
47
|
}
|
|
101
48
|
//#endregion
|
|
102
49
|
//#region src/runtime-spi.d.ts
|
|
@@ -126,17 +73,16 @@ interface RuntimeTelemetryEvent {
|
|
|
126
73
|
readonly outcome: TelemetryOutcome;
|
|
127
74
|
readonly durationMs?: number;
|
|
128
75
|
}
|
|
129
|
-
interface RuntimeCoreOptions<TContract = unknown,
|
|
76
|
+
interface RuntimeCoreOptions<TContract = unknown, TDriver = unknown> {
|
|
130
77
|
readonly familyAdapter: RuntimeFamilyAdapter<TContract>;
|
|
131
78
|
readonly driver: TDriver;
|
|
132
79
|
readonly verify: RuntimeVerifyOptions;
|
|
133
|
-
readonly
|
|
80
|
+
readonly middleware?: readonly Middleware<TContract>[];
|
|
134
81
|
readonly mode?: 'strict' | 'permissive';
|
|
135
|
-
readonly log?:
|
|
82
|
+
readonly log?: RuntimeLog;
|
|
136
83
|
}
|
|
137
|
-
interface RuntimeCore<TContract = unknown,
|
|
84
|
+
interface RuntimeCore<TContract = unknown, TDriver = unknown> extends RuntimeQueryable, RuntimeExecutor<ExecutionPlan> {
|
|
138
85
|
readonly _typeContract?: TContract;
|
|
139
|
-
readonly _typeAdapter?: TAdapter;
|
|
140
86
|
readonly _typeDriver?: TDriver;
|
|
141
87
|
connection(): Promise<RuntimeConnection>;
|
|
142
88
|
telemetry(): RuntimeTelemetryEvent | null;
|
|
@@ -150,10 +96,23 @@ interface RuntimeTransaction extends RuntimeQueryable {
|
|
|
150
96
|
commit(): Promise<void>;
|
|
151
97
|
rollback(): Promise<void>;
|
|
152
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Shared query execution trait for anything that can run an ExecutionPlan:
|
|
101
|
+
* RuntimeCore, RuntimeConnection, and RuntimeTransaction. This is a
|
|
102
|
+
* SQL-domain internal mixin — it is NOT the cross-family SPI.
|
|
103
|
+
*
|
|
104
|
+
* For the cross-family SPI, see RuntimeExecutor in framework-components.
|
|
105
|
+
* RuntimeCore nominally extends both this interface and RuntimeExecutor.
|
|
106
|
+
*
|
|
107
|
+
* The execute signature uses the same `_row` phantom intersection as
|
|
108
|
+
* RuntimeExecutor so that RuntimeCore can extend both without conflicts.
|
|
109
|
+
*/
|
|
153
110
|
interface RuntimeQueryable {
|
|
154
|
-
execute<Row
|
|
111
|
+
execute<Row>(plan: ExecutionPlan & {
|
|
112
|
+
readonly _row?: Row;
|
|
113
|
+
}): AsyncIterableResult$1<Row>;
|
|
155
114
|
}
|
|
156
|
-
declare function createRuntimeCore<TContract = unknown,
|
|
115
|
+
declare function createRuntimeCore<TContract = unknown, TDriver = unknown>(options: RuntimeCoreOptions<TContract, TDriver>): RuntimeCore<TContract, TDriver>;
|
|
157
116
|
//#endregion
|
|
158
|
-
export { AfterExecuteResult, AsyncIterableResult, BudgetFinding, LintFinding, Log, MarkerReader, MarkerStatement,
|
|
117
|
+
export { type AfterExecuteResult, AsyncIterableResult, type BudgetFinding, type LintFinding, type RuntimeLog as Log, type MarkerReader, type MarkerStatement, type Middleware, type MiddlewareContext, type RawGuardrailResult, type RuntimeConnection, type RuntimeCore, type RuntimeCoreOptions, type RuntimeErrorEnvelope, type RuntimeFamilyAdapter, type RuntimeQueryable, type RuntimeTelemetryEvent, type RuntimeTransaction, type RuntimeVerifyOptions, type Severity, type TelemetryOutcome, computeSqlFingerprint, createRuntimeCore, evaluateRawGuardrails, parseContractMarkerRow, runtimeError };
|
|
159
118
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/fingerprint.ts","../src/guardrails/raw.ts","../src/marker.ts","../src/middleware/types.ts","../src/runtime-spi.ts","../src/runtime-core.ts"],"sourcesContent":[],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,94 +1,7 @@
|
|
|
1
|
+
import { AsyncIterableResult, AsyncIterableResult as AsyncIterableResult$1, runtimeError, runtimeError as runtimeError$1 } from "@prisma-next/framework-components/runtime";
|
|
1
2
|
import { createHash } from "node:crypto";
|
|
2
3
|
import { type } from "arktype";
|
|
3
4
|
|
|
4
|
-
//#region src/errors.ts
|
|
5
|
-
function runtimeError(code, message, details) {
|
|
6
|
-
const error = new Error(message);
|
|
7
|
-
Object.defineProperty(error, "name", {
|
|
8
|
-
value: "RuntimeError",
|
|
9
|
-
configurable: true
|
|
10
|
-
});
|
|
11
|
-
return Object.assign(error, {
|
|
12
|
-
code,
|
|
13
|
-
category: resolveCategory(code),
|
|
14
|
-
severity: "error",
|
|
15
|
-
message,
|
|
16
|
-
details
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
function resolveCategory(code) {
|
|
20
|
-
const prefix = code.split(".")[0] ?? "RUNTIME";
|
|
21
|
-
switch (prefix) {
|
|
22
|
-
case "PLAN":
|
|
23
|
-
case "CONTRACT":
|
|
24
|
-
case "LINT":
|
|
25
|
-
case "BUDGET": return prefix;
|
|
26
|
-
default: return "RUNTIME";
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
//#endregion
|
|
31
|
-
//#region src/async-iterable-result.ts
|
|
32
|
-
/**
|
|
33
|
-
* Custom async iterable result that extends AsyncIterable with a toArray() method.
|
|
34
|
-
* This provides a convenient way to collect all results from an async iterator.
|
|
35
|
-
*/
|
|
36
|
-
var AsyncIterableResult = class {
|
|
37
|
-
generator;
|
|
38
|
-
consumed = false;
|
|
39
|
-
consumedBy;
|
|
40
|
-
bufferedArrayPromise;
|
|
41
|
-
constructor(generator) {
|
|
42
|
-
this.generator = generator;
|
|
43
|
-
}
|
|
44
|
-
[Symbol.asyncIterator]() {
|
|
45
|
-
if (this.consumed) throw runtimeError("RUNTIME.ITERATOR_CONSUMED", `AsyncIterableResult iterator has already been consumed via ${this.consumedBy === "bufferedArray" ? "toArray()/then()" : "for-await loop"}. Each AsyncIterableResult can only be iterated once.`, {
|
|
46
|
-
consumedBy: this.consumedBy,
|
|
47
|
-
suggestion: this.consumedBy === "bufferedArray" ? "If you need to iterate multiple times, store the results from toArray() in a variable and reuse that." : "If you need to iterate multiple times, use toArray() to collect all results first."
|
|
48
|
-
});
|
|
49
|
-
this.consumed = true;
|
|
50
|
-
this.consumedBy = "iterator";
|
|
51
|
-
return this.generator;
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Collects all values from the async iterator into an array.
|
|
55
|
-
* Once called, the iterator is consumed and cannot be reused.
|
|
56
|
-
*/
|
|
57
|
-
toArray() {
|
|
58
|
-
if (this.consumedBy === "iterator") return Promise.reject(runtimeError("RUNTIME.ITERATOR_CONSUMED", "AsyncIterableResult iterator has already been consumed via for-await loop. Each AsyncIterableResult can only be iterated once.", {
|
|
59
|
-
consumedBy: this.consumedBy,
|
|
60
|
-
suggestion: "The iterator was already consumed by a for-await loop. Use toArray() or await the result before iterating."
|
|
61
|
-
}));
|
|
62
|
-
if (this.bufferedArrayPromise) return this.bufferedArrayPromise;
|
|
63
|
-
this.consumed = true;
|
|
64
|
-
this.consumedBy = "bufferedArray";
|
|
65
|
-
this.bufferedArrayPromise = (async () => {
|
|
66
|
-
const out = [];
|
|
67
|
-
for await (const item of this.generator) out.push(item);
|
|
68
|
-
return out;
|
|
69
|
-
})();
|
|
70
|
-
return this.bufferedArrayPromise;
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Returns the first row, or null if the result set is empty.
|
|
74
|
-
*/
|
|
75
|
-
async first() {
|
|
76
|
-
return (await this.toArray())[0] ?? null;
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Returns the first row, or throws if the result set is empty.
|
|
80
|
-
*/
|
|
81
|
-
async firstOrThrow() {
|
|
82
|
-
const row = await this.first();
|
|
83
|
-
if (row === null) throw runtimeError("RUNTIME.NO_ROWS", "Expected at least one row, but none were returned", {});
|
|
84
|
-
return row;
|
|
85
|
-
}
|
|
86
|
-
then(onfulfilled, onrejected) {
|
|
87
|
-
return this.toArray().then(onfulfilled, onrejected);
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
//#endregion
|
|
92
5
|
//#region src/fingerprint.ts
|
|
93
6
|
const STRING_LITERAL_REGEX = /'(?:''|[^'])*'/g;
|
|
94
7
|
const NUMERIC_LITERAL_REGEX = /\b\d+(?:\.\d+)?\b/g;
|
|
@@ -235,15 +148,14 @@ function parseContractMarkerRow(row) {
|
|
|
235
148
|
//#region src/runtime-core.ts
|
|
236
149
|
var RuntimeCoreImpl = class {
|
|
237
150
|
_typeContract;
|
|
238
|
-
_typeAdapter;
|
|
239
151
|
_typeDriver;
|
|
240
152
|
contract;
|
|
241
153
|
familyAdapter;
|
|
242
154
|
driver;
|
|
243
|
-
|
|
155
|
+
middleware;
|
|
244
156
|
mode;
|
|
245
157
|
verify;
|
|
246
|
-
|
|
158
|
+
middlewareContext;
|
|
247
159
|
verified;
|
|
248
160
|
startupVerified;
|
|
249
161
|
_telemetry;
|
|
@@ -252,16 +164,14 @@ var RuntimeCoreImpl = class {
|
|
|
252
164
|
this.contract = familyAdapter.contract;
|
|
253
165
|
this.familyAdapter = familyAdapter;
|
|
254
166
|
this.driver = driver;
|
|
255
|
-
this.
|
|
167
|
+
this.middleware = options.middleware ?? [];
|
|
256
168
|
this.mode = options.mode ?? "strict";
|
|
257
169
|
this.verify = options.verify;
|
|
258
170
|
this.verified = options.verify.mode === "startup" ? false : options.verify.mode === "always";
|
|
259
171
|
this.startupVerified = false;
|
|
260
172
|
this._telemetry = null;
|
|
261
|
-
this.
|
|
173
|
+
this.middlewareContext = {
|
|
262
174
|
contract: this.contract,
|
|
263
|
-
adapter: options.familyAdapter,
|
|
264
|
-
driver: this.driver,
|
|
265
175
|
mode: this.mode,
|
|
266
176
|
now: () => Date.now(),
|
|
267
177
|
log: options.log ?? {
|
|
@@ -277,18 +187,18 @@ var RuntimeCoreImpl = class {
|
|
|
277
187
|
const readStatement = this.familyAdapter.markerReader.readMarkerStatement();
|
|
278
188
|
const result = await this.driver.query(readStatement.sql, readStatement.params);
|
|
279
189
|
if (result.rows.length === 0) {
|
|
280
|
-
if (this.verify.requireMarker) throw runtimeError("CONTRACT.MARKER_MISSING", "Contract marker not found in database");
|
|
190
|
+
if (this.verify.requireMarker) throw runtimeError$1("CONTRACT.MARKER_MISSING", "Contract marker not found in database");
|
|
281
191
|
this.verified = true;
|
|
282
192
|
return;
|
|
283
193
|
}
|
|
284
194
|
const marker = parseContractMarkerRow(result.rows[0]);
|
|
285
195
|
const contract = this.contract;
|
|
286
|
-
if (marker.storageHash !== contract.storage.storageHash) throw runtimeError("CONTRACT.MARKER_MISMATCH", "Database storage hash does not match contract", {
|
|
196
|
+
if (marker.storageHash !== contract.storage.storageHash) throw runtimeError$1("CONTRACT.MARKER_MISMATCH", "Database storage hash does not match contract", {
|
|
287
197
|
expected: contract.storage.storageHash,
|
|
288
198
|
actual: marker.storageHash
|
|
289
199
|
});
|
|
290
200
|
const expectedProfile = contract.profileHash ?? null;
|
|
291
|
-
if (expectedProfile !== null && marker.profileHash !== expectedProfile) throw runtimeError("CONTRACT.MARKER_MISMATCH", "Database profile hash does not match contract", {
|
|
201
|
+
if (expectedProfile !== null && marker.profileHash !== expectedProfile) throw runtimeError$1("CONTRACT.MARKER_MISMATCH", "Database profile hash does not match contract", {
|
|
292
202
|
expectedProfile,
|
|
293
203
|
actualProfile: marker.profileHash
|
|
294
204
|
});
|
|
@@ -356,13 +266,13 @@ var RuntimeCoreImpl = class {
|
|
|
356
266
|
if (self.verify.mode === "onFirstUse") await self.verifyPlanIfNeeded(plan);
|
|
357
267
|
try {
|
|
358
268
|
if (self.verify.mode === "always") await self.verifyPlanIfNeeded(plan);
|
|
359
|
-
for (const
|
|
269
|
+
for (const mw of self.middleware) if (mw.beforeExecute) await mw.beforeExecute(plan, self.middlewareContext);
|
|
360
270
|
const encodedParams = plan.params;
|
|
361
271
|
for await (const row of queryable.execute({
|
|
362
272
|
sql: plan.sql,
|
|
363
273
|
params: encodedParams
|
|
364
274
|
})) {
|
|
365
|
-
for (const
|
|
275
|
+
for (const mw of self.middleware) if (mw.onRow) await mw.onRow(row, plan, self.middlewareContext);
|
|
366
276
|
rowCount++;
|
|
367
277
|
yield row;
|
|
368
278
|
}
|
|
@@ -371,23 +281,23 @@ var RuntimeCoreImpl = class {
|
|
|
371
281
|
} catch (error) {
|
|
372
282
|
if (self._telemetry === null) self.recordTelemetry(plan, "runtime-error", Date.now() - startedAt);
|
|
373
283
|
const latencyMs$1 = Date.now() - startedAt;
|
|
374
|
-
for (const
|
|
375
|
-
await
|
|
284
|
+
for (const mw of self.middleware) if (mw.afterExecute) try {
|
|
285
|
+
await mw.afterExecute(plan, {
|
|
376
286
|
rowCount,
|
|
377
287
|
latencyMs: latencyMs$1,
|
|
378
288
|
completed
|
|
379
|
-
}, self.
|
|
289
|
+
}, self.middlewareContext);
|
|
380
290
|
} catch {}
|
|
381
291
|
throw error;
|
|
382
292
|
}
|
|
383
293
|
const latencyMs = Date.now() - startedAt;
|
|
384
|
-
for (const
|
|
294
|
+
for (const mw of self.middleware) if (mw.afterExecute) await mw.afterExecute(plan, {
|
|
385
295
|
rowCount,
|
|
386
296
|
latencyMs,
|
|
387
297
|
completed
|
|
388
|
-
}, self.
|
|
298
|
+
}, self.middlewareContext);
|
|
389
299
|
};
|
|
390
|
-
return new AsyncIterableResult(iterator(this));
|
|
300
|
+
return new AsyncIterableResult$1(iterator(this));
|
|
391
301
|
}
|
|
392
302
|
};
|
|
393
303
|
function createRuntimeCore(options) {
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["out: Row[]","lints: LintFinding[]","budgets: BudgetFinding[]","parsed: unknown","#executeWith","latencyMs"],"sources":["../src/errors.ts","../src/async-iterable-result.ts","../src/fingerprint.ts","../src/guardrails/raw.ts","../src/marker.ts","../src/runtime-core.ts"],"sourcesContent":["export interface RuntimeErrorEnvelope extends Error {\n readonly code: string;\n readonly category: 'PLAN' | 'CONTRACT' | 'LINT' | 'BUDGET' | 'RUNTIME';\n readonly severity: 'error';\n readonly details?: Record<string, unknown>;\n}\n\nexport function runtimeError(\n code: string,\n message: string,\n details?: Record<string, unknown>,\n): RuntimeErrorEnvelope {\n const error = new Error(message) as RuntimeErrorEnvelope;\n Object.defineProperty(error, 'name', {\n value: 'RuntimeError',\n configurable: true,\n });\n\n return Object.assign(error, {\n code,\n category: resolveCategory(code),\n severity: 'error' as const,\n message,\n details,\n });\n}\n\nfunction resolveCategory(code: string): RuntimeErrorEnvelope['category'] {\n const prefix = code.split('.')[0] ?? 'RUNTIME';\n switch (prefix) {\n case 'PLAN':\n case 'CONTRACT':\n case 'LINT':\n case 'BUDGET':\n return prefix;\n default:\n return 'RUNTIME';\n }\n}\n","import { runtimeError } from './errors';\n\n/**\n * Custom async iterable result that extends AsyncIterable with a toArray() method.\n * This provides a convenient way to collect all results from an async iterator.\n */\nexport class AsyncIterableResult<Row> implements AsyncIterable<Row>, PromiseLike<Row[]> {\n private readonly generator: AsyncGenerator<Row, void, unknown>;\n private consumed = false;\n private consumedBy: 'bufferedArray' | 'iterator' | undefined;\n private bufferedArrayPromise: Promise<Row[]> | undefined;\n\n constructor(generator: AsyncGenerator<Row, void, unknown>) {\n this.generator = generator;\n }\n\n [Symbol.asyncIterator](): AsyncIterator<Row> {\n if (this.consumed) {\n throw runtimeError(\n 'RUNTIME.ITERATOR_CONSUMED',\n `AsyncIterableResult iterator has already been consumed via ${this.consumedBy === 'bufferedArray' ? 'toArray()/then()' : 'for-await loop'}. Each AsyncIterableResult can only be iterated once.`,\n {\n consumedBy: this.consumedBy,\n suggestion:\n this.consumedBy === 'bufferedArray'\n ? 'If you need to iterate multiple times, store the results from toArray() in a variable and reuse that.'\n : 'If you need to iterate multiple times, use toArray() to collect all results first.',\n },\n );\n }\n this.consumed = true;\n this.consumedBy = 'iterator';\n return this.generator;\n }\n\n /**\n * Collects all values from the async iterator into an array.\n * Once called, the iterator is consumed and cannot be reused.\n */\n toArray(): Promise<Row[]> {\n if (this.consumedBy === 'iterator') {\n return Promise.reject(\n runtimeError(\n 'RUNTIME.ITERATOR_CONSUMED',\n 'AsyncIterableResult iterator has already been consumed via for-await loop. Each AsyncIterableResult can only be iterated once.',\n {\n consumedBy: this.consumedBy,\n suggestion:\n 'The iterator was already consumed by a for-await loop. Use toArray() or await the result before iterating.',\n },\n ),\n );\n }\n\n if (this.bufferedArrayPromise) {\n return this.bufferedArrayPromise;\n }\n\n this.consumed = true;\n this.consumedBy = 'bufferedArray';\n this.bufferedArrayPromise = (async () => {\n const out: Row[] = [];\n for await (const item of this.generator) {\n out.push(item);\n }\n return out;\n })();\n return this.bufferedArrayPromise;\n }\n\n /**\n * Returns the first row, or null if the result set is empty.\n */\n async first(): Promise<Row | null> {\n const rows = await this.toArray();\n return rows[0] ?? null;\n }\n\n /**\n * Returns the first row, or throws if the result set is empty.\n */\n async firstOrThrow(): Promise<Row> {\n const row = await this.first();\n if (row === null)\n throw runtimeError(\n 'RUNTIME.NO_ROWS',\n 'Expected at least one row, but none were returned',\n {},\n );\n return row;\n }\n\n // biome-ignore lint/suspicious/noThenProperty: PromiseLike implementation is intentional for await support.\n then<TResult1 = Row[], TResult2 = never>(\n onfulfilled?: ((value: Row[]) => TResult1 | PromiseLike<TResult1>) | undefined | null,\n onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | undefined | null,\n ): PromiseLike<TResult1 | TResult2> {\n return this.toArray().then(onfulfilled, onrejected);\n }\n}\n","import { createHash } from 'node:crypto';\n\nconst STRING_LITERAL_REGEX = /'(?:''|[^'])*'/g;\nconst NUMERIC_LITERAL_REGEX = /\\b\\d+(?:\\.\\d+)?\\b/g;\nconst WHITESPACE_REGEX = /\\s+/g;\n\nexport function computeSqlFingerprint(sql: string): string {\n const withoutStrings = sql.replace(STRING_LITERAL_REGEX, '?');\n const withoutNumbers = withoutStrings.replace(NUMERIC_LITERAL_REGEX, '?');\n const normalized = withoutNumbers.replace(WHITESPACE_REGEX, ' ').trim().toLowerCase();\n\n const hash = createHash('sha256').update(normalized).digest('hex');\n return `sha256:${hash}`;\n}\n","import type { ExecutionPlan, PlanMeta, PlanRefs } from '@prisma-next/contract/types';\n\nexport type LintSeverity = 'error' | 'warn';\nexport type BudgetSeverity = 'error' | 'warn';\n\nexport interface LintFinding {\n readonly code: `LINT.${string}`;\n readonly severity: LintSeverity;\n readonly message: string;\n readonly details?: Record<string, unknown>;\n}\n\nexport interface BudgetFinding {\n readonly code: `BUDGET.${string}`;\n readonly severity: BudgetSeverity;\n readonly message: string;\n readonly details?: Record<string, unknown>;\n}\n\nexport interface RawGuardrailConfig {\n readonly budgets?: {\n readonly unboundedSelectSeverity?: BudgetSeverity;\n readonly estimatedRows?: number;\n };\n}\n\nexport interface RawGuardrailResult {\n readonly lints: LintFinding[];\n readonly budgets: BudgetFinding[];\n readonly statement: 'select' | 'mutation' | 'other';\n}\n\nconst SELECT_STAR_REGEX = /select\\s+\\*/i;\nconst LIMIT_REGEX = /\\blimit\\b/i;\nconst MUTATION_PREFIX_REGEX = /^(insert|update|delete|create|alter|drop|truncate)\\b/i;\n\nconst READ_ONLY_INTENTS = new Set(['read', 'report', 'readonly']);\n\nexport function evaluateRawGuardrails(\n plan: ExecutionPlan,\n config?: RawGuardrailConfig,\n): RawGuardrailResult {\n const lints: LintFinding[] = [];\n const budgets: BudgetFinding[] = [];\n\n const normalized = normalizeWhitespace(plan.sql);\n const statementType = classifyStatement(normalized);\n\n if (statementType === 'select') {\n if (SELECT_STAR_REGEX.test(normalized)) {\n lints.push(\n createLint('LINT.SELECT_STAR', 'error', 'Raw SQL plan selects all columns via *', {\n sql: snippet(plan.sql),\n }),\n );\n }\n\n if (!LIMIT_REGEX.test(normalized)) {\n const severity = config?.budgets?.unboundedSelectSeverity ?? 'error';\n lints.push(\n createLint('LINT.NO_LIMIT', 'warn', 'Raw SQL plan omits LIMIT clause', {\n sql: snippet(plan.sql),\n }),\n );\n\n budgets.push(\n createBudget(\n 'BUDGET.ROWS_EXCEEDED',\n severity,\n 'Raw SQL plan is unbounded and may exceed row budget',\n {\n sql: snippet(plan.sql),\n ...(config?.budgets?.estimatedRows !== undefined\n ? { estimatedRows: config.budgets.estimatedRows }\n : {}),\n },\n ),\n );\n }\n }\n\n if (isMutationStatement(statementType) && isReadOnlyIntent(plan.meta)) {\n lints.push(\n createLint(\n 'LINT.READ_ONLY_MUTATION',\n 'error',\n 'Raw SQL plan mutates data despite read-only intent',\n {\n sql: snippet(plan.sql),\n intent: plan.meta.annotations?.['intent'],\n },\n ),\n );\n }\n\n const refs = plan.meta.refs;\n if (refs) {\n evaluateIndexCoverage(refs, lints);\n }\n\n return { lints, budgets, statement: statementType };\n}\n\nfunction evaluateIndexCoverage(refs: PlanRefs, lints: LintFinding[]) {\n const predicateColumns = refs.columns ?? [];\n if (predicateColumns.length === 0) {\n return;\n }\n\n const indexes = refs.indexes ?? [];\n\n if (indexes.length === 0) {\n lints.push(\n createLint(\n 'LINT.UNINDEXED_PREDICATE',\n 'warn',\n 'Raw SQL plan predicates lack supporting indexes',\n {\n predicates: predicateColumns,\n },\n ),\n );\n return;\n }\n\n const hasSupportingIndex = predicateColumns.every((column) =>\n indexes.some(\n (index) =>\n index.table === column.table &&\n index.columns.some((col) => col.toLowerCase() === column.column.toLowerCase()),\n ),\n );\n\n if (!hasSupportingIndex) {\n lints.push(\n createLint(\n 'LINT.UNINDEXED_PREDICATE',\n 'warn',\n 'Raw SQL plan predicates lack supporting indexes',\n {\n predicates: predicateColumns,\n },\n ),\n );\n }\n}\n\nfunction classifyStatement(sql: string): 'select' | 'mutation' | 'other' {\n const trimmed = sql.trim();\n const lower = trimmed.toLowerCase();\n\n if (lower.startsWith('with')) {\n if (lower.includes('select')) {\n return 'select';\n }\n }\n\n if (lower.startsWith('select')) {\n return 'select';\n }\n\n if (MUTATION_PREFIX_REGEX.test(trimmed)) {\n return 'mutation';\n }\n\n return 'other';\n}\n\nfunction isMutationStatement(statement: 'select' | 'mutation' | 'other'): boolean {\n return statement === 'mutation';\n}\n\nfunction isReadOnlyIntent(meta: PlanMeta): boolean {\n const annotations = meta.annotations as { intent?: string } | undefined;\n const intent =\n typeof annotations?.intent === 'string' ? annotations.intent.toLowerCase() : undefined;\n return intent !== undefined && READ_ONLY_INTENTS.has(intent);\n}\n\nfunction normalizeWhitespace(value: string): string {\n return value.replace(/\\s+/g, ' ').trim();\n}\n\nfunction snippet(sql: string): string {\n return normalizeWhitespace(sql).slice(0, 200);\n}\n\nfunction createLint(\n code: LintFinding['code'],\n severity: LintFinding['severity'],\n message: string,\n details?: Record<string, unknown>,\n): LintFinding {\n return { code, severity, message, ...(details ? { details } : {}) };\n}\n\nfunction createBudget(\n code: BudgetFinding['code'],\n severity: BudgetFinding['severity'],\n message: string,\n details?: Record<string, unknown>,\n): BudgetFinding {\n return { code, severity, message, ...(details ? { details } : {}) };\n}\n","import type { ContractMarkerRecord } from '@prisma-next/contract/types';\nimport { type } from 'arktype';\n\nexport interface ContractMarkerRow {\n core_hash: string;\n profile_hash: string;\n contract_json: unknown | null;\n canonical_version: number | null;\n updated_at: Date;\n app_tag: string | null;\n meta: unknown | null;\n}\n\nconst MetaSchema = type({ '[string]': 'unknown' });\n\nfunction parseMeta(meta: unknown): Record<string, unknown> {\n if (meta === null || meta === undefined) {\n return {};\n }\n\n let parsed: unknown;\n if (typeof meta === 'string') {\n try {\n parsed = JSON.parse(meta);\n } catch {\n return {};\n }\n } else {\n parsed = meta;\n }\n\n const result = MetaSchema(parsed);\n if (result instanceof type.errors) {\n return {};\n }\n\n return result as Record<string, unknown>;\n}\n\nconst ContractMarkerRowSchema = type({\n core_hash: 'string',\n profile_hash: 'string',\n 'contract_json?': 'unknown | null',\n 'canonical_version?': 'number | null',\n 'updated_at?': 'Date | string',\n 'app_tag?': 'string | null',\n 'meta?': 'unknown | null',\n});\n\nexport function parseContractMarkerRow(row: unknown): ContractMarkerRecord {\n const result = ContractMarkerRowSchema(row);\n if (result instanceof type.errors) {\n const messages = result.map((p: { message: string }) => p.message).join('; ');\n throw new Error(`Invalid contract marker row: ${messages}`);\n }\n\n const validatedRow = result as {\n core_hash: string;\n profile_hash: string;\n contract_json?: unknown | null;\n canonical_version?: number | null;\n updated_at?: Date | string;\n app_tag?: string | null;\n meta?: unknown | null;\n };\n\n const updatedAt = validatedRow.updated_at\n ? validatedRow.updated_at instanceof Date\n ? validatedRow.updated_at\n : new Date(validatedRow.updated_at)\n : new Date();\n\n return {\n storageHash: validatedRow.core_hash,\n profileHash: validatedRow.profile_hash,\n contractJson: validatedRow.contract_json ?? null,\n canonicalVersion: validatedRow.canonical_version ?? null,\n updatedAt,\n appTag: validatedRow.app_tag ?? null,\n meta: parseMeta(validatedRow.meta),\n };\n}\n","import type { ExecutionPlan } from '@prisma-next/contract/types';\nimport { AsyncIterableResult } from './async-iterable-result';\nimport { runtimeError } from './errors';\nimport { computeSqlFingerprint } from './fingerprint';\nimport { parseContractMarkerRow } from './marker';\nimport type { Log, Plugin, PluginContext } from './plugins/types';\nimport type { RuntimeFamilyAdapter } from './runtime-spi';\n\nexport interface RuntimeVerifyOptions {\n readonly mode: 'onFirstUse' | 'startup' | 'always';\n readonly requireMarker: boolean;\n}\n\nexport type TelemetryOutcome = 'success' | 'runtime-error';\n\nexport interface RuntimeTelemetryEvent {\n readonly lane: string;\n readonly target: string;\n readonly fingerprint: string;\n readonly outcome: TelemetryOutcome;\n readonly durationMs?: number;\n}\n\nexport interface RuntimeCoreOptions<TContract = unknown, TAdapter = unknown, TDriver = unknown> {\n readonly familyAdapter: RuntimeFamilyAdapter<TContract>;\n readonly driver: TDriver;\n readonly verify: RuntimeVerifyOptions;\n readonly plugins?: readonly Plugin<TContract, TAdapter, TDriver>[];\n readonly mode?: 'strict' | 'permissive';\n readonly log?: Log;\n}\n\nexport interface RuntimeCore<TContract = unknown, TAdapter = unknown, TDriver = unknown>\n extends RuntimeQueryable {\n // Type parameters are used in the implementation for type safety\n readonly _typeContract?: TContract;\n readonly _typeAdapter?: TAdapter;\n readonly _typeDriver?: TDriver;\n connection(): Promise<RuntimeConnection>;\n telemetry(): RuntimeTelemetryEvent | null;\n close(): Promise<void>;\n}\n\nexport interface RuntimeConnection extends RuntimeQueryable {\n transaction(): Promise<RuntimeTransaction>;\n release(): Promise<void>;\n}\n\nexport interface RuntimeTransaction extends RuntimeQueryable {\n commit(): Promise<void>;\n rollback(): Promise<void>;\n}\n\nexport interface RuntimeQueryable {\n execute<Row = Record<string, unknown>>(plan: ExecutionPlan<Row>): AsyncIterableResult<Row>;\n}\n\ninterface DriverWithQuery<_TDriver> {\n query(sql: string, params: readonly unknown[]): Promise<{ rows: ReadonlyArray<unknown> }>;\n}\n\ninterface DriverWithConnection<_TDriver> {\n acquireConnection(): Promise<DriverConnection>;\n}\n\nexport interface DriverConnection extends Queryable {\n beginTransaction(): Promise<DriverTransaction>;\n release(): Promise<void>;\n}\n\nexport interface DriverTransaction extends Queryable {\n commit(): Promise<void>;\n rollback(): Promise<void>;\n}\n\nexport interface Queryable {\n execute<Row = Record<string, unknown>>(options: {\n sql: string;\n params: readonly unknown[];\n }): AsyncIterable<Row>;\n}\n\ninterface DriverWithClose<_TDriver> {\n close(): Promise<void>;\n}\n\nclass RuntimeCoreImpl<TContract = unknown, TAdapter = unknown, TDriver = unknown>\n implements RuntimeCore<TContract, TAdapter, TDriver>\n{\n readonly _typeContract?: TContract;\n readonly _typeAdapter?: TAdapter;\n readonly _typeDriver?: TDriver;\n private readonly contract: TContract;\n private readonly familyAdapter: RuntimeFamilyAdapter<TContract>;\n private readonly driver: TDriver;\n private readonly plugins: readonly Plugin<TContract, TAdapter, TDriver>[];\n private readonly mode: 'strict' | 'permissive';\n private readonly verify: RuntimeVerifyOptions;\n private readonly pluginContext: PluginContext<TContract, TAdapter, TDriver>;\n\n private verified: boolean;\n private startupVerified: boolean;\n private _telemetry: RuntimeTelemetryEvent | null;\n\n constructor(options: RuntimeCoreOptions<TContract, TAdapter, TDriver>) {\n const { familyAdapter, driver } = options;\n this.contract = familyAdapter.contract;\n this.familyAdapter = familyAdapter;\n this.driver = driver;\n this.plugins = options.plugins ?? [];\n this.mode = options.mode ?? 'strict';\n this.verify = options.verify;\n this.verified = options.verify.mode === 'startup' ? false : options.verify.mode === 'always';\n this.startupVerified = false;\n this._telemetry = null;\n\n this.pluginContext = {\n contract: this.contract,\n adapter: options.familyAdapter as unknown as TAdapter,\n driver: this.driver,\n mode: this.mode,\n now: () => Date.now(),\n log: options.log ?? {\n info: () => {\n // No-op in MVP - diagnostics stay out of runtime core\n },\n warn: () => {\n // No-op in MVP - diagnostics stay out of runtime core\n },\n error: () => {\n // No-op in MVP - diagnostics stay out of runtime core\n },\n },\n };\n }\n\n private async verifyPlanIfNeeded(_plan: ExecutionPlan): Promise<void> {\n void _plan;\n if (this.verify.mode === 'always') {\n this.verified = false;\n }\n\n if (this.verified) {\n return;\n }\n\n const readStatement = this.familyAdapter.markerReader.readMarkerStatement();\n const driver = this.driver as unknown as DriverWithQuery<TDriver>;\n const result = await driver.query(readStatement.sql, readStatement.params);\n\n if (result.rows.length === 0) {\n if (this.verify.requireMarker) {\n throw runtimeError('CONTRACT.MARKER_MISSING', 'Contract marker not found in database');\n }\n\n this.verified = true;\n return;\n }\n\n const marker = parseContractMarkerRow(result.rows[0]);\n\n const contract = this.contract as {\n storage: { storageHash: string };\n execution?: { executionHash?: string | null };\n profileHash?: string | null;\n };\n if (marker.storageHash !== contract.storage.storageHash) {\n throw runtimeError(\n 'CONTRACT.MARKER_MISMATCH',\n 'Database storage hash does not match contract',\n {\n expected: contract.storage.storageHash,\n actual: marker.storageHash,\n },\n );\n }\n\n const expectedProfile = contract.profileHash ?? null;\n if (expectedProfile !== null && marker.profileHash !== expectedProfile) {\n throw runtimeError(\n 'CONTRACT.MARKER_MISMATCH',\n 'Database profile hash does not match contract',\n {\n expectedProfile,\n actualProfile: marker.profileHash,\n },\n );\n }\n\n this.verified = true;\n this.startupVerified = true;\n }\n\n private validatePlan(plan: ExecutionPlan): void {\n this.familyAdapter.validatePlan(plan, this.contract);\n }\n\n private recordTelemetry(\n plan: ExecutionPlan,\n outcome: TelemetryOutcome,\n durationMs?: number,\n ): void {\n const contract = this.contract as { target: string };\n this._telemetry = Object.freeze({\n lane: plan.meta.lane,\n target: contract.target,\n fingerprint: computeSqlFingerprint(plan.sql),\n outcome,\n ...(durationMs !== undefined ? { durationMs } : {}),\n });\n }\n\n execute<Row = Record<string, unknown>>(plan: ExecutionPlan<Row>): AsyncIterableResult<Row> {\n return this.#executeWith(plan, this.driver as Queryable);\n }\n\n async connection(): Promise<RuntimeConnection> {\n const driver = this.driver as unknown as DriverWithConnection<TDriver>;\n const driverConn = await driver.acquireConnection();\n const self = this;\n\n const runtimeConnection: RuntimeConnection = {\n async transaction(): Promise<RuntimeTransaction> {\n const driverTx = await driverConn.beginTransaction();\n const runtimeTx: RuntimeTransaction = {\n async commit(): Promise<void> {\n await driverTx.commit();\n },\n async rollback(): Promise<void> {\n await driverTx.rollback();\n },\n execute<Row = Record<string, unknown>>(\n plan: ExecutionPlan<Row>,\n ): AsyncIterableResult<Row> {\n return self.#executeWith(plan, driverTx);\n },\n };\n return runtimeTx;\n },\n execute<Row = Record<string, unknown>>(plan: ExecutionPlan<Row>): AsyncIterableResult<Row> {\n return self.#executeWith(plan, driverConn);\n },\n async release(): Promise<void> {\n await driverConn.release();\n },\n };\n\n return runtimeConnection;\n }\n\n telemetry(): RuntimeTelemetryEvent | null {\n return this._telemetry;\n }\n\n close(): Promise<void> {\n const driver = this.driver as unknown as DriverWithClose<TDriver>;\n if (typeof driver.close === 'function') {\n return driver.close();\n }\n return Promise.resolve();\n }\n\n #executeWith<Row = Record<string, unknown>>(\n plan: ExecutionPlan<Row>,\n queryable: Queryable,\n ): AsyncIterableResult<Row> {\n this.validatePlan(plan);\n this._telemetry = null;\n\n const iterator = async function* (\n self: RuntimeCoreImpl<TContract, TAdapter, TDriver>,\n ): AsyncGenerator<Row, void, unknown> {\n const startedAt = Date.now();\n let rowCount = 0;\n let completed = false;\n\n if (!self.startupVerified && self.verify.mode === 'startup') {\n await self.verifyPlanIfNeeded(plan);\n }\n\n if (self.verify.mode === 'onFirstUse') {\n await self.verifyPlanIfNeeded(plan);\n }\n\n try {\n if (self.verify.mode === 'always') {\n await self.verifyPlanIfNeeded(plan);\n }\n\n for (const plugin of self.plugins) {\n if (plugin.beforeExecute) {\n await plugin.beforeExecute(plan, self.pluginContext);\n }\n }\n\n const encodedParams = plan.params;\n\n for await (const row of queryable.execute<Record<string, unknown>>({\n sql: plan.sql,\n params: encodedParams,\n })) {\n for (const plugin of self.plugins) {\n if (plugin.onRow) {\n await plugin.onRow(row, plan, self.pluginContext);\n }\n }\n rowCount++;\n yield row as Row;\n }\n\n completed = true;\n self.recordTelemetry(plan, 'success', Date.now() - startedAt);\n } catch (error) {\n if (self._telemetry === null) {\n self.recordTelemetry(plan, 'runtime-error', Date.now() - startedAt);\n }\n\n const latencyMs = Date.now() - startedAt;\n for (const plugin of self.plugins) {\n if (plugin.afterExecute) {\n try {\n await plugin.afterExecute(\n plan,\n { rowCount, latencyMs, completed },\n self.pluginContext,\n );\n } catch {\n // Ignore errors from afterExecute hooks\n }\n }\n }\n\n throw error;\n }\n\n const latencyMs = Date.now() - startedAt;\n for (const plugin of self.plugins) {\n if (plugin.afterExecute) {\n await plugin.afterExecute(plan, { rowCount, latencyMs, completed }, self.pluginContext);\n }\n }\n };\n\n return new AsyncIterableResult(iterator(this));\n }\n}\n\nexport function createRuntimeCore<TContract = unknown, TAdapter = unknown, TDriver = unknown>(\n options: RuntimeCoreOptions<TContract, TAdapter, TDriver>,\n): RuntimeCore<TContract, TAdapter, TDriver> {\n return new RuntimeCoreImpl(options);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["lints: LintFinding[]","budgets: BudgetFinding[]","parsed: unknown","runtimeError","#executeWith","latencyMs","AsyncIterableResult"],"sources":["../src/fingerprint.ts","../src/guardrails/raw.ts","../src/marker.ts","../src/runtime-core.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\n\nconst STRING_LITERAL_REGEX = /'(?:''|[^'])*'/g;\nconst NUMERIC_LITERAL_REGEX = /\\b\\d+(?:\\.\\d+)?\\b/g;\nconst WHITESPACE_REGEX = /\\s+/g;\n\nexport function computeSqlFingerprint(sql: string): string {\n const withoutStrings = sql.replace(STRING_LITERAL_REGEX, '?');\n const withoutNumbers = withoutStrings.replace(NUMERIC_LITERAL_REGEX, '?');\n const normalized = withoutNumbers.replace(WHITESPACE_REGEX, ' ').trim().toLowerCase();\n\n const hash = createHash('sha256').update(normalized).digest('hex');\n return `sha256:${hash}`;\n}\n","import type { ExecutionPlan, PlanMeta, PlanRefs } from '@prisma-next/contract/types';\n\nexport type LintSeverity = 'error' | 'warn';\nexport type BudgetSeverity = 'error' | 'warn';\n\nexport interface LintFinding {\n readonly code: `LINT.${string}`;\n readonly severity: LintSeverity;\n readonly message: string;\n readonly details?: Record<string, unknown>;\n}\n\nexport interface BudgetFinding {\n readonly code: `BUDGET.${string}`;\n readonly severity: BudgetSeverity;\n readonly message: string;\n readonly details?: Record<string, unknown>;\n}\n\nexport interface RawGuardrailConfig {\n readonly budgets?: {\n readonly unboundedSelectSeverity?: BudgetSeverity;\n readonly estimatedRows?: number;\n };\n}\n\nexport interface RawGuardrailResult {\n readonly lints: LintFinding[];\n readonly budgets: BudgetFinding[];\n readonly statement: 'select' | 'mutation' | 'other';\n}\n\nconst SELECT_STAR_REGEX = /select\\s+\\*/i;\nconst LIMIT_REGEX = /\\blimit\\b/i;\nconst MUTATION_PREFIX_REGEX = /^(insert|update|delete|create|alter|drop|truncate)\\b/i;\n\nconst READ_ONLY_INTENTS = new Set(['read', 'report', 'readonly']);\n\nexport function evaluateRawGuardrails(\n plan: ExecutionPlan,\n config?: RawGuardrailConfig,\n): RawGuardrailResult {\n const lints: LintFinding[] = [];\n const budgets: BudgetFinding[] = [];\n\n const normalized = normalizeWhitespace(plan.sql);\n const statementType = classifyStatement(normalized);\n\n if (statementType === 'select') {\n if (SELECT_STAR_REGEX.test(normalized)) {\n lints.push(\n createLint('LINT.SELECT_STAR', 'error', 'Raw SQL plan selects all columns via *', {\n sql: snippet(plan.sql),\n }),\n );\n }\n\n if (!LIMIT_REGEX.test(normalized)) {\n const severity = config?.budgets?.unboundedSelectSeverity ?? 'error';\n lints.push(\n createLint('LINT.NO_LIMIT', 'warn', 'Raw SQL plan omits LIMIT clause', {\n sql: snippet(plan.sql),\n }),\n );\n\n budgets.push(\n createBudget(\n 'BUDGET.ROWS_EXCEEDED',\n severity,\n 'Raw SQL plan is unbounded and may exceed row budget',\n {\n sql: snippet(plan.sql),\n ...(config?.budgets?.estimatedRows !== undefined\n ? { estimatedRows: config.budgets.estimatedRows }\n : {}),\n },\n ),\n );\n }\n }\n\n if (isMutationStatement(statementType) && isReadOnlyIntent(plan.meta)) {\n lints.push(\n createLint(\n 'LINT.READ_ONLY_MUTATION',\n 'error',\n 'Raw SQL plan mutates data despite read-only intent',\n {\n sql: snippet(plan.sql),\n intent: plan.meta.annotations?.['intent'],\n },\n ),\n );\n }\n\n const refs = plan.meta.refs;\n if (refs) {\n evaluateIndexCoverage(refs, lints);\n }\n\n return { lints, budgets, statement: statementType };\n}\n\nfunction evaluateIndexCoverage(refs: PlanRefs, lints: LintFinding[]) {\n const predicateColumns = refs.columns ?? [];\n if (predicateColumns.length === 0) {\n return;\n }\n\n const indexes = refs.indexes ?? [];\n\n if (indexes.length === 0) {\n lints.push(\n createLint(\n 'LINT.UNINDEXED_PREDICATE',\n 'warn',\n 'Raw SQL plan predicates lack supporting indexes',\n {\n predicates: predicateColumns,\n },\n ),\n );\n return;\n }\n\n const hasSupportingIndex = predicateColumns.every((column) =>\n indexes.some(\n (index) =>\n index.table === column.table &&\n index.columns.some((col) => col.toLowerCase() === column.column.toLowerCase()),\n ),\n );\n\n if (!hasSupportingIndex) {\n lints.push(\n createLint(\n 'LINT.UNINDEXED_PREDICATE',\n 'warn',\n 'Raw SQL plan predicates lack supporting indexes',\n {\n predicates: predicateColumns,\n },\n ),\n );\n }\n}\n\nfunction classifyStatement(sql: string): 'select' | 'mutation' | 'other' {\n const trimmed = sql.trim();\n const lower = trimmed.toLowerCase();\n\n if (lower.startsWith('with')) {\n if (lower.includes('select')) {\n return 'select';\n }\n }\n\n if (lower.startsWith('select')) {\n return 'select';\n }\n\n if (MUTATION_PREFIX_REGEX.test(trimmed)) {\n return 'mutation';\n }\n\n return 'other';\n}\n\nfunction isMutationStatement(statement: 'select' | 'mutation' | 'other'): boolean {\n return statement === 'mutation';\n}\n\nfunction isReadOnlyIntent(meta: PlanMeta): boolean {\n const annotations = meta.annotations as { intent?: string } | undefined;\n const intent =\n typeof annotations?.intent === 'string' ? annotations.intent.toLowerCase() : undefined;\n return intent !== undefined && READ_ONLY_INTENTS.has(intent);\n}\n\nfunction normalizeWhitespace(value: string): string {\n return value.replace(/\\s+/g, ' ').trim();\n}\n\nfunction snippet(sql: string): string {\n return normalizeWhitespace(sql).slice(0, 200);\n}\n\nfunction createLint(\n code: LintFinding['code'],\n severity: LintFinding['severity'],\n message: string,\n details?: Record<string, unknown>,\n): LintFinding {\n return { code, severity, message, ...(details ? { details } : {}) };\n}\n\nfunction createBudget(\n code: BudgetFinding['code'],\n severity: BudgetFinding['severity'],\n message: string,\n details?: Record<string, unknown>,\n): BudgetFinding {\n return { code, severity, message, ...(details ? { details } : {}) };\n}\n","import type { ContractMarkerRecord } from '@prisma-next/contract/types';\nimport { type } from 'arktype';\n\nexport interface ContractMarkerRow {\n core_hash: string;\n profile_hash: string;\n contract_json: unknown | null;\n canonical_version: number | null;\n updated_at: Date;\n app_tag: string | null;\n meta: unknown | null;\n}\n\nconst MetaSchema = type({ '[string]': 'unknown' });\n\nfunction parseMeta(meta: unknown): Record<string, unknown> {\n if (meta === null || meta === undefined) {\n return {};\n }\n\n let parsed: unknown;\n if (typeof meta === 'string') {\n try {\n parsed = JSON.parse(meta);\n } catch {\n return {};\n }\n } else {\n parsed = meta;\n }\n\n const result = MetaSchema(parsed);\n if (result instanceof type.errors) {\n return {};\n }\n\n return result as Record<string, unknown>;\n}\n\nconst ContractMarkerRowSchema = type({\n core_hash: 'string',\n profile_hash: 'string',\n 'contract_json?': 'unknown | null',\n 'canonical_version?': 'number | null',\n 'updated_at?': 'Date | string',\n 'app_tag?': 'string | null',\n 'meta?': 'unknown | null',\n});\n\nexport function parseContractMarkerRow(row: unknown): ContractMarkerRecord {\n const result = ContractMarkerRowSchema(row);\n if (result instanceof type.errors) {\n const messages = result.map((p: { message: string }) => p.message).join('; ');\n throw new Error(`Invalid contract marker row: ${messages}`);\n }\n\n const validatedRow = result as {\n core_hash: string;\n profile_hash: string;\n contract_json?: unknown | null;\n canonical_version?: number | null;\n updated_at?: Date | string;\n app_tag?: string | null;\n meta?: unknown | null;\n };\n\n const updatedAt = validatedRow.updated_at\n ? validatedRow.updated_at instanceof Date\n ? validatedRow.updated_at\n : new Date(validatedRow.updated_at)\n : new Date();\n\n return {\n storageHash: validatedRow.core_hash,\n profileHash: validatedRow.profile_hash,\n contractJson: validatedRow.contract_json ?? null,\n canonicalVersion: validatedRow.canonical_version ?? null,\n updatedAt,\n appTag: validatedRow.app_tag ?? null,\n meta: parseMeta(validatedRow.meta),\n };\n}\n","import type { ExecutionPlan } from '@prisma-next/contract/types';\nimport type { RuntimeExecutor } from '@prisma-next/framework-components/runtime';\nimport { AsyncIterableResult, runtimeError } from '@prisma-next/framework-components/runtime';\nimport { computeSqlFingerprint } from './fingerprint';\nimport { parseContractMarkerRow } from './marker';\nimport type { Log, Middleware, MiddlewareContext } from './middleware/types';\nimport type { RuntimeFamilyAdapter } from './runtime-spi';\n\nexport interface RuntimeVerifyOptions {\n readonly mode: 'onFirstUse' | 'startup' | 'always';\n readonly requireMarker: boolean;\n}\n\nexport type TelemetryOutcome = 'success' | 'runtime-error';\n\nexport interface RuntimeTelemetryEvent {\n readonly lane: string;\n readonly target: string;\n readonly fingerprint: string;\n readonly outcome: TelemetryOutcome;\n readonly durationMs?: number;\n}\n\nexport interface RuntimeCoreOptions<TContract = unknown, TDriver = unknown> {\n readonly familyAdapter: RuntimeFamilyAdapter<TContract>;\n readonly driver: TDriver;\n readonly verify: RuntimeVerifyOptions;\n readonly middleware?: readonly Middleware<TContract>[];\n readonly mode?: 'strict' | 'permissive';\n readonly log?: Log;\n}\n\nexport interface RuntimeCore<TContract = unknown, TDriver = unknown>\n extends RuntimeQueryable,\n RuntimeExecutor<ExecutionPlan> {\n readonly _typeContract?: TContract;\n readonly _typeDriver?: TDriver;\n connection(): Promise<RuntimeConnection>;\n telemetry(): RuntimeTelemetryEvent | null;\n close(): Promise<void>;\n}\n\nexport interface RuntimeConnection extends RuntimeQueryable {\n transaction(): Promise<RuntimeTransaction>;\n release(): Promise<void>;\n}\n\nexport interface RuntimeTransaction extends RuntimeQueryable {\n commit(): Promise<void>;\n rollback(): Promise<void>;\n}\n\n/**\n * Shared query execution trait for anything that can run an ExecutionPlan:\n * RuntimeCore, RuntimeConnection, and RuntimeTransaction. This is a\n * SQL-domain internal mixin — it is NOT the cross-family SPI.\n *\n * For the cross-family SPI, see RuntimeExecutor in framework-components.\n * RuntimeCore nominally extends both this interface and RuntimeExecutor.\n *\n * The execute signature uses the same `_row` phantom intersection as\n * RuntimeExecutor so that RuntimeCore can extend both without conflicts.\n */\nexport interface RuntimeQueryable {\n execute<Row>(plan: ExecutionPlan & { readonly _row?: Row }): AsyncIterableResult<Row>;\n}\n\ninterface DriverWithQuery<_TDriver> {\n query(sql: string, params: readonly unknown[]): Promise<{ rows: ReadonlyArray<unknown> }>;\n}\n\ninterface DriverWithConnection<_TDriver> {\n acquireConnection(): Promise<DriverConnection>;\n}\n\nexport interface DriverConnection extends Queryable {\n beginTransaction(): Promise<DriverTransaction>;\n release(): Promise<void>;\n}\n\nexport interface DriverTransaction extends Queryable {\n commit(): Promise<void>;\n rollback(): Promise<void>;\n}\n\nexport interface Queryable {\n execute<Row = Record<string, unknown>>(options: {\n sql: string;\n params: readonly unknown[];\n }): AsyncIterable<Row>;\n}\n\ninterface DriverWithClose<_TDriver> {\n close(): Promise<void>;\n}\n\nclass RuntimeCoreImpl<TContract = unknown, TDriver = unknown>\n implements RuntimeCore<TContract, TDriver>\n{\n readonly _typeContract?: TContract;\n readonly _typeDriver?: TDriver;\n private readonly contract: TContract;\n private readonly familyAdapter: RuntimeFamilyAdapter<TContract>;\n private readonly driver: TDriver;\n private readonly middleware: readonly Middleware<TContract>[];\n private readonly mode: 'strict' | 'permissive';\n private readonly verify: RuntimeVerifyOptions;\n private readonly middlewareContext: MiddlewareContext<TContract>;\n\n private verified: boolean;\n private startupVerified: boolean;\n private _telemetry: RuntimeTelemetryEvent | null;\n\n constructor(options: RuntimeCoreOptions<TContract, TDriver>) {\n const { familyAdapter, driver } = options;\n this.contract = familyAdapter.contract;\n this.familyAdapter = familyAdapter;\n this.driver = driver;\n this.middleware = options.middleware ?? [];\n this.mode = options.mode ?? 'strict';\n this.verify = options.verify;\n this.verified = options.verify.mode === 'startup' ? false : options.verify.mode === 'always';\n this.startupVerified = false;\n this._telemetry = null;\n\n this.middlewareContext = {\n contract: this.contract,\n mode: this.mode,\n now: () => Date.now(),\n log: options.log ?? {\n info: () => {},\n warn: () => {},\n error: () => {},\n },\n };\n }\n\n private async verifyPlanIfNeeded(_plan: ExecutionPlan): Promise<void> {\n void _plan;\n if (this.verify.mode === 'always') {\n this.verified = false;\n }\n\n if (this.verified) {\n return;\n }\n\n const readStatement = this.familyAdapter.markerReader.readMarkerStatement();\n const driver = this.driver as unknown as DriverWithQuery<TDriver>;\n const result = await driver.query(readStatement.sql, readStatement.params);\n\n if (result.rows.length === 0) {\n if (this.verify.requireMarker) {\n throw runtimeError('CONTRACT.MARKER_MISSING', 'Contract marker not found in database');\n }\n\n this.verified = true;\n return;\n }\n\n const marker = parseContractMarkerRow(result.rows[0]);\n\n const contract = this.contract as {\n storage: { storageHash: string };\n execution?: { executionHash?: string | null };\n profileHash?: string | null;\n };\n if (marker.storageHash !== contract.storage.storageHash) {\n throw runtimeError(\n 'CONTRACT.MARKER_MISMATCH',\n 'Database storage hash does not match contract',\n {\n expected: contract.storage.storageHash,\n actual: marker.storageHash,\n },\n );\n }\n\n const expectedProfile = contract.profileHash ?? null;\n if (expectedProfile !== null && marker.profileHash !== expectedProfile) {\n throw runtimeError(\n 'CONTRACT.MARKER_MISMATCH',\n 'Database profile hash does not match contract',\n {\n expectedProfile,\n actualProfile: marker.profileHash,\n },\n );\n }\n\n this.verified = true;\n this.startupVerified = true;\n }\n\n private validatePlan(plan: ExecutionPlan): void {\n this.familyAdapter.validatePlan(plan, this.contract);\n }\n\n private recordTelemetry(\n plan: ExecutionPlan,\n outcome: TelemetryOutcome,\n durationMs?: number,\n ): void {\n const contract = this.contract as { target: string };\n this._telemetry = Object.freeze({\n lane: plan.meta.lane,\n target: contract.target,\n fingerprint: computeSqlFingerprint(plan.sql),\n outcome,\n ...(durationMs !== undefined ? { durationMs } : {}),\n });\n }\n\n execute<Row = Record<string, unknown>>(plan: ExecutionPlan<Row>): AsyncIterableResult<Row> {\n return this.#executeWith(plan, this.driver as Queryable);\n }\n\n async connection(): Promise<RuntimeConnection> {\n const driver = this.driver as unknown as DriverWithConnection<TDriver>;\n const driverConn = await driver.acquireConnection();\n const self = this;\n\n const runtimeConnection: RuntimeConnection = {\n async transaction(): Promise<RuntimeTransaction> {\n const driverTx = await driverConn.beginTransaction();\n const runtimeTx: RuntimeTransaction = {\n async commit(): Promise<void> {\n await driverTx.commit();\n },\n async rollback(): Promise<void> {\n await driverTx.rollback();\n },\n execute<Row = Record<string, unknown>>(\n plan: ExecutionPlan<Row>,\n ): AsyncIterableResult<Row> {\n return self.#executeWith(plan, driverTx);\n },\n };\n return runtimeTx;\n },\n execute<Row = Record<string, unknown>>(plan: ExecutionPlan<Row>): AsyncIterableResult<Row> {\n return self.#executeWith(plan, driverConn);\n },\n async release(): Promise<void> {\n await driverConn.release();\n },\n };\n\n return runtimeConnection;\n }\n\n telemetry(): RuntimeTelemetryEvent | null {\n return this._telemetry;\n }\n\n close(): Promise<void> {\n const driver = this.driver as unknown as DriverWithClose<TDriver>;\n if (typeof driver.close === 'function') {\n return driver.close();\n }\n return Promise.resolve();\n }\n\n #executeWith<Row = Record<string, unknown>>(\n plan: ExecutionPlan<Row>,\n queryable: Queryable,\n ): AsyncIterableResult<Row> {\n this.validatePlan(plan);\n this._telemetry = null;\n\n const iterator = async function* (\n self: RuntimeCoreImpl<TContract, TDriver>,\n ): AsyncGenerator<Row, void, unknown> {\n const startedAt = Date.now();\n let rowCount = 0;\n let completed = false;\n\n if (!self.startupVerified && self.verify.mode === 'startup') {\n await self.verifyPlanIfNeeded(plan);\n }\n\n if (self.verify.mode === 'onFirstUse') {\n await self.verifyPlanIfNeeded(plan);\n }\n\n try {\n if (self.verify.mode === 'always') {\n await self.verifyPlanIfNeeded(plan);\n }\n\n for (const mw of self.middleware) {\n if (mw.beforeExecute) {\n await mw.beforeExecute(plan, self.middlewareContext);\n }\n }\n\n const encodedParams = plan.params;\n\n for await (const row of queryable.execute<Record<string, unknown>>({\n sql: plan.sql,\n params: encodedParams,\n })) {\n for (const mw of self.middleware) {\n if (mw.onRow) {\n await mw.onRow(row, plan, self.middlewareContext);\n }\n }\n rowCount++;\n yield row as Row;\n }\n\n completed = true;\n self.recordTelemetry(plan, 'success', Date.now() - startedAt);\n } catch (error) {\n if (self._telemetry === null) {\n self.recordTelemetry(plan, 'runtime-error', Date.now() - startedAt);\n }\n\n const latencyMs = Date.now() - startedAt;\n for (const mw of self.middleware) {\n if (mw.afterExecute) {\n try {\n await mw.afterExecute(\n plan,\n { rowCount, latencyMs, completed },\n self.middlewareContext,\n );\n } catch {\n // Ignore errors from afterExecute hooks\n }\n }\n }\n\n throw error;\n }\n\n const latencyMs = Date.now() - startedAt;\n for (const mw of self.middleware) {\n if (mw.afterExecute) {\n await mw.afterExecute(plan, { rowCount, latencyMs, completed }, self.middlewareContext);\n }\n }\n };\n\n return new AsyncIterableResult(iterator(this));\n }\n}\n\nexport function createRuntimeCore<TContract = unknown, TDriver = unknown>(\n options: RuntimeCoreOptions<TContract, TDriver>,\n): RuntimeCore<TContract, TDriver> {\n return new RuntimeCoreImpl(options);\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma-next/runtime-executor",
|
|
3
|
-
"version": "0.3.0-dev.
|
|
3
|
+
"version": "0.3.0-dev.159",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"description": "Target-agnostic execution engine for Prisma Next",
|
|
7
7
|
"dependencies": {
|
|
8
8
|
"arktype": "^2.1.25",
|
|
9
|
-
"@prisma-next/
|
|
10
|
-
"@prisma-next/
|
|
9
|
+
"@prisma-next/contract": "0.3.0-dev.159",
|
|
10
|
+
"@prisma-next/framework-components": "0.3.0-dev.159",
|
|
11
|
+
"@prisma-next/operations": "0.3.0-dev.159"
|
|
11
12
|
},
|
|
12
13
|
"devDependencies": {
|
|
13
14
|
"@prisma/dev": "0.19.1",
|
|
14
15
|
"tsdown": "0.18.4",
|
|
15
16
|
"typescript": "5.9.3",
|
|
16
17
|
"vitest": "4.0.17",
|
|
17
|
-
"@prisma-next/test-utils": "0.0.1",
|
|
18
18
|
"@prisma-next/tsconfig": "0.0.0",
|
|
19
|
+
"@prisma-next/test-utils": "0.0.1",
|
|
19
20
|
"@prisma-next/tsdown": "0.0.0"
|
|
20
21
|
},
|
|
21
22
|
"files": [
|
package/src/exports/index.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export
|
|
3
|
-
export { runtimeError } from '../errors';
|
|
1
|
+
export type { RuntimeErrorEnvelope } from '@prisma-next/framework-components/runtime';
|
|
2
|
+
export { AsyncIterableResult, runtimeError } from '@prisma-next/framework-components/runtime';
|
|
4
3
|
export { computeSqlFingerprint } from '../fingerprint';
|
|
5
4
|
export type { BudgetFinding, LintFinding, RawGuardrailResult } from '../guardrails/raw';
|
|
6
5
|
export { evaluateRawGuardrails } from '../guardrails/raw';
|
|
@@ -8,10 +7,10 @@ export { parseContractMarkerRow } from '../marker';
|
|
|
8
7
|
export type {
|
|
9
8
|
AfterExecuteResult,
|
|
10
9
|
Log,
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
Middleware,
|
|
11
|
+
MiddlewareContext,
|
|
13
12
|
Severity,
|
|
14
|
-
} from '../
|
|
13
|
+
} from '../middleware/types';
|
|
15
14
|
export type {
|
|
16
15
|
RuntimeConnection,
|
|
17
16
|
RuntimeCore,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ExecutionPlan } from '@prisma-next/contract/types';
|
|
2
|
+
import type {
|
|
3
|
+
AfterExecuteResult,
|
|
4
|
+
RuntimeLog,
|
|
5
|
+
RuntimeMiddleware,
|
|
6
|
+
RuntimeMiddlewareContext,
|
|
7
|
+
} from '@prisma-next/framework-components/runtime';
|
|
8
|
+
|
|
9
|
+
export type Severity = 'error' | 'warn' | 'info';
|
|
10
|
+
|
|
11
|
+
export type { AfterExecuteResult, RuntimeLog as Log };
|
|
12
|
+
|
|
13
|
+
export interface MiddlewareContext<TContract = unknown> extends RuntimeMiddlewareContext {
|
|
14
|
+
readonly contract: TContract;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface Middleware<TContract = unknown> extends RuntimeMiddleware {
|
|
18
|
+
beforeExecute?(plan: ExecutionPlan, ctx: MiddlewareContext<TContract>): Promise<void>;
|
|
19
|
+
onRow?(
|
|
20
|
+
row: Record<string, unknown>,
|
|
21
|
+
plan: ExecutionPlan,
|
|
22
|
+
ctx: MiddlewareContext<TContract>,
|
|
23
|
+
): Promise<void>;
|
|
24
|
+
afterExecute?(
|
|
25
|
+
plan: ExecutionPlan,
|
|
26
|
+
result: AfterExecuteResult,
|
|
27
|
+
ctx: MiddlewareContext<TContract>,
|
|
28
|
+
): Promise<void>;
|
|
29
|
+
}
|
package/src/runtime-core.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { ExecutionPlan } from '@prisma-next/contract/types';
|
|
2
|
-
import {
|
|
3
|
-
import { runtimeError } from '
|
|
2
|
+
import type { RuntimeExecutor } from '@prisma-next/framework-components/runtime';
|
|
3
|
+
import { AsyncIterableResult, runtimeError } from '@prisma-next/framework-components/runtime';
|
|
4
4
|
import { computeSqlFingerprint } from './fingerprint';
|
|
5
5
|
import { parseContractMarkerRow } from './marker';
|
|
6
|
-
import type { Log,
|
|
6
|
+
import type { Log, Middleware, MiddlewareContext } from './middleware/types';
|
|
7
7
|
import type { RuntimeFamilyAdapter } from './runtime-spi';
|
|
8
8
|
|
|
9
9
|
export interface RuntimeVerifyOptions {
|
|
@@ -21,20 +21,19 @@ export interface RuntimeTelemetryEvent {
|
|
|
21
21
|
readonly durationMs?: number;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export interface RuntimeCoreOptions<TContract = unknown,
|
|
24
|
+
export interface RuntimeCoreOptions<TContract = unknown, TDriver = unknown> {
|
|
25
25
|
readonly familyAdapter: RuntimeFamilyAdapter<TContract>;
|
|
26
26
|
readonly driver: TDriver;
|
|
27
27
|
readonly verify: RuntimeVerifyOptions;
|
|
28
|
-
readonly
|
|
28
|
+
readonly middleware?: readonly Middleware<TContract>[];
|
|
29
29
|
readonly mode?: 'strict' | 'permissive';
|
|
30
30
|
readonly log?: Log;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
export interface RuntimeCore<TContract = unknown,
|
|
34
|
-
extends RuntimeQueryable
|
|
35
|
-
|
|
33
|
+
export interface RuntimeCore<TContract = unknown, TDriver = unknown>
|
|
34
|
+
extends RuntimeQueryable,
|
|
35
|
+
RuntimeExecutor<ExecutionPlan> {
|
|
36
36
|
readonly _typeContract?: TContract;
|
|
37
|
-
readonly _typeAdapter?: TAdapter;
|
|
38
37
|
readonly _typeDriver?: TDriver;
|
|
39
38
|
connection(): Promise<RuntimeConnection>;
|
|
40
39
|
telemetry(): RuntimeTelemetryEvent | null;
|
|
@@ -51,8 +50,19 @@ export interface RuntimeTransaction extends RuntimeQueryable {
|
|
|
51
50
|
rollback(): Promise<void>;
|
|
52
51
|
}
|
|
53
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Shared query execution trait for anything that can run an ExecutionPlan:
|
|
55
|
+
* RuntimeCore, RuntimeConnection, and RuntimeTransaction. This is a
|
|
56
|
+
* SQL-domain internal mixin — it is NOT the cross-family SPI.
|
|
57
|
+
*
|
|
58
|
+
* For the cross-family SPI, see RuntimeExecutor in framework-components.
|
|
59
|
+
* RuntimeCore nominally extends both this interface and RuntimeExecutor.
|
|
60
|
+
*
|
|
61
|
+
* The execute signature uses the same `_row` phantom intersection as
|
|
62
|
+
* RuntimeExecutor so that RuntimeCore can extend both without conflicts.
|
|
63
|
+
*/
|
|
54
64
|
export interface RuntimeQueryable {
|
|
55
|
-
execute<Row
|
|
65
|
+
execute<Row>(plan: ExecutionPlan & { readonly _row?: Row }): AsyncIterableResult<Row>;
|
|
56
66
|
}
|
|
57
67
|
|
|
58
68
|
interface DriverWithQuery<_TDriver> {
|
|
@@ -84,52 +94,43 @@ interface DriverWithClose<_TDriver> {
|
|
|
84
94
|
close(): Promise<void>;
|
|
85
95
|
}
|
|
86
96
|
|
|
87
|
-
class RuntimeCoreImpl<TContract = unknown,
|
|
88
|
-
implements RuntimeCore<TContract,
|
|
97
|
+
class RuntimeCoreImpl<TContract = unknown, TDriver = unknown>
|
|
98
|
+
implements RuntimeCore<TContract, TDriver>
|
|
89
99
|
{
|
|
90
100
|
readonly _typeContract?: TContract;
|
|
91
|
-
readonly _typeAdapter?: TAdapter;
|
|
92
101
|
readonly _typeDriver?: TDriver;
|
|
93
102
|
private readonly contract: TContract;
|
|
94
103
|
private readonly familyAdapter: RuntimeFamilyAdapter<TContract>;
|
|
95
104
|
private readonly driver: TDriver;
|
|
96
|
-
private readonly
|
|
105
|
+
private readonly middleware: readonly Middleware<TContract>[];
|
|
97
106
|
private readonly mode: 'strict' | 'permissive';
|
|
98
107
|
private readonly verify: RuntimeVerifyOptions;
|
|
99
|
-
private readonly
|
|
108
|
+
private readonly middlewareContext: MiddlewareContext<TContract>;
|
|
100
109
|
|
|
101
110
|
private verified: boolean;
|
|
102
111
|
private startupVerified: boolean;
|
|
103
112
|
private _telemetry: RuntimeTelemetryEvent | null;
|
|
104
113
|
|
|
105
|
-
constructor(options: RuntimeCoreOptions<TContract,
|
|
114
|
+
constructor(options: RuntimeCoreOptions<TContract, TDriver>) {
|
|
106
115
|
const { familyAdapter, driver } = options;
|
|
107
116
|
this.contract = familyAdapter.contract;
|
|
108
117
|
this.familyAdapter = familyAdapter;
|
|
109
118
|
this.driver = driver;
|
|
110
|
-
this.
|
|
119
|
+
this.middleware = options.middleware ?? [];
|
|
111
120
|
this.mode = options.mode ?? 'strict';
|
|
112
121
|
this.verify = options.verify;
|
|
113
122
|
this.verified = options.verify.mode === 'startup' ? false : options.verify.mode === 'always';
|
|
114
123
|
this.startupVerified = false;
|
|
115
124
|
this._telemetry = null;
|
|
116
125
|
|
|
117
|
-
this.
|
|
126
|
+
this.middlewareContext = {
|
|
118
127
|
contract: this.contract,
|
|
119
|
-
adapter: options.familyAdapter as unknown as TAdapter,
|
|
120
|
-
driver: this.driver,
|
|
121
128
|
mode: this.mode,
|
|
122
129
|
now: () => Date.now(),
|
|
123
130
|
log: options.log ?? {
|
|
124
|
-
info: () => {
|
|
125
|
-
|
|
126
|
-
},
|
|
127
|
-
warn: () => {
|
|
128
|
-
// No-op in MVP - diagnostics stay out of runtime core
|
|
129
|
-
},
|
|
130
|
-
error: () => {
|
|
131
|
-
// No-op in MVP - diagnostics stay out of runtime core
|
|
132
|
-
},
|
|
131
|
+
info: () => {},
|
|
132
|
+
warn: () => {},
|
|
133
|
+
error: () => {},
|
|
133
134
|
},
|
|
134
135
|
};
|
|
135
136
|
}
|
|
@@ -268,7 +269,7 @@ class RuntimeCoreImpl<TContract = unknown, TAdapter = unknown, TDriver = unknown
|
|
|
268
269
|
this._telemetry = null;
|
|
269
270
|
|
|
270
271
|
const iterator = async function* (
|
|
271
|
-
self: RuntimeCoreImpl<TContract,
|
|
272
|
+
self: RuntimeCoreImpl<TContract, TDriver>,
|
|
272
273
|
): AsyncGenerator<Row, void, unknown> {
|
|
273
274
|
const startedAt = Date.now();
|
|
274
275
|
let rowCount = 0;
|
|
@@ -287,9 +288,9 @@ class RuntimeCoreImpl<TContract = unknown, TAdapter = unknown, TDriver = unknown
|
|
|
287
288
|
await self.verifyPlanIfNeeded(plan);
|
|
288
289
|
}
|
|
289
290
|
|
|
290
|
-
for (const
|
|
291
|
-
if (
|
|
292
|
-
await
|
|
291
|
+
for (const mw of self.middleware) {
|
|
292
|
+
if (mw.beforeExecute) {
|
|
293
|
+
await mw.beforeExecute(plan, self.middlewareContext);
|
|
293
294
|
}
|
|
294
295
|
}
|
|
295
296
|
|
|
@@ -299,9 +300,9 @@ class RuntimeCoreImpl<TContract = unknown, TAdapter = unknown, TDriver = unknown
|
|
|
299
300
|
sql: plan.sql,
|
|
300
301
|
params: encodedParams,
|
|
301
302
|
})) {
|
|
302
|
-
for (const
|
|
303
|
-
if (
|
|
304
|
-
await
|
|
303
|
+
for (const mw of self.middleware) {
|
|
304
|
+
if (mw.onRow) {
|
|
305
|
+
await mw.onRow(row, plan, self.middlewareContext);
|
|
305
306
|
}
|
|
306
307
|
}
|
|
307
308
|
rowCount++;
|
|
@@ -316,13 +317,13 @@ class RuntimeCoreImpl<TContract = unknown, TAdapter = unknown, TDriver = unknown
|
|
|
316
317
|
}
|
|
317
318
|
|
|
318
319
|
const latencyMs = Date.now() - startedAt;
|
|
319
|
-
for (const
|
|
320
|
-
if (
|
|
320
|
+
for (const mw of self.middleware) {
|
|
321
|
+
if (mw.afterExecute) {
|
|
321
322
|
try {
|
|
322
|
-
await
|
|
323
|
+
await mw.afterExecute(
|
|
323
324
|
plan,
|
|
324
325
|
{ rowCount, latencyMs, completed },
|
|
325
|
-
self.
|
|
326
|
+
self.middlewareContext,
|
|
326
327
|
);
|
|
327
328
|
} catch {
|
|
328
329
|
// Ignore errors from afterExecute hooks
|
|
@@ -334,9 +335,9 @@ class RuntimeCoreImpl<TContract = unknown, TAdapter = unknown, TDriver = unknown
|
|
|
334
335
|
}
|
|
335
336
|
|
|
336
337
|
const latencyMs = Date.now() - startedAt;
|
|
337
|
-
for (const
|
|
338
|
-
if (
|
|
339
|
-
await
|
|
338
|
+
for (const mw of self.middleware) {
|
|
339
|
+
if (mw.afterExecute) {
|
|
340
|
+
await mw.afterExecute(plan, { rowCount, latencyMs, completed }, self.middlewareContext);
|
|
340
341
|
}
|
|
341
342
|
}
|
|
342
343
|
};
|
|
@@ -345,8 +346,8 @@ class RuntimeCoreImpl<TContract = unknown, TAdapter = unknown, TDriver = unknown
|
|
|
345
346
|
}
|
|
346
347
|
}
|
|
347
348
|
|
|
348
|
-
export function createRuntimeCore<TContract = unknown,
|
|
349
|
-
options: RuntimeCoreOptions<TContract,
|
|
350
|
-
): RuntimeCore<TContract,
|
|
349
|
+
export function createRuntimeCore<TContract = unknown, TDriver = unknown>(
|
|
350
|
+
options: RuntimeCoreOptions<TContract, TDriver>,
|
|
351
|
+
): RuntimeCore<TContract, TDriver> {
|
|
351
352
|
return new RuntimeCoreImpl(options);
|
|
352
353
|
}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { runtimeError } from './errors';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Custom async iterable result that extends AsyncIterable with a toArray() method.
|
|
5
|
-
* This provides a convenient way to collect all results from an async iterator.
|
|
6
|
-
*/
|
|
7
|
-
export class AsyncIterableResult<Row> implements AsyncIterable<Row>, PromiseLike<Row[]> {
|
|
8
|
-
private readonly generator: AsyncGenerator<Row, void, unknown>;
|
|
9
|
-
private consumed = false;
|
|
10
|
-
private consumedBy: 'bufferedArray' | 'iterator' | undefined;
|
|
11
|
-
private bufferedArrayPromise: Promise<Row[]> | undefined;
|
|
12
|
-
|
|
13
|
-
constructor(generator: AsyncGenerator<Row, void, unknown>) {
|
|
14
|
-
this.generator = generator;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
[Symbol.asyncIterator](): AsyncIterator<Row> {
|
|
18
|
-
if (this.consumed) {
|
|
19
|
-
throw runtimeError(
|
|
20
|
-
'RUNTIME.ITERATOR_CONSUMED',
|
|
21
|
-
`AsyncIterableResult iterator has already been consumed via ${this.consumedBy === 'bufferedArray' ? 'toArray()/then()' : 'for-await loop'}. Each AsyncIterableResult can only be iterated once.`,
|
|
22
|
-
{
|
|
23
|
-
consumedBy: this.consumedBy,
|
|
24
|
-
suggestion:
|
|
25
|
-
this.consumedBy === 'bufferedArray'
|
|
26
|
-
? 'If you need to iterate multiple times, store the results from toArray() in a variable and reuse that.'
|
|
27
|
-
: 'If you need to iterate multiple times, use toArray() to collect all results first.',
|
|
28
|
-
},
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
this.consumed = true;
|
|
32
|
-
this.consumedBy = 'iterator';
|
|
33
|
-
return this.generator;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Collects all values from the async iterator into an array.
|
|
38
|
-
* Once called, the iterator is consumed and cannot be reused.
|
|
39
|
-
*/
|
|
40
|
-
toArray(): Promise<Row[]> {
|
|
41
|
-
if (this.consumedBy === 'iterator') {
|
|
42
|
-
return Promise.reject(
|
|
43
|
-
runtimeError(
|
|
44
|
-
'RUNTIME.ITERATOR_CONSUMED',
|
|
45
|
-
'AsyncIterableResult iterator has already been consumed via for-await loop. Each AsyncIterableResult can only be iterated once.',
|
|
46
|
-
{
|
|
47
|
-
consumedBy: this.consumedBy,
|
|
48
|
-
suggestion:
|
|
49
|
-
'The iterator was already consumed by a for-await loop. Use toArray() or await the result before iterating.',
|
|
50
|
-
},
|
|
51
|
-
),
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (this.bufferedArrayPromise) {
|
|
56
|
-
return this.bufferedArrayPromise;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
this.consumed = true;
|
|
60
|
-
this.consumedBy = 'bufferedArray';
|
|
61
|
-
this.bufferedArrayPromise = (async () => {
|
|
62
|
-
const out: Row[] = [];
|
|
63
|
-
for await (const item of this.generator) {
|
|
64
|
-
out.push(item);
|
|
65
|
-
}
|
|
66
|
-
return out;
|
|
67
|
-
})();
|
|
68
|
-
return this.bufferedArrayPromise;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Returns the first row, or null if the result set is empty.
|
|
73
|
-
*/
|
|
74
|
-
async first(): Promise<Row | null> {
|
|
75
|
-
const rows = await this.toArray();
|
|
76
|
-
return rows[0] ?? null;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Returns the first row, or throws if the result set is empty.
|
|
81
|
-
*/
|
|
82
|
-
async firstOrThrow(): Promise<Row> {
|
|
83
|
-
const row = await this.first();
|
|
84
|
-
if (row === null)
|
|
85
|
-
throw runtimeError(
|
|
86
|
-
'RUNTIME.NO_ROWS',
|
|
87
|
-
'Expected at least one row, but none were returned',
|
|
88
|
-
{},
|
|
89
|
-
);
|
|
90
|
-
return row;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// biome-ignore lint/suspicious/noThenProperty: PromiseLike implementation is intentional for await support.
|
|
94
|
-
then<TResult1 = Row[], TResult2 = never>(
|
|
95
|
-
onfulfilled?: ((value: Row[]) => TResult1 | PromiseLike<TResult1>) | undefined | null,
|
|
96
|
-
onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | undefined | null,
|
|
97
|
-
): PromiseLike<TResult1 | TResult2> {
|
|
98
|
-
return this.toArray().then(onfulfilled, onrejected);
|
|
99
|
-
}
|
|
100
|
-
}
|
package/src/errors.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
export interface RuntimeErrorEnvelope extends Error {
|
|
2
|
-
readonly code: string;
|
|
3
|
-
readonly category: 'PLAN' | 'CONTRACT' | 'LINT' | 'BUDGET' | 'RUNTIME';
|
|
4
|
-
readonly severity: 'error';
|
|
5
|
-
readonly details?: Record<string, unknown>;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function runtimeError(
|
|
9
|
-
code: string,
|
|
10
|
-
message: string,
|
|
11
|
-
details?: Record<string, unknown>,
|
|
12
|
-
): RuntimeErrorEnvelope {
|
|
13
|
-
const error = new Error(message) as RuntimeErrorEnvelope;
|
|
14
|
-
Object.defineProperty(error, 'name', {
|
|
15
|
-
value: 'RuntimeError',
|
|
16
|
-
configurable: true,
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
return Object.assign(error, {
|
|
20
|
-
code,
|
|
21
|
-
category: resolveCategory(code),
|
|
22
|
-
severity: 'error' as const,
|
|
23
|
-
message,
|
|
24
|
-
details,
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function resolveCategory(code: string): RuntimeErrorEnvelope['category'] {
|
|
29
|
-
const prefix = code.split('.')[0] ?? 'RUNTIME';
|
|
30
|
-
switch (prefix) {
|
|
31
|
-
case 'PLAN':
|
|
32
|
-
case 'CONTRACT':
|
|
33
|
-
case 'LINT':
|
|
34
|
-
case 'BUDGET':
|
|
35
|
-
return prefix;
|
|
36
|
-
default:
|
|
37
|
-
return 'RUNTIME';
|
|
38
|
-
}
|
|
39
|
-
}
|
package/src/plugins/types.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import type { ExecutionPlan } from '@prisma-next/contract/types';
|
|
2
|
-
|
|
3
|
-
export type Severity = 'error' | 'warn' | 'info';
|
|
4
|
-
|
|
5
|
-
export interface Log {
|
|
6
|
-
info(event: unknown): void;
|
|
7
|
-
warn(event: unknown): void;
|
|
8
|
-
error(event: unknown): void;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface PluginContext<TContract = unknown, TAdapter = unknown, TDriver = unknown> {
|
|
12
|
-
readonly contract: TContract;
|
|
13
|
-
readonly adapter: TAdapter;
|
|
14
|
-
readonly driver: TDriver;
|
|
15
|
-
readonly mode: 'strict' | 'permissive';
|
|
16
|
-
readonly now: () => number;
|
|
17
|
-
readonly log: Log;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface AfterExecuteResult {
|
|
21
|
-
readonly rowCount: number;
|
|
22
|
-
readonly latencyMs: number;
|
|
23
|
-
readonly completed: boolean;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface Plugin<TContract = unknown, TAdapter = unknown, TDriver = unknown> {
|
|
27
|
-
readonly name: string;
|
|
28
|
-
beforeExecute?(
|
|
29
|
-
plan: ExecutionPlan,
|
|
30
|
-
ctx: PluginContext<TContract, TAdapter, TDriver>,
|
|
31
|
-
): Promise<void>;
|
|
32
|
-
onRow?(
|
|
33
|
-
row: Record<string, unknown>,
|
|
34
|
-
plan: ExecutionPlan,
|
|
35
|
-
ctx: PluginContext<TContract, TAdapter, TDriver>,
|
|
36
|
-
): Promise<void>;
|
|
37
|
-
afterExecute?(
|
|
38
|
-
plan: ExecutionPlan,
|
|
39
|
-
result: AfterExecuteResult,
|
|
40
|
-
ctx: PluginContext<TContract, TAdapter, TDriver>,
|
|
41
|
-
): Promise<void>;
|
|
42
|
-
}
|