@spine-event-engine/server 2.0.0-snapshot.2 → 2.0.0-snapshot.21

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 (227) hide show
  1. package/README.md +225 -518
  2. package/REFERENCE.md +498 -49
  3. package/dist/browser/browser-server.d.ts +95 -0
  4. package/dist/browser/browser-server.d.ts.map +1 -0
  5. package/dist/{server → browser}/browser-server.js +93 -7
  6. package/dist/browser/browser-server.js.map +1 -0
  7. package/dist/browser/durable-subscription-bindings.d.ts.map +1 -0
  8. package/dist/browser/durable-subscription-bindings.js.map +1 -0
  9. package/dist/browser/index.d.ts +12 -0
  10. package/dist/browser/index.d.ts.map +1 -0
  11. package/dist/browser/index.js +24 -0
  12. package/dist/browser/index.js.map +1 -0
  13. package/dist/browser/options.d.ts +183 -0
  14. package/dist/browser/options.d.ts.map +1 -0
  15. package/dist/browser/options.js +15 -0
  16. package/dist/browser/options.js.map +1 -0
  17. package/dist/bus/command-bus.d.ts +2 -0
  18. package/dist/bus/command-bus.d.ts.map +1 -1
  19. package/dist/bus/command-bus.js +36 -1
  20. package/dist/bus/command-bus.js.map +1 -1
  21. package/dist/bus/event-dispatcher-registry.d.ts +2 -2
  22. package/dist/bus/event-dispatcher-registry.js +3 -3
  23. package/dist/bus/event-dispatcher-registry.js.map +1 -1
  24. package/dist/context/bounded-context.d.ts +238 -83
  25. package/dist/context/bounded-context.d.ts.map +1 -1
  26. package/dist/context/bounded-context.js +1083 -286
  27. package/dist/context/bounded-context.js.map +1 -1
  28. package/dist/context/effective-tenant.d.ts +9 -0
  29. package/dist/context/effective-tenant.d.ts.map +1 -0
  30. package/dist/context/effective-tenant.js +58 -0
  31. package/dist/context/effective-tenant.js.map +1 -0
  32. package/dist/context/entity-inbox.d.ts.map +1 -1
  33. package/dist/context/entity-inbox.js +24 -17
  34. package/dist/context/entity-inbox.js.map +1 -1
  35. package/dist/context/local-inbox-handoff.d.ts +7 -3
  36. package/dist/context/local-inbox-handoff.d.ts.map +1 -1
  37. package/dist/context/local-inbox-handoff.js +32 -26
  38. package/dist/context/local-inbox-handoff.js.map +1 -1
  39. package/dist/context/projection-handoff.d.ts.map +1 -1
  40. package/dist/context/projection-handoff.js +36 -31
  41. package/dist/context/projection-handoff.js.map +1 -1
  42. package/dist/context/tenant-index.d.ts.map +1 -1
  43. package/dist/context/tenant-index.js +72 -2
  44. package/dist/context/tenant-index.js.map +1 -1
  45. package/dist/delivery/delivery-ports.d.ts +14 -4
  46. package/dist/delivery/delivery-ports.d.ts.map +1 -1
  47. package/dist/delivery/delivery.d.ts +19 -21
  48. package/dist/delivery/delivery.d.ts.map +1 -1
  49. package/dist/delivery/delivery.js +541 -158
  50. package/dist/delivery/delivery.js.map +1 -1
  51. package/dist/delivery/inbox-storage.d.ts +12 -3
  52. package/dist/delivery/inbox-storage.d.ts.map +1 -1
  53. package/dist/delivery/inbox-storage.js +160 -67
  54. package/dist/delivery/inbox-storage.js.map +1 -1
  55. package/dist/delivery/inbox.d.ts +9 -0
  56. package/dist/delivery/inbox.d.ts.map +1 -1
  57. package/dist/delivery/inbox.js +11 -0
  58. package/dist/delivery/inbox.js.map +1 -1
  59. package/dist/delivery/sharded-work-registry.d.ts.map +1 -1
  60. package/dist/delivery/sharded-work-registry.js +2 -1
  61. package/dist/delivery/sharded-work-registry.js.map +1 -1
  62. package/dist/entity/entity-metadata.d.ts +14 -5
  63. package/dist/entity/entity-metadata.d.ts.map +1 -1
  64. package/dist/entity/entity-metadata.js +38 -2
  65. package/dist/entity/entity-metadata.js.map +1 -1
  66. package/dist/entity/entity-storage-descriptor.d.ts +25 -5
  67. package/dist/entity/entity-storage-descriptor.d.ts.map +1 -1
  68. package/dist/entity/entity-storage-descriptor.js +69 -23
  69. package/dist/entity/entity-storage-descriptor.js.map +1 -1
  70. package/dist/entity/entity-transaction.d.ts +46 -42
  71. package/dist/entity/entity-transaction.d.ts.map +1 -1
  72. package/dist/entity/entity-transaction.js +130 -35
  73. package/dist/entity/entity-transaction.js.map +1 -1
  74. package/dist/entity/entity.d.ts +243 -98
  75. package/dist/entity/entity.d.ts.map +1 -1
  76. package/dist/entity/entity.js +405 -226
  77. package/dist/entity/entity.js.map +1 -1
  78. package/dist/entity/spec-scanner.d.ts +13 -1
  79. package/dist/entity/spec-scanner.d.ts.map +1 -1
  80. package/dist/entity/spec-scanner.js +20 -3
  81. package/dist/entity/spec-scanner.js.map +1 -1
  82. package/dist/handler/command-registration-readiness.d.ts +6 -6
  83. package/dist/handler/command-registration-readiness.d.ts.map +1 -1
  84. package/dist/handler/command-registration-readiness.js +4 -2
  85. package/dist/handler/command-registration-readiness.js.map +1 -1
  86. package/dist/handler/declared-rejections.d.ts +11 -0
  87. package/dist/handler/declared-rejections.d.ts.map +1 -0
  88. package/dist/handler/declared-rejections.js +34 -0
  89. package/dist/handler/declared-rejections.js.map +1 -0
  90. package/dist/handler/event-registration-readiness.d.ts +13 -56
  91. package/dist/handler/event-registration-readiness.d.ts.map +1 -1
  92. package/dist/handler/event-registration-readiness.js +23 -43
  93. package/dist/handler/event-registration-readiness.js.map +1 -1
  94. package/dist/handler/generated-handler-registry.d.ts +72 -33
  95. package/dist/handler/generated-handler-registry.d.ts.map +1 -1
  96. package/dist/handler/generated-handler-registry.js +421 -43
  97. package/dist/handler/generated-handler-registry.js.map +1 -1
  98. package/dist/handler/generated-registry-discovery.d.ts.map +1 -1
  99. package/dist/handler/generated-registry-discovery.js +2 -4
  100. package/dist/handler/generated-registry-discovery.js.map +1 -1
  101. package/dist/handler/handler-decorators.d.ts +91 -37
  102. package/dist/handler/handler-decorators.d.ts.map +1 -1
  103. package/dist/handler/handler-decorators.js +124 -31
  104. package/dist/handler/handler-decorators.js.map +1 -1
  105. package/dist/handler/handler-metadata.d.ts +105 -60
  106. package/dist/handler/handler-metadata.d.ts.map +1 -1
  107. package/dist/handler/handler-metadata.js +275 -57
  108. package/dist/handler/handler-metadata.js.map +1 -1
  109. package/dist/handler/registration-readiness-metadata.d.ts +4 -0
  110. package/dist/handler/registration-readiness-metadata.d.ts.map +1 -1
  111. package/dist/handler/registration-readiness-metadata.js +84 -2
  112. package/dist/handler/registration-readiness-metadata.js.map +1 -1
  113. package/dist/handler/standalone.d.ts +25 -0
  114. package/dist/handler/standalone.d.ts.map +1 -0
  115. package/dist/handler/standalone.js +38 -0
  116. package/dist/handler/standalone.js.map +1 -0
  117. package/dist/index.d.ts +9 -9
  118. package/dist/index.d.ts.map +1 -1
  119. package/dist/index.js +3 -3
  120. package/dist/index.js.map +1 -1
  121. package/dist/repository/command-routing.d.ts +63 -10
  122. package/dist/repository/command-routing.d.ts.map +1 -1
  123. package/dist/repository/command-routing.js +41 -8
  124. package/dist/repository/command-routing.js.map +1 -1
  125. package/dist/repository/event-routing.d.ts +64 -9
  126. package/dist/repository/event-routing.d.ts.map +1 -1
  127. package/dist/repository/event-routing.js +42 -7
  128. package/dist/repository/event-routing.js.map +1 -1
  129. package/dist/repository/primitive-id.d.ts +12 -23
  130. package/dist/repository/primitive-id.d.ts.map +1 -1
  131. package/dist/repository/primitive-id.js +2 -27
  132. package/dist/repository/primitive-id.js.map +1 -1
  133. package/dist/repository/repository.d.ts +317 -87
  134. package/dist/repository/repository.d.ts.map +1 -1
  135. package/dist/repository/repository.js +3746 -856
  136. package/dist/repository/repository.js.map +1 -1
  137. package/dist/repository/state-update-routing.d.ts +67 -8
  138. package/dist/repository/state-update-routing.d.ts.map +1 -1
  139. package/dist/repository/state-update-routing.js +45 -6
  140. package/dist/repository/state-update-routing.js.map +1 -1
  141. package/dist/runtime/runtime.js +1 -1
  142. package/dist/runtime/runtime.js.map +1 -1
  143. package/dist/runtime/signal-metadata.d.ts +22 -47
  144. package/dist/runtime/signal-metadata.d.ts.map +1 -1
  145. package/dist/runtime/signal-metadata.js +34 -69
  146. package/dist/runtime/signal-metadata.js.map +1 -1
  147. package/dist/runtime/signal-publisher.d.ts +144 -0
  148. package/dist/runtime/signal-publisher.d.ts.map +1 -0
  149. package/dist/runtime/signal-publisher.js +248 -0
  150. package/dist/runtime/signal-publisher.js.map +1 -0
  151. package/dist/runtime/standalone-handler-runtime.d.ts +130 -0
  152. package/dist/runtime/standalone-handler-runtime.d.ts.map +1 -0
  153. package/dist/runtime/standalone-handler-runtime.js +382 -0
  154. package/dist/runtime/standalone-handler-runtime.js.map +1 -0
  155. package/dist/server/environment-attachment.d.ts.map +1 -1
  156. package/dist/server/environment-attachment.js +2 -1
  157. package/dist/server/environment-attachment.js.map +1 -1
  158. package/dist/server/environment-delivery-worker.d.ts.map +1 -1
  159. package/dist/server/environment-delivery-worker.js +21 -12
  160. package/dist/server/environment-delivery-worker.js.map +1 -1
  161. package/dist/server/managed-server-application.d.ts +4 -1
  162. package/dist/server/managed-server-application.d.ts.map +1 -1
  163. package/dist/server/managed-server-application.js +2 -2
  164. package/dist/server/managed-server-application.js.map +1 -1
  165. package/dist/server/node-coordinator.js +2 -2
  166. package/dist/server/node-coordinator.js.map +1 -1
  167. package/dist/server/server-log.d.ts.map +1 -1
  168. package/dist/server/server-log.js +4 -0
  169. package/dist/server/server-log.js.map +1 -1
  170. package/dist/server/server.d.ts +9 -177
  171. package/dist/server/server.d.ts.map +1 -1
  172. package/dist/server/server.js +428 -178
  173. package/dist/server/server.js.map +1 -1
  174. package/dist/services/query-reader.d.ts +19 -0
  175. package/dist/services/query-reader.d.ts.map +1 -0
  176. package/dist/services/query-reader.js +43 -0
  177. package/dist/services/query-reader.js.map +1 -0
  178. package/dist/services/registered-targets.d.ts +73 -0
  179. package/dist/services/registered-targets.d.ts.map +1 -0
  180. package/dist/services/registered-targets.js +127 -0
  181. package/dist/services/registered-targets.js.map +1 -0
  182. package/dist/services/spine-services.d.ts +26 -12
  183. package/dist/services/spine-services.d.ts.map +1 -1
  184. package/dist/services/spine-services.js +1047 -969
  185. package/dist/services/spine-services.js.map +1 -1
  186. package/dist/spi/delivery.d.ts +5 -0
  187. package/dist/spi/delivery.d.ts.map +1 -0
  188. package/dist/spi/delivery.js +18 -0
  189. package/dist/spi/delivery.js.map +1 -0
  190. package/dist/spi/handler-registry.d.ts +8 -0
  191. package/dist/spi/handler-registry.d.ts.map +1 -0
  192. package/dist/spi/handler-registry.js +15 -0
  193. package/dist/spi/handler-registry.js.map +1 -0
  194. package/dist/stand/stand.d.ts +73 -1
  195. package/dist/stand/stand.d.ts.map +1 -1
  196. package/dist/stand/stand.js +221 -48
  197. package/dist/stand/stand.js.map +1 -1
  198. package/dist/stand/subscription-observer.d.ts.map +1 -1
  199. package/dist/stand/subscription-observer.js +5 -12
  200. package/dist/stand/subscription-observer.js.map +1 -1
  201. package/dist/stand/subscription-registry.js +1 -1
  202. package/dist/stand/subscription-registry.js.map +1 -1
  203. package/dist/testing/index.d.ts +25 -3
  204. package/dist/testing/index.d.ts.map +1 -1
  205. package/dist/testing/index.js +23 -3
  206. package/dist/testing/index.js.map +1 -1
  207. package/dist/tsconfig.tsbuildinfo +1 -1
  208. package/package.json +33 -23
  209. package/dist/handler/build-time-handler-analyzer.d.ts +0 -150
  210. package/dist/handler/build-time-handler-analyzer.d.ts.map +0 -1
  211. package/dist/handler/build-time-handler-analyzer.js +0 -1101
  212. package/dist/handler/build-time-handler-analyzer.js.map +0 -1
  213. package/dist/handler/generated-registry-writer.d.ts +0 -58
  214. package/dist/handler/generated-registry-writer.d.ts.map +0 -1
  215. package/dist/handler/generated-registry-writer.js +0 -458
  216. package/dist/handler/generated-registry-writer.js.map +0 -1
  217. package/dist/handler/handler-codegen.d.ts +0 -48
  218. package/dist/handler/handler-codegen.d.ts.map +0 -1
  219. package/dist/handler/handler-codegen.js +0 -214
  220. package/dist/handler/handler-codegen.js.map +0 -1
  221. package/dist/server/browser-server.d.ts +0 -49
  222. package/dist/server/browser-server.d.ts.map +0 -1
  223. package/dist/server/browser-server.js.map +0 -1
  224. package/dist/server/durable-subscription-bindings.d.ts.map +0 -1
  225. package/dist/server/durable-subscription-bindings.js.map +0 -1
  226. /package/dist/{server → browser}/durable-subscription-bindings.d.ts +0 -0
  227. /package/dist/{server → browser}/durable-subscription-bindings.js +0 -0
