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
@@ -20,6 +20,8 @@ import isDate from "./utils/is-date.js"
20
20
  import isPlainObject from "./utils/plain-object.js"
21
21
  import {RansackQueryError, normalizeRansackGroup, parseRansackSort} from "./utils/ransack.js"
22
22
 
23
+ /** @typedef {import("./database/query/model-class-query.js").default & Record<symbol, Set<string> | undefined>} FrontendModelQueryMetadata */
24
+
23
25
  /**
24
26
  * Runs normalize frontend model preload.
25
27
  * @param {import("./database/query/index.js").NestedPreloadRecord | string | string[] | boolean | undefined | null} preload - Preload shorthand.
@@ -31,7 +33,7 @@ function normalizeFrontendModelPreload(preload) {
31
33
  try {
32
34
  return normalizeQueryPreload(preload)
33
35
  } catch (error) {
34
- throwFrontendModelQueryErrorForParserError(error)
36
+ return throwFrontendModelQueryErrorForParserError(error)
35
37
  }
36
38
  }
37
39
 
@@ -46,7 +48,7 @@ function normalizeFrontendModelJoins(joins) {
46
48
  try {
47
49
  return normalizeQueryJoins(joins)
48
50
  } catch (error) {
49
- throwFrontendModelQueryErrorForParserError(error)
51
+ return throwFrontendModelQueryErrorForParserError(error)
50
52
  }
51
53
  }
52
54
 
@@ -190,10 +192,10 @@ function frontendSyncReplaySafeError(message, cause) {
190
192
  /**
191
193
  * Runs frontend model query metadata.
192
194
  * @param {import("./database/query/model-class-query.js").default} query - Query instance.
193
- * @returns {import("./database/query/model-class-query.js").default & {[frontendModelJoinedPathsSymbol]?: Set<string>, [frontendModelGroupedColumnsSymbol]?: Set<string>}} - Query metadata access helper.
195
+ * @returns {FrontendModelQueryMetadata} - Query metadata access helper.
194
196
  */
195
197
  function frontendModelQueryMetadata(query) {
196
- return /** @type {import("./database/query/model-class-query.js").default & {[frontendModelJoinedPathsSymbol]?: Set<string>, [frontendModelGroupedColumnsSymbol]?: Set<string>}} */ (query)
198
+ return /** @type {FrontendModelQueryMetadata} */ (query)
197
199
  }
198
200
 
199
201
  /**
@@ -1253,7 +1255,7 @@ export default class FrontendModelController extends Controller {
1253
1255
  try {
1254
1256
  return normalizeQuerySort(this.frontendModelParams().sort)
1255
1257
  } catch (error) {
1256
- throwFrontendModelQueryErrorForParserError(error)
1258
+ return throwFrontendModelQueryErrorForParserError(error)
1257
1259
  }
1258
1260
  }
1259
1261
 
@@ -1265,7 +1267,7 @@ export default class FrontendModelController extends Controller {
1265
1267
  try {
1266
1268
  return normalizeQueryGroup(this.frontendModelParams().group)
1267
1269
  } catch (error) {
1268
- throwFrontendModelQueryErrorForParserError(error)
1270
+ return throwFrontendModelQueryErrorForParserError(error)
1269
1271
  }
1270
1272
  }
1271
1273
 
@@ -1304,7 +1306,7 @@ export default class FrontendModelController extends Controller {
1304
1306
 
1305
1307
  return pluck
1306
1308
  } catch (error) {
1307
- throwFrontendModelQueryErrorForParserError(error)
1309
+ return throwFrontendModelQueryErrorForParserError(error)
1308
1310
  }
1309
1311
  }
1310
1312
 
@@ -1351,8 +1353,8 @@ export default class FrontendModelController extends Controller {
1351
1353
  * its backend model class by looking up the resource by modelName
1352
1354
  * across all configured backend projects. Returns null when no
1353
1355
  * resource matches the user-provided ability entry.
1354
- * @param {string} modelName
1355
- * @returns {typeof import("./database/record/index.js").default | null}
1356
+ * @param {string} modelName - Frontend model name from an ability request.
1357
+ * @returns {typeof import("./database/record/index.js").default | null} - Backend model class exposed under that frontend name, if present.
1356
1358
  */
