@spine-event-engine/server 2.0.0-snapshot.2 → 2.0.0-snapshot.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/README.md +225 -518
  2. package/REFERENCE.md +498 -49
  3. package/dist/browser/browser-server.d.ts +95 -0
  4. package/dist/browser/browser-server.d.ts.map +1 -0
  5. package/dist/{server → browser}/browser-server.js +93 -7
  6. package/dist/browser/browser-server.js.map +1 -0
  7. package/dist/browser/durable-subscription-bindings.d.ts.map +1 -0
  8. package/dist/browser/durable-subscription-bindings.js.map +1 -0
  9. package/dist/browser/index.d.ts +12 -0
  10. package/dist/browser/index.d.ts.map +1 -0
  11. package/dist/browser/index.js +24 -0
  12. package/dist/browser/index.js.map +1 -0
  13. package/dist/browser/options.d.ts +183 -0
  14. package/dist/browser/options.d.ts.map +1 -0
  15. package/dist/browser/options.js +15 -0
  16. package/dist/browser/options.js.map +1 -0
  17. package/dist/bus/command-bus.d.ts +2 -0
  18. package/dist/bus/command-bus.d.ts.map +1 -1
  19. package/dist/bus/command-bus.js +36 -1
  20. package/dist/bus/command-bus.js.map +1 -1
  21. package/dist/bus/event-dispatcher-registry.d.ts +2 -2
  22. package/dist/bus/event-dispatcher-registry.js +3 -3
  23. package/dist/bus/event-dispatcher-registry.js.map +1 -1
  24. package/dist/context/bounded-context.d.ts +238 -83
  25. package/dist/context/bounded-context.d.ts.map +1 -1
  26. package/dist/context/bounded-context.js +1083 -286
  27. package/dist/context/bounded-context.js.map +1 -1
  28. package/dist/context/effective-tenant.d.ts +9 -0
  29. package/dist/context/effective-tenant.d.ts.map +1 -0
  30. package/dist/context/effective-tenant.js +58 -0
  31. package/dist/context/effective-tenant.js.map +1 -0
  32. package/dist/context/entity-inbox.d.ts.map +1 -1
  33. package/dist/context/entity-inbox.js +24 -17
  34. package/dist/context/entity-inbox.js.map +1 -1
  35. package/dist/context/local-inbox-handoff.d.ts +7 -3
  36. package/dist/context/local-inbox-handoff.d.ts.map +1 -1
  37. package/dist/context/local-inbox-handoff.js +32 -26
  38. package/dist/context/local-inbox-handoff.js.map +1 -1
  39. package/dist/context/projection-handoff.d.ts.map +1 -1
  40. package/dist/context/projection-handoff.js +36 -31
  41. package/dist/context/projection-handoff.js.map +1 -1
  42. package/dist/context/tenant-index.d.ts.map +1 -1
  43. package/dist/context/tenant-index.js +72 -2
  44. package/dist/context/tenant-index.js.map +1 -1
  45. package/dist/delivery/delivery-ports.d.ts +14 -4
  46. package/dist/delivery/delivery-ports.d.ts.map +1 -1
  47. package/dist/delivery/delivery.d.ts +19 -21
  48. package/dist/delivery/delivery.d.ts.map +1 -1
  49. package/dist/delivery/delivery.js +541 -158
  50. package/dist/delivery/delivery.js.map +1 -1
  51. package/dist/delivery/inbox-storage.d.ts +12 -3
  52. package/dist/delivery/inbox-storage.d.ts.map +1 -1
  53. package/dist/delivery/inbox-storage.js +160 -67
  54. package/dist/delivery/inbox-storage.js.map +1 -1
  55. package/dist/delivery/inbox.d.ts +9 -0
  56. package/dist/delivery/inbox.d.ts.map +1 -1
  57. package/dist/delivery/inbox.js +11 -0
  58. package/dist/delivery/inbox.js.map +1 -1
  59. package/dist/delivery/sharded-work-registry.d.ts.map +1 -1
  60. package/dist/delivery/sharded-work-registry.js +2 -1
  61. package/dist/delivery/sharded-work-registry.js.map +1 -1
  62. package/dist/entity/entity-metadata.d.ts +14 -5
  63. package/dist/entity/entity-metadata.d.ts.map +1 -1
  64. package/dist/entity/entity-metadata.js +38 -2
  65. package/dist/entity/entity-metadata.js.map +1 -1
  66. package/dist/entity/entity-storage-descriptor.d.ts +25 -5
  67. package/dist/entity/entity-storage-descriptor.d.ts.map +1 -1
  68. package/dist/entity/entity-storage-descriptor.js +69 -23
  69. package/dist/entity/entity-storage-descriptor.js.map +1 -1
  70. package/dist/entity/entity-transaction.d.ts +46 -42
  71. package/dist/entity/entity-transaction.d.ts.map +1 -1
  72. package/dist/entity/entity-transaction.js +130 -35
  73. package/dist/entity/entity-transaction.js.map +1 -1
  74. package/dist/entity/entity.d.ts +243 -98
  75. package/dist/entity/entity.d.ts.map +1 -1
  76. package/dist/entity/entity.js +405 -226
  77. package/dist/entity/entity.js.map +1 -1
  78. package/dist/entity/spec-scanner.d.ts +13 -1
  79. package/dist/entity/spec-scanner.d.ts.map +1 -1
  80. package/dist/entity/spec-scanner.js +20 -3
  81. package/dist/entity/spec-scanner.js.map +1 -1
  82. package/dist/handler/command-registration-readiness.d.ts +6 -6
  83. package/dist/handler/command-registration-readiness.d.ts.map +1 -1
  84. package/dist/handler/command-registration-readiness.js +4 -2
  85. package/dist/handler/command-registration-readiness.js.map +1 -1
  86. package/dist/handler/declared-rejections.d.ts +11 -0
  87. package/dist/handler/declared-rejections.d.ts.map +1 -0
  88. package/dist/handler/declared-rejections.js +34 -0
  89. package/dist/handler/declared-rejections.js.map +1 -0
  90. package/dist/handler/event-registration-readiness.d.ts +13 -56
  91. package/dist/handler/event-registration-readiness.d.ts.map +1 -1
  92. package/dist/handler/event-registration-readiness.js +23 -43
  93. package/dist/handler/event-registration-readiness.js.map +1 -1
  94. package/dist/handler/generated-handler-registry.d.ts +72 -33
  95. package/dist/handler/generated-handler-registry.d.ts.map +1 -1
  96. package/dist/handler/generated-handler-registry.js +421 -43
  97. package/dist/handler/generated-handler-registry.js.map +1 -1
  98. package/dist/handler/generated-registry-discovery.d.ts.map +1 -1
  99. package/dist/handler/generated-registry-discovery.js +2 -4
  100. package/dist/handler/generated-registry-discovery.js.map +1 -1
  101. package/dist/handler/handler-decorators.d.ts +91 -37
  102. package/dist/handler/handler-decorators.d.ts.map +1 -1
  103. package/dist/handler/handler-decorators.js +124 -31
  104. package/dist/handler/handler-decorators.js.map +1 -1
  105. package/dist/handler/handler-metadata.d.ts +105 -60
  106. package/dist/handler/handler-metadata.d.ts.map +1 -1
  107. package/dist/handler/handler-metadata.js +275 -57
  108. package/dist/handler/handler-metadata.js.map +1 -1
  109. package/dist/handler/registration-readiness-metadata.d.ts +4 -0
  110. package/dist/handler/registration-readiness-metadata.d.ts.map +1 -1
  111. package/dist/handler/registration-readiness-metadata.js +84 -2
  112. package/dist/handler/registration-readiness-metadata.js.map +1 -1
  113. package/dist/handler/standalone.d.ts +25 -0
  114. package/dist/handler/standalone.d.ts.map +1 -0
  115. package/dist/handler/standalone.js +38 -0
  116. package/dist/handler/standalone.js.map +1 -0
  117. package/dist/index.d.ts +9 -9
  118. package/dist/index.d.ts.map +1 -1
  119. package/dist/index.js +3 -3
  120. package/dist/index.js.map +1 -1
  121. package/dist/repository/command-routing.d.ts +63 -10
  122. package/dist/repository/command-routing.d.ts.map +1 -1
  123. package/dist/repository/command-routing.js +41 -8
  124. package/dist/repository/command-routing.js.map +1 -1
  125. package/dist/repository/event-routing.d.ts +64 -9
  126. package/dist/repository/event-routing.d.ts.map +1 -1
  127. package/dist/repository/event-routing.js +42 -7
  128. package/dist/repository/event-routing.js.map +1 -1
  129. package/dist/repository/primitive-id.d.ts +12 -23
  130. package/dist/repository/primitive-id.d.ts.map +1 -1
  131. package/dist/repository/primitive-id.js +2 -27
  132. package/dist/repository/primitive-id.js.map +1 -1
  133. package/dist/repository/repository.d.ts +317 -87
  134. package/dist/repository/repository.d.ts.map +1 -1
  135. package/dist/repository/repository.js +3746 -856
  136. package/dist/repository/repository.js.map +1 -1
  137. package/dist/repository/state-update-routing.d.ts +67 -8
  138. package/dist/repository/state-update-routing.d.ts.map +1 -1
  139. package/dist/repository/state-update-routing.js +45 -6
  140. package/dist/repository/state-update-routing.js.map +1 -1
  141. package/dist/runtime/runtime.js +1 -1
  142. package/dist/runtime/runtime.js.map +1 -1
  143. package/dist/runtime/signal-metadata.d.ts +22 -47
  144. package/dist/runtime/signal-metadata.d.ts.map +1 -1
  145. package/dist/runtime/signal-metadata.js +34 -69
  146. package/dist/runtime/signal-metadata.js.map +1 -1
  147. package/dist/runtime/signal-publisher.d.ts +144 -0
  148. package/dist/runtime/signal-publisher.d.ts.map +1 -0
  149. package/dist/runtime/signal-publisher.js +248 -0
  150. package/dist/runtime/signal-publisher.js.map +1 -0
  151. package/dist/runtime/standalone-handler-runtime.d.ts +130 -0
  152. package/dist/runtime/standalone-handler-runtime.d.ts.map +1 -0
  153. package/dist/runtime/standalone-handler-runtime.js +382 -0
  154. package/dist/runtime/standalone-handler-runtime.js.map +1 -0
  155. package/dist/server/environment-attachment.d.ts.map +1 -1
  156. package/dist/server/environment-attachment.js +2 -1
  157. package/dist/server/environment-attachment.js.map +1 -1
  158. package/dist/server/environment-delivery-worker.d.ts.map +1 -1
  159. package/dist/server/environment-delivery-worker.js +21 -12
  160. package/dist/server/environment-delivery-worker.js.map +1 -1
  161. package/dist/server/managed-server-application.d.ts +4 -1
  162. package/dist/server/managed-server-application.d.ts.map +1 -1
  163. package/dist/server/managed-server-application.js +2 -2
  164. package/dist/server/managed-server-application.js.map +1 -1
  165. package/dist/server/node-coordinator.js +2 -2
  166. package/dist/server/node-coordinator.js.map +1 -1
  167. package/dist/server/server-log.d.ts.map +1 -1
  168. package/dist/server/server-log.js +4 -0
  169. package/dist/server/server-log.js.map +1 -1
  170. package/dist/server/server.d.ts +9 -177
  171. package/dist/server/server.d.ts.map +1 -1
  172. package/dist/server/server.js +428 -178
  173. package/dist/server/server.js.map +1 -1
  174. package/dist/services/query-reader.d.ts +19 -0
  175. package/dist/services/query-reader.d.ts.map +1 -0
  176. package/dist/services/query-reader.js +43 -0
  177. package/dist/services/query-reader.js.map +1 -0
  178. package/dist/services/registered-targets.d.ts +73 -0
  179. package/dist/services/registered-targets.d.ts.map +1 -0
  180. package/dist/services/registered-targets.js +127 -0
  181. package/dist/services/registered-targets.js.map +1 -0
  182. package/dist/services/spine-services.d.ts +26 -12
  183. package/dist/services/spine-services.d.ts.map +1 -1
  184. package/dist/services/spine-services.js +1047 -969
  185. package/dist/services/spine-services.js.map +1 -1
  186. package/dist/spi/delivery.d.ts +5 -0
  187. package/dist/spi/delivery.d.ts.map +1 -0
  188. package/dist/spi/delivery.js +18 -0
  189. package/dist/spi/delivery.js.map +1 -0
  190. package/dist/spi/handler-registry.d.ts +8 -0
  191. package/dist/spi/handler-registry.d.ts.map +1 -0
  192. package/dist/spi/handler-registry.js +15 -0
  193. package/dist/spi/handler-registry.js.map +1 -0
  194. package/dist/stand/stand.d.ts +73 -1
  195. package/dist/stand/stand.d.ts.map +1 -1
  196. package/dist/stand/stand.js +221 -48
  197. package/dist/stand/stand.js.map +1 -1
  198. package/dist/stand/subscription-observer.d.ts.map +1 -1
  199. package/dist/stand/subscription-observer.js +5 -12
  200. package/dist/stand/subscription-observer.js.map +1 -1
  201. package/dist/stand/subscription-registry.js +1 -1
  202. package/dist/stand/subscription-registry.js.map +1 -1
  203. package/dist/testing/index.d.ts +25 -3
  204. package/dist/testing/index.d.ts.map +1 -1
  205. package/dist/testing/index.js +23 -3
  206. package/dist/testing/index.js.map +1 -1
  207. package/dist/tsconfig.tsbuildinfo +1 -1
  208. package/package.json +33 -23
  209. package/dist/handler/build-time-handler-analyzer.d.ts +0 -150
  210. package/dist/handler/build-time-handler-analyzer.d.ts.map +0 -1
  211. package/dist/handler/build-time-handler-analyzer.js +0 -1101
  212. package/dist/handler/build-time-handler-analyzer.js.map +0 -1
  213. package/dist/handler/generated-registry-writer.d.ts +0 -58
  214. package/dist/handler/generated-registry-writer.d.ts.map +0 -1
  215. package/dist/handler/generated-registry-writer.js +0 -458
  216. package/dist/handler/generated-registry-writer.js.map +0 -1
  217. package/dist/handler/handler-codegen.d.ts +0 -48
  218. package/dist/handler/handler-codegen.d.ts.map +0 -1
  219. package/dist/handler/handler-codegen.js +0 -214
  220. package/dist/handler/handler-codegen.js.map +0 -1
  221. package/dist/server/browser-server.d.ts +0 -49
  222. package/dist/server/browser-server.d.ts.map +0 -1
  223. package/dist/server/browser-server.js.map +0 -1
  224. package/dist/server/durable-subscription-bindings.d.ts.map +0 -1
  225. package/dist/server/durable-subscription-bindings.js.map +0 -1
  226. /package/dist/{server → browser}/durable-subscription-bindings.d.ts +0 -0
  227. /package/dist/{server → browser}/durable-subscription-bindings.js +0 -0
@@ -17,9 +17,10 @@ import { isAbsolute, relative, resolve, sep } from "node:path";
17
17
  import { fileURLToPath, pathToFileURL } from "node:url";
18
18
  import { clone, create, getOption, hasOption } from "@bufbuild/protobuf";
