velocious 1.0.568 → 1.0.570

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 (152) hide show
  1. package/README.md +30 -0
  2. package/build/configuration.js +36 -0
  3. package/build/database/drivers/base.js +244 -93
  4. package/build/database/drivers/mssql/index.js +10 -7
  5. package/build/database/drivers/mysql/index.js +10 -5
  6. package/build/database/drivers/pgsql/index.js +4 -4
  7. package/build/database/drivers/sqlite/base.js +2 -2
  8. package/build/database/drivers/sqlite/connection-sql-js.js +62 -2
  9. package/build/database/drivers/sqlite/index.web.js +74 -0
  10. package/build/database/operation-connection.js +121 -0
  11. package/build/database/operation-lease.js +47 -0
  12. package/build/database/operation.js +150 -0
  13. package/build/database/pool/base.js +26 -1
  14. package/build/database/pool/single-multi-use.js +49 -0
  15. package/build/database/query/model-class-query.js +38 -7
  16. package/build/database/query/preloader/belongs-to.js +3 -2
  17. package/build/database/query/preloader/has-many.js +4 -3
  18. package/build/database/query/preloader/has-one.js +2 -1
  19. package/build/database/query/preloader/query-for-model.js +17 -0
  20. package/build/database/query/query-data.js +12 -3
  21. package/build/database/query/with-count.js +5 -3
  22. package/build/database/record/acts-as-list.js +11 -8
  23. package/build/database/record/attachments/store.js +15 -11
  24. package/build/database/record/auditing.js +12 -9
  25. package/build/database/record/counter-cache-magnitude.js +5 -3
  26. package/build/database/record/index.js +95 -14
  27. package/build/database/record/instance-relationships/belongs-to.js +4 -2
  28. package/build/database/record/instance-relationships/has-many.js +7 -5
  29. package/build/database/record/instance-relationships/has-one.js +4 -2
  30. package/build/database/record/validators/uniqueness.js +3 -2
  31. package/build/frontend-models/websocket-publishers.js +2 -2
  32. package/build/src/configuration.d.ts +12 -0
  33. package/build/src/configuration.d.ts.map +1 -1
  34. package/build/src/configuration.js +35 -1
  35. package/build/src/database/drivers/base.d.ts +96 -23
  36. package/build/src/database/drivers/base.d.ts.map +1 -1
  37. package/build/src/database/drivers/base.js +230 -90
  38. package/build/src/database/drivers/mssql/index.d.ts +4 -1
  39. package/build/src/database/drivers/mssql/index.d.ts.map +1 -1
  40. package/build/src/database/drivers/mssql/index.js +11 -8
  41. package/build/src/database/drivers/mysql/index.d.ts +2 -1
  42. package/build/src/database/drivers/mysql/index.d.ts.map +1 -1
  43. package/build/src/database/drivers/mysql/index.js +10 -6
  44. package/build/src/database/drivers/pgsql/index.d.ts +2 -1
  45. package/build/src/database/drivers/pgsql/index.d.ts.map +1 -1
  46. package/build/src/database/drivers/pgsql/index.js +5 -5
  47. package/build/src/database/drivers/sqlite/base.d.ts +1 -1
  48. package/build/src/database/drivers/sqlite/base.d.ts.map +1 -1
  49. package/build/src/database/drivers/sqlite/base.js +3 -3
  50. package/build/src/database/drivers/sqlite/connection-sql-js.d.ts +20 -0
  51. package/build/src/database/drivers/sqlite/connection-sql-js.d.ts.map +1 -1
  52. package/build/src/database/drivers/sqlite/connection-sql-js.js +56 -3
  53. package/build/src/database/drivers/sqlite/index.web.d.ts.map +1 -1
  54. package/build/src/database/drivers/sqlite/index.web.js +64 -1
  55. package/build/src/database/operation-connection.d.ts +67 -0
  56. package/build/src/database/operation-connection.d.ts.map +1 -0
  57. package/build/src/database/operation-connection.js +102 -0
  58. package/build/src/database/operation-lease.d.ts +27 -0
  59. package/build/src/database/operation-lease.d.ts.map +1 -0
  60. package/build/src/database/operation-lease.js +44 -0
  61. package/build/src/database/operation.d.ts +85 -0
  62. package/build/src/database/operation.d.ts.map +1 -0
  63. package/build/src/database/operation.js +132 -0
  64. package/build/src/database/pool/base.d.ts +14 -0
  65. package/build/src/database/pool/base.d.ts.map +1 -1
  66. package/build/src/database/pool/base.js +24 -2
  67. package/build/src/database/pool/single-multi-use.d.ts +1 -0
  68. package/build/src/database/pool/single-multi-use.d.ts.map +1 -1
  69. package/build/src/database/pool/single-multi-use.js +49 -1
  70. package/build/src/database/query/model-class-query.d.ts +14 -0
  71. package/build/src/database/query/model-class-query.d.ts.map +1 -1
  72. package/build/src/database/query/model-class-query.js +32 -8
  73. package/build/src/database/query/preloader/belongs-to.d.ts.map +1 -1
  74. package/build/src/database/query/preloader/belongs-to.js +4 -3
  75. package/build/src/database/query/preloader/has-many.d.ts.map +1 -1
  76. package/build/src/database/query/preloader/has-many.js +5 -4
  77. package/build/src/database/query/preloader/has-one.d.ts.map +1 -1
  78. package/build/src/database/query/preloader/has-one.js +3 -2
  79. package/build/src/database/query/preloader/query-for-model.d.ts +10 -0
  80. package/build/src/database/query/preloader/query-for-model.d.ts.map +1 -0
  81. package/build/src/database/query/preloader/query-for-model.js +16 -0
  82. package/build/src/database/query/query-data.d.ts.map +1 -1
  83. package/build/src/database/query/query-data.js +13 -4
  84. package/build/src/database/query/with-count.d.ts.map +1 -1
  85. package/build/src/database/query/with-count.js +6 -4
  86. package/build/src/database/record/acts-as-list.js +12 -9
  87. package/build/src/database/record/attachments/attachment-record.d.ts +9 -0
  88. package/build/src/database/record/attachments/attachment-record.d.ts.map +1 -1
  89. package/build/src/database/record/attachments/store.d.ts +4 -2
  90. package/build/src/database/record/attachments/store.d.ts.map +1 -1
  91. package/build/src/database/record/attachments/store.js +16 -12
  92. package/build/src/database/record/auditing.d.ts.map +1 -1
  93. package/build/src/database/record/auditing.js +13 -10
  94. package/build/src/database/record/counter-cache-magnitude.js +6 -4
  95. package/build/src/database/record/index.d.ts +42 -2
  96. package/build/src/database/record/index.d.ts.map +1 -1
  97. package/build/src/database/record/index.js +87 -15
  98. package/build/src/database/record/instance-relationships/belongs-to.d.ts.map +1 -1
  99. package/build/src/database/record/instance-relationships/belongs-to.js +4 -3
  100. package/build/src/database/record/instance-relationships/has-many.d.ts.map +1 -1
  101. package/build/src/database/record/instance-relationships/has-many.js +7 -5
  102. package/build/src/database/record/instance-relationships/has-one.d.ts.map +1 -1
  103. package/build/src/database/record/instance-relationships/has-one.js +4 -3
  104. package/build/src/database/record/validators/uniqueness.d.ts.map +1 -1
  105. package/build/src/database/record/validators/uniqueness.js +4 -3
  106. package/build/src/frontend-models/websocket-publishers.js +3 -3
  107. package/build/src/sync/server-sequence-allocator.d.ts +16 -4
  108. package/build/src/sync/server-sequence-allocator.d.ts.map +1 -1
  109. package/build/src/sync/server-sequence-allocator.js +37 -11
  110. package/build/src/sync/sync-client.d.ts.map +1 -1
  111. package/build/src/sync/sync-client.js +7 -3
  112. package/build/src/sync/sync-publisher.d.ts +2 -1
  113. package/build/src/sync/sync-publisher.d.ts.map +1 -1
  114. package/build/src/sync/sync-publisher.js +11 -6
  115. package/build/sync/server-sequence-allocator.js +43 -10
  116. package/build/sync/sync-client.js +6 -2
  117. package/build/sync/sync-publisher.js +10 -5
  118. package/build/tsconfig.tsbuildinfo +1 -1
  119. package/package.json +1 -1
  120. package/src/configuration.js +36 -0
  121. package/src/database/drivers/base.js +244 -93
  122. package/src/database/drivers/mssql/index.js +10 -7
  123. package/src/database/drivers/mysql/index.js +10 -5
  124. package/src/database/drivers/pgsql/index.js +4 -4
  125. package/src/database/drivers/sqlite/base.js +2 -2
  126. package/src/database/drivers/sqlite/connection-sql-js.js +62 -2
  127. package/src/database/drivers/sqlite/index.web.js +74 -0
  128. package/src/database/operation-connection.js +121 -0
  129. package/src/database/operation-lease.js +47 -0
  130. package/src/database/operation.js +150 -0
  131. package/src/database/pool/base.js +26 -1
  132. package/src/database/pool/single-multi-use.js +49 -0
  133. package/src/database/query/model-class-query.js +38 -7
  134. package/src/database/query/preloader/belongs-to.js +3 -2
  135. package/src/database/query/preloader/has-many.js +4 -3
  136. package/src/database/query/preloader/has-one.js +2 -1
  137. package/src/database/query/preloader/query-for-model.js +17 -0
  138. package/src/database/query/query-data.js +12 -3
  139. package/src/database/query/with-count.js +5 -3
  140. package/src/database/record/acts-as-list.js +11 -8
  141. package/src/database/record/attachments/store.js +15 -11
  142. package/src/database/record/auditing.js +12 -9
  143. package/src/database/record/counter-cache-magnitude.js +5 -3
  144. package/src/database/record/index.js +95 -14
  145. package/src/database/record/instance-relationships/belongs-to.js +4 -2
  146. package/src/database/record/instance-relationships/has-many.js +7 -5
  147. package/src/database/record/instance-relationships/has-one.js +4 -2
  148. package/src/database/record/validators/uniqueness.js +3 -2
  149. package/src/frontend-models/websocket-publishers.js +2 -2
  150. package/src/sync/server-sequence-allocator.js +43 -10
  151. package/src/sync/sync-client.js +6 -2
  152. package/src/sync/sync-publisher.js +10 -5