1357
1359
  _frontendModelClassForAbilities(modelName) {
1358
1360
  if (typeof modelName !== "string" || modelName.length === 0) return null
@@ -1383,9 +1385,9 @@ export default class FrontendModelController extends Controller {
1383
1385
  * preloaded relationships at any depth. Used to evaluate per-record
1384
1386
  * abilities against nested preloaded children with a single batched
1385
1387
  * query per (modelClass, action) pair.
1386
- * @param {import("./database/record/index.js").default[]} rootModels
1387
- * @param {string} modelName
1388
- * @returns {import("./database/record/index.js").default[]}
1388
+ * @param {import("./database/record/index.js").default[]} rootModels - Loaded roots whose relationship graphs should be traversed.
1389
+ * @param {string} modelName - Model name records must match.
1390
+ * @returns {import("./database/record/index.js").default[]} - Matching records reachable from the loaded roots.
1389
1391
  */
1390
1392
  _frontendModelCollectRecordsForName(rootModels, modelName) {
1391
1393
  /**
@@ -1399,7 +1401,8 @@ export default class FrontendModelController extends Controller {
1399
1401
 
1400
1402
  /**
1401
1403
  * Walk.
1402
- * @param {import("./database/record/index.js").default | null | undefined} record */
1404
+ * @param {import("./database/record/index.js").default | null | undefined} record - Loaded record whose relationship graph should be visited.
1405
+ */
1403
1406
  const walk = (record) => {
1404
1407
  if (!record || typeof record !== "object") return
1405
1408
  if (seen.has(record)) return
@@ -1437,7 +1440,7 @@ export default class FrontendModelController extends Controller {
1437
1440
  * `_setComputedAbility`. Runs one batched `authorized query + pluck`
1438
1441
  * per (modelClass, action) pair, regardless of how many records
1439
1442
  * were loaded.
1440
- * @param {import("./database/record/index.js").default[]} rootModels
1443
+ * @param {import("./database/record/index.js").default[]} rootModels - Loaded roots that receive computed ability results.
1441
1444
  * @returns {Promise<void>}
1442
1445
  */
1443
1446
  async frontendModelComputeAbilities(rootModels) {
@@ -1491,7 +1494,7 @@ export default class FrontendModelController extends Controller {
1491
1494
  * Unknown entries are silently skipped — downstream code resolves
1492
1495
  * model names to classes when applying the check, so unresolved
1493
1496
  * names naturally become no-ops.
1494
- * @returns {Array<{modelName: string, actions: string[]}>}
1497
+ * @returns {Array<{modelName: string, actions: string[]}>} - Normalized model ability requests.
1495
1498
  */
1496
1499
  frontendModelAbilities() {
1497
1500
  const raw = this.frontendModelParams().abilities
@@ -1529,7 +1532,7 @@ export default class FrontendModelController extends Controller {
1529
1532
  *
1530
1533
  * Returns the raw nested-record spec (shape validated by the
1531
1534
  * normalizer inside `Query.queryData`) or `null` when not requested.
1532
- * @returns {import("./database/query/query-data.js").QueryDataSpec | null}
1535
+ * @returns {import("./database/query/query-data.js").QueryDataSpec | null} - Normalized query-data specification.
1533
1536
  */
1534
1537
  frontendModelQueryData() {
1535
1538
  const raw = this.frontendModelParams().queryData
@@ -1853,7 +1856,7 @@ export default class FrontendModelController extends Controller {
1853
1856
  try {
1854
1857
  return normalizeRansackGroup(this.frontendModelClass(), filterParams)
1855
1858
  } catch (error) {
1856
- throwFrontendModelQueryErrorForParserError(error)
1859
+ return throwFrontendModelQueryErrorForParserError(error)
1857
1860
  }
1858
1861
  }
1859
1862
 
@@ -1866,7 +1869,7 @@ export default class FrontendModelController extends Controller {
1866
1869
  try {
1867
1870
  return parseRansackSort(this.frontendModelClass(), sortString)
1868
1871
  } catch (error) {
1869
- throwFrontendModelQueryErrorForParserError(error)
1872
+ return throwFrontendModelQueryErrorForParserError(error)
1870
1873
  }
1871
1874
  }
1872
1875
 
@@ -2698,12 +2701,14 @@ export default class FrontendModelController extends Controller {
2698
2701
  /**
2699
2702
  * Resource attribute method name.
2700
2703
  * @param {string} attributeName - Attribute name.
2704
+ * @returns {string} - Resource attribute method name.
2701
2705
  */
2702
2706
  const resourceAttributeMethodName = (attributeName) => `${attributeName}Attribute`
2703
2707
 
2704
2708
  /**
2705
2709
  * Resource has attribute.
2706
2710
  * @param {string} attributeName - Attribute name.
2711
+ * @returns {ReturnType<FrontendModelBaseResource["resourceMethod"]>} - Resource attribute method details.
2707
2712
  */
2708
2713
  const resourceAttributeMethod = (attributeName) => {
2709
2714
  const methodName = resourceAttributeMethodName(attributeName)
@@ -2714,6 +2719,7 @@ export default class FrontendModelController extends Controller {
2714
2719
  /**
2715
2720
  * Prototype attribute method.
2716
2721
  * @param {string} attributeName - Attribute name.
2722
+ * @returns {{method: (...args: Array<?>) => ?, ownerName: string} | undefined} - Prototype method details when present.
2717
2723
  */
2718
2724
  const prototypeAttributeMethod = (attributeName) => {
2719
2725
  let currentPrototype = Object.getPrototypeOf(model)
@@ -2735,6 +2741,7 @@ export default class FrontendModelController extends Controller {
2735
2741
  /**
2736
2742
  * Serialized attribute value.
2737
2743
  * @param {string} attributeName - Attribute name.
2744
+ * @returns {Promise<?>} - Serialized attribute value.
2738
2745
  */
2739
2746
  const serializedAttributeValue = async (attributeName) => {
2740
2747
  // Check resource instance first (virtual/computed attributes via ${name}Attribute convention)
@@ -2758,6 +2765,7 @@ export default class FrontendModelController extends Controller {
2758
2765
  /**
2759
2766
  * Attribute exists.
2760
2767
  * @param {string} attributeName - Attribute name.
2768
+ * @returns {boolean} - Whether the attribute exists.
2761
2769
  */
2762
2770
  const attributeExists = (attributeName) => {
2763
2771
  return (attributeName in modelAttributes) || (attributeName in /** @type {Record<string, ?>} */ (model)) || Boolean(resourceAttributeMethod(attributeName))
@@ -1128,10 +1128,10 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
1128
1128
  }
1129
1129
 
1130
1130
  if (notPermittedAttachments.length > 0) {
1131
- throw new Error(`Frontend model attachment names not permitted by permittedParams(): ${notPermittedAttachments.join(", ")}`)
1131
+ throw VelociousError.safe(`Frontend model attachment names not permitted by permittedParams(): ${notPermittedAttachments.join(", ")}`, {code: "frontend-model-attribute-error"})
1132
1132
  }
1133
1133
  if (invalidAttachments.length > 0) {
1134
- throw new Error(`Invalid frontend model attachment names: ${invalidAttachments.join(", ")}`)
1134
+ throw VelociousError.safe(`Invalid frontend model attachment names: ${invalidAttachments.join(", ")}`, {code: "frontend-model-attribute-error"})
1135
1135
  }
1136
1136
  }
1137
1137
 
@@ -1978,11 +1978,11 @@ function filterWritableFrontendModelAttributes(
1978
1978
  }
1979
1979
 
1980
1980
  if (notPermittedAttributes.length > 0) {
1981
- throw new Error(`Frontend model write attributes not permitted by permittedParams(): ${notPermittedAttributes.join(", ")}`)
1981
+ throw VelociousError.safe(`Frontend model write attributes not permitted by permittedParams(): ${notPermittedAttributes.join(", ")}`, {code: "frontend-model-attribute-error"})
1982
1982
  }
1983
1983
 
1984
1984
  if (invalidAttributes.length > 0) {
1985
- throw new Error(`Invalid frontend model write attributes: ${invalidAttributes.join(", ")}`)
1985
+ throw VelociousError.safe(`Invalid frontend model write attributes: ${invalidAttributes.join(", ")}`, {code: "frontend-model-attribute-error"})
1986
1986
  }
1987
1987
 
1988
1988
  return writableAttributes
@@ -104,7 +104,7 @@ import {readPayloadAssociationCount, readPayloadComputedAbility, readPayloadQuer
104
104
  * frontend models passed model classes into relationship helpers, while newer
105
105
  * generated models pass instance types.
106
106
  * @template {FrontendModelBase<any, any, any> | typeof FrontendModelBase} T
107
- * @typedef {T extends new (...args: any[]) => infer Instance ? Instance : T} FrontendModelRelationshipModel
107
+ * @typedef {T extends typeof FrontendModelBase ? InstanceType<T> : T} FrontendModelRelationshipModel
108
108
  */
109
109
  /**
110
110
  * FrontendModelTransportConfig type.
@@ -1799,7 +1799,7 @@ async function performSharedFrontendModelApiRequest(requestPayload) {
1799
1799
  /**
1800
1800
  * Throws a frontend-model HTTP error with backend-provided envelope details when available.
1801
1801
  * @param {{commandLabel: string, response: Response, responseText: string}} args - Error response details.
1802
- * @returns {never}
1802
+ * @returns {never} - Always throws an unknown-attribute error.
1803
1803
  */
1804
1804
  function throwFrontendModelHttpError({commandLabel, response, responseText}) {
1805
1805
  // Surface the backend's friendly errorMessage envelope (the
@@ -2612,7 +2612,7 @@ export default class FrontendModelBase {
2612
2612
  * column of the same name. Returns the attached value, or 0 when
2613
2613
  * `.withCount(...)` wasn't requested for this attribute.
2614
2614
  * @param {string} attributeName - Attribute name, e.g. `"tasksCount"` or a custom name from `.withCount({customName: {...}})`.
2615
- * @returns {number}
2615
+ * @returns {number} - Attached association count, or zero when absent.
2616
2616
  */
2617
2617
  readCount(attributeName) {
2618
2618
  return readPayloadAssociationCount(/** @type {import("../record-payload-values.js").RecordPayloadValuesTarget} */ (/** @type {?} */ (this)), attributeName)
@@ -2638,7 +2638,7 @@ export default class FrontendModelBase {
2638
2638
  * on `record.can("update")` without first checking whether the
2639
2639
  * ability was loaded.
2640
2640
  * @param {string} action - Ability action name, e.g. `"update"`.
2641
- * @returns {boolean}
2641
+ * @returns {boolean} - Whether the requested ability is allowed.
2642
2642
  */
2643
2643
  can(action) {
2644
2644
  return readPayloadComputedAbility(/** @type {import("../record-payload-values.js").RecordPayloadValuesTarget} */ (/** @type {?} */ (this)), action)
@@ -2663,7 +2663,7 @@ export default class FrontendModelBase {
2663
2663
  * name. Returns `null` when no registered fn produced that alias for
2664
2664
  * this record (e.g. no child rows matched the aggregate).
2665
2665
  * @param {string} name - queryData alias name.
2666
- * @returns {?}
2666
+ * @returns {?} - Attached query-data value.
2667
2667
  */
2668
2668
  queryData(name) {
2669
2669
  return readPayloadQueryData(/** @type {import("../record-payload-values.js").RecordPayloadValuesTarget} */ (/** @type {?} */ (this)), name)
@@ -166,8 +166,8 @@ export function normalizePreload(preload) {
166
166
  * query API into the strict internal entries used in the transport
167
167
  * payload. Shares the shape semantics with the backend normalizer in
168
168
  * `database/query/with-count.js`.
169
- * @param {string | string[] | Record<string, boolean | {relationship?: string, where?: Record<string, ?>}>} spec
170
- * @returns {Array<{attributeName: string, relationshipName: string, where?: Record<string, ?>}>}
169
+ * @param {string | string[] | Record<string, boolean | {relationship?: string, where?: Record<string, ?>}>} spec - Association-count shorthand to normalize.
170
+ * @returns {Array<{attributeName: string, relationshipName: string, where?: Record<string, ?>}>} - Normalized association-count requests.
171
171
  */
172
172
  function normalizeWithCountFrontend(spec) {
173
173
  if (spec == null) return []
@@ -222,9 +222,9 @@ function normalizeWithCountFrontend(spec) {
222
222
  * shorthand (applies to the query's own model class) and the keyed
223
223
  * `{ModelName: [action, ...]}` form (applies to records of that model
224
224
  * class, useful for preloaded children).
225
- * @param {string[] | Record<string, string[]>} spec
226
- * @param {{getModelName: () => string}} rootModelClass
227
- * @returns {Array<{modelName: string, actions: string[]}>}
225
+ * @param {string[] | Record<string, string[]>} spec - Ability actions grouped by model, or root-model action shorthand.
226
+ * @param {{getModelName: () => string}} rootModelClass - Query root used by the flat action shorthand.
227
+ * @returns {Array<{modelName: string, actions: string[]}>} - Normalized model ability requests.
228
228
  */
229
229
  function normalizeAbilitiesSpec(spec, rootModelClass) {
230
230
  if (spec == null) return []
@@ -1181,8 +1181,8 @@ export default class FrontendModelQuery {
1181
1181
  * strings — typically `"update"` / `"destroy"` / `"create"` /
1182
1182
  * `"read"`, but any custom action registered on the resource's
1183
1183
  * authorization ability is accepted.
1184
- * @param {string[] | Record<string, string[]>} spec
1185
- * @returns {this}
1184
+ * @param {string[] | Record<string, string[]>} spec - Ability actions to request for root or named models.
1185
+ * @returns {this} - This query for chaining.
1186
1186
  */
1187
1187
  abilities(spec) {
1188
1188
  for (const entry of normalizeAbilitiesSpec(spec, this.modelClass)) {
@@ -1194,7 +1194,7 @@ export default class FrontendModelQuery {
1194
1194
 
1195
1195
  /**
1196
1196
  * Runs merge ability entry.
1197
- * @param {{modelName: string, actions: string[]}} entry
1197
+ * @param {{modelName: string, actions: string[]}} entry - Normalized model ability request to append.
1198
1198
  * @returns {void}
1199
1199
  */
1200
1200
  _mergeAbilityEntry(entry) {
@@ -1215,8 +1215,8 @@ export default class FrontendModelQuery {
1215
1215
  * counts to each returned record. Parses the same shapes as the
1216
1216
  * backend `ModelClassQuery#withCount`, then ships the normalized
1217
1217
  * entries as part of the `index` command payload.
1218
- * @param {string | string[] | Record<string, boolean | {relationship?: string, where?: Record<string, ?>}>} spec
1219
- * @returns {this}
1218
+ * @param {string | string[] | Record<string, boolean | {relationship?: string, where?: Record<string, ?>}>} spec - Relationships whose counts should be serialized.
1219
+ * @returns {this} - This query for chaining.
1220
1220
  */
1221
1221
  withCount(spec) {
1222
1222
  for (const entry of normalizeWithCountFrontend(spec)) {
@@ -1233,8 +1233,8 @@ export default class FrontendModelQuery {
1233
1233
  * frontend ships only these names; the SQL fragments stay server-
1234
1234
  * side. All resulting aliases are attached to the root record and
1235
1235
  * read back with `record.queryData(aliasName)`.
1236
- * @param {string | Array<string | Record<string, ?>> | Record<string, ?>} spec
1237
- * @returns {this}
1236
+ * @param {string | Array<string | Record<string, ?>> | Record<string, ?>} spec - Backend query-data names and arguments to serialize.
1237
+ * @returns {this} - This query for chaining.
1238
1238
  */
1239
1239
  queryData(spec) {
1240
1240
  if (spec == null) return this
@@ -2,7 +2,6 @@
2
2
 
3
3
  import crypto from "crypto"
4
4
  import fs from "node:fs/promises"
5
- import {createReadStream} from "node:fs"
6
5
  import {digg} from "diggerize"
7
6
  import {ensureError} from "typanic"
8
7
  import EventEmitter from "../../utils/event-emitter.js"
@@ -58,6 +57,9 @@ export default class VeoliciousHttpServerClient {
58
57
  * Narrows the runtime value to the documented type.
59
58
  * @type {RequestRunner[]} */
60
59
  this.requestRunners = []
60
+
61
+ /** @type {Set<(result: "completed" | "aborted") => Promise<void>>} */
62
+ this.pendingFileResponses = new Set()
61
63
  }
62
64
 
63
65
  /**
@@ -355,6 +357,7 @@ export default class VeoliciousHttpServerClient {
355
357
  const response = digg(requestRunner, "response")
356
358
  const request = requestRunner.getRequest()
357
359
  const filePath = response.getFilePath()
360
+ const fileOnFinished = response.getFileOnFinished()
358
361
  const date = new Date()
359
362
  const connectionHeader = request.header("connection")?.toLowerCase()?.trim()
360
363
  const httpVersion = request.httpVersion()
@@ -423,8 +426,9 @@ export default class VeoliciousHttpServerClient {
423
426
 
424
427
  if (isBodylessStatus) {
425
428
  this.logger.debug(() => ["sendResponse body suppressed for no-body status", {clientCount: this.clientCount, statusCode: response.getStatusCode()}])
429
+ if (hasFilePath) await this.sendFileOutput(filePath, false, fileOnFinished)
426
430
  } else if (hasFilePath) {
427
- await this.sendFileOutput(filePath)
431
+ await this.sendFileOutput(filePath, true, fileOnFinished)
428
432
  } else {
429
433
  this.events.emit("output", body)
430
434
  this.logger.debug(() => ["sendResponse body emitted", {clientCount: this.clientCount, bodyLength: bodyIsString ? body.length : body.byteLength}])
@@ -441,27 +445,69 @@ export default class VeoliciousHttpServerClient {
441
445
  /**
442
446
  * Runs send file output.
443
447
  * @param {string} filePath - File path.
448
+ * @param {boolean} sendBody - Whether the file body should be sent.
449
+ * @param {((result: "completed" | "aborted") => void | Promise<void>) | null} onFinished - Completion callback.
444
450
  * @returns {Promise<void>} - Resolves when complete.
445
451
  */
446
- async sendFileOutput(filePath) {
452
+ async sendFileOutput(filePath, sendBody, onFinished) {
447
453
  this.logger.debug(() => ["sendFileOutput start", {clientCount: this.clientCount, filePath}])
448
- let totalBytes = 0
449
- let chunkCount = 0
454
+
455
+ const result = await new Promise((resolve) => {
456
+ /** @type {Promise<void> | null} */
457
+ let settlement = null
458
+ const settle = (/** @type {"completed" | "aborted"} */ transferResult) => {
459
+ if (settlement) return settlement
460
+
461
+ this.pendingFileResponses.delete(settle)
462
+ settlement = this.runFileOnFinished({filePath, onFinished, result: transferResult})
463
+ .finally(() => resolve(transferResult))
464
+
465
+ return settlement
466
+ }
467
+
468
+ this.pendingFileResponses.add(settle)
469
+ this.events.emit("file", {filePath, sendBody, settle})
470
+ })
471
+
472
+ this.logger.debug(() => ["sendFileOutput done", {clientCount: this.clientCount, filePath, result}])
473
+ }
474
+
475
+ /**
476
+ * Runs a file completion callback without allowing cleanup failures to replace the committed response.
477
+ * @param {object} args - Completion details.
478
+ * @param {string} args.filePath - File path.
479
+ * @param {((result: "completed" | "aborted") => void | Promise<void>) | null} args.onFinished - Completion callback.
480
+ * @param {"completed" | "aborted"} args.result - Transfer result.
481
+ * @returns {Promise<void>} - Resolves after callback cleanup and error reporting finish.
482
+ */
483
+ async runFileOnFinished({filePath, onFinished, result}) {
484
+ if (!onFinished) return
450
485
 
451
486
  try {
452
- for await (const chunk of createReadStream(filePath)) {
453
- chunkCount += 1
454
- totalBytes += chunk.length
455
- this.logger.debug(() => ["sendFileOutput chunk", {clientCount: this.clientCount, chunkCount, chunkLength: chunk.length, totalBytes}])
456
- this.events.emit("output", chunk)
487
+ await onFinished(result)
488
+ } catch (caughtError) {
489
+ const error = ensureError(caughtError)
490
+
491
+ await this.logger.error(() => ["File response onFinished callback failed", {clientCount: this.clientCount, filePath, result}, error])
492
+
493
+ const errorPayload = {
494
+ context: {clientCount: this.clientCount, filePath, result, stage: "send-file-on-finished"},
495
+ error
457
496
  }
458
- this.logger.debug(() => ["sendFileOutput done", {clientCount: this.clientCount, chunkCount, totalBytes}])
459
- } catch (error) {
460
- this.logger.error(() => [`Velocious client ${this.clientCount} failed while streaming file output: ${filePath}`, error])
461
- throw error
497
+
498
+ this.configuration.getErrorEvents().emit("framework-error", errorPayload)
499
+ this.configuration.getErrorEvents().emit("all-error", {...errorPayload, errorType: "framework-error"})
462
500
  }
463
501
  }
464
502
 
503
+ /**
504
+ * Aborts all file responses awaiting transport acknowledgement.
505
+ * @returns {Promise<void>} - Resolves after pending callbacks settle.
506
+ */
507
+ async abortPendingFileResponses() {
508
+ await Promise.all([...this.pendingFileResponses].map((settle) => settle("aborted")))
509
+ }
510
+
465
511
  /**
466
512
  * Runs should close connection.
467
513
  * @param {import("./request.js").default | import("./websocket-request.js").default} request - Request object.
@@ -488,7 +534,7 @@ export default class VeoliciousHttpServerClient {
488
534
  * cannot carry a message body: every 1xx informational, 204 No
489
535
  * Content, and 304 Not Modified.
490
536
  * @param {number} statusCode - HTTP status code.
491
- * @returns {boolean}
537
+ * @returns {boolean} - Whether the status code forbids a response body.
492
538
  */
493
539
  function isNoBodyStatusCode(statusCode) {
494
540
  return (statusCode >= 100 && statusCode < 200) || statusCode === 204 || statusCode === 304
@@ -135,7 +135,13 @@ export default class VelociousHttpServerClientRequestRunner {
135
135
  this.requestTiming.startedAtMs = Date.now()
136
136
 
137
137
  return await this.configuration.runWithRequestTiming(this.requestTiming, async () => {
138
- await this._run()
138
+ // Run the whole request inside any per-test shared connection context so an
139
+ // in-process handler executes on the test's connection (and open transaction).
140
+ // No shared connection is set outside tests / in worker threads, so this is a
141
+ // no-op there.
142
+ await this.configuration.runWithTestSharedConnectionContexts(async () => {
143
+ await this._run()
144
+ })
139
145
  })
140
146
  }
141
147
 
@@ -88,6 +88,11 @@ export default class VelociousHttpServerClientResponse {
88
88
  * @type {string | null} */
89
89
  filePath = null
90
90
 
91
+ /**
92
+ * File response completion callback.
93
+ * @type {((result: "completed" | "aborted") => void | Promise<void>) | null} */
94
+ fileOnFinished = null
95
+
91
96
  /**
92
97
  * Headers.
93
98
  * @type {Record<string, string[]>} */
@@ -163,6 +168,7 @@ export default class VelociousHttpServerClientResponse {
163
168
  */
164
169
  setBody(value) {
165
170
  this.filePath = null
171
+ this.fileOnFinished = null
166
172
  this.body = value
167
173
  }
168
174
 
@@ -174,13 +180,23 @@ export default class VelociousHttpServerClientResponse {
174
180
  return this.filePath
175
181
  }
176
182
 
183
+ /**
184
+ * Gets the file response completion callback.
185
+ * @returns {((result: "completed" | "aborted") => void | Promise<void>) | null} - File response completion callback.
186
+ */
187
+ getFileOnFinished() {
188
+ return this.fileOnFinished
189
+ }
190
+
177
191
  /**
178
192
  * Runs set file path.
179
193
  * @param {string} path - File path.
194
+ * @param {((result: "completed" | "aborted") => void | Promise<void>) | null} [onFinished] - Completion callback.
180
195
  * @returns {void} - No return value.
181
196
  */
182
- setFilePath(path) {
197
+ setFilePath(path, onFinished = null) {
183
198
  this.filePath = path
199
+ this.fileOnFinished = onFinished
184
200
  this.body = null
185
201
  }
186
202
 
@@ -239,7 +239,7 @@ export default class VelociousHttpServerClientWebsocketSession {
239
239
  * Removes a closed connection from the session registry. Called by
240
240
  * `VelociousWebsocketConnection.close()` after it sends the final
241
241
  * `connection-closed` frame.
242
- * @param {string} connectionId
242
+ * @param {string} connectionId - Closed connection identifier to remove.
243
243
  * @returns {void}
244
244
  */
245
245
  _removeConnection(connectionId) {
@@ -450,7 +450,7 @@ export default class VelociousHttpServerClientWebsocketSession {
450
450
  * The actual message dispatch, extracted so
451
451
  * `configuration.getWebsocketAroundRequest()` can wrap it in any
452
452
  * per-request context (AsyncLocalStorage, tracing, etc.).
453
- * @param {WebsocketSessionMessage} message
453
+ * @param {WebsocketSessionMessage} message - Decoded client frame to dispatch by message type.
454
454
  * @returns {Promise<void>}
455
455
  */
456
456
  async _handleMessageInner(message) {
@@ -731,8 +731,8 @@ export default class VelociousHttpServerClientWebsocketSession {
731
731
  * fragmented message. Returns true when the fragment was accepted
732
732
  * and false when the per-message cap was hit and the socket has
733
733
  * been closed.
734
- * @param {Buffer} payload
735
- * @returns {boolean}
734
+ * @param {Buffer} payload - Continuation-frame bytes to append.
735
+ * @returns {boolean} - Whether the fragment was accepted.
736
736
  */
737
737
  _appendFragment(payload) {
738
738
  // Guard pushing first so `_enforceFragmentLimits` sees the final
@@ -750,7 +750,7 @@ export default class VelociousHttpServerClientWebsocketSession {
750
750
  * close frame, and tears the session down. Returns true when the
751
751
  * caller can continue processing, false when the session is being
752
752
  * closed.
753
- * @returns {boolean}
753
+ * @returns {boolean} - Whether fragment processing may continue.
754
754
  */
755
755
  _enforceFragmentLimits() {
756
756
  if (this._fragmentedPayloads === null) return true
@@ -1024,7 +1024,7 @@ export default class VelociousHttpServerClientWebsocketSession {
1024
1024
  * The returned promise is stored at pause time and awaited at
1025
1025
  * resume time so we can reject resume attempts from a different
1026
1026
  * authenticated caller (signed out, swapped user, expired cookie).
1027
- * @returns {Promise<?>}
1027
+ * @returns {Promise<?>} - Captured authenticated identity for resume validation.
1028
1028
  */
1029
1029
  async _captureResumeIdentity() {
1030
1030
  const resolver = this.configuration.getWebsocketSessionIdentityResolver?.()
@@ -1087,7 +1087,7 @@ export default class VelociousHttpServerClientWebsocketSession {
1087
1087
  * created one whose socket just connected) transfers state from
1088
1088
  * the paused session and instructs the client via
1089
1089
  * `session-resumed` or `session-gone`.
1090
- * @param {Record<string, ?>} message
1090
+ * @param {Record<string, ?>} message - Session-resume frame containing the paused session identifier.
1091
1091
  * @returns {Promise<void>}
1092
1092
  */
1093
1093
  async _handleSessionResume(message) {
@@ -1168,7 +1168,7 @@ export default class VelociousHttpServerClientWebsocketSession {
1168
1168
  * Fires `onClose(reason)` on every live app-defined connection, then
1169
1169
  * drops them from the registry. No network frame is sent — the
1170
1170
  * socket is already going away.
1171
- * @param {"session_destroyed" | "grace_expired" | "error"} reason
1171
+ * @param {"session_destroyed" | "grace_expired" | "error"} reason - Permanent teardown reason passed to each connection.
1172
1172
  * @returns {Promise<void>}
1173
1173
  */
1174
1174
  async _teardownConnections(reason) {
@@ -1194,7 +1194,7 @@ export default class VelociousHttpServerClientWebsocketSession {
1194
1194
  * registered connection class, stores it on `_connections`, and
1195
1195
  * fires `onConnect()`. Sends `connection-opened` on success or
1196
1196
  * `connection-error` on failure.
1197
- * @param {Record<string, ?>} message
1197
+ * @param {Record<string, ?>} message - Connection-open frame naming the connection type and identifier.
1198
1198
  * @returns {Promise<void>}
1199
1199
  */
1200
1200
  async _handleConnectionOpen(message) {
@@ -1242,7 +1242,7 @@ export default class VelociousHttpServerClientWebsocketSession {
1242
1242
 
1243
1243
  /**
1244
1244
  * Handles a `{type: "connection-message"}` from the client.
1245
- * @param {Record<string, ?>} message
1245
+ * @param {Record<string, ?>} message - Connection-message frame containing the target identifier and body.
1246
1246
  * @returns {Promise<void>}
1247
1247
  */
1248
1248
  async _handleConnectionMessage(message) {
@@ -1267,7 +1267,7 @@ export default class VelociousHttpServerClientWebsocketSession {
1267
1267
  /**
1268
1268
  * Handles a `{type: "connection-close"}` from the client — fires
1269
1269
  * `onClose("client_close")` and confirms with `connection-closed`.
1270
- * @param {Record<string, ?>} message
1270
+ * @param {Record<string, ?>} message - Connection-close frame containing the target identifier.
1271
1271
  * @returns {Promise<void>}
1272
1272
  */
1273
1273
  async _handleConnectionClose(message) {
@@ -1296,7 +1296,7 @@ export default class VelociousHttpServerClientWebsocketSession {
1296
1296
  * Handles `{type: "channel-subscribe"}` — runs `canSubscribe()`,
1297
1297
  * registers with the Configuration's global routing registry on
1298
1298
  * success, and sends `channel-subscribed` or `channel-error`.
1299
- * @param {Record<string, ?>} message
1299
+ * @param {Record<string, ?>} message - Channel-subscribe frame describing the requested subscription.
1300
1300
  * @returns {Promise<void>}
1301
1301
  */
1302
1302
  async _handleChannelSubscribe(message) {
@@ -1421,7 +1421,7 @@ export default class VelociousHttpServerClientWebsocketSession {
1421
1421
  /**
1422
1422
  * Handles `{type: "channel-unsubscribe"}` from the client — calls
1423
1423
  * `unsubscribed()` and sends `channel-unsubscribed`.
1424
- * @param {Record<string, ?>} message
1424
+ * @param {Record<string, ?>} message - Channel-unsubscribe frame containing the subscription identifier.
1425
1425
  * @returns {Promise<void>}
1426
1426
  */
1427
1427
  async _handleChannelUnsubscribe(message) {