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
@@ -22,10 +22,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
22
22
  };
23
23
  Object.defineProperty(exports, "__esModule", { value: true });
24
24
  exports.pipelineClientNeedsDiscard = pipelineClientNeedsDiscard;
25
+ exports.batchMayWrite = batchMayWrite;
25
26
  exports.runPipelined = runPipelined;
26
27
  exports.supportsExtendedPipeline = supportsExtendedPipeline;
27
28
  const _pg_1 = __importDefault(require("pg"));
28
29
  const errors_js_1 = require("./errors.js");
30
+ const transaction_state_js_1 = require("./transaction-state.js");
29
31
  // Read off pg's own module object (`pg.Result` IS `pg/lib/result`, `pg.utils`
30
32
  // IS `pg/lib/utils`) rather than imported by subpath, so the whole driver comes
31
33
  // through the one `#pg` alias that edge bundles swap out (see src/pg-edge.ts).
@@ -94,6 +96,16 @@ function restoreListeners(emitter, snapshot) {
94
96
  // ---------------------------------------------------------------------------
95
97
  // Core pipeline execution
96
98
  // ---------------------------------------------------------------------------
99
+ /** A statement that only reads: its transform may run after COMMIT, since there is nothing to undo. */
100
+ const READ_ONLY_STATEMENT = /^\s*select\b/i;
101
+ /**
102
+ * Whether a transactional batch must hold its COMMIT until the transforms have
103
+ * run. Anything that is not a plain SELECT counts as a write, so a statement
104
+ * this cannot classify costs a round trip rather than correctness.
105
+ */
106
+ function batchMayWrite(queries) {
107
+ return queries.some((q) => !READ_ONLY_STATEMENT.test(q.sql));
108
+ }
97
109
  /**
98
110
  * Execute multiple queries using the Postgres extended-query pipeline protocol.
99
111
  *
@@ -120,11 +132,20 @@ async function runPipelined(client, queries, options = {}) {
120
132
  // State machine
121
133
  // -----------------------------------------------------------------------
122
134
  /**
123
- * Total expected commandComplete events:
124
- * - Transactional: BEGIN + N queries + COMMIT = N + 2
125
- * - Non-transactional: N queries
135
+ * True when the batch is sent as BEGIN + queries + Sync and the COMMIT
136
+ * follows only once every transform has succeeded (see
137
+ * {@link PipelineRunOptions.transactional}).
126
138
  */
127
- const totalCommands = transactional ? queries.length + 2 : queries.length;
139
+ const holdCommit = transactional && batchMayWrite(queries);
140
+ /**
141
+ * Where the exchange is: the batch itself, or the single follow-up
142
+ * statement (the held COMMIT, or the recovery ROLLBACK) sent after it.
143
+ */
144
+ let phase = 'batch';
145
+ /** The command tag the held COMMIT came back with (`ROLLBACK` when the server rolled back). */
146
+ let commitTag;
147
+ /** Transformed results, computed before a held COMMIT is sent. */
148
+ let transformed;
128
149
  /**
129
150
  * Expected readyForQuery events:
130
151
  * - Transactional: 1 (single Sync at end)
@@ -150,20 +171,17 @@ async function runPipelined(client, queries, options = {}) {
150
171
  let cleaned = false;
151
172
  /** Transaction status reported by the most recent ReadyForQuery. */
152
173
  let txStatus;
153
- /** Whether the recovery ROLLBACK has already been sent (it is sent at most once). */
154
- let rollbackSent = false;
155
174
  /**
156
175
  * Map commandComplete index to the corresponding results[] index.
157
176
  * In transactional mode: index 0 = BEGIN, 1..N = queries, N+1 = COMMIT
158
- * In non-transactional mode: index maps 1:1
177
+ * (absent when the COMMIT is held). In non-transactional mode: 1:1.
178
+ * Follow-up statements belong to no query.
159
179
  */
160
180
  function commandToQueryIndex(cmdIdx) {
161
- if (transactional) {
162
- if (cmdIdx === 0 || cmdIdx === totalCommands - 1)
163
- return null;
164
- return cmdIdx - 1;
165
- }
166
- return cmdIdx;
181
+ if (phase !== 'batch')
182
+ return null;
183
+ const qIdx = transactional ? cmdIdx - 1 : cmdIdx;
184
+ return qIdx >= 0 && qIdx < queries.length ? qIdx : null;
167
185
  }
168
186
  /** Get the current query-results index for row data */
169
187
  function currentQueryIndex() {
@@ -222,30 +240,56 @@ async function runPipelined(client, queries, options = {}) {
222
240
  return true;
223
241
  if (txStatus === undefined && !(transactional && pipelineError))
224
242
  return true;
225
- if (!transactional || rollbackSent) {
226
- // Either nothing to roll back (non-transactional mode opens no
227
- // transaction of its own), or the rollback already ran and did not
228
- // clear the status. Hand the client back marked for disposal.
229
- if (txStatus === 'E' || txStatus === 'T') {
230
- Object.defineProperty(client, PIPELINE_DISCARD, { value: true, enumerable: false, configurable: true });
231
- }
243
+ if (!transactional) {
244
+ // Non-transactional mode opens no transaction of its own, so there is
245
+ // nothing to roll back. Hand the client back marked for disposal.
246
+ markDiscardIfInTransaction();
232
247
  return true;
233
248
  }
234
- rollbackSent = true;
249
+ return !sendFollowUp('ROLLBACK', 'rollback');
250
+ }
251
+ /** Mark the client for disposal when the backend still reports an open or failed transaction. */
252
+ function markDiscardIfInTransaction() {
253
+ if (txStatus === 'E' || txStatus === 'T') {
254
+ Object.defineProperty(client, PIPELINE_DISCARD, { value: true, enumerable: false, configurable: true });
255
+ }
256
+ }
257
+ /**
258
+ * Send one statement after the batch (the held COMMIT or the recovery
259
+ * ROLLBACK) with its own Sync; its ReadyForQuery finishes the exchange.
260
+ * Returns false when the socket would not take it, in which case the
261
+ * connection is marked for disposal, since it cannot be repaired from here.
262
+ */
263
+ function sendFollowUp(text, next) {
264
+ phase = next;
235
265
  try {
236
- connection.parse({ text: 'ROLLBACK', name: '' });
237
- connection.bind({ portal: '', statement: '', values: [], valueMapper: prepareValue });
238
- connection.execute({ portal: '', rows: 0 });
239
- connection.sync();
240
- return false;
266
+ connection.stream.cork?.();
267
+ try {
268
+ connection.parse({ text, name: '' });
269
+ connection.bind({ portal: '', statement: '', values: [], valueMapper: prepareValue });
270
+ connection.execute({ portal: '', rows: 0 });
271
+ connection.sync();
272
+ }
273
+ finally {
274
+ connection.stream.uncork?.();
275
+ }
276
+ return true;
241
277
  }
242
- catch {
243
- // The socket would not take the rollback, so this connection cannot be
244
- // repaired from here. Mark it and let the caller drop it.
278
+ catch (err) {
245
279
  Object.defineProperty(client, PIPELINE_DISCARD, { value: true, enumerable: false, configurable: true });
246
- return true;
280
+ if (!pipelineError)
281
+ pipelineError = err instanceof Error ? err : new Error(String(err));
282
+ return false;
247
283
  }
248
284
  }
285
+ /** Run every query's transform, in order; the first throw propagates. */
286
+ function transformAll() {
287
+ const out = [];
288
+ for (let i = 0; i < queries.length; i++) {
289
+ out.push(queries[i].transform(results[i]));
290
+ }
291
+ return out;
292
+ }
249
293
  function finalize() {
250
294
  cleanup();
251
295
  if (transactional && pipelineError) {
@@ -288,14 +332,14 @@ async function runPipelined(client, queries, options = {}) {
288
332
  }));
289
333
  return;
290
334
  }
291
- // All succeeded, transform results
292
- try {
293
- const transformed = [];
294
- for (let i = 0; i < queries.length; i++) {
295
- const q = queries[i];
296
- transformed.push(q.transform(results[i]));
297
- }
335
+ // All succeeded. A held COMMIT ran the transforms before committing;
336
+ // a read-only batch runs them now (there is nothing to undo).
337
+ if (transformed) {
298
338
  resolve(transformed);
339
+ return;
340
+ }
341
+ try {
342
+ resolve(transformAll());
299
343
  }
300
344
  catch (err) {
301
345
  reject(err);
@@ -328,6 +372,8 @@ async function runPipelined(client, queries, options = {}) {
328
372
  }
329
373
  }
330
374
  function onCommandComplete(msg) {
375
+ if (phase === 'commit')
376
+ commitTag = msg.command ?? msg.text;
331
377
  const qIdx = currentQueryIndex();
332
378
  if (qIdx !== null && qIdx >= 0 && qIdx < results.length) {
333
379
  results[qIdx].addCommandComplete(msg);
@@ -374,15 +420,41 @@ async function runPipelined(client, queries, options = {}) {
374
420
  }
375
421
  function onReadyForQuery(msg) {
376
422
  txStatus = msg?.status;
377
- // The recovery ROLLBACK's own ReadyForQuery is not one of the batch's, so
423
+ // A follow-up statement's own ReadyForQuery is not one of the batch's, so
378
424
  // it must not advance the counter that decides when the batch is done.
379
- if (rollbackSent) {
425
+ if (phase === 'rollback') {
426
+ markDiscardIfInTransaction();
427
+ finalize();
428
+ return;
429
+ }
430
+ if (phase === 'commit') {
431
+ // PostgreSQL answers COMMIT in a failed transaction with the tag
432
+ // ROLLBACK and no error. Nothing here sends COMMIT after an error, but
433
+ // the answer is checked rather than assumed.
434
+ if (!pipelineError && commitTag === 'ROLLBACK')
435
+ pipelineError = (0, transaction_state_js_1.rolledBackError)(undefined, 'transaction');
436
+ markDiscardIfInTransaction();
380
437
  finalize();
381
438
  return;
382
439
  }
383
440
  rfqCount++;
384
441
  if (rfqCount < expectedRfq)
385
442
  return;
443
+ if (holdCommit && !pipelineError && txStatus !== 'E') {
444
+ try {
445
+ transformed = transformAll();
446
+ }
447
+ catch (err) {
448
+ // A transform refused its result: roll the batch's writes back.
449
+ pipelineError = err instanceof Error ? err : new Error(String(err));
450
+ if (!sendFollowUp('ROLLBACK', 'rollback'))
451
+ finalize();
452
+ return;
453
+ }
454
+ if (!sendFollowUp('COMMIT', 'commit'))
455
+ finalize();
456
+ return;
457
+ }
386
458
  if (settleTransactionState())
387
459
  finalize();
388
460
  }
@@ -429,7 +501,8 @@ async function runPipelined(client, queries, options = {}) {
429
501
  if (transactional) {
430
502
  // ---- Transactional mode ----
431
503
  // BEGIN + N×(parse+bind+describe+execute) + COMMIT + sync
432
- // One sync = one ReadyForQuery
504
+ // One sync = one ReadyForQuery. A batch that may write leaves the
505
+ // COMMIT out here and sends it after the transforms (holdCommit).
433
506
  // BEGIN
434
507
  connection.parse({ text: 'BEGIN', name: '' });
435
508
  connection.bind({ portal: '', statement: '', values: [], valueMapper: prepareValue });
@@ -446,10 +519,11 @@ async function runPipelined(client, queries, options = {}) {
446
519
  connection.describe({ type: 'P', name: '' });
447
520
  connection.execute({ portal: '', rows: 0 });
448
521
  }
449
- // COMMIT
450
- connection.parse({ text: 'COMMIT', name: '' });
451
- connection.bind({ portal: '', statement: '', values: [], valueMapper: prepareValue });
452
- connection.execute({ portal: '', rows: 0 });
522
+ if (!holdCommit) {
523
+ connection.parse({ text: 'COMMIT', name: '' });
524
+ connection.bind({ portal: '', statement: '', values: [], valueMapper: prepareValue });
525
+ connection.execute({ portal: '', rows: 0 });
526
+ }
453
527
  // Single Sync
454
528
  connection.sync();
455
529
  }
@@ -22,10 +22,21 @@
22
22
  Object.defineProperty(exports, "__esModule", { value: true });
23
23
  exports.executePipeline = executePipeline;
24
24
  exports.pipelineSupported = pipelineSupported;
25
+ const checkout_js_1 = require("./checkout.js");
25
26
  const connection_guard_js_1 = require("./connection-guard.js");
26
27
  const dialect_js_1 = require("./dialect.js");
27
28
  const errors_js_1 = require("./errors.js");
28
29
  const pipeline_submittable_js_1 = require("./pipeline-submittable.js");
30
+ const transaction_state_js_1 = require("./transaction-state.js");
31
+ /**
32
+ * Run one deferred query on the batch's connection. An engine without
33
+ * RETURNING (MySQL) attaches a `reselect` plan that runs the write and then
34
+ * fetches its row; sending `q.sql` alone would hand the transform a row-less
35
+ * result, which a single-row write reads as "not found".
36
+ */
37
+ function runOne(client, q) {
38
+ return q.reselect ? q.reselect((sql, params) => client.query(sql, params)) : client.query(q.sql, q.params);
39
+ }
29
40
  /**
30
41
  * Execute queries sequentially on an already-acquired connection.
31
42
  * This is the fallback path for clients that don't support the extended-query
@@ -55,19 +66,16 @@ async function runSequential(client, queries, dialect, options = {}) {
55
66
  for (const q of queries) {
56
67
  let raw;
57
68
  try {
58
- raw = await client.query(q.sql, q.params);
69
+ raw = await runOne(client, q);
59
70
  }
60
71
  catch (err) {
61
72
  throw (0, errors_js_1.wrapPgError)(err);
62
73
  }
74
+ // Transformed BEFORE the COMMIT, so a transform that refuses its result
75
+ // (a `...OrThrow` miss, an optimistic-lock conflict) rolls the batch back.
63
76
  results.push(q.transform(raw));
64
77
  }
65
- try {
66
- await client.query(dialect.commitStatement());
67
- }
68
- catch (err) {
69
- throw (0, errors_js_1.wrapPgError)(err);
70
- }
78
+ await (0, transaction_state_js_1.commitTransaction)(client, dialect.commitStatement());
71
79
  return results;
72
80
  }
73
81
  catch (err) {
@@ -145,7 +153,7 @@ async function runIndependent(client, queries) {
145
153
  const q = queries[i];
146
154
  let raw;
147
155
  try {
148
- raw = await client.query(q.sql, q.params);
156
+ raw = await runOne(client, q);
149
157
  }
150
158
  catch (err) {
151
159
  const wrapped = (0, errors_js_1.wrapPgError)(err);
@@ -192,24 +200,11 @@ async function executePipeline(pool, queries, options) {
192
200
  if (queries.length === 0) {
193
201
  return [];
194
202
  }
195
- // Acquire a single client, reused for both capability check and execution.
196
- // The checkout is wrapped because a connection-level failure (28P01 wrong
197
- // password, an unverifiable TLS cert, ECONNREFUSED) happens HERE, before any
198
- // query runs. Unwrapped it escapes as a raw pg DatabaseError whose `.code`
199
- // holds a SQLSTATE, which is the same property Turbine puts TURBINE_E0NN in,
200
- // so a caller switching on `.code` silently receives a value from a foreign
201
- // namespace. The transaction and nested-write checkouts were wrapped for this
202
- // reason; this was the last sibling still bare.
203
- let client;
204
- try {
205
- client = await pool.connect();
206
- }
207
- catch (err) {
208
- throw (0, errors_js_1.wrapPgError)(err);
209
- }
210
- // Guarded for the checkout window, see connection-guard.ts: a connection that
211
- // dies mid-batch emits 'error', which with no listener exits the process.
212
- const checkout = (0, connection_guard_js_1.guardCheckout)(client);
203
+ // One client, reused for both capability check and execution. The shared
204
+ // checkout types a connection-level failure (wrong password, TLS, refused,
205
+ // an exhausted pool) and guards the checkout window: a connection that dies
206
+ // mid-batch emits 'error', which with no listener exits the process.
207
+ const { client, checkout } = await (0, checkout_js_1.acquireConnection)(pool);
213
208
  try {
214
209
  if ((0, pipeline_submittable_js_1.supportsExtendedPipeline)(client)) {
215
210
  // Real pipeline path, uses extended-query protocol wire methods
@@ -13,8 +13,8 @@
13
13
  * shape the SQL introspectors produce, so a code-first PowDB database can be
14
14
  * introspected for bootstrap/verification. It is transport-agnostic: the caller
15
15
  * supplies an `exec(powql)` that returns row objects **keyed by column name**.
16
- * - Embedded / owned pool: `exec = async (q) => ({ rows: await db.raw([q]) })`
17
- * using a live `turbinePowDB` client's `raw` tagged template.
16
+ * - Embedded / owned pool: `exec = async (q) => ({ rows: await db.rawUnsafe(q) })`
17
+ * using a live `turbinePowDB` client.
18
18
  * - Networked: the raw `@zvndev/powdb-client` returns POSITIONAL rows
19
19
  * (`{ columns: string[], rows: string[][] }`), so zip them into records.
20
20
  * A bare `(await client.query(q)).rows` would hand this function `string[][]`
@@ -66,6 +66,7 @@
66
66
  * wired to a networked `exec`, and `cli/config.ts` teaching the generate
67
67
  * funnel to construct a PowDB client instead of a `pg` client for `powdb://`.
68
68
  */
69
+ import { type TurbineLogger } from './logger.js';
69
70
  import { type PowdbCapabilities } from './powdb-shared.js';
70
71
  import type { SchemaMetadata } from './schema.js';
71
72
  /** A minimal rows-returning executor over a PowDB connection (embedded or networked). */
@@ -86,6 +87,8 @@ export interface PowdbIntrospectOptions {
86
87
  * to run ungated; the pool paths that know the version pass it through.
87
88
  */
88
89
  capabilities?: PowdbCapabilities;
90
+ /** Where a table without a usable primary key is reported. Defaults to the console logger. */
91
+ logger?: TurbineLogger;
89
92
  }
90
93
  /**
91
94
  * Read a live PowDB database into {@link SchemaMetadata} via `schema` +
@@ -14,8 +14,8 @@
14
14
  * shape the SQL introspectors produce, so a code-first PowDB database can be
15
15
  * introspected for bootstrap/verification. It is transport-agnostic: the caller
16
16
  * supplies an `exec(powql)` that returns row objects **keyed by column name**.
17
- * - Embedded / owned pool: `exec = async (q) => ({ rows: await db.raw([q]) })`
18
- * using a live `turbinePowDB` client's `raw` tagged template.
17
+ * - Embedded / owned pool: `exec = async (q) => ({ rows: await db.rawUnsafe(q) })`
18
+ * using a live `turbinePowDB` client.
19
19
  * - Networked: the raw `@zvndev/powdb-client` returns POSITIONAL rows
20
20
  * (`{ columns: string[], rows: string[][] }`), so zip them into records.
21
21
  * A bare `(await client.query(q)).rows` would hand this function `string[][]`
@@ -71,6 +71,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
71
71
  exports.introspectPowdbDatabase = introspectPowdbDatabase;
72
72
  const errors_js_1 = require("./errors.js");
73
73
  const introspect_js_1 = require("./introspect.js");
74
+ const logger_js_1 = require("./logger.js");
74
75
  const powdb_shared_js_1 = require("./powdb-shared.js");
75
76
  const schema_js_1 = require("./schema.js");
76
77
  /** Coerce a wire cell to string (legacy wire cells are strings; native cells may be typed). */
@@ -136,7 +137,9 @@ async function introspectPowdbDatabase(exec, options = {}) {
136
137
  }
137
138
  // include / exclude + default bookkeeping-table exclusions (F12).
138
139
  const tableNames = (0, introspect_js_1.applyTableFilters)(candidateTables, options);
139
- const tables = {};
140
+ // Keyed by catalog names, so prototype-less: a table or column named
141
+ // `__proto__`, `constructor` or `toString` is data, never a prototype member.
142
+ const tables = (0, introspect_js_1.nullRecord)();
140
143
  for (const tableName of tableNames) {
141
144
  // `describe` needs the table name in bare-identifier position → quote it so
142
145
  // a reserved-word / non-bare table name (`order`) does not become a parse
@@ -157,11 +160,11 @@ async function introspectPowdbDatabase(exec, options = {}) {
157
160
  index: asString(r.index),
158
161
  }));
159
162
  const columns = [];
160
- const columnMap = {};
161
- const reverseColumnMap = {};
163
+ const columnMap = (0, introspect_js_1.nullRecord)();
164
+ const reverseColumnMap = (0, introspect_js_1.nullRecord)();
162
165
  const dateColumns = new Set();
163
- const dialectTypes = {};
164
- const pgTypes = {};
166
+ const dialectTypes = (0, introspect_js_1.nullRecord)();
167
+ const pgTypes = (0, introspect_js_1.nullRecord)();
165
168
  const allColumns = [];
166
169
  const uniqueColumns = [];
167
170
  const indexes = [];
@@ -209,15 +212,17 @@ async function introspectPowdbDatabase(exec, options = {}) {
209
212
  });
210
213
  }
211
214
  }
215
+ // Two columns resolving to one field would fold into one property on read.
216
+ (0, introspect_js_1.assertDistinctColumnFields)(tableName, columns);
212
217
  // Primary key: first non-nullable unique column, preferring one named `id`.
213
218
  let primaryKey = [];
214
219
  if (pkCandidates.length > 0) {
215
220
  primaryKey = [pkCandidates.includes('id') ? 'id' : pkCandidates[0]];
216
221
  }
217
222
  else {
218
- console.warn(`[turbine] PowDB introspection: table "${tableName}" has no non-nullable unique column; ` +
223
+ (0, logger_js_1.logTo)(options.logger ?? logger_js_1.consoleLogger, 'warn', `PowDB introspection: table "${(0, introspect_js_1.printableCatalogName)(tableName)}" has no non-nullable unique column; ` +
219
224
  'primaryKey is [] (single-row operations will fail). Supply a primary key via code-first ' +
220
- '`defineSchema` metadata if this table needs findUnique/update/delete by id.');
225
+ '`defineSchema` metadata if this table needs findUnique/update/delete by id.', { code: 'powdb-introspect-no-primary-key', table: tableName });
221
226
  }
222
227
  tables[tableName] = {
223
228
  name: tableName,
@@ -233,7 +238,7 @@ async function introspectPowdbDatabase(exec, options = {}) {
233
238
  // Populated below from `schema links` when the engine supports link
234
239
  // introspection (>= 0.19.1); stays `{}` otherwise (PowDB has no declared
235
240
  // foreign keys, so pre-link introspection reports no relations).
236
- relations: {},
241
+ relations: (0, introspect_js_1.nullRecord)(),
237
242
  indexes,
238
243
  };
239
244
  }
@@ -243,7 +248,7 @@ async function introspectPowdbDatabase(exec, options = {}) {
243
248
  if (options.capabilities?.linkIntrospection) {
244
249
  await introspectPowdbLinks(exec, tables);
245
250
  }
246
- return { tables, enums: {} };
251
+ return { tables, enums: (0, introspect_js_1.nullRecord)() };
247
252
  }
248
253
  /**
249
254
  * Read `schema links` and populate {@link TableMetadata.relations}: the first
@@ -278,8 +283,8 @@ async function introspectPowdbLinks(exec, tables) {
278
283
  cardinality: asString(r.cardinality),
279
284
  }));
280
285
  for (const link of linkRows) {
281
- const owner = tables[link.owner];
282
- const target = tables[link.target];
286
+ const owner = Object.hasOwn(tables, link.owner) ? tables[link.owner] : undefined;
287
+ const target = Object.hasOwn(tables, link.target) ? tables[link.target] : undefined;
283
288
  // A link referencing a filtered-out (include/exclude) table is skipped: we
284
289
  // cannot describe the target, so the relation would be unusable.
285
290
  if (!owner || !target)
@@ -324,13 +329,13 @@ async function introspectPowdbLinks(exec, tables) {
324
329
  }
325
330
  /** True when `name` already names a relation OR a column field/name on `meta`. */
326
331
  function relationNameTaken(meta, name) {
327
- if (meta.relations[name])
332
+ if (Object.hasOwn(meta.relations, name))
328
333
  return true;
329
334
  return meta.columns.some((c) => c.field === name || c.name === name);
330
335
  }
331
336
  /** Add a relation to a table's relations map (keyed by its camelCase name). */
332
337
  function addRelation(meta, rel) {
333
- if (meta.relations[rel.name])
338
+ if (Object.hasOwn(meta.relations, rel.name))
334
339
  return; // never clobber an existing relation
335
340
  meta.relations[rel.name] = rel;
336
341
  }
@@ -233,15 +233,36 @@ export type PowqlType = 'str' | 'int' | 'float' | 'bool' | 'json';
233
233
  * asserts the equivalence over a corpus rather than asserting my reading of it.
234
234
  */
235
235
  export declare function baseTsType(tsType: string): string;
236
+ /** A schema's enum types (`SchemaMetadata.enums`): type name to labels, in declaration order. */
237
+ export type PowdbEnums = Readonly<Record<string, readonly string[]>>;
236
238
  /**
237
- * Does this column map to PowDB's native `json` document type? A Postgres
239
+ * The labels of the enum type this column is declared as, or `undefined` when
240
+ * it is not an enum column of `enums`.
241
+ *
242
+ * An enum column carries its TYPE NAME as `dialectType` / `pgType` and a
243
+ * `tsType` that says nothing useful (`unknown` from `defineSchema`, a union of
244
+ * the labels from introspection), so the column's own metadata cannot answer
245
+ * the question: the schema's `enums` map is the only authority. PowDB has no
246
+ * enum type, so such a column is a `str` holding the label text, which is what
247
+ * PostgreSQL stores and returns. Without this the tsType heuristic in
248
+ * {@link isJsonColumn} declared a `defineSchema` enum column as `json`, and
249
+ * writing a label failed as invalid JSON.
250
+ */
251
+ export declare function powdbEnumLabels(col: ColumnMetadata, enums: PowdbEnums | undefined): readonly string[] | undefined;
252
+ /**
253
+ * Does this column map to PowDB's native `json` document type? An enum column
254
+ * of `enums` never does (see {@link powdbEnumLabels}). A Postgres
238
255
  * `json`/`jsonb` type (via `dialectType`/`pgType`) is authoritative; otherwise
239
256
  * the tsType heuristic (`Record<…>`, `object`, `unknown`, an object/array
240
257
  * literal) that the four scalar branches do not claim. Array columns never map
241
258
  * to json, a PowDB array only exists INSIDE a json document, so a Postgres
242
259
  * array column has no PowDB shape and still throws in {@link powqlColumnType}.
260
+ *
261
+ * `enums` is optional for callers outside Turbine; every call inside the
262
+ * engine passes the schema's map (a source scan in fx-powdb-r2.test.ts
263
+ * checks it).
243
264
  */
244
- export declare function isJsonColumn(col: ColumnMetadata): boolean;
265
+ export declare function isJsonColumn(col: ColumnMetadata, enums?: PowdbEnums): boolean;
245
266
  /**
246
267
  * Map a Turbine column to the PowQL DDL type used in `defineSchema` →
247
268
  * `type T { … }`. Turbine never emits PowDB's `uuid`/`datetime`/`bytes` types,
@@ -249,10 +270,20 @@ export declare function isJsonColumn(col: ColumnMetadata): boolean;
249
270
  * - `Date` → `int` (epoch micros) - `boolean` → `bool`
250
271
  * - integral `number`/`bigint` → `int` - fractional `number` → `float`
251
272
  * - JSON / object columns → `json` (native PowDB document type, ≥ 0.12)
273
+ * - an enum column of `enums` → `str` (the label text)
252
274
  * - everything else (incl. UUID/PK strings) → `str`
253
275
  * Array (non-json) and bytes columns throw, they have no PowDB equivalent.
254
276
  */
255
- export declare function powqlColumnType(col: ColumnMetadata): PowqlType;
277
+ export declare function powqlColumnType(col: ColumnMetadata, enums?: PowdbEnums): PowqlType;
278
+ /**
279
+ * Is this a `numeric` / `decimal` / `money` column that PowDB stores as `str`?
280
+ *
281
+ * A Postgres-sourced decimal surfaces as a JS string (so its digits survive),
282
+ * and PowDB has no decimal type, so the column is DDL'd as PowQL `str`. Every
283
+ * comparison, ordering and aggregate the engine runs on it is then a TEXT
284
+ * operation, which is why the query generator refuses those on such a column.
285
+ */
286
+ export declare function isPowdbDecimalTextColumn(col: ColumnMetadata, enums?: PowdbEnums): boolean;
256
287
  /**
257
288
  * Is a column stored as `int` epoch micros but surfaced as a JS `Date`?
258
289
  *
@@ -325,7 +356,7 @@ export declare function quotePowqlDotted(name: string): string;
325
356
  * Every PowDB value arrives as a string; NULL arrives as the bareword `"null"`.
326
357
  * Metadata resolves the `"null"` ambiguity for nullable non-string columns.
327
358
  */
328
- export declare function coerceValue(raw: string, col: ColumnMetadata): unknown;
359
+ export declare function coerceValue(raw: string, col: ColumnMetadata, enums?: PowdbEnums): unknown;
329
360
  /**
330
361
  * Coerce a single cell that arrived over the NATIVE typed wire (decoded from a
331
362
  * {@link PowdbWireValue}, so already a JS `bigint`/`number`/`boolean`/`string`/
@@ -354,7 +385,7 @@ export declare function coerceNativeValue(value: unknown, col: ColumnMetadata):
354
385
  * cells arrive pre-typed and go through {@link coerceNativeValue} (see F3).
355
386
  * Callers on the native transport pass `this.pool.capabilities.nativeRaw`.
356
387
  */
357
- export declare function rowToEntity(raw: Record<string, unknown>, meta: TableMetadata, native?: boolean): Record<string, unknown>;
388
+ export declare function rowToEntity(raw: Record<string, unknown>, meta: TableMetadata, native?: boolean, enums?: PowdbEnums): Record<string, unknown>;
358
389
  /**
359
390
  * True when `err` is the stale-wire-frame {@link ConnectionError} produced by
360
391
  * {@link wrapPowdbError} (its `.cause` is a `protocol_error` PowDBError, or the