@spine-event-engine/server 2.0.0-snapshot.16 → 2.0.0-snapshot.20
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 +17 -3
- package/REFERENCE.md +66 -12
- package/dist/context/bounded-context.d.ts +72 -14
- package/dist/context/bounded-context.d.ts.map +1 -1
- package/dist/context/bounded-context.js +130 -49
- package/dist/context/bounded-context.js.map +1 -1
- package/dist/context/effective-tenant.d.ts +9 -0
- package/dist/context/effective-tenant.d.ts.map +1 -0
- package/dist/context/effective-tenant.js +58 -0
- package/dist/context/effective-tenant.js.map +1 -0
- package/dist/context/tenant-index.d.ts.map +1 -1
- package/dist/context/tenant-index.js +70 -1
- package/dist/context/tenant-index.js.map +1 -1
- package/dist/delivery/delivery.d.ts.map +1 -1
- package/dist/delivery/delivery.js +270 -26
- package/dist/delivery/delivery.js.map +1 -1
- package/dist/delivery/inbox-storage.d.ts +7 -0
- package/dist/delivery/inbox-storage.d.ts.map +1 -1
- package/dist/delivery/inbox-storage.js +110 -0
- package/dist/delivery/inbox-storage.js.map +1 -1
- package/dist/entity/entity-metadata.d.ts +2 -2
- package/dist/entity/entity-metadata.js +2 -2
- package/dist/entity/entity-metadata.js.map +1 -1
- package/dist/entity/entity-storage-descriptor.d.ts +24 -4
- package/dist/entity/entity-storage-descriptor.d.ts.map +1 -1
- package/dist/entity/entity-storage-descriptor.js +69 -23
- package/dist/entity/entity-storage-descriptor.js.map +1 -1
- package/dist/entity/entity.d.ts +19 -18
- package/dist/entity/entity.d.ts.map +1 -1
- package/dist/entity/entity.js +61 -24
- package/dist/entity/entity.js.map +1 -1
- package/dist/entity/spec-scanner.d.ts +13 -1
- package/dist/entity/spec-scanner.d.ts.map +1 -1
- package/dist/entity/spec-scanner.js +20 -3
- package/dist/entity/spec-scanner.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/repository/command-routing.d.ts +63 -10
- package/dist/repository/command-routing.d.ts.map +1 -1
- package/dist/repository/command-routing.js +41 -8
- package/dist/repository/command-routing.js.map +1 -1
- package/dist/repository/event-routing.d.ts +64 -9
- package/dist/repository/event-routing.d.ts.map +1 -1
- package/dist/repository/event-routing.js +42 -7
- package/dist/repository/event-routing.js.map +1 -1
- package/dist/repository/repository.d.ts +96 -10
- package/dist/repository/repository.d.ts.map +1 -1
- package/dist/repository/repository.js +437 -133
- package/dist/repository/repository.js.map +1 -1
- package/dist/repository/state-update-routing.d.ts +67 -8
- package/dist/repository/state-update-routing.d.ts.map +1 -1
- package/dist/repository/state-update-routing.js +45 -6
- package/dist/repository/state-update-routing.js.map +1 -1
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +407 -109
- package/dist/server/server.js.map +1 -1
- package/dist/services/registered-targets.d.ts +73 -0
- package/dist/services/registered-targets.d.ts.map +1 -0
- package/dist/services/registered-targets.js +127 -0
- package/dist/services/registered-targets.js.map +1 -0
- package/dist/services/spine-services.d.ts +26 -12
- package/dist/services/spine-services.d.ts.map +1 -1
- package/dist/services/spine-services.js +1044 -962
- package/dist/services/spine-services.js.map +1 -1
- package/dist/stand/stand.d.ts +73 -1
- package/dist/stand/stand.d.ts.map +1 -1
- package/dist/stand/stand.js +220 -48
- package/dist/stand/stand.js.map +1 -1
- package/dist/stand/subscription-observer.d.ts.map +1 -1
- package/dist/stand/subscription-observer.js +4 -11
- package/dist/stand/subscription-observer.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +12 -12
|
@@ -11,17 +11,20 @@
|
|
|
11
11
|
* or implied. See the License for the specific language governing permissions and limitations under
|
|
12
12
|
* the License.
|
|
13
13
|
*/
|
|
14
|
-
import { clone, create, ScalarType, toBinary } from "@bufbuild/protobuf";
|
|
14
|
+
import { clone, create, ScalarType, toBinary, } from "@bufbuild/protobuf";
|
|
15
15
|
import { AnySchema, Int32ValueSchema, Int64ValueSchema, StringValueSchema, TimestampSchema, } from "@bufbuild/protobuf/wkt";
|
|
16
16
|
import { ValidationException, RejectionThrowable, Validate, TypeUrls, AnyMessages, Identifiers, StringifierRegistry, } from "@spine-event-engine/core";
|
|
17
17
|
import { CommandContextSchema, ActorContextSchema, CommandIdSchema, CommandSchema, EventContextSchema, EventIdSchema, EventSchema, MessageIdSchema, TenantIdSchema, RejectionEventContextSchema, VersionSchema, EntityOption_Kind, } from "@spine-event-engine/proto";
|
|
18
18
|
import * as EntityLog from "@spine-event-engine/proto/generated/spine/system/server/entity_log_events_pb.js";
|
|
19
19
|
import { EntityTypeNameSchema } from "@spine-event-engine/proto/generated/spine/system/server/entity_type_pb.js";
|
|
20
|
-
import {} from "@spine-event-engine/
|
|
20
|
+
import { EntityRecordSchema, } from "@spine-event-engine/proto/generated/spine/server/entity/entity_pb.js";
|
|
21
|
+
import { StorageQueryPolicy, } from "@spine-event-engine/storage";
|
|
21
22
|
import { TenantBoundary } from "@spine-event-engine/storage/provider";
|
|
22
23
|
import { EntityCommitStorageFactories } from "@spine-event-engine/storage/provider";
|
|
23
24
|
import { CommandValidationError } from "../bus/command-errors.js";
|
|
24
25
|
import { SignalPublisher } from "../runtime/signal-publisher.js";
|
|
26
|
+
import { ServerEnvironment, serverEnvironmentAccess } from "../server/server-environment.js";
|
|
27
|
+
import { emitServerWarning } from "../server/server-log.js";
|
|
25
28
|
import { CommandRoutingInternals, } from "./command-routing.js";
|
|
26
29
|
import { EventRoutingInternals } from "./event-routing.js";
|
|
27
30
|
import { StateUpdateRoutingInternals, } from "./state-update-routing.js";
|
|
@@ -34,12 +37,14 @@ import { ShardIndex } from "../delivery/shard-index.js";
|
|
|
34
37
|
import { Aggregate, ProcessManager, Projection, entityHistoryAccess, processManagerQueryAccess, transactionalEntityAccess, } from "../entity/entity.js";
|
|
35
38
|
import { describeEntityMetadata, attachEntitySchema, } from "../entity/entity-metadata.js";
|
|
36
39
|
import { EntityIds, EntityRecords, entityStorageDescriptor, } from "../entity/entity-storage-descriptor.js";
|
|
37
|
-
import {
|
|
40
|
+
import { repositorySpecScanner } from "../entity/spec-scanner.js";
|
|
38
41
|
import { CommandRegistrationReadiness, } from "../handler/command-registration-readiness.js";
|
|
39
42
|
import { EventRegistrationReadiness, } from "../handler/event-registration-readiness.js";
|
|
40
43
|
import { EventHandlerFilters, } from "../handler/event-handler-filter.js";
|
|
41
44
|
import { SignalMetadata } from "../runtime/signal-metadata.js";
|
|
42
45
|
import { QueryReader } from "../services/query-reader.js";
|
|
46
|
+
import { RegisteredTargets } from "../services/registered-targets.js";
|
|
47
|
+
import { EffectiveTenants } from "../context/effective-tenant.js";
|
|
43
48
|
import { HandlerMetadataRegistry, HandlerMetadataValues, } from "../handler/handler-metadata.js";
|
|
44
49
|
import { DeclaredRejections } from "../handler/declared-rejections.js";
|
|
45
50
|
import { standAccess } from "../stand/stand.js";
|
|
@@ -93,6 +98,7 @@ export class Repository {
|
|
|
93
98
|
*/
|
|
94
99
|
constructor(options) {
|
|
95
100
|
const { entityType, entityFamily, schema, metadata } = RepositoryIdentity.describeOptions(options);
|
|
101
|
+
RepositoryRoutes.requireRoutingEntities(entityType, options);
|
|
96
102
|
attachEntitySchema(entityType, schema);
|
|
97
103
|
this.#entityType = entityType;
|
|
98
104
|
this.#entityFamily = entityFamily;
|
|
@@ -186,7 +192,7 @@ export class Repository {
|
|
|
186
192
|
* @returns The calculated command route.
|
|
187
193
|
*/
|
|
188
194
|
routeCommand(command) {
|
|
189
|
-
return this.#routing.routeCommand(command);
|
|
195
|
+
return RepositoryRoutingReads.call(this, RepositoryTenants.readCommandTenant(command), (reads) => this.#routing.routeCommand(command, reads));
|
|
190
196
|
}
|
|
191
197
|
/**
|
|
192
198
|
* Routes an Event without invoking a handler.
|
|
@@ -201,7 +207,10 @@ export class Repository {
|
|
|
201
207
|
* @returns The calculated event route.
|
|
202
208
|
*/
|
|
203
209
|
routeEvent(event) {
|
|
204
|
-
return this.#routing.routeEvent(event)
|
|
210
|
+
return RepositoryRoutingReads.call(this, RepositoryTenants.readEventTenant(event), (reads) => this.#routing.routeEvent(event, reads)).then((route) => {
|
|
211
|
+
RepositoryRoutes.warnLargeRoute(route.messageFullTypeName, this.stateFullTypeName, route.entityIds.length);
|
|
212
|
+
return route;
|
|
213
|
+
});
|
|
205
214
|
}
|
|
206
215
|
}
|
|
207
216
|
const repositorySnapshots = new WeakMap();
|
|
@@ -355,7 +364,11 @@ export const repositoryAccess = Object.freeze({
|
|
|
355
364
|
if (routing === undefined) {
|
|
356
365
|
throw new TypeError("State-update routing requires a Repository instance.");
|
|
357
366
|
}
|
|
358
|
-
return routing.routeStateUpdate(event)
|
|
367
|
+
return RepositoryRoutingReads.call(repository, RepositoryTenants.readEventTenant(event), (reads) => routing.routeStateUpdate(event, reads)).then((route) => {
|
|
368
|
+
if (route !== undefined)
|
|
369
|
+
RepositoryRoutes.warnLargeRoute(route.messageFullTypeName, repository.stateSchema.typeName, route.entityIds.length);
|
|
370
|
+
return route;
|
|
371
|
+
});
|
|
359
372
|
},
|
|
360
373
|
/**
|
|
361
374
|
* Looks up the Aggregate or Process Manager inbox replay target.
|
|
@@ -436,6 +449,223 @@ const RepositoryCreation = {
|
|
|
436
449
|
throw new TypeError(`Repository onCreate must synchronously return a ${entityType.name} instance.`);
|
|
437
450
|
},
|
|
438
451
|
};
|
|
452
|
+
/**
|
|
453
|
+
* Binds repository queries to a single routing invocation's tenant and lifetime.
|
|
454
|
+
* Routing integration closes the returned access after its callback settles.
|
|
455
|
+
*
|
|
456
|
+
* @internal
|
|
457
|
+
*/
|
|
458
|
+
export const repositoryReadAccess = Object.freeze({
|
|
459
|
+
/**
|
|
460
|
+
* Creates read-only access for a registered repository with an active runtime.
|
|
461
|
+
*
|
|
462
|
+
* @typeParam EntityType The receiving repository's application Entity class.
|
|
463
|
+
* @param repository Receiving repository.
|
|
464
|
+
* @param tenantId Tenant carried by the incoming signal, if multitenant.
|
|
465
|
+
* @returns Bound reads and a close operation for the invocation boundary.
|
|
466
|
+
*/
|
|
467
|
+
bind(repository, tenantId) {
|
|
468
|
+
const runtime = repositoryRuntimes.get(repository);
|
|
469
|
+
if (runtime === undefined)
|
|
470
|
+
throw new Error("Repository reads require an active runtime binding.");
|
|
471
|
+
return new RepositoryReadScope(repository, runtime, tenantId);
|
|
472
|
+
},
|
|
473
|
+
});
|
|
474
|
+
/**
|
|
475
|
+
* Keeps route-local read access alive until an asynchronous callback settles.
|
|
476
|
+
*/
|
|
477
|
+
const RepositoryRoutingReads = {
|
|
478
|
+
/**
|
|
479
|
+
* Invokes routing with the signal tenant and closes its read scope afterward.
|
|
480
|
+
*
|
|
481
|
+
* @typeParam Result Completed route result.
|
|
482
|
+
* @param repository Receiving repository.
|
|
483
|
+
* @param tenantId Signal tenant, when present.
|
|
484
|
+
* @param callback Route computation using read-only repository access.
|
|
485
|
+
* @returns Completed route result.
|
|
486
|
+
*/
|
|
487
|
+
async call(repository, tenantId, callback) {
|
|
488
|
+
const scope = repositoryRuntimes.has(repository)
|
|
489
|
+
? repositoryReadAccess.bind(repository, tenantId)
|
|
490
|
+
: undefined;
|
|
491
|
+
const reads = scope ?? this.unbound();
|
|
492
|
+
try {
|
|
493
|
+
return await callback(reads);
|
|
494
|
+
}
|
|
495
|
+
finally {
|
|
496
|
+
scope?.close();
|
|
497
|
+
}
|
|
498
|
+
},
|
|
499
|
+
/**
|
|
500
|
+
* Rejects reads when a route is invoked before repository runtime binding.
|
|
501
|
+
*
|
|
502
|
+
* @returns Read operations that report the missing runtime.
|
|
503
|
+
*/
|
|
504
|
+
unbound() {
|
|
505
|
+
const unavailable = () => Promise.reject(new Error("Repository reads require an active runtime binding."));
|
|
506
|
+
return { findIds: unavailable, findStates: unavailable, find: unavailable };
|
|
507
|
+
},
|
|
508
|
+
};
|
|
509
|
+
/**
|
|
510
|
+
* Executes read-only queries within one receiving repository and tenant scope.
|
|
511
|
+
*
|
|
512
|
+
* @typeParam EntityType Registered application Entity class.
|
|
513
|
+
*/
|
|
514
|
+
class RepositoryReadScope {
|
|
515
|
+
#repository;
|
|
516
|
+
#runtime;
|
|
517
|
+
#context;
|
|
518
|
+
#active = true;
|
|
519
|
+
/**
|
|
520
|
+
* Captures the runtime and validates the incoming signal's effective tenant.
|
|
521
|
+
*
|
|
522
|
+
* @param repository Receiving repository.
|
|
523
|
+
* @param runtime Active repository runtime.
|
|
524
|
+
* @param tenantId Tenant from the signal, when the context is multitenant.
|
|
525
|
+
*/
|
|
526
|
+
constructor(repository, runtime, tenantId) {
|
|
527
|
+
this.#repository = repository;
|
|
528
|
+
this.#runtime = runtime;
|
|
529
|
+
this.#context = RepositoryTenants.storageContextForTenant(runtime.context, tenantId);
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Reads the typed IDs of all matching current records.
|
|
533
|
+
*
|
|
534
|
+
* @param query Description for the registered state schema.
|
|
535
|
+
* @returns Matching IDs in query order.
|
|
536
|
+
*/
|
|
537
|
+
async findIds(query) {
|
|
538
|
+
return (await this.read(query)).map((entry) => entry.id);
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Returns cloned complete states from matching current records.
|
|
542
|
+
*
|
|
543
|
+
* @param query Description for the registered state schema.
|
|
544
|
+
* @returns Independent generated state messages in query order.
|
|
545
|
+
*/
|
|
546
|
+
async findStates(query) {
|
|
547
|
+
const schema = this.#repository.stateSchema;
|
|
548
|
+
return (await this.read(query)).map((entry) => clone(schema, EntityRecords.unpack(schema, entry.record).state));
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Restores each matching application Entity through the repository hook.
|
|
552
|
+
*
|
|
553
|
+
* @param query Description for the registered state schema.
|
|
554
|
+
* @returns Instances with stored Version and lifecycle flags.
|
|
555
|
+
*/
|
|
556
|
+
async find(query) {
|
|
557
|
+
const repository = this.#repository;
|
|
558
|
+
return (await this.read(query)).map((entry) => {
|
|
559
|
+
const stored = EntityRecords.unpack(repository.stateSchema, entry.record);
|
|
560
|
+
return RepositoryCreation.create(repository, {
|
|
561
|
+
id: entry.id,
|
|
562
|
+
schema: repository.stateSchema,
|
|
563
|
+
state: clone(repository.stateSchema, stored.state),
|
|
564
|
+
version: stored.versionMessage,
|
|
565
|
+
lifecycle: { archived: stored.archived, deleted: stored.deleted },
|
|
566
|
+
});
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Closes this routing invocation's read access to new operations.
|
|
571
|
+
*/
|
|
572
|
+
close() {
|
|
573
|
+
this.#active = false;
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Reads current storage with the complete normalized plan.
|
|
577
|
+
*
|
|
578
|
+
* @param query Description for the registered state schema.
|
|
579
|
+
* @returns Matching stored records and typed IDs.
|
|
580
|
+
*/
|
|
581
|
+
async read(query) {
|
|
582
|
+
if (!this.#active || repositoryRuntimes.get(this.#repository) !== this.#runtime)
|
|
583
|
+
throw new Error("Repository read access has finished.");
|
|
584
|
+
if (query.schema !== this.#repository.stateSchema)
|
|
585
|
+
throw new TypeError("Repository query must select the receiving Entity state schema.");
|
|
586
|
+
const plan = RepositoryReadPredicates.plan(query.buildPlan());
|
|
587
|
+
const storage = RepositoryStorage.openReadEntityStorage(this.#runtime.storageFactory, RepositoryStorage.entityStorageInput(this.#repository, this.#context));
|
|
588
|
+
try {
|
|
589
|
+
return await storage.current.query(plan);
|
|
590
|
+
}
|
|
591
|
+
finally {
|
|
592
|
+
storage.close();
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Applies repository lifecycle defaults only when IDs and lifecycle filters are absent.
|
|
598
|
+
*/
|
|
599
|
+
const RepositoryReadPredicates = {
|
|
600
|
+
/**
|
|
601
|
+
* Builds an exhaustive normalized plan and validates it before provider access.
|
|
602
|
+
*
|
|
603
|
+
* @param compiled Storage-neutral query plan from the shared description.
|
|
604
|
+
* @returns Plan with repository lifecycle selection and explicit application bounds.
|
|
605
|
+
*/
|
|
606
|
+
plan(compiled) {
|
|
607
|
+
const predicate = this.flattenConjunctions(this.withLifecycleDefault(compiled.predicate));
|
|
608
|
+
const plan = {
|
|
609
|
+
predicate,
|
|
610
|
+
...(compiled.order === undefined ? {} : { order: compiled.order }),
|
|
611
|
+
...(compiled.limit === undefined ? {} : { limit: compiled.limit }),
|
|
612
|
+
exhaustive: true,
|
|
613
|
+
};
|
|
614
|
+
StorageQueryPolicy.validate(plan, {
|
|
615
|
+
comparisons: ["equal", "greaterThan", "lessThan", "greaterOrEqual", "lessOrEqual"],
|
|
616
|
+
features: ["either", "nested", "order", "limit"],
|
|
617
|
+
});
|
|
618
|
+
return plan;
|
|
619
|
+
},
|
|
620
|
+
/**
|
|
621
|
+
* Adds active-record filters when no ID or lifecycle clause occurs in the tree.
|
|
622
|
+
*
|
|
623
|
+
* @param predicate Compiled query predicate, when present.
|
|
624
|
+
* @returns Predicate selected for repository reads.
|
|
625
|
+
*/
|
|
626
|
+
withLifecycleDefault(predicate) {
|
|
627
|
+
if (predicate !== undefined && this.explicitSelection(predicate))
|
|
628
|
+
return predicate;
|
|
629
|
+
const active = [
|
|
630
|
+
{ kind: "comparison", column: "archived", operator: "equal", value: false },
|
|
631
|
+
{ kind: "comparison", column: "deleted", operator: "equal", value: false },
|
|
632
|
+
];
|
|
633
|
+
return {
|
|
634
|
+
kind: "all",
|
|
635
|
+
predicates: predicate === undefined ? active : [predicate, ...active],
|
|
636
|
+
};
|
|
637
|
+
},
|
|
638
|
+
/**
|
|
639
|
+
* Normalizes adjacent conjunctions so providers that require flat filters can admit them.
|
|
640
|
+
*
|
|
641
|
+
* @param predicate Compiled predicate after lifecycle selection.
|
|
642
|
+
* @returns Equivalent predicate with no directly nested conjunctions.
|
|
643
|
+
*/
|
|
644
|
+
flattenConjunctions(predicate) {
|
|
645
|
+
if (predicate.kind === "comparison" || predicate.kind === "ids")
|
|
646
|
+
return predicate;
|
|
647
|
+
const children = predicate.predicates.map((child) => this.flattenConjunctions(child));
|
|
648
|
+
return {
|
|
649
|
+
kind: predicate.kind,
|
|
650
|
+
predicates: predicate.kind === "all"
|
|
651
|
+
? children.flatMap((child) => (child.kind === "all" ? child.predicates : [child]))
|
|
652
|
+
: children,
|
|
653
|
+
};
|
|
654
|
+
},
|
|
655
|
+
/**
|
|
656
|
+
* Checks for an explicit ID or lifecycle clause at any predicate depth.
|
|
657
|
+
*
|
|
658
|
+
* @param predicate Compiled predicate to inspect.
|
|
659
|
+
* @returns Whether the application selected IDs or lifecycle state explicitly.
|
|
660
|
+
*/
|
|
661
|
+
explicitSelection(predicate) {
|
|
662
|
+
if (predicate.kind === "ids")
|
|
663
|
+
return true;
|
|
664
|
+
if (predicate.kind === "comparison")
|
|
665
|
+
return predicate.column === "archived" || predicate.column === "deleted";
|
|
666
|
+
return predicate.predicates.some((child) => this.explicitSelection(child));
|
|
667
|
+
},
|
|
668
|
+
};
|
|
439
669
|
/**
|
|
440
670
|
* Loads and persists Aggregate state within one tenant storage context.
|
|
441
671
|
*/
|
|
@@ -512,11 +742,9 @@ class AggregateExecutionSupport {
|
|
|
512
742
|
const lifecycle = RepositoryEntities.repositoryLifecycle(loaded.entity);
|
|
513
743
|
const state = RepositoryEntities.repositoryState(loaded.entity);
|
|
514
744
|
const versionMessage = RepositoryEntities.repositoryVersion(loaded.entity);
|
|
515
|
-
const deferred = await standAccess.deferUpdate(this.#runtime.stand, this.#repository.stateSchema, state, RepositoryStand.standUpdateOptions(this.#storageContext.tenantId, versionMessage, lifecycle));
|
|
745
|
+
const deferred = await standAccess.deferUpdate(this.#runtime.stand, this.#repository.stateSchema, state, RepositoryStand.standUpdateOptions(this.#storageContext.tenantId, versionMessage, lifecycle), this.#repository.metadata);
|
|
516
746
|
try {
|
|
517
|
-
|
|
518
|
-
if (outcome !== "committed")
|
|
519
|
-
throw new Error("Concurrent Aggregate state commit conflict.");
|
|
747
|
+
await this.#commitAggregateRecord(loaded, entityId, events, diagnostics, deferred);
|
|
520
748
|
entityStateHistoryCaches.get(loaded.entity)?.clear();
|
|
521
749
|
}
|
|
522
750
|
catch (error) {
|
|
@@ -547,20 +775,17 @@ class AggregateExecutionSupport {
|
|
|
547
775
|
*
|
|
548
776
|
* @param loaded Loaded Aggregate and commit storage.
|
|
549
777
|
* @param entityId Aggregate identifier.
|
|
550
|
-
* @param state Accepted Aggregate state.
|
|
551
|
-
* @param version Current Spine Version.
|
|
552
|
-
* @param lifecycle Accepted lifecycle flags.
|
|
553
778
|
* @param events Produced Events to retain and publish.
|
|
554
779
|
* @param diagnostics Events to retain in the diagnostic history.
|
|
555
|
-
* @
|
|
780
|
+
* @param deferred Stand update containing the prepared record.
|
|
781
|
+
* @returns Completion after the storage commit.
|
|
556
782
|
*/
|
|
557
|
-
#commitAggregateRecord(loaded, entityId,
|
|
558
|
-
const record =
|
|
783
|
+
#commitAggregateRecord(loaded, entityId, events, diagnostics, deferred) {
|
|
784
|
+
const record = RepositoryStand.preparedRecord(this.#repository, deferred, entityId);
|
|
559
785
|
return loaded.commits.commit({
|
|
560
786
|
context: this.#storageContext,
|
|
561
787
|
entity: loaded.storageInput,
|
|
562
788
|
entityId,
|
|
563
|
-
...(loaded.current === undefined ? {} : { expected: loaded.current }),
|
|
564
789
|
next: record,
|
|
565
790
|
...(RepositoryStorage.historyConfiguration(this.#repository).stateHistory
|
|
566
791
|
? { states: [record] }
|
|
@@ -670,7 +895,7 @@ class AggregateCommandExecution {
|
|
|
670
895
|
*/
|
|
671
896
|
async run(replayedRoute) {
|
|
672
897
|
void RepositorySignals.requireCommandId(this.#command);
|
|
673
|
-
const intake = this.#readIntake(replayedRoute);
|
|
898
|
+
const intake = await this.#readIntake(replayedRoute);
|
|
674
899
|
if (intake === undefined)
|
|
675
900
|
return undefined;
|
|
676
901
|
return this.#runAssignee(intake);
|
|
@@ -681,11 +906,11 @@ class AggregateCommandExecution {
|
|
|
681
906
|
* @param replayedRoute Accepted inbox route, when present.
|
|
682
907
|
* @returns Decoded message and route with an assignee, or `undefined`.
|
|
683
908
|
*/
|
|
684
|
-
#readIntake(replayedRoute) {
|
|
909
|
+
async #readIntake(replayedRoute) {
|
|
685
910
|
const commandMessage = EntityInvocation.requireSignalMessage(this.#command.message, "command");
|
|
686
911
|
const commandSchema = RepositoryRoutes.schemaForTypeUrl(this.#routing.commandSchemas, commandMessage.typeUrl, "command");
|
|
687
912
|
const message = EntityInvocation.unpackRequired(commandMessage, commandSchema, "command");
|
|
688
|
-
const route = replayedRoute ?? this.#repository.routeCommand(this.#command);
|
|
913
|
+
const route = replayedRoute ?? (await this.#repository.routeCommand(this.#command));
|
|
689
914
|
const assignee = this.#routing.commandReadiness?.findCommandAssignee(route.messageFullTypeName);
|
|
690
915
|
return assignee === undefined ? undefined : Object.freeze({ message, route, assignee });
|
|
691
916
|
}
|
|
@@ -1077,7 +1302,7 @@ class ProjectionEventExecution {
|
|
|
1077
1302
|
* @returns Completion after inbox handoff.
|
|
1078
1303
|
*/
|
|
1079
1304
|
async run(acceptedRoute) {
|
|
1080
|
-
const intake = this.#readIntake(acceptedRoute);
|
|
1305
|
+
const intake = await this.#readIntake(acceptedRoute);
|
|
1081
1306
|
if (intake.subscribers.length === 0) {
|
|
1082
1307
|
return;
|
|
1083
1308
|
}
|
|
@@ -1093,7 +1318,7 @@ class ProjectionEventExecution {
|
|
|
1093
1318
|
* @returns Completion after target delivery.
|
|
1094
1319
|
*/
|
|
1095
1320
|
async runTarget(entityId, acceptedRoute) {
|
|
1096
|
-
const intake = this.#readIntake(acceptedRoute);
|
|
1321
|
+
const intake = await this.#readIntake(acceptedRoute);
|
|
1097
1322
|
if (intake.subscribers.length === 0) {
|
|
1098
1323
|
return;
|
|
1099
1324
|
}
|
|
@@ -1106,7 +1331,7 @@ class ProjectionEventExecution {
|
|
|
1106
1331
|
* @returns Completion after all targets receive the Event.
|
|
1107
1332
|
*/
|
|
1108
1333
|
async runDirect(acceptedRoute) {
|
|
1109
|
-
const intake = this.#readIntake(acceptedRoute);
|
|
1334
|
+
const intake = await this.#readIntake(acceptedRoute);
|
|
1110
1335
|
if (intake.subscribers.length === 0) {
|
|
1111
1336
|
return;
|
|
1112
1337
|
}
|
|
@@ -1154,8 +1379,8 @@ class ProjectionEventExecution {
|
|
|
1154
1379
|
* @param acceptedRoute Route accepted before delivery, when present.
|
|
1155
1380
|
* @returns Route and matching subscribers.
|
|
1156
1381
|
*/
|
|
1157
|
-
#readIntake(acceptedRoute) {
|
|
1158
|
-
const route = acceptedRoute ?? this.#repository.routeEvent(this.#event);
|
|
1382
|
+
async #readIntake(acceptedRoute) {
|
|
1383
|
+
const route = acceptedRoute ?? (await this.#repository.routeEvent(this.#event));
|
|
1159
1384
|
const packedMessage = EntityInvocation.requireSignalMessage(this.#event.message, "event");
|
|
1160
1385
|
const eventSchema = RepositoryRoutes.schemaForTypeUrl(this.#routing.eventSchemas, packedMessage.typeUrl, "event");
|
|
1161
1386
|
const message = EntityInvocation.unpackRequired(packedMessage, eventSchema, "event");
|
|
@@ -1182,8 +1407,8 @@ class ProjectionEventExecution {
|
|
|
1182
1407
|
const state = RepositoryEntities.repositoryState(loaded.entity);
|
|
1183
1408
|
const version = RepositoryEntities.repositoryVersion(loaded.entity);
|
|
1184
1409
|
const lifecycle = RepositoryEntities.repositoryLifecycle(loaded.entity);
|
|
1185
|
-
const deferred = await standAccess.deferUpdate(this.#runtime.stand, this.#repository.stateSchema, state, RepositoryStand.standUpdateOptions(tenantOptions.tenantId, version, lifecycle));
|
|
1186
|
-
await this.#commitProjectionOrCancel(loaded, entityId,
|
|
1410
|
+
const deferred = await standAccess.deferUpdate(this.#runtime.stand, this.#repository.stateSchema, state, RepositoryStand.standUpdateOptions(tenantOptions.tenantId, version, lifecycle), this.#repository.metadata);
|
|
1411
|
+
await this.#commitProjectionOrCancel(loaded, entityId, deferred);
|
|
1187
1412
|
this.#notifyProjection(deferred);
|
|
1188
1413
|
this.#publishProjectionChange(loaded, oldState, mode, state, lifecycle, version);
|
|
1189
1414
|
}
|
|
@@ -1192,17 +1417,12 @@ class ProjectionEventExecution {
|
|
|
1192
1417
|
*
|
|
1193
1418
|
* @param loaded Loaded Projection and commit storage.
|
|
1194
1419
|
* @param entityId Projection identifier.
|
|
1195
|
-
* @param state Accepted Projection state.
|
|
1196
|
-
* @param version Current Spine Version.
|
|
1197
|
-
* @param lifecycle Accepted lifecycle flags.
|
|
1198
1420
|
* @param deferred Deferred Stand update to cancel if storage rejects the commit.
|
|
1199
1421
|
* @returns Completion after a successful storage commit.
|
|
1200
1422
|
*/
|
|
1201
|
-
async #commitProjectionOrCancel(loaded, entityId,
|
|
1423
|
+
async #commitProjectionOrCancel(loaded, entityId, deferred) {
|
|
1202
1424
|
try {
|
|
1203
|
-
|
|
1204
|
-
if (outcome !== "committed")
|
|
1205
|
-
throw new Error("Concurrent Projection state commit conflict.");
|
|
1425
|
+
await this.#commitProjectionRecord(loaded, entityId, deferred);
|
|
1206
1426
|
}
|
|
1207
1427
|
catch (error) {
|
|
1208
1428
|
deferred.cancel();
|
|
@@ -1227,18 +1447,15 @@ class ProjectionEventExecution {
|
|
|
1227
1447
|
*
|
|
1228
1448
|
* @param loaded Loaded Projection and commit storage.
|
|
1229
1449
|
* @param entityId Projection identifier.
|
|
1230
|
-
* @param
|
|
1231
|
-
* @
|
|
1232
|
-
* @param lifecycle Accepted lifecycle flags.
|
|
1233
|
-
* @returns The storage commit outcome.
|
|
1450
|
+
* @param deferred Stand update containing the prepared record.
|
|
1451
|
+
* @returns Completion after the storage commit.
|
|
1234
1452
|
*/
|
|
1235
|
-
#commitProjectionRecord(loaded, entityId,
|
|
1236
|
-
const record =
|
|
1453
|
+
#commitProjectionRecord(loaded, entityId, deferred) {
|
|
1454
|
+
const record = RepositoryStand.preparedRecord(this.#repository, deferred, entityId);
|
|
1237
1455
|
return loaded.commits.commit({
|
|
1238
1456
|
context: loaded.storageInput.context,
|
|
1239
1457
|
entity: loaded.storageInput,
|
|
1240
1458
|
entityId,
|
|
1241
|
-
...(loaded.current === undefined ? {} : { expected: loaded.current }),
|
|
1242
1459
|
next: record,
|
|
1243
1460
|
...(RepositoryStorage.historyConfiguration(this.#repository).stateHistory
|
|
1244
1461
|
? { states: [record] }
|
|
@@ -1362,13 +1579,38 @@ const ProcessManagerQueries = Object.freeze({
|
|
|
1362
1579
|
const context = actorContext === undefined
|
|
1363
1580
|
? create(ActorContextSchema)
|
|
1364
1581
|
: clone(ActorContextSchema, actorContext);
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1582
|
+
const effectiveTenant = EffectiveTenants.current(runtime.context.multitenant, tenantId);
|
|
1583
|
+
context.tenantId = effectiveTenant;
|
|
1584
|
+
return processManagerQueryAccess.bind(entity, (plan, schema, query) => ProcessManagerQueries.read(runtime, effectiveTenant, plan, schema, query), context);
|
|
1585
|
+
},
|
|
1586
|
+
/**
|
|
1587
|
+
* Routes a handler read to the registered state target before reaching storage.
|
|
1588
|
+
*
|
|
1589
|
+
* @typeParam Schema State schema supplied by the handler.
|
|
1590
|
+
* @param runtime Current context services and local Stand.
|
|
1591
|
+
* @param tenantId Effective tenant of the current operation.
|
|
1592
|
+
* @param plan Compiled read criteria.
|
|
1593
|
+
* @param schema State schema supplied by the handler.
|
|
1594
|
+
* @param query Wire query carrying the actor and requested fields.
|
|
1595
|
+
* @returns Detached state snapshots decoded with the caller's schema.
|
|
1596
|
+
*/
|
|
1597
|
+
async read(runtime, tenantId, plan, schema, query) {
|
|
1598
|
+
const routes = RegisteredTargets.forStand(runtime.stand);
|
|
1599
|
+
const typeUrl = TypeUrls.derive(schema);
|
|
1600
|
+
const target = routes?.find(typeUrl);
|
|
1601
|
+
if (routes !== undefined && target === undefined)
|
|
1602
|
+
throw new Error(`No bounded context registered query target "${typeUrl}".`);
|
|
1603
|
+
if (target !== undefined && target.context.stand() !== runtime.stand) {
|
|
1604
|
+
const visibility = target.repository.metadata.visibility;
|
|
1605
|
+
if (visibility !== "query" && visibility !== "full")
|
|
1606
|
+
throw new Error(`Query target "${typeUrl}" does not have query visibility.`);
|
|
1607
|
+
}
|
|
1608
|
+
const registeredSchema = target?.repository.stateSchema ?? schema;
|
|
1609
|
+
if (target !== undefined)
|
|
1610
|
+
routes?.validate(target, plan);
|
|
1611
|
+
const destination = EffectiveTenants.destination(tenantId, target?.context.isMultitenant ?? runtime.context.multitenant);
|
|
1612
|
+
const results = await QueryReader.read(target?.context.stand() ?? runtime.stand, registeredSchema, plan, destination, 10_000, query);
|
|
1613
|
+
return Object.freeze(results.map((result) => clone(schema, result.state)));
|
|
1372
1614
|
},
|
|
1373
1615
|
});
|
|
1374
1616
|
/**
|
|
@@ -1433,8 +1675,8 @@ class ProcessManagerExecutionSupport {
|
|
|
1433
1675
|
const entityId = loaded.entity.id;
|
|
1434
1676
|
const state = RepositoryEntities.repositoryState(loaded.entity);
|
|
1435
1677
|
const lifecycle = RepositoryEntities.repositoryLifecycle(loaded.entity);
|
|
1436
|
-
const deferred = await standAccess.deferUpdate(this.#runtime.stand, this.#repository.stateSchema, state, RepositoryStand.standUpdateOptions(options.tenantId, version, lifecycle));
|
|
1437
|
-
await this.#storeProcessManagerRecord(loaded, entityId,
|
|
1678
|
+
const deferred = await standAccess.deferUpdate(this.#runtime.stand, this.#repository.stateSchema, state, RepositoryStand.standUpdateOptions(options.tenantId, version, lifecycle), this.#repository.metadata);
|
|
1679
|
+
await this.#storeProcessManagerRecord(loaded, entityId, events, deferred);
|
|
1438
1680
|
this.#notifyProcessManager(deferred, events);
|
|
1439
1681
|
return true;
|
|
1440
1682
|
}
|
|
@@ -1456,19 +1698,13 @@ class ProcessManagerExecutionSupport {
|
|
|
1456
1698
|
*
|
|
1457
1699
|
* @param loaded Process Manager and commit storage.
|
|
1458
1700
|
* @param entityId Process Manager identifier.
|
|
1459
|
-
* @param state Accepted Process Manager state.
|
|
1460
|
-
* @param version Current Spine Version.
|
|
1461
|
-
* @param lifecycle Accepted lifecycle flags.
|
|
1462
1701
|
* @param events Diagnostic Events to retain when configured.
|
|
1463
1702
|
* @param deferred Stand update to notify or cancel.
|
|
1464
1703
|
* @returns Completion after durable persistence.
|
|
1465
1704
|
*/
|
|
1466
|
-
async #storeProcessManagerRecord(loaded, entityId,
|
|
1705
|
+
async #storeProcessManagerRecord(loaded, entityId, events, deferred) {
|
|
1467
1706
|
try {
|
|
1468
|
-
|
|
1469
|
-
if (outcome !== "committed") {
|
|
1470
|
-
throw new Error("Concurrent Process Manager state commit conflict.");
|
|
1471
|
-
}
|
|
1707
|
+
await this.#commitProcessManagerRecord(loaded, entityId, events, deferred);
|
|
1472
1708
|
entityStateHistoryCaches.get(loaded.entity)?.clear();
|
|
1473
1709
|
}
|
|
1474
1710
|
catch (error) {
|
|
@@ -1481,20 +1717,17 @@ class ProcessManagerExecutionSupport {
|
|
|
1481
1717
|
*
|
|
1482
1718
|
* @param loaded Loaded Process Manager and commit storage.
|
|
1483
1719
|
* @param entityId Process Manager identifier.
|
|
1484
|
-
* @param state Accepted Process Manager state.
|
|
1485
|
-
* @param version Current Spine Version.
|
|
1486
|
-
* @param lifecycle Accepted lifecycle flags.
|
|
1487
1720
|
* @param events Diagnostic Events to retain when configured.
|
|
1488
|
-
* @
|
|
1721
|
+
* @param deferred Stand update containing the prepared record.
|
|
1722
|
+
* @returns Completion after the storage commit.
|
|
1489
1723
|
*/
|
|
1490
|
-
#commitProcessManagerRecord(loaded, entityId,
|
|
1724
|
+
#commitProcessManagerRecord(loaded, entityId, events, deferred) {
|
|
1491
1725
|
const history = RepositoryStorage.historyConfiguration(this.#repository);
|
|
1492
|
-
const record =
|
|
1726
|
+
const record = RepositoryStand.preparedRecord(this.#repository, deferred, entityId);
|
|
1493
1727
|
return loaded.commits.commit({
|
|
1494
1728
|
context: loaded.storageInput.context,
|
|
1495
1729
|
entity: loaded.storageInput,
|
|
1496
1730
|
entityId,
|
|
1497
|
-
...(loaded.current === undefined ? {} : { expected: loaded.current }),
|
|
1498
1731
|
next: record,
|
|
1499
1732
|
...(history.stateHistory ? { states: [record] } : {}),
|
|
1500
1733
|
...(history.processManagerEventHistory
|
|
@@ -1549,7 +1782,7 @@ class ProcessManagerCommandExecution {
|
|
|
1549
1782
|
*/
|
|
1550
1783
|
async run(replayedRoute) {
|
|
1551
1784
|
RepositorySignals.requireCommandId(this.#command);
|
|
1552
|
-
const intake = this.#readIntake(replayedRoute);
|
|
1785
|
+
const intake = await this.#readIntake(replayedRoute);
|
|
1553
1786
|
if (intake === undefined)
|
|
1554
1787
|
return undefined;
|
|
1555
1788
|
const tenantOptions = RepositoryTenants.commandStandOptions(this.#runtime.context, this.#command);
|
|
@@ -1580,11 +1813,11 @@ class ProcessManagerCommandExecution {
|
|
|
1580
1813
|
* @param replayedRoute Accepted inbox route, when present.
|
|
1581
1814
|
* @returns Decoded Command, route, and assignee, or `undefined`.
|
|
1582
1815
|
*/
|
|
1583
|
-
#readIntake(replayedRoute) {
|
|
1816
|
+
async #readIntake(replayedRoute) {
|
|
1584
1817
|
const commandMessage = EntityInvocation.requireSignalMessage(this.#command.message, "command");
|
|
1585
1818
|
const commandSchema = RepositoryRoutes.schemaForTypeUrl(this.#routing.commandSchemas, commandMessage.typeUrl, "command");
|
|
1586
1819
|
const message = EntityInvocation.unpackRequired(commandMessage, commandSchema, "command");
|
|
1587
|
-
const route = replayedRoute ?? this.#repository.routeCommand(this.#command);
|
|
1820
|
+
const route = replayedRoute ?? (await this.#repository.routeCommand(this.#command));
|
|
1588
1821
|
const assignee = this.#routing.commandReadiness?.findCommandAssignee(route.messageFullTypeName);
|
|
1589
1822
|
return assignee === undefined ? undefined : Object.freeze({ assignee, message, route });
|
|
1590
1823
|
}
|
|
@@ -2612,7 +2845,7 @@ const RepositorySignals = {
|
|
|
2612
2845
|
* @returns New Event context with producer metadata.
|
|
2613
2846
|
*/
|
|
2614
2847
|
eventContextWithProducer(context, repository, entityId, version) {
|
|
2615
|
-
const producerId = EntityIds.
|
|
2848
|
+
const producerId = EntityIds.packField(repository.idField.descriptor, entityId);
|
|
2616
2849
|
return create(EventContextSchema, {
|
|
2617
2850
|
...context,
|
|
2618
2851
|
producerId,
|
|
@@ -2953,7 +3186,7 @@ class EntityStateChangePublishing {
|
|
|
2953
3186
|
* @returns Packed identifier.
|
|
2954
3187
|
*/
|
|
2955
3188
|
#packEntityId(repository, entityId) {
|
|
2956
|
-
return EntityIds.
|
|
3189
|
+
return EntityIds.packField(repository.idField.descriptor, entityId);
|
|
2957
3190
|
}
|
|
2958
3191
|
}
|
|
2959
3192
|
const EntityStateChangePublisher = Object.freeze(new EntityStateChangePublishing());
|
|
@@ -3079,7 +3312,7 @@ class HandlerDispatchPublishing {
|
|
|
3079
3312
|
* @returns Packed identifier.
|
|
3080
3313
|
*/
|
|
3081
3314
|
#packEntityId(repository, entityId) {
|
|
3082
|
-
return EntityIds.
|
|
3315
|
+
return EntityIds.packField(repository.idField.descriptor, entityId);
|
|
3083
3316
|
}
|
|
3084
3317
|
/**
|
|
3085
3318
|
* Packs a subscriber or reactor dispatch diagnostic message.
|
|
@@ -3112,8 +3345,23 @@ const HandlerDispatchPublisher = Object.freeze(new HandlerDispatchPublishing());
|
|
|
3112
3345
|
Object.freeze(RepositorySignals);
|
|
3113
3346
|
/**
|
|
3114
3347
|
* Internal repository stand operations.
|
|
3348
|
+
* @internal
|
|
3115
3349
|
*/
|
|
3116
|
-
const RepositoryStand = {
|
|
3350
|
+
export const RepositoryStand = {
|
|
3351
|
+
/**
|
|
3352
|
+
* Returns the validated Stand snapshot with the authoritative routed ID.
|
|
3353
|
+
*
|
|
3354
|
+
* @param repository Supplies the state schema and validated ID descriptor.
|
|
3355
|
+
* @param deferred Contains the prepared Stand record.
|
|
3356
|
+
* @param entityId Authoritative routed Entity identifier.
|
|
3357
|
+
* @returns An isolated prepared record with the authoritative packed ID.
|
|
3358
|
+
*/
|
|
3359
|
+
preparedRecord(repository, deferred, entityId) {
|
|
3360
|
+
const expectedId = EntityIds.packField(repository.idField.descriptor, entityId);
|
|
3361
|
+
const record = clone(EntityRecordSchema, deferred.record);
|
|
3362
|
+
record.entityId = expectedId;
|
|
3363
|
+
return record;
|
|
3364
|
+
},
|
|
3117
3365
|
/**
|
|
3118
3366
|
* Builds tenant, Version, and lifecycle options for a Stand update.
|
|
3119
3367
|
*
|
|
@@ -3242,16 +3490,16 @@ const RepositoryTenants = {
|
|
|
3242
3490
|
* @returns Tenant-aware storage context.
|
|
3243
3491
|
*/
|
|
3244
3492
|
storageContextForCommand(context, command) {
|
|
3245
|
-
if (!context.multitenant) {
|
|
3246
|
-
return Object.freeze({ name: context.name, multitenant: false });
|
|
3247
|
-
}
|
|
3248
3493
|
const tenantId = RepositoryTenants.readCommandTenant(command);
|
|
3249
|
-
if (tenantId === undefined)
|
|
3494
|
+
if (context.multitenant && tenantId === undefined)
|
|
3250
3495
|
throw new Error(`Multitenant command for "${context.name}" requires tenantId.`);
|
|
3496
|
+
const effective = EffectiveTenants.current(context.multitenant, tenantId);
|
|
3497
|
+
if (!context.multitenant)
|
|
3498
|
+
return Object.freeze({ name: context.name, multitenant: false });
|
|
3251
3499
|
return Object.freeze({
|
|
3252
3500
|
name: context.name,
|
|
3253
3501
|
multitenant: true,
|
|
3254
|
-
tenantId:
|
|
3502
|
+
tenantId: effective,
|
|
3255
3503
|
});
|
|
3256
3504
|
},
|
|
3257
3505
|
/**
|
|
@@ -3262,16 +3510,16 @@ const RepositoryTenants = {
|
|
|
3262
3510
|
* @returns Tenant-aware storage context.
|
|
3263
3511
|
*/
|
|
3264
3512
|
storageContextForEvent(context, event) {
|
|
3265
|
-
if (!context.multitenant) {
|
|
3266
|
-
return Object.freeze({ name: context.name, multitenant: false });
|
|
3267
|
-
}
|
|
3268
3513
|
const tenantId = RepositoryTenants.readEventTenant(event);
|
|
3269
|
-
if (tenantId === undefined)
|
|
3514
|
+
if (context.multitenant && tenantId === undefined)
|
|
3270
3515
|
throw new Error(`Multitenant event for "${context.name}" requires tenantId.`);
|
|
3516
|
+
const effective = EffectiveTenants.current(context.multitenant, tenantId);
|
|
3517
|
+
if (!context.multitenant)
|
|
3518
|
+
return Object.freeze({ name: context.name, multitenant: false });
|
|
3271
3519
|
return Object.freeze({
|
|
3272
3520
|
name: context.name,
|
|
3273
3521
|
multitenant: true,
|
|
3274
|
-
tenantId:
|
|
3522
|
+
tenantId: effective,
|
|
3275
3523
|
});
|
|
3276
3524
|
},
|
|
3277
3525
|
/**
|
|
@@ -3282,14 +3530,15 @@ const RepositoryTenants = {
|
|
|
3282
3530
|
* @returns Tenant-aware storage context.
|
|
3283
3531
|
*/
|
|
3284
3532
|
storageContextForTenant(context, tenantId) {
|
|
3533
|
+
if (context.multitenant && tenantId === undefined)
|
|
3534
|
+
throw new Error(`Multitenant storage for "${context.name}" requires tenantId.`);
|
|
3535
|
+
const effective = EffectiveTenants.current(context.multitenant, tenantId);
|
|
3285
3536
|
if (!context.multitenant)
|
|
3286
3537
|
return Object.freeze({ name: context.name, multitenant: false });
|
|
3287
|
-
if (tenantId === undefined)
|
|
3288
|
-
throw new Error(`Multitenant storage for "${context.name}" requires tenantId.`);
|
|
3289
3538
|
return Object.freeze({
|
|
3290
3539
|
name: context.name,
|
|
3291
3540
|
multitenant: true,
|
|
3292
|
-
tenantId:
|
|
3541
|
+
tenantId: effective,
|
|
3293
3542
|
});
|
|
3294
3543
|
},
|
|
3295
3544
|
/**
|
|
@@ -3548,6 +3797,29 @@ Object.freeze(RepositoryHandlers);
|
|
|
3548
3797
|
* Internal repository routes operations.
|
|
3549
3798
|
*/
|
|
3550
3799
|
const RepositoryRoutes = {
|
|
3800
|
+
/**
|
|
3801
|
+
* Checks every class-aware route declaration against its receiving Entity.
|
|
3802
|
+
*
|
|
3803
|
+
* @param registered Entity constructor registered by the repository.
|
|
3804
|
+
* @param routes Route declaration options selected for the repository.
|
|
3805
|
+
*/
|
|
3806
|
+
requireRoutingEntities(registered, routes) {
|
|
3807
|
+
RepositoryRoutes.requireRoutingEntity(registered, CommandRoutingInternals.entityType(routes.commandRouting), "Command");
|
|
3808
|
+
RepositoryRoutes.requireRoutingEntity(registered, EventRoutingInternals.entityType(routes.eventRouting), "Event");
|
|
3809
|
+
RepositoryRoutes.requireRoutingEntity(registered, StateUpdateRoutingInternals.entityType(routes.stateUpdateRouting), "State-update");
|
|
3810
|
+
},
|
|
3811
|
+
/**
|
|
3812
|
+
* Rejects class-aware declarations for a different registered Entity constructor.
|
|
3813
|
+
*
|
|
3814
|
+
* @param registered Entity constructor registered by the repository.
|
|
3815
|
+
* @param selected Constructor selected by class-aware declarations, if any.
|
|
3816
|
+
* @param kind Command, Event, or state-update route kind for diagnostics.
|
|
3817
|
+
*/
|
|
3818
|
+
requireRoutingEntity(registered, selected, kind) {
|
|
3819
|
+
if (selected !== undefined && selected !== registered) {
|
|
3820
|
+
throw new TypeError(`${kind} routing Entity class "${selected.name}" does not match "${registered.name}".`);
|
|
3821
|
+
}
|
|
3822
|
+
},
|
|
3551
3823
|
/**
|
|
3552
3824
|
* Builds immutable routing from an Entity constructor and its declarations.
|
|
3553
3825
|
*
|
|
@@ -3816,7 +4088,7 @@ const RepositoryRoutes = {
|
|
|
3816
4088
|
* @returns Function that routes one Command.
|
|
3817
4089
|
*/
|
|
3818
4090
|
commandSelector(schemas, readiness, routes, idField) {
|
|
3819
|
-
return (command) => RepositoryRoutes.routeCommand(command, readiness.command, schemas.command, idField, routes.command);
|
|
4091
|
+
return (command, reads) => RepositoryRoutes.routeCommand(command, readiness.command, schemas.command, idField, routes.command, reads);
|
|
3820
4092
|
},
|
|
3821
4093
|
/**
|
|
3822
4094
|
* Builds an Event route function from readiness and custom declarations.
|
|
@@ -3830,7 +4102,7 @@ const RepositoryRoutes = {
|
|
|
3830
4102
|
* @returns Function that routes one Event.
|
|
3831
4103
|
*/
|
|
3832
4104
|
eventSelector(schemas, readiness, filters, routes, idField) {
|
|
3833
|
-
return (event) => RepositoryRoutes.routeEvent(event, readiness.event, filters.commandReactionMap, schemas.event, idField, routes.event);
|
|
4105
|
+
return (event, reads) => RepositoryRoutes.routeEvent(event, readiness.event, filters.commandReactionMap, schemas.event, idField, routes.event, reads);
|
|
3834
4106
|
},
|
|
3835
4107
|
/**
|
|
3836
4108
|
* Builds a state-update route function for Projection subscriptions.
|
|
@@ -3843,7 +4115,7 @@ const RepositoryRoutes = {
|
|
|
3843
4115
|
* @returns Function that routes one state-change Event.
|
|
3844
4116
|
*/
|
|
3845
4117
|
stateSelector(schemas, subscriptions, routes, idField) {
|
|
3846
|
-
return (event) => RepositoryRoutes.routeStateUpdate(event, schemas.state, subscriptions, idField, routes.state);
|
|
4118
|
+
return (event, reads) => RepositoryRoutes.routeStateUpdate(event, schemas.state, subscriptions, idField, routes.state, reads);
|
|
3847
4119
|
},
|
|
3848
4120
|
/**
|
|
3849
4121
|
* Routes a Command to its registered assignee and target Entity ID.
|
|
@@ -3854,9 +4126,10 @@ const RepositoryRoutes = {
|
|
|
3854
4126
|
* @param schemas Accepted Command schemas.
|
|
3855
4127
|
* @param targetIdField Canonical target Entity ID field.
|
|
3856
4128
|
* @param commandRoutes Custom Command routes by schema.
|
|
4129
|
+
* @param reads Query access for this routing invocation.
|
|
3857
4130
|
* @returns Deferred Command route for one target.
|
|
3858
4131
|
*/
|
|
3859
|
-
routeCommand(command, readiness, schemas, targetIdField, commandRoutes) {
|
|
4132
|
+
async routeCommand(command, readiness, schemas, targetIdField, commandRoutes, reads) {
|
|
3860
4133
|
const message = command.message;
|
|
3861
4134
|
if (message === undefined || message.typeUrl === "") {
|
|
3862
4135
|
throw new Error("Repository command routing requires command.message.typeUrl.");
|
|
@@ -3866,10 +4139,10 @@ const RepositoryRoutes = {
|
|
|
3866
4139
|
if (assignee === undefined) {
|
|
3867
4140
|
throw new Error(`Repository command routing has no assignee for "${schema.typeName}".`);
|
|
3868
4141
|
}
|
|
3869
|
-
const
|
|
3870
|
-
const candidateId =
|
|
4142
|
+
const route = commandRoutes.get(schema);
|
|
4143
|
+
const candidateId = route === undefined
|
|
3871
4144
|
? RepositoryRoutes.readFirstFieldId(message, schema, "command")
|
|
3872
|
-
: RepositoryRoutes.callCommandRoute(
|
|
4145
|
+
: await RepositoryRoutes.callCommandRoute(route, message, schema, command.context, reads);
|
|
3873
4146
|
return Object.freeze({
|
|
3874
4147
|
entityId: RepositoryRoutes.readRouteId(candidateId, targetIdField, "command"),
|
|
3875
4148
|
messageFullTypeName: schema.typeName,
|
|
@@ -3902,14 +4175,15 @@ const RepositoryRoutes = {
|
|
|
3902
4175
|
* @param message Packed Command payload.
|
|
3903
4176
|
* @param schema Registered Command schema.
|
|
3904
4177
|
* @param context Source Command context, when present.
|
|
4178
|
+
* @param reads Query access for this routing invocation.
|
|
3905
4179
|
* @returns Candidate target Entity ID.
|
|
3906
4180
|
*/
|
|
3907
|
-
callCommandRoute(route, message, schema, context) {
|
|
4181
|
+
async callCommandRoute(route, message, schema, context, reads) {
|
|
3908
4182
|
const unpacked = AnyMessages.unpack(message, schema);
|
|
3909
4183
|
if (unpacked === undefined) {
|
|
3910
4184
|
throw new Error("Repository command routing requires a readable Command message.");
|
|
3911
4185
|
}
|
|
3912
|
-
return route(unpacked, context ?? create(CommandContextSchema));
|
|
4186
|
+
return await route(unpacked, context ?? create(CommandContextSchema), reads);
|
|
3913
4187
|
},
|
|
3914
4188
|
/**
|
|
3915
4189
|
* Routes an Event to interested Entity IDs using custom or default routing.
|
|
@@ -3921,19 +4195,20 @@ const RepositoryRoutes = {
|
|
|
3921
4195
|
* @param schemas Accepted Event schemas.
|
|
3922
4196
|
* @param targetIdField Canonical target Entity ID field.
|
|
3923
4197
|
* @param eventRoutes Custom Event routes by schema.
|
|
4198
|
+
* @param reads Query access for this routing invocation.
|
|
3924
4199
|
* @returns Deferred Event route with target IDs.
|
|
3925
4200
|
*/
|
|
3926
|
-
routeEvent(event, readiness, commandReactions, schemas, targetIdField, eventRoutes) {
|
|
4201
|
+
async routeEvent(event, readiness, commandReactions, schemas, targetIdField, eventRoutes, reads) {
|
|
3927
4202
|
const message = event.message;
|
|
3928
4203
|
if (message === undefined || message.typeUrl === "")
|
|
3929
4204
|
throw new Error("Repository event routing requires event.message.typeUrl.");
|
|
3930
4205
|
const schema = RepositoryRoutes.schemaForTypeUrl(schemas, message.typeUrl, "event");
|
|
3931
4206
|
if (!RepositoryRoutes.hasEventReceiver(schema.typeName, commandReactions, readiness))
|
|
3932
4207
|
throw new Error(`Repository event routing has no receiver for "${schema.typeName}".`);
|
|
3933
|
-
const
|
|
3934
|
-
const entityIds =
|
|
4208
|
+
const route = eventRoutes.get(schema);
|
|
4209
|
+
const entityIds = route === undefined
|
|
3935
4210
|
? [RepositoryRoutes.readEventEntityId(event, message, schema, targetIdField)]
|
|
3936
|
-
: RepositoryRoutes.callEventRoute(
|
|
4211
|
+
: await RepositoryRoutes.callEventRoute(route, message, schema, event.context, targetIdField, reads);
|
|
3937
4212
|
return Object.freeze({
|
|
3938
4213
|
entityIds: Object.freeze([...entityIds]),
|
|
3939
4214
|
messageFullTypeName: schema.typeName,
|
|
@@ -4022,19 +4297,21 @@ const RepositoryRoutes = {
|
|
|
4022
4297
|
* @param subscriptions State subscribers by source type.
|
|
4023
4298
|
* @param targetIdField Canonical Projection ID field.
|
|
4024
4299
|
* @param routes Custom state-update routes by schema.
|
|
4300
|
+
* @param reads Query access for this routing invocation.
|
|
4025
4301
|
* @returns Route and decoded state, or `undefined` when uninterested.
|
|
4026
4302
|
*/
|
|
4027
|
-
routeStateUpdate(event, schemas, subscriptions, targetIdField, routes) {
|
|
4028
|
-
const
|
|
4303
|
+
async routeStateUpdate(event, schemas, subscriptions, targetIdField, routes, reads) {
|
|
4304
|
+
const operation = "Repository state-update routing";
|
|
4305
|
+
const update = RepositoryRoutes.decodeStateUpdate(event, schemas, operation);
|
|
4029
4306
|
const candidates = subscriptions.get(update?.schema.typeName ?? "") ?? [];
|
|
4030
4307
|
const interested = RepositoryRoutes.originSubscribers(candidates, event);
|
|
4031
4308
|
if (update === undefined || interested.length === 0)
|
|
4032
4309
|
return undefined;
|
|
4033
4310
|
const { schema, state } = update;
|
|
4034
|
-
const
|
|
4035
|
-
const candidateIds =
|
|
4311
|
+
const route = routes.get(schema);
|
|
4312
|
+
const candidateIds = route === undefined
|
|
4036
4313
|
? [RepositoryRoutes.firstCompatibleId(state, schema, targetIdField, "state update")]
|
|
4037
|
-
: RepositoryRoutes.callStateUpdateRoute(
|
|
4314
|
+
: await RepositoryRoutes.callStateUpdateRoute(route, state, event.context, targetIdField, reads);
|
|
4038
4315
|
return Object.freeze({
|
|
4039
4316
|
entityIds: Object.freeze([...candidateIds]),
|
|
4040
4317
|
messageFullTypeName: schema.typeName,
|
|
@@ -4087,14 +4364,13 @@ const RepositoryRoutes = {
|
|
|
4087
4364
|
* @param state Decoded source Entity state.
|
|
4088
4365
|
* @param context Source Event context, when present.
|
|
4089
4366
|
* @param targetIdField Canonical Projection ID field.
|
|
4367
|
+
* @param reads Query access for this routing invocation.
|
|
4090
4368
|
* @returns Validated Projection target IDs.
|
|
4091
4369
|
*/
|
|
4092
|
-
callStateUpdateRoute(route, state, context, targetIdField) {
|
|
4093
|
-
const candidates = route(state, context ?? create(EventContextSchema));
|
|
4370
|
+
async callStateUpdateRoute(route, state, context, targetIdField, reads) {
|
|
4371
|
+
const candidates = await route(state, context ?? create(EventContextSchema), reads);
|
|
4094
4372
|
if (!Array.isArray(candidates))
|
|
4095
4373
|
throw new Error("Repository state-update routing requires an array of Entity IDs.");
|
|
4096
|
-
if (candidates.length > 1_000)
|
|
4097
|
-
throw new Error("Repository state-update routing accepts at most 1,000 Entity IDs.");
|
|
4098
4374
|
const unique = new Map();
|
|
4099
4375
|
for (const candidate of candidates) {
|
|
4100
4376
|
const id = RepositoryRoutes.readRouteId(candidate, targetIdField, "state update");
|
|
@@ -4102,7 +4378,8 @@ const RepositoryRoutes = {
|
|
|
4102
4378
|
if (!unique.has(key))
|
|
4103
4379
|
unique.set(key, structuredClone(id));
|
|
4104
4380
|
}
|
|
4105
|
-
|
|
4381
|
+
const recipients = Object.freeze([...unique.values()]);
|
|
4382
|
+
return recipients;
|
|
4106
4383
|
},
|
|
4107
4384
|
/**
|
|
4108
4385
|
* Calls a custom Event route and validates its target IDs.
|
|
@@ -4113,20 +4390,18 @@ const RepositoryRoutes = {
|
|
|
4113
4390
|
* @param schema Registered Event schema.
|
|
4114
4391
|
* @param context Source Event context, when present.
|
|
4115
4392
|
* @param targetIdField Canonical target Entity ID field.
|
|
4393
|
+
* @param reads Query access for this routing invocation.
|
|
4116
4394
|
* @returns Validated target Entity IDs.
|
|
4117
4395
|
*/
|
|
4118
|
-
callEventRoute(route, message, schema, context, targetIdField) {
|
|
4396
|
+
async callEventRoute(route, message, schema, context, targetIdField, reads) {
|
|
4119
4397
|
const unpacked = AnyMessages.unpack(message, schema);
|
|
4120
4398
|
if (unpacked === undefined) {
|
|
4121
4399
|
throw new Error("Repository event routing requires a readable Event message.");
|
|
4122
4400
|
}
|
|
4123
|
-
const candidates = route(unpacked, context ?? create(EventContextSchema));
|
|
4401
|
+
const candidates = await route(unpacked, context ?? create(EventContextSchema), reads);
|
|
4124
4402
|
if (!Array.isArray(candidates)) {
|
|
4125
4403
|
throw new Error("Repository event routing requires an array of Entity IDs.");
|
|
4126
4404
|
}
|
|
4127
|
-
if (candidates.length > 1_000) {
|
|
4128
|
-
throw new Error("Repository event routing accepts at most 1,000 Entity IDs.");
|
|
4129
|
-
}
|
|
4130
4405
|
const unique = new Map();
|
|
4131
4406
|
for (const candidate of candidates) {
|
|
4132
4407
|
const id = RepositoryRoutes.readRouteId(candidate, targetIdField, "event");
|
|
@@ -4134,7 +4409,20 @@ const RepositoryRoutes = {
|
|
|
4134
4409
|
if (!unique.has(key))
|
|
4135
4410
|
unique.set(key, structuredClone(id));
|
|
4136
4411
|
}
|
|
4137
|
-
|
|
4412
|
+
const recipients = Object.freeze([...unique.values()]);
|
|
4413
|
+
return recipients;
|
|
4414
|
+
},
|
|
4415
|
+
/**
|
|
4416
|
+
* Records a warning for a large final route without changing its recipients.
|
|
4417
|
+
*
|
|
4418
|
+
* @param signal Signal type selected by the route.
|
|
4419
|
+
* @param repository Receiving repository state type.
|
|
4420
|
+
* @param count Distinct validated recipient count.
|
|
4421
|
+
*/
|
|
4422
|
+
warnLargeRoute(signal, repository, count) {
|
|
4423
|
+
if (count <= 1_000)
|
|
4424
|
+
return;
|
|
4425
|
+
emitServerWarning(serverEnvironmentAccess.loggerFor(ServerEnvironment.instance()), `Routing ${signal} to ${String(count)} recipients in repository ${repository}.`, { operation: "repository.route", count, entityType: repository });
|
|
4138
4426
|
},
|
|
4139
4427
|
/**
|
|
4140
4428
|
* Finds a registered message schema by its type URL.
|
|
@@ -4405,6 +4693,21 @@ Object.freeze(RepositoryRoutes);
|
|
|
4405
4693
|
* Internal repository storage operations.
|
|
4406
4694
|
*/
|
|
4407
4695
|
const RepositoryStorage = {
|
|
4696
|
+
/**
|
|
4697
|
+
* Opens provider Entity storage for one read scope without a commit handle.
|
|
4698
|
+
*
|
|
4699
|
+
* @typeParam I Entity identifier type.
|
|
4700
|
+
* @typeParam S Generated Entity state type.
|
|
4701
|
+
* @param factory Provider storage factory.
|
|
4702
|
+
* @param input Entity storage location and schema.
|
|
4703
|
+
* @returns The independently closeable Entity storage ports.
|
|
4704
|
+
*/
|
|
4705
|
+
openReadEntityStorage(factory, input) {
|
|
4706
|
+
const candidate = factory;
|
|
4707
|
+
if (candidate.createEntityStorage === undefined)
|
|
4708
|
+
throw new Error("StorageFactory does not provide Entity storage for repository reads.");
|
|
4709
|
+
return candidate.createEntityStorage(input);
|
|
4710
|
+
},
|
|
4408
4711
|
/**
|
|
4409
4712
|
* Opens Entity storage and an atomic commit port from a storage factory.
|
|
4410
4713
|
*
|
|
@@ -4441,9 +4744,9 @@ const RepositoryStorage = {
|
|
|
4441
4744
|
* @returns Shared Entity storage handle.
|
|
4442
4745
|
*/
|
|
4443
4746
|
openRepositoryEntityStorage(repository, factory, input) {
|
|
4444
|
-
const handle = RepositoryStorage.openEntityStorage(factory, input);
|
|
4445
4747
|
const key = JSON.stringify({ context: input.context, state: input.sourceType.typeName });
|
|
4446
4748
|
let handles = repositoryEntityHandles.get(repository);
|
|
4749
|
+
const handle = RepositoryStorage.openEntityStorage(factory, input);
|
|
4447
4750
|
if (handles === undefined) {
|
|
4448
4751
|
handles = new Map();
|
|
4449
4752
|
repositoryEntityHandles.set(repository, handles);
|
|
@@ -4464,7 +4767,7 @@ const RepositoryStorage = {
|
|
|
4464
4767
|
* @returns Entity storage description for the provider.
|
|
4465
4768
|
*/
|
|
4466
4769
|
entityStorageInput(repository, context) {
|
|
4467
|
-
const descriptor = entityStorageDescriptor(context,
|
|
4770
|
+
const descriptor = entityStorageDescriptor(context, repositorySpecScanner.scan(repository.entityType, repository.metadata), repository.metadata);
|
|
4468
4771
|
const history = RepositoryStorage.historyConfiguration(repository);
|
|
4469
4772
|
return {
|
|
4470
4773
|
...descriptor,
|
|
@@ -5483,7 +5786,7 @@ const InboxHandoff = {
|
|
|
5483
5786
|
* @returns Completion of Inbox receipt and its selected delivery operation.
|
|
5484
5787
|
*/
|
|
5485
5788
|
async handoffEntityCommand(repository, runtime, command) {
|
|
5486
|
-
const route = repository.routeCommand(command);
|
|
5789
|
+
const route = await repository.routeCommand(command);
|
|
5487
5790
|
const commandId = RepositorySignals.requireCommandId(command);
|
|
5488
5791
|
const whenReceived = new Date();
|
|
5489
5792
|
const keepUntil = new Date(whenReceived.getTime() + inboxDedupMs);
|
|
@@ -5576,8 +5879,12 @@ const InboxHandoff = {
|
|
|
5576
5879
|
storageFactory: runtime.storageFactory,
|
|
5577
5880
|
strategy: runtime.entityInbox.strategy(),
|
|
5578
5881
|
});
|
|
5579
|
-
|
|
5580
|
-
|
|
5882
|
+
for (let start = 0; start < entityIds.length; start += 1_000) {
|
|
5883
|
+
const inputs = entityIds
|
|
5884
|
+
.slice(start, start + 1_000)
|
|
5885
|
+
.map((entityId) => InboxHandoff.pmEventInboxInput(repository, eventId.value, event, entityId, keepUntil));
|
|
5886
|
+
await runtime.entityInbox.receiveAll(delivery, inputs, deliveryTenantId);
|
|
5887
|
+
}
|
|
5581
5888
|
},
|
|
5582
5889
|
/**
|
|
5583
5890
|
* Builds a pending Process Manager Event delivery for a single target.
|
|
@@ -5653,9 +5960,8 @@ const RepositoryDispatch = {
|
|
|
5653
5960
|
const dispatcher = Object.freeze({
|
|
5654
5961
|
messageSchemas: () => routing.eventSchemas,
|
|
5655
5962
|
externalEventSchemas: () => routing.externalEventSchemas,
|
|
5656
|
-
accept: (event) => {
|
|
5657
|
-
accepted.set(event, repository.routeEvent(event));
|
|
5658
|
-
return Promise.resolve();
|
|
5963
|
+
accept: async (event) => {
|
|
5964
|
+
accepted.set(event, await repository.routeEvent(event));
|
|
5659
5965
|
},
|
|
5660
5966
|
dispatch: (event) => {
|
|
5661
5967
|
const route = accepted.get(event);
|
|
@@ -5681,9 +5987,8 @@ const RepositoryDispatch = {
|
|
|
5681
5987
|
const dispatcher = Object.freeze({
|
|
5682
5988
|
messageSchemas: () => Object.freeze([schema]),
|
|
5683
5989
|
externalEventSchemas: () => (hasOrigin("external") ? [schema] : []),
|
|
5684
|
-
accept: (event) => {
|
|
5685
|
-
accepted.set(event,
|
|
5686
|
-
return Promise.resolve();
|
|
5990
|
+
accept: async (event) => {
|
|
5991
|
+
accepted.set(event, (await repositoryAccess.routeStateUpdate(repository, event)) ?? null);
|
|
5687
5992
|
},
|
|
5688
5993
|
dispatch: (event) => RepositoryDispatch.dispatchAcceptedState(repository, routing, event, accepted),
|
|
5689
5994
|
});
|
|
@@ -5758,8 +6063,7 @@ const RepositoryDispatch = {
|
|
|
5758
6063
|
return (event, rebuild) => {
|
|
5759
6064
|
const runtime = repositoryRuntimes.get(repository);
|
|
5760
6065
|
if (runtime === undefined) {
|
|
5761
|
-
|
|
5762
|
-
return Promise.resolve();
|
|
6066
|
+
return repository.routeEvent(event).then(() => undefined);
|
|
5763
6067
|
}
|
|
5764
6068
|
return new ProjectionEventExecution(repository, routing, runtime, event, rebuild).runDirect();
|
|
5765
6069
|
};
|
|
@@ -5777,10 +6081,10 @@ const RepositoryDispatch = {
|
|
|
5777
6081
|
const runtime = repositoryRuntimes.get(repository);
|
|
5778
6082
|
if (runtime === undefined) {
|
|
5779
6083
|
if (acceptedRoute === undefined)
|
|
5780
|
-
|
|
6084
|
+
await repository.routeEvent(event);
|
|
5781
6085
|
return;
|
|
5782
6086
|
}
|
|
5783
|
-
const route = acceptedRoute ?? repository.routeEvent(event);
|
|
6087
|
+
const route = acceptedRoute ?? (await repository.routeEvent(event));
|
|
5784
6088
|
switch (repository.entityFamily) {
|
|
5785
6089
|
case "aggregate":
|
|
5786
6090
|
await RepositoryDispatch.dispatchAggregateEvent(repository, routing, runtime, event, route);
|
|
@@ -5822,10 +6126,10 @@ const RepositoryDispatch = {
|
|
|
5822
6126
|
const runtime = repositoryRuntimes.get(repository);
|
|
5823
6127
|
if (runtime === undefined) {
|
|
5824
6128
|
if (acceptedRoute === undefined)
|
|
5825
|
-
|
|
6129
|
+
await repositoryAccess.routeStateUpdate(repository, event);
|
|
5826
6130
|
return;
|
|
5827
6131
|
}
|
|
5828
|
-
const route = acceptedRoute ??
|
|
6132
|
+
const route = acceptedRoute ?? (await repositoryAccess.routeStateUpdate(repository, event));
|
|
5829
6133
|
if (route === undefined)
|
|
5830
6134
|
return;
|
|
5831
6135
|
for (const entityId of route.entityIds) {
|
|
@@ -5843,7 +6147,7 @@ const RepositoryDispatch = {
|
|
|
5843
6147
|
async dispatchRepositoryCommand(repository, routing, command) {
|
|
5844
6148
|
const runtime = repositoryRuntimes.get(repository);
|
|
5845
6149
|
if (runtime === undefined) {
|
|
5846
|
-
|
|
6150
|
+
await repository.routeCommand(command);
|
|
5847
6151
|
return;
|
|
5848
6152
|
}
|
|
5849
6153
|
if (repository.entityFamily === "aggregate") {
|
|
@@ -5854,7 +6158,7 @@ const RepositoryDispatch = {
|
|
|
5854
6158
|
await InboxHandoff.handoffEntityCommand(repository, runtime, command);
|
|
5855
6159
|
return;
|
|
5856
6160
|
}
|
|
5857
|
-
|
|
6161
|
+
await repository.routeCommand(command);
|
|
5858
6162
|
},
|
|
5859
6163
|
};
|
|
5860
6164
|
Object.freeze(RepositoryDispatch);
|