velocious 1.0.567 → 1.0.569

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 (247) hide show
  1. package/README.md +49 -1
  2. package/build/configuration.js +36 -0
  3. package/build/database/drivers/base-table.js +4 -3
  4. package/build/database/drivers/base.js +195 -54
  5. package/build/database/drivers/index-metadata.js +28 -0
  6. package/build/database/drivers/mssql/column.js +2 -34
  7. package/build/database/drivers/mssql/columns-index.js +37 -0
  8. package/build/database/drivers/mssql/index.js +10 -7
  9. package/build/database/drivers/mssql/table.js +55 -11
  10. package/build/database/drivers/mysql/index.js +10 -5
  11. package/build/database/drivers/mysql/sql/alter-table.js +13 -0
  12. package/build/database/drivers/pgsql/column.js +2 -27
  13. package/build/database/drivers/pgsql/columns-index.js +37 -0
  14. package/build/database/drivers/pgsql/index.js +4 -4
  15. package/build/database/drivers/pgsql/table.js +61 -10
  16. package/build/database/drivers/sqlite/base.js +2 -2
  17. package/build/database/drivers/sqlite/connection-sql-js.js +62 -2
  18. package/build/database/drivers/sqlite/index.web.js +74 -0
  19. package/build/database/drivers/sqlite/sql/alter-table.js +18 -0
  20. package/build/database/migration/index.js +92 -3
  21. package/build/database/operation-connection.js +110 -0
  22. package/build/database/operation-lease.js +47 -0
  23. package/build/database/operation.js +140 -0
  24. package/build/database/pool/base.js +26 -1
  25. package/build/database/pool/single-multi-use.js +49 -0
  26. package/build/database/query/alter-table-base.js +20 -1
  27. package/build/database/query/model-class-query.js +38 -7
  28. package/build/database/query/preloader/belongs-to.js +3 -2
  29. package/build/database/query/preloader/has-many.js +4 -3
  30. package/build/database/query/preloader/has-one.js +2 -1
  31. package/build/database/query/preloader/query-for-model.js +17 -0
  32. package/build/database/query/query-data.js +12 -3
  33. package/build/database/query/with-count.js +5 -3
  34. package/build/database/record/acts-as-list.js +11 -8
  35. package/build/database/record/attachments/normalize-input.js +38 -10
  36. package/build/database/record/attachments/storage-drivers/filesystem.js +33 -2
  37. package/build/database/record/attachments/storage-drivers/native.js +17 -2
  38. package/build/database/record/attachments/storage-drivers/s3.js +27 -7
  39. package/build/database/record/attachments/store.js +173 -53
  40. package/build/database/record/auditing.js +12 -9
  41. package/build/database/record/counter-cache-magnitude.js +5 -3
  42. package/build/database/record/index.js +95 -14
  43. package/build/database/record/instance-relationships/belongs-to.js +4 -2
  44. package/build/database/record/instance-relationships/has-many.js +7 -5
  45. package/build/database/record/instance-relationships/has-one.js +4 -2
  46. package/build/database/record/validators/uniqueness.js +3 -2
  47. package/build/database/table-data/table-foreign-key.js +9 -1
  48. package/build/environment-handlers/base.js +1 -1
  49. package/build/environment-handlers/node/attachment-path-source.js +144 -0
  50. package/build/environment-handlers/node.js +28 -3
  51. package/build/frontend-models/websocket-publishers.js +2 -2
  52. package/build/src/configuration.d.ts +12 -0
  53. package/build/src/configuration.d.ts.map +1 -1
  54. package/build/src/configuration.js +35 -1
  55. package/build/src/database/drivers/base-table.d.ts.map +1 -1
  56. package/build/src/database/drivers/base-table.js +4 -3
  57. package/build/src/database/drivers/base.d.ts +73 -20
  58. package/build/src/database/drivers/base.d.ts.map +1 -1
  59. package/build/src/database/drivers/base.js +181 -54
  60. package/build/src/database/drivers/index-metadata.d.ts +41 -0
  61. package/build/src/database/drivers/index-metadata.d.ts.map +1 -0
  62. package/build/src/database/drivers/index-metadata.js +26 -0
  63. package/build/src/database/drivers/mssql/column.d.ts.map +1 -1
  64. package/build/src/database/drivers/mssql/column.js +3 -31
  65. package/build/src/database/drivers/mssql/columns-index.d.ts +41 -0
  66. package/build/src/database/drivers/mssql/columns-index.d.ts.map +1 -1
  67. package/build/src/database/drivers/mssql/columns-index.js +35 -1
  68. package/build/src/database/drivers/mssql/index.d.ts +4 -1
  69. package/build/src/database/drivers/mssql/index.d.ts.map +1 -1
  70. package/build/src/database/drivers/mssql/index.js +11 -8
  71. package/build/src/database/drivers/mssql/table.d.ts +42 -0
  72. package/build/src/database/drivers/mssql/table.d.ts.map +1 -1
  73. package/build/src/database/drivers/mssql/table.js +49 -12
  74. package/build/src/database/drivers/mysql/index.d.ts +2 -1
  75. package/build/src/database/drivers/mysql/index.d.ts.map +1 -1
  76. package/build/src/database/drivers/mysql/index.js +10 -6
  77. package/build/src/database/drivers/mysql/sql/alter-table.d.ts.map +1 -1
  78. package/build/src/database/drivers/mysql/sql/alter-table.js +12 -1
  79. package/build/src/database/drivers/pgsql/column.d.ts.map +1 -1
  80. package/build/src/database/drivers/pgsql/column.js +3 -24
  81. package/build/src/database/drivers/pgsql/columns-index.d.ts +41 -0
  82. package/build/src/database/drivers/pgsql/columns-index.d.ts.map +1 -1
  83. package/build/src/database/drivers/pgsql/columns-index.js +35 -1
  84. package/build/src/database/drivers/pgsql/index.d.ts +2 -1
  85. package/build/src/database/drivers/pgsql/index.d.ts.map +1 -1
  86. package/build/src/database/drivers/pgsql/index.js +5 -5
  87. package/build/src/database/drivers/pgsql/table.d.ts +42 -0
  88. package/build/src/database/drivers/pgsql/table.d.ts.map +1 -1
  89. package/build/src/database/drivers/pgsql/table.js +54 -11
  90. package/build/src/database/drivers/sqlite/base.d.ts +1 -1
  91. package/build/src/database/drivers/sqlite/base.d.ts.map +1 -1
  92. package/build/src/database/drivers/sqlite/base.js +3 -3
  93. package/build/src/database/drivers/sqlite/connection-sql-js.d.ts +20 -0
  94. package/build/src/database/drivers/sqlite/connection-sql-js.d.ts.map +1 -1
  95. package/build/src/database/drivers/sqlite/connection-sql-js.js +56 -3
  96. package/build/src/database/drivers/sqlite/index.web.d.ts.map +1 -1
  97. package/build/src/database/drivers/sqlite/index.web.js +64 -1
  98. package/build/src/database/drivers/sqlite/sql/alter-table.d.ts.map +1 -1
  99. package/build/src/database/drivers/sqlite/sql/alter-table.js +15 -1
  100. package/build/src/database/migration/index.d.ts +35 -1
  101. package/build/src/database/migration/index.d.ts.map +1 -1
  102. package/build/src/database/migration/index.js +78 -4
  103. package/build/src/database/operation-connection.d.ts +61 -0
  104. package/build/src/database/operation-connection.d.ts.map +1 -0
  105. package/build/src/database/operation-connection.js +93 -0
  106. package/build/src/database/operation-lease.d.ts +27 -0
  107. package/build/src/database/operation-lease.d.ts.map +1 -0
  108. package/build/src/database/operation-lease.js +44 -0
  109. package/build/src/database/operation.d.ts +77 -0
  110. package/build/src/database/operation.d.ts.map +1 -0
  111. package/build/src/database/operation.js +123 -0
  112. package/build/src/database/pool/base.d.ts +14 -0
  113. package/build/src/database/pool/base.d.ts.map +1 -1
  114. package/build/src/database/pool/base.js +24 -2
  115. package/build/src/database/pool/single-multi-use.d.ts +1 -0
  116. package/build/src/database/pool/single-multi-use.d.ts.map +1 -1
  117. package/build/src/database/pool/single-multi-use.js +49 -1
  118. package/build/src/database/query/alter-table-base.d.ts +6 -0
  119. package/build/src/database/query/alter-table-base.d.ts.map +1 -1
  120. package/build/src/database/query/alter-table-base.js +18 -2
  121. package/build/src/database/query/model-class-query.d.ts +14 -0
  122. package/build/src/database/query/model-class-query.d.ts.map +1 -1
  123. package/build/src/database/query/model-class-query.js +32 -8
  124. package/build/src/database/query/preloader/belongs-to.d.ts.map +1 -1
  125. package/build/src/database/query/preloader/belongs-to.js +4 -3
  126. package/build/src/database/query/preloader/has-many.d.ts.map +1 -1
  127. package/build/src/database/query/preloader/has-many.js +5 -4
  128. package/build/src/database/query/preloader/has-one.d.ts.map +1 -1
  129. package/build/src/database/query/preloader/has-one.js +3 -2
  130. package/build/src/database/query/preloader/query-for-model.d.ts +10 -0
  131. package/build/src/database/query/preloader/query-for-model.d.ts.map +1 -0
  132. package/build/src/database/query/preloader/query-for-model.js +16 -0
  133. package/build/src/database/query/query-data.d.ts.map +1 -1
  134. package/build/src/database/query/query-data.js +13 -4
  135. package/build/src/database/query/with-count.d.ts.map +1 -1
  136. package/build/src/database/query/with-count.js +6 -4
  137. package/build/src/database/record/acts-as-list.js +12 -9
  138. package/build/src/database/record/attachments/attachment-record.d.ts +9 -0
  139. package/build/src/database/record/attachments/attachment-record.d.ts.map +1 -1
  140. package/build/src/database/record/attachments/normalize-input.d.ts +33 -4
  141. package/build/src/database/record/attachments/normalize-input.d.ts.map +1 -1
  142. package/build/src/database/record/attachments/normalize-input.js +39 -11
  143. package/build/src/database/record/attachments/storage-drivers/filesystem.d.ts +2 -5
  144. package/build/src/database/record/attachments/storage-drivers/filesystem.d.ts.map +1 -1
  145. package/build/src/database/record/attachments/storage-drivers/filesystem.js +28 -3
  146. package/build/src/database/record/attachments/storage-drivers/native.d.ts +2 -6
  147. package/build/src/database/record/attachments/storage-drivers/native.d.ts.map +1 -1
  148. package/build/src/database/record/attachments/storage-drivers/native.js +14 -3
  149. package/build/src/database/record/attachments/storage-drivers/s3.d.ts +2 -6
  150. package/build/src/database/record/attachments/storage-drivers/s3.d.ts.map +1 -1
  151. package/build/src/database/record/attachments/storage-drivers/s3.js +29 -8
  152. package/build/src/database/record/attachments/store.d.ts +31 -2
  153. package/build/src/database/record/attachments/store.d.ts.map +1 -1
  154. package/build/src/database/record/attachments/store.js +158 -54
  155. package/build/src/database/record/auditing.d.ts.map +1 -1
  156. package/build/src/database/record/auditing.js +13 -10
  157. package/build/src/database/record/counter-cache-magnitude.js +6 -4
  158. package/build/src/database/record/index.d.ts +42 -2
  159. package/build/src/database/record/index.d.ts.map +1 -1
  160. package/build/src/database/record/index.js +87 -15
  161. package/build/src/database/record/instance-relationships/belongs-to.d.ts.map +1 -1
  162. package/build/src/database/record/instance-relationships/belongs-to.js +4 -3
  163. package/build/src/database/record/instance-relationships/has-many.d.ts.map +1 -1
  164. package/build/src/database/record/instance-relationships/has-many.js +7 -5
  165. package/build/src/database/record/instance-relationships/has-one.d.ts.map +1 -1
  166. package/build/src/database/record/instance-relationships/has-one.js +4 -3
  167. package/build/src/database/record/validators/uniqueness.d.ts.map +1 -1
  168. package/build/src/database/record/validators/uniqueness.js +4 -3
  169. package/build/src/database/table-data/table-foreign-key.d.ts +9 -1
  170. package/build/src/database/table-data/table-foreign-key.d.ts.map +1 -1
  171. package/build/src/database/table-data/table-foreign-key.js +9 -2
  172. package/build/src/environment-handlers/base.d.ts +2 -5
  173. package/build/src/environment-handlers/base.d.ts.map +1 -1
  174. package/build/src/environment-handlers/base.js +2 -2
  175. package/build/src/environment-handlers/node/attachment-path-source.d.ts +64 -0
  176. package/build/src/environment-handlers/node/attachment-path-source.d.ts.map +1 -0
  177. package/build/src/environment-handlers/node/attachment-path-source.js +116 -0
  178. package/build/src/environment-handlers/node.d.ts +12 -0
  179. package/build/src/environment-handlers/node.d.ts.map +1 -1
  180. package/build/src/environment-handlers/node.js +24 -4
  181. package/build/src/frontend-models/websocket-publishers.js +3 -3
  182. package/build/src/sync/server-sequence-allocator.d.ts +16 -4
  183. package/build/src/sync/server-sequence-allocator.d.ts.map +1 -1
  184. package/build/src/sync/server-sequence-allocator.js +37 -11
  185. package/build/src/sync/sync-client.d.ts.map +1 -1
  186. package/build/src/sync/sync-client.js +7 -3
  187. package/build/src/sync/sync-publisher.d.ts +2 -1
  188. package/build/src/sync/sync-publisher.d.ts.map +1 -1
  189. package/build/src/sync/sync-publisher.js +11 -6
  190. package/build/sync/server-sequence-allocator.js +43 -10
  191. package/build/sync/sync-client.js +6 -2
  192. package/build/sync/sync-publisher.js +10 -5
  193. package/build/tsconfig.tsbuildinfo +1 -1
  194. package/package.json +1 -1
  195. package/src/configuration.js +36 -0
  196. package/src/database/drivers/base-table.js +4 -3
  197. package/src/database/drivers/base.js +195 -54
  198. package/src/database/drivers/index-metadata.js +28 -0
  199. package/src/database/drivers/mssql/column.js +2 -34
  200. package/src/database/drivers/mssql/columns-index.js +37 -0
  201. package/src/database/drivers/mssql/index.js +10 -7
  202. package/src/database/drivers/mssql/table.js +55 -11
  203. package/src/database/drivers/mysql/index.js +10 -5
  204. package/src/database/drivers/mysql/sql/alter-table.js +13 -0
  205. package/src/database/drivers/pgsql/column.js +2 -27
  206. package/src/database/drivers/pgsql/columns-index.js +37 -0
  207. package/src/database/drivers/pgsql/index.js +4 -4
  208. package/src/database/drivers/pgsql/table.js +61 -10
  209. package/src/database/drivers/sqlite/base.js +2 -2
  210. package/src/database/drivers/sqlite/connection-sql-js.js +62 -2
  211. package/src/database/drivers/sqlite/index.web.js +74 -0
  212. package/src/database/drivers/sqlite/sql/alter-table.js +18 -0
  213. package/src/database/migration/index.js +92 -3
  214. package/src/database/operation-connection.js +110 -0
  215. package/src/database/operation-lease.js +47 -0
  216. package/src/database/operation.js +140 -0
  217. package/src/database/pool/base.js +26 -1
  218. package/src/database/pool/single-multi-use.js +49 -0
  219. package/src/database/query/alter-table-base.js +20 -1
  220. package/src/database/query/model-class-query.js +38 -7
  221. package/src/database/query/preloader/belongs-to.js +3 -2
  222. package/src/database/query/preloader/has-many.js +4 -3
  223. package/src/database/query/preloader/has-one.js +2 -1
  224. package/src/database/query/preloader/query-for-model.js +17 -0
  225. package/src/database/query/query-data.js +12 -3
  226. package/src/database/query/with-count.js +5 -3
  227. package/src/database/record/acts-as-list.js +11 -8
  228. package/src/database/record/attachments/normalize-input.js +38 -10
  229. package/src/database/record/attachments/storage-drivers/filesystem.js +33 -2
  230. package/src/database/record/attachments/storage-drivers/native.js +17 -2
  231. package/src/database/record/attachments/storage-drivers/s3.js +27 -7
  232. package/src/database/record/attachments/store.js +173 -53
  233. package/src/database/record/auditing.js +12 -9
  234. package/src/database/record/counter-cache-magnitude.js +5 -3
  235. package/src/database/record/index.js +95 -14
  236. package/src/database/record/instance-relationships/belongs-to.js +4 -2
  237. package/src/database/record/instance-relationships/has-many.js +7 -5
  238. package/src/database/record/instance-relationships/has-one.js +4 -2
  239. package/src/database/record/validators/uniqueness.js +3 -2
  240. package/src/database/table-data/table-foreign-key.js +9 -1
  241. package/src/environment-handlers/base.js +1 -1
  242. package/src/environment-handlers/node/attachment-path-source.js +144 -0
  243. package/src/environment-handlers/node.js +28 -3
  244. package/src/frontend-models/websocket-publishers.js +2 -2
  245. package/src/sync/server-sequence-allocator.js +43 -10
  246. package/src/sync/sync-client.js +6 -2
  247. package/src/sync/sync-publisher.js +10 -5
