turbine-orm 0.81.0 → 0.90.0

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.
Files changed (295) hide show
  1. package/README.md +26 -25
  2. package/dist/checkout.d.ts +27 -2
  3. package/dist/checkout.js +75 -4
  4. package/dist/cjs/checkout.d.ts +27 -2
  5. package/dist/cjs/checkout.js +76 -3
  6. package/dist/cjs/cli/code-first-tags.d.ts +62 -0
  7. package/dist/cjs/cli/code-first-tags.js +124 -0
  8. package/dist/cjs/cli/compile-query.js +9 -0
  9. package/dist/cjs/cli/config.d.ts +35 -6
  10. package/dist/cjs/cli/config.js +17 -1
  11. package/dist/cjs/cli/destructive.d.ts +7 -4
  12. package/dist/cjs/cli/destructive.js +224 -20
  13. package/dist/cjs/cli/error-catalog.js +36 -0
  14. package/dist/cjs/cli/error-text.d.ts +15 -0
  15. package/dist/cjs/cli/error-text.js +40 -0
  16. package/dist/cjs/cli/index.d.ts +62 -4
  17. package/dist/cjs/cli/index.js +568 -108
  18. package/dist/cjs/cli/local-server-guard.d.ts +53 -0
  19. package/dist/cjs/cli/local-server-guard.js +119 -0
  20. package/dist/cjs/cli/local-server-output.d.ts +41 -0
  21. package/dist/cjs/cli/local-server-output.js +103 -0
  22. package/dist/cjs/cli/mcp.d.ts +8 -0
  23. package/dist/cjs/cli/mcp.js +121 -32
  24. package/dist/cjs/cli/migrate.d.ts +113 -5
  25. package/dist/cjs/cli/migrate.js +270 -21
  26. package/dist/cjs/cli/observe.d.ts +6 -1
  27. package/dist/cjs/cli/observe.js +85 -55
  28. package/dist/cjs/cli/pii-predicate-guard.d.ts +43 -23
  29. package/dist/cjs/cli/pii-predicate-guard.js +98 -37
  30. package/dist/cjs/cli/prisma-resolve.js +17 -13
  31. package/dist/cjs/cli/prisma-schema.js +12 -4
  32. package/dist/cjs/cli/studio-ui.generated.js +1 -1
  33. package/dist/cjs/cli/studio.d.ts +14 -2
  34. package/dist/cjs/cli/studio.js +244 -69
  35. package/dist/cjs/cli/ui.d.ts +1 -1
  36. package/dist/cjs/client.d.ts +558 -85
  37. package/dist/cjs/client.js +1270 -395
  38. package/dist/cjs/connection-guard.d.ts +13 -0
  39. package/dist/cjs/connection-guard.js +28 -0
  40. package/dist/cjs/connection-url.d.ts +51 -16
  41. package/dist/cjs/connection-url.js +67 -34
  42. package/dist/cjs/diagnostics.d.ts +81 -0
  43. package/dist/cjs/diagnostics.js +147 -0
  44. package/dist/cjs/dialect.d.ts +449 -20
  45. package/dist/cjs/dialect.js +117 -76
  46. package/dist/cjs/engine-config.d.ts +7 -6
  47. package/dist/cjs/errors.d.ts +126 -6
  48. package/dist/cjs/errors.js +256 -13
  49. package/dist/cjs/generate.d.ts +57 -7
  50. package/dist/cjs/generate.js +635 -279
  51. package/dist/cjs/global-filters-config.d.ts +59 -0
  52. package/dist/cjs/global-filters-config.js +367 -0
  53. package/dist/cjs/index.d.ts +9 -4
  54. package/dist/cjs/index.js +10 -2
  55. package/dist/cjs/introspect.d.ts +31 -6
  56. package/dist/cjs/introspect.js +108 -50
  57. package/dist/cjs/log-codes.d.ts +12 -0
  58. package/dist/cjs/log-codes.js +77 -0
  59. package/dist/cjs/logger.d.ts +87 -0
  60. package/dist/cjs/logger.js +98 -0
  61. package/dist/cjs/mssql.d.ts +8 -5
  62. package/dist/cjs/mssql.js +352 -65
  63. package/dist/cjs/mysql.d.ts +3 -5
  64. package/dist/cjs/mysql.js +420 -70
  65. package/dist/cjs/nested-write.d.ts +3 -0
  66. package/dist/cjs/nested-write.js +179 -114
  67. package/dist/cjs/pipeline-submittable.d.ts +12 -0
  68. package/dist/cjs/pipeline-submittable.js +118 -44
  69. package/dist/cjs/pipeline.js +21 -26
  70. package/dist/cjs/powdb-introspect.d.ts +5 -2
  71. package/dist/cjs/powdb-introspect.js +20 -15
  72. package/dist/cjs/powdb-shared.d.ts +36 -5
  73. package/dist/cjs/powdb-shared.js +67 -8
  74. package/dist/cjs/powdb.d.ts +4 -0
  75. package/dist/cjs/powdb.js +183 -51
  76. package/dist/cjs/powql.d.ts +394 -51
  77. package/dist/cjs/powql.js +1829 -284
  78. package/dist/cjs/prisma-compat.d.ts +23 -15
  79. package/dist/cjs/prisma-compat.js +507 -372
  80. package/dist/cjs/query/aggregates.d.ts +17 -1
  81. package/dist/cjs/query/aggregates.js +261 -29
  82. package/dist/cjs/query/batched-loader.d.ts +25 -6
  83. package/dist/cjs/query/batched-loader.js +103 -20
  84. package/dist/cjs/query/builder.d.ts +325 -184
  85. package/dist/cjs/query/builder.js +1452 -490
  86. package/dist/cjs/query/column-ref.d.ts +61 -0
  87. package/dist/cjs/query/column-ref.js +73 -0
  88. package/dist/cjs/query/compound-unique.d.ts +21 -0
  89. package/dist/cjs/query/compound-unique.js +98 -18
  90. package/dist/cjs/query/deferred.d.ts +53 -16
  91. package/dist/cjs/query/expr.d.ts +217 -0
  92. package/dist/cjs/query/expr.js +859 -0
  93. package/dist/cjs/query/filters.d.ts +155 -53
  94. package/dist/cjs/query/filters.js +613 -76
  95. package/dist/cjs/query/index.d.ts +2 -0
  96. package/dist/cjs/query/index.js +7 -1
  97. package/dist/cjs/query/json-value.d.ts +43 -0
  98. package/dist/cjs/query/json-value.js +59 -0
  99. package/dist/cjs/query/normalize-args.d.ts +87 -0
  100. package/dist/cjs/query/normalize-args.js +527 -0
  101. package/dist/cjs/query/option-surface.d.ts +16 -4
  102. package/dist/cjs/query/option-surface.js +57 -14
  103. package/dist/cjs/query/relation-names.d.ts +14 -0
  104. package/dist/cjs/query/relation-names.js +77 -18
  105. package/dist/cjs/query/relations.d.ts +30 -3
  106. package/dist/cjs/query/relations.js +219 -63
  107. package/dist/cjs/query/row-lock.d.ts +49 -0
  108. package/dist/cjs/query/row-lock.js +138 -0
  109. package/dist/cjs/query/sql-comment.d.ts +161 -0
  110. package/dist/cjs/query/sql-comment.js +381 -0
  111. package/dist/cjs/query/stream-idle.d.ts +132 -0
  112. package/dist/cjs/query/stream-idle.js +212 -0
  113. package/dist/cjs/query/table-client.d.ts +101 -0
  114. package/dist/cjs/query/table-client.js +74 -0
  115. package/dist/cjs/query/types.d.ts +677 -298
  116. package/dist/cjs/query/utils.d.ts +105 -6
  117. package/dist/cjs/query/utils.js +426 -19
  118. package/dist/cjs/query/warn-registry.d.ts +18 -0
  119. package/dist/cjs/query/warn-registry.js +18 -0
  120. package/dist/cjs/query/where-compile.d.ts +138 -14
  121. package/dist/cjs/query/where-compile.js +513 -29
  122. package/dist/cjs/query/where.d.ts +159 -59
  123. package/dist/cjs/query/where.js +716 -246
  124. package/dist/cjs/query/writes.d.ts +112 -50
  125. package/dist/cjs/query/writes.js +1498 -200
  126. package/dist/cjs/query-events.d.ts +4 -2
  127. package/dist/cjs/query-events.js +3 -1
  128. package/dist/cjs/realtime.d.ts +7 -3
  129. package/dist/cjs/realtime.js +13 -2
  130. package/dist/cjs/schema-builder.d.ts +55 -4
  131. package/dist/cjs/schema-builder.js +382 -18
  132. package/dist/cjs/schema-metadata.d.ts +21 -1
  133. package/dist/cjs/schema-metadata.js +29 -7
  134. package/dist/cjs/schema-sql.d.ts +75 -5
  135. package/dist/cjs/schema-sql.js +497 -45
  136. package/dist/cjs/schema.d.ts +26 -5
  137. package/dist/cjs/schema.js +110 -13
  138. package/dist/cjs/sql-placeholders.d.ts +56 -0
  139. package/dist/cjs/sql-placeholders.js +204 -0
  140. package/dist/cjs/sqlite.d.ts +75 -20
  141. package/dist/cjs/sqlite.js +593 -95
  142. package/dist/cjs/supplied-pool-types.d.ts +48 -0
  143. package/dist/cjs/supplied-pool-types.js +256 -0
  144. package/dist/cjs/transaction-control.d.ts +51 -0
  145. package/dist/cjs/transaction-control.js +316 -0
  146. package/dist/cjs/transaction-state.d.ts +342 -0
  147. package/dist/cjs/transaction-state.js +707 -0
  148. package/dist/cjs/typed-sql.d.ts +37 -3
  149. package/dist/cjs/typed-sql.js +102 -8
  150. package/dist/cli/code-first-tags.d.ts +62 -0
  151. package/dist/cli/code-first-tags.js +120 -0
  152. package/dist/cli/compile-query.js +9 -0
  153. package/dist/cli/config.d.ts +35 -6
  154. package/dist/cli/config.js +16 -1
  155. package/dist/cli/destructive.d.ts +7 -4
  156. package/dist/cli/destructive.js +224 -20
  157. package/dist/cli/error-catalog.js +36 -0
  158. package/dist/cli/error-text.d.ts +15 -0
  159. package/dist/cli/error-text.js +37 -0
  160. package/dist/cli/index.d.ts +62 -4
  161. package/dist/cli/index.js +569 -112
  162. package/dist/cli/local-server-guard.d.ts +53 -0
  163. package/dist/cli/local-server-guard.js +113 -0
  164. package/dist/cli/local-server-output.d.ts +41 -0
  165. package/dist/cli/local-server-output.js +98 -0
  166. package/dist/cli/mcp.d.ts +8 -0
  167. package/dist/cli/mcp.js +121 -34
  168. package/dist/cli/migrate.d.ts +113 -5
  169. package/dist/cli/migrate.js +261 -21
  170. package/dist/cli/observe.d.ts +6 -1
  171. package/dist/cli/observe.js +84 -54
  172. package/dist/cli/pii-predicate-guard.d.ts +43 -23
  173. package/dist/cli/pii-predicate-guard.js +100 -35
  174. package/dist/cli/prisma-resolve.js +15 -11
  175. package/dist/cli/prisma-schema.js +12 -4
  176. package/dist/cli/studio-ui.generated.js +1 -1
  177. package/dist/cli/studio.d.ts +14 -2
  178. package/dist/cli/studio.js +246 -71
  179. package/dist/cli/ui.d.ts +1 -1
  180. package/dist/client.d.ts +558 -85
  181. package/dist/client.js +1275 -400
  182. package/dist/connection-guard.d.ts +13 -0
  183. package/dist/connection-guard.js +27 -0
  184. package/dist/connection-url.d.ts +51 -16
  185. package/dist/connection-url.js +63 -34
  186. package/dist/diagnostics.d.ts +81 -0
  187. package/dist/diagnostics.js +144 -0
  188. package/dist/dialect.d.ts +449 -20
  189. package/dist/dialect.js +115 -43
  190. package/dist/engine-config.d.ts +7 -6
  191. package/dist/errors.d.ts +126 -6
  192. package/dist/errors.js +252 -12
  193. package/dist/generate.d.ts +57 -7
  194. package/dist/generate.js +635 -281
  195. package/dist/global-filters-config.d.ts +59 -0
  196. package/dist/global-filters-config.js +361 -0
  197. package/dist/index.d.ts +9 -4
  198. package/dist/index.js +4 -2
  199. package/dist/introspect.d.ts +31 -6
  200. package/dist/introspect.js +106 -50
  201. package/dist/log-codes.d.ts +12 -0
  202. package/dist/log-codes.js +74 -0
  203. package/dist/logger.d.ts +87 -0
  204. package/dist/logger.js +93 -0
  205. package/dist/mssql.d.ts +8 -5
  206. package/dist/mssql.js +354 -67
  207. package/dist/mysql.d.ts +3 -5
  208. package/dist/mysql.js +423 -73
  209. package/dist/nested-write.d.ts +3 -0
  210. package/dist/nested-write.js +177 -112
  211. package/dist/pipeline-submittable.d.ts +12 -0
  212. package/dist/pipeline-submittable.js +117 -44
  213. package/dist/pipeline.js +21 -26
  214. package/dist/powdb-introspect.d.ts +5 -2
  215. package/dist/powdb-introspect.js +21 -16
  216. package/dist/powdb-shared.d.ts +36 -5
  217. package/dist/powdb-shared.js +65 -8
  218. package/dist/powdb.d.ts +4 -0
  219. package/dist/powdb.js +183 -51
  220. package/dist/powql.d.ts +394 -51
  221. package/dist/powql.js +1836 -291
  222. package/dist/prisma-compat.d.ts +23 -15
  223. package/dist/prisma-compat.js +508 -373
  224. package/dist/query/aggregates.d.ts +17 -1
  225. package/dist/query/aggregates.js +263 -32
  226. package/dist/query/batched-loader.d.ts +25 -6
  227. package/dist/query/batched-loader.js +101 -21
  228. package/dist/query/builder.d.ts +325 -184
  229. package/dist/query/builder.js +1458 -495
  230. package/dist/query/column-ref.d.ts +61 -0
  231. package/dist/query/column-ref.js +66 -0
  232. package/dist/query/compound-unique.d.ts +21 -0
  233. package/dist/query/compound-unique.js +97 -19
  234. package/dist/query/deferred.d.ts +53 -16
  235. package/dist/query/expr.d.ts +217 -0
  236. package/dist/query/expr.js +848 -0
  237. package/dist/query/filters.d.ts +155 -53
  238. package/dist/query/filters.js +597 -72
  239. package/dist/query/index.d.ts +2 -0
  240. package/dist/query/index.js +4 -0
  241. package/dist/query/json-value.d.ts +43 -0
  242. package/dist/query/json-value.js +54 -0
  243. package/dist/query/normalize-args.d.ts +87 -0
  244. package/dist/query/normalize-args.js +517 -0
  245. package/dist/query/option-surface.d.ts +16 -4
  246. package/dist/query/option-surface.js +56 -13
  247. package/dist/query/relation-names.d.ts +14 -0
  248. package/dist/query/relation-names.js +77 -19
  249. package/dist/query/relations.d.ts +30 -3
  250. package/dist/query/relations.js +220 -67
  251. package/dist/query/row-lock.d.ts +49 -0
  252. package/dist/query/row-lock.js +131 -0
  253. package/dist/query/sql-comment.d.ts +161 -0
  254. package/dist/query/sql-comment.js +374 -0
  255. package/dist/query/stream-idle.d.ts +132 -0
  256. package/dist/query/stream-idle.js +203 -0
  257. package/dist/query/table-client.d.ts +101 -0
  258. package/dist/query/table-client.js +70 -0
  259. package/dist/query/types.d.ts +677 -298
  260. package/dist/query/utils.d.ts +105 -6
  261. package/dist/query/utils.js +414 -18
  262. package/dist/query/warn-registry.d.ts +18 -0
  263. package/dist/query/warn-registry.js +18 -0
  264. package/dist/query/where-compile.d.ts +138 -14
  265. package/dist/query/where-compile.js +508 -31
  266. package/dist/query/where.d.ts +159 -59
  267. package/dist/query/where.js +711 -248
  268. package/dist/query/writes.d.ts +112 -50
  269. package/dist/query/writes.js +1496 -202
  270. package/dist/query-events.d.ts +4 -2
  271. package/dist/query-events.js +3 -1
  272. package/dist/realtime.d.ts +7 -3
  273. package/dist/realtime.js +13 -2
  274. package/dist/schema-builder.d.ts +55 -4
  275. package/dist/schema-builder.js +381 -18
  276. package/dist/schema-metadata.d.ts +21 -1
  277. package/dist/schema-metadata.js +28 -7
  278. package/dist/schema-sql.d.ts +75 -5
  279. package/dist/schema-sql.js +494 -44
  280. package/dist/schema.d.ts +26 -5
  281. package/dist/schema.js +109 -13
  282. package/dist/sql-placeholders.d.ts +56 -0
  283. package/dist/sql-placeholders.js +200 -0
  284. package/dist/sqlite.d.ts +75 -20
  285. package/dist/sqlite.js +597 -99
  286. package/dist/supplied-pool-types.d.ts +48 -0
  287. package/dist/supplied-pool-types.js +247 -0
  288. package/dist/transaction-control.d.ts +51 -0
  289. package/dist/transaction-control.js +311 -0
  290. package/dist/transaction-state.d.ts +342 -0
  291. package/dist/transaction-state.js +696 -0
  292. package/dist/typed-sql.d.ts +37 -3
  293. package/dist/typed-sql.js +100 -8
  294. package/package.json +7 -4
  295. package/skills/turbine-orm/SKILL.md +56 -6
