@spine-event-engine/server 2.0.0-snapshot.24 → 2.0.0-snapshot.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -19
- package/REFERENCE.md +538 -150
- package/dist/agent/agent-acceptance-receipts.d.ts +69 -0
- package/dist/agent/agent-acceptance-receipts.d.ts.map +1 -0
- package/dist/agent/agent-acceptance-receipts.js +198 -0
- package/dist/agent/agent-acceptance-receipts.js.map +1 -0
- package/dist/agent/agent-execution-capacity.d.ts +37 -9
- package/dist/agent/agent-execution-capacity.d.ts.map +1 -1
- package/dist/agent/agent-execution-capacity.js +73 -15
- package/dist/agent/agent-execution-capacity.js.map +1 -1
- package/dist/agent/agent-history.d.ts +33 -12
- package/dist/agent/agent-history.d.ts.map +1 -1
- package/dist/agent/agent-history.js +61 -18
- package/dist/agent/agent-history.js.map +1 -1
- package/dist/agent/agent-live-ai-audit.d.ts +95 -0
- package/dist/agent/agent-live-ai-audit.d.ts.map +1 -0
- package/dist/agent/agent-live-ai-audit.js +290 -0
- package/dist/agent/agent-live-ai-audit.js.map +1 -0
- package/dist/agent/agent-live-ai-runtime.d.ts +300 -0
- package/dist/agent/agent-live-ai-runtime.d.ts.map +1 -0
- package/dist/agent/agent-live-ai-runtime.js +611 -0
- package/dist/agent/agent-live-ai-runtime.js.map +1 -0
- package/dist/agent/agent-live-audit.d.ts +67 -0
- package/dist/agent/agent-live-audit.d.ts.map +1 -0
- package/dist/agent/agent-live-audit.js +121 -0
- package/dist/agent/agent-live-audit.js.map +1 -0
- package/dist/agent/agent-live-budget.d.ts +84 -0
- package/dist/agent/agent-live-budget.d.ts.map +1 -0
- package/dist/agent/agent-live-budget.js +161 -0
- package/dist/agent/agent-live-budget.js.map +1 -0
- package/dist/agent/agent-live-fault.d.ts +18 -0
- package/dist/agent/agent-live-fault.d.ts.map +1 -0
- package/dist/agent/{agent-execution-fault.js → agent-live-fault.js} +7 -7
- package/dist/agent/agent-live-fault.js.map +1 -0
- package/dist/agent/agent-live-inputs.d.ts +74 -0
- package/dist/agent/agent-live-inputs.d.ts.map +1 -0
- package/dist/agent/agent-live-inputs.js +15 -0
- package/dist/agent/agent-live-inputs.js.map +1 -0
- package/dist/agent/agent-live-interaction-events.d.ts +46 -0
- package/dist/agent/agent-live-interaction-events.d.ts.map +1 -0
- package/dist/agent/agent-live-interaction-events.js +104 -0
- package/dist/agent/agent-live-interaction-events.js.map +1 -0
- package/dist/agent/agent-live-mcp-controls.d.ts +129 -0
- package/dist/agent/agent-live-mcp-controls.d.ts.map +1 -0
- package/dist/agent/agent-live-mcp-controls.js +228 -0
- package/dist/agent/agent-live-mcp-controls.js.map +1 -0
- package/dist/agent/agent-live-mcp-host.d.ts +88 -0
- package/dist/agent/agent-live-mcp-host.d.ts.map +1 -0
- package/dist/agent/{agent-mcp-host.js → agent-live-mcp-host.js} +1 -1
- package/dist/agent/agent-live-mcp-host.js.map +1 -0
- package/dist/agent/{agent-mcp-runtime.d.ts → agent-live-mcp-runtime.d.ts} +6 -6
- package/dist/agent/agent-live-mcp-runtime.d.ts.map +1 -0
- package/dist/agent/{agent-mcp-runtime.js → agent-live-mcp-runtime.js} +13 -42
- package/dist/agent/agent-live-mcp-runtime.js.map +1 -0
- package/dist/agent/agent-live-read-runtime.d.ts +72 -0
- package/dist/agent/agent-live-read-runtime.d.ts.map +1 -0
- package/dist/agent/agent-live-read-runtime.js +122 -0
- package/dist/agent/agent-live-read-runtime.js.map +1 -0
- package/dist/agent/agent-model-selection.d.ts +21 -34
- package/dist/agent/agent-model-selection.d.ts.map +1 -1
- package/dist/agent/agent-model-selection.js +61 -78
- package/dist/agent/agent-model-selection.js.map +1 -1
- package/dist/browser/browser-server.d.ts +163 -0
- package/dist/browser/browser-server.d.ts.map +1 -1
- package/dist/browser/browser-server.js +579 -188
- package/dist/browser/browser-server.js.map +1 -1
- package/dist/browser/durable-subscription-bindings.d.ts +5 -0
- package/dist/browser/durable-subscription-bindings.d.ts.map +1 -1
- package/dist/browser/durable-subscription-bindings.js +226 -38
- package/dist/browser/durable-subscription-bindings.js.map +1 -1
- package/dist/browser/options.d.ts +5 -0
- package/dist/browser/options.d.ts.map +1 -1
- package/dist/bus/command-bus.d.ts +17 -18
- package/dist/bus/command-bus.d.ts.map +1 -1
- package/dist/bus/command-bus.js +97 -54
- package/dist/bus/command-bus.js.map +1 -1
- package/dist/bus/command-errors.d.ts +20 -4
- package/dist/bus/command-errors.d.ts.map +1 -1
- package/dist/bus/command-errors.js +47 -7
- package/dist/bus/command-errors.js.map +1 -1
- package/dist/bus/event-bus.d.ts +20 -4
- package/dist/bus/event-bus.d.ts.map +1 -1
- package/dist/bus/event-bus.js +166 -14
- package/dist/bus/event-bus.js.map +1 -1
- package/dist/context/bounded-context.d.ts +169 -98
- package/dist/context/bounded-context.d.ts.map +1 -1
- package/dist/context/bounded-context.js +837 -503
- package/dist/context/bounded-context.js.map +1 -1
- package/dist/context/entity-inbox.d.ts +7 -2
- package/dist/context/entity-inbox.d.ts.map +1 -1
- package/dist/context/entity-inbox.js +22 -13
- package/dist/context/entity-inbox.js.map +1 -1
- package/dist/context/local-inbox-handoff.d.ts +34 -34
- package/dist/context/local-inbox-handoff.d.ts.map +1 -1
- package/dist/context/local-inbox-handoff.js +108 -150
- package/dist/context/local-inbox-handoff.js.map +1 -1
- package/dist/context/projection-handoff.d.ts +2 -2
- package/dist/context/projection-handoff.d.ts.map +1 -1
- package/dist/context/projection-handoff.js +10 -8
- package/dist/context/projection-handoff.js.map +1 -1
- package/dist/context/tenant-index.d.ts +36 -31
- package/dist/context/tenant-index.d.ts.map +1 -1
- package/dist/context/tenant-index.js +123 -125
- package/dist/context/tenant-index.js.map +1 -1
- package/dist/context/tenant-unknown-error.d.ts +33 -0
- package/dist/context/tenant-unknown-error.d.ts.map +1 -0
- package/dist/context/tenant-unknown-error.js +75 -0
- package/dist/context/tenant-unknown-error.js.map +1 -0
- package/dist/delivery/agent-delivery-scope.d.ts +66 -0
- package/dist/delivery/agent-delivery-scope.d.ts.map +1 -0
- package/dist/delivery/agent-delivery-scope.js +44 -0
- package/dist/delivery/agent-delivery-scope.js.map +1 -0
- package/dist/delivery/delivery-builder.d.ts +25 -1
- package/dist/delivery/delivery-builder.d.ts.map +1 -1
- package/dist/delivery/delivery-builder.js +89 -0
- package/dist/delivery/delivery-builder.js.map +1 -1
- package/dist/delivery/delivery-monitor.d.ts +1 -1
- package/dist/delivery/delivery-monitor.js +1 -1
- package/dist/delivery/delivery-run-control.d.ts +6 -0
- package/dist/delivery/delivery-run-control.d.ts.map +1 -1
- package/dist/delivery/delivery-run-control.js +10 -0
- package/dist/delivery/delivery-run-control.js.map +1 -1
- package/dist/delivery/delivery-supervisor.d.ts.map +1 -1
- package/dist/delivery/delivery-supervisor.js +28 -4
- package/dist/delivery/delivery-supervisor.js.map +1 -1
- package/dist/delivery/delivery-tenant.d.ts +17 -0
- package/dist/delivery/delivery-tenant.d.ts.map +1 -0
- package/dist/delivery/delivery-tenant.js +50 -0
- package/dist/delivery/delivery-tenant.js.map +1 -0
- package/dist/delivery/delivery.d.ts +96 -2
- package/dist/delivery/delivery.d.ts.map +1 -1
- package/dist/delivery/delivery.js +693 -53
- package/dist/delivery/delivery.js.map +1 -1
- package/dist/delivery/inbox-records.d.ts.map +1 -1
- package/dist/delivery/inbox-records.js +9 -16
- package/dist/delivery/inbox-records.js.map +1 -1
- package/dist/delivery/inbox-storage.d.ts +6 -0
- package/dist/delivery/inbox-storage.d.ts.map +1 -1
- package/dist/delivery/inbox-storage.js +43 -6
- package/dist/delivery/inbox-storage.js.map +1 -1
- package/dist/delivery/inbox.d.ts.map +1 -1
- package/dist/delivery/inbox.js +3 -2
- package/dist/delivery/inbox.js.map +1 -1
- package/dist/delivery/reception-observation.d.ts +12 -4
- package/dist/delivery/reception-observation.d.ts.map +1 -1
- package/dist/delivery/reception-observation.js +14 -3
- package/dist/delivery/reception-observation.js.map +1 -1
- package/dist/delivery/sharded-work-registry.d.ts +6 -0
- package/dist/delivery/sharded-work-registry.d.ts.map +1 -1
- package/dist/delivery/sharded-work-registry.js +41 -4
- package/dist/delivery/sharded-work-registry.js.map +1 -1
- package/dist/entity/entity-storage-descriptor.d.ts.map +1 -1
- package/dist/entity/entity-storage-descriptor.js +7 -4
- package/dist/entity/entity-storage-descriptor.js.map +1 -1
- package/dist/entity/entity.d.ts +59 -0
- package/dist/entity/entity.d.ts.map +1 -1
- package/dist/entity/entity.js +60 -0
- package/dist/entity/entity.js.map +1 -1
- package/dist/handler/app-query-handler.d.ts +60 -0
- package/dist/handler/app-query-handler.d.ts.map +1 -0
- package/dist/{bus/saved-dispatcher-binding.js → handler/app-query-handler.js} +8 -12
- package/dist/handler/app-query-handler.js.map +1 -0
- package/dist/handler/generated-registry-discovery.d.ts +6 -0
- package/dist/handler/generated-registry-discovery.d.ts.map +1 -1
- package/dist/handler/generated-registry-discovery.js +71 -2
- package/dist/handler/generated-registry-discovery.js.map +1 -1
- package/dist/handler/handler-decorators.d.ts +8 -29
- package/dist/handler/handler-decorators.d.ts.map +1 -1
- package/dist/handler/handler-decorators.js +33 -210
- package/dist/handler/handler-decorators.js.map +1 -1
- package/dist/handler/spine-metadata-inventory.d.ts +99 -0
- package/dist/handler/spine-metadata-inventory.d.ts.map +1 -0
- package/dist/handler/spine-metadata-inventory.js +162 -0
- package/dist/handler/spine-metadata-inventory.js.map +1 -0
- package/dist/index.d.ts +9 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/integration/external-messages.d.ts.map +1 -1
- package/dist/integration/external-messages.js +9 -15
- package/dist/integration/external-messages.js.map +1 -1
- package/dist/integration/integration-broker.d.ts.map +1 -1
- package/dist/integration/integration-broker.js +112 -6
- package/dist/integration/integration-broker.js.map +1 -1
- package/dist/integration/third-party-context.d.ts.map +1 -1
- package/dist/integration/third-party-context.js +7 -10
- package/dist/integration/third-party-context.js.map +1 -1
- package/dist/repository/primitive-id.js +26 -11
- package/dist/repository/primitive-id.js.map +1 -1
- package/dist/repository/repository.d.ts +75 -60
- package/dist/repository/repository.d.ts.map +1 -1
- package/dist/repository/repository.js +1757 -1818
- package/dist/repository/repository.js.map +1 -1
- package/dist/runtime/runtime.d.ts +13 -0
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +18 -0
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/signal-metadata.d.ts.map +1 -1
- package/dist/runtime/signal-metadata.js +6 -6
- package/dist/runtime/signal-metadata.js.map +1 -1
- package/dist/runtime/signal-publisher.d.ts +0 -6
- package/dist/runtime/signal-publisher.d.ts.map +1 -1
- package/dist/runtime/signal-publisher.js +1 -9
- package/dist/runtime/signal-publisher.js.map +1 -1
- package/dist/runtime/standalone-handler-runtime.d.ts +0 -7
- package/dist/runtime/standalone-handler-runtime.d.ts.map +1 -1
- package/dist/runtime/standalone-handler-runtime.js +13 -99
- package/dist/runtime/standalone-handler-runtime.js.map +1 -1
- package/dist/server/managed-delivery-gate.d.ts +34 -0
- package/dist/server/managed-delivery-gate.d.ts.map +1 -0
- package/dist/server/managed-delivery-gate.js +70 -0
- package/dist/server/managed-delivery-gate.js.map +1 -0
- package/dist/server/managed-server-application.d.ts +12 -11
- package/dist/server/managed-server-application.d.ts.map +1 -1
- package/dist/server/managed-server-application.js +48 -29
- package/dist/server/managed-server-application.js.map +1 -1
- package/dist/server/node-coordinator.d.ts +18 -5
- package/dist/server/node-coordinator.d.ts.map +1 -1
- package/dist/server/node-coordinator.js +554 -84
- package/dist/server/node-coordinator.js.map +1 -1
- package/dist/server/reserved-spine-rpc-paths.json +1 -0
- package/dist/server/server-environment.d.ts +220 -25
- package/dist/server/server-environment.d.ts.map +1 -1
- package/dist/server/server-environment.js +734 -145
- package/dist/server/server-environment.js.map +1 -1
- package/dist/server/server-log.d.ts +7 -0
- package/dist/server/server-log.d.ts.map +1 -1
- package/dist/server/server-log.js +14 -1
- package/dist/server/server-log.js.map +1 -1
- package/dist/server/server.d.ts +60 -29
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +155 -201
- package/dist/server/server.js.map +1 -1
- package/dist/services/app-query-execution.d.ts +53 -0
- package/dist/services/app-query-execution.d.ts.map +1 -0
- package/dist/services/app-query-execution.js +408 -0
- package/dist/services/app-query-execution.js.map +1 -0
- package/dist/services/query-reader.d.ts +32 -7
- package/dist/services/query-reader.d.ts.map +1 -1
- package/dist/services/query-reader.js +22 -2
- package/dist/services/query-reader.js.map +1 -1
- package/dist/services/read-request-status.d.ts +65 -0
- package/dist/services/read-request-status.d.ts.map +1 -0
- package/dist/services/read-request-status.js +151 -0
- package/dist/services/read-request-status.js.map +1 -0
- package/dist/services/spine-services.d.ts +37 -8
- package/dist/services/spine-services.d.ts.map +1 -1
- package/dist/services/spine-services.js +1103 -470
- package/dist/services/spine-services.js.map +1 -1
- package/dist/services/trusted-reader-ingress.d.ts +92 -0
- package/dist/services/trusted-reader-ingress.d.ts.map +1 -0
- package/dist/services/trusted-reader-ingress.js +89 -0
- package/dist/services/trusted-reader-ingress.js.map +1 -0
- package/dist/spi/handler-registry.d.ts +2 -0
- package/dist/spi/handler-registry.d.ts.map +1 -1
- package/dist/spi/handler-registry.js +1 -1
- package/dist/spi/handler-registry.js.map +1 -1
- package/dist/stand/event-origin-filter.js +3 -3
- package/dist/stand/event-origin-filter.js.map +1 -1
- package/dist/stand/read-side-filter-engine.d.ts +46 -0
- package/dist/stand/read-side-filter-engine.d.ts.map +1 -0
- package/dist/stand/read-side-filter-engine.js +356 -0
- package/dist/stand/read-side-filter-engine.js.map +1 -0
- package/dist/stand/read-side-filter.d.ts +247 -0
- package/dist/stand/read-side-filter.d.ts.map +1 -0
- package/dist/stand/read-side-filter.js +39 -0
- package/dist/stand/read-side-filter.js.map +1 -0
- package/dist/stand/read-side-message.d.ts +66 -0
- package/dist/stand/read-side-message.d.ts.map +1 -0
- package/dist/stand/read-side-message.js +167 -0
- package/dist/stand/read-side-message.js.map +1 -0
- package/dist/stand/stand-direct-delivery.d.ts +161 -0
- package/dist/stand/stand-direct-delivery.d.ts.map +1 -0
- package/dist/stand/stand-direct-delivery.js +210 -0
- package/dist/stand/stand-direct-delivery.js.map +1 -0
- package/dist/stand/stand.d.ts +94 -42
- package/dist/stand/stand.d.ts.map +1 -1
- package/dist/stand/stand.js +460 -60
- package/dist/stand/stand.js.map +1 -1
- package/dist/stand/subscription-observer.d.ts.map +1 -1
- package/dist/stand/subscription-observer.js +157 -40
- package/dist/stand/subscription-observer.js.map +1 -1
- package/dist/stand/subscription-read-filters.d.ts +40 -0
- package/dist/stand/subscription-read-filters.d.ts.map +1 -0
- package/dist/stand/subscription-read-filters.js +91 -0
- package/dist/stand/subscription-read-filters.js.map +1 -0
- package/dist/stand/subscription-reader-binding.d.ts +20 -0
- package/dist/stand/subscription-reader-binding.d.ts.map +1 -0
- package/dist/stand/subscription-reader-binding.js +56 -0
- package/dist/stand/subscription-reader-binding.js.map +1 -0
- package/dist/stand/subscription-records.d.ts +30 -3
- package/dist/stand/subscription-records.d.ts.map +1 -1
- package/dist/stand/subscription-records.js +61 -16
- package/dist/stand/subscription-records.js.map +1 -1
- package/dist/stand/subscription-registry.d.ts +54 -15
- package/dist/stand/subscription-registry.d.ts.map +1 -1
- package/dist/stand/subscription-registry.js +164 -41
- package/dist/stand/subscription-registry.js.map +1 -1
- package/dist/stand/subscription-runtime.d.ts +12 -5
- package/dist/stand/subscription-runtime.d.ts.map +1 -1
- package/dist/stand/subscription-runtime.js +183 -34
- package/dist/stand/subscription-runtime.js.map +1 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +4 -3
- package/dist/testing/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +14 -14
- package/dist/agent/agent-admission.d.ts +0 -80
- package/dist/agent/agent-admission.d.ts.map +0 -1
- package/dist/agent/agent-admission.js +0 -127
- package/dist/agent/agent-admission.js.map +0 -1
- package/dist/agent/agent-ai-runtime.d.ts +0 -76
- package/dist/agent/agent-ai-runtime.d.ts.map +0 -1
- package/dist/agent/agent-ai-runtime.js +0 -1269
- package/dist/agent/agent-ai-runtime.js.map +0 -1
- package/dist/agent/agent-execution-fault.d.ts +0 -18
- package/dist/agent/agent-execution-fault.d.ts.map +0 -1
- package/dist/agent/agent-execution-fault.js.map +0 -1
- package/dist/agent/agent-execution-session.d.ts +0 -103
- package/dist/agent/agent-execution-session.d.ts.map +0 -1
- package/dist/agent/agent-execution-session.js +0 -238
- package/dist/agent/agent-execution-session.js.map +0 -1
- package/dist/agent/agent-interaction-audit.d.ts +0 -58
- package/dist/agent/agent-interaction-audit.d.ts.map +0 -1
- package/dist/agent/agent-interaction-audit.js +0 -114
- package/dist/agent/agent-interaction-audit.js.map +0 -1
- package/dist/agent/agent-interaction-events.d.ts +0 -40
- package/dist/agent/agent-interaction-events.d.ts.map +0 -1
- package/dist/agent/agent-interaction-events.js +0 -129
- package/dist/agent/agent-interaction-events.js.map +0 -1
- package/dist/agent/agent-mcp-budget.d.ts +0 -125
- package/dist/agent/agent-mcp-budget.d.ts.map +0 -1
- package/dist/agent/agent-mcp-budget.js +0 -566
- package/dist/agent/agent-mcp-budget.js.map +0 -1
- package/dist/agent/agent-mcp-host.d.ts +0 -125
- package/dist/agent/agent-mcp-host.d.ts.map +0 -1
- package/dist/agent/agent-mcp-host.js.map +0 -1
- package/dist/agent/agent-mcp-runtime.d.ts.map +0 -1
- package/dist/agent/agent-mcp-runtime.js.map +0 -1
- package/dist/agent/agent-read-runtime.d.ts +0 -70
- package/dist/agent/agent-read-runtime.d.ts.map +0 -1
- package/dist/agent/agent-read-runtime.js +0 -320
- package/dist/agent/agent-read-runtime.js.map +0 -1
- package/dist/agent/agent-revisions.d.ts +0 -35
- package/dist/agent/agent-revisions.d.ts.map +0 -1
- package/dist/agent/agent-revisions.js +0 -133
- package/dist/agent/agent-revisions.js.map +0 -1
- package/dist/agent/agent-scheduler.d.ts +0 -99
- package/dist/agent/agent-scheduler.d.ts.map +0 -1
- package/dist/agent/agent-scheduler.js +0 -398
- package/dist/agent/agent-scheduler.js.map +0 -1
- package/dist/bus/saved-dispatcher-binding.d.ts +0 -53
- package/dist/bus/saved-dispatcher-binding.d.ts.map +0 -1
- package/dist/bus/saved-dispatcher-binding.js.map +0 -1
- package/dist/delivery/delivery-run-coordinator.d.ts +0 -218
- package/dist/delivery/delivery-run-coordinator.d.ts.map +0 -1
- package/dist/delivery/delivery-run-coordinator.js +0 -609
- package/dist/delivery/delivery-run-coordinator.js.map +0 -1
- package/dist/delivery/parked-delivery-obligations.d.ts +0 -182
- package/dist/delivery/parked-delivery-obligations.d.ts.map +0 -1
- package/dist/delivery/parked-delivery-obligations.js +0 -390
- package/dist/delivery/parked-delivery-obligations.js.map +0 -1
- package/dist/server/environment-attachment.d.ts +0 -356
- package/dist/server/environment-attachment.d.ts.map +0 -1
- package/dist/server/environment-attachment.js +0 -2062
- package/dist/server/environment-attachment.js.map +0 -1
- package/dist/server/environment-delivery-records.d.ts +0 -77
- package/dist/server/environment-delivery-records.d.ts.map +0 -1
- package/dist/server/environment-delivery-records.js +0 -282
- package/dist/server/environment-delivery-records.js.map +0 -1
- package/dist/server/environment-delivery-worker.d.ts +0 -161
- package/dist/server/environment-delivery-worker.d.ts.map +0 -1
- package/dist/server/environment-delivery-worker.js +0 -973
- package/dist/server/environment-delivery-worker.js.map +0 -1
|
@@ -11,45 +11,34 @@
|
|
|
11
11
|
* or implied. See the License for the specific language governing permissions and limitations under
|
|
12
12
|
* the License.
|
|
13
13
|
*/
|
|
14
|
-
import { constants as fsConstants } from "node:fs";
|
|
15
|
-
import { access, realpath } from "node:fs/promises";
|
|
16
|
-
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
17
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
18
14
|
import { clone, create, getOption, hasOption } from "@bufbuild/protobuf";
|
|
19
15
|
import { freezeRegistry, isAiModel, registryOptions } from "@spine-event-engine/ai/spi/runtime";
|
|
20
16
|
import { TypeUrls } from "@spine-event-engine/core";
|
|
21
17
|
import { EventSchema, EventContextSchema, BoundedContextNameSchema, TenantIdSchema, } from "@spine-event-engine/proto";
|
|
22
18
|
import { SPI_type, internal_all, internal_type } from "@spine-event-engine/proto";
|
|
23
|
-
import { ColumnTypes, EventStore,
|
|
24
|
-
import {
|
|
25
|
-
/**
|
|
26
|
-
* Storage family shared by paired System Event publication and trusted reads.
|
|
27
|
-
* @internal
|
|
28
|
-
*/
|
|
29
|
-
export const systemEventGroup = new StorageGroup("spine_system_events");
|
|
19
|
+
import { ColumnTypes, EventStore, RecordColumn, } from "@spine-event-engine/storage";
|
|
20
|
+
import { AgentHistoryStorageFactories, EventStoreGroups, eventStoreRecordSpec, StoragePreparations, TenantBoundary, } from "@spine-event-engine/storage/provider";
|
|
30
21
|
import { CommandBus, commandBusAccess } from "../bus/command-bus.js";
|
|
31
22
|
import { EventBus, eventBusAccess, isSystemEventSchema, } from "../bus/event-bus.js";
|
|
32
|
-
import { DeliveryReadiness
|
|
23
|
+
import { DeliveryReadiness } from "./local-inbox-handoff.js";
|
|
33
24
|
import { LocalEntityInbox } from "./entity-inbox.js";
|
|
34
25
|
import { LocalProjectionInbox } from "./projection-handoff.js";
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
37
|
-
import {
|
|
38
|
-
import { AgentExecutionCapacity } from "../agent/agent-execution-capacity.js";
|
|
26
|
+
import { TenantAdmissions } from "./tenant-unknown-error.js";
|
|
27
|
+
import { AgentExecutionCapacity, } from "../agent/agent-execution-capacity.js";
|
|
28
|
+
import { AgentAcceptanceReceipts, agentAcceptanceReceiptGroup, agentAcceptanceReceiptSpec, } from "../agent/agent-acceptance-receipts.js";
|
|
39
29
|
import { Repository, repositoryAccess, } from "../repository/repository.js";
|
|
40
|
-
import {
|
|
30
|
+
import { SpineMetadataInventory } from "../handler/spine-metadata-inventory.js";
|
|
41
31
|
import { HandlerRegistryIngestor, } from "../handler/generated-handler-registry.js";
|
|
42
32
|
import { AbstractAssignee, AbstractCommander, AbstractEventReactor, AbstractEventSubscriber, } from "../handler/standalone.js";
|
|
43
33
|
import { HandlerMetadataRegistry, } from "../handler/handler-metadata.js";
|
|
44
34
|
import { SignalMetadata } from "../runtime/signal-metadata.js";
|
|
45
|
-
import {
|
|
35
|
+
import { runtimeAccess } from "../runtime/runtime.js";
|
|
36
|
+
import { Stand, standAccess } from "../stand/stand.js";
|
|
37
|
+
import { ReadSideFilter } from "../stand/read-side-filter.js";
|
|
46
38
|
import { SubscriptionRuntime, subscriptionRuntimeAccess } from "../stand/subscription-runtime.js";
|
|
47
|
-
import { StorageSubscriptionRegistry, } from "../stand/subscription-registry.js";
|
|
48
|
-
import { UniformAcrossAllShards } from "../delivery/delivery-builder.js";
|
|
49
|
-
import { InboxTargets } from "../delivery/inbox.js";
|
|
50
|
-
import { ShardIndex } from "../delivery/shard-index.js";
|
|
39
|
+
import { StorageSubscriptionRegistry, subscriptionRecordGroup, } from "../stand/subscription-registry.js";
|
|
51
40
|
import { IntegrationBroker } from "../integration/integration-broker.js";
|
|
52
|
-
import { ServerEnvironment } from "../server/server-environment.js";
|
|
41
|
+
import { ServerEnvironment, serverEnvironmentAccess } from "../server/server-environment.js";
|
|
53
42
|
import { SignalPublisher } from "../runtime/signal-publisher.js";
|
|
54
43
|
import { StandaloneHandlerRuntime, } from "../runtime/standalone-handler-runtime.js";
|
|
55
44
|
/**
|
|
@@ -73,20 +62,51 @@ export class BoundedContextNameError extends Error {
|
|
|
73
62
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
74
63
|
}
|
|
75
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Rejects an Entity state type already served by another open domain context.
|
|
67
|
+
*/
|
|
68
|
+
export class DuplicateEntityStateException extends Error {
|
|
69
|
+
// prettier-ignore
|
|
70
|
+
/**
|
|
71
|
+
* Wire type URL of the conflicting Entity state.
|
|
72
|
+
*/
|
|
73
|
+
stateType;
|
|
74
|
+
/**
|
|
75
|
+
* Name of the context that already serves the state.
|
|
76
|
+
*/
|
|
77
|
+
registeredContext;
|
|
78
|
+
/**
|
|
79
|
+
* Name of the context attempting registration.
|
|
80
|
+
*/
|
|
81
|
+
conflictingContext;
|
|
82
|
+
/**
|
|
83
|
+
* Creates a duplicate-state registration error.
|
|
84
|
+
* @param stateType Entity state wire type URL.
|
|
85
|
+
* @param registeredContext Existing domain context name.
|
|
86
|
+
* @param conflictingContext New domain context name.
|
|
87
|
+
*/
|
|
88
|
+
constructor(stateType, registeredContext, conflictingContext) {
|
|
89
|
+
super(`Entity state "${stateType}" is already registered in "${registeredContext}"; ` +
|
|
90
|
+
`register it in only one open domain Bounded Context (attempted in "${conflictingContext}").`);
|
|
91
|
+
this.name = "DuplicateEntityStateException";
|
|
92
|
+
this.stateType = stateType;
|
|
93
|
+
this.registeredContext = registeredContext;
|
|
94
|
+
this.conflictingContext = conflictingContext;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
76
97
|
const frameworkConstructionToken = Object.freeze({
|
|
77
98
|
frameworkConstructionToken: true,
|
|
78
99
|
});
|
|
79
|
-
const generatedRegistryFile = "generated/handler/generated-handler-registry.js";
|
|
80
100
|
const errorDetailLimit = 500;
|
|
81
|
-
const moduleSchemeRe = /^[A-Za-z][A-Za-z\d+.-]*:/;
|
|
82
101
|
const internalStoragePrefix = "__spine/";
|
|
83
|
-
const generatedRegistryLoadAttempts = new Map();
|
|
84
102
|
const eventSubscribers = new WeakMap();
|
|
85
103
|
const contextSystemPairings = new WeakMap();
|
|
86
104
|
const contextTenantIndexes = new WeakMap();
|
|
87
105
|
const contextStorageFactories = new WeakMap();
|
|
88
106
|
const contextDeliveryDescriptors = new WeakMap();
|
|
89
107
|
const contextSubscriptionRuntimes = new WeakMap();
|
|
108
|
+
const contextPreparations = new WeakMap();
|
|
109
|
+
const contextInputPreparations = new WeakMap();
|
|
90
110
|
const contextLoggers = new WeakMap();
|
|
91
111
|
const contextSignalPublishers = new WeakMap();
|
|
92
112
|
const contextEventBuses = new WeakMap();
|
|
@@ -97,7 +117,9 @@ const contextClosePhases = new WeakMap();
|
|
|
97
117
|
const contextIntegrations = new WeakMap();
|
|
98
118
|
const systemEventPosters = new WeakMap();
|
|
99
119
|
const builderBuilds = new WeakMap();
|
|
120
|
+
const explicitPreparations = new WeakMap();
|
|
100
121
|
let constructBoundedContext;
|
|
122
|
+
let registerBoundedContext;
|
|
101
123
|
let constructBoundedContextBuilder;
|
|
102
124
|
let constructContextSpec;
|
|
103
125
|
/**
|
|
@@ -113,22 +135,37 @@ export class BoundedContext {
|
|
|
113
135
|
#eventEndpoint;
|
|
114
136
|
#entityInbox;
|
|
115
137
|
#projectionInbox;
|
|
116
|
-
#
|
|
138
|
+
#deliveryReadiness;
|
|
139
|
+
#stopAgentAvailability;
|
|
117
140
|
#registeredRepositories = [];
|
|
118
141
|
#repositoryViews = new Set();
|
|
119
142
|
#storageFactory;
|
|
143
|
+
#environment;
|
|
120
144
|
#ai;
|
|
121
145
|
#stand;
|
|
122
146
|
#systemStand;
|
|
123
147
|
#subscriptionRuntime;
|
|
124
|
-
|
|
125
|
-
|
|
148
|
+
/**
|
|
149
|
+
* Explicit storage and subscription preparation for this built context.
|
|
150
|
+
*/
|
|
151
|
+
runtime;
|
|
152
|
+
#agentReceipts;
|
|
153
|
+
#agentController = new AbortController();
|
|
154
|
+
#agentReservations = new Set();
|
|
155
|
+
#activeReplays = new Set();
|
|
156
|
+
#activePreparations = new Set();
|
|
157
|
+
#tenantPreparations = new Map();
|
|
158
|
+
#preparationController = new AbortController();
|
|
159
|
+
#sharedPreparation;
|
|
126
160
|
#closed;
|
|
127
161
|
/**
|
|
128
162
|
* Registers the framework-only construction hook for this module.
|
|
129
163
|
*/
|
|
130
164
|
static {
|
|
131
165
|
constructBoundedContext = (input) => new BoundedContext(input);
|
|
166
|
+
registerBoundedContext = (context, repositories) => {
|
|
167
|
+
context.#registerRepositories(repositories);
|
|
168
|
+
};
|
|
132
169
|
}
|
|
133
170
|
/**
|
|
134
171
|
* Creates a Bounded Context from its prepared framework services.
|
|
@@ -145,9 +182,12 @@ export class BoundedContext {
|
|
|
145
182
|
this.#stand = input.stand;
|
|
146
183
|
this.#systemStand = input.systemStand;
|
|
147
184
|
this.#subscriptionRuntime = input.subscriptionRuntime;
|
|
185
|
+
this.runtime = this.#createRuntime();
|
|
148
186
|
this.#storageFactory = input.storageFactory;
|
|
187
|
+
this.#agentReceipts = new AgentAcceptanceReceipts(input.storageFactory);
|
|
188
|
+
this.#environment = ServerEnvironment.instance();
|
|
149
189
|
this.#ai = input.ai;
|
|
150
|
-
this.#
|
|
190
|
+
this.#installTenantAdmission();
|
|
151
191
|
this.#commandEndpoint = Object.freeze({
|
|
152
192
|
acceptedCommandTypes: () => this.#commandBus.acceptedCommandTypes(),
|
|
153
193
|
post: (command) => this.#commandBus.post(command),
|
|
@@ -156,79 +196,305 @@ export class BoundedContext {
|
|
|
156
196
|
acceptedEventTypes: () => ContextParts.exposedEventTypeUrls(this.#eventBus),
|
|
157
197
|
post: (event) => ContextParts.postContextEvent(this, event),
|
|
158
198
|
});
|
|
159
|
-
const delivery = this.#createDelivery(input.
|
|
199
|
+
const delivery = this.#createDelivery(input.tenantIndex);
|
|
200
|
+
this.#deliveryReadiness = delivery.deliveryReadiness;
|
|
160
201
|
this.#entityInbox = delivery.entityInbox;
|
|
161
202
|
this.#projectionInbox = delivery.projectionInbox;
|
|
162
203
|
this.#installReferences(input, delivery);
|
|
163
|
-
this.#
|
|
164
|
-
this.#startAgentScheduler(delivery.tenantIndex);
|
|
204
|
+
serverEnvironmentAccess.addContext(this.#environment, this);
|
|
165
205
|
Object.freeze(this);
|
|
166
206
|
}
|
|
167
207
|
/**
|
|
168
|
-
*
|
|
208
|
+
* Exposes explicit preparation for this built context.
|
|
209
|
+
*
|
|
210
|
+
* @returns Stable explicit preparation methods.
|
|
169
211
|
*/
|
|
170
|
-
#
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
return;
|
|
176
|
-
this.#agentScheduler = new AgentScheduler({
|
|
177
|
-
repositories: repositories.length,
|
|
178
|
-
page: (after, signal) => tenantIndex.page({
|
|
179
|
-
count: 16,
|
|
180
|
-
signal,
|
|
181
|
-
...(after === undefined ? {} : { after }),
|
|
182
|
-
}),
|
|
183
|
-
scope: (id, index) => {
|
|
184
|
-
const repository = repositories[index];
|
|
185
|
-
if (repository === undefined)
|
|
186
|
-
throw new Error("Agent repository scope is unavailable.");
|
|
187
|
-
return this.#agentScope(repository, tenantIndex.tenantMode === "single-tenant" ? undefined : id);
|
|
188
|
-
},
|
|
189
|
-
}, AgentExecutionCapacity.for(this.#ai), (error) => {
|
|
190
|
-
if (this.#agentErrors.length < 32)
|
|
191
|
-
this.#agentErrors.push(error);
|
|
212
|
+
#createRuntime() {
|
|
213
|
+
return Object.freeze({
|
|
214
|
+
createStorageStructures: (...ids) => this.#explicitStructures("create", ids),
|
|
215
|
+
checkStructureCompatibility: (...ids) => this.#explicitStructures("check", ids),
|
|
216
|
+
startSubscriptionMonitoring: () => this.#explicitMonitoring(),
|
|
192
217
|
});
|
|
193
|
-
this.#agentScheduler.start();
|
|
194
218
|
}
|
|
195
219
|
/**
|
|
196
|
-
*
|
|
220
|
+
* Installs live membership checks on context signal buses.
|
|
197
221
|
*
|
|
198
|
-
* @param repository Registered Agent repository.
|
|
199
|
-
* @param tenantId Accepted tenant, absent only in a single-tenant Bounded Context.
|
|
200
|
-
* @returns The matching indexed provider scan scope.
|
|
201
222
|
*/
|
|
202
|
-
#
|
|
203
|
-
|
|
223
|
+
#installTenantAdmission() {
|
|
224
|
+
explicitPreparations.set(this, (mode, ids) => this.#prepareExplicitLocal(mode, ids));
|
|
225
|
+
commandBusAccess.bindEnvironment(this.#commandBus, this.#environment);
|
|
226
|
+
eventBusAccess.bindEnvironment(this.#eventBus, this.#environment);
|
|
227
|
+
eventBusAccess.bindEnvironment(this.#systemEventBus, this.#environment);
|
|
228
|
+
commandBusAccess.setAdmission(this.#commandBus, (command) => this.#admitTenant(command.context?.actorContext?.tenantId));
|
|
229
|
+
eventBusAccess.setAdmission(this.#eventBus, (event) => this.#admitTenant(ContextParts.readReplayTenant(event)));
|
|
230
|
+
standAccess.setAdmission(this.#stand, (tenantId) => this.#admitTenant(tenantId));
|
|
231
|
+
standAccess.setPublicAdmission(this.#stand, () => {
|
|
232
|
+
if (closingContexts.has(this) && !runtimeAccess.isRunningAdmittedWork(this.#environment))
|
|
233
|
+
throw new Error("Bounded Context is closing.");
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Checks this context's live index before repository work.
|
|
238
|
+
*
|
|
239
|
+
* @param tenantId Captured tenant identity, when applicable.
|
|
240
|
+
*/
|
|
241
|
+
async #admitTenant(tenantId) {
|
|
242
|
+
await TenantAdmissions.require(this.tenantIndex, this.#snapshot.name.value, this.isMultitenant, tenantId);
|
|
243
|
+
await this.#prepareInput(tenantId);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Initializes only the admitted input's complete context and tenant requirements.
|
|
247
|
+
* @param tenantId Captured input tenant identity, when applicable.
|
|
248
|
+
* @returns Completion after storage and first monitoring pass are ready.
|
|
249
|
+
*/
|
|
250
|
+
async #prepareInput(tenantId) {
|
|
251
|
+
await this.#prepareShared("create");
|
|
252
|
+
await this.#prepareTenant(tenantId, "create");
|
|
253
|
+
if (!closingContexts.has(this))
|
|
254
|
+
await this.#subscriptionRuntime.start();
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Prepares the fixed shared subscription registry layout once per context.
|
|
258
|
+
* @param mode Required provider preparation mode.
|
|
259
|
+
* @returns Shared preparation completion.
|
|
260
|
+
*/
|
|
261
|
+
#prepareShared(mode) {
|
|
262
|
+
const registry = this.#subscriptionRuntime.registry();
|
|
263
|
+
if (!(registry instanceof StorageSubscriptionRegistry))
|
|
264
|
+
return Promise.resolve();
|
|
265
|
+
if (this.#sharedPreparation !== undefined)
|
|
266
|
+
return this.#sharedPreparation;
|
|
267
|
+
const attempt = this.#trackPreparation(() => registry.prepare(mode));
|
|
268
|
+
this.#sharedPreparation = attempt;
|
|
269
|
+
void attempt.catch(() => {
|
|
270
|
+
if (this.#sharedPreparation === attempt)
|
|
271
|
+
this.#sharedPreparation = undefined;
|
|
272
|
+
});
|
|
273
|
+
return attempt;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Shares one pending complete-tenant preparation without caching a stale model flag.
|
|
277
|
+
* @param tenantId Captured tenant identity, when applicable.
|
|
278
|
+
* @param mode Required provider preparation mode.
|
|
279
|
+
* @returns Complete tenant preparation attempt.
|
|
280
|
+
*/
|
|
281
|
+
#prepareTenant(tenantId, mode) {
|
|
282
|
+
const context = this.#storageContextFor(tenantId);
|
|
283
|
+
const key = TenantBoundary.of(context).key;
|
|
284
|
+
const pending = this.#tenantPreparations.get(key);
|
|
285
|
+
if (pending !== undefined)
|
|
286
|
+
return pending;
|
|
287
|
+
const attempt = this.#trackPreparation(() => this.#prepareTenantNow(context, mode));
|
|
288
|
+
this.#tenantPreparations.set(key, attempt);
|
|
289
|
+
void attempt.then(() => this.#tenantPreparations.delete(key), () => this.#tenantPreparations.delete(key));
|
|
290
|
+
return attempt;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Prepares exact Event, Entity and Agent families for one captured destination.
|
|
294
|
+
* @param context Captured physical storage destination.
|
|
295
|
+
* @param mode Required provider preparation mode.
|
|
296
|
+
* @returns Completion after all declared families are ready.
|
|
297
|
+
*/
|
|
298
|
+
async #prepareTenantNow(context, mode) {
|
|
299
|
+
const preparation = StoragePreparations.for(this.#storageFactory);
|
|
300
|
+
await preparation.prepareRecord(context, eventStoreRecordSpec, EventStoreGroups.forContext(context.name), mode);
|
|
301
|
+
const system = ContextParts.requireSystemPairing(this).system;
|
|
302
|
+
if (system.storesEvents) {
|
|
303
|
+
const systemContext = { ...context, name: system.name.value };
|
|
304
|
+
await preparation.prepareRecord(systemContext, eventStoreRecordSpec, EventStoreGroups.forContext(system.name.value, "system"), mode);
|
|
305
|
+
}
|
|
306
|
+
for (const repository of this.#repositoryViews)
|
|
307
|
+
await repositoryAccess.prepareStorage(repository, context, this.#storageFactory, mode);
|
|
308
|
+
if ([...this.#repositoryViews].some((repository) => repository.entityFamily === "agent"))
|
|
309
|
+
await preparation.prepareRecord(context, agentAcceptanceReceiptSpec, agentAcceptanceReceiptGroup, mode);
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Captures the same tenant scope used by repository and Event storage.
|
|
313
|
+
* @param tenantId Complete selected tenant identity, when applicable.
|
|
314
|
+
* @returns A detached provider storage context.
|
|
315
|
+
*/
|
|
316
|
+
#storageContextFor(tenantId) {
|
|
317
|
+
if (!this.isMultitenant)
|
|
318
|
+
return { name: this.name.value, multitenant: false };
|
|
319
|
+
if (tenantId === undefined)
|
|
320
|
+
throw new Error("Multitenant preparation requires tenantId.");
|
|
204
321
|
return {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
repository.stateFullTypeName,
|
|
209
|
-
]),
|
|
210
|
-
pending: (after, count) => repositoryAccess.pendingAcceptedAgents(repository, tenantId, after, count),
|
|
211
|
-
run: (key, signal) => repositoryAccess.runAcceptedAgent(repository, tenantId, key, signal),
|
|
322
|
+
name: this.name.value,
|
|
323
|
+
multitenant: true,
|
|
324
|
+
tenantId: TenantBoundary.from(tenantId).tenantId,
|
|
212
325
|
};
|
|
213
326
|
}
|
|
327
|
+
/**
|
|
328
|
+
* Retains an admitted preparation until closure can safely release its provider.
|
|
329
|
+
* @param onPrepare Complete provider preparation callback.
|
|
330
|
+
* @returns Tracked preparation completion.
|
|
331
|
+
*/
|
|
332
|
+
#trackPreparation(onPrepare) {
|
|
333
|
+
const attempt = Promise.resolve().then(onPrepare);
|
|
334
|
+
this.#activePreparations.add(attempt);
|
|
335
|
+
void attempt.then(() => this.#activePreparations.delete(attempt), () => this.#activePreparations.delete(attempt));
|
|
336
|
+
return attempt;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Completes independently requested eager structure and monitor phases.
|
|
340
|
+
* @param options Independent build-time initialization choices.
|
|
341
|
+
* @returns Completion after requested phases settle.
|
|
342
|
+
*/
|
|
343
|
+
async #prepareBuild(options) {
|
|
344
|
+
if (options.createStorageStructures !== false) {
|
|
345
|
+
await this.#requiredPrepare(() => this.#prepareShared("create"));
|
|
346
|
+
await this.#prepareCurrentTenants("create");
|
|
347
|
+
}
|
|
348
|
+
if (options.startSubscriptionMonitoring !== false) {
|
|
349
|
+
await this.#requiredPrepare(() => this.#prepareShared("create"));
|
|
350
|
+
await this.#requiredPrepare(() => this.#subscriptionRuntime.start());
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Classifies a requested startup phase without changing shared lazy attempts.
|
|
355
|
+
* @param onPrepare Required provider or initial-monitor phase.
|
|
356
|
+
* @returns Completion of the requested phase.
|
|
357
|
+
*/
|
|
358
|
+
async #requiredPrepare(onPrepare) {
|
|
359
|
+
serverEnvironmentAccess.requireAvailable(this.#environment);
|
|
360
|
+
try {
|
|
361
|
+
await onPrepare();
|
|
362
|
+
}
|
|
363
|
+
catch (error) {
|
|
364
|
+
serverEnvironmentAccess.failStartup(this.#environment, error);
|
|
365
|
+
throw error;
|
|
366
|
+
}
|
|
367
|
+
serverEnvironmentAccess.requireAvailable(this.#environment);
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Traverses the current live index in finite pages without a tenant cap.
|
|
371
|
+
* @param mode Creation or read-only check.
|
|
372
|
+
* @returns Completion after currently listed tenants are prepared.
|
|
373
|
+
*/
|
|
374
|
+
async #prepareCurrentTenants(mode) {
|
|
375
|
+
if (!this.isMultitenant)
|
|
376
|
+
return this.#requiredPrepare(() => this.#prepareTenant(undefined, mode));
|
|
377
|
+
let after;
|
|
378
|
+
do {
|
|
379
|
+
serverEnvironmentAccess.requireAvailable(this.#environment);
|
|
380
|
+
const page = await this.tenantIndex.page({
|
|
381
|
+
count: 64,
|
|
382
|
+
signal: this.#preparationController.signal,
|
|
383
|
+
...(after === undefined ? {} : { after }),
|
|
384
|
+
});
|
|
385
|
+
serverEnvironmentAccess.requireAvailable(this.#environment);
|
|
386
|
+
for (const id of page.ids) {
|
|
387
|
+
serverEnvironmentAccess.requireAvailable(this.#environment);
|
|
388
|
+
await TenantAdmissions.require(this.tenantIndex, this.name.value, true, id);
|
|
389
|
+
serverEnvironmentAccess.requireAvailable(this.#environment);
|
|
390
|
+
await this.#requiredPrepare(() => this.#prepareTenant(id, mode));
|
|
391
|
+
}
|
|
392
|
+
after = page.hasMore ? page.after : undefined;
|
|
393
|
+
} while (after !== undefined);
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Admits explicit structure work and joins fatal cleanup after local settlement.
|
|
397
|
+
* @param mode Creation or read-only compatibility mode.
|
|
398
|
+
* @param tenantIds Explicit complete tenant identities, if supplied.
|
|
399
|
+
* @returns Completion after the selected structures are ready.
|
|
400
|
+
*/
|
|
401
|
+
async #explicitStructures(mode, tenantIds) {
|
|
402
|
+
const captured = tenantIds.map((id) => TenantBoundary.from(id).tenantId);
|
|
403
|
+
const admitted = boundedContextAccess.admitServiceRegistryOperation(this, () => this.#prepareExplicitLocal(mode, captured));
|
|
404
|
+
return this.#joinRequiredCleanup(admitted);
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Runs explicit preparation while the caller holds this context's admission.
|
|
408
|
+
*
|
|
409
|
+
* @param mode Creation or read-only check.
|
|
410
|
+
* @param ids Captured tenant identities, when supplied.
|
|
411
|
+
* @returns Completion of the local preparation.
|
|
412
|
+
*/
|
|
413
|
+
async #prepareExplicitLocal(mode, ids) {
|
|
414
|
+
const selected = await this.#selectedTenants(ids);
|
|
415
|
+
await this.#requiredPrepare(() => this.#prepareShared(mode));
|
|
416
|
+
if (selected === undefined)
|
|
417
|
+
return this.#prepareCurrentTenants(mode);
|
|
418
|
+
for (const id of selected) {
|
|
419
|
+
await TenantAdmissions.require(this.tenantIndex, this.name.value, this.isMultitenant, id);
|
|
420
|
+
await this.#requiredPrepare(() => this.#prepareTenant(id, mode));
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Validates all explicit IDs before any provider preparation.
|
|
425
|
+
*
|
|
426
|
+
* @param ids Captured explicit tenant identities.
|
|
427
|
+
* @returns Distinct current IDs or undefined for all current tenants.
|
|
428
|
+
*/
|
|
429
|
+
async #selectedTenants(ids) {
|
|
430
|
+
if (ids.length === 0)
|
|
431
|
+
return undefined;
|
|
432
|
+
const selected = new Map();
|
|
433
|
+
for (const id of ids) {
|
|
434
|
+
await TenantAdmissions.require(this.tenantIndex, this.name.value, this.isMultitenant, id);
|
|
435
|
+
const boundary = TenantBoundary.from(id);
|
|
436
|
+
selected.set(boundary.key, boundary.tenantId);
|
|
437
|
+
}
|
|
438
|
+
return [...selected.values()];
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Starts the one initial monitor pass as an explicit required phase.
|
|
442
|
+
*
|
|
443
|
+
* @returns Completion after the initial pass.
|
|
444
|
+
*/
|
|
445
|
+
#explicitMonitoring() {
|
|
446
|
+
const admitted = boundedContextAccess.admitServiceRegistryOperation(this, async () => {
|
|
447
|
+
await this.#requiredPrepare(() => this.#prepareShared("create"));
|
|
448
|
+
await this.#requiredPrepare(() => this.#subscriptionRuntime.start());
|
|
449
|
+
});
|
|
450
|
+
return this.#joinRequiredCleanup(admitted);
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Waits for recorded fatal cleanup only after context admission settles.
|
|
454
|
+
*
|
|
455
|
+
* @param admitted Locally admitted operation.
|
|
456
|
+
* @returns Completion after local work or fatal cleanup.
|
|
457
|
+
*/
|
|
458
|
+
async #joinRequiredCleanup(admitted) {
|
|
459
|
+
try {
|
|
460
|
+
await admitted;
|
|
461
|
+
}
|
|
462
|
+
catch (error) {
|
|
463
|
+
if (serverEnvironmentAccess.hasStartupFailure(this.#environment))
|
|
464
|
+
return serverEnvironmentAccess.awaitStartupFailure(this.#environment);
|
|
465
|
+
throw error;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
214
468
|
/**
|
|
215
469
|
* Creates tenant-aware Inboxes and their shared readiness signal.
|
|
216
470
|
*
|
|
217
|
-
* @param
|
|
471
|
+
* @param tenantIndex Effective membership index for this context.
|
|
218
472
|
* @returns Inboxes, tenant index, and readiness signal.
|
|
219
473
|
*/
|
|
220
|
-
#createDelivery(
|
|
221
|
-
const deliveryReadiness =
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
const entityInbox = new LocalEntityInbox(this.#snapshot.name.value, deliveryReadiness, keepTenant, this.#deliveryStrategy);
|
|
229
|
-
const projectionInbox = new LocalProjectionInbox(this.#snapshot.name.value, deliveryReadiness, keepTenant);
|
|
474
|
+
#createDelivery(tenantIndex) {
|
|
475
|
+
const deliveryReadiness = this.#createReadiness();
|
|
476
|
+
const onAdmit = (tenantId) => this.#admitTenant(tenantId);
|
|
477
|
+
const entityInbox = new LocalEntityInbox(this.#snapshot.name.value, deliveryReadiness, onAdmit, this.#environment.delivery.strategy);
|
|
478
|
+
const projectionInbox = new LocalProjectionInbox(this.#snapshot.name.value, deliveryReadiness, onAdmit);
|
|
479
|
+
deliveryReadiness.configureDirect((message, tenantId) => message.label === "UPDATE_SUBSCRIBER"
|
|
480
|
+
? projectionInbox.replay(message, tenantId)
|
|
481
|
+
: entityInbox.replay(message, tenantId));
|
|
230
482
|
return { deliveryReadiness, tenantIndex, entityInbox, projectionInbox };
|
|
231
483
|
}
|
|
484
|
+
/**
|
|
485
|
+
* Creates readiness for the shared Delivery and this context's Agent capacity.
|
|
486
|
+
* @returns Shared readiness with the Agent capacity wake installed.
|
|
487
|
+
*/
|
|
488
|
+
#createReadiness() {
|
|
489
|
+
const readiness = new DeliveryReadiness(undefined, serverEnvironmentAccess.coreDelivery(this.#environment), (shard) => {
|
|
490
|
+
serverEnvironmentAccess.notifyDelivery(this.#environment, shard);
|
|
491
|
+
}, serverEnvironmentAccess.remoteSupervised(this.#environment));
|
|
492
|
+
if (this.#ai !== undefined)
|
|
493
|
+
this.#stopAgentAvailability = AgentExecutionCapacity.for(this.#ai).onAvailable(() => {
|
|
494
|
+
readiness.retryDeferred();
|
|
495
|
+
});
|
|
496
|
+
return readiness;
|
|
497
|
+
}
|
|
232
498
|
/**
|
|
233
499
|
* Installs framework references used after context construction.
|
|
234
500
|
*
|
|
@@ -244,42 +510,29 @@ export class BoundedContext {
|
|
|
244
510
|
contextTenantIndexes.set(this, delivery.tenantIndex);
|
|
245
511
|
contextStorageFactories.set(this, input.storageFactory);
|
|
246
512
|
contextSubscriptionRuntimes.set(this, this.#subscriptionRuntime);
|
|
513
|
+
contextPreparations.set(this, (options) => this.#prepareBuild(options));
|
|
514
|
+
contextInputPreparations.set(this, (tenantId) => this.#prepareInput(tenantId));
|
|
247
515
|
contextSignalPublishers.set(this, this.#publisher);
|
|
248
516
|
contextClosePhases.set(this, {
|
|
249
517
|
begin: () => {
|
|
250
518
|
this.#beginClose();
|
|
251
519
|
},
|
|
252
520
|
drain: () => this.#drainWork(),
|
|
521
|
+
trackReplay: (onReplay) => this.#trackReplay(onReplay),
|
|
253
522
|
});
|
|
254
|
-
contextDeliveryDescriptors.set(this, ContextParts.createDeliveryDescriptor(this.#
|
|
523
|
+
contextDeliveryDescriptors.set(this, ContextParts.createDeliveryDescriptor(this.#entityInbox, this.#projectionInbox));
|
|
255
524
|
}
|
|
256
525
|
/**
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
* @param repositories Prepared repositories.
|
|
260
|
-
* @param tenantIndex Index to close if registration fails.
|
|
526
|
+
* Prepares repository registrations, reserves their state types, and commits their Stand and
|
|
527
|
+
* inbox targets; rolls back uncommitted storage preparation on failure.
|
|
261
528
|
*/
|
|
262
|
-
#registerAndStart(repositories, tenantIndex) {
|
|
263
|
-
try {
|
|
264
|
-
this.#registerRepositories(repositories);
|
|
265
|
-
this.#subscriptionRuntime.start();
|
|
266
|
-
}
|
|
267
|
-
catch (error) {
|
|
268
|
-
try {
|
|
269
|
-
ContextParts.cleanupFailedContext(this, tenantIndex);
|
|
270
|
-
}
|
|
271
|
-
catch (cleanupError) {
|
|
272
|
-
throw new AggregateError([error, cleanupError], "Bounded Context build failed, and tenant index cleanup also failed.");
|
|
273
|
-
}
|
|
274
|
-
throw error;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
529
|
#registerRepositories(repositories) {
|
|
278
530
|
const preparedRepositories = this.#prepareRepositories(repositories);
|
|
279
531
|
try {
|
|
280
532
|
for (const preparedRepository of preparedRepositories) {
|
|
281
533
|
ContextParts.rejectRegisteredRepository(preparedRepository.repository);
|
|
282
534
|
}
|
|
535
|
+
this.#reserveStates(preparedRepositories);
|
|
283
536
|
for (const preparedRepository of preparedRepositories) {
|
|
284
537
|
this.#stand.register(preparedRepository.snapshot.stateSchema, {
|
|
285
538
|
columns: ContextParts.repositoryColumns(preparedRepository.snapshot),
|
|
@@ -299,6 +552,23 @@ export class BoundedContext {
|
|
|
299
552
|
this.#failRegistration(error, preparedRepositories);
|
|
300
553
|
}
|
|
301
554
|
}
|
|
555
|
+
/**
|
|
556
|
+
* Reserves Entity state types before registration mutates delivery targets.
|
|
557
|
+
*
|
|
558
|
+
* @param repositories Prepared repositories for this context.
|
|
559
|
+
*/
|
|
560
|
+
#reserveStates(repositories) {
|
|
561
|
+
for (const repository of repositories) {
|
|
562
|
+
const stateType = TypeUrls.derive(repository.snapshot.stateSchema);
|
|
563
|
+
const prior = serverEnvironmentAccess.reserveState(this.#environment, this, stateType);
|
|
564
|
+
if (prior !== undefined)
|
|
565
|
+
throw new DuplicateEntityStateException(stateType, prior.name.value, this.name.value);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Builds prepared repository registrations using this context’s storage and delivery services,
|
|
570
|
+
* cleaning earlier preparations if a later one fails.
|
|
571
|
+
*/
|
|
302
572
|
#prepareRepositories(repositories) {
|
|
303
573
|
const registration = this.#repositoryRegistration();
|
|
304
574
|
const preparedRepositories = [];
|
|
@@ -312,14 +582,28 @@ export class BoundedContext {
|
|
|
312
582
|
}
|
|
313
583
|
return preparedRepositories;
|
|
314
584
|
}
|
|
585
|
+
/**
|
|
586
|
+
* Reserves shared-registry capacity before Agent receipt admission.
|
|
587
|
+
*/
|
|
588
|
+
#reserveAgent(key, signal) {
|
|
589
|
+
const reservation = this.#ai === undefined
|
|
590
|
+
? undefined
|
|
591
|
+
: AgentExecutionCapacity.for(this.#ai).reserve(key, signal);
|
|
592
|
+
if (reservation !== undefined) {
|
|
593
|
+
this.#agentReservations.add(reservation.completed);
|
|
594
|
+
void reservation.completed.then(() => this.#agentReservations.delete(reservation.completed), () => this.#agentReservations.delete(reservation.completed));
|
|
595
|
+
}
|
|
596
|
+
return reservation;
|
|
597
|
+
}
|
|
315
598
|
/**
|
|
316
599
|
* Builds callbacks shared by every repository prepared for this Bounded Context.
|
|
317
600
|
*/
|
|
318
601
|
#repositoryRegistration() {
|
|
319
|
-
|
|
602
|
+
return {
|
|
320
603
|
name: ContextParts.cloneName(this.#snapshot.name),
|
|
321
604
|
storageContext: ContextParts.createStorageMode(this.#snapshot.spec),
|
|
322
605
|
storageFactory: this.#storageFactory,
|
|
606
|
+
onAdmit: (tenantId) => this.#admitTenant(tenantId),
|
|
323
607
|
...(this.#ai === undefined ? {} : { ai: this.#ai }),
|
|
324
608
|
stand: this.#stand,
|
|
325
609
|
entityInbox: this.#entityInbox,
|
|
@@ -331,23 +615,42 @@ export class BoundedContext {
|
|
|
331
615
|
registerSystemEventSchema: (schema) => {
|
|
332
616
|
eventBusAccess.registerSchemas(this.#systemEventBus, [schema]);
|
|
333
617
|
},
|
|
334
|
-
publishAgentEvent: (event) => event
|
|
335
|
-
? this.#publisher.publishEvent(event)
|
|
336
|
-
: this.#publisher.publishRejectionEvent(event),
|
|
337
|
-
prepareSavedCommand: (command) => commandBusAccess.prepareSaved(this.#commandBus, command),
|
|
338
|
-
publishSavedCommand: (command, plan) => commandBusAccess.postSavedFollowUp(this.#commandBus, command, plan),
|
|
618
|
+
publishAgentEvent: (event) => this.#publishAgentEvent(event),
|
|
339
619
|
publishAgentSystemEvent: (event) => this.#publisher.publishSystemEvent(event),
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
this.#
|
|
620
|
+
agentReceipts: this.#agentReceipts,
|
|
621
|
+
agentSignal: this.#agentController.signal,
|
|
622
|
+
reserveAgent: (key, signal) => this.#reserveAgent(key, signal),
|
|
623
|
+
deferAgent: (message, tenantId) => {
|
|
624
|
+
this.#deferAgent(message, tenantId);
|
|
345
625
|
},
|
|
346
626
|
};
|
|
347
|
-
return registration;
|
|
348
627
|
}
|
|
628
|
+
/**
|
|
629
|
+
* Publishes an Agent event on its selected ordinary or rejection bus.
|
|
630
|
+
*/
|
|
631
|
+
#publishAgentEvent(event) {
|
|
632
|
+
return event.context?.rejection === undefined
|
|
633
|
+
? this.#publisher.publishEvent(event)
|
|
634
|
+
: this.#publisher.publishRejectionEvent(event);
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Records a deferred Agent shard for the next available capacity slot.
|
|
638
|
+
*/
|
|
639
|
+
#deferAgent(message, tenantId) {
|
|
640
|
+
this.#deliveryReadiness.defer({
|
|
641
|
+
label: message.label,
|
|
642
|
+
targetTypeUrl: message.inboxId.targetTypeUrl,
|
|
643
|
+
shard: message.shard,
|
|
644
|
+
...(tenantId === undefined ? {} : { tenantId }),
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Closes storage for repositories that were prepared but not committed, then rethrows the
|
|
649
|
+
* registration error with cleanup failures aggregated.
|
|
650
|
+
*/
|
|
349
651
|
#failRegistration(error, preparedRepositories) {
|
|
350
|
-
const
|
|
652
|
+
const uncommitted = preparedRepositories.filter(({ repository }) => !this.#repositoryViews.has(repository));
|
|
653
|
+
const closeErrors = ContextParts.closePreparedRepositories(uncommitted);
|
|
351
654
|
if (closeErrors.length > 0) {
|
|
352
655
|
throw new AggregateError([error, ...closeErrors], "Repository registration failed, and prepared repository storage cleanup also failed.");
|
|
353
656
|
}
|
|
@@ -395,6 +698,14 @@ export class BoundedContext {
|
|
|
395
698
|
get isMultitenant() {
|
|
396
699
|
return this.#snapshot.tenantMode === "multitenant";
|
|
397
700
|
}
|
|
701
|
+
/**
|
|
702
|
+
* Returns the live index configured for this Bounded Context.
|
|
703
|
+
*
|
|
704
|
+
* @returns The effective configured index.
|
|
705
|
+
*/
|
|
706
|
+
get tenantIndex() {
|
|
707
|
+
return ContextParts.requireTenantIndex(this);
|
|
708
|
+
}
|
|
398
709
|
/**
|
|
399
710
|
* Returns a copy-safe specification used to build this context.
|
|
400
711
|
*
|
|
@@ -497,6 +808,10 @@ export class BoundedContext {
|
|
|
497
808
|
async catchUpReadSide(options = {}) {
|
|
498
809
|
return eventBusAccess.runExclusive(this.#eventBus, () => this.#catchUpReadSideOnce(options));
|
|
499
810
|
}
|
|
811
|
+
/**
|
|
812
|
+
* Clears registered Projection state for the selected tenant slice, reads stored events,
|
|
813
|
+
* validates each event’s tenant, and dispatches replay sequentially.
|
|
814
|
+
*/
|
|
500
815
|
async #catchUpReadSideOnce(options) {
|
|
501
816
|
const storageContext = ContextParts.catchUpStorageContext(this.#snapshot.spec, options);
|
|
502
817
|
const tenantOptions = ContextParts.catchUpStandOptions(storageContext);
|
|
@@ -539,7 +854,9 @@ export class BoundedContext {
|
|
|
539
854
|
*/
|
|
540
855
|
#beginClose() {
|
|
541
856
|
closingContexts.add(this);
|
|
542
|
-
this.#
|
|
857
|
+
this.#preparationController.abort(new Error("Bounded Context closed."));
|
|
858
|
+
serverEnvironmentAccess.stopRecipients(this.#environment, this);
|
|
859
|
+
this.#agentController.abort(new Error("Bounded Context closed."));
|
|
543
860
|
this.#publisher.beginClose();
|
|
544
861
|
commandBusAccess.beginClose(this.#commandBus);
|
|
545
862
|
eventBusAccess.beginClose(this.#eventBus);
|
|
@@ -565,22 +882,35 @@ export class BoundedContext {
|
|
|
565
882
|
*/
|
|
566
883
|
async #closeOnce() {
|
|
567
884
|
const errors = [];
|
|
568
|
-
|
|
885
|
+
this.#stopAgentAvailability?.();
|
|
886
|
+
this.#stopAgentAvailability = undefined;
|
|
569
887
|
await this.#closePart(() => this.#drainWork(), errors);
|
|
888
|
+
await this.#drainAdmittedContextWork(errors);
|
|
889
|
+
await this.#closePart(() => this.#deliveryReadiness.close(), errors);
|
|
890
|
+
await this.#closePart(() => ContextParts.drainContextWork(this.#commandBus, this.#eventBus, this.#systemEventBus, this.#publisher), errors);
|
|
891
|
+
await this.#closePart(() => this.#agentReceipts.close(), errors);
|
|
570
892
|
await this.#closeBuses(errors);
|
|
571
893
|
await this.#closePart(() => this.#stand.close(), errors);
|
|
572
894
|
await this.#closePart(() => this.#systemStand.close(), errors);
|
|
573
895
|
await this.#closePart(() => this.#subscriptionRuntime.finishClose(), errors);
|
|
574
|
-
await this.#closePart(() => {
|
|
575
|
-
ContextParts.requireTenantIndex(this).close();
|
|
576
|
-
}, errors);
|
|
577
896
|
await this.#closeRepositories(errors);
|
|
897
|
+
serverEnvironmentAccess.removeContext(this.#environment, this);
|
|
578
898
|
if (errors.length > 0) {
|
|
579
899
|
this.#closed = undefined;
|
|
580
900
|
throw new AggregateError(ContextParts.flattenErrors(errors), "BoundedContext close failed.");
|
|
581
901
|
}
|
|
582
902
|
ContextParts.clearContextMetadata(this);
|
|
583
903
|
}
|
|
904
|
+
/**
|
|
905
|
+
* Settles entered replay, local follow-ups, and reserved Agent turns.
|
|
906
|
+
* @param errors Collects independent close failures.
|
|
907
|
+
*/
|
|
908
|
+
async #drainAdmittedContextWork(errors) {
|
|
909
|
+
await this.#closePart(() => Promise.allSettled([...this.#activePreparations]).then(() => undefined), errors);
|
|
910
|
+
await this.#closePart(() => Promise.allSettled([...this.#activeReplays]).then(() => undefined), errors);
|
|
911
|
+
await this.#closePart(() => this.#entityInbox.whenFollowUpsIdle(), errors);
|
|
912
|
+
await this.#closePart(() => Promise.allSettled([...this.#agentReservations]).then(() => undefined), errors);
|
|
913
|
+
}
|
|
584
914
|
/**
|
|
585
915
|
* Closes bus intake and subscription drains before the Stands.
|
|
586
916
|
*
|
|
@@ -602,13 +932,27 @@ export class BoundedContext {
|
|
|
602
932
|
* @param errors Failures collected during context closure.
|
|
603
933
|
*/
|
|
604
934
|
async #closeRepositories(errors) {
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
repositoryAccess.clearRuntime(repository);
|
|
935
|
+
const closing = [...this.#repositoryViews].map((repository) => {
|
|
936
|
+
return repositoryAccess.clearRuntime(repository).finally(() => {
|
|
608
937
|
registeredRepositories.delete(repository);
|
|
609
|
-
}
|
|
938
|
+
});
|
|
939
|
+
});
|
|
940
|
+
for (const result of await Promise.allSettled(closing)) {
|
|
941
|
+
if (result.status === "rejected")
|
|
942
|
+
errors.push(result.reason);
|
|
610
943
|
}
|
|
611
944
|
}
|
|
945
|
+
/**
|
|
946
|
+
* Registers an entered shared replay through its complete follow-up callback.
|
|
947
|
+
* @param onReplay Complete replay callback.
|
|
948
|
+
* @returns Completion of the captured callback.
|
|
949
|
+
*/
|
|
950
|
+
#trackReplay(onReplay) {
|
|
951
|
+
const replay = Promise.resolve().then(onReplay);
|
|
952
|
+
this.#activeReplays.add(replay);
|
|
953
|
+
void replay.then(() => this.#activeReplays.delete(replay), () => this.#activeReplays.delete(replay));
|
|
954
|
+
return replay;
|
|
955
|
+
}
|
|
612
956
|
/**
|
|
613
957
|
* Calls a close action and collects its failure before later cleanup.
|
|
614
958
|
* @param work Close action.
|
|
@@ -622,6 +966,73 @@ export class BoundedContext {
|
|
|
622
966
|
* Exposes framework-only operations for built contexts and their builders.
|
|
623
967
|
*/
|
|
624
968
|
export const boundedContextAccess = Object.freeze({
|
|
969
|
+
/**
|
|
970
|
+
* Reports whether close has started for the given context.
|
|
971
|
+
*
|
|
972
|
+
* @param context Completed context.
|
|
973
|
+
* @returns Whether close has started.
|
|
974
|
+
*/
|
|
975
|
+
isClosing: (context) => closingContexts.has(context),
|
|
976
|
+
/**
|
|
977
|
+
* Prepares an admitted context without joining global fatal cleanup.
|
|
978
|
+
*
|
|
979
|
+
* @param context Admitted context.
|
|
980
|
+
* @param mode Creation or read-only check.
|
|
981
|
+
* @param ids Captured tenant identities.
|
|
982
|
+
* @returns Completion of local preparation.
|
|
983
|
+
*/
|
|
984
|
+
prepareExplicit(context, mode, ids) {
|
|
985
|
+
const prepare = explicitPreparations.get(context);
|
|
986
|
+
if (prepare === undefined)
|
|
987
|
+
return Promise.reject(new TypeError("Preparation requires a built context."));
|
|
988
|
+
return prepare(mode, ids);
|
|
989
|
+
},
|
|
990
|
+
/**
|
|
991
|
+
* Admits a service membership and preparation chain before its first await.
|
|
992
|
+
*
|
|
993
|
+
* @param context Selected built context.
|
|
994
|
+
* @param tenantId Captured tenant identity, when applicable.
|
|
995
|
+
* @param onMembership Checks live tenant membership.
|
|
996
|
+
* @param onReady Performs the admitted one-shot registry mutation, if present.
|
|
997
|
+
* @returns Tracked completion of membership and model preparation.
|
|
998
|
+
*/
|
|
999
|
+
admitServiceInput(context, tenantId, onMembership, onReady) {
|
|
1000
|
+
if (closingContexts.has(context))
|
|
1001
|
+
return Promise.reject(new Error("Bounded Context is closing."));
|
|
1002
|
+
const prepare = contextInputPreparations.get(context);
|
|
1003
|
+
const phase = contextClosePhases.get(context);
|
|
1004
|
+
if (prepare === undefined || phase === undefined)
|
|
1005
|
+
return Promise.reject(new TypeError("Service input requires a built context."));
|
|
1006
|
+
return phase.trackReplay(async () => {
|
|
1007
|
+
await onMembership();
|
|
1008
|
+
await prepare(tenantId);
|
|
1009
|
+
await onReady?.();
|
|
1010
|
+
});
|
|
1011
|
+
},
|
|
1012
|
+
/**
|
|
1013
|
+
* Admits one shared subscription-registry operation before asynchronous lookup.
|
|
1014
|
+
*
|
|
1015
|
+
* @param context Selected built context.
|
|
1016
|
+
* @param onOperation Complete registry operation.
|
|
1017
|
+
* @returns Tracked operation completion.
|
|
1018
|
+
*/
|
|
1019
|
+
admitServiceRegistryOperation(context, onOperation) {
|
|
1020
|
+
if (closingContexts.has(context))
|
|
1021
|
+
return Promise.reject(new Error("Bounded Context is closing."));
|
|
1022
|
+
const phase = contextClosePhases.get(context);
|
|
1023
|
+
if (phase === undefined)
|
|
1024
|
+
return Promise.reject(new TypeError("Service registry requires a built context."));
|
|
1025
|
+
return phase.trackReplay(onOperation);
|
|
1026
|
+
},
|
|
1027
|
+
/**
|
|
1028
|
+
* Registers replay work with the context close lifecycle so shutdown waits for it.
|
|
1029
|
+
*/
|
|
1030
|
+
trackReplay(context, onReplay) {
|
|
1031
|
+
const phase = contextClosePhases.get(context);
|
|
1032
|
+
if (phase === undefined)
|
|
1033
|
+
return Promise.reject(new TypeError("Replay requires a built context."));
|
|
1034
|
+
return phase.trackReplay(onReplay);
|
|
1035
|
+
},
|
|
625
1036
|
/**
|
|
626
1037
|
* Resolves a view only within the Bounded Context that issued it.
|
|
627
1038
|
*
|
|
@@ -638,18 +1049,27 @@ export const boundedContextAccess = Object.freeze({
|
|
|
638
1049
|
? issued.repository
|
|
639
1050
|
: undefined;
|
|
640
1051
|
},
|
|
1052
|
+
/**
|
|
1053
|
+
* Stops new work at this Bounded Context and begins shutdown of its runtime facilities.
|
|
1054
|
+
*/
|
|
641
1055
|
beginClose(context) {
|
|
642
1056
|
const phase = contextClosePhases.get(context);
|
|
643
1057
|
if (phase === undefined)
|
|
644
1058
|
throw new TypeError("Close phase requires a built BoundedContext.");
|
|
645
1059
|
phase.begin();
|
|
646
1060
|
},
|
|
1061
|
+
/**
|
|
1062
|
+
* Waits for accepted runtime work and replay operations to finish.
|
|
1063
|
+
*/
|
|
647
1064
|
drainWork(context) {
|
|
648
1065
|
const phase = contextClosePhases.get(context);
|
|
649
1066
|
if (phase === undefined)
|
|
650
1067
|
return Promise.reject(new TypeError("Drain requires a built BoundedContext."));
|
|
651
1068
|
return phase.drain();
|
|
652
1069
|
},
|
|
1070
|
+
/**
|
|
1071
|
+
* Installs the context logger on both EventBuses, the signal publisher, and subscription runtime.
|
|
1072
|
+
*/
|
|
653
1073
|
installLogger(context, logger) {
|
|
654
1074
|
if (!contextStorageFactories.has(context)) {
|
|
655
1075
|
throw new TypeError("Context logger requires a built BoundedContext instance.");
|
|
@@ -669,8 +1089,11 @@ export const boundedContextAccess = Object.freeze({
|
|
|
669
1089
|
if (runtime === undefined) {
|
|
670
1090
|
throw new TypeError("Context logger requires a built BoundedContext instance.");
|
|
671
1091
|
}
|
|
672
|
-
subscriptionRuntimeAccess.installLogger(runtime, logger);
|
|
1092
|
+
subscriptionRuntimeAccess.installLogger(runtime, logger, context.name.value);
|
|
673
1093
|
},
|
|
1094
|
+
/**
|
|
1095
|
+
* Returns the diagnostic logger installed on this Bounded Context.
|
|
1096
|
+
*/
|
|
674
1097
|
loggerFor(context) {
|
|
675
1098
|
const logger = contextLoggers.get(context);
|
|
676
1099
|
if (logger === undefined) {
|
|
@@ -678,18 +1101,27 @@ export const boundedContextAccess = Object.freeze({
|
|
|
678
1101
|
}
|
|
679
1102
|
return logger;
|
|
680
1103
|
},
|
|
1104
|
+
/**
|
|
1105
|
+
* Checks whether a value is a BoundedContextBuilder created by this module.
|
|
1106
|
+
*/
|
|
681
1107
|
isBuilder(value) {
|
|
682
1108
|
return (typeof value === "object" &&
|
|
683
1109
|
value !== null &&
|
|
684
1110
|
builderBuilds.has(value));
|
|
685
1111
|
},
|
|
686
|
-
|
|
1112
|
+
/**
|
|
1113
|
+
* Builds the supplied context builder and completes its configured initialization.
|
|
1114
|
+
*/
|
|
1115
|
+
build(builder) {
|
|
687
1116
|
const build = builderBuilds.get(builder);
|
|
688
1117
|
if (build === undefined) {
|
|
689
1118
|
throw new TypeError("Builder access requires a BoundedContextBuilder instance.");
|
|
690
1119
|
}
|
|
691
|
-
return build(
|
|
1120
|
+
return build();
|
|
692
1121
|
},
|
|
1122
|
+
/**
|
|
1123
|
+
* Attaches a subscriber to the context domain EventBus for the requested event type.
|
|
1124
|
+
*/
|
|
693
1125
|
subscribeToEvent(context, typeUrl, subscriber) {
|
|
694
1126
|
const subscribe = eventSubscribers.get(context);
|
|
695
1127
|
if (subscribe === undefined) {
|
|
@@ -697,6 +1129,9 @@ export const boundedContextAccess = Object.freeze({
|
|
|
697
1129
|
}
|
|
698
1130
|
return subscribe(typeUrl, subscriber);
|
|
699
1131
|
},
|
|
1132
|
+
/**
|
|
1133
|
+
* Posts a framework System Event through the context System EventBus.
|
|
1134
|
+
*/
|
|
700
1135
|
postSystemEvent(context, event) {
|
|
701
1136
|
const post = systemEventPosters.get(context);
|
|
702
1137
|
if (post === undefined) {
|
|
@@ -704,12 +1139,18 @@ export const boundedContextAccess = Object.freeze({
|
|
|
704
1139
|
}
|
|
705
1140
|
return post(event);
|
|
706
1141
|
},
|
|
1142
|
+
/**
|
|
1143
|
+
* Posts an external domain Event through the context EventBus.
|
|
1144
|
+
*/
|
|
707
1145
|
postExternalEvent(context, event) {
|
|
708
1146
|
const imported = clone(EventSchema, event);
|
|
709
1147
|
imported.context = clone(EventContextSchema, imported.context ?? create(EventContextSchema));
|
|
710
1148
|
imported.context.external = true;
|
|
711
1149
|
return context.eventBus().post(imported);
|
|
712
1150
|
},
|
|
1151
|
+
/**
|
|
1152
|
+
* Subscribes to events produced by this context and forwards matching signals to the supplied observer.
|
|
1153
|
+
*/
|
|
713
1154
|
observeProducedSignals(context, observer) {
|
|
714
1155
|
const publisher = contextSignalPublishers.get(context);
|
|
715
1156
|
if (publisher === undefined) {
|
|
@@ -717,12 +1158,21 @@ export const boundedContextAccess = Object.freeze({
|
|
|
717
1158
|
}
|
|
718
1159
|
return publisher.observe(observer);
|
|
719
1160
|
},
|
|
1161
|
+
/**
|
|
1162
|
+
* Returns the domain/System specification pairing captured when the context was built.
|
|
1163
|
+
*/
|
|
720
1164
|
systemPairing(context) {
|
|
721
1165
|
return ContextParts.cloneSystemPairing(ContextParts.requireSystemPairing(context));
|
|
722
1166
|
},
|
|
1167
|
+
/**
|
|
1168
|
+
* Returns the live tenant membership index configured for this context.
|
|
1169
|
+
*/
|
|
723
1170
|
tenantIndex(context) {
|
|
724
1171
|
return ContextParts.requireTenantIndex(context);
|
|
725
1172
|
},
|
|
1173
|
+
/**
|
|
1174
|
+
* Returns the storage factory captured for repository and Stand operations in this context.
|
|
1175
|
+
*/
|
|
726
1176
|
storageFactory(context) {
|
|
727
1177
|
const storageFactory = contextStorageFactories.get(context);
|
|
728
1178
|
if (storageFactory === undefined) {
|
|
@@ -730,6 +1180,9 @@ export const boundedContextAccess = Object.freeze({
|
|
|
730
1180
|
}
|
|
731
1181
|
return storageFactory;
|
|
732
1182
|
},
|
|
1183
|
+
/**
|
|
1184
|
+
* Returns the persistent or in-memory registry used to store Stand subscription definitions.
|
|
1185
|
+
*/
|
|
733
1186
|
subscriptionRegistry(context) {
|
|
734
1187
|
const runtime = contextSubscriptionRuntimes.get(context);
|
|
735
1188
|
if (runtime === undefined) {
|
|
@@ -737,13 +1190,24 @@ export const boundedContextAccess = Object.freeze({
|
|
|
737
1190
|
}
|
|
738
1191
|
return runtime.registry();
|
|
739
1192
|
},
|
|
740
|
-
|
|
1193
|
+
/**
|
|
1194
|
+
* Consumes a registered Stand subscription update and returns its managed delivery handle.
|
|
1195
|
+
*/
|
|
1196
|
+
consumeSubscription(context, id, onUpdate, onInvalidated, expected) {
|
|
741
1197
|
const runtime = contextSubscriptionRuntimes.get(context);
|
|
742
1198
|
if (runtime === undefined) {
|
|
743
1199
|
throw new TypeError("Subscription consumption requires a built BoundedContext instance.");
|
|
744
1200
|
}
|
|
745
|
-
return runtime.consume(id,
|
|
1201
|
+
return runtime.consume(id, (update) => {
|
|
1202
|
+
onUpdate({
|
|
1203
|
+
update,
|
|
1204
|
+
filter: (facts) => standAccess.filterSubscription(context.stand(), update, facts),
|
|
1205
|
+
});
|
|
1206
|
+
}, onInvalidated, expected);
|
|
746
1207
|
},
|
|
1208
|
+
/**
|
|
1209
|
+
* Returns the context delivery descriptor used to route Entity and Projection inbox work.
|
|
1210
|
+
*/
|
|
747
1211
|
delivery(context) {
|
|
748
1212
|
const descriptor = contextDeliveryDescriptors.get(context);
|
|
749
1213
|
if (descriptor === undefined) {
|
|
@@ -764,13 +1228,12 @@ export class BoundedContextBuilder {
|
|
|
764
1228
|
#eventReceivers = [];
|
|
765
1229
|
#repositories = new Set();
|
|
766
1230
|
#entityTypes = new Set();
|
|
1231
|
+
#readFilters = new Set();
|
|
767
1232
|
#generatedRepositoryOptions = new Map();
|
|
768
|
-
#deliveryStrategy = UniformAcrossAllShards.singleShard();
|
|
769
|
-
#storageFactory;
|
|
770
1233
|
#aiRegistry;
|
|
1234
|
+
#tenantIndex;
|
|
771
1235
|
#subscriptionRegistry;
|
|
772
1236
|
#persistSystemEvents = false;
|
|
773
|
-
#generatedRegistryRoot;
|
|
774
1237
|
/**
|
|
775
1238
|
* Registers the framework-only construction hook for this module.
|
|
776
1239
|
*/
|
|
@@ -786,7 +1249,7 @@ export class BoundedContextBuilder {
|
|
|
786
1249
|
constructor(specSnapshot, token) {
|
|
787
1250
|
ContextParts.requireFrameworkConstructionToken(token, "BoundedContextBuilder instances are framework-owned.");
|
|
788
1251
|
this.#specSnapshot = ContextParts.cloneSpecSnapshot(specSnapshot);
|
|
789
|
-
builderBuilds.set(this, (
|
|
1252
|
+
builderBuilds.set(this, () => this.#buildAsyncWith());
|
|
790
1253
|
Object.freeze(this);
|
|
791
1254
|
}
|
|
792
1255
|
/**
|
|
@@ -821,6 +1284,15 @@ export class BoundedContextBuilder {
|
|
|
821
1284
|
isMultitenant() {
|
|
822
1285
|
return this.#specSnapshot.multitenant;
|
|
823
1286
|
}
|
|
1287
|
+
/**
|
|
1288
|
+
* Adds an outward read policy to this Bounded Context.
|
|
1289
|
+
* @param filter Policy applied before Server-level outward policies.
|
|
1290
|
+
* @returns This builder for further configuration.
|
|
1291
|
+
*/
|
|
1292
|
+
addReadSideFilter(filter) {
|
|
1293
|
+
this.#readFilters.add(filter);
|
|
1294
|
+
return this;
|
|
1295
|
+
}
|
|
824
1296
|
/**
|
|
825
1297
|
* Adds an explicitly assembled repository or an Entity class.
|
|
826
1298
|
*
|
|
@@ -861,7 +1333,7 @@ export class BoundedContextBuilder {
|
|
|
861
1333
|
*
|
|
862
1334
|
* @param dispatcher Raw command dispatcher, or an `AbstractCommander` instance.
|
|
863
1335
|
* A standalone commander requires generated receiver metadata and therefore
|
|
864
|
-
* this builder's `
|
|
1336
|
+
* this builder's `build()` path. It installs its Command and Event sides
|
|
865
1337
|
* exactly once.
|
|
866
1338
|
*
|
|
867
1339
|
* @returns This builder for further configuration.
|
|
@@ -888,7 +1360,7 @@ export class BoundedContextBuilder {
|
|
|
888
1360
|
* Adds an event dispatcher to the context being built.
|
|
889
1361
|
*
|
|
890
1362
|
* @param dispatcher Raw event dispatcher, or a standalone reactor/subscriber.
|
|
891
|
-
* Standalone receivers require generated receiver metadata and `
|
|
1363
|
+
* Standalone receivers require generated receiver metadata and `build()`.
|
|
892
1364
|
*
|
|
893
1365
|
* @returns This builder for further configuration.
|
|
894
1366
|
*/
|
|
@@ -905,7 +1377,7 @@ export class BoundedContextBuilder {
|
|
|
905
1377
|
* Adds a generated standalone command assignee.
|
|
906
1378
|
*
|
|
907
1379
|
* The registered instance is matched by exact constructor to generated
|
|
908
|
-
* receiver metadata when `
|
|
1380
|
+
* receiver metadata when `build()` assembles the context.
|
|
909
1381
|
*
|
|
910
1382
|
* @param assignee Generated standalone assignee instance.
|
|
911
1383
|
* @returns This builder for further configuration.
|
|
@@ -925,13 +1397,13 @@ export class BoundedContextBuilder {
|
|
|
925
1397
|
return this;
|
|
926
1398
|
}
|
|
927
1399
|
/**
|
|
928
|
-
* Sets a
|
|
1400
|
+
* Sets a live tenant index for this Bounded Context.
|
|
929
1401
|
*
|
|
930
|
-
* @param
|
|
931
|
-
* @returns
|
|
1402
|
+
* @param tenantIndex Application membership index.
|
|
1403
|
+
* @returns This builder.
|
|
932
1404
|
*/
|
|
933
|
-
|
|
934
|
-
this.#
|
|
1405
|
+
withTenantIndex(tenantIndex) {
|
|
1406
|
+
this.#tenantIndex = tenantIndex;
|
|
935
1407
|
return this;
|
|
936
1408
|
}
|
|
937
1409
|
/**
|
|
@@ -970,69 +1442,112 @@ export class BoundedContextBuilder {
|
|
|
970
1442
|
return this;
|
|
971
1443
|
}
|
|
972
1444
|
/**
|
|
973
|
-
*
|
|
1445
|
+
* Builds a context after loading generated handlers and preparing runtime resources.
|
|
974
1446
|
*
|
|
975
|
-
* @param
|
|
976
|
-
* @returns
|
|
1447
|
+
* @param options Independent eager structure and subscription timing choices.
|
|
1448
|
+
* @returns The built context after requested initialization and integration are ready.
|
|
977
1449
|
*/
|
|
978
|
-
|
|
979
|
-
this.#
|
|
980
|
-
return this;
|
|
1450
|
+
build(options = {}) {
|
|
1451
|
+
return this.#buildAsyncWith(options);
|
|
981
1452
|
}
|
|
982
1453
|
/**
|
|
983
|
-
*
|
|
984
|
-
*
|
|
985
|
-
* @param root Names the compiled package or application root.
|
|
986
|
-
* @returns Returns this builder for further configuration.
|
|
1454
|
+
* Loads generated handlers and repositories, assembles context facilities, and completes
|
|
1455
|
+
* requested initialization before publishing the context.
|
|
987
1456
|
*/
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
1457
|
+
async #buildAsyncWith(options = {}) {
|
|
1458
|
+
const environment = ServerEnvironment.instance();
|
|
1459
|
+
const finishBuild = serverEnvironmentAccess.beginContextBuild(environment);
|
|
1460
|
+
let built;
|
|
1461
|
+
let failed = false;
|
|
1462
|
+
let failure;
|
|
1463
|
+
try {
|
|
1464
|
+
await serverEnvironmentAccess.awaitSetup(environment);
|
|
1465
|
+
built = await this.#buildAsyncReady(options, environment);
|
|
1466
|
+
}
|
|
1467
|
+
catch (error) {
|
|
1468
|
+
failed = true;
|
|
1469
|
+
failure = error;
|
|
1470
|
+
}
|
|
1471
|
+
finally {
|
|
1472
|
+
finishBuild();
|
|
1473
|
+
}
|
|
1474
|
+
if (serverEnvironmentAccess.hasStartupFailure(environment))
|
|
1475
|
+
return serverEnvironmentAccess.awaitStartupFailure(environment);
|
|
1476
|
+
if (failed)
|
|
1477
|
+
throw failure;
|
|
1478
|
+
if (built === undefined)
|
|
1479
|
+
throw new Error("Bounded Context build did not complete.");
|
|
1480
|
+
return built;
|
|
991
1481
|
}
|
|
992
1482
|
/**
|
|
993
|
-
*
|
|
1483
|
+
* Loads generated metadata and finishes one asynchronous context assembly.
|
|
994
1484
|
*
|
|
995
|
-
* @returns
|
|
1485
|
+
* @returns The integrated context.
|
|
996
1486
|
*/
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1487
|
+
async #buildAsyncReady(options, environment) {
|
|
1488
|
+
const generated = this.#loadGeneratedArtifacts([...this.#entityTypes]);
|
|
1489
|
+
serverEnvironmentAccess.requireAvailable(environment);
|
|
1490
|
+
const repositories = [...this.#repositories, ...generated.repositories];
|
|
1491
|
+
const context = await this.#buildWithResources(repositories, environment.storageFactory, generated.standalone, this.#aiRegistry ?? environment.ai);
|
|
1492
|
+
try {
|
|
1493
|
+
await this.#integrateBuiltContext(context, options, environment);
|
|
1494
|
+
}
|
|
1495
|
+
catch (error) {
|
|
1496
|
+
return this.#closeFailedBuild(context, environment, error);
|
|
1000
1497
|
}
|
|
1001
|
-
|
|
1002
|
-
return this.#buildWith([...this.#repositories], this.#storageFactory ?? new InMemoryStorageFactory(), [], this.#aiRegistry);
|
|
1498
|
+
return context;
|
|
1003
1499
|
}
|
|
1004
1500
|
/**
|
|
1005
|
-
*
|
|
1006
|
-
*
|
|
1007
|
-
*
|
|
1008
|
-
*
|
|
1009
|
-
* asynchronous path so their exact constructors can be matched to generated
|
|
1010
|
-
* receiver metadata.
|
|
1011
|
-
*
|
|
1012
|
-
* @returns Resolves to the built context.
|
|
1501
|
+
* Completes required integration and requested initialization before publication.
|
|
1502
|
+
* @param context Constructed context.
|
|
1503
|
+
* @param options Requested eager initialization phases.
|
|
1504
|
+
* @param environment Captured environment lifetime.
|
|
1013
1505
|
*/
|
|
1014
|
-
async
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1506
|
+
async #integrateBuiltContext(context, options, environment) {
|
|
1507
|
+
serverEnvironmentAccess.requireAvailable(environment);
|
|
1508
|
+
boundedContextAccess.installLogger(context, serverEnvironmentAccess.loggerFor(environment));
|
|
1509
|
+
await ContextParts.integrationReady(context).catch((error) => {
|
|
1510
|
+
serverEnvironmentAccess.failStartup(environment, error);
|
|
1511
|
+
throw error;
|
|
1512
|
+
});
|
|
1513
|
+
serverEnvironmentAccess.requireAvailable(environment);
|
|
1514
|
+
const prepare = contextPreparations.get(context);
|
|
1515
|
+
if (prepare === undefined)
|
|
1516
|
+
throw new Error("Context preparation is unavailable.");
|
|
1517
|
+
await prepare(options);
|
|
1518
|
+
serverEnvironmentAccess.requireAvailable(environment);
|
|
1519
|
+
await serverEnvironmentAccess.completeContext(environment, context);
|
|
1520
|
+
}
|
|
1521
|
+
/**
|
|
1522
|
+
* Releases a failed partial context before its build ticket settles.
|
|
1523
|
+
* @param context Context assembled before the failure.
|
|
1524
|
+
* @param environment Captured environment lifetime.
|
|
1525
|
+
* @param error Original build failure.
|
|
1526
|
+
* @returns Rejection retaining the original and any local cleanup failure.
|
|
1527
|
+
*/
|
|
1528
|
+
async #closeFailedBuild(context, environment, error) {
|
|
1021
1529
|
try {
|
|
1022
|
-
await
|
|
1530
|
+
await context.close();
|
|
1023
1531
|
}
|
|
1024
|
-
catch (
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
catch (closeError) {
|
|
1029
|
-
throw new AggregateError([error, closeError], "BoundedContext broker open failed during cleanup.");
|
|
1532
|
+
catch (closeError) {
|
|
1533
|
+
if (serverEnvironmentAccess.hasStartupFailure(environment)) {
|
|
1534
|
+
serverEnvironmentAccess.recordStartupCleanup(environment, closeError);
|
|
1535
|
+
throw error;
|
|
1030
1536
|
}
|
|
1031
|
-
throw error;
|
|
1537
|
+
throw new AggregateError([error, closeError], "BoundedContext open cleanup failed.");
|
|
1032
1538
|
}
|
|
1033
|
-
|
|
1539
|
+
throw error;
|
|
1034
1540
|
}
|
|
1035
|
-
|
|
1541
|
+
/**
|
|
1542
|
+
* Assembles a context and cleans partial resources on failure.
|
|
1543
|
+
*
|
|
1544
|
+
* @param repositories Repositories selected for the context.
|
|
1545
|
+
* @param storageFactory Storage factory for the context.
|
|
1546
|
+
* @param standalone Generated standalone handler groups.
|
|
1547
|
+
* @param ai Optional AI registry.
|
|
1548
|
+
* @returns The assembled context.
|
|
1549
|
+
*/
|
|
1550
|
+
async #buildWithResources(repositories, storageFactory, standalone, ai) {
|
|
1036
1551
|
const resources = this.#subscriptionRegistry === undefined ? {} : { registry: this.#subscriptionRegistry };
|
|
1037
1552
|
this.#subscriptionRegistry = undefined;
|
|
1038
1553
|
const registeredRepositories = [...repositories];
|
|
@@ -1040,9 +1555,13 @@ export class BoundedContextBuilder {
|
|
|
1040
1555
|
return this.#assembleContext(registeredRepositories, storageFactory, standalone, resources, ai);
|
|
1041
1556
|
}
|
|
1042
1557
|
catch (error) {
|
|
1043
|
-
return this.#failBuild(resources, error);
|
|
1558
|
+
return await this.#failBuild(resources, error);
|
|
1044
1559
|
}
|
|
1045
1560
|
}
|
|
1561
|
+
/**
|
|
1562
|
+
* Assembles dispatchers, buses, Stand, subscription runtime, and integration hooks, registering
|
|
1563
|
+
* the completed context with its environment.
|
|
1564
|
+
*/
|
|
1046
1565
|
#assembleContext(repositories, storageFactory, standalone, resources, ai) {
|
|
1047
1566
|
ContextParts.preflightRepositories(repositories);
|
|
1048
1567
|
this.#preflightAgents(repositories, storageFactory, ai);
|
|
@@ -1059,6 +1578,10 @@ export class BoundedContextBuilder {
|
|
|
1059
1578
|
]);
|
|
1060
1579
|
const running = this.#buildRuntime(storageFactory, buses, resources);
|
|
1061
1580
|
const context = this.#createContext(repositories, storageFactory, buses, running, ai);
|
|
1581
|
+
resources.context = context;
|
|
1582
|
+
if (registerBoundedContext === undefined)
|
|
1583
|
+
throw new Error("Context registration is unavailable.");
|
|
1584
|
+
registerBoundedContext(context, repositories);
|
|
1062
1585
|
ContextParts.attachIntegration(context, buses.eventBus, buses.systemSpec, ContextParts.externalEventSchemas([
|
|
1063
1586
|
...dispatchers.domain,
|
|
1064
1587
|
...(standaloneEvent === undefined ? [] : [standaloneEvent]),
|
|
@@ -1083,13 +1606,8 @@ export class BoundedContextBuilder {
|
|
|
1083
1606
|
throw new TypeError("Agent registration requires persisted System Events.");
|
|
1084
1607
|
if (!AgentHistoryStorageFactories.supports(storageFactory))
|
|
1085
1608
|
throw new TypeError("Agent registration requires indexed history storage.");
|
|
1086
|
-
if (!AgentExecutionStorageFactories.supports(storageFactory))
|
|
1087
|
-
throw new TypeError("Agent registration requires durable execution storage.");
|
|
1088
|
-
this.#checkAgentOutputBindings(agents);
|
|
1089
1609
|
for (const repository of agents) {
|
|
1090
1610
|
const config = repositoryAccess.agentConfiguration(repository);
|
|
1091
|
-
if (config.codeRevision === undefined || config.codeRevision.trim() === "")
|
|
1092
|
-
throw new TypeError("Agent registration requires a code revision.");
|
|
1093
1611
|
if (config.ai === undefined)
|
|
1094
1612
|
throw new TypeError("Agent registration requires repository capabilities.");
|
|
1095
1613
|
if (config.ai.models.some((model) => !isAiModel(model)))
|
|
@@ -1097,17 +1615,8 @@ export class BoundedContextBuilder {
|
|
|
1097
1615
|
}
|
|
1098
1616
|
}
|
|
1099
1617
|
/**
|
|
1100
|
-
*
|
|
1618
|
+
* Collects repository and configured event recipients into domain and System dispatcher groups.
|
|
1101
1619
|
*/
|
|
1102
|
-
#checkAgentOutputBindings(agents) {
|
|
1103
|
-
const rawCommands = new Set([...this.#commandDispatchers].flatMap((dispatcher) => dispatcher.messageSchemas().map((schema) => TypeUrls.derive(schema))));
|
|
1104
|
-
for (const repository of agents) {
|
|
1105
|
-
if (repositoryAccess
|
|
1106
|
-
.producedCommandSchemas(repository)
|
|
1107
|
-
.some((schema) => rawCommands.has(TypeUrls.derive(schema))))
|
|
1108
|
-
throw new TypeError("Agent Command output requires a durable dispatcher binding.");
|
|
1109
|
-
}
|
|
1110
|
-
}
|
|
1111
1620
|
#buildDispatchers(repositories) {
|
|
1112
1621
|
const event = [
|
|
1113
1622
|
...ContextParts.repositoryEventDispatchers(repositories),
|
|
@@ -1121,6 +1630,9 @@ export class BoundedContextBuilder {
|
|
|
1121
1630
|
],
|
|
1122
1631
|
};
|
|
1123
1632
|
}
|
|
1633
|
+
/**
|
|
1634
|
+
* Creates command, domain Event, and System Event buses plus their stores and signal publisher.
|
|
1635
|
+
*/
|
|
1124
1636
|
#buildBuses(repositories, storageFactory, dispatchers, resources) {
|
|
1125
1637
|
const commandBus = new CommandBus([
|
|
1126
1638
|
...this.#commandDispatchers,
|
|
@@ -1145,10 +1657,13 @@ export class BoundedContextBuilder {
|
|
|
1145
1657
|
systemSpec: system.spec,
|
|
1146
1658
|
};
|
|
1147
1659
|
}
|
|
1660
|
+
/**
|
|
1661
|
+
* Creates the System context store, EventBus, and Stand, then registers System event dispatchers.
|
|
1662
|
+
*/
|
|
1148
1663
|
#buildSystemBuses(storageFactory, dispatchers, resources) {
|
|
1149
1664
|
const systemSpec = ContextParts.createSystemSpec(this.#specSnapshot, this.#persistSystemEvents);
|
|
1150
1665
|
const systemEventStore = systemSpec.storesEvents
|
|
1151
|
-
? new EventStore(ContextParts.createStorageMode(systemSpec), storageFactory,
|
|
1666
|
+
? new EventStore(ContextParts.createStorageMode(systemSpec), storageFactory, EventStoreGroups.forContext(systemSpec.name.value, "system"))
|
|
1152
1667
|
: undefined;
|
|
1153
1668
|
if (systemEventStore !== undefined)
|
|
1154
1669
|
resources.systemEventStore = systemEventStore;
|
|
@@ -1168,6 +1683,10 @@ export class BoundedContextBuilder {
|
|
|
1168
1683
|
spec: systemSpec,
|
|
1169
1684
|
};
|
|
1170
1685
|
}
|
|
1686
|
+
/**
|
|
1687
|
+
* Matches generated standalone handlers to configured instances and registers their dispatchers
|
|
1688
|
+
* on the appropriate buses.
|
|
1689
|
+
*/
|
|
1171
1690
|
#installStandalone(standalone, buses) {
|
|
1172
1691
|
ContextParts.assertUniqueCommandReceptors(standalone);
|
|
1173
1692
|
const runtime = standalone.length === 0
|
|
@@ -1184,59 +1703,98 @@ export class BoundedContextBuilder {
|
|
|
1184
1703
|
buses.systemEventBus.register(state);
|
|
1185
1704
|
return event;
|
|
1186
1705
|
}
|
|
1706
|
+
/**
|
|
1707
|
+
* Creates the context Stand, subscription registry, and runtime that observes domain and System events.
|
|
1708
|
+
*/
|
|
1187
1709
|
#buildRuntime(storageFactory, buses, resources) {
|
|
1188
|
-
const stand =
|
|
1189
|
-
context: ContextParts.createStorageMode(this.#specSnapshot),
|
|
1190
|
-
storageFactory,
|
|
1191
|
-
});
|
|
1710
|
+
const stand = this.#buildStand(storageFactory);
|
|
1192
1711
|
resources.stand = stand;
|
|
1193
1712
|
const registry = resources.registry ??
|
|
1194
|
-
new StorageSubscriptionRegistry(ContextParts.createSubscriptionStorageContext(this.#specSnapshot), storageFactory);
|
|
1713
|
+
new StorageSubscriptionRegistry(ContextParts.createSubscriptionStorageContext(this.#specSnapshot), storageFactory, subscriptionRecordGroup(this.#specSnapshot.name.value));
|
|
1195
1714
|
resources.registry = registry;
|
|
1196
1715
|
const runtime = new SubscriptionRuntime(stand, buses.systemStand, buses.eventBus, buses.systemEventBus, registry);
|
|
1197
1716
|
resources.runtime = runtime;
|
|
1198
1717
|
return { stand, registry, runtime };
|
|
1199
1718
|
}
|
|
1719
|
+
/**
|
|
1720
|
+
* Builds the context Stand with a captured live membership directory.
|
|
1721
|
+
*
|
|
1722
|
+
* @param storageFactory Provider used for Entity records.
|
|
1723
|
+
* @returns Direct read-side Stand.
|
|
1724
|
+
*/
|
|
1725
|
+
#buildStand(storageFactory) {
|
|
1726
|
+
const tenantIndex = this.#tenantIndex ?? ServerEnvironment.instance().tenantIndex;
|
|
1727
|
+
const stand = new Stand({
|
|
1728
|
+
context: ContextParts.createStorageMode(this.#specSnapshot),
|
|
1729
|
+
storageFactory,
|
|
1730
|
+
});
|
|
1731
|
+
if (this.#readFilters.size > 0)
|
|
1732
|
+
standAccess.installReadFilters(stand, [...this.#readFilters]);
|
|
1733
|
+
standAccess.setAdmission(stand, (tenantId) => TenantAdmissions.require(tenantIndex, this.#specSnapshot.name.value, this.#specSnapshot.multitenant, tenantId));
|
|
1734
|
+
return stand;
|
|
1735
|
+
}
|
|
1736
|
+
/**
|
|
1737
|
+
* Constructs the immutable BoundedContext using assembled buses, runtime parts, environment
|
|
1738
|
+
* delivery strategy, and tenant index.
|
|
1739
|
+
*/
|
|
1200
1740
|
#createContext(repositories, storageFactory, buses, running, ai) {
|
|
1201
|
-
return ContextParts.createBoundedContext(this.#specSnapshot, storageFactory, repositories,
|
|
1741
|
+
return ContextParts.createBoundedContext(this.#specSnapshot, storageFactory, repositories, ServerEnvironment.instance().delivery.strategy, buses, running, ai, this.#tenantIndex ?? ServerEnvironment.instance().tenantIndex);
|
|
1202
1742
|
}
|
|
1203
|
-
|
|
1743
|
+
/**
|
|
1744
|
+
* Closes partial context resources after assembly fails and aggregates cleanup errors with the original failure.
|
|
1745
|
+
*/
|
|
1746
|
+
async #failBuild(resources, error) {
|
|
1204
1747
|
const cleanupErrors = [];
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1748
|
+
if (resources.context !== undefined) {
|
|
1749
|
+
try {
|
|
1750
|
+
await resources.context.close();
|
|
1751
|
+
}
|
|
1752
|
+
catch (closeError) {
|
|
1753
|
+
cleanupErrors.push(closeError);
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
else {
|
|
1757
|
+
ContextParts.attemptCleanup(() => resources.publisher?.abortAssembly(), cleanupErrors);
|
|
1758
|
+
const registry = resources.runtime ?? resources.registry;
|
|
1759
|
+
const closing = [registry, resources.stand, resources.systemStand];
|
|
1760
|
+
for (const resource of closing) {
|
|
1761
|
+
if (resource === undefined)
|
|
1762
|
+
continue;
|
|
1763
|
+
try {
|
|
1764
|
+
await resource.close();
|
|
1765
|
+
}
|
|
1766
|
+
catch (closeError) {
|
|
1767
|
+
cleanupErrors.push(closeError);
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
await ContextParts.cleanupBuildBuses(resources, cleanupErrors);
|
|
1771
|
+
}
|
|
1213
1772
|
if (cleanupErrors.length > 0) {
|
|
1214
1773
|
throw new AggregateError([error, ...cleanupErrors], "Bounded Context build failed during cleanup.");
|
|
1215
1774
|
}
|
|
1216
1775
|
throw error;
|
|
1217
1776
|
}
|
|
1218
|
-
|
|
1777
|
+
/**
|
|
1778
|
+
* Selects generated metadata for configured Entity and standalone handler types and creates
|
|
1779
|
+
* repository views and handler groups.
|
|
1780
|
+
*/
|
|
1781
|
+
#loadGeneratedArtifacts(entityTypes) {
|
|
1219
1782
|
if (entityTypes.length === 0 && this.#standaloneInstances().length === 0) {
|
|
1220
1783
|
return Object.freeze({ repositories: Object.freeze([]), standalone: Object.freeze([]) });
|
|
1221
1784
|
}
|
|
1222
|
-
const
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
const registries = (
|
|
1227
|
-
.load({
|
|
1228
|
-
modules: [registryModule],
|
|
1229
|
-
...ContextParts.generatedRegistryCacheBust(registryKey),
|
|
1230
|
-
})
|
|
1231
|
-
.catch((error) => {
|
|
1232
|
-
ContextParts.recordGeneratedRegistryFailure(registryKey);
|
|
1233
|
-
throw error;
|
|
1234
|
-
}));
|
|
1785
|
+
const selected = [
|
|
1786
|
+
...entityTypes,
|
|
1787
|
+
...this.#standaloneInstances().map((instance) => instance.constructor),
|
|
1788
|
+
];
|
|
1789
|
+
const registries = [SpineMetadataInventory.select(selected)];
|
|
1235
1790
|
const metadata = ContextParts.ingestGeneratedRegistries(registries);
|
|
1236
1791
|
const repositories = Object.freeze(entityTypes.map((entityType) => ContextParts.createGeneratedRepository(entityType, registries, metadata, this.#generatedRepositoryOptions.get(entityType))));
|
|
1237
1792
|
const standalone = Object.freeze(registries.flatMap((registry) => registry.receivers.filter((receiver) => receiver.receiverKind === "standalone")));
|
|
1238
1793
|
return Object.freeze({ repositories, standalone });
|
|
1239
1794
|
}
|
|
1795
|
+
/**
|
|
1796
|
+
* Returns a frozen list of configured standalone assignee, commander, and event-receiver instances.
|
|
1797
|
+
*/
|
|
1240
1798
|
#standaloneInstances() {
|
|
1241
1799
|
return Object.freeze([...this.#assignees, ...this.#commanders, ...this.#eventReceivers]);
|
|
1242
1800
|
}
|
|
@@ -1409,24 +1967,29 @@ const ContextParts = Object.freeze({
|
|
|
1409
1967
|
*
|
|
1410
1968
|
* @param resources Partially created resources.
|
|
1411
1969
|
* @param errors Collection receiving failures.
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1970
|
+
* @returns Completion after all created buses and stores close.
|
|
1971
|
+
*/
|
|
1972
|
+
async cleanupBuildBuses(resources, errors) {
|
|
1973
|
+
const closing = [];
|
|
1974
|
+
const { commandBus, systemEventBus, systemEventStore, eventBus, eventStore } = resources;
|
|
1975
|
+
if (commandBus !== undefined)
|
|
1976
|
+
closing.push(() => commandBus.close());
|
|
1977
|
+
if (systemEventBus !== undefined)
|
|
1978
|
+
closing.push(() => systemEventBus.close());
|
|
1979
|
+
else if (systemEventStore !== undefined)
|
|
1980
|
+
closing.push(() => systemEventStore.close());
|
|
1981
|
+
if (eventBus !== undefined)
|
|
1982
|
+
closing.push(() => eventBus.close());
|
|
1983
|
+
else if (eventStore !== undefined)
|
|
1984
|
+
closing.push(() => eventStore.close());
|
|
1985
|
+
for (const close of closing) {
|
|
1986
|
+
try {
|
|
1987
|
+
await close();
|
|
1988
|
+
}
|
|
1989
|
+
catch (error) {
|
|
1990
|
+
errors.push(error);
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1430
1993
|
},
|
|
1431
1994
|
/**
|
|
1432
1995
|
* Returns constituent failures from nested aggregate errors.
|
|
@@ -1476,10 +2039,11 @@ const ContextParts = Object.freeze({
|
|
|
1476
2039
|
* @param buses Prepared domain and System buses.
|
|
1477
2040
|
* @param running Prepared Stand and subscription runtime.
|
|
1478
2041
|
* @param ai Effective AI registry when Agent repositories are present.
|
|
2042
|
+
* @param tenantIndex Effective live membership index.
|
|
1479
2043
|
* @returns Constructed bounded context.
|
|
1480
2044
|
*/
|
|
1481
|
-
createBoundedContext(specSnapshot, storageFactory, repositories, deliveryStrategy, buses, running, ai) {
|
|
1482
|
-
|
|
2045
|
+
createBoundedContext(specSnapshot, storageFactory, repositories, deliveryStrategy, buses, running, ai, tenantIndex) {
|
|
2046
|
+
const context = constructBoundedContext({
|
|
1483
2047
|
snapshot: ContextParts.snapshotFor(specSnapshot),
|
|
1484
2048
|
commandBus: buses.commandBus,
|
|
1485
2049
|
eventBus: buses.eventBus,
|
|
@@ -1490,11 +2054,14 @@ const ContextParts = Object.freeze({
|
|
|
1490
2054
|
subscriptionRuntime: running.runtime,
|
|
1491
2055
|
systemSpec: buses.systemSpec,
|
|
1492
2056
|
storageFactory,
|
|
2057
|
+
tenantIndex: tenantIndex ?? ServerEnvironment.instance().tenantIndex,
|
|
1493
2058
|
repositories,
|
|
1494
2059
|
deliveryStrategy,
|
|
1495
2060
|
...(ai === undefined ? {} : { ai }),
|
|
1496
2061
|
token: frameworkConstructionToken,
|
|
1497
2062
|
});
|
|
2063
|
+
standAccess.bindProducingContext(running.stand, context);
|
|
2064
|
+
return context;
|
|
1498
2065
|
},
|
|
1499
2066
|
/**
|
|
1500
2067
|
* Captures public context identity from the build specification.
|
|
@@ -1533,35 +2100,6 @@ const ContextParts = Object.freeze({
|
|
|
1533
2100
|
multitenant: false,
|
|
1534
2101
|
});
|
|
1535
2102
|
},
|
|
1536
|
-
/**
|
|
1537
|
-
* Captures the shard count and validates later shard results.
|
|
1538
|
-
*
|
|
1539
|
-
* @param strategy Configured delivery strategy.
|
|
1540
|
-
* @returns Immutable delivery strategy snapshot.
|
|
1541
|
-
*/
|
|
1542
|
-
snapshotDeliveryStrategy(strategy) {
|
|
1543
|
-
if (!Number.isSafeInteger(strategy.shardCount) || strategy.shardCount <= 0) {
|
|
1544
|
-
throw new Error("Delivery strategy shard count must be a positive safe integer.");
|
|
1545
|
-
}
|
|
1546
|
-
const shardCount = strategy.shardCount;
|
|
1547
|
-
return Object.freeze({
|
|
1548
|
-
shardCount,
|
|
1549
|
-
/**
|
|
1550
|
-
* Returns a target shard after verifying the captured shard total.
|
|
1551
|
-
*
|
|
1552
|
-
* @param targetId Inbox target identifier.
|
|
1553
|
-
* @param targetType Inbox target type.
|
|
1554
|
-
* @returns Validated shard index.
|
|
1555
|
-
*/
|
|
1556
|
-
shardFor(targetId, targetType) {
|
|
1557
|
-
const shard = strategy.shardFor(InboxTargets.clone(targetId), targetType);
|
|
1558
|
-
if (shard.ofTotal !== shardCount) {
|
|
1559
|
-
throw new Error("Delivery strategy shard total must equal its resolved shard count.");
|
|
1560
|
-
}
|
|
1561
|
-
return new ShardIndex(shard.index, shard.ofTotal);
|
|
1562
|
-
},
|
|
1563
|
-
});
|
|
1564
|
-
},
|
|
1565
2103
|
/**
|
|
1566
2104
|
* Creates the storage context for read-side catch-up.
|
|
1567
2105
|
*
|
|
@@ -1740,8 +2278,7 @@ const ContextParts = Object.freeze({
|
|
|
1740
2278
|
},
|
|
1741
2279
|
});
|
|
1742
2280
|
const ready = broker.open();
|
|
1743
|
-
//
|
|
1744
|
-
// observable operation without letting Node report an unhandled rejection.
|
|
2281
|
+
// The builder awaits readiness; retain a rejection handler until it observes the result.
|
|
1745
2282
|
void ready.catch(() => undefined);
|
|
1746
2283
|
contextIntegrations.set(context, { broker, ready });
|
|
1747
2284
|
},
|
|
@@ -1878,166 +2415,9 @@ const ContextParts = Object.freeze({
|
|
|
1878
2415
|
*/
|
|
1879
2416
|
requireEntityClass(entityType, operation) {
|
|
1880
2417
|
if (typeof entityType !== "function") {
|
|
1881
|
-
throw new TypeError(`${operation} requires a Repository instance. Use an entity class only with
|
|
1882
|
-
}
|
|
1883
|
-
},
|
|
1884
|
-
/**
|
|
1885
|
-
* Rejects synchronous assembly when classes need generated metadata.
|
|
1886
|
-
*
|
|
1887
|
-
* @param entityTypes Entity classes to assemble.
|
|
1888
|
-
*/
|
|
1889
|
-
rejectSyncEntityAssembly(entityTypes) {
|
|
1890
|
-
if (entityTypes.size === 0) {
|
|
1891
|
-
return;
|
|
1892
|
-
}
|
|
1893
|
-
throw new Error("BoundedContextBuilder.build() cannot assemble entity classes from generated metadata. " +
|
|
1894
|
-
"Use buildAsync().");
|
|
1895
|
-
},
|
|
1896
|
-
/**
|
|
1897
|
-
* Validates that a root exists when generated metadata must be loaded.
|
|
1898
|
-
*
|
|
1899
|
-
* @param root Configured registry location.
|
|
1900
|
-
* @returns Configured root; throws when absent.
|
|
1901
|
-
*/
|
|
1902
|
-
requireGeneratedRegistryRoot(root) {
|
|
1903
|
-
if (root !== undefined) {
|
|
1904
|
-
return root;
|
|
1905
|
-
}
|
|
1906
|
-
throw new Error("BoundedContextBuilder.buildAsync() requires withGeneratedRegistryRoot(root) " +
|
|
1907
|
-
"when assembling entity classes from generated metadata.");
|
|
1908
|
-
},
|
|
1909
|
-
/**
|
|
1910
|
-
* Resolves the generated registry module and verifies it stays beneath the configured root.
|
|
1911
|
-
*
|
|
1912
|
-
* @param root Configured filesystem path or file URL.
|
|
1913
|
-
* @returns Registry module file URL.
|
|
1914
|
-
*/
|
|
1915
|
-
async trustedGeneratedRegistryModule(root) {
|
|
1916
|
-
const trustedRoot = await ContextParts.canonicalGeneratedRegistryRoot(root);
|
|
1917
|
-
const registryPath = resolve(trustedRoot, generatedRegistryFile);
|
|
1918
|
-
const canonicalRegistryPath = await ContextParts.canonicalReadableRegistryPath(registryPath);
|
|
1919
|
-
if (ContextParts.resolvesOutsideRoot(trustedRoot, canonicalRegistryPath)) {
|
|
1920
|
-
throw new Error(`Generated handler registry module "${canonicalRegistryPath}" must resolve within ` +
|
|
1921
|
-
`the configured generated registry root "${trustedRoot}".`);
|
|
1922
|
-
}
|
|
1923
|
-
return pathToFileURL(canonicalRegistryPath);
|
|
1924
|
-
},
|
|
1925
|
-
/**
|
|
1926
|
-
* Resolves a generated registry root to its canonical filesystem path.
|
|
1927
|
-
*
|
|
1928
|
-
* @param root Configured path or file URL.
|
|
1929
|
-
* @returns Canonical directory path.
|
|
1930
|
-
*/
|
|
1931
|
-
async canonicalGeneratedRegistryRoot(root) {
|
|
1932
|
-
const rootPath = ContextParts.generatedRegistryRootPath(root);
|
|
1933
|
-
try {
|
|
1934
|
-
return await realpath(rootPath);
|
|
1935
|
-
}
|
|
1936
|
-
catch (error) {
|
|
1937
|
-
throw new Error(`Generated registry root "${rootPath}" must be an existing readable directory.`, { cause: error });
|
|
1938
|
-
}
|
|
1939
|
-
},
|
|
1940
|
-
/**
|
|
1941
|
-
* Converts a registry root path or file URL to an absolute filesystem path.
|
|
1942
|
-
*
|
|
1943
|
-
* @param root Configured path or file URL.
|
|
1944
|
-
* @returns Absolute filesystem path.
|
|
1945
|
-
*/
|
|
1946
|
-
generatedRegistryRootPath(root) {
|
|
1947
|
-
if (root instanceof URL) {
|
|
1948
|
-
return ContextParts.fileUrlPath(root, "Generated registry root");
|
|
1949
|
-
}
|
|
1950
|
-
if (ContextParts.isUrlLike(root)) {
|
|
1951
|
-
return ContextParts.fileUrlPath(ContextParts.parseRootUrl(root), "Generated registry root");
|
|
1952
|
-
}
|
|
1953
|
-
return resolve(root);
|
|
1954
|
-
},
|
|
1955
|
-
/**
|
|
1956
|
-
* Validates a file URL and converts it to an absolute filesystem path.
|
|
1957
|
-
*
|
|
1958
|
-
* @param url File URL to convert.
|
|
1959
|
-
* @param label Label used in validation errors.
|
|
1960
|
-
* @returns Filesystem path.
|
|
1961
|
-
*/
|
|
1962
|
-
fileUrlPath(url, label) {
|
|
1963
|
-
if (url.protocol !== "file:") {
|
|
1964
|
-
throw new Error(`${label} "${url.href}" must use the file: URL scheme.`);
|
|
1965
|
-
}
|
|
1966
|
-
if (url.search.length > 0 || url.hash.length > 0) {
|
|
1967
|
-
throw new Error(`${label} "${url.href}" must not include a query or hash.`);
|
|
1968
|
-
}
|
|
1969
|
-
return resolve(fileURLToPath(url));
|
|
1970
|
-
},
|
|
1971
|
-
/**
|
|
1972
|
-
* Parses a generated registry root string as a URL.
|
|
1973
|
-
*
|
|
1974
|
-
* @param root Registry root string.
|
|
1975
|
-
* @returns Parsed URL.
|
|
1976
|
-
*/
|
|
1977
|
-
parseRootUrl(root) {
|
|
1978
|
-
try {
|
|
1979
|
-
return new URL(root);
|
|
1980
|
-
}
|
|
1981
|
-
catch (error) {
|
|
1982
|
-
throw new Error(`Generated registry root "${root}" is not a valid URL.`, { cause: error });
|
|
1983
|
-
}
|
|
1984
|
-
},
|
|
1985
|
-
/**
|
|
1986
|
-
* Checks strings for a URL scheme, excluding Windows drive paths.
|
|
1987
|
-
*
|
|
1988
|
-
* @param value String to inspect.
|
|
1989
|
-
* @returns True when the string has a URL scheme.
|
|
1990
|
-
*/
|
|
1991
|
-
isUrlLike(value) {
|
|
1992
|
-
return moduleSchemeRe.test(value) && !/^[A-Za-z]:[\\/]/.test(value);
|
|
1993
|
-
},
|
|
1994
|
-
/**
|
|
1995
|
-
* Verifies registry-module readability and resolves its canonical path.
|
|
1996
|
-
*
|
|
1997
|
-
* @param registryPath Registry module filesystem path.
|
|
1998
|
-
* @returns Canonical readable path.
|
|
1999
|
-
*/
|
|
2000
|
-
async canonicalReadableRegistryPath(registryPath) {
|
|
2001
|
-
try {
|
|
2002
|
-
await access(registryPath, fsConstants.R_OK);
|
|
2003
|
-
return await realpath(registryPath);
|
|
2004
|
-
}
|
|
2005
|
-
catch (error) {
|
|
2006
|
-
throw new Error(`Generated handler registry module "${registryPath}" must exist and be readable.`, { cause: error });
|
|
2418
|
+
throw new TypeError(`${operation} requires a Repository instance. Use an entity class only with build().`);
|
|
2007
2419
|
}
|
|
2008
2420
|
},
|
|
2009
|
-
/**
|
|
2010
|
-
* Checks whether a canonical path escapes a canonical root.
|
|
2011
|
-
*
|
|
2012
|
-
* @param canonicalRoot Root directory.
|
|
2013
|
-
* @param canonicalPath Candidate path.
|
|
2014
|
-
* @returns True when the candidate escapes the root.
|
|
2015
|
-
*/
|
|
2016
|
-
resolvesOutsideRoot(canonicalRoot, canonicalPath) {
|
|
2017
|
-
const relativePath = relative(canonicalRoot, canonicalPath);
|
|
2018
|
-
return (relativePath.startsWith("..") ||
|
|
2019
|
-
relativePath === ".." ||
|
|
2020
|
-
relativePath.split(sep).includes("..") ||
|
|
2021
|
-
isAbsolute(relativePath));
|
|
2022
|
-
},
|
|
2023
|
-
/**
|
|
2024
|
-
* Creates a cache-bust query after a prior registry load failure.
|
|
2025
|
-
*
|
|
2026
|
-
* @param registryKey Canonical registry identifier.
|
|
2027
|
-
* @returns Retry query, or an empty object on the first attempt.
|
|
2028
|
-
*/
|
|
2029
|
-
generatedRegistryCacheBust(registryKey) {
|
|
2030
|
-
const attempt = generatedRegistryLoadAttempts.get(registryKey) ?? 0;
|
|
2031
|
-
return attempt === 0 ? {} : { cacheBust: `retry-${attempt.toString()}` };
|
|
2032
|
-
},
|
|
2033
|
-
/**
|
|
2034
|
-
* Records another failed load for a registry.
|
|
2035
|
-
*
|
|
2036
|
-
* @param registryKey Canonical registry identifier.
|
|
2037
|
-
*/
|
|
2038
|
-
recordGeneratedRegistryFailure(registryKey) {
|
|
2039
|
-
generatedRegistryLoadAttempts.set(registryKey, (generatedRegistryLoadAttempts.get(registryKey) ?? 0) + 1);
|
|
2040
|
-
},
|
|
2041
2421
|
/**
|
|
2042
2422
|
* Loads generated handler registries into a metadata registry.
|
|
2043
2423
|
*
|
|
@@ -2295,16 +2675,6 @@ const ContextParts = Object.freeze({
|
|
|
2295
2675
|
}
|
|
2296
2676
|
return Object.freeze([...schemas.values()]);
|
|
2297
2677
|
},
|
|
2298
|
-
/**
|
|
2299
|
-
* Clears metadata for a failed context build and closes its tenant index.
|
|
2300
|
-
*
|
|
2301
|
-
* @param context Context whose build failed.
|
|
2302
|
-
* @param tenantIndex Tenant index to close.
|
|
2303
|
-
*/
|
|
2304
|
-
cleanupFailedContext(context, tenantIndex) {
|
|
2305
|
-
ContextParts.clearContextMetadata(context);
|
|
2306
|
-
tenantIndex.close();
|
|
2307
|
-
},
|
|
2308
2678
|
/**
|
|
2309
2679
|
* Removes runtime metadata associated with a context.
|
|
2310
2680
|
*
|
|
@@ -2327,68 +2697,25 @@ const ContextParts = Object.freeze({
|
|
|
2327
2697
|
contextStorageFactories.delete(context);
|
|
2328
2698
|
contextDeliveryDescriptors.delete(context);
|
|
2329
2699
|
contextSubscriptionRuntimes.delete(context);
|
|
2700
|
+
contextInputPreparations.delete(context);
|
|
2701
|
+
contextPreparations.delete(context);
|
|
2330
2702
|
contextIntegrations.delete(context);
|
|
2331
2703
|
eventSubscribers.delete(context);
|
|
2332
2704
|
systemEventPosters.delete(context);
|
|
2333
2705
|
},
|
|
2334
2706
|
/**
|
|
2335
|
-
* Creates delivery operations from the context inboxes
|
|
2707
|
+
* Creates delivery operations from the context inboxes.
|
|
2336
2708
|
*
|
|
2337
|
-
* @param context Built context snapshot.
|
|
2338
|
-
* @param storageFactory Context storage provider.
|
|
2339
|
-
* @param tenantIndex Tenant index for startup scopes.
|
|
2340
2709
|
* @param entityInbox Registered Entity inbox.
|
|
2341
2710
|
* @param projections Projection inbox.
|
|
2342
|
-
* @param readiness Delivery readiness coordinator.
|
|
2343
2711
|
* @returns Delivery descriptor.
|
|
2344
2712
|
*/
|
|
2345
|
-
createDeliveryDescriptor(
|
|
2713
|
+
createDeliveryDescriptor(entityInbox, projections) {
|
|
2346
2714
|
return Object.freeze({
|
|
2347
|
-
storageFactory,
|
|
2348
|
-
startupScopes: () => ContextParts.deliveryStartupScopes(tenantIndex),
|
|
2349
|
-
storageContext: (scope) => ContextParts.deliveryStorageContext(context, scope),
|
|
2350
2715
|
endpoints: () => Object.freeze([...entityInbox.endpoints(), ...projections.endpoints()]),
|
|
2351
2716
|
replay: (message, tenantId) => message.label === "UPDATE_SUBSCRIBER"
|
|
2352
2717
|
? projections.replay(message, tenantId)
|
|
2353
2718
|
: entityInbox.replay(message, tenantId),
|
|
2354
|
-
onReady: (onReady) => readiness.onReady(onReady),
|
|
2355
|
-
transition: (scopes, onReady, options) => readiness.transition(scopes, onReady, options),
|
|
2356
|
-
});
|
|
2357
|
-
},
|
|
2358
|
-
/**
|
|
2359
|
-
* Lists delivery startup scopes, one per tenant in multitenant contexts.
|
|
2360
|
-
*
|
|
2361
|
-
* @param tenantIndex Tenant index supplying configured scopes.
|
|
2362
|
-
* @returns Frozen startup scope list.
|
|
2363
|
-
*/
|
|
2364
|
-
async deliveryStartupScopes(tenantIndex) {
|
|
2365
|
-
if (tenantIndex.tenantMode === "single-tenant") {
|
|
2366
|
-
return Object.freeze([Object.freeze({})]);
|
|
2367
|
-
}
|
|
2368
|
-
return Object.freeze((await tenantIndex.all()).map((tenantId) => Object.freeze({ tenantId })));
|
|
2369
|
-
},
|
|
2370
|
-
/**
|
|
2371
|
-
* Creates tenant-validated storage context for a delivery scope.
|
|
2372
|
-
*
|
|
2373
|
-
* @param context Built context snapshot.
|
|
2374
|
-
* @param scope Selected startup scope.
|
|
2375
|
-
* @returns Storage context for the selected tenant.
|
|
2376
|
-
*/
|
|
2377
|
-
deliveryStorageContext(context, scope) {
|
|
2378
|
-
if (context.tenantMode === "single-tenant") {
|
|
2379
|
-
if (scope.tenantId !== undefined) {
|
|
2380
|
-
throw new Error(`Single-tenant context "${context.name.value}" does not accept tenantId.`);
|
|
2381
|
-
}
|
|
2382
|
-
return Object.freeze({ name: context.name.value, multitenant: false });
|
|
2383
|
-
}
|
|
2384
|
-
const tenantId = scope.tenantId;
|
|
2385
|
-
if (tenantId === undefined) {
|
|
2386
|
-
throw new Error(`Multitenant context "${context.name.value}" requires tenantId.`);
|
|
2387
|
-
}
|
|
2388
|
-
return Object.freeze({
|
|
2389
|
-
name: context.name.value,
|
|
2390
|
-
multitenant: true,
|
|
2391
|
-
tenantId: TenantBoundary.from(tenantId).tenantId,
|
|
2392
2719
|
});
|
|
2393
2720
|
},
|
|
2394
2721
|
/**
|
|
@@ -2488,7 +2815,7 @@ const ContextParts = Object.freeze({
|
|
|
2488
2815
|
registeredRepositories.set(repository, { name: registration.name });
|
|
2489
2816
|
},
|
|
2490
2817
|
close: () => {
|
|
2491
|
-
repositoryAccess.
|
|
2818
|
+
repositoryAccess.clearRuntimeIdle(repository);
|
|
2492
2819
|
},
|
|
2493
2820
|
};
|
|
2494
2821
|
},
|
|
@@ -2502,6 +2829,7 @@ const ContextParts = Object.freeze({
|
|
|
2502
2829
|
repositoryAccess.bindRuntime(repository, {
|
|
2503
2830
|
context: registration.storageContext,
|
|
2504
2831
|
storageFactory: registration.storageFactory,
|
|
2832
|
+
onAdmit: registration.onAdmit,
|
|
2505
2833
|
...(registration.ai === undefined ? {} : { ai: registration.ai }),
|
|
2506
2834
|
stand: registration.stand,
|
|
2507
2835
|
signalMetadata: new SignalMetadata(),
|
|
@@ -2511,11 +2839,11 @@ const ContextParts = Object.freeze({
|
|
|
2511
2839
|
registerEventSchema: registration.registerEventSchema,
|
|
2512
2840
|
registerSystemEventSchema: registration.registerSystemEventSchema,
|
|
2513
2841
|
publishAgentEvent: registration.publishAgentEvent,
|
|
2514
|
-
prepareSavedCommand: registration.prepareSavedCommand,
|
|
2515
|
-
publishSavedCommand: registration.publishSavedCommand,
|
|
2516
2842
|
publishAgentSystemEvent: registration.publishAgentSystemEvent,
|
|
2517
|
-
|
|
2518
|
-
|
|
2843
|
+
agentReceipts: registration.agentReceipts,
|
|
2844
|
+
agentSignal: registration.agentSignal,
|
|
2845
|
+
reserveAgent: registration.reserveAgent,
|
|
2846
|
+
deferAgent: registration.deferAgent,
|
|
2519
2847
|
});
|
|
2520
2848
|
},
|
|
2521
2849
|
/**
|
|
@@ -2637,7 +2965,7 @@ const ContextParts = Object.freeze({
|
|
|
2637
2965
|
});
|
|
2638
2966
|
}
|
|
2639
2967
|
finally {
|
|
2640
|
-
eventStore.close();
|
|
2968
|
+
await eventStore.close();
|
|
2641
2969
|
}
|
|
2642
2970
|
},
|
|
2643
2971
|
/**
|
|
@@ -2787,9 +3115,15 @@ const ContextParts = Object.freeze({
|
|
|
2787
3115
|
* @internal
|
|
2788
3116
|
*/
|
|
2789
3117
|
export const boundedContextIntegrationAccess = Object.freeze({
|
|
3118
|
+
/**
|
|
3119
|
+
* Publishes an imported Event through the context’s import path.
|
|
3120
|
+
*/
|
|
2790
3121
|
publishImported(context, event) {
|
|
2791
3122
|
return ContextParts.publishImported(context, event);
|
|
2792
3123
|
},
|
|
3124
|
+
/**
|
|
3125
|
+
* Signals that standalone context integration is ready.
|
|
3126
|
+
*/
|
|
2793
3127
|
ready(context) {
|
|
2794
3128
|
return ContextParts.integrationReady(context);
|
|
2795
3129
|
},
|