@prisma/orm-family-sql 8.0.0-rc.17-dev.26 → 8.0.0-rc.17-dev.28

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/runtime.mjs CHANGED
@@ -4,7 +4,7 @@ import { t as collectOrderedParamRefs } from "./util-BBgcP3gI-XJBkxo1K.mjs";
4
4
  import { t as createSqlParamRefMutator } from "./middleware-yBAfiVWn-CzNCthJT.mjs";
5
5
  import { t as buildSqlAggregateDescriptorRegistry } from "./aggregate-descriptor-registry-DrJ1vHEo.mjs";
6
6
  import { t as buildCodecDescriptorRegistry } from "./codec-descriptor-registry-CYK0Wzja.mjs";
7
- import { i as runPreparedQuery, n as preparedStatementQuery, r as runPreparedExecute, t as preparedStatementExecute } from "./prepared-query-BJr50wPm-ChoqfJbX.mjs";
7
+ import { a as runPreparedQuery, c as transactionStart, i as runPreparedExecute, l as wasRefusedBeforeBegin, n as preparedStatementExecute, o as settlementOf, r as preparedStatementQuery, s as transactionSettlement, t as createTransactionStart } from "./transaction-state-BfXhs7D7-3k10jZ8I.mjs";
8
8
  import { isStructuredError, structuredError } from "@prisma/orm-framework/utils/structured-error";
9
9
  import { ifDefined } from "@prisma/orm-framework/utils/defined";
10
10
  import { createHash } from "node:crypto";
@@ -13,11 +13,11 @@ import { blindCast } from "@prisma/orm-framework/utils/casts";
13
13
  import { assertNever, isInternalError } from "@prisma/orm-framework/utils/internal-error";
14
14
  import { canonicalStringify } from "@prisma/orm-framework/utils/canonical-stringify";
15
15
  import { checkContractComponentRequirements, mergeCapabilityMatrices } from "@prisma/orm-framework/components/components";
16
- import { AsyncIterableResult, RuntimeCore, applyMutationDefaults, assertMutationDefaultGeneratorsAvailable, checkAborted, checkMiddlewareCompatibility, collectMutationDefaultGenerators, onQueryEndOutsideTransaction, raceAgainstAbort, reportExecuteEnding, reportQueryEnding, runBeforeExecuteChain, runBeforeQueryChain, runExecuteWithMiddleware, runQueryWithMiddleware, runtimeError as runtimeError$1 } from "@prisma/orm-framework/components/runtime";
16
+ import { AsyncIterableResult, RuntimeCore, applyMutationDefaults, assertMutationDefaultGeneratorsAvailable, checkAborted, checkMiddlewareCompatibility, collectMutationDefaultGenerators, createStateStore, executeStatement, raceAgainstAbort, runtimeError as runtimeError$1, startTransactionActivity, streamStatement } from "@prisma/orm-framework/components/runtime";
17
17
  import { canonicalizeJson } from "@prisma/orm-framework/components/utils";
18
18
  import { createExecutionStack } from "@prisma/orm-framework/components/execution";
19
19
  import { hashContent } from "@prisma/orm-framework/utils/hash-content";
