@spine-event-engine/server 2.0.0-snapshot.26 → 2.0.0-snapshot.29
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 +61 -8
- package/REFERENCE.md +267 -45
- 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 +1 -1
- package/dist/agent/agent-live-read-runtime.js +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 +14 -7
- package/dist/browser/browser-server.d.ts.map +1 -1
- package/dist/browser/browser-server.js +462 -129
- package/dist/browser/browser-server.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 +48 -2
- package/dist/browser/options.d.ts.map +1 -1
- package/dist/bus/command-bus.d.ts +6 -1
- package/dist/bus/command-bus.d.ts.map +1 -1
- package/dist/bus/command-bus.js +6 -1
- package/dist/bus/command-bus.js.map +1 -1
- package/dist/bus/event-bus.d.ts +6 -1
- package/dist/bus/event-bus.d.ts.map +1 -1
- package/dist/bus/event-bus.js +6 -1
- package/dist/bus/event-bus.js.map +1 -1
- package/dist/context/bounded-context.d.ts +30 -28
- package/dist/context/bounded-context.d.ts.map +1 -1
- package/dist/context/bounded-context.js +137 -83
- 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 -120
- 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 +13 -11
- package/dist/context/projection-handoff.d.ts.map +1 -1
- package/dist/context/projection-handoff.js +77 -34
- 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-builder.d.ts +8 -7
- package/dist/delivery/delivery-builder.d.ts.map +1 -1
- package/dist/delivery/delivery-builder.js +2 -2
- package/dist/delivery/delivery-builder.js.map +1 -1
- package/dist/delivery/delivery-ports.d.ts +2 -2
- 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 +87 -38
- package/dist/delivery/delivery.js.map +1 -1
- package/dist/delivery/inbox-retention.d.ts +7 -0
- package/dist/delivery/inbox-retention.d.ts.map +1 -0
- package/dist/delivery/inbox-retention.js +29 -0
- package/dist/delivery/inbox-retention.js.map +1 -0
- package/dist/delivery/inbox-storage.d.ts +2 -2
- package/dist/delivery/inbox-storage.d.ts.map +1 -1
- package/dist/delivery/inbox-storage.js +32 -8
- package/dist/delivery/inbox-storage.js.map +1 -1
- package/dist/delivery/inbox.d.ts +5 -9
- package/dist/delivery/inbox.d.ts.map +1 -1
- package/dist/delivery/inbox.js +4 -6
- package/dist/delivery/inbox.js.map +1 -1
- package/dist/delivery/reception-observation.d.ts +3 -3
- package/dist/delivery/reception-observation.js +2 -2
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- 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 +25 -33
- package/dist/repository/repository.d.ts.map +1 -1
- package/dist/repository/repository.js +245 -230
- 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/node-coordinator.d.ts.map +1 -1
- package/dist/server/node-coordinator.js +220 -54
- package/dist/server/node-coordinator.js.map +1 -1
- package/dist/server/server-environment.d.ts +6 -5
- package/dist/server/server-environment.d.ts.map +1 -1
- package/dist/server/server-environment.js +13 -9
- package/dist/server/server-environment.js.map +1 -1
- package/dist/server/server.js +1 -0
- package/dist/server/server.js.map +1 -1
- package/dist/services/app-query-execution.js +2 -2
- package/dist/services/app-query-execution.js.map +1 -1
- 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/spine-services.d.ts +6 -0
- package/dist/services/spine-services.d.ts.map +1 -1
- package/dist/services/spine-services.js +128 -30
- package/dist/services/spine-services.js.map +1 -1
- package/dist/services/trusted-reader-ingress.d.ts +23 -5
- package/dist/services/trusted-reader-ingress.d.ts.map +1 -1
- package/dist/services/trusted-reader-ingress.js +18 -5
- package/dist/services/trusted-reader-ingress.js.map +1 -1
- package/dist/spi/delivery.d.ts +1 -0
- package/dist/spi/delivery.d.ts.map +1 -1
- package/dist/spi/delivery.js +1 -0
- package/dist/spi/delivery.js.map +1 -1
- package/dist/stand/stand.d.ts +1 -1
- package/dist/stand/stand.js +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +16 -14
|
@@ -24,6 +24,7 @@ import { DeliveryReadiness } from "./local-inbox-handoff.js";
|
|
|
24
24
|
import { LocalEntityInbox } from "./entity-inbox.js";
|
|
25
25
|
import { LocalProjectionInbox } from "./projection-handoff.js";
|
|
26
26
|
import { TenantAdmissions } from "./tenant-unknown-error.js";
|
|
27
|
+
import { SINGLE_TENANT } from "./tenant-index.js";
|
|
27
28
|
import { AgentExecutionCapacity, } from "../agent/agent-execution-capacity.js";
|
|
28
29
|
import { AgentAcceptanceReceipts, agentAcceptanceReceiptGroup, agentAcceptanceReceiptSpec, } from "../agent/agent-acceptance-receipts.js";
|
|
29
30
|
import { Repository, repositoryAccess, } from "../repository/repository.js";
|
|
@@ -152,7 +153,7 @@ export class BoundedContext {
|
|
|
152
153
|
#agentReceipts;
|
|
153
154
|
#agentController = new AbortController();
|
|
154
155
|
#agentReservations = new Set();
|
|
155
|
-
#
|
|
156
|
+
#activeDeliveries = new Set();
|
|
156
157
|
#activePreparations = new Set();
|
|
157
158
|
#tenantPreparations = new Map();
|
|
158
159
|
#preparationController = new AbortController();
|
|
@@ -226,7 +227,7 @@ export class BoundedContext {
|
|
|
226
227
|
eventBusAccess.bindEnvironment(this.#eventBus, this.#environment);
|
|
227
228
|
eventBusAccess.bindEnvironment(this.#systemEventBus, this.#environment);
|
|
228
229
|
commandBusAccess.setAdmission(this.#commandBus, (command) => this.#admitTenant(command.context?.actorContext?.tenantId));
|
|
229
|
-
eventBusAccess.setAdmission(this.#eventBus, (event) => this.#admitTenant(ContextParts.
|
|
230
|
+
eventBusAccess.setAdmission(this.#eventBus, (event) => this.#admitTenant(ContextParts.readCatchUpTenant(event)));
|
|
230
231
|
standAccess.setAdmission(this.#stand, (tenantId) => this.#admitTenant(tenantId));
|
|
231
232
|
standAccess.setPublicAdmission(this.#stand, () => {
|
|
232
233
|
if (closingContexts.has(this) && !runtimeAccess.isRunningAdmittedWork(this.#environment))
|
|
@@ -374,10 +375,13 @@ export class BoundedContext {
|
|
|
374
375
|
async #prepareCurrentTenants(mode) {
|
|
375
376
|
if (!this.isMultitenant)
|
|
376
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.");
|
|
377
381
|
let after;
|
|
378
382
|
do {
|
|
379
383
|
serverEnvironmentAccess.requireAvailable(this.#environment);
|
|
380
|
-
const page = await
|
|
384
|
+
const page = await index.page({
|
|
381
385
|
count: 64,
|
|
382
386
|
signal: this.#preparationController.signal,
|
|
383
387
|
...(after === undefined ? {} : { after }),
|
|
@@ -385,7 +389,7 @@ export class BoundedContext {
|
|
|
385
389
|
serverEnvironmentAccess.requireAvailable(this.#environment);
|
|
386
390
|
for (const id of page.ids) {
|
|
387
391
|
serverEnvironmentAccess.requireAvailable(this.#environment);
|
|
388
|
-
await TenantAdmissions.require(
|
|
392
|
+
await TenantAdmissions.require(index, this.name.value, true, id);
|
|
389
393
|
serverEnvironmentAccess.requireAvailable(this.#environment);
|
|
390
394
|
await this.#requiredPrepare(() => this.#prepareTenant(id, mode));
|
|
391
395
|
}
|
|
@@ -468,17 +472,17 @@ export class BoundedContext {
|
|
|
468
472
|
/**
|
|
469
473
|
* Creates tenant-aware Inboxes and their shared readiness signal.
|
|
470
474
|
*
|
|
471
|
-
* @param tenantIndex
|
|
472
|
-
* @returns Inboxes, tenant
|
|
475
|
+
* @param tenantIndex Captured marker or directory for this Bounded Context.
|
|
476
|
+
* @returns Inboxes, tenant setting, and readiness signal.
|
|
473
477
|
*/
|
|
474
478
|
#createDelivery(tenantIndex) {
|
|
475
479
|
const deliveryReadiness = this.#createReadiness();
|
|
476
480
|
const onAdmit = (tenantId) => this.#admitTenant(tenantId);
|
|
477
481
|
const entityInbox = new LocalEntityInbox(this.#snapshot.name.value, deliveryReadiness, onAdmit, this.#environment.delivery.strategy);
|
|
478
|
-
const projectionInbox = new LocalProjectionInbox(this.#snapshot.name.value, deliveryReadiness, onAdmit);
|
|
482
|
+
const projectionInbox = new LocalProjectionInbox(this.#snapshot.name.value, deliveryReadiness, onAdmit, this.#environment.delivery.strategy);
|
|
479
483
|
deliveryReadiness.configureDirect((message, tenantId) => message.label === "UPDATE_SUBSCRIBER"
|
|
480
|
-
? projectionInbox.
|
|
481
|
-
: entityInbox.
|
|
484
|
+
? projectionInbox.deliver(message, tenantId)
|
|
485
|
+
: entityInbox.deliver(message, tenantId));
|
|
482
486
|
return { deliveryReadiness, tenantIndex, entityInbox, projectionInbox };
|
|
483
487
|
}
|
|
484
488
|
/**
|
|
@@ -518,7 +522,7 @@ export class BoundedContext {
|
|
|
518
522
|
this.#beginClose();
|
|
519
523
|
},
|
|
520
524
|
drain: () => this.#drainWork(),
|
|
521
|
-
|
|
525
|
+
trackDelivery: (onDelivery) => this.#trackDelivery(onDelivery),
|
|
522
526
|
});
|
|
523
527
|
contextDeliveryDescriptors.set(this, ContextParts.createDeliveryDescriptor(this.#entityInbox, this.#projectionInbox));
|
|
524
528
|
}
|
|
@@ -699,9 +703,10 @@ export class BoundedContext {
|
|
|
699
703
|
return this.#snapshot.tenantMode === "multitenant";
|
|
700
704
|
}
|
|
701
705
|
/**
|
|
702
|
-
* Returns the
|
|
706
|
+
* Returns the captured tenant setting for this Bounded Context.
|
|
703
707
|
*
|
|
704
|
-
* @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.
|
|
705
710
|
*/
|
|
706
711
|
get tenantIndex() {
|
|
707
712
|
return ContextParts.requireTenantIndex(this);
|
|
@@ -781,17 +786,16 @@ export class BoundedContext {
|
|
|
781
786
|
throw new Error("Entity constructor is not registered in this Bounded Context.");
|
|
782
787
|
}
|
|
783
788
|
/**
|
|
784
|
-
* Clears
|
|
789
|
+
* Clears registered Projection state and processes already-stored Events locally.
|
|
785
790
|
*
|
|
786
|
-
*
|
|
787
|
-
* process-local maintenance helper and provides no Projection targeting,
|
|
791
|
+
* This process-local maintenance helper provides no Projection targeting,
|
|
788
792
|
* historical starting point, durable operation identity, progress,
|
|
789
793
|
* historical/live coordination, restart, resumption, or multi-node work.
|
|
790
794
|
*
|
|
791
795
|
* Supported boundary:
|
|
792
796
|
* - projection subscribers only;
|
|
793
797
|
* - already-stored events only;
|
|
794
|
-
* - clear then
|
|
798
|
+
* - clear then dispatch stored Events, with no event re-append;
|
|
795
799
|
* - single-tenant contexts reject `tenantId`;
|
|
796
800
|
* - multitenant contexts require the exact non-blank `tenantId`;
|
|
797
801
|
* - process-local sequential execution on the same EventBus runtime queue as
|
|
@@ -803,14 +807,14 @@ export class BoundedContext {
|
|
|
803
807
|
* - durable live-traffic catch-up orchestration across processes.
|
|
804
808
|
*
|
|
805
809
|
* @param options Selects the tenant slice to rebuild.
|
|
806
|
-
* @returns
|
|
810
|
+
* @returns Dispatch and clear counts for the selected slice.
|
|
807
811
|
*/
|
|
808
812
|
async catchUpReadSide(options = {}) {
|
|
809
813
|
return eventBusAccess.runExclusive(this.#eventBus, () => this.#catchUpReadSideOnce(options));
|
|
810
814
|
}
|
|
811
815
|
/**
|
|
812
816
|
* Clears registered Projection state for the selected tenant slice, reads stored events,
|
|
813
|
-
* validates each
|
|
817
|
+
* validates each Event’s tenant, and dispatches stored Events sequentially.
|
|
814
818
|
*/
|
|
815
819
|
async #catchUpReadSideOnce(options) {
|
|
816
820
|
const storageContext = ContextParts.catchUpStorageContext(this.#snapshot.spec, options);
|
|
@@ -819,7 +823,7 @@ export class BoundedContext {
|
|
|
819
823
|
const clearTargets = ContextParts.projectionStateClearTargets(projections);
|
|
820
824
|
const clearedStateTypes = [];
|
|
821
825
|
let clearedEntityCount = 0;
|
|
822
|
-
let
|
|
826
|
+
let processedEventCount = 0;
|
|
823
827
|
for (const target of clearTargets) {
|
|
824
828
|
clearedEntityCount += await this.#stand.clear(target.schema, tenantOptions);
|
|
825
829
|
clearedStateTypes.push(target.typeUrl);
|
|
@@ -827,15 +831,15 @@ export class BoundedContext {
|
|
|
827
831
|
const events = await ContextParts.readStoredEvents(storageContext, this.#storageFactory);
|
|
828
832
|
for (const event of events) {
|
|
829
833
|
try {
|
|
830
|
-
ContextParts.
|
|
831
|
-
|
|
834
|
+
ContextParts.validateCatchUpTenant(storageContext, event);
|
|
835
|
+
processedEventCount += await ContextParts.dispatchStoredProjectionEvent(projections, event, true);
|
|
832
836
|
}
|
|
833
837
|
catch (error) {
|
|
834
|
-
throw ContextParts.
|
|
838
|
+
throw ContextParts.catchUpDispatchError(event, error);
|
|
835
839
|
}
|
|
836
840
|
}
|
|
837
841
|
return Object.freeze({
|
|
838
|
-
|
|
842
|
+
processedEventCount,
|
|
839
843
|
clearedEntityCount,
|
|
840
844
|
clearedStateTypes: Object.freeze(clearedStateTypes),
|
|
841
845
|
});
|
|
@@ -902,13 +906,12 @@ export class BoundedContext {
|
|
|
902
906
|
ContextParts.clearContextMetadata(this);
|
|
903
907
|
}
|
|
904
908
|
/**
|
|
905
|
-
* Settles entered
|
|
909
|
+
* Settles entered deliveries, outgoing handoffs, and reserved Agent turns.
|
|
906
910
|
* @param errors Collects independent close failures.
|
|
907
911
|
*/
|
|
908
912
|
async #drainAdmittedContextWork(errors) {
|
|
909
913
|
await this.#closePart(() => Promise.allSettled([...this.#activePreparations]).then(() => undefined), errors);
|
|
910
|
-
await this.#closePart(() => Promise.allSettled([...this.#
|
|
911
|
-
await this.#closePart(() => this.#entityInbox.whenFollowUpsIdle(), errors);
|
|
914
|
+
await this.#closePart(() => Promise.allSettled([...this.#activeDeliveries]).then(() => undefined), errors);
|
|
912
915
|
await this.#closePart(() => Promise.allSettled([...this.#agentReservations]).then(() => undefined), errors);
|
|
913
916
|
}
|
|
914
917
|
/**
|
|
@@ -943,15 +946,15 @@ export class BoundedContext {
|
|
|
943
946
|
}
|
|
944
947
|
}
|
|
945
948
|
/**
|
|
946
|
-
* Registers an entered
|
|
947
|
-
* @param
|
|
948
|
-
* @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.
|
|
949
952
|
*/
|
|
950
|
-
#
|
|
951
|
-
const
|
|
952
|
-
this.#
|
|
953
|
-
void
|
|
954
|
-
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;
|
|
955
958
|
}
|
|
956
959
|
/**
|
|
957
960
|
* Calls a close action and collects its failure before later cleanup.
|
|
@@ -1003,7 +1006,7 @@ export const boundedContextAccess = Object.freeze({
|
|
|
1003
1006
|
const phase = contextClosePhases.get(context);
|
|
1004
1007
|
if (prepare === undefined || phase === undefined)
|
|
1005
1008
|
return Promise.reject(new TypeError("Service input requires a built context."));
|
|
1006
|
-
return phase.
|
|
1009
|
+
return phase.trackDelivery(async () => {
|
|
1007
1010
|
await onMembership();
|
|
1008
1011
|
await prepare(tenantId);
|
|
1009
1012
|
await onReady?.();
|
|
@@ -1022,16 +1025,16 @@ export const boundedContextAccess = Object.freeze({
|
|
|
1022
1025
|
const phase = contextClosePhases.get(context);
|
|
1023
1026
|
if (phase === undefined)
|
|
1024
1027
|
return Promise.reject(new TypeError("Service registry requires a built context."));
|
|
1025
|
-
return phase.
|
|
1028
|
+
return phase.trackDelivery(onOperation);
|
|
1026
1029
|
},
|
|
1027
1030
|
/**
|
|
1028
|
-
* Registers
|
|
1031
|
+
* Registers entered delivery work with the context close lifecycle.
|
|
1029
1032
|
*/
|
|
1030
|
-
|
|
1033
|
+
trackDelivery(context, onDelivery) {
|
|
1031
1034
|
const phase = contextClosePhases.get(context);
|
|
1032
1035
|
if (phase === undefined)
|
|
1033
|
-
return Promise.reject(new TypeError("
|
|
1034
|
-
return phase.
|
|
1036
|
+
return Promise.reject(new TypeError("Delivery requires a built context."));
|
|
1037
|
+
return phase.trackDelivery(onDelivery);
|
|
1035
1038
|
},
|
|
1036
1039
|
/**
|
|
1037
1040
|
* Resolves a view only within the Bounded Context that issued it.
|
|
@@ -1059,7 +1062,7 @@ export const boundedContextAccess = Object.freeze({
|
|
|
1059
1062
|
phase.begin();
|
|
1060
1063
|
},
|
|
1061
1064
|
/**
|
|
1062
|
-
* Waits for accepted runtime work and
|
|
1065
|
+
* Waits for accepted runtime work and entered delivery operations to finish.
|
|
1063
1066
|
*/
|
|
1064
1067
|
drainWork(context) {
|
|
1065
1068
|
const phase = contextClosePhases.get(context);
|
|
@@ -1165,7 +1168,7 @@ export const boundedContextAccess = Object.freeze({
|
|
|
1165
1168
|
return ContextParts.cloneSystemPairing(ContextParts.requireSystemPairing(context));
|
|
1166
1169
|
},
|
|
1167
1170
|
/**
|
|
1168
|
-
* Returns the
|
|
1171
|
+
* Returns the captured marker or directory for this Bounded Context.
|
|
1169
1172
|
*/
|
|
1170
1173
|
tenantIndex(context) {
|
|
1171
1174
|
return ContextParts.requireTenantIndex(context);
|
|
@@ -1397,7 +1400,9 @@ export class BoundedContextBuilder {
|
|
|
1397
1400
|
return this;
|
|
1398
1401
|
}
|
|
1399
1402
|
/**
|
|
1400
|
-
* 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.
|
|
1401
1406
|
*
|
|
1402
1407
|
* @param tenantIndex Application membership index.
|
|
1403
1408
|
* @returns This builder.
|
|
@@ -1485,10 +1490,11 @@ export class BoundedContextBuilder {
|
|
|
1485
1490
|
* @returns The integrated context.
|
|
1486
1491
|
*/
|
|
1487
1492
|
async #buildAsyncReady(options, environment) {
|
|
1493
|
+
const tenantIndex = this.#selectTenantIndex(environment);
|
|
1488
1494
|
const generated = this.#loadGeneratedArtifacts([...this.#entityTypes]);
|
|
1489
1495
|
serverEnvironmentAccess.requireAvailable(environment);
|
|
1490
1496
|
const repositories = [...this.#repositories, ...generated.repositories];
|
|
1491
|
-
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);
|
|
1492
1498
|
try {
|
|
1493
1499
|
await this.#integrateBuiltContext(context, options, environment);
|
|
1494
1500
|
}
|
|
@@ -1497,6 +1503,20 @@ export class BoundedContextBuilder {
|
|
|
1497
1503
|
}
|
|
1498
1504
|
return context;
|
|
1499
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
|
+
}
|
|
1500
1520
|
/**
|
|
1501
1521
|
* Completes required integration and requested initialization before publication.
|
|
1502
1522
|
* @param context Constructed context.
|
|
@@ -1545,14 +1565,15 @@ export class BoundedContextBuilder {
|
|
|
1545
1565
|
* @param storageFactory Storage factory for the context.
|
|
1546
1566
|
* @param standalone Generated standalone handler groups.
|
|
1547
1567
|
* @param ai Optional AI registry.
|
|
1568
|
+
* @param tenantIndex Captured marker or directory for admission.
|
|
1548
1569
|
* @returns The assembled context.
|
|
1549
1570
|
*/
|
|
1550
|
-
async #buildWithResources(repositories, storageFactory, standalone, ai) {
|
|
1571
|
+
async #buildWithResources(repositories, storageFactory, standalone, ai, tenantIndex) {
|
|
1551
1572
|
const resources = this.#subscriptionRegistry === undefined ? {} : { registry: this.#subscriptionRegistry };
|
|
1552
1573
|
this.#subscriptionRegistry = undefined;
|
|
1553
1574
|
const registeredRepositories = [...repositories];
|
|
1554
1575
|
try {
|
|
1555
|
-
return this.#assembleContext(registeredRepositories, storageFactory, standalone, resources, ai);
|
|
1576
|
+
return this.#assembleContext(registeredRepositories, storageFactory, standalone, resources, ai, tenantIndex);
|
|
1556
1577
|
}
|
|
1557
1578
|
catch (error) {
|
|
1558
1579
|
return await this.#failBuild(resources, error);
|
|
@@ -1561,8 +1582,16 @@ export class BoundedContextBuilder {
|
|
|
1561
1582
|
/**
|
|
1562
1583
|
* Assembles dispatchers, buses, Stand, subscription runtime, and integration hooks, registering
|
|
1563
1584
|
* the completed context with its environment.
|
|
1564
|
-
|
|
1565
|
-
|
|
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) {
|
|
1566
1595
|
ContextParts.preflightRepositories(repositories);
|
|
1567
1596
|
this.#preflightAgents(repositories, storageFactory, ai);
|
|
1568
1597
|
if (ai !== undefined)
|
|
@@ -1576,17 +1605,28 @@ export class BoundedContextBuilder {
|
|
|
1576
1605
|
...ContextParts.repositoryProducedEventSchemas(repositories),
|
|
1577
1606
|
...ContextParts.standaloneProducedEventSchemas(standalone),
|
|
1578
1607
|
]);
|
|
1579
|
-
const running = this.#buildRuntime(storageFactory, buses, resources);
|
|
1580
|
-
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);
|
|
1581
1610
|
resources.context = context;
|
|
1582
1611
|
if (registerBoundedContext === undefined)
|
|
1583
1612
|
throw new Error("Context registration is unavailable.");
|
|
1584
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) {
|
|
1585
1626
|
ContextParts.attachIntegration(context, buses.eventBus, buses.systemSpec, ContextParts.externalEventSchemas([
|
|
1586
1627
|
...dispatchers.domain,
|
|
1587
1628
|
...(standaloneEvent === undefined ? [] : [standaloneEvent]),
|
|
1588
1629
|
]));
|
|
1589
|
-
return context;
|
|
1590
1630
|
}
|
|
1591
1631
|
/**
|
|
1592
1632
|
* Rejects missing Agent prerequisites before repository intake is installed.
|
|
@@ -1705,9 +1745,15 @@ export class BoundedContextBuilder {
|
|
|
1705
1745
|
}
|
|
1706
1746
|
/**
|
|
1707
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.
|
|
1708
1754
|
*/
|
|
1709
|
-
#buildRuntime(storageFactory, buses, resources) {
|
|
1710
|
-
const stand = this.#buildStand(storageFactory);
|
|
1755
|
+
#buildRuntime(storageFactory, buses, resources, tenantIndex) {
|
|
1756
|
+
const stand = this.#buildStand(storageFactory, tenantIndex);
|
|
1711
1757
|
resources.stand = stand;
|
|
1712
1758
|
const registry = resources.registry ??
|
|
1713
1759
|
new StorageSubscriptionRegistry(ContextParts.createSubscriptionStorageContext(this.#specSnapshot), storageFactory, subscriptionRecordGroup(this.#specSnapshot.name.value));
|
|
@@ -1717,13 +1763,13 @@ export class BoundedContextBuilder {
|
|
|
1717
1763
|
return { stand, registry, runtime };
|
|
1718
1764
|
}
|
|
1719
1765
|
/**
|
|
1720
|
-
* Builds the
|
|
1766
|
+
* Builds the Bounded Context Stand with captured tenant admission.
|
|
1721
1767
|
*
|
|
1722
1768
|
* @param storageFactory Provider used for Entity records.
|
|
1769
|
+
* @param tenantIndex Captured marker or directory.
|
|
1723
1770
|
* @returns Direct read-side Stand.
|
|
1724
1771
|
*/
|
|
1725
|
-
#buildStand(storageFactory) {
|
|
1726
|
-
const tenantIndex = this.#tenantIndex ?? ServerEnvironment.instance().tenantIndex;
|
|
1772
|
+
#buildStand(storageFactory, tenantIndex) {
|
|
1727
1773
|
const stand = new Stand({
|
|
1728
1774
|
context: ContextParts.createStorageMode(this.#specSnapshot),
|
|
1729
1775
|
storageFactory,
|
|
@@ -1735,10 +1781,18 @@ export class BoundedContextBuilder {
|
|
|
1735
1781
|
}
|
|
1736
1782
|
/**
|
|
1737
1783
|
* Constructs the immutable BoundedContext using assembled buses, runtime parts, environment
|
|
1738
|
-
* delivery strategy, and tenant
|
|
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.
|
|
1739
1793
|
*/
|
|
1740
|
-
#createContext(repositories, storageFactory, buses, running, ai) {
|
|
1741
|
-
return ContextParts.createBoundedContext(this.#specSnapshot, storageFactory, repositories, ServerEnvironment.instance().delivery.strategy, buses, running, ai,
|
|
1794
|
+
#createContext(repositories, storageFactory, buses, running, tenantIndex, ai) {
|
|
1795
|
+
return ContextParts.createBoundedContext(this.#specSnapshot, storageFactory, repositories, ServerEnvironment.instance().delivery.strategy, buses, running, ai, tenantIndex);
|
|
1742
1796
|
}
|
|
1743
1797
|
/**
|
|
1744
1798
|
* Closes partial context resources after assembly fails and aggregates cleanup errors with the original failure.
|
|
@@ -1873,28 +1927,28 @@ export class ContextSpec {
|
|
|
1873
1927
|
}
|
|
1874
1928
|
const registeredRepositories = new WeakMap();
|
|
1875
1929
|
/**
|
|
1876
|
-
* Reports failure while
|
|
1930
|
+
* Reports failure while dispatching a stored Event during read-side catch-up.
|
|
1877
1931
|
*/
|
|
1878
|
-
class
|
|
1879
|
-
code = "
|
|
1932
|
+
class CatchUpDispatchError extends Error {
|
|
1933
|
+
code = "READ_SIDE_CATCH_UP_DISPATCH_FAILED";
|
|
1880
1934
|
eventId;
|
|
1881
1935
|
detail;
|
|
1882
1936
|
/**
|
|
1883
|
-
* Creates a
|
|
1937
|
+
* Creates a catch-up error tied to the stored Event that failed.
|
|
1884
1938
|
*
|
|
1885
1939
|
* @param eventId Identifies the stored event.
|
|
1886
1940
|
* @param detail Contains the bounded cause name and message.
|
|
1887
1941
|
*/
|
|
1888
1942
|
constructor(eventId, detail) {
|
|
1889
1943
|
super(`Read-side catch-up failed for stored event "${eventId}".`);
|
|
1890
|
-
this.name = "
|
|
1944
|
+
this.name = "ReadSideCatchUpDispatchError";
|
|
1891
1945
|
this.eventId = eventId;
|
|
1892
1946
|
this.detail = detail;
|
|
1893
1947
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
1894
1948
|
}
|
|
1895
1949
|
}
|
|
1896
1950
|
/**
|
|
1897
|
-
* Assembles private bounded-context lifecycle and
|
|
1951
|
+
* Assembles private bounded-context lifecycle and catch-up details.
|
|
1898
1952
|
*/
|
|
1899
1953
|
const ContextParts = Object.freeze({
|
|
1900
1954
|
/**
|
|
@@ -2039,7 +2093,7 @@ const ContextParts = Object.freeze({
|
|
|
2039
2093
|
* @param buses Prepared domain and System buses.
|
|
2040
2094
|
* @param running Prepared Stand and subscription runtime.
|
|
2041
2095
|
* @param ai Effective AI registry when Agent repositories are present.
|
|
2042
|
-
* @param tenantIndex
|
|
2096
|
+
* @param tenantIndex Captured marker or directory.
|
|
2043
2097
|
* @returns Constructed bounded context.
|
|
2044
2098
|
*/
|
|
2045
2099
|
createBoundedContext(specSnapshot, storageFactory, repositories, deliveryStrategy, buses, running, ai, tenantIndex) {
|
|
@@ -2054,7 +2108,7 @@ const ContextParts = Object.freeze({
|
|
|
2054
2108
|
subscriptionRuntime: running.runtime,
|
|
2055
2109
|
systemSpec: buses.systemSpec,
|
|
2056
2110
|
storageFactory,
|
|
2057
|
-
tenantIndex
|
|
2111
|
+
tenantIndex,
|
|
2058
2112
|
repositories,
|
|
2059
2113
|
deliveryStrategy,
|
|
2060
2114
|
...(ai === undefined ? {} : { ai }),
|
|
@@ -2329,7 +2383,7 @@ const ContextParts = Object.freeze({
|
|
|
2329
2383
|
* @param event Imported event to validate.
|
|
2330
2384
|
*/
|
|
2331
2385
|
validateImportedTenant(context, event) {
|
|
2332
|
-
const tenantId = ContextParts.
|
|
2386
|
+
const tenantId = ContextParts.readCatchUpTenant(event);
|
|
2333
2387
|
if (!context.isMultitenant) {
|
|
2334
2388
|
if (tenantId !== undefined) {
|
|
2335
2389
|
throw new Error(`Single-tenant context "${context.name.value}" does not accept tenantId.`);
|
|
@@ -2384,10 +2438,10 @@ const ContextParts = Object.freeze({
|
|
|
2384
2438
|
return pairing;
|
|
2385
2439
|
},
|
|
2386
2440
|
/**
|
|
2387
|
-
* Reads the
|
|
2441
|
+
* Reads the marker or directory recorded for a built Bounded Context.
|
|
2388
2442
|
*
|
|
2389
|
-
* @param context Context to inspect.
|
|
2390
|
-
* @returns
|
|
2443
|
+
* @param context Bounded Context to inspect.
|
|
2444
|
+
* @returns Captured marker or directory; throws if unavailable.
|
|
2391
2445
|
*/
|
|
2392
2446
|
requireTenantIndex(context) {
|
|
2393
2447
|
const tenantIndex = contextTenantIndexes.get(context);
|
|
@@ -2713,9 +2767,9 @@ const ContextParts = Object.freeze({
|
|
|
2713
2767
|
createDeliveryDescriptor(entityInbox, projections) {
|
|
2714
2768
|
return Object.freeze({
|
|
2715
2769
|
endpoints: () => Object.freeze([...entityInbox.endpoints(), ...projections.endpoints()]),
|
|
2716
|
-
|
|
2717
|
-
? projections.
|
|
2718
|
-
: entityInbox.
|
|
2770
|
+
deliver: (message, tenantId) => message.label === "UPDATE_SUBSCRIBER"
|
|
2771
|
+
? projections.deliver(message, tenantId)
|
|
2772
|
+
: entityInbox.deliver(message, tenantId),
|
|
2719
2773
|
});
|
|
2720
2774
|
},
|
|
2721
2775
|
/**
|
|
@@ -2986,12 +3040,12 @@ const ContextParts = Object.freeze({
|
|
|
2986
3040
|
* @param context Catch-up storage context.
|
|
2987
3041
|
* @param event Stored event to validate.
|
|
2988
3042
|
*/
|
|
2989
|
-
|
|
3043
|
+
validateCatchUpTenant(context, event) {
|
|
2990
3044
|
if (!context.multitenant) {
|
|
2991
3045
|
return;
|
|
2992
3046
|
}
|
|
2993
3047
|
const expectedTenantId = context.tenantId;
|
|
2994
|
-
const envelopeTenantId = ContextParts.
|
|
3048
|
+
const envelopeTenantId = ContextParts.readCatchUpTenant(event);
|
|
2995
3049
|
if (envelopeTenantId === undefined) {
|
|
2996
3050
|
throw new Error("Read-side catch-up requires stored event envelope tenant.");
|
|
2997
3051
|
}
|
|
@@ -3005,7 +3059,7 @@ const ContextParts = Object.freeze({
|
|
|
3005
3059
|
* @param event Event whose origin is inspected.
|
|
3006
3060
|
* @returns Cloned tenant ID when present.
|
|
3007
3061
|
*/
|
|
3008
|
-
|
|
3062
|
+
readCatchUpTenant(event) {
|
|
3009
3063
|
switch (event.context?.origin.case) {
|
|
3010
3064
|
case "importContext":
|
|
3011
3065
|
return ContextParts.tenantIdValue(event.context.origin.value.tenantId);
|
|
@@ -3047,14 +3101,14 @@ const ContextParts = Object.freeze({
|
|
|
3047
3101
|
return matching.length > 0 ? 1 : 0;
|
|
3048
3102
|
},
|
|
3049
3103
|
/**
|
|
3050
|
-
* Creates a
|
|
3104
|
+
* Creates a catch-up error with the Event ID and bounded cause details.
|
|
3051
3105
|
*
|
|
3052
3106
|
* @param event Event that failed.
|
|
3053
|
-
* @param cause Thrown
|
|
3054
|
-
* @returns
|
|
3107
|
+
* @param cause Thrown catch-up cause.
|
|
3108
|
+
* @returns Catch-up error.
|
|
3055
3109
|
*/
|
|
3056
|
-
|
|
3057
|
-
return new
|
|
3110
|
+
catchUpDispatchError(event, cause) {
|
|
3111
|
+
return new CatchUpDispatchError(event.id?.value ?? "(missing)", ContextParts.catchUpDispatchDetail(cause));
|
|
3058
3112
|
},
|
|
3059
3113
|
/**
|
|
3060
3114
|
* Returns an error string truncated to a maximum length.
|
|
@@ -3072,7 +3126,7 @@ const ContextParts = Object.freeze({
|
|
|
3072
3126
|
* @param error Thrown value.
|
|
3073
3127
|
* @returns Immutable bounded details.
|
|
3074
3128
|
*/
|
|
3075
|
-
|
|
3129
|
+
catchUpDispatchDetail(error) {
|
|
3076
3130
|
if (error instanceof Error) {
|
|
3077
3131
|
return Object.freeze({
|
|
3078
3132
|
name: ContextParts.boundedErrorString(error.name, errorDetailLimit) || "Error",
|