@prisma-next/runtime-executor 0.3.0-dev.14 → 0.3.0-dev.147

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.mjs ADDED
@@ -0,0 +1,399 @@
1
+ import { createHash } from "node:crypto";
2
+ import { type } from "arktype";
3
+
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
+ //#region src/fingerprint.ts
93
+ const STRING_LITERAL_REGEX = /'(?:''|[^'])*'/g;
94
+ const NUMERIC_LITERAL_REGEX = /\b\d+(?:\.\d+)?\b/g;
95
+ const WHITESPACE_REGEX = /\s+/g;
96
+ function computeSqlFingerprint(sql) {
97
+ const normalized = sql.replace(STRING_LITERAL_REGEX, "?").replace(NUMERIC_LITERAL_REGEX, "?").replace(WHITESPACE_REGEX, " ").trim().toLowerCase();
98
+ return `sha256:${createHash("sha256").update(normalized).digest("hex")}`;
99
+ }
100
+
101
+ //#endregion
102
+ //#region src/guardrails/raw.ts
103
+ const SELECT_STAR_REGEX = /select\s+\*/i;
104
+ const LIMIT_REGEX = /\blimit\b/i;
105
+ const MUTATION_PREFIX_REGEX = /^(insert|update|delete|create|alter|drop|truncate)\b/i;
106
+ const READ_ONLY_INTENTS = new Set([
107
+ "read",
108
+ "report",
109
+ "readonly"
110
+ ]);
111
+ function evaluateRawGuardrails(plan, config) {
112
+ const lints = [];
113
+ const budgets = [];
114
+ const normalized = normalizeWhitespace(plan.sql);
115
+ const statementType = classifyStatement(normalized);
116
+ if (statementType === "select") {
117
+ if (SELECT_STAR_REGEX.test(normalized)) lints.push(createLint("LINT.SELECT_STAR", "error", "Raw SQL plan selects all columns via *", { sql: snippet(plan.sql) }));
118
+ if (!LIMIT_REGEX.test(normalized)) {
119
+ const severity = config?.budgets?.unboundedSelectSeverity ?? "error";
120
+ lints.push(createLint("LINT.NO_LIMIT", "warn", "Raw SQL plan omits LIMIT clause", { sql: snippet(plan.sql) }));
121
+ budgets.push(createBudget("BUDGET.ROWS_EXCEEDED", severity, "Raw SQL plan is unbounded and may exceed row budget", {
122
+ sql: snippet(plan.sql),
123
+ ...config?.budgets?.estimatedRows !== void 0 ? { estimatedRows: config.budgets.estimatedRows } : {}
124
+ }));
125
+ }
126
+ }
127
+ if (isMutationStatement(statementType) && isReadOnlyIntent(plan.meta)) lints.push(createLint("LINT.READ_ONLY_MUTATION", "error", "Raw SQL plan mutates data despite read-only intent", {
128
+ sql: snippet(plan.sql),
129
+ intent: plan.meta.annotations?.["intent"]
130
+ }));
131
+ const refs = plan.meta.refs;
132
+ if (refs) evaluateIndexCoverage(refs, lints);
133
+ return {
134
+ lints,
135
+ budgets,
136
+ statement: statementType
137
+ };
138
+ }
139
+ function evaluateIndexCoverage(refs, lints) {
140
+ const predicateColumns = refs.columns ?? [];
141
+ if (predicateColumns.length === 0) return;
142
+ const indexes = refs.indexes ?? [];
143
+ if (indexes.length === 0) {
144
+ lints.push(createLint("LINT.UNINDEXED_PREDICATE", "warn", "Raw SQL plan predicates lack supporting indexes", { predicates: predicateColumns }));
145
+ return;
146
+ }
147
+ if (!predicateColumns.every((column) => indexes.some((index) => index.table === column.table && index.columns.some((col) => col.toLowerCase() === column.column.toLowerCase())))) lints.push(createLint("LINT.UNINDEXED_PREDICATE", "warn", "Raw SQL plan predicates lack supporting indexes", { predicates: predicateColumns }));
148
+ }
149
+ function classifyStatement(sql) {
150
+ const trimmed = sql.trim();
151
+ const lower = trimmed.toLowerCase();
152
+ if (lower.startsWith("with")) {
153
+ if (lower.includes("select")) return "select";
154
+ }
155
+ if (lower.startsWith("select")) return "select";
156
+ if (MUTATION_PREFIX_REGEX.test(trimmed)) return "mutation";
157
+ return "other";
158
+ }
159
+ function isMutationStatement(statement) {
160
+ return statement === "mutation";
161
+ }
162
+ function isReadOnlyIntent(meta) {
163
+ const annotations = meta.annotations;
164
+ const intent = typeof annotations?.intent === "string" ? annotations.intent.toLowerCase() : void 0;
165
+ return intent !== void 0 && READ_ONLY_INTENTS.has(intent);
166
+ }
167
+ function normalizeWhitespace(value) {
168
+ return value.replace(/\s+/g, " ").trim();
169
+ }
170
+ function snippet(sql) {
171
+ return normalizeWhitespace(sql).slice(0, 200);
172
+ }
173
+ function createLint(code, severity, message, details) {
174
+ return {
175
+ code,
176
+ severity,
177
+ message,
178
+ ...details ? { details } : {}
179
+ };
180
+ }
181
+ function createBudget(code, severity, message, details) {
182
+ return {
183
+ code,
184
+ severity,
185
+ message,
186
+ ...details ? { details } : {}
187
+ };
188
+ }
189
+
190
+ //#endregion
191
+ //#region src/marker.ts
192
+ const MetaSchema = type({ "[string]": "unknown" });
193
+ function parseMeta(meta) {
194
+ if (meta === null || meta === void 0) return {};
195
+ let parsed;
196
+ if (typeof meta === "string") try {
197
+ parsed = JSON.parse(meta);
198
+ } catch {
199
+ return {};
200
+ }
201
+ else parsed = meta;
202
+ const result = MetaSchema(parsed);
203
+ if (result instanceof type.errors) return {};
204
+ return result;
205
+ }
206
+ const ContractMarkerRowSchema = type({
207
+ core_hash: "string",
208
+ profile_hash: "string",
209
+ "contract_json?": "unknown | null",
210
+ "canonical_version?": "number | null",
211
+ "updated_at?": "Date | string",
212
+ "app_tag?": "string | null",
213
+ "meta?": "unknown | null"
214
+ });
215
+ function parseContractMarkerRow(row) {
216
+ const result = ContractMarkerRowSchema(row);
217
+ if (result instanceof type.errors) {
218
+ const messages = result.map((p) => p.message).join("; ");
219
+ throw new Error(`Invalid contract marker row: ${messages}`);
220
+ }
221
+ const validatedRow = result;
222
+ const updatedAt = validatedRow.updated_at ? validatedRow.updated_at instanceof Date ? validatedRow.updated_at : new Date(validatedRow.updated_at) : /* @__PURE__ */ new Date();
223
+ return {
224
+ storageHash: validatedRow.core_hash,
225
+ profileHash: validatedRow.profile_hash,
226
+ contractJson: validatedRow.contract_json ?? null,
227
+ canonicalVersion: validatedRow.canonical_version ?? null,
228
+ updatedAt,
229
+ appTag: validatedRow.app_tag ?? null,
230
+ meta: parseMeta(validatedRow.meta)
231
+ };
232
+ }
233
+
234
+ //#endregion
235
+ //#region src/runtime-core.ts
236
+ var RuntimeCoreImpl = class {
237
+ _typeContract;
238
+ _typeAdapter;
239
+ _typeDriver;
240
+ contract;
241
+ familyAdapter;
242
+ driver;
243
+ plugins;
244
+ mode;
245
+ verify;
246
+ pluginContext;
247
+ verified;
248
+ startupVerified;
249
+ _telemetry;
250
+ constructor(options) {
251
+ const { familyAdapter, driver } = options;
252
+ this.contract = familyAdapter.contract;
253
+ this.familyAdapter = familyAdapter;
254
+ this.driver = driver;
255
+ this.plugins = options.plugins ?? [];
256
+ this.mode = options.mode ?? "strict";
257
+ this.verify = options.verify;
258
+ this.verified = options.verify.mode === "startup" ? false : options.verify.mode === "always";
259
+ this.startupVerified = false;
260
+ this._telemetry = null;
261
+ this.pluginContext = {
262
+ contract: this.contract,
263
+ adapter: options.familyAdapter,
264
+ driver: this.driver,
265
+ mode: this.mode,
266
+ now: () => Date.now(),
267
+ log: options.log ?? {
268
+ info: () => {},
269
+ warn: () => {},
270
+ error: () => {}
271
+ }
272
+ };
273
+ }
274
+ async verifyPlanIfNeeded(_plan) {
275
+ if (this.verify.mode === "always") this.verified = false;
276
+ if (this.verified) return;
277
+ const readStatement = this.familyAdapter.markerReader.readMarkerStatement();
278
+ const result = await this.driver.query(readStatement.sql, readStatement.params);
279
+ if (result.rows.length === 0) {
280
+ if (this.verify.requireMarker) throw runtimeError("CONTRACT.MARKER_MISSING", "Contract marker not found in database");
281
+ this.verified = true;
282
+ return;
283
+ }
284
+ const marker = parseContractMarkerRow(result.rows[0]);
285
+ const contract = this.contract;
286
+ if (marker.storageHash !== contract.storage.storageHash) throw runtimeError("CONTRACT.MARKER_MISMATCH", "Database storage hash does not match contract", {
287
+ expected: contract.storage.storageHash,
288
+ actual: marker.storageHash
289
+ });
290
+ const expectedProfile = contract.profileHash ?? null;
291
+ if (expectedProfile !== null && marker.profileHash !== expectedProfile) throw runtimeError("CONTRACT.MARKER_MISMATCH", "Database profile hash does not match contract", {
292
+ expectedProfile,
293
+ actualProfile: marker.profileHash
294
+ });
295
+ this.verified = true;
296
+ this.startupVerified = true;
297
+ }
298
+ validatePlan(plan) {
299
+ this.familyAdapter.validatePlan(plan, this.contract);
300
+ }
301
+ recordTelemetry(plan, outcome, durationMs) {
302
+ const contract = this.contract;
303
+ this._telemetry = Object.freeze({
304
+ lane: plan.meta.lane,
305
+ target: contract.target,
306
+ fingerprint: computeSqlFingerprint(plan.sql),
307
+ outcome,
308
+ ...durationMs !== void 0 ? { durationMs } : {}
309
+ });
310
+ }
311
+ execute(plan) {
312
+ return this.#executeWith(plan, this.driver);
313
+ }
314
+ async connection() {
315
+ const driverConn = await this.driver.acquireConnection();
316
+ const self = this;
317
+ return {
318
+ async transaction() {
319
+ const driverTx = await driverConn.beginTransaction();
320
+ return {
321
+ async commit() {
322
+ await driverTx.commit();
323
+ },
324
+ async rollback() {
325
+ await driverTx.rollback();
326
+ },
327
+ execute(plan) {
328
+ return self.#executeWith(plan, driverTx);
329
+ }
330
+ };
331
+ },
332
+ execute(plan) {
333
+ return self.#executeWith(plan, driverConn);
334
+ },
335
+ async release() {
336
+ await driverConn.release();
337
+ }
338
+ };
339
+ }
340
+ telemetry() {
341
+ return this._telemetry;
342
+ }
343
+ close() {
344
+ const driver = this.driver;
345
+ if (typeof driver.close === "function") return driver.close();
346
+ return Promise.resolve();
347
+ }
348
+ #executeWith(plan, queryable) {
349
+ this.validatePlan(plan);
350
+ this._telemetry = null;
351
+ const iterator = async function* (self) {
352
+ const startedAt = Date.now();
353
+ let rowCount = 0;
354
+ let completed = false;
355
+ if (!self.startupVerified && self.verify.mode === "startup") await self.verifyPlanIfNeeded(plan);
356
+ if (self.verify.mode === "onFirstUse") await self.verifyPlanIfNeeded(plan);
357
+ try {
358
+ if (self.verify.mode === "always") await self.verifyPlanIfNeeded(plan);
359
+ for (const plugin of self.plugins) if (plugin.beforeExecute) await plugin.beforeExecute(plan, self.pluginContext);
360
+ const encodedParams = plan.params;
361
+ for await (const row of queryable.execute({
362
+ sql: plan.sql,
363
+ params: encodedParams
364
+ })) {
365
+ for (const plugin of self.plugins) if (plugin.onRow) await plugin.onRow(row, plan, self.pluginContext);
366
+ rowCount++;
367
+ yield row;
368
+ }
369
+ completed = true;
370
+ self.recordTelemetry(plan, "success", Date.now() - startedAt);
371
+ } catch (error) {
372
+ if (self._telemetry === null) self.recordTelemetry(plan, "runtime-error", Date.now() - startedAt);
373
+ const latencyMs$1 = Date.now() - startedAt;
374
+ for (const plugin of self.plugins) if (plugin.afterExecute) try {
375
+ await plugin.afterExecute(plan, {
376
+ rowCount,
377
+ latencyMs: latencyMs$1,
378
+ completed
379
+ }, self.pluginContext);
380
+ } catch {}
381
+ throw error;
382
+ }
383
+ const latencyMs = Date.now() - startedAt;
384
+ for (const plugin of self.plugins) if (plugin.afterExecute) await plugin.afterExecute(plan, {
385
+ rowCount,
386
+ latencyMs,
387
+ completed
388
+ }, self.pluginContext);
389
+ };
390
+ return new AsyncIterableResult(iterator(this));
391
+ }
392
+ };
393
+ function createRuntimeCore(options) {
394
+ return new RuntimeCoreImpl(options);
395
+ }
396
+
397
+ //#endregion
398
+ export { AsyncIterableResult, computeSqlFingerprint, createRuntimeCore, evaluateRawGuardrails, parseContractMarkerRow, runtimeError };
399
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +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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"}
package/package.json CHANGED
@@ -1,40 +1,47 @@
1
1
  {
2
2
  "name": "@prisma-next/runtime-executor",
3
- "version": "0.3.0-dev.14",
3
+ "version": "0.3.0-dev.147",
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/contract": "0.3.0-dev.14",
10
- "@prisma-next/operations": "0.3.0-dev.14"
9
+ "@prisma-next/contract": "0.3.0-dev.147",
10
+ "@prisma-next/operations": "0.3.0-dev.147"
11
11
  },