package/README.md CHANGED
@@ -34,9 +34,11 @@
34
34
  * In-process driver schema metadata caching (see [docs/schema-metadata-cache.md](docs/schema-metadata-cache.md))
35
35
  * Planned local-first shared-resource sync architecture (see [docs/offline-sync.md](docs/offline-sync.md))
36
36
  * Selective named database connection checkouts, bounded pool waits, and debugging held connections (see [docs/database-connections.md](docs/database-connections.md))
37
+ * Explicit singular-database operation transactions whose model scopes preserve ownership through records, relationships, lifecycle work, nested savepoints, and commit callbacks (see [docs/operation-scoped-transactions.md](docs/operation-scoped-transactions.md))
37
38
  * AbortSignal-driven MySQL/MariaDB query cancellation for raw, model, and cross-tenant aggregate queries (see [docs/database-query-cancellation.md](docs/database-query-cancellation.md))
38
39
  * Optional built-in debug endpoint for inspecting server and database connection state (see [docs/debug-endpoint.md](docs/debug-endpoint.md))
39
40
  * Optional built-in API manifest endpoint describing every registered frontend-model resource as human- and machine-readable JSON (see [docs/api-manifest-endpoint.md](docs/api-manifest-endpoint.md))
41
+ * Backend record attachments with filesystem, S3, native callback, and bounded Node path-input persistence (see [docs/attachments.md](docs/attachments.md))
40
42
 
