@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.
- package/README.md +225 -518
- package/REFERENCE.md +498 -49
- package/dist/browser/browser-server.d.ts +95 -0
- package/dist/browser/browser-server.d.ts.map +1 -0
- package/dist/{server → browser}/browser-server.js +93 -7
- package/dist/browser/browser-server.js.map +1 -0
- package/dist/browser/durable-subscription-bindings.d.ts.map +1 -0
- package/dist/browser/durable-subscription-bindings.js.map +1 -0
- package/dist/browser/index.d.ts +12 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +24 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/options.d.ts +183 -0
- package/dist/browser/options.d.ts.map +1 -0
- package/dist/browser/options.js +15 -0
- package/dist/browser/options.js.map +1 -0
- package/dist/bus/command-bus.d.ts +2 -0
- package/dist/bus/command-bus.d.ts.map +1 -1
- package/dist/bus/command-bus.js +36 -1
- package/dist/bus/command-bus.js.map +1 -1
- package/dist/bus/event-dispatcher-registry.d.ts +2 -2
- package/dist/bus/event-dispatcher-registry.js +3 -3
- package/dist/bus/event-dispatcher-registry.js.map +1 -1
- package/dist/context/bounded-context.d.ts +238 -83
- package/dist/context/bounded-context.d.ts.map +1 -1
- package/dist/context/bounded-context.js +1083 -286
- package/dist/context/bounded-context.js.map +1 -1
- package/dist/context/effective-tenant.d.ts +9 -0
- package/dist/context/effective-tenant.d.ts.map +1 -0
- package/dist/context/effective-tenant.js +58 -0
- package/dist/context/effective-tenant.js.map +1 -0
- package/dist/context/entity-inbox.d.ts.map +1 -1
- package/dist/context/entity-inbox.js +24 -17
- package/dist/context/entity-inbox.js.map +1 -1
- package/dist/context/local-inbox-handoff.d.ts +7 -3
- package/dist/context/local-inbox-handoff.d.ts.map +1 -1
- package/dist/context/local-inbox-handoff.js +32 -26
- package/dist/context/local-inbox-handoff.js.map +1 -1
- package/dist/context/projection-handoff.d.ts.map +1 -1
- package/dist/context/projection-handoff.js +36 -31
- package/dist/context/projection-handoff.js.map +1 -1
- package/dist/context/tenant-index.d.ts.map +1 -1
- package/dist/context/tenant-index.js +72 -2
- package/dist/context/tenant-index.js.map +1 -1
- package/dist/delivery/delivery-ports.d.ts +14 -4
- package/dist/delivery/delivery-ports.d.ts.map +1 -1
- package/dist/delivery/delivery.d.ts +19 -21
- package/dist/delivery/delivery.d.ts.map +1 -1
- package/dist/delivery/delivery.js +541 -158
- package/dist/delivery/delivery.js.map +1 -1
- package/dist/delivery/inbox-storage.d.ts +12 -3
- package/dist/delivery/inbox-storage.d.ts.map +1 -1
- package/dist/delivery/inbox-storage.js +160 -67
- package/dist/delivery/inbox-storage.js.map +1 -1
- package/dist/delivery/inbox.d.ts +9 -0
- package/dist/delivery/inbox.d.ts.map +1 -1
- package/dist/delivery/inbox.js +11 -0
- package/dist/delivery/inbox.js.map +1 -1
- package/dist/delivery/sharded-work-registry.d.ts.map +1 -1
- package/dist/delivery/sharded-work-registry.js +2 -1
- package/dist/delivery/sharded-work-registry.js.map +1 -1
- package/dist/entity/entity-metadata.d.ts +14 -5
- package/dist/entity/entity-metadata.d.ts.map +1 -1
- package/dist/entity/entity-metadata.js +38 -2
- package/dist/entity/entity-metadata.js.map +1 -1
- package/dist/entity/entity-storage-descriptor.d.ts +25 -5
- package/dist/entity/entity-storage-descriptor.d.ts.map +1 -1
- package/dist/entity/entity-storage-descriptor.js +69 -23
- package/dist/entity/entity-storage-descriptor.js.map +1 -1
- package/dist/entity/entity-transaction.d.ts +46 -42
- package/dist/entity/entity-transaction.d.ts.map +1 -1
- package/dist/entity/entity-transaction.js +130 -35
- package/dist/entity/entity-transaction.js.map +1 -1
- package/dist/entity/entity.d.ts +243 -98
- package/dist/entity/entity.d.ts.map +1 -1
- package/dist/entity/entity.js +405 -226
- package/dist/entity/entity.js.map +1 -1
- package/dist/entity/spec-scanner.d.ts +13 -1
- package/dist/entity/spec-scanner.d.ts.map +1 -1
- package/dist/entity/spec-scanner.js +20 -3
- package/dist/entity/spec-scanner.js.map +1 -1
- package/dist/handler/command-registration-readiness.d.ts +6 -6
- package/dist/handler/command-registration-readiness.d.ts.map +1 -1
- package/dist/handler/command-registration-readiness.js +4 -2
- package/dist/handler/command-registration-readiness.js.map +1 -1
- package/dist/handler/declared-rejections.d.ts +11 -0
- package/dist/handler/declared-rejections.d.ts.map +1 -0
- package/dist/handler/declared-rejections.js +34 -0
- package/dist/handler/declared-rejections.js.map +1 -0
- package/dist/handler/event-registration-readiness.d.ts +13 -56
- package/dist/handler/event-registration-readiness.d.ts.map +1 -1
- package/dist/handler/event-registration-readiness.js +23 -43
- package/dist/handler/event-registration-readiness.js.map +1 -1
- package/dist/handler/generated-handler-registry.d.ts +72 -33
- package/dist/handler/generated-handler-registry.d.ts.map +1 -1
- package/dist/handler/generated-handler-registry.js +421 -43
- package/dist/handler/generated-handler-registry.js.map +1 -1
- package/dist/handler/generated-registry-discovery.d.ts.map +1 -1
- package/dist/handler/generated-registry-discovery.js +2 -4
- package/dist/handler/generated-registry-discovery.js.map +1 -1
- package/dist/handler/handler-decorators.d.ts +91 -37
- package/dist/handler/handler-decorators.d.ts.map +1 -1
- package/dist/handler/handler-decorators.js +124 -31
- package/dist/handler/handler-decorators.js.map +1 -1
- package/dist/handler/handler-metadata.d.ts +105 -60
- package/dist/handler/handler-metadata.d.ts.map +1 -1
- package/dist/handler/handler-metadata.js +275 -57
- package/dist/handler/handler-metadata.js.map +1 -1
- package/dist/handler/registration-readiness-metadata.d.ts +4 -0
- package/dist/handler/registration-readiness-metadata.d.ts.map +1 -1
- package/dist/handler/registration-readiness-metadata.js +84 -2
- package/dist/handler/registration-readiness-metadata.js.map +1 -1
- package/dist/handler/standalone.d.ts +25 -0
- package/dist/handler/standalone.d.ts.map +1 -0
- package/dist/handler/standalone.js +38 -0
- package/dist/handler/standalone.js.map +1 -0
- package/dist/index.d.ts +9 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/repository/command-routing.d.ts +63 -10
- package/dist/repository/command-routing.d.ts.map +1 -1
- package/dist/repository/command-routing.js +41 -8
- package/dist/repository/command-routing.js.map +1 -1
- package/dist/repository/event-routing.d.ts +64 -9
- package/dist/repository/event-routing.d.ts.map +1 -1
- package/dist/repository/event-routing.js +42 -7
- package/dist/repository/event-routing.js.map +1 -1
- package/dist/repository/primitive-id.d.ts +12 -23
- package/dist/repository/primitive-id.d.ts.map +1 -1
- package/dist/repository/primitive-id.js +2 -27
- package/dist/repository/primitive-id.js.map +1 -1
- package/dist/repository/repository.d.ts +317 -87
- package/dist/repository/repository.d.ts.map +1 -1
- package/dist/repository/repository.js +3746 -856
- package/dist/repository/repository.js.map +1 -1
- package/dist/repository/state-update-routing.d.ts +67 -8
- package/dist/repository/state-update-routing.d.ts.map +1 -1
- package/dist/repository/state-update-routing.js +45 -6
- package/dist/repository/state-update-routing.js.map +1 -1
- package/dist/runtime/runtime.js +1 -1
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/signal-metadata.d.ts +22 -47
- package/dist/runtime/signal-metadata.d.ts.map +1 -1
- package/dist/runtime/signal-metadata.js +34 -69
- package/dist/runtime/signal-metadata.js.map +1 -1
- package/dist/runtime/signal-publisher.d.ts +144 -0
- package/dist/runtime/signal-publisher.d.ts.map +1 -0
- package/dist/runtime/signal-publisher.js +248 -0
- package/dist/runtime/signal-publisher.js.map +1 -0
- package/dist/runtime/standalone-handler-runtime.d.ts +130 -0
- package/dist/runtime/standalone-handler-runtime.d.ts.map +1 -0
- package/dist/runtime/standalone-handler-runtime.js +382 -0
- package/dist/runtime/standalone-handler-runtime.js.map +1 -0
- package/dist/server/environment-attachment.d.ts.map +1 -1
- package/dist/server/environment-attachment.js +2 -1
- package/dist/server/environment-attachment.js.map +1 -1
- package/dist/server/environment-delivery-worker.d.ts.map +1 -1
- package/dist/server/environment-delivery-worker.js +21 -12
- package/dist/server/environment-delivery-worker.js.map +1 -1
- package/dist/server/managed-server-application.d.ts +4 -1
- package/dist/server/managed-server-application.d.ts.map +1 -1
- package/dist/server/managed-server-application.js +2 -2
- package/dist/server/managed-server-application.js.map +1 -1
- package/dist/server/node-coordinator.js +2 -2
- package/dist/server/node-coordinator.js.map +1 -1
- package/dist/server/server-log.d.ts.map +1 -1
- package/dist/server/server-log.js +4 -0
- package/dist/server/server-log.js.map +1 -1
- package/dist/server/server.d.ts +9 -177
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +428 -178
- package/dist/server/server.js.map +1 -1
- package/dist/services/query-reader.d.ts +19 -0
- package/dist/services/query-reader.d.ts.map +1 -0
- package/dist/services/query-reader.js +43 -0
- package/dist/services/query-reader.js.map +1 -0
- package/dist/services/registered-targets.d.ts +73 -0
- package/dist/services/registered-targets.d.ts.map +1 -0
- package/dist/services/registered-targets.js +127 -0
- package/dist/services/registered-targets.js.map +1 -0
- package/dist/services/spine-services.d.ts +26 -12
- package/dist/services/spine-services.d.ts.map +1 -1
- package/dist/services/spine-services.js +1047 -969
- package/dist/services/spine-services.js.map +1 -1
- package/dist/spi/delivery.d.ts +5 -0
- package/dist/spi/delivery.d.ts.map +1 -0
- package/dist/spi/delivery.js +18 -0
- package/dist/spi/delivery.js.map +1 -0
- package/dist/spi/handler-registry.d.ts +8 -0
- package/dist/spi/handler-registry.d.ts.map +1 -0
- package/dist/spi/handler-registry.js +15 -0
- package/dist/spi/handler-registry.js.map +1 -0
- package/dist/stand/stand.d.ts +73 -1
- package/dist/stand/stand.d.ts.map +1 -1
- package/dist/stand/stand.js +221 -48
- package/dist/stand/stand.js.map +1 -1
- package/dist/stand/subscription-observer.d.ts.map +1 -1
- package/dist/stand/subscription-observer.js +5 -12
- package/dist/stand/subscription-observer.js.map +1 -1
- package/dist/stand/subscription-registry.js +1 -1
- package/dist/stand/subscription-registry.js.map +1 -1
- package/dist/testing/index.d.ts +25 -3
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +23 -3
- package/dist/testing/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +33 -23
- package/dist/handler/build-time-handler-analyzer.d.ts +0 -150
- package/dist/handler/build-time-handler-analyzer.d.ts.map +0 -1
- package/dist/handler/build-time-handler-analyzer.js +0 -1101
- package/dist/handler/build-time-handler-analyzer.js.map +0 -1
- package/dist/handler/generated-registry-writer.d.ts +0 -58
- package/dist/handler/generated-registry-writer.d.ts.map +0 -1
- package/dist/handler/generated-registry-writer.js +0 -458
- package/dist/handler/generated-registry-writer.js.map +0 -1
- package/dist/handler/handler-codegen.d.ts +0 -48
- package/dist/handler/handler-codegen.d.ts.map +0 -1
- package/dist/handler/handler-codegen.js +0 -214
- package/dist/handler/handler-codegen.js.map +0 -1
- package/dist/server/browser-server.d.ts +0 -49
- package/dist/server/browser-server.d.ts.map +0 -1
- package/dist/server/browser-server.js.map +0 -1
- package/dist/server/durable-subscription-bindings.d.ts.map +0 -1
- package/dist/server/durable-subscription-bindings.js.map +0 -1
- /package/dist/{server → browser}/durable-subscription-bindings.d.ts +0 -0
- /package/dist/{server → browser}/durable-subscription-bindings.js +0 -0
package/dist/server/server.js
CHANGED
|
@@ -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 {
|
|
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
|
|
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 =
|
|
184
|
-
|
|
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
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
304
|
-
|
|
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
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
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
|
|
783
|
+
* @internal
|
|
582
784
|
*/
|
|
583
785
|
const ServerValues = Object.freeze({
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
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
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
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
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
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.`);
|