@@ -15,17 +15,18 @@ import * as http2 from "node:http2";
15
15
  import { connectNodeAdapter } from "@connectrpc/connect-node";
16
16
  import { BoundedContext, BoundedContextBuilder, boundedContextAccess, } from "../context/bounded-context.js";
17
17
  import { SpineServices, spineServicesAccess, } from "../services/spine-services.js";
18
- import { BrowserServer } from "./browser-server.js";
18
+ import { RegisteredTargets } from "../services/registered-targets.js";
19
19
  import { ProcessServerCoordinator } from "./process-server-coordinator.js";
20
20
  import { CloseErrors, RetryableCloseGroup } from "./retryable-close.js";
21
21
  import { ServerEnvironment, serverEnvironmentAccess } from "./server-environment.js";
22
- import { EnvironmentType } from "./environment.js";
23
22
  const defaultHost = "127.0.0.1";
24
23
  const defaultPort = 0;
25
24
  const defaultMessageMaxBytes = 4_194_304;
26
25
  const maximumMessageMaxBytes = 0xffff_ffff;
27
26
  const gracefulSessionDrainMs = 100;
28
27
  const runningContexts = new WeakMap();
28
+ const serverHosts = new WeakMap();
29
+ const assemblingStands = new WeakMap();
29
30
  /**
30
31
  * Configures and starts local Spine Connect/gRPC-compatible services.
31
32
  *
@@ -43,7 +44,6 @@ export class Server {
43
44
  #resources = [];
44
45
  #listenerLifecycles = [];
45
46
  #services;
46
- #browser;
47
47
  #environment;
48
48
  #starting;
49
49
  #startingOwnership;
@@ -64,8 +64,8 @@ export class Server {
64
64
  this.#contexts.push(...(options.contexts ?? []));
65
65
  this.#resources.push(...(options.resources ?? []));
66
66
  this.#services = options.services ?? {};
67
- this.#browser = options.browser;
68
67
  this.#environment = ServerEnvironment.instance();
68
+ serverHosts.set(this, this.#host);
69
69
  }
70
70
  /**
71
71
  * Creates a local-only server builder for one port.
@@ -169,8 +169,7 @@ export class Server {
169
169
  * Concurrent calls on one builder return one managed handle. Run-managed
170
170
  * siblings share an active generation but reject while caller-managed
171
171
  * ownership is active. The final local environment-owning run retirement
172
- * permanently closes its environment; a standalone browser Gateway remains
173
- * signal-managed but never owns or closes that environment. A failed final
172
+ * permanently closes its environment. A failed final
174
173
  * close stays retryable through `close()` or a later process signal.
175
174
  * Caller-managed servers never close their environment.
176
175
  *
@@ -180,10 +179,8 @@ export class Server {
180
179
  const current = this.#run;
181
180
  if (current !== undefined)
182
181
  return current;
183
- const environment = ServerValues.isStandaloneBrowser(this.#browser)
184
- ? undefined
185
- : this.#environment;
186
- const running = this.#start("server").then((server) => ProcessServerCoordinator.add(server, environment, environment === undefined ? undefined : serverEnvironmentAccess.loggerFor(environment), () => {
182
+ const environment = this.#environment;
183
+ const running = this.#start("server").then((server) => ProcessServerCoordinator.add(server, environment, serverEnvironmentAccess.loggerFor(environment), () => {
187
184
  if (this.#run === running) {
188
185
  this.#run = undefined;
189
186
  }
@@ -196,129 +193,244 @@ export class Server {
196
193
  });
197
194
  return running;
198
195
  }
196
+ /**
197
+ * Builds contexts, attaches delivery, and opens the listener for one start attempt.
198
+ *
199
+ * @param ownership Whether the environment is managed by this Server.
200
+ * @returns The running listener after lifecycle startup.
201
+ */
199
202
  async #startOnce(ownership) {