19
19
  import { TypeUrls } from "@spine-event-engine/core";
20
- import { EventSchema, BoundedContextNameSchema, TenantIdSchema, } from "@spine-event-engine/proto";
20
+ import { EventSchema, EventContextSchema, BoundedContextNameSchema, TenantIdSchema, } from "@spine-event-engine/proto";
21
21
  import { SPI_type, internal_all, internal_type } from "@spine-event-engine/proto";
22
- import { ColumnTypes, EventStore, InMemoryStorageFactory, RecordColumn, TenantBoundary, } from "@spine-event-engine/storage";
22
+ import { ColumnTypes, EventStore, InMemoryStorageFactory, RecordColumn, } from "@spine-event-engine/storage";
23
+ import { TenantBoundary } from "@spine-event-engine/storage/provider";
23
24
  import { CommandBus, commandBusAccess } from "../bus/command-bus.js";
24
25
  import { EventBus, eventBusAccess, } from "../bus/event-bus.js";
25
26
  import { DeliveryReadiness, } from "./local-inbox-handoff.js";
@@ -29,6 +30,7 @@ import { TenantIndexes } from "./tenant-index.js";
29
30
  import { Repository, repositoryAccess, } from "../repository/repository.js";
30
31
  import { GeneratedRegistryDiscovery } from "../handler/generated-registry-discovery.js";
31
32
  import { HandlerRegistryIngestor, } from "../handler/generated-handler-registry.js";
33
+ import { AbstractAssignee, AbstractCommander, AbstractEventReactor, AbstractEventSubscriber, } from "../handler/standalone.js";
32
34
  import { HandlerMetadataRegistry, } from "../handler/handler-metadata.js";
33
35
  import { SignalMetadata } from "../runtime/signal-metadata.js";
34
36
  import { Stand } from "../stand/stand.js";
@@ -37,9 +39,10 @@ import { StorageSubscriptionRegistry, } from "../stand/subscription-registry.js"
37
39
  import { UniformAcrossAllShards } from "../delivery/delivery-builder.js";
38
40
  import { InboxTargets } from "../delivery/inbox.js";
39
41
  import { ShardIndex } from "../delivery/shard-index.js";
40
- import { emitServerError } from "../server/server-log.js";
41
42
  import { IntegrationBroker } from "../integration/integration-broker.js";
42
43
  import { ServerEnvironment } from "../server/server-environment.js";
44
+ import { SignalPublisher } from "../runtime/signal-publisher.js";
45
+ import { StandaloneHandlerRuntime, } from "../runtime/standalone-handler-runtime.js";
43
46
  /**
44
47
  * Error thrown when a bounded context name cannot be accepted.
45
48
  */
