velocious 1.0.643 → 1.0.645

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 (279) hide show
  1. package/README.md +21 -10
  2. package/build/authorization/ability.js +36 -13
  3. package/build/background-jobs/adapter.js +9 -0
  4. package/build/background-jobs/main.js +35 -10
  5. package/build/background-jobs/store.js +217 -38
  6. package/build/background-jobs/types.js +15 -0
  7. package/build/configuration-types.js +1 -1
  8. package/build/configuration.js +45 -9
  9. package/build/database/drivers/base-column.js +8 -0
  10. package/build/database/drivers/mssql/sql/update.js +1 -3
  11. package/build/database/drivers/mysql/sql/update.js +1 -3
  12. package/build/database/drivers/pgsql/column.js +22 -0
  13. package/build/database/drivers/pgsql/sql/update.js +1 -3
  14. package/build/database/drivers/sqlite/sql/alter-table.js +11 -1
  15. package/build/database/drivers/sqlite/sql/update.js +1 -3
  16. package/build/database/query/index.js +2 -2
  17. package/build/database/query/model-class-query.js +26 -10
  18. package/build/database/query/preloader/has-many.js +4 -1
  19. package/build/database/query/query-data.js +2 -1
  20. package/build/database/query/update-base.js +14 -0
  21. package/build/database/query/with-count.js +3 -1
  22. package/build/database/record/acts-as-list.js +37 -13
  23. package/build/database/record/attachments/store.js +336 -26
  24. package/build/database/record/auditing.js +6 -3
  25. package/build/database/record/index.js +197 -42
  26. package/build/database/record/instance-relationships/belongs-to.js +2 -1
  27. package/build/database/record/validators/uniqueness.js +8 -5
  28. package/build/environment-handlers/node/cli/commands/generate/frontend-models.js +24 -8
  29. package/build/frontend-model-controller.js +136 -53
  30. package/build/frontend-model-resource/base-resource.js +87 -37
  31. package/build/frontend-model-resource/velocious-attachment-resource.js +98 -31
  32. package/build/frontend-models/base.js +307 -59
  33. package/build/frontend-models/preloader.js +6 -3
  34. package/build/frontend-models/query.js +15 -6
  35. package/build/frontend-models/resource-definition.js +21 -2
  36. package/build/frontend-models/use-destroyed-event.js +1 -1
  37. package/build/frontend-models/use-model-class-event.js +2 -2
  38. package/build/frontend-models/use-updated-event.js +1 -1
  39. package/build/frontend-models/websocket-channel.js +256 -93
  40. package/build/frontend-models/websocket-publishers.js +218 -18
  41. package/build/http-server/client/websocket-session.js +13 -1
  42. package/build/http-server/websocket-channel.js +15 -2
  43. package/build/src/authorization/ability.d.ts +9 -3
  44. package/build/src/authorization/ability.d.ts.map +1 -1
  45. package/build/src/authorization/ability.js +33 -14
  46. package/build/src/background-jobs/adapter.d.ts +6 -0
  47. package/build/src/background-jobs/adapter.d.ts.map +1 -1
  48. package/build/src/background-jobs/adapter.js +9 -1
  49. package/build/src/background-jobs/main.d.ts +6 -0
  50. package/build/src/background-jobs/main.d.ts.map +1 -1
  51. package/build/src/background-jobs/main.js +34 -11
  52. package/build/src/background-jobs/store.d.ts +66 -12
  53. package/build/src/background-jobs/store.d.ts.map +1 -1
  54. package/build/src/background-jobs/store.js +191 -35
  55. package/build/src/background-jobs/types.d.ts +55 -0
  56. package/build/src/background-jobs/types.d.ts.map +1 -1
  57. package/build/src/background-jobs/types.js +16 -1
  58. package/build/src/configuration-types.d.ts +3 -3
  59. package/build/src/configuration-types.d.ts.map +1 -1
  60. package/build/src/configuration-types.js +2 -2
  61. package/build/src/configuration.d.ts +18 -8
  62. package/build/src/configuration.d.ts.map +1 -1
  63. package/build/src/configuration.js +38 -7
  64. package/build/src/database/drivers/base-column.d.ts +5 -0
  65. package/build/src/database/drivers/base-column.d.ts.map +1 -1
  66. package/build/src/database/drivers/base-column.js +8 -1
  67. package/build/src/database/drivers/mssql/sql/update.d.ts.map +1 -1
  68. package/build/src/database/drivers/mssql/sql/update.js +2 -4
  69. package/build/src/database/drivers/mysql/sql/update.d.ts.map +1 -1
  70. package/build/src/database/drivers/mysql/sql/update.js +2 -4
  71. package/build/src/database/drivers/pgsql/column.d.ts +5 -0
  72. package/build/src/database/drivers/pgsql/column.d.ts.map +1 -1
  73. package/build/src/database/drivers/pgsql/column.js +19 -1
  74. package/build/src/database/drivers/pgsql/sql/update.d.ts.map +1 -1
  75. package/build/src/database/drivers/pgsql/sql/update.js +2 -4
  76. package/build/src/database/drivers/sqlite/sql/alter-table.d.ts.map +1 -1
  77. package/build/src/database/drivers/sqlite/sql/alter-table.js +6 -2
  78. package/build/src/database/drivers/sqlite/sql/update.d.ts.map +1 -1
  79. package/build/src/database/drivers/sqlite/sql/update.js +2 -4
  80. package/build/src/database/query/index.d.ts +4 -4
  81. package/build/src/database/query/index.d.ts.map +1 -1
  82. package/build/src/database/query/index.js +3 -3
  83. package/build/src/database/query/model-class-query.d.ts +8 -2
  84. package/build/src/database/query/model-class-query.d.ts.map +1 -1
  85. package/build/src/database/query/model-class-query.js +25 -11
  86. package/build/src/database/query/preloader/has-many.d.ts.map +1 -1
  87. package/build/src/database/query/preloader/has-many.js +4 -2
  88. package/build/src/database/query/query-data.d.ts.map +1 -1
  89. package/build/src/database/query/query-data.js +3 -2
  90. package/build/src/database/query/update-base.d.ts +7 -0
  91. package/build/src/database/query/update-base.d.ts.map +1 -1
  92. package/build/src/database/query/update-base.js +13 -1
  93. package/build/src/database/query/with-count.d.ts.map +1 -1
  94. package/build/src/database/query/with-count.js +3 -2
  95. package/build/src/database/record/acts-as-list.d.ts.map +1 -1
  96. package/build/src/database/record/acts-as-list.js +35 -14
  97. package/build/src/database/record/attachments/store.d.ts +67 -0
  98. package/build/src/database/record/attachments/store.d.ts.map +1 -1
  99. package/build/src/database/record/attachments/store.js +301 -25
  100. package/build/src/database/record/auditing.d.ts.map +1 -1
  101. package/build/src/database/record/auditing.js +7 -4
  102. package/build/src/database/record/index.d.ts +78 -26
  103. package/build/src/database/record/index.d.ts.map +1 -1
  104. package/build/src/database/record/index.js +171 -42
  105. package/build/src/database/record/instance-relationships/belongs-to.d.ts.map +1 -1
  106. package/build/src/database/record/instance-relationships/belongs-to.js +3 -2
  107. package/build/src/database/record/validators/uniqueness.d.ts.map +1 -1
  108. package/build/src/database/record/validators/uniqueness.js +8 -6
  109. package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts +4 -4
  110. package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts.map +1 -1
  111. package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.js +24 -12
  112. package/build/src/frontend-model-controller.d.ts +29 -8
  113. package/build/src/frontend-model-controller.d.ts.map +1 -1
  114. package/build/src/frontend-model-controller.js +122 -51
  115. package/build/src/frontend-model-resource/base-resource.d.ts +51 -24
  116. package/build/src/frontend-model-resource/base-resource.d.ts.map +1 -1
  117. package/build/src/frontend-model-resource/base-resource.js +83 -37
  118. package/build/src/frontend-model-resource/velocious-attachment-resource.d.ts +45 -26
  119. package/build/src/frontend-model-resource/velocious-attachment-resource.d.ts.map +1 -1
  120. package/build/src/frontend-model-resource/velocious-attachment-resource.js +89 -31
  121. package/build/src/frontend-models/base.d.ts +53 -20
  122. package/build/src/frontend-models/base.d.ts.map +1 -1
  123. package/build/src/frontend-models/base.js +275 -59
  124. package/build/src/frontend-models/preloader.d.ts.map +1 -1
  125. package/build/src/frontend-models/preloader.js +6 -4
  126. package/build/src/frontend-models/query.d.ts +2 -2
  127. package/build/src/frontend-models/query.d.ts.map +1 -1
  128. package/build/src/frontend-models/query.js +14 -7
  129. package/build/src/frontend-models/resource-definition.d.ts.map +1 -1
  130. package/build/src/frontend-models/resource-definition.js +20 -3
  131. package/build/src/frontend-models/use-destroyed-event.d.ts +1 -1
  132. package/build/src/frontend-models/use-destroyed-event.d.ts.map +1 -1
  133. package/build/src/frontend-models/use-destroyed-event.js +2 -2
  134. package/build/src/frontend-models/use-model-class-event.d.ts +2 -2
  135. package/build/src/frontend-models/use-model-class-event.d.ts.map +1 -1
  136. package/build/src/frontend-models/use-model-class-event.js +3 -3
  137. package/build/src/frontend-models/use-updated-event.d.ts +1 -1
  138. package/build/src/frontend-models/use-updated-event.d.ts.map +1 -1
  139. package/build/src/frontend-models/use-updated-event.js +2 -2
  140. package/build/src/frontend-models/websocket-channel.d.ts +60 -41
  141. package/build/src/frontend-models/websocket-channel.d.ts.map +1 -1
  142. package/build/src/frontend-models/websocket-channel.js +225 -85
  143. package/build/src/frontend-models/websocket-publishers.d.ts +9 -0
  144. package/build/src/frontend-models/websocket-publishers.d.ts.map +1 -1
  145. package/build/src/frontend-models/websocket-publishers.js +190 -19
  146. package/build/src/http-server/client/websocket-session.d.ts.map +1 -1
  147. package/build/src/http-server/client/websocket-session.js +11 -2
  148. package/build/src/http-server/websocket-channel.d.ts +20 -8
  149. package/build/src/http-server/websocket-channel.d.ts.map +1 -1
  150. package/build/src/http-server/websocket-channel.js +15 -3
  151. package/build/src/sync/assets/cache.d.ts +288 -0
  152. package/build/src/sync/assets/cache.d.ts.map +1 -0
  153. package/build/src/sync/assets/cache.js +883 -0
  154. package/build/src/sync/assets/types.d.ts +199 -0
  155. package/build/src/sync/assets/types.d.ts.map +1 -0
  156. package/build/src/sync/assets/types.js +56 -0
  157. package/build/src/sync/sync-api-client.d.ts.map +1 -1
  158. package/build/src/sync/sync-api-client.js +4 -3
  159. package/build/src/sync/sync-client.d.ts.map +1 -1
  160. package/build/src/sync/sync-client.js +3 -2
  161. package/build/src/sync/sync-envelope-replay-service.d.ts.map +1 -1
  162. package/build/src/sync/sync-envelope-replay-service.js +6 -5
  163. package/build/src/sync/sync-publisher.d.ts.map +1 -1
  164. package/build/src/sync/sync-publisher.js +4 -3
  165. package/build/src/testing/browser-frontend-model-event-hook-scenarios.d.ts +2 -2
  166. package/build/src/testing/browser-frontend-model-event-hook-scenarios.d.ts.map +1 -1
  167. package/build/src/testing/browser-frontend-model-event-hook-scenarios.js +3 -3
  168. package/build/src/testing/test-profiler.d.ts +4 -4
  169. package/build/src/testing/test-profiler.d.ts.map +1 -1
  170. package/build/src/testing/test-profiler.js +3 -3
  171. package/build/src/testing/test-runner.d.ts +279 -117
  172. package/build/src/testing/test-runner.d.ts.map +1 -1
  173. package/build/src/testing/test-runner.js +503 -933
  174. package/build/src/testing/test.d.ts +11 -72
  175. package/build/src/testing/test.d.ts.map +1 -1
  176. package/build/src/testing/test.js +131 -284
  177. package/build/src/testing/velocious-attempt-executor.d.ts +54 -0
  178. package/build/src/testing/velocious-attempt-executor.d.ts.map +1 -0
  179. package/build/src/testing/velocious-attempt-executor.js +408 -0
  180. package/build/src/testing/velocious-runner-reporter.d.ts +67 -0
  181. package/build/src/testing/velocious-runner-reporter.d.ts.map +1 -0
  182. package/build/src/testing/velocious-runner-reporter.js +240 -0
  183. package/build/src/testing/velocious-suite-hook-executor.d.ts +19 -0
  184. package/build/src/testing/velocious-suite-hook-executor.d.ts.map +1 -0
  185. package/build/src/testing/velocious-suite-hook-executor.js +41 -0
  186. package/build/src/testing/velocious-test-arguments.d.ts +39 -0
  187. package/build/src/testing/velocious-test-arguments.d.ts.map +1 -0
  188. package/build/src/testing/velocious-test-arguments.js +55 -0
  189. package/build/src/utils/model-primary-key.d.ts +73 -0
  190. package/build/src/utils/model-primary-key.d.ts.map +1 -0
  191. package/build/src/utils/model-primary-key.js +144 -0
  192. package/build/src/utils/sha256-bytes-hex.d.ts +7 -0
  193. package/build/src/utils/sha256-bytes-hex.d.ts.map +1 -0
  194. package/build/src/utils/sha256-bytes-hex.js +117 -0
  195. package/build/src/utils/sha256-hex.d.ts +2 -4
  196. package/build/src/utils/sha256-hex.d.ts.map +1 -1
  197. package/build/src/utils/sha256-hex.js +6 -120
  198. package/build/sync/assets/cache.js +998 -0
  199. package/build/sync/assets/types.js +62 -0
  200. package/build/sync/sync-api-client.js +3 -2
  201. package/build/sync/sync-client.js +2 -1
  202. package/build/sync/sync-envelope-replay-service.js +5 -4
  203. package/build/sync/sync-publisher.js +3 -2
  204. package/build/testing/browser-frontend-model-event-hook-scenarios.js +2 -2
  205. package/build/testing/test-profiler.js +2 -2
  206. package/build/testing/test-runner.js +496 -990
  207. package/build/testing/test.js +164 -306
  208. package/build/testing/velocious-attempt-executor.js +441 -0
  209. package/build/testing/velocious-runner-reporter.js +264 -0
  210. package/build/testing/velocious-suite-hook-executor.js +43 -0
  211. package/build/testing/velocious-test-arguments.js +64 -0
  212. package/build/tsconfig.tsbuildinfo +1 -1
  213. package/build/utils/model-primary-key.js +171 -0
  214. package/build/utils/sha256-bytes-hex.js +132 -0
  215. package/build/utils/sha256-hex.js +7 -135
  216. package/package.json +5 -4
  217. package/src/authorization/ability.js +36 -13
  218. package/src/background-jobs/adapter.js +9 -0
  219. package/src/background-jobs/main.js +35 -10
  220. package/src/background-jobs/store.js +217 -38
  221. package/src/background-jobs/types.js +15 -0
  222. package/src/configuration-types.js +1 -1
  223. package/src/configuration.js +45 -9
  224. package/src/database/drivers/base-column.js +8 -0
  225. package/src/database/drivers/mssql/sql/update.js +1 -3
  226. package/src/database/drivers/mysql/sql/update.js +1 -3
  227. package/src/database/drivers/pgsql/column.js +22 -0
  228. package/src/database/drivers/pgsql/sql/update.js +1 -3
  229. package/src/database/drivers/sqlite/sql/alter-table.js +11 -1
  230. package/src/database/drivers/sqlite/sql/update.js +1 -3
  231. package/src/database/query/index.js +2 -2
  232. package/src/database/query/model-class-query.js +26 -10
  233. package/src/database/query/preloader/has-many.js +4 -1
  234. package/src/database/query/query-data.js +2 -1
  235. package/src/database/query/update-base.js +14 -0
  236. package/src/database/query/with-count.js +3 -1
  237. package/src/database/record/acts-as-list.js +37 -13
  238. package/src/database/record/attachments/store.js +336 -26
  239. package/src/database/record/auditing.js +6 -3
  240. package/src/database/record/index.js +197 -42
  241. package/src/database/record/instance-relationships/belongs-to.js +2 -1
  242. package/src/database/record/validators/uniqueness.js +8 -5
  243. package/src/environment-handlers/node/cli/commands/generate/frontend-models.js +24 -8
  244. package/src/frontend-model-controller.js +136 -53
  245. package/src/frontend-model-resource/base-resource.js +87 -37
  246. package/src/frontend-model-resource/velocious-attachment-resource.js +98 -31
  247. package/src/frontend-models/base.js +307 -59
  248. package/src/frontend-models/preloader.js +6 -3
  249. package/src/frontend-models/query.js +15 -6
  250. package/src/frontend-models/resource-definition.js +21 -2
  251. package/src/frontend-models/use-destroyed-event.js +1 -1
  252. package/src/frontend-models/use-model-class-event.js +2 -2
  253. package/src/frontend-models/use-updated-event.js +1 -1
  254. package/src/frontend-models/websocket-channel.js +256 -93
  255. package/src/frontend-models/websocket-publishers.js +218 -18
  256. package/src/http-server/client/websocket-session.js +13 -1
  257. package/src/http-server/websocket-channel.js +15 -2
  258. package/src/sync/assets/cache.js +998 -0
  259. package/src/sync/assets/types.js +62 -0
  260. package/src/sync/sync-api-client.js +3 -2
  261. package/src/sync/sync-client.js +2 -1
  262. package/src/sync/sync-envelope-replay-service.js +5 -4
  263. package/src/sync/sync-publisher.js +3 -2
  264. package/src/testing/browser-frontend-model-event-hook-scenarios.js +2 -2
  265. package/src/testing/test-profiler.js +2 -2
  266. package/src/testing/test-runner.js +496 -990
  267. package/src/testing/test.js +164 -306
  268. package/src/testing/velocious-attempt-executor.js +441 -0
  269. package/src/testing/velocious-runner-reporter.js +264 -0
  270. package/src/testing/velocious-suite-hook-executor.js +43 -0
  271. package/src/testing/velocious-test-arguments.js +64 -0
  272. package/src/utils/model-primary-key.js +171 -0
  273. package/src/utils/sha256-bytes-hex.js +132 -0
  274. package/src/utils/sha256-hex.js +7 -135
  275. package/build/src/testing/testing-package-adapter.d.ts +0 -9
  276. package/build/src/testing/testing-package-adapter.d.ts.map +0 -1
  277. package/build/src/testing/testing-package-adapter.js +0 -69
  278. package/build/testing/testing-package-adapter.js +0 -76
  279. package/src/testing/testing-package-adapter.js +0 -76