12
12
  "devDependencies": {
13
13
  "@prisma/dev": "0.19.1",
14
- "@vitest/coverage-v8": "4.0.16",
15
- "tsup": "8.5.1",
14
+ "tsdown": "0.18.4",
16
15
  "typescript": "5.9.3",
17
- "vitest": "4.0.16",
18
- "@prisma-next/test-utils": "0.0.1"
16
+ "vitest": "4.0.17",
17
+ "@prisma-next/test-utils": "0.0.1",
18
+ "@prisma-next/tsconfig": "0.0.0",
19
+ "@prisma-next/tsdown": "0.0.0"
19
20
  },
20
21
  "files": [
21
22
  "dist",
22
23
  "src"
23
24
  ],
24
25
  "exports": {
25
- ".": {
26
- "types": "./dist/index.d.ts",
27
- "import": "./dist/index.js"
28
- }
26
+ ".": "./dist/index.mjs",
27
+ "./package.json": "./package.json"
28
+ },
29
+ "main": "./dist/index.mjs",
30
+ "module": "./dist/index.mjs",
31
+ "types": "./dist/index.d.mts",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/prisma/prisma-next.git",
35
+ "directory": "packages/1-framework/4-runtime/runtime-executor"
29
36
  },
30
37
  "scripts": {
31
- "build": "tsup --config tsup.config.ts && tsc --project tsconfig.build.json",
38
+ "build": "tsdown",
32
39
  "test": "vitest run",
33
40
  "test:coverage": "vitest run --coverage",
34
41
  "typecheck": "tsc --project tsconfig.json --noEmit",
35
42
  "lint": "biome check . --error-on-warnings",
36
43
  "lint:fix": "biome check --write .",
37
44
  "lint:fix:unsafe": "biome check --write --unsafe .",
38
- "clean": "node ../../../scripts/clean.mjs"
45
+ "clean": "rm -rf dist dist-tsc dist-tsc-prod coverage .tmp-output"
39
46
  }