20
- //#region ../../../2-sql/5-runtime/dist/exports-Dkz8HVrQ.mjs
20
+ //#region ../../../2-sql/5-runtime/dist/exports-BxFmkVE5.mjs
21
21
  /**
22
22
  * Build an {@link AstCodecResolver} bound to a descriptor registry and a per-call instance-context factory.
23
23
  *
@@ -216,6 +216,7 @@ function emitBudgetViolation(error, shouldBlock, ctx) {
216
216
  details: error.details
217
217
  });
218
218
  }
219
+ const observedRows = Symbol("budgets.observedRows");
219
220
  function budgets(options) {
220
221
  const maxRows = options?.maxRows ?? 1e4;
221
222
  const defaultTableRows = options?.defaultTableRows ?? 1e4;
@@ -223,39 +224,38 @@ function budgets(options) {
223
224
  const maxLatencyMs = options?.maxLatencyMs ?? 1e3;
224
225
  const rowSeverity = options?.severities?.rowCount ?? "error";
225
226
  const latencySeverity = options?.severities?.latency ?? "warn";
226
- const observedRowsByPlan = /* @__PURE__ */ new WeakMap();
227
- const beforeOperation = async (plan, ctx) => {
228
- observedRowsByPlan.set(plan, { count: 0 });
229
- if (isQueryAst(plan.ast) && plan.ast.kind === "select") evaluateSelectAst(plan.ast, ctx);
230
- };
231
- const afterOperation = async (_plan, result, ctx) => {
232
- const latencyMs = result.latencyMs;
233
- if (latencyMs > maxLatencyMs) {
234
- const shouldBlock = latencySeverity === "error" || ctx.mode === "strict";
235
- emitBudgetViolation(runtimeError$1("BUDGET.TIME_EXCEEDED", "Query latency exceeds budget", {
236
- latencyMs,
237
- maxLatencyMs
238
- }), shouldBlock, ctx);
239
- }
240
- };
241
227
  return Object.freeze({
242
228
  name: "budgets",
243
229
  familyId: "sql",
244
- beforeQuery: beforeOperation,
245
- beforeExecute: beforeOperation,
246
- async onRow(_row, plan, _ctx) {
247
- const state = observedRowsByPlan.get(plan);
248
- if (!state) return;
249
- state.count += 1;
250
- if (state.count > maxRows) throw runtimeError$1("BUDGET.ROWS_EXCEEDED", "Observed row count exceeds budget", {
230
+ async beforeEncode(plan, ctx) {
231
+ if (isQueryAst(plan.ast) && plan.ast.kind === "select") evaluateSelectAst(plan.ast, ctx);
232
+ },
233
+ async onRow(_row, _plan, ctx) {
234
+ const observed = ctx.state(observedRows);
235
+ const count = (typeof observed["count"] === "number" ? observed["count"] : 0) + 1;
236
+ observed["count"] = count;
237
+ if (count <= maxRows || observed["warned"] === true) return;
238
+ const shouldBlock = rowSeverity === "error" || ctx.mode === "strict";
239
+ if (!shouldBlock) observed["warned"] = true;
240
+ emitBudgetViolation(runtimeError$1("BUDGET.ROWS_EXCEEDED", "Observed row count exceeds budget", {
251
241
  source: "observed",
252
- observedRows: state.count,
242
+ observedRows: count,
253
243
  maxRows
254
- });
244
+ }), shouldBlock, ctx);
255
245
  },
256
- afterQuery: afterOperation,
257
- afterExecute: afterOperation
246
+ async activityEnded(_activity, result, ctx) {
247
+ if (result.kind === "statement") checkLatency(result.latencyMs, ctx);
248
+ }
258
249
  });
250
+ function checkLatency(latencyMs, ctx) {
251
+ if (latencyMs > maxLatencyMs) {
252
+ const shouldBlock = latencySeverity === "error" || ctx.mode === "strict";
253
+ emitBudgetViolation(runtimeError$1("BUDGET.TIME_EXCEEDED", "Query latency exceeds budget", {
254
+ latencyMs,
255
+ maxLatencyMs
256
+ }), shouldBlock, ctx);
257
+ }
258
+ }
259
259
  function evaluateSelectAst(ast, ctx) {
260
260
  const hasAggNoGroup = hasAggregateWithoutGroupBy(ast);
261
261
  const estimated = estimateRowsFromAst(ast, tableRows, defaultTableRows, hasAggNoGroup);
@@ -455,8 +455,7 @@ function lints(options) {
455
455
  return Object.freeze({
456
456
  name: "lints",
457
457
  familyId: "sql",
458
- beforeQuery: beforeOperation,
459
- beforeExecute: beforeOperation
458
+ beforeEncode: beforeOperation
460
459
  });
461
460
  }
462
461
  var PreparedStatementImpl = class {
@@ -1227,6 +1226,7 @@ var SqlRuntimeBase = class extends RuntimeCore {
1227
1226
  #refusing = false;
1228
1227
  #heldQueryables = /* @__PURE__ */ new WeakSet();
1229
1228
  #preparedStatementHandles = /* @__PURE__ */ new WeakMap();
1229
+ #holdFinishedChildren;
1230
1230
  #openTransactions = /* @__PURE__ */ new WeakMap();
1231
1231
  codecRegistryValidated;
1232
1232
  _telemetry;
@@ -1239,13 +1239,13 @@ var SqlRuntimeBase = class extends RuntimeCore {
1239
1239
  now: () => Date.now(),
1240
1240
  log: log ?? noopLog,
1241
1241
  contentHash: (exec) => computeSqlContentHash(blindCast(exec)),
1242
- scope: "runtime",
1243
- planExecutionId: ""
1242
+ scope: "runtime"
1244
1243
  };
1245
1244
  super({
1246
1245
  middleware: middleware ?? [],
1247
1246
  ctx: sqlCtx
1248
1247
  });
1248
+ this.#holdFinishedChildren = this.middleware.some((mw) => mw.activityFinalized !== void 0);
1249
1249
  this.contract = context.contract;
1250
1250
  this.adapter = adapter;
1251
1251
  this.driver = driver;
@@ -1267,9 +1267,8 @@ var SqlRuntimeBase = class extends RuntimeCore {
1267
1267
  * Implementation note: SQL splits lower-then-encode across
1268
1268
  * {@link lowerToDraft} + {@link encodeDraftParams} so the selected
1269
1269
  * operation's middleware chain can run between them:
1270
- * {@link prepareQueryExecution} uses `runBeforeQueryChain` for `query()`,
1271
- * while {@link prepareExecuteExecution} uses `runBeforeExecuteChain` for
1272
- * `execute()` (cipherstash bulk-encrypt, for example, mutates pre-encode
1270
+ * {@link prepareOperation} runs the statement's `beforeEncode` chain
1271
+ * between them (cipherstash bulk-encrypt, for example, mutates pre-encode
1273
1272
  * `ParamRef.value` slots). This protected hook composes the two back into
1274
1273
  * the cross-family `lower()` shape `RuntimeCore` expects. The production
1275
1274
  * operation methods use the matching split form before driver execution.
@@ -1320,12 +1319,8 @@ var SqlRuntimeBase = class extends RuntimeCore {
1320
1319
  });
1321
1320
  }
1322
1321
  /**
1323
- * SQL pre-compile hook. Runs the registered middleware `beforeCompile` chain over the plan's draft (AST + meta). Returns the original plan unchanged when no middleware rewrote the AST; otherwise returns a new plan carrying the rewritten AST and meta. The AST is the authoritative source of execution metadata, so a rewrite needs no sidecar reconciliation here — the lowering adapter and the encoder both walk the rewritten
1324
- * AST directly.
1322
+ * Runs the registered middleware `beforeCompile` chain over the plan's draft (AST + meta). Returns the original plan unchanged when no middleware rewrote the AST; otherwise returns a new plan carrying the rewritten AST and meta. The lowering adapter and the encoder both walk the rewritten AST directly.
1325
1323
  */
1326
- runBeforeCompile(plan) {
1327
- return this.compilePlan(plan, this.sqlCtx);
1328
- }
1329
1324
  async compilePlan(plan, middlewareCtx) {
1330
1325
  const rewrittenDraft = await runBeforeCompileChain(this.middleware, {
1331
1326
  ast: plan.ast,
@@ -1374,25 +1369,19 @@ var SqlRuntimeBase = class extends RuntimeCore {
1374
1369
  if (queryable === this.driver && this.#refusing) throw runtimeClosedError();
1375
1370
  return this.#inFlight.begin();
1376
1371
  }
1377
- scheduleAfterTransaction(queryable, exec, ctx) {
1378
- const runStage = this.afterTransactionStageFor(exec, ctx);
1379
- if (runStage === void 0) return void 0;
1380
- const rememberOnTransaction = this.#openTransactions.get(queryable);
1381
- return rememberOnTransaction === void 0 ? onQueryEndOutsideTransaction(runStage) : rememberOnTransaction(runStage);
1372
+ parentOf(queryable) {
1373
+ return this.#openTransactions.get(queryable);
1382
1374
  }
1383
1375
  getListDecoder() {
1384
1376
  return sqlNativeArrayListDecoder;
1385
1377
  }
1386
- streamRows(exec, createDecodeContext, driverCall, codecCtx, execMiddlewareCtx, queryable, onDriverAnswered) {
1387
- return reportQueryEnding(this.scheduleAfterTransaction(queryable, exec, execMiddlewareCtx), () => this.streamDecodedRows(exec, createDecodeContext, driverCall, codecCtx, execMiddlewareCtx, queryable, onDriverAnswered));
1388
- }
1389
- async *streamDecodedRows(exec, createDecodeContext, driverCall, codecCtx, execMiddlewareCtx, queryable, onDriverAnswered) {
1378
+ async *streamDecodedRows(run, exec, createDecodeContext, driverCall, codecCtx, queryable, onDriverAnswered) {
1390
1379
  const rowDecodeContext = createDecodeContext();
1391
1380
  await this.setupDriverExecution(exec, queryable);
1392
1381
  const startedAt = Date.now();
1393
1382
  let telemetryOutcome = null;
1394
1383
  try {
1395
- const iterator = runQueryWithMiddleware(exec, this.middleware, execMiddlewareCtx, driverCall)[Symbol.asyncIterator]();
1384
+ const iterator = run.runQuery(exec, driverCall)[Symbol.asyncIterator]();
1396
1385
  let answered = false;
1397
1386
  try {
1398
1387
  while (true) {
@@ -1418,25 +1407,18 @@ var SqlRuntimeBase = class extends RuntimeCore {
1418
1407
  const scope = options?.scope ?? "runtime";
1419
1408
  return {
1420
1409
  codecCtx: signal === void 0 ? {} : { signal },
1421
- middlewareCtx: {
1410
+ contextFields: {
1422
1411
  ...this.sqlCtx,
1423
1412
  ...ifDefined("signal", signal),
1424
- ...scope !== "runtime" ? { scope } : {},
1425
- planExecutionId: crypto.randomUUID()
1413
+ ...scope !== "runtime" ? { scope } : {}
1426
1414
  }
1427
1415
  };
1428
1416
  }
1429
- prepareQueryExecution(plan, codecCtx, middlewareCtx) {
1430
- return this.prepareOperation(plan, codecCtx, middlewareCtx, runBeforeQueryChain);
1431
- }
1432
- prepareExecuteExecution(plan, codecCtx, middlewareCtx) {
1433
- return this.prepareOperation(plan, codecCtx, middlewareCtx, runBeforeExecuteChain);
1434
- }
1435
- async prepareOperation(plan, codecCtx, middlewareCtx, runBefore) {
1417
+ async prepareOperation(plan, codecCtx, run) {
1436
1418
  checkAborted(codecCtx, "stream");
1437
1419
  if (isExecutionPlan(plan)) {
1438
1420
  const mutator = createSqlParamRefMutator(plan);
1439
- await runBefore(plan, this.middleware, middlewareCtx, mutator);
1421
+ await run.beforeEncode(plan, mutator);
1440
1422
  return Object.freeze({
1441
1423
  ...plan,
1442
1424
  params: await encodeParams({
@@ -1445,10 +1427,10 @@ var SqlRuntimeBase = class extends RuntimeCore {
1445
1427
  }, codecCtx, this.contractCodecs)
1446
1428
  });
1447
1429
  }
1448
- const compiled = await this.compilePlan(plan, middlewareCtx);
1430
+ const compiled = await this.compilePlan(plan, run.ctx);
1449
1431
  const draft = this.lowerToDraft(compiled);
1450
1432
  const mutator = createSqlParamRefMutator(draft);
1451
- await runBefore(draft, this.middleware, middlewareCtx, mutator);
1433
+ await run.beforeEncode(draft, mutator);
1452
1434
  const draftWithMutations = Object.freeze({
1453
1435
  ...draft,
1454
1436
  params: mutator.currentParams()
@@ -1459,15 +1441,22 @@ var SqlRuntimeBase = class extends RuntimeCore {
1459
1441
  queryAgainstQueryable(plan, queryable, options) {
1460
1442
  this.ensureCodecRegistryValidated();
1461
1443
  const self = this;
1462
- const { codecCtx, middlewareCtx } = this.createQueryContexts(options);
1444
+ const { codecCtx, contextFields } = this.createQueryContexts(options);
1463
1445
  const generator = async function* () {
1464
1446
  const endOperation = self.beginOperation(queryable);
1465
1447
  try {
1466
- const exec = await self.prepareQueryExecution(plan, codecCtx, middlewareCtx);
1467
- yield* self.streamRows(exec, () => buildDecodeContext(exec.ast, self.contractCodecs), () => queryable.query({
1468
- sql: exec.sql,
1469
- params: exec.params
1470
- }), codecCtx, middlewareCtx, queryable, endOperation);
1448
+ yield* streamStatement({
1449
+ middleware: self.middleware,
1450
+ plan,
1451
+ context: contextFields,
1452
+ parent: self.parentOf(queryable)
1453
+ }, async function* (run) {
1454
+ const exec = await self.prepareOperation(plan, codecCtx, run);
1455
+ yield* self.streamDecodedRows(run, exec, () => buildDecodeContext(exec.ast, self.contractCodecs), () => queryable.query({
1456
+ sql: exec.sql,
1457
+ params: exec.params
1458
+ }), codecCtx, queryable, endOperation);
1459
+ });
1471
1460
  } finally {
1472
1461
  endOperation();
1473
1462
  }
@@ -1485,33 +1474,41 @@ var SqlRuntimeBase = class extends RuntimeCore {
1485
1474
  }
1486
1475
  async executeStatisticsInFlight(plan, queryable, options) {
1487
1476
  this.ensureCodecRegistryValidated();
1488
- const { codecCtx, middlewareCtx } = this.createQueryContexts(options);
1489
- const exec = await this.prepareExecuteExecution(plan, codecCtx, middlewareCtx);
1490
- return this.executeEncodedPlan(exec, queryable, codecCtx, middlewareCtx, () => queryable.execute({
1491
- sql: exec.sql,
1492
- params: exec.params
1493
- }));
1494
- }
1495
- executeEncodedPlan(exec, queryable, codecCtx, middlewareCtx, driverCall) {
1496
- return reportExecuteEnding(this.scheduleAfterTransaction(queryable, exec, middlewareCtx), async () => {
1497
- await this.setupDriverExecution(exec, queryable);
1498
- checkAborted(codecCtx, "stream");
1499
- const startedAt = Date.now();
1500
- let telemetryOutcome = "success";
1501
- try {
1502
- return await runExecuteWithMiddleware(exec, this.middleware, middlewareCtx, driverCall);
1503
- } catch (error) {
1504
- telemetryOutcome = "runtime-error";
1505
- throw error;
1506
- } finally {
1507
- this.recordTelemetry(exec, telemetryOutcome, Date.now() - startedAt);
1508
- }
1477
+ const { codecCtx, contextFields } = this.createQueryContexts(options);
1478
+ return this.executeOn(queryable, plan, contextFields, async (run) => {
1479
+ const exec = await this.prepareOperation(plan, codecCtx, run);
1480
+ return this.executeEncodedPlan(run, exec, queryable, codecCtx, () => queryable.execute({
1481
+ sql: exec.sql,
1482
+ params: exec.params
1483
+ }));
1509
1484
  });
1510
1485
  }
1486
+ executeOn(queryable, plan, context, body) {
1487
+ return executeStatement({
1488
+ middleware: this.middleware,
1489
+ plan,
1490
+ context,
1491
+ parent: this.parentOf(queryable)
1492
+ }, body);
1493
+ }
1494
+ async executeEncodedPlan(run, exec, queryable, codecCtx, driverCall) {
1495
+ await this.setupDriverExecution(exec, queryable);
1496
+ checkAborted(codecCtx, "stream");
1497
+ const startedAt = Date.now();
1498
+ let telemetryOutcome = "success";
1499
+ try {
1500
+ return await run.runExecute(exec, driverCall);
1501
+ } catch (error) {
1502
+ telemetryOutcome = "runtime-error";
1503
+ throw error;
1504
+ } finally {
1505
+ this.recordTelemetry(exec, telemetryOutcome, Date.now() - startedAt);
1506
+ }
1507
+ }
1511
1508
  async prepare(declaration, callback) {
1512
1509
  this.ensureCodecRegistryValidated();
1513
1510
  const userPlan = callback(buildBindSiteParams(declaration));
1514
- const finalPlan = await this.runBeforeCompile(userPlan);
1511
+ const finalPlan = await this.compilePlan(userPlan, standaloneContext(this.sqlCtx));
1515
1512
  const orderedRefs = collectOrderedParamRefs(finalPlan.ast);
1516
1513
  const referencedNames = /* @__PURE__ */ new Set();
1517
1514
  for (const ref of orderedRefs) if (ref.kind === "prepared-param-ref") referencedNames.add(ref.name);
@@ -1537,18 +1534,23 @@ var SqlRuntimeBase = class extends RuntimeCore {
1537
1534
  runPreparedQueryAgainstQueryable(ps, userParams, queryable, options) {
1538
1535
  this.ensureCodecRegistryValidated();
1539
1536
  const self = this;
1540
- const { codecCtx, middlewareCtx: execMiddlewareCtx } = this.createQueryContexts(options);
1537
+ const { codecCtx, contextFields } = this.createQueryContexts(options);
1541
1538
  const generator = async function* () {
1542
1539
  const endOperation = self.beginOperation(queryable);
1543
1540
  try {
1544
- yield* self.streamPreparedRows(ps, userParams, queryable, codecCtx, execMiddlewareCtx, endOperation);
1541
+ yield* streamStatement({
1542
+ middleware: self.middleware,
1543
+ plan: ps,
1544
+ context: contextFields,
1545
+ parent: self.parentOf(queryable)
1546
+ }, (run) => self.streamPreparedRows(run, ps, userParams, queryable, codecCtx, endOperation));
1545
1547
  } finally {
1546
1548
  endOperation();
1547
1549
  }
1548
1550
  };
1549
1551
  return new AsyncIterableResult(generator());
1550
1552
  }
1551
- async *streamPreparedRows(ps, userParams, queryable, codecCtx, execMiddlewareCtx, onDriverAnswered) {
1553
+ async *streamPreparedRows(run, ps, userParams, queryable, codecCtx, onDriverAnswered) {
1552
1554
  checkAborted(codecCtx, "stream");
1553
1555
  const preEncodeValues = resolvePreparedSlotValues(ps, userParams);
1554
1556
  const preEncodeExec = {
@@ -1558,7 +1560,7 @@ var SqlRuntimeBase = class extends RuntimeCore {
1558
1560
  meta: ps.meta
1559
1561
  };
1560
1562
  const mutator = createSqlParamRefMutator(preEncodeExec);
1561
- await runBeforeQueryChain(preEncodeExec, this.middleware, execMiddlewareCtx, mutator);
1563
+ await run.beforeEncode(preEncodeExec, mutator);
1562
1564
  const encodedParams = await encodeParamsWithMetadata(mutator.currentParams(), ps.paramMetadata, codecCtx, this.contractCodecs);
1563
1565
  const exec = {
1564
1566
  sql: ps.sql,
@@ -1577,7 +1579,7 @@ var SqlRuntimeBase = class extends RuntimeCore {
1577
1579
  }
1578
1580
  }
1579
1581
  };
1580
- yield* this.streamRows(exec, () => ps.decodeContext, () => queryable.query(request), codecCtx, execMiddlewareCtx, queryable, onDriverAnswered);
1582
+ yield* this.streamDecodedRows(run, exec, () => ps.decodeContext, () => queryable.query(request), codecCtx, queryable, onDriverAnswered);
1581
1583
  }
1582
1584
  /** Execute a prepared statement's statistics against a caller-supplied queryable through the full pipeline. */
1583
1585
  async runPreparedExecuteAgainstQueryable(ps, userParams, queryable, options) {
@@ -1590,7 +1592,10 @@ var SqlRuntimeBase = class extends RuntimeCore {
1590
1592
  }
1591
1593
  async runPreparedExecuteInFlight(ps, userParams, queryable, options) {
1592
1594
  this.ensureCodecRegistryValidated();
1593
- const { codecCtx, middlewareCtx } = this.createQueryContexts(options);
1595
+ const { codecCtx, contextFields } = this.createQueryContexts(options);
1596
+ return this.executeOn(queryable, ps, contextFields, (run) => this.executePrepared(run, ps, userParams, queryable, codecCtx));
1597
+ }
1598
+ async executePrepared(run, ps, userParams, queryable, codecCtx) {
1594
1599
  checkAborted(codecCtx, "stream");
1595
1600
  const preEncodeValues = resolvePreparedSlotValues(ps, userParams);
1596
1601
  const preEncodeExec = {
@@ -1600,7 +1605,7 @@ var SqlRuntimeBase = class extends RuntimeCore {
1600
1605
  meta: ps.meta
1601
1606
  };
1602
1607
  const mutator = createSqlParamRefMutator(preEncodeExec);
1603
- await runBeforeExecuteChain(preEncodeExec, this.middleware, middlewareCtx, mutator);
1608
+ await run.beforeEncode(preEncodeExec, mutator);
1604
1609
  const exec = {
1605
1610
  sql: ps.sql,
1606
1611
  params: await encodeParamsWithMetadata(mutator.currentParams(), ps.paramMetadata, codecCtx, this.contractCodecs),
@@ -1618,7 +1623,7 @@ var SqlRuntimeBase = class extends RuntimeCore {
1618
1623
  }
1619
1624
  }
1620
1625
  };
1621
- return this.executeEncodedPlan(exec, queryable, codecCtx, middlewareCtx, () => queryable.execute(request));
1626
+ return this.executeEncodedPlan(run, exec, queryable, codecCtx, () => queryable.execute(request));
1622
1627
  }
1623
1628
  async connection() {
1624
1629
  const endOperation = this.beginOperation(this.driver);
@@ -1630,11 +1635,10 @@ var SqlRuntimeBase = class extends RuntimeCore {
1630
1635
  }
1631
1636
  this.#heldQueryables.add(driverConn);
1632
1637
  const self = this;
1633
- return {
1634
- async transaction() {
1635
- const driverTx = await self.#inFlight.track(() => driverConn.beginTransaction());
1636
- self.#heldQueryables.add(driverTx);
1637
- return self.wrapTransaction(driverTx, driverConn);
1638
+ const wrappedConnection = {
1639
+ [transactionStart]: createTransactionStart(),
1640
+ transaction() {
1641
+ return self.beginTransactionOn(driverConn, wrappedConnection);
1638
1642
  },
1639
1643
  async release() {
1640
1644
  await self.#inFlight.track(() => driverConn.release());
@@ -1667,33 +1671,106 @@ var SqlRuntimeBase = class extends RuntimeCore {
1667
1671
  });
1668
1672
  }
1669
1673
  };
1674
+ return wrappedConnection;
1670
1675
  }
1671
1676
  /**
1672
- * Wraps a driver transaction so every query run on it passes through the runtime with `scope: 'transaction'`, and fires each query's `afterTransaction` stage once when the transaction ends. `connection` is the connection `driverTx` was begun on. A query sent on it, or on the transaction, from now until `commit()` or `rollback()` is called runs inside the transaction, so its stage also fires when the transaction ends. A subclass that begins a driver transaction itself returns it through this wrapper. A transaction a subclass wraps reads the contract marker through the driver; only the connections and transactions this class hands out read it through themselves.
1677
+ * Begins a transaction on `connection`, which `owner` wraps, as a transaction activity. When the transaction's `activityStarted` hooks refuse the start, no `BEGIN` is sent, `owner` is recorded as clean, and `withTransaction` releases it; after any other start failure it destroys it. The transaction's `activityStarted` hooks run before `BEGIN`. Every statement sent on the transaction, or on `connection`, from the end of `BEGIN` until `commit()` or `rollback()` is called is the transaction's child, and is finalized when the transaction ends, with its outcome. `commit()` and `rollback()` resolve after every finalization hook has run. A subclass that holds its own connection begins its transactions here.
1673
1678
  */
1674
- wrapTransaction(driverTx, connection) {
1675
- const remembered = this.anyMiddlewareDeclaresAfterTransaction ? this.rememberQueriesUntilEnd([driverTx, connection]) : void 0;
1679
+ async beginTransactionOn(connection, owner) {
1680
+ owner[transactionStart].refusedBeforeBegin = false;
1681
+ let activity;
1682
+ try {
1683
+ activity = await startTransactionActivity({
1684
+ middleware: this.middleware,
1685
+ context: {
1686
+ ...this.sqlCtx,
1687
+ scope: "transaction"
1688
+ },
1689
+ holdFinishedChildren: this.#holdFinishedChildren
1690
+ });
1691
+ } catch (error) {
1692
+ owner[transactionStart].refusedBeforeBegin = true;
1693
+ throw error;
1694
+ }
1695
+ let driverTx;
1696
+ try {
1697
+ driverTx = await this.#inFlight.track(() => connection.beginTransaction());
1698
+ } catch (error) {
1699
+ await activity.end({
1700
+ by: "rollback",
1701
+ outcome: "failed",
1702
+ commitAttempted: false
1703
+ });
1704
+ throw error;
1705
+ }
1706
+ if (this.#heldQueryables.has(connection)) this.#heldQueryables.add(driverTx);
1707
+ return this.wrapTransaction(driverTx, connection, activity);
1708
+ }
1709
+ wrapTransaction(driverTx, connection, activity) {
1710
+ this.#openTransactions.set(driverTx, activity);
1711
+ this.#openTransactions.set(connection, activity);
1712
+ const stopAdopting = () => {
1713
+ for (const queryable of [driverTx, connection]) if (this.#openTransactions.get(queryable) === activity) this.#openTransactions.delete(queryable);
1714
+ };
1676
1715
  let commitAttempted = false;
1716
+ const settlement = {
1717
+ committed: false,
1718
+ rolledBack: false,
1719
+ logDroppedError(error) {
1720
+ try {
1721
+ activity.ctx.log.error({
1722
+ event: "middleware.activityEnded.error",
1723
+ error
1724
+ });
1725
+ } catch {}
1726
+ }
1727
+ };
1728
+ let commitEnding;
1677
1729
  const self = this;
1678
1730
  return {
1731
+ [transactionSettlement]: settlement,
1679
1732
  async commit() {
1680
1733
  commitAttempted = true;
1681
- remembered?.stopRemembering();
1734
+ stopAdopting();
1682
1735
  try {
1683
1736
  await self.#inFlight.track(() => driverTx.commit());
1684
1737
  } catch (error) {
1685
- await remembered?.end("unknown");
1738
+ await activity.end({
1739
+ by: "commit",
1740
+ outcome: "failed"
1741
+ });
1686
1742
  throw error;
1687
1743
  }
1688
- await remembered?.end("committed");
1744
+ settlement.committed = true;
1745
+ commitEnding = activity.end({
1746
+ by: "commit",
1747
+ outcome: "succeeded"
1748
+ });
1749
+ await commitEnding;
1689
1750
  },
1690
1751
  async rollback() {
1691
- remembered?.stopRemembering();
1752
+ stopAdopting();
1753
+ if (settlement.committed) {
1754
+ await commitEnding?.catch(() => void 0);
1755
+ return;
1756
+ }
1757
+ if (settlement.rolledBack) return;
1692
1758
  try {
1693
1759
  await self.#inFlight.track(() => driverTx.rollback());
1694
- } finally {
1695
- await remembered?.end(commitAttempted ? "unknown" : "rolled-back");
1760
+ } catch (error) {
1761
+ await activity.end({
1762
+ by: "rollback",
1763
+ outcome: "failed",
1764
+ commitAttempted
1765
+ });
1766
+ throw error;
1696
1767
  }
1768
+ settlement.rolledBack = true;
1769
+ await activity.end({
1770
+ by: "rollback",
1771
+ outcome: "succeeded",
1772
+ commitAttempted
1773
+ });
1697
1774
  },
1698
1775
  query(plan, options) {
1699
1776
  return self.queryAgainstQueryable(plan, driverTx, {
@@ -1721,26 +1798,6 @@ var SqlRuntimeBase = class extends RuntimeCore {
1721
1798
  }
1722
1799
  };
1723
1800
  }
1724
- rememberQueriesUntilEnd(queryables) {
1725
- const awaitingTransactionEnd = [];
1726
- let aQueryFailed = false;
1727
- const remember = (runStage) => {
1728
- awaitingTransactionEnd.push(runStage);
1729
- return async (ending) => {
1730
- if (ending === "failed") aQueryFailed = true;
1731
- };
1732
- };
1733
- for (const queryable of queryables) this.#openTransactions.set(queryable, remember);
1734
- return {
1735
- stopRemembering: () => {
1736
- for (const queryable of queryables) if (this.#openTransactions.get(queryable) === remember) this.#openTransactions.delete(queryable);
1737
- },
1738
- end: async (outcome) => {
1739
- const reported = outcome === "committed" && aQueryFailed ? "unknown" : outcome;
1740
- for (const runStage of awaitingTransactionEnd.splice(0)) await runStage({ outcome: reported });
1741
- }
1742
- };
1743
- }
1744
1801
  telemetry() {
1745
1802
  return this._telemetry;
1746
1803
  }
@@ -1811,12 +1868,27 @@ var SqlRuntimeBase = class extends RuntimeCore {
1811
1868
  });
1812
1869
  }
1813
1870
  };
1871
+ function standaloneContext(fields) {
1872
+ return {
1873
+ ...fields,
1874
+ parent: void 0,
1875
+ state: createStateStore().state,
1876
+ inTransaction: () => false
1877
+ };
1878
+ }
1814
1879
  function transactionClosedError() {
1815
1880
  return runtimeError$1("RUNTIME.TRANSACTION_CLOSED", "Cannot use a transaction operation after the transaction has ended. Consume query results and await execute calls inside the transaction callback.", {});
1816
1881
  }
1817
1882
  async function withTransaction(runtime, fn) {
1818
1883
  const connection = await runtime.connection();
1819
- const transaction = await connection.transaction();
1884
+ let transaction;
1885
+ try {
1886
+ transaction = await connection.transaction();
1887
+ } catch (error) {
1888
+ if (wasRefusedBeforeBegin(connection)) await connection.release().catch(() => void 0);
1889
+ else await connection.destroy(error).catch(() => void 0);
1890
+ throw error;
1891
+ }
1820
1892
  let invalidated = false;
1821
1893
  async function* guardedStream(inner) {
1822
1894
  if (invalidated) throw transactionClosedError();
@@ -1860,6 +1932,11 @@ async function withTransaction(runtime, fn) {
1860
1932
  try {
1861
1933
  await transaction.rollback();
1862
1934
  } catch (rollbackError) {
1935
+ const settlement = settlementOf(transaction);
1936
+ if (settlement?.rolledBack === true) {
1937
+ settlement.logDroppedError(rollbackError);
1938
+ throw error;
1939
+ }
1863
1940
  await destroyConnection(rollbackError);
1864
1941
  const wrapped = runtimeError$1("RUNTIME.TRANSACTION_ROLLBACK_FAILED", "Transaction rollback failed after callback error", { rollbackError });
1865
1942
  wrapped.cause = error;
@@ -1872,10 +1949,13 @@ async function withTransaction(runtime, fn) {
1872
1949
  try {
1873
1950
  await transaction.commit();
1874
1951
  } catch (commitError) {
1952
+ if (settlementOf(transaction)?.committed === true) throw commitError;
1875
1953
  try {
1876
1954
  await transaction.rollback();
1877
- } catch {
1878
- await destroyConnection(commitError);
1955
+ } catch (rollbackError) {
1956
+ const settlement = settlementOf(transaction);
1957
+ if (settlement?.rolledBack === true) settlement.logDroppedError(rollbackError);
1958
+ else await destroyConnection(commitError);
1879
1959
  }
1880
1960
  const wrapped = runtimeError$1("RUNTIME.TRANSACTION_COMMIT_FAILED", "Transaction commit failed", { commitError });
1881
1961
  wrapped.cause = commitError;