@prisma/client-engine-runtime 7.10.0-dev.3 → 7.10.0-dev.31

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 CHANGED
@@ -92,6 +92,16 @@ function safeJsonStringify(obj) {
92
92
  return val;
93
93
  });
94
94
  }
95
+ var MAX_PUSH_SPREAD_ARGS = 8192;
96
+ function appendToArray(target, source) {
97
+ if (source.length <= MAX_PUSH_SPREAD_ARGS) {
98
+ target.push(...source);
99
+ return;
100
+ }
101
+ for (let i = 0; i < source.length; i += MAX_PUSH_SPREAD_ARGS) {
102
+ target.push(...source.slice(i, i + MAX_PUSH_SPREAD_ARGS));
103
+ }
104
+ }
95
105
 
96
106
  // src/json-protocol.ts
97
107
  function normalizeJsonProtocolValues(result) {
@@ -173,10 +183,8 @@ function deserializeTaggedValue({ $type, value }) {
173
183
  switch ($type) {
174
184
  case "BigInt":
175
185
  return BigInt(value);
176
- case "Bytes": {
177
- const { buffer, byteOffset, byteLength } = Buffer.from(value, "base64");
178
- return new Uint8Array(buffer, byteOffset, byteLength);
179
- }
186
+ case "Bytes":
187
+ return new Uint8Array(Buffer.from(value, "base64"));
180
188
  case "DateTime":
181
189
  return new Date(value);
182
190
  case "Decimal":
@@ -290,6 +298,7 @@ function getErrorCode(err) {
290
298
  case "UniqueConstraintViolation":
291
299
  return "P2002";
292
300
  case "ForeignKeyConstraintViolation":
301
+ case "RestrictViolation":
293
302
  return "P2003";
294
303
  case "InvalidInputValue":
295
304
  return "P2007";
@@ -362,6 +371,7 @@ function renderErrorMessage(err) {
362
371
  case "UniqueConstraintViolation":
363
372
  return `Unique constraint failed on the ${renderConstraint(err.cause.constraint)}`;
364
373
  case "ForeignKeyConstraintViolation":
374
+ case "RestrictViolation":
365
375
  return `Foreign key constraint violated on the ${renderConstraint(err.cause.constraint)}`;
366
376
  case "UnsupportedNativeDataType":
367
377
  return `Failed to deserialize column of type '${err.cause.type}'. If you're using $queryRaw and this column is explicitly marked as \`Unsupported\` in your Prisma schema, try casting this column to any supported Prisma type such as \`String\`.`;
@@ -1095,8 +1105,9 @@ function renderTemplateSql(fragments, placeholderFormat, params, argTypes) {
1095
1105
  if (fragment.type === "stringChunk") {
1096
1106
  continue;
1097
1107
  }
1098
- const length = flattenedParams.length;
1099
- const added = flattenedParams.push(...flattenedFragmentParams(fragment)) - length;
1108
+ const fragmentParams = Array.from(flattenedFragmentParams(fragment));
1109
+ const added = fragmentParams.length;
1110
+ appendToArray(flattenedParams, fragmentParams);
1100
1111
  if (fragment.argType.arity === "tuple") {
1101
1112
  if (added % fragment.argType.elements.length !== 0) {
1102
1113
  throw new Error(
@@ -1559,17 +1570,27 @@ var QueryInterpreter = class _QueryInterpreter {
1559
1570
  });
1560
1571
  }
1561
1572
  async run(queryPlan, options) {
1562
- const { value } = await this.interpretNode(queryPlan, {
1563
- ...options,
1564
- generators: this.#generators.snapshot()
1565
- }).catch((e) => rethrowAsUserFacing(e));
1573
+ const generators = this.#generators.snapshot();
1574
+ const context = { ...options, generators };
1575
+ const purified = purifyQueryPlan(queryPlan, (node) => this.interpretNode(node, context))?.catch(
1576
+ (e) => rethrowAsUserFacing(e)
1577
+ );
1578
+ if (purified) {
1579
+ try {
1580
+ return this.#interpretPureNode(await purified, context.scope, generators).value;
1581
+ } catch (e) {
1582
+ rethrowAsUserFacing(e);
1583
+ }
1584
+ }
1585
+ const { value } = await this.interpretNode(queryPlan, context).catch((e) => rethrowAsUserFacing(e));
1566
1586
  return value;
1567
1587
  }
1568
1588
  async interpretNode(node, context) {
1569
1589
  switch (node.type) {
1570
1590
  case "value": {
1571
1591
  return {
1572
- value: evaluateArg(node.args, context.scope, context.generators)
1592
+ value: evaluateArg(node.args, context.scope, context.generators),
1593
+ lastInsertId: node.lastInsertId
1573
1594
  };
1574
1595
  }
1575
1596
  case "seq": {
@@ -1579,9 +1600,6 @@ var QueryInterpreter = class _QueryInterpreter {
1579
1600
  }
1580
1601
  return result ?? { value: void 0 };
1581
1602
  }
1582
- case "get": {
1583
- return { value: context.scope[node.args.name] };
1584
- }
1585
1603
  case "let": {
1586
1604
  const nestedScope = Object.create(context.scope);
1587
1605
  for (const binding of node.args.bindings) {
@@ -1590,15 +1608,6 @@ var QueryInterpreter = class _QueryInterpreter {
1590
1608
  }
1591
1609
  return this.interpretNode(node.args.expr, { ...context, scope: nestedScope });
1592
1610
  }
1593
- case "getFirstNonEmpty": {
1594
- for (const name of node.args.names) {
1595
- const value = context.scope[name];
1596
- if (!isEmpty(value)) {
1597
- return { value };
1598
- }
1599
- }
1600
- return { value: [] };
1601
- }
1602
1611
  case "concat": {
1603
1612
  const parts = await Promise.all(
1604
1613
  node.args.map((arg) => this.interpretNode(arg, context).then((res) => res.value))
@@ -1645,7 +1654,7 @@ var QueryInterpreter = class _QueryInterpreter {
1645
1654
  if (results === void 0) {
1646
1655
  results = result;
1647
1656
  } else {
1648
- results.rows.push(...result.rows);
1657
+ appendToArray(results.rows, result.rows);
1649
1658
  results.lastInsertId = result.lastInsertId;
1650
1659
  }
1651
1660
  }
@@ -1725,9 +1734,6 @@ var QueryInterpreter = class _QueryInterpreter {
1725
1734
  return await this.interpretNode(node.args.else, context);
1726
1735
  }
1727
1736
  }
1728
- case "unit": {
1729
- return { value: void 0 };
1730
- }
1731
1737
  case "diff": {
1732
1738
  const { value: from } = await this.interpretNode(node.args.from, context);
1733
1739
  const { value: to } = await this.interpretNode(node.args.to, context);
@@ -1757,6 +1763,139 @@ var QueryInterpreter = class _QueryInterpreter {
1757
1763
  }
1758
1764
  return { value: record, lastInsertId };
1759
1765
  }
1766
+ default:
1767
+ return this.#interpretPureNode(node, context.scope, context.generators);
1768
+ }
1769
+ }
1770
+ #interpretPureNode(node, scope, generators) {
1771
+ switch (node.type) {
1772
+ case "value": {
1773
+ return { value: evaluateArg(node.args, scope, generators), lastInsertId: node.lastInsertId };
1774
+ }
1775
+ case "seq": {
1776
+ let result;
1777
+ for (const arg of node.args) {
1778
+ result = this.#interpretPureNode(arg, scope, generators);
1779
+ }
1780
+ return result ?? { value: void 0 };
1781
+ }
1782
+ case "get": {
1783
+ return { value: scope[node.args.name] };
1784
+ }
1785
+ case "let": {
1786
+ const nestedScope = Object.create(scope);
1787
+ for (const binding of node.args.bindings) {
1788
+ const { value } = this.#interpretPureNode(binding.expr, nestedScope, generators);
1789
+ nestedScope[binding.name] = value;
1790
+ }
1791
+ return this.#interpretPureNode(node.args.expr, nestedScope, generators);
1792
+ }
1793
+ case "getFirstNonEmpty": {
1794
+ for (const name of node.args.names) {
1795
+ const value = scope[name];
1796
+ if (!isEmpty(value)) {
1797
+ return { value };
1798
+ }
1799
+ }
1800
+ return { value: [] };
1801
+ }
1802
+ case "concat": {
1803
+ const parts = node.args.map((arg) => this.#interpretPureNode(arg, scope, generators).value);
1804
+ return {
1805
+ value: parts.length > 0 ? parts.reduce((acc, part) => acc.concat(asList(part)), []) : []
1806
+ };
1807
+ }
1808
+ case "sum": {
1809
+ const parts = node.args.map((arg) => this.#interpretPureNode(arg, scope, generators).value);
1810
+ return {
1811
+ value: parts.length > 0 ? parts.reduce((acc, part) => asNumber2(acc) + asNumber2(part)) : 0
1812
+ };
1813
+ }
1814
+ case "reverse": {
1815
+ const { value, lastInsertId } = this.#interpretPureNode(node.args, scope, generators);
1816
+ return { value: Array.isArray(value) ? value.reverse() : value, lastInsertId };
1817
+ }
1818
+ case "unique": {
1819
+ const { value, lastInsertId } = this.#interpretPureNode(node.args, scope, generators);
1820
+ if (!Array.isArray(value)) {
1821
+ return { value, lastInsertId };
1822
+ }
1823
+ if (value.length > 1) {
1824
+ throw new Error(`Expected zero or one element, got ${value.length}`);
1825
+ }
1826
+ return { value: value[0] ?? null, lastInsertId };
1827
+ }
1828
+ case "required": {
1829
+ const { value, lastInsertId } = this.#interpretPureNode(node.args, scope, generators);
1830
+ if (isEmpty(value)) {
1831
+ throw new Error("Required value is empty");
1832
+ }
1833
+ return { value, lastInsertId };
1834
+ }
1835
+ case "mapField": {
1836
+ const { value, lastInsertId } = this.#interpretPureNode(node.args.records, scope, generators);
1837
+ return { value: mapField2(value, node.args.field), lastInsertId };
1838
+ }
1839
+ case "join": {
1840
+ const { value: parent, lastInsertId } = this.#interpretPureNode(node.args.parent, scope, generators);
1841
+ if (parent === null) {
1842
+ return { value: null, lastInsertId };
1843
+ }
1844
+ const children = node.args.children.map((joinExpr) => ({
1845
+ joinExpr,
1846
+ childRecords: this.#interpretPureNode(joinExpr.child, scope, generators).value
1847
+ }));
1848
+ return { value: attachChildrenToParents(parent, children, node.args.canAssumeStrictEquality), lastInsertId };
1849
+ }
1850
+ case "dataMap": {
1851
+ const { value, lastInsertId } = this.#interpretPureNode(node.args.expr, scope, generators);
1852
+ return { value: applyDataMap(value, node.args.structure, node.args.enums), lastInsertId };
1853
+ }
1854
+ case "validate": {
1855
+ const { value, lastInsertId } = this.#interpretPureNode(node.args.expr, scope, generators);
1856
+ performValidation(value, node.args.rules, node.args);
1857
+ return { value, lastInsertId };
1858
+ }
1859
+ case "if": {
1860
+ const { value } = this.#interpretPureNode(node.args.value, scope, generators);
1861
+ if (doesSatisfyRule(value, node.args.rule)) {
1862
+ return this.#interpretPureNode(node.args.then, scope, generators);
1863
+ } else {
1864
+ return this.#interpretPureNode(node.args.else, scope, generators);
1865
+ }
1866
+ }
1867
+ case "unit": {
1868
+ return { value: void 0 };
1869
+ }
1870
+ case "diff": {
1871
+ const { value: from } = this.#interpretPureNode(node.args.from, scope, generators);
1872
+ const { value: to } = this.#interpretPureNode(node.args.to, scope, generators);
1873
+ const keyGetter = (item) => item !== null ? getRecordKey(asRecord(item), node.args.fields) : null;
1874
+ const toSet = new Set(asList(to).map(keyGetter));
1875
+ return { value: asList(from).filter((item) => !toSet.has(keyGetter(item))) };
1876
+ }
1877
+ case "process": {
1878
+ const { value, lastInsertId } = this.#interpretPureNode(node.args.expr, scope, generators);
1879
+ const ops = cloneObject(node.args.operations);
1880
+ evaluateProcessingParameters(ops, scope, generators);
1881
+ return { value: processRecords(value, ops), lastInsertId };
1882
+ }
1883
+ case "initializeRecord": {
1884
+ const { lastInsertId } = this.#interpretPureNode(node.args.expr, scope, generators);
1885
+ const record = {};
1886
+ for (const [key, initializer] of Object.entries(node.args.fields)) {
1887
+ record[key] = evalFieldInitializer(initializer, lastInsertId, scope, generators);
1888
+ }
1889
+ return { value: record, lastInsertId };
1890
+ }
1891
+ case "mapRecord": {
1892
+ const { value, lastInsertId } = this.#interpretPureNode(node.args.expr, scope, generators);
1893
+ const record = value === null ? {} : asRecord(value);
1894
+ for (const [key, entry] of Object.entries(node.args.fields)) {
1895
+ record[key] = evalFieldOperation(entry, record[key], scope, generators);
1896
+ }
1897
+ return { value: record, lastInsertId };
1898
+ }
1760
1899
  default:
1761
1900
  assertNever(node, `Unexpected node type: ${node.type}`);
1762
1901
  }
@@ -1934,6 +2073,112 @@ function evalFieldOperation(op, value, scope, generators) {
1934
2073
  assertNever(op, `Unexpected field operation type: ${op["type"]}`);
1935
2074
  }
1936
2075
  }
2076
+ function purifyQueryPlan(node, evalNode) {
2077
+ const impureNode = findUniqueUnconditionalImpureNode(node);
2078
+ if (!impureNode) {
2079
+ return void 0;
2080
+ }
2081
+ return evalNode(impureNode).then((result) => {
2082
+ const evaluated = {
2083
+ type: "value",
2084
+ args: result.value,
2085
+ lastInsertId: result.lastInsertId
2086
+ };
2087
+ const purified = replaceImpureNode(node, impureNode, evaluated);
2088
+ if (!purified) {
2089
+ throw new Error("Could not substitute the evaluated impure node into the query plan");
2090
+ }
2091
+ return purified;
2092
+ });
2093
+ }
2094
+ function replaceImpureNode(node, target, replacement) {
2095
+ if (node === target) {
2096
+ return replacement;
2097
+ }
2098
+ switch (node.type) {
2099
+ case "seq":
2100
+ case "sum":
2101
+ case "concat": {
2102
+ for (let i = 0; i < node.args.length; i++) {
2103
+ const child = replaceImpureNode(node.args[i], target, replacement);
2104
+ if (child) {
2105
+ return { ...node, args: node.args.map((arg, j) => j === i ? child : arg) };
2106
+ }
2107
+ }
2108
+ return void 0;
2109
+ }
2110
+ case "dataMap":
2111
+ case "validate":
2112
+ case "initializeRecord":
2113
+ case "mapRecord":
2114
+ case "process": {
2115
+ const expr = replaceImpureNode(node.args.expr, target, replacement);
2116
+ return expr && { ...node, args: { ...node.args, expr } };
2117
+ }
2118
+ case "mapField": {
2119
+ const records = replaceImpureNode(node.args.records, target, replacement);
2120
+ return records && { ...node, args: { ...node.args, records } };
2121
+ }
2122
+ case "reverse":
2123
+ case "unique":
2124
+ case "required": {
2125
+ const args = replaceImpureNode(node.args, target, replacement);
2126
+ return args && { ...node, args };
2127
+ }
2128
+ default:
2129
+ return void 0;
2130
+ }
2131
+ }
2132
+ function findUniqueUnconditionalImpureNode(node) {
2133
+ switch (node.type) {
2134
+ case "query":
2135
+ case "execute":
2136
+ return node;
2137
+ case "seq":
2138
+ case "sum":
2139
+ case "concat": {
2140
+ let found = void 0;
2141
+ for (const child of node.args) {
2142
+ const childFound = findUniqueUnconditionalImpureNode(child);
2143
+ if (childFound === null) {
2144
+ return null;
2145
+ }
2146
+ if (childFound) {
2147
+ if (found) {
2148
+ return null;
2149
+ }
2150
+ found = childFound;
2151
+ }
2152
+ }
2153
+ return found;
2154
+ }
2155
+ case "dataMap":
2156
+ case "validate":
2157
+ case "initializeRecord":
2158
+ case "mapRecord":
2159
+ case "process":
2160
+ return findUniqueUnconditionalImpureNode(node.args.expr);
2161
+ case "mapField":
2162
+ return findUniqueUnconditionalImpureNode(node.args.records);
2163
+ case "reverse":
2164
+ case "unique":
2165
+ case "required":
2166
+ return findUniqueUnconditionalImpureNode(node.args);
2167
+ case "let":
2168
+ case "join":
2169
+ case "diff":
2170
+ case "if":
2171
+ case "transaction":
2172
+ return null;
2173
+ case "value":
2174
+ case "get":
2175
+ case "getFirstNonEmpty":
2176
+ case "unit":
2177
+ return void 0;
2178
+ default:
2179
+ assertNever(node, `Unexpected node type: ${node.type}`);
2180
+ }
2181
+ }
1937
2182
  function applyComments(query, sqlCommenter) {
1938
2183
  if (!sqlCommenter || sqlCommenter.plugins.length === 0) {
1939
2184
  return query;
@@ -2500,6 +2745,14 @@ var InvalidTransactionIsolationLevelError = class extends TransactionManagerErro
2500
2745
 
2501
2746
  // src/transaction-manager/transaction-manager.ts
2502
2747
  var MAX_CLOSED_TRANSACTIONS = 100;
2748
+ var CANCEL_ROLLBACK_GRACE_MS = 2e3;
2749
+ function trackStartingTransaction() {
2750
+ let markSettled;
2751
+ const settled = new Promise((resolve) => {
2752
+ markSettled = resolve;
2753
+ });
2754
+ return { abortController: new AbortController(), settled, markSettled };
2755
+ }
2503
2756
  var debug = Debug("prisma:client:transactionManager");
2504
2757
  var COMMIT_QUERY = () => ({ sql: "COMMIT", args: [], argTypes: [] });
2505
2758
  var ROLLBACK_QUERY = () => ({ sql: "ROLLBACK", args: [], argTypes: [] });
@@ -2519,6 +2772,9 @@ var TransactionManager = class {
2519
2772
  // List of last closed transactions. Max MAX_CLOSED_TRANSACTIONS entries.
2520
2773
  // Used to provide better error messages than a generic "transaction not found".
2521
2774
  closedTransactions = [];
2775
+ // Transactions that are still being started. Tracked separately so that
2776
+ // `cancelAllTransactions` can reach them: they are not in `transactions` yet.
2777
+ #startingTransactions = /* @__PURE__ */ new Set();
2522
2778
  driverAdapter;
2523
2779
  transactionOptions;
2524
2780
  tracingHelper;
@@ -2582,56 +2838,91 @@ var TransactionManager = class {
2582
2838
  return { id: existing.id };
2583
2839
  });
2584
2840
  }
2585
- const transaction = {
2586
- id: await randomUUID(),
2587
- status: "waiting",
2588
- timer: void 0,
2589
- timeout: options.timeout,
2590
- startedAt: Date.now(),
2591
- transaction: void 0,
2592
- operationQueue: Promise.resolve(),
2593
- depth: 1,
2594
- savepoints: [],
2595
- savepointCounter: 0
2596
- };
2597
- const abortController = new AbortController();
2598
- const startTimer = createTimeoutIfDefined(() => abortController.abort(), options.maxWait);
2599
- startTimer?.unref?.();
2600
- const startTransactionPromise = this.driverAdapter.startTransaction(options.isolationLevel).catch(rethrowAsUserFacing);
2601
- transaction.transaction = await Promise.race([
2602
- startTransactionPromise.finally(() => clearTimeout(startTimer)),
2603
- once(abortController.signal, "abort").then(() => void 0)
2604
- ]);
2605
- this.transactions.set(transaction.id, transaction);
2606
- switch (transaction.status) {
2607
- case "waiting":
2608
- if (abortController.signal.aborted) {
2609
- void startTransactionPromise.then(async (tx) => {
2610
- if (tx.options.usePhantomQuery) {
2611
- await tx.rollback();
2612
- } else {
2613
- try {
2614
- await tx.executeRaw(ROLLBACK_QUERY());
2615
- } finally {
2616
- await tx.rollback();
2617
- }
2618
- }
2619
- }).catch((e) => debug("error in discarded transaction:", e));
2620
- await this.#closeTransaction(transaction, "timed_out");
2621
- throw new TransactionStartTimeoutError();
2841
+ const starting = trackStartingTransaction();
2842
+ const { abortController } = starting;
2843
+ this.#startingTransactions.add(starting);
2844
+ let discarding;
2845
+ try {
2846
+ const transaction = {
2847
+ id: await randomUUID(),
2848
+ status: "waiting",
2849
+ timer: void 0,
2850
+ timeout: options.timeout,
2851
+ startedAt: Date.now(),
2852
+ transaction: void 0,
2853
+ operationQueue: Promise.resolve(),
2854
+ depth: 1,
2855
+ savepoints: [],
2856
+ savepointCounter: 0
2857
+ };
2858
+ if (abortController.signal.aborted) {
2859
+ throw new TransactionStartTimeoutError();
2860
+ }
2861
+ const startTimer = createTimeoutIfDefined(() => abortController.abort(), options.maxWait);
2862
+ startTimer?.unref?.();
2863
+ const startTransactionPromise = this.driverAdapter.startTransaction(options.isolationLevel).catch(rethrowAsUserFacing);
2864
+ transaction.transaction = await Promise.race([
2865
+ startTransactionPromise.finally(() => clearTimeout(startTimer)),
2866
+ once(abortController.signal, "abort").then(() => void 0)
2867
+ ]);
2868
+ this.transactions.set(transaction.id, transaction);
2869
+ switch (transaction.status) {
2870
+ case "waiting":
2871
+ if (abortController.signal.aborted) {
2872
+ transaction.transaction = void 0;
2873
+ discarding = this.#discardStartedTransaction(startTransactionPromise);
2874
+ await this.#closeTransaction(transaction, "timed_out");
2875
+ throw new TransactionStartTimeoutError();
2876
+ }
2877
+ transaction.status = "running";
2878
+ transaction.startedAt = Date.now();
2879
+ transaction.timer = this.#startTransactionTimeout(transaction.id, options.timeout);
2880
+ return { id: transaction.id };
2881
+ case "timed_out":
2882
+ case "running":
2883
+ case "committed":
2884
+ case "rolled_back":
2885
+ throw new TransactionInternalConsistencyError(
2886
+ `Transaction in invalid state ${transaction.status} although it just finished startup.`
2887
+ );
2888
+ default:
2889
+ return assertNever(transaction["status"], "Unknown transaction status.");
2890
+ }
2891
+ } finally {
2892
+ this.#startingTransactions.delete(starting);
2893
+ if (discarding) {
2894
+ void discarding.finally(starting.markSettled);
2895
+ } else {
2896
+ starting.markSettled();
2897
+ }
2898
+ }
2899
+ }
2900
+ /**
2901
+ * Rolls back a transaction whose start was abandoned, and releases its connection.
2902
+ *
2903
+ * The `startTransaction` promise may still be running in the background. If it eventually
2904
+ * succeeds, we need to roll back and release the connection to avoid leaking it and
2905
+ * exhausting the connection pool. For adapters that don't use phantom queries (e.g. pg/neon),
2906
+ * `rollback()` only releases the connection without sending SQL, so we send an explicit
2907
+ * ROLLBACK first; otherwise the connection returns to the pool mid-transaction because
2908
+ * `BEGIN` already ran on the wire during startup.
2909
+ *
2910
+ * Errors are only logged: the caller has already reported the failure that led here.
2911
+ */
2912
+ async #discardStartedTransaction(startTransactionPromise) {
2913
+ try {
2914
+ const tx = await startTransactionPromise;
2915
+ if (tx.options.usePhantomQuery) {
2916
+ await tx.rollback();
2917
+ } else {
2918
+ try {
2919
+ await tx.executeRaw(ROLLBACK_QUERY());
2920
+ } finally {
2921
+ await tx.rollback();
2622
2922
  }
2623
- transaction.status = "running";
2624
- transaction.timer = this.#startTransactionTimeout(transaction.id, options.timeout);
2625
- return { id: transaction.id };
2626
- case "timed_out":
2627
- case "running":
2628
- case "committed":
2629
- case "rolled_back":
2630
- throw new TransactionInternalConsistencyError(
2631
- `Transaction in invalid state ${transaction.status} although it just finished startup.`
2632
- );
2633
- default:
2634
- assertNever(transaction["status"], "Unknown transaction status.");
2923
+ }
2924
+ } catch (e) {
2925
+ debug("error in discarded transaction:", e);
2635
2926
  }
2636
2927
  }
2637
2928
  async commitTransaction(transactionId) {
@@ -2722,16 +3013,21 @@ var TransactionManager = class {
2722
3013
  return transaction;
2723
3014
  }
2724
3015
  async cancelAllTransactions() {
2725
- await Promise.allSettled(
2726
- [...this.transactions.values()].map(
3016
+ const starting = [...this.#startingTransactions];
3017
+ for (const { abortController } of starting) {
3018
+ abortController.abort();
3019
+ }
3020
+ await Promise.allSettled([
3021
+ ...[...this.transactions.values()].map(
2727
3022
  (tx) => this.#runSerialized(tx, async () => {
2728
3023
  const current = this.transactions.get(tx.id);
2729
3024
  if (current) {
2730
3025
  await this.#closeTransaction(current, "rolled_back");
2731
3026
  }
2732
3027
  })
2733
- )
2734
- );
3028
+ ),
3029
+ ...starting.map(({ settled }) => settleWithin(settled, CANCEL_ROLLBACK_GRACE_MS))
3030
+ ]);
2735
3031
  }
2736
3032
  #nextSavepointName(transaction) {
2737
3033
  return `prisma_sp_${transaction.savepointCounter++}`;
@@ -2884,6 +3180,14 @@ var TransactionManager = class {
2884
3180
  function createTimeoutIfDefined(cb, ms) {
2885
3181
  return ms !== void 0 ? setTimeout(cb, ms) : void 0;
2886
3182
  }
3183
+ function settleWithin(promise, timeout) {
3184
+ let timer;
3185
+ const deadline = new Promise((resolve) => {
3186
+ timer = setTimeout(resolve, timeout);
3187
+ timer?.unref?.();
3188
+ });
3189
+ return Promise.race([promise, deadline]).finally(() => clearTimeout(timer));
3190
+ }
2887
3191
  export {
2888
3192
  DataMapperError,
2889
3193
  QueryInterpreter,
@@ -1,7 +1,7 @@
1
1
  import { ConnectionInfo, SqlQueryable, SqlResultSet } from '@prisma/driver-adapter-utils';
2
2
  import type { SqlCommenterPlugin, SqlCommenterQueryInfo } from '@prisma/sqlcommenter';
3
3
  import { QueryEvent } from '../events';
4
- import { QueryPlanNode } from '../query-plan';
4
+ import { ImpureQueryPlanNode, PureQueryPlanNode, QueryPlanNode } from '../query-plan';
5
5
  import { type SchemaProvider } from '../schema';
6
6
  import { type TracingHelper } from '../tracing';
7
7
  import { type TransactionManager } from '../transaction-manager/transaction-manager';
@@ -43,3 +43,22 @@ export declare class QueryInterpreter {
43
43
  run(queryPlan: DeepReadonly<QueryPlanNode>, options: QueryRuntimeOptions): Promise<unknown>;
44
44
  private interpretNode;
45
45
  }
46
+ type IntermediateValue = {
47
+ value: Value;
48
+ lastInsertId?: string;
49
+ };
50
+ /**
51
+ * Attempts to convert a query plan into a pure one by finding the single impure node
52
+ * that is unconditionally evaluated exactly once, evaluating it eagerly via `evalNode`,
53
+ * and substituting its result into the plan as a constant. The remaining plan can then
54
+ * be interpreted synchronously.
55
+ *
56
+ * Returns `undefined` if the plan cannot be purified this way (it contains no impure
57
+ * nodes, more than one of them, or unsupported constructs).
58
+ *
59
+ * Query plans are cached and shared between requests, so the input plan is never
60
+ * modified; the returned plan shares all unaffected subtrees with the input and only
61
+ * copies the nodes on the path to the substituted one.
62
+ */
63
+ export declare function purifyQueryPlan(node: DeepReadonly<QueryPlanNode>, evalNode: (node: DeepReadonly<ImpureQueryPlanNode>) => Promise<IntermediateValue>): Promise<DeepReadonly<PureQueryPlanNode>> | undefined;
64
+ export {};
@@ -0,0 +1 @@
1
+ export {};