velocious 1.0.562 → 1.0.564

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 (245) hide show
  1. package/README.md +5 -1
  2. package/build/background-jobs/cron-expression.js +12 -13
  3. package/build/background-jobs/main.js +6 -6
  4. package/build/background-jobs/runner-graceful-shutdown.js +2 -3
  5. package/build/background-jobs/scheduler.js +3 -3
  6. package/build/background-jobs/worker.js +8 -9
  7. package/build/beacon/client.js +3 -4
  8. package/build/configuration-types.js +1 -0
  9. package/build/configuration.js +44 -3
  10. package/build/database/drivers/sqlite/web-persistence.js +2 -5
  11. package/build/database/pool/async-tracked-multi-connection.js +65 -15
  12. package/build/database/pool/base.js +23 -7
  13. package/build/database/query/index.js +21 -22
  14. package/build/database/query/model-class-query.js +5 -6
  15. package/build/database/record/attachments/normalize-input.js +9 -10
  16. package/build/database/record/attachments/store.js +2 -2
  17. package/build/database/record/auditing.js +7 -8
  18. package/build/database/record/index.js +4 -5
  19. package/build/environment-handlers/node/cli/commands/generate/frontend-models.js +1 -2
  20. package/build/frontend-model-controller.js +49 -56
  21. package/build/frontend-models/base.js +17 -19
  22. package/build/frontend-models/outgoing-event-buffer.js +3 -4
  23. package/build/frontend-models/query.js +40 -44
  24. package/build/frontend-models/resource-definition.js +7 -8
  25. package/build/frontend-models/transport-serialization.js +10 -12
  26. package/build/frontend-models/websocket-channel.js +12 -13
  27. package/build/http-server/client/websocket-session.js +7 -8
  28. package/build/http-server/client-delivery-queue.js +167 -0
  29. package/build/http-server/server-client.js +9 -0
  30. package/build/http-server/websocket-event-log-store.js +4 -6
  31. package/build/http-server/websocket-events-host.js +42 -12
  32. package/build/http-server/worker-handler/in-process.js +53 -14
  33. package/build/http-server/worker-handler/index.js +88 -18
  34. package/build/http-server/worker-handler/worker-thread.js +2 -2
  35. package/build/plugins/sqljs-wasm-route.js +3 -5
  36. package/build/src/background-jobs/cron-expression.d.ts +0 -12
  37. package/build/src/background-jobs/cron-expression.d.ts.map +1 -1
  38. package/build/src/background-jobs/cron-expression.js +13 -13
  39. package/build/src/background-jobs/main.d.ts.map +1 -1
  40. package/build/src/background-jobs/main.js +7 -7
  41. package/build/src/background-jobs/runner-graceful-shutdown.d.ts +0 -6
  42. package/build/src/background-jobs/runner-graceful-shutdown.d.ts.map +1 -1
  43. package/build/src/background-jobs/runner-graceful-shutdown.js +3 -3
  44. package/build/src/background-jobs/scheduler.d.ts +0 -3
  45. package/build/src/background-jobs/scheduler.d.ts.map +1 -1
  46. package/build/src/background-jobs/scheduler.js +4 -4
  47. package/build/src/background-jobs/worker.d.ts +0 -8
  48. package/build/src/background-jobs/worker.d.ts.map +1 -1
  49. package/build/src/background-jobs/worker.js +9 -9
  50. package/build/src/beacon/client.d.ts +0 -4
  51. package/build/src/beacon/client.d.ts.map +1 -1
  52. package/build/src/beacon/client.js +4 -4
  53. package/build/src/configuration-types.d.ts +8 -0
  54. package/build/src/configuration-types.d.ts.map +1 -1
  55. package/build/src/configuration-types.js +2 -1
  56. package/build/src/configuration.d.ts +34 -1
  57. package/build/src/configuration.d.ts.map +1 -1
  58. package/build/src/configuration.js +40 -4
  59. package/build/src/database/drivers/sqlite/web-persistence.d.ts +0 -14
  60. package/build/src/database/drivers/sqlite/web-persistence.d.ts.map +1 -1
  61. package/build/src/database/drivers/sqlite/web-persistence.js +3 -3
  62. package/build/src/database/pool/async-tracked-multi-connection.d.ts +29 -1
  63. package/build/src/database/pool/async-tracked-multi-connection.d.ts.map +1 -1
  64. package/build/src/database/pool/async-tracked-multi-connection.js +57 -15
  65. package/build/src/database/pool/base.d.ts +53 -4
  66. package/build/src/database/pool/base.d.ts.map +1 -1
  67. package/build/src/database/pool/base.js +23 -6
  68. package/build/src/database/query/index.d.ts +18 -39
  69. package/build/src/database/query/index.d.ts.map +1 -1
  70. package/build/src/database/query/index.js +22 -22
  71. package/build/src/database/query/model-class-query.d.ts +0 -5
  72. package/build/src/database/query/model-class-query.d.ts.map +1 -1
  73. package/build/src/database/query/model-class-query.js +6 -6
  74. package/build/src/database/record/attachments/normalize-input.d.ts +0 -9
  75. package/build/src/database/record/attachments/normalize-input.d.ts.map +1 -1
  76. package/build/src/database/record/attachments/normalize-input.js +10 -10
  77. package/build/src/database/record/attachments/store.d.ts.map +1 -1
  78. package/build/src/database/record/attachments/store.js +2 -2
  79. package/build/src/database/record/auditing.d.ts +17 -24
  80. package/build/src/database/record/auditing.d.ts.map +1 -1
  81. package/build/src/database/record/auditing.js +8 -8
  82. package/build/src/database/record/index.d.ts +4 -4
  83. package/build/src/database/record/index.d.ts.map +1 -1
  84. package/build/src/database/record/index.js +5 -5
  85. package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts +0 -39
  86. package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts.map +1 -1
  87. package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.js +2 -2
  88. package/build/src/frontend-model-controller.d.ts +1 -1
  89. package/build/src/frontend-model-controller.d.ts.map +1 -1
  90. package/build/src/frontend-model-controller.js +50 -50
  91. package/build/src/frontend-models/base.d.ts.map +1 -1
  92. package/build/src/frontend-models/base.js +18 -18
  93. package/build/src/frontend-models/outgoing-event-buffer.d.ts.map +1 -1
  94. package/build/src/frontend-models/outgoing-event-buffer.js +3 -3
  95. package/build/src/frontend-models/query.d.ts +56 -37
  96. package/build/src/frontend-models/query.d.ts.map +1 -1
  97. package/build/src/frontend-models/query.js +39 -39
  98. package/build/src/frontend-models/resource-definition.d.ts +0 -7
  99. package/build/src/frontend-models/resource-definition.d.ts.map +1 -1
  100. package/build/src/frontend-models/resource-definition.js +8 -8
  101. package/build/src/frontend-models/transport-serialization.d.ts +0 -10
  102. package/build/src/frontend-models/transport-serialization.d.ts.map +1 -1
  103. package/build/src/frontend-models/transport-serialization.js +11 -11
  104. package/build/src/frontend-models/websocket-channel.d.ts +0 -12
  105. package/build/src/frontend-models/websocket-channel.d.ts.map +1 -1
  106. package/build/src/frontend-models/websocket-channel.js +13 -13
  107. package/build/src/http-server/client/websocket-session.d.ts.map +1 -1
  108. package/build/src/http-server/client/websocket-session.js +8 -8
  109. package/build/src/http-server/client-delivery-queue.d.ts +121 -0
  110. package/build/src/http-server/client-delivery-queue.d.ts.map +1 -0
  111. package/build/src/http-server/client-delivery-queue.js +152 -0
  112. package/build/src/http-server/server-client.d.ts +6 -0
  113. package/build/src/http-server/server-client.d.ts.map +1 -1
  114. package/build/src/http-server/server-client.js +10 -1
  115. package/build/src/http-server/websocket-event-log-store.d.ts +0 -14
  116. package/build/src/http-server/websocket-event-log-store.d.ts.map +1 -1
  117. package/build/src/http-server/websocket-event-log-store.js +5 -5
  118. package/build/src/http-server/websocket-events-host.d.ts +15 -4
  119. package/build/src/http-server/websocket-events-host.d.ts.map +1 -1
  120. package/build/src/http-server/websocket-events-host.js +39 -13
  121. package/build/src/http-server/worker-handler/in-process.d.ts +19 -1
  122. package/build/src/http-server/worker-handler/in-process.d.ts.map +1 -1
  123. package/build/src/http-server/worker-handler/in-process.js +50 -14
  124. package/build/src/http-server/worker-handler/index.d.ts +36 -4
  125. package/build/src/http-server/worker-handler/index.d.ts.map +1 -1
  126. package/build/src/http-server/worker-handler/index.js +82 -18
  127. package/build/src/http-server/worker-handler/worker-thread.js +3 -3
  128. package/build/src/plugins/sqljs-wasm-route.d.ts.map +1 -1
  129. package/build/src/plugins/sqljs-wasm-route.js +4 -4
  130. package/build/src/sync/conflict-strategy.d.ts +0 -15
  131. package/build/src/sync/conflict-strategy.d.ts.map +1 -1
  132. package/build/src/sync/conflict-strategy.js +3 -3
  133. package/build/src/sync/device-identity.d.ts +0 -45
  134. package/build/src/sync/device-identity.d.ts.map +1 -1
  135. package/build/src/sync/device-identity.js +4 -4
  136. package/build/src/sync/local-mutation-log.d.ts +0 -41
  137. package/build/src/sync/local-mutation-log.d.ts.map +1 -1
  138. package/build/src/sync/local-mutation-log.js +9 -9
  139. package/build/src/sync/offline-grant.d.ts +0 -26
  140. package/build/src/sync/offline-grant.d.ts.map +1 -1
  141. package/build/src/sync/offline-grant.js +4 -4
  142. package/build/src/sync/peer-mutation-bundle.d.ts +0 -14
  143. package/build/src/sync/peer-mutation-bundle.d.ts.map +1 -1
  144. package/build/src/sync/peer-mutation-bundle.js +3 -3
  145. package/build/src/sync/server-change-feed.d.ts.map +1 -1
  146. package/build/src/sync/server-change-feed.js +33 -33
  147. package/build/src/sync/sync-api-client.d.ts +0 -9
  148. package/build/src/sync/sync-api-client.d.ts.map +1 -1
  149. package/build/src/sync/sync-api-client.js +2 -2
  150. package/build/src/sync/sync-envelope-replay-service.d.ts +38 -21
  151. package/build/src/sync/sync-envelope-replay-service.d.ts.map +1 -1
  152. package/build/src/sync/sync-envelope-replay-service.js +18 -18
  153. package/build/src/sync/sync-resource-base.d.ts +0 -7
  154. package/build/src/sync/sync-resource-base.d.ts.map +1 -1
  155. package/build/src/sync/sync-resource-base.js +2 -2
  156. package/build/src/sync/sync-scope-store.d.ts +0 -9
  157. package/build/src/sync/sync-scope-store.d.ts.map +1 -1
  158. package/build/src/sync/sync-scope-store.js +10 -10
  159. package/build/src/testing/test-filter-parser.d.ts +0 -10
  160. package/build/src/testing/test-filter-parser.d.ts.map +1 -1
  161. package/build/src/testing/test-filter-parser.js +11 -11
  162. package/build/src/testing/test-runner.d.ts +25 -91
  163. package/build/src/testing/test-runner.d.ts.map +1 -1
  164. package/build/src/testing/test-runner.js +113 -98
  165. package/build/src/testing/test.d.ts +8 -0
  166. package/build/src/testing/test.d.ts.map +1 -1
  167. package/build/src/testing/test.js +9 -9
  168. package/build/src/utils/backtrace-cleaner.d.ts.map +1 -1
  169. package/build/src/utils/backtrace-cleaner.js +9 -9
  170. package/build/src/utils/model-scope.d.ts +0 -8
  171. package/build/src/utils/model-scope.d.ts.map +1 -1
  172. package/build/src/utils/model-scope.js +3 -3
  173. package/build/src/utils/ransack.d.ts +52 -19
  174. package/build/src/utils/ransack.d.ts.map +1 -1
  175. package/build/src/utils/ransack.js +26 -26
  176. package/build/sync/conflict-strategy.js +3 -6
  177. package/build/sync/device-identity.js +4 -9
  178. package/build/sync/local-mutation-log.js +9 -14
  179. package/build/sync/offline-grant.js +4 -7
  180. package/build/sync/peer-mutation-bundle.js +2 -4
  181. package/build/sync/server-change-feed.js +32 -34
  182. package/build/sync/sync-api-client.js +1 -2
  183. package/build/sync/sync-envelope-replay-service.js +17 -19
  184. package/build/sync/sync-resource-base.js +1 -2
  185. package/build/sync/sync-scope-store.js +9 -10
  186. package/build/testing/test-filter-parser.js +10 -11
  187. package/build/testing/test-runner.js +116 -108
  188. package/build/testing/test.js +8 -9
  189. package/build/tsconfig.tsbuildinfo +1 -1
  190. package/build/utils/backtrace-cleaner.js +8 -9
  191. package/build/utils/model-scope.js +3 -4
  192. package/build/utils/ransack.js +27 -34
  193. package/package.json +3 -1
  194. package/scripts/eslint-rules/typedefs-first.js +67 -0
  195. package/scripts/prewarm-chromedriver.js +26 -0
  196. package/src/background-jobs/cron-expression.js +12 -13
  197. package/src/background-jobs/main.js +6 -6
  198. package/src/background-jobs/runner-graceful-shutdown.js +2 -3
  199. package/src/background-jobs/scheduler.js +3 -3
  200. package/src/background-jobs/worker.js +8 -9
  201. package/src/beacon/client.js +3 -4
  202. package/src/configuration-types.js +1 -0
  203. package/src/configuration.js +44 -3
  204. package/src/database/drivers/sqlite/web-persistence.js +2 -5
  205. package/src/database/pool/async-tracked-multi-connection.js +65 -15
  206. package/src/database/pool/base.js +23 -7
  207. package/src/database/query/index.js +21 -22
  208. package/src/database/query/model-class-query.js +5 -6
  209. package/src/database/record/attachments/normalize-input.js +9 -10
  210. package/src/database/record/attachments/store.js +2 -2
  211. package/src/database/record/auditing.js +7 -8
  212. package/src/database/record/index.js +4 -5
  213. package/src/environment-handlers/node/cli/commands/generate/frontend-models.js +1 -2
  214. package/src/frontend-model-controller.js +49 -56
  215. package/src/frontend-models/base.js +17 -19
  216. package/src/frontend-models/outgoing-event-buffer.js +3 -4
  217. package/src/frontend-models/query.js +40 -44
  218. package/src/frontend-models/resource-definition.js +7 -8
  219. package/src/frontend-models/transport-serialization.js +10 -12
  220. package/src/frontend-models/websocket-channel.js +12 -13
  221. package/src/http-server/client/websocket-session.js +7 -8
  222. package/src/http-server/client-delivery-queue.js +167 -0
  223. package/src/http-server/server-client.js +9 -0
  224. package/src/http-server/websocket-event-log-store.js +4 -6
  225. package/src/http-server/websocket-events-host.js +42 -12
  226. package/src/http-server/worker-handler/in-process.js +53 -14
  227. package/src/http-server/worker-handler/index.js +88 -18
  228. package/src/http-server/worker-handler/worker-thread.js +2 -2
  229. package/src/plugins/sqljs-wasm-route.js +3 -5
  230. package/src/sync/conflict-strategy.js +3 -6
  231. package/src/sync/device-identity.js +4 -9
  232. package/src/sync/local-mutation-log.js +9 -14
  233. package/src/sync/offline-grant.js +4 -7
  234. package/src/sync/peer-mutation-bundle.js +2 -4
  235. package/src/sync/server-change-feed.js +32 -34
  236. package/src/sync/sync-api-client.js +1 -2
  237. package/src/sync/sync-envelope-replay-service.js +17 -19
  238. package/src/sync/sync-resource-base.js +1 -2
  239. package/src/sync/sync-scope-store.js +9 -10
  240. package/src/testing/test-filter-parser.js +10 -11
  241. package/src/testing/test-runner.js +116 -108
  242. package/src/testing/test.js +8 -9
  243. package/src/utils/backtrace-cleaner.js +8 -9
  244. package/src/utils/model-scope.js +3 -4
  245. package/src/utils/ransack.js +27 -34