@@ -22,9 +22,7 @@ export default class VelociousDatabaseConnectionDriversMysqlSqlUpdate extends Up
22
22
  for (let columnName in this.conditions) {
23
23
  if (count > 0) sql += " AND "
24
24
 
25
- sql += this.getOptions().quoteColumnName(columnName)
26
- sql += " = "
27
- sql += this.formatValue(this.conditions[columnName])
25
+ sql += this.formatCondition(columnName, this.conditions[columnName])
28
26
  count++
29
27
  }
30
28
 
@@ -481,7 +481,7 @@ export default class VelociousDatabaseQuery {
481
481
  * Runs execute query.
482
482
  * @param {object} [args] - Options object.
483
483
  * @param {string} [args.logName] - Query log name.
484
- * @returns {Promise<Array<object>>} Array of results from the database
484
+ * @returns {Promise<import("../drivers/base.js").QueryResultType>} Array of results from the database
485
485
  */
486
486
  async _executeQuery({logName = this.queryLogName("Load")} = {}) {
487
487
  const sql = this.toSql()
@@ -503,7 +503,7 @@ export default class VelociousDatabaseQuery {
503
503
 
504
504
  /**
505
505
  * Runs results.
506
- * @returns {Promise<Array<object>>} Array of results from the database
506
+ * @returns {Promise<import("../drivers/base.js").QueryResultType>} Array of results from the database
507
507
  */
508
508
  async results() {
509
509
  return await this._executeQuery()
@@ -15,6 +15,7 @@ import JoinTracker from "./join-tracker.js"
15
15
  import RecordNotFoundError from "../record/record-not-found-error.js"
16
16
  import {normalizeRansackGroup, parseRansackSort} from "../../utils/ransack.js"
17
17
  import {isModelScopeDescriptor} from "../../utils/model-scope.js"
18
+ import {modelPrimaryKeyConditions, scalarModelPrimaryKey} from "../../utils/model-primary-key.js"
18
19
  import WhereCombinator from "./where-combinator.js"
19
20
  import WhereModelClassHash from "./where-model-class-hash.js"
20
21
  import WhereNot from "./where-not.js"
@@ -323,7 +324,8 @@ export default class VelociousDatabaseQueryModelClassQuery extends DatabaseQuery
323
324
  // setPrimaryKey([...]) on legacy tables — has no column COUNT can reference (an array primary key
324
325
  // cannot be quoted as a single COUNT(column), and primaryKey() falls back to "id" for the no-pk
325
326
  // case, so hasPrimaryKey() detects that one).
326
- const hasSingleColumnPrimaryKey = this.getModelClass().hasPrimaryKey() && !Array.isArray(this.getModelClass().primaryKey())
327
+ const primaryKey = this.getModelClass().primaryKey()
328
+ const hasSingleColumnPrimaryKey = this.getModelClass().hasPrimaryKey() && !Array.isArray(primaryKey)
327
329
 
328
330
  // Pagination, or an ungrouped query on a model with no single primary-key column, counts via the
329
331
  // subquery form. It references no primary-key column and preserves DISTINCT over joins — which a
@@ -340,7 +342,7 @@ export default class VelociousDatabaseQueryModelClassQuery extends DatabaseQuery
340
342
 
341
343
  const distinctPrefix = this._distinct ? "DISTINCT " : ""
342
344
  const countExpression = hasSingleColumnPrimaryKey
343
- ? `${this.driver.quoteTable(this.getModelClass().tableName())}.${this.driver.quoteColumn(this.getModelClass().primaryKey())}`
345
+ ? `${this.driver.quoteTable(this.getModelClass().tableName())}.${this.driver.quoteColumn(/** @type {string} */ (primaryKey))}`
344
346
  : "*"
345
347
  let sql = `COUNT(${distinctPrefix}${countExpression})`
346
348
 
@@ -807,7 +809,8 @@ export default class VelociousDatabaseQueryModelClassQuery extends DatabaseQuery
807
809
  if (joinsSql.length > 0) {
808
810
  // Use a subquery for cross-driver compatibility (SQLite
809
811
  // doesn't support UPDATE ... JOIN).
810
- const pk = driver.quoteColumn(this.getModelClass().primaryKey())
812
+ const primaryKey = scalarModelPrimaryKey(this.getModelClass().primaryKey(), `${this.getModelClass().name}.updateAll() with joins`)
813
+ const pk = driver.quoteColumn(primaryKey)
811
814
  const qt = driver.quoteTable(tableName)
812
815
 
813
816
  sql = `UPDATE ${qt} SET ${setCols} WHERE ${pk} IN (SELECT ${qt}.${pk} FROM ${qt}${joinsSql}${whereSql})`
@@ -820,7 +823,7 @@ export default class VelociousDatabaseQueryModelClassQuery extends DatabaseQuery
820
823
 
821
824
  /**
822
825
  * Runs find.
823
- * @param {number|string} recordId - Record id.
826
+ * @param {import("../../utils/model-primary-key.js").ModelPrimaryKeyValue} recordId - Record id.
824
827
  * @returns {Promise<InstanceType<MC>>} - Resolves with the find.
825
828
  */
826
829
  async find(recordId) {
@@ -829,7 +832,7 @@ export default class VelociousDatabaseQueryModelClassQuery extends DatabaseQuery
829
832
  * @type {{[key: string]: number | string}} */
830
833
  const conditions = {}
831
834
 
832
- conditions[this.getModelClass().primaryKey()] = recordId
835
+ Object.assign(conditions, modelPrimaryKeyConditions(this.getModelClass().primaryKey(), recordId))
833
836
 
834
837
  const newQuery = /** @type {VelociousDatabaseQueryModelClassQuery<MC>} */ (this.clone())
835
838
 
@@ -838,7 +841,7 @@ export default class VelociousDatabaseQueryModelClassQuery extends DatabaseQuery
838
841
  const record = (await newQuery.first())
839
842
 
840
843
  if (!record) {
841
- throw new RecordNotFoundError(`Couldn't find ${this.getModelClass().name} with '${this.getModelClass().primaryKey()}'=${recordId}`)
844
+ throw new RecordNotFoundError(`Couldn't find ${this.getModelClass().name} with '${this.getModelClass().primaryKey()}'=${JSON.stringify(recordId)}`)
842
845
  }
843
846
 
844
847
  return record
@@ -940,7 +943,7 @@ export default class VelociousDatabaseQueryModelClassQuery extends DatabaseQuery
940
943
  * @returns {Promise<InstanceType<MC> | null>} - Resolves with the first.
941
944
  */
942
945
  async first() {
943
- const newQuery = this.clone().limit(1).reorder(`${this.driver.quoteTable(this.getModelClass().tableName())}.${this.driver.quoteColumn(this.getModelClass().orderableColumn())}`)
946
+ const newQuery = this.clone().limit(1).reorder(this._defaultIdentityOrder("ASC"))
944
947
  const results = await newQuery.toArray()
945
948
 
946
949
  return results[0] || null
@@ -951,13 +954,26 @@ export default class VelociousDatabaseQueryModelClassQuery extends DatabaseQuery
951
954
  * @returns {Promise<InstanceType<MC> | null>} - Resolves with the last.
952
955
  */
953
956
  async last() {
954
- const primaryKey = this.getModelClass().primaryKey()
955
- const tableName = this.getModelClass().tableName()
956
- const results = await this.clone().reorder(`${this.driver.quoteTable(tableName)}.${this.driver.quoteColumn(primaryKey)} DESC`).limit(1).toArray()
957
+ const results = await this.clone().reorder(this._defaultIdentityOrder("DESC")).limit(1).toArray()
957
958
 
958
959
  return results[0] || null
959
960
  }
960
961
 
962
+ /**
963
+ * Builds the deterministic default order for the model identity.
964
+ * @param {"ASC" | "DESC"} direction - Sort direction.
965
+ * @returns {string} - SQL order expression.
966
+ */
967
+ _defaultIdentityOrder(direction) {
968
+ const ModelClass = this.getModelClass()
969
+ const primaryKey = ModelClass.primaryKey()
970
+ const orderableColumns = Array.isArray(primaryKey) ? primaryKey : [ModelClass.orderableColumn()]
971
+
972
+ return orderableColumns
973
+ .map((column) => `${this.driver.quoteTable(ModelClass.tableName())}.${this.driver.quoteColumn(column)} ${direction}`)
974
+ .join(", ")
975
+ }
976
+
961
977
  /**
962
978
  * Runs preload.
963
979
  * @param {import("./index.js").NestedPreloadRecord | string | Array<string | import("./index.js").NestedPreloadRecord>} data - Data payload.
@@ -1,5 +1,7 @@
1
1
  // @ts-check
2
2
 
3
+ import {scalarModelPrimaryKey} from "../../../utils/model-primary-key.js"
4
+
3
5
  import ensureModelClassInitialized from "./ensure-model-class-initialized.js"
4
6
  import PreloaderSelection from "./selection.js"
5
7
  import preloadQueryForModel, { bindPreloadModelClass } from "./query-for-model.js"
@@ -185,7 +187,8 @@ export default class VelociousDatabaseQueryPreloaderHasMany {
185
187
 
186
188
  for (const throughModel of throughModels) {
187
189
  const parentId = /** @type {string | number} */ (throughModel.readColumn(throughForeignKey))
188
- const throughId = /** @type {string | number} */ (throughModel.readColumn(throughModelClass.primaryKey()))
190
+ const throughPrimaryKey = scalarModelPrimaryKey(throughModelClass.primaryKey(), `Has-many-through preload for ${throughModelClass.name}`)
191
+ const throughId = /** @type {string | number} */ (throughModel.readColumn(throughPrimaryKey))
189
192
 
190
193
  if (!(parentId in parentToTargetIds)) parentToTargetIds[parentId] = []
191
194
 
@@ -1,6 +1,7 @@
1
1
  // @ts-check
2
2
 
3
3
  import {isPlainObject} from "is-plain-object"
4
+ import {scalarModelPrimaryKey} from "../../utils/model-primary-key.js"
4
5
 
5
6
  /**
6
7
  * QueryDataEntry type.
@@ -188,7 +189,7 @@ function resolveTargetModelClass(rootModelClass, chain) {
188
189
  export async function runQueryData({rootModelClass, rootModels, entries}) {
189
190
  if (rootModels.length === 0 || entries.length === 0) return
190
191
 
191
- const primaryKey = rootModelClass.primaryKey()
192
+ const primaryKey = scalarModelPrimaryKey(rootModelClass.primaryKey(), `queryData for ${rootModelClass.name}`)
192
193
  const rootIds = rootModels.map((model) => /** @type {string | number} */ (model.readColumn(primaryKey)))
193
194
  const sourceModel = rootModels[0]
194
195
  const preparedEntries = entries.map((entry, entryIndex) => prepareEntry({
@@ -35,6 +35,20 @@ export default class VelociousDatabaseQueryUpdateBase {
35
35
  return this.getOptions().quote(value)
36
36
  }
37
37
 
38
+ /**
39
+ * Formats one hash condition with SQL null semantics.
40
+ * @param {string} columnName - Column name.
41
+ * @param {ReturnType<typeof JSON.parse>} value - Condition value.
42
+ * @returns {string} - SQL condition.
43
+ */
44
+ formatCondition(columnName, value) {
45
+ const column = this.getOptions().quoteColumnName(columnName)
46
+
47
+ if (value === null) return `${column} IS NULL`
48
+
49
+ return `${column} = ${this.formatValue(value)}`
50
+ }
51
+
38
52
  toSql() {
39
53
  throw new Error("'toSql' wasn't implemented")
40
54
  }
@@ -1,5 +1,7 @@
1
1
  // @ts-check
2
2
 
3
+ import {scalarModelPrimaryKey} from "../../utils/model-primary-key.js"
4
+
3
5
  /**
4
6
  * WithCountEntry type.
5
7
  * @typedef {object} WithCountEntry
@@ -109,7 +111,7 @@ function entryFromName(name) {
109
111
  export async function runWithCount({models, modelClass, entries}) {
110
112
  if (models.length === 0 || entries.length === 0) return
111
113
 
112
- const primaryKey = modelClass.primaryKey()
114
+ const primaryKey = scalarModelPrimaryKey(modelClass.primaryKey(), `withCount for ${modelClass.name}`)
113
115
  const parentIds = models.map((model) => /** @type {string | number} */ (model.readColumn(primaryKey)))
114
116
  const sourceModel = models[0]
115
117
  const queryGroups = new Map()
@@ -1,5 +1,7 @@
1
1
  // @ts-check
2
2
 
3
+ import {scalarModelPrimaryKey, scalarModelPrimaryKeyValue} from "../../utils/model-primary-key.js"
4
+
3
5
  /** @file Registers gap-less positional list callbacks on a model class. */
4
6
 
5
7
  /**
@@ -8,6 +10,24 @@
8
10
  */
9
11
  const ACTS_AS_LIST_SHIFTING = Symbol("actsAsListShifting")
10
12
 
13
+ /**
14
+ * Returns the scalar primary-key column required by acts-as-list SQL.
15
+ * @param {typeof import("./index.js").default} modelClass - List model class.
16
+ * @returns {string} - Scalar primary-key column.
17
+ */
18
+ function actsAsListPrimaryKey(modelClass) {
19
+ return scalarModelPrimaryKey(modelClass.primaryKey(), `actsAsList for ${modelClass.name}`)
20
+ }
21
+
22
+ /**
23
+ * Returns the scalar record id required by acts-as-list SQL.
24
+ * @param {import("./index.js").default} record - List record.
25
+ * @returns {string | number} - Scalar record id.
26
+ */
27
+ function actsAsListRecordId(record) {
28
+ return scalarModelPrimaryKeyValue(record.id(), `actsAsList for ${record.getModelClass().name}`)
29
+ }
30
+
11
31
  /**
12
32
  * Runs set shifting flag.
13
33
  * @param {import("./index.js").default} record - Model instance.
@@ -111,7 +131,7 @@ export default function registerActsAsListCallbacks(modelClass, positionColumn,
111
131
  await moveOutOfWay({record, positionColumn, scope, scopeValue: oldScopeValue})
112
132
  setShiftingFlag(record, false)
113
133
  await shiftPositionsDown({record, positionColumn, scope, scopeValue: oldScopeValue, fromPosition: oldPosition + 1})
114
- await shiftPositionsUp({record, positionColumn, scope, scopeValue: newScopeValue, fromPosition: newPosition, excludeRecordId: record.id()})
134
+ await shiftPositionsUp({record, positionColumn, scope, scopeValue: newScopeValue, fromPosition: newPosition, excludeRecordId: actsAsListRecordId(record)})
115
135
  await placeMovedRecord({record, positionColumn, scope, scopeValue: newScopeValue, position: newPosition})
116
136
  return
117
137
  }
@@ -180,15 +200,17 @@ async function placeMovedRecord({record, positionColumn, scope, scopeValue, posi
180
200
  const preservedChanges = {...record._changes}
181
201
  const scopeColumnSql = connection.quoteColumn(scopeCol)
182
202
  const positionColumnSql = connection.quoteColumn(posCol)
183
- const primaryKeySql = connection.quoteColumn(modelClass.primaryKey())
203
+ const primaryKey = actsAsListPrimaryKey(modelClass)
204
+ const primaryKeySql = connection.quoteColumn(primaryKey)
205
+ const recordId = actsAsListRecordId(record)
184
206
 
185
207
  delete preservedChanges[scopeCol]
186
208
  delete preservedChanges[posCol]
187
209
 
188
210
  await connection.query(
189
- `UPDATE ${tableSql} SET ${scopeColumnSql} = ${connection.quote(scopeValue)}, ${positionColumnSql} = ${connection.quote(position)} WHERE ${primaryKeySql} = ${connection.quote(record.id())}`
211
+ `UPDATE ${tableSql} SET ${scopeColumnSql} = ${connection.quote(scopeValue)}, ${positionColumnSql} = ${connection.quote(position)} WHERE ${primaryKeySql} = ${connection.quote(recordId)}`
190
212
  )
191
- await record._reloadWithId(record.id())
213
+ await record._reloadWithId(recordId)
192
214
  record._changes = preservedChanges
193
215
  clearBelongsToChangeForScope(record)
194
216
  }
@@ -234,21 +256,22 @@ async function shiftPositionsUp({record, positionColumn, scope, fromPosition, to
234
256
  const positionColumnName = modelClass.getColumnNameForAttributeName(positionColumn)
235
257
  const positionColumnSql = connection.quoteColumn(positionColumnName)
236
258
  const scopeColumnSql = connection.quoteColumn(scopeColumnName)
237
- const primaryKeySql = connection.quoteColumn(modelClass.primaryKey())
259
+ const primaryKey = actsAsListPrimaryKey(modelClass)
260
+ const primaryKeySql = connection.quoteColumn(primaryKey)
238
261
  const tableSql = connection.quoteTable(tableName)
239
262
  const quotedScope = connection.quote(resolvedScopeValue)
240
263
 
241
264
  // Load rows in descending order so we bump the highest first
242
265
  let query = record
243
266
  .queryForModel(modelClass)
244
- .select(modelClass.primaryKey())
267
+ .select(primaryKey)
245
268
  .select(positionColumn)
246
269
  .where({[scopeColumnName]: resolvedScopeValue})
247
270
  .where(`${positionColumnSql} >= ${connection.quote(fromPosition)}`)
248
271
  .where(`${positionColumnSql} > 0`)
249
272
  .order(`${positionColumnSql} DESC`)
250
273
 
251
- const recordIdToExclude = excludeRecordId || (record.isPersisted() ? record.id() : null)
274
+ const recordIdToExclude = excludeRecordId || (record.isPersisted() ? actsAsListRecordId(record) : null)
252
275
 
253
276
  if (recordIdToExclude != null) {
254
277
  query = query.where(`${primaryKeySql} != ${connection.quote(recordIdToExclude)}`)
@@ -300,19 +323,20 @@ async function shiftPositionsDown({record, positionColumn, scope, fromPosition,
300
323
  const positionColumnName = modelClass.getColumnNameForAttributeName(positionColumn)
301
324
  const positionColumnSql = connection.quoteColumn(positionColumnName)
302
325
  const scopeColumnSql = connection.quoteColumn(scopeColumnName)
303
- const primaryKeySql = connection.quoteColumn(modelClass.primaryKey())
326
+ const primaryKey = actsAsListPrimaryKey(modelClass)
327
+ const primaryKeySql = connection.quoteColumn(primaryKey)
304
328
  const tableSql = connection.quoteTable(tableName)
305
329
  const quotedScope = connection.quote(resolvedScopeValue)
306
330
 
307
331
  // Load rows in ascending order so we shift the lowest gap first
308
332
  let query = record
309
333
  .queryForModel(modelClass)
310
- .select(modelClass.primaryKey())
334
+ .select(primaryKey)
311
335
  .select(positionColumn)
312
336
  .where({[scopeColumnName]: resolvedScopeValue})
313
337
  .where(`${positionColumnSql} >= ${connection.quote(fromPosition)}`)
314
338
  .where(`${positionColumnSql} > 0`)
315
- .where(`${primaryKeySql} != ${connection.quote(record.id())}`)
339
+ .where(`${primaryKeySql} != ${connection.quote(actsAsListRecordId(record))}`)
316
340
  .order({column: positionColumnName, direction: "ASC"})
317
341
 
318
342
  if (toPosition != null) {
@@ -399,7 +423,7 @@ function resolveScopeValue(record, scope) {
399
423
  const loaded = instanceRelationship.loaded()
400
424
 
401
425
  if (loaded && !Array.isArray(loaded) && typeof loaded.id === "function") {
402
- return loaded.id()
426
+ return scalarModelPrimaryKeyValue(loaded.id(), `actsAsList scope relationship for ${modelClass.name}`)
403
427
  }
404
428
  }
405
429
 
@@ -427,13 +451,13 @@ async function moveOutOfWay({record, positionColumn, scope, scopeValue}) {
427
451
  const positionColumnSql = connection.quoteColumn(modelClass.getColumnNameForAttributeName(positionColumn))
428
452
  const scopeColumnSql = connection.quoteColumn(modelClass.getColumnNameForAttributeName(scope))
429
453
  const tableSql = connection.quoteTable(tableName)
430
- const pkSql = connection.quoteColumn(modelClass.primaryKey())
454
+ const pkSql = connection.quoteColumn(actsAsListPrimaryKey(modelClass))
431
455
 
432
456
  setShiftingFlag(record, true)
433
457
 
434
458
  try {
435
459
  await connection.query(
436
- `UPDATE ${tableSql} SET ${positionColumnSql} = -${positionColumnSql} WHERE ${scopeColumnSql} = ${connection.quote(resolvedScopeValue)} AND ${pkSql} = ${connection.quote(record.id())}`
460
+ `UPDATE ${tableSql} SET ${positionColumnSql} = -${positionColumnSql} WHERE ${scopeColumnSql} = ${connection.quote(resolvedScopeValue)} AND ${pkSql} = ${connection.quote(actsAsListRecordId(record))}`
437
461
  )
438
462
  } finally {
439
463
  // Don't clear the flag here — the caller will do that after shifts