velocious 1.0.515 → 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 (198) hide show
  1. package/README.md +3 -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 +31 -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/query/where-model-class-hash.js +2 -0
  18. package/build/database/query/with-count.js +1 -1
  19. package/build/database/record/acts-as-list.js +2 -2
  20. package/build/database/record/auditing.js +23 -6
  21. package/build/database/record/index.js +7 -7
  22. package/build/database/record/validators/uniqueness.js +3 -3
  23. package/build/database/tenants/data-copier.js +23 -23
  24. package/build/database/tenants/schema-cloner.js +27 -27
  25. package/build/environment-handlers/browser.js +0 -1
  26. package/build/environment-handlers/node/cli/commands/background-jobs-runner.js +2 -1
  27. package/build/environment-handlers/node/cli/commands/lint/relationships.js +2 -2
  28. package/build/environment-handlers/node.js +1 -1
  29. package/build/frontend-model-controller.js +26 -18
  30. package/build/frontend-models/base.js +5 -5
  31. package/build/frontend-models/query.js +12 -12
  32. package/build/http-server/client/index.js +61 -15
  33. package/build/http-server/client/response.js +17 -1
  34. package/build/http-server/client/websocket-session.js +13 -13
  35. package/build/http-server/server-client.js +96 -0
  36. package/build/http-server/websocket-channel.js +12 -11
  37. package/build/http-server/websocket-connection.js +12 -11
  38. package/build/http-server/websocket-events-host.js +1 -1
  39. package/build/http-server/worker-handler/in-process.js +48 -9
  40. package/build/http-server/worker-handler/index.js +67 -7
  41. package/build/http-server/worker-handler/worker-thread.js +68 -0
  42. package/build/src/background-jobs/cron-expression.d.ts +2 -2
  43. package/build/src/background-jobs/cron-expression.js +10 -10
  44. package/build/src/background-jobs/forked-runner-child.js +2 -2
  45. package/build/src/background-jobs/job-runner.d.ts +5 -1
  46. package/build/src/background-jobs/job-runner.d.ts.map +1 -1
  47. package/build/src/background-jobs/job-runner.js +22 -16
  48. package/build/src/background-jobs/json-socket.d.ts +4 -0
  49. package/build/src/background-jobs/json-socket.d.ts.map +1 -1
  50. package/build/src/background-jobs/json-socket.js +5 -1
  51. package/build/src/background-jobs/main.d.ts +62 -10
  52. package/build/src/background-jobs/main.d.ts.map +1 -1
  53. package/build/src/background-jobs/main.js +171 -19
  54. package/build/src/background-jobs/status-reporter.d.ts +6 -2
  55. package/build/src/background-jobs/status-reporter.d.ts.map +1 -1
  56. package/build/src/background-jobs/status-reporter.js +13 -6
  57. package/build/src/background-jobs/store.d.ts +48 -25
  58. package/build/src/background-jobs/store.d.ts.map +1 -1
  59. package/build/src/background-jobs/store.js +123 -50
  60. package/build/src/background-jobs/types.d.ts +36 -3
  61. package/build/src/background-jobs/types.d.ts.map +1 -1
  62. package/build/src/background-jobs/types.js +12 -4
  63. package/build/src/background-jobs/worker.d.ts +7 -5
  64. package/build/src/background-jobs/worker.d.ts.map +1 -1
  65. package/build/src/background-jobs/worker.js +10 -6
  66. package/build/src/beacon/client.d.ts +6 -3
  67. package/build/src/beacon/client.d.ts.map +1 -1
  68. package/build/src/beacon/client.js +14 -7
  69. package/build/src/configuration-types.d.ts +1 -1
  70. package/build/src/configuration.d.ts +31 -29
  71. package/build/src/configuration.d.ts.map +1 -1
  72. package/build/src/configuration.js +32 -30
  73. package/build/src/controller.d.ts +3 -0
  74. package/build/src/controller.d.ts.map +1 -1
  75. package/build/src/controller.js +9 -4
  76. package/build/src/database/drivers/sqlite/base.js +2 -2
  77. package/build/src/database/drivers/sqlite/index.d.ts +7 -6
  78. package/build/src/database/drivers/sqlite/index.d.ts.map +1 -1
  79. package/build/src/database/drivers/sqlite/index.js +16 -15
  80. package/build/src/database/migrations-ledger.d.ts +16 -16
  81. package/build/src/database/migrations-ledger.js +17 -17
  82. package/build/src/database/query/where-model-class-hash.d.ts.map +1 -1
  83. package/build/src/database/query/where-model-class-hash.js +3 -1
  84. package/build/src/database/query/with-count.js +2 -2
  85. package/build/src/database/record/acts-as-list.js +3 -3
  86. package/build/src/database/record/auditing.d.ts +1 -1
  87. package/build/src/database/record/auditing.d.ts.map +1 -1
  88. package/build/src/database/record/auditing.js +24 -7
  89. package/build/src/database/record/index.d.ts +7 -7
  90. package/build/src/database/record/index.js +8 -8
  91. package/build/src/database/record/validators/uniqueness.d.ts +3 -3
  92. package/build/src/database/record/validators/uniqueness.js +4 -4
  93. package/build/src/database/tenants/data-copier.d.ts +18 -18
  94. package/build/src/database/tenants/data-copier.js +24 -24
  95. package/build/src/database/tenants/schema-cloner.d.ts +27 -27
  96. package/build/src/database/tenants/schema-cloner.js +28 -28
  97. package/build/src/environment-handlers/browser.d.ts +9 -0
  98. package/build/src/environment-handlers/browser.d.ts.map +1 -1
  99. package/build/src/environment-handlers/browser.js +1 -2
  100. package/build/src/environment-handlers/node/cli/commands/background-jobs-runner.d.ts.map +1 -1
  101. package/build/src/environment-handlers/node/cli/commands/background-jobs-runner.js +3 -2
  102. package/build/src/environment-handlers/node/cli/commands/lint/relationships.js +3 -3
  103. package/build/src/environment-handlers/node.js +2 -2
  104. package/build/src/frontend-model-controller.d.ts +9 -8
  105. package/build/src/frontend-model-controller.d.ts.map +1 -1
  106. package/build/src/frontend-model-controller.js +26 -19
  107. package/build/src/frontend-models/base.d.ts +4 -4
  108. package/build/src/frontend-models/base.d.ts.map +1 -1
  109. package/build/src/frontend-models/base.js +6 -6
  110. package/build/src/frontend-models/query.d.ts +7 -7
  111. package/build/src/frontend-models/query.js +13 -13
  112. package/build/src/http-server/client/index.d.ts +23 -1
  113. package/build/src/http-server/client/index.d.ts.map +1 -1
  114. package/build/src/http-server/client/index.js +55 -17
  115. package/build/src/http-server/client/response.d.ts +11 -1
  116. package/build/src/http-server/client/response.d.ts.map +1 -1
  117. package/build/src/http-server/client/response.js +16 -2
  118. package/build/src/http-server/client/websocket-session.d.ts +13 -13
  119. package/build/src/http-server/client/websocket-session.js +14 -14
  120. package/build/src/http-server/server-client.d.ts +13 -0
  121. package/build/src/http-server/server-client.d.ts.map +1 -1
  122. package/build/src/http-server/server-client.js +94 -1
  123. package/build/src/http-server/websocket-channel.d.ts +12 -11
  124. package/build/src/http-server/websocket-channel.d.ts.map +1 -1
  125. package/build/src/http-server/websocket-channel.js +13 -12
  126. package/build/src/http-server/websocket-connection.d.ts +12 -11
  127. package/build/src/http-server/websocket-connection.d.ts.map +1 -1
  128. package/build/src/http-server/websocket-connection.js +13 -12
  129. package/build/src/http-server/websocket-events-host.d.ts +1 -1
  130. package/build/src/http-server/websocket-events-host.js +2 -2
  131. package/build/src/http-server/worker-handler/in-process.d.ts +2 -0
  132. package/build/src/http-server/worker-handler/in-process.d.ts.map +1 -1
  133. package/build/src/http-server/worker-handler/in-process.js +42 -11
  134. package/build/src/http-server/worker-handler/index.d.ts +21 -0
  135. package/build/src/http-server/worker-handler/index.d.ts.map +1 -1
  136. package/build/src/http-server/worker-handler/index.js +61 -7
  137. package/build/src/http-server/worker-handler/worker-thread.d.ts +30 -0
  138. package/build/src/http-server/worker-handler/worker-thread.d.ts.map +1 -1
  139. package/build/src/http-server/worker-handler/worker-thread.js +61 -1
  140. package/build/src/sync/device-identity.d.ts +2 -2
  141. package/build/src/sync/device-identity.d.ts.map +1 -1
  142. package/build/src/sync/device-identity.js +4 -4
  143. package/build/src/sync/local-mutation-log.d.ts +0 -1
  144. package/build/src/sync/local-mutation-log.d.ts.map +1 -1
  145. package/build/src/sync/local-mutation-log.js +1 -2
  146. package/build/src/tenants/tenant-iterator.d.ts +8 -8
  147. package/build/src/tenants/tenant-iterator.js +9 -9
  148. package/build/src/tenants/tenant.d.ts +6 -6
  149. package/build/src/tenants/tenant.js +7 -7
  150. package/build/sync/device-identity.js +3 -3
  151. package/build/sync/local-mutation-log.js +0 -1
  152. package/build/tenants/tenant-iterator.js +8 -8
  153. package/build/tenants/tenant.js +6 -6
  154. package/package.json +2 -2
  155. package/src/background-jobs/cron-expression.js +9 -9
  156. package/src/background-jobs/forked-runner-child.js +1 -1
  157. package/src/background-jobs/job-runner.js +21 -15
  158. package/src/background-jobs/json-socket.js +4 -0
  159. package/src/background-jobs/main.js +180 -18
  160. package/src/background-jobs/status-reporter.js +12 -5
  161. package/src/background-jobs/store.js +132 -50
  162. package/src/background-jobs/types.js +11 -3
  163. package/src/background-jobs/worker.js +9 -5
  164. package/src/beacon/client.js +13 -6
  165. package/src/configuration.js +31 -29
  166. package/src/controller.js +9 -3
  167. package/src/database/drivers/sqlite/base.js +1 -1
  168. package/src/database/drivers/sqlite/index.js +15 -14
  169. package/src/database/migrations-ledger.js +16 -16
  170. package/src/database/query/where-model-class-hash.js +2 -0
  171. package/src/database/query/with-count.js +1 -1
  172. package/src/database/record/acts-as-list.js +2 -2
  173. package/src/database/record/auditing.js +23 -6
  174. package/src/database/record/index.js +7 -7
  175. package/src/database/record/validators/uniqueness.js +3 -3
  176. package/src/database/tenants/data-copier.js +23 -23
  177. package/src/database/tenants/schema-cloner.js +27 -27
  178. package/src/environment-handlers/browser.js +0 -1
  179. package/src/environment-handlers/node/cli/commands/background-jobs-runner.js +2 -1
  180. package/src/environment-handlers/node/cli/commands/lint/relationships.js +2 -2
  181. package/src/environment-handlers/node.js +1 -1
  182. package/src/frontend-model-controller.js +26 -18
  183. package/src/frontend-models/base.js +5 -5
  184. package/src/frontend-models/query.js +12 -12
  185. package/src/http-server/client/index.js +61 -15
  186. package/src/http-server/client/response.js +17 -1
  187. package/src/http-server/client/websocket-session.js +13 -13
  188. package/src/http-server/server-client.js +96 -0
  189. package/src/http-server/websocket-channel.js +12 -11
  190. package/src/http-server/websocket-connection.js +12 -11
  191. package/src/http-server/websocket-events-host.js +1 -1
  192. package/src/http-server/worker-handler/in-process.js +48 -9
  193. package/src/http-server/worker-handler/index.js +67 -7
  194. package/src/http-server/worker-handler/worker-thread.js +68 -0
  195. package/src/sync/device-identity.js +3 -3
  196. package/src/sync/local-mutation-log.js +0 -1
  197. package/src/tenants/tenant-iterator.js +8 -8
  198. package/src/tenants/tenant.js +6 -6