@@ -5,28 +5,6 @@ import {normalizeRansackGroup, parseRansackSort} from "../utils/ransack.js"
5
5
  import {isModelScopeDescriptor} from "../utils/model-scope.js"
6
6
  import isPlainObject from "../utils/plain-object.js"
7
7
 
8
- /** Error raised when a frontend-model query descriptor is malformed. */
9
- export class FrontendModelQueryError extends Error {
10
- /**
11
- * Creates a frontend-model query error.
12
- * @param {string} message - Error message.
13
- */
14
- constructor(message) {
15
- super(message)
16
-
17
- this.name = "FrontendModelQueryError"
18
- }
19
- }
20
-
21
- /**
22
- * Builds a query descriptor error.
23
- * @param {string} message - Error message.
24
- * @returns {FrontendModelQueryError} - Query descriptor error.
25
- */
26
- function frontendModelQueryError(message) {
27
- return new FrontendModelQueryError(message)
28
- }
29
-
30
8
  /**
31
9
  * FrontendModelSearch type.
32
10
  * @typedef {object} FrontendModelSearch
@@ -97,6 +75,46 @@ function frontendModelQueryError(message) {
97
75
  * @property {FrontendModelEventFilterPayload | null} eventFilterPayload - Normalized event filter payload, or null when unfiltered.
98
76
  * @property {FrontendModelProjectionPayload} projectionPayload - Normalized event serialization projection payload.
99
77
  */