41
43
  # Setup
42
44
 
@@ -50,6 +52,25 @@ npx velocious init
50
52
 
51
53
  By default, Velocious looks for your configuration in `src/config/configuration.js`. If you keep the configuration elsewhere, make sure your app imports it early and calls `configuration.setCurrent()`.
52
54
 
55
+ # Operation-scoped transactions
56
+
57
+ Use `configuration.withTransaction` for an atomic unit of model work on one database:
58
+
59
+ ```js
60
+ await configuration.withTransaction({databaseIdentifier: "default", name: "accept ticket"}, async (operation) => {
61
+ const ticket = await operation.forModel(Ticket).find(ticketId)
62
+
63
+ ticket.setAccepted(true)
64
+ await ticket.save()
65
+
66
+ await operation.afterCommit(async () => {
67
+ await publishAcceptedTicket(ticket.id())
68
+ })
69
+ })
70
+ ```
71
+
72
+ Use operation-bound model scopes and their loaded records throughout the callback. `operation.transaction` adds a nested savepoint, and `operation.connection()` is the deliberate escape hatch for owned raw SQL. Cross-database models, same-identifier tenant switches to another physical database, and operation handles used after the callback are rejected. On shared SQLite/SQL.js pools, unrelated work waits for the operation lease, while admission during an already-open ordinary transaction is rejected. See [operation-scoped transactions](docs/operation-scoped-transactions.md) for pool behavior, after-commit failure semantics, and migration guidance.
73
+
53
74
  # Development
