@spine-event-engine/server 2.0.0-snapshot.2
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/LICENSE +201 -0
- package/README.md +569 -0
- package/REFERENCE.md +488 -0
- package/dist/bus/command-bus.d.ts +61 -0
- package/dist/bus/command-bus.d.ts.map +1 -0
- package/dist/bus/command-bus.js +201 -0
- package/dist/bus/command-bus.js.map +1 -0
- package/dist/bus/command-dispatcher-registry.d.ts +33 -0
- package/dist/bus/command-dispatcher-registry.d.ts.map +1 -0
- package/dist/bus/command-dispatcher-registry.js +75 -0
- package/dist/bus/command-dispatcher-registry.js.map +1 -0
- package/dist/bus/command-dispatcher.d.ts +25 -0
- package/dist/bus/command-dispatcher.d.ts.map +1 -0
- package/dist/bus/command-dispatcher.js +15 -0
- package/dist/bus/command-dispatcher.js.map +1 -0
- package/dist/bus/command-errors.d.ts +23 -0
- package/dist/bus/command-errors.d.ts.map +1 -0
- package/dist/bus/command-errors.js +52 -0
- package/dist/bus/command-errors.js.map +1 -0
- package/dist/bus/event-bus.d.ts +113 -0
- package/dist/bus/event-bus.d.ts.map +1 -0
- package/dist/bus/event-bus.js +518 -0
- package/dist/bus/event-bus.js.map +1 -0
- package/dist/bus/event-dispatcher-origin-schemas.d.ts +17 -0
- package/dist/bus/event-dispatcher-origin-schemas.d.ts.map +1 -0
- package/dist/bus/event-dispatcher-origin-schemas.js +32 -0
- package/dist/bus/event-dispatcher-origin-schemas.js.map +1 -0
- package/dist/bus/event-dispatcher-registry.d.ts +48 -0
- package/dist/bus/event-dispatcher-registry.d.ts.map +1 -0
- package/dist/bus/event-dispatcher-registry.js +176 -0
- package/dist/bus/event-dispatcher-registry.js.map +1 -0
- package/dist/bus/event-dispatcher.d.ts +48 -0
- package/dist/bus/event-dispatcher.d.ts.map +1 -0
- package/dist/bus/event-dispatcher.js +15 -0
- package/dist/bus/event-dispatcher.js.map +1 -0
- package/dist/context/bounded-context.d.ts +606 -0
- package/dist/context/bounded-context.d.ts.map +1 -0
- package/dist/context/bounded-context.js +1780 -0
- package/dist/context/bounded-context.js.map +1 -0
- package/dist/context/entity-inbox.d.ts +63 -0
- package/dist/context/entity-inbox.d.ts.map +1 -0
- package/dist/context/entity-inbox.js +380 -0
- package/dist/context/entity-inbox.js.map +1 -0
- package/dist/context/local-inbox-handoff.d.ts +244 -0
- package/dist/context/local-inbox-handoff.d.ts.map +1 -0
- package/dist/context/local-inbox-handoff.js +334 -0
- package/dist/context/local-inbox-handoff.js.map +1 -0
- package/dist/context/projection-handoff.d.ts +46 -0
- package/dist/context/projection-handoff.d.ts.map +1 -0
- package/dist/context/projection-handoff.js +172 -0
- package/dist/context/projection-handoff.js.map +1 -0
- package/dist/context/tenant-index.d.ts +41 -0
- package/dist/context/tenant-index.d.ts.map +1 -0
- package/dist/context/tenant-index.js +92 -0
- package/dist/context/tenant-index.js.map +1 -0
- package/dist/delivery/conditional-pickup.d.ts +25 -0
- package/dist/delivery/conditional-pickup.d.ts.map +1 -0
- package/dist/delivery/conditional-pickup.js +26 -0
- package/dist/delivery/conditional-pickup.js.map +1 -0
- package/dist/delivery/delivery-builder.d.ts +216 -0
- package/dist/delivery/delivery-builder.d.ts.map +1 -0
- package/dist/delivery/delivery-builder.js +323 -0
- package/dist/delivery/delivery-builder.js.map +1 -0
- package/dist/delivery/delivery-lease.d.ts +15 -0
- package/dist/delivery/delivery-lease.d.ts.map +1 -0
- package/dist/delivery/delivery-lease.js +44 -0
- package/dist/delivery/delivery-lease.js.map +1 -0
- package/dist/delivery/delivery-loop.d.ts +113 -0
- package/dist/delivery/delivery-loop.d.ts.map +1 -0
- package/dist/delivery/delivery-loop.js +119 -0
- package/dist/delivery/delivery-loop.js.map +1 -0
- package/dist/delivery/delivery-monitor.d.ts +165 -0
- package/dist/delivery/delivery-monitor.d.ts.map +1 -0
- package/dist/delivery/delivery-monitor.js +168 -0
- package/dist/delivery/delivery-monitor.js.map +1 -0
- package/dist/delivery/delivery-ports.d.ts +141 -0
- package/dist/delivery/delivery-ports.d.ts.map +1 -0
- package/dist/delivery/delivery-ports.js +15 -0
- package/dist/delivery/delivery-ports.js.map +1 -0
- package/dist/delivery/delivery-run-control.d.ts +50 -0
- package/dist/delivery/delivery-run-control.d.ts.map +1 -0
- package/dist/delivery/delivery-run-control.js +66 -0
- package/dist/delivery/delivery-run-control.js.map +1 -0
- package/dist/delivery/delivery-run-coordinator.d.ts +218 -0
- package/dist/delivery/delivery-run-coordinator.d.ts.map +1 -0
- package/dist/delivery/delivery-run-coordinator.js +609 -0
- package/dist/delivery/delivery-run-coordinator.js.map +1 -0
- package/dist/delivery/delivery-storage-error.d.ts +13 -0
- package/dist/delivery/delivery-storage-error.d.ts.map +1 -0
- package/dist/delivery/delivery-storage-error.js +30 -0
- package/dist/delivery/delivery-storage-error.js.map +1 -0
- package/dist/delivery/delivery-supervisor.d.ts +175 -0
- package/dist/delivery/delivery-supervisor.d.ts.map +1 -0
- package/dist/delivery/delivery-supervisor.js +469 -0
- package/dist/delivery/delivery-supervisor.js.map +1 -0
- package/dist/delivery/delivery-worker.d.ts +180 -0
- package/dist/delivery/delivery-worker.d.ts.map +1 -0
- package/dist/delivery/delivery-worker.js +232 -0
- package/dist/delivery/delivery-worker.js.map +1 -0
- package/dist/delivery/delivery.d.ts +238 -0
- package/dist/delivery/delivery.d.ts.map +1 -0
- package/dist/delivery/delivery.js +400 -0
- package/dist/delivery/delivery.js.map +1 -0
- package/dist/delivery/inbox-records.d.ts +19 -0
- package/dist/delivery/inbox-records.d.ts.map +1 -0
- package/dist/delivery/inbox-records.js +291 -0
- package/dist/delivery/inbox-records.js.map +1 -0
- package/dist/delivery/inbox-storage.d.ts +81 -0
- package/dist/delivery/inbox-storage.d.ts.map +1 -0
- package/dist/delivery/inbox-storage.js +353 -0
- package/dist/delivery/inbox-storage.js.map +1 -0
- package/dist/delivery/inbox.d.ts +263 -0
- package/dist/delivery/inbox.d.ts.map +1 -0
- package/dist/delivery/inbox.js +172 -0
- package/dist/delivery/inbox.js.map +1 -0
- package/dist/delivery/parked-delivery-obligations.d.ts +182 -0
- package/dist/delivery/parked-delivery-obligations.d.ts.map +1 -0
- package/dist/delivery/parked-delivery-obligations.js +390 -0
- package/dist/delivery/parked-delivery-obligations.js.map +1 -0
- package/dist/delivery/shard-index.d.ts +41 -0
- package/dist/delivery/shard-index.d.ts.map +1 -0
- package/dist/delivery/shard-index.js +67 -0
- package/dist/delivery/shard-index.js.map +1 -0
- package/dist/delivery/sharded-work-registry.d.ts +114 -0
- package/dist/delivery/sharded-work-registry.d.ts.map +1 -0
- package/dist/delivery/sharded-work-registry.js +360 -0
- package/dist/delivery/sharded-work-registry.js.map +1 -0
- package/dist/entity/entity-metadata.d.ts +173 -0
- package/dist/entity/entity-metadata.d.ts.map +1 -0
- package/dist/entity/entity-metadata.js +187 -0
- package/dist/entity/entity-metadata.js.map +1 -0
- package/dist/entity/entity-storage-descriptor.d.ts +64 -0
- package/dist/entity/entity-storage-descriptor.d.ts.map +1 -0
- package/dist/entity/entity-storage-descriptor.js +227 -0
- package/dist/entity/entity-storage-descriptor.js.map +1 -0
- package/dist/entity/entity-transaction.d.ts +372 -0
- package/dist/entity/entity-transaction.d.ts.map +1 -0
- package/dist/entity/entity-transaction.js +413 -0
- package/dist/entity/entity-transaction.js.map +1 -0
- package/dist/entity/entity-transition-validation.d.ts +47 -0
- package/dist/entity/entity-transition-validation.d.ts.map +1 -0
- package/dist/entity/entity-transition-validation.js +327 -0
- package/dist/entity/entity-transition-validation.js.map +1 -0
- package/dist/entity/entity.d.ts +499 -0
- package/dist/entity/entity.d.ts.map +1 -0
- package/dist/entity/entity.js +829 -0
- package/dist/entity/entity.js.map +1 -0
- package/dist/entity/implicit-required-id.d.ts +15 -0
- package/dist/entity/implicit-required-id.d.ts.map +1 -0
- package/dist/entity/implicit-required-id.js +123 -0
- package/dist/entity/implicit-required-id.js.map +1 -0
- package/dist/entity/spec-scanner.d.ts +14 -0
- package/dist/entity/spec-scanner.d.ts.map +1 -0
- package/dist/entity/spec-scanner.js +37 -0
- package/dist/entity/spec-scanner.js.map +1 -0
- package/dist/handler/build-time-handler-analyzer.d.ts +150 -0
- package/dist/handler/build-time-handler-analyzer.d.ts.map +1 -0
- package/dist/handler/build-time-handler-analyzer.js +1101 -0
- package/dist/handler/build-time-handler-analyzer.js.map +1 -0
- package/dist/handler/command-registration-readiness.d.ts +99 -0
- package/dist/handler/command-registration-readiness.d.ts.map +1 -0
- package/dist/handler/command-registration-readiness.js +118 -0
- package/dist/handler/command-registration-readiness.js.map +1 -0
- package/dist/handler/event-handler-filter.d.ts +43 -0
- package/dist/handler/event-handler-filter.d.ts.map +1 -0
- package/dist/handler/event-handler-filter.js +128 -0
- package/dist/handler/event-handler-filter.js.map +1 -0
- package/dist/handler/event-registration-readiness.d.ts +196 -0
- package/dist/handler/event-registration-readiness.d.ts.map +1 -0
- package/dist/handler/event-registration-readiness.js +187 -0
- package/dist/handler/event-registration-readiness.js.map +1 -0
- package/dist/handler/external.d.ts +10 -0
- package/dist/handler/external.d.ts.map +1 -0
- package/dist/handler/external.js +15 -0
- package/dist/handler/external.js.map +1 -0
- package/dist/handler/generated-handler-registry.d.ts +154 -0
- package/dist/handler/generated-handler-registry.d.ts.map +1 -0
- package/dist/handler/generated-handler-registry.js +223 -0
- package/dist/handler/generated-handler-registry.js.map +1 -0
- package/dist/handler/generated-registry-discovery.d.ts +94 -0
- package/dist/handler/generated-registry-discovery.d.ts.map +1 -0
- package/dist/handler/generated-registry-discovery.js +228 -0
- package/dist/handler/generated-registry-discovery.js.map +1 -0
- package/dist/handler/generated-registry-writer.d.ts +58 -0
- package/dist/handler/generated-registry-writer.d.ts.map +1 -0
- package/dist/handler/generated-registry-writer.js +458 -0
- package/dist/handler/generated-registry-writer.js.map +1 -0
- package/dist/handler/handler-codegen.d.ts +48 -0
- package/dist/handler/handler-codegen.d.ts.map +1 -0
- package/dist/handler/handler-codegen.js +214 -0
- package/dist/handler/handler-codegen.js.map +1 -0
- package/dist/handler/handler-decorators.d.ts +134 -0
- package/dist/handler/handler-decorators.d.ts.map +1 -0
- package/dist/handler/handler-decorators.js +239 -0
- package/dist/handler/handler-decorators.js.map +1 -0
- package/dist/handler/handler-metadata.d.ts +510 -0
- package/dist/handler/handler-metadata.d.ts.map +1 -0
- package/dist/handler/handler-metadata.js +391 -0
- package/dist/handler/handler-metadata.js.map +1 -0
- package/dist/handler/registration-readiness-metadata.d.ts +68 -0
- package/dist/handler/registration-readiness-metadata.d.ts.map +1 -0
- package/dist/handler/registration-readiness-metadata.js +170 -0
- package/dist/handler/registration-readiness-metadata.js.map +1 -0
- package/dist/handler/rejection-source.d.ts +11 -0
- package/dist/handler/rejection-source.d.ts.map +1 -0
- package/dist/handler/rejection-source.js +25 -0
- package/dist/handler/rejection-source.js.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +54 -0
- package/dist/index.js.map +1 -0
- package/dist/integration/external-messages.d.ts +49 -0
- package/dist/integration/external-messages.d.ts.map +1 -0
- package/dist/integration/external-messages.js +112 -0
- package/dist/integration/external-messages.js.map +1 -0
- package/dist/integration/integration-broker.d.ts +74 -0
- package/dist/integration/integration-broker.d.ts.map +1 -0
- package/dist/integration/integration-broker.js +444 -0
- package/dist/integration/integration-broker.js.map +1 -0
- package/dist/integration/third-party-context.d.ts +56 -0
- package/dist/integration/third-party-context.d.ts.map +1 -0
- package/dist/integration/third-party-context.js +130 -0
- package/dist/integration/third-party-context.js.map +1 -0
- package/dist/repository/command-errors.d.ts +25 -0
- package/dist/repository/command-errors.d.ts.map +1 -0
- package/dist/repository/command-errors.js +43 -0
- package/dist/repository/command-errors.js.map +1 -0
- package/dist/repository/command-routing.d.ts +72 -0
- package/dist/repository/command-routing.d.ts.map +1 -0
- package/dist/repository/command-routing.js +98 -0
- package/dist/repository/command-routing.js.map +1 -0
- package/dist/repository/commit-fence.d.ts +22 -0
- package/dist/repository/commit-fence.d.ts.map +1 -0
- package/dist/repository/commit-fence.js +37 -0
- package/dist/repository/commit-fence.js.map +1 -0
- package/dist/repository/event-routing.d.ts +71 -0
- package/dist/repository/event-routing.d.ts.map +1 -0
- package/dist/repository/event-routing.js +96 -0
- package/dist/repository/event-routing.js.map +1 -0
- package/dist/repository/primitive-id.d.ts +86 -0
- package/dist/repository/primitive-id.d.ts.map +1 -0
- package/dist/repository/primitive-id.js +110 -0
- package/dist/repository/primitive-id.js.map +1 -0
- package/dist/repository/repository.d.ts +670 -0
- package/dist/repository/repository.d.ts.map +1 -0
- package/dist/repository/repository.js +3298 -0
- package/dist/repository/repository.js.map +1 -0
- package/dist/repository/routing-declarations.d.ts +75 -0
- package/dist/repository/routing-declarations.d.ts.map +1 -0
- package/dist/repository/routing-declarations.js +101 -0
- package/dist/repository/routing-declarations.js.map +1 -0
- package/dist/repository/state-update-routing.d.ts +66 -0
- package/dist/repository/state-update-routing.d.ts.map +1 -0
- package/dist/repository/state-update-routing.js +91 -0
- package/dist/repository/state-update-routing.js.map +1 -0
- package/dist/runtime/runtime.d.ts +156 -0
- package/dist/runtime/runtime.d.ts.map +1 -0
- package/dist/runtime/runtime.js +215 -0
- package/dist/runtime/runtime.js.map +1 -0
- package/dist/runtime/signal-intake.d.ts +84 -0
- package/dist/runtime/signal-intake.d.ts.map +1 -0
- package/dist/runtime/signal-intake.js +94 -0
- package/dist/runtime/signal-intake.js.map +1 -0
- package/dist/runtime/signal-metadata.d.ts +242 -0
- package/dist/runtime/signal-metadata.d.ts.map +1 -0
- package/dist/runtime/signal-metadata.js +368 -0
- package/dist/runtime/signal-metadata.js.map +1 -0
- package/dist/server/browser-server.d.ts +49 -0
- package/dist/server/browser-server.d.ts.map +1 -0
- package/dist/server/browser-server.js +633 -0
- package/dist/server/browser-server.js.map +1 -0
- package/dist/server/durable-subscription-bindings.d.ts +122 -0
- package/dist/server/durable-subscription-bindings.d.ts.map +1 -0
- package/dist/server/durable-subscription-bindings.js +524 -0
- package/dist/server/durable-subscription-bindings.js.map +1 -0
- package/dist/server/environment-attachment.d.ts +356 -0
- package/dist/server/environment-attachment.d.ts.map +1 -0
- package/dist/server/environment-attachment.js +2061 -0
- package/dist/server/environment-attachment.js.map +1 -0
- package/dist/server/environment-delivery-records.d.ts +77 -0
- package/dist/server/environment-delivery-records.d.ts.map +1 -0
- package/dist/server/environment-delivery-records.js +282 -0
- package/dist/server/environment-delivery-records.js.map +1 -0
- package/dist/server/environment-delivery-worker.d.ts +161 -0
- package/dist/server/environment-delivery-worker.d.ts.map +1 -0
- package/dist/server/environment-delivery-worker.js +664 -0
- package/dist/server/environment-delivery-worker.js.map +1 -0
- package/dist/server/environment.d.ts +43 -0
- package/dist/server/environment.d.ts.map +1 -0
- package/dist/server/environment.js +90 -0
- package/dist/server/environment.js.map +1 -0
- package/dist/server/managed-child-subscription.d.ts +11 -0
- package/dist/server/managed-child-subscription.d.ts.map +1 -0
- package/dist/server/managed-child-subscription.js +44 -0
- package/dist/server/managed-child-subscription.js.map +1 -0
- package/dist/server/managed-server-application.d.ts +324 -0
- package/dist/server/managed-server-application.d.ts.map +1 -0
- package/dist/server/managed-server-application.js +937 -0
- package/dist/server/managed-server-application.js.map +1 -0
- package/dist/server/node-coordinator.d.ts +191 -0
- package/dist/server/node-coordinator.d.ts.map +1 -0
- package/dist/server/node-coordinator.js +552 -0
- package/dist/server/node-coordinator.js.map +1 -0
- package/dist/server/process-server-coordinator.d.ts +15 -0
- package/dist/server/process-server-coordinator.d.ts.map +1 -0
- package/dist/server/process-server-coordinator.js +112 -0
- package/dist/server/process-server-coordinator.js.map +1 -0
- package/dist/server/reserved-spine-rpc-paths.json +8 -0
- package/dist/server/retryable-close.d.ts +28 -0
- package/dist/server/retryable-close.d.ts.map +1 -0
- package/dist/server/retryable-close.js +129 -0
- package/dist/server/retryable-close.js.map +1 -0
- package/dist/server/server-environment.d.ts +180 -0
- package/dist/server/server-environment.d.ts.map +1 -0
- package/dist/server/server-environment.js +418 -0
- package/dist/server/server-environment.js.map +1 -0
- package/dist/server/server-log.d.ts +18 -0
- package/dist/server/server-log.d.ts.map +1 -0
- package/dist/server/server-log.js +96 -0
- package/dist/server/server-log.js.map +1 -0
- package/dist/server/server.d.ts +389 -0
- package/dist/server/server.d.ts.map +1 -0
- package/dist/server/server.js +752 -0
- package/dist/server/server.js.map +1 -0
- package/dist/services/spine-services.d.ts +88 -0
- package/dist/services/spine-services.d.ts.map +1 -0
- package/dist/services/spine-services.js +1586 -0
- package/dist/services/spine-services.js.map +1 -0
- package/dist/stand/stand.d.ts +294 -0
- package/dist/stand/stand.d.ts.map +1 -0
- package/dist/stand/stand.js +680 -0
- package/dist/stand/stand.js.map +1 -0
- package/dist/stand/subscription-observer.d.ts +53 -0
- package/dist/stand/subscription-observer.d.ts.map +1 -0
- package/dist/stand/subscription-observer.js +444 -0
- package/dist/stand/subscription-observer.js.map +1 -0
- package/dist/stand/subscription-records.d.ts +12 -0
- package/dist/stand/subscription-records.d.ts.map +1 -0
- package/dist/stand/subscription-records.js +99 -0
- package/dist/stand/subscription-records.js.map +1 -0
- package/dist/stand/subscription-registry.d.ts +285 -0
- package/dist/stand/subscription-registry.d.ts.map +1 -0
- package/dist/stand/subscription-registry.js +403 -0
- package/dist/stand/subscription-registry.js.map +1 -0
- package/dist/stand/subscription-runtime.d.ts +102 -0
- package/dist/stand/subscription-runtime.d.ts.map +1 -0
- package/dist/stand/subscription-runtime.js +380 -0
- package/dist/stand/subscription-runtime.js.map +1 -0
- package/dist/testing/index.d.ts +14 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +34 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +65 -0
package/README.md
ADDED
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
# Spine application server for Node.js
|
|
2
|
+
|
|
3
|
+
This package runs a Spine bounded context in Node. It assembles entities,
|
|
4
|
+
handlers, storage, command/event processing, query-side Projections,
|
|
5
|
+
subscriptions, and a local Connect/gRPC-compatible server.
|
|
6
|
+
|
|
7
|
+
## Route a schema or interface token
|
|
8
|
+
|
|
9
|
+
Use `.route(Schema, via)` for one message or `.route(Token, via)` for a named
|
|
10
|
+
message-interface family. Exact schema routes take priority, then the first
|
|
11
|
+
registered matching token, then replacement/default. The [To-Do example](../../examples/todo/USER_GUIDE.md)
|
|
12
|
+
uses `TaskEvent`, an assignment token, and an exact `TaskReassigned` route.
|
|
13
|
+
|
|
14
|
+
For a standalone application, use `await server.run()` to let the framework
|
|
15
|
+
close the server on `SIGINT` or `SIGTERM` and permanently close the environment
|
|
16
|
+
after the final run-managed server retires. Embedded applications use
|
|
17
|
+
`await server.start()` and close the returned server themselves; `start()`
|
|
18
|
+
never closes the shared environment. Active caller-managed and run-managed
|
|
19
|
+
generations are exclusive, while matching `start()` or `run()` siblings share.
|
|
20
|
+
Add the
|
|
21
|
+
`browser` option with exact allowed origins and your session, authorization,
|
|
22
|
+
and trusted-context collaborators to serve Connect and gRPC-Web without
|
|
23
|
+
application listener or CORS code.
|
|
24
|
+
|
|
25
|
+
### Run complete replicas on one deployment node
|
|
26
|
+
|
|
27
|
+
For a deployment node that uses several application processes, put the same
|
|
28
|
+
application assembly in one ESM entry module and start it with
|
|
29
|
+
`ManagedServerApplication.run()`. `processCount` is an explicit deployment
|
|
30
|
+
setting. It is not inferred from CPU cores, and it has no relationship to the
|
|
31
|
+
Delivery shard count chosen by the application.
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { ManagedServerApplication, type RunningServer } from "@spine-event-engine/server";
|
|
35
|
+
|
|
36
|
+
declare function startCompleteApplicationReplica(endpoint: {
|
|
37
|
+
host: string;
|
|
38
|
+
port: number;
|
|
39
|
+
}): Promise<RunningServer>;
|
|
40
|
+
|
|
41
|
+
await ManagedServerApplication.run({
|
|
42
|
+
processCount: 4,
|
|
43
|
+
host: "0.0.0.0",
|
|
44
|
+
port: 50051,
|
|
45
|
+
moduleUrl: import.meta.url,
|
|
46
|
+
createServer: startCompleteApplicationReplica,
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`startCompleteApplicationReplica()` is application code: it configures the
|
|
51
|
+
production `ServerEnvironment`, opens the application's Delivery facility,
|
|
52
|
+
assembles every Bounded Context, adds them to `Server`, and starts that server
|
|
53
|
+
at the supplied private endpoint. See the [To-Do replica assembly](../../examples/todo/src/multi-process-replica.ts) for a concrete implementation.
|
|
54
|
+
|
|
55
|
+
When another host owns process signals, use the same options with `start()`
|
|
56
|
+
and close the returned handle from that host:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { ManagedServerApplication, type RunningServer } from "@spine-event-engine/server";
|
|
60
|
+
|
|
61
|
+
declare function startCompleteApplicationReplica(endpoint: {
|
|
62
|
+
host: string;
|
|
63
|
+
port: number;
|
|
64
|
+
}): Promise<RunningServer>;
|
|
65
|
+
|
|
66
|
+
const managed = await ManagedServerApplication.start({
|
|
67
|
+
processCount: 4,
|
|
68
|
+
host: "0.0.0.0",
|
|
69
|
+
port: 50051,
|
|
70
|
+
moduleUrl: import.meta.url,
|
|
71
|
+
createServer: startCompleteApplicationReplica,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
await managed.close();
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The parent starts four separate children. Each child runs this same entry
|
|
78
|
+
module, assembles the complete application, opens its own loopback listener,
|
|
79
|
+
and completes its local readiness work before it is admitted. `run()` first
|
|
80
|
+
waits for every configured child. Afterwards it stays ready while at least one
|
|
81
|
+
child is ready; replacements continue while it is degraded, and it becomes
|
|
82
|
+
unready if no child remains ready. A crashed child is replaced with bounded
|
|
83
|
+
exponential delay; surviving children keep serving.
|
|
84
|
+
The parent listens at the configured `host` and required nonzero `port` and
|
|
85
|
+
forwards generated unary Command and Query gRPC calls to one READY child.
|
|
86
|
+
The Coordinator fans each Gateway-owned logical subscription to the complete
|
|
87
|
+
replicas and merges their updates. Each child observes the application's
|
|
88
|
+
configured remote/shared Delivery facility directly; the parent does not proxy
|
|
89
|
+
Delivery. Application assembly must select the Delivery facility and shard
|
|
90
|
+
strategy appropriate to the deployment. The framework does not infer or
|
|
91
|
+
certify their provenance, and managed process count neither detects CPU cores
|
|
92
|
+
nor changes Delivery shard selection.
|
|
93
|
+
|
|
94
|
+
Children progress `STARTING → SYNCHRONIZING → READY`. During synchronization,
|
|
95
|
+
retained definitions are recreated and active streams attach before unary or
|
|
96
|
+
Delivery admission; inactive definitions do not block readiness. Shutdown
|
|
97
|
+
progresses `READY → DRAINING → CLOSED`: new unary and shard admission stops,
|
|
98
|
+
active Delivery work may finish while subscription relays remain connected,
|
|
99
|
+
then the child closes. The small parent/child channel carries only these
|
|
100
|
+
lifecycle facts, including an opaque subscription-installation acknowledgement.
|
|
101
|
+
It never carries Commands, Events, queries, subscription definitions or
|
|
102
|
+
updates, or Delivery notifications. `ManagedServerApplication.run()` owns
|
|
103
|
+
`SIGINT` and `SIGTERM` cleanup for its children. An embedding host uses
|
|
104
|
+
`ManagedServerApplication.start()` instead and closes the returned managed
|
|
105
|
+
handle itself; `start()` installs no process-signal handlers. Child listener
|
|
106
|
+
topology remains private. Use direct `Server.run()` for one explicit local
|
|
107
|
+
process or browser-oriented hosting.
|
|
108
|
+
|
|
109
|
+
For detailed contracts intended for coding agents, see the
|
|
110
|
+
[REFERENCE.md documentation for agents](REFERENCE.md).
|
|
111
|
+
|
|
112
|
+
## 💡 Why use it?
|
|
113
|
+
|
|
114
|
+
- ✅ Builds bounded contexts with Aggregates, Process Managers, and query-side
|
|
115
|
+
Projections.
|
|
116
|
+
- ✅ Validates commands before application handlers run.
|
|
117
|
+
- ✅ Exposes Command, Query, and Subscription services over native gRPC.
|
|
118
|
+
- ✅ Optionally exposes authenticated Connect and gRPC-Web browser access.
|
|
119
|
+
- ✅ Manages readiness, rollback, process signals, and ordered shutdown.
|
|
120
|
+
|
|
121
|
+
## 🧭 Understand the two event paths
|
|
122
|
+
|
|
123
|
+
Each domain bounded context has an internal paired System Context. Domain events
|
|
124
|
+
go to the domain `EventBus` and are durably appended to the domain `EventStore`.
|
|
125
|
+
System events, such as committed entity-state notifications, go only to the
|
|
126
|
+
System Context's `EventBus`; they never enter the domain EventStore. By default
|
|
127
|
+
system events are forgotten after validation and notification. Call
|
|
128
|
+
`persistSystemEvents()` when the application needs a separate optional System
|
|
129
|
+
Context event store.
|
|
130
|
+
|
|
131
|
+
Enable that option while assembling the context, before `build()` or
|
|
132
|
+
`buildAsync()`:
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
import { BoundedContext } from "@spine-event-engine/server";
|
|
136
|
+
|
|
137
|
+
const context = BoundedContext.singleTenant("Tasks").persistSystemEvents().build();
|
|
138
|
+
await context.close();
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`Stand` is the read side: it serves queries from current entity state. Exposed
|
|
142
|
+
domain-event subscriptions observe domain events on the domain `EventBus`.
|
|
143
|
+
Entity subscriptions observe `EntityStateChanged` on the paired System Context
|
|
144
|
+
`EventBus` and turn those notifications into updates. A subscription is best
|
|
145
|
+
effort, so clients use a query for initial state and recovery after reconnect
|
|
146
|
+
or a possible gap.
|
|
147
|
+
|
|
148
|
+
## 🧱 Build a bounded context
|
|
149
|
+
|
|
150
|
+
Start with an explicit tenant mode. Register entity classes after their Proto
|
|
151
|
+
model and generated handler registry are available.
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
// docs-snippet-path: packages/server/test/context/bounded-context.test.ts
|
|
155
|
+
import { BoundedContext } from "@spine-event-engine/server";
|
|
156
|
+
|
|
157
|
+
const context = await BoundedContext.singleTenant("Tasks").buildAsync();
|
|
158
|
+
await context.close();
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Use `withStorageFactory()` to choose storage. Use
|
|
162
|
+
`withGeneratedRegistryRoot()` for framework-generated repository assembly, or
|
|
163
|
+
register explicitly assembled repositories with `add()`. `buildAsync()` is the
|
|
164
|
+
normal choice for an application that uses generated handlers.
|
|
165
|
+
|
|
166
|
+
### Keep Stand subscription definitions
|
|
167
|
+
|
|
168
|
+
Stand subscription definitions use your context storage by default. One
|
|
169
|
+
`spine.client.SubscriptionRecord` is stored for each explicit subscription ID.
|
|
170
|
+
Pending definitions expire if they are not activated; reconciliation is
|
|
171
|
+
best-effort at a ten-second interval, so subscription updates are not complete
|
|
172
|
+
delivery and clients re-query after reconnects or gaps.
|
|
173
|
+
|
|
174
|
+
The storage record exposes the physical columns `status` and
|
|
175
|
+
`when_activation_expires`. Cleanup asks the provider for 26 pending records,
|
|
176
|
+
ordered by `when_activation_expires` and then ID. It deletes at most 25 expired
|
|
177
|
+
records and reports more work only when the observed 26th record is expired.
|
|
178
|
+
|
|
179
|
+
For an application-specific registry, provide one complete implementation.
|
|
180
|
+
The built context closes it.
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
import { BoundedContext, type StandSubscriptionRegistry } from "@spine-event-engine/server";
|
|
184
|
+
|
|
185
|
+
declare const registry: StandSubscriptionRegistry;
|
|
186
|
+
|
|
187
|
+
const context = BoundedContext.singleTenant("Tasks").withSubscriptionRegistry(registry).build();
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
`InMemorySubscriptionRegistry` is useful in local development and tests. When a
|
|
191
|
+
context using it is attached to a production `ServerEnvironment`, it emits one
|
|
192
|
+
warning naming the context because definitions disappear on restart; startup
|
|
193
|
+
still continues.
|
|
194
|
+
|
|
195
|
+
### Managed subscription fan-out
|
|
196
|
+
|
|
197
|
+
`SubscriptionService` remains a best-effort live stream: it has no replay or
|
|
198
|
+
completeness promise. In standalone mode, its default registry is persistent.
|
|
199
|
+
In managed complete-replica mode, the framework owns every child `Server` and
|
|
200
|
+
requires each actual Bounded Context registry to be exactly an
|
|
201
|
+
`InMemorySubscriptionRegistry`; persistent and custom registries are rejected
|
|
202
|
+
and the assembled child is closed before READY.
|
|
203
|
+
|
|
204
|
+
The Gateway alone owns durable logical subscription bindings. It rehydrates
|
|
205
|
+
them after restart, while its Coordinator fans a definition out to READY
|
|
206
|
+
replicas, merges their live updates, and propagates cancellation. Definition
|
|
207
|
+
payloads never cross parent/child IPC. Late or replacement replicas first
|
|
208
|
+
synchronize retained definitions and only then become eligible for ordinary
|
|
209
|
+
traffic. Real-process event and Delivery handoff verification is documented
|
|
210
|
+
separately from this server configuration guide.
|
|
211
|
+
|
|
212
|
+
### Follow one definition from creation to cleanup
|
|
213
|
+
|
|
214
|
+
The registry stores a definition first, then makes it active. Applications use
|
|
215
|
+
the generated `SubscriptionId` throughout the lifecycle. `get()` and
|
|
216
|
+
`snapshot()` return cloned values, so treat them as observations rather than
|
|
217
|
+
mutable working objects. Their Protobuf byte arrays remain mutable, but do not
|
|
218
|
+
alias stored or caller bytes.
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
import { create } from "@bufbuild/protobuf";
|
|
222
|
+
import { InMemorySubscriptionRegistry } from "@spine-event-engine/server";
|
|
223
|
+
import { SubscriptionIdSchema, SubscriptionSchema } from "@spine-event-engine/proto/client";
|
|
224
|
+
|
|
225
|
+
const registry = new InMemorySubscriptionRegistry();
|
|
226
|
+
const id = create(SubscriptionIdSchema, { value: "daily-report" });
|
|
227
|
+
const definition = create(SubscriptionSchema, {
|
|
228
|
+
id,
|
|
229
|
+
topic: { id: { value: "reports.daily" } },
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
await registry.create(definition); // pending for up to 30 seconds
|
|
233
|
+
await registry.activate(id); // active definitions do not expire
|
|
234
|
+
const current = await registry.get(id); // one isolated snapshot
|
|
235
|
+
const all = await registry.snapshot(); // complete, identifier-sorted copies
|
|
236
|
+
if (current !== undefined) await registry.delete(id);
|
|
237
|
+
await registry.cleanup(); // removes at most 25 expired pending entries
|
|
238
|
+
await registry.close();
|
|
239
|
+
|
|
240
|
+
void all;
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
The built-in durable registry requires record storage with atomic
|
|
244
|
+
compare-and-set. Context construction fails fast when the selected storage
|
|
245
|
+
provider cannot supply that capability; it never silently falls back to memory.
|
|
246
|
+
|
|
247
|
+
Provider configuration for `SubscriptionRecord` applies to these definitions.
|
|
248
|
+
For example, a MySQL factory can assign `SubscriptionRecordSchema` to a chosen
|
|
249
|
+
table, and a Datastore factory can provide storage for that same schema. The
|
|
250
|
+
registry uses the configured provider when the context is built.
|
|
251
|
+
|
|
252
|
+
An aggregate receives a generated command type in an `@Assign` method. The
|
|
253
|
+
generator discovers this method and writes the registry used by `buildAsync()`;
|
|
254
|
+
run `spine-proto handlers` after changing it.
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
// docs-snippet-path: examples/orders/src/index.ts
|
|
258
|
+
import { create } from "@bufbuild/protobuf";
|
|
259
|
+
import { Aggregate, Assign } from "@spine-event-engine/server";
|
|
260
|
+
import {
|
|
261
|
+
CreateOrderSchema,
|
|
262
|
+
type CreateOrder,
|
|
263
|
+
} from "../generated/spine/examples/orders/commands_pb.js";
|
|
264
|
+
import { OrderSchema } from "../generated/spine/examples/orders/entities_pb.js";
|
|
265
|
+
import {
|
|
266
|
+
OrderCreatedSchema,
|
|
267
|
+
type OrderCreated,
|
|
268
|
+
} from "../generated/spine/examples/orders/events_pb.js";
|
|
269
|
+
|
|
270
|
+
class OrderAggregate extends Aggregate<string, typeof OrderSchema> {
|
|
271
|
+
@Assign createOrder(command: CreateOrder): OrderCreated {
|
|
272
|
+
this.update((draft) =>
|
|
273
|
+
Object.assign(draft, create(OrderSchema, { id: this.id, skuId: command.skuId })),
|
|
274
|
+
);
|
|
275
|
+
return create(OrderCreatedSchema, { id: this.id, skuId: command.skuId });
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## 🚀 Run a standalone server
|
|
281
|
+
|
|
282
|
+
`Server` exposes the supplied built contexts through the Spine command, query,
|
|
283
|
+
and subscription services. Its default host is local-only.
|
|
284
|
+
|
|
285
|
+
```ts
|
|
286
|
+
// docs-snippet-path: packages/server/src/server/server.ts
|
|
287
|
+
import { Server, type BoundedContext } from "@spine-event-engine/server";
|
|
288
|
+
|
|
289
|
+
declare const context: BoundedContext;
|
|
290
|
+
const server = new Server({ contexts: [context], port: 0 });
|
|
291
|
+
const running = await server.run();
|
|
292
|
+
console.log(running.baseUrl);
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Use `run()` for a standalone process. It installs framework `SIGINT` and
|
|
296
|
+
`SIGTERM` shutdown; siblings share its generation, and a failed final close is
|
|
297
|
+
retryable by a later signal or `close()`. Use `start()` when another host handles
|
|
298
|
+
the process lifecycle; it shares only caller-managed siblings and never closes
|
|
299
|
+
the environment.
|
|
300
|
+
|
|
301
|
+
```ts
|
|
302
|
+
import { Server, type BoundedContext } from "@spine-event-engine/server";
|
|
303
|
+
|
|
304
|
+
declare const context: BoundedContext;
|
|
305
|
+
const running = await new Server({ contexts: [context], port: 8080 }).run();
|
|
306
|
+
console.log(`Spine server ready at ${running.baseUrl}`);
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## Delivery and subscriptions
|
|
310
|
+
|
|
311
|
+
An `@Assign` command for an Aggregate or Process Manager first persists in that
|
|
312
|
+
Entity's Inbox. Local intake can then directly drain the persisted work in the
|
|
313
|
+
current request path. With an attached `ServerEnvironment`, delivery workers
|
|
314
|
+
replay admitted Inbox work through the same path. In shared remote delivery,
|
|
315
|
+
every matching node may attempt a shard, while one active lease owner performs
|
|
316
|
+
one bounded drain page; later drains can have a different lease owner. A page
|
|
317
|
+
bound does not cap all pending work: the owner may take later pages until the
|
|
318
|
+
delivery policy releases the shard. Events and later Process Manager commands
|
|
319
|
+
use the same path. See [REFERENCE.md](REFERENCE.md) for operational delivery
|
|
320
|
+
details.
|
|
321
|
+
|
|
322
|
+
Domain-event subscriptions observe exposed domain events on the domain
|
|
323
|
+
`EventBus`. Entity subscriptions observe committed `EntityStateChanged`
|
|
324
|
+
notifications on the paired System Context `EventBus`. Stand's default registry
|
|
325
|
+
uses the application's `StorageFactory`; a builder may supply another
|
|
326
|
+
implementation. A definition is pending for at most 30 seconds, active
|
|
327
|
+
definitions have no framework TTL, and cancellation physically deletes the
|
|
328
|
+
definition. Nodes reconcile their local listeners from a complete
|
|
329
|
+
snapshot every 10 seconds. Active streams and queues are process-local.
|
|
330
|
+
|
|
331
|
+
### Serve browser clients
|
|
332
|
+
|
|
333
|
+
Add `browser` configuration to expose Connect and gRPC-Web without writing a
|
|
334
|
+
listener, router, CORS middleware, or shutdown coordinator. The application
|
|
335
|
+
still supplies its authorization policy and trusted actor context resolver.
|
|
336
|
+
Choose exactly one admission mode:
|
|
337
|
+
|
|
338
|
+
- supply `sessions` when browser callers must authenticate; or
|
|
339
|
+
- set `publicAccess: true` for an intentionally public endpoint with no login
|
|
340
|
+
session.
|
|
341
|
+
|
|
342
|
+
Never supply both. Public access does not manufacture a session or an expiry.
|
|
343
|
+
The application policy must still authorize every operation and rebuild trusted
|
|
344
|
+
actor and tenant context from request facts.
|
|
345
|
+
|
|
346
|
+
For a separately hosted TS or JVM backend, set `browser.backend.baseUrl` to its
|
|
347
|
+
canonical HTTP(S) origin. The gateway keeps `ResolveContext` local and forwards
|
|
348
|
+
the five application RPCs only after the configured admission, authorization,
|
|
349
|
+
and trusted-context rewrite. An authenticated standalone mode requires explicit
|
|
350
|
+
subscription bindings: local development and tests may explicitly supply an
|
|
351
|
+
in-memory binding, while production also needs a type registry and named
|
|
352
|
+
`DurableSubscriptionBindings`. A public standalone mode instead owns
|
|
353
|
+
process-local bindings. A Gateway restart drops those definitions, so browsers
|
|
354
|
+
reconnect, query authoritative state, and subscribe again. The external backend
|
|
355
|
+
remains under caller control.
|
|
356
|
+
|
|
357
|
+
Use `browser.backend.baseUrls` for non-empty unique origins. Unary calls use
|
|
358
|
+
round-robin without retry; native streams fan out best-effort, so clients
|
|
359
|
+
re-query authoritative state after duplicate updates or a generic loss notice.
|
|
360
|
+
|
|
361
|
+
Applications may add only explicit OAuth-style callbacks with `authRoutes`.
|
|
362
|
+
Each route uses an exact `GET`/`POST` path, per-route origins, a safe body limit,
|
|
363
|
+
and a finite timeout. This is not a general HTTP router; use it for bounded
|
|
364
|
+
identity exchanges and validate OAuth state in the application handler.
|
|
365
|
+
The host admits at most 64 active auth requests by default (or the configured
|
|
366
|
+
positive `maxActiveAuthRequests`); excess requests receive 503 before a handler
|
|
367
|
+
runs. A route timeout covers request intake, handler work, and response
|
|
368
|
+
transfer. Auth response bodies are bounded by `writeMaxBytes`; an overflow
|
|
369
|
+
returns 413 without copying application headers.
|
|
370
|
+
|
|
371
|
+
```ts
|
|
372
|
+
import {
|
|
373
|
+
DurableSubscriptionBindings,
|
|
374
|
+
Server,
|
|
375
|
+
type BoundedContext,
|
|
376
|
+
} from "@spine-event-engine/server";
|
|
377
|
+
|
|
378
|
+
declare const context: BoundedContext;
|
|
379
|
+
declare const sessions: import("@spine-event-engine/auth").SessionResolver;
|
|
380
|
+
declare const authorize: import("@spine-event-engine/auth").AuthorizationPolicy["authorize"];
|
|
381
|
+
declare const contextResolver: import("@spine-event-engine/auth").ContextResolver;
|
|
382
|
+
declare const clock: import("@spine-event-engine/auth").Clock;
|
|
383
|
+
declare const registryStorage: import("@spine-event-engine/storage").StorageFactory;
|
|
384
|
+
|
|
385
|
+
const bindings = new DurableSubscriptionBindings({
|
|
386
|
+
storageFactory: registryStorage,
|
|
387
|
+
namespace: "my-app",
|
|
388
|
+
nextId: () => crypto.randomUUID(),
|
|
389
|
+
cleanup: async () => undefined,
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
const running = await new Server({
|
|
393
|
+
contexts: [context],
|
|
394
|
+
port: 8090,
|
|
395
|
+
browser: {
|
|
396
|
+
origins: ["http://127.0.0.1:5173"],
|
|
397
|
+
sessions,
|
|
398
|
+
authorize,
|
|
399
|
+
contexts: contextResolver,
|
|
400
|
+
clock,
|
|
401
|
+
bindings,
|
|
402
|
+
},
|
|
403
|
+
}).run();
|
|
404
|
+
|
|
405
|
+
void running;
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
For an intentionally public browser application, replace `sessions` and
|
|
409
|
+
`bindings` in the example with `publicAccess: true`. Do not supply both modes.
|
|
410
|
+
Public mode uses process-local bindings and has no synthetic session expiry.
|
|
411
|
+
|
|
412
|
+
In production, authenticated browser access requires
|
|
413
|
+
`DurableSubscriptionBindings`. Startup rejects missing or in-memory bindings
|
|
414
|
+
before opening a listener. The registry uses the storage factory that your
|
|
415
|
+
application supplies and closes only the handle, so you can use a separate
|
|
416
|
+
factory from application-data storage or intentionally share one. Its namespace
|
|
417
|
+
separates applications sharing a provider. Combined authenticated browser mode
|
|
418
|
+
may omit bindings to use an in-memory registry; authenticated standalone mode
|
|
419
|
+
must always supply them explicitly.
|
|
420
|
+
|
|
421
|
+
Each durable binding stores one approved `GatewayAuthenticatedSubscription`:
|
|
422
|
+
its public ID, complete subscription, and expiry. The stored Topic retains the
|
|
423
|
+
trusted Actor and Tenant, so Activate and Cancel must match that context.
|
|
424
|
+
|
|
425
|
+
- Cancellation cleans up the backend before deleting the record; a failure
|
|
426
|
+
leaves it available for retry.
|
|
427
|
+
- Request-time cleanup is finite; a maintenance loop may call
|
|
428
|
+
`purgeExpired(nowMs)`.
|
|
429
|
+
- Restart rehydrates unexpired definitions. An expired definition is removed
|
|
430
|
+
only after cleanup succeeds.
|
|
431
|
+
|
|
432
|
+
This is a single-Gateway persistence model. It has no multi-process
|
|
433
|
+
coordination, quotas, reservations, or durable fingerprints. Configure MySQL
|
|
434
|
+
with `setTableName(GatewayAuthenticatedSubscriptionSchema, table)` or Datastore
|
|
435
|
+
with `useRecordStorage(GatewayAuthenticatedSubscriptionSchema, creator)`.
|
|
436
|
+
|
|
437
|
+
The server validates commands before handler code runs. Invalid payloads are
|
|
438
|
+
returned as `COMMAND_VALIDATION_ERROR`; invalid state transitions are returned
|
|
439
|
+
as `COMMAND_STATE_TRANSITION_VALIDATION_FAILED`. Domain rejections roll back
|
|
440
|
+
the entity change and are posted independently as typed rejection events.
|
|
441
|
+
|
|
442
|
+
## 🔎 Read state and updates
|
|
443
|
+
|
|
444
|
+
`context.stand()` provides framework-managed current entity state reads and
|
|
445
|
+
in-process update subscriptions. `SpineServices` translates the standard query
|
|
446
|
+
and subscription service requests for registered state routes.
|
|
447
|
+
|
|
448
|
+
```ts
|
|
449
|
+
// docs-snippet-path: packages/server/test/context/bounded-context.test.ts
|
|
450
|
+
import type { BoundedContext } from "@spine-event-engine/server";
|
|
451
|
+
|
|
452
|
+
declare const context: BoundedContext;
|
|
453
|
+
const stand = context.stand();
|
|
454
|
+
void stand;
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
Subscriptions and active queues are local-process resources. A client must
|
|
458
|
+
re-query after reconnect; this package does not promise cluster-complete
|
|
459
|
+
updates, event replay for subscriptions, or exactly-once observation.
|
|
460
|
+
|
|
461
|
+
## 🌐 Delivery and environment
|
|
462
|
+
|
|
463
|
+
`DeliveryBuilder` builds a delivery from its inbox, work registry, shard, and
|
|
464
|
+
page-size choices. `DeliverySupervisor({ source, delivery, onMessage })` receives
|
|
465
|
+
the separate public `DeliverySource` used to observe remote shards.
|
|
466
|
+
`Environment` resolves the Node deployment profile; the singleton
|
|
467
|
+
`ServerEnvironment` exposes configured storage, optional delivery/tracing
|
|
468
|
+
facilities, and its process lifecycle. They do not create a production topology
|
|
469
|
+
or durable scheduler for you.
|
|
470
|
+
|
|
471
|
+
`DeliveryMonitor` is the customizable explicit failure-policy seam. Its hooks
|
|
472
|
+
may return a value or a promise; a failed reception defaults to durable
|
|
473
|
+
`markDelivered()` and continues independent targets. Applications may instead
|
|
474
|
+
select one immediate `repeatDispatching()` action. The monitor adds no attempts,
|
|
475
|
+
quarantine, receipts, markers, timers, backoff, dead-letter storage, or
|
|
476
|
+
scheduler policy. Each delivery lifetime has an opaque `WorkerId`; graceful
|
|
477
|
+
stop waits for admitted work to settle before releasing its shard session.
|
|
478
|
+
|
|
479
|
+
The default Entity Inbox has one shard. Configure more shards through the public
|
|
480
|
+
builder chain:
|
|
481
|
+
|
|
482
|
+
```ts
|
|
483
|
+
import { BoundedContext, UniformAcrossAllShards } from "@spine-event-engine/server";
|
|
484
|
+
|
|
485
|
+
const context = BoundedContext.singleTenant("Tasks")
|
|
486
|
+
.withDeliveryStrategy(UniformAcrossAllShards.forNumber(3))
|
|
487
|
+
.build();
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
Aggregate commands and Process Manager commands/events
|
|
491
|
+
derive a target shard themselves and persist before handler replay. Local delivery
|
|
492
|
+
drains in the posting request; after `ServerEnvironment` supplies delivery ports,
|
|
493
|
+
posting persists and acknowledges while a worker replays later. Projection delivery
|
|
494
|
+
is separate.
|
|
495
|
+
|
|
496
|
+
An openable `ServerEnvironmentDelivery` is opened before the first environment
|
|
497
|
+
attachment is admitted, then supplies its inbox and work-registry ports to both
|
|
498
|
+
the existing finite and supervisor delivery paths; a close-only local delivery
|
|
499
|
+
remains supported. The environment stops attachments before closing delivery,
|
|
500
|
+
tracing, and storage in that order. Use `RemoteDelivery` from
|
|
501
|
+
`@spine-event-engine/delivery-client` when an application selects a remote
|
|
502
|
+
delivery endpoint with direct authoritative removal.
|
|
503
|
+
|
|
504
|
+
Shard ownership excludes concurrent delivery within a shard. Pending and
|
|
505
|
+
delivered Inbox rows are stored directly, and delivered rows are the
|
|
506
|
+
deduplication fact. Handler effects and the delivered-row compare-and-set are
|
|
507
|
+
not transactional: a lost acknowledgement can redeliver after restart, so
|
|
508
|
+
downstream handling must be idempotent.
|
|
509
|
+
|
|
510
|
+
## ⚠️ Runtime boundaries
|
|
511
|
+
|
|
512
|
+
The package is Node-only. It does not choose an identity provider, session
|
|
513
|
+
database, production storage, TLS setup, or durable multi-machine delivery.
|
|
514
|
+
Subscription updates are notices rather than complete history, so clients
|
|
515
|
+
re-query entity state after reconnecting.
|
|
516
|
+
|
|
517
|
+
## 🔗 Learn more
|
|
518
|
+
|
|
519
|
+
- [End-user guide](../../docs/USER_GUIDE.md)
|
|
520
|
+
- [Authentication](../auth/README.md)
|
|
521
|
+
- [Node client](../client-node/README.md)
|
|
522
|
+
- [Browser client](../client-web/README.md)
|
|
523
|
+
- [Message Board application](../../examples/message-board/README.md)
|
|
524
|
+
- [Reference for coding agents](REFERENCE.md)
|
|
525
|
+
|
|
526
|
+
## Cross-context external events
|
|
527
|
+
|
|
528
|
+
Every built `BoundedContext` owns one private integration broker. It publishes
|
|
529
|
+
domestic events only when another context has requested them, and an imported
|
|
530
|
+
event is delivered only to handlers declared with `External<T>`. The marker is
|
|
531
|
+
type-only and transparent at runtime:
|
|
532
|
+
|
|
533
|
+
```ts
|
|
534
|
+
// docs-snippet-path: examples/todo/src/index.ts
|
|
535
|
+
import { Subscribe, type External } from "@spine-event-engine/server";
|
|
536
|
+
import type { TaskCreated } from "../generated/spine/examples/todo/task_events_pb.js";
|
|
537
|
+
|
|
538
|
+
class TaskProjection {
|
|
539
|
+
@Subscribe
|
|
540
|
+
onImported(event: External<TaskCreated>): void {
|
|
541
|
+
void event;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
`External<T>` must be the direct first receptor parameter. External commands
|
|
547
|
+
are invalid; external events, rejections, and supported state subscriptions
|
|
548
|
+
are valid. A context publishes only the event types requested by interested
|
|
549
|
+
contexts. This requested-only export, plus domestic/external filtering, is the
|
|
550
|
+
loop-prevention rule: there is no hop counter, broker Inbox, retry, replay,
|
|
551
|
+
deduplication, or producer election. Delivery is best effort; many consumers
|
|
552
|
+
may observe one domain producer at a time.
|
|
553
|
+
|
|
554
|
+
The broker uses private `TransportFactory` message channels.
|
|
555
|
+
`ServerEnvironment` owns one process-wide `InMemoryTransportFactory` by
|
|
556
|
+
default for private IntegrationBroker message channels, including production.
|
|
557
|
+
An application may supply `integrationChannelFactory` to override that local
|
|
558
|
+
channel factory. Production resolution still requires `storageFactory` and the
|
|
559
|
+
complete application `typeRegistry`; it does not fall back to in-memory storage
|
|
560
|
+
or to the core-only registry. No application signal-binding setting is part of
|
|
561
|
+
the environment. The channel factory is private IntegrationBroker plumbing,
|
|
562
|
+
not a general runtime transport.
|
|
563
|
+
|
|
564
|
+
`ThirdPartyContext.singleTenant(name)` or `.multitenant(name)` creates the
|
|
565
|
+
public import facade. `emittedEvent(event, actor)` requires a generated event,
|
|
566
|
+
preserves the actor tenant policy, and uses the actor timestamp (or fills one
|
|
567
|
+
for a `UserId`). An unknown local event schema rejects before publication; a
|
|
568
|
+
valid event with no current interested context is a successful no-op. Close the
|
|
569
|
+
hidden context when imports are finished.
|