78
+ /**
79
+ * FrontendModelSort type.
80
+ * @typedef {object} FrontendModelSort
81
+ * @property {string} column - Attribute name to sort by.
82
+ * @property {"asc" | "desc"} direction - Sort direction.
83
+ * @property {string[]} path - Relationship path from root model.
84
+ */
85
+ /**
86
+ * FrontendModelGroup type.
87
+ * @typedef {object} FrontendModelGroup
88
+ * @property {string} column - Attribute name to group by.
89
+ * @property {string[]} path - Relationship path from root model.
90
+ */
91
+ /**
92
+ * FrontendModelPluck type.
93
+ * @typedef {object} FrontendModelPluck
94
+ * @property {string} column - Attribute name to pluck.
95
+ * @property {string[]} path - Relationship path from root model.
96
+ */
97
+ /** Error raised when a frontend-model query descriptor is malformed. */
98
+ export class FrontendModelQueryError extends Error {
99
+ /**
100
+ * Creates a frontend-model query error.
101
+ * @param {string} message - Error message.
102
+ */
103
+ constructor(message) {
104
+ super(message)
105
+
106
+ this.name = "FrontendModelQueryError"
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Builds a query descriptor error.
112
+ * @param {string} message - Error message.
113
+ * @returns {FrontendModelQueryError} - Query descriptor error.
114
+ */
115
+ function frontendModelQueryError(message) {
116
+ return new FrontendModelQueryError(message)
117
+ }
100
118
 
101
119
  /**
102
120
  * Runs the normalizePreload helper.
@@ -490,28 +508,6 @@ export function normalizeJoins(joins) {
490
508
  return normalized
491
509
  }
492
510
 
493
- /**
494
- * FrontendModelSort type.
495
- * @typedef {object} FrontendModelSort
496
- * @property {string} column - Attribute name to sort by.
497
- * @property {"asc" | "desc"} direction - Sort direction.
498
- * @property {string[]} path - Relationship path from root model.
499
- */
500
-
501
- /**
502
- * FrontendModelGroup type.
503
- * @typedef {object} FrontendModelGroup
504
- * @property {string} column - Attribute name to group by.
505
- * @property {string[]} path - Relationship path from root model.
506
- */
507
-
508
- /**
509
- * FrontendModelPluck type.
510
- * @typedef {object} FrontendModelPluck
511
- * @property {string} column - Attribute name to pluck.
512
- * @property {string[]} path - Relationship path from root model.
513
- */
514
-
515
511
  /**
516
512
  * Runs normalize sort direction.
517
513
  * @param {?} direction - Direction value.
@@ -5,6 +5,13 @@ import FrontendModelBaseResource from "../frontend-model-resource/base-resource.
5
5
  import restArgsError from "../utils/rest-args-error.js"
6
6
  import {validateFrontendModelResourceCommandName} from "./resource-config-validation.js"
7
7
 
8
+ /**
9
+ * Resolved frontend-model resource registration for a replay resource type.
10
+ * @typedef {object} FrontendModelResolvedResourceRegistration
11
+ * @property {string} modelName - Effective frontend model name (modelName override or registry key).
12
+ * @property {import("../configuration-types.js").FrontendModelResourceClassType} resourceClass - Registered resource class.
13
+ * @property {import("../configuration-types.js").NormalizedFrontendModelResourceConfiguration} resourceConfiguration - Normalized resource configuration.
14
+ */
8
15
  const BASE_FRONTEND_MODEL_ABILITY_ACTIONS = ["create", "destroy", "read", "update"]
9
16
  const SHA256_INITIAL_HASH = [
10
17
  0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
@@ -1027,14 +1034,6 @@ function normalizeFrontendModelResourcePathForMatch(path) {
1027
1034
  return withLeadingSlash
1028
1035
  }
1029
1036
 
1030
- /**
1031
- * Resolved frontend-model resource registration for a replay resource type.
1032
- * @typedef {object} FrontendModelResolvedResourceRegistration
1033
- * @property {string} modelName - Effective frontend model name (modelName override or registry key).
1034
- * @property {import("../configuration-types.js").FrontendModelResourceClassType} resourceClass - Registered resource class.
1035
- * @property {import("../configuration-types.js").NormalizedFrontendModelResourceConfiguration} resourceConfiguration - Normalized resource configuration.
1036
- */
1037
-
1038
1037
  /**
1039
1038
  * Resolves the registered frontend-model resource class for a resource type
1040
1039
  * across all backend projects. A resource's effective name is its
@@ -4,28 +4,26 @@ import {resolveFrontendModelClass} from "./model-registry.js"
4
4
  import isPlainObject from "../utils/plain-object.js"
5
5
  import {formatDateInTimeZone, validateTimeZone} from "../time-zone.js"
6
6
 
7
- const TYPE_KEY = "__velocious_type"
8
- const TYPE_DATE = "date"
9
- const TYPE_UNDEFINED = "undefined"
10
- const TYPE_BIGINT = "bigint"
11
- const TYPE_NUMBER = "number"
12
- const TYPE_FRONTEND_MODEL = "frontend_model"
13
- const NUMBER_NAN = "NaN"
14
- const NUMBER_POSITIVE_INFINITY = "Infinity"
15
- const NUMBER_NEGATIVE_INFINITY = "-Infinity"
16
- const PRELOADED_RELATIONSHIPS_KEY = "__preloadedRelationships"
17
-
18
7
  /**
19
8
  * Frontend model transport serialization options.
20
9
  * @typedef {object} FrontendModelTransportSerializationOptions
21
10
  * @property {string | undefined} [timeZone] - IANA timezone used when serializing Date instants.
22
11
  */
23
-
24
12
  /**
25
13
  * Normalized frontend model transport serialization options.
26
14
  * @typedef {object} NormalizedFrontendModelTransportSerializationOptions
27
15
  * @property {string | undefined} timeZone - Validated IANA timezone used when serializing Date instants.
28
16
  */
17
+ const TYPE_KEY = "__velocious_type"
18
+ const TYPE_DATE = "date"
19
+ const TYPE_UNDEFINED = "undefined"
20
+ const TYPE_BIGINT = "bigint"
21
+ const TYPE_NUMBER = "number"
22
+ const TYPE_FRONTEND_MODEL = "frontend_model"
23
+ const NUMBER_NAN = "NaN"
24
+ const NUMBER_POSITIVE_INFINITY = "Infinity"
25
+ const NUMBER_NEGATIVE_INFINITY = "-Infinity"
26
+ const PRELOADED_RELATIONSHIPS_KEY = "__preloadedRelationships"
29
27
 
30
28
  /**
31
29
  * Assign a key to a plain object without triggering the `__proto__` setter.
@@ -4,6 +4,18 @@ import VelociousWebsocketChannel from "../http-server/websocket-channel.js"
4
4
  import Response from "../http-server/client/response.js"
5
5
  import {serializeFrontendModelTransportValue} from "./transport-serialization.js"
6
6
 
7
+ /**
8
+ * Defines this typedef.
9
+ * @typedef {{action?: string, id?: string | number, matchedEventFilterKeys?: string[], record?: import("./query.js").FrontendModelTransportValue, [key: string]: import("./query.js").FrontendModelTransportValue | string[] | undefined}} FrontendModelLifecycleBroadcastBody
10
+ */
11
+ /**
12
+ * Defines this typedef.
13
+ * @typedef {{headers?: () => Record<string, string | string[] | undefined>, remoteAddress?: () => string | undefined}} FrontendModelWebsocketUpgradeRequest
14
+ */
15
+ /**
16
+ * Defines this typedef.
17
+ * @typedef {{headers: () => Record<string, string | string[] | undefined>, header: (name: string) => string | string[] | undefined, metadata: (key?: string) => Record<string, import("./query.js").FrontendModelTransportValue> | import("./query.js").FrontendModelTransportValue | undefined, path: () => string, httpMethod: () => string, remoteAddress: () => string | undefined, origin: () => string | string[] | undefined}} FrontendModelWebsocketSyntheticRequest
18
+ */
7
19
  const EVENT_FILTER_KEYS = new Set(["joins", "key", "searches", "where"])
8
20
 
9
21
  // Mirrors FRONTEND_MODELS_CHANNEL_NAME in ./websocket-publishers.js, duplicated here
@@ -21,19 +33,6 @@ function transportSerializationOptionsForConfiguration(configuration) {
21
33
  }
22
34
  }
23
35
 
24
- /**
25
- * Defines this typedef.
26
- * @typedef {{action?: string, id?: string | number, matchedEventFilterKeys?: string[], record?: import("./query.js").FrontendModelTransportValue, [key: string]: import("./query.js").FrontendModelTransportValue | string[] | undefined}} FrontendModelLifecycleBroadcastBody
27
- */
28
- /**
29
- * Defines this typedef.
30
- * @typedef {{headers?: () => Record<string, string | string[] | undefined>, remoteAddress?: () => string | undefined}} FrontendModelWebsocketUpgradeRequest
31
- */
32
- /**
33
- * Defines this typedef.
34
- * @typedef {{headers: () => Record<string, string | string[] | undefined>, header: (name: string) => string | string[] | undefined, metadata: (key?: string) => Record<string, import("./query.js").FrontendModelTransportValue> | import("./query.js").FrontendModelTransportValue | undefined, path: () => string, httpMethod: () => string, remoteAddress: () => string | undefined, origin: () => string | string[] | undefined}} FrontendModelWebsocketSyntheticRequest
35
- */
36
-
37
36
  /**
38
37
  * Per-session channel subscription for frontend-model lifecycle events.
39
38
  * Replaces the legacy `FrontendModelWebsocketChannel` (Phase 3).
@@ -9,6 +9,10 @@ import WebsocketRequest from "./websocket-request.js"
9
9
  import WebsocketChannel from "../websocket-channel.js"
10
10
  import {websocketEventLogStoreForConfiguration} from "../websocket-event-log-store.js"
11
11
 
12
+ /**
13
+ * Defines this typedef.
14
+ * @typedef {{type: "subscribe", channel: string, lastEventId?: string, params?: Record<string, ?>} | {type: "metadata", data?: Record<string, ?>} | {type?: "request", body?: ?, headers?: Record<string, ?>, id?: string | number | null, method: string, path: string} | Record<string, ?>} WebsocketSessionMessage
15
+ */
12
16
  const WEBSOCKET_FINAL_FRAME = 0x80
13
17
  const WEBSOCKET_OPCODE_CONTINUATION = 0x0
14
18
  const WEBSOCKET_OPCODE_TEXT = 0x1
@@ -31,11 +35,6 @@ const WEBSOCKET_MAX_INBOUND_FRAME_BYTES_BIGINT = BigInt(WEBSOCKET_MAX_FINAL_FRAM
31
35
  /** Cap on fragment count for a single fragmented message. */
32
36
  const WEBSOCKET_MAX_FRAGMENTED_MESSAGE_FRAGMENTS = 1024
33
37
 
34
- /**
35
- * Defines this typedef.
36
- * @typedef {{type: "subscribe", channel: string, lastEventId?: string, params?: Record<string, ?>} | {type: "metadata", data?: Record<string, ?>} | {type?: "request", body?: ?, headers?: Record<string, ?>, id?: string | number | null, method: string, path: string} | Record<string, ?>} WebsocketSessionMessage
37
- */
38
-
39
38
  /**
40
39
  * Runs subscribe message.
41
40
  * @param {WebsocketSessionMessage} message - Raw websocket message.
@@ -423,7 +422,7 @@ export default class VelociousHttpServerClientWebsocketSession {
423
422
  sendGoodbye(client) {
424
423
  const frame = Buffer.from([WEBSOCKET_FINAL_FRAME | WEBSOCKET_OPCODE_CLOSE, 0x00])
425
424
 
426
- client.events.emit("output", frame)
425
+ client.events.emit("output", frame, {websocketFrame: true})
427
426
  }
428
427
 
429
428
  /**
@@ -967,7 +966,7 @@ export default class VelociousHttpServerClientWebsocketSession {
967
966
  header[0] = WEBSOCKET_FINAL_FRAME | opcode
968
967
  header[1] = payload.length
969
968
 
970
- this.client.events.emit("output", Buffer.concat([header, payload]))
969
+ this.client.events.emit("output", Buffer.concat([header, payload]), {websocketFrame: true})
971
970
  }
972
971
 
973
972
  /**
@@ -1012,7 +1011,7 @@ export default class VelociousHttpServerClientWebsocketSession {
1012
1011
 
1013
1012
  header[0] = WEBSOCKET_FINAL_FRAME | WEBSOCKET_OPCODE_TEXT
1014
1013
 
1015
- this.client.events.emit("output", Buffer.concat([header, payload]))
1014
+ this.client.events.emit("output", Buffer.concat([header, payload]), {websocketFrame: true})
1016
1015
  }
1017
1016
 
1018
1017
  /**
@@ -0,0 +1,167 @@
1
+ /**
2
+ * @typedef {object} DeliveryTask
3
+ * @property {number} byteLength - Retained complete-buffer bytes.
4
+ * @property {boolean} countedFrame - Whether this task is an outbound frame.
5
+ * @property {() => Promise<void>} delivery - Delivery operation.
6
+ * @property {(error?: Error) => void} settle - Settles the enqueue promise.
7
+ */
8
+ // @ts-check
9
+
10
+ export class ClientDeliveryQueueOverflowError extends Error {
11
+ /**
12
+ * Builds an outbound queue overflow error.
13
+ * @param {object} args - Overflow details.
14
+ * @param {number} args.clientCount - Client identifier.
15
+ * @param {number} args.maxBytes - Configured byte high-water mark.
16
+ * @param {number} args.maxFrames - Configured frame high-water mark.
17
+ * @param {number} args.pendingBytes - Bytes retained before rejecting the frame.
18
+ * @param {number} args.pendingFrames - Frames retained before rejecting the frame.
19
+ * @param {number} args.rejectedBytes - Rejected frame size.
20
+ */
21
+ constructor({clientCount, maxBytes, maxFrames, pendingBytes, pendingFrames, rejectedBytes}) {
22
+ super(`WebSocket client ${clientCount} exceeded its outbound queue limit (${pendingFrames}/${maxFrames} frames, ${pendingBytes}/${maxBytes} bytes; rejected ${rejectedBytes} bytes)`)
23
+ this.name = "ClientDeliveryQueueOverflowError"
24
+ }
25
+ }
26
+
27
+ export default class ClientDeliveryQueue {
28
+ /**
29
+ * Builds a per-client delivery queue.
30
+ * @param {object} args - Queue options.
31
+ * @param {number} args.clientCount - Client identifier.
32
+ * @param {number} args.maxBytes - Byte high-water mark.
33
+ * @param {number} args.maxFrames - Frame high-water mark.
34
+ * @param {(error: ClientDeliveryQueueOverflowError) => void} args.onOverflow - Overflow handler.
35
+ */
36
+ constructor({clientCount, maxBytes, maxFrames, onOverflow}) {
37
+ this.clientCount = clientCount
38
+ this.maxBytes = maxBytes
39
+ this.maxFrames = maxFrames
40
+ this.onOverflow = onOverflow
41
+ /** @type {DeliveryTask[]} */
42
+ this.tasks = []
43
+ /** @type {DeliveryTask | undefined} */
44
+ this.activeTask = undefined
45
+ this.pendingBytes = 0
46
+ this.pendingFrames = 0
47
+ this.destroyed = false
48
+ }
49
+
50
+ /**
51
+ * Enqueues one complete output buffer.
52
+ * @param {object} args - Delivery details.
53
+ * @param {number} args.byteLength - Exact buffer byte length.
54
+ * @param {() => Promise<void>} args.delivery - Delivery operation.
55
+ * @returns {Promise<void>} - Settles after delivery or teardown.
56
+ */
57
+ enqueueFrame({byteLength, delivery}) {
58
+ if (this.destroyed) return Promise.resolve()
59
+
60
+ if (this.pendingFrames + 1 > this.maxFrames || this.pendingBytes + byteLength > this.maxBytes) {
61
+ const error = new ClientDeliveryQueueOverflowError({
62
+ clientCount: this.clientCount,
63
+ maxBytes: this.maxBytes,
64
+ maxFrames: this.maxFrames,
65
+ pendingBytes: this.pendingBytes,
66
+ pendingFrames: this.pendingFrames,
67
+ rejectedBytes: byteLength
68
+ })
69
+
70
+ this.onOverflow(error)
71
+ return Promise.reject(error)
72
+ }
73
+
74
+ this.pendingFrames += 1
75
+ this.pendingBytes += byteLength
76
+ return this._enqueue({byteLength, countedFrame: true, delivery})
77
+ }
78
+
79
+ /**
80
+ * Enqueues an ordering-only operation that retains no complete output frame.
81
+ * @param {() => Promise<void>} delivery - Delivery operation.
82
+ * @returns {Promise<void>} - Settles after delivery or teardown.
83
+ */
84
+ enqueueControl(delivery) {
85
+ if (this.destroyed) return Promise.resolve()
86
+
87
+ return this._enqueue({byteLength: 0, countedFrame: false, delivery})
88
+ }
89
+
90
+ /**
91
+ * Releases queued and active accounting during explicit client teardown.
92
+ * @returns {void}
93
+ */
94
+ destroy() {
95
+ if (this.destroyed) return
96
+
97
+ this.destroyed = true
98
+ const tasks = this.activeTask ? [this.activeTask, ...this.tasks] : this.tasks
99
+
100
+ this.activeTask = undefined
101
+ this.tasks = []
102
+ this.pendingBytes = 0
103
+ this.pendingFrames = 0
104
+
105
+ for (const task of tasks) task.settle()
106
+ }
107
+
108
+ /**
109
+ * Gets current retained-buffer accounting.
110
+ * @returns {{pendingBytes: number, pendingFrames: number}} - Current retained-buffer accounting.
111
+ */
112
+ snapshot() {
113
+ return {pendingBytes: this.pendingBytes, pendingFrames: this.pendingFrames}
114
+ }
115
+
116
+ /**
117
+ * Enqueues a delivery task.
118
+ * @param {Omit<DeliveryTask, "settle">} task - Task to enqueue.
119
+ * @returns {Promise<void>} - Task completion.
120
+ */
121
+ _enqueue(task) {
122
+ const promise = new Promise((resolve, reject) => {
123
+ this.tasks.push({
124
+ ...task,
125
+ settle: (error) => error ? reject(error) : resolve(undefined)
126
+ })
127
+ })
128
+
129
+ this._drain()
130
+ return promise
131
+ }
132
+
133
+ /**
134
+ * Starts the next task when idle.
135
+ * @returns {void} - No return value.
136
+ */
137
+ _drain() {
138
+ if (this.destroyed || this.activeTask) return
139
+
140
+ const task = this.tasks.shift()
141
+ if (!task) return
142
+
143
+ this.activeTask = task
144
+ void task.delivery().then(
145
+ () => this._finish(task),
146
+ (error) => this._finish(task, error)
147
+ )
148
+ }
149
+
150
+ /**
151
+ * Finishes the active delivery task.
152
+ * @param {DeliveryTask} task - Completed task.
153
+ * @param {Error} [error] - Delivery error.
154
+ * @returns {void}
155
+ */
156
+ _finish(task, error) {
157
+ if (this.destroyed || this.activeTask !== task) return
158
+
159
+ this.activeTask = undefined
160
+ if (task.countedFrame) {
161
+ this.pendingBytes -= task.byteLength
162
+ this.pendingFrames -= 1
163
+ }
164
+ task.settle(error)
165
+ this._drain()
166
+ }
167
+ }
@@ -76,6 +76,15 @@ export default class ServerClient {
76
76
  })
77
77
  }
78
78
 
79
+ /**
80
+ * Immediately destroys the socket and all transport-owned write buffers.
81
+ * @param {Error} error - Destruction reason.
82
+ * @returns {void}
83
+ */
84
+ destroy(error) {
85
+ if (!this.socket.destroyed) this.socket.destroy(error)
86
+ }
87
+
79
88
  /**
80
89
  * On socket data.
81
90
  * @param {Buffer} chunk - Chunk.
@@ -4,11 +4,6 @@ import {randomUUID} from "crypto"
4
4
  import TableData from "../database/table-data/index.js"
5
5
  import Logger from "../logger.js"
6
6
 
7
- const EVENTS_TABLE = "websocket_channel_events"
8
- const REPLAY_CHANNELS_TABLE = "websocket_replay_channels"
9
- const DEFAULT_RETENTION_MS = 10 * 60 * 1000
10
- const stores = new WeakMap()
11
-
12
7
  /**
13
8
  * WebsocketEventRow type.
14
9
  * @typedef {object} WebsocketEventRow
@@ -18,12 +13,15 @@ const stores = new WeakMap()
18
13
  * @property {string} payload_json - Serialized payload.
19
14
  * @property {number | string} sequence - Sequence number.
20
15
  */
21
-
22
16
  /**
23
17
  * WebsocketReplayChannelRow type.
24
18
  * @typedef {object} WebsocketReplayChannelRow
25
19
  * @property {string} channel - Channel name.
26
20
  */
21
+ const EVENTS_TABLE = "websocket_channel_events"
22
+ const REPLAY_CHANNELS_TABLE = "websocket_replay_channels"
23
+ const DEFAULT_RETENTION_MS = 10 * 60 * 1000
24
+ const stores = new WeakMap()
27
25
 
28
26
  /**
29
27
  * Runs the websocketEventLogStoreForConfiguration helper.
@@ -8,6 +8,10 @@ export class VelociousHttpServerWebsocketEventsHost {
8
8
  * Narrows the runtime value to the documented type.
9
9
  * @type {Set<import("./worker-handler/index.js").default>} */
10
10
  this.handlers = new Set()
11
+ /**
12
+ * Broadcast handlers grouped by the configuration that owns them.
13
+ * @type {Map<import("../configuration.js").default, Set<import("./worker-handler/index.js").default>>} */
14
+ this.broadcastHandlersByConfiguration = new Map()
11
15
  this.publishQueue = Promise.resolve()
12
16
  }
