@spine-event-engine/server 2.0.0-snapshot.25 → 2.0.0-snapshot.28
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 +104 -16
- package/REFERENCE.md +486 -57
- package/dist/agent/agent-history.d.ts +19 -3
- package/dist/agent/agent-history.d.ts.map +1 -1
- package/dist/agent/agent-history.js +41 -10
- package/dist/agent/agent-history.js.map +1 -1
- package/dist/agent/agent-live-ai-runtime.d.ts +1 -1
- package/dist/agent/agent-live-ai-runtime.js +1 -1
- package/dist/agent/agent-live-budget.d.ts +1 -1
- package/dist/agent/agent-live-budget.js +1 -1
- package/dist/agent/agent-live-read-runtime.d.ts +13 -2
- package/dist/agent/agent-live-read-runtime.d.ts.map +1 -1
- package/dist/agent/agent-live-read-runtime.js +6 -5
- package/dist/agent/agent-live-read-runtime.js.map +1 -1
- package/dist/browser/browser-admission.d.ts +47 -0
- package/dist/browser/browser-admission.d.ts.map +1 -0
- package/dist/browser/browser-admission.js +200 -0
- package/dist/browser/browser-admission.js.map +1 -0
- package/dist/browser/browser-server.d.ts +171 -1
- package/dist/browser/browser-server.d.ts.map +1 -1
- package/dist/browser/browser-server.js +1027 -303
- package/dist/browser/browser-server.js.map +1 -1
- package/dist/browser/durable-subscription-bindings.d.ts +5 -0
- package/dist/browser/durable-subscription-bindings.d.ts.map +1 -1
- package/dist/browser/durable-subscription-bindings.js +226 -38
- package/dist/browser/durable-subscription-bindings.js.map +1 -1
- package/dist/browser/index.d.ts +1 -1
- package/dist/browser/index.d.ts.map +1 -1
- package/dist/browser/options.d.ts +53 -2
- package/dist/browser/options.d.ts.map +1 -1
- package/dist/bus/command-bus.d.ts +9 -1
- package/dist/bus/command-bus.d.ts.map +1 -1
- package/dist/bus/command-bus.js +75 -10
- package/dist/bus/command-bus.js.map +1 -1
- package/dist/bus/command-errors.d.ts +20 -4
- package/dist/bus/command-errors.d.ts.map +1 -1
- package/dist/bus/command-errors.js +47 -7
- package/dist/bus/command-errors.js.map +1 -1
- package/dist/bus/event-bus.d.ts +9 -1
- package/dist/bus/event-bus.d.ts.map +1 -1
- package/dist/bus/event-bus.js +136 -9
- package/dist/bus/event-bus.js.map +1 -1
- package/dist/context/bounded-context.d.ts +59 -39
- package/dist/context/bounded-context.d.ts.map +1 -1
- package/dist/context/bounded-context.js +277 -262
- package/dist/context/bounded-context.js.map +1 -1
- package/dist/context/entity-inbox.d.ts +12 -17
- package/dist/context/entity-inbox.d.ts.map +1 -1
- package/dist/context/entity-inbox.js +106 -119
- package/dist/context/entity-inbox.js.map +1 -1
- package/dist/context/local-inbox-handoff.d.ts +13 -13
- package/dist/context/local-inbox-handoff.d.ts.map +1 -1
- package/dist/context/local-inbox-handoff.js +60 -32
- package/dist/context/local-inbox-handoff.js.map +1 -1
- package/dist/context/persistent-tenant-index.d.ts +81 -0
- package/dist/context/persistent-tenant-index.d.ts.map +1 -0
- package/dist/context/persistent-tenant-index.js +270 -0
- package/dist/context/persistent-tenant-index.js.map +1 -0
- package/dist/context/projection-handoff.d.ts +9 -9
- package/dist/context/projection-handoff.d.ts.map +1 -1
- package/dist/context/projection-handoff.js +31 -23
- package/dist/context/projection-handoff.js.map +1 -1
- package/dist/context/tenant-index.d.ts +7 -6
- package/dist/context/tenant-index.d.ts.map +1 -1
- package/dist/context/tenant-index.js +2 -188
- package/dist/context/tenant-index.js.map +1 -1
- package/dist/context/tenant-unknown-error.d.ts +2 -2
- package/dist/context/tenant-unknown-error.d.ts.map +1 -1
- package/dist/context/tenant-unknown-error.js +4 -1
- package/dist/context/tenant-unknown-error.js.map +1 -1
- package/dist/delivery/delivery-supervisor.d.ts.map +1 -1
- package/dist/delivery/delivery-supervisor.js +12 -3
- package/dist/delivery/delivery-supervisor.js.map +1 -1
- package/dist/delivery/delivery.d.ts +4 -3
- package/dist/delivery/delivery.d.ts.map +1 -1
- package/dist/delivery/delivery.js +82 -34
- package/dist/delivery/delivery.js.map +1 -1
- package/dist/delivery/reception-observation.d.ts +3 -3
- package/dist/delivery/reception-observation.js +2 -2
- package/dist/entity/entity.d.ts +59 -0
- package/dist/entity/entity.d.ts.map +1 -1
- package/dist/entity/entity.js +60 -0
- package/dist/entity/entity.js.map +1 -1
- package/dist/handler/app-query-handler.d.ts +60 -0
- package/dist/handler/app-query-handler.d.ts.map +1 -0
- package/dist/handler/app-query-handler.js +23 -0
- package/dist/handler/app-query-handler.js.map +1 -0
- package/dist/handler/generated-registry-discovery.d.ts +6 -0
- package/dist/handler/generated-registry-discovery.d.ts.map +1 -1
- package/dist/handler/generated-registry-discovery.js +71 -2
- package/dist/handler/generated-registry-discovery.js.map +1 -1
- package/dist/handler/handler-decorators.d.ts +8 -29
- package/dist/handler/handler-decorators.d.ts.map +1 -1
- package/dist/handler/handler-decorators.js +33 -210
- package/dist/handler/handler-decorators.js.map +1 -1
- package/dist/handler/spine-metadata-inventory.d.ts +99 -0
- package/dist/handler/spine-metadata-inventory.d.ts.map +1 -0
- package/dist/handler/spine-metadata-inventory.js +162 -0
- package/dist/handler/spine-metadata-inventory.js.map +1 -0
- package/dist/index.d.ts +9 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/repository/command-routing.d.ts +1 -1
- package/dist/repository/command-routing.js +1 -1
- package/dist/repository/event-routing.d.ts +1 -1
- package/dist/repository/event-routing.js +1 -1
- package/dist/repository/repository.d.ts +61 -26
- package/dist/repository/repository.d.ts.map +1 -1
- package/dist/repository/repository.js +612 -306
- package/dist/repository/repository.js.map +1 -1
- package/dist/repository/state-update-routing.d.ts +1 -1
- package/dist/repository/state-update-routing.js +1 -1
- package/dist/runtime/signal-publisher.d.ts +23 -8
- package/dist/runtime/signal-publisher.d.ts.map +1 -1
- package/dist/runtime/signal-publisher.js +11 -8
- package/dist/runtime/signal-publisher.js.map +1 -1
- package/dist/server/managed-server-application.d.ts +12 -11
- package/dist/server/managed-server-application.d.ts.map +1 -1
- package/dist/server/managed-server-application.js +41 -25
- package/dist/server/managed-server-application.js.map +1 -1
- package/dist/server/node-coordinator.d.ts +18 -5
- package/dist/server/node-coordinator.d.ts.map +1 -1
- package/dist/server/node-coordinator.js +744 -108
- package/dist/server/node-coordinator.js.map +1 -1
- package/dist/server/reserved-spine-rpc-paths.json +1 -0
- package/dist/server/server-environment.d.ts +23 -5
- package/dist/server/server-environment.d.ts.map +1 -1
- package/dist/server/server-environment.js +89 -24
- package/dist/server/server-environment.js.map +1 -1
- package/dist/server/server.d.ts +45 -0
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +112 -3
- package/dist/server/server.js.map +1 -1
- package/dist/services/app-query-execution.d.ts +53 -0
- package/dist/services/app-query-execution.d.ts.map +1 -0
- package/dist/services/app-query-execution.js +408 -0
- package/dist/services/app-query-execution.js.map +1 -0
- package/dist/services/native-operation.d.ts +34 -0
- package/dist/services/native-operation.d.ts.map +1 -0
- package/dist/services/native-operation.js +77 -0
- package/dist/services/native-operation.js.map +1 -0
- package/dist/services/query-reader.d.ts +32 -7
- package/dist/services/query-reader.d.ts.map +1 -1
- package/dist/services/query-reader.js +22 -2
- package/dist/services/query-reader.js.map +1 -1
- package/dist/services/read-request-status.d.ts +65 -0
- package/dist/services/read-request-status.d.ts.map +1 -0
- package/dist/services/read-request-status.js +151 -0
- package/dist/services/read-request-status.js.map +1 -0
- package/dist/services/spine-services.d.ts +42 -7
- package/dist/services/spine-services.d.ts.map +1 -1
- package/dist/services/spine-services.js +1005 -175
- package/dist/services/spine-services.js.map +1 -1
- package/dist/services/trusted-reader-ingress.d.ts +110 -0
- package/dist/services/trusted-reader-ingress.d.ts.map +1 -0
- package/dist/services/trusted-reader-ingress.js +102 -0
- package/dist/services/trusted-reader-ingress.js.map +1 -0
- package/dist/spi/handler-registry.d.ts +2 -0
- package/dist/spi/handler-registry.d.ts.map +1 -1
- package/dist/spi/handler-registry.js +1 -1
- package/dist/spi/handler-registry.js.map +1 -1
- package/dist/stand/read-side-filter-engine.d.ts +46 -0
- package/dist/stand/read-side-filter-engine.d.ts.map +1 -0
- package/dist/stand/read-side-filter-engine.js +356 -0
- package/dist/stand/read-side-filter-engine.js.map +1 -0
- package/dist/stand/read-side-filter.d.ts +247 -0
- package/dist/stand/read-side-filter.d.ts.map +1 -0
- package/dist/stand/read-side-filter.js +39 -0
- package/dist/stand/read-side-filter.js.map +1 -0
- package/dist/stand/read-side-message.d.ts +66 -0
- package/dist/stand/read-side-message.d.ts.map +1 -0
- package/dist/stand/read-side-message.js +167 -0
- package/dist/stand/read-side-message.js.map +1 -0
- package/dist/stand/stand-direct-delivery.d.ts +161 -0
- package/dist/stand/stand-direct-delivery.d.ts.map +1 -0
- package/dist/stand/stand-direct-delivery.js +210 -0
- package/dist/stand/stand-direct-delivery.js.map +1 -0
- package/dist/stand/stand.d.ts +81 -43
- package/dist/stand/stand.d.ts.map +1 -1
- package/dist/stand/stand.js +411 -55
- package/dist/stand/stand.js.map +1 -1
- package/dist/stand/subscription-observer.d.ts.map +1 -1
- package/dist/stand/subscription-observer.js +103 -0
- package/dist/stand/subscription-observer.js.map +1 -1
- package/dist/stand/subscription-read-filters.d.ts +40 -0
- package/dist/stand/subscription-read-filters.d.ts.map +1 -0
- package/dist/stand/subscription-read-filters.js +91 -0
- package/dist/stand/subscription-read-filters.js.map +1 -0
- package/dist/stand/subscription-reader-binding.d.ts +20 -0
- package/dist/stand/subscription-reader-binding.d.ts.map +1 -0
- package/dist/stand/subscription-reader-binding.js +56 -0
- package/dist/stand/subscription-reader-binding.js.map +1 -0
- package/dist/stand/subscription-records.d.ts +30 -3
- package/dist/stand/subscription-records.d.ts.map +1 -1
- package/dist/stand/subscription-records.js +61 -16
- package/dist/stand/subscription-records.js.map +1 -1
- package/dist/stand/subscription-registry.d.ts +49 -14
- package/dist/stand/subscription-registry.d.ts.map +1 -1
- package/dist/stand/subscription-registry.js +150 -31
- package/dist/stand/subscription-registry.js.map +1 -1
- package/dist/stand/subscription-runtime.d.ts +7 -2
- package/dist/stand/subscription-runtime.d.ts.map +1 -1
- package/dist/stand/subscription-runtime.js +131 -25
- package/dist/stand/subscription-runtime.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +16 -14
|
@@ -11,10 +11,6 @@
|
|
|
11
11
|
* or implied. See the License for the specific language governing permissions and limitations under
|
|
12
12
|
* the License.
|
|
13
13
|
*/
|
|
14
|
-
import { constants as fsConstants } from "node:fs";
|
|
15
|
-
import { access, realpath } from "node:fs/promises";
|
|
16
|
-
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
17
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
18
14
|
import { clone, create, getOption, hasOption } from "@bufbuild/protobuf";
|
|
19
15
|
import { freezeRegistry, isAiModel, registryOptions } from "@spine-event-engine/ai/spi/runtime";
|
|
20
16
|
import { TypeUrls } from "@spine-event-engine/core";
|
|
@@ -28,18 +24,20 @@ import { DeliveryReadiness } from "./local-inbox-handoff.js";
|
|
|
28
24
|
import { LocalEntityInbox } from "./entity-inbox.js";
|
|
29
25
|
import { LocalProjectionInbox } from "./projection-handoff.js";
|
|
30
26
|
import { TenantAdmissions } from "./tenant-unknown-error.js";
|
|
27
|
+
import { SINGLE_TENANT } from "./tenant-index.js";
|
|
31
28
|
import { AgentExecutionCapacity, } from "../agent/agent-execution-capacity.js";
|
|
32
29
|
import { AgentAcceptanceReceipts, agentAcceptanceReceiptGroup, agentAcceptanceReceiptSpec, } from "../agent/agent-acceptance-receipts.js";
|
|
33
30
|
import { Repository, repositoryAccess, } from "../repository/repository.js";
|
|
34
|
-
import {
|
|
31
|
+
import { SpineMetadataInventory } from "../handler/spine-metadata-inventory.js";
|
|
35
32
|
import { HandlerRegistryIngestor, } from "../handler/generated-handler-registry.js";
|
|
36
33
|
import { AbstractAssignee, AbstractCommander, AbstractEventReactor, AbstractEventSubscriber, } from "../handler/standalone.js";
|
|
37
34
|
import { HandlerMetadataRegistry, } from "../handler/handler-metadata.js";
|
|
38
35
|
import { SignalMetadata } from "../runtime/signal-metadata.js";
|
|
39
36
|
import { runtimeAccess } from "../runtime/runtime.js";
|
|
40
37
|
import { Stand, standAccess } from "../stand/stand.js";
|
|
38
|
+
import { ReadSideFilter } from "../stand/read-side-filter.js";
|
|
41
39
|
import { SubscriptionRuntime, subscriptionRuntimeAccess } from "../stand/subscription-runtime.js";
|
|
42
|
-
import { StorageSubscriptionRegistry,
|
|
40
|
+
import { StorageSubscriptionRegistry, subscriptionRecordGroup, } from "../stand/subscription-registry.js";
|
|
43
41
|
import { IntegrationBroker } from "../integration/integration-broker.js";
|
|
44
42
|
import { ServerEnvironment, serverEnvironmentAccess } from "../server/server-environment.js";
|
|
45
43
|
import { SignalPublisher } from "../runtime/signal-publisher.js";
|
|
@@ -100,11 +98,8 @@ export class DuplicateEntityStateException extends Error {
|
|
|
100
98
|
const frameworkConstructionToken = Object.freeze({
|
|
101
99
|
frameworkConstructionToken: true,
|
|
102
100
|
});
|
|
103
|
-
const generatedRegistryFile = "generated/handler/generated-handler-registry.js";
|
|
104
101
|
const errorDetailLimit = 500;
|
|
105
|
-
const moduleSchemeRe = /^[A-Za-z][A-Za-z\d+.-]*:/;
|
|
106
102
|
const internalStoragePrefix = "__spine/";
|
|
107
|
-
const generatedRegistryLoadAttempts = new Map();
|
|
108
103
|
const eventSubscribers = new WeakMap();
|
|
109
104
|
const contextSystemPairings = new WeakMap();
|
|
110
105
|
const contextTenantIndexes = new WeakMap();
|
|
@@ -158,7 +153,7 @@ export class BoundedContext {
|
|
|
158
153
|
#agentReceipts;
|
|
159
154
|
#agentController = new AbortController();
|
|
160
155
|
#agentReservations = new Set();
|
|
161
|
-
#
|
|
156
|
+
#activeDeliveries = new Set();
|
|
162
157
|
#activePreparations = new Set();
|
|
163
158
|
#tenantPreparations = new Map();
|
|
164
159
|
#preparationController = new AbortController();
|
|
@@ -232,7 +227,7 @@ export class BoundedContext {
|
|
|
232
227
|
eventBusAccess.bindEnvironment(this.#eventBus, this.#environment);
|
|
233
228
|
eventBusAccess.bindEnvironment(this.#systemEventBus, this.#environment);
|
|
234
229
|
commandBusAccess.setAdmission(this.#commandBus, (command) => this.#admitTenant(command.context?.actorContext?.tenantId));
|
|
235
|
-
eventBusAccess.setAdmission(this.#eventBus, (event) => this.#admitTenant(ContextParts.
|
|
230
|
+
eventBusAccess.setAdmission(this.#eventBus, (event) => this.#admitTenant(ContextParts.readCatchUpTenant(event)));
|
|
236
231
|
standAccess.setAdmission(this.#stand, (tenantId) => this.#admitTenant(tenantId));
|
|
237
232
|
standAccess.setPublicAdmission(this.#stand, () => {
|
|
238
233
|
if (closingContexts.has(this) && !runtimeAccess.isRunningAdmittedWork(this.#environment))
|
|
@@ -265,11 +260,12 @@ export class BoundedContext {
|
|
|
265
260
|
* @returns Shared preparation completion.
|
|
266
261
|
*/
|
|
267
262
|
#prepareShared(mode) {
|
|
268
|
-
|
|
263
|
+
const registry = this.#subscriptionRuntime.registry();
|
|
264
|
+
if (!(registry instanceof StorageSubscriptionRegistry))
|
|
269
265
|
return Promise.resolve();
|
|
270
266
|
if (this.#sharedPreparation !== undefined)
|
|
271
267
|
return this.#sharedPreparation;
|
|
272
|
-
const attempt = this.#trackPreparation(() =>
|
|
268
|
+
const attempt = this.#trackPreparation(() => registry.prepare(mode));
|
|
273
269
|
this.#sharedPreparation = attempt;
|
|
274
270
|
void attempt.catch(() => {
|
|
275
271
|
if (this.#sharedPreparation === attempt)
|
|
@@ -379,10 +375,13 @@ export class BoundedContext {
|
|
|
379
375
|
async #prepareCurrentTenants(mode) {
|
|
380
376
|
if (!this.isMultitenant)
|
|
381
377
|
return this.#requiredPrepare(() => this.#prepareTenant(undefined, mode));
|
|
378
|
+
const index = this.tenantIndex;
|
|
379
|
+
if (index === SINGLE_TENANT)
|
|
380
|
+
throw new Error("Multitenant preparation requires a tenant index.");
|
|
382
381
|
let after;
|
|
383
382
|
do {
|
|
384
383
|
serverEnvironmentAccess.requireAvailable(this.#environment);
|
|
385
|
-
const page = await
|
|
384
|
+
const page = await index.page({
|
|
386
385
|
count: 64,
|
|
387
386
|
signal: this.#preparationController.signal,
|
|
388
387
|
...(after === undefined ? {} : { after }),
|
|
@@ -390,7 +389,7 @@ export class BoundedContext {
|
|
|
390
389
|
serverEnvironmentAccess.requireAvailable(this.#environment);
|
|
391
390
|
for (const id of page.ids) {
|
|
392
391
|
serverEnvironmentAccess.requireAvailable(this.#environment);
|
|
393
|
-
await TenantAdmissions.require(
|
|
392
|
+
await TenantAdmissions.require(index, this.name.value, true, id);
|
|
394
393
|
serverEnvironmentAccess.requireAvailable(this.#environment);
|
|
395
394
|
await this.#requiredPrepare(() => this.#prepareTenant(id, mode));
|
|
396
395
|
}
|
|
@@ -473,8 +472,8 @@ export class BoundedContext {
|
|
|
473
472
|
/**
|
|
474
473
|
* Creates tenant-aware Inboxes and their shared readiness signal.
|
|
475
474
|
*
|
|
476
|
-
* @param tenantIndex
|
|
477
|
-
* @returns Inboxes, tenant
|
|
475
|
+
* @param tenantIndex Captured marker or directory for this Bounded Context.
|
|
476
|
+
* @returns Inboxes, tenant setting, and readiness signal.
|
|
478
477
|
*/
|
|
479
478
|
#createDelivery(tenantIndex) {
|
|
480
479
|
const deliveryReadiness = this.#createReadiness();
|
|
@@ -482,8 +481,8 @@ export class BoundedContext {
|
|
|
482
481
|
const entityInbox = new LocalEntityInbox(this.#snapshot.name.value, deliveryReadiness, onAdmit, this.#environment.delivery.strategy);
|
|
483
482
|
const projectionInbox = new LocalProjectionInbox(this.#snapshot.name.value, deliveryReadiness, onAdmit);
|
|
484
483
|
deliveryReadiness.configureDirect((message, tenantId) => message.label === "UPDATE_SUBSCRIBER"
|
|
485
|
-
? projectionInbox.
|
|
486
|
-
: entityInbox.
|
|
484
|
+
? projectionInbox.deliver(message, tenantId)
|
|
485
|
+
: entityInbox.deliver(message, tenantId));
|
|
487
486
|
return { deliveryReadiness, tenantIndex, entityInbox, projectionInbox };
|
|
488
487
|
}
|
|
489
488
|
/**
|
|
@@ -523,10 +522,14 @@ export class BoundedContext {
|
|
|
523
522
|
this.#beginClose();
|
|
524
523
|
},
|
|
525
524
|
drain: () => this.#drainWork(),
|
|
526
|
-
|
|
525
|
+
trackDelivery: (onDelivery) => this.#trackDelivery(onDelivery),
|
|
527
526
|
});
|
|
528
527
|
contextDeliveryDescriptors.set(this, ContextParts.createDeliveryDescriptor(this.#entityInbox, this.#projectionInbox));
|
|
529
528
|
}
|
|
529
|
+
/**
|
|
530
|
+
* Prepares repository registrations, reserves their state types, and commits their Stand and
|
|
531
|
+
* inbox targets; rolls back uncommitted storage preparation on failure.
|
|
532
|
+
*/
|
|
530
533
|
#registerRepositories(repositories) {
|
|
531
534
|
const preparedRepositories = this.#prepareRepositories(repositories);
|
|
532
535
|
try {
|
|
@@ -566,6 +569,10 @@ export class BoundedContext {
|
|
|
566
569
|
throw new DuplicateEntityStateException(stateType, prior.name.value, this.name.value);
|
|
567
570
|
}
|
|
568
571
|
}
|
|
572
|
+
/**
|
|
573
|
+
* Builds prepared repository registrations using this context’s storage and delivery services,
|
|
574
|
+
* cleaning earlier preparations if a later one fails.
|
|
575
|
+
*/
|
|
569
576
|
#prepareRepositories(repositories) {
|
|
570
577
|
const registration = this.#repositoryRegistration();
|
|
571
578
|
const preparedRepositories = [];
|
|
@@ -641,6 +648,10 @@ export class BoundedContext {
|
|
|
641
648
|
...(tenantId === undefined ? {} : { tenantId }),
|
|
642
649
|
});
|
|
643
650
|
}
|
|
651
|
+
/**
|
|
652
|
+
* Closes storage for repositories that were prepared but not committed, then rethrows the
|
|
653
|
+
* registration error with cleanup failures aggregated.
|
|
654
|
+
*/
|
|
644
655
|
#failRegistration(error, preparedRepositories) {
|
|
645
656
|
const uncommitted = preparedRepositories.filter(({ repository }) => !this.#repositoryViews.has(repository));
|
|
646
657
|
const closeErrors = ContextParts.closePreparedRepositories(uncommitted);
|
|
@@ -692,9 +703,10 @@ export class BoundedContext {
|
|
|
692
703
|
return this.#snapshot.tenantMode === "multitenant";
|
|
693
704
|
}
|
|
694
705
|
/**
|
|
695
|
-
* Returns the
|
|
706
|
+
* Returns the captured tenant setting for this Bounded Context.
|
|
696
707
|
*
|
|
697
|
-
* @returns
|
|
708
|
+
* @returns SINGLE_TENANT in single mode, even when the environment has an index;
|
|
709
|
+
* otherwise the effective directory. Narrow before calling index methods.
|
|
698
710
|
*/
|
|
699
711
|
get tenantIndex() {
|
|
700
712
|
return ContextParts.requireTenantIndex(this);
|
|
@@ -774,17 +786,16 @@ export class BoundedContext {
|
|
|
774
786
|
throw new Error("Entity constructor is not registered in this Bounded Context.");
|
|
775
787
|
}
|
|
776
788
|
/**
|
|
777
|
-
* Clears
|
|
789
|
+
* Clears registered Projection state and processes already-stored Events locally.
|
|
778
790
|
*
|
|
779
|
-
*
|
|
780
|
-
* process-local maintenance helper and provides no Projection targeting,
|
|
791
|
+
* This process-local maintenance helper provides no Projection targeting,
|
|
781
792
|
* historical starting point, durable operation identity, progress,
|
|
782
793
|
* historical/live coordination, restart, resumption, or multi-node work.
|
|
783
794
|
*
|
|
784
795
|
* Supported boundary:
|
|
785
796
|
* - projection subscribers only;
|
|
786
797
|
* - already-stored events only;
|
|
787
|
-
* - clear then
|
|
798
|
+
* - clear then dispatch stored Events, with no event re-append;
|
|
788
799
|
* - single-tenant contexts reject `tenantId`;
|
|
789
800
|
* - multitenant contexts require the exact non-blank `tenantId`;
|
|
790
801
|
* - process-local sequential execution on the same EventBus runtime queue as
|
|
@@ -796,11 +807,15 @@ export class BoundedContext {
|
|
|
796
807
|
* - durable live-traffic catch-up orchestration across processes.
|
|
797
808
|
*
|
|
798
809
|
* @param options Selects the tenant slice to rebuild.
|
|
799
|
-
* @returns
|
|
810
|
+
* @returns Dispatch and clear counts for the selected slice.
|
|
800
811
|
*/
|
|
801
812
|
async catchUpReadSide(options = {}) {
|
|
802
813
|
return eventBusAccess.runExclusive(this.#eventBus, () => this.#catchUpReadSideOnce(options));
|
|
803
814
|
}
|
|
815
|
+
/**
|
|
816
|
+
* Clears registered Projection state for the selected tenant slice, reads stored events,
|
|
817
|
+
* validates each Event’s tenant, and dispatches stored Events sequentially.
|
|
818
|
+
*/
|
|
804
819
|
async #catchUpReadSideOnce(options) {
|
|
805
820
|
const storageContext = ContextParts.catchUpStorageContext(this.#snapshot.spec, options);
|
|
806
821
|
const tenantOptions = ContextParts.catchUpStandOptions(storageContext);
|
|
@@ -808,7 +823,7 @@ export class BoundedContext {
|
|
|
808
823
|
const clearTargets = ContextParts.projectionStateClearTargets(projections);
|
|
809
824
|
const clearedStateTypes = [];
|
|
810
825
|
let clearedEntityCount = 0;
|
|
811
|
-
let
|
|
826
|
+
let processedEventCount = 0;
|
|
812
827
|
for (const target of clearTargets) {
|
|
813
828
|
clearedEntityCount += await this.#stand.clear(target.schema, tenantOptions);
|
|
814
829
|
clearedStateTypes.push(target.typeUrl);
|
|
@@ -816,15 +831,15 @@ export class BoundedContext {
|
|
|
816
831
|
const events = await ContextParts.readStoredEvents(storageContext, this.#storageFactory);
|
|
817
832
|
for (const event of events) {
|
|
818
833
|
try {
|
|
819
|
-
ContextParts.
|
|
820
|
-
|
|
834
|
+
ContextParts.validateCatchUpTenant(storageContext, event);
|
|
835
|
+
processedEventCount += await ContextParts.dispatchStoredProjectionEvent(projections, event, true);
|
|
821
836
|
}
|
|
822
837
|
catch (error) {
|
|
823
|
-
throw ContextParts.
|
|
838
|
+
throw ContextParts.catchUpDispatchError(event, error);
|
|
824
839
|
}
|
|
825
840
|
}
|
|
826
841
|
return Object.freeze({
|
|
827
|
-
|
|
842
|
+
processedEventCount,
|
|
828
843
|
clearedEntityCount,
|
|
829
844
|
clearedStateTypes: Object.freeze(clearedStateTypes),
|
|
830
845
|
});
|
|
@@ -891,13 +906,12 @@ export class BoundedContext {
|
|
|
891
906
|
ContextParts.clearContextMetadata(this);
|
|
892
907
|
}
|
|
893
908
|
/**
|
|
894
|
-
* Settles entered
|
|
909
|
+
* Settles entered deliveries, outgoing handoffs, and reserved Agent turns.
|
|
895
910
|
* @param errors Collects independent close failures.
|
|
896
911
|
*/
|
|
897
912
|
async #drainAdmittedContextWork(errors) {
|
|
898
913
|
await this.#closePart(() => Promise.allSettled([...this.#activePreparations]).then(() => undefined), errors);
|
|
899
|
-
await this.#closePart(() => Promise.allSettled([...this.#
|
|
900
|
-
await this.#closePart(() => this.#entityInbox.whenFollowUpsIdle(), errors);
|
|
914
|
+
await this.#closePart(() => Promise.allSettled([...this.#activeDeliveries]).then(() => undefined), errors);
|
|
901
915
|
await this.#closePart(() => Promise.allSettled([...this.#agentReservations]).then(() => undefined), errors);
|
|
902
916
|
}
|
|
903
917
|
/**
|
|
@@ -932,15 +946,15 @@ export class BoundedContext {
|
|
|
932
946
|
}
|
|
933
947
|
}
|
|
934
948
|
/**
|
|
935
|
-
* Registers an entered
|
|
936
|
-
* @param
|
|
937
|
-
* @returns Completion
|
|
949
|
+
* Registers an entered Inbox delivery through recipient processing and outgoing handoff.
|
|
950
|
+
* @param onDelivery Delivers the stored message.
|
|
951
|
+
* @returns Completion after the delivery callback returns.
|
|
938
952
|
*/
|
|
939
|
-
#
|
|
940
|
-
const
|
|
941
|
-
this.#
|
|
942
|
-
void
|
|
943
|
-
return
|
|
953
|
+
#trackDelivery(onDelivery) {
|
|
954
|
+
const delivery = Promise.resolve().then(onDelivery);
|
|
955
|
+
this.#activeDeliveries.add(delivery);
|
|
956
|
+
void delivery.then(() => this.#activeDeliveries.delete(delivery), () => this.#activeDeliveries.delete(delivery));
|
|
957
|
+
return delivery;
|
|
944
958
|
}
|
|
945
959
|
/**
|
|
946
960
|
* Calls a close action and collects its failure before later cleanup.
|
|
@@ -992,7 +1006,7 @@ export const boundedContextAccess = Object.freeze({
|
|
|
992
1006
|
const phase = contextClosePhases.get(context);
|
|
993
1007
|
if (prepare === undefined || phase === undefined)
|
|
994
1008
|
return Promise.reject(new TypeError("Service input requires a built context."));
|
|
995
|
-
return phase.
|
|
1009
|
+
return phase.trackDelivery(async () => {
|
|
996
1010
|
await onMembership();
|
|
997
1011
|
await prepare(tenantId);
|
|
998
1012
|
await onReady?.();
|
|
@@ -1011,13 +1025,16 @@ export const boundedContextAccess = Object.freeze({
|
|
|
1011
1025
|
const phase = contextClosePhases.get(context);
|
|
1012
1026
|
if (phase === undefined)
|
|
1013
1027
|
return Promise.reject(new TypeError("Service registry requires a built context."));
|
|
1014
|
-
return phase.
|
|
1028
|
+
return phase.trackDelivery(onOperation);
|
|
1015
1029
|
},
|
|
1016
|
-
|
|
1030
|
+
/**
|
|
1031
|
+
* Registers entered delivery work with the context close lifecycle.
|
|
1032
|
+
*/
|
|
1033
|
+
trackDelivery(context, onDelivery) {
|
|
1017
1034
|
const phase = contextClosePhases.get(context);
|
|
1018
1035
|
if (phase === undefined)
|
|
1019
|
-
return Promise.reject(new TypeError("
|
|
1020
|
-
return phase.
|
|
1036
|
+
return Promise.reject(new TypeError("Delivery requires a built context."));
|
|
1037
|
+
return phase.trackDelivery(onDelivery);
|
|
1021
1038
|
},
|
|
1022
1039
|
/**
|
|
1023
1040
|
* Resolves a view only within the Bounded Context that issued it.
|
|
@@ -1035,18 +1052,27 @@ export const boundedContextAccess = Object.freeze({
|
|
|
1035
1052
|
? issued.repository
|
|
1036
1053
|
: undefined;
|
|
1037
1054
|
},
|
|
1055
|
+
/**
|
|
1056
|
+
* Stops new work at this Bounded Context and begins shutdown of its runtime facilities.
|
|
1057
|
+
*/
|
|
1038
1058
|
beginClose(context) {
|
|
1039
1059
|
const phase = contextClosePhases.get(context);
|
|
1040
1060
|
if (phase === undefined)
|
|
1041
1061
|
throw new TypeError("Close phase requires a built BoundedContext.");
|
|
1042
1062
|
phase.begin();
|
|
1043
1063
|
},
|
|
1064
|
+
/**
|
|
1065
|
+
* Waits for accepted runtime work and entered delivery operations to finish.
|
|
1066
|
+
*/
|
|
1044
1067
|
drainWork(context) {
|
|
1045
1068
|
const phase = contextClosePhases.get(context);
|
|
1046
1069
|
if (phase === undefined)
|
|
1047
1070
|
return Promise.reject(new TypeError("Drain requires a built BoundedContext."));
|
|
1048
1071
|
return phase.drain();
|
|
1049
1072
|
},
|
|
1073
|
+
/**
|
|
1074
|
+
* Installs the context logger on both EventBuses, the signal publisher, and subscription runtime.
|
|
1075
|
+
*/
|
|
1050
1076
|
installLogger(context, logger) {
|
|
1051
1077
|
if (!contextStorageFactories.has(context)) {
|
|
1052
1078
|
throw new TypeError("Context logger requires a built BoundedContext instance.");
|
|
@@ -1068,6 +1094,9 @@ export const boundedContextAccess = Object.freeze({
|
|
|
1068
1094
|
}
|
|
1069
1095
|
subscriptionRuntimeAccess.installLogger(runtime, logger, context.name.value);
|
|
1070
1096
|
},
|
|
1097
|
+
/**
|
|
1098
|
+
* Returns the diagnostic logger installed on this Bounded Context.
|
|
1099
|
+
*/
|
|
1071
1100
|
loggerFor(context) {
|
|
1072
1101
|
const logger = contextLoggers.get(context);
|
|
1073
1102
|
if (logger === undefined) {
|
|
@@ -1075,11 +1104,17 @@ export const boundedContextAccess = Object.freeze({
|
|
|
1075
1104
|
}
|
|
1076
1105
|
return logger;
|
|
1077
1106
|
},
|
|
1107
|
+
/**
|
|
1108
|
+
* Checks whether a value is a BoundedContextBuilder created by this module.
|
|
1109
|
+
*/
|
|
1078
1110
|
isBuilder(value) {
|
|
1079
1111
|
return (typeof value === "object" &&
|
|
1080
1112
|
value !== null &&
|
|
1081
1113
|
builderBuilds.has(value));
|
|
1082
1114
|
},
|
|
1115
|
+
/**
|
|
1116
|
+
* Builds the supplied context builder and completes its configured initialization.
|
|
1117
|
+
*/
|
|
1083
1118
|
build(builder) {
|
|
1084
1119
|
const build = builderBuilds.get(builder);
|
|
1085
1120
|
if (build === undefined) {
|
|
@@ -1087,6 +1122,9 @@ export const boundedContextAccess = Object.freeze({
|
|
|
1087
1122
|
}
|
|
1088
1123
|
return build();
|
|
1089
1124
|
},
|
|
1125
|
+
/**
|
|
1126
|
+
* Attaches a subscriber to the context domain EventBus for the requested event type.
|
|
1127
|
+
*/
|
|
1090
1128
|
subscribeToEvent(context, typeUrl, subscriber) {
|
|
1091
1129
|
const subscribe = eventSubscribers.get(context);
|
|
1092
1130
|
if (subscribe === undefined) {
|
|
@@ -1094,6 +1132,9 @@ export const boundedContextAccess = Object.freeze({
|
|
|
1094
1132
|
}
|
|
1095
1133
|
return subscribe(typeUrl, subscriber);
|
|
1096
1134
|
},
|
|
1135
|
+
/**
|
|
1136
|
+
* Posts a framework System Event through the context System EventBus.
|
|
1137
|
+
*/
|
|
1097
1138
|
postSystemEvent(context, event) {
|
|
1098
1139
|
const post = systemEventPosters.get(context);
|
|
1099
1140
|
if (post === undefined) {
|
|
@@ -1101,12 +1142,18 @@ export const boundedContextAccess = Object.freeze({
|
|
|
1101
1142
|
}
|
|
1102
1143
|
return post(event);
|
|
1103
1144
|
},
|
|
1145
|
+
/**
|
|
1146
|
+
* Posts an external domain Event through the context EventBus.
|
|
1147
|
+
*/
|
|
1104
1148
|
postExternalEvent(context, event) {
|
|
1105
1149
|
const imported = clone(EventSchema, event);
|
|
1106
1150
|
imported.context = clone(EventContextSchema, imported.context ?? create(EventContextSchema));
|
|
1107
1151
|
imported.context.external = true;
|
|
1108
1152
|
return context.eventBus().post(imported);
|
|
1109
1153
|
},
|
|
1154
|
+
/**
|
|
1155
|
+
* Subscribes to events produced by this context and forwards matching signals to the supplied observer.
|
|
1156
|
+
*/
|
|
1110
1157
|
observeProducedSignals(context, observer) {
|
|
1111
1158
|
const publisher = contextSignalPublishers.get(context);
|
|
1112
1159
|
if (publisher === undefined) {
|
|
@@ -1114,12 +1161,21 @@ export const boundedContextAccess = Object.freeze({
|
|
|
1114
1161
|
}
|
|
1115
1162
|
return publisher.observe(observer);
|
|
1116
1163
|
},
|
|
1164
|
+
/**
|
|
1165
|
+
* Returns the domain/System specification pairing captured when the context was built.
|
|
1166
|
+
*/
|
|
1117
1167
|
systemPairing(context) {
|
|
1118
1168
|
return ContextParts.cloneSystemPairing(ContextParts.requireSystemPairing(context));
|
|
1119
1169
|
},
|
|
1170
|
+
/**
|
|
1171
|
+
* Returns the captured marker or directory for this Bounded Context.
|
|
1172
|
+
*/
|
|
1120
1173
|
tenantIndex(context) {
|
|
1121
1174
|
return ContextParts.requireTenantIndex(context);
|
|
1122
1175
|
},
|
|
1176
|
+
/**
|
|
1177
|
+
* Returns the storage factory captured for repository and Stand operations in this context.
|
|
1178
|
+
*/
|
|
1123
1179
|
storageFactory(context) {
|
|
1124
1180
|
const storageFactory = contextStorageFactories.get(context);
|
|
1125
1181
|
if (storageFactory === undefined) {
|
|
@@ -1127,6 +1183,9 @@ export const boundedContextAccess = Object.freeze({
|
|
|
1127
1183
|
}
|
|
1128
1184
|
return storageFactory;
|
|
1129
1185
|
},
|
|
1186
|
+
/**
|
|
1187
|
+
* Returns the persistent or in-memory registry used to store Stand subscription definitions.
|
|
1188
|
+
*/
|
|
1130
1189
|
subscriptionRegistry(context) {
|
|
1131
1190
|
const runtime = contextSubscriptionRuntimes.get(context);
|
|
1132
1191
|
if (runtime === undefined) {
|
|
@@ -1134,13 +1193,24 @@ export const boundedContextAccess = Object.freeze({
|
|
|
1134
1193
|
}
|
|
1135
1194
|
return runtime.registry();
|
|
1136
1195
|
},
|
|
1137
|
-
|
|
1196
|
+
/**
|
|
1197
|
+
* Consumes a registered Stand subscription update and returns its managed delivery handle.
|
|
1198
|
+
*/
|
|
1199
|
+
consumeSubscription(context, id, onUpdate, onInvalidated, expected) {
|
|
1138
1200
|
const runtime = contextSubscriptionRuntimes.get(context);
|
|
1139
1201
|
if (runtime === undefined) {
|
|
1140
1202
|
throw new TypeError("Subscription consumption requires a built BoundedContext instance.");
|
|
1141
1203
|
}
|
|
1142
|
-
return runtime.consume(id,
|
|
1204
|
+
return runtime.consume(id, (update) => {
|
|
1205
|
+
onUpdate({
|
|
1206
|
+
update,
|
|
1207
|
+
filter: (facts) => standAccess.filterSubscription(context.stand(), update, facts),
|
|
1208
|
+
});
|
|
1209
|
+
}, onInvalidated, expected);
|
|
1143
1210
|
},
|
|
1211
|
+
/**
|
|
1212
|
+
* Returns the context delivery descriptor used to route Entity and Projection inbox work.
|
|
1213
|
+
*/
|
|
1144
1214
|
delivery(context) {
|
|
1145
1215
|
const descriptor = contextDeliveryDescriptors.get(context);
|
|
1146
1216
|
if (descriptor === undefined) {
|
|
@@ -1161,12 +1231,12 @@ export class BoundedContextBuilder {
|
|
|
1161
1231
|
#eventReceivers = [];
|
|
1162
1232
|
#repositories = new Set();
|
|
1163
1233
|
#entityTypes = new Set();
|
|
1234
|
+
#readFilters = new Set();
|
|
1164
1235
|
#generatedRepositoryOptions = new Map();
|
|
1165
1236
|
#aiRegistry;
|
|
1166
1237
|
#tenantIndex;
|
|
1167
1238
|
#subscriptionRegistry;
|
|
1168
1239
|
#persistSystemEvents = false;
|
|
1169
|
-
#generatedRegistryRoot;
|
|
1170
1240
|
/**
|
|
1171
1241
|
* Registers the framework-only construction hook for this module.
|
|
1172
1242
|
*/
|
|
@@ -1217,6 +1287,15 @@ export class BoundedContextBuilder {
|
|
|
1217
1287
|
isMultitenant() {
|
|
1218
1288
|
return this.#specSnapshot.multitenant;
|
|
1219
1289
|
}
|
|
1290
|
+
/**
|
|
1291
|
+
* Adds an outward read policy to this Bounded Context.
|
|
1292
|
+
* @param filter Policy applied before Server-level outward policies.
|
|
1293
|
+
* @returns This builder for further configuration.
|
|
1294
|
+
*/
|
|
1295
|
+
addReadSideFilter(filter) {
|
|
1296
|
+
this.#readFilters.add(filter);
|
|
1297
|
+
return this;
|
|
1298
|
+
}
|
|
1220
1299
|
/**
|
|
1221
1300
|
* Adds an explicitly assembled repository or an Entity class.
|
|
1222
1301
|
*
|
|
@@ -1321,7 +1400,9 @@ export class BoundedContextBuilder {
|
|
|
1321
1400
|
return this;
|
|
1322
1401
|
}
|
|
1323
1402
|
/**
|
|
1324
|
-
* Sets a
|
|
1403
|
+
* Sets a directory that overrides the environment index for this multitenant Bounded Context.
|
|
1404
|
+
* The environment must already have a real index at build time; an override
|
|
1405
|
+
* cannot enable multitenancy in a SINGLE_TENANT environment.
|
|
1325
1406
|
*
|
|
1326
1407
|
* @param tenantIndex Application membership index.
|
|
1327
1408
|
* @returns This builder.
|
|
@@ -1365,16 +1446,6 @@ export class BoundedContextBuilder {
|
|
|
1365
1446
|
this.#subscriptionRegistry = registry;
|
|
1366
1447
|
return this;
|
|
1367
1448
|
}
|
|
1368
|
-
/**
|
|
1369
|
-
* Sets a trusted compiled application root for generated handler metadata.
|
|
1370
|
-
*
|
|
1371
|
-
* @param root Names the compiled package or application root.
|
|
1372
|
-
* @returns Returns this builder for further configuration.
|
|
1373
|
-
*/
|
|
1374
|
-
withGeneratedRegistryRoot(root) {
|
|
1375
|
-
this.#generatedRegistryRoot = root;
|
|
1376
|
-
return this;
|
|
1377
|
-
}
|
|
1378
1449
|
/**
|
|
1379
1450
|
* Builds a context after loading generated handlers and preparing runtime resources.
|
|
1380
1451
|
*
|
|
@@ -1384,6 +1455,10 @@ export class BoundedContextBuilder {
|
|
|
1384
1455
|
build(options = {}) {
|
|
1385
1456
|
return this.#buildAsyncWith(options);
|
|
1386
1457
|
}
|
|
1458
|
+
/**
|
|
1459
|
+
* Loads generated handlers and repositories, assembles context facilities, and completes
|
|
1460
|
+
* requested initialization before publishing the context.
|
|
1461
|
+
*/
|
|
1387
1462
|
async #buildAsyncWith(options = {}) {
|
|
1388
1463
|
const environment = ServerEnvironment.instance();
|
|
1389
1464
|
const finishBuild = serverEnvironmentAccess.beginContextBuild(environment);
|
|
@@ -1415,10 +1490,11 @@ export class BoundedContextBuilder {
|
|
|
1415
1490
|
* @returns The integrated context.
|
|
1416
1491
|
*/
|
|
1417
1492
|
async #buildAsyncReady(options, environment) {
|
|
1418
|
-
const
|
|
1493
|
+
const tenantIndex = this.#selectTenantIndex(environment);
|
|
1494
|
+
const generated = this.#loadGeneratedArtifacts([...this.#entityTypes]);
|
|
1419
1495
|
serverEnvironmentAccess.requireAvailable(environment);
|
|
1420
1496
|
const repositories = [...this.#repositories, ...generated.repositories];
|
|
1421
|
-
const context = await this.#buildWithResources(repositories, environment.storageFactory, generated.standalone, this.#aiRegistry ?? environment.ai);
|
|
1497
|
+
const context = await this.#buildWithResources(repositories, environment.storageFactory, generated.standalone, this.#aiRegistry ?? environment.ai, tenantIndex);
|
|
1422
1498
|
try {
|
|
1423
1499
|
await this.#integrateBuiltContext(context, options, environment);
|
|
1424
1500
|
}
|
|
@@ -1427,6 +1503,20 @@ export class BoundedContextBuilder {
|
|
|
1427
1503
|
}
|
|
1428
1504
|
return context;
|
|
1429
1505
|
}
|
|
1506
|
+
/**
|
|
1507
|
+
* Captures tenant selection before acquiring business storage.
|
|
1508
|
+
*
|
|
1509
|
+
* @param environment Process facilities selected for this build.
|
|
1510
|
+
* @returns Marker for single mode or the effective directory for multi mode.
|
|
1511
|
+
*/
|
|
1512
|
+
#selectTenantIndex(environment) {
|
|
1513
|
+
if (!this.#specSnapshot.multitenant)
|
|
1514
|
+
return SINGLE_TENANT;
|
|
1515
|
+
const configured = environment.tenantIndex;
|
|
1516
|
+
if (configured === SINGLE_TENANT)
|
|
1517
|
+
throw new Error("Multitenant Bounded Context requires an environment tenant index.");
|
|
1518
|
+
return this.#tenantIndex ?? configured;
|
|
1519
|
+
}
|
|
1430
1520
|
/**
|
|
1431
1521
|
* Completes required integration and requested initialization before publication.
|
|
1432
1522
|
* @param context Constructed context.
|
|
@@ -1475,20 +1565,33 @@ export class BoundedContextBuilder {
|
|
|
1475
1565
|
* @param storageFactory Storage factory for the context.
|
|
1476
1566
|
* @param standalone Generated standalone handler groups.
|
|
1477
1567
|
* @param ai Optional AI registry.
|
|
1568
|
+
* @param tenantIndex Captured marker or directory for admission.
|
|
1478
1569
|
* @returns The assembled context.
|
|
1479
1570
|
*/
|
|
1480
|
-
async #buildWithResources(repositories, storageFactory, standalone, ai) {
|
|
1571
|
+
async #buildWithResources(repositories, storageFactory, standalone, ai, tenantIndex) {
|
|
1481
1572
|
const resources = this.#subscriptionRegistry === undefined ? {} : { registry: this.#subscriptionRegistry };
|
|
1482
1573
|
this.#subscriptionRegistry = undefined;
|
|
1483
1574
|
const registeredRepositories = [...repositories];
|
|
1484
1575
|
try {
|
|
1485
|
-
return this.#assembleContext(registeredRepositories, storageFactory, standalone, resources, ai);
|
|
1576
|
+
return this.#assembleContext(registeredRepositories, storageFactory, standalone, resources, ai, tenantIndex);
|
|
1486
1577
|
}
|
|
1487
1578
|
catch (error) {
|
|
1488
1579
|
return await this.#failBuild(resources, error);
|
|
1489
1580
|
}
|
|
1490
1581
|
}
|
|
1491
|
-
|
|
1582
|
+
/**
|
|
1583
|
+
* Assembles dispatchers, buses, Stand, subscription runtime, and integration hooks, registering
|
|
1584
|
+
* the completed context with its environment.
|
|
1585
|
+
*
|
|
1586
|
+
* @param repositories Registered repository views.
|
|
1587
|
+
* @param storageFactory Provider for business records.
|
|
1588
|
+
* @param standalone Generated standalone handlers.
|
|
1589
|
+
* @param resources Partial resources to close if assembly fails.
|
|
1590
|
+
* @param ai Selected AI registry, when configured.
|
|
1591
|
+
* @param tenantIndex Captured marker or directory.
|
|
1592
|
+
* @returns Assembled Bounded Context.
|
|
1593
|
+
*/
|
|
1594
|
+
#assembleContext(repositories, storageFactory, standalone, resources, ai, tenantIndex) {
|
|
1492
1595
|
ContextParts.preflightRepositories(repositories);
|
|
1493
1596
|
this.#preflightAgents(repositories, storageFactory, ai);
|
|
1494
1597
|
if (ai !== undefined)
|
|
@@ -1502,17 +1605,28 @@ export class BoundedContextBuilder {
|
|
|
1502
1605
|
...ContextParts.repositoryProducedEventSchemas(repositories),
|
|
1503
1606
|
...ContextParts.standaloneProducedEventSchemas(standalone),
|
|
1504
1607
|
]);
|
|
1505
|
-
const running = this.#buildRuntime(storageFactory, buses, resources);
|
|
1506
|
-
const context = this.#createContext(repositories, storageFactory, buses, running, ai);
|
|
1608
|
+
const running = this.#buildRuntime(storageFactory, buses, resources, tenantIndex);
|
|
1609
|
+
const context = this.#createContext(repositories, storageFactory, buses, running, tenantIndex, ai);
|
|
1507
1610
|
resources.context = context;
|
|
1508
1611
|
if (registerBoundedContext === undefined)
|
|
1509
1612
|
throw new Error("Context registration is unavailable.");
|
|
1510
1613
|
registerBoundedContext(context, repositories);
|
|
1614
|
+
this.#attachIntegration(context, buses, dispatchers, standaloneEvent);
|
|
1615
|
+
return context;
|
|
1616
|
+
}
|
|
1617
|
+
/**
|
|
1618
|
+
* Registers external Event routes after Bounded Context construction.
|
|
1619
|
+
*
|
|
1620
|
+
* @param context Completed Bounded Context.
|
|
1621
|
+
* @param buses Signal buses and System metadata.
|
|
1622
|
+
* @param dispatchers Repository dispatchers.
|
|
1623
|
+
* @param standaloneEvent Generated standalone Event dispatcher, when present.
|
|
1624
|
+
*/
|
|
1625
|
+
#attachIntegration(context, buses, dispatchers, standaloneEvent) {
|
|
1511
1626
|
ContextParts.attachIntegration(context, buses.eventBus, buses.systemSpec, ContextParts.externalEventSchemas([
|
|
1512
1627
|
...dispatchers.domain,
|
|
1513
1628
|
...(standaloneEvent === undefined ? [] : [standaloneEvent]),
|
|
1514
1629
|
]));
|
|
1515
|
-
return context;
|
|
1516
1630
|
}
|
|
1517
1631
|
/**
|
|
1518
1632
|
* Rejects missing Agent prerequisites before repository intake is installed.
|
|
@@ -1540,6 +1654,9 @@ export class BoundedContextBuilder {
|
|
|
1540
1654
|
throw new TypeError("Agent repository capabilities must be factory-created models.");
|
|
1541
1655
|
}
|
|
1542
1656
|
}
|
|
1657
|
+
/**
|
|
1658
|
+
* Collects repository and configured event recipients into domain and System dispatcher groups.
|
|
1659
|
+
*/
|
|
1543
1660
|
#buildDispatchers(repositories) {
|
|
1544
1661
|
const event = [
|
|
1545
1662
|
...ContextParts.repositoryEventDispatchers(repositories),
|
|
@@ -1553,6 +1670,9 @@ export class BoundedContextBuilder {
|
|
|
1553
1670
|
],
|
|
1554
1671
|
};
|
|
1555
1672
|
}
|
|
1673
|
+
/**
|
|
1674
|
+
* Creates command, domain Event, and System Event buses plus their stores and signal publisher.
|
|
1675
|
+
*/
|
|
1556
1676
|
#buildBuses(repositories, storageFactory, dispatchers, resources) {
|
|
1557
1677
|
const commandBus = new CommandBus([
|
|
1558
1678
|
...this.#commandDispatchers,
|
|
@@ -1577,6 +1697,9 @@ export class BoundedContextBuilder {
|
|
|
1577
1697
|
systemSpec: system.spec,
|
|
1578
1698
|
};
|
|
1579
1699
|
}
|
|
1700
|
+
/**
|
|
1701
|
+
* Creates the System context store, EventBus, and Stand, then registers System event dispatchers.
|
|
1702
|
+
*/
|
|
1580
1703
|
#buildSystemBuses(storageFactory, dispatchers, resources) {
|
|
1581
1704
|
const systemSpec = ContextParts.createSystemSpec(this.#specSnapshot, this.#persistSystemEvents);
|
|
1582
1705
|
const systemEventStore = systemSpec.storesEvents
|
|
@@ -1600,6 +1723,10 @@ export class BoundedContextBuilder {
|
|
|
1600
1723
|
spec: systemSpec,
|
|
1601
1724
|
};
|
|
1602
1725
|
}
|
|
1726
|
+
/**
|
|
1727
|
+
* Matches generated standalone handlers to configured instances and registers their dispatchers
|
|
1728
|
+
* on the appropriate buses.
|
|
1729
|
+
*/
|
|
1603
1730
|
#installStandalone(standalone, buses) {
|
|
1604
1731
|
ContextParts.assertUniqueCommandReceptors(standalone);
|
|
1605
1732
|
const runtime = standalone.length === 0
|
|
@@ -1616,34 +1743,60 @@ export class BoundedContextBuilder {
|
|
|
1616
1743
|
buses.systemEventBus.register(state);
|
|
1617
1744
|
return event;
|
|
1618
1745
|
}
|
|
1619
|
-
|
|
1620
|
-
|
|
1746
|
+
/**
|
|
1747
|
+
* Creates the context Stand, subscription registry, and runtime that observes domain and System events.
|
|
1748
|
+
*
|
|
1749
|
+
* @param storageFactory Provider for business records.
|
|
1750
|
+
* @param buses Assembled signal buses.
|
|
1751
|
+
* @param resources Partial resources to close if assembly fails.
|
|
1752
|
+
* @param tenantIndex Captured marker or directory.
|
|
1753
|
+
* @returns Assembled Stand and subscription runtime.
|
|
1754
|
+
*/
|
|
1755
|
+
#buildRuntime(storageFactory, buses, resources, tenantIndex) {
|
|
1756
|
+
const stand = this.#buildStand(storageFactory, tenantIndex);
|
|
1621
1757
|
resources.stand = stand;
|
|
1622
1758
|
const registry = resources.registry ??
|
|
1623
|
-
new StorageSubscriptionRegistry(ContextParts.createSubscriptionStorageContext(this.#specSnapshot), storageFactory);
|
|
1759
|
+
new StorageSubscriptionRegistry(ContextParts.createSubscriptionStorageContext(this.#specSnapshot), storageFactory, subscriptionRecordGroup(this.#specSnapshot.name.value));
|
|
1624
1760
|
resources.registry = registry;
|
|
1625
1761
|
const runtime = new SubscriptionRuntime(stand, buses.systemStand, buses.eventBus, buses.systemEventBus, registry);
|
|
1626
1762
|
resources.runtime = runtime;
|
|
1627
1763
|
return { stand, registry, runtime };
|
|
1628
1764
|
}
|
|
1629
1765
|
/**
|
|
1630
|
-
* Builds the
|
|
1766
|
+
* Builds the Bounded Context Stand with captured tenant admission.
|
|
1631
1767
|
*
|
|
1632
1768
|
* @param storageFactory Provider used for Entity records.
|
|
1769
|
+
* @param tenantIndex Captured marker or directory.
|
|
1633
1770
|
* @returns Direct read-side Stand.
|
|
1634
1771
|
*/
|
|
1635
|
-
#buildStand(storageFactory) {
|
|
1636
|
-
const tenantIndex = this.#tenantIndex ?? ServerEnvironment.instance().tenantIndex;
|
|
1772
|
+
#buildStand(storageFactory, tenantIndex) {
|
|
1637
1773
|
const stand = new Stand({
|
|
1638
1774
|
context: ContextParts.createStorageMode(this.#specSnapshot),
|
|
1639
1775
|
storageFactory,
|
|
1640
1776
|
});
|
|
1777
|
+
if (this.#readFilters.size > 0)
|
|
1778
|
+
standAccess.installReadFilters(stand, [...this.#readFilters]);
|
|
1641
1779
|
standAccess.setAdmission(stand, (tenantId) => TenantAdmissions.require(tenantIndex, this.#specSnapshot.name.value, this.#specSnapshot.multitenant, tenantId));
|
|
1642
1780
|
return stand;
|
|
1643
1781
|
}
|
|
1644
|
-
|
|
1645
|
-
|
|
1782
|
+
/**
|
|
1783
|
+
* Constructs the immutable BoundedContext using assembled buses, runtime parts, environment
|
|
1784
|
+
* delivery strategy, and tenant setting.
|
|
1785
|
+
*
|
|
1786
|
+
* @param repositories Registered repository views.
|
|
1787
|
+
* @param storageFactory Provider for business records.
|
|
1788
|
+
* @param buses Assembled signal buses.
|
|
1789
|
+
* @param running Prepared Stand and subscription runtime.
|
|
1790
|
+
* @param tenantIndex Captured marker or directory.
|
|
1791
|
+
* @param ai Selected AI registry, when configured.
|
|
1792
|
+
* @returns Immutable Bounded Context.
|
|
1793
|
+
*/
|
|
1794
|
+
#createContext(repositories, storageFactory, buses, running, tenantIndex, ai) {
|
|
1795
|
+
return ContextParts.createBoundedContext(this.#specSnapshot, storageFactory, repositories, ServerEnvironment.instance().delivery.strategy, buses, running, ai, tenantIndex);
|
|
1646
1796
|
}
|
|
1797
|
+
/**
|
|
1798
|
+
* Closes partial context resources after assembly fails and aggregates cleanup errors with the original failure.
|
|
1799
|
+
*/
|
|
1647
1800
|
async #failBuild(resources, error) {
|
|
1648
1801
|
const cleanupErrors = [];
|
|
1649
1802
|
if (resources.context !== undefined) {
|
|
@@ -1675,28 +1828,27 @@ export class BoundedContextBuilder {
|
|
|
1675
1828
|
}
|
|
1676
1829
|
throw error;
|
|
1677
1830
|
}
|
|
1678
|
-
|
|
1831
|
+
/**
|
|
1832
|
+
* Selects generated metadata for configured Entity and standalone handler types and creates
|
|
1833
|
+
* repository views and handler groups.
|
|
1834
|
+
*/
|
|
1835
|
+
#loadGeneratedArtifacts(entityTypes) {
|
|
1679
1836
|
if (entityTypes.length === 0 && this.#standaloneInstances().length === 0) {
|
|
1680
1837
|
return Object.freeze({ repositories: Object.freeze([]), standalone: Object.freeze([]) });
|
|
1681
1838
|
}
|
|
1682
|
-
const
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
const registries = (
|
|
1687
|
-
.load({
|
|
1688
|
-
modules: [registryModule],
|
|
1689
|
-
...ContextParts.generatedRegistryCacheBust(registryKey),
|
|
1690
|
-
})
|
|
1691
|
-
.catch((error) => {
|
|
1692
|
-
ContextParts.recordGeneratedRegistryFailure(registryKey);
|
|
1693
|
-
throw error;
|
|
1694
|
-
}));
|
|
1839
|
+
const selected = [
|
|
1840
|
+
...entityTypes,
|
|
1841
|
+
...this.#standaloneInstances().map((instance) => instance.constructor),
|
|
1842
|
+
];
|
|
1843
|
+
const registries = [SpineMetadataInventory.select(selected)];
|
|
1695
1844
|
const metadata = ContextParts.ingestGeneratedRegistries(registries);
|
|
1696
1845
|
const repositories = Object.freeze(entityTypes.map((entityType) => ContextParts.createGeneratedRepository(entityType, registries, metadata, this.#generatedRepositoryOptions.get(entityType))));
|
|
1697
1846
|
const standalone = Object.freeze(registries.flatMap((registry) => registry.receivers.filter((receiver) => receiver.receiverKind === "standalone")));
|
|
1698
1847
|
return Object.freeze({ repositories, standalone });
|
|
1699
1848
|
}
|
|
1849
|
+
/**
|
|
1850
|
+
* Returns a frozen list of configured standalone assignee, commander, and event-receiver instances.
|
|
1851
|
+
*/
|
|
1700
1852
|
#standaloneInstances() {
|
|
1701
1853
|
return Object.freeze([...this.#assignees, ...this.#commanders, ...this.#eventReceivers]);
|
|
1702
1854
|
}
|
|
@@ -1775,28 +1927,28 @@ export class ContextSpec {
|
|
|
1775
1927
|
}
|
|
1776
1928
|
const registeredRepositories = new WeakMap();
|
|
1777
1929
|
/**
|
|
1778
|
-
* Reports failure while
|
|
1930
|
+
* Reports failure while dispatching a stored Event during read-side catch-up.
|
|
1779
1931
|
*/
|
|
1780
|
-
class
|
|
1781
|
-
code = "
|
|
1932
|
+
class CatchUpDispatchError extends Error {
|
|
1933
|
+
code = "READ_SIDE_CATCH_UP_DISPATCH_FAILED";
|
|
1782
1934
|
eventId;
|
|
1783
1935
|
detail;
|
|
1784
1936
|
/**
|
|
1785
|
-
* Creates a
|
|
1937
|
+
* Creates a catch-up error tied to the stored Event that failed.
|
|
1786
1938
|
*
|
|
1787
1939
|
* @param eventId Identifies the stored event.
|
|
1788
1940
|
* @param detail Contains the bounded cause name and message.
|
|
1789
1941
|
*/
|
|
1790
1942
|
constructor(eventId, detail) {
|
|
1791
1943
|
super(`Read-side catch-up failed for stored event "${eventId}".`);
|
|
1792
|
-
this.name = "
|
|
1944
|
+
this.name = "ReadSideCatchUpDispatchError";
|
|
1793
1945
|
this.eventId = eventId;
|
|
1794
1946
|
this.detail = detail;
|
|
1795
1947
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
1796
1948
|
}
|
|
1797
1949
|
}
|
|
1798
1950
|
/**
|
|
1799
|
-
* Assembles private bounded-context lifecycle and
|
|
1951
|
+
* Assembles private bounded-context lifecycle and catch-up details.
|
|
1800
1952
|
*/
|
|
1801
1953
|
const ContextParts = Object.freeze({
|
|
1802
1954
|
/**
|
|
@@ -1941,11 +2093,11 @@ const ContextParts = Object.freeze({
|
|
|
1941
2093
|
* @param buses Prepared domain and System buses.
|
|
1942
2094
|
* @param running Prepared Stand and subscription runtime.
|
|
1943
2095
|
* @param ai Effective AI registry when Agent repositories are present.
|
|
1944
|
-
* @param tenantIndex
|
|
2096
|
+
* @param tenantIndex Captured marker or directory.
|
|
1945
2097
|
* @returns Constructed bounded context.
|
|
1946
2098
|
*/
|
|
1947
2099
|
createBoundedContext(specSnapshot, storageFactory, repositories, deliveryStrategy, buses, running, ai, tenantIndex) {
|
|
1948
|
-
|
|
2100
|
+
const context = constructBoundedContext({
|
|
1949
2101
|
snapshot: ContextParts.snapshotFor(specSnapshot),
|
|
1950
2102
|
commandBus: buses.commandBus,
|
|
1951
2103
|
eventBus: buses.eventBus,
|
|
@@ -1956,12 +2108,14 @@ const ContextParts = Object.freeze({
|
|
|
1956
2108
|
subscriptionRuntime: running.runtime,
|
|
1957
2109
|
systemSpec: buses.systemSpec,
|
|
1958
2110
|
storageFactory,
|
|
1959
|
-
tenantIndex
|
|
2111
|
+
tenantIndex,
|
|
1960
2112
|
repositories,
|
|
1961
2113
|
deliveryStrategy,
|
|
1962
2114
|
...(ai === undefined ? {} : { ai }),
|
|
1963
2115
|
token: frameworkConstructionToken,
|
|
1964
2116
|
});
|
|
2117
|
+
standAccess.bindProducingContext(running.stand, context);
|
|
2118
|
+
return context;
|
|
1965
2119
|
},
|
|
1966
2120
|
/**
|
|
1967
2121
|
* Captures public context identity from the build specification.
|
|
@@ -2229,7 +2383,7 @@ const ContextParts = Object.freeze({
|
|
|
2229
2383
|
* @param event Imported event to validate.
|
|
2230
2384
|
*/
|
|
2231
2385
|
validateImportedTenant(context, event) {
|
|
2232
|
-
const tenantId = ContextParts.
|
|
2386
|
+
const tenantId = ContextParts.readCatchUpTenant(event);
|
|
2233
2387
|
if (!context.isMultitenant) {
|
|
2234
2388
|
if (tenantId !== undefined) {
|
|
2235
2389
|
throw new Error(`Single-tenant context "${context.name.value}" does not accept tenantId.`);
|
|
@@ -2284,10 +2438,10 @@ const ContextParts = Object.freeze({
|
|
|
2284
2438
|
return pairing;
|
|
2285
2439
|
},
|
|
2286
2440
|
/**
|
|
2287
|
-
* Reads the
|
|
2441
|
+
* Reads the marker or directory recorded for a built Bounded Context.
|
|
2288
2442
|
*
|
|
2289
|
-
* @param context Context to inspect.
|
|
2290
|
-
* @returns
|
|
2443
|
+
* @param context Bounded Context to inspect.
|
|
2444
|
+
* @returns Captured marker or directory; throws if unavailable.
|
|
2291
2445
|
*/
|
|
2292
2446
|
requireTenantIndex(context) {
|
|
2293
2447
|
const tenantIndex = contextTenantIndexes.get(context);
|
|
@@ -2318,151 +2472,6 @@ const ContextParts = Object.freeze({
|
|
|
2318
2472
|
throw new TypeError(`${operation} requires a Repository instance. Use an entity class only with build().`);
|
|
2319
2473
|
}
|
|
2320
2474
|
},
|
|
2321
|
-
/**
|
|
2322
|
-
* Validates that a root exists when generated metadata must be loaded.
|
|
2323
|
-
*
|
|
2324
|
-
* @param root Configured registry location.
|
|
2325
|
-
* @returns Configured root; throws when absent.
|
|
2326
|
-
*/
|
|
2327
|
-
requireGeneratedRegistryRoot(root) {
|
|
2328
|
-
if (root !== undefined) {
|
|
2329
|
-
return root;
|
|
2330
|
-
}
|
|
2331
|
-
throw new Error("BoundedContextBuilder.build() requires withGeneratedRegistryRoot(root) " +
|
|
2332
|
-
"when assembling entity classes from generated metadata.");
|
|
2333
|
-
},
|
|
2334
|
-
/**
|
|
2335
|
-
* Resolves the generated registry module and verifies it stays beneath the configured root.
|
|
2336
|
-
*
|
|
2337
|
-
* @param root Configured filesystem path or file URL.
|
|
2338
|
-
* @returns Registry module file URL.
|
|
2339
|
-
*/
|
|
2340
|
-
async trustedGeneratedRegistryModule(root) {
|
|
2341
|
-
const trustedRoot = await ContextParts.canonicalGeneratedRegistryRoot(root);
|
|
2342
|
-
const registryPath = resolve(trustedRoot, generatedRegistryFile);
|
|
2343
|
-
const canonicalRegistryPath = await ContextParts.canonicalReadableRegistryPath(registryPath);
|
|
2344
|
-
if (ContextParts.resolvesOutsideRoot(trustedRoot, canonicalRegistryPath)) {
|
|
2345
|
-
throw new Error(`Generated handler registry module "${canonicalRegistryPath}" must resolve within ` +
|
|
2346
|
-
`the configured generated registry root "${trustedRoot}".`);
|
|
2347
|
-
}
|
|
2348
|
-
return pathToFileURL(canonicalRegistryPath);
|
|
2349
|
-
},
|
|
2350
|
-
/**
|
|
2351
|
-
* Resolves a generated registry root to its canonical filesystem path.
|
|
2352
|
-
*
|
|
2353
|
-
* @param root Configured path or file URL.
|
|
2354
|
-
* @returns Canonical directory path.
|
|
2355
|
-
*/
|
|
2356
|
-
async canonicalGeneratedRegistryRoot(root) {
|
|
2357
|
-
const rootPath = ContextParts.generatedRegistryRootPath(root);
|
|
2358
|
-
try {
|
|
2359
|
-
return await realpath(rootPath);
|
|
2360
|
-
}
|
|
2361
|
-
catch (error) {
|
|
2362
|
-
throw new Error(`Generated registry root "${rootPath}" must be an existing readable directory.`, { cause: error });
|
|
2363
|
-
}
|
|
2364
|
-
},
|
|
2365
|
-
/**
|
|
2366
|
-
* Converts a registry root path or file URL to an absolute filesystem path.
|
|
2367
|
-
*
|
|
2368
|
-
* @param root Configured path or file URL.
|
|
2369
|
-
* @returns Absolute filesystem path.
|
|
2370
|
-
*/
|
|
2371
|
-
generatedRegistryRootPath(root) {
|
|
2372
|
-
if (root instanceof URL) {
|
|
2373
|
-
return ContextParts.fileUrlPath(root, "Generated registry root");
|
|
2374
|
-
}
|
|
2375
|
-
if (ContextParts.isUrlLike(root)) {
|
|
2376
|
-
return ContextParts.fileUrlPath(ContextParts.parseRootUrl(root), "Generated registry root");
|
|
2377
|
-
}
|
|
2378
|
-
return resolve(root);
|
|
2379
|
-
},
|
|
2380
|
-
/**
|
|
2381
|
-
* Validates a file URL and converts it to an absolute filesystem path.
|
|
2382
|
-
*
|
|
2383
|
-
* @param url File URL to convert.
|
|
2384
|
-
* @param label Label used in validation errors.
|
|
2385
|
-
* @returns Filesystem path.
|
|
2386
|
-
*/
|
|
2387
|
-
fileUrlPath(url, label) {
|
|
2388
|
-
if (url.protocol !== "file:") {
|
|
2389
|
-
throw new Error(`${label} "${url.href}" must use the file: URL scheme.`);
|
|
2390
|
-
}
|
|
2391
|
-
if (url.search.length > 0 || url.hash.length > 0) {
|
|
2392
|
-
throw new Error(`${label} "${url.href}" must not include a query or hash.`);
|
|
2393
|
-
}
|
|
2394
|
-
return resolve(fileURLToPath(url));
|
|
2395
|
-
},
|
|
2396
|
-
/**
|
|
2397
|
-
* Parses a generated registry root string as a URL.
|
|
2398
|
-
*
|
|
2399
|
-
* @param root Registry root string.
|
|
2400
|
-
* @returns Parsed URL.
|
|
2401
|
-
*/
|
|
2402
|
-
parseRootUrl(root) {
|
|
2403
|
-
try {
|
|
2404
|
-
return new URL(root);
|
|
2405
|
-
}
|
|
2406
|
-
catch (error) {
|
|
2407
|
-
throw new Error(`Generated registry root "${root}" is not a valid URL.`, { cause: error });
|
|
2408
|
-
}
|
|
2409
|
-
},
|
|
2410
|
-
/**
|
|
2411
|
-
* Checks strings for a URL scheme, excluding Windows drive paths.
|
|
2412
|
-
*
|
|
2413
|
-
* @param value String to inspect.
|
|
2414
|
-
* @returns True when the string has a URL scheme.
|
|
2415
|
-
*/
|
|
2416
|
-
isUrlLike(value) {
|
|
2417
|
-
return moduleSchemeRe.test(value) && !/^[A-Za-z]:[\\/]/.test(value);
|
|
2418
|
-
},
|
|
2419
|
-
/**
|
|
2420
|
-
* Verifies registry-module readability and resolves its canonical path.
|
|
2421
|
-
*
|
|
2422
|
-
* @param registryPath Registry module filesystem path.
|
|
2423
|
-
* @returns Canonical readable path.
|
|
2424
|
-
*/
|
|
2425
|
-
async canonicalReadableRegistryPath(registryPath) {
|
|
2426
|
-
try {
|
|
2427
|
-
await access(registryPath, fsConstants.R_OK);
|
|
2428
|
-
return await realpath(registryPath);
|
|
2429
|
-
}
|
|
2430
|
-
catch (error) {
|
|
2431
|
-
throw new Error(`Generated handler registry module "${registryPath}" must exist and be readable.`, { cause: error });
|
|
2432
|
-
}
|
|
2433
|
-
},
|
|
2434
|
-
/**
|
|
2435
|
-
* Checks whether a canonical path escapes a canonical root.
|
|
2436
|
-
*
|
|
2437
|
-
* @param canonicalRoot Root directory.
|
|
2438
|
-
* @param canonicalPath Candidate path.
|
|
2439
|
-
* @returns True when the candidate escapes the root.
|
|
2440
|
-
*/
|
|
2441
|
-
resolvesOutsideRoot(canonicalRoot, canonicalPath) {
|
|
2442
|
-
const relativePath = relative(canonicalRoot, canonicalPath);
|
|
2443
|
-
return (relativePath.startsWith("..") ||
|
|
2444
|
-
relativePath === ".." ||
|
|
2445
|
-
relativePath.split(sep).includes("..") ||
|
|
2446
|
-
isAbsolute(relativePath));
|
|
2447
|
-
},
|
|
2448
|
-
/**
|
|
2449
|
-
* Creates a cache-bust query after a prior registry load failure.
|
|
2450
|
-
*
|
|
2451
|
-
* @param registryKey Canonical registry identifier.
|
|
2452
|
-
* @returns Retry query, or an empty object on the first attempt.
|
|
2453
|
-
*/
|
|
2454
|
-
generatedRegistryCacheBust(registryKey) {
|
|
2455
|
-
const attempt = generatedRegistryLoadAttempts.get(registryKey) ?? 0;
|
|
2456
|
-
return attempt === 0 ? {} : { cacheBust: `retry-${attempt.toString()}` };
|
|
2457
|
-
},
|
|
2458
|
-
/**
|
|
2459
|
-
* Records another failed load for a registry.
|
|
2460
|
-
*
|
|
2461
|
-
* @param registryKey Canonical registry identifier.
|
|
2462
|
-
*/
|
|
2463
|
-
recordGeneratedRegistryFailure(registryKey) {
|
|
2464
|
-
generatedRegistryLoadAttempts.set(registryKey, (generatedRegistryLoadAttempts.get(registryKey) ?? 0) + 1);
|
|
2465
|
-
},
|
|
2466
2475
|
/**
|
|
2467
2476
|
* Loads generated handler registries into a metadata registry.
|
|
2468
2477
|
*
|
|
@@ -2758,9 +2767,9 @@ const ContextParts = Object.freeze({
|
|
|
2758
2767
|
createDeliveryDescriptor(entityInbox, projections) {
|
|
2759
2768
|
return Object.freeze({
|
|
2760
2769
|
endpoints: () => Object.freeze([...entityInbox.endpoints(), ...projections.endpoints()]),
|
|
2761
|
-
|
|
2762
|
-
? projections.
|
|
2763
|
-
: entityInbox.
|
|
2770
|
+
deliver: (message, tenantId) => message.label === "UPDATE_SUBSCRIBER"
|
|
2771
|
+
? projections.deliver(message, tenantId)
|
|
2772
|
+
: entityInbox.deliver(message, tenantId),
|
|
2764
2773
|
});
|
|
2765
2774
|
},
|
|
2766
2775
|
/**
|
|
@@ -3031,12 +3040,12 @@ const ContextParts = Object.freeze({
|
|
|
3031
3040
|
* @param context Catch-up storage context.
|
|
3032
3041
|
* @param event Stored event to validate.
|
|
3033
3042
|
*/
|
|
3034
|
-
|
|
3043
|
+
validateCatchUpTenant(context, event) {
|
|
3035
3044
|
if (!context.multitenant) {
|
|
3036
3045
|
return;
|
|
3037
3046
|
}
|
|
3038
3047
|
const expectedTenantId = context.tenantId;
|
|
3039
|
-
const envelopeTenantId = ContextParts.
|
|
3048
|
+
const envelopeTenantId = ContextParts.readCatchUpTenant(event);
|
|
3040
3049
|
if (envelopeTenantId === undefined) {
|
|
3041
3050
|
throw new Error("Read-side catch-up requires stored event envelope tenant.");
|
|
3042
3051
|
}
|
|
@@ -3050,7 +3059,7 @@ const ContextParts = Object.freeze({
|
|
|
3050
3059
|
* @param event Event whose origin is inspected.
|
|
3051
3060
|
* @returns Cloned tenant ID when present.
|
|
3052
3061
|
*/
|
|
3053
|
-
|
|
3062
|
+
readCatchUpTenant(event) {
|
|
3054
3063
|
switch (event.context?.origin.case) {
|
|
3055
3064
|
case "importContext":
|
|
3056
3065
|
return ContextParts.tenantIdValue(event.context.origin.value.tenantId);
|
|
@@ -3092,14 +3101,14 @@ const ContextParts = Object.freeze({
|
|
|
3092
3101
|
return matching.length > 0 ? 1 : 0;
|
|
3093
3102
|
},
|
|
3094
3103
|
/**
|
|
3095
|
-
* Creates a
|
|
3104
|
+
* Creates a catch-up error with the Event ID and bounded cause details.
|
|
3096
3105
|
*
|
|
3097
3106
|
* @param event Event that failed.
|
|
3098
|
-
* @param cause Thrown
|
|
3099
|
-
* @returns
|
|
3107
|
+
* @param cause Thrown catch-up cause.
|
|
3108
|
+
* @returns Catch-up error.
|
|
3100
3109
|
*/
|
|
3101
|
-
|
|
3102
|
-
return new
|
|
3110
|
+
catchUpDispatchError(event, cause) {
|
|
3111
|
+
return new CatchUpDispatchError(event.id?.value ?? "(missing)", ContextParts.catchUpDispatchDetail(cause));
|
|
3103
3112
|
},
|
|
3104
3113
|
/**
|
|
3105
3114
|
* Returns an error string truncated to a maximum length.
|
|
@@ -3117,7 +3126,7 @@ const ContextParts = Object.freeze({
|
|
|
3117
3126
|
* @param error Thrown value.
|
|
3118
3127
|
* @returns Immutable bounded details.
|
|
3119
3128
|
*/
|
|
3120
|
-
|
|
3129
|
+
catchUpDispatchDetail(error) {
|
|
3121
3130
|
if (error instanceof Error) {
|
|
3122
3131
|
return Object.freeze({
|
|
3123
3132
|
name: ContextParts.boundedErrorString(error.name, errorDetailLimit) || "Error",
|
|
@@ -3160,9 +3169,15 @@ const ContextParts = Object.freeze({
|
|
|
3160
3169
|
* @internal
|
|
3161
3170
|
*/
|
|
3162
3171
|
export const boundedContextIntegrationAccess = Object.freeze({
|
|
3172
|
+
/**
|
|
3173
|
+
* Publishes an imported Event through the context’s import path.
|
|
3174
|
+
*/
|
|
3163
3175
|
publishImported(context, event) {
|
|
3164
3176
|
return ContextParts.publishImported(context, event);
|
|
3165
3177
|
},
|
|
3178
|
+
/**
|
|
3179
|
+
* Signals that standalone context integration is ready.
|
|
3180
|
+
*/
|
|
3166
3181
|
ready(context) {
|
|
3167
3182
|
return ContextParts.integrationReady(context);
|
|
3168
3183
|
},
|