54
75
 
55
76
  When working on Velocious itself, npm scripts are cross-platform (Windows `cmd`/PowerShell and POSIX shells):
@@ -521,6 +542,9 @@ Task.hasOneAttachment("descriptionFile")
521
542
  Task.hasOneAttachment("archivedPdf", {driver: "s3"})
522
543
  ```
523
544
 
545
+ See [Backend record attachments](docs/attachments.md) for the complete input,
546
+ storage-driver, lifecycle, and path-security contracts.
547
+
524
548
  You can also pass a driver class or instance directly on the attachment:
525
549
 
526
550
  ```js
@@ -538,6 +562,10 @@ await task.descriptionFile().attach({
538
562
  content: "my file content",
539
563
  filename: "file.doc"
540
564
  })
565
+ await task.archivedPdf().attach({
566
+ path: "/var/app/uploads/archive.pdf",
567
+ contentType: "application/pdf"
568
+ })
541
569
  const descriptionFileUrl = await task.descriptionFile().url()
542
570
  await task.update({
543
571
  descriptionFile: {
@@ -605,7 +633,25 @@ new Configuration({
605
633
  ```
606
634
 
607
635
  Then `{path: "..."}`
608
- inputs are only accepted when the file resolves inside one of the allowed prefixes.
636
+ inputs are only accepted when the path resolves inside one of the allowed
637
+ prefixes and the once-opened handle identifies a regular file. Its exact byte
638
+ size comes from that handle's stat snapshot. The filesystem driver copies it
639
+ with a bounded, backpressured stream and the S3 driver sends a Node `Readable`;
640
+ on current nullable schemas neither driver first materializes the whole file as
641
+ a Buffer or Base64 value. Legacy schemas with a non-null `content_base64`
642
+ column instead materialize the opened snapshot once before driver persistence
643
+ and reuse those exact bytes for storage and the database Base64. Path
644
+ replacement cannot switch the opened source, truncation is rejected, later
645
+ appends are ignored, and the source is closed after persistence.
646
+
647
+ The native driver's documented `write({contentBase64, ...})` callback remains
648
+ source-compatible. For path input only, that driver reads and Base64-encodes the
649
+ opened source after driver selection on current schemas; legacy path input
650
+ arrives pre-materialized with the same Base64 written to the database. Existing
651
+ Buffer, string, browser, and `UploadedFile` inputs keep their in-memory
652
+ behavior. See
653
+ [docs/attachments.md](docs/attachments.md#normalized-storage-driver-input) for
654
+ the normalized input passed to custom drivers.
609
655
 
610
656
  For frontend models, configure `resourceConfig().attachments` and use:
611
657
 
@@ -1148,6 +1194,8 @@ export default class CreateEvents extends Migration {
1148
1194
  }
1149
1195
  ```
1150
1196
 
1197
+ To reverse an `addReference("tasks", "project", {foreignKey: true, type: "uuid"})` migration, use `await this.removeReference("tasks", "project")` in `down()`. It removes the generated foreign key, index, and column; see [removing references and foreign keys](docs/database-migrations.md#removing-references-and-foreign-keys) for custom-column and foreign-key-only cases.
1198
+
1151
1199
  Migrations that must be rerunnable can guard changes with `tableExists(...)`, `columnExists(table, column)` and `indexExists(table, index)` — a missing table yields `false` rather than throwing. See [docs/database-migrations.md](docs/database-migrations.md#guarding-schema-changes-in-rerunnable-migrations).
1152
1200
 
1153
1201
  ## Run migrations from the command line
@@ -16,6 +16,7 @@ import {digg} from "diggerize"
16
16
  import gettextConfig from "gettext-universal/build/src/config.js"
17
17
  import translate from "gettext-universal/build/src/translate.js"
18
18
  import Ability from "./authorization/ability.js"
19
+ import DatabaseOperation from "./database/operation.js"
19
20
  import {initializeAuditedModelRelationships} from "./database/record/auditing.js"
20
21
  import EventEmitter from "./utils/event-emitter.js"
21
22
  import VelociousWebsocketChannelSubscribers from "./http-server/websocket-channel-subscribers.js"
@@ -2896,6 +2897,41 @@ export default class VelociousConfiguration {
2896
2897
  })
2897
2898
  }
2898
2899
 
2900
+ /**
2901
+ * Runs explicit model work in a transaction pinned to one database connection.
2902
+ * @template T
2903
+ * @param {{databaseIdentifier: string, name?: string}} options - Operation options.
2904
+ * @param {(operation: DatabaseOperation) => Promise<T>} callback - Operation callback.
2905
+ * @returns {Promise<T>} - Resolves with the callback result.
2906
+ */
2907
+ async withTransaction({databaseIdentifier, name = "Configuration.withTransaction", ...restArgs}, callback) {
2908
+ restArgsError(restArgs)
2909
+
2910
+ if (!databaseIdentifier) throw new Error("Configuration.withTransaction requires a databaseIdentifier")
2911
+ if (typeof callback != "function") throw new Error("Configuration.withTransaction requires a callback")
2912
+ if (!this.getDatabaseIdentifiers().includes(databaseIdentifier)) {
2913
+ throw new Error(`Unknown or inactive database identifier: ${databaseIdentifier}`)
2914
+ }
2915
+
2916
+ const pool = this.getDatabasePool(databaseIdentifier)
2917
+
2918
+ return await pool.withOperationConnection({name}, async (connection, owner) => {
2919
+ const operation = new DatabaseOperation({
2920
+ configuration: this,
2921
+ configurationReuseKey: pool.getConnectionConfigurationReuseKey(connection),
2922
+ connection,
2923
+ databaseIdentifier,
2924
+ owner
2925
+ })
2926
+
2927
+ try {
2928
+ return await operation.transaction(async () => await callback(operation))
2929
+ } finally {
2930
+ operation.complete()
2931
+ }
2932
+ })
2933
+ }
2934
+
2899
2935
  /**
2900
2936
  * Runs callback with database connections for the requested identifiers.
2901
2937
  * @template T
@@ -104,13 +104,15 @@ export default class VelociousDatabaseDriversBaseTable {
104
104
  }
105
105
 
106
106
  for (const foreignKey of await this.getForeignKeys()) {
107
- tableData.addForeignKey(foreignKey.getTableDataForeignKey())
107
+ const tableDataForeignKey = foreignKey.getTableDataForeignKey()
108
+
109
+ tableData.addForeignKey(tableDataForeignKey)
108
110
 
109
111
  const tableDataColumn = tableDataColumns.find((tableDataColumn) => tableDataColumn.getName() == foreignKey.getColumnName())
110
112
 
111
113
  if (!tableDataColumn) throw new Error(`Couldn't find table data column for foreign key: ${foreignKey.getColumnName()}`)
112
114
 
113
- tableDataColumn.setForeignKey(foreignKey)
115
+ tableDataColumn.setForeignKey(tableDataForeignKey)
114
116
  }
115
117
 
116
118
  for (const index of await this.getIndexes()) {
@@ -153,4 +155,3 @@ export default class VelociousDatabaseDriversBaseTable {
153
155
  return await this.getDriver().query(sql)
154
156
  }
155
157
  }
156
-
@@ -61,6 +61,7 @@
61
61
  * @property {boolean} [sessionTimeZone] - Whether to ensure the configured database session time zone before the query.
62
62
  * @property {AbortSignal} [signal] - Aborts the in-flight query (destroying its connection) when it fires.
63
63
  * @property {string} [sourceStack] - Stack captured at the caller boundary.
64
+ * @property {symbol} [operationOwner] - Opaque owner for an operation-leased connection.
64
65
  */
65
66
 
66
67
  /**
@@ -127,6 +128,21 @@ import TableForeignKey from "../table-data/table-foreign-key.js"
127
128
  import wait from "awaitery/build/wait.js"
128
129
  import {optionalPositiveInteger} from "typanic"
129
130
 
131
+ /**
132
+ * Marks a callback failure that happened after the owning transaction was durably committed.
133
+ * The public transaction boundary unwraps it before deadlock classification.
134
+ */
135
+ class VelociousDatabaseAfterCommitCallbackError extends Error {
136
+ /**
137
+ * Runs constructor.
138
+ * @param {?} callbackError - Original callback failure.
139
+ */
140
+ constructor(callbackError) {
141
+ super("Database afterCommit callback failed")
142
+ this.callbackError = callbackError
143
+ }
144
+ }
145
+
130
146
  /**
131
147
  * Runs now ms.
132
148
  * @returns {number} - Current high-resolution-ish timestamp in milliseconds.
@@ -175,6 +191,11 @@ export default class VelociousDatabaseDriversBase {
175
191
  _activeQuery = null
176
192
  /** @type {Map<string, number>} */
177
193
  _heldAdvisoryLocks = new Map()
194
+ /**
195
+ * Exclusive operation lease installed by a single-multi-use pool.
196
+ * @type {import("../operation-lease.js").default | undefined}
197
+ */
198
+ _operationLease = undefined
178
199
 
179
200
  /**
180
201
  * Runs constructor.
@@ -224,6 +245,34 @@ export default class VelociousDatabaseDriversBase {
224
245
  }
225
246
  }
226
247
 
248
+ /**
249
+ * Runs remove foreign key.
250
+ * @param {string} tableName - Table name.
251
+ * @param {import("./base-foreign-key.js").default} foreignKeyMetadata - Foreign key metadata.
252
+ * @returns {Promise<void>} - Resolves when complete.
253
+ */
254
+ async removeForeignKey(tableName, foreignKeyMetadata) {
255
+ this._assertNotReadOnly()
256
+
257
+ const tableForeignKey = new TableForeignKey({
258
+ columnName: foreignKeyMetadata.getColumnName(),
259
+ dropForeignKey: true,
260
+ name: foreignKeyMetadata.getName(),
261
+ referencedColumnName: foreignKeyMetadata.getReferencedColumnName(),
262
+ referencedTableName: foreignKeyMetadata.getReferencedTableName(),
263
+ tableName
264
+ })
265
+ const tableData = new TableData(tableName)
266
+
267
+ tableData.addForeignKey(tableForeignKey)
268
+
269
+ const alterTableSQLs = await this.alterTableSQLs(tableData)
270
+
271
+ for (const alterTableSQL of alterTableSQLs) {
272
+ await this.query(alterTableSQL)
273
+ }
274
+ }
275
+
227
276
  /**
228
277
  * Runs alter table sqls.
229
278
  * @abstract
@@ -460,6 +509,46 @@ export default class VelociousDatabaseDriversBase {
460
509
  return this.configuration
461
510
  }
462
511
 
512
+ /**
513
+ * Installs an operation lease atomically with ordinary transaction admission.
514
+ * @param {import("../operation-lease.js").default} operationLease - Active lease.
515
+ * @returns {Promise<void>} - Resolves once the lease owns transaction admission.
516
+ */
517
+ async setOperationLease(operationLease) {
518
+ await this._transactionsActionsMutex.sync(async () => {
519
+ if (this._operationLease) throw new Error("A database operation lease is already active")
520
+ if (this._transactionsCount > 0) {
521
+ throw new Error("Cannot start a database operation while an unrelated ordinary transaction is already active")
522
+ }
523
+
524
+ this._operationLease = operationLease
525
+ })
526
+ }
527
+
528
+ /**
529
+ * Clears the matching operation lease.
530
+ * @param {import("../operation-lease.js").default} operationLease - Lease to clear.
531
+ * @returns {void}
532
+ */
533
+ clearOperationLease(operationLease) {
534
+ if (this._operationLease !== operationLease) {
535
+ throw new Error("Cannot clear a database operation lease owned by another operation")
536
+ }
537
+
538
+ this._operationLease = undefined
539
+ }
540
+
541
+ /**
542
+ * Waits for an unrelated operation lease to release.
543
+ * @param {symbol | undefined} operationOwner - Candidate operation owner.
544
+ * @returns {Promise<void>}
545
+ */
546
+ async _waitForOperationLease(operationOwner) {
547
+ const operationLease = this._operationLease
548
+
549
+ if (operationLease) await operationLease.wait(operationOwner)
550
+ }
551
+
463
552
  /**
464
553
  * Runs get id seq.
465
554
  * @returns {number | undefined} - The id seq.
@@ -711,9 +800,10 @@ export default class VelociousDatabaseDriversBase {
711
800
  /**
712
801
  * Runs last insert id.
713
802
  * @abstract
803
+ * @param {QueryOptions} [_options] - Query ownership options.
714
804
  * @returns {Promise<number>} - Resolves with the last insert id.
715
805
  */
716
- lastInsertID() {
806
+ lastInsertID(_options = {}) {
717
807
  throw new Error(`${this.constructor.name}#lastInsertID not implemented`)
718
808
  }
719
809
 
@@ -904,12 +994,16 @@ export default class VelociousDatabaseDriversBase {
904
994
  * The outermost transaction retries the whole callback on a deadlock / lock-wait-timeout,
905
995
  * because such errors roll the entire transaction back and the standard recovery is to
906
996
  * restart it. Nested savepoints let the deadlock bubble up to this outer retry.
907
- * @param {() => Promise<void>} callback - Callback function.
908
- * @returns {Promise<?>} - Resolves with the transaction result.
997
+ * @template T
998
+ * @param {() => Promise<T>} callback - Callback function.
999
+ * @param {Pick<QueryOptions, "operationOwner">} [options] - Transaction ownership.
1000
+ * @returns {Promise<T>} - Resolves with the transaction result.
909
1001
  */
910
- async transaction(callback) {
1002
+ async transaction(callback, options = {}) {
1003
+ await this._waitForOperationLease(options.operationOwner)
1004
+
911
1005
  if (this._transactionsCount > 0) {
912
- return await this._runTransactionAttempt(callback)
1006
+ return await this._runTransactionAttempt(callback, options)
913
1007
  }
914
1008
 
915
1009
  const args = this.getArgs()
@@ -922,8 +1016,10 @@ export default class VelociousDatabaseDriversBase {
922
1016
  attempt++
923
1017
 
924
1018
  try {
925
- return await this._runTransactionAttempt(callback)
1019
+ return await this._runTransactionAttempt(callback, options)
926
1020
  } catch (error) {
1021
+ if (error instanceof VelociousDatabaseAfterCommitCallbackError) throw error.callbackError
1022
+
927
1023
  const retryInfo = error instanceof Error ? this.retryableDatabaseError(error) : {retry: false, reconnect: false}
928
1024
 
929
1025
  if (retryInfo.deadlock && attempt < maxAttempts && this._transactionsCount == 0) {
@@ -965,10 +1061,12 @@ export default class VelociousDatabaseDriversBase {
965
1061
  * Runs a single transaction attempt: starts a transaction (or a savepoint when nested), runs
966
1062
  * `callback`, and commits — rolling back on error. {@link transaction} wraps this with deadlock
967
1063
  * retry at the outermost level.
968
- * @param {() => Promise<void>} callback - Callback function.
1064
+ * @template T
1065
+ * @param {() => Promise<T>} callback - Callback function.
1066
+ * @param {Pick<QueryOptions, "operationOwner">} options - Transaction ownership.
969
1067
  * @returns {Promise<?>} - Resolves with the transaction result.
970
1068
  */
971
- async _runTransactionAttempt(callback) {
1069
+ async _runTransactionAttempt(callback, options) {
972
1070
  const savePointName = this.generateSavePointName()
973
1071
  /**
974
1072
  * Callback frame.
@@ -979,14 +1077,19 @@ export default class VelociousDatabaseDriversBase {
979
1077
 
980
1078
  this._afterCommitCallbackFrames.push(callbackFrame)
981
1079
 
982
- if (this._transactionsCount == 0) {
983
- this.logger.debug("Start transaction")
984
- await this.startTransaction()
985
- transactionStarted = true
986
- } else {
987
- this.logger.debug("Start savepoint", savePointName)
988
- await this.startSavePoint(savePointName)
989
- savePointStarted = true
1080
+ try {
1081
+ if (this._transactionsCount == 0) {
1082
+ this.logger.debug("Start transaction")
1083
+ await this.startTransaction(options)
1084
+ transactionStarted = true
1085
+ } else {
1086
+ this.logger.debug("Start savepoint", savePointName)
1087
+ await this.startSavePoint(savePointName, options)
1088
+ savePointStarted = true
1089
+ }
1090
+ } catch (error) {
1091
+ this._afterCommitCallbackFrames.pop()
1092
+ throw error
990
1093
  }
991
1094
 
992
1095
  let result
@@ -996,15 +1099,13 @@ export default class VelociousDatabaseDriversBase {
996
1099
 
997
1100
  if (savePointStarted) {
998
1101
  this.logger.debug("Release savepoint", savePointName)
999
- await this.releaseSavePoint(savePointName)
1102
+ await this.releaseSavePoint(savePointName, options)
1000
1103
  }
1001
1104
 
1002
1105
  if (transactionStarted) {
1003
1106
  this.logger.debug("Commit transaction")
1004
- await this.commitTransaction()
1107
+ await this.commitTransaction(options)
1005
1108
  }
1006
-
1007
- await this._commitAfterCommitCallbackFrame()
1008
1109
  } catch (error) {
1009
1110
  if (error instanceof Error) {
1010
1111
  this.logger.debug("Transaction error", error.message)
@@ -1017,14 +1118,14 @@ export default class VelociousDatabaseDriversBase {
1017
1118
  if (savePointStarted) {
1018
1119
  this.logger.debug("Rollback savepoint", savePointName)
1019
1120
  try {
1020
- await this.rollbackSavePoint(savePointName)
1121
+ await this.rollbackSavePoint(savePointName, options)
1021
1122
  } catch (savePointError) {
1022
1123
  const message = savePointError instanceof Error ? savePointError.message : `${savePointError}`
1023
1124
 
1024
1125
  // MySQL sometimes drops savepoints unexpectedly; fall back to rolling back the full transaction
1025
1126
  if (message.includes("SAVEPOINT") || message.includes("ER_SP_DOES_NOT_EXIST")) {
1026
1127
  this.logger.debug("Savepoint rollback failed; rolling back entire transaction instead")
1027
- await this.rollbackTransaction()
1128
+ await this.rollbackTransaction(options)
1028
1129
  transactionRolledBack = true
1029
1130
  } else {
1030
1131
  throw savePointError
@@ -1038,7 +1139,7 @@ export default class VelociousDatabaseDriversBase {
1038
1139
  // `_transactionsCount` below zero, which would then defeat the outermost deadlock-retry guard.
1039
1140
  if (transactionStarted && !transactionRolledBack && this._transactionsCount > 0) {
1040
1141
  this.logger.debug("Rollback transaction")
1041
- await this.rollbackTransaction()
1142
+ await this.rollbackTransaction(options)
1042
1143
  }
1043
1144
 
1044
1145
  this._afterCommitCallbackFrames.pop()
@@ -1046,6 +1147,12 @@ export default class VelociousDatabaseDriversBase {
1046
1147
  throw error
1047
1148
  }
1048
1149
 
1150
+ try {
1151
+ await this._commitAfterCommitCallbackFrame()
1152
+ } catch (error) {
1153
+ throw new VelociousDatabaseAfterCommitCallbackError(error)
1154
+ }
1155
+
1049
1156
  return result
1050
1157
  }
1051
1158
 
@@ -1053,9 +1160,12 @@ export default class VelociousDatabaseDriversBase {
1053
1160
  * Runs a callback after the surrounding transaction commits.
1054
1161
  * If no transaction is active, the callback runs immediately.
1055
1162
  * @param {() => void | Promise<void>} callback - Callback.
1163
+ * @param {Pick<QueryOptions, "operationOwner">} [options] - Callback ownership.
1056
1164
  * @returns {Promise<void>} - Resolves when the callback has been registered or run.
1057
1165
  */
1058
- async afterCommit(callback) {
1166
+ async afterCommit(callback, options = {}) {
1167
+ await this._waitForOperationLease(options.operationOwner)
1168
+
1059
1169
  const currentFrame = this._afterCommitCallbackFrames[this._afterCommitCallbackFrames.length - 1]
1060
1170
 
1061
1171
  if (!currentFrame) {
@@ -1074,40 +1184,60 @@ export default class VelociousDatabaseDriversBase {
1074
1184
 
1075
1185
  /**
1076
1186
  * Runs start transaction.
1187
+ * @param {Pick<QueryOptions, "operationOwner">} [options] - Transaction ownership.
1077
1188
  * @returns {Promise<void>} - Resolves when complete.
1078
1189
  */
1079
- async startTransaction() {
1080
- await this._transactionsActionsMutex.sync(async () => {
1081
- await this._startTransactionAction()
1082
- this._transactionsCount++
1083
- })
1190
+ async startTransaction(options = {}) {
1191
+ while (true) {
1192
+ /** @type {import("../operation-lease.js").default | undefined} */
1193
+ let blockingOperationLease
1194
+
1195
+ await this._transactionsActionsMutex.sync(async () => {
1196
+ const operationLease = this._operationLease
1197
+
1198
+ if (operationLease && options.operationOwner !== operationLease.owner) {
1199
+ blockingOperationLease = operationLease
1200
+ return
1201
+ }
1202
+
1203
+ await this._startTransactionAction(options)
1204
+ this._transactionsCount++
1205
+ })
1206
+
1207
+ if (!blockingOperationLease) return
1208
+
1209
+ await blockingOperationLease.wait(options.operationOwner)
1210
+ }
1084
1211
  }
1085
1212
 
1086
1213
  /**
1087
1214
  * Runs start transaction action.
1215
+ * @param {Pick<QueryOptions, "operationOwner">} [options] - Transaction ownership.
1088
1216
  * @returns {Promise<void>} - Resolves when complete.
1089
1217
  */
1090
- async _startTransactionAction() {
1091
- await this.query("BEGIN TRANSACTION")
1218
+ async _startTransactionAction(options = {}) {
1219
+ await this.query("BEGIN TRANSACTION", options)
1092
1220
  }
1093
1221
 
1094
1222
  /**
1095
1223
  * Runs commit transaction.
1224
+ * @param {Pick<QueryOptions, "operationOwner">} [options] - Transaction ownership.
1096
1225
  * @returns {Promise<void>} - Resolves when complete.
1097
1226
  */
1098
- async commitTransaction() {
1227
+ async commitTransaction(options = {}) {
1099
1228
  await this._transactionsActionsMutex.sync(async () => {
1100
- await this._commitTransactionAction()
1229
+ await this._commitTransactionAction(options)
1101
1230
  this._transactionsCount--
1102
1231
  })
1103
1232
  }
1104
1233
 
1105
1234
  /**
1106
1235
  * Runs commit transaction action.
1236
+ * @param {Pick<QueryOptions, "operationOwner">} [options] - Transaction ownership.
1107
1237
  * @returns {Promise<void>} - Resolves when complete.
1108
1238
  */
1109
- async _commitTransactionAction() {
1110
- await this.query("COMMIT")
1239
+ async _commitTransactionAction(options = {}) {
1240
+ await this.query("COMMIT", options)
1111
1241
  }
1112
1242
 
1113
1243
  /**
@@ -1155,6 +1285,7 @@ export default class VelociousDatabaseDriversBase {
1155
1285
  * @returns {Promise<QueryResultType>} - Resolves with the query.
1156
1286
  */
1157
1287
  async query(sql, options = {}) {
1288
+ await this._waitForOperationLease(options.operationOwner)
1158
1289
  this._assertWritableQuery(sql)
1159
1290
 
1160
1291
  let tries = 0
@@ -1204,16 +1335,18 @@ export default class VelociousDatabaseDriversBase {
1204
1335
  /**
1205
1336
  * Executes a mutation and returns the number of rows changed by that statement.
1206
1337
  * @param {string} sql - Mutation SQL string.
1338
+ * @param {QueryOptions} [options] - Query ownership options.
1207
1339
  * @returns {Promise<number>} - Affected row count.
1208
1340
  */
1209
- async affectedRows(sql) {
1341
+ async affectedRows(sql, options = {}) {
1342
+ await this._waitForOperationLease(options.operationOwner)
1210
1343
  this._assertWritableQuery(sql)
1211
- await this.beforeQuery(sql, {})
1344
+ await this.beforeQuery(sql, options)
1212
1345
 
1213
1346
  try {
1214
1347
  return await this._affectedRowsActual(sql)
1215
1348
  } finally {
1216
- await this.afterQuery(sql, {})
1349
+ await this.afterQuery(sql, options)
1217
1350
  }
1218
1351
  }
1219
1352
 
@@ -1581,12 +1714,13 @@ export default class VelociousDatabaseDriversBase {
1581
1714
 
1582
1715
  /**
1583
1716
  * Runs rollback transaction.
1717
+ * @param {Pick<QueryOptions, "operationOwner">} [options] - Transaction ownership.
1584
1718
  * @returns {Promise<void>} - Resolves when complete.
1585
1719
  */
1586
- async rollbackTransaction() {
1720
+ async rollbackTransaction(options = {}) {
1587
1721
  await this._transactionsActionsMutex.sync(async () => {
1588
1722
  try {
1589
- await this._rollbackTransactionAction()
1723
+ await this._rollbackTransactionAction(options)
1590
1724
  } finally {
1591
1725
  this._transactionsCount--
1592
1726
 
@@ -1602,10 +1736,11 @@ export default class VelociousDatabaseDriversBase {
1602
1736
 
1603
1737
  /**
1604
1738
  * Runs rollback transaction action.
1739
+ * @param {Pick<QueryOptions, "operationOwner">} [options] - Transaction ownership.
1605
1740
  * @returns {Promise<void>} - Resolves when complete.
1606
1741
  */
1607
- async _rollbackTransactionAction() {
1608
- await this.query("ROLLBACK")
1742
+ async _rollbackTransactionAction(options = {}) {
1743
+ await this.query("ROLLBACK", options)
1609
1744
  }
1610
1745
 
1611
1746
  /**
@@ -1619,21 +1754,23 @@ export default class VelociousDatabaseDriversBase {
1619
1754
  /**
1620
1755
  * Runs start save point.
1621
1756
  * @param {string} savePointName - Save point name.
1757
+ * @param {Pick<QueryOptions, "operationOwner">} [options] - Transaction ownership.
1622
1758
  * @returns {Promise<void>} - Resolves when complete.
1623
1759
  */
1624
- async startSavePoint(savePointName) {
1760
+ async startSavePoint(savePointName, options = {}) {
1625
1761
  await this._transactionsActionsMutex.sync(async () => {
1626
- await this._startSavePointAction(savePointName)
1762
+ await this._startSavePointAction(savePointName, options)
1627
1763
  })
1628
1764
  }
1629
1765
 
1630
1766
  /**
1631
1767
  * Runs start save point action.
1632
1768
  * @param {string} savePointName - Save point name.
1769
+ * @param {Pick<QueryOptions, "operationOwner">} [options] - Transaction ownership.
1633
1770
  * @returns {Promise<void>} - Resolves when complete.
1634
1771
  */
1635
- async _startSavePointAction(savePointName) {
1636
- await this.query(`SAVEPOINT ${savePointName}`)
1772
+ async _startSavePointAction(savePointName, options = {}) {
1773
+ await this.query(`SAVEPOINT ${savePointName}`, options)
1637
1774
  }
1638
1775
 
1639
1776
  /**
@@ -1663,22 +1800,24 @@ export default class VelociousDatabaseDriversBase {
1663
1800
  /**
1664
1801
  * Runs release save point.
1665
1802
  * @param {string} savePointName - Save point name.
1803
+ * @param {Pick<QueryOptions, "operationOwner">} [options] - Transaction ownership.
1666
1804
  * @returns {Promise<void>} - Resolves when complete.
1667
1805
  */
1668
- async releaseSavePoint(savePointName) {
1806
+ async releaseSavePoint(savePointName, options = {}) {
1669
1807
  await this._transactionsActionsMutex.sync(async () => {
1670
- await this._releaseSavePointAction(savePointName)
1808
+ await this._releaseSavePointAction(savePointName, options)
1671
1809
  })
1672
1810
  }
1673
1811
 
1674
1812
  /**
1675
1813
  * Runs release save point action.
1676
1814
  * @param {string} savePointName - Save point name.
1815
+ * @param {Pick<QueryOptions, "operationOwner">} [options] - Transaction ownership.
1677
1816
  * @returns {Promise<void>} - Resolves when complete.
1678
1817
  */
1679
- async _releaseSavePointAction(savePointName) {
1818
+ async _releaseSavePointAction(savePointName, options = {}) {
1680
1819
  try {
1681
- await this.query(`RELEASE SAVEPOINT ${savePointName}`)
1820
+ await this.query(`RELEASE SAVEPOINT ${savePointName}`, options)
1682
1821
  } catch (error) {
1683
1822
  const message = error instanceof Error ? error.message : `${error}`
1684
1823
 
@@ -1695,21 +1834,23 @@ export default class VelociousDatabaseDriversBase {
1695
1834
  /**
1696
1835
  * Runs rollback save point.
1697
1836
  * @param {string} savePointName - Save point name.
1837
+ * @param {Pick<QueryOptions, "operationOwner">} [options] - Transaction ownership.
1698
1838
  * @returns {Promise<void>} - Resolves when complete.
1699
1839
  */
1700
- async rollbackSavePoint(savePointName) {
1840
+ async rollbackSavePoint(savePointName, options = {}) {
1701
1841
  await this._transactionsActionsMutex.sync(async () => {
1702
- await this._rollbackSavePointAction(savePointName)
1842
+ await this._rollbackSavePointAction(savePointName, options)
1703
1843
  })
1704
1844
  }
1705
1845
 
1706
1846
  /**
1707
1847
  * Runs rollback save point action.
1708
1848
  * @param {string} savePointName - Save point name.
1849
+ * @param {Pick<QueryOptions, "operationOwner">} [options] - Transaction ownership.
1709
1850
  * @returns {Promise<void>} - Resolves when complete.
1710
1851
  */
1711
- async _rollbackSavePointAction(savePointName) {
1712
- await this.query(`ROLLBACK TO SAVEPOINT ${savePointName}`)
1852
+ async _rollbackSavePointAction(savePointName, options = {}) {
1853
+ await this.query(`ROLLBACK TO SAVEPOINT ${savePointName}`, options)
1713
1854
  }
1714
1855
 
1715
1856
  /**