13
17
 
@@ -30,8 +34,23 @@ export class VelociousHttpServerWebsocketEventsHost {
30
34
  */
31
35
  register(handler) {
32
36
  this.handlers.add(handler)
37
+ let configurationHandlers = this.broadcastHandlersByConfiguration.get(handler.configuration)
38
+
39
+ if (!configurationHandlers) {
40
+ configurationHandlers = new Set()
41
+ this.broadcastHandlersByConfiguration.set(handler.configuration, configurationHandlers)
42
+ }
43
+
44
+ configurationHandlers.add(handler)
45
+
46
+ return () => {
47
+ this.handlers.delete(handler)
48
+ configurationHandlers.delete(handler)
33
49
 
34
- return () => this.handlers.delete(handler)
50
+ if (configurationHandlers.size === 0) {
51
+ this.broadcastHandlersByConfiguration.delete(handler.configuration)
52
+ }
53
+ }
35
54
  }
36
55
 
37
56
  /**
@@ -69,17 +88,24 @@ export class VelociousHttpServerWebsocketEventsHost {
69
88
  * @param {string} args.channel - Channel name.
70
89
  * @param {Record<string, ?>} args.broadcastParams - Routing filter params.
71
90
  * @param {?} args.body - Message body.
91
+ * @param {import("../configuration.js").default} args.configuration - Originating configuration.
72
92
  * @returns {void}
73
93
  */
74
- broadcastV2({body, broadcastParams, channel}) {
94
+ broadcastV2({body, broadcastParams, channel, configuration}) {
75
95
  // Chain onto publishQueue so persistence completes before
76
96
  // the next broadcast — without this, a subscriber that connects
77
97
  // immediately after a broadcast could miss the just-persisted
78
98
  // event when replaying from lastEventId on a slow DB.
79
99
  this._queuePublish(async () => {
80
- const persistedEvent = await this._persistV2EventIfNeeded({body, channel})
100
+ const persistedEvent = await this._persistV2EventIfNeeded({body, channel, configuration})
101
+ const dispatchedTargets = new Set()
81
102
 
82
- for (const handler of this.handlers) {
103
+ for (const handler of this.broadcastHandlersByConfiguration.get(configuration) || []) {
104
+ const dispatchKey = handler.websocketV2BroadcastDispatchKey()
105
+
106
+ if (dispatchedTargets.has(dispatchKey)) continue
107
+
108
+ dispatchedTargets.add(dispatchKey)
83
109
  handler.dispatchWebsocketV2Broadcast({
84
110
  body,
85
111
  broadcastParams,
@@ -88,18 +114,19 @@ export class VelociousHttpServerWebsocketEventsHost {
88
114
  createdAt: persistedEvent?.createdAt
89
115
  })
90
116
  }
91
- }, "Failed to persist/broadcast V2 event")
117
+ }, "Failed to persist/broadcast V2 event", configuration)
92
118
  }
93
119
 
94
120
  /**
95
121
  * Runs queue publish.
96
122
  * @param {() => Promise<void>} callback - Publish work to run in order.
97
123
  * @param {string} errorMessage - Message logged when publish work fails.
124
+ * @param {import("../configuration.js").default} [originatingConfiguration] - Configuration whose context owns the work.
98
125
  * @returns {void}
99
126
  */
100
- _queuePublish(callback, errorMessage) {
127
+ _queuePublish(callback, errorMessage, originatingConfiguration) {
101
128
  const handler = this.handlers.values().next().value
102
- const configuration = handler?.configuration
129
+ const configuration = originatingConfiguration || handler?.configuration
103
130
 
104
131
  this.publishQueue = this.publishQueue
105
132
  .then(async () => {
@@ -120,10 +147,11 @@ export class VelociousHttpServerWebsocketEventsHost {
120
147
  * @param {object} args - Options.
121
148
  * @param {?} args.body - Event body.
122
149
  * @param {string} args.channel - Channel name.
150
+ * @param {import("../configuration.js").default} args.configuration - Originating configuration.
123
151
  * @returns {Promise<{createdAt: string, id: string} | null>} - Persisted event metadata when storage is enabled.
124
152
  */
125
- async _persistV2EventIfNeeded({body, channel}) {
126
- return await this._persistChannelEventIfNeeded({channel, payload: body})
153
+ async _persistV2EventIfNeeded({body, channel, configuration}) {
154
+ return await this._persistChannelEventIfNeeded({channel, payload: body, configuration})
127
155
  }
128
156
 
129
157
  /**
@@ -142,14 +170,16 @@ export class VelociousHttpServerWebsocketEventsHost {
142
170
  * @param {object} args - Options object.
143
171
  * @param {string} args.channel - Channel name.
144
172
  * @param {?} args.payload - Payload data.
173
+ * @param {import("../configuration.js").default} [args.configuration] - Configuration owning the event store.
145
174
  * @returns {Promise<{createdAt: string, id: string} | null>} - Persisted event metadata.
146
175
  */
147
- async _persistChannelEventIfNeeded({channel, payload}) {
176
+ async _persistChannelEventIfNeeded({channel, payload, configuration}) {
148
177
  const handler = this.handlers.values().next().value
178
+ const eventConfiguration = configuration || handler?.configuration
149
179
 
150
- if (!handler?.configuration) return null
180
+ if (!eventConfiguration) return null
151
181
 
152
- const websocketEventLogStore = websocketEventLogStoreForConfiguration(handler.configuration)
182
+ const websocketEventLogStore = websocketEventLogStoreForConfiguration(eventConfiguration)
153
183
  const shouldPersist = await websocketEventLogStore.shouldPersistChannel(channel)
154
184
 
155
185
  if (!shouldPersist) return null