@prisma-next/runtime-executor 0.3.0-dev.34 → 0.3.0-dev.35

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,599 @@
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
+ then(onfulfilled, onrejected) {
73
+ return this.toArray().then(onfulfilled, onrejected);
74
+ }
75
+ };
76
+
77
+ //#endregion
78
+ //#region src/fingerprint.ts
79
+ const STRING_LITERAL_REGEX = /'(?:''|[^'])*'/g;
80
+ const NUMERIC_LITERAL_REGEX = /\b\d+(?:\.\d+)?\b/g;
81
+ const WHITESPACE_REGEX = /\s+/g;
82
+ function computeSqlFingerprint(sql) {
83
+ const normalized = sql.replace(STRING_LITERAL_REGEX, "?").replace(NUMERIC_LITERAL_REGEX, "?").replace(WHITESPACE_REGEX, " ").trim().toLowerCase();
84
+ return `sha256:${createHash("sha256").update(normalized).digest("hex")}`;
85
+ }
86
+
87
+ //#endregion
88
+ //#region src/guardrails/raw.ts
89
+ const SELECT_STAR_REGEX = /select\s+\*/i;
90
+ const LIMIT_REGEX = /\blimit\b/i;
91
+ const MUTATION_PREFIX_REGEX = /^(insert|update|delete|create|alter|drop|truncate)\b/i;
92
+ const READ_ONLY_INTENTS = new Set([
93
+ "read",
94
+ "report",
95
+ "readonly"
96
+ ]);
97
+ function evaluateRawGuardrails(plan, config) {
98
+ const lints$1 = [];
99
+ const budgets$1 = [];
100
+ const normalized = normalizeWhitespace(plan.sql);
101
+ const statementType = classifyStatement(normalized);
102
+ if (statementType === "select") {
103
+ if (SELECT_STAR_REGEX.test(normalized)) lints$1.push(createLint("LINT.SELECT_STAR", "error", "Raw SQL plan selects all columns via *", { sql: snippet(plan.sql) }));
104
+ if (!LIMIT_REGEX.test(normalized)) {
105
+ const severity = config?.budgets?.unboundedSelectSeverity ?? "error";
106
+ lints$1.push(createLint("LINT.NO_LIMIT", "warn", "Raw SQL plan omits LIMIT clause", { sql: snippet(plan.sql) }));
107
+ budgets$1.push(createBudget("BUDGET.ROWS_EXCEEDED", severity, "Raw SQL plan is unbounded and may exceed row budget", {
108
+ sql: snippet(plan.sql),
109
+ ...config?.budgets?.estimatedRows !== void 0 ? { estimatedRows: config.budgets.estimatedRows } : {}
110
+ }));
111
+ }
112
+ }
113
+ if (isMutationStatement(statementType) && isReadOnlyIntent(plan.meta)) lints$1.push(createLint("LINT.READ_ONLY_MUTATION", "error", "Raw SQL plan mutates data despite read-only intent", {
114
+ sql: snippet(plan.sql),
115
+ intent: plan.meta.annotations?.["intent"]
116
+ }));
117
+ const refs = plan.meta.refs;
118
+ if (refs) evaluateIndexCoverage(refs, lints$1);
119
+ return {
120
+ lints: lints$1,
121
+ budgets: budgets$1,
122
+ statement: statementType
123
+ };
124
+ }
125
+ function evaluateIndexCoverage(refs, lints$1) {
126
+ const predicateColumns = refs.columns ?? [];
127
+ if (predicateColumns.length === 0) return;
128
+ const indexes = refs.indexes ?? [];
129
+ if (indexes.length === 0) {
130
+ lints$1.push(createLint("LINT.UNINDEXED_PREDICATE", "warn", "Raw SQL plan predicates lack supporting indexes", { predicates: predicateColumns }));
131
+ return;
132
+ }
133
+ if (!predicateColumns.every((column) => indexes.some((index) => index.table === column.table && index.columns.some((col) => col.toLowerCase() === column.column.toLowerCase())))) lints$1.push(createLint("LINT.UNINDEXED_PREDICATE", "warn", "Raw SQL plan predicates lack supporting indexes", { predicates: predicateColumns }));
134
+ }
135
+ function classifyStatement(sql) {
136
+ const trimmed = sql.trim();
137
+ const lower = trimmed.toLowerCase();
138
+ if (lower.startsWith("with")) {
139
+ if (lower.includes("select")) return "select";
140
+ }
141
+ if (lower.startsWith("select")) return "select";
142
+ if (MUTATION_PREFIX_REGEX.test(trimmed)) return "mutation";
143
+ return "other";
144
+ }
145
+ function isMutationStatement(statement) {
146
+ return statement === "mutation";
147
+ }
148
+ function isReadOnlyIntent(meta) {
149
+ const annotations = meta.annotations;
150
+ const intent = typeof annotations?.intent === "string" ? annotations.intent.toLowerCase() : void 0;
151
+ return intent !== void 0 && READ_ONLY_INTENTS.has(intent);
152
+ }
153
+ function normalizeWhitespace(value) {
154
+ return value.replace(/\s+/g, " ").trim();
155
+ }
156
+ function snippet(sql) {
157
+ return normalizeWhitespace(sql).slice(0, 200);
158
+ }
159
+ function createLint(code, severity, message, details) {
160
+ return {
161
+ code,
162
+ severity,
163
+ message,
164
+ ...details ? { details } : {}
165
+ };
166
+ }
167
+ function createBudget(code, severity, message, details) {
168
+ return {
169
+ code,
170
+ severity,
171
+ message,
172
+ ...details ? { details } : {}
173
+ };
174
+ }
175
+
176
+ //#endregion
177
+ //#region src/marker.ts
178
+ const MetaSchema = type({ "[string]": "unknown" });
179
+ function parseMeta(meta) {
180
+ if (meta === null || meta === void 0) return {};
181
+ let parsed;
182
+ if (typeof meta === "string") try {
183
+ parsed = JSON.parse(meta);
184
+ } catch {
185
+ return {};
186
+ }
187
+ else parsed = meta;
188
+ const result = MetaSchema(parsed);
189
+ if (result instanceof type.errors) return {};
190
+ return result;
191
+ }
192
+ const ContractMarkerRowSchema = type({
193
+ core_hash: "string",
194
+ profile_hash: "string",
195
+ "contract_json?": "unknown | null",
196
+ "canonical_version?": "number | null",
197
+ "updated_at?": "Date | string",
198
+ "app_tag?": "string | null",
199
+ "meta?": "unknown | null"
200
+ });
201
+ function parseContractMarkerRow(row) {
202
+ const result = ContractMarkerRowSchema(row);
203
+ if (result instanceof type.errors) {
204
+ const messages = result.map((p) => p.message).join("; ");
205
+ throw new Error(`Invalid contract marker row: ${messages}`);
206
+ }
207
+ const validatedRow = result;
208
+ const updatedAt = validatedRow.updated_at ? validatedRow.updated_at instanceof Date ? validatedRow.updated_at : new Date(validatedRow.updated_at) : /* @__PURE__ */ new Date();
209
+ return {
210
+ storageHash: validatedRow.core_hash,
211
+ profileHash: validatedRow.profile_hash,
212
+ contractJson: validatedRow.contract_json ?? null,
213
+ canonicalVersion: validatedRow.canonical_version ?? null,
214
+ updatedAt,
215
+ appTag: validatedRow.app_tag ?? null,
216
+ meta: parseMeta(validatedRow.meta)
217
+ };
218
+ }
219
+
220
+ //#endregion
221
+ //#region src/plugins/budgets.ts
222
+ async function computeEstimatedRows(plan, driver) {
223
+ if (typeof driver.explain !== "function") return;
224
+ try {
225
+ return extractEstimatedRows((await driver.explain(plan.sql, [...plan.params])).rows);
226
+ } catch {
227
+ return;
228
+ }
229
+ }
230
+ function extractEstimatedRows(rows) {
231
+ for (const row of rows) {
232
+ const estimate = findPlanRows(row);
233
+ if (estimate !== void 0) return estimate;
234
+ }
235
+ }
236
+ function findPlanRows(node) {
237
+ if (!node || typeof node !== "object") return;
238
+ const explainNode = node;
239
+ const planRows = explainNode["Plan Rows"];
240
+ if (typeof planRows === "number") return planRows;
241
+ if ("Plan" in explainNode && explainNode.Plan !== void 0) {
242
+ const nested = findPlanRows(explainNode.Plan);
243
+ if (nested !== void 0) return nested;
244
+ }
245
+ if (Array.isArray(explainNode.Plans)) for (const child of explainNode.Plans) {
246
+ const nested = findPlanRows(child);
247
+ if (nested !== void 0) return nested;
248
+ }
249
+ for (const value of Object.values(node)) if (typeof value === "object" && value !== null) {
250
+ const nested = findPlanRows(value);
251
+ if (nested !== void 0) return nested;
252
+ }
253
+ }
254
+ function budgetError(code, message, details) {
255
+ const error = new Error(message);
256
+ Object.defineProperty(error, "name", {
257
+ value: "RuntimeError",
258
+ configurable: true
259
+ });
260
+ return Object.assign(error, {
261
+ code,
262
+ category: "BUDGET",
263
+ severity: "error",
264
+ details
265
+ });
266
+ }
267
+ function estimateRows(plan, tableRows, defaultTableRows) {
268
+ if (!plan.ast) return null;
269
+ const table = plan.meta.refs?.tables?.[0];
270
+ if (!table) return null;
271
+ const tableEstimate = tableRows[table] ?? defaultTableRows;
272
+ if (plan.ast && typeof plan.ast === "object" && "kind" in plan.ast && plan.ast.kind === "select" && "limit" in plan.ast && typeof plan.ast.limit === "number") return Math.min(plan.ast.limit, tableEstimate);
273
+ return tableEstimate;
274
+ }
275
+ function hasDetectableLimit(plan) {
276
+ if (plan.ast && typeof plan.ast === "object" && "kind" in plan.ast && plan.ast.kind === "select" && "limit" in plan.ast && typeof plan.ast.limit === "number") return true;
277
+ const annotations = plan.meta.annotations;
278
+ return typeof annotations?.limit === "number" || typeof annotations?.LIMIT === "number";
279
+ }
280
+ function budgets(options) {
281
+ const maxRows = options?.maxRows ?? 1e4;
282
+ const defaultTableRows = options?.defaultTableRows ?? 1e4;
283
+ const tableRows = options?.tableRows ?? {};
284
+ const maxLatencyMs = options?.maxLatencyMs ?? 1e3;
285
+ const rowSeverity = options?.severities?.rowCount ?? "error";
286
+ const latencySeverity = options?.severities?.latency ?? "warn";
287
+ let observedRows = 0;
288
+ return Object.freeze({
289
+ name: "budgets",
290
+ async beforeExecute(plan, ctx) {
291
+ observedRows = 0;
292
+ ctx.now();
293
+ const estimated = estimateRows(plan, tableRows, defaultTableRows);
294
+ const isUnbounded = !hasDetectableLimit(plan);
295
+ const isSelect = plan.sql.trimStart().toUpperCase().startsWith("SELECT");
296
+ if (isSelect && isUnbounded) {
297
+ if (estimated !== null && estimated >= maxRows) {
298
+ const error$1 = budgetError("BUDGET.ROWS_EXCEEDED", "Unbounded SELECT query exceeds budget", {
299
+ source: "heuristic",
300
+ estimatedRows: estimated,
301
+ maxRows
302
+ });
303
+ if (rowSeverity === "error" || ctx.mode === "strict") throw error$1;
304
+ ctx.log.warn({
305
+ code: error$1.code,
306
+ message: error$1.message,
307
+ details: error$1.details
308
+ });
309
+ return;
310
+ }
311
+ const error = budgetError("BUDGET.ROWS_EXCEEDED", "Unbounded SELECT query exceeds budget", {
312
+ source: "heuristic",
313
+ maxRows
314
+ });
315
+ if (rowSeverity === "error" || ctx.mode === "strict") throw error;
316
+ ctx.log.warn({
317
+ code: error.code,
318
+ message: error.message,
319
+ details: error.details
320
+ });
321
+ return;
322
+ }
323
+ if (estimated !== null) {
324
+ if (estimated > maxRows) {
325
+ const error = budgetError("BUDGET.ROWS_EXCEEDED", "Estimated row count exceeds budget", {
326
+ source: "heuristic",
327
+ estimatedRows: estimated,
328
+ maxRows
329
+ });
330
+ if (rowSeverity === "error" || ctx.mode === "strict") throw error;
331
+ ctx.log.warn({
332
+ code: error.code,
333
+ message: error.message,
334
+ details: error.details
335
+ });
336
+ }
337
+ return;
338
+ }
339
+ if (!plan.ast) {
340
+ if (options?.explain?.enabled === true && isSelect && typeof ctx.driver === "object" && ctx.driver !== null) {
341
+ const estimatedRows = await computeEstimatedRows(plan, ctx.driver);
342
+ if (estimatedRows !== void 0) {
343
+ if (estimatedRows > maxRows) {
344
+ const error = budgetError("BUDGET.ROWS_EXCEEDED", "Estimated row count exceeds budget", {
345
+ source: "explain",
346
+ estimatedRows,
347
+ maxRows
348
+ });
349
+ if (rowSeverity === "error" || ctx.mode === "strict") throw error;
350
+ ctx.log.warn({
351
+ code: error.code,
352
+ message: error.message,
353
+ details: error.details
354
+ });
355
+ }
356
+ return;
357
+ }
358
+ }
359
+ }
360
+ },
361
+ async onRow(_row, _plan, _ctx) {
362
+ observedRows += 1;
363
+ if (observedRows > maxRows) throw budgetError("BUDGET.ROWS_EXCEEDED", "Observed row count exceeds budget", {
364
+ source: "observed",
365
+ observedRows,
366
+ maxRows
367
+ });
368
+ },
369
+ async afterExecute(_plan, result, ctx) {
370
+ const latencyMs = result.latencyMs;
371
+ if (latencyMs > maxLatencyMs) {
372
+ const error = budgetError("BUDGET.TIME_EXCEEDED", "Query latency exceeds budget", {
373
+ latencyMs,
374
+ maxLatencyMs
375
+ });
376
+ if (latencySeverity === "error" && ctx.mode === "strict") throw error;
377
+ ctx.log.warn({
378
+ code: error.code,
379
+ message: error.message,
380
+ details: error.details
381
+ });
382
+ }
383
+ }
384
+ });
385
+ }
386
+
387
+ //#endregion
388
+ //#region src/plugins/lints.ts
389
+ function lintError(code, message, details) {
390
+ const error = new Error(message);
391
+ Object.defineProperty(error, "name", {
392
+ value: "RuntimeError",
393
+ configurable: true
394
+ });
395
+ return Object.assign(error, {
396
+ code,
397
+ category: "LINT",
398
+ severity: "error",
399
+ details
400
+ });
401
+ }
402
+ function lints(options) {
403
+ return Object.freeze({
404
+ name: "lints",
405
+ async beforeExecute(plan, ctx) {
406
+ if (plan.ast) return;
407
+ const evaluation = evaluateRawGuardrails(plan);
408
+ for (const lint of evaluation.lints) {
409
+ const effectiveSeverity = getConfiguredSeverity(lint.code, options) ?? lint.severity;
410
+ if (effectiveSeverity === "error") throw lintError(lint.code, lint.message, lint.details);
411
+ if (effectiveSeverity === "warn") ctx.log.warn({
412
+ code: lint.code,
413
+ message: lint.message,
414
+ details: lint.details
415
+ });
416
+ }
417
+ }
418
+ });
419
+ }
420
+ function getConfiguredSeverity(code, options) {
421
+ const severities = options?.severities;
422
+ if (!severities) return;
423
+ if (code === "LINT.SELECT_STAR") return severities.selectStar;
424
+ if (code === "LINT.NO_LIMIT") return severities.noLimit;
425
+ if (code === "LINT.READ_ONLY_MUTATION") return severities.readOnlyMutation;
426
+ if (code === "LINT.UNINDEXED_PREDICATE") return severities.unindexedPredicate;
427
+ }
428
+
429
+ //#endregion
430
+ //#region src/runtime-core.ts
431
+ var RuntimeCoreImpl = class {
432
+ _typeContract;
433
+ _typeAdapter;
434
+ _typeDriver;
435
+ contract;
436
+ familyAdapter;
437
+ driver;
438
+ plugins;
439
+ mode;
440
+ verify;
441
+ operationRegistry;
442
+ pluginContext;
443
+ verified;
444
+ startupVerified;
445
+ _telemetry;
446
+ constructor(options) {
447
+ const { familyAdapter, driver } = options;
448
+ this.contract = familyAdapter.contract;
449
+ this.familyAdapter = familyAdapter;
450
+ this.driver = driver;
451
+ this.plugins = options.plugins ?? [];
452
+ this.mode = options.mode ?? "strict";
453
+ this.verify = options.verify;
454
+ this.operationRegistry = options.operationRegistry;
455
+ this.verified = options.verify.mode === "startup" ? false : options.verify.mode === "always";
456
+ this.startupVerified = false;
457
+ this._telemetry = null;
458
+ this.pluginContext = {
459
+ contract: this.contract,
460
+ adapter: options.familyAdapter,
461
+ driver: this.driver,
462
+ mode: this.mode,
463
+ now: () => Date.now(),
464
+ log: options.log ?? {
465
+ info: () => {},
466
+ warn: () => {},
467
+ error: () => {}
468
+ }
469
+ };
470
+ }
471
+ async verifyPlanIfNeeded(_plan) {
472
+ if (this.verify.mode === "always") this.verified = false;
473
+ if (this.verified) return;
474
+ const readStatement = this.familyAdapter.markerReader.readMarkerStatement();
475
+ const result = await this.driver.query(readStatement.sql, readStatement.params);
476
+ if (result.rows.length === 0) {
477
+ if (this.verify.requireMarker) throw runtimeError("CONTRACT.MARKER_MISSING", "Contract marker not found in database");
478
+ this.verified = true;
479
+ return;
480
+ }
481
+ const marker = parseContractMarkerRow(result.rows[0]);
482
+ const contract = this.contract;
483
+ if (marker.storageHash !== contract.storageHash) throw runtimeError("CONTRACT.MARKER_MISMATCH", "Database storage hash does not match contract", {
484
+ expected: contract.storageHash,
485
+ actual: marker.storageHash
486
+ });
487
+ const expectedProfile = contract.profileHash ?? null;
488
+ if (expectedProfile !== null && marker.profileHash !== expectedProfile) throw runtimeError("CONTRACT.MARKER_MISMATCH", "Database profile hash does not match contract", {
489
+ expectedProfile,
490
+ actualProfile: marker.profileHash
491
+ });
492
+ this.verified = true;
493
+ this.startupVerified = true;
494
+ }
495
+ validatePlan(plan) {
496
+ this.familyAdapter.validatePlan(plan, this.contract);
497
+ }
498
+ recordTelemetry(plan, outcome, durationMs) {
499
+ const contract = this.contract;
500
+ this._telemetry = Object.freeze({
501
+ lane: plan.meta.lane,
502
+ target: contract.target,
503
+ fingerprint: computeSqlFingerprint(plan.sql),
504
+ outcome,
505
+ ...durationMs !== void 0 ? { durationMs } : {}
506
+ });
507
+ }
508
+ execute(plan) {
509
+ return this.#executeWith(plan, this.driver);
510
+ }
511
+ async connection() {
512
+ const driverConn = await this.driver.acquireConnection();
513
+ const self = this;
514
+ return {
515
+ async transaction() {
516
+ const driverTx = await driverConn.beginTransaction();
517
+ return {
518
+ async commit() {
519
+ await driverTx.commit();
520
+ },
521
+ async rollback() {
522
+ await driverTx.rollback();
523
+ },
524
+ execute(plan) {
525
+ return self.#executeWith(plan, driverTx);
526
+ }
527
+ };
528
+ },
529
+ execute(plan) {
530
+ return self.#executeWith(plan, driverConn);
531
+ },
532
+ async release() {
533
+ await driverConn.release();
534
+ }
535
+ };
536
+ }
537
+ telemetry() {
538
+ return this._telemetry;
539
+ }
540
+ operations() {
541
+ return this.operationRegistry;
542
+ }
543
+ close() {
544
+ const driver = this.driver;
545
+ if (typeof driver.close === "function") return driver.close();
546
+ return Promise.resolve();
547
+ }
548
+ #executeWith(plan, queryable) {
549
+ this.validatePlan(plan);
550
+ this._telemetry = null;
551
+ const iterator = async function* (self) {
552
+ const startedAt = Date.now();
553
+ let rowCount = 0;
554
+ let completed = false;
555
+ if (!self.startupVerified && self.verify.mode === "startup") await self.verifyPlanIfNeeded(plan);
556
+ if (self.verify.mode === "onFirstUse") await self.verifyPlanIfNeeded(plan);
557
+ try {
558
+ if (self.verify.mode === "always") await self.verifyPlanIfNeeded(plan);
559
+ for (const plugin of self.plugins) if (plugin.beforeExecute) await plugin.beforeExecute(plan, self.pluginContext);
560
+ const encodedParams = plan.params;
561
+ for await (const row of queryable.execute({
562
+ sql: plan.sql,
563
+ params: encodedParams
564
+ })) {
565
+ for (const plugin of self.plugins) if (plugin.onRow) await plugin.onRow(row, plan, self.pluginContext);
566
+ rowCount++;
567
+ yield row;
568
+ }
569
+ completed = true;
570
+ self.recordTelemetry(plan, "success", Date.now() - startedAt);
571
+ } catch (error) {
572
+ if (self._telemetry === null) self.recordTelemetry(plan, "runtime-error", Date.now() - startedAt);
573
+ const latencyMs$1 = Date.now() - startedAt;
574
+ for (const plugin of self.plugins) if (plugin.afterExecute) try {
575
+ await plugin.afterExecute(plan, {
576
+ rowCount,
577
+ latencyMs: latencyMs$1,
578
+ completed
579
+ }, self.pluginContext);
580
+ } catch {}
581
+ throw error;
582
+ }
583
+ const latencyMs = Date.now() - startedAt;
584
+ for (const plugin of self.plugins) if (plugin.afterExecute) await plugin.afterExecute(plan, {
585
+ rowCount,
586
+ latencyMs,
587
+ completed
588
+ }, self.pluginContext);
589
+ };
590
+ return new AsyncIterableResult(iterator(this));
591
+ }
592
+ };
593
+ function createRuntimeCore(options) {
594
+ return new RuntimeCoreImpl(options);
595
+ }
596
+
597
+ //#endregion
598
+ export { AsyncIterableResult, budgets, computeSqlFingerprint, createRuntimeCore, evaluateRawGuardrails, lints, parseContractMarkerRow, runtimeError };
599
+ //# sourceMappingURL=index.mjs.map