@@ -64,10 +67,8 @@ export class BoundedContextNameError extends Error {
64
67
  const frameworkConstructionToken = Object.freeze({
65
68
  frameworkConstructionToken: true,
66
69
  });
67
- const dispatchFailureLimit = 10;
68
- const dispatchErrorMessageLimit = 500;
69
- const dispatchErrorStackLimit = 2_000;
70
70
  const generatedRegistryFile = "generated/handler/generated-handler-registry.js";
71
+ const errorDetailLimit = 500;
71
72
  const moduleSchemeRe = /^[A-Za-z][A-Za-z\d+.-]*:/;
72
73
  const internalStoragePrefix = "__spine/";
73
74
  const generatedRegistryLoadAttempts = new Map();
@@ -78,9 +79,10 @@ const contextStorageFactories = new WeakMap();
78
79
  const contextDeliveryDescriptors = new WeakMap();
79
80
  const contextSubscriptionRuntimes = new WeakMap();
80
81
  const contextLoggers = new WeakMap();
82
+ const contextSignalPublishers = new WeakMap();
81
83
  const contextEventBuses = new WeakMap();
82
84
  const closingContexts = new WeakSet();
83
- const contextDispatchFailureRecorders = new WeakMap();
85
+ const contextClosePhases = new WeakMap();
84
86
  const contextIntegrations = new WeakMap();
85
87
  const systemEventPosters = new WeakMap();
86
88
  const builderBuilds = new WeakMap();
@@ -95,13 +97,13 @@ export class BoundedContext {
95
97
  #commandBus;
96
98
  #eventBus;
97
99
  #systemEventBus;
100
+ #publisher;
98
101
  #commandEndpoint;
99
102
  #eventEndpoint;
100
103
  #entityInbox;
101
104
  #projectionInbox;
102
105
  #deliveryStrategy;
103
106
  #registeredRepositories = [];
104
- #storedEventDispatchFailures = [];
105
107
  #repositoryViews = new Set();
106
108
  #storageFactory;
107
109
  #stand;
@@ -112,36 +114,25 @@ export class BoundedContext {
112
114
  * Registers the framework-only construction hook for this module.
113
115
  */
114
116
  static {
115
- constructBoundedContext = (snapshot, commandBus, eventBus, systemEventBus, stand, systemStand, runtime, systemSpec, storageFactory, repositories, deliveryStrategy, token) => new BoundedContext(snapshot, commandBus, eventBus, systemEventBus, stand, systemStand, runtime, systemSpec, storageFactory, repositories, deliveryStrategy, token);
116
- }
117
- /**
118
- * Creates a framework-owned bounded context.
119
- *
120
- * @param snapshot Contains the immutable context metadata.
121
- * @param commandBus Dispatches commands accepted by this context.
122
- * @param eventBus Dispatches events accepted by this context.
123
- * @param systemEventBus Dispatches framework-only System events.
124
- * @param stand Stores read-side state for this context.
125
- * @param systemStand Stores read-side state for the paired System Context.
126
- * @param subscriptionRuntime Coordinates pair-owned subscription delivery.
127
- * @param systemSpec Contains paired System Context metadata.
128
- * @param storageFactory Creates context storage.
129
- * @param repositories Lists repositories to register.
130
- * @param deliveryStrategy Selects immutable Entity Inbox shards.
131
- * @param token Proves framework-controlled construction.
117
+ constructBoundedContext = (input) => new BoundedContext(input);
118
+ }
119
+ /**
120
+ * Creates a Bounded Context from its prepared framework services.
121
+ *
122
+ * @param input Prepared services and framework construction token.
132
123
  */
133
- constructor(snapshot, commandBus, eventBus, systemEventBus, stand, systemStand, subscriptionRuntime, systemSpec, storageFactory, repositories, deliveryStrategy, token) {
134
- ContextParts.requireFrameworkConstructionToken(token, "BoundedContext instances are framework-owned.");
135
- this.#snapshot = ContextParts.cloneContextSnapshot(snapshot);
136
- this.#commandBus = commandBus;
137
- this.#eventBus = eventBus;
138
- this.#systemEventBus = systemEventBus;
139
- contextEventBuses.set(this, [eventBus, systemEventBus]);
140
- this.#stand = stand;
141
- this.#systemStand = systemStand;
142
- this.#subscriptionRuntime = subscriptionRuntime;
143
- this.#storageFactory = storageFactory;
144
- this.#deliveryStrategy = ContextParts.snapshotDeliveryStrategy(deliveryStrategy);
124
+ constructor(input) {
125
+ ContextParts.requireFrameworkConstructionToken(input.token, "BoundedContext instances are framework-owned.");
126
+ this.#snapshot = ContextParts.cloneContextSnapshot(input.snapshot);
127
+ this.#commandBus = input.commandBus;
128
+ this.#eventBus = input.eventBus;
129
+ this.#systemEventBus = input.systemEventBus;
130
+ this.#publisher = input.publisher;
131
+ this.#stand = input.stand;
132
+ this.#systemStand = input.systemStand;
133
+ this.#subscriptionRuntime = input.subscriptionRuntime;
134
+ this.#storageFactory = input.storageFactory;
135
+ this.#deliveryStrategy = ContextParts.snapshotDeliveryStrategy(input.deliveryStrategy);
145
136
  this.#commandEndpoint = Object.freeze({
146
137
  acceptedCommandTypes: () => this.#commandBus.acceptedCommandTypes(),
147
138
  post: (command) => this.#commandBus.post(command),
@@ -150,9 +141,20 @@ export class BoundedContext {
150
141
  acceptedEventTypes: () => ContextParts.exposedEventTypeUrls(this.#eventBus),
151
142
  post: (event) => ContextParts.postContextEvent(this, event),
152
143
  });
153
- contextDispatchFailureRecorders.set(this, (event, error) => {
154
- this.#recordDispatchFailure(event, error);
155
- });
144
+ const delivery = this.#createDelivery(input.storageFactory);
145
+ this.#entityInbox = delivery.entityInbox;
146
+ this.#projectionInbox = delivery.projectionInbox;
147
+ this.#installReferences(input, delivery);
148
+ this.#registerAndStart(input.repositories, delivery.tenantIndex);
149
+ Object.freeze(this);
150
+ }
151
+ /**
152
+ * Creates tenant-aware Inboxes and their shared readiness signal.
153
+ *
154
+ * @param storageFactory Provider supplying the tenant catalog.
155
+ * @returns Inboxes, tenant index, and readiness signal.
156
+ */
157
+ #createDelivery(storageFactory) {
156
158
  const deliveryReadiness = new DeliveryReadiness();
157
159
  const tenantIndex = TenantIndexes.create({
158
160
  contextName: this.#snapshot.name.value,
@@ -160,15 +162,40 @@ export class BoundedContext {
160
162
  storageFactory,
161
163
  });
162
164
  const keepTenant = (tenantId) => tenantIndex.keep(tenantId);
163
- this.#entityInbox = new LocalEntityInbox(this.#snapshot.name.value, deliveryReadiness, keepTenant, this.#deliveryStrategy);
164
- this.#projectionInbox = new LocalProjectionInbox(this.#snapshot.name.value, deliveryReadiness, keepTenant);
165
+ const entityInbox = new LocalEntityInbox(this.#snapshot.name.value, deliveryReadiness, keepTenant, this.#deliveryStrategy);
166
+ const projectionInbox = new LocalProjectionInbox(this.#snapshot.name.value, deliveryReadiness, keepTenant);
167
+ return { deliveryReadiness, tenantIndex, entityInbox, projectionInbox };
168
+ }
169
+ /**
170
+ * Installs framework references used after context construction.
171
+ *
172
+ * @param input Prepared framework services.
173
+ * @param delivery Tenant index and Inboxes prepared for this context.
174
+ */
175
+ #installReferences(input, delivery) {
176
+ contextEventBuses.set(this, [this.#eventBus, this.#systemEventBus]);
165
177
  eventSubscribers.set(this, (typeUrl, subscriber) => eventBusAccess.subscribe(this.#eventBus, typeUrl, subscriber));
166
- systemEventPosters.set(this, (event) => this.#systemEventBus.post(event));
167
- contextSystemPairings.set(this, ContextParts.createSystemPairing(this.#snapshot, systemSpec));
168
- contextTenantIndexes.set(this, tenantIndex);
169
- contextStorageFactories.set(this, storageFactory);
170
- contextSubscriptionRuntimes.set(this, subscriptionRuntime);
171
- contextDeliveryDescriptors.set(this, ContextParts.createDeliveryDescriptor(this.#snapshot, storageFactory, tenantIndex, this.#entityInbox, this.#projectionInbox, deliveryReadiness));
178
+ systemEventPosters.set(this, (event) => this.#publisher.publishSystemEvent(event));
179
+ contextSystemPairings.set(this, ContextParts.createSystemPairing(this.#snapshot, input.systemSpec));
180
+ contextTenantIndexes.set(this, delivery.tenantIndex);
181
+ contextStorageFactories.set(this, input.storageFactory);
182
+ contextSubscriptionRuntimes.set(this, this.#subscriptionRuntime);
183
+ contextSignalPublishers.set(this, this.#publisher);
184
+ contextClosePhases.set(this, {
185
+ begin: () => {
186
+ this.#beginClose();
187
+ },
188
+ drain: () => this.#drainWork(),
189
+ });
190
+ contextDeliveryDescriptors.set(this, ContextParts.createDeliveryDescriptor(this.#snapshot, input.storageFactory, delivery.tenantIndex, this.#entityInbox, this.#projectionInbox, delivery.deliveryReadiness));
191
+ }
192
+ /**
193
+ * Registers repositories and starts subscriptions, closing the tenant index on failure.
194
+ *
195
+ * @param repositories Prepared repositories.
196
+ * @param tenantIndex Index to close if registration fails.
197
+ */
198
+ #registerAndStart(repositories, tenantIndex) {
172
199
  try {
173
200
  this.#registerRepositories(repositories);
174
201
  this.#subscriptionRuntime.start();
@@ -182,7 +209,6 @@ export class BoundedContext {
182
209
  }
183
210
  throw error;
184
211
  }
185
- Object.freeze(this);
186
212
  }
187
213
  #registerRepositories(repositories) {
188
214
  const preparedRepositories = this.#prepareRepositories(repositories);
@@ -217,20 +243,13 @@ export class BoundedContext {
217
243
  stand: this.#stand,
218
244
  entityInbox: this.#entityInbox,
219
245
  projectionInbox: this.#projectionInbox,
220
- dispatchStored: (event) => eventBusAccess.postStored(this.#eventBus, event),
221
- dispatchStoredFollowUp: (event) => eventBusAccess.postStoredFollowUp(this.#eventBus, event),
222
- postEventFollowUp: (event) => eventBusAccess.postFollowUp(this.#eventBus, event),
246
+ publisher: this.#publisher,
223
247
  registerEventSchema: (schema) => {
224
248
  eventBusAccess.registerSchemas(this.#eventBus, [schema]);
225
249
  },
226
250
  registerSystemEventSchema: (schema) => {
227
251
  eventBusAccess.registerSchemas(this.#systemEventBus, [schema]);
228
252
  },
229
- postSystemFollowUp: (event) => eventBusAccess.postFollowUp(this.#systemEventBus, event),
230
- onPostCommand: (command) => commandBusAccess.postInternal(this.#commandBus, command),
231
- recordDispatchFailure: (event, error) => {
232
- this.#recordDispatchFailure(event, error);
233
- },
234
253
  };
235
254
  const preparedRepositories = [];
236
255
  try {
@@ -309,7 +328,7 @@ export class BoundedContext {
309
328
  return ContextParts.cloneContextSnapshot(this.#snapshot);
310
329
  }
311
330
  /**
312
- * Returns the command endpoint owned by this context.
331
+ * Returns this context's command endpoint.
313
332
  *
314
333
  * @returns Returns the context command endpoint.
315
334
  */
@@ -317,7 +336,7 @@ export class BoundedContext {
317
336
  return this.#commandEndpoint;
318
337
  }
319
338
  /**
320
- * Returns the event endpoint owned by this context.
339
+ * Returns this context's event endpoint.
321
340
  *
322
341
  * @returns Returns the context event endpoint.
323
342
  */
@@ -325,7 +344,7 @@ export class BoundedContext {
325
344
  return this.#eventEndpoint;
326
345
  }
327
346
  /**
328
- * Returns the context-owned read-side Stand.
347
+ * Returns the Stand that stores this context's read-side state.
329
348
  *
330
349
  * @returns Returns the read-side state store.
331
350
  */
@@ -340,17 +359,6 @@ export class BoundedContext {
340
359
  registeredRepositories() {
341
360
  return this.#registeredRepositories.map((snapshot) => ContextParts.createRepositoryView(snapshot));
342
361
  }
343
- /**
344
- * Returns copy-safe diagnostics for asynchronous event follow-up failures.
345
- *
346
- * Entries can describe already-stored event dispatch or an independent
347
- * follow-up post that failed before storage.
348
- *
349
- * @returns Returns immutable failure diagnostics.
350
- */
351
- storedEventDispatchFailures() {
352
- return this.#storedEventDispatchFailures.map(ContextParts.cloneDispatchFailure);
353
- }
354
362
  /**
355
363
  * Clears and locally replays every registered Projection from already-stored events.
356
364
  *
@@ -408,31 +416,54 @@ export class BoundedContext {
408
416
  });
409
417
  }
410
418
  /**
411
- * Closes context-owned buses, Stand, and repository storage/runtime bindings.
419
+ * Closes this context's buses, Stand, and repository storage and runtime bindings.
412
420
  *
413
421
  * Close is idempotent and returns the same close outcome on repeated calls.
414
- * The context attempts every owned close hook; when any hook fails, the
422
+ * The context attempts every close hook; when any hook fails, the
415
423
  * returned promise rejects with an `AggregateError` after the remaining hooks
416
424
  * have also been attempted.
417
425
  *
418
- * @returns A promise that settles after all owned resources close.
426
+ * @returns A promise that settles after all resources close.
419
427
  */
420
428
  close() {
421
- closingContexts.add(this);
422
- eventBusAccess.beginClose(this.#eventBus);
429
+ this.#beginClose();
423
430
  this.#closed ??= this.#closeOnce();
424
431
  return this.#closed;
425
432
  }
426
- async #closeOnce() {
427
- const errors = [];
428
- await ContextParts.closeContextPart(() => ContextParts.closeIntegration(this), errors);
433
+ /**
434
+ * Stops admission to this context's signal and bus work.
435
+ */
436
+ #beginClose() {
437
+ closingContexts.add(this);
438
+ this.#publisher.beginClose();
429
439
  commandBusAccess.beginClose(this.#commandBus);
430
440
  eventBusAccess.beginClose(this.#eventBus);
431
- await ContextParts.closeContextPart(() => ContextParts.drainContextBuses(this.#commandBus, this.#eventBus), errors);
441
+ eventBusAccess.beginClose(this.#systemEventBus);
442
+ }
443
+ /**
444
+ * Closes integration intake and drains accepted context work before Stand close.
445
+ *
446
+ * @returns Completion after the context's accepted work settles.
447
+ */
448
+ async #drainWork() {
449
+ const errors = [];
450
+ await ContextParts.closeContextPart(() => ContextParts.closeIntegration(this), errors);
451
+ await ContextParts.closeContextPart(() => ContextParts.drainContextWork(this.#commandBus, this.#eventBus, this.#systemEventBus, this.#publisher), errors);
452
+ if (errors.length > 0)
453
+ throw new AggregateError(errors, "BoundedContext drain failed.");
454
+ }
455
+ /**
456
+ * Closes the context's remaining stores and repository registrations.
457
+ *
458
+ * @returns Completion after resource closure.
459
+ */
460
+ async #closeOnce() {
461
+ const errors = [];
462
+ await ContextParts.closeContextPart(() => this.#drainWork(), errors);
463
+ this.#publisher.finishClose();
432
464
  await ContextParts.closeContextPart(() => commandBusAccess.finishClose(this.#commandBus), errors);
433
465
  await ContextParts.closeContextPart(() => eventBusAccess.finishClose(this.#eventBus), errors);
434
466
  this.#subscriptionRuntime.beginClose();
435
- eventBusAccess.beginClose(this.#systemEventBus);
436
467
  await ContextParts.closeContextPart(() => eventBusAccess.drain(this.#systemEventBus), errors);
437
468
  await ContextParts.closeContextPart(() => this.#subscriptionRuntime.drainClose(), errors);
438
469
  await ContextParts.closeContextPart(() => eventBusAccess.finishClose(this.#systemEventBus), errors);
@@ -442,34 +473,24 @@ export class BoundedContext {
442
473
  await ContextParts.closeContextPart(() => {
443
474
  ContextParts.requireTenantIndex(this).close();
444
475
  }, errors);
445
- for (const repository of this.#repositoryViews) {
446
- await ContextParts.closeContextPart(() => {
447
- repositoryAccess.clearRuntime(repository);
448
- registeredRepositories.delete(repository);
449
- }, errors);
450
- }
476
+ await this.#closeRepositories(errors);
451
477
  if (errors.length > 0) {
452
478
  this.#closed = undefined;
453
479
  throw new AggregateError(ContextParts.flattenErrors(errors), "BoundedContext close failed.");
454
480
  }
455
481
  ContextParts.clearContextMetadata(this);
456
482
  }
457
- #recordDispatchFailure(event, error) {
458
- this.#storedEventDispatchFailures.push(Object.freeze({
459
- event: clone(EventSchema, event),
460
- error: ContextParts.snapshotDispatchError(error),
461
- }));
462
- if (this.#storedEventDispatchFailures.length > dispatchFailureLimit) {
463
- this.#storedEventDispatchFailures.splice(0, this.#storedEventDispatchFailures.length - dispatchFailureLimit);
464
- }
465
- const logger = contextLoggers.get(this);
466
- const eventType = event.message?.typeUrl;
467
- if (logger !== undefined && eventType !== undefined && eventType.length > 0) {
468
- emitServerError(logger, "Repository follow-up dispatch failed.", {
469
- eventType,
470
- operation: "repository.follow_up",
471
- reasonCode: "dispatch_failed",
472
- });
483
+ /**
484
+ * Clears repository runtime registration after Stand closure.
485
+ *
486
+ * @param errors Failures collected during context closure.
487
+ */
488
+ async #closeRepositories(errors) {
489
+ for (const repository of this.#repositoryViews) {
490
+ await ContextParts.closeContextPart(() => {
491
+ repositoryAccess.clearRuntime(repository);
492
+ registeredRepositories.delete(repository);
493
+ }, errors);
473
494
  }
474
495
  }
475
496
  }
@@ -477,6 +498,18 @@ export class BoundedContext {
477
498
  * Exposes framework-only operations for built contexts and their builders.
478
499
  */
479
500
  export const boundedContextAccess = Object.freeze({
501
+ beginClose(context) {
502
+ const phase = contextClosePhases.get(context);
503
+ if (phase === undefined)
504
+ throw new TypeError("Close phase requires a built BoundedContext.");
505
+ phase.begin();
506
+ },
507
+ drainWork(context) {
508
+ const phase = contextClosePhases.get(context);
509
+ if (phase === undefined)
510
+ return Promise.reject(new TypeError("Drain requires a built BoundedContext."));
511
+ return phase.drain();
512
+ },
480
513
  installLogger(context, logger) {
481
514
  if (!contextStorageFactories.has(context)) {
482
515
  throw new TypeError("Context logger requires a built BoundedContext instance.");
@@ -488,6 +521,10 @@ export const boundedContextAccess = Object.freeze({
488
521
  }
489
522
  eventBusAccess.installLogger(buses[0], logger);
490
523
  eventBusAccess.installLogger(buses[1], logger);
524
+ const publisher = contextSignalPublishers.get(context);
525
+ if (publisher === undefined)
526
+ throw new TypeError("Context logger requires a built BoundedContext instance.");
527
+ publisher.installLogger(logger);
491
528
  const runtime = contextSubscriptionRuntimes.get(context);
492
529
  if (runtime === undefined) {
493
530
  throw new TypeError("Context logger requires a built BoundedContext instance.");
@@ -501,13 +538,6 @@ export const boundedContextAccess = Object.freeze({
501
538
  }
502
539
  return logger;
503
540
  },
504
- recordDispatchFailure(context, event, error) {
505
- const record = contextDispatchFailureRecorders.get(context);
506
- if (record === undefined) {
507
- throw new TypeError("Dispatch failure recording requires a built BoundedContext instance.");
508
- }
509
- record(event, error);
510
- },
511
541
  isBuilder(value) {
512
542
  return (typeof value === "object" &&
513
543
  value !== null &&
@@ -534,6 +564,19 @@ export const boundedContextAccess = Object.freeze({
534
564
  }
535
565
  return post(event);
536
566
  },
567
+ postExternalEvent(context, event) {
568
+ const imported = clone(EventSchema, event);
569
+ imported.context = clone(EventContextSchema, imported.context ?? create(EventContextSchema));
570
+ imported.context.external = true;
571
+ return context.eventBus().post(imported);
572
+ },
573
+ observeProducedSignals(context, observer) {
574
+ const publisher = contextSignalPublishers.get(context);
575
+ if (publisher === undefined) {
576
+ throw new TypeError("Produced signal observation requires a built BoundedContext instance.");
577
+ }
578
+ return publisher.observe(observer);
579
+ },
537
580
  systemPairing(context) {
538
581
  return ContextParts.cloneSystemPairing(ContextParts.requireSystemPairing(context));
539
582
  },
@@ -576,6 +619,9 @@ export class BoundedContextBuilder {
576
619
  #specSnapshot;
577
620
  #commandDispatchers = new Set();
578
621
  #eventDispatchers = new Set();
622
+ #assignees = [];
623
+ #commanders = [];
624
+ #eventReceivers = [];
579
625
  #repositories = new Set();
580
626
  #entityTypes = new Set();
581
627
  #generatedRepositoryOptions = new Map();
@@ -591,7 +637,7 @@ export class BoundedContextBuilder {
591
637
  constructBoundedContextBuilder = (snapshot, token) => new BoundedContextBuilder(snapshot, token);
592
638
  }
593
639
  /**
594
- * Creates a framework-owned context builder.
640
+ * Creates a builder from a prepared context specification.
595
641
  *
596
642
  * @param specSnapshot Contains the initial context specification.
597
643
  * @param token Proves framework-controlled construction.
@@ -637,6 +683,7 @@ export class BoundedContextBuilder {
637
683
  /**
638
684
  * Adds an explicitly assembled repository or an Entity class.
639
685
  *
686
+ * @typeParam EntityType Concrete Entity class represented by this entry.
640
687
  * @param entry The repository or Entity class to register.
641
688
  * @param options Optional settings used only when an Entity class is supplied.
642
689
  * @returns This builder for further configuration.
@@ -659,6 +706,7 @@ export class BoundedContextBuilder {
659
706
  /**
660
707
  * Removes a repository from the context registration list.
661
708
  *
709
+ * @typeParam EntityType Concrete Entity class represented by the repository.
662
710
  * @param repository Identifies the repository to remove.
663
711
  * @returns Returns this builder for further configuration.
664
712
  */
@@ -670,10 +718,17 @@ export class BoundedContextBuilder {
670
718
  /**
671
719
  * Adds a command dispatcher to the context being built.
672
720
  *
673
- * @param dispatcher Dispatches commands accepted by this context.
674
- * @returns Returns this builder for further configuration.
721
+ * @param dispatcher Raw command dispatcher, or an `AbstractCommander` instance.
722
+ * A standalone commander requires generated receiver metadata and therefore
723
+ * this builder's `buildAsync()` path. It installs its Command and Event sides
724
+ * exactly once.
725
+ * @returns This builder for further configuration.
675
726
  */
676
727
  addCommandDispatcher(dispatcher) {
728
+ if (dispatcher instanceof AbstractCommander) {
729
+ this.#commanders.push(dispatcher);
730
+ return this;
731
+ }
677
732
  this.#commandDispatchers.add(dispatcher);
678
733
  return this;
679
734
  }
@@ -690,13 +745,32 @@ export class BoundedContextBuilder {
690
745
  /**
691
746
  * Adds an event dispatcher to the context being built.
692
747
  *
693
- * @param dispatcher Dispatches events accepted by this context.
694
- * @returns Returns this builder for further configuration.
748
+ * @param dispatcher Raw event dispatcher, or a standalone reactor/subscriber.
749
+ * Standalone receivers require generated receiver metadata and `buildAsync()`.
750
+ * @returns This builder for further configuration.
695
751
  */
696
752
  addEventDispatcher(dispatcher) {
753
+ if (dispatcher instanceof AbstractEventReactor ||
754
+ dispatcher instanceof AbstractEventSubscriber) {
755
+ this.#eventReceivers.push(dispatcher);
756
+ return this;
757
+ }
697
758
  this.#eventDispatchers.add(dispatcher);
698
759
  return this;
699
760
  }
761
+ /**
762
+ * Adds a generated standalone command assignee.
763
+ *
764
+ * The registered instance is matched by exact constructor to generated
765
+ * receiver metadata when `buildAsync()` assembles the context.
766
+ *
767
+ * @param assignee Generated standalone assignee instance.
768
+ * @returns This builder for further configuration.
769
+ */
770
+ addAssignee(assignee) {
771
+ this.#assignees.push(assignee);
772
+ return this;
773
+ }
700
774
  /**
701
775
  * Removes an event dispatcher from the context being built.
702
776
  *
@@ -743,7 +817,7 @@ export class BoundedContextBuilder {
743
817
  return this;
744
818
  }
745
819
  /**
746
- * Sets the target-to-shard strategy for the context-owned Entity Inbox.
820
+ * Sets how Entity Inbox targets are assigned to shards.
747
821
  *
748
822
  * @param strategy Selects the durable shard for Aggregate and Process Manager targets.
749
823
  * @returns Returns this builder for further configuration.
@@ -768,11 +842,19 @@ export class BoundedContextBuilder {
768
842
  * @returns Returns the built context.
769
843
  */
770
844
  build() {
845
+ if (this.#standaloneInstances().length > 0) {
846
+ throw new Error("Standalone generated handlers require buildAsync().");
847
+ }
771
848
  ContextParts.rejectSyncEntityAssembly(this.#entityTypes);
772
849
  return this.#buildWith([...this.#repositories], this.#storageFactory ?? new InMemoryStorageFactory());
773
850
  }
774
851
  /**
775
- * Builds a context after loading generated metadata for added entity classes.
852
+ * Builds a context after loading generated metadata for added entity classes
853
+ * and registered standalone handlers.
854
+ *
855
+ * Standalone assignees, commanders, reactors, and subscribers require this
856
+ * asynchronous path so their exact constructors can be matched to generated
857
+ * receiver metadata.
776
858
  *
777
859
  * @returns Resolves to the built context.
778
860
  */
@@ -780,11 +862,9 @@ export class BoundedContextBuilder {
780
862
  return this.#buildAsyncWith();
781
863
  }
782
864
  async #buildAsyncWith(defaultStorageFactory) {
783
- const repositories = [
784
- ...this.#repositories,
785
- ...(await this.#loadGeneratedRepositories([...this.#entityTypes])),
786
- ];
787
- const context = this.#buildWith(repositories, this.#storageFactory ?? defaultStorageFactory ?? new InMemoryStorageFactory());
865
+ const generated = await this.#loadGeneratedArtifacts([...this.#entityTypes]);
866
+ const repositories = [...this.#repositories, ...generated.repositories];
867
+ const context = this.#buildWith(repositories, this.#storageFactory ?? defaultStorageFactory ?? new InMemoryStorageFactory(), generated.standalone);
788
868
  try {
789
869
  await ContextParts.integrationReady(context);
790
870
  }
@@ -799,88 +879,146 @@ export class BoundedContextBuilder {
799
879
  }
800
880
  return context;
801
881
  }
802
- #buildWith(repositories, storageFactory) {
803
- let registry = this.#subscriptionRegistry;
882
+ #buildWith(repositories, storageFactory, standalone = []) {
883
+ const resources = this.#subscriptionRegistry === undefined ? {} : { registry: this.#subscriptionRegistry };
804
884
  this.#subscriptionRegistry = undefined;
805
885
  const registeredRepositories = [...repositories];
806
- let eventStore;
807
- let systemEventStore;
808
- let eventBus;
809
- let systemEventBus;
810
- let stand;
811
- let systemStand;
812
- let runtime;
813
886
  try {
814
- ContextParts.preflightRepositories(registeredRepositories);
815
- const eventDispatchers = [
816
- ...ContextParts.repositoryEventDispatchers(registeredRepositories),
817
- ...this.#eventDispatchers,
818
- ];
819
- const repositorySystemEventDispatchers = ContextParts.repositorySystemEventDispatchers(registeredRepositories);
820
- const domainEventDispatchers = ContextParts.domainEventDispatchers(eventDispatchers);
821
- const systemEventDispatchers = [
822
- ...ContextParts.systemEventDispatchers(eventDispatchers),
823
- ...repositorySystemEventDispatchers,
824
- ];
825
- const commandBus = new CommandBus([
826
- ...this.#commandDispatchers,
827
- ...ContextParts.repositoryCommandDispatchers(registeredRepositories),
828
- ]);
829
- const systemSpec = ContextParts.createSystemSpec(this.#specSnapshot, this.#persistSystemEvents);
830
- systemEventStore = systemSpec.storesEvents
831
- ? new EventStore(ContextParts.createStorageMode(systemSpec), storageFactory)
832
- : undefined;
833
- systemEventBus = eventBusAccess.createSystemBus(systemEventStore);
834
- for (const dispatcher of systemEventDispatchers)
835
- systemEventBus.register(dispatcher);
836
- systemStand = new Stand({
837
- context: ContextParts.createStorageMode(systemSpec),
838
- storageFactory,
839
- });
840
- eventStore = this.createEventStore(storageFactory);
841
- eventBus = new EventBus(eventStore);
842
- for (const dispatcher of domainEventDispatchers)
843
- eventBus.register(dispatcher);
844
- eventBusAccess.registerSchemas(eventBus, ContextParts.repositoryProducedEventSchemas(registeredRepositories));
845
- stand = new Stand({
846
- context: ContextParts.createStorageMode(this.#specSnapshot),
847
- storageFactory,
848
- });
849
- registry ??= new StorageSubscriptionRegistry(ContextParts.createSubscriptionStorageContext(this.#specSnapshot), storageFactory);
850
- runtime = new SubscriptionRuntime(stand, systemStand, eventBus, systemEventBus, registry);
851
- const context = ContextParts.createBoundedContext(this.#specSnapshot, commandBus, eventBus, systemEventBus, stand, systemStand, runtime, systemSpec, storageFactory, registeredRepositories, this.#deliveryStrategy);
852
- ContextParts.attachIntegration(context, eventBus, systemSpec, ContextParts.externalEventSchemas(domainEventDispatchers));
853
- return context;
887
+ return this.#assembleContext(registeredRepositories, storageFactory, standalone, resources);
854
888
  }
855
889
  catch (error) {
856
- const cleanupErrors = [];
857
- ContextParts.attemptCleanup(() => runtime?.abortClose(), cleanupErrors);
858
- if (runtime === undefined) {
859
- ContextParts.attemptCleanup(() => void registry?.close().catch(() => undefined), cleanupErrors);
860
- }
861
- ContextParts.attemptCleanup(() => void stand?.close().catch(() => undefined), cleanupErrors);
862
- ContextParts.attemptCleanup(() => void systemStand?.close().catch(() => undefined), cleanupErrors);
863
- ContextParts.attemptCleanup(() => {
864
- if (systemEventBus !== undefined)
865
- eventBusAccess.abortClose(systemEventBus);
866
- else if (systemEventStore !== undefined)
867
- systemEventStore.close();
868
- }, cleanupErrors);
869
- ContextParts.attemptCleanup(() => {
870
- if (eventBus !== undefined)
871
- eventBusAccess.abortClose(eventBus);
872
- else if (eventStore !== undefined)
873
- eventStore.close();
874
- }, cleanupErrors);
875
- if (cleanupErrors.length > 0) {
876
- throw new AggregateError([error, ...cleanupErrors], "Bounded Context build failed during cleanup.");
877
- }
878
- throw error;
890
+ return this.#failBuild(resources, error);
879
891
  }
880
892
  }
881
- async #loadGeneratedRepositories(entityTypes) {
882
- if (entityTypes.length === 0) {
883
- return Object.freeze([]);
893
+ #assembleContext(repositories, storageFactory, standalone, resources) {
894
+ ContextParts.preflightRepositories(repositories);
895
+ const dispatchers = this.#buildDispatchers(repositories);
896
+ const buses = this.#buildBuses(repositories, storageFactory, dispatchers, resources);
897
+ const standaloneEvent = this.#installStandalone(standalone, buses);
898
+ for (const dispatcher of dispatchers.domain)
899
+ buses.eventBus.register(dispatcher);
900
+ eventBusAccess.registerSchemas(buses.eventBus, [
901
+ ...ContextParts.repositoryProducedEventSchemas(repositories),
902
+ ...ContextParts.standaloneProducedEventSchemas(standalone),
903
+ ]);
904
+ const running = this.#buildRuntime(storageFactory, buses, resources);
905
+ const context = this.#createContext(repositories, storageFactory, buses, running);
906
+ ContextParts.attachIntegration(context, buses.eventBus, buses.systemSpec, ContextParts.externalEventSchemas([
907
+ ...dispatchers.domain,
908
+ ...(standaloneEvent === undefined ? [] : [standaloneEvent]),
909
+ ]));
910
+ return context;
911
+ }
912
+ #buildDispatchers(repositories) {
913
+ const event = [
914
+ ...ContextParts.repositoryEventDispatchers(repositories),
915
+ ...this.#eventDispatchers,
916
+ ];
917
+ return {
918
+ domain: ContextParts.domainEventDispatchers(event),
919
+ system: [
920
+ ...ContextParts.systemEventDispatchers(event),
921
+ ...ContextParts.repositorySystemEventDispatchers(repositories),
922
+ ],
923
+ };
924
+ }
925
+ #buildBuses(repositories, storageFactory, dispatchers, resources) {
926
+ const commandBus = new CommandBus([
927
+ ...this.#commandDispatchers,
928
+ ...ContextParts.repositoryCommandDispatchers(repositories),
929
+ ]);
930
+ resources.commandBus = commandBus;
931
+ const system = this.#buildSystemBuses(storageFactory, dispatchers.system, resources);
932
+ const eventStore = this.createEventStore(storageFactory);
933
+ resources.eventStore = eventStore;
934
+ const eventBus = new EventBus(eventStore);
935
+ resources.eventBus = eventBus;
936
+ const publisher = new SignalPublisher(commandBus, eventBus, system.eventBus, this.#specSnapshot.name.value);
937
+ resources.publisher = publisher;
938
+ return {
939
+ commandBus,
940
+ eventStore,
941
+ systemEventStore: system.eventStore,
942
+ eventBus,
943
+ systemEventBus: system.eventBus,
944
+ publisher,
945
+ systemStand: system.stand,
946
+ systemSpec: system.spec,
947
+ };
948
+ }
949
+ #buildSystemBuses(storageFactory, dispatchers, resources) {
950
+ const systemSpec = ContextParts.createSystemSpec(this.#specSnapshot, this.#persistSystemEvents);
951
+ const systemEventStore = systemSpec.storesEvents
952
+ ? new EventStore(ContextParts.createStorageMode(systemSpec), storageFactory)
953
+ : undefined;
954
+ if (systemEventStore !== undefined)
955
+ resources.systemEventStore = systemEventStore;
956
+ const systemEventBus = eventBusAccess.createSystemBus(systemEventStore);
957
+ resources.systemEventBus = systemEventBus;
958
+ for (const dispatcher of dispatchers)
959
+ systemEventBus.register(dispatcher);
960
+ const systemStand = new Stand({
961
+ context: ContextParts.createStorageMode(systemSpec),
962
+ storageFactory,
963
+ });
964
+ resources.systemStand = systemStand;
965
+ return {
966
+ eventStore: systemEventStore,
967
+ eventBus: systemEventBus,
968
+ stand: systemStand,
969
+ spec: systemSpec,
970
+ };
971
+ }
972
+ #installStandalone(standalone, buses) {
973
+ ContextParts.assertUniqueCommandReceptors(standalone);
974
+ const runtime = standalone.length === 0
975
+ ? undefined
976
+ : new StandaloneHandlerRuntime(ContextParts.matchStandaloneHandlers(standalone, this.#standaloneInstances(), buses.publisher));
977
+ const command = runtime?.commandDispatcher();
978
+ const event = runtime?.eventDispatcher();
979
+ const state = runtime?.stateDispatcher();
980
+ if (command !== undefined)
981
+ buses.commandBus.register(command);
982
+ if (event !== undefined)
983
+ buses.eventBus.register(event);
984
+ if (state !== undefined)
985
+ buses.systemEventBus.register(state);
986
+ return event;
987
+ }
988
+ #buildRuntime(storageFactory, buses, resources) {
989
+ const stand = new Stand({
990
+ context: ContextParts.createStorageMode(this.#specSnapshot),
991
+ storageFactory,
992
+ });
993
+ resources.stand = stand;
994
+ const registry = resources.registry ??
995
+ new StorageSubscriptionRegistry(ContextParts.createSubscriptionStorageContext(this.#specSnapshot), storageFactory);
996
+ resources.registry = registry;
997
+ const runtime = new SubscriptionRuntime(stand, buses.systemStand, buses.eventBus, buses.systemEventBus, registry);
998
+ resources.runtime = runtime;
999
+ return { stand, registry, runtime };
1000
+ }
1001
+ #createContext(repositories, storageFactory, buses, running) {
1002
+ return ContextParts.createBoundedContext(this.#specSnapshot, buses.commandBus, buses.eventBus, buses.systemEventBus, buses.publisher, running.stand, buses.systemStand, running.runtime, buses.systemSpec, storageFactory, repositories, this.#deliveryStrategy);
1003
+ }
1004
+ #failBuild(resources, error) {
1005
+ const cleanupErrors = [];
1006
+ ContextParts.attemptCleanup(() => resources.runtime?.abortClose(), cleanupErrors);
1007
+ ContextParts.attemptCleanup(() => resources.publisher?.abortAssembly(), cleanupErrors);
1008
+ if (resources.runtime === undefined) {
1009
+ ContextParts.attemptCleanup(() => void resources.registry?.close().catch(() => undefined), cleanupErrors);
1010
+ }
1011
+ ContextParts.attemptCleanup(() => void resources.stand?.close().catch(() => undefined), cleanupErrors);
1012
+ ContextParts.attemptCleanup(() => void resources.systemStand?.close().catch(() => undefined), cleanupErrors);
1013
+ ContextParts.cleanupBuildBuses(resources, cleanupErrors);
1014
+ if (cleanupErrors.length > 0) {
1015
+ throw new AggregateError([error, ...cleanupErrors], "Bounded Context build failed during cleanup.");
1016
+ }
1017
+ throw error;
1018
+ }
1019
+ async #loadGeneratedArtifacts(entityTypes) {
1020
+ if (entityTypes.length === 0 && this.#standaloneInstances().length === 0) {
1021
+ return Object.freeze({ repositories: Object.freeze([]), standalone: Object.freeze([]) });
884
1022
  }
885
1023
  const root = ContextParts.requireGeneratedRegistryRoot(this.#generatedRegistryRoot);
886
1024
  const discovery = new GeneratedRegistryDiscovery();
@@ -896,8 +1034,19 @@ export class BoundedContextBuilder {
896
1034
  throw error;
897
1035
  }));
898
1036
  const metadata = ContextParts.ingestGeneratedRegistries(registries);
899
- return Object.freeze(entityTypes.map((entityType) => ContextParts.createGeneratedRepository(entityType, registries, metadata, this.#generatedRepositoryOptions.get(entityType))));
1037
+ const repositories = Object.freeze(entityTypes.map((entityType) => ContextParts.createGeneratedRepository(entityType, registries, metadata, this.#generatedRepositoryOptions.get(entityType))));
1038
+ const standalone = Object.freeze(registries.flatMap((registry) => registry.receivers.filter((receiver) => receiver.receiverKind === "standalone")));
1039
+ return Object.freeze({ repositories, standalone });
1040
+ }
1041
+ #standaloneInstances() {
1042
+ return Object.freeze([...this.#assignees, ...this.#commanders, ...this.#eventReceivers]);
900
1043
  }
1044
+ /**
1045
+ * Creates the domain EventStore using this context's storage mode.
1046
+ *
1047
+ * @param storageFactory Storage provider for event records.
1048
+ * @returns Domain EventStore.
1049
+ */
901
1050
  createEventStore(storageFactory) {
902
1051
  return new EventStore(ContextParts.createStorageMode(this.#specSnapshot), storageFactory);
903
1052
  }
@@ -914,7 +1063,7 @@ export class ContextSpec {
914
1063
  constructContextSpec = (snapshot, token) => new ContextSpec(snapshot, token);
915
1064
  }
916
1065
  /**
917
- * Creates a framework-owned context specification.
1066
+ * Creates a context specification from prepared values.
918
1067
  *
919
1068
  * @param snapshot Contains immutable specification values.
920
1069
  * @param token Proves framework-controlled construction.
@@ -966,10 +1115,19 @@ export class ContextSpec {
966
1115
  }
967
1116
  }
968
1117
  const registeredRepositories = new WeakMap();
1118
+ /**
1119
+ * Reports failure while redispatching a stored event during read-side replay.
1120
+ */
969
1121
  class CatchUpReplayError extends Error {
970
1122
  code = "READ_SIDE_CATCH_UP_REPLAY_FAILED";
971
1123
  eventId;
972
1124
  detail;
1125
+ /**
1126
+ * Creates a replay error tied to the stored event that failed.
1127
+ *
1128
+ * @param eventId Identifies the stored event.
1129
+ * @param detail Contains the bounded cause name and message.
1130
+ */
973
1131
  constructor(eventId, detail) {
974
1132
  super(`Read-side catch-up failed for stored event "${eventId}".`);
975
1133
  this.name = "ReadCatchUpReplayError";
@@ -982,6 +1140,63 @@ class CatchUpReplayError extends Error {
982
1140
  * Assembles private bounded-context lifecycle and replay details.
983
1141
  */
984
1142
  const ContextParts = Object.freeze({
1143
+ /**
1144
+ * Rejects duplicate command receptors in generated standalone groups.
1145
+ *
1146
+ * @param receivers Generated groups to validate.
1147
+ */
1148
+ assertUniqueCommandReceptors(receivers) {
1149
+ const receptorByType = new Map();
1150
+ for (const receiver of receivers) {
1151
+ for (const handler of receiver.handlers) {
1152
+ if (handler.kind !== "command-assignment" && handler.kind !== "command-substitution")
1153
+ continue;
1154
+ const typeName = TypeUrls.derive(handler.input.schema);
1155
+ const prior = receptorByType.get(typeName);
1156
+ if (prior !== undefined)
1157
+ throw new Error(`Standalone command receptors conflict for "${typeName}": ${prior} and ${receiver.receiverType.name}.`);
1158
+ receptorByType.set(typeName, receiver.receiverType.name);
1159
+ }
1160
+ }
1161
+ },
1162
+ /**
1163
+ * Matches generated standalone groups to their registered instances.
1164
+ *
1165
+ * @param generated Generated handler groups.
1166
+ * @param instances Registered standalone instances.
1167
+ * @param publisher Publisher used by bindings.
1168
+ * @returns Frozen bindings in generated order.
1169
+ */
1170
+ matchStandaloneHandlers(generated, instances, publisher) {
1171
+ const byConstructor = new Map();
1172
+ for (const instance of instances) {
1173
+ const constructor = instance.constructor;
1174
+ if (byConstructor.has(constructor)) {
1175
+ throw new Error(`Standalone receiver ${constructor.name} is registered more than once.`);
1176
+ }
1177
+ byConstructor.set(constructor, instance);
1178
+ }
1179
+ const bindings = [];
1180
+ for (const receiver of generated) {
1181
+ const receiverType = receiver.receiverType;
1182
+ const instance = byConstructor.get(receiverType);
1183
+ if (instance === undefined) {
1184
+ throw new Error(`Generated standalone receiver ${receiverType.name} has no explicitly registered instance.`);
1185
+ }
1186
+ bindings.push(Object.freeze({ group: receiver, instance, publisher }));
1187
+ byConstructor.delete(receiverType);
1188
+ }
1189
+ if (byConstructor.size > 0) {
1190
+ throw new Error(`Registered standalone receiver ${[...byConstructor.keys()][0]?.name ?? "unknown"} has no generated metadata.`);
1191
+ }
1192
+ return Object.freeze(bindings);
1193
+ },
1194
+ /**
1195
+ * Calls cleanup and records any thrown error.
1196
+ *
1197
+ * @param onCleanup Cleanup action.
1198
+ * @param errors Collection receiving failures.
1199
+ */
985
1200
  attemptCleanup(onCleanup, errors) {
986
1201
  try {
987
1202
  onCleanup();
@@ -990,39 +1205,136 @@ const ContextParts = Object.freeze({
990
1205
  errors.push(error);
991
1206
  }
992
1207
  },
1208
+ /**
1209
+ * Closes buses and stores after context assembly fails.
1210
+ *
1211
+ * @param resources Partially created resources.
1212
+ * @param errors Collection receiving failures.
1213
+ */
1214
+ cleanupBuildBuses(resources, errors) {
1215
+ ContextParts.attemptCleanup(() => {
1216
+ if (resources.commandBus !== undefined)
1217
+ commandBusAccess.abortClose(resources.commandBus);
1218
+ }, errors);
1219
+ ContextParts.attemptCleanup(() => {
1220
+ if (resources.systemEventBus !== undefined)
1221
+ eventBusAccess.abortClose(resources.systemEventBus);
1222
+ else
1223
+ resources.systemEventStore?.close();
1224
+ }, errors);
1225
+ ContextParts.attemptCleanup(() => {
1226
+ if (resources.eventBus !== undefined)
1227
+ eventBusAccess.abortClose(resources.eventBus);
1228
+ else
1229
+ resources.eventStore?.close();
1230
+ }, errors);
1231
+ },
1232
+ /**
1233
+ * Returns constituent failures from nested aggregate errors.
1234
+ *
1235
+ * @param errors Errors to expand.
1236
+ * @returns Flattened error list.
1237
+ */
993
1238
  flattenErrors(errors) {
994
1239
  return errors.flatMap((error) => error instanceof AggregateError ? ContextParts.flattenErrors(error.errors) : [error]);
995
1240
  },
1241
+ /**
1242
+ * Rejects construction without the framework token.
1243
+ *
1244
+ * @param token Supplied token.
1245
+ * @param message Error message for an invalid token.
1246
+ */
996
1247
  requireFrameworkConstructionToken(token, message) {
997
1248
  if (token !== frameworkConstructionToken) {
998
1249
  throw new TypeError(message);
999
1250
  }
1000
1251
  },
1252
+ /**
1253
+ * Creates a context specification from its snapshot.
1254
+ *
1255
+ * @param specSnapshot Snapshot used for construction.
1256
+ * @returns Constructed context specification.
1257
+ */
1001
1258
  createContextSpec(specSnapshot) {
1002
1259
  return constructContextSpec(specSnapshot, frameworkConstructionToken);
1003
1260
  },
1261
+ /**
1262
+ * Creates a builder for a context snapshot.
1263
+ *
1264
+ * @param specSnapshot Context configuration snapshot.
1265
+ * @returns Constructed context builder.
1266
+ */
1004
1267
  createBoundedContextBuilder(specSnapshot) {
1005
1268
  return constructBoundedContextBuilder(specSnapshot, frameworkConstructionToken);
1006
1269
  },
1007
- createBoundedContext(specSnapshot, commandBus, eventBus, systemEventBus, stand, systemStand, runtime, systemSpec, storageFactory, repositories, deliveryStrategy) {
1270
+ /**
1271
+ * Creates a bounded context from assembled runtime components.
1272
+ *
1273
+ * @param specSnapshot Context configuration.
1274
+ * @param commandBus Command dispatch bus.
1275
+ * @param eventBus Domain event bus.
1276
+ * @param systemEventBus System event bus.
1277
+ * @param publisher Signal publisher.
1278
+ * @param stand Domain read-side Stand.
1279
+ * @param systemStand System read-side Stand.
1280
+ * @param runtime Subscription runtime.
1281
+ * @param systemSpec Paired System specification.
1282
+ * @param storageFactory Storage provider.
1283
+ * @param repositories Registered repository views.
1284
+ * @param deliveryStrategy Delivery sharding strategy.
1285
+ * @returns Constructed bounded context.
1286
+ */
1287
+ createBoundedContext(specSnapshot, commandBus, eventBus, systemEventBus, publisher, stand, systemStand, runtime, systemSpec, storageFactory, repositories, deliveryStrategy) {
1008
1288
  return constructBoundedContext({
1009
- name: specSnapshot.name,
1010
- tenantMode: ContextParts.toTenantMode(specSnapshot.multitenant),
1011
- spec: specSnapshot,
1012
- }, commandBus, eventBus, systemEventBus, stand, systemStand, runtime, systemSpec, storageFactory, repositories, deliveryStrategy, frameworkConstructionToken);
1289
+ snapshot: {
1290
+ name: specSnapshot.name,
1291
+ tenantMode: ContextParts.toTenantMode(specSnapshot.multitenant),
1292
+ spec: specSnapshot,
1293
+ },
1294
+ commandBus,
1295
+ eventBus,
1296
+ systemEventBus,
1297
+ publisher,
1298
+ stand,
1299
+ systemStand,
1300
+ subscriptionRuntime: runtime,
1301
+ systemSpec,
1302
+ storageFactory,
1303
+ repositories,
1304
+ deliveryStrategy,
1305
+ token: frameworkConstructionToken,
1306
+ });
1013
1307
  },
1308
+ /**
1309
+ * Creates the storage mode represented by a context snapshot.
1310
+ *
1311
+ * @param specSnapshot Context configuration snapshot.
1312
+ * @returns Storage mode for the context.
1313
+ */
1014
1314
  createStorageMode(specSnapshot) {
1015
1315
  return Object.freeze({
1016
1316
  name: specSnapshot.name.value,
1017
1317
  multitenant: specSnapshot.multitenant,
1018
1318
  });
1019
1319
  },
1320
+ /**
1321
+ * Creates a non-tenant-scoped subscription storage context.
1322
+ *
1323
+ * @param specSnapshot Context configuration snapshot.
1324
+ * @returns Subscription storage context.
1325
+ */
1020
1326
  createSubscriptionStorageContext(specSnapshot) {
1021
1327
  return Object.freeze({
1022
1328
  name: `${specSnapshot.name.value}:subscriptions`,
1023
1329
  multitenant: false,
1024
1330
  });
1025
1331
  },
1332
+ /**
1333
+ * Captures the shard count and validates later shard results.
1334
+ *
1335
+ * @param strategy Configured delivery strategy.
1336
+ * @returns Immutable delivery strategy snapshot.
1337
+ */
1026
1338
  snapshotDeliveryStrategy(strategy) {
1027
1339
  if (!Number.isSafeInteger(strategy.shardCount) || strategy.shardCount <= 0) {
1028
1340
  throw new Error("Delivery strategy shard count must be a positive safe integer.");
@@ -1030,6 +1342,13 @@ const ContextParts = Object.freeze({
1030
1342
  const shardCount = strategy.shardCount;
1031
1343
  return Object.freeze({
1032
1344
  shardCount,
1345
+ /**
1346
+ * Returns a target shard after verifying the captured shard total.
1347
+ *
1348
+ * @param targetId Inbox target identifier.
1349
+ * @param targetType Inbox target type.
1350
+ * @returns Validated shard index.
1351
+ */
1033
1352
  shardFor(targetId, targetType) {
1034
1353
  const shard = strategy.shardFor(InboxTargets.clone(targetId), targetType);
1035
1354
  if (shard.ofTotal !== shardCount) {
@@ -1039,6 +1358,13 @@ const ContextParts = Object.freeze({
1039
1358
  },
1040
1359
  });
1041
1360
  },
1361
+ /**
1362
+ * Creates the storage context for read-side catch-up.
1363
+ *
1364
+ * @param specSnapshot Context configuration.
1365
+ * @param options Catch-up tenant options.
1366
+ * @returns Storage context for the selected scope.
1367
+ */
1042
1368
  catchUpStorageContext(specSnapshot, options) {
1043
1369
  if (!specSnapshot.multitenant) {
1044
1370
  if (options.tenantId !== undefined) {
@@ -1056,6 +1382,12 @@ const ContextParts = Object.freeze({
1056
1382
  tenantId: TenantBoundary.from(tenantId).tenantId,
1057
1383
  });
1058
1384
  },
1385
+ /**
1386
+ * Creates a bounded-context name after checking reserved names.
1387
+ *
1388
+ * @param value Candidate context name.
1389
+ * @returns Immutable validated name.
1390
+ */
1059
1391
  createBoundedContextName(value) {
1060
1392
  if (typeof value !== "string" ||
1061
1393
  value.trim().length === 0 ||
@@ -1064,6 +1396,13 @@ const ContextParts = Object.freeze({
1064
1396
  }
1065
1397
  return Object.freeze({ value });
1066
1398
  },
1399
+ /**
1400
+ * Creates a context snapshot with event storage enabled.
1401
+ *
1402
+ * @param name Context name.
1403
+ * @param multitenant Whether tenant isolation is enabled.
1404
+ * @returns Immutable specification snapshot.
1405
+ */
1067
1406
  createSpecSnapshot(name, multitenant) {
1068
1407
  return Object.freeze({
1069
1408
  name: ContextParts.createBoundedContextName(name),
@@ -1071,9 +1410,21 @@ const ContextParts = Object.freeze({
1071
1410
  storesEvents: true,
1072
1411
  });
1073
1412
  },
1413
+ /**
1414
+ * Copies a bounded-context name into an immutable value.
1415
+ *
1416
+ * @param name Source context name.
1417
+ * @returns Immutable name copy.
1418
+ */
1074
1419
  cloneName(name) {
1075
1420
  return ContextParts.createBoundedContextName(name.value);
1076
1421
  },
1422
+ /**
1423
+ * Copies a context specification snapshot and its name.
1424
+ *
1425
+ * @param spec Source specification.
1426
+ * @returns Immutable specification copy.
1427
+ */
1077
1428
  cloneSpecSnapshot(spec) {
1078
1429
  return Object.freeze({
1079
1430
  name: ContextParts.cloneName(spec.name),
@@ -1081,6 +1432,12 @@ const ContextParts = Object.freeze({
1081
1432
  storesEvents: spec.storesEvents,
1082
1433
  });
1083
1434
  },
1435
+ /**
1436
+ * Copies a bounded-context snapshot and its specification.
1437
+ *
1438
+ * @param snapshot Source context snapshot.
1439
+ * @returns Immutable context snapshot copy.
1440
+ */
1084
1441
  cloneContextSnapshot(snapshot) {
1085
1442
  return Object.freeze({
1086
1443
  name: ContextParts.cloneName(snapshot.name),
@@ -1088,6 +1445,13 @@ const ContextParts = Object.freeze({
1088
1445
  spec: ContextParts.cloneSpecSnapshot(snapshot.spec),
1089
1446
  });
1090
1447
  },
1448
+ /**
1449
+ * Creates the System specification paired with a domain context.
1450
+ *
1451
+ * @param domainSpec Domain specification.
1452
+ * @param storesEvents Whether System events are stored.
1453
+ * @returns System context snapshot.
1454
+ */
1091
1455
  createSystemSpec(domainSpec, storesEvents) {
1092
1456
  return Object.freeze({
1093
1457
  name: ContextParts.createBoundedContextName(`${domainSpec.name.value}_System`),
@@ -1095,24 +1459,49 @@ const ContextParts = Object.freeze({
1095
1459
  storesEvents,
1096
1460
  });
1097
1461
  },
1462
+ /**
1463
+ * Creates a pairing snapshot for domain and System specifications.
1464
+ *
1465
+ * @param snapshot Domain snapshot.
1466
+ * @param systemSpec Paired System specification.
1467
+ * @returns Immutable context pairing.
1468
+ */
1098
1469
  createSystemPairing(snapshot, systemSpec) {
1099
1470
  return Object.freeze({
1100
1471
  domain: ContextParts.cloneContextSnapshot(snapshot),
1101
1472
  system: ContextParts.cloneSpecSnapshot(systemSpec),
1102
1473
  });
1103
1474
  },
1475
+ /**
1476
+ * Copies both specifications in a System pairing.
1477
+ *
1478
+ * @param pairing Pairing to copy.
1479
+ * @returns Immutable pairing copy.
1480
+ */
1104
1481
  cloneSystemPairing(pairing) {
1105
1482
  return Object.freeze({
1106
1483
  domain: ContextParts.cloneContextSnapshot(pairing.domain),
1107
1484
  system: ContextParts.cloneSpecSnapshot(pairing.system),
1108
1485
  });
1109
1486
  },
1487
+ /**
1488
+ * Lists non-internal event type URLs exposed by an event bus.
1489
+ *
1490
+ * @param eventBus Bus whose schemas are inspected.
1491
+ * @returns Frozen exposed type URLs.
1492
+ */
1110
1493
  exposedEventTypeUrls(eventBus) {
1111
1494
  return Object.freeze(eventBusAccess
1112
1495
  .eventSchemas(eventBus)
1113
1496
  .filter((schema) => !ContextParts.isInternalEventSchema(schema))
1114
1497
  .map((schema) => TypeUrls.derive(schema)));
1115
1498
  },
1499
+ /**
1500
+ * Collects external event schemas by type URL.
1501
+ *
1502
+ * @param dispatchers Dispatchers to inspect.
1503
+ * @returns Unique frozen schemas.
1504
+ */
1116
1505
  externalEventSchemas(dispatchers) {
1117
1506
  const schemas = new Map();
1118
1507
  for (const dispatcher of dispatchers) {
@@ -1122,6 +1511,14 @@ const ContextParts = Object.freeze({
1122
1511
  }
1123
1512
  return Object.freeze([...schemas.values()]);
1124
1513
  },
1514
+ /**
1515
+ * Opens the integration broker and associates context readiness.
1516
+ *
1517
+ * @param context Context receiving integration.
1518
+ * @param eventBus Event bus supplied to broker.
1519
+ * @param systemSpec Paired System specification.
1520
+ * @param externalEventSchemas Schemas accepted from outside.
1521
+ */
1125
1522
  attachIntegration(context, eventBus, systemSpec, externalEventSchemas) {
1126
1523
  const broker = new IntegrationBroker({
1127
1524
  contextName: create(BoundedContextNameSchema, { value: context.name.value }),
@@ -1144,6 +1541,13 @@ const ContextParts = Object.freeze({
1144
1541
  void ready.catch(() => undefined);
1145
1542
  contextIntegrations.set(context, { broker, ready });
1146
1543
  },
1544
+ /**
1545
+ * Posts an event after integration readiness and tenant validation.
1546
+ *
1547
+ * @param context Target context.
1548
+ * @param event Event to post.
1549
+ * @returns Promise fulfilled after posting.
1550
+ */
1147
1551
  postContextEvent(context, event) {
1148
1552
  const buses = contextEventBuses.get(context);
1149
1553
  if (buses === undefined)
@@ -1155,15 +1559,34 @@ const ContextParts = Object.freeze({
1155
1559
  return buses[0].post(event);
1156
1560
  });
1157
1561
  },
1562
+ /**
1563
+ * Closes the context integration broker.
1564
+ *
1565
+ * @param context Context whose broker is closed.
1566
+ * @returns Promise fulfilled after closing.
1567
+ */
1158
1568
  closeIntegration(context) {
1159
1569
  return contextIntegrations.get(context)?.broker.close() ?? Promise.resolve();
1160
1570
  },
1571
+ /**
1572
+ * Publishes an imported event through the integration broker.
1573
+ *
1574
+ * @param context Context integration.
1575
+ * @param event Imported event.
1576
+ * @returns Promise fulfilled after publishing.
1577
+ */
1161
1578
  publishImported(context, event) {
1162
1579
  const integration = contextIntegrations.get(context);
1163
1580
  if (integration === undefined)
1164
1581
  return Promise.reject(new Error("Context integration is unavailable."));
1165
1582
  return integration.ready.then(() => integration.broker.publishImported(event));
1166
1583
  },
1584
+ /**
1585
+ * Checks imported event tenant against the context tenant mode.
1586
+ *
1587
+ * @param context Target context.
1588
+ * @param event Imported event to validate.
1589
+ */
1167
1590
  validateImportedTenant(context, event) {
1168
1591
  const tenantId = ContextParts.readReplayTenant(event);
1169
1592
  if (!context.isMultitenant) {
@@ -1177,17 +1600,41 @@ const ContextParts = Object.freeze({
1177
1600
  }
1178
1601
  TenantBoundary.from(tenantId);
1179
1602
  },
1603
+ /**
1604
+ * Waits for the integration broker to become ready.
1605
+ *
1606
+ * @param context Context whose readiness is observed.
1607
+ * @returns Readiness promise.
1608
+ */
1180
1609
  integrationReady(context) {
1181
1610
  return contextIntegrations.get(context)?.ready ?? Promise.resolve();
1182
1611
  },
1612
+ /**
1613
+ * Checks whether a schema carries an internal or SPI marker.
1614
+ *
1615
+ * @param schema Event schema to inspect.
1616
+ * @returns True when an internal marker is set.
1617
+ */
1183
1618
  isInternalEventSchema(schema) {
1184
1619
  return ((hasOption(schema, internal_type) && getOption(schema, internal_type)) ||
1185
1620
  (hasOption(schema, SPI_type) && getOption(schema, SPI_type)) ||
1186
1621
  (hasOption(schema.file, internal_all) && getOption(schema.file, internal_all)));
1187
1622
  },
1623
+ /**
1624
+ * Converts the multitenant flag to a tenant mode.
1625
+ *
1626
+ * @param multitenant Context isolation flag.
1627
+ * @returns Corresponding tenant mode.
1628
+ */
1188
1629
  toTenantMode(multitenant) {
1189
1630
  return multitenant ? "multitenant" : "single-tenant";
1190
1631
  },
1632
+ /**
1633
+ * Reads the System pairing recorded for a built context.
1634
+ *
1635
+ * @param context Context to inspect.
1636
+ * @returns Pairing snapshot; throws if unpaired.
1637
+ */
1191
1638
  requireSystemPairing(context) {
1192
1639
  const pairing = contextSystemPairings.get(context);
1193
1640
  if (pairing === undefined) {
@@ -1195,6 +1642,12 @@ const ContextParts = Object.freeze({
1195
1642
  }
1196
1643
  return pairing;
1197
1644
  },
1645
+ /**
1646
+ * Reads the tenant index recorded for a built context.
1647
+ *
1648
+ * @param context Context to inspect.
1649
+ * @returns Tenant index; throws if unavailable.
1650
+ */
1198
1651
  requireTenantIndex(context) {
1199
1652
  const tenantIndex = contextTenantIndexes.get(context);
1200
1653
  if (tenantIndex === undefined) {
@@ -1202,16 +1655,33 @@ const ContextParts = Object.freeze({
1202
1655
  }
1203
1656
  return tenantIndex;
1204
1657
  },
1658
+ /**
1659
+ * Rejects values that are not Repository instances.
1660
+ *
1661
+ * @param repository Candidate value.
1662
+ * @param operation Operation name included in the error.
1663
+ */
1205
1664
  requireRepositoryInstance(repository, operation) {
1206
1665
  if (!repositoryAccess.hasInstance(repository)) {
1207
1666
  throw new TypeError(`${operation} requires a Repository instance.`);
1208
1667
  }
1209
1668
  },
1669
+ /**
1670
+ * Rejects values that are not Entity classes.
1671
+ *
1672
+ * @param entityType Candidate Entity constructor.
1673
+ * @param operation Operation name included in the error.
1674
+ */
1210
1675
  requireEntityClass(entityType, operation) {
1211
1676
  if (typeof entityType !== "function") {
1212
1677
  throw new TypeError(`${operation} requires a Repository instance. Use an entity class only with buildAsync().`);
1213
1678
  }
1214
1679
  },
1680
+ /**
1681
+ * Rejects synchronous assembly when classes need generated metadata.
1682
+ *
1683
+ * @param entityTypes Entity classes to assemble.
1684
+ */
1215
1685
  rejectSyncEntityAssembly(entityTypes) {
1216
1686
  if (entityTypes.size === 0) {
1217
1687
  return;
@@ -1219,6 +1689,12 @@ const ContextParts = Object.freeze({
1219
1689
  throw new Error("BoundedContextBuilder.build() cannot assemble entity classes from generated metadata. " +
1220
1690
  "Use buildAsync().");
1221
1691
  },
1692
+ /**
1693
+ * Validates that a root exists when generated metadata must be loaded.
1694
+ *
1695
+ * @param root Configured registry location.
1696
+ * @returns Configured root; throws when absent.
1697
+ */
1222
1698
  requireGeneratedRegistryRoot(root) {
1223
1699
  if (root !== undefined) {
1224
1700
  return root;
@@ -1226,6 +1702,12 @@ const ContextParts = Object.freeze({
1226
1702
  throw new Error("BoundedContextBuilder.buildAsync() requires withGeneratedRegistryRoot(root) " +
1227
1703
  "when assembling entity classes from generated metadata.");
1228
1704
  },
1705
+ /**
1706
+ * Resolves the generated registry module and verifies it stays beneath the configured root.
1707
+ *
1708
+ * @param root Configured filesystem path or file URL.
1709
+ * @returns Registry module file URL.
1710
+ */
1229
1711
  async trustedGeneratedRegistryModule(root) {
1230
1712
  const trustedRoot = await ContextParts.canonicalGeneratedRegistryRoot(root);
1231
1713
  const registryPath = resolve(trustedRoot, generatedRegistryFile);
@@ -1236,6 +1718,12 @@ const ContextParts = Object.freeze({
1236
1718
  }
1237
1719
  return pathToFileURL(canonicalRegistryPath);
1238
1720
  },
1721
+ /**
1722
+ * Resolves a generated registry root to its canonical filesystem path.
1723
+ *
1724
+ * @param root Configured path or file URL.
1725
+ * @returns Canonical directory path.
1726
+ */
1239
1727
  async canonicalGeneratedRegistryRoot(root) {
1240
1728
  const rootPath = ContextParts.generatedRegistryRootPath(root);
1241
1729
  try {
@@ -1245,6 +1733,12 @@ const ContextParts = Object.freeze({
1245
1733
  throw new Error(`Generated registry root "${rootPath}" must be an existing readable directory.`, { cause: error });
1246
1734
  }
1247
1735
  },
1736
+ /**
1737
+ * Converts a registry root path or file URL to an absolute filesystem path.
1738
+ *
1739
+ * @param root Configured path or file URL.
1740
+ * @returns Absolute filesystem path.
1741
+ */
1248
1742
  generatedRegistryRootPath(root) {
1249
1743
  if (root instanceof URL) {
1250
1744
  return ContextParts.fileUrlPath(root, "Generated registry root");
@@ -1254,6 +1748,13 @@ const ContextParts = Object.freeze({
1254
1748
  }
1255
1749
  return resolve(root);
1256
1750
  },
1751
+ /**
1752
+ * Validates a file URL and converts it to an absolute filesystem path.
1753
+ *
1754
+ * @param url File URL to convert.
1755
+ * @param label Label used in validation errors.
1756
+ * @returns Filesystem path.
1757
+ */
1257
1758
  fileUrlPath(url, label) {
1258
1759
  if (url.protocol !== "file:") {
1259
1760
  throw new Error(`${label} "${url.href}" must use the file: URL scheme.`);
@@ -1263,6 +1764,12 @@ const ContextParts = Object.freeze({
1263
1764
  }
1264
1765
  return resolve(fileURLToPath(url));
1265
1766
  },
1767
+ /**
1768
+ * Parses a generated registry root string as a URL.
1769
+ *
1770
+ * @param root Registry root string.
1771
+ * @returns Parsed URL.
1772
+ */
1266
1773
  parseRootUrl(root) {
1267
1774
  try {
1268
1775
  return new URL(root);
@@ -1271,9 +1778,21 @@ const ContextParts = Object.freeze({
1271
1778
  throw new Error(`Generated registry root "${root}" is not a valid URL.`, { cause: error });
1272
1779
  }
1273
1780
  },
1781
+ /**
1782
+ * Checks strings for a URL scheme, excluding Windows drive paths.
1783
+ *
1784
+ * @param value String to inspect.
1785
+ * @returns True when the string has a URL scheme.
1786
+ */
1274
1787
  isUrlLike(value) {
1275
1788
  return moduleSchemeRe.test(value) && !/^[A-Za-z]:[\\/]/.test(value);
1276
1789
  },
1790
+ /**
1791
+ * Verifies registry-module readability and resolves its canonical path.
1792
+ *
1793
+ * @param registryPath Registry module filesystem path.
1794
+ * @returns Canonical readable path.
1795
+ */
1277
1796
  async canonicalReadableRegistryPath(registryPath) {
1278
1797
  try {
1279
1798
  await access(registryPath, fsConstants.R_OK);
@@ -1283,6 +1802,13 @@ const ContextParts = Object.freeze({
1283
1802
  throw new Error(`Generated handler registry module "${registryPath}" must exist and be readable.`, { cause: error });
1284
1803
  }
1285
1804
  },
1805
+ /**
1806
+ * Checks whether a canonical path escapes a canonical root.
1807
+ *
1808
+ * @param canonicalRoot Root directory.
1809
+ * @param canonicalPath Candidate path.
1810
+ * @returns True when the candidate escapes the root.
1811
+ */
1286
1812
  resolvesOutsideRoot(canonicalRoot, canonicalPath) {
1287
1813
  const relativePath = relative(canonicalRoot, canonicalPath);
1288
1814
  return (relativePath.startsWith("..") ||
@@ -1290,13 +1816,30 @@ const ContextParts = Object.freeze({
1290
1816
  relativePath.split(sep).includes("..") ||
1291
1817
  isAbsolute(relativePath));
1292
1818
  },
1819
+ /**
1820
+ * Creates a cache-bust query after a prior registry load failure.
1821
+ *
1822
+ * @param registryKey Canonical registry identifier.
1823
+ * @returns Retry query, or an empty object on the first attempt.
1824
+ */
1293
1825
  generatedRegistryCacheBust(registryKey) {
1294
1826
  const attempt = generatedRegistryLoadAttempts.get(registryKey) ?? 0;
1295
1827
  return attempt === 0 ? {} : { cacheBust: `retry-${attempt.toString()}` };
1296
1828
  },
1829
+ /**
1830
+ * Records another failed load for a registry.
1831
+ *
1832
+ * @param registryKey Canonical registry identifier.
1833
+ */
1297
1834
  recordGeneratedRegistryFailure(registryKey) {
1298
1835
  generatedRegistryLoadAttempts.set(registryKey, (generatedRegistryLoadAttempts.get(registryKey) ?? 0) + 1);
1299
1836
  },
1837
+ /**
1838
+ * Loads generated handler registries into a metadata registry.
1839
+ *
1840
+ * @param registries Generated registries to ingest.
1841
+ * @returns Populated handler metadata registry.
1842
+ */
1300
1843
  ingestGeneratedRegistries(registries) {
1301
1844
  const registry = new HandlerMetadataRegistry();
1302
1845
  const ingestor = new HandlerRegistryIngestor();
@@ -1305,6 +1848,15 @@ const ContextParts = Object.freeze({
1305
1848
  }
1306
1849
  return registry;
1307
1850
  },
1851
+ /**
1852
+ * Creates a repository from generated Entity handler metadata.
1853
+ *
1854
+ * @param entityType Entity class.
1855
+ * @param registries Generated registries to search.
1856
+ * @param metadata Ingested handler metadata.
1857
+ * @param options Repository options overriding defaults.
1858
+ * @returns Configured repository view.
1859
+ */
1308
1860
  createGeneratedRepository(entityType, registries, metadata, options) {
1309
1861
  const generated = ContextParts.findGeneratedEntity(entityType, registries);
1310
1862
  const handlers = ContextParts.findGeneratedHandlers(entityType, generated, metadata);
@@ -1316,15 +1868,30 @@ const ContextParts = Object.freeze({
1316
1868
  }, options);
1317
1869
  return new Repository(repositoryOptions);
1318
1870
  },
1871
+ /**
1872
+ * Finds the generated Entity handler group for an Entity class.
1873
+ *
1874
+ * @param entityType Entity class to find.
1875
+ * @param registries Generated registries to search.
1876
+ * @returns Matching handler group; throws when absent.
1877
+ */
1319
1878
  findGeneratedEntity(entityType, registries) {
1320
1879
  for (const registry of registries) {
1321
- const generated = registry.entities.find((entity) => entity.entityType === entityType);
1880
+ const generated = registry.receivers.find((receiver) => receiver.receiverKind === "entity" && receiver.receiverType === entityType);
1322
1881
  if (generated !== undefined) {
1323
1882
  return generated;
1324
1883
  }
1325
1884
  }
1326
1885
  throw new Error(`Generated handler registry is missing metadata for ${entityType.name}.`);
1327
1886
  },
1887
+ /**
1888
+ * Finds handler metadata for an Entity class and state schema.
1889
+ *
1890
+ * @param entityType Entity class.
1891
+ * @param generated Matching generated Entity group.
1892
+ * @param metadata Handler metadata registry.
1893
+ * @returns Matching handler metadata; throws when absent.
1894
+ */
1328
1895
  findGeneratedHandlers(entityType, generated, metadata) {
1329
1896
  const matches = metadata.findByState(generated.stateSchema.typeName);
1330
1897
  const handlers = matches.find((candidate) => candidate.entityType === entityType);
@@ -1333,11 +1900,45 @@ const ContextParts = Object.freeze({
1333
1900
  }
1334
1901
  return handlers;
1335
1902
  },
1903
+ /**
1904
+ * Collects event schemas declared as returned or thrown handler outcomes.
1905
+ *
1906
+ * @param generated Generated Entity handler group.
1907
+ * @returns Frozen unique event schemas.
1908
+ */
1336
1909
  aggregateAssignedEvents(generated) {
1337
- return ContextParts.uniqueSchemas(generated.handlers.flatMap((handler) => handler.kind === "command-assignment" || handler.kind === "event-reaction"
1338
- ? handler.emittedSchemas
1339
- : []));
1910
+ return ContextParts.uniqueSchemas(generated.handlers.flatMap((handler) => [
1911
+ ...(handler.kind === "command-assignment" || handler.kind === "event-reaction"
1912
+ ? handler.outcomes.returned
1913
+ : []),
1914
+ ...(handler.kind === "command-assignment" || handler.kind === "command-substitution"
1915
+ ? handler.outcomes.thrown
1916
+ : []),
1917
+ ]));
1918
+ },
1919
+ /**
1920
+ * Collects event schemas produced by standalone handler outcomes.
1921
+ *
1922
+ * @param receivers Generated standalone groups.
1923
+ * @returns Frozen unique event schemas.
1924
+ */
1925
+ standaloneProducedEventSchemas(receivers) {
1926
+ return ContextParts.uniqueSchemas(receivers.flatMap((receiver) => receiver.handlers.flatMap((handler) => [
1927
+ ...(handler.kind === "command-assignment" || handler.kind === "event-reaction"
1928
+ ? handler.outcomes.returned
1929
+ : []),
1930
+ ...(handler.kind === "command-assignment" || handler.kind === "command-substitution"
1931
+ ? handler.outcomes.thrown
1932
+ : []),
1933
+ ])));
1340
1934
  },
1935
+ /**
1936
+ * Returns unique schemas by Protobuf type name, preserving the last schema.
1937
+ *
1938
+ * @typeParam Schema Descriptor schema type retained in the result.
1939
+ * @param schemas Schemas to deduplicate.
1940
+ * @returns Frozen deduplicated schemas.
1941
+ */
1341
1942
  uniqueSchemas(schemas) {
1342
1943
  const byTypeName = new Map();
1343
1944
  for (const schema of schemas) {
@@ -1345,6 +1946,11 @@ const ContextParts = Object.freeze({
1345
1946
  }
1346
1947
  return Object.freeze([...byTypeName.values()]);
1347
1948
  },
1949
+ /**
1950
+ * Validates repository uniqueness, registration state, and projection cycles.
1951
+ *
1952
+ * @param repositories Repository views to validate.
1953
+ */
1348
1954
  preflightRepositories(repositories) {
1349
1955
  const entityTypes = new Set();
1350
1956
  const stateTypeNames = new Set();
@@ -1367,6 +1973,11 @@ const ContextParts = Object.freeze({
1367
1973
  }
1368
1974
  ContextParts.rejectStateCycles(repositories);
1369
1975
  },
1976
+ /**
1977
+ * Rejects cycles among repository state-subscription dependencies.
1978
+ *
1979
+ * @param repositories Repository views whose dependencies are checked.
1980
+ */
1370
1981
  rejectStateCycles(repositories) {
1371
1982
  const dependencies = new Map();
1372
1983
  for (const repository of repositories) {
@@ -1397,30 +2008,66 @@ const ContextParts = Object.freeze({
1397
2008
  for (const stateType of dependencies.keys())
1398
2009
  visit(stateType);
1399
2010
  },
2011
+ /**
2012
+ * Collects command dispatchers configured by repositories.
2013
+ *
2014
+ * @param repositories Repository views to inspect.
2015
+ * @returns Present command dispatchers.
2016
+ */
1400
2017
  repositoryCommandDispatchers(repositories) {
1401
2018
  return repositories.flatMap((repository) => {
1402
2019
  const dispatcher = repositoryAccess.commandDispatcher(repository);
1403
2020
  return dispatcher === undefined ? [] : [dispatcher];
1404
2021
  });
1405
2022
  },
2023
+ /**
2024
+ * Collects event dispatchers configured by repositories.
2025
+ *
2026
+ * @param repositories Repository views to inspect.
2027
+ * @returns Present event dispatchers.
2028
+ */
1406
2029
  repositoryEventDispatchers(repositories) {
1407
2030
  return repositories.flatMap((repository) => {
1408
2031
  const dispatcher = repositoryAccess.eventDispatcher(repository);
1409
2032
  return dispatcher === undefined ? [] : [dispatcher];
1410
2033
  });
1411
2034
  },
2035
+ /**
2036
+ * Collects System event dispatchers configured by repositories.
2037
+ *
2038
+ * @param repositories Repository views to inspect.
2039
+ * @returns Present System event dispatchers.
2040
+ */
1412
2041
  repositorySystemEventDispatchers(repositories) {
1413
2042
  return repositories.flatMap((repository) => {
1414
2043
  const dispatcher = repositoryAccess.systemEventDispatcher(repository);
1415
2044
  return dispatcher === undefined ? [] : [dispatcher];
1416
2045
  });
1417
2046
  },
2047
+ /**
2048
+ * Returns event dispatchers excluding System routes.
2049
+ *
2050
+ * @param dispatchers Event dispatchers to filter.
2051
+ * @returns Frozen domain event dispatchers.
2052
+ */
1418
2053
  domainEventDispatchers(dispatchers) {
1419
2054
  return Object.freeze(dispatchers.filter((dispatcher) => !ContextParts.isSystemEventDispatcher(dispatcher)));
1420
2055
  },
2056
+ /**
2057
+ * Returns System event dispatchers.
2058
+ *
2059
+ * @param dispatchers Event dispatchers to filter.
2060
+ * @returns Frozen System event dispatchers.
2061
+ */
1421
2062
  systemEventDispatchers(dispatchers) {
1422
2063
  return Object.freeze(dispatchers.filter((dispatcher) => ContextParts.isSystemEventDispatcher(dispatcher)));
1423
2064
  },
2065
+ /**
2066
+ * Checks a dispatcher's event family and rejects mixed families.
2067
+ *
2068
+ * @param dispatcher Dispatcher to inspect.
2069
+ * @returns True when its schemas are System event schemas.
2070
+ */
1424
2071
  isSystemEventDispatcher(dispatcher) {
1425
2072
  const schemas = [...dispatcher.messageSchemas()];
1426
2073
  const systemSchemas = schemas.filter((schema) => schema.typeName.startsWith("spine.system."));
@@ -1429,6 +2076,12 @@ const ContextParts = Object.freeze({
1429
2076
  }
1430
2077
  return systemSchemas.length > 0;
1431
2078
  },
2079
+ /**
2080
+ * Collects repository-produced event schemas by type URL.
2081
+ *
2082
+ * @param repositories Repository views to inspect.
2083
+ * @returns Frozen unique event schemas.
2084
+ */
1432
2085
  repositoryProducedEventSchemas(repositories) {
1433
2086
  const schemas = new Map();
1434
2087
  for (const repository of repositories) {
@@ -1438,10 +2091,21 @@ const ContextParts = Object.freeze({
1438
2091
  }
1439
2092
  return Object.freeze([...schemas.values()]);
1440
2093
  },
2094
+ /**
2095
+ * Clears metadata for a failed context build and closes its tenant index.
2096
+ *
2097
+ * @param context Context whose build failed.
2098
+ * @param tenantIndex Tenant index to close.
2099
+ */
1441
2100
  cleanupFailedContext(context, tenantIndex) {
1442
2101
  ContextParts.clearContextMetadata(context);
1443
2102
  tenantIndex.close();
1444
2103
  },
2104
+ /**
2105
+ * Removes runtime metadata associated with a context.
2106
+ *
2107
+ * @param context Context whose WeakMap metadata is removed.
2108
+ */
1445
2109
  clearContextMetadata(context) {
1446
2110
  const buses = contextEventBuses.get(context);
1447
2111
  if (buses !== undefined) {
@@ -1451,7 +2115,8 @@ const ContextParts = Object.freeze({
1451
2115
  const runtime = contextSubscriptionRuntimes.get(context);
1452
2116
  if (runtime !== undefined)
1453
2117
  subscriptionRuntimeAccess.clearLogger(runtime);
1454
- contextDispatchFailureRecorders.delete(context);
2118
+ contextSignalPublishers.get(context)?.clearLogger();
2119
+ contextSignalPublishers.delete(context);
1455
2120
  contextLoggers.delete(context);
1456
2121
  contextSystemPairings.delete(context);
1457
2122
  contextTenantIndexes.delete(context);
@@ -1462,48 +2127,72 @@ const ContextParts = Object.freeze({
1462
2127
  eventSubscribers.delete(context);
1463
2128
  systemEventPosters.delete(context);
1464
2129
  },
2130
+ /**
2131
+ * Creates delivery operations from the context inboxes and readiness state.
2132
+ *
2133
+ * @param context Built context snapshot.
2134
+ * @param storageFactory Context storage provider.
2135
+ * @param tenantIndex Tenant index for startup scopes.
2136
+ * @param entityInbox Registered Entity inbox.
2137
+ * @param projections Projection inbox.
2138
+ * @param readiness Delivery readiness coordinator.
2139
+ * @returns Delivery descriptor.
2140
+ */
1465
2141
  createDeliveryDescriptor(context, storageFactory, tenantIndex, entityInbox, projections, readiness) {
1466
2142
  return Object.freeze({
1467
2143
  storageFactory,
1468
- async startupScopes() {
1469
- if (tenantIndex.tenantMode === "single-tenant") {
1470
- return Object.freeze([Object.freeze({})]);
1471
- }
1472
- return Object.freeze((await tenantIndex.all()).map((tenantId) => Object.freeze({ tenantId })));
1473
- },
1474
- storageContext(scope) {
1475
- if (context.tenantMode === "single-tenant") {
1476
- if (scope.tenantId !== undefined) {
1477
- throw new Error(`Single-tenant context "${context.name.value}" does not accept tenantId.`);
1478
- }
1479
- return Object.freeze({ name: context.name.value, multitenant: false });
1480
- }
1481
- const tenantId = scope.tenantId;
1482
- if (tenantId === undefined) {
1483
- throw new Error(`Multitenant context "${context.name.value}" requires tenantId.`);
1484
- }
1485
- return Object.freeze({
1486
- name: context.name.value,
1487
- multitenant: true,
1488
- tenantId: TenantBoundary.from(tenantId).tenantId,
1489
- });
1490
- },
1491
- endpoints() {
1492
- return Object.freeze([...entityInbox.endpoints(), ...projections.endpoints()]);
1493
- },
1494
- replay(message, tenantId) {
1495
- return message.label === "UPDATE_SUBSCRIBER"
1496
- ? projections.replay(message, tenantId)
1497
- : entityInbox.replay(message, tenantId);
1498
- },
1499
- onReady(onReady) {
1500
- return readiness.onReady(onReady);
1501
- },
1502
- transition(scopes, onReady, options) {
1503
- return readiness.transition(scopes, onReady, options);
1504
- },
2144
+ startupScopes: () => ContextParts.deliveryStartupScopes(tenantIndex),
2145
+ storageContext: (scope) => ContextParts.deliveryStorageContext(context, scope),
2146
+ endpoints: () => Object.freeze([...entityInbox.endpoints(), ...projections.endpoints()]),
2147
+ replay: (message, tenantId) => message.label === "UPDATE_SUBSCRIBER"
2148
+ ? projections.replay(message, tenantId)
2149
+ : entityInbox.replay(message, tenantId),
2150
+ onReady: (onReady) => readiness.onReady(onReady),
2151
+ transition: (scopes, onReady, options) => readiness.transition(scopes, onReady, options),
2152
+ });
2153
+ },
2154
+ /**
2155
+ * Lists delivery startup scopes, one per tenant in multitenant contexts.
2156
+ *
2157
+ * @param tenantIndex Tenant index supplying configured scopes.
2158
+ * @returns Frozen startup scope list.
2159
+ */
2160
+ async deliveryStartupScopes(tenantIndex) {
2161
+ if (tenantIndex.tenantMode === "single-tenant") {
2162
+ return Object.freeze([Object.freeze({})]);
2163
+ }
2164
+ return Object.freeze((await tenantIndex.all()).map((tenantId) => Object.freeze({ tenantId })));
2165
+ },
2166
+ /**
2167
+ * Creates tenant-validated storage context for a delivery scope.
2168
+ *
2169
+ * @param context Built context snapshot.
2170
+ * @param scope Selected startup scope.
2171
+ * @returns Storage context for the selected tenant.
2172
+ */
2173
+ deliveryStorageContext(context, scope) {
2174
+ if (context.tenantMode === "single-tenant") {
2175
+ if (scope.tenantId !== undefined) {
2176
+ throw new Error(`Single-tenant context "${context.name.value}" does not accept tenantId.`);
2177
+ }
2178
+ return Object.freeze({ name: context.name.value, multitenant: false });
2179
+ }
2180
+ const tenantId = scope.tenantId;
2181
+ if (tenantId === undefined) {
2182
+ throw new Error(`Multitenant context "${context.name.value}" requires tenantId.`);
2183
+ }
2184
+ return Object.freeze({
2185
+ name: context.name.value,
2186
+ multitenant: true,
2187
+ tenantId: TenantBoundary.from(tenantId).tenantId,
1505
2188
  });
1506
2189
  },
2190
+ /**
2191
+ * Closes prepared repositories and collects close failures.
2192
+ *
2193
+ * @param preparedRepositories Prepared repository resources to close.
2194
+ * @returns Collected close errors.
2195
+ */
1507
2196
  closePreparedRepositories(preparedRepositories) {
1508
2197
  const errors = [];
1509
2198
  for (const preparedRepository of preparedRepositories) {
@@ -1516,6 +2205,13 @@ const ContextParts = Object.freeze({
1516
2205
  }
1517
2206
  return errors;
1518
2207
  },
2208
+ /**
2209
+ * Calls one asynchronous context close action and records failures.
2210
+ *
2211
+ * @param close Close operation.
2212
+ * @param errors Failure collection.
2213
+ * @returns Promise fulfilled after the close attempt is recorded.
2214
+ */
1519
2215
  async closeContextPart(close, errors) {
1520
2216
  try {
1521
2217
  await close();
@@ -1524,17 +2220,37 @@ const ContextParts = Object.freeze({
1524
2220
  ContextParts.collectCloseError(error, errors);
1525
2221
  }
1526
2222
  },
1527
- async drainContextBuses(commandBus, eventBus) {
2223
+ /**
2224
+ * Waits until accepted command, event, System event, and signal work drains.
2225
+ *
2226
+ * @param commandBus Command bus.
2227
+ * @param eventBus Domain event bus.
2228
+ * @param systemEventBus System event bus.
2229
+ * @param publisher Signal publisher.
2230
+ * @returns Promise fulfilled when accepted work stabilizes.
2231
+ */
2232
+ async drainContextWork(commandBus, eventBus, systemEventBus, publisher) {
1528
2233
  let observedCommandWork = -1;
1529
2234
  let observedEventWork = -1;
2235
+ let observedSystemEventWork = -1;
1530
2236
  do {
1531
2237
  observedCommandWork = commandBusAccess.acceptedWorkCount(commandBus);
1532
2238
  observedEventWork = eventBusAccess.acceptedWorkCount(eventBus);
2239
+ observedSystemEventWork = eventBusAccess.acceptedWorkCount(systemEventBus);
1533
2240
  await commandBusAccess.drain(commandBus);
1534
2241
  await eventBusAccess.drain(eventBus);
2242
+ await eventBusAccess.drain(systemEventBus);
2243
+ await publisher.drain();
1535
2244
  } while (commandBusAccess.acceptedWorkCount(commandBus) !== observedCommandWork ||
1536
- eventBusAccess.acceptedWorkCount(eventBus) !== observedEventWork);
2245
+ eventBusAccess.acceptedWorkCount(eventBus) !== observedEventWork ||
2246
+ eventBusAccess.acceptedWorkCount(systemEventBus) !== observedSystemEventWork);
1537
2247
  },
2248
+ /**
2249
+ * Adds an error or aggregate causes to the close error collection.
2250
+ *
2251
+ * @param error Close failure.
2252
+ * @param errors Failure collection.
2253
+ */
1538
2254
  collectCloseError(error, errors) {
1539
2255
  if (error instanceof AggregateError) {
1540
2256
  const causes = error.errors;
@@ -1545,6 +2261,13 @@ const ContextParts = Object.freeze({
1545
2261
  }
1546
2262
  errors.push(error);
1547
2263
  },
2264
+ /**
2265
+ * Binds a repository to context runtime services and supplies commit and close operations.
2266
+ *
2267
+ * @param repository Repository to prepare.
2268
+ * @param registration Context services and schemas.
2269
+ * @returns Prepared repository.
2270
+ */
1548
2271
  prepareRepositoryForContext(repository, registration) {
1549
2272
  ContextParts.requireRepositoryInstance(repository, "BoundedContext repository registration");
1550
2273
  const snapshot = ContextParts.repositorySnapshot(repository);
@@ -1556,14 +2279,9 @@ const ContextParts = Object.freeze({
1556
2279
  signalMetadata: new SignalMetadata(),
1557
2280
  entityInbox: registration.entityInbox,
1558
2281
  projectionInbox: registration.projectionInbox,
1559
- dispatchStored: registration.dispatchStored,
1560
- dispatchStoredFollowUp: registration.dispatchStoredFollowUp,
1561
- postEventFollowUp: registration.postEventFollowUp,
2282
+ publisher: registration.publisher,
1562
2283
  registerEventSchema: registration.registerEventSchema,
1563
2284
  registerSystemEventSchema: registration.registerSystemEventSchema,
1564
- postSystemFollowUp: registration.postSystemFollowUp,
1565
- onPostCommand: registration.onPostCommand,
1566
- recordDispatchFailure: registration.recordDispatchFailure,
1567
2285
  });
1568
2286
  const entityInboxTarget = repositoryAccess.entityInboxTarget(repository);
1569
2287
  const projectionInboxTarget = repositoryAccess.projectionInboxTarget(repository);
@@ -1580,6 +2298,12 @@ const ContextParts = Object.freeze({
1580
2298
  },
1581
2299
  };
1582
2300
  },
2301
+ /**
2302
+ * Captures repository metadata needed for context registration.
2303
+ *
2304
+ * @param repository Repository to inspect.
2305
+ * @returns Frozen registration snapshot.
2306
+ */
1583
2307
  repositorySnapshot(repository) {
1584
2308
  const snapshot = repositoryAccess.snapshot(repository);
1585
2309
  return Object.freeze({
@@ -1592,6 +2316,11 @@ const ContextParts = Object.freeze({
1592
2316
  snapshot,
1593
2317
  });
1594
2318
  },
2319
+ /**
2320
+ * Rejects a repository already registered with a bounded context.
2321
+ *
2322
+ * @param repository Repository to check.
2323
+ */
1595
2324
  rejectRegisteredRepository(repository) {
1596
2325
  const snapshot = ContextParts.repositorySnapshot(repository);
1597
2326
  const registration = registeredRepositories.get(repository);
@@ -1600,9 +2329,21 @@ const ContextParts = Object.freeze({
1600
2329
  `"${registration.name.value}".`);
1601
2330
  }
1602
2331
  },
2332
+ /**
2333
+ * Creates storage record columns from Entity metadata.
2334
+ *
2335
+ * @param snapshot Registration snapshot supplying fields.
2336
+ * @returns Record columns.
2337
+ */
1603
2338
  repositoryColumns(snapshot) {
1604
2339
  return snapshot.metadata.columns.map((field) => new RecordColumn(field.name, ColumnTypes.fromField(field.descriptor), (record) => ContextParts.readRecordField(record, field.localName)));
1605
2340
  },
2341
+ /**
2342
+ * Creates an immutable repository view from a registration snapshot.
2343
+ *
2344
+ * @param snapshot Registration snapshot.
2345
+ * @returns Immutable repository view.
2346
+ */
1606
2347
  createRepositoryView(snapshot) {
1607
2348
  return Object.freeze({
1608
2349
  entityType: snapshot.entityType,
@@ -1614,35 +2355,12 @@ const ContextParts = Object.freeze({
1614
2355
  snapshot: snapshot.snapshot,
1615
2356
  });
1616
2357
  },
1617
- cloneDispatchFailure(failure) {
1618
- return Object.freeze({
1619
- event: clone(EventSchema, failure.event),
1620
- error: ContextParts.cloneDispatchError(failure.error),
1621
- });
1622
- },
1623
- snapshotDispatchError(error) {
1624
- if (error instanceof Error) {
1625
- const snapshot = {
1626
- name: ContextParts.boundedErrorString(error.name, dispatchErrorMessageLimit) || "Error",
1627
- message: ContextParts.boundedErrorString(error.message, dispatchErrorMessageLimit),
1628
- ...(typeof error.stack === "string"
1629
- ? { stack: ContextParts.boundedErrorString(error.stack, dispatchErrorStackLimit) }
1630
- : {}),
1631
- };
1632
- return Object.freeze(snapshot);
1633
- }
1634
- return Object.freeze({
1635
- name: "NonErrorThrow",
1636
- message: ContextParts.boundedErrorString(String(error), dispatchErrorMessageLimit),
1637
- });
1638
- },
1639
- cloneDispatchError(error) {
1640
- return Object.freeze({
1641
- name: error.name,
1642
- message: error.message,
1643
- ...(error.stack === undefined ? {} : { stack: error.stack }),
1644
- });
1645
- },
2358
+ /**
2359
+ * Builds dispatch descriptors for Projection repositories with event handlers.
2360
+ *
2361
+ * @param repositories Repository views to inspect.
2362
+ * @returns Frozen Projection dispatch descriptors.
2363
+ */
1646
2364
  projectionDispatchers(repositories) {
1647
2365
  const projections = [];
1648
2366
  for (const repository of repositories) {
@@ -1661,6 +2379,12 @@ const ContextParts = Object.freeze({
1661
2379
  }
1662
2380
  return Object.freeze(projections);
1663
2381
  },
2382
+ /**
2383
+ * Collects unique Projection state schemas for clearing.
2384
+ *
2385
+ * @param projections Projection dispatch descriptors.
2386
+ * @returns Frozen unique clear targets.
2387
+ */
1664
2388
  projectionStateClearTargets(projections) {
1665
2389
  const unique = new Map();
1666
2390
  for (const projection of projections) {
@@ -1673,6 +2397,13 @@ const ContextParts = Object.freeze({
1673
2397
  }
1674
2398
  return Object.freeze([...unique.values()]);
1675
2399
  },
2400
+ /**
2401
+ * Reads stored events in deterministic timestamp, producer, version, and ID order.
2402
+ *
2403
+ * @param context Event-store storage context.
2404
+ * @param storageFactory Event storage provider.
2405
+ * @returns Stored events in deterministic order.
2406
+ */
1676
2407
  async readStoredEvents(context, storageFactory) {
1677
2408
  const eventStore = new EventStore(context, storageFactory);
1678
2409
  try {
@@ -1689,12 +2420,24 @@ const ContextParts = Object.freeze({
1689
2420
  eventStore.close();
1690
2421
  }
1691
2422
  },
2423
+ /**
2424
+ * Builds Stand options for the catch-up tenant scope.
2425
+ *
2426
+ * @param context Catch-up storage context.
2427
+ * @returns Options with a cloned tenant ID when multitenant.
2428
+ */
1692
2429
  catchUpStandOptions(context) {
1693
2430
  if (!context.multitenant) {
1694
2431
  return {};
1695
2432
  }
1696
2433
  return Object.freeze({ tenantId: clone(TenantIdSchema, context.tenantId) });
1697
2434
  },
2435
+ /**
2436
+ * Verifies a stored event envelope belongs to the catch-up tenant.
2437
+ *
2438
+ * @param context Catch-up storage context.
2439
+ * @param event Stored event to validate.
2440
+ */
1698
2441
  validateReplayTenant(context, event) {
1699
2442
  if (!context.multitenant) {
1700
2443
  return;
@@ -1708,6 +2451,12 @@ const ContextParts = Object.freeze({
1708
2451
  throw new Error("Read-side catch-up stored event envelope tenant does not match.");
1709
2452
  }
1710
2453
  },
2454
+ /**
2455
+ * Reads the tenant ID from an event import or past-message origin.
2456
+ *
2457
+ * @param event Event whose origin is inspected.
2458
+ * @returns Cloned tenant ID when present.
2459
+ */
1711
2460
  readReplayTenant(event) {
1712
2461
  switch (event.context?.origin.case) {
1713
2462
  case "importContext":
@@ -1718,9 +2467,23 @@ const ContextParts = Object.freeze({
1718
2467
  return undefined;
1719
2468
  }
1720
2469
  },
2470
+ /**
2471
+ * Copies a tenant ID when present.
2472
+ *
2473
+ * @param tenantId Optional tenant ID.
2474
+ * @returns Cloned tenant ID or undefined.
2475
+ */
1721
2476
  tenantIdValue(tenantId) {
1722
2477
  return tenantId === undefined ? undefined : clone(TenantIdSchema, tenantId);
1723
2478
  },
2479
+ /**
2480
+ * Dispatches a stored event to matching Projection handlers and direct updates.
2481
+ *
2482
+ * @param projections Projection dispatch descriptors.
2483
+ * @param event Stored event.
2484
+ * @param rebuild Whether direct dispatch is a rebuild.
2485
+ * @returns One when a Projection matched, otherwise zero.
2486
+ */
1724
2487
  async dispatchStoredProjectionEvent(projections, event, rebuild) {
1725
2488
  const typeUrl = event.message?.typeUrl;
1726
2489
  if (typeUrl === undefined || typeUrl === "") {
@@ -1735,24 +2498,51 @@ const ContextParts = Object.freeze({
1735
2498
  }
1736
2499
  return matching.length > 0 ? 1 : 0;
1737
2500
  },
2501
+ /**
2502
+ * Creates a replay error with the event ID and bounded cause details.
2503
+ *
2504
+ * @param event Event that failed.
2505
+ * @param cause Thrown replay cause.
2506
+ * @returns Replay error.
2507
+ */
1738
2508
  catchUpReplayError(event, cause) {
1739
2509
  return new CatchUpReplayError(event.id?.value ?? "(missing)", ContextParts.catchUpReplayDetail(cause));
1740
2510
  },
2511
+ /**
2512
+ * Returns an error string truncated to a maximum length.
2513
+ *
2514
+ * @param value String to bound.
2515
+ * @param limit Maximum character count.
2516
+ * @returns Bounded string.
2517
+ */
1741
2518
  boundedErrorString(value, limit) {
1742
2519
  return value.length <= limit ? value : `${value.slice(0, limit - 3)}...`;
1743
2520
  },
2521
+ /**
2522
+ * Converts a thrown value to bounded error name and message details.
2523
+ *
2524
+ * @param error Thrown value.
2525
+ * @returns Immutable bounded details.
2526
+ */
1744
2527
  catchUpReplayDetail(error) {
1745
2528
  if (error instanceof Error) {
1746
2529
  return Object.freeze({
1747
- name: ContextParts.boundedErrorString(error.name, dispatchErrorMessageLimit) || "Error",
1748
- message: ContextParts.boundedErrorString(error.message, dispatchErrorMessageLimit),
2530
+ name: ContextParts.boundedErrorString(error.name, errorDetailLimit) || "Error",
2531
+ message: ContextParts.boundedErrorString(error.message, errorDetailLimit),
1749
2532
  });
1750
2533
  }
1751
2534
  return Object.freeze({
1752
2535
  name: "NonErrorThrow",
1753
- message: ContextParts.boundedErrorString(String(error), dispatchErrorMessageLimit),
2536
+ message: ContextParts.boundedErrorString(String(error), errorDetailLimit),
1754
2537
  });
1755
2538
  },
2539
+ /**
2540
+ * Reads and requires the Entity ID field from a stored record.
2541
+ *
2542
+ * @param record Protobuf record.
2543
+ * @param snapshot Repository metadata identifying the ID field.
2544
+ * @returns ID field value; throws when missing.
2545
+ */
1756
2546
  readRecordId(record, snapshot) {
1757
2547
  const value = ContextParts.readRecordField(record, snapshot.idField.localName);
1758
2548
  if (value === undefined || value === null) {
@@ -1760,6 +2550,13 @@ const ContextParts = Object.freeze({
1760
2550
  }
1761
2551
  return value;
1762
2552
  },
2553
+ /**
2554
+ * Reads a field from a Protobuf record by descriptor local name.
2555
+ *
2556
+ * @param record Record message.
2557
+ * @param localName Descriptor local field name.
2558
+ * @returns Field value.
2559
+ */
1763
2560
  readRecordField(record, localName) {
1764
2561
  return record[localName];
1765
2562
  },