40
47
  }
@@ -4,10 +4,11 @@ import { runtimeError } from './errors';
4
4
  * Custom async iterable result that extends AsyncIterable with a toArray() method.
5
5
  * This provides a convenient way to collect all results from an async iterator.
6
6
  */
7
- export class AsyncIterableResult<Row> implements AsyncIterable<Row> {
7
+ export class AsyncIterableResult<Row> implements AsyncIterable<Row>, PromiseLike<Row[]> {
8
8
  private readonly generator: AsyncGenerator<Row, void, unknown>;
9
9
  private consumed = false;
10
- private consumedBy: 'toArray' | 'iterator' | undefined;
10
+ private consumedBy: 'bufferedArray' | 'iterator' | undefined;
11
+ private bufferedArrayPromise: Promise<Row[]> | undefined;
11
12
 
12
13
  constructor(generator: AsyncGenerator<Row, void, unknown>) {
13
14
  this.generator = generator;
@@ -17,11 +18,11 @@ export class AsyncIterableResult<Row> implements AsyncIterable<Row> {
17
18
  if (this.consumed) {
18
19
  throw runtimeError(
19
20
  'RUNTIME.ITERATOR_CONSUMED',
20
- `AsyncIterableResult iterator has already been consumed via ${this.consumedBy === 'toArray' ? 'toArray()' : 'for-await loop'}. Each AsyncIterableResult can only be iterated once.`,
21
+ `AsyncIterableResult iterator has already been consumed via ${this.consumedBy === 'bufferedArray' ? 'toArray()/then()' : 'for-await loop'}. Each AsyncIterableResult can only be iterated once.`,
21
22
  {
22
23
  consumedBy: this.consumedBy,
23
24
  suggestion:
24
- this.consumedBy === 'toArray'
25
+ this.consumedBy === 'bufferedArray'
25
26
  ? 'If you need to iterate multiple times, store the results from toArray() in a variable and reuse that.'
26
27
  : 'If you need to iterate multiple times, use toArray() to collect all results first.',
27
28
  },
@@ -36,26 +37,64 @@ export class AsyncIterableResult<Row> implements AsyncIterable<Row> {
36
37
  * Collects all values from the async iterator into an array.
37
38
  * Once called, the iterator is consumed and cannot be reused.
38
39
  */
39
- async toArray(): Promise<Row[]> {
40
- if (this.consumed) {
41
- throw runtimeError(
42
- 'RUNTIME.ITERATOR_CONSUMED',
43
- `AsyncIterableResult iterator has already been consumed via ${this.consumedBy === 'toArray' ? 'toArray()' : 'for-await loop'}. Each AsyncIterableResult can only be iterated once.`,
44
- {
45
- consumedBy: this.consumedBy,
46
- suggestion:
47
- this.consumedBy === 'toArray'
48
- ? 'You cannot call toArray() twice on the same AsyncIterableResult. Store the result from the first call in a variable and reuse that.'
49
- : 'The iterator was already consumed by a for-await loop. Use toArray() to collect all results before iterating.',
50
- },
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
+ ),
51
52
  );
52
53
  }
53
- this.consumed = true;
54
- this.consumedBy = 'toArray';
55
- const out: Row[] = [];
56
- for await (const item of this.generator) {
57
- out.push(item);
54
+
55
+ if (this.bufferedArrayPromise) {
56
+ return this.bufferedArrayPromise;
58
57
  }
59
- return out;
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);
60
99
  }
61
100
  }
@@ -4,12 +4,7 @@ export { runtimeError } from '../errors';
4
4
  export { computeSqlFingerprint } from '../fingerprint';
5
5
  export type { BudgetFinding, LintFinding, RawGuardrailResult } from '../guardrails/raw';
6
6
  export { evaluateRawGuardrails } from '../guardrails/raw';
7
- export type { ContractMarkerRecord } from '../marker';
8
7
  export { parseContractMarkerRow } from '../marker';
9
- export type { BudgetsOptions } from '../plugins/budgets';
10
- export { budgets } from '../plugins/budgets';
11
- export type { LintsOptions } from '../plugins/lints';
12
- export { lints } from '../plugins/lints';
13
8
  export type {
14
9
  AfterExecuteResult,
15
10
  Log,
@@ -18,9 +13,12 @@ export type {
18
13
  Severity,
19
14
  } from '../plugins/types';
20
15
  export type {
16
+ RuntimeConnection,
21
17
  RuntimeCore,
22
18
  RuntimeCoreOptions,
19
+ RuntimeQueryable,
23
20
  RuntimeTelemetryEvent,
21
+ RuntimeTransaction,
24
22
  RuntimeVerifyOptions,
25
23
  TelemetryOutcome,
26
24
  } from '../runtime-core';