200
- const browser = this.#browser;
201
- const standalone = ServerValues.isStandaloneBrowser(browser);
202
- if (browser?.backend !== undefined)
203
- BrowserServer.backendUrls(ServerValues.browserBackendUrls(browser.backend));
204
- if (standalone &&
205
- (this.#contexts.length > 0 ||
206
- this.#resources.length > 0 ||
207
- Object.keys(this.#services).length > 0 ||
208
- this.#listenerLifecycles.length > 0))
209
- throw new Error("Standalone browser server cannot own local contexts, services, or resources.");
210
- if (browser !== undefined)
211
- BrowserServer.requireDurableBindings(browser, this.#environment.environment.type === EnvironmentType.Production);
212
- if (standalone && browser !== undefined)
213
- return BrowserServer.open(browser.backend === undefined
214
- ? undefined
215
- : BrowserServer.backendUrls(ServerValues.browserBackendUrls(browser.backend)), {
216
- ...browser,
217
- host: browser.host ?? this.#host,
218
- port: browser.port ?? this.#port,
219
- readMaxBytes: this.#readMaxBytes,
220
- writeMaxBytes: this.#writeMaxBytes,
221
- production: this.#environment.environment.type === EnvironmentType.Production,
222
- });
223
- const contexts = await ServerValues.buildContexts(this.#contexts, this.#environment.storageFactory);
224
- const logger = serverEnvironmentAccess.loggerFor(this.#environment);
225
- for (const context of contexts) {
226
- boundedContextAccess.installLogger(context, logger);
227
- serverEnvironmentAccess.warnVolatileRegistry(this.#environment, context);
228
- }
229
- let attachment;
203
+ const admission = { token: {}, contexts: [] };
204
+ const contexts = await this.#prepareContexts(admission);
205
+ const { attachment, routes } = await this.#attachContexts(contexts, ownership, admission);
206
+ const running = await this.#openListener(contexts, attachment, routes);
207
+ await this.#startLifecycles(running);
208
+ return running;
209
+ }
210
+ /**
211
+ * Opens the service listener with retryable cleanup for every preparation phase.
212
+ *
213
+ * @param contexts Contexts exposed by the listener.
214
+ * @param attachment Active environment delivery attachment.
215
+ * @param routes Routes installed for these contexts.
216
+ * @returns The prepared running listener.
217
+ */
218
+ async #openListener(contexts, attachment, routes) {
219
+ const closeables = [...contexts, ...this.#resources];
220
+ const cleanup = {
221
+ closeGroup: new RetryableCloseGroup(closeables, "Server start cleanup failed while closing owned contexts/resources."),
222
+ attachment,
223
+ routes,
224
+ failedStartRollback: undefined,
225
+ };
230
226
  try {
231
- attachment = await serverEnvironmentAccess.attach(this.#environment, {
232
- ownership,
233
- descriptors: contexts.map((context) => boundedContextAccess.delivery(context)),
234
- });
227
+ return await this.#prepareListener(contexts, attachment, routes, closeables, cleanup);
235
228
  }
236
229
  catch (error) {
237
- const closeGroup = new RetryableCloseGroup([...contexts, ...this.#resources], "Server start cleanup failed while closing owned contexts/resources.");
238
- if (serverEnvironmentAccess.failedStartRetryPending(this.#environment, error)) {
239
- this.#failedStartCleanup = {
240
- closeGroup,
241
- failedStartRollback: { rejection: error },
242
- };
243
- }
244
- else {
245
- try {
246
- await closeGroup.close();
247
- }
248
- catch (cleanupError) {
249
- this.#failedStartCleanup = {
250
- closeGroup,
251
- failedStartRollback: undefined,
252
- };
253
- throw ServerValues.attachmentCleanupError(error, cleanupError);
254
- }
255
- this.#failedStartConsumed = true;
256
- }
257
- throw error;
258
- }
259
- const closeables = [...contexts, ...this.#resources];
260
- const services = new SpineServices({
261
- contexts,
262
- ...this.#services,
263
- });
264
- spineServicesAccess.installLogger(services, logger);
265
- const sessions = new Set();
266
- const httpServer = ServerValues.createHttpServer(services, sessions, this.#readMaxBytes, this.#writeMaxBytes);
267
- const listener = this.#browser === undefined
268
- ? { host: this.#host, port: this.#port }
269
- : { host: defaultHost, port: defaultPort };
270
- const address = await ServerValues.listen(httpServer, listener.host, listener.port).catch(async (error) => {
271
- const cleanup = {
272
- closeGroup: new RetryableCloseGroup(closeables, "Server start cleanup failed while closing owned contexts/resources."),
273
- network: { server: httpServer, sessions },
274
- attachment,
275
- failedStartRollback: undefined,
276
- };
277
230
  this.#failedStartCleanup = cleanup;
278
231
  return this.#cleanupFailedListenerStart(cleanup, error);
279
- });
232
+ }
233
+ }
234
+ /**
235
+ * Constructs services and the HTTP adapter, then binds the listener.
236
+ *
237
+ * @param contexts Contexts exposed by this Server.
238
+ * @param attachment Active delivery attachment.
239
+ * @param routes Installed state routes.
240
+ * @param closeables Contexts and resources for eventual shutdown.
241
+ * @param cleanup Retained cleanup state if preparation fails.
242
+ * @returns The prepared running listener.
243
+ */
244
+ async #prepareListener(contexts, attachment, routes, closeables, cleanup) {
245
+ const services = new SpineServices({ contexts, ...this.#services });
246
+ cleanup.services = services;
247
+ spineServicesAccess.installLogger(services, serverEnvironmentAccess.loggerFor(this.#environment));
248
+ const { httpServer, sessions } = this.#createHttpListener(services);
249
+ cleanup.network = { server: httpServer, sessions };
250
+ const address = await ServerValues.listen(httpServer, this.#host, this.#port);
280
251
  const host = typeof address.address === "string" ? address.address : this.#host;
281
- const running = new RunningHttp2Server({
252
+ return this.#createRunningServer({
282
253
  server: httpServer,
283
254
  sessions,
284
255
  environment: this.#environment,
285
256
  attachment,
257
+ routes,
286
258
  services,
287
259
  host,
288
260
  port: address.port,
261
+ contexts,
289
262
  closeables,
263
+ });
264
+ }
265
+ /**
266
+ * Prepares an HTTP listener and its tracked sessions for startup.
267
+ *
268
+ * @param services Service routes exposed by the listener.
269
+ * @returns The unbound listener and its session set.
270
+ */
271
+ #createHttpListener(services) {
272
+ const sessions = new Set();
273
+ const httpServer = ServerValues.createHttpServer(services, sessions, this.#readMaxBytes, this.#writeMaxBytes);
274
+ return { httpServer, sessions };
275
+ }
276
+ /**
277
+ * Creates the listener handle and associates its context set.
278
+ *
279
+ * @param options Listener resources prepared during startup.
280
+ * @returns The not-yet-started listener handle.
281
+ */
282
+ #createRunningServer(options) {
283
+ const running = new RunningHttp2Server({
284
+ ...options,
290
285
  listenerLifecycles: this.#listenerLifecycles,
291
286
  });
292
- runningContexts.set(running, contexts);
287
+ runningContexts.set(running, options.contexts);
288
+ return running;
289
+ }
290
+ /**
291
+ * Reserves prebuilt contexts before awaiting builders, then assembles each builder.
292
+ *
293
+ * @param admission Attempt-local reservations retained through cleanup.
294
+ * @returns Prepared contexts for this Server.
295
+ */
296
+ async #prepareContexts(admission) {
297
+ const contexts = [];
298
+ let conflict;
299
+ let conflicted = false;
300
+ for (const entry of this.#contexts) {
301
+ if (boundedContextAccess.isBuilder(entry))
302
+ continue;
303
+ try {
304
+ this.#admitContext(entry, admission);
305
+ }
306
+ catch (error) {
307
+ if (!conflicted)
308
+ conflict = error;
309
+ conflicted = true;
310
+ }
311
+ }
293
312
  try {
294
- await running.startLifecycles();
313
+ if (conflicted)
314
+ throw conflict;
315
+ for (const entry of this.#contexts) {
316
+ const context = boundedContextAccess.isBuilder(entry)
317
+ ? await boundedContextAccess.build(entry, this.#environment.storageFactory)
318
+ : entry;
319
+ if (boundedContextAccess.isBuilder(entry))
320
+ this.#admitContext(context, admission);
321
+ contexts.push(context);
322
+ }
323
+ return contexts;
295
324
  }
296
325
  catch (error) {
297
- if (running.hasPendingClose())
298
- this.#failedListenerLifecycle = running;
299
- else
300
- this.#failedStartConsumed = true;
326
+ await this.#cleanupFailedAttachment(admission.contexts, undefined, error, admission);
301
327
  throw error;
302
328
  }
303
- if (browser === undefined)
304
- return running;
329
+ }
330
+ /**
331
+ * Reserves one context Stand while this Server assembles its routes.
332
+ *
333
+ * @param context Context to reserve.
334
+ * @param admission Attempt-local reservations and token.
335
+ */
336
+ #admitContext(context, admission) {
337
+ const stand = context.stand();
338
+ const token = assemblingStands.get(stand);
339
+ if (token !== undefined || RegisteredTargets.forStand(stand) !== undefined) {
340
+ throw new Error("Stand is already associated with another Server or assembly.");
341
+ }
342
+ assemblingStands.set(stand, admission.token);
343
+ admission.contexts.push(context);
344
+ }
345
+ /**
346
+ * Clears only reservations made by the given assembly attempt.
347
+ *
348
+ * @param admission Attempt whose reserved Stands may be released.
349
+ */
350
+ #releaseAdmission(admission) {
351
+ for (const context of admission.contexts) {
352
+ const stand = context.stand();
353
+ if (assemblingStands.get(stand) === admission.token)
354
+ assemblingStands.delete(stand);
355
+ }
356
+ admission.contexts.length = 0;
357
+ }
358
+ /**
359
+ * Validates state routes before delivery recovery can invoke handlers.
360
+ *
361
+ * @param contexts Built contexts for this Server.
362
+ * @param ownership Environment attachment mode.
363
+ * @param admission Reservations transferred to installed routes before attachment.
364
+ * @returns The environment attachment and its installed routes.
365
+ */
366
+ async #attachContexts(contexts, ownership, admission) {
367
+ const freshContexts = contexts.filter((context) => RegisteredTargets.forStand(context.stand()) === undefined);
368
+ let routes;
305
369
  try {
306
- return await BrowserServer.open(running, {
307
- ...browser,
308
- host: browser.host ?? this.#host,
309
- port: browser.port ?? this.#port,
310
- readMaxBytes: this.#readMaxBytes,
311
- writeMaxBytes: this.#writeMaxBytes,
312
- production: this.#environment.environment.type === EnvironmentType.Production,
370
+ routes = new RegisteredTargets(contexts);
371
+ routes.install(contexts);
372
+ this.#releaseAdmission(admission);
373
+ const logger = serverEnvironmentAccess.loggerFor(this.#environment);
374
+ for (const context of contexts) {
375
+ boundedContextAccess.installLogger(context, logger);
376
+ serverEnvironmentAccess.warnVolatileRegistry(this.#environment, context);
377
+ }
378
+ const attachment = await serverEnvironmentAccess.attach(this.#environment, {
379
+ ownership,
380
+ descriptors: contexts.map((context) => boundedContextAccess.delivery(context)),
313
381
  });
382
+ return { attachment, routes };
314
383
  }
315
384
  catch (error) {
316
- try {
317
- await running.close();
318
- }
319
- catch (closeError) {
320
- throw new AggregateError([error, closeError], "Server browser startup failed and rollback failed.");
321
- }
385
+ await this.#cleanupFailedAttachment(freshContexts, routes, error, admission);
386
+ throw error;
387
+ }
388
+ }
389
+ /**
390
+ * Retains retryable cleanup when route validation or attachment fails.
391
+ *
392
+ * @param contexts Fresh contexts to close after failure.
393
+ * @param routes Routes installed by this attempt, if any.
394
+ * @param error Original route or attachment failure.
395
+ * @param admission Reservations retained until context cleanup succeeds.
396
+ */
397
+ async #cleanupFailedAttachment(contexts, routes, error, admission) {
398
+ const closeGroup = new RetryableCloseGroup([...contexts, ...this.#resources], "Server start cleanup failed while closing owned contexts/resources.");
399
+ if (serverEnvironmentAccess.failedStartRetryPending(this.#environment, error)) {
400
+ this.#failedStartCleanup = {
401
+ closeGroup,
402
+ failedStartRollback: { rejection: error },
403
+ routes,
404
+ admission,
405
+ };
406
+ return;
407
+ }
408
+ try {
409
+ await closeGroup.close();
410
+ }
411
+ catch (cleanupError) {
412
+ this.#failedStartCleanup = { closeGroup, failedStartRollback: undefined, routes, admission };
413
+ throw ServerValues.attachmentCleanupError(error, cleanupError);
414
+ }
415
+ routes?.release();
416
+ if (admission !== undefined)
417
+ this.#releaseAdmission(admission);
418
+ this.#failedStartConsumed = true;
419
+ }
420
+ /**
421
+ * Starts listener lifecycles and preserves failed close retries.
422
+ *
423
+ * @param running Listener whose lifecycles begin.
424
+ */
425
+ async #startLifecycles(running) {
426
+ try {
427
+ await running.startLifecycles();
428
+ }
429
+ catch (error) {
430
+ if (running.hasPendingClose())
431
+ this.#failedListenerLifecycle = running;
432
+ else
433
+ this.#failedStartConsumed = true;
322
434
  throw error;
323
435
  }
324
436
  }
@@ -351,6 +463,32 @@ export class Server {
351
463
  if (!(await this.#advanceFailedStartAttachment(cleanup, errors))) {
352
464
  return;
353
465
  }
466
+ await this.#retryFailedStartRollback(cleanup, errors);
467
+ let closeFailed = false;
468
+ try {
469
+ await cleanup.closeGroup.close();
470
+ }
471
+ catch (error) {
472
+ closeFailed = true;
473
+ CloseErrors.collect(error, errors);
474
+ }
475
+ if (!closeFailed && cleanup.attachment === undefined && this.#failedStartCleanup === cleanup) {
476
+ cleanup.routes?.release();
477
+ if (cleanup.admission !== undefined)
478
+ this.#releaseAdmission(cleanup.admission);
479
+ if (cleanup.services !== undefined)
480
+ spineServicesAccess.clearLogger(cleanup.services);
481
+ this.#failedStartCleanup = undefined;
482
+ this.#failedStartConsumed = true;
483
+ }
484
+ }
485
+ /**
486
+ * Retries environment rollback before closing contexts from a failed start.
487
+ *
488
+ * @param cleanup Retained failed-start state.
489
+ * @param errors Errors collected across cleanup phases.
490
+ */
491
+ async #retryFailedStartRollback(cleanup, errors) {
354
492
  const failedStartRollback = cleanup.failedStartRollback;
355
493
  if (failedStartRollback !== undefined &&
356
494
  serverEnvironmentAccess.failedStartRetryPending(this.#environment, failedStartRollback.rejection)) {
@@ -364,18 +502,6 @@ export class Server {
364
502
  CloseErrors.collect(error, errors);
365
503
  }
366
504
  }
367
- let closeFailed = false;
368
- try {
369
- await cleanup.closeGroup.close();
370
- }
371
- catch (error) {
372
- closeFailed = true;
373
- CloseErrors.collect(error, errors);
374
- }
375
- if (!closeFailed && cleanup.attachment === undefined && this.#failedStartCleanup === cleanup) {
376
- this.#failedStartCleanup = undefined;
377
- this.#failedStartConsumed = true;
378
- }
379
505
  }
380
506
  async #closeFailedStartNetwork(cleanup, errors) {
381
507
  const network = cleanup.network;
@@ -427,6 +553,23 @@ export class Server {
427
553
  }
428
554
  }
429
555
  }
556
+ /**
557
+ * Checks whether browser composition may start a private native listener.
558
+ *
559
+ * @internal
560
+ */
561
+ export const serverBuilderAccess = Object.freeze({
562
+ isLoopback(server) {
563
+ const host = serverHosts.get(server);
564
+ return host !== undefined && isLoopbackHost(host);
565
+ },
566
+ });
567
+ function isLoopbackHost(host) {
568
+ if (host === "::1" || host === "::ffff:127.0.0.1")
569
+ return true;
570
+ const match = /^127\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/u.exec(host);
571
+ return match?.slice(1).every((part) => Number(part) <= 255) === true;
572
+ }
430
573
  /**
431
574
  * Provides framework-only `RunningServer` context facts.
432
575
  *
@@ -442,14 +585,19 @@ export const runningServerAccess = Object.freeze({
442
585
  return server instanceof RunningHttp2Server ? server.drainDelivery() : Promise.resolve();
443
586
  },
444
587
  });
588
+ /**
589
+ * Holds listener resources and closes them in recovery-safe phases.
590
+ */
445
591
  class RunningHttp2Server {
446
592
  #server;
447
593
  #sessions;
448
594
  #closeables;
595
+ #contexts;
449
596
  #listenerLifecycles;
450
597
  #startedLifecycles = [];
451
598
  #environment;
452
599
  #attachment;
600
+ #routes;
453
601
  #services;
454
602
  host;
455
603
  port;
@@ -458,19 +606,31 @@ class RunningHttp2Server {
458
606
  #networkClosed = false;
459
607
  #attachmentDetached = false;
460
608
  #closeGroup;
609
+ /**
610
+ * Captures the running listener's network, environment, and cleanup resources.
611
+ *
612
+ * @param options Resources prepared by Server startup.
613
+ */
461
614
  constructor(options) {
462
615
  this.#server = options.server;
463
616
  this.#sessions = options.sessions;
464
617
  this.#closeables = options.closeables;
618
+ this.#contexts = options.contexts;
465
619
  this.#listenerLifecycles = options.listenerLifecycles;
466
620
  this.#environment = options.environment;
467
621
  this.#attachment = options.attachment;
622
+ this.#routes = options.routes;
468
623
  this.#services = options.services;
469
624
  this.host = options.host;
470
625
  this.port = options.port;
471
626
  this.baseUrl = `http://${ServerValues.formatHostForUrl(options.host)}:${options.port.toString()}`;
472
627
  this.#closeGroup = new RetryableCloseGroup(this.#closeables, "Server close failed while closing owned contexts/resources.");
473
628
  }
629
+ /**
630
+ * Closes this listener once, preserving a failed close for retry.
631
+ *
632
+ * @returns Completion after resources close.
633
+ */
474
634
  close() {
475
635
  this.#closed ??= this.#closeOnce().catch((error) => {
476
636
  this.#closed = undefined;
@@ -500,11 +660,16 @@ class RunningHttp2Server {
500
660
  throw error;
501
661
  }
502
662
  }
663
+ /**
664
+ * Checks whether a failed lifecycle start still needs close retry.
665
+ *
666
+ * @returns `true` when no close completion is retained.
667
+ */
503
668
  hasPendingClose() {
504
669
  return this.#closed === undefined;
505
670
  }
506
671
  /**
507
- * Drains the server-owned Delivery attachment while network sessions remain available.
672
+ * Completes the server's Delivery detachment while network sessions remain available.
508
673
  *
509
674
  * @returns Completion after the attachment drains.
510
675
  */
@@ -519,7 +684,35 @@ class RunningHttp2Server {
519
684
  }
520
685
  this.#attachmentDetached = true;
521
686
  }
687
+ /**
688
+ * Closes network, delivery, accepted context work, and registered resources.
689
+ *
690
+ * @returns Completion after all resources close.
691
+ */
522
692
  async #closeOnce() {
693
+ await this.#closeListenerAndNetwork();
694
+ const { detachErrors, detachRejected } = await this.#detachDelivery();
695
+ await this.#drainContexts();
696
+ try {
697
+ await this.#closeGroup.close();
698
+ this.#routes.release();
699
+ }
700
+ catch (error) {
701
+ if (!detachRejected)
702
+ throw error;
703
+ CloseErrors.collect(error, detachErrors);
704
+ throw new AggregateError(detachErrors, "Server close failed while detaching delivery and closing owned contexts/resources.");
705
+ }
706
+ if (detachRejected)
707
+ ServerValues.throwRunningDetachErrors(detachErrors);
708
+ spineServicesAccess.clearLogger(this.#services);
709
+ }
710
+ /**
711
+ * Closes lifecycle attachments and network intake before delivery detachment.
712
+ *
713
+ * @returns Completion after the listener and sessions close.
714
+ */
715
+ async #closeListenerAndNetwork() {
523
716
  while (this.#startedLifecycles.length > 0) {
524
717
  const lifecycle = this.#startedLifecycles.at(-1);
525
718
  if (lifecycle === undefined)
@@ -531,6 +724,13 @@ class RunningHttp2Server {
531
724
  await ServerValues.closeNetwork(this.#server, this.#sessions);
532
725
  this.#networkClosed = true;
533
726
  }
727
+ }
728
+ /**
729
+ * Completes Delivery detachment while retaining errors for safe endpoint cleanup.
730
+ *
731
+ * @returns Detachment errors and whether detachment itself was rejected.
732
+ */
733
+ async #detachDelivery() {
534
734
  const detachErrors = [];
535
735
  let detachRejected = false;
536
736
  if (!this.#attachmentDetached) {
@@ -560,42 +760,38 @@ class RunningHttp2Server {
560
760
  }
561
761
  }
562
762
  }
563
- try {
564
- await this.#closeGroup.close();
565
- }
566
- catch (error) {
567
- if (!detachRejected) {
568
- throw error;
569
- }
570
- CloseErrors.collect(error, detachErrors);
571
- throw new AggregateError(detachErrors, "Server close failed while detaching delivery and closing owned contexts/resources.");
572
- }
573
- if (detachRejected) {
574
- ServerValues.throwRunningDetachErrors(detachErrors);
763
+ return { detachErrors, detachRejected };
764
+ }
765
+ /**
766
+ * Waits for every context's accepted work before any Stand closes.
767
+ *
768
+ * @returns Completion after all context work drains.
769
+ */
770
+ async #drainContexts() {
771
+ for (const context of this.#contexts)
772
+ boundedContextAccess.beginClose(context);
773
+ const drained = await Promise.allSettled(this.#contexts.map((context) => boundedContextAccess.drainWork(context)));
774
+ const drainErrors = drained.flatMap((result) => result.status === "rejected" ? [result.reason] : []);
775
+ if (drainErrors.length > 0) {
776
+ throw new AggregateError(drainErrors, "Server close failed while draining contexts.");
575
777
  }
576
- spineServicesAccess.clearLogger(this.#services);
577
778
  }
578
779
  }
579
780
  /**
781
+ * Groups private server assembly, network, and shutdown operations.
580
782
  *
581
- * @internal Groups private server assembly, network, and shutdown operations.
783
+ * @internal
582
784
  */
583
785
  const ServerValues = Object.freeze({
584
- async buildContexts(entries, defaultStorageFactory) {
585
- const contexts = [];
586
- try {
587
- for (const entry of entries) {
588
- contexts.push(boundedContextAccess.isBuilder(entry)
589
- ? await boundedContextAccess.build(entry, defaultStorageFactory)
590
- : entry);
591
- }
592
- return contexts;
593
- }
594
- catch (error) {
595
- await ServerValues.cleanupBuiltContexts(contexts, error);
596
- throw error;
597
- }
598
- },
786
+ /**
787
+ * Creates the HTTP/2 listener with Connect routes and session tracking.
788
+ *
789
+ * @param services Public service adapters.
790
+ * @param sessions Set collecting active HTTP/2 sessions.
791
+ * @param readMaxBytes Maximum request size.
792
+ * @param writeMaxBytes Maximum response size.
793
+ * @returns An unbound HTTP/2 server.
794
+ */
599
795
  createHttpServer(services, sessions, readMaxBytes, writeMaxBytes) {
600
796
  const server = http2.createServer(connectNodeAdapter({
601
797
  routes: (router) => {
@@ -610,6 +806,14 @@ const ServerValues = Object.freeze({
610
806
  });
611
807
  return server;
612
808
  },
809
+ /**
810
+ * Binds a listener and resolves its actual address.
811
+ *
812
+ * @param server HTTP/2 server to bind.
813
+ * @param host Network host.
814
+ * @param port Requested port, including zero for an ephemeral port.
815
+ * @returns Bound listener address.
816
+ */
613
817
  listen(server, host, port) {
614
818
  return new Promise((resolve, reject) => {
615
819
  const cleanup = () => {
@@ -629,32 +833,38 @@ const ServerValues = Object.freeze({
629
833
  server.listen(port, host);
630
834
  });
631
835
  },
632
- async cleanupBuiltContexts(contexts, startError) {
633
- try {
634
- await new RetryableCloseGroup(contexts, "Server start cleanup failed while closing owned contexts/resources.").close();
635
- }
636
- catch (error) {
637
- throw new AggregateError([startError, ...ServerValues.toCleanupErrors(error)], "Server start failed while building bounded contexts.");
638
- }
639
- },
640
- toCleanupErrors(error) {
641
- if (error instanceof AggregateError) {
642
- return error.errors;
643
- }
644
- return [error];
645
- },
836
+ /**
837
+ * Collects attachment and immediate cleanup failures for startup reporting.
838
+ *
839
+ * @param startError Attachment or duplicate registration failure.
840
+ * @param cleanupError Failure while closing prepared resources.
841
+ * @returns Both failures in one AggregateError.
842
+ */
646
843
  attachmentCleanupError(startError, cleanupError) {
647
844
  const errors = [];
648
845
  CloseErrors.collect(startError, errors);
649
846
  CloseErrors.collect(cleanupError, errors);
650
847
  return new AggregateError(errors, "Server attachment failed and immediate dependency cleanup also failed.");
651
848
  },
849
+ /**
850
+ * Throws one deferred cleanup failure or groups several.
851
+ *
852
+ * @param errors Deferred cleanup failures.
853
+ * @returns Never; this method throws.
854
+ */
652
855
  throwCleanupErrors(errors) {
653
856
  if (errors.length === 1) {
654
857
  throw errors[0];
655
858
  }
656
859
  throw new AggregateError(errors, "Server deferred failed-start cleanup failed.");
657
860
  },
861
+ /**
862
+ * Throws a listener-start failure with any rollback failures.
863
+ *
864
+ * @param startError Original listener failure.
865
+ * @param cleanupErrors Failures while closing startup resources.
866
+ * @returns Never; this method throws.
867
+ */
658
868
  throwListenerStartError(startError, cleanupErrors) {
659
869
  if (cleanupErrors.length === 0) {
660
870
  throw startError;
@@ -666,18 +876,37 @@ const ServerValues = Object.freeze({
666
876
  }
667
877
  throw new AggregateError(errors, "Server start failed while opening listener and cleanup also failed.");
668
878
  },
879
+ /**
880
+ * Throws one delivery-detachment error or groups several.
881
+ *
882
+ * @param errors Delivery-detachment failures.
883
+ * @returns Never; this method throws.
884
+ */
669
885
  throwRunningDetachErrors(errors) {
670
886
  if (errors.length === 1) {
671
887
  throw errors[0];
672
888
  }
673
889
  throw new AggregateError(errors, "Server close failed while detaching delivery.");
674
890
  },
891
+ /**
892
+ * Closes the HTTP/2 server and all tracked sessions.
893
+ *
894
+ * @param server Listener being closed.
895
+ * @param sessions Sessions admitted by the listener.
896
+ * @returns Completion after the network releases its resources.
897
+ */
675
898
  async closeNetwork(server, sessions) {
676
899
  const closed = ServerValues.closeHttpServer(server);
677
900
  await ServerValues.closeSessions(sessions);
678
901
  await closed;
679
902
  await ServerValues.nextTurn();
680
903
  },
904
+ /**
905
+ * Closes the listener, including a listener that never started.
906
+ *
907
+ * @param server Listener being closed.
908
+ * @returns Completion after the listener closes.
909
+ */
681
910
  closeHttpServer(server) {
682
911
  return new Promise((resolve, reject) => {
683
912
  if (!server.listening) {
@@ -694,9 +923,21 @@ const ServerValues = Object.freeze({
694
923
  });
695
924
  });
696
925
  },
926
+ /**
927
+ * Closes all active HTTP/2 sessions.
928
+ *
929
+ * @param sessions Active sessions to close.
930
+ * @returns Completion after every session closes.
931
+ */
697
932
  async closeSessions(sessions) {
698
933
  await Promise.all([...sessions].map((session) => ServerValues.closeSession(session)));
699
934
  },
935
+ /**
936
+ * Closes one session gracefully or destroys it after a bounded wait.
937
+ *
938
+ * @param session Active HTTP/2 session.
939
+ * @returns Completion after close or forced destruction.
940
+ */
700
941
  closeSession(session) {
701
942
  return new Promise((resolve) => {
702
943
  const timer = setTimeout(() => {
@@ -712,14 +953,31 @@ const ServerValues = Object.freeze({
712
953
  session.close();
713
954
  });
714
955
  },
956
+ /**
957
+ * Waits one event-loop turn for pending close callbacks.
958
+ *
959
+ * @returns Completion on the next turn.
960
+ */
715
961
  nextTurn() {
716
962
  return new Promise((resolve) => {
717
963
  setImmediate(resolve);
718
964
  });
719
965
  },
966
+ /**
967
+ * Wraps an IPv6 host for URL authority syntax.
968
+ *
969
+ * @param host Bound network host.
970
+ * @returns Host formatted for the base URL.
971
+ */
720
972
  formatHostForUrl(host) {
721
973
  return host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
722
974
  },
975
+ /**
976
+ * Normalizes a non-blank host for listener binding.
977
+ *
978
+ * @param host Configured host, if supplied.
979
+ * @returns Normalized host or the loopback default.
980
+ */
723
981
  normalizeHost(host) {
724
982
  const normalized = host?.trim() ?? defaultHost;
725
983
  if (normalized.length === 0) {
@@ -727,21 +985,13 @@ const ServerValues = Object.freeze({
727
985
  }
728
986
  return normalized;
729
987
  },
730
- browserBackendUrls(backend) {
731
- const source = backend;
732
- if (typeof source.baseUrl === "string" && source.baseUrls === undefined)
733
- return [source.baseUrl];
734
- if (source.baseUrl === undefined && Array.isArray(source.baseUrls)) {
735
- const values = source.baseUrls;
736
- if (values.some((value) => typeof value !== "string"))
737
- throw new Error("Server browser backend URLs must be strings.");
738
- return values;
739
- }
740
- throw new Error("Server browser backend must configure exactly one of baseUrl or baseUrls.");
741
- },
742
- isStandaloneBrowser(browser) {
743
- return browser?.backend !== undefined || browser?.discovery !== undefined;
744
- },
988
+ /**
989
+ * Validates a configured HTTP message-size limit.
990
+ *
991
+ * @param value Requested byte limit.
992
+ * @param name Option name used in diagnostics.
993
+ * @returns Accepted byte limit.
994
+ */
745
995
  normalizeMessageMaxBytes(value, name) {
746
996
  if (!Number.isInteger(value) || value < 1 || value > maximumMessageMaxBytes) {
747
997
  throw new Error(`Server ${name} must be an integer from 1 through 4294967295.`);