velocious 1.0.514 → 1.0.516

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 (255) hide show
  1. package/README.md +4 -3
  2. package/build/background-jobs/cron-expression.js +9 -9
  3. package/build/background-jobs/forked-runner-child.js +1 -1
  4. package/build/background-jobs/job-runner.js +21 -15
  5. package/build/background-jobs/json-socket.js +4 -0
  6. package/build/background-jobs/main.js +180 -18
  7. package/build/background-jobs/status-reporter.js +12 -5
  8. package/build/background-jobs/store.js +132 -50
  9. package/build/background-jobs/types.js +11 -3
  10. package/build/background-jobs/worker.js +9 -5
  11. package/build/beacon/client.js +13 -6
  12. package/build/configuration.js +54 -29
  13. package/build/controller.js +9 -3
  14. package/build/database/drivers/sqlite/base.js +1 -1
  15. package/build/database/drivers/sqlite/index.js +15 -14
  16. package/build/database/migrations-ledger.js +16 -16
  17. package/build/database/pool/async-tracked-multi-connection.js +16 -0
  18. package/build/database/pool/base.js +26 -0
  19. package/build/database/query/where-model-class-hash.js +2 -0
  20. package/build/database/query/with-count.js +1 -1
  21. package/build/database/record/acts-as-list.js +2 -2
  22. package/build/database/record/auditing.js +23 -6
  23. package/build/database/record/index.js +7 -7
  24. package/build/database/record/validators/uniqueness.js +3 -3
  25. package/build/database/tenants/data-copier.js +23 -23
  26. package/build/database/tenants/schema-cloner.js +27 -27
  27. package/build/environment-handlers/browser.js +0 -1
  28. package/build/environment-handlers/node/cli/commands/background-jobs-runner.js +2 -1
  29. package/build/environment-handlers/node/cli/commands/lint/relationships.js +2 -2
  30. package/build/environment-handlers/node.js +1 -1
  31. package/build/frontend-model-controller.js +26 -18
  32. package/build/frontend-model-resource/base-resource.js +4 -4
  33. package/build/frontend-models/base.js +5 -5
  34. package/build/frontend-models/query.js +12 -12
  35. package/build/http-server/client/index.js +61 -15
  36. package/build/http-server/client/request-runner.js +7 -1
  37. package/build/http-server/client/response.js +17 -1
  38. package/build/http-server/client/websocket-session.js +13 -13
  39. package/build/http-server/server-client.js +96 -0
  40. package/build/http-server/websocket-channel.js +12 -11
  41. package/build/http-server/websocket-connection.js +12 -11
  42. package/build/http-server/websocket-events-host.js +1 -1
  43. package/build/http-server/worker-handler/in-process.js +48 -9
  44. package/build/http-server/worker-handler/index.js +67 -7
  45. package/build/http-server/worker-handler/worker-thread.js +68 -0
  46. package/build/src/background-jobs/cron-expression.d.ts +2 -2
  47. package/build/src/background-jobs/cron-expression.js +10 -10
  48. package/build/src/background-jobs/forked-runner-child.js +2 -2
  49. package/build/src/background-jobs/job-runner.d.ts +5 -1
  50. package/build/src/background-jobs/job-runner.d.ts.map +1 -1
  51. package/build/src/background-jobs/job-runner.js +22 -16
  52. package/build/src/background-jobs/json-socket.d.ts +4 -0
  53. package/build/src/background-jobs/json-socket.d.ts.map +1 -1
  54. package/build/src/background-jobs/json-socket.js +5 -1
  55. package/build/src/background-jobs/main.d.ts +62 -10
  56. package/build/src/background-jobs/main.d.ts.map +1 -1
  57. package/build/src/background-jobs/main.js +171 -19
  58. package/build/src/background-jobs/status-reporter.d.ts +6 -2
  59. package/build/src/background-jobs/status-reporter.d.ts.map +1 -1
  60. package/build/src/background-jobs/status-reporter.js +13 -6
  61. package/build/src/background-jobs/store.d.ts +48 -25
  62. package/build/src/background-jobs/store.d.ts.map +1 -1
  63. package/build/src/background-jobs/store.js +123 -50
  64. package/build/src/background-jobs/types.d.ts +36 -3
  65. package/build/src/background-jobs/types.d.ts.map +1 -1
  66. package/build/src/background-jobs/types.js +12 -4
  67. package/build/src/background-jobs/worker.d.ts +7 -5
  68. package/build/src/background-jobs/worker.d.ts.map +1 -1
  69. package/build/src/background-jobs/worker.js +10 -6
  70. package/build/src/beacon/client.d.ts +6 -3
  71. package/build/src/beacon/client.d.ts.map +1 -1
  72. package/build/src/beacon/client.js +14 -7
  73. package/build/src/configuration-types.d.ts +1 -1
  74. package/build/src/configuration.d.ts +42 -29
  75. package/build/src/configuration.d.ts.map +1 -1
  76. package/build/src/configuration.js +52 -30
  77. package/build/src/controller.d.ts +3 -0
  78. package/build/src/controller.d.ts.map +1 -1
  79. package/build/src/controller.js +9 -4
  80. package/build/src/database/drivers/sqlite/base.js +2 -2
  81. package/build/src/database/drivers/sqlite/index.d.ts +7 -6
  82. package/build/src/database/drivers/sqlite/index.d.ts.map +1 -1
  83. package/build/src/database/drivers/sqlite/index.js +16 -15
  84. package/build/src/database/migrations-ledger.d.ts +16 -16
  85. package/build/src/database/migrations-ledger.js +17 -17
  86. package/build/src/database/pool/async-tracked-multi-connection.d.ts +0 -11
  87. package/build/src/database/pool/async-tracked-multi-connection.d.ts.map +1 -1
  88. package/build/src/database/pool/async-tracked-multi-connection.js +16 -1
  89. package/build/src/database/pool/base.d.ts +21 -0
  90. package/build/src/database/pool/base.d.ts.map +1 -1
  91. package/build/src/database/pool/base.js +24 -1
  92. package/build/src/database/query/where-model-class-hash.d.ts.map +1 -1
  93. package/build/src/database/query/where-model-class-hash.js +3 -1
  94. package/build/src/database/query/with-count.js +2 -2
  95. package/build/src/database/record/acts-as-list.js +3 -3
  96. package/build/src/database/record/auditing.d.ts +1 -1
  97. package/build/src/database/record/auditing.d.ts.map +1 -1
  98. package/build/src/database/record/auditing.js +24 -7
  99. package/build/src/database/record/index.d.ts +7 -7
  100. package/build/src/database/record/index.js +8 -8
  101. package/build/src/database/record/validators/uniqueness.d.ts +3 -3
  102. package/build/src/database/record/validators/uniqueness.js +4 -4
  103. package/build/src/database/tenants/data-copier.d.ts +18 -18
  104. package/build/src/database/tenants/data-copier.js +24 -24
  105. package/build/src/database/tenants/schema-cloner.d.ts +27 -27
  106. package/build/src/database/tenants/schema-cloner.js +28 -28
  107. package/build/src/environment-handlers/browser.d.ts +9 -0
  108. package/build/src/environment-handlers/browser.d.ts.map +1 -1
  109. package/build/src/environment-handlers/browser.js +1 -2
  110. package/build/src/environment-handlers/node/cli/commands/background-jobs-runner.d.ts.map +1 -1
  111. package/build/src/environment-handlers/node/cli/commands/background-jobs-runner.js +3 -2
  112. package/build/src/environment-handlers/node/cli/commands/lint/relationships.js +3 -3
  113. package/build/src/environment-handlers/node.js +2 -2
  114. package/build/src/frontend-model-controller.d.ts +9 -8
  115. package/build/src/frontend-model-controller.d.ts.map +1 -1
  116. package/build/src/frontend-model-controller.js +26 -19
  117. package/build/src/frontend-model-resource/base-resource.js +5 -5
  118. package/build/src/frontend-models/base.d.ts +4 -4
  119. package/build/src/frontend-models/base.d.ts.map +1 -1
  120. package/build/src/frontend-models/base.js +6 -6
  121. package/build/src/frontend-models/query.d.ts +7 -7
  122. package/build/src/frontend-models/query.js +13 -13
  123. package/build/src/http-server/client/index.d.ts +23 -1
  124. package/build/src/http-server/client/index.d.ts.map +1 -1
  125. package/build/src/http-server/client/index.js +55 -17
  126. package/build/src/http-server/client/request-runner.d.ts.map +1 -1
  127. package/build/src/http-server/client/request-runner.js +8 -2
  128. package/build/src/http-server/client/response.d.ts +11 -1
  129. package/build/src/http-server/client/response.d.ts.map +1 -1
  130. package/build/src/http-server/client/response.js +16 -2
  131. package/build/src/http-server/client/websocket-session.d.ts +13 -13
  132. package/build/src/http-server/client/websocket-session.js +14 -14
  133. package/build/src/http-server/server-client.d.ts +13 -0
  134. package/build/src/http-server/server-client.d.ts.map +1 -1
  135. package/build/src/http-server/server-client.js +94 -1
  136. package/build/src/http-server/websocket-channel.d.ts +12 -11
  137. package/build/src/http-server/websocket-channel.d.ts.map +1 -1
  138. package/build/src/http-server/websocket-channel.js +13 -12
  139. package/build/src/http-server/websocket-connection.d.ts +12 -11
  140. package/build/src/http-server/websocket-connection.d.ts.map +1 -1
  141. package/build/src/http-server/websocket-connection.js +13 -12
  142. package/build/src/http-server/websocket-events-host.d.ts +1 -1
  143. package/build/src/http-server/websocket-events-host.js +2 -2
  144. package/build/src/http-server/worker-handler/in-process.d.ts +2 -0
  145. package/build/src/http-server/worker-handler/in-process.d.ts.map +1 -1
  146. package/build/src/http-server/worker-handler/in-process.js +42 -11
  147. package/build/src/http-server/worker-handler/index.d.ts +21 -0
  148. package/build/src/http-server/worker-handler/index.d.ts.map +1 -1
  149. package/build/src/http-server/worker-handler/index.js +61 -7
  150. package/build/src/http-server/worker-handler/worker-thread.d.ts +30 -0
  151. package/build/src/http-server/worker-handler/worker-thread.d.ts.map +1 -1
  152. package/build/src/http-server/worker-handler/worker-thread.js +61 -1
  153. package/build/src/sync/device-identity.d.ts +2 -2
  154. package/build/src/sync/device-identity.d.ts.map +1 -1
  155. package/build/src/sync/device-identity.js +4 -4
  156. package/build/src/sync/local-mutation-log.d.ts +0 -1
  157. package/build/src/sync/local-mutation-log.d.ts.map +1 -1
  158. package/build/src/sync/local-mutation-log.js +1 -2
  159. package/build/src/sync/query-scope.d.ts +5 -0
  160. package/build/src/sync/query-scope.d.ts.map +1 -1
  161. package/build/src/sync/query-scope.js +7 -2
  162. package/build/src/sync/sync-client-types.d.ts +2 -2
  163. package/build/src/sync/sync-client-types.d.ts.map +1 -1
  164. package/build/src/sync/sync-client-types.js +1 -1
  165. package/build/src/sync/sync-client.d.ts +17 -2
  166. package/build/src/sync/sync-client.d.ts.map +1 -1
  167. package/build/src/sync/sync-client.js +28 -14
  168. package/build/src/sync/sync-realtime-bridge.d.ts +6 -2
  169. package/build/src/sync/sync-realtime-bridge.d.ts.map +1 -1
  170. package/build/src/sync/sync-realtime-bridge.js +14 -3
  171. package/build/src/sync/sync-resource-base.d.ts +14 -3
  172. package/build/src/sync/sync-resource-base.d.ts.map +1 -1
  173. package/build/src/sync/sync-resource-base.js +20 -4
  174. package/build/src/sync/sync-scope-store.d.ts +3 -3
  175. package/build/src/sync/sync-scope-store.d.ts.map +1 -1
  176. package/build/src/sync/sync-scope-store.js +7 -5
  177. package/build/src/sync/sync-websocket-channel.d.ts +10 -0
  178. package/build/src/sync/sync-websocket-channel.d.ts.map +1 -1
  179. package/build/src/sync/sync-websocket-channel.js +20 -3
  180. package/build/src/tenants/tenant-iterator.d.ts +8 -8
  181. package/build/src/tenants/tenant-iterator.js +9 -9
  182. package/build/src/tenants/tenant.d.ts +6 -6
  183. package/build/src/tenants/tenant.js +7 -7
  184. package/build/src/testing/test-runner.d.ts +16 -0
  185. package/build/src/testing/test-runner.d.ts.map +1 -1
  186. package/build/src/testing/test-runner.js +79 -19
  187. package/build/sync/device-identity.js +3 -3
  188. package/build/sync/local-mutation-log.js +0 -1
  189. package/build/sync/query-scope.js +6 -1
  190. package/build/sync/sync-client-types.js +1 -1
  191. package/build/sync/sync-client.js +28 -15
  192. package/build/sync/sync-realtime-bridge.js +13 -2
  193. package/build/sync/sync-resource-base.js +20 -3
  194. package/build/sync/sync-scope-store.js +6 -4
  195. package/build/sync/sync-websocket-channel.js +18 -2
  196. package/build/tenants/tenant-iterator.js +8 -8
  197. package/build/tenants/tenant.js +6 -6
  198. package/build/testing/test-runner.js +86 -20
  199. package/package.json +2 -2
  200. package/src/background-jobs/cron-expression.js +9 -9
  201. package/src/background-jobs/forked-runner-child.js +1 -1
  202. package/src/background-jobs/job-runner.js +21 -15
  203. package/src/background-jobs/json-socket.js +4 -0
  204. package/src/background-jobs/main.js +180 -18
  205. package/src/background-jobs/status-reporter.js +12 -5
  206. package/src/background-jobs/store.js +132 -50
  207. package/src/background-jobs/types.js +11 -3
  208. package/src/background-jobs/worker.js +9 -5
  209. package/src/beacon/client.js +13 -6
  210. package/src/configuration.js +54 -29
  211. package/src/controller.js +9 -3
  212. package/src/database/drivers/sqlite/base.js +1 -1
  213. package/src/database/drivers/sqlite/index.js +15 -14
  214. package/src/database/migrations-ledger.js +16 -16
  215. package/src/database/pool/async-tracked-multi-connection.js +16 -0
  216. package/src/database/pool/base.js +26 -0
  217. package/src/database/query/where-model-class-hash.js +2 -0
  218. package/src/database/query/with-count.js +1 -1
  219. package/src/database/record/acts-as-list.js +2 -2
  220. package/src/database/record/auditing.js +23 -6
  221. package/src/database/record/index.js +7 -7
  222. package/src/database/record/validators/uniqueness.js +3 -3
  223. package/src/database/tenants/data-copier.js +23 -23
  224. package/src/database/tenants/schema-cloner.js +27 -27
  225. package/src/environment-handlers/browser.js +0 -1
  226. package/src/environment-handlers/node/cli/commands/background-jobs-runner.js +2 -1
  227. package/src/environment-handlers/node/cli/commands/lint/relationships.js +2 -2
  228. package/src/environment-handlers/node.js +1 -1
  229. package/src/frontend-model-controller.js +26 -18
  230. package/src/frontend-model-resource/base-resource.js +4 -4
  231. package/src/frontend-models/base.js +5 -5
  232. package/src/frontend-models/query.js +12 -12
  233. package/src/http-server/client/index.js +61 -15
  234. package/src/http-server/client/request-runner.js +7 -1
  235. package/src/http-server/client/response.js +17 -1
  236. package/src/http-server/client/websocket-session.js +13 -13
  237. package/src/http-server/server-client.js +96 -0
  238. package/src/http-server/websocket-channel.js +12 -11
  239. package/src/http-server/websocket-connection.js +12 -11
  240. package/src/http-server/websocket-events-host.js +1 -1
  241. package/src/http-server/worker-handler/in-process.js +48 -9
  242. package/src/http-server/worker-handler/index.js +67 -7
  243. package/src/http-server/worker-handler/worker-thread.js +68 -0
  244. package/src/sync/device-identity.js +3 -3
  245. package/src/sync/local-mutation-log.js +0 -1
  246. package/src/sync/query-scope.js +6 -1
  247. package/src/sync/sync-client-types.js +1 -1
  248. package/src/sync/sync-client.js +28 -15
  249. package/src/sync/sync-realtime-bridge.js +13 -2
  250. package/src/sync/sync-resource-base.js +20 -3
  251. package/src/sync/sync-scope-store.js +6 -4
  252. package/src/sync/sync-websocket-channel.js +18 -2
  253. package/src/tenants/tenant-iterator.js +8 -8
  254. package/src/tenants/tenant.js +6 -6
  255. package/src/testing/test-runner.js +86 -20
