@spine-event-engine/server 2.0.0-snapshot.24 → 2.0.0-snapshot.25
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 +38 -11
- package/REFERENCE.md +201 -130
- package/dist/agent/agent-acceptance-receipts.d.ts +69 -0
- package/dist/agent/agent-acceptance-receipts.d.ts.map +1 -0
- package/dist/agent/agent-acceptance-receipts.js +198 -0
- package/dist/agent/agent-acceptance-receipts.js.map +1 -0
- package/dist/agent/agent-execution-capacity.d.ts +37 -9
- package/dist/agent/agent-execution-capacity.d.ts.map +1 -1
- package/dist/agent/agent-execution-capacity.js +73 -15
- package/dist/agent/agent-execution-capacity.js.map +1 -1
- package/dist/agent/agent-history.d.ts +15 -10
- package/dist/agent/agent-history.d.ts.map +1 -1
- package/dist/agent/agent-history.js +26 -14
- package/dist/agent/agent-history.js.map +1 -1
- package/dist/agent/agent-live-ai-audit.d.ts +95 -0
- package/dist/agent/agent-live-ai-audit.d.ts.map +1 -0
- package/dist/agent/agent-live-ai-audit.js +290 -0
- package/dist/agent/agent-live-ai-audit.js.map +1 -0
- package/dist/agent/agent-live-ai-runtime.d.ts +300 -0
- package/dist/agent/agent-live-ai-runtime.d.ts.map +1 -0
- package/dist/agent/agent-live-ai-runtime.js +611 -0
- package/dist/agent/agent-live-ai-runtime.js.map +1 -0
- package/dist/agent/agent-live-audit.d.ts +67 -0
- package/dist/agent/agent-live-audit.d.ts.map +1 -0
- package/dist/agent/agent-live-audit.js +121 -0
- package/dist/agent/agent-live-audit.js.map +1 -0
- package/dist/agent/agent-live-budget.d.ts +84 -0
- package/dist/agent/agent-live-budget.d.ts.map +1 -0
- package/dist/agent/agent-live-budget.js +161 -0
- package/dist/agent/agent-live-budget.js.map +1 -0
- package/dist/agent/agent-live-fault.d.ts +18 -0
- package/dist/agent/agent-live-fault.d.ts.map +1 -0
- package/dist/agent/{agent-execution-fault.js → agent-live-fault.js} +7 -7
- package/dist/agent/agent-live-fault.js.map +1 -0
- package/dist/agent/agent-live-inputs.d.ts +74 -0
- package/dist/agent/agent-live-inputs.d.ts.map +1 -0
- package/dist/{bus/saved-dispatcher-binding.js → agent/agent-live-inputs.js} +2 -14
- package/dist/agent/agent-live-inputs.js.map +1 -0
- package/dist/agent/agent-live-interaction-events.d.ts +46 -0
- package/dist/agent/agent-live-interaction-events.d.ts.map +1 -0
- package/dist/agent/agent-live-interaction-events.js +104 -0
- package/dist/agent/agent-live-interaction-events.js.map +1 -0
- package/dist/agent/agent-live-mcp-controls.d.ts +129 -0
- package/dist/agent/agent-live-mcp-controls.d.ts.map +1 -0
- package/dist/agent/agent-live-mcp-controls.js +228 -0
- package/dist/agent/agent-live-mcp-controls.js.map +1 -0
- package/dist/agent/agent-live-mcp-host.d.ts +88 -0
- package/dist/agent/agent-live-mcp-host.d.ts.map +1 -0
- package/dist/agent/{agent-mcp-host.js → agent-live-mcp-host.js} +1 -1
- package/dist/agent/agent-live-mcp-host.js.map +1 -0
- package/dist/agent/{agent-mcp-runtime.d.ts → agent-live-mcp-runtime.d.ts} +6 -6
- package/dist/agent/agent-live-mcp-runtime.d.ts.map +1 -0
- package/dist/agent/{agent-mcp-runtime.js → agent-live-mcp-runtime.js} +13 -42
- package/dist/agent/agent-live-mcp-runtime.js.map +1 -0
- package/dist/agent/agent-live-read-runtime.d.ts +61 -0
- package/dist/agent/agent-live-read-runtime.d.ts.map +1 -0
- package/dist/agent/agent-live-read-runtime.js +121 -0
- package/dist/agent/agent-live-read-runtime.js.map +1 -0
- package/dist/agent/agent-model-selection.d.ts +21 -34
- package/dist/agent/agent-model-selection.d.ts.map +1 -1
- package/dist/agent/agent-model-selection.js +61 -78
- package/dist/agent/agent-model-selection.js.map +1 -1
- package/dist/bus/command-bus.d.ts +14 -18
- package/dist/bus/command-bus.d.ts.map +1 -1
- package/dist/bus/command-bus.js +31 -48
- package/dist/bus/command-bus.js.map +1 -1
- package/dist/bus/event-bus.d.ts +17 -4
- package/dist/bus/event-bus.d.ts.map +1 -1
- package/dist/bus/event-bus.js +42 -12
- package/dist/bus/event-bus.js.map +1 -1
- package/dist/context/bounded-context.d.ts +140 -87
- package/dist/context/bounded-context.d.ts.map +1 -1
- package/dist/context/bounded-context.js +702 -329
- package/dist/context/bounded-context.js.map +1 -1
- package/dist/context/entity-inbox.d.ts +7 -2
- package/dist/context/entity-inbox.d.ts.map +1 -1
- package/dist/context/entity-inbox.js +22 -13
- package/dist/context/entity-inbox.js.map +1 -1
- package/dist/context/local-inbox-handoff.d.ts +34 -34
- package/dist/context/local-inbox-handoff.d.ts.map +1 -1
- package/dist/context/local-inbox-handoff.js +108 -150
- package/dist/context/local-inbox-handoff.js.map +1 -1
- package/dist/context/projection-handoff.d.ts +2 -2
- package/dist/context/projection-handoff.d.ts.map +1 -1
- package/dist/context/projection-handoff.js +10 -8
- package/dist/context/projection-handoff.js.map +1 -1
- package/dist/context/tenant-index.d.ts +36 -31
- package/dist/context/tenant-index.d.ts.map +1 -1
- package/dist/context/tenant-index.js +123 -125
- package/dist/context/tenant-index.js.map +1 -1
- package/dist/context/tenant-unknown-error.d.ts +33 -0
- package/dist/context/tenant-unknown-error.d.ts.map +1 -0
- package/dist/context/tenant-unknown-error.js +75 -0
- package/dist/context/tenant-unknown-error.js.map +1 -0
- package/dist/delivery/agent-delivery-scope.d.ts +66 -0
- package/dist/delivery/agent-delivery-scope.d.ts.map +1 -0
- package/dist/delivery/agent-delivery-scope.js +44 -0
- package/dist/delivery/agent-delivery-scope.js.map +1 -0
- package/dist/delivery/delivery-builder.d.ts +25 -1
- package/dist/delivery/delivery-builder.d.ts.map +1 -1
- package/dist/delivery/delivery-builder.js +89 -0
- package/dist/delivery/delivery-builder.js.map +1 -1
- package/dist/delivery/delivery-monitor.d.ts +1 -1
- package/dist/delivery/delivery-monitor.js +1 -1
- package/dist/delivery/delivery-run-control.d.ts +6 -0
- package/dist/delivery/delivery-run-control.d.ts.map +1 -1
- package/dist/delivery/delivery-run-control.js +10 -0
- package/dist/delivery/delivery-run-control.js.map +1 -1
- package/dist/delivery/delivery-supervisor.d.ts.map +1 -1
- package/dist/delivery/delivery-supervisor.js +28 -4
- package/dist/delivery/delivery-supervisor.js.map +1 -1
- package/dist/delivery/delivery-tenant.d.ts +17 -0
- package/dist/delivery/delivery-tenant.d.ts.map +1 -0
- package/dist/delivery/delivery-tenant.js +50 -0
- package/dist/delivery/delivery-tenant.js.map +1 -0
- package/dist/delivery/delivery.d.ts +96 -2
- package/dist/delivery/delivery.d.ts.map +1 -1
- package/dist/delivery/delivery.js +693 -53
- package/dist/delivery/delivery.js.map +1 -1
- package/dist/delivery/inbox-records.d.ts.map +1 -1
- package/dist/delivery/inbox-records.js +9 -16
- package/dist/delivery/inbox-records.js.map +1 -1
- package/dist/delivery/inbox-storage.d.ts +6 -0
- package/dist/delivery/inbox-storage.d.ts.map +1 -1
- package/dist/delivery/inbox-storage.js +43 -6
- package/dist/delivery/inbox-storage.js.map +1 -1
- package/dist/delivery/inbox.d.ts.map +1 -1
- package/dist/delivery/inbox.js +3 -2
- package/dist/delivery/inbox.js.map +1 -1
- package/dist/delivery/reception-observation.d.ts +12 -4
- package/dist/delivery/reception-observation.d.ts.map +1 -1
- package/dist/delivery/reception-observation.js +14 -3
- package/dist/delivery/reception-observation.js.map +1 -1
- package/dist/delivery/sharded-work-registry.d.ts +6 -0
- package/dist/delivery/sharded-work-registry.d.ts.map +1 -1
- package/dist/delivery/sharded-work-registry.js +41 -4
- package/dist/delivery/sharded-work-registry.js.map +1 -1
- package/dist/entity/entity-storage-descriptor.d.ts.map +1 -1
- package/dist/entity/entity-storage-descriptor.js +7 -4
- package/dist/entity/entity-storage-descriptor.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/integration/external-messages.d.ts.map +1 -1
- package/dist/integration/external-messages.js +9 -15
- package/dist/integration/external-messages.js.map +1 -1
- package/dist/integration/integration-broker.d.ts.map +1 -1
- package/dist/integration/integration-broker.js +112 -6
- package/dist/integration/integration-broker.js.map +1 -1
- package/dist/integration/third-party-context.d.ts.map +1 -1
- package/dist/integration/third-party-context.js +7 -10
- package/dist/integration/third-party-context.js.map +1 -1
- package/dist/repository/primitive-id.js +26 -11
- package/dist/repository/primitive-id.js.map +1 -1
- package/dist/repository/repository.d.ts +32 -60
- package/dist/repository/repository.d.ts.map +1 -1
- package/dist/repository/repository.js +1446 -1787
- package/dist/repository/repository.js.map +1 -1
- package/dist/runtime/runtime.d.ts +13 -0
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +18 -0
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/signal-metadata.d.ts.map +1 -1
- package/dist/runtime/signal-metadata.js +6 -6
- package/dist/runtime/signal-metadata.js.map +1 -1
- package/dist/runtime/signal-publisher.d.ts +0 -6
- package/dist/runtime/signal-publisher.d.ts.map +1 -1
- package/dist/runtime/signal-publisher.js +1 -9
- package/dist/runtime/signal-publisher.js.map +1 -1
- package/dist/runtime/standalone-handler-runtime.d.ts +0 -7
- package/dist/runtime/standalone-handler-runtime.d.ts.map +1 -1
- package/dist/runtime/standalone-handler-runtime.js +13 -99
- package/dist/runtime/standalone-handler-runtime.js.map +1 -1
- package/dist/server/managed-delivery-gate.d.ts +34 -0
- package/dist/server/managed-delivery-gate.d.ts.map +1 -0
- package/dist/server/managed-delivery-gate.js +70 -0
- package/dist/server/managed-delivery-gate.js.map +1 -0
- package/dist/server/managed-server-application.d.ts.map +1 -1
- package/dist/server/managed-server-application.js +7 -4
- package/dist/server/managed-server-application.js.map +1 -1
- package/dist/server/server-environment.d.ts +203 -25
- package/dist/server/server-environment.d.ts.map +1 -1
- package/dist/server/server-environment.js +661 -133
- package/dist/server/server-environment.js.map +1 -1
- package/dist/server/server-log.d.ts +7 -0
- package/dist/server/server-log.d.ts.map +1 -1
- package/dist/server/server-log.js +14 -1
- package/dist/server/server-log.js.map +1 -1
- package/dist/server/server.d.ts +18 -32
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +50 -204
- package/dist/server/server.js.map +1 -1
- package/dist/services/spine-services.d.ts +1 -1
- package/dist/services/spine-services.d.ts.map +1 -1
- package/dist/services/spine-services.js +236 -335
- package/dist/services/spine-services.js.map +1 -1
- package/dist/stand/event-origin-filter.js +3 -3
- package/dist/stand/event-origin-filter.js.map +1 -1
- package/dist/stand/stand.d.ts +14 -0
- package/dist/stand/stand.d.ts.map +1 -1
- package/dist/stand/stand.js +53 -9
- package/dist/stand/stand.js.map +1 -1
- package/dist/stand/subscription-observer.d.ts.map +1 -1
- package/dist/stand/subscription-observer.js +54 -40
- package/dist/stand/subscription-observer.js.map +1 -1
- package/dist/stand/subscription-registry.d.ts +6 -2
- package/dist/stand/subscription-registry.d.ts.map +1 -1
- package/dist/stand/subscription-registry.js +15 -11
- package/dist/stand/subscription-registry.js.map +1 -1
- package/dist/stand/subscription-runtime.d.ts +5 -3
- package/dist/stand/subscription-runtime.d.ts.map +1 -1
- package/dist/stand/subscription-runtime.js +52 -9
- package/dist/stand/subscription-runtime.js.map +1 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +4 -3
- package/dist/testing/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +14 -14
- package/dist/agent/agent-admission.d.ts +0 -80
- package/dist/agent/agent-admission.d.ts.map +0 -1
- package/dist/agent/agent-admission.js +0 -127
- package/dist/agent/agent-admission.js.map +0 -1
- package/dist/agent/agent-ai-runtime.d.ts +0 -76
- package/dist/agent/agent-ai-runtime.d.ts.map +0 -1
- package/dist/agent/agent-ai-runtime.js +0 -1269
- package/dist/agent/agent-ai-runtime.js.map +0 -1
- package/dist/agent/agent-execution-fault.d.ts +0 -18
- package/dist/agent/agent-execution-fault.d.ts.map +0 -1
- package/dist/agent/agent-execution-fault.js.map +0 -1
- package/dist/agent/agent-execution-session.d.ts +0 -103
- package/dist/agent/agent-execution-session.d.ts.map +0 -1
- package/dist/agent/agent-execution-session.js +0 -238
- package/dist/agent/agent-execution-session.js.map +0 -1
- package/dist/agent/agent-interaction-audit.d.ts +0 -58
- package/dist/agent/agent-interaction-audit.d.ts.map +0 -1
- package/dist/agent/agent-interaction-audit.js +0 -114
- package/dist/agent/agent-interaction-audit.js.map +0 -1
- package/dist/agent/agent-interaction-events.d.ts +0 -40
- package/dist/agent/agent-interaction-events.d.ts.map +0 -1
- package/dist/agent/agent-interaction-events.js +0 -129
- package/dist/agent/agent-interaction-events.js.map +0 -1
- package/dist/agent/agent-mcp-budget.d.ts +0 -125
- package/dist/agent/agent-mcp-budget.d.ts.map +0 -1
- package/dist/agent/agent-mcp-budget.js +0 -566
- package/dist/agent/agent-mcp-budget.js.map +0 -1
- package/dist/agent/agent-mcp-host.d.ts +0 -125
- package/dist/agent/agent-mcp-host.d.ts.map +0 -1
- package/dist/agent/agent-mcp-host.js.map +0 -1
- package/dist/agent/agent-mcp-runtime.d.ts.map +0 -1
- package/dist/agent/agent-mcp-runtime.js.map +0 -1
- package/dist/agent/agent-read-runtime.d.ts +0 -70
- package/dist/agent/agent-read-runtime.d.ts.map +0 -1
- package/dist/agent/agent-read-runtime.js +0 -320
- package/dist/agent/agent-read-runtime.js.map +0 -1
- package/dist/agent/agent-revisions.d.ts +0 -35
- package/dist/agent/agent-revisions.d.ts.map +0 -1
- package/dist/agent/agent-revisions.js +0 -133
- package/dist/agent/agent-revisions.js.map +0 -1
- package/dist/agent/agent-scheduler.d.ts +0 -99
- package/dist/agent/agent-scheduler.d.ts.map +0 -1
- package/dist/agent/agent-scheduler.js +0 -398
- package/dist/agent/agent-scheduler.js.map +0 -1
- package/dist/bus/saved-dispatcher-binding.d.ts +0 -53
- package/dist/bus/saved-dispatcher-binding.d.ts.map +0 -1
- package/dist/bus/saved-dispatcher-binding.js.map +0 -1
- package/dist/delivery/delivery-run-coordinator.d.ts +0 -218
- package/dist/delivery/delivery-run-coordinator.d.ts.map +0 -1
- package/dist/delivery/delivery-run-coordinator.js +0 -609
- package/dist/delivery/delivery-run-coordinator.js.map +0 -1
- package/dist/delivery/parked-delivery-obligations.d.ts +0 -182
- package/dist/delivery/parked-delivery-obligations.d.ts.map +0 -1
- package/dist/delivery/parked-delivery-obligations.js +0 -390
- package/dist/delivery/parked-delivery-obligations.js.map +0 -1
- package/dist/server/environment-attachment.d.ts +0 -356
- package/dist/server/environment-attachment.d.ts.map +0 -1
- package/dist/server/environment-attachment.js +0 -2062
- package/dist/server/environment-attachment.js.map +0 -1
- package/dist/server/environment-delivery-records.d.ts +0 -77
- package/dist/server/environment-delivery-records.d.ts.map +0 -1
- package/dist/server/environment-delivery-records.js +0 -282
- package/dist/server/environment-delivery-records.js.map +0 -1
- package/dist/server/environment-delivery-worker.d.ts +0 -161
- package/dist/server/environment-delivery-worker.d.ts.map +0 -1
- package/dist/server/environment-delivery-worker.js +0 -973
- package/dist/server/environment-delivery-worker.js.map +0 -1
|
@@ -20,36 +20,28 @@ import { freezeRegistry, isAiModel, registryOptions } from "@spine-event-engine/
|
|
|
20
20
|
import { TypeUrls } from "@spine-event-engine/core";
|
|
21
21
|
import { EventSchema, EventContextSchema, BoundedContextNameSchema, TenantIdSchema, } from "@spine-event-engine/proto";
|
|
22
22
|
import { SPI_type, internal_all, internal_type } from "@spine-event-engine/proto";
|
|
23
|
-
import { ColumnTypes, EventStore,
|
|
24
|
-
import {
|
|
25
|
-
/**
|
|
26
|
-
* Storage family shared by paired System Event publication and trusted reads.
|
|
27
|
-
* @internal
|
|
28
|
-
*/
|
|
29
|
-
export const systemEventGroup = new StorageGroup("spine_system_events");
|
|
23
|
+
import { ColumnTypes, EventStore, RecordColumn, } from "@spine-event-engine/storage";
|
|
24
|
+
import { AgentHistoryStorageFactories, EventStoreGroups, eventStoreRecordSpec, StoragePreparations, TenantBoundary, } from "@spine-event-engine/storage/provider";
|
|
30
25
|
import { CommandBus, commandBusAccess } from "../bus/command-bus.js";
|
|
31
26
|
import { EventBus, eventBusAccess, isSystemEventSchema, } from "../bus/event-bus.js";
|
|
32
|
-
import { DeliveryReadiness
|
|
27
|
+
import { DeliveryReadiness } from "./local-inbox-handoff.js";
|
|
33
28
|
import { LocalEntityInbox } from "./entity-inbox.js";
|
|
34
29
|
import { LocalProjectionInbox } from "./projection-handoff.js";
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
37
|
-
import {
|
|
38
|
-
import { AgentExecutionCapacity } from "../agent/agent-execution-capacity.js";
|
|
30
|
+
import { TenantAdmissions } from "./tenant-unknown-error.js";
|
|
31
|
+
import { AgentExecutionCapacity, } from "../agent/agent-execution-capacity.js";
|
|
32
|
+
import { AgentAcceptanceReceipts, agentAcceptanceReceiptGroup, agentAcceptanceReceiptSpec, } from "../agent/agent-acceptance-receipts.js";
|
|
39
33
|
import { Repository, repositoryAccess, } from "../repository/repository.js";
|
|
40
34
|
import { GeneratedRegistryDiscovery } from "../handler/generated-registry-discovery.js";
|
|
41
35
|
import { HandlerRegistryIngestor, } from "../handler/generated-handler-registry.js";
|
|
42
36
|
import { AbstractAssignee, AbstractCommander, AbstractEventReactor, AbstractEventSubscriber, } from "../handler/standalone.js";
|
|
43
37
|
import { HandlerMetadataRegistry, } from "../handler/handler-metadata.js";
|
|
44
38
|
import { SignalMetadata } from "../runtime/signal-metadata.js";
|
|
45
|
-
import {
|
|
39
|
+
import { runtimeAccess } from "../runtime/runtime.js";
|
|
40
|
+
import { Stand, standAccess } from "../stand/stand.js";
|
|
46
41
|
import { SubscriptionRuntime, subscriptionRuntimeAccess } from "../stand/subscription-runtime.js";
|
|
47
|
-
import { StorageSubscriptionRegistry, } from "../stand/subscription-registry.js";
|
|
48
|
-
import { UniformAcrossAllShards } from "../delivery/delivery-builder.js";
|
|
49
|
-
import { InboxTargets } from "../delivery/inbox.js";
|
|
50
|
-
import { ShardIndex } from "../delivery/shard-index.js";
|
|
42
|
+
import { StorageSubscriptionRegistry, subscriptionRecordSpec, } from "../stand/subscription-registry.js";
|
|
51
43
|
import { IntegrationBroker } from "../integration/integration-broker.js";
|
|
52
|
-
import { ServerEnvironment } from "../server/server-environment.js";
|
|
44
|
+
import { ServerEnvironment, serverEnvironmentAccess } from "../server/server-environment.js";
|
|
53
45
|
import { SignalPublisher } from "../runtime/signal-publisher.js";
|
|
54
46
|
import { StandaloneHandlerRuntime, } from "../runtime/standalone-handler-runtime.js";
|
|
55
47
|
/**
|
|
@@ -73,6 +65,38 @@ export class BoundedContextNameError extends Error {
|
|
|
73
65
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
74
66
|
}
|
|
75
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Rejects an Entity state type already served by another open domain context.
|
|
70
|
+
*/
|
|
71
|
+
export class DuplicateEntityStateException extends Error {
|
|
72
|
+
// prettier-ignore
|
|
73
|
+
/**
|
|
74
|
+
* Wire type URL of the conflicting Entity state.
|
|
75
|
+
*/
|
|
76
|
+
stateType;
|
|
77
|
+
/**
|
|
78
|
+
* Name of the context that already serves the state.
|
|
79
|
+
*/
|
|
80
|
+
registeredContext;
|
|
81
|
+
/**
|
|
82
|
+
* Name of the context attempting registration.
|
|
83
|
+
*/
|
|
84
|
+
conflictingContext;
|
|
85
|
+
/**
|
|
86
|
+
* Creates a duplicate-state registration error.
|
|
87
|
+
* @param stateType Entity state wire type URL.
|
|
88
|
+
* @param registeredContext Existing domain context name.
|
|
89
|
+
* @param conflictingContext New domain context name.
|
|
90
|
+
*/
|
|
91
|
+
constructor(stateType, registeredContext, conflictingContext) {
|
|
92
|
+
super(`Entity state "${stateType}" is already registered in "${registeredContext}"; ` +
|
|
93
|
+
`register it in only one open domain Bounded Context (attempted in "${conflictingContext}").`);
|
|
94
|
+
this.name = "DuplicateEntityStateException";
|
|
95
|
+
this.stateType = stateType;
|
|
96
|
+
this.registeredContext = registeredContext;
|
|
97
|
+
this.conflictingContext = conflictingContext;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
76
100
|
const frameworkConstructionToken = Object.freeze({
|
|
77
101
|
frameworkConstructionToken: true,
|
|
78
102
|
});
|
|
@@ -87,6 +111,8 @@ const contextTenantIndexes = new WeakMap();
|
|
|
87
111
|
const contextStorageFactories = new WeakMap();
|
|
88
112
|
const contextDeliveryDescriptors = new WeakMap();
|
|
89
113
|
const contextSubscriptionRuntimes = new WeakMap();
|
|
114
|
+
const contextPreparations = new WeakMap();
|
|
115
|
+
const contextInputPreparations = new WeakMap();
|
|
90
116
|
const contextLoggers = new WeakMap();
|
|
91
117
|
const contextSignalPublishers = new WeakMap();
|
|
92
118
|
const contextEventBuses = new WeakMap();
|
|
@@ -97,7 +123,9 @@ const contextClosePhases = new WeakMap();
|
|
|
97
123
|
const contextIntegrations = new WeakMap();
|
|
98
124
|
const systemEventPosters = new WeakMap();
|
|
99
125
|
const builderBuilds = new WeakMap();
|
|
126
|
+
const explicitPreparations = new WeakMap();
|
|
100
127
|
let constructBoundedContext;
|
|
128
|
+
let registerBoundedContext;
|
|
101
129
|
let constructBoundedContextBuilder;
|
|
102
130
|
let constructContextSpec;
|
|
103
131
|
/**
|
|
@@ -113,22 +141,37 @@ export class BoundedContext {
|
|
|
113
141
|
#eventEndpoint;
|
|
114
142
|
#entityInbox;
|
|
115
143
|
#projectionInbox;
|
|
116
|
-
#
|
|
144
|
+
#deliveryReadiness;
|
|
145
|
+
#stopAgentAvailability;
|
|
117
146
|
#registeredRepositories = [];
|
|
118
147
|
#repositoryViews = new Set();
|
|
119
148
|
#storageFactory;
|
|
149
|
+
#environment;
|
|
120
150
|
#ai;
|
|
121
151
|
#stand;
|
|
122
152
|
#systemStand;
|
|
123
153
|
#subscriptionRuntime;
|
|
124
|
-
|
|
125
|
-
|
|
154
|
+
/**
|
|
155
|
+
* Explicit storage and subscription preparation for this built context.
|
|
156
|
+
*/
|
|
157
|
+
runtime;
|
|
158
|
+
#agentReceipts;
|
|
159
|
+
#agentController = new AbortController();
|
|
160
|
+
#agentReservations = new Set();
|
|
161
|
+
#activeReplays = new Set();
|
|
162
|
+
#activePreparations = new Set();
|
|
163
|
+
#tenantPreparations = new Map();
|
|
164
|
+
#preparationController = new AbortController();
|
|
165
|
+
#sharedPreparation;
|
|
126
166
|
#closed;
|
|
127
167
|
/**
|
|
128
168
|
* Registers the framework-only construction hook for this module.
|
|
129
169
|
*/
|
|
130
170
|
static {
|
|
131
171
|
constructBoundedContext = (input) => new BoundedContext(input);
|
|
172
|
+
registerBoundedContext = (context, repositories) => {
|
|
173
|
+
context.#registerRepositories(repositories);
|
|
174
|
+
};
|
|
132
175
|
}
|
|
133
176
|
/**
|
|
134
177
|
* Creates a Bounded Context from its prepared framework services.
|
|
@@ -145,9 +188,12 @@ export class BoundedContext {
|
|
|
145
188
|
this.#stand = input.stand;
|
|
146
189
|
this.#systemStand = input.systemStand;
|
|
147
190
|
this.#subscriptionRuntime = input.subscriptionRuntime;
|
|
191
|
+
this.runtime = this.#createRuntime();
|
|
148
192
|
this.#storageFactory = input.storageFactory;
|
|
193
|
+
this.#agentReceipts = new AgentAcceptanceReceipts(input.storageFactory);
|
|
194
|
+
this.#environment = ServerEnvironment.instance();
|
|
149
195
|
this.#ai = input.ai;
|
|
150
|
-
this.#
|
|
196
|
+
this.#installTenantAdmission();
|
|
151
197
|
this.#commandEndpoint = Object.freeze({
|
|
152
198
|
acceptedCommandTypes: () => this.#commandBus.acceptedCommandTypes(),
|
|
153
199
|
post: (command) => this.#commandBus.post(command),
|
|
@@ -156,79 +202,304 @@ export class BoundedContext {
|
|
|
156
202
|
acceptedEventTypes: () => ContextParts.exposedEventTypeUrls(this.#eventBus),
|
|
157
203
|
post: (event) => ContextParts.postContextEvent(this, event),
|
|
158
204
|
});
|
|
159
|
-
const delivery = this.#createDelivery(input.
|
|
205
|
+
const delivery = this.#createDelivery(input.tenantIndex);
|
|
206
|
+
this.#deliveryReadiness = delivery.deliveryReadiness;
|
|
160
207
|
this.#entityInbox = delivery.entityInbox;
|
|
161
208
|
this.#projectionInbox = delivery.projectionInbox;
|
|
162
209
|
this.#installReferences(input, delivery);
|
|
163
|
-
this.#
|
|
164
|
-
this.#startAgentScheduler(delivery.tenantIndex);
|
|
210
|
+
serverEnvironmentAccess.addContext(this.#environment, this);
|
|
165
211
|
Object.freeze(this);
|
|
166
212
|
}
|
|
167
213
|
/**
|
|
168
|
-
*
|
|
214
|
+
* Exposes explicit preparation for this built context.
|
|
215
|
+
*
|
|
216
|
+
* @returns Stable explicit preparation methods.
|
|
169
217
|
*/
|
|
170
|
-
#
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
return;
|
|
176
|
-
this.#agentScheduler = new AgentScheduler({
|
|
177
|
-
repositories: repositories.length,
|
|
178
|
-
page: (after, signal) => tenantIndex.page({
|
|
179
|
-
count: 16,
|
|
180
|
-
signal,
|
|
181
|
-
...(after === undefined ? {} : { after }),
|
|
182
|
-
}),
|
|
183
|
-
scope: (id, index) => {
|
|
184
|
-
const repository = repositories[index];
|
|
185
|
-
if (repository === undefined)
|
|
186
|
-
throw new Error("Agent repository scope is unavailable.");
|
|
187
|
-
return this.#agentScope(repository, tenantIndex.tenantMode === "single-tenant" ? undefined : id);
|
|
188
|
-
},
|
|
189
|
-
}, AgentExecutionCapacity.for(this.#ai), (error) => {
|
|
190
|
-
if (this.#agentErrors.length < 32)
|
|
191
|
-
this.#agentErrors.push(error);
|
|
218
|
+
#createRuntime() {
|
|
219
|
+
return Object.freeze({
|
|
220
|
+
createStorageStructures: (...ids) => this.#explicitStructures("create", ids),
|
|
221
|
+
checkStructureCompatibility: (...ids) => this.#explicitStructures("check", ids),
|
|
222
|
+
startSubscriptionMonitoring: () => this.#explicitMonitoring(),
|
|
192
223
|
});
|
|
193
|
-
this.#agentScheduler.start();
|
|
194
224
|
}
|
|
195
225
|
/**
|
|
196
|
-
*
|
|
226
|
+
* Installs live membership checks on context signal buses.
|
|
197
227
|
*
|
|
198
|
-
* @param repository Registered Agent repository.
|
|
199
|
-
* @param tenantId Accepted tenant, absent only in a single-tenant Bounded Context.
|
|
200
|
-
* @returns The matching indexed provider scan scope.
|
|
201
228
|
*/
|
|
202
|
-
#
|
|
203
|
-
|
|
229
|
+
#installTenantAdmission() {
|
|
230
|
+
explicitPreparations.set(this, (mode, ids) => this.#prepareExplicitLocal(mode, ids));
|
|
231
|
+
commandBusAccess.bindEnvironment(this.#commandBus, this.#environment);
|
|
232
|
+
eventBusAccess.bindEnvironment(this.#eventBus, this.#environment);
|
|
233
|
+
eventBusAccess.bindEnvironment(this.#systemEventBus, this.#environment);
|
|
234
|
+
commandBusAccess.setAdmission(this.#commandBus, (command) => this.#admitTenant(command.context?.actorContext?.tenantId));
|
|
235
|
+
eventBusAccess.setAdmission(this.#eventBus, (event) => this.#admitTenant(ContextParts.readReplayTenant(event)));
|
|
236
|
+
standAccess.setAdmission(this.#stand, (tenantId) => this.#admitTenant(tenantId));
|
|
237
|
+
standAccess.setPublicAdmission(this.#stand, () => {
|
|
238
|
+
if (closingContexts.has(this) && !runtimeAccess.isRunningAdmittedWork(this.#environment))
|
|
239
|
+
throw new Error("Bounded Context is closing.");
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Checks this context's live index before repository work.
|
|
244
|
+
*
|
|
245
|
+
* @param tenantId Captured tenant identity, when applicable.
|
|
246
|
+
*/
|
|
247
|
+
async #admitTenant(tenantId) {
|
|
248
|
+
await TenantAdmissions.require(this.tenantIndex, this.#snapshot.name.value, this.isMultitenant, tenantId);
|
|
249
|
+
await this.#prepareInput(tenantId);
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Initializes only the admitted input's complete context and tenant requirements.
|
|
253
|
+
* @param tenantId Captured input tenant identity, when applicable.
|
|
254
|
+
* @returns Completion after storage and first monitoring pass are ready.
|
|
255
|
+
*/
|
|
256
|
+
async #prepareInput(tenantId) {
|
|
257
|
+
await this.#prepareShared("create");
|
|
258
|
+
await this.#prepareTenant(tenantId, "create");
|
|
259
|
+
if (!closingContexts.has(this))
|
|
260
|
+
await this.#subscriptionRuntime.start();
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Prepares the fixed shared subscription registry layout once per context.
|
|
264
|
+
* @param mode Required provider preparation mode.
|
|
265
|
+
* @returns Shared preparation completion.
|
|
266
|
+
*/
|
|
267
|
+
#prepareShared(mode) {
|
|
268
|
+
if (!(this.#subscriptionRuntime.registry() instanceof StorageSubscriptionRegistry))
|
|
269
|
+
return Promise.resolve();
|
|
270
|
+
if (this.#sharedPreparation !== undefined)
|
|
271
|
+
return this.#sharedPreparation;
|
|
272
|
+
const attempt = this.#trackPreparation(() => StoragePreparations.for(this.#storageFactory).prepareRecord(ContextParts.createSubscriptionStorageContext(this.#snapshot.spec), subscriptionRecordSpec, undefined, mode));
|
|
273
|
+
this.#sharedPreparation = attempt;
|
|
274
|
+
void attempt.catch(() => {
|
|
275
|
+
if (this.#sharedPreparation === attempt)
|
|
276
|
+
this.#sharedPreparation = undefined;
|
|
277
|
+
});
|
|
278
|
+
return attempt;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Shares one pending complete-tenant preparation without caching a stale model flag.
|
|
282
|
+
* @param tenantId Captured tenant identity, when applicable.
|
|
283
|
+
* @param mode Required provider preparation mode.
|
|
284
|
+
* @returns Complete tenant preparation attempt.
|
|
285
|
+
*/
|
|
286
|
+
#prepareTenant(tenantId, mode) {
|
|
287
|
+
const context = this.#storageContextFor(tenantId);
|
|
288
|
+
const key = TenantBoundary.of(context).key;
|
|
289
|
+
const pending = this.#tenantPreparations.get(key);
|
|
290
|
+
if (pending !== undefined)
|
|
291
|
+
return pending;
|
|
292
|
+
const attempt = this.#trackPreparation(() => this.#prepareTenantNow(context, mode));
|
|
293
|
+
this.#tenantPreparations.set(key, attempt);
|
|
294
|
+
void attempt.then(() => this.#tenantPreparations.delete(key), () => this.#tenantPreparations.delete(key));
|
|
295
|
+
return attempt;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Prepares exact Event, Entity and Agent families for one captured destination.
|
|
299
|
+
* @param context Captured physical storage destination.
|
|
300
|
+
* @param mode Required provider preparation mode.
|
|
301
|
+
* @returns Completion after all declared families are ready.
|
|
302
|
+
*/
|
|
303
|
+
async #prepareTenantNow(context, mode) {
|
|
304
|
+
const preparation = StoragePreparations.for(this.#storageFactory);
|
|
305
|
+
await preparation.prepareRecord(context, eventStoreRecordSpec, EventStoreGroups.forContext(context.name), mode);
|
|
306
|
+
const system = ContextParts.requireSystemPairing(this).system;
|
|
307
|
+
if (system.storesEvents) {
|
|
308
|
+
const systemContext = { ...context, name: system.name.value };
|
|
309
|
+
await preparation.prepareRecord(systemContext, eventStoreRecordSpec, EventStoreGroups.forContext(system.name.value, "system"), mode);
|
|
310
|
+
}
|
|
311
|
+
for (const repository of this.#repositoryViews)
|
|
312
|
+
await repositoryAccess.prepareStorage(repository, context, this.#storageFactory, mode);
|
|
313
|
+
if ([...this.#repositoryViews].some((repository) => repository.entityFamily === "agent"))
|
|
314
|
+
await preparation.prepareRecord(context, agentAcceptanceReceiptSpec, agentAcceptanceReceiptGroup, mode);
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Captures the same tenant scope used by repository and Event storage.
|
|
318
|
+
* @param tenantId Complete selected tenant identity, when applicable.
|
|
319
|
+
* @returns A detached provider storage context.
|
|
320
|
+
*/
|
|
321
|
+
#storageContextFor(tenantId) {
|
|
322
|
+
if (!this.isMultitenant)
|
|
323
|
+
return { name: this.name.value, multitenant: false };
|
|
324
|
+
if (tenantId === undefined)
|
|
325
|
+
throw new Error("Multitenant preparation requires tenantId.");
|
|
204
326
|
return {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
repository.stateFullTypeName,
|
|
209
|
-
]),
|
|
210
|
-
pending: (after, count) => repositoryAccess.pendingAcceptedAgents(repository, tenantId, after, count),
|
|
211
|
-
run: (key, signal) => repositoryAccess.runAcceptedAgent(repository, tenantId, key, signal),
|
|
327
|
+
name: this.name.value,
|
|
328
|
+
multitenant: true,
|
|
329
|
+
tenantId: TenantBoundary.from(tenantId).tenantId,
|
|
212
330
|
};
|
|
213
331
|
}
|
|
332
|
+
/**
|
|
333
|
+
* Retains an admitted preparation until closure can safely release its provider.
|
|
334
|
+
* @param onPrepare Complete provider preparation callback.
|
|
335
|
+
* @returns Tracked preparation completion.
|
|
336
|
+
*/
|
|
337
|
+
#trackPreparation(onPrepare) {
|
|
338
|
+
const attempt = Promise.resolve().then(onPrepare);
|
|
339
|
+
this.#activePreparations.add(attempt);
|
|
340
|
+
void attempt.then(() => this.#activePreparations.delete(attempt), () => this.#activePreparations.delete(attempt));
|
|
341
|
+
return attempt;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Completes independently requested eager structure and monitor phases.
|
|
345
|
+
* @param options Independent build-time initialization choices.
|
|
346
|
+
* @returns Completion after requested phases settle.
|
|
347
|
+
*/
|
|
348
|
+
async #prepareBuild(options) {
|
|
349
|
+
if (options.createStorageStructures !== false) {
|
|
350
|
+
await this.#requiredPrepare(() => this.#prepareShared("create"));
|
|
351
|
+
await this.#prepareCurrentTenants("create");
|
|
352
|
+
}
|
|
353
|
+
if (options.startSubscriptionMonitoring !== false) {
|
|
354
|
+
await this.#requiredPrepare(() => this.#prepareShared("create"));
|
|
355
|
+
await this.#requiredPrepare(() => this.#subscriptionRuntime.start());
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Classifies a requested startup phase without changing shared lazy attempts.
|
|
360
|
+
* @param onPrepare Required provider or initial-monitor phase.
|
|
361
|
+
* @returns Completion of the requested phase.
|
|
362
|
+
*/
|
|
363
|
+
async #requiredPrepare(onPrepare) {
|
|
364
|
+
serverEnvironmentAccess.requireAvailable(this.#environment);
|
|
365
|
+
try {
|
|
366
|
+
await onPrepare();
|
|
367
|
+
}
|
|
368
|
+
catch (error) {
|
|
369
|
+
serverEnvironmentAccess.failStartup(this.#environment, error);
|
|
370
|
+
throw error;
|
|
371
|
+
}
|
|
372
|
+
serverEnvironmentAccess.requireAvailable(this.#environment);
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Traverses the current live index in finite pages without a tenant cap.
|
|
376
|
+
* @param mode Creation or read-only check.
|
|
377
|
+
* @returns Completion after currently listed tenants are prepared.
|
|
378
|
+
*/
|
|
379
|
+
async #prepareCurrentTenants(mode) {
|
|
380
|
+
if (!this.isMultitenant)
|
|
381
|
+
return this.#requiredPrepare(() => this.#prepareTenant(undefined, mode));
|
|
382
|
+
let after;
|
|
383
|
+
do {
|
|
384
|
+
serverEnvironmentAccess.requireAvailable(this.#environment);
|
|
385
|
+
const page = await this.tenantIndex.page({
|
|
386
|
+
count: 64,
|
|
387
|
+
signal: this.#preparationController.signal,
|
|
388
|
+
...(after === undefined ? {} : { after }),
|
|
389
|
+
});
|
|
390
|
+
serverEnvironmentAccess.requireAvailable(this.#environment);
|
|
391
|
+
for (const id of page.ids) {
|
|
392
|
+
serverEnvironmentAccess.requireAvailable(this.#environment);
|
|
393
|
+
await TenantAdmissions.require(this.tenantIndex, this.name.value, true, id);
|
|
394
|
+
serverEnvironmentAccess.requireAvailable(this.#environment);
|
|
395
|
+
await this.#requiredPrepare(() => this.#prepareTenant(id, mode));
|
|
396
|
+
}
|
|
397
|
+
after = page.hasMore ? page.after : undefined;
|
|
398
|
+
} while (after !== undefined);
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Admits explicit structure work and joins fatal cleanup after local settlement.
|
|
402
|
+
* @param mode Creation or read-only compatibility mode.
|
|
403
|
+
* @param tenantIds Explicit complete tenant identities, if supplied.
|
|
404
|
+
* @returns Completion after the selected structures are ready.
|
|
405
|
+
*/
|
|
406
|
+
async #explicitStructures(mode, tenantIds) {
|
|
407
|
+
const captured = tenantIds.map((id) => TenantBoundary.from(id).tenantId);
|
|
408
|
+
const admitted = boundedContextAccess.admitServiceRegistryOperation(this, () => this.#prepareExplicitLocal(mode, captured));
|
|
409
|
+
return this.#joinRequiredCleanup(admitted);
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Runs explicit preparation while the caller holds this context's admission.
|
|
413
|
+
*
|
|
414
|
+
* @param mode Creation or read-only check.
|
|
415
|
+
* @param ids Captured tenant identities, when supplied.
|
|
416
|
+
* @returns Completion of the local preparation.
|
|
417
|
+
*/
|
|
418
|
+
async #prepareExplicitLocal(mode, ids) {
|
|
419
|
+
const selected = await this.#selectedTenants(ids);
|
|
420
|
+
await this.#requiredPrepare(() => this.#prepareShared(mode));
|
|
421
|
+
if (selected === undefined)
|
|
422
|
+
return this.#prepareCurrentTenants(mode);
|
|
423
|
+
for (const id of selected) {
|
|
424
|
+
await TenantAdmissions.require(this.tenantIndex, this.name.value, this.isMultitenant, id);
|
|
425
|
+
await this.#requiredPrepare(() => this.#prepareTenant(id, mode));
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Validates all explicit IDs before any provider preparation.
|
|
430
|
+
*
|
|
431
|
+
* @param ids Captured explicit tenant identities.
|
|
432
|
+
* @returns Distinct current IDs or undefined for all current tenants.
|
|
433
|
+
*/
|
|
434
|
+
async #selectedTenants(ids) {
|
|
435
|
+
if (ids.length === 0)
|
|
436
|
+
return undefined;
|
|
437
|
+
const selected = new Map();
|
|
438
|
+
for (const id of ids) {
|
|
439
|
+
await TenantAdmissions.require(this.tenantIndex, this.name.value, this.isMultitenant, id);
|
|
440
|
+
const boundary = TenantBoundary.from(id);
|
|
441
|
+
selected.set(boundary.key, boundary.tenantId);
|
|
442
|
+
}
|
|
443
|
+
return [...selected.values()];
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Starts the one initial monitor pass as an explicit required phase.
|
|
447
|
+
*
|
|
448
|
+
* @returns Completion after the initial pass.
|
|
449
|
+
*/
|
|
450
|
+
#explicitMonitoring() {
|
|
451
|
+
const admitted = boundedContextAccess.admitServiceRegistryOperation(this, async () => {
|
|
452
|
+
await this.#requiredPrepare(() => this.#prepareShared("create"));
|
|
453
|
+
await this.#requiredPrepare(() => this.#subscriptionRuntime.start());
|
|
454
|
+
});
|
|
455
|
+
return this.#joinRequiredCleanup(admitted);
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Waits for recorded fatal cleanup only after context admission settles.
|
|
459
|
+
*
|
|
460
|
+
* @param admitted Locally admitted operation.
|
|
461
|
+
* @returns Completion after local work or fatal cleanup.
|
|
462
|
+
*/
|
|
463
|
+
async #joinRequiredCleanup(admitted) {
|
|
464
|
+
try {
|
|
465
|
+
await admitted;
|
|
466
|
+
}
|
|
467
|
+
catch (error) {
|
|
468
|
+
if (serverEnvironmentAccess.hasStartupFailure(this.#environment))
|
|
469
|
+
return serverEnvironmentAccess.awaitStartupFailure(this.#environment);
|
|
470
|
+
throw error;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
214
473
|
/**
|
|
215
474
|
* Creates tenant-aware Inboxes and their shared readiness signal.
|
|
216
475
|
*
|
|
217
|
-
* @param
|
|
476
|
+
* @param tenantIndex Effective membership index for this context.
|
|
218
477
|
* @returns Inboxes, tenant index, and readiness signal.
|
|
219
478
|
*/
|
|
220
|
-
#createDelivery(
|
|
221
|
-
const deliveryReadiness =
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
const entityInbox = new LocalEntityInbox(this.#snapshot.name.value, deliveryReadiness, keepTenant, this.#deliveryStrategy);
|
|
229
|
-
const projectionInbox = new LocalProjectionInbox(this.#snapshot.name.value, deliveryReadiness, keepTenant);
|
|
479
|
+
#createDelivery(tenantIndex) {
|
|
480
|
+
const deliveryReadiness = this.#createReadiness();
|
|
481
|
+
const onAdmit = (tenantId) => this.#admitTenant(tenantId);
|
|
482
|
+
const entityInbox = new LocalEntityInbox(this.#snapshot.name.value, deliveryReadiness, onAdmit, this.#environment.delivery.strategy);
|
|
483
|
+
const projectionInbox = new LocalProjectionInbox(this.#snapshot.name.value, deliveryReadiness, onAdmit);
|
|
484
|
+
deliveryReadiness.configureDirect((message, tenantId) => message.label === "UPDATE_SUBSCRIBER"
|
|
485
|
+
? projectionInbox.replay(message, tenantId)
|
|
486
|
+
: entityInbox.replay(message, tenantId));
|
|
230
487
|
return { deliveryReadiness, tenantIndex, entityInbox, projectionInbox };
|
|
231
488
|
}
|
|
489
|
+
/**
|
|
490
|
+
* Creates readiness for the shared Delivery and this context's Agent capacity.
|
|
491
|
+
* @returns Shared readiness with the Agent capacity wake installed.
|
|
492
|
+
*/
|
|
493
|
+
#createReadiness() {
|
|
494
|
+
const readiness = new DeliveryReadiness(undefined, serverEnvironmentAccess.coreDelivery(this.#environment), (shard) => {
|
|
495
|
+
serverEnvironmentAccess.notifyDelivery(this.#environment, shard);
|
|
496
|
+
}, serverEnvironmentAccess.remoteSupervised(this.#environment));
|
|
497
|
+
if (this.#ai !== undefined)
|
|
498
|
+
this.#stopAgentAvailability = AgentExecutionCapacity.for(this.#ai).onAvailable(() => {
|
|
499
|
+
readiness.retryDeferred();
|
|
500
|
+
});
|
|
501
|
+
return readiness;
|
|
502
|
+
}
|
|
232
503
|
/**
|
|
233
504
|
* Installs framework references used after context construction.
|
|
234
505
|
*
|
|
@@ -244,35 +515,17 @@ export class BoundedContext {
|
|
|
244
515
|
contextTenantIndexes.set(this, delivery.tenantIndex);
|
|
245
516
|
contextStorageFactories.set(this, input.storageFactory);
|
|
246
517
|
contextSubscriptionRuntimes.set(this, this.#subscriptionRuntime);
|
|
518
|
+
contextPreparations.set(this, (options) => this.#prepareBuild(options));
|
|
519
|
+
contextInputPreparations.set(this, (tenantId) => this.#prepareInput(tenantId));
|
|
247
520
|
contextSignalPublishers.set(this, this.#publisher);
|
|
248
521
|
contextClosePhases.set(this, {
|
|
249
522
|
begin: () => {
|
|
250
523
|
this.#beginClose();
|
|
251
524
|
},
|
|
252
525
|
drain: () => this.#drainWork(),
|
|
526
|
+
trackReplay: (onReplay) => this.#trackReplay(onReplay),
|
|
253
527
|
});
|
|
254
|
-
contextDeliveryDescriptors.set(this, ContextParts.createDeliveryDescriptor(this.#
|
|
255
|
-
}
|
|
256
|
-
/**
|
|
257
|
-
* Registers repositories and starts subscriptions, closing the tenant index on failure.
|
|
258
|
-
*
|
|
259
|
-
* @param repositories Prepared repositories.
|
|
260
|
-
* @param tenantIndex Index to close if registration fails.
|
|
261
|
-
*/
|
|
262
|
-
#registerAndStart(repositories, tenantIndex) {
|
|
263
|
-
try {
|
|
264
|
-
this.#registerRepositories(repositories);
|
|
265
|
-
this.#subscriptionRuntime.start();
|
|
266
|
-
}
|
|
267
|
-
catch (error) {
|
|
268
|
-
try {
|
|
269
|
-
ContextParts.cleanupFailedContext(this, tenantIndex);
|
|
270
|
-
}
|
|
271
|
-
catch (cleanupError) {
|
|
272
|
-
throw new AggregateError([error, cleanupError], "Bounded Context build failed, and tenant index cleanup also failed.");
|
|
273
|
-
}
|
|
274
|
-
throw error;
|
|
275
|
-
}
|
|
528
|
+
contextDeliveryDescriptors.set(this, ContextParts.createDeliveryDescriptor(this.#entityInbox, this.#projectionInbox));
|
|
276
529
|
}
|
|
277
530
|
#registerRepositories(repositories) {
|
|
278
531
|
const preparedRepositories = this.#prepareRepositories(repositories);
|
|
@@ -280,6 +533,7 @@ export class BoundedContext {
|
|
|
280
533
|
for (const preparedRepository of preparedRepositories) {
|
|
281
534
|
ContextParts.rejectRegisteredRepository(preparedRepository.repository);
|
|
282
535
|
}
|
|
536
|
+
this.#reserveStates(preparedRepositories);
|
|
283
537
|
for (const preparedRepository of preparedRepositories) {
|
|
284
538
|
this.#stand.register(preparedRepository.snapshot.stateSchema, {
|
|
285
539
|
columns: ContextParts.repositoryColumns(preparedRepository.snapshot),
|
|
@@ -299,6 +553,19 @@ export class BoundedContext {
|
|
|
299
553
|
this.#failRegistration(error, preparedRepositories);
|
|
300
554
|
}
|
|
301
555
|
}
|
|
556
|
+
/**
|
|
557
|
+
* Reserves Entity state types before registration mutates delivery targets.
|
|
558
|
+
*
|
|
559
|
+
* @param repositories Prepared repositories for this context.
|
|
560
|
+
*/
|
|
561
|
+
#reserveStates(repositories) {
|
|
562
|
+
for (const repository of repositories) {
|
|
563
|
+
const stateType = TypeUrls.derive(repository.snapshot.stateSchema);
|
|
564
|
+
const prior = serverEnvironmentAccess.reserveState(this.#environment, this, stateType);
|
|
565
|
+
if (prior !== undefined)
|
|
566
|
+
throw new DuplicateEntityStateException(stateType, prior.name.value, this.name.value);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
302
569
|
#prepareRepositories(repositories) {
|
|
303
570
|
const registration = this.#repositoryRegistration();
|
|
304
571
|
const preparedRepositories = [];
|
|
@@ -312,14 +579,28 @@ export class BoundedContext {
|
|
|
312
579
|
}
|
|
313
580
|
return preparedRepositories;
|
|
314
581
|
}
|
|
582
|
+
/**
|
|
583
|
+
* Reserves shared-registry capacity before Agent receipt admission.
|
|
584
|
+
*/
|
|
585
|
+
#reserveAgent(key, signal) {
|
|
586
|
+
const reservation = this.#ai === undefined
|
|
587
|
+
? undefined
|
|
588
|
+
: AgentExecutionCapacity.for(this.#ai).reserve(key, signal);
|
|
589
|
+
if (reservation !== undefined) {
|
|
590
|
+
this.#agentReservations.add(reservation.completed);
|
|
591
|
+
void reservation.completed.then(() => this.#agentReservations.delete(reservation.completed), () => this.#agentReservations.delete(reservation.completed));
|
|
592
|
+
}
|
|
593
|
+
return reservation;
|
|
594
|
+
}
|
|
315
595
|
/**
|
|
316
596
|
* Builds callbacks shared by every repository prepared for this Bounded Context.
|
|
317
597
|
*/
|
|
318
598
|
#repositoryRegistration() {
|
|
319
|
-
|
|
599
|
+
return {
|
|
320
600
|
name: ContextParts.cloneName(this.#snapshot.name),
|
|
321
601
|
storageContext: ContextParts.createStorageMode(this.#snapshot.spec),
|
|
322
602
|
storageFactory: this.#storageFactory,
|
|
603
|
+
onAdmit: (tenantId) => this.#admitTenant(tenantId),
|
|
323
604
|
...(this.#ai === undefined ? {} : { ai: this.#ai }),
|
|
324
605
|
stand: this.#stand,
|
|
325
606
|
entityInbox: this.#entityInbox,
|
|
@@ -331,23 +612,38 @@ export class BoundedContext {
|
|
|
331
612
|
registerSystemEventSchema: (schema) => {
|
|
332
613
|
eventBusAccess.registerSchemas(this.#systemEventBus, [schema]);
|
|
333
614
|
},
|
|
334
|
-
publishAgentEvent: (event) => event
|
|
335
|
-
? this.#publisher.publishEvent(event)
|
|
336
|
-
: this.#publisher.publishRejectionEvent(event),
|
|
337
|
-
prepareSavedCommand: (command) => commandBusAccess.prepareSaved(this.#commandBus, command),
|
|
338
|
-
publishSavedCommand: (command, plan) => commandBusAccess.postSavedFollowUp(this.#commandBus, command, plan),
|
|
615
|
+
publishAgentEvent: (event) => this.#publishAgentEvent(event),
|
|
339
616
|
publishAgentSystemEvent: (event) => this.#publisher.publishSystemEvent(event),
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
this.#
|
|
617
|
+
agentReceipts: this.#agentReceipts,
|
|
618
|
+
agentSignal: this.#agentController.signal,
|
|
619
|
+
reserveAgent: (key, signal) => this.#reserveAgent(key, signal),
|
|
620
|
+
deferAgent: (message, tenantId) => {
|
|
621
|
+
this.#deferAgent(message, tenantId);
|
|
345
622
|
},
|
|
346
623
|
};
|
|
347
|
-
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Publishes an Agent event on its selected ordinary or rejection bus.
|
|
627
|
+
*/
|
|
628
|
+
#publishAgentEvent(event) {
|
|
629
|
+
return event.context?.rejection === undefined
|
|
630
|
+
? this.#publisher.publishEvent(event)
|
|
631
|
+
: this.#publisher.publishRejectionEvent(event);
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Records a deferred Agent shard for the next available capacity slot.
|
|
635
|
+
*/
|
|
636
|
+
#deferAgent(message, tenantId) {
|
|
637
|
+
this.#deliveryReadiness.defer({
|
|
638
|
+
label: message.label,
|
|
639
|
+
targetTypeUrl: message.inboxId.targetTypeUrl,
|
|
640
|
+
shard: message.shard,
|
|
641
|
+
...(tenantId === undefined ? {} : { tenantId }),
|
|
642
|
+
});
|
|
348
643
|
}
|
|
349
644
|
#failRegistration(error, preparedRepositories) {
|
|
350
|
-
const
|
|
645
|
+
const uncommitted = preparedRepositories.filter(({ repository }) => !this.#repositoryViews.has(repository));
|
|
646
|
+
const closeErrors = ContextParts.closePreparedRepositories(uncommitted);
|
|
351
647
|
if (closeErrors.length > 0) {
|
|
352
648
|
throw new AggregateError([error, ...closeErrors], "Repository registration failed, and prepared repository storage cleanup also failed.");
|
|
353
649
|
}
|
|
@@ -395,6 +691,14 @@ export class BoundedContext {
|
|
|
395
691
|
get isMultitenant() {
|
|
396
692
|
return this.#snapshot.tenantMode === "multitenant";
|
|
397
693
|
}
|
|
694
|
+
/**
|
|
695
|
+
* Returns the live index configured for this Bounded Context.
|
|
696
|
+
*
|
|
697
|
+
* @returns The effective configured index.
|
|
698
|
+
*/
|
|
699
|
+
get tenantIndex() {
|
|
700
|
+
return ContextParts.requireTenantIndex(this);
|
|
701
|
+
}
|
|
398
702
|
/**
|
|
399
703
|
* Returns a copy-safe specification used to build this context.
|
|
400
704
|
*
|
|
@@ -539,7 +843,9 @@ export class BoundedContext {
|
|
|
539
843
|
*/
|
|
540
844
|
#beginClose() {
|
|
541
845
|
closingContexts.add(this);
|
|
542
|
-
this.#
|
|
846
|
+
this.#preparationController.abort(new Error("Bounded Context closed."));
|
|
847
|
+
serverEnvironmentAccess.stopRecipients(this.#environment, this);
|
|
848
|
+
this.#agentController.abort(new Error("Bounded Context closed."));
|
|
543
849
|
this.#publisher.beginClose();
|
|
544
850
|
commandBusAccess.beginClose(this.#commandBus);
|
|
545
851
|
eventBusAccess.beginClose(this.#eventBus);
|
|
@@ -565,22 +871,35 @@ export class BoundedContext {
|
|
|
565
871
|
*/
|
|
566
872
|
async #closeOnce() {
|
|
567
873
|
const errors = [];
|
|
568
|
-
|
|
874
|
+
this.#stopAgentAvailability?.();
|
|
875
|
+
this.#stopAgentAvailability = undefined;
|
|
569
876
|
await this.#closePart(() => this.#drainWork(), errors);
|
|
877
|
+
await this.#drainAdmittedContextWork(errors);
|
|
878
|
+
await this.#closePart(() => this.#deliveryReadiness.close(), errors);
|
|
879
|
+
await this.#closePart(() => ContextParts.drainContextWork(this.#commandBus, this.#eventBus, this.#systemEventBus, this.#publisher), errors);
|
|
880
|
+
await this.#closePart(() => this.#agentReceipts.close(), errors);
|
|
570
881
|
await this.#closeBuses(errors);
|
|
571
882
|
await this.#closePart(() => this.#stand.close(), errors);
|
|
572
883
|
await this.#closePart(() => this.#systemStand.close(), errors);
|
|
573
884
|
await this.#closePart(() => this.#subscriptionRuntime.finishClose(), errors);
|
|
574
|
-
await this.#closePart(() => {
|
|
575
|
-
ContextParts.requireTenantIndex(this).close();
|
|
576
|
-
}, errors);
|
|
577
885
|
await this.#closeRepositories(errors);
|
|
886
|
+
serverEnvironmentAccess.removeContext(this.#environment, this);
|
|
578
887
|
if (errors.length > 0) {
|
|
579
888
|
this.#closed = undefined;
|
|
580
889
|
throw new AggregateError(ContextParts.flattenErrors(errors), "BoundedContext close failed.");
|
|
581
890
|
}
|
|
582
891
|
ContextParts.clearContextMetadata(this);
|
|
583
892
|
}
|
|
893
|
+
/**
|
|
894
|
+
* Settles entered replay, local follow-ups, and reserved Agent turns.
|
|
895
|
+
* @param errors Collects independent close failures.
|
|
896
|
+
*/
|
|
897
|
+
async #drainAdmittedContextWork(errors) {
|
|
898
|
+
await this.#closePart(() => Promise.allSettled([...this.#activePreparations]).then(() => undefined), errors);
|
|
899
|
+
await this.#closePart(() => Promise.allSettled([...this.#activeReplays]).then(() => undefined), errors);
|
|
900
|
+
await this.#closePart(() => this.#entityInbox.whenFollowUpsIdle(), errors);
|
|
901
|
+
await this.#closePart(() => Promise.allSettled([...this.#agentReservations]).then(() => undefined), errors);
|
|
902
|
+
}
|
|
584
903
|
/**
|
|
585
904
|
* Closes bus intake and subscription drains before the Stands.
|
|
586
905
|
*
|
|
@@ -602,13 +921,27 @@ export class BoundedContext {
|
|
|
602
921
|
* @param errors Failures collected during context closure.
|
|
603
922
|
*/
|
|
604
923
|
async #closeRepositories(errors) {
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
repositoryAccess.clearRuntime(repository);
|
|
924
|
+
const closing = [...this.#repositoryViews].map((repository) => {
|
|
925
|
+
return repositoryAccess.clearRuntime(repository).finally(() => {
|
|
608
926
|
registeredRepositories.delete(repository);
|
|
609
|
-
}
|
|
927
|
+
});
|
|
928
|
+
});
|
|
929
|
+
for (const result of await Promise.allSettled(closing)) {
|
|
930
|
+
if (result.status === "rejected")
|
|
931
|
+
errors.push(result.reason);
|
|
610
932
|
}
|
|
611
933
|
}
|
|
934
|
+
/**
|
|
935
|
+
* Registers an entered shared replay through its complete follow-up callback.
|
|
936
|
+
* @param onReplay Complete replay callback.
|
|
937
|
+
* @returns Completion of the captured callback.
|
|
938
|
+
*/
|
|
939
|
+
#trackReplay(onReplay) {
|
|
940
|
+
const replay = Promise.resolve().then(onReplay);
|
|
941
|
+
this.#activeReplays.add(replay);
|
|
942
|
+
void replay.then(() => this.#activeReplays.delete(replay), () => this.#activeReplays.delete(replay));
|
|
943
|
+
return replay;
|
|
944
|
+
}
|
|
612
945
|
/**
|
|
613
946
|
* Calls a close action and collects its failure before later cleanup.
|
|
614
947
|
* @param work Close action.
|
|
@@ -622,6 +955,70 @@ export class BoundedContext {
|
|
|
622
955
|
* Exposes framework-only operations for built contexts and their builders.
|
|
623
956
|
*/
|
|
624
957
|
export const boundedContextAccess = Object.freeze({
|
|
958
|
+
/**
|
|
959
|
+
* Reports whether close has started for the given context.
|
|
960
|
+
*
|
|
961
|
+
* @param context Completed context.
|
|
962
|
+
* @returns Whether close has started.
|
|
963
|
+
*/
|
|
964
|
+
isClosing: (context) => closingContexts.has(context),
|
|
965
|
+
/**
|
|
966
|
+
* Prepares an admitted context without joining global fatal cleanup.
|
|
967
|
+
*
|
|
968
|
+
* @param context Admitted context.
|
|
969
|
+
* @param mode Creation or read-only check.
|
|
970
|
+
* @param ids Captured tenant identities.
|
|
971
|
+
* @returns Completion of local preparation.
|
|
972
|
+
*/
|
|
973
|
+
prepareExplicit(context, mode, ids) {
|
|
974
|
+
const prepare = explicitPreparations.get(context);
|
|
975
|
+
if (prepare === undefined)
|
|
976
|
+
return Promise.reject(new TypeError("Preparation requires a built context."));
|
|
977
|
+
return prepare(mode, ids);
|
|
978
|
+
},
|
|
979
|
+
/**
|
|
980
|
+
* Admits a service membership and preparation chain before its first await.
|
|
981
|
+
*
|
|
982
|
+
* @param context Selected built context.
|
|
983
|
+
* @param tenantId Captured tenant identity, when applicable.
|
|
984
|
+
* @param onMembership Checks live tenant membership.
|
|
985
|
+
* @param onReady Performs the admitted one-shot registry mutation, if present.
|
|
986
|
+
* @returns Tracked completion of membership and model preparation.
|
|
987
|
+
*/
|
|
988
|
+
admitServiceInput(context, tenantId, onMembership, onReady) {
|
|
989
|
+
if (closingContexts.has(context))
|
|
990
|
+
return Promise.reject(new Error("Bounded Context is closing."));
|
|
991
|
+
const prepare = contextInputPreparations.get(context);
|
|
992
|
+
const phase = contextClosePhases.get(context);
|
|
993
|
+
if (prepare === undefined || phase === undefined)
|
|
994
|
+
return Promise.reject(new TypeError("Service input requires a built context."));
|
|
995
|
+
return phase.trackReplay(async () => {
|
|
996
|
+
await onMembership();
|
|
997
|
+
await prepare(tenantId);
|
|
998
|
+
await onReady?.();
|
|
999
|
+
});
|
|
1000
|
+
},
|
|
1001
|
+
/**
|
|
1002
|
+
* Admits one shared subscription-registry operation before asynchronous lookup.
|
|
1003
|
+
*
|
|
1004
|
+
* @param context Selected built context.
|
|
1005
|
+
* @param onOperation Complete registry operation.
|
|
1006
|
+
* @returns Tracked operation completion.
|
|
1007
|
+
*/
|
|
1008
|
+
admitServiceRegistryOperation(context, onOperation) {
|
|
1009
|
+
if (closingContexts.has(context))
|
|
1010
|
+
return Promise.reject(new Error("Bounded Context is closing."));
|
|
1011
|
+
const phase = contextClosePhases.get(context);
|
|
1012
|
+
if (phase === undefined)
|
|
1013
|
+
return Promise.reject(new TypeError("Service registry requires a built context."));
|
|
1014
|
+
return phase.trackReplay(onOperation);
|
|
1015
|
+
},
|
|
1016
|
+
trackReplay(context, onReplay) {
|
|
1017
|
+
const phase = contextClosePhases.get(context);
|
|
1018
|
+
if (phase === undefined)
|
|
1019
|
+
return Promise.reject(new TypeError("Replay requires a built context."));
|
|
1020
|
+
return phase.trackReplay(onReplay);
|
|
1021
|
+
},
|
|
625
1022
|
/**
|
|
626
1023
|
* Resolves a view only within the Bounded Context that issued it.
|
|
627
1024
|
*
|
|
@@ -669,7 +1066,7 @@ export const boundedContextAccess = Object.freeze({
|
|
|
669
1066
|
if (runtime === undefined) {
|
|
670
1067
|
throw new TypeError("Context logger requires a built BoundedContext instance.");
|
|
671
1068
|
}
|
|
672
|
-
subscriptionRuntimeAccess.installLogger(runtime, logger);
|
|
1069
|
+
subscriptionRuntimeAccess.installLogger(runtime, logger, context.name.value);
|
|
673
1070
|
},
|
|
674
1071
|
loggerFor(context) {
|
|
675
1072
|
const logger = contextLoggers.get(context);
|
|
@@ -683,12 +1080,12 @@ export const boundedContextAccess = Object.freeze({
|
|
|
683
1080
|
value !== null &&
|
|
684
1081
|
builderBuilds.has(value));
|
|
685
1082
|
},
|
|
686
|
-
build(builder
|
|
1083
|
+
build(builder) {
|
|
687
1084
|
const build = builderBuilds.get(builder);
|
|
688
1085
|
if (build === undefined) {
|
|
689
1086
|
throw new TypeError("Builder access requires a BoundedContextBuilder instance.");
|
|
690
1087
|
}
|
|
691
|
-
return build(
|
|
1088
|
+
return build();
|
|
692
1089
|
},
|
|
693
1090
|
subscribeToEvent(context, typeUrl, subscriber) {
|
|
694
1091
|
const subscribe = eventSubscribers.get(context);
|
|
@@ -765,9 +1162,8 @@ export class BoundedContextBuilder {
|
|
|
765
1162
|
#repositories = new Set();
|
|
766
1163
|
#entityTypes = new Set();
|
|
767
1164
|
#generatedRepositoryOptions = new Map();
|
|
768
|
-
#deliveryStrategy = UniformAcrossAllShards.singleShard();
|
|
769
|
-
#storageFactory;
|
|
770
1165
|
#aiRegistry;
|
|
1166
|
+
#tenantIndex;
|
|
771
1167
|
#subscriptionRegistry;
|
|
772
1168
|
#persistSystemEvents = false;
|
|
773
1169
|
#generatedRegistryRoot;
|
|
@@ -786,7 +1182,7 @@ export class BoundedContextBuilder {
|
|
|
786
1182
|
constructor(specSnapshot, token) {
|
|
787
1183
|
ContextParts.requireFrameworkConstructionToken(token, "BoundedContextBuilder instances are framework-owned.");
|
|
788
1184
|
this.#specSnapshot = ContextParts.cloneSpecSnapshot(specSnapshot);
|
|
789
|
-
builderBuilds.set(this, (
|
|
1185
|
+
builderBuilds.set(this, () => this.#buildAsyncWith());
|
|
790
1186
|
Object.freeze(this);
|
|
791
1187
|
}
|
|
792
1188
|
/**
|
|
@@ -861,7 +1257,7 @@ export class BoundedContextBuilder {
|
|
|
861
1257
|
*
|
|
862
1258
|
* @param dispatcher Raw command dispatcher, or an `AbstractCommander` instance.
|
|
863
1259
|
* A standalone commander requires generated receiver metadata and therefore
|
|
864
|
-
* this builder's `
|
|
1260
|
+
* this builder's `build()` path. It installs its Command and Event sides
|
|
865
1261
|
* exactly once.
|
|
866
1262
|
*
|
|
867
1263
|
* @returns This builder for further configuration.
|
|
@@ -888,7 +1284,7 @@ export class BoundedContextBuilder {
|
|
|
888
1284
|
* Adds an event dispatcher to the context being built.
|
|
889
1285
|
*
|
|
890
1286
|
* @param dispatcher Raw event dispatcher, or a standalone reactor/subscriber.
|
|
891
|
-
* Standalone receivers require generated receiver metadata and `
|
|
1287
|
+
* Standalone receivers require generated receiver metadata and `build()`.
|
|
892
1288
|
*
|
|
893
1289
|
* @returns This builder for further configuration.
|
|
894
1290
|
*/
|
|
@@ -905,7 +1301,7 @@ export class BoundedContextBuilder {
|
|
|
905
1301
|
* Adds a generated standalone command assignee.
|
|
906
1302
|
*
|
|
907
1303
|
* The registered instance is matched by exact constructor to generated
|
|
908
|
-
* receiver metadata when `
|
|
1304
|
+
* receiver metadata when `build()` assembles the context.
|
|
909
1305
|
*
|
|
910
1306
|
* @param assignee Generated standalone assignee instance.
|
|
911
1307
|
* @returns This builder for further configuration.
|
|
@@ -925,13 +1321,13 @@ export class BoundedContextBuilder {
|
|
|
925
1321
|
return this;
|
|
926
1322
|
}
|
|
927
1323
|
/**
|
|
928
|
-
* Sets a
|
|
1324
|
+
* Sets a live tenant index for this Bounded Context.
|
|
929
1325
|
*
|
|
930
|
-
* @param
|
|
931
|
-
* @returns
|
|
1326
|
+
* @param tenantIndex Application membership index.
|
|
1327
|
+
* @returns This builder.
|
|
932
1328
|
*/
|
|
933
|
-
|
|
934
|
-
this.#
|
|
1329
|
+
withTenantIndex(tenantIndex) {
|
|
1330
|
+
this.#tenantIndex = tenantIndex;
|
|
935
1331
|
return this;
|
|
936
1332
|
}
|
|
937
1333
|
/**
|
|
@@ -969,16 +1365,6 @@ export class BoundedContextBuilder {
|
|
|
969
1365
|
this.#subscriptionRegistry = registry;
|
|
970
1366
|
return this;
|
|
971
1367
|
}
|
|
972
|
-
/**
|
|
973
|
-
* Sets how Entity Inbox targets are assigned to shards.
|
|
974
|
-
*
|
|
975
|
-
* @param strategy Selects the durable shard for Aggregate and Process Manager targets.
|
|
976
|
-
* @returns Returns this builder for further configuration.
|
|
977
|
-
*/
|
|
978
|
-
withDeliveryStrategy(strategy) {
|
|
979
|
-
this.#deliveryStrategy = ContextParts.snapshotDeliveryStrategy(strategy);
|
|
980
|
-
return this;
|
|
981
|
-
}
|
|
982
1368
|
/**
|
|
983
1369
|
* Sets a trusted compiled application root for generated handler metadata.
|
|
984
1370
|
*
|
|
@@ -990,49 +1376,108 @@ export class BoundedContextBuilder {
|
|
|
990
1376
|
return this;
|
|
991
1377
|
}
|
|
992
1378
|
/**
|
|
993
|
-
* Builds a context
|
|
1379
|
+
* Builds a context after loading generated handlers and preparing runtime resources.
|
|
994
1380
|
*
|
|
995
|
-
* @
|
|
1381
|
+
* @param options Independent eager structure and subscription timing choices.
|
|
1382
|
+
* @returns The built context after requested initialization and integration are ready.
|
|
996
1383
|
*/
|
|
997
|
-
build() {
|
|
998
|
-
|
|
999
|
-
|
|
1384
|
+
build(options = {}) {
|
|
1385
|
+
return this.#buildAsyncWith(options);
|
|
1386
|
+
}
|
|
1387
|
+
async #buildAsyncWith(options = {}) {
|
|
1388
|
+
const environment = ServerEnvironment.instance();
|
|
1389
|
+
const finishBuild = serverEnvironmentAccess.beginContextBuild(environment);
|
|
1390
|
+
let built;
|
|
1391
|
+
let failed = false;
|
|
1392
|
+
let failure;
|
|
1393
|
+
try {
|
|
1394
|
+
await serverEnvironmentAccess.awaitSetup(environment);
|
|
1395
|
+
built = await this.#buildAsyncReady(options, environment);
|
|
1396
|
+
}
|
|
1397
|
+
catch (error) {
|
|
1398
|
+
failed = true;
|
|
1399
|
+
failure = error;
|
|
1400
|
+
}
|
|
1401
|
+
finally {
|
|
1402
|
+
finishBuild();
|
|
1000
1403
|
}
|
|
1001
|
-
|
|
1002
|
-
|
|
1404
|
+
if (serverEnvironmentAccess.hasStartupFailure(environment))
|
|
1405
|
+
return serverEnvironmentAccess.awaitStartupFailure(environment);
|
|
1406
|
+
if (failed)
|
|
1407
|
+
throw failure;
|
|
1408
|
+
if (built === undefined)
|
|
1409
|
+
throw new Error("Bounded Context build did not complete.");
|
|
1410
|
+
return built;
|
|
1003
1411
|
}
|
|
1004
1412
|
/**
|
|
1005
|
-
*
|
|
1006
|
-
* and registered standalone handlers.
|
|
1413
|
+
* Loads generated metadata and finishes one asynchronous context assembly.
|
|
1007
1414
|
*
|
|
1008
|
-
*
|
|
1009
|
-
* asynchronous path so their exact constructors can be matched to generated
|
|
1010
|
-
* receiver metadata.
|
|
1011
|
-
*
|
|
1012
|
-
* @returns Resolves to the built context.
|
|
1415
|
+
* @returns The integrated context.
|
|
1013
1416
|
*/
|
|
1014
|
-
async
|
|
1015
|
-
return this.#buildAsyncWith();
|
|
1016
|
-
}
|
|
1017
|
-
async #buildAsyncWith(defaultStorageFactory, defaultAi) {
|
|
1417
|
+
async #buildAsyncReady(options, environment) {
|
|
1018
1418
|
const generated = await this.#loadGeneratedArtifacts([...this.#entityTypes]);
|
|
1419
|
+
serverEnvironmentAccess.requireAvailable(environment);
|
|
1019
1420
|
const repositories = [...this.#repositories, ...generated.repositories];
|
|
1020
|
-
const context = this.#
|
|
1421
|
+
const context = await this.#buildWithResources(repositories, environment.storageFactory, generated.standalone, this.#aiRegistry ?? environment.ai);
|
|
1021
1422
|
try {
|
|
1022
|
-
await
|
|
1423
|
+
await this.#integrateBuiltContext(context, options, environment);
|
|
1023
1424
|
}
|
|
1024
1425
|
catch (error) {
|
|
1025
|
-
|
|
1026
|
-
await context.close();
|
|
1027
|
-
}
|
|
1028
|
-
catch (closeError) {
|
|
1029
|
-
throw new AggregateError([error, closeError], "BoundedContext broker open failed during cleanup.");
|
|
1030
|
-
}
|
|
1031
|
-
throw error;
|
|
1426
|
+
return this.#closeFailedBuild(context, environment, error);
|
|
1032
1427
|
}
|
|
1033
1428
|
return context;
|
|
1034
1429
|
}
|
|
1035
|
-
|
|
1430
|
+
/**
|
|
1431
|
+
* Completes required integration and requested initialization before publication.
|
|
1432
|
+
* @param context Constructed context.
|
|
1433
|
+
* @param options Requested eager initialization phases.
|
|
1434
|
+
* @param environment Captured environment lifetime.
|
|
1435
|
+
*/
|
|
1436
|
+
async #integrateBuiltContext(context, options, environment) {
|
|
1437
|
+
serverEnvironmentAccess.requireAvailable(environment);
|
|
1438
|
+
boundedContextAccess.installLogger(context, serverEnvironmentAccess.loggerFor(environment));
|
|
1439
|
+
await ContextParts.integrationReady(context).catch((error) => {
|
|
1440
|
+
serverEnvironmentAccess.failStartup(environment, error);
|
|
1441
|
+
throw error;
|
|
1442
|
+
});
|
|
1443
|
+
serverEnvironmentAccess.requireAvailable(environment);
|
|
1444
|
+
const prepare = contextPreparations.get(context);
|
|
1445
|
+
if (prepare === undefined)
|
|
1446
|
+
throw new Error("Context preparation is unavailable.");
|
|
1447
|
+
await prepare(options);
|
|
1448
|
+
serverEnvironmentAccess.requireAvailable(environment);
|
|
1449
|
+
await serverEnvironmentAccess.completeContext(environment, context);
|
|
1450
|
+
}
|
|
1451
|
+
/**
|
|
1452
|
+
* Releases a failed partial context before its build ticket settles.
|
|
1453
|
+
* @param context Context assembled before the failure.
|
|
1454
|
+
* @param environment Captured environment lifetime.
|
|
1455
|
+
* @param error Original build failure.
|
|
1456
|
+
* @returns Rejection retaining the original and any local cleanup failure.
|
|
1457
|
+
*/
|
|
1458
|
+
async #closeFailedBuild(context, environment, error) {
|
|
1459
|
+
try {
|
|
1460
|
+
await context.close();
|
|
1461
|
+
}
|
|
1462
|
+
catch (closeError) {
|
|
1463
|
+
if (serverEnvironmentAccess.hasStartupFailure(environment)) {
|
|
1464
|
+
serverEnvironmentAccess.recordStartupCleanup(environment, closeError);
|
|
1465
|
+
throw error;
|
|
1466
|
+
}
|
|
1467
|
+
throw new AggregateError([error, closeError], "BoundedContext open cleanup failed.");
|
|
1468
|
+
}
|
|
1469
|
+
throw error;
|
|
1470
|
+
}
|
|
1471
|
+
/**
|
|
1472
|
+
* Assembles a context and cleans partial resources on failure.
|
|
1473
|
+
*
|
|
1474
|
+
* @param repositories Repositories selected for the context.
|
|
1475
|
+
* @param storageFactory Storage factory for the context.
|
|
1476
|
+
* @param standalone Generated standalone handler groups.
|
|
1477
|
+
* @param ai Optional AI registry.
|
|
1478
|
+
* @returns The assembled context.
|
|
1479
|
+
*/
|
|
1480
|
+
async #buildWithResources(repositories, storageFactory, standalone, ai) {
|
|
1036
1481
|
const resources = this.#subscriptionRegistry === undefined ? {} : { registry: this.#subscriptionRegistry };
|
|
1037
1482
|
this.#subscriptionRegistry = undefined;
|
|
1038
1483
|
const registeredRepositories = [...repositories];
|
|
@@ -1040,7 +1485,7 @@ export class BoundedContextBuilder {
|
|
|
1040
1485
|
return this.#assembleContext(registeredRepositories, storageFactory, standalone, resources, ai);
|
|
1041
1486
|
}
|
|
1042
1487
|
catch (error) {
|
|
1043
|
-
return this.#failBuild(resources, error);
|
|
1488
|
+
return await this.#failBuild(resources, error);
|
|
1044
1489
|
}
|
|
1045
1490
|
}
|
|
1046
1491
|
#assembleContext(repositories, storageFactory, standalone, resources, ai) {
|
|
@@ -1059,6 +1504,10 @@ export class BoundedContextBuilder {
|
|
|
1059
1504
|
]);
|
|
1060
1505
|
const running = this.#buildRuntime(storageFactory, buses, resources);
|
|
1061
1506
|
const context = this.#createContext(repositories, storageFactory, buses, running, ai);
|
|
1507
|
+
resources.context = context;
|
|
1508
|
+
if (registerBoundedContext === undefined)
|
|
1509
|
+
throw new Error("Context registration is unavailable.");
|
|
1510
|
+
registerBoundedContext(context, repositories);
|
|
1062
1511
|
ContextParts.attachIntegration(context, buses.eventBus, buses.systemSpec, ContextParts.externalEventSchemas([
|
|
1063
1512
|
...dispatchers.domain,
|
|
1064
1513
|
...(standaloneEvent === undefined ? [] : [standaloneEvent]),
|
|
@@ -1083,31 +1532,14 @@ export class BoundedContextBuilder {
|
|
|
1083
1532
|
throw new TypeError("Agent registration requires persisted System Events.");
|
|
1084
1533
|
if (!AgentHistoryStorageFactories.supports(storageFactory))
|
|
1085
1534
|
throw new TypeError("Agent registration requires indexed history storage.");
|
|
1086
|
-
if (!AgentExecutionStorageFactories.supports(storageFactory))
|
|
1087
|
-
throw new TypeError("Agent registration requires durable execution storage.");
|
|
1088
|
-
this.#checkAgentOutputBindings(agents);
|
|
1089
1535
|
for (const repository of agents) {
|
|
1090
1536
|
const config = repositoryAccess.agentConfiguration(repository);
|
|
1091
|
-
if (config.codeRevision === undefined || config.codeRevision.trim() === "")
|
|
1092
|
-
throw new TypeError("Agent registration requires a code revision.");
|
|
1093
1537
|
if (config.ai === undefined)
|
|
1094
1538
|
throw new TypeError("Agent registration requires repository capabilities.");
|
|
1095
1539
|
if (config.ai.models.some((model) => !isAiModel(model)))
|
|
1096
1540
|
throw new TypeError("Agent repository capabilities must be factory-created models.");
|
|
1097
1541
|
}
|
|
1098
1542
|
}
|
|
1099
|
-
/**
|
|
1100
|
-
* Rejects Agent outcomes whose raw matching dispatcher cannot bind a saved plan.
|
|
1101
|
-
*/
|
|
1102
|
-
#checkAgentOutputBindings(agents) {
|
|
1103
|
-
const rawCommands = new Set([...this.#commandDispatchers].flatMap((dispatcher) => dispatcher.messageSchemas().map((schema) => TypeUrls.derive(schema))));
|
|
1104
|
-
for (const repository of agents) {
|
|
1105
|
-
if (repositoryAccess
|
|
1106
|
-
.producedCommandSchemas(repository)
|
|
1107
|
-
.some((schema) => rawCommands.has(TypeUrls.derive(schema))))
|
|
1108
|
-
throw new TypeError("Agent Command output requires a durable dispatcher binding.");
|
|
1109
|
-
}
|
|
1110
|
-
}
|
|
1111
1543
|
#buildDispatchers(repositories) {
|
|
1112
1544
|
const event = [
|
|
1113
1545
|
...ContextParts.repositoryEventDispatchers(repositories),
|
|
@@ -1148,7 +1580,7 @@ export class BoundedContextBuilder {
|
|
|
1148
1580
|
#buildSystemBuses(storageFactory, dispatchers, resources) {
|
|
1149
1581
|
const systemSpec = ContextParts.createSystemSpec(this.#specSnapshot, this.#persistSystemEvents);
|
|
1150
1582
|
const systemEventStore = systemSpec.storesEvents
|
|
1151
|
-
? new EventStore(ContextParts.createStorageMode(systemSpec), storageFactory,
|
|
1583
|
+
? new EventStore(ContextParts.createStorageMode(systemSpec), storageFactory, EventStoreGroups.forContext(systemSpec.name.value, "system"))
|
|
1152
1584
|
: undefined;
|
|
1153
1585
|
if (systemEventStore !== undefined)
|
|
1154
1586
|
resources.systemEventStore = systemEventStore;
|
|
@@ -1185,10 +1617,7 @@ export class BoundedContextBuilder {
|
|
|
1185
1617
|
return event;
|
|
1186
1618
|
}
|
|
1187
1619
|
#buildRuntime(storageFactory, buses, resources) {
|
|
1188
|
-
const stand =
|
|
1189
|
-
context: ContextParts.createStorageMode(this.#specSnapshot),
|
|
1190
|
-
storageFactory,
|
|
1191
|
-
});
|
|
1620
|
+
const stand = this.#buildStand(storageFactory);
|
|
1192
1621
|
resources.stand = stand;
|
|
1193
1622
|
const registry = resources.registry ??
|
|
1194
1623
|
new StorageSubscriptionRegistry(ContextParts.createSubscriptionStorageContext(this.#specSnapshot), storageFactory);
|
|
@@ -1197,19 +1626,50 @@ export class BoundedContextBuilder {
|
|
|
1197
1626
|
resources.runtime = runtime;
|
|
1198
1627
|
return { stand, registry, runtime };
|
|
1199
1628
|
}
|
|
1629
|
+
/**
|
|
1630
|
+
* Builds the context Stand with a captured live membership directory.
|
|
1631
|
+
*
|
|
1632
|
+
* @param storageFactory Provider used for Entity records.
|
|
1633
|
+
* @returns Direct read-side Stand.
|
|
1634
|
+
*/
|
|
1635
|
+
#buildStand(storageFactory) {
|
|
1636
|
+
const tenantIndex = this.#tenantIndex ?? ServerEnvironment.instance().tenantIndex;
|
|
1637
|
+
const stand = new Stand({
|
|
1638
|
+
context: ContextParts.createStorageMode(this.#specSnapshot),
|
|
1639
|
+
storageFactory,
|
|
1640
|
+
});
|
|
1641
|
+
standAccess.setAdmission(stand, (tenantId) => TenantAdmissions.require(tenantIndex, this.#specSnapshot.name.value, this.#specSnapshot.multitenant, tenantId));
|
|
1642
|
+
return stand;
|
|
1643
|
+
}
|
|
1200
1644
|
#createContext(repositories, storageFactory, buses, running, ai) {
|
|
1201
|
-
return ContextParts.createBoundedContext(this.#specSnapshot, storageFactory, repositories,
|
|
1645
|
+
return ContextParts.createBoundedContext(this.#specSnapshot, storageFactory, repositories, ServerEnvironment.instance().delivery.strategy, buses, running, ai, this.#tenantIndex ?? ServerEnvironment.instance().tenantIndex);
|
|
1202
1646
|
}
|
|
1203
|
-
#failBuild(resources, error) {
|
|
1647
|
+
async #failBuild(resources, error) {
|
|
1204
1648
|
const cleanupErrors = [];
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1649
|
+
if (resources.context !== undefined) {
|
|
1650
|
+
try {
|
|
1651
|
+
await resources.context.close();
|
|
1652
|
+
}
|
|
1653
|
+
catch (closeError) {
|
|
1654
|
+
cleanupErrors.push(closeError);
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
else {
|
|
1658
|
+
ContextParts.attemptCleanup(() => resources.publisher?.abortAssembly(), cleanupErrors);
|
|
1659
|
+
const registry = resources.runtime ?? resources.registry;
|
|
1660
|
+
const closing = [registry, resources.stand, resources.systemStand];
|
|
1661
|
+
for (const resource of closing) {
|
|
1662
|
+
if (resource === undefined)
|
|
1663
|
+
continue;
|
|
1664
|
+
try {
|
|
1665
|
+
await resource.close();
|
|
1666
|
+
}
|
|
1667
|
+
catch (closeError) {
|
|
1668
|
+
cleanupErrors.push(closeError);
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
await ContextParts.cleanupBuildBuses(resources, cleanupErrors);
|
|
1672
|
+
}
|
|
1213
1673
|
if (cleanupErrors.length > 0) {
|
|
1214
1674
|
throw new AggregateError([error, ...cleanupErrors], "Bounded Context build failed during cleanup.");
|
|
1215
1675
|
}
|
|
@@ -1409,24 +1869,29 @@ const ContextParts = Object.freeze({
|
|
|
1409
1869
|
*
|
|
1410
1870
|
* @param resources Partially created resources.
|
|
1411
1871
|
* @param errors Collection receiving failures.
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1872
|
+
* @returns Completion after all created buses and stores close.
|
|
1873
|
+
*/
|
|
1874
|
+
async cleanupBuildBuses(resources, errors) {
|
|
1875
|
+
const closing = [];
|
|
1876
|
+
const { commandBus, systemEventBus, systemEventStore, eventBus, eventStore } = resources;
|
|
1877
|
+
if (commandBus !== undefined)
|
|
1878
|
+
closing.push(() => commandBus.close());
|
|
1879
|
+
if (systemEventBus !== undefined)
|
|
1880
|
+
closing.push(() => systemEventBus.close());
|
|
1881
|
+
else if (systemEventStore !== undefined)
|
|
1882
|
+
closing.push(() => systemEventStore.close());
|
|
1883
|
+
if (eventBus !== undefined)
|
|
1884
|
+
closing.push(() => eventBus.close());
|
|
1885
|
+
else if (eventStore !== undefined)
|
|
1886
|
+
closing.push(() => eventStore.close());
|
|
1887
|
+
for (const close of closing) {
|
|
1888
|
+
try {
|
|
1889
|
+
await close();
|
|
1890
|
+
}
|
|
1891
|
+
catch (error) {
|
|
1892
|
+
errors.push(error);
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1430
1895
|
},
|
|
1431
1896
|
/**
|
|
1432
1897
|
* Returns constituent failures from nested aggregate errors.
|
|
@@ -1476,9 +1941,10 @@ const ContextParts = Object.freeze({
|
|
|
1476
1941
|
* @param buses Prepared domain and System buses.
|
|
1477
1942
|
* @param running Prepared Stand and subscription runtime.
|
|
1478
1943
|
* @param ai Effective AI registry when Agent repositories are present.
|
|
1944
|
+
* @param tenantIndex Effective live membership index.
|
|
1479
1945
|
* @returns Constructed bounded context.
|
|
1480
1946
|
*/
|
|
1481
|
-
createBoundedContext(specSnapshot, storageFactory, repositories, deliveryStrategy, buses, running, ai) {
|
|
1947
|
+
createBoundedContext(specSnapshot, storageFactory, repositories, deliveryStrategy, buses, running, ai, tenantIndex) {
|
|
1482
1948
|
return constructBoundedContext({
|
|
1483
1949
|
snapshot: ContextParts.snapshotFor(specSnapshot),
|
|
1484
1950
|
commandBus: buses.commandBus,
|
|
@@ -1490,6 +1956,7 @@ const ContextParts = Object.freeze({
|
|
|
1490
1956
|
subscriptionRuntime: running.runtime,
|
|
1491
1957
|
systemSpec: buses.systemSpec,
|
|
1492
1958
|
storageFactory,
|
|
1959
|
+
tenantIndex: tenantIndex ?? ServerEnvironment.instance().tenantIndex,
|
|
1493
1960
|
repositories,
|
|
1494
1961
|
deliveryStrategy,
|
|
1495
1962
|
...(ai === undefined ? {} : { ai }),
|
|
@@ -1533,35 +2000,6 @@ const ContextParts = Object.freeze({
|
|
|
1533
2000
|
multitenant: false,
|
|
1534
2001
|
});
|
|
1535
2002
|
},
|
|
1536
|
-
/**
|
|
1537
|
-
* Captures the shard count and validates later shard results.
|
|
1538
|
-
*
|
|
1539
|
-
* @param strategy Configured delivery strategy.
|
|
1540
|
-
* @returns Immutable delivery strategy snapshot.
|
|
1541
|
-
*/
|
|
1542
|
-
snapshotDeliveryStrategy(strategy) {
|
|
1543
|
-
if (!Number.isSafeInteger(strategy.shardCount) || strategy.shardCount <= 0) {
|
|
1544
|
-
throw new Error("Delivery strategy shard count must be a positive safe integer.");
|
|
1545
|
-
}
|
|
1546
|
-
const shardCount = strategy.shardCount;
|
|
1547
|
-
return Object.freeze({
|
|
1548
|
-
shardCount,
|
|
1549
|
-
/**
|
|
1550
|
-
* Returns a target shard after verifying the captured shard total.
|
|
1551
|
-
*
|
|
1552
|
-
* @param targetId Inbox target identifier.
|
|
1553
|
-
* @param targetType Inbox target type.
|
|
1554
|
-
* @returns Validated shard index.
|
|
1555
|
-
*/
|
|
1556
|
-
shardFor(targetId, targetType) {
|
|
1557
|
-
const shard = strategy.shardFor(InboxTargets.clone(targetId), targetType);
|
|
1558
|
-
if (shard.ofTotal !== shardCount) {
|
|
1559
|
-
throw new Error("Delivery strategy shard total must equal its resolved shard count.");
|
|
1560
|
-
}
|
|
1561
|
-
return new ShardIndex(shard.index, shard.ofTotal);
|
|
1562
|
-
},
|
|
1563
|
-
});
|
|
1564
|
-
},
|
|
1565
2003
|
/**
|
|
1566
2004
|
* Creates the storage context for read-side catch-up.
|
|
1567
2005
|
*
|
|
@@ -1740,8 +2178,7 @@ const ContextParts = Object.freeze({
|
|
|
1740
2178
|
},
|
|
1741
2179
|
});
|
|
1742
2180
|
const ready = broker.open();
|
|
1743
|
-
//
|
|
1744
|
-
// observable operation without letting Node report an unhandled rejection.
|
|
2181
|
+
// The builder awaits readiness; retain a rejection handler until it observes the result.
|
|
1745
2182
|
void ready.catch(() => undefined);
|
|
1746
2183
|
contextIntegrations.set(context, { broker, ready });
|
|
1747
2184
|
},
|
|
@@ -1878,21 +2315,9 @@ const ContextParts = Object.freeze({
|
|
|
1878
2315
|
*/
|
|
1879
2316
|
requireEntityClass(entityType, operation) {
|
|
1880
2317
|
if (typeof entityType !== "function") {
|
|
1881
|
-
throw new TypeError(`${operation} requires a Repository instance. Use an entity class only with
|
|
2318
|
+
throw new TypeError(`${operation} requires a Repository instance. Use an entity class only with build().`);
|
|
1882
2319
|
}
|
|
1883
2320
|
},
|
|
1884
|
-
/**
|
|
1885
|
-
* Rejects synchronous assembly when classes need generated metadata.
|
|
1886
|
-
*
|
|
1887
|
-
* @param entityTypes Entity classes to assemble.
|
|
1888
|
-
*/
|
|
1889
|
-
rejectSyncEntityAssembly(entityTypes) {
|
|
1890
|
-
if (entityTypes.size === 0) {
|
|
1891
|
-
return;
|
|
1892
|
-
}
|
|
1893
|
-
throw new Error("BoundedContextBuilder.build() cannot assemble entity classes from generated metadata. " +
|
|
1894
|
-
"Use buildAsync().");
|
|
1895
|
-
},
|
|
1896
2321
|
/**
|
|
1897
2322
|
* Validates that a root exists when generated metadata must be loaded.
|
|
1898
2323
|
*
|
|
@@ -1903,7 +2328,7 @@ const ContextParts = Object.freeze({
|
|
|
1903
2328
|
if (root !== undefined) {
|
|
1904
2329
|
return root;
|
|
1905
2330
|
}
|
|
1906
|
-
throw new Error("BoundedContextBuilder.
|
|
2331
|
+
throw new Error("BoundedContextBuilder.build() requires withGeneratedRegistryRoot(root) " +
|
|
1907
2332
|
"when assembling entity classes from generated metadata.");
|
|
1908
2333
|
},
|
|
1909
2334
|
/**
|
|
@@ -2295,16 +2720,6 @@ const ContextParts = Object.freeze({
|
|
|
2295
2720
|
}
|
|
2296
2721
|
return Object.freeze([...schemas.values()]);
|
|
2297
2722
|
},
|
|
2298
|
-
/**
|
|
2299
|
-
* Clears metadata for a failed context build and closes its tenant index.
|
|
2300
|
-
*
|
|
2301
|
-
* @param context Context whose build failed.
|
|
2302
|
-
* @param tenantIndex Tenant index to close.
|
|
2303
|
-
*/
|
|
2304
|
-
cleanupFailedContext(context, tenantIndex) {
|
|
2305
|
-
ContextParts.clearContextMetadata(context);
|
|
2306
|
-
tenantIndex.close();
|
|
2307
|
-
},
|
|
2308
2723
|
/**
|
|
2309
2724
|
* Removes runtime metadata associated with a context.
|
|
2310
2725
|
*
|
|
@@ -2327,68 +2742,25 @@ const ContextParts = Object.freeze({
|
|
|
2327
2742
|
contextStorageFactories.delete(context);
|
|
2328
2743
|
contextDeliveryDescriptors.delete(context);
|
|
2329
2744
|
contextSubscriptionRuntimes.delete(context);
|
|
2745
|
+
contextInputPreparations.delete(context);
|
|
2746
|
+
contextPreparations.delete(context);
|
|
2330
2747
|
contextIntegrations.delete(context);
|
|
2331
2748
|
eventSubscribers.delete(context);
|
|
2332
2749
|
systemEventPosters.delete(context);
|
|
2333
2750
|
},
|
|
2334
2751
|
/**
|
|
2335
|
-
* Creates delivery operations from the context inboxes
|
|
2752
|
+
* Creates delivery operations from the context inboxes.
|
|
2336
2753
|
*
|
|
2337
|
-
* @param context Built context snapshot.
|
|
2338
|
-
* @param storageFactory Context storage provider.
|
|
2339
|
-
* @param tenantIndex Tenant index for startup scopes.
|
|
2340
2754
|
* @param entityInbox Registered Entity inbox.
|
|
2341
2755
|
* @param projections Projection inbox.
|
|
2342
|
-
* @param readiness Delivery readiness coordinator.
|
|
2343
2756
|
* @returns Delivery descriptor.
|
|
2344
2757
|
*/
|
|
2345
|
-
createDeliveryDescriptor(
|
|
2758
|
+
createDeliveryDescriptor(entityInbox, projections) {
|
|
2346
2759
|
return Object.freeze({
|
|
2347
|
-
storageFactory,
|
|
2348
|
-
startupScopes: () => ContextParts.deliveryStartupScopes(tenantIndex),
|
|
2349
|
-
storageContext: (scope) => ContextParts.deliveryStorageContext(context, scope),
|
|
2350
2760
|
endpoints: () => Object.freeze([...entityInbox.endpoints(), ...projections.endpoints()]),
|
|
2351
2761
|
replay: (message, tenantId) => message.label === "UPDATE_SUBSCRIBER"
|
|
2352
2762
|
? projections.replay(message, tenantId)
|
|
2353
2763
|
: entityInbox.replay(message, tenantId),
|
|
2354
|
-
onReady: (onReady) => readiness.onReady(onReady),
|
|
2355
|
-
transition: (scopes, onReady, options) => readiness.transition(scopes, onReady, options),
|
|
2356
|
-
});
|
|
2357
|
-
},
|
|
2358
|
-
/**
|
|
2359
|
-
* Lists delivery startup scopes, one per tenant in multitenant contexts.
|
|
2360
|
-
*
|
|
2361
|
-
* @param tenantIndex Tenant index supplying configured scopes.
|
|
2362
|
-
* @returns Frozen startup scope list.
|
|
2363
|
-
*/
|
|
2364
|
-
async deliveryStartupScopes(tenantIndex) {
|
|
2365
|
-
if (tenantIndex.tenantMode === "single-tenant") {
|
|
2366
|
-
return Object.freeze([Object.freeze({})]);
|
|
2367
|
-
}
|
|
2368
|
-
return Object.freeze((await tenantIndex.all()).map((tenantId) => Object.freeze({ tenantId })));
|
|
2369
|
-
},
|
|
2370
|
-
/**
|
|
2371
|
-
* Creates tenant-validated storage context for a delivery scope.
|
|
2372
|
-
*
|
|
2373
|
-
* @param context Built context snapshot.
|
|
2374
|
-
* @param scope Selected startup scope.
|
|
2375
|
-
* @returns Storage context for the selected tenant.
|
|
2376
|
-
*/
|
|
2377
|
-
deliveryStorageContext(context, scope) {
|
|
2378
|
-
if (context.tenantMode === "single-tenant") {
|
|
2379
|
-
if (scope.tenantId !== undefined) {
|
|
2380
|
-
throw new Error(`Single-tenant context "${context.name.value}" does not accept tenantId.`);
|
|
2381
|
-
}
|
|
2382
|
-
return Object.freeze({ name: context.name.value, multitenant: false });
|
|
2383
|
-
}
|
|
2384
|
-
const tenantId = scope.tenantId;
|
|
2385
|
-
if (tenantId === undefined) {
|
|
2386
|
-
throw new Error(`Multitenant context "${context.name.value}" requires tenantId.`);
|
|
2387
|
-
}
|
|
2388
|
-
return Object.freeze({
|
|
2389
|
-
name: context.name.value,
|
|
2390
|
-
multitenant: true,
|
|
2391
|
-
tenantId: TenantBoundary.from(tenantId).tenantId,
|
|
2392
2764
|
});
|
|
2393
2765
|
},
|
|
2394
2766
|
/**
|
|
@@ -2488,7 +2860,7 @@ const ContextParts = Object.freeze({
|
|
|
2488
2860
|
registeredRepositories.set(repository, { name: registration.name });
|
|
2489
2861
|
},
|
|
2490
2862
|
close: () => {
|
|
2491
|
-
repositoryAccess.
|
|
2863
|
+
repositoryAccess.clearRuntimeIdle(repository);
|
|
2492
2864
|
},
|
|
2493
2865
|
};
|
|
2494
2866
|
},
|
|
@@ -2502,6 +2874,7 @@ const ContextParts = Object.freeze({
|
|
|
2502
2874
|
repositoryAccess.bindRuntime(repository, {
|
|
2503
2875
|
context: registration.storageContext,
|
|
2504
2876
|
storageFactory: registration.storageFactory,
|
|
2877
|
+
onAdmit: registration.onAdmit,
|
|
2505
2878
|
...(registration.ai === undefined ? {} : { ai: registration.ai }),
|
|
2506
2879
|
stand: registration.stand,
|
|
2507
2880
|
signalMetadata: new SignalMetadata(),
|
|
@@ -2511,11 +2884,11 @@ const ContextParts = Object.freeze({
|
|
|
2511
2884
|
registerEventSchema: registration.registerEventSchema,
|
|
2512
2885
|
registerSystemEventSchema: registration.registerSystemEventSchema,
|
|
2513
2886
|
publishAgentEvent: registration.publishAgentEvent,
|
|
2514
|
-
prepareSavedCommand: registration.prepareSavedCommand,
|
|
2515
|
-
publishSavedCommand: registration.publishSavedCommand,
|
|
2516
2887
|
publishAgentSystemEvent: registration.publishAgentSystemEvent,
|
|
2517
|
-
|
|
2518
|
-
|
|
2888
|
+
agentReceipts: registration.agentReceipts,
|
|
2889
|
+
agentSignal: registration.agentSignal,
|
|
2890
|
+
reserveAgent: registration.reserveAgent,
|
|
2891
|
+
deferAgent: registration.deferAgent,
|
|
2519
2892
|
});
|
|
2520
2893
|
},
|
|
2521
2894
|
/**
|
|
@@ -2637,7 +3010,7 @@ const ContextParts = Object.freeze({
|
|
|
2637
3010
|
});
|
|
2638
3011
|
}
|
|
2639
3012
|
finally {
|
|
2640
|
-
eventStore.close();
|
|
3013
|
+
await eventStore.close();
|
|
2641
3014
|
}
|
|
2642
3015
|
},
|
|
2643
3016
|
/**
|