@@ -24,7 +24,7 @@ import WhereParser from "../query-parser/where-parser.js"
24
24
  /**
25
25
  * Defines this typedef.
26
26
  * @template {typeof import("../record/index.js").default} [MC=typeof import("../record/index.js").default]
27
- * @typedef {import("./index.js").QueryArgsType & {modelClass: MC, joinBasePath?: string[], joinTracker?: import("./join-tracker.js").default, forceQualifyBaseTable?: boolean, withCount?: import("./with-count.js").WithCountEntry[], queryData?: import("./query-data.js").QueryDataEntry[]}} ModelClassQueryArgsType
27
+ * @typedef {import("./index.js").QueryArgsType & {modelClass: MC, joinBasePath?: string[], joinTracker?: import("./join-tracker.js").default, forceQualifyBaseTable?: boolean, withCount?: import("./with-count.js").WithCountEntry[], queryData?: import("./query-data.js").QueryDataEntry[], operation?: import("../operation.js").default}} ModelClassQueryArgsType
28
28
  */
29
29
  /**
30
30
  * Runs unquote sql identifier.
@@ -200,6 +200,7 @@ export default class VelociousDatabaseQueryModelClassQuery extends DatabaseQuery
200
200
  this._joinBasePath = args.joinBasePath || []
201
201
  this._joinTracker = args.joinTracker || new JoinTracker({modelClass: this.modelClass})
202
202
  this._forceQualifyBaseTable = Boolean(args.forceQualifyBaseTable)
203
+ this._operation = args.operation
203
204
 
204
205
  /**
205
206
  * Narrows the runtime value to the documented type.
@@ -240,7 +241,8 @@ export default class VelociousDatabaseQueryModelClassQuery extends DatabaseQuery
240
241
  joinTracker: this._joinTracker.clone(),
241
242
  forceQualifyBaseTable: this._forceQualifyBaseTable,
242
243
  withCount: [...this._withCount],
243
- queryData: [...this._queryData]
244
+ queryData: [...this._queryData],
245
+ operation: this._operation
244
246
  }))
245
247
 
246
248
  // @ts-expect-error
@@ -723,7 +725,11 @@ export default class VelociousDatabaseQueryModelClassQuery extends DatabaseQuery
723
725
  * @returns {VelociousDatabaseQueryModelClassQuery<MC>} - The scoped join query.
724
726
  */