@@ -2056,7 +2056,8 @@ export default class VelociousConfiguration {
2056
2056
 
2057
2057
  /**
2058
2058
  * Runs get translator.
2059
- * @returns {(msgID: string, args?: Record<string, ?>) => string} */
2059
+ * @returns {(msgID: string, args?: Record<string, ?>) => string} - The configured translator.
2060
+ */
2060
2061
  getTranslator() {
2061
2062
  if (this._translator) return this._translator
2062
2063
 
@@ -2156,8 +2157,8 @@ export default class VelociousConfiguration {
2156
2157
  * Registers a `VelociousWebsocketConnection` subclass under a name.
2157
2158
  * Clients that send `{type: "connection-open", connectionType: name}`
2158
2159
  * will have this class instantiated for their connection.
2159
- * @param {string} name
2160
- * @param {typeof import("./http-server/websocket-connection.js").default} ConnectionClass
2160
+ * @param {string} name - Client-facing connection type name.
2161
+ * @param {typeof import("./http-server/websocket-connection.js").default} ConnectionClass - Websocket connection class.
2161
2162
  * @returns {void}
2162
2163
  */
2163
2164
  registerWebsocketConnection(name, ConnectionClass) {
@@ -2168,8 +2169,8 @@ export default class VelociousConfiguration {
2168
2169
 
2169
2170
  /**
2170
2171
  * Runs get websocket connection class.
2171
- * @param {string} name
2172
- * @returns {typeof import("./http-server/websocket-connection.js").default | undefined}
2172
+ * @param {string} name - Connection type name to look up.
2173
+ * @returns {typeof import("./http-server/websocket-connection.js").default | undefined} - Registered websocket connection class.
2173
2174
  */
2174
2175
  getWebsocketConnectionClass(name) {
2175
2176
  return this._websocketConnectionClasses.get(name)
@@ -2178,8 +2179,8 @@ export default class VelociousConfiguration {
2178
2179
  /**
2179
2180
  * Registers a `VelociousWebsocketChannel` subclass under a name.
2180
2181
  * Clients subscribe via `{type: "channel-subscribe", channelType: name, ...}`.
2181
- * @param {string} name
2182
- * @param {typeof import("./http-server/websocket-channel.js").default} ChannelClass
2182
+ * @param {string} name - Client-facing channel type name.
2183
+ * @param {typeof import("./http-server/websocket-channel.js").default} ChannelClass - Websocket channel class.
2183
2184
  * @returns {void}
2184
2185
  */
2185
2186
  registerWebsocketChannel(name, ChannelClass) {
@@ -2190,8 +2191,8 @@ export default class VelociousConfiguration {
2190
2191
 
2191
2192
  /**
2192
2193
  * Runs get websocket channel class.
2193
- * @param {string} name
2194
- * @returns {typeof import("./http-server/websocket-channel.js").default | undefined}
2194
+ * @param {string} name - Channel type name to look up.
2195
+ * @returns {typeof import("./http-server/websocket-channel.js").default | undefined} - Registered websocket channel class.
2195
2196
  */
2196
2197
  getWebsocketChannelClass(name) {
2197
2198
  return this._websocketChannelClasses.get(name)
@@ -2201,8 +2202,8 @@ export default class VelociousConfiguration {
2201
2202
  * Tracks a live channel subscription in the global routing registry.
2202
2203
  * Called by the session when `canSubscribe()` resolves truthy; the
2203
2204
  * session calls `_unregisterWebsocketChannelSubscription` on unsubscribe.
2204
- * @param {string} name
2205
- * @param {import("./http-server/websocket-channel.js").default} subscription
2205
+ * @param {string} name - Channel type used as the routing key.
2206
+ * @param {import("./http-server/websocket-channel.js").default} subscription - Live channel subscription to register.
2206
2207
  * @returns {void}
2207
2208
  */
2208
2209
  _registerWebsocketChannelSubscription(name, subscription) {
@@ -2218,8 +2219,8 @@ export default class VelociousConfiguration {
2218
2219
 
2219
2220
  /**
2220
2221
  * Runs unregister websocket channel subscription.
2221
- * @param {string} name
2222
- * @param {import("./http-server/websocket-channel.js").default} subscription
2222
+ * @param {string} name - Channel type used as the routing key.
2223
+ * @param {import("./http-server/websocket-channel.js").default} subscription - Live channel subscription to remove.
2223
2224
  * @returns {void}
2224
2225
  */
2225
2226
  _unregisterWebsocketChannelSubscription(name, subscription) {
@@ -2262,7 +2263,7 @@ export default class VelociousConfiguration {
2262
2263
  * connection message / channel dispatch. The wrapper receives the
2263
2264
  * session and a `next` callback; it must call `next()` to run the
2264
2265
  * handler. Use it to set up AsyncLocalStorage per request.
2265
- * @param {((session: import("./http-server/client/websocket-session.js").default, next: () => Promise<void>) => Promise<void>) | null} wrapper
2266
+ * @param {((session: import("./http-server/client/websocket-session.js").default, next: () => Promise<void>) => Promise<void>) | null} wrapper - Per-message session-context wrapper, or null to disable it.
2266
2267
  * @returns {void}
2267
2268
  */
2268
2269
  setWebsocketAroundRequest(wrapper) {
@@ -2271,7 +2272,7 @@ export default class VelociousConfiguration {
2271
2272
 
2272
2273
  /**
2273
2274
  * Runs get websocket around request.
2274
- * @returns {((session: import("./http-server/client/websocket-session.js").default, next: () => Promise<void>) => Promise<void>) | null}
2275
+ * @returns {((session: import("./http-server/client/websocket-session.js").default, next: () => Promise<void>) => Promise<void>) | null} - Websocket session wrapper.
2275
2276
  */
2276
2277
  getWebsocketAroundRequest() {
2277
2278
  return this._websocketAroundRequest
@@ -2282,7 +2283,7 @@ export default class VelociousConfiguration {
2282
2283
  * HTTP and WS-borne. Receives `{request, response, next}` and must
2283
2284
  * call `next()` to run the action. Use it for per-request context
2284
2285
  * like AsyncLocalStorage-scoped locale or tracing.
2285
- * @param {((context: {request: import("./http-server/client/request.js").default | import("./http-server/client/websocket-request.js").default, response: import("./http-server/client/response.js").default, next: () => Promise<void>}) => Promise<void>) | null} wrapper
2286
+ * @param {((context: {request: import("./http-server/client/request.js").default | import("./http-server/client/websocket-request.js").default, response: import("./http-server/client/response.js").default, next: () => Promise<void>}) => Promise<void>) | null} wrapper - Per-action request-context wrapper, or null to disable it.
2286
2287
  * @returns {void}
2287
2288
  */
2288
2289
  setAroundAction(wrapper) {
@@ -2291,7 +2292,7 @@ export default class VelociousConfiguration {
2291
2292
 
2292
2293
  /**
2293
2294
  * Runs get around action.
2294
- * @returns {((context: {request: import("./http-server/client/request.js").default | import("./http-server/client/websocket-request.js").default, response: import("./http-server/client/response.js").default, next: () => Promise<void>}) => Promise<void>) | null}
2295
+ * @returns {((context: {request: import("./http-server/client/request.js").default | import("./http-server/client/websocket-request.js").default, response: import("./http-server/client/response.js").default, next: () => Promise<void>}) => Promise<void>) | null} - HTTP request wrapper.
2295
2296
  */
2296
2297
  getAroundAction() {
2297
2298
  return this._aroundAction
@@ -2310,7 +2311,7 @@ export default class VelociousConfiguration {
2310
2311
  *
2311
2312
  * Return `null`/`undefined` to mean "no identity" — resumes still
2312
2313
  * succeed if pause and resume both resolve to a nullish value.
2313
- * @param {((session: import("./http-server/client/websocket-session.js").default) => ? | Promise<?>) | null} resolver
2314
+ * @param {((session: import("./http-server/client/websocket-session.js").default) => ? | Promise<?>) | null} resolver - Authenticated-caller identity resolver, or null to disable identity checks.
2314
2315
  * @returns {void}
2315
2316
  */
2316
2317
  setWebsocketSessionIdentityResolver(resolver) {
@@ -2319,14 +2320,15 @@ export default class VelociousConfiguration {
2319
2320
 
2320
2321
  /**
2321
2322
  * Runs get websocket session identity resolver.
2322
- * @returns {((session: import("./http-server/client/websocket-session.js").default) => ? | Promise<?>) | null} */
2323
+ * @returns {((session: import("./http-server/client/websocket-session.js").default) => ? | Promise<?>) | null} - The configured identity resolver.
2324
+ */
2323
2325
  getWebsocketSessionIdentityResolver() {
2324
2326
  return this._websocketSessionIdentityResolver
2325
2327
  }
2326
2328
 
2327
2329
  /**
2328
2330
  * Runs set websocket session grace seconds.
2329
- * @param {number} seconds
2331
+ * @param {number} seconds - Grace period before a paused session expires.
2330
2332
  * @returns {void}
2331
2333
  */
2332
2334
  setWebsocketSessionGraceSeconds(seconds) {
@@ -2336,7 +2338,7 @@ export default class VelociousConfiguration {
2336
2338
 
2337
2339
  /**
2338
2340
  * Runs set websocket session heartbeat seconds.
2339
- * @param {number} seconds
2341
+ * @param {number} seconds - Heartbeat interval, with zero disabling reaping.
2340
2342
  * @returns {void}
2341
2343
  */
2342
2344
  setWebsocketSessionHeartbeatSeconds(seconds) {
@@ -2349,7 +2351,7 @@ export default class VelociousConfiguration {
2349
2351
  * timer. When the timer fires, the session's permanent teardown
2350
2352
  * hook is invoked. Called by the session itself from `_handleClose`
2351
2353
  * when there is resumable state (live Connections / Channel subs).
2352
- * @param {import("./http-server/client/websocket-session.js").default} session
2354
+ * @param {import("./http-server/client/websocket-session.js").default} session - Resumable session to retain during its grace period.
2353
2355
  * @returns {void}
2354
2356
  */
2355
2357
  _pauseWebsocketSession(session) {
@@ -2372,8 +2374,8 @@ export default class VelociousConfiguration {
2372
2374
  /**
2373
2375
  * Looks up a paused session by id (does NOT remove it — caller is
2374
2376
  * expected to call `_resumeWebsocketSession` to complete the handoff).
2375
- * @param {string} sessionId
2376
- * @returns {import("./http-server/client/websocket-session.js").default | null}
2377
+ * @param {string} sessionId - Paused session identifier to look up.
2378
+ * @returns {import("./http-server/client/websocket-session.js").default | null} - Paused session with the requested identifier, if present.
2377
2379
  */
2378
2380
  _findPausedWebsocketSession(sessionId) {
2379
2381
  return this._pausedWebsocketSessions.get(sessionId)?.session || null
@@ -2383,7 +2385,7 @@ export default class VelociousConfiguration {
2383
2385
  * Removes a paused session from the registry and cancels its grace
2384
2386
  * timer. Called on successful resume handoff and on explicit
2385
2387
  * expiry.
2386
- * @param {string} sessionId
2388
+ * @param {string} sessionId - Paused session identifier to remove and cancel.
2387
2389
  * @returns {void}
2388
2390
  */
2389
2391
  _clearPausedWebsocketSession(sessionId) {
@@ -2398,7 +2400,7 @@ export default class VelociousConfiguration {
2398
2400
  /**
2399
2401
  * Grace-timer callback. Calls the session's permanent-teardown
2400
2402
  * hook and drops it from the registry.
2401
- * @param {string} sessionId
2403
+ * @param {string} sessionId - Paused session identifier whose grace period expired.
2402
2404
  * @returns {void}
2403
2405
  */
2404
2406
  _expireWebsocketSession(sessionId) {
@@ -2416,9 +2418,9 @@ export default class VelociousConfiguration {
2416
2418
 
2417
2419
  /**
2418
2420
  * Runs broadcast to channel.
2419
- * @param {string} name
2420
- * @param {Record<string, ?>} broadcastParams
2421
- * @param {?} body
2421
+ * @param {string} name - Channel type receiving the broadcast.
2422
+ * @param {Record<string, ?>} broadcastParams - Values used to match eligible subscriptions.
2423
+ * @param {?} body - Broadcast payload delivered to matching subscriptions.
2422
2424
  * @returns {void}
2423
2425
  */
2424
2426
  broadcastToChannel(name, broadcastParams, body) {
@@ -2830,6 +2832,29 @@ export default class VelociousConfiguration {
2830
2832
  return runCallback()
2831
2833
  }
2832
2834
 
2835
+ /**
2836
+ * Runs a callback inside every pool's test shared connection context (a no-op for
2837
+ * pools without one). In-process request handling is wrapped in this so a request
2838
+ * runs on the same connection — and open transaction — as the test that issued it,
2839
+ * letting request specs clean up by rolling back instead of truncating. Outside
2840
+ * tests no shared connection is set, so this just runs the callback.
2841
+ * @template T
2842
+ * @param {() => T} callback - Callback to run inside the shared connection contexts.
2843
+ * @returns {T} - Callback result.
2844
+ */
2845
+ runWithTestSharedConnectionContexts(callback) {
2846
+ let runCallback = callback
2847
+
2848
+ for (const pool of Object.values(this.databasePools)) {
2849
+ if (!pool) continue
2850
+ const previousRunCallback = runCallback
2851
+
2852
+ runCallback = () => pool.runWithTestSharedConnection(previousRunCallback)
2853
+ }
2854
+
2855
+ return runCallback()
2856
+ }
2857
+
2833
2858
  /**
2834
2859
  * Runs is missing current connection error.
2835
2860
  * @param {?} error - Error thrown while looking up the current connection.
package/src/controller.js CHANGED
@@ -169,7 +169,7 @@ export default class VelociousController {
169
169
  const beforeActions = currentControllerClass._beforeActions
170
170
 
171
171
  if (beforeActions) {
172
- const controllerPrototype = /** @type {Record<string, Function | undefined>} */ (/** @type {?} */ (currentControllerClass.prototype))
172
+ const controllerPrototype = /** @type {Record<string, ((...args: Array<?>) => ?) | undefined>} */ (/** @type {?} */ (currentControllerClass.prototype))
173
173
 
174
174
  for (const beforeActionName of beforeActions) {
175
175
  const beforeAction = controllerPrototype[beforeActionName]
@@ -266,6 +266,7 @@ export default class VelociousController {
266
266
  /**
267
267
  * Runs render json arg.
268
268
  * @param {object} json - JSON payload.
269
+ * @returns {void} - Sets the response JSON payload.
269
270
  */
270
271
  renderJsonArg(json) {
271
272
  return this._measureViewRender(() => {
@@ -339,11 +340,12 @@ export default class VelociousController {
339
340
  * @param {object} [args] - Options object.
340
341
  * @param {string} [args.contentType] - Content type.
341
342
  * @param {number | string} [args.status] - Status.
343
+ * @param {(result: "completed" | "aborted") => void | Promise<void>} [args.onFinished] - Called once after file delivery completes or aborts.
342
344
  * @returns {void} - No return value.
343
345
  */
344
346
  sendFile(filePath, args = {}) {
345
347
  this._measureViewRender(() => {
346
- const {contentType, status, ...restArgs} = args
348
+ const {contentType, onFinished, status, ...restArgs} = args
347
349
 
348
350
  restArgsError(restArgs)
349
351
 
@@ -351,6 +353,10 @@ export default class VelociousController {
351
353
  throw new Error(`Expected file path to be a non-empty string, got: ${String(filePath)}`)
352
354
  }
353
355
 
356
+ if (onFinished !== undefined && typeof onFinished !== "function") {
357
+ throw new Error(`Expected onFinished to be a function, got: ${typeof onFinished}`)
358
+ }
359
+
354
360
  const detectedContentType = contentType || this.sendFileContentType(filePath)
355
361
 
356
362
  if (detectedContentType) {
@@ -361,7 +367,7 @@ export default class VelociousController {
361
367
  this._response.setStatus(status)
362
368
  }
363
369
 
364
- this._response.setFilePath(filePath)
370
+ this._response.setFilePath(filePath, onFinished || null)
365
371
  })
366
372
  }
367
373
 
@@ -401,7 +401,7 @@ export default class VelociousDatabaseDriversSqliteBase extends Base {
401
401
  const waiters = state.waitersByName.get(name) || []
402
402
  /**
403
403
  * Timeout handle.
404
- * @type {NodeJS.Timeout | null} */
404
+ * @type {ReturnType<typeof setTimeout> | null} */
405
405
  let timeoutHandle = null
406
406
  /**
407
407
  * Remove and resolve.
@@ -93,7 +93,7 @@ export default class VelociousDatabaseDriversSqliteNode extends Base {
93
93
  * (create and remove) per critical section.
94
94
  * @param {string} name - Lock name.
95
95
  * @param {{timeoutMs?: number | null}} [args] - Optional timeout in milliseconds; `null`, `undefined`, or negative blocks forever.
96
- * @returns {Promise<boolean>}
96
+ * @returns {Promise<boolean>} - Whether the advisory lock was acquired.
97
97
  */
98
98
  async acquireAdvisoryLock(name, {timeoutMs} = {}) {
99
99
  const deadline = typeof timeoutMs === "number" && timeoutMs >= 0 ? Date.now() + timeoutMs : null
@@ -121,7 +121,7 @@ export default class VelociousDatabaseDriversSqliteNode extends Base {
121
121
  /**
122
122
  * Runs try acquire advisory lock.
123
123
  * @param {string} name - Lock name.
124
- * @returns {Promise<boolean>}
124
+ * @returns {Promise<boolean>} - Whether the advisory lock was acquired immediately.
125
125
  */
126
126
  async tryAcquireAdvisoryLock(name) {
127
127
  const inProcessAcquired = await super.tryAcquireAdvisoryLock(name)
@@ -151,7 +151,7 @@ export default class VelociousDatabaseDriversSqliteNode extends Base {
151
151
  * and the filesystem state is left alone so we never delete somebody
152
152
  * else's lock directory.
153
153
  * @param {string} name - Lock name.
154
- * @returns {Promise<boolean>}
154
+ * @returns {Promise<boolean>} - Whether the advisory lock was released.
155
155
  */
156
156
  async releaseAdvisoryLock(name) {
157
157
  const inProcessReleased = await super.releaseAdvisoryLock(name)
@@ -166,7 +166,7 @@ export default class VelociousDatabaseDriversSqliteNode extends Base {
166
166
  /**
167
167
  * Runs is advisory lock held.
168
168
  * @param {string} name - Lock name.
169
- * @returns {Promise<boolean>}
169
+ * @returns {Promise<boolean>} - Whether the advisory lock is held.
170
170
  */
171
171
  async isAdvisoryLockHeld(name) {
172
172
  if (await super.isAdvisoryLockHeld(name)) return true
@@ -176,7 +176,8 @@ export default class VelociousDatabaseDriversSqliteNode extends Base {
176
176
 
177
177
  /**
178
178
  * Runs resolve advisory lock directory.
179
- * @returns {string} */
179
+ * @returns {string} - The advisory-lock directory.
180
+ */
180
181
  _resolveAdvisoryLockDirectory() {
181
182
  if (!this._advisoryLockDirectory) {
182
183
  // Fall back to deriving the directory for callers that invoked
@@ -192,7 +193,7 @@ export default class VelociousDatabaseDriversSqliteNode extends Base {
192
193
  /**
193
194
  * Runs advisory lock path.
194
195
  * @param {string} name - Lock name.
195
- * @returns {string}
196
+ * @returns {string} - Filesystem path for the advisory lock.
196
197
  */
197
198
  _advisoryLockPath(name) {
198
199
  const hash = createHash("sha256").update(name).digest("hex").slice(0, 16)
@@ -228,7 +229,7 @@ export default class VelociousDatabaseDriversSqliteNode extends Base {
228
229
  * Runs acquire advisory lock file.
229
230
  * @param {string} name - Lock name.
230
231
  * @param {{timeoutMs?: number | null}} args - Timeout args.
231
- * @returns {Promise<boolean>}
232
+ * @returns {Promise<boolean>} - Whether the advisory-lock file was acquired.
232
233
  */
233
234
  async _acquireAdvisoryLockFile(name, {timeoutMs}) {
234
235
  await this._ensureAdvisoryLockDirectory()
@@ -247,7 +248,7 @@ export default class VelociousDatabaseDriversSqliteNode extends Base {
247
248
 
248
249
  return true
249
250
  } catch (error) {
250
- if (/** @type {NodeJS.ErrnoException} */ (error)?.code !== "EEXIST") throw error
251
+ if (/** @type {Error & {code?: string}} */ (error)?.code !== "EEXIST") throw error
251
252
 
252
253
  if (await this._isAdvisoryLockStale(lockPath)) {
253
254
  await fs.rm(lockPath, {recursive: true, force: true})
@@ -270,7 +271,7 @@ export default class VelociousDatabaseDriversSqliteNode extends Base {
270
271
  /**
271
272
  * Runs try acquire advisory lock file.
272
273
  * @param {string} name - Lock name.
273
- * @returns {Promise<boolean>}
274
+ * @returns {Promise<boolean>} - Whether the advisory-lock file was acquired immediately.
274
275
  */
275
276
  async _tryAcquireAdvisoryLockFile(name) {
276
277
  await this._ensureAdvisoryLockDirectory()
@@ -283,7 +284,7 @@ export default class VelociousDatabaseDriversSqliteNode extends Base {
283
284
 
284
285
  return true
285
286
  } catch (error) {
286
- if (/** @type {NodeJS.ErrnoException} */ (error)?.code !== "EEXIST") throw error
287
+ if (/** @type {Error & {code?: string}} */ (error)?.code !== "EEXIST") throw error
287
288
 
288
289
  if (await this._isAdvisoryLockStale(lockPath)) {
289
290
  await fs.rm(lockPath, {recursive: true, force: true})
@@ -294,7 +295,7 @@ export default class VelociousDatabaseDriversSqliteNode extends Base {
294
295
 
295
296
  return true
296
297
  } catch (retryError) {
297
- if (/** @type {NodeJS.ErrnoException} */ (retryError)?.code === "EEXIST") return false
298
+ if (/** @type {Error & {code?: string}} */ (retryError)?.code === "EEXIST") return false
298
299
 
299
300
  throw retryError
300
301
  }
@@ -324,7 +325,7 @@ export default class VelociousDatabaseDriversSqliteNode extends Base {
324
325
  /**
325
326
  * Runs is advisory lock file held.
326
327
  * @param {string} name - Lock name.
327
- * @returns {Promise<boolean>}
328
+ * @returns {Promise<boolean>} - Whether the advisory-lock file exists and is active.
328
329
  */
329
330
  async _isAdvisoryLockFileHeld(name) {
330
331
  const lockPath = this._advisoryLockPath(name)
@@ -345,7 +346,7 @@ export default class VelociousDatabaseDriversSqliteNode extends Base {
345
346
  * probe a PID on another machine; operators in that situation should
346
347
  * remove stale lock directories by hand if they linger.
347
348
  * @param {string} lockPath - Absolute path of the lock directory.
348
- * @returns {Promise<boolean>}
349
+ * @returns {Promise<boolean>} - Whether the advisory-lock file is stale.
349
350
  */
350
351
  async _isAdvisoryLockStale(lockPath) {
351
352
  /**
@@ -382,7 +383,7 @@ export default class VelociousDatabaseDriversSqliteNode extends Base {
382
383
 
383
384
  return false
384
385
  } catch (error) {
385
- return /** @type {NodeJS.ErrnoException} */ (error)?.code === "ESRCH"
386
+ return /** @type {Error & {code?: string}} */ (error)?.code === "ESRCH"
386
387
  }
387
388
  }
388
389
  }
@@ -18,7 +18,7 @@ const TABLE_NAME = "schema_migrations"
18
18
  export default class MigrationsLedger {
19
19
  /**
20
20
  * The ledger table name.
21
- * @returns {string}
21
+ * @returns {string} - Ledger table name.
22
22
  */
23
23
  static tableName() {
24
24
  return TABLE_NAME
@@ -26,8 +26,8 @@ export default class MigrationsLedger {
26
26
 
27
27
  /**
28
28
  * Whether the ledger table exists on the given database.
29
- * @param {import("./drivers/base.js").default} db
30
- * @returns {Promise<boolean>}
29
+ * @param {import("./drivers/base.js").default} db - Database whose migration ledger is inspected.
30
+ * @returns {Promise<boolean>} - Whether the ledger table exists.
31
31
  */
32
32
  static async tableExists(db) {
33
33
  const table = await db.getTableByName(TABLE_NAME, {throwError: false})
@@ -38,7 +38,7 @@ export default class MigrationsLedger {
38
38
  /**
39
39
  * Creates the ledger table if it does not exist. This is the single definition of
40
40
  * the `schema_migrations` table shape.
41
- * @param {import("./drivers/base.js").default} db
41
+ * @param {import("./drivers/base.js").default} db - Database that should contain the ledger table.
42
42
  * @returns {Promise<void>}
43
43
  */
44
44
  static async ensureTable(db) {
@@ -57,8 +57,8 @@ export default class MigrationsLedger {
57
57
 
58
58
  /**
59
59
  * Every applied migration version recorded in the ledger.
60
- * @param {import("./drivers/base.js").default} db
61
- * @returns {Promise<string[]>}
60
+ * @param {import("./drivers/base.js").default} db - Database whose applied versions are loaded.
61
+ * @returns {Promise<string[]>} - Applied migration versions.
62
62
  */
63
63
  static async appliedVersions(db) {
64
64
  const rows = await db.select(TABLE_NAME)
@@ -68,9 +68,9 @@ export default class MigrationsLedger {
68
68
 
69
69
  /**
70
70
  * Whether the given version is recorded as applied.
71
- * @param {import("./drivers/base.js").default} db
72
- * @param {string} version
73
- * @returns {Promise<boolean>}
71
+ * @param {import("./drivers/base.js").default} db - Database whose ledger is queried.
72
+ * @param {string} version - Migration version to look up.
73
+ * @returns {Promise<boolean>} - Whether the migration version is applied.
74
74
  */
75
75
  static async hasVersion(db, version) {
76
76
  const rows = await db.newQuery()
@@ -84,8 +84,8 @@ export default class MigrationsLedger {
84
84
  /**
85
85
  * Records a single version as applied. The targeted existence check keeps the
86
86
  * migrator's per-migration hot path cheap (no full-table load per migration).
87
- * @param {import("./drivers/base.js").default} db
88
- * @param {string} version
87
+ * @param {import("./drivers/base.js").default} db - Database whose ledger receives the version.
88
+ * @param {string} version - Migration version to record as applied.
89
89
  * @returns {Promise<void>}
90
90
  */
91
91
  static async recordVersion(db, version) {
@@ -96,8 +96,8 @@ export default class MigrationsLedger {
96
96
 
97
97
  /**
98
98
  * Removes a version from the ledger (used when migrating down).
99
- * @param {import("./drivers/base.js").default} db
100
- * @param {string} version
99
+ * @param {import("./drivers/base.js").default} db - Database whose ledger loses the version.
100
+ * @param {string} version - Migration version to mark as unapplied.
101
101
  * @returns {Promise<void>}
102
102
  */
103
103
  static async removeVersion(db, version) {
@@ -108,8 +108,8 @@ export default class MigrationsLedger {
108
108
  * Baselines a database's ledger: records each version as applied without running
109
109
  * its migration. Idempotent — already-recorded versions are skipped. Ensures the
110
110
  * ledger table exists first, then loads the existing set once for the whole batch.
111
- * @param {import("./drivers/base.js").default} db
112
- * @param {string[]} versions
111
+ * @param {import("./drivers/base.js").default} db - Database whose ledger should be baselined.
112
+ * @param {string[]} versions - Migration versions to record without running them.
113
113
  * @returns {Promise<string[]>} The versions that were newly recorded.
114
114
  */
115
115
  static async markApplied(db, versions) {
@@ -136,7 +136,7 @@ export default class MigrationsLedger {
136
136
  * provisioning path advanced `targetDb`'s schema to match `sourceDb` out of band
137
137
  * (e.g. cloning table structure between databases): the migrations are, by
138
138
  * construction, already applied on the target, so record them without re-running.
139
- * @param {{sourceDb: import("./drivers/base.js").default, targetDb: import("./drivers/base.js").default}} args
139
+ * @param {{sourceDb: import("./drivers/base.js").default, targetDb: import("./drivers/base.js").default}} args - Source ledger and target database to baseline.
140
140
  * @returns {Promise<string[]>} The versions that were newly recorded on the target.
141
141
  */
142
142
  static async baselineFromDatabase({sourceDb, targetDb}) {
@@ -809,6 +809,22 @@ export default class VelociousDatabasePoolAsyncTrackedMultiConnection extends Ba
809
809
  this._testSharedConnection = undefined
810
810
  }
811
811
 
812
+ /**
813
+ * Runs a callback inside the test shared connection's async context, so nested
814
+ * `getCurrentConnection`/`ensureConnections` reuse it (with a real context) rather
815
+ * than checking out a fresh pooled connection. Used to run an in-process request
816
+ * handler on the same connection — and open transaction — as the test body. No-op
817
+ * (runs the callback as-is) when no shared connection is set.
818
+ * @template T
819
+ * @param {() => T} callback - Callback to run in the shared connection's context.
820
+ * @returns {T} - Callback result.
821
+ */
822
+ runWithTestSharedConnection(callback) {
823
+ if (!this._testSharedConnection) return callback()
824
+
825
+ return this.asyncLocalStorage.run(this._testSharedConnection.getIdSeq(), callback)
826
+ }
827
+
812
828
  /**
813
829
  * Returns the connection tied to the current async context, if any.
814
830
  * Falls back to the test shared connection when no async context exists.
@@ -144,6 +144,32 @@ class VelociousDatabasePoolBase {
144
144
  return this.getCurrentConnection()
145
145
  }
146
146
 
147
+ /**
148
+ * Pins a connection to be returned to callers that run without a connection-context
149
+ * pin (used by the test runner to share one connection with in-process HTTP handlers).
150
+ * Base pools that do not track async context ignore it; async-context pools override.
151
+ * @param {import("../drivers/base.js").default} _connection - Shared connection.
152
+ * @returns {void}
153
+ */
154
+ setTestSharedConnection(_connection) {}
155
+
156
+ /**
157
+ * Clears the shared connection set by {@link setTestSharedConnection}. No-op by default.
158
+ * @returns {void}
159
+ */
160
+ clearTestSharedConnection() {}
161
+
162
+ /**
163
+ * Runs a callback inside the test shared connection's context. Base pools that do not
164
+ * track async context just run the callback as-is; async-context pools override.
165
+ * @template T
166
+ * @param {() => T} callback - Callback to run.
167
+ * @returns {T} - Callback result.
168
+ */
169
+ runWithTestSharedConnection(callback) {
170
+ return callback()
171
+ }
172
+
147
173
  /**
148
174
  * Returns whether the current connection is pinned to an execution context.
149
175
  * @returns {boolean} - Whether the current connection can be reused by nested code.
@@ -316,6 +316,7 @@ export default class VelociousDatabaseQueryWhereModelClassHash extends WhereBase
316
316
  /**
317
317
  * Normalize.
318
318
  * @param {?} entry - Value to normalize.
319
+ * @returns {?} - SQLite predicate value with booleans encoded as 1 or 0.
319
320
  */
320
321
  const normalize = (entry) => {
321
322
  if (entry === true) return 1
@@ -353,6 +354,7 @@ export default class VelociousDatabaseQueryWhereModelClassHash extends WhereBase
353
354
  /**
354
355
  * Normalize.
355
356
  * @param {?} entry - Value to normalize.
357
+ * @returns {?} - Column-compatible predicate value, or the no-match sentinel for numeric UUIDs.
356
358
  */
357
359
  const normalize = (entry) => {
358
360
  if (isUuidType && typeof entry === "number") return NO_MATCH
@@ -85,7 +85,7 @@ export function normalizeWithCount(spec) {
85
85
  /**
86
86
  * Runs entry from name.
87
87
  * @param {string} name - Relationship name (attribute name is derived by appending "Count").
88
- * @returns {WithCountEntry}
88
+ * @returns {WithCountEntry} - Normalized association-count entry.
89
89
  */
90
90
  function entryFromName(name) {
91
91
  return {
@@ -21,7 +21,7 @@ function setShiftingFlag(record, value) {
21
21
  /**
22
22
  * Runs is shifting.
23
23
  * @param {import("./index.js").default} record - Model instance.
24
- * @returns {boolean}
24
+ * @returns {boolean} - Whether list positions are currently shifting.
25
25
  */
26
26
  function isShifting(record) {
27
27
  // @ts-ignore - Symbol indexing on Record instances
@@ -341,7 +341,7 @@ async function highestPositionInScope({record, positionColumn, scope, scopeValue
341
341
  * _belongsToChanges.
342
342
  * @param {import("./index.js").default} record - Model instance.
343
343
  * @param {string} scope - camelCase scope attribute name (e.g. "projectId").
344
- * @returns {string | number | null}
344
+ * @returns {string | number | null} - Current list position value.
345
345
  */
346
346
  function resolveScopeValue(record, scope) {
347
347
  const attrValue = record.readAttribute(scope)