@@ -0,0 +1,707 @@
1
+ "use strict";
2
+ /**
3
+ * turbine-orm, the state one open transaction shares
4
+ *
5
+ * Everything a `$transaction` hands out (table accessors, raw SQL, nested
6
+ * `$transaction` blocks, streams) runs on ONE pooled connection. This module
7
+ * owns what those share:
8
+ *
9
+ * - whether the transaction is still open. Once it settles, a `tx` that was
10
+ * captured somewhere (an un-awaited promise, an `async` `forEach`, a timer)
11
+ * must not send anything: its connection is back in the pool and may
12
+ * already be running another request's transaction.
13
+ * - the first statement failure. PostgreSQL aborts a transaction at its first
14
+ * error and answers the COMMIT that follows with `ROLLBACK`; that failure is
15
+ * what the caller needs to see as the cause.
16
+ * - savepoint blocks. Savepoints are a stack on the connection, so two blocks
17
+ * open at once would share it and a rollback in one would undo the other.
18
+ * Blocks that share a parent therefore run one at a time, and a statement
19
+ * at the parent's level waits for a block opened before it.
20
+ * - what is still running: statements on the wire (what a cancel request
21
+ * stops, and what a ROLLBACK must wait for) and work started but not
22
+ * settled (which, when the callback returns, means a missing `await`).
23
+ * - whether the server's transaction is already aborted. On PostgreSQL a
24
+ * failed statement leaves the transaction ignoring every statement until a
25
+ * rollback to a savepoint set before it, so a statement sent then is
26
+ * refused here, with the failure that caused it, instead of coming back as
27
+ * an untyped SQLSTATE 25P02 that no retry would recognise.
28
+ * - which `tx` a statement came through. A nested block's callback gets its
29
+ * own handle, and a statement through an OUTER handle from inside the
30
+ * block is refused: it would run in the block's savepoint and be undone
31
+ * with it.
32
+ * - transaction-control SQL on the raw paths, which is refused (see
33
+ * {@link TransactionState.runRaw}).
34
+ *
35
+ * A leaf (errors.ts, the zero-import transaction-control.ts, and type-only
36
+ * imports), so query/ can reach it without an import edge back to client.ts: a QueryInterface inside a transaction holds
37
+ * only its pool, and that pool carries this state under
38
+ * {@link TRANSACTION_STATE}.
39
+ */
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.TransactionState = exports.TRANSACTION_STATE = void 0;
42
+ exports.transactionStateOf = transactionStateOf;
43
+ exports.withSavepoint = withSavepoint;
44
+ exports.commitTransaction = commitTransaction;
45
+ exports.commitWasRolledBack = commitWasRolledBack;
46
+ exports.rolledBackError = rolledBackError;
47
+ exports.closedTransactionError = closedTransactionError;
48
+ exports.unawaitedWorkError = unawaitedWorkError;
49
+ const node_async_hooks_1 = require("node:async_hooks");
50
+ const errors_js_1 = require("./errors.js");
51
+ const transaction_control_js_1 = require("./transaction-control.js");
52
+ /** The key a transaction-scoped pool carries its {@link TransactionState} under. */
53
+ exports.TRANSACTION_STATE = Symbol.for('turbine-orm.transactionState');
54
+ /** The savepoint block the current async context is running inside, if any. */
55
+ const currentFrame = new node_async_hooks_1.AsyncLocalStorage();
56
+ class TransactionState {
57
+ client;
58
+ dialect;
59
+ /** True when multi-statement writes run inside their own savepoint (see {@link withSavepoint}). */
60
+ savepointWrites;
61
+ endedAs;
62
+ failure;
63
+ /** A statement cut short that may still have run: the transaction must roll back (see {@link abort}). */
64
+ aborted;
65
+ /**
66
+ * The server's own error that left the PostgreSQL transaction in its aborted
67
+ * state, until a rollback to a savepoint set before it. Only recorded where
68
+ * a failed statement aborts the transaction (see {@link abortsOnError}) and
69
+ * only for a failure the SERVER reported: a client-side error (a value the
70
+ * driver could not serialize) never reached the transaction and changes
71
+ * nothing about what it will accept.
72
+ */
73
+ dead;
74
+ /** True where one failed statement makes the server ignore the rest of the transaction (PostgreSQL). */
75
+ abortsOnError;
76
+ /** The lexical rules for finding transaction-control SQL in raw text, or undefined where none apply (PowDB). */
77
+ controlSyntax;
78
+ tick = 0;
79
+ savepointCount = 0;
80
+ root;
81
+ /** Frames whose SAVEPOINT is set and not yet released or rolled back, outermost first. */
82
+ openFrames = [];
83
+ /** Statements sent on the connection that have not answered yet. */
84
+ onWire = 0;
85
+ /** Statements and savepoint blocks started through the transaction that have not settled. */
86
+ started = 0;
87
+ idleWaiters = [];
88
+ /** Settles at the first {@link end}, releasing anything still queued behind a block. */
89
+ endedSignal;
90
+ signalEnded;
91
+ api;
92
+ constructor(client, dialect, options = {}) {
93
+ this.client = client;
94
+ this.dialect = dialect;
95
+ this.root = {
96
+ state: this,
97
+ parent: undefined,
98
+ handle: true,
99
+ tail: Promise.resolve(),
100
+ children: 0,
101
+ openedAt: 0,
102
+ open: true,
103
+ };
104
+ this.savepointWrites = options.userTransaction === true && dialectHasSavepoints(dialect);
105
+ this.api = options.api ?? '$transaction';
106
+ this.abortsOnError = dialect.capabilities.abortsTransactionOnError;
107
+ this.controlSyntax = dialect.capabilities.statementSyntax ?? undefined;
108
+ this.endedSignal = new Promise((resolve) => {
109
+ this.signalEnded = resolve;
110
+ });
111
+ }
112
+ /** Whether the transaction has settled (see {@link end}). */
113
+ get ended() {
114
+ return this.endedAs !== undefined;
115
+ }
116
+ /**
117
+ * The first statement failure not undone by a savepoint rollback, if any.
118
+ * Where the server aborted the transaction, the failure that did it: that is
119
+ * the one a retry decision follows.
120
+ */
121
+ get firstFailure() {
122
+ return this.dead?.error ?? this.failure?.error;
123
+ }
124
+ /** The scope of the transaction itself: what the `tx` handed to the callback is bound to. */
125
+ get rootScope() {
126
+ return this.root;
127
+ }
128
+ /** Why the transaction must roll back instead of committing (see {@link abort}), if it must. */
129
+ get abortedWith() {
130
+ return this.aborted?.error;
131
+ }
132
+ /**
133
+ * Whether a statement or savepoint block started through the transaction is
134
+ * still running. True when the callback settles means work it started was
135
+ * not awaited.
136
+ */
137
+ get busy() {
138
+ return this.started > 0;
139
+ }
140
+ /** Whether a statement is on the wire right now (what a cancel request would stop). */
141
+ get running() {
142
+ return this.onWire > 0;
143
+ }
144
+ /**
145
+ * Run the transaction's callback. Statements and blocks it starts are then
146
+ * known to sit at the transaction's top level, which is what lets a block
147
+ * whose async context was lost be told apart from a sibling.
148
+ */
149
+ enter(fn) {
150
+ return currentFrame.run(this.root, fn);
151
+ }
152
+ /**
153
+ * Resolve `true` once no statement is on the wire, or `false` after `ms`.
154
+ * Called after {@link end}, when nothing new can be sent.
155
+ */
156
+ whenIdle(ms) {
157
+ if (this.onWire === 0)
158
+ return Promise.resolve(true);
159
+ return new Promise((resolve) => {
160
+ const timer = setTimeout(() => resolve(false), ms);
161
+ this.idleWaiters.push(() => {
162
+ clearTimeout(timer);
163
+ resolve(true);
164
+ });
165
+ });
166
+ }
167
+ /**
168
+ * Mark the transaction settled. Called as soon as the callback settles,
169
+ * BEFORE the COMMIT or ROLLBACK, and again with the outcome; from the first
170
+ * call on every statement issued through the transaction is refused.
171
+ */
172
+ end(how) {
173
+ if (this.endedAs === undefined)
174
+ this.signalEnded();
175
+ this.endedAs = how;
176
+ }
177
+ /** Throw the closed-transaction error when the transaction has settled. */
178
+ assertOpen() {
179
+ if (this.endedAs !== undefined)
180
+ throw closedTransactionError(this.endedAs, this.api);
181
+ }
182
+ /**
183
+ * Run a statement issued THROUGH the transaction (an accessor, raw SQL, a
184
+ * stream): refused once the transaction has settled, and its failure recorded
185
+ * as a candidate cause for a later ROLLBACK-at-COMMIT.
186
+ *
187
+ * `scope` is the handle the statement came through (the transaction's own
188
+ * `tx`, or the one a nested block's callback received). A statement through
189
+ * an OUTER handle from inside an open block is refused: it would run in the
190
+ * block's savepoint and be undone with it.
191
+ *
192
+ * A statement issued while a savepoint block started before it at the same
193
+ * level is still open waits for that block. Sent at once it would run inside
194
+ * the block's savepoint and be undone if the block rolled back, after it had
195
+ * already reported success.
196
+ *
197
+ * On PostgreSQL a statement sent after one failed is not sent at all: the
198
+ * server would ignore it (SQLSTATE 25P02) and the failure that caused that
199
+ * would be lost. `recovery` marks the one statement that is sent regardless,
200
+ * a rollback to a savepoint the caller set by hand (see {@link runRaw}).
201
+ */
202
+ async run(statement, scope = this.root, recovery = false) {
203
+ this.assertOpen();
204
+ this.assertNotFromInsideBlock(scope, 'A statement');
205
+ this.started++;
206
+ try {
207
+ const inside = currentFrame.getStore();
208
+ if (inside?.state === this && inside.children > 0) {
209
+ await Promise.race([inside.tail, this.endedSignal]);
210
+ this.assertOpen();
211
+ }
212
+ if (!recovery)
213
+ this.assertNotAborted();
214
+ const result = await this.send(statement);
215
+ if (recovery)
216
+ this.clearAbort();
217
+ return result;
218
+ }
219
+ finally {
220
+ this.started--;
221
+ }
222
+ }
223
+ /**
224
+ * {@link run} for a statement the caller wrote (`tx.raw`, `tx.rawUnsafe`,
225
+ * `tx.sql`, the connection `$connection()` and `transaction()` hand out).
226
+ *
227
+ * Transaction-control SQL is refused with E003 before anything is sent.
228
+ * `COMMIT`, `ROLLBACK` or `BEGIN` ends or reshapes the transaction behind the
229
+ * state that tracks it, so later writes would autocommit and the callback's
230
+ * own throw would keep them. A savepoint of the caller's own sits on the
231
+ * connection's stack beside the ones Turbine sets, so those are refused too
232
+ * inside `$transaction`, where `tx.$transaction(...)` is the savepoint.
233
+ *
234
+ * The raw `transaction()` API has no such block, so `SAVEPOINT`, `RELEASE`
235
+ * and `ROLLBACK TO` stay available there: the only way to recover from a
236
+ * failed statement without losing the transaction. A `ROLLBACK TO` is also
237
+ * the one statement sent while the transaction is aborted, and when it
238
+ * succeeds the abort is over.
239
+ */
240
+ async runRaw(sql, statement, scope = this.root) {
241
+ return this.run(statement, scope, this.screenRaw(sql));
242
+ }
243
+ /**
244
+ * Check raw SQL for transaction control without sending it: throws E003 for
245
+ * a statement the caller may not send, and answers whether the text rolls
246
+ * back to a savepoint (the one control statement that is allowed to run in an
247
+ * aborted transaction).
248
+ */
249
+ screenRaw(sql) {
250
+ if (this.controlSyntax === undefined || typeof sql !== 'string' || !(0, transaction_control_js_1.mayHoldTransactionControl)(sql))
251
+ return false;
252
+ const found = (0, transaction_control_js_1.findTransactionControl)(sql, this.controlSyntax);
253
+ if (found.length === 0)
254
+ return false;
255
+ const refused = found.find((s) => !(this.api === 'transaction' && SAVEPOINT_KINDS.has(s.kind)));
256
+ if (refused !== undefined)
257
+ throw transactionControlError(refused, this.api);
258
+ return found.every((s) => s.kind === 'rollback-to');
259
+ }
260
+ /** Put one statement on the wire, counted for {@link whenIdle} and recorded on failure. */
261
+ async send(statement) {
262
+ this.onWire++;
263
+ try {
264
+ return await statement();
265
+ }
266
+ catch (err) {
267
+ this.noteFailure(err);
268
+ if (this.abortsOnError && reportedByServer(err))
269
+ this.noteDead(err);
270
+ throw err;
271
+ }
272
+ finally {
273
+ this.onWire--;
274
+ if (this.onWire === 0 && this.idleWaiters.length > 0) {
275
+ const waiters = this.idleWaiters;
276
+ this.idleWaiters = [];
277
+ for (const wake of waiters)
278
+ wake();
279
+ }
280
+ }
281
+ }
282
+ /** Record `err` as the first failure, unless one is already recorded. */
283
+ noteFailure(err) {
284
+ if (this.failure === undefined)
285
+ this.failure = { error: err, at: ++this.tick };
286
+ }
287
+ /** Record `err` as what aborted the server's transaction, unless something already did. */
288
+ noteDead(err) {
289
+ if (this.dead === undefined)
290
+ this.dead = { error: err, at: ++this.tick };
291
+ }
292
+ /**
293
+ * Mark the transaction as one that must not commit, because of `err`: a
294
+ * statement timed out and was cancelled, and whether it ran cannot be
295
+ * relied on. The server aborts the transaction when the cancel lands, but a
296
+ * cancel that arrives after the statement finished does nothing, and a
297
+ * COMMIT would then keep it. A rollback to a savepoint set before the
298
+ * statement clears the mark, as it clears the failure.
299
+ *
300
+ * Statements after it are refused like those after any failed statement: the
301
+ * transaction cannot commit, so there is nothing to send them for.
302
+ */
303
+ abort(err) {
304
+ this.noteFailure(err);
305
+ if (this.aborted === undefined)
306
+ this.aborted = { error: err, at: ++this.tick };
307
+ // The cancel's own error (57014) may already be recorded as the cause; this one says more.
308
+ if (this.abortsOnError)
309
+ this.dead = { error: err, at: this.dead?.at ?? ++this.tick };
310
+ }
311
+ /**
312
+ * Refuse a statement the PostgreSQL transaction would ignore, with the
313
+ * failure that aborted it. The same error a COMMIT in this state gets
314
+ * ({@link rolledBackError}), so a deadlock, serialization conflict or lock
315
+ * wait that was caught and followed by one more statement is still the
316
+ * retryable class `$retry` looks for.
317
+ */
318
+ assertNotAborted() {
319
+ if (this.dead !== undefined)
320
+ throw rolledBackError(this.dead.error, 'statement', this.api);
321
+ }
322
+ /** The caller rolled back to a savepoint of their own and the server accepted it: the abort is over. */
323
+ clearAbort() {
324
+ if (this.dead === undefined)
325
+ return;
326
+ if (this.failure !== undefined && this.failure.error === this.dead.error)
327
+ this.failure = undefined;
328
+ this.dead = undefined;
329
+ }
330
+ /**
331
+ * Refuse `what` issued through `scope` when the async context it was issued
332
+ * from is inside a block opened through a handle nested under `scope`.
333
+ *
334
+ * `tx.$transaction(async (inner) => { await tx.users.update(...) })` sends
335
+ * the update through the OUTER handle, but the connection is inside the
336
+ * block's savepoint, so a throw from the block would undo an update the
337
+ * caller placed outside it. The block's callback has its own handle for work
338
+ * inside it. A context this state cannot see (a timer created before the
339
+ * transaction, a callback API that drops the async context) is not refused:
340
+ * it cannot be told apart from a statement at the right level.
341
+ */
342
+ assertNotFromInsideBlock(scope, what) {
343
+ const here = currentFrame.getStore();
344
+ if (here === undefined || here.state !== this || here === scope)
345
+ return;
346
+ let inner;
347
+ for (let frame = here; frame !== undefined; frame = frame.parent) {
348
+ if (frame === scope) {
349
+ if (inner !== undefined)
350
+ throw outerHandleError(what);
351
+ return;
352
+ }
353
+ // A write's own savepoint belongs to the handle that issued the write.
354
+ if (frame.handle && frame.open && inner === undefined)
355
+ inner = frame;
356
+ }
357
+ }
358
+ /**
359
+ * Run `block` inside a savepoint: released when it resolves, rolled back to
360
+ * when it rejects, so the rest of the transaction is unaffected either way.
361
+ *
362
+ * Blocks that share a parent run one at a time. A savepoint is a position on
363
+ * the connection's one stack, so a block that opened second sits inside the
364
+ * first: rolling the first back would discard the second even after it had
365
+ * released and reported success. A block started from INSIDE another block's
366
+ * callback is nested under it and does not wait.
367
+ *
368
+ * A block whose callback swallowed a statement failure resolves normally,
369
+ * but PostgreSQL refuses to RELEASE a savepoint inside an aborted
370
+ * transaction. That case rolls the savepoint back and rejects with the same
371
+ * rolled-back error a COMMIT gets, carrying the failure as `cause`, so the
372
+ * outer transaction stays usable.
373
+ *
374
+ * This is the savepoint of ONE multi-statement write, which belongs to the
375
+ * handle that issued it. {@link withBlock} is a block the caller opened.
376
+ */
377
+ withSavepoint(block) {
378
+ return this.openSavepoint(block, false, this.root);
379
+ }
380
+ /**
381
+ * Run `block` as a nested `tx.$transaction(...)`: a savepoint (see
382
+ * {@link withSavepoint}) whose callback receives a handle of its own, bound to
383
+ * the scope it is given. `caller` is the handle `$transaction` was called on.
384
+ */
385
+ withBlock(block, caller) {
386
+ return this.openSavepoint(block, true, caller);
387
+ }
388
+ async openSavepoint(block, handle, caller) {
389
+ this.assertOpen();
390
+ if (handle)
391
+ this.assertNotFromInsideBlock(caller, 'A nested `$transaction`');
392
+ const inside = currentFrame.getStore();
393
+ const known = inside?.state === this;
394
+ // No frame of this transaction means the caller's async context was lost
395
+ // (a callback API that does not carry it, such as a timer created before
396
+ // the transaction). Such a block cannot be told apart from a sibling, and
397
+ // waiting as one could mean waiting on its own ancestor forever, so it
398
+ // nests under the innermost open savepoint: where the connection puts it.
399
+ const parent = known ? inside : (this.openFrames[this.openFrames.length - 1] ?? this.root);
400
+ const previous = parent.tail;
401
+ let releaseSlot;
402
+ const mine = new Promise((resolve) => {
403
+ releaseSlot = resolve;
404
+ });
405
+ parent.tail = known ? mine : Promise.all([previous, mine]).then(() => undefined);
406
+ parent.children++;
407
+ this.started++;
408
+ let frame;
409
+ try {
410
+ if (known) {
411
+ await Promise.race([previous, this.endedSignal]);
412
+ this.assertOpen();
413
+ }
414
+ this.assertNotAborted();
415
+ const name = `sp_${++this.savepointCount}`;
416
+ await this.control(this.dialect.savepointStatement(name));
417
+ const opened = {
418
+ state: this,
419
+ parent,
420
+ handle,
421
+ tail: Promise.resolve(),
422
+ children: 0,
423
+ openedAt: ++this.tick,
424
+ open: true,
425
+ };
426
+ frame = opened;
427
+ this.openFrames.push(opened);
428
+ let result;
429
+ try {
430
+ result = await currentFrame.run(opened, () => block(opened));
431
+ }
432
+ catch (err) {
433
+ await this.rollbackTo(name, opened);
434
+ throw err;
435
+ }
436
+ // A failure the block caught (or a statement its timeout cut short) leaves
437
+ // the server's savepoint unusable, and PostgreSQL would refuse the RELEASE
438
+ // anyway, so the block rolls back without asking.
439
+ const doomed = this.doomedSince(opened);
440
+ if (doomed !== undefined) {
441
+ await this.rollbackTo(name, opened);
442
+ throw rolledBackError(doomed.error, 'savepoint');
443
+ }
444
+ try {
445
+ await this.control(this.dialect.releaseSavepointStatement(name));
446
+ }
447
+ catch (err) {
448
+ const cause = this.failureSince(opened) ?? err;
449
+ await this.rollbackTo(name, opened);
450
+ throw rolledBackError(cause, 'savepoint');
451
+ }
452
+ return result;
453
+ }
454
+ finally {
455
+ if (frame !== undefined) {
456
+ frame.open = false;
457
+ const at = this.openFrames.lastIndexOf(frame);
458
+ if (at !== -1)
459
+ this.openFrames.splice(at, 1);
460
+ }
461
+ parent.children--;
462
+ this.started--;
463
+ releaseSlot();
464
+ }
465
+ }
466
+ /** The recorded failure, when it happened after `frame`'s savepoint was set. */
467
+ failureSince(frame) {
468
+ return this.failure !== undefined && this.failure.at > frame.openedAt ? this.failure.error : undefined;
469
+ }
470
+ /**
471
+ * What stops `frame` from being released: a statement cut short by its
472
+ * timeout, or (on PostgreSQL) a failure the server aborted the savepoint for,
473
+ * either one after the savepoint was set.
474
+ */
475
+ doomedSince(frame) {
476
+ if (this.aborted !== undefined && this.aborted.at > frame.openedAt)
477
+ return this.aborted;
478
+ if (this.dead !== undefined && this.dead.at > frame.openedAt)
479
+ return this.dead;
480
+ return undefined;
481
+ }
482
+ async rollbackTo(name, frame) {
483
+ try {
484
+ await this.control(this.dialect.rollbackToSavepointStatement(name));
485
+ }
486
+ catch {
487
+ // The transaction cannot take the rollback (it settled, or the connection
488
+ // is gone). The error that brought the block down is the one to report.
489
+ return;
490
+ }
491
+ if (this.failureSince(frame) !== undefined)
492
+ this.failure = undefined;
493
+ if (this.aborted !== undefined && this.aborted.at > frame.openedAt)
494
+ this.aborted = undefined;
495
+ if (this.dead !== undefined && this.dead.at > frame.openedAt)
496
+ this.dead = undefined;
497
+ }
498
+ /**
499
+ * A savepoint statement: refused once settled, failures recorded and typed.
500
+ * Typed INSIDE the statement, so the failure the state records is the Turbine
501
+ * error and not the driver's.
502
+ */
503
+ async control(sql) {
504
+ this.assertOpen();
505
+ await this.send(async () => {
506
+ try {
507
+ await this.client.query(sql);
508
+ }
509
+ catch (err) {
510
+ throw (0, errors_js_1.wrapPgError)(err);
511
+ }
512
+ });
513
+ }
514
+ }
515
+ exports.TransactionState = TransactionState;
516
+ /** The state carried by a transaction-scoped pool, or undefined for any other pool. */
517
+ function transactionStateOf(pool) {
518
+ if (pool === null || typeof pool !== 'object')
519
+ return undefined;
520
+ const state = pool[exports.TRANSACTION_STATE];
521
+ return state instanceof TransactionState ? state : undefined;
522
+ }
523
+ /**
524
+ * Run a multi-statement operation as one unit inside the caller's transaction.
525
+ *
526
+ * When `pool` belongs to a transaction the caller opened (`$transaction`,
527
+ * `$withSession`), `operation` runs inside its own savepoint: if it throws
528
+ * part way through, the statements it already ran are rolled back and the
529
+ * caller's transaction carries on as if the operation had never started. On
530
+ * any other pool (no transaction, or Turbine's own implicit transaction, which
531
+ * is atomic already) it simply runs `operation`.
532
+ *
533
+ * Every user-facing write that issues more than one statement inside a
534
+ * transaction already goes through this from `TransactionClient`'s accessors
535
+ * (nested creates and updates, lookup-first upserts); call it from any new
536
+ * multi-statement operation that can be reached some other way.
537
+ */
538
+ function withSavepoint(pool, operation) {
539
+ const state = transactionStateOf(pool);
540
+ return state?.savepointWrites ? state.withSavepoint(operation) : operation();
541
+ }
542
+ /**
543
+ * Send COMMIT and refuse to report success when the server rolled back instead.
544
+ *
545
+ * PostgreSQL answers COMMIT in an aborted transaction with the command tag
546
+ * `ROLLBACK` and no error. Other engines never set `command` on a COMMIT
547
+ * result, so the check is inert for them.
548
+ */
549
+ async function commitTransaction(client, commitSql, firstFailure, api = '$transaction') {
550
+ let result;
551
+ try {
552
+ result = await client.query(commitSql);
553
+ }
554
+ catch (err) {
555
+ throw (0, errors_js_1.wrapPgError)(err);
556
+ }
557
+ if (commitWasRolledBack(result))
558
+ throw rolledBackError(firstFailure, 'transaction', api);
559
+ }
560
+ /** Whether a COMMIT result reports that the server rolled the transaction back. */
561
+ function commitWasRolledBack(result) {
562
+ return result?.command === 'ROLLBACK';
563
+ }
564
+ /**
565
+ * The error for work that was rolled back although the caller expected it to
566
+ * commit (a transaction) or be kept (a savepoint block).
567
+ *
568
+ * A retryable first failure (deadlock, serialization conflict, lock not
569
+ * available) keeps its class, so `$retry` and `isRetryable` checks retry the
570
+ * whole transaction, which is the documented response to all three. Anything
571
+ * else is a {@link TransactionRolledBackError} (E019) with the first failure as
572
+ * `cause`: nothing was committed, and the server did the rolling back.
573
+ */
574
+ function rolledBackError(firstFailure, scope, api = '$transaction') {
575
+ const head = scope === 'transaction'
576
+ ? 'The transaction was rolled back, not committed: a statement inside it failed, and PostgreSQL ' +
577
+ 'aborts a transaction at its first error, so COMMIT ended it as a ROLLBACK and none of its writes were saved.'
578
+ : scope === 'savepoint'
579
+ ? 'The nested `$transaction` block was rolled back, not released: a statement inside it failed, and ' +
580
+ 'PostgreSQL aborts at the first error, so none of the writes inside the block were kept. The outer ' +
581
+ 'transaction is usable again.'
582
+ : 'This statement was not sent: an earlier statement in the transaction failed, and PostgreSQL ignores ' +
583
+ 'every statement after a failure until the transaction, or the savepoint it ran in, is rolled back. ' +
584
+ 'Nothing in the transaction can be saved now.';
585
+ const message = `${head}${firstFailure !== undefined ? ' The statement that failed first is attached as `cause`.' : ''} ` +
586
+ 'Catching an error inside the callback does not undo the abort. To recover from an expected failure and ' +
587
+ 'keep the rest of the work, ' +
588
+ (api === '$transaction'
589
+ ? 'run the statement that can fail in its own nested `tx.$transaction(...)`, which rolls back only that savepoint.'
590
+ : 'send `SAVEPOINT s` before the statement that can fail and `ROLLBACK TO SAVEPOINT s` when it does, which ' +
591
+ 'undoes only that statement.');
592
+ // A failure that came through a raw driver connection (`transaction()`, `$connection()`,
593
+ // `tx.sql`) is the driver's error, not yet a typed one; its class decides whether to retry.
594
+ const typed = (0, errors_js_1.wrapPgError)(firstFailure);
595
+ if (typed instanceof errors_js_1.LockNotAvailableError)
596
+ return new errors_js_1.LockNotAvailableError({ message, cause: firstFailure });
597
+ if (typed instanceof errors_js_1.SerializationFailureError) {
598
+ return new errors_js_1.SerializationFailureError({ message, cause: firstFailure });
599
+ }
600
+ if (typed instanceof errors_js_1.DeadlockError)
601
+ return new errors_js_1.DeadlockError({ message, cause: firstFailure });
602
+ return new errors_js_1.TransactionRolledBackError(message, firstFailure !== undefined ? { cause: firstFailure } : undefined);
603
+ }
604
+ /** The error for a statement sent through a transaction that has settled. */
605
+ function closedTransactionError(how, api = '$transaction') {
606
+ const handle = api === '$transaction' ? '`tx`' : 'the client `transaction()` passed in';
607
+ return new errors_js_1.ValidationError(`This transaction ${how}, so a query issued through ${handle} afterwards was refused rather than sent on a ` +
608
+ `connection that no longer belongs to it. Every query on it has to finish inside the \`${api}\` callback. ` +
609
+ 'The usual causes are a missing `await`, an `async` callback passed to `forEach` (use `for ... of` or ' +
610
+ '`await Promise.all(...)`), or the handle kept in a variable or a timer. Run work that belongs after the ' +
611
+ 'transaction on the Turbine client itself.');
612
+ }
613
+ /**
614
+ * The error for a callback that returned while work it started through the
615
+ * transaction was still running. Committing then would save whatever part of
616
+ * that work had run, so the transaction is rolled back instead.
617
+ */
618
+ function unawaitedWorkError(api) {
619
+ return new errors_js_1.ValidationError(`The \`${api}\` callback returned while a query it started on the transaction was still running, so the ` +
620
+ 'transaction was rolled back instead of committing part of that work. Await every query before the ' +
621
+ 'callback returns. The usual causes are a missing `await`, an `async` callback passed to `forEach` (use ' +
622
+ '`for ... of` or `await Promise.all(...)`), or a promise that was started and not returned.');
623
+ }
624
+ /**
625
+ * The error for a statement (or a nested `$transaction`) issued through an
626
+ * outer `tx` from inside one of its open nested blocks.
627
+ */
628
+ function outerHandleError(what) {
629
+ return new errors_js_1.ValidationError(`${what} was issued through the outer \`tx\` from inside one of its nested \`$transaction\` blocks. The ` +
630
+ "connection is inside that block's savepoint, so the work would be undone if the block rolled back, " +
631
+ "although the outer transaction goes on to commit. Use the `tx` the block's callback received for work " +
632
+ 'inside the block (`tx.$transaction(async (inner) => { await inner.users.update(...) })`), or issue this ' +
633
+ 'outside the block.');
634
+ }
635
+ const SAVEPOINT_KINDS = new Set(['savepoint', 'release', 'rollback-to']);
636
+ /** The error for transaction-control SQL on a transaction's raw paths: what it would do, and what to do instead. */
637
+ function transactionControlError(statement, api) {
638
+ const where = api === '$transaction' ? 'inside a `$transaction` callback' : 'inside a `transaction()` callback';
639
+ const ends = 'it would end the transaction behind the back of the code that manages it, so every later statement ' +
640
+ 'would run on its own and be kept even if the callback then throws';
641
+ let advice;
642
+ let reason;
643
+ switch (statement.kind) {
644
+ case 'commit':
645
+ reason = ends;
646
+ advice = 'The transaction commits when the callback returns. Return from the callback to commit.';
647
+ break;
648
+ case 'rollback':
649
+ reason = ends;
650
+ advice =
651
+ api === '$transaction'
652
+ ? 'Throw an error from the callback to roll the transaction back, or run the work you may want to ' +
653
+ "undo in a nested `tx.$transaction(...)`, which rolls back only that block's savepoint."
654
+ : 'Throw an error from the callback to roll the transaction back, or send `SAVEPOINT s` first and ' +
655
+ '`ROLLBACK TO SAVEPOINT s` to undo part of the work.';
656
+ break;
657
+ case 'begin':
658
+ reason = 'a transaction is already open, and a second BEGIN does not nest';
659
+ advice =
660
+ api === '$transaction'
661
+ ? 'For a block that can roll back on its own, use `tx.$transaction(...)`, which sets a savepoint.'
662
+ : 'For a block that can roll back on its own, send `SAVEPOINT s`.';
663
+ break;
664
+ case 'set-transaction':
665
+ reason = 'the transaction has already begun, so its characteristics can no longer be changed';
666
+ advice = 'Pass `isolationLevel` and `readOnly` as the options of `$transaction(fn, options)` (or `transaction`).';
667
+ break;
668
+ case 'prepare':
669
+ reason = ends;
670
+ advice = 'Prepare the transaction from a connection you manage yourself, outside Turbine.';
671
+ break;
672
+ default:
673
+ // savepoint, release, rollback-to: only reachable inside `$transaction`.
674
+ reason =
675
+ 'a savepoint written by hand sits on the connection beside the ones `tx.$transaction` sets, and ' +
676
+ 'a rollback to the wrong one discards work the transaction state still counts as kept';
677
+ advice =
678
+ 'Use a nested `tx.$transaction(async (inner) => { ... })`, which sets, releases and rolls back its own savepoint.';
679
+ }
680
+ return new errors_js_1.ValidationError(`${statement.label} cannot be sent ${where}: ${reason}. ${advice}`);
681
+ }
682
+ /**
683
+ * Whether the SERVER reported `err`: a driver error carrying a SQLSTATE and a
684
+ * severity, directly or as the `cause` of the Turbine error wrapping it. A
685
+ * client-side failure (a value the driver could not serialize, a closed
686
+ * client) has neither, and says nothing about the state of the transaction.
687
+ */
688
+ function reportedByServer(err) {
689
+ let current = err;
690
+ for (let depth = 0; depth < 4 && current !== null && typeof current === 'object'; depth++) {
691
+ const { code, severity } = current;
692
+ if (typeof code === 'string' && typeof severity === 'string' && /^[0-9A-Z]{5}$/.test(code))
693
+ return true;
694
+ current = current.cause;
695
+ }
696
+ return false;
697
+ }
698
+ /** Whether `dialect` can set a savepoint at all (PowDB cannot, and says so by throwing). */
699
+ function dialectHasSavepoints(dialect) {
700
+ try {
701
+ dialect.savepointStatement('sp_probe');
702
+ return true;
703
+ }
704
+ catch {
705
+ return false;
706
+ }
707
+ }