725
727
  buildJoinScopeQuery(targetModelClass, joinPath) {
726
- const scopedQuery = /** @type {VelociousDatabaseQueryModelClassQuery<MC>} */ (targetModelClass._newQuery())
728
+ const scopedQuery = /** @type {VelociousDatabaseQueryModelClassQuery<MC>} */ (
729
+ this._operation
730
+ ? this._operation.forModel(targetModelClass)
731
+ : targetModelClass._newQuery()
732
+ )
727
733
 
728
734
  scopedQuery._joinTracker = this._joinTracker
729
735
  scopedQuery._joinBasePath = joinPath
@@ -864,8 +870,7 @@ export default class VelociousDatabaseQueryModelClassQuery extends DatabaseQuery
864
870
 
865
871
  if (record) return record
866
872
 
867
- const ModelClass = this.getModelClass()
868
- const newRecord = /** @type {InstanceType<MC>} */ (new ModelClass(conditions))
873
+ const newRecord = this.build(conditions)
869
874
 
870
875
  if (callback) {
871
876
  callback(newRecord)
@@ -874,6 +879,33 @@ export default class VelociousDatabaseQueryModelClassQuery extends DatabaseQuery
874
879
  return newRecord
875
880
  }
876
881
 
882
+ /**
883
+ * Builds a record owned by the query's operation, when present.
884
+ * @param {Record<string, ?>} [attributes] - Initial attributes.
885
+ * @returns {InstanceType<MC>} - Built record.
886
+ */
887
+ build(attributes = {}) {
888
+ const ModelClass = this.getModelClass()
889
+ const record = /** @type {InstanceType<MC>} */ (new ModelClass(attributes))
890
+
891
+ if (this._operation) this._operation.bindRecord(record)
892
+
893
+ return record
894
+ }
895
+
896
+ /**
897
+ * Creates a record owned by the query's operation, when present.
898
+ * @param {Record<string, ?>} [attributes] - Initial attributes.
899
+ * @returns {Promise<InstanceType<MC>>} - Created record.
900
+ */
901
+ async create(attributes = {}) {
902
+ const record = this.build(attributes)
903
+
904
+ await record.save()
905
+
906
+ return record
907
+ }
908
+
877
909
  /**
878
910
  * Runs first.
879
911
  * @returns {Promise<InstanceType<MC> | null>} - Resolves with the first.
@@ -917,8 +949,7 @@ export default class VelociousDatabaseQueryModelClassQuery extends DatabaseQuery
917
949
  const results = await this.results()
918
950
 
919
951
  for (const result of results) {
920
- const ModelClass = this.getModelClass()
921
- const model = /** @type {InstanceType<MC>} */ (new ModelClass())
952
+ const model = this.build()
922
953
 
923
954
  model.loadExistingRecord(result)
924
955
  models.push(model)
@@ -2,6 +2,7 @@
2
2
 
3
3
  import ensureModelClassInitialized from "./ensure-model-class-initialized.js"
4
4
  import PreloaderSelection from "./selection.js"
5
+ import preloadQueryForModel from "./query-for-model.js"
5
6
  import restArgsError from "../../../utils/rest-args-error.js"
6
7
 
7
8
  export default class VelociousDatabaseQueryPreloaderBelongsTo {
@@ -94,7 +95,7 @@ export default class VelociousDatabaseQueryPreloaderBelongsTo {
94
95
  whereArgs[primaryKey] = [...foreignKeyValues]
95
96
 
96
97
  // Load target models to be preloaded on the given models
97
- let query = targetModelClass.where(whereArgs)
98
+ let query = preloadQueryForModel(modelsToLoad, targetModelClass).where(whereArgs)
98
99
 
99
100
  query = this.relationship.applyScope(query)
100
101
  query = this.selection.applyToQuery({query, targetModelClass, mappingColumns: [primaryKey]})
@@ -211,7 +212,7 @@ export default class VelociousDatabaseQueryPreloaderBelongsTo {
211
212
 
212
213
  whereArgs[primaryKey] = [...foreignKeyValuesByType[targetType]]
213
214
 
214
- let query = targetModelClass.where(whereArgs)
215
+ let query = preloadQueryForModel(this.models, targetModelClass).where(whereArgs)
215
216
 
216
217
  query = this.relationship.applyScope(query)
217
218
  query = this.selection.applyToQuery({query, targetModelClass, mappingColumns: [primaryKey]})
@@ -2,6 +2,7 @@
2
2
 
3
3
  import ensureModelClassInitialized from "./ensure-model-class-initialized.js"
4
4
  import PreloaderSelection from "./selection.js"
5
+ import preloadQueryForModel from "./query-for-model.js"
5
6
  import restArgsError from "../../../utils/rest-args-error.js"
6
7
 
7
8
  /**
@@ -155,7 +156,7 @@ export default class VelociousDatabaseQueryPreloaderHasMany {
155
156
  }
156
157
 
157
158
  // Step 1: Query the through table to build parent→target ID mapping
158
- const throughModels = await throughModelClass
159
+ const throughModels = await preloadQueryForModel(modelsToLoad, throughModelClass)
159
160
  .where({[throughForeignKey]: [...modelsPrimaryKeyValues]})
160
161
  .toArray()
161
162
 
@@ -186,7 +187,7 @@ export default class VelociousDatabaseQueryPreloaderHasMany {
186
187
  let targetModels = []
187
188
 
188
189
  if (allTargetIds.size > 0) {
189
- let query = targetModelClass.where({[targetForeignKey]: [...allTargetIds]})
190
+ let query = preloadQueryForModel(modelsToLoad, targetModelClass).where({[targetForeignKey]: [...allTargetIds]})
190
191
 
191
192
  query = this.relationship.applyScope(query)
192
193
  query = this.selection.applyToQuery({query, targetModelClass, mappingColumns: [targetForeignKey]})
@@ -299,7 +300,7 @@ export default class VelociousDatabaseQueryPreloaderHasMany {
299
300
 
300
301
  await ensureModelClassInitialized(targetModelClass, this.relationship.getConfiguration())
301
302
 
302
- let query = targetModelClass.where(whereArgs)
303
+ let query = preloadQueryForModel(modelsToLoad, targetModelClass).where(whereArgs)
303
304
 
304
305
  query = this.relationship.applyScope(query)
305
306
  query = this.selection.applyToQuery({query, targetModelClass, mappingColumns: [foreignKey]})
@@ -2,6 +2,7 @@
2
2
 
3
3
  import ensureModelClassInitialized from "./ensure-model-class-initialized.js"
4
4
  import PreloaderSelection from "./selection.js"
5
+ import preloadQueryForModel from "./query-for-model.js"
5
6
  import restArgsError from "../../../utils/rest-args-error.js"
6
7
 
7
8
  export default class VelociousDatabaseQueryPreloaderHasOne {
@@ -87,7 +88,7 @@ export default class VelociousDatabaseQueryPreloaderHasOne {
87
88
  await ensureModelClassInitialized(targetModelClass, this.relationship.getConfiguration())
88
89
 
89
90
  // Load target models to be preloaded on the given models
90
- let query = targetModelClass.where(whereArgs)
91
+ let query = preloadQueryForModel(this.models, targetModelClass).where(whereArgs)
91
92
 
92
93
  query = this.relationship.applyScope(query)
93
94
  query = this.selection.applyToQuery({query, targetModelClass, mappingColumns: [foreignKey]})
@@ -0,0 +1,17 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * Builds a target query preserving the explicit database operation owned by
5
+ * the source records.
6
+ * @template {typeof import("../../record/index.js").default} MC
7
+ * @param {import("../../record/index.js").default[]} models - Source records.
8
+ * @param {MC} ModelClass - Target model class.
9
+ * @returns {import("../model-class-query.js").default<MC>} - Target query.
10
+ */
11
+ export default function preloadQueryForModel(models, ModelClass) {
12
+ const sourceModel = models[0]
13
+
14
+ if (!sourceModel) throw new Error("Cannot build a preload query without source records")
15
+
16
+ return sourceModel.queryForModel(ModelClass)
17
+ }
@@ -190,7 +190,15 @@ export async function runQueryData({rootModelClass, rootModels, entries}) {
190
190
 
191
191
  const primaryKey = rootModelClass.primaryKey()
192
192
  const rootIds = rootModels.map((model) => /** @type {string | number} */ (model.readColumn(primaryKey)))
193
- const preparedEntries = entries.map((entry, entryIndex) => prepareEntry({entry, entryIndex, primaryKey, rootIds, rootModelClass}))
193
+ const sourceModel = rootModels[0]
194
+ const preparedEntries = entries.map((entry, entryIndex) => prepareEntry({
195
+ entry,
196
+ entryIndex,
197
+ primaryKey,
198
+ rootIds,
199
+ rootModelClass,
200
+ sourceModel
201
+ }))
194
202
  /**
195
203
  * Compatible query groups.
196
204
  * @type {Array<{aliases: Set<string>, query: import("./model-class-query.js").default, signature: string}>} */
@@ -231,9 +239,10 @@ export async function runQueryData({rootModelClass, rootModels, entries}) {
231
239
  * @param {string} args.primaryKey - Root model primary key column.
232
240
  * @param {Array<string | number>} args.rootIds - Root primary-key values.
233
241
  * @param {typeof import("../record/index.js").default} args.rootModelClass - Root model class.
242
+ * @param {import("../record/index.js").default} args.sourceModel - Loaded operation owner.
234
243
  * @returns {{aliases: string[], query: import("./model-class-query.js").default, signature: string}} - Prepared entry.
235
244
  */
236
- function prepareEntry({entry, entryIndex, primaryKey, rootIds, rootModelClass}) {
245
+ function prepareEntry({entry, entryIndex, primaryKey, rootIds, rootModelClass, sourceModel}) {
237
246
  const targetModelClass = resolveTargetModelClass(rootModelClass, entry.chain)
238
247
  const fn = targetModelClass.getQueryDataByName(entry.fnName)
239
248
 
@@ -242,7 +251,7 @@ function prepareEntry({entry, entryIndex, primaryKey, rootIds, rootModelClass})
242
251
  `Declare it with ${targetModelClass.name}.queryData(${JSON.stringify(entry.fnName)}, ({query, tableName}) => query.select(...))`)
243
252
  }
244
253
 
245
- const query = rootModelClass._newQuery()
254
+ const query = sourceModel.queryForModel(rootModelClass)
246
255
 
247
256
  // Empty out any defaults the query factory added — queryData runs
248
257
  // a bare aggregate, not a full model load.
@@ -111,10 +111,11 @@ export async function runWithCount({models, modelClass, entries}) {
111
111
 
112
112
  const primaryKey = modelClass.primaryKey()
113
113
  const parentIds = models.map((model) => /** @type {string | number} */ (model.readColumn(primaryKey)))
114
+ const sourceModel = models[0]
114
115
  const queryGroups = new Map()
115
116
 
116
117
  for (const entry of entries) {
117
- const countQuery = queryForEntry({entry, modelClass, parentIds})
118
+ const countQuery = queryForEntry({entry, modelClass, parentIds, sourceModel})
118
119
  const sql = countQuery.toSql()
119
120
  const existingGroup = queryGroups.get(sql)
120
121
 
@@ -138,9 +139,10 @@ export async function runWithCount({models, modelClass, entries}) {
138
139
  * @param {WithCountEntry} args.entry - Entry being evaluated.
139
140
  * @param {typeof import("../record/index.js").default} args.modelClass - Parent model class.
140
141
  * @param {Array<string | number>} args.parentIds - Primary keys of the loaded parents.
142
+ * @param {import("../record/index.js").default} args.sourceModel - Loaded operation owner.
141
143
  * @returns {import("./model-class-query.js").default} - Prepared count query.
142
144
  */
143
- function queryForEntry({entry, modelClass, parentIds}) {
145
+ function queryForEntry({entry, modelClass, parentIds, sourceModel}) {
144
146
  const relationship = modelClass.getRelationshipByName(entry.relationshipName)
145
147
 
146
148
  if (!relationship) {
@@ -172,7 +174,7 @@ function queryForEntry({entry, modelClass, parentIds}) {
172
174
  Object.assign(whereConditions, entry.where)
173
175
  }
174
176
 
175
- const baseQuery = targetModelClass._newQuery()
177
+ const baseQuery = sourceModel.queryForModel(targetModelClass)
176
178
  baseQuery._forceQualifyBaseTable = true
177
179
  baseQuery.where(whereConditions)
178
180
 
@@ -173,7 +173,7 @@ function assertPositivePosition({position, positionColumn, persisted = false}) {
173
173
  */
174
174
  async function placeMovedRecord({record, positionColumn, scope, scopeValue, position}) {
175
175
  const modelClass = /** @type {typeof import("./index.js").default} */ (record.constructor)
176
- const connection = modelClass.connection()
176
+ const connection = record.connection()
177
177
  const tableSql = connection.quoteTable(modelClass._getTable().getName())
178
178
  const scopeCol = modelClass.getColumnNameForAttributeName(scope)
179
179
  const posCol = modelClass.getColumnNameForAttributeName(positionColumn)
@@ -224,7 +224,7 @@ function clearBelongsToChangeForScope(record) {
224
224
  */
225
225
  async function shiftPositionsUp({record, positionColumn, scope, fromPosition, toPosition, scopeValue, excludeRecordId}) {
226
226
  const modelClass = /** @type {typeof import("./index.js").default} */ (record.constructor)
227
- const connection = modelClass.connection()
227
+ const connection = record.connection()
228
228
  const tableName = modelClass._getTable().getName()
229
229
  const resolvedScopeValue = scopeValue != null ? scopeValue : resolveScopeValue(record, scope)
230
230
  const scopeColumnName = modelClass.getColumnNameForAttributeName(scope)
@@ -239,7 +239,8 @@ async function shiftPositionsUp({record, positionColumn, scope, fromPosition, to
239
239
  const quotedScope = connection.quote(resolvedScopeValue)
240
240
 
241
241
  // Load rows in descending order so we bump the highest first
242
- let query = modelClass
242
+ let query = record
243
+ .queryForModel(modelClass)
243
244
  .select(modelClass.primaryKey())
244
245
  .select(positionColumn)
245
246
  .where({[scopeColumnName]: resolvedScopeValue})
@@ -289,7 +290,7 @@ async function shiftPositionsUp({record, positionColumn, scope, fromPosition, to
289
290
  */
290
291
  async function shiftPositionsDown({record, positionColumn, scope, fromPosition, toPosition, scopeValue}) {
291
292
  const modelClass = /** @type {typeof import("./index.js").default} */ (record.constructor)
292
- const connection = modelClass.connection()
293
+ const connection = record.connection()
293
294
  const tableName = modelClass._getTable().getName()
294
295
  const resolvedScopeValue = scopeValue != null ? scopeValue : resolveScopeValue(record, scope)
295
296
  const scopeColumnName = modelClass.getColumnNameForAttributeName(scope)
@@ -304,7 +305,8 @@ async function shiftPositionsDown({record, positionColumn, scope, fromPosition,
304
305
  const quotedScope = connection.quote(resolvedScopeValue)
305
306
 
306
307
  // Load rows in ascending order so we shift the lowest gap first
307
- let query = modelClass
308
+ let query = record
309
+ .queryForModel(modelClass)
308
310
  .select(modelClass.primaryKey())
309
311
  .select(positionColumn)
310
312
  .where({[scopeColumnName]: resolvedScopeValue})
@@ -345,7 +347,7 @@ async function shiftPositionsDown({record, positionColumn, scope, fromPosition,
345
347
  */
346
348
  async function highestPositionInScope({record, positionColumn, scope, scopeValue}) {
347
349
  const modelClass = /** @type {typeof import("./index.js").default} */ (record.constructor)
348
- const connection = modelClass.connection()
350
+ const connection = record.connection()
349
351
  const scopeColumnName = modelClass.getColumnNameForAttributeName(scope)
350
352
  const positionColumnName = modelClass.getColumnNameForAttributeName(positionColumn)
351
353
  const positionColumnSql = connection.quoteColumn(positionColumnName)
@@ -353,7 +355,8 @@ async function highestPositionInScope({record, positionColumn, scope, scopeValue
353
355
 
354
356
  if (resolvedScopeValue == null) return 0
355
357
 
356
- const rows = await modelClass
358
+ const rows = await record
359
+ .queryForModel(modelClass)
357
360
  .select(positionColumn)
358
361
  .where({[scopeColumnName]: resolvedScopeValue})
359
362
  .order(`${positionColumnSql} DESC`)
@@ -415,7 +418,7 @@ function resolveScopeValue(record, scope) {
415
418
  */
416
419
  async function moveOutOfWay({record, positionColumn, scope, scopeValue}) {
417
420
  const modelClass = /** @type {typeof import("./index.js").default} */ (record.constructor)
418
- const connection = modelClass.connection()
421
+ const connection = record.connection()
419
422
  const tableName = modelClass._getTable().getName()
420
423
  const resolvedScopeValue = scopeValue != null ? scopeValue : resolveScopeValue(record, scope)
421
424
 
@@ -88,9 +88,10 @@ export default class RecordAttachmentsStore {
88
88
 
89
89
  /**
90
90
  * Runs ensure ready.
91
+ * @param {import("../index.js").default} [model] - Operation-owning model.
91
92
  * @returns {Promise<void>} - Resolves when schema is ready.
92
93
  */
93
- async ensureReady() {
94
+ async ensureReady(model) {
94
95
  if (this._readyPromise) {
95
96
  await this._readyPromise
96
97
  return
@@ -124,7 +125,7 @@ export default class RecordAttachmentsStore {
124
125
  await db.createTable(table)
125
126
  this._driverColumnsAvailable = true
126
127
  this._contentBase64Nullable = true
127
- })
128
+ }, model)
128
129
  })()
129
130
 
130
131
  try {
@@ -144,7 +145,7 @@ export default class RecordAttachmentsStore {
144
145
  * @returns {Promise<void>} - Resolves when complete.
145
146
  */
146
147
  async attach({input, model, name, replace}) {
147
- await this.ensureReady()
148
+ await this.ensureReady(model)
148
149
  const attachmentsConfiguration = this.configuration.getAttachmentsConfiguration?.() || {}
149
150
  const allowPathInput = attachmentsConfiguration.allowPathInput === true
150
151
  const allowedPathPrefixes = Array.isArray(attachmentsConfiguration.allowedPathPrefixes)
@@ -298,7 +299,7 @@ export default class RecordAttachmentsStore {
298
299
  })
299
300
 
300
301
  rowPersisted = true
301
- })
302
+ }, model)
302
303
  } catch (error) {
303
304
  if (!rowPersisted && storageKey && typeof attachmentDriver.delete === "function") {
304
305
  try {
@@ -435,7 +436,7 @@ export default class RecordAttachmentsStore {
435
436
  * @returns {Promise<Record<string, ?> | null>} - Attachment row.
436
437
  */
437
438
  async findOne({id, model, name}) {
438
- await this.ensureReady()
439
+ await this.ensureReady(model)
439
440
 
440
441
  return await this._withDb(async (db) => {
441
442
  const recordType = model.getModelClass().getModelName()
@@ -455,7 +456,7 @@ export default class RecordAttachmentsStore {
455
456
  const rows = await query.results()
456
457
 
457
458
  return rows[0] || null
458
- })
459
+ }, model)
459
460
  }
460
461
 
461
462
  /**
@@ -466,7 +467,7 @@ export default class RecordAttachmentsStore {
466
467
  * @returns {Promise<Array<Record<string, ?>>>} - Attachment rows.
467
468
  */
468
469
  async findMany({model, name}) {
469
- await this.ensureReady()
470
+ await this.ensureReady(model)
470
471
 
471
472
  return await this._withDb(async (db) => {
472
473
  const recordType = model.getModelClass().getModelName()
@@ -479,7 +480,7 @@ export default class RecordAttachmentsStore {
479
480
  .order("created_at_ms ASC")
480
481
 
481
482
  return await query.results()
482
- })
483
+ }, model)
483
484
  }
484
485
 
485
486
  /**
@@ -517,7 +518,7 @@ export default class RecordAttachmentsStore {
517
518
  * @returns {Promise<number>} - Number of attachments purged.
518
519
  */
519
520
  async purgeAll({model, name}) {
520
- await this.ensureReady()
521
+ await this.ensureReady(model)
521
522
 
522
523
  return await this._withDb(async (db) => {
523
524
  const recordType = model.getModelClass().getModelName()
@@ -549,7 +550,7 @@ export default class RecordAttachmentsStore {
549
550
  }
550
551
 
551
552
  return rows.length
552
- })
553
+ }, model)
553
554
  }
554
555
 
555
556
  /**
@@ -732,9 +733,12 @@ export default class RecordAttachmentsStore {
732
733
  * Runs with db.
733
734
  * @template T
734
735
  * @param {(db: import("../../../database/drivers/base.js").default) => Promise<T>} callback - Callback.
736
+ * @param {import("../index.js").default} [model] - Operation-owning model.
735
737
  * @returns {Promise<T>} - Callback result.
736
738
  */
737
- async _withDb(callback) {
739
+ async _withDb(callback, model) {
740
+ if (model && model.databaseOperation()) return await callback(model.connection())
741
+
738
742
  const pool = this.configuration.getDatabasePool(this.databaseIdentifier)
739
743
  /**
740
744
  * Defines result.
@@ -148,18 +148,20 @@ function dedicatedAuditTableName(modelClass) {
148
148
  * Resolves audit table data for a model class. Cached per model.
149
149
  * Called lazily on first createAudit / withoutAudit / relationship usage.
150
150
  * @param {AuditedModelClass} modelClass - Audited model class.
151
+ * @param {import("../drivers/base.js").default} [connection] - Explicit record-owned connection.
151
152
  * @returns {Promise<AuditTableData>} Resolved audit table metadata.
152
153
  */
153
- async function resolveAuditTableData(modelClass) {
154
+ async function resolveAuditTableData(modelClass, connection) {
154
155
  if (modelClass._auditTableResolved && modelClass._auditTableData) {
155
156
  return modelClass._auditTableData
156
157
  }
157
158
 
158
- const tableData = await buildAuditTableData(modelClass)
159
+ const resolvedConnection = connection || modelClass.connection()
160
+ const tableData = await buildAuditTableData(modelClass, resolvedConnection)
159
161
  const configuration = modelClass._getConfiguration()
160
162
 
161
163
  tableData.auditClass.registerRecordClass({configuration})
162
- await tableData.auditClass.initializeRecord({configuration})
164
+ await tableData.auditClass.initializeRecord({configuration, connection: resolvedConnection})
163
165
 
164
166
  modelClass._auditTableData = tableData
165
167
  modelClass._auditTableResolved = true
@@ -174,11 +176,12 @@ async function resolveAuditTableData(modelClass) {
174
176
  * registered Audit model for shared tables; falls back to a framework-owned
175
177
  * dynamic class.
176
178
  * @param {AuditedModelClass} modelClass - Audited model class.
179
+ * @param {import("../drivers/base.js").default} connection - Explicit record-owned connection.
177
180
  * @returns {Promise<AuditTableData>} Audit table metadata.
178
181
  */
179
- async function buildAuditTableData(modelClass) {
182
+ async function buildAuditTableData(modelClass, connection) {
180
183
  const dedicatedTable = dedicatedAuditTableName(modelClass)
181
- const dedicatedExists = await dedicatedTableExistsForConnection(modelClass, dedicatedTable)
184
+ const dedicatedExists = await dedicatedTableExistsForConnection(modelClass, dedicatedTable, connection)
182
185
 
183
186
  if (dedicatedExists) {
184
187
  const auditClass = dedicatedAuditClass(modelClass, dedicatedTable)
@@ -217,9 +220,10 @@ async function buildAuditTableData(modelClass) {
217
220
  * Checks whether a dedicated audit table exists for a model's connection.
218
221
  * @param {AuditedModelClass} modelClass - Audited model class.
219
222
  * @param {string} tableName - Dedicated audit table name to check.
223
+ * @param {import("../drivers/base.js").default} connection - Explicit record-owned connection.
220
224
  * @returns {Promise<boolean>} Whether the table exists.
221
225
  */
222
- async function dedicatedTableExistsForConnection(modelClass, tableName) {
226
+ async function dedicatedTableExistsForConnection(modelClass, tableName, connection) {
223
227
  const databaseIdentifier = modelClass.getDatabaseIdentifier()
224
228
  const cacheKey = `${databaseIdentifier}:${tableName}`
225
229
  const cached = dedicatedTableCache.get(cacheKey)
@@ -228,7 +232,6 @@ async function dedicatedTableExistsForConnection(modelClass, tableName) {
228
232
  return cached
229
233
  }
230
234
 
231
- const connection = modelClass.connection()
232
235
  const table = await connection.getTableByName(tableName, {throwError: false})
233
236
  const exists = Boolean(table)
234
237
 
@@ -546,11 +549,11 @@ async function createAudit(record, args) {
546
549
  async function createAuditWithCurrentConnection(record, args, modelClass) {
547
550
  if (!record.isPersisted()) throw new Error(`Cannot audit unpersisted ${modelClass.getModelName()} record`)
548
551
 
549
- const tableData = await resolveAuditTableData(modelClass)
552
+ const db = record.connection()
553
+ const tableData = await resolveAuditTableData(modelClass, db)
550
554
  const action = normalizeAction(args.action)
551
555
  const auditedChanges = args.auditedChanges === undefined ? null : args.auditedChanges
552
556
  const params = args.params === undefined ? null : args.params
553
- const db = modelClass.connection()
554
557
  const currentDate = new Date()
555
558
 
556
559
  const auditActionId = await findOrCreateLookupId({
@@ -204,9 +204,11 @@ async function incrementParentCounter(record, definition, parentId, amount) {
204
204
  throw new Error(`magnitudeCounterCache on ${modelClass.getModelName()} could not resolve the "${definition.belongsTo}" parent model class`)
205
205
  }
206
206
 
207
- // Update through the PARENT model's connection: the row being modified belongs
208
- // to the parent, which may live on a different database/tenant than the child.
209
- const db = parentModelClass.connection()
207
+ // Resolve through the record so an operation both owns the parent write and
208
+ // rejects a relationship whose parent routes to another database.
209
+ const db = record
210
+ .queryForModel(parentModelClass)
211
+ .driver
210
212
  const counterColumnSql = db.quoteColumn(definition.counterColumn)
211
213
  const truncatedAmount = Math.trunc(amount)
212
214