@@ -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) {
@@ -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}) {
@@ -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)
@@ -74,7 +74,12 @@ let globalEventConnections = {}
74
74
 
75
75
  /** @type {AuditEventsType} */
76
76
  const AuditEvents = {
77
- /** Fire all registered callbacks for a model type + action. @param {string} type @param {string} action @param {AuditEventPayload} args */
77
+ /**
78
+ * Fire all registered callbacks for a model type and action.
79
+ * @param {string} type - Audited model type whose listeners should fire.
80
+ * @param {string} action - Audit action whose listeners should fire.
81
+ * @param {AuditEventPayload} args - Audit event delivered to matching listeners.
82
+ */
78
83
  call(type, action, args) {
79
84
  const actions = globalEventConnections[type] || {}
80
85
  const callbacks = actions[action] || []
@@ -84,7 +89,13 @@ const AuditEvents = {
84
89
  }
85
90
  },
86
91
 
87
- /** Register a callback for a model type + action. Returns an unsubscribe function. @param {string} type @param {string} action @param {(args: AuditEventPayload) => void} callback @returns {() => void} */
92
+ /**
93
+ * Register a callback for a model type and action.
94
+ * @param {string} type - Audited model type to observe.
95
+ * @param {string} action - Audit action to observe.
96
+ * @param {(args: AuditEventPayload) => void} callback - Listener invoked for matching audit events.
97
+ * @returns {() => void} - Callback that removes the registration.
98
+ */
88
99
  connect(type, action, callback) {
89
100
  if (!globalEventConnections[type]) {
90
101
  globalEventConnections[type] = {}
@@ -109,7 +120,7 @@ const AuditEvents = {
109
120
  }
110
121
  },
111
122
 
112
- /** Clear all registered callbacks. @returns {void} */
123
+ /** Clear all registered callbacks. */
113
124
  reset() {
114
125
  globalEventConnections = {}
115
126
  }
@@ -244,7 +255,10 @@ function sharedAuditClass(modelClass) {
244
255
  * Framework-owned Audit model for the shared `audits` table.
245
256
  */
246
257
  class Audit extends dbRecordClass {
247
- /** Returns the backing table name. @returns {string} */
258
+ /**
259
+ * Returns the backing table name.
260
+ * @returns {string} - Shared `audits` table name.
261
+ */
248
262
  static tableName() {
249
263
  return "audits"
250
264
  }
@@ -291,7 +305,10 @@ function dedicatedAuditClass(modelClass, tableName) {
291
305
  * Framework-owned per-model Audit class.
292
306
  */
293
307
  class ModelAudit extends dbRecordClass {
294
- /** Returns the backing table name. @returns {string} */
308
+ /**
309
+ * Returns the backing table name.
310
+ * @returns {string} - Dedicated audit table supplied for this model class.
311
+ */
295
312
  static tableName() {
296
313
  return tableName
297
314
  }
@@ -906,7 +923,7 @@ function normalizeAction(action) {
906
923
  * Creates the shared audit tables migration up/down callbacks for use inside
907
924
  * a Migration class. The `table` parameter is a Migration instance.
908
925
  * @param {{id?: {type: string}}} [options] - ID column options.
909
- * @returns {{down: (table: import("../migration/index.js").default) => Promise<void>, up: (table: import("../migration/index.js").default) => Promise<void>}}
926
+ * @returns {{down: (table: import("../migration/index.js").default) => Promise<void>, up: (table: import("../migration/index.js").default) => Promise<void>}} - Up/down callbacks for the shared audit tables.
910
927
  */
911
928
  function createSharedAuditTablesMigration(options = {}) {
912
929
  const opts = /** @type {{id?: {type: string}}} */ (options)
@@ -2688,7 +2688,7 @@ class VelociousDatabaseRecord {
2688
2688
  * @param {string} name - Lock name (for the error message).
2689
2689
  * @param {() => Promise<T>} callback - Callback holding the lock.
2690
2690
  * @param {number | null} [holdTimeoutMs] - Max hold time; falsy disables the timeout.
2691
- * @returns {Promise<T>}
2691
+ * @returns {Promise<T>} - Callback result after the lock-protected operation.
2692
2692
  */
2693
2693
  static async runWithAdvisoryLockHoldTimeout(name, callback, holdTimeoutMs) {
2694
2694
  return await AdvisoryLockRunner.runWithAdvisoryLockHoldTimeout(name, callback, holdTimeoutMs)
@@ -3816,7 +3816,7 @@ class VelociousDatabaseRecord {
3816
3816
  * column of the same name. Returns the attached number, or 0 when
3817
3817
  * `.withCount(...)` wasn't requested for this attribute.
3818
3818
  * @param {string} attributeName - Attribute name, e.g. `"tasksCount"` or a custom `"activeMembersCount"` from `.withCount({activeMembersCount: {...}})`.
3819
- * @returns {number}
3819
+ * @returns {number} - Attached association count, or zero when absent.
3820
3820
  */
3821
3821
  readCount(attributeName) {
3822
3822
  return readPayloadAssociationCount(/** @type {import("../../record-payload-values.js").RecordPayloadValuesTarget} */ (/** @type {?} */ (this)), attributeName)
@@ -3837,7 +3837,7 @@ class VelociousDatabaseRecord {
3837
3837
  * All attached association counts as a plain object. Used by the
3838
3838
  * frontend-model serializer to ship counts alongside the record
3839
3839
  * attributes on the wire.
3840
- * @returns {Record<string, number>}
3840
+ * @returns {Record<string, number>} - Association counts keyed by attribute name.
3841
3841
  */
3842
3842
  associationCounts() {
3843
3843
  /**
@@ -3864,7 +3864,7 @@ class VelociousDatabaseRecord {
3864
3864
  * registered fn for this record (e.g. no child rows matched the
3865
3865
  * aggregate).
3866
3866
  * @param {string} name - queryData attribute name (matches a SELECT alias from the registered fn).
3867
- * @returns {?}
3867
+ * @returns {?} - Attached query-data value.
3868
3868
  */
3869
3869
  queryData(name) {
3870
3870
  return readPayloadQueryData(/** @type {import("../../record-payload-values.js").RecordPayloadValuesTarget} */ (/** @type {?} */ (this)), name)
@@ -3886,7 +3886,7 @@ class VelociousDatabaseRecord {
3886
3886
  * All attached queryData values as a plain object. Used by the
3887
3887
  * frontend-model serializer to ship queryData alongside the record
3888
3888
  * attributes on the wire.
3889
- * @returns {Record<string, ?>}
3889
+ * @returns {Record<string, ?>} - Query-data values keyed by name.
3890
3890
  */
3891
3891
  queryDataValues() {
3892
3892
  /**
@@ -3914,7 +3914,7 @@ class VelociousDatabaseRecord {
3914
3914
  * `record.can("update")` without first checking whether the ability
3915
3915
  * was loaded.
3916
3916
  * @param {string} action - Ability action name, e.g. `"update"`.
3917
- * @returns {boolean}
3917
+ * @returns {boolean} - Whether the requested ability is allowed.
3918
3918
  */
3919
3919
  can(action) {
3920
3920
  return readPayloadComputedAbility(/** @type {import("../../record-payload-values.js").RecordPayloadValuesTarget} */ (/** @type {?} */ (this)), action)
@@ -3936,7 +3936,7 @@ class VelociousDatabaseRecord {
3936
3936
  * All attached per-record ability results as a plain object. Used
3937
3937
  * by the frontend-model serializer to ship results alongside the
3938
3938
  * record attributes on the wire.
3939
- * @returns {Record<string, boolean>}
3939
+ * @returns {Record<string, boolean>} - Ability results keyed by action.
3940
3940
  */
3941
3941
  computedAbilities() {
3942
3942
  /**