@spine-event-engine/server 2.0.0-snapshot.2 → 2.0.0-snapshot.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +225 -518
- package/REFERENCE.md +498 -49
- package/dist/browser/browser-server.d.ts +95 -0
- package/dist/browser/browser-server.d.ts.map +1 -0
- package/dist/{server → browser}/browser-server.js +93 -7
- package/dist/browser/browser-server.js.map +1 -0
- package/dist/browser/durable-subscription-bindings.d.ts.map +1 -0
- package/dist/browser/durable-subscription-bindings.js.map +1 -0
- package/dist/browser/index.d.ts +12 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +24 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/options.d.ts +183 -0
- package/dist/browser/options.d.ts.map +1 -0
- package/dist/browser/options.js +15 -0
- package/dist/browser/options.js.map +1 -0
- package/dist/bus/command-bus.d.ts +2 -0
- package/dist/bus/command-bus.d.ts.map +1 -1
- package/dist/bus/command-bus.js +36 -1
- package/dist/bus/command-bus.js.map +1 -1
- package/dist/bus/event-dispatcher-registry.d.ts +2 -2
- package/dist/bus/event-dispatcher-registry.js +3 -3
- package/dist/bus/event-dispatcher-registry.js.map +1 -1
- package/dist/context/bounded-context.d.ts +238 -83
- package/dist/context/bounded-context.d.ts.map +1 -1
- package/dist/context/bounded-context.js +1083 -286
- package/dist/context/bounded-context.js.map +1 -1
- package/dist/context/effective-tenant.d.ts +9 -0
- package/dist/context/effective-tenant.d.ts.map +1 -0
- package/dist/context/effective-tenant.js +58 -0
- package/dist/context/effective-tenant.js.map +1 -0
- package/dist/context/entity-inbox.d.ts.map +1 -1
- package/dist/context/entity-inbox.js +24 -17
- package/dist/context/entity-inbox.js.map +1 -1
- package/dist/context/local-inbox-handoff.d.ts +7 -3
- package/dist/context/local-inbox-handoff.d.ts.map +1 -1
- package/dist/context/local-inbox-handoff.js +32 -26
- package/dist/context/local-inbox-handoff.js.map +1 -1
- package/dist/context/projection-handoff.d.ts.map +1 -1
- package/dist/context/projection-handoff.js +36 -31
- package/dist/context/projection-handoff.js.map +1 -1
- package/dist/context/tenant-index.d.ts.map +1 -1
- package/dist/context/tenant-index.js +72 -2
- package/dist/context/tenant-index.js.map +1 -1
- package/dist/delivery/delivery-ports.d.ts +14 -4
- package/dist/delivery/delivery-ports.d.ts.map +1 -1
- package/dist/delivery/delivery.d.ts +19 -21
- package/dist/delivery/delivery.d.ts.map +1 -1
- package/dist/delivery/delivery.js +541 -158
- package/dist/delivery/delivery.js.map +1 -1
- package/dist/delivery/inbox-storage.d.ts +12 -3
- package/dist/delivery/inbox-storage.d.ts.map +1 -1
- package/dist/delivery/inbox-storage.js +160 -67
- package/dist/delivery/inbox-storage.js.map +1 -1
- package/dist/delivery/inbox.d.ts +9 -0
- package/dist/delivery/inbox.d.ts.map +1 -1
- package/dist/delivery/inbox.js +11 -0
- package/dist/delivery/inbox.js.map +1 -1
- package/dist/delivery/sharded-work-registry.d.ts.map +1 -1
- package/dist/delivery/sharded-work-registry.js +2 -1
- package/dist/delivery/sharded-work-registry.js.map +1 -1
- package/dist/entity/entity-metadata.d.ts +14 -5
- package/dist/entity/entity-metadata.d.ts.map +1 -1
- package/dist/entity/entity-metadata.js +38 -2
- package/dist/entity/entity-metadata.js.map +1 -1
- package/dist/entity/entity-storage-descriptor.d.ts +25 -5
- package/dist/entity/entity-storage-descriptor.d.ts.map +1 -1
- package/dist/entity/entity-storage-descriptor.js +69 -23
- package/dist/entity/entity-storage-descriptor.js.map +1 -1
- package/dist/entity/entity-transaction.d.ts +46 -42
- package/dist/entity/entity-transaction.d.ts.map +1 -1
- package/dist/entity/entity-transaction.js +130 -35
- package/dist/entity/entity-transaction.js.map +1 -1
- package/dist/entity/entity.d.ts +243 -98
- package/dist/entity/entity.d.ts.map +1 -1
- package/dist/entity/entity.js +405 -226
- package/dist/entity/entity.js.map +1 -1
- package/dist/entity/spec-scanner.d.ts +13 -1
- package/dist/entity/spec-scanner.d.ts.map +1 -1
- package/dist/entity/spec-scanner.js +20 -3
- package/dist/entity/spec-scanner.js.map +1 -1
- package/dist/handler/command-registration-readiness.d.ts +6 -6
- package/dist/handler/command-registration-readiness.d.ts.map +1 -1
- package/dist/handler/command-registration-readiness.js +4 -2
- package/dist/handler/command-registration-readiness.js.map +1 -1
- package/dist/handler/declared-rejections.d.ts +11 -0
- package/dist/handler/declared-rejections.d.ts.map +1 -0
- package/dist/handler/declared-rejections.js +34 -0
- package/dist/handler/declared-rejections.js.map +1 -0
- package/dist/handler/event-registration-readiness.d.ts +13 -56
- package/dist/handler/event-registration-readiness.d.ts.map +1 -1
- package/dist/handler/event-registration-readiness.js +23 -43
- package/dist/handler/event-registration-readiness.js.map +1 -1
- package/dist/handler/generated-handler-registry.d.ts +72 -33
- package/dist/handler/generated-handler-registry.d.ts.map +1 -1
- package/dist/handler/generated-handler-registry.js +421 -43
- package/dist/handler/generated-handler-registry.js.map +1 -1
- package/dist/handler/generated-registry-discovery.d.ts.map +1 -1
- package/dist/handler/generated-registry-discovery.js +2 -4
- package/dist/handler/generated-registry-discovery.js.map +1 -1
- package/dist/handler/handler-decorators.d.ts +91 -37
- package/dist/handler/handler-decorators.d.ts.map +1 -1
- package/dist/handler/handler-decorators.js +124 -31
- package/dist/handler/handler-decorators.js.map +1 -1
- package/dist/handler/handler-metadata.d.ts +105 -60
- package/dist/handler/handler-metadata.d.ts.map +1 -1
- package/dist/handler/handler-metadata.js +275 -57
- package/dist/handler/handler-metadata.js.map +1 -1
- package/dist/handler/registration-readiness-metadata.d.ts +4 -0
- package/dist/handler/registration-readiness-metadata.d.ts.map +1 -1
- package/dist/handler/registration-readiness-metadata.js +84 -2
- package/dist/handler/registration-readiness-metadata.js.map +1 -1
- package/dist/handler/standalone.d.ts +25 -0
- package/dist/handler/standalone.d.ts.map +1 -0
- package/dist/handler/standalone.js +38 -0
- package/dist/handler/standalone.js.map +1 -0
- package/dist/index.d.ts +9 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/repository/command-routing.d.ts +63 -10
- package/dist/repository/command-routing.d.ts.map +1 -1
- package/dist/repository/command-routing.js +41 -8
- package/dist/repository/command-routing.js.map +1 -1
- package/dist/repository/event-routing.d.ts +64 -9
- package/dist/repository/event-routing.d.ts.map +1 -1
- package/dist/repository/event-routing.js +42 -7
- package/dist/repository/event-routing.js.map +1 -1
- package/dist/repository/primitive-id.d.ts +12 -23
- package/dist/repository/primitive-id.d.ts.map +1 -1
- package/dist/repository/primitive-id.js +2 -27
- package/dist/repository/primitive-id.js.map +1 -1
- package/dist/repository/repository.d.ts +317 -87
- package/dist/repository/repository.d.ts.map +1 -1
- package/dist/repository/repository.js +3746 -856
- package/dist/repository/repository.js.map +1 -1
- package/dist/repository/state-update-routing.d.ts +67 -8
- package/dist/repository/state-update-routing.d.ts.map +1 -1
- package/dist/repository/state-update-routing.js +45 -6
- package/dist/repository/state-update-routing.js.map +1 -1
- package/dist/runtime/runtime.js +1 -1
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/signal-metadata.d.ts +22 -47
- package/dist/runtime/signal-metadata.d.ts.map +1 -1
- package/dist/runtime/signal-metadata.js +34 -69
- package/dist/runtime/signal-metadata.js.map +1 -1
- package/dist/runtime/signal-publisher.d.ts +144 -0
- package/dist/runtime/signal-publisher.d.ts.map +1 -0
- package/dist/runtime/signal-publisher.js +248 -0
- package/dist/runtime/signal-publisher.js.map +1 -0
- package/dist/runtime/standalone-handler-runtime.d.ts +130 -0
- package/dist/runtime/standalone-handler-runtime.d.ts.map +1 -0
- package/dist/runtime/standalone-handler-runtime.js +382 -0
- package/dist/runtime/standalone-handler-runtime.js.map +1 -0
- package/dist/server/environment-attachment.d.ts.map +1 -1
- package/dist/server/environment-attachment.js +2 -1
- package/dist/server/environment-attachment.js.map +1 -1
- package/dist/server/environment-delivery-worker.d.ts.map +1 -1
- package/dist/server/environment-delivery-worker.js +21 -12
- package/dist/server/environment-delivery-worker.js.map +1 -1
- package/dist/server/managed-server-application.d.ts +4 -1
- package/dist/server/managed-server-application.d.ts.map +1 -1
- package/dist/server/managed-server-application.js +2 -2
- package/dist/server/managed-server-application.js.map +1 -1
- package/dist/server/node-coordinator.js +2 -2
- package/dist/server/node-coordinator.js.map +1 -1
- package/dist/server/server-log.d.ts.map +1 -1
- package/dist/server/server-log.js +4 -0
- package/dist/server/server-log.js.map +1 -1
- package/dist/server/server.d.ts +9 -177
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +428 -178
- package/dist/server/server.js.map +1 -1
- package/dist/services/query-reader.d.ts +19 -0
- package/dist/services/query-reader.d.ts.map +1 -0
- package/dist/services/query-reader.js +43 -0
- package/dist/services/query-reader.js.map +1 -0
- package/dist/services/registered-targets.d.ts +73 -0
- package/dist/services/registered-targets.d.ts.map +1 -0
- package/dist/services/registered-targets.js +127 -0
- package/dist/services/registered-targets.js.map +1 -0
- package/dist/services/spine-services.d.ts +26 -12
- package/dist/services/spine-services.d.ts.map +1 -1
- package/dist/services/spine-services.js +1047 -969
- package/dist/services/spine-services.js.map +1 -1
- package/dist/spi/delivery.d.ts +5 -0
- package/dist/spi/delivery.d.ts.map +1 -0
- package/dist/spi/delivery.js +18 -0
- package/dist/spi/delivery.js.map +1 -0
- package/dist/spi/handler-registry.d.ts +8 -0
- package/dist/spi/handler-registry.d.ts.map +1 -0
- package/dist/spi/handler-registry.js +15 -0
- package/dist/spi/handler-registry.js.map +1 -0
- package/dist/stand/stand.d.ts +73 -1
- package/dist/stand/stand.d.ts.map +1 -1
- package/dist/stand/stand.js +221 -48
- package/dist/stand/stand.js.map +1 -1
- package/dist/stand/subscription-observer.d.ts.map +1 -1
- package/dist/stand/subscription-observer.js +5 -12
- package/dist/stand/subscription-observer.js.map +1 -1
- package/dist/stand/subscription-registry.js +1 -1
- package/dist/stand/subscription-registry.js.map +1 -1
- package/dist/testing/index.d.ts +25 -3
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +23 -3
- package/dist/testing/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +33 -23
- package/dist/handler/build-time-handler-analyzer.d.ts +0 -150
- package/dist/handler/build-time-handler-analyzer.d.ts.map +0 -1
- package/dist/handler/build-time-handler-analyzer.js +0 -1101
- package/dist/handler/build-time-handler-analyzer.js.map +0 -1
- package/dist/handler/generated-registry-writer.d.ts +0 -58
- package/dist/handler/generated-registry-writer.d.ts.map +0 -1
- package/dist/handler/generated-registry-writer.js +0 -458
- package/dist/handler/generated-registry-writer.js.map +0 -1
- package/dist/handler/handler-codegen.d.ts +0 -48
- package/dist/handler/handler-codegen.d.ts.map +0 -1
- package/dist/handler/handler-codegen.js +0 -214
- package/dist/handler/handler-codegen.js.map +0 -1
- package/dist/server/browser-server.d.ts +0 -49
- package/dist/server/browser-server.d.ts.map +0 -1
- package/dist/server/browser-server.js.map +0 -1
- package/dist/server/durable-subscription-bindings.d.ts.map +0 -1
- package/dist/server/durable-subscription-bindings.js.map +0 -1
- /package/dist/{server → browser}/durable-subscription-bindings.d.ts +0 -0
- /package/dist/{server → browser}/durable-subscription-bindings.js +0 -0
package/REFERENCE.md
CHANGED
|
@@ -1,6 +1,81 @@
|
|
|
1
1
|
# @spine-event-engine/server reference
|
|
2
2
|
|
|
3
|
-
This reference
|
|
3
|
+
This reference gives application developers and coding agents the detailed
|
|
4
|
+
server contracts. For an introduction and examples, start with the
|
|
5
|
+
[server README](README.md).
|
|
6
|
+
|
|
7
|
+
## Testing entry point
|
|
8
|
+
|
|
9
|
+
`@spine-event-engine/server/testing` is for framework-package tests and controlled
|
|
10
|
+
in-process fixtures. It exposes test reset and integration-frame helpers; managed-host
|
|
11
|
+
internals remain repository-only fixture code. This entrypoint is deliberately outside
|
|
12
|
+
the end-user root API and may change with test infrastructure rather than normal server
|
|
13
|
+
compatibility guarantees.
|
|
14
|
+
|
|
15
|
+
## Delivery SPI
|
|
16
|
+
|
|
17
|
+
Delivery adapters import `conditionalPickUp` from
|
|
18
|
+
`@spine-event-engine/server/spi/delivery`. It registers or looks up an optional
|
|
19
|
+
conditional-pickup capability for a `DeliveryWorkRegistry`; it is not part of
|
|
20
|
+
the server root API and does not independently create delivery work.
|
|
21
|
+
|
|
22
|
+
## Generated handler-registry SPI
|
|
23
|
+
|
|
24
|
+
Generated model modules import only their registry data types from
|
|
25
|
+
`@spine-event-engine/server/spi/handler-registry`. The stable contract is the
|
|
26
|
+
unversioned `GeneratedHandlerRegistry` receiver collection, receiver records,
|
|
27
|
+
handler records, kinds, arity, and optional `where` data. The subpath has no
|
|
28
|
+
runtime values. Server-side ingestion, validation errors, and their error codes
|
|
29
|
+
are implementation details; generated code must not depend on them. Retired
|
|
30
|
+
versioned registries are rejected and must be regenerated.
|
|
31
|
+
|
|
32
|
+
## Standalone handlers
|
|
33
|
+
|
|
34
|
+
Generated standalone handlers extend `AbstractAssignee`, `AbstractCommander`,
|
|
35
|
+
`AbstractEventReactor`, or `AbstractEventSubscriber`. Register instances with
|
|
36
|
+
`addAssignee()`, `addCommandDispatcher()`, or `addEventDispatcher()` as their
|
|
37
|
+
role requires, then use `buildAsync()` to load generated receiver metadata and
|
|
38
|
+
match the exact constructor. Assignees consume Commands and produce Events;
|
|
39
|
+
commanders consume Commands, Events, or rejections and produce Commands;
|
|
40
|
+
reactors consume Events or rejections and may produce Events; subscribers
|
|
41
|
+
consume Events, rejections, or Entity states and produce no signal. State
|
|
42
|
+
subscribers use the System Event Bus. Produced signals are published in-process
|
|
43
|
+
on a best-effort basis; this is not an exactly-once contract.
|
|
44
|
+
|
|
45
|
+
## Entity constructor dependencies
|
|
46
|
+
|
|
47
|
+
Supply `onCreate` when an Entity constructor needs an application service:
|
|
48
|
+
`new Repository({ entityType, schema, onCreate })` for an explicit repository,
|
|
49
|
+
or `.add(EntityClass, { onCreate })` when Spine creates the repository. The
|
|
50
|
+
callback receives `EntityOptions` with the registered class's exact identifier
|
|
51
|
+
and state-schema types. Its result must be an instance of that class.
|
|
52
|
+
|
|
53
|
+
`onCreate` is required when the class cannot be constructed with framework
|
|
54
|
+
options alone. It is optional for constructors that need only `EntityOptions`, or
|
|
55
|
+
constructors whose additional parameters are optional or have defaults.
|
|
56
|
+
|
|
57
|
+
For each new object, the callback must synchronously return a fresh instance
|
|
58
|
+
and pass the supplied options unchanged to its constructor. Those options
|
|
59
|
+
contain the ID, schema, state, Spine `Version`, and lifecycle flags. The same
|
|
60
|
+
callback runs when restoring a stored Aggregate, Projection, or Process Manager,
|
|
61
|
+
and when creating a Projection during a rebuild. It is not a once-per-ID hook.
|
|
62
|
+
Queries that read stored state without constructing an Entity do not call it.
|
|
63
|
+
|
|
64
|
+
Construct and initialize shared services before building the context. Keep
|
|
65
|
+
network calls and other business work in handlers, not in `onCreate`. A thrown
|
|
66
|
+
construction error fails that dispatch before its handler runs; it is not a
|
|
67
|
+
domain rejection. An asynchronous callback or a result of the wrong class is
|
|
68
|
+
rejected. Services are neither serialized nor closed by Spine; close them in
|
|
69
|
+
application shutdown after closing the context.
|
|
70
|
+
|
|
71
|
+
Spine still connects Entity history and Process Manager queries. A constructor
|
|
72
|
+
callback does not receive the current actor or tenant. Pass these to a service
|
|
73
|
+
from the handler when needed. Queries made through the Process Manager still
|
|
74
|
+
use the handler's context. Services are passed directly; no dependency container
|
|
75
|
+
is required, and the storage format is unchanged.
|
|
76
|
+
|
|
77
|
+
See the [Entity dependency guide](https://github.com/SpineEventEngine/spine-ts/blob/master/docs/USER_GUIDE.md#give-an-entity-an-application-service)
|
|
78
|
+
and the [Projects example](https://github.com/SpineEventEngine/spine-ts/blob/master/examples/projects/README.md).
|
|
4
79
|
|
|
5
80
|
## Integration broker and event origin
|
|
6
81
|
|
|
@@ -8,8 +83,8 @@ Each `BoundedContext` creates and closes exactly one private integration broker.
|
|
|
8
83
|
The broker has distinct status, configuration, and event exchanges. Event
|
|
9
84
|
export is requested-only: a context installs a domestic publisher only for
|
|
10
85
|
event types requested by another context, and withdrawal removes only that
|
|
11
|
-
requester's interest.
|
|
12
|
-
|
|
86
|
+
requester's interest. Each event type should have one producing domain and may
|
|
87
|
+
have many consumers; the broker does not enforce that producer restriction.
|
|
13
88
|
|
|
14
89
|
The status exchange announces `BoundedContextOnline`; observing a foreign
|
|
15
90
|
announcement resends the local complete wanted-event document. The
|
|
@@ -95,13 +170,64 @@ To override the default channel factory, add
|
|
|
95
170
|
|
|
96
171
|
## Routing lifecycle
|
|
97
172
|
|
|
98
|
-
Event, command, and state-update routing accepts exact schemas and
|
|
99
|
-
message-interface tokens. It selects exact
|
|
100
|
-
then replacement
|
|
101
|
-
validated typed targets are stored and reused for retries.
|
|
173
|
+
Event, command, and state-update routing accepts exact schemas and generated
|
|
174
|
+
message-interface tokens. It selects an exact schema first, then the first
|
|
175
|
+
matching token, then the replacement or default route. The route runs when the message is accepted; its
|
|
176
|
+
validated typed targets are stored and reused for retries. Route callbacks may
|
|
177
|
+
return a value or a Promise. `CommandRouting.create(EntityClass)`,
|
|
178
|
+
`EventRouting.create(EntityClass)`, and `StateUpdateRouting.create(ProjectionClass)`
|
|
179
|
+
infer the receiving ID, state, and application Entity types. The selected class
|
|
180
|
+
must be the same constructor registered by the repository;
|
|
181
|
+
construction rejects a mismatch even when two classes share ID and state types.
|
|
182
|
+
The callback's third argument exposes `findIds(query)`, `findStates(query)`, and `find(query)`
|
|
183
|
+
for that repository in the signal's tenant. The read access rejects new calls
|
|
184
|
+
after the route finishes. ID-only `create<Id>()` declarations remain valid.
|
|
185
|
+
Direct `repository.routeCommand()` and `repository.routeEvent()` calls return
|
|
186
|
+
Promises and must be awaited. Commands select exactly one ID; Events and state
|
|
187
|
+
updates may select any number of distinct IDs. After validation and stable
|
|
188
|
+
deduplication, routes above 1,000 recipients emit one warning and retain every
|
|
189
|
+
recipient. Inbox replay uses stored target IDs without rerouting. The legacy-named
|
|
102
190
|
local `catchUpReadSide()` helper is unrelated to retry: it resets and replays
|
|
103
191
|
the whole local read side, and is not Projection catch-up.
|
|
104
192
|
|
|
193
|
+
## Message-valued Entity IDs
|
|
194
|
+
|
|
195
|
+
The first state field determines the Entity ID type. It can be a primitive or
|
|
196
|
+
a generated Protobuf message; for a message ID, all declared fields form the
|
|
197
|
+
identifier. Nested and composite IDs are supported.
|
|
198
|
+
|
|
199
|
+
```proto
|
|
200
|
+
message ShipmentId {
|
|
201
|
+
spine.core.UserId owner = 1;
|
|
202
|
+
int32 number = 2;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
message Shipment {
|
|
206
|
+
ShipmentId id = 1;
|
|
207
|
+
string status = 2;
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
The root `MessageId` type accepts generated messages such as `CommandId`:
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
import { create } from "@bufbuild/protobuf";
|
|
215
|
+
import { type MessageId } from "@spine-event-engine/server";
|
|
216
|
+
import { CommandIdSchema } from "@spine-event-engine/proto";
|
|
217
|
+
|
|
218
|
+
const id: MessageId = create(CommandIdSchema, { uuid: "shipment-7" });
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Admission validates a message ID against the schema declared by the state
|
|
222
|
+
field, including generated validation rules, before it is stored or replayed.
|
|
223
|
+
When default routing finds a message but the Entity ID type is primitive, a
|
|
224
|
+
custom route returns the intended primitive ID.
|
|
225
|
+
|
|
226
|
+
Without a custom route, Command routing uses the Command's declared first
|
|
227
|
+
field. Event routing uses a compatible producer ID when available and otherwise
|
|
228
|
+
uses the Event's declared first field. State-update routing selects the first
|
|
229
|
+
state field compatible with the Entity ID.
|
|
230
|
+
|
|
105
231
|
## Handler routing and operations
|
|
106
232
|
|
|
107
233
|
Generated handler metadata routes a command, event, or state update by its
|
|
@@ -110,6 +236,291 @@ there is no exact route. This is TypeScript routing; it does not consume
|
|
|
110
236
|
`(is).java_type` or `(every_is).java_type`, and it has no decorator-based
|
|
111
237
|
route registration API.
|
|
112
238
|
|
|
239
|
+
Handlers may return their established result directly or through exactly one built-in `Promise<T>`.
|
|
240
|
+
Nested promises and structural or imported thenable lookalikes are rejected during handler analysis.
|
|
241
|
+
The entity transaction remains open until that promise settles; rejection rolls
|
|
242
|
+
back framework state and suppresses produced output, but cannot undo external
|
|
243
|
+
side effects. Process Managers may use their protected read-only query surface
|
|
244
|
+
for eventually consistent Entity state during a handler. Aggregates must not
|
|
245
|
+
use these reads for invariants.
|
|
246
|
+
|
|
247
|
+
`select(schema, columns)` supports `byId`, typed `where`, `orderBy`,
|
|
248
|
+
`limit`, `read`, `findById`, and `all`. A Process Manager query returns at most
|
|
249
|
+
1,000 states and accepts at most 1,000 explicit IDs at execution. A generated
|
|
250
|
+
`StateQuery.create()...build()` value also works with `this.select(query).read()`; its read
|
|
251
|
+
binds the current handler actor and tenant. `all()` is a convenience and can be costly on a large read model;
|
|
252
|
+
prefer an ID-targeted or ordered bounded query.
|
|
253
|
+
|
|
254
|
+
Use `EntityQuery.all(...)` to require every predicate and `EntityQuery.either(...)`
|
|
255
|
+
to accept any branch. Query execution inherits the active handler's actor and
|
|
256
|
+
effective tenant; the protected read-only facade has no tenant override.
|
|
257
|
+
|
|
258
|
+
`RepositoryReadQueries<EntityType>` describes the receiving repository's typed
|
|
259
|
+
`findIds`, `findStates`, and `find` reads. A read returns all matches unless the
|
|
260
|
+
shared generated query sets a limit. States are detached messages; `find`
|
|
261
|
+
restores application Entity instances with stored Version and lifecycle flags.
|
|
262
|
+
The framework binds each read scope to the incoming signal tenant. Routing
|
|
263
|
+
callbacks receive that scope as their third argument while they run.
|
|
264
|
+
|
|
265
|
+
For contexts registered with one `Server`, the target Entity's type URL selects
|
|
266
|
+
its context. No context name or network request is required. The Server rejects
|
|
267
|
+
duplicate Entity type registrations before starting delivery recovery or
|
|
268
|
+
listening, identifying the type and both contexts. Contexts used without a
|
|
269
|
+
Server retain local querying; separate Servers do not share query routes.
|
|
270
|
+
|
|
271
|
+
Cross-context targets must declare `query` or `full` visibility. This applies
|
|
272
|
+
to any Entity family with queryable state. Unknown targets, foreign targets
|
|
273
|
+
without query visibility, and tenant mismatches produce errors before storage
|
|
274
|
+
reads. Existing local and public-query visibility behavior is unchanged.
|
|
275
|
+
|
|
276
|
+
Single-tenant execution resolves to the built-in `SINGLE_TENANT` identity;
|
|
277
|
+
omitting the wire tenant field does not create tenant-free execution. Named
|
|
278
|
+
tenants can query their corresponding multitenant partition, not a single-tenant
|
|
279
|
+
destination. `SINGLE_TENANT` can query a single-tenant destination or its same
|
|
280
|
+
partition in a multitenant destination. The query never replaces the tenant to
|
|
281
|
+
suit the target. A valid query with no matching record keeps its ordinary empty
|
|
282
|
+
result (`undefined` from `findById()`). Reads remain eventually consistent.
|
|
283
|
+
|
|
284
|
+
Routes are available before recovered handlers execute. Server shutdown drains
|
|
285
|
+
accepted handlers across contexts before closing any target read-side; failed
|
|
286
|
+
startup validation participates in context and resource cleanup.
|
|
287
|
+
|
|
288
|
+
```ts
|
|
289
|
+
import type { Message } from "@bufbuild/protobuf";
|
|
290
|
+
import type { GenMessage } from "@bufbuild/protobuf/codegenv2";
|
|
291
|
+
import { EntityQuery, type EntityColumn } from "@spine-event-engine/core";
|
|
292
|
+
import { ProcessManager } from "@spine-event-engine/server";
|
|
293
|
+
|
|
294
|
+
type RequestView = Message<"RequestView"> & {
|
|
295
|
+
id: string;
|
|
296
|
+
status: string;
|
|
297
|
+
createdAt: number;
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
declare const RequestViewSchema: GenMessage<RequestView>;
|
|
301
|
+
declare const RequestViewColumns: {
|
|
302
|
+
readonly status: EntityColumn<typeof RequestViewSchema, "status", string, "equal">;
|
|
303
|
+
readonly createdAt: EntityColumn<
|
|
304
|
+
typeof RequestViewSchema,
|
|
305
|
+
"createdAt",
|
|
306
|
+
number,
|
|
307
|
+
"equal" | "greaterThan"
|
|
308
|
+
>;
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
abstract class RequestCoordinator extends ProcessManager<string, typeof RequestViewSchema> {
|
|
312
|
+
protected async pendingRequests(requestId: string): Promise<void> {
|
|
313
|
+
const matching = await this.select(RequestViewSchema, RequestViewColumns)
|
|
314
|
+
.where(EntityQuery.eq(RequestViewColumns.status, "pending"))
|
|
315
|
+
.orderBy(RequestViewColumns.createdAt, "asc")
|
|
316
|
+
.limit(10)
|
|
317
|
+
.read();
|
|
318
|
+
const one = await this.select(RequestViewSchema, RequestViewColumns).findById(requestId);
|
|
319
|
+
const everyVisible = await this.select(RequestViewSchema, RequestViewColumns).all();
|
|
320
|
+
void [matching, one, everyVisible];
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
`all()` remains capped at 1,000 states but may still be costly; use `findById()`
|
|
326
|
+
or an ordered bounded `read()` where possible.
|
|
327
|
+
|
|
328
|
+
A command-input `@Command` method is a command substitution receptor: it is
|
|
329
|
+
the one effective receptor for that Command type (instead of an `@Assign`),
|
|
330
|
+
commits its Entity state before its one-or-more returned Commands are detached
|
|
331
|
+
for in-process produced-command enqueue, and receives an optional `CommandContext`.
|
|
332
|
+
Only Process Manager repositories support `@Command` handlers. Aggregate and
|
|
333
|
+
Projection repositories reject command-input substitutions and event- or
|
|
334
|
+
rejection-input command reactions during generated metadata ingestion and
|
|
335
|
+
repository construction.
|
|
336
|
+
Event- and rejection-input `@Command` methods remain Event- or
|
|
337
|
+
rejection-to-command reactions on Event Bus.
|
|
338
|
+
|
|
339
|
+
### Handler return types
|
|
340
|
+
|
|
341
|
+
Use a TypeScript union such as `CreateAccessGrant | ExtendAccessGrant` to return
|
|
342
|
+
one of several Commands. A tuple such as
|
|
343
|
+
`readonly [AccessGrantCreated, AccessRequestCompleted?]` returns multiple Events
|
|
344
|
+
in order, with the second optional. Either declaration may have one outer
|
|
345
|
+
`Promise` layer. Generated metadata lists every possible schema; runtime checks
|
|
346
|
+
each result against the invoked handler's list. A whole result may be absent
|
|
347
|
+
for `@React` and Event/rejection-input `@Command`, using a generated signal type
|
|
348
|
+
unioned with `undefined`. A reaction that never emits can declare `undefined`
|
|
349
|
+
alone or `Promise<undefined>`. Either reaction kind may also return an empty
|
|
350
|
+
typed array. Command-input `@Command` and `@Assign` require nonempty results.
|
|
351
|
+
`@Subscribe` declares `void` or `Promise<void>` and produces no signals.
|
|
352
|
+
`@Throws` declares thrown rejections separately from normal results.
|
|
353
|
+
|
|
354
|
+
Only subscriptions accept `void`. An absent reaction result or optional tuple
|
|
355
|
+
entry uses `undefined`, never `null`. The framework validates all returned
|
|
356
|
+
values before committing Entity changes or publishing outputs. An invalid
|
|
357
|
+
subscription result fails even if it is an empty array. A valid no-output
|
|
358
|
+
reaction still saves its Entity state changes.
|
|
359
|
+
|
|
360
|
+
Other supported forms are `T[]`, `readonly T[]`, `Array<T>`, `ReadonlyArray<T>`,
|
|
361
|
+
named tuple entries, union alternatives inside tuple entries, and local or
|
|
362
|
+
imported aliases resolving to concrete generated messages. Only one flat
|
|
363
|
+
collection level is supported. Rest tuples, nested collections, nested promises,
|
|
364
|
+
custom thenables, framework envelopes, and unresolved/`any`/`unknown` result
|
|
365
|
+
branches are rejected. TypeScript enforces tuple structure; the runtime checks
|
|
366
|
+
declared message types, required nonempty results, and result order, not tuple
|
|
367
|
+
arity or positional schema constraints. See the
|
|
368
|
+
[user guide](https://github.com/SpineEventEngine/spine-ts/blob/master/docs/USER_GUIDE.md#choose-what-a-handler-returns) for a compact
|
|
369
|
+
return-type table and examples.
|
|
370
|
+
|
|
371
|
+
These independent examples use generated review-workflow messages. The Commander
|
|
372
|
+
chooses whether to start or schedule a review. The reactor example shows a
|
|
373
|
+
different workflow: record a started review and optionally schedule a follow-up.
|
|
374
|
+
They illustrate return declarations, not two handlers to register together.
|
|
375
|
+
|
|
376
|
+
<!-- docs-snippet-path: packages/server/test/runtime/standalone-handler-runtime.test.ts -->
|
|
377
|
+
|
|
378
|
+
```ts
|
|
379
|
+
import { create } from "@bufbuild/protobuf";
|
|
380
|
+
import {
|
|
381
|
+
AbstractCommander,
|
|
382
|
+
AbstractEventReactor,
|
|
383
|
+
Command,
|
|
384
|
+
React,
|
|
385
|
+
} from "@spine-event-engine/server";
|
|
386
|
+
import {
|
|
387
|
+
StartReviewSchema,
|
|
388
|
+
ScheduleReviewSchema,
|
|
389
|
+
type StartReview,
|
|
390
|
+
type ScheduleReview,
|
|
391
|
+
} from "../../test-fixtures/generated/handler-registry/commands_pb.js";
|
|
392
|
+
import {
|
|
393
|
+
ReviewStartedSchema,
|
|
394
|
+
ReviewFollowUpScheduledSchema,
|
|
395
|
+
type ReviewStarted,
|
|
396
|
+
type ReviewFollowUpScheduled,
|
|
397
|
+
type ReviewTaskAssigned,
|
|
398
|
+
} from "../../test-fixtures/generated/handler-registry/events_pb.js";
|
|
399
|
+
|
|
400
|
+
export class ReviewCommander extends AbstractCommander {
|
|
401
|
+
@Command
|
|
402
|
+
planReview(event: ReviewTaskAssigned): StartReview | ScheduleReview {
|
|
403
|
+
// Return one Command, choosing its type using the application's rules.
|
|
404
|
+
return event.name === "Security review"
|
|
405
|
+
? create(ScheduleReviewSchema, { id: event.id })
|
|
406
|
+
: create(StartReviewSchema, { id: event.id });
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
export class ReviewReactor extends AbstractEventReactor {
|
|
411
|
+
@React
|
|
412
|
+
recordReview(event: ReviewTaskAssigned): readonly [ReviewStarted, ReviewFollowUpScheduled?] {
|
|
413
|
+
const started = create(ReviewStartedSchema, { id: event.id });
|
|
414
|
+
// A tuple returns ordered Events. Its optional second value may be absent.
|
|
415
|
+
if (event.name !== "Security review") return [started];
|
|
416
|
+
return [
|
|
417
|
+
started,
|
|
418
|
+
create(ReviewFollowUpScheduledSchema, { id: event.id, name: "Security follow-up" }),
|
|
419
|
+
];
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
Produced commands retain the source actor, tenant, origin, and causal lineage.
|
|
425
|
+
The enqueue is post-commit best effort, not an atomic outbox or exactly-once
|
|
426
|
+
delivery: a process crash between commit and enqueue can lose a child. Accepted
|
|
427
|
+
produced commands drain during context close. A contained child failure is diagnosed,
|
|
428
|
+
but does not retroactively fail an already accepted source command or durably
|
|
429
|
+
retry that child.
|
|
430
|
+
|
|
431
|
+
For a generated Process Manager, a command-input handler uses distinct domain
|
|
432
|
+
Command input and output types, and the generated registry supplies those
|
|
433
|
+
schemas. This self-contained example explicitly ingests the unversioned data that an
|
|
434
|
+
application build normally emits, then assembles a repository from it; it does
|
|
435
|
+
not claim that its fixture discovers a registry artifact automatically:
|
|
436
|
+
|
|
437
|
+
<!-- docs-snippet-path: packages/server-blackbox-tests/test/project-event-routing.test.ts -->
|
|
438
|
+
|
|
439
|
+
```ts
|
|
440
|
+
import { create } from "@bufbuild/protobuf";
|
|
441
|
+
import { SignalEnvelopes } from "@spine-event-engine/core";
|
|
442
|
+
import {
|
|
443
|
+
CommandContextSchema,
|
|
444
|
+
CommandIdSchema,
|
|
445
|
+
type CommandContext,
|
|
446
|
+
} from "@spine-event-engine/proto";
|
|
447
|
+
import {
|
|
448
|
+
BoundedContext,
|
|
449
|
+
Command,
|
|
450
|
+
type EntityHandlersMetadata,
|
|
451
|
+
HandlerRegistryIngestor,
|
|
452
|
+
ProcessManager,
|
|
453
|
+
Repository,
|
|
454
|
+
} from "@spine-event-engine/server";
|
|
455
|
+
import type { GeneratedHandlerRegistry } from "@spine-event-engine/server/spi/handler-registry";
|
|
456
|
+
|
|
457
|
+
import {
|
|
458
|
+
ApproveProjectSchema,
|
|
459
|
+
ScheduleProjectSchema,
|
|
460
|
+
type ApproveProject,
|
|
461
|
+
type ScheduleProject,
|
|
462
|
+
} from "../generated/spine/server/testing/project_commands_pb.js";
|
|
463
|
+
import {
|
|
464
|
+
CoordinationStateSchema,
|
|
465
|
+
OrganizationIdSchema,
|
|
466
|
+
ProjectIdSchema,
|
|
467
|
+
type ProjectId,
|
|
468
|
+
} from "../generated/spine/server/testing/project_workflow_pb.js";
|
|
469
|
+
|
|
470
|
+
class ApprovalCoordinator extends ProcessManager<ProjectId, typeof CoordinationStateSchema> {
|
|
471
|
+
@Command
|
|
472
|
+
approve(command: ApproveProject, context: CommandContext): ScheduleProject {
|
|
473
|
+
this.update((draft) => Object.assign(draft, { id: this.id, projectName: command.status }));
|
|
474
|
+
return create(ScheduleProjectSchema, { project: command.project, status: command.status });
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const project = create(ProjectIdSchema, {
|
|
479
|
+
organization: create(OrganizationIdSchema, { code: "org-a" }),
|
|
480
|
+
number: 1,
|
|
481
|
+
});
|
|
482
|
+
const registry: GeneratedHandlerRegistry = {
|
|
483
|
+
receivers: [
|
|
484
|
+
{
|
|
485
|
+
receiverKind: "entity",
|
|
486
|
+
receiverType: ApprovalCoordinator,
|
|
487
|
+
stateSchema: CoordinationStateSchema,
|
|
488
|
+
handlers: [
|
|
489
|
+
{
|
|
490
|
+
kind: "command-substitution",
|
|
491
|
+
methodName: "approve",
|
|
492
|
+
input: { schema: ApproveProjectSchema, origin: "domestic" },
|
|
493
|
+
outcomes: { returned: [ScheduleProjectSchema], thrown: [] },
|
|
494
|
+
parameterCount: 2,
|
|
495
|
+
},
|
|
496
|
+
],
|
|
497
|
+
},
|
|
498
|
+
],
|
|
499
|
+
};
|
|
500
|
+
const [handlers] = new HandlerRegistryIngestor().ingest(registry);
|
|
501
|
+
if (handlers === undefined) throw new Error("Generated Process Manager metadata is missing.");
|
|
502
|
+
const repository = new Repository<typeof ApprovalCoordinator>({
|
|
503
|
+
entityType: ApprovalCoordinator,
|
|
504
|
+
schema: CoordinationStateSchema,
|
|
505
|
+
handlers: handlers as EntityHandlersMetadata<ApprovalCoordinator, typeof CoordinationStateSchema>,
|
|
506
|
+
});
|
|
507
|
+
const context = BoundedContext.singleTenant("Projects").add(repository).build();
|
|
508
|
+
await context.commandBus().post(
|
|
509
|
+
SignalEnvelopes.command({
|
|
510
|
+
context: create(CommandContextSchema),
|
|
511
|
+
schema: ApproveProjectSchema,
|
|
512
|
+
message: create(ApproveProjectSchema, { project, status: "approved" }),
|
|
513
|
+
}),
|
|
514
|
+
);
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
The generated record declares `ApproveProjectSchema` as the input and
|
|
518
|
+
`ScheduleProjectSchema` as the emitted Command schema; registry ingestion, not
|
|
519
|
+
manual decorator materialization, constructs the output-bearing metadata.
|
|
520
|
+
Application builds emit the record to
|
|
521
|
+
`generated/handler/generated-handler-registry.js`; those applications use
|
|
522
|
+
`buildAsync()` with their compiled package root.
|
|
523
|
+
|
|
113
524
|
One `@Where({ eventField, equals })` equality filter may narrow an event- or
|
|
114
525
|
rejection-consuming `@Subscribe`, `@React`, or `@Command` handler after type
|
|
115
526
|
routing. `eventField` and `equals` are typed string literals; invalid or
|
|
@@ -132,6 +543,10 @@ handler registry for classes registered with `add(EntityClass)`;
|
|
|
132
543
|
explicit `Repository` registration. A built context contains `CommandBus`,
|
|
133
544
|
`EventBus`, `Stand`, repositories, and its storage lifecycle.
|
|
134
545
|
|
|
546
|
+
Generated writer output is an unversioned receiver collection and records
|
|
547
|
+
command substitutions explicitly. The ingestor rejects retired versioned
|
|
548
|
+
registry shapes with an instruction to regenerate.
|
|
549
|
+
|
|
135
550
|
### Stand subscription registry
|
|
136
551
|
|
|
137
552
|
Each built context also has one `StandSubscriptionRegistry`. By default the
|
|
@@ -144,8 +559,8 @@ the builder on the first build attempt.
|
|
|
144
559
|
|
|
145
560
|
`SubscriptionService` streams are live best-effort notifications, not replay,
|
|
146
561
|
ordering, or completeness contracts. A standalone Server defaults to the
|
|
147
|
-
persistent registry. In a managed
|
|
148
|
-
each child Server and accepts only every context's actual
|
|
562
|
+
persistent registry. In a group of managed server replicas, the framework starts
|
|
563
|
+
and closes each child Server and accepts only every context's actual
|
|
149
564
|
`InMemorySubscriptionRegistry`; a persistent or custom registry is rejected
|
|
150
565
|
and the assembled child is closed before READY. Durable logical bindings remain
|
|
151
566
|
Gateway-only. The Coordinator fans each logical definition to ready children,
|
|
@@ -156,8 +571,8 @@ child streams before admission; subscribed but inactive definitions do not
|
|
|
156
571
|
block readiness. Each child observes application-configured remote/shared
|
|
157
572
|
Delivery directly. During DRAINING, new unary and shard admission stops while
|
|
158
573
|
active Delivery and its subscription updates finish before child close. The
|
|
159
|
-
application
|
|
160
|
-
does not infer or certify configuration provenance.
|
|
574
|
+
application configures the Delivery facility and selects the shard strategy;
|
|
575
|
+
managed mode does not infer or certify configuration provenance.
|
|
161
576
|
|
|
162
577
|
Create records begin `pending` and expire after 30 seconds unless activated.
|
|
163
578
|
Active records have no framework TTL. Cancellation physically deletes the
|
|
@@ -202,11 +617,21 @@ record-storage handle, while the context closes the registry.
|
|
|
202
617
|
|
|
203
618
|
`Entity` is the state base class. `Aggregate`, `Projection`, and
|
|
204
619
|
`ProcessManager` identify the three entity families. Handler decorators are
|
|
205
|
-
`@Assign`, `@Command`, `@React`,
|
|
620
|
+
`@Assign`, `@Command`, `@React`, and `@Subscribe`. A command-accepting
|
|
621
|
+
handler uses `@Throws(GeneratedRejection)` to declare its possible domain
|
|
622
|
+
rejections. Write the primary handler decorator first and `@Throws` immediately
|
|
623
|
+
below it; declaration order does not affect behavior. Generated metadata records
|
|
624
|
+
normal and rejection outcomes before context assembly, so clients may subscribe
|
|
625
|
+
to a declared rejection even when no server handler consumes it. Throwing an
|
|
626
|
+
undeclared generated rejection is a technical handler failure. In a transactional
|
|
206
627
|
handler, `update(mutator)` changes the active draft and returns the draft;
|
|
207
628
|
`tryUpdate(mutator)` validates a scratch draft and returns violations without
|
|
208
629
|
applying an invalid change. Entity lifecycle and version changes are committed
|
|
209
630
|
only after the transaction accepts.
|
|
631
|
+
Every Entity exposes the generated Spine `Version` message, starting at number
|
|
632
|
+
zero. A dispatch that produces Events or changes state or lifecycle advances it
|
|
633
|
+
once; no-op and rejected dispatches do not. Repositories restore and persist the
|
|
634
|
+
full Version, including its timestamp, across current state, history, and Stand.
|
|
210
635
|
|
|
211
636
|
## Signals, validation, and rejection behavior
|
|
212
637
|
|
|
@@ -216,10 +641,13 @@ command-validation failure, which `SpineServices` maps to
|
|
|
216
641
|
`COMMAND_VALIDATION_ERROR` with a packed `spine.validation.ValidationError`.
|
|
217
642
|
Entity transition validation failures map to
|
|
218
643
|
`COMMAND_STATE_TRANSITION_VALIDATION_FAILED` with the same detail type.
|
|
644
|
+
Framework-created runtime command and event metadata uses fresh Node secure
|
|
645
|
+
UUIDs; fixed IDs belong only to existing source envelopes retained through the
|
|
646
|
+
normal origin chain.
|
|
219
647
|
|
|
220
648
|
An application handler throws a generated core `RejectionThrowable` for a
|
|
221
|
-
domain rejection
|
|
222
|
-
output; it schedules the typed rejection event independently. Command service
|
|
649
|
+
domain rejection declared with `@Throws`. The repository rolls back state,
|
|
650
|
+
version, lifecycle, and output; it schedules the typed rejection event independently. Command service
|
|
223
651
|
acknowledgement remains an accepted `Ack`. Rejection-event posting can be
|
|
224
652
|
unobserved by inactive, full, or closed subscriptions and a posting failure is
|
|
225
653
|
an internal diagnostic, not a retry guarantee.
|
|
@@ -261,7 +689,7 @@ callback failures are isolated from post-commit observation and other
|
|
|
261
689
|
consumers. The package does not guarantee cluster-complete observations,
|
|
262
690
|
subscription replay, event-gap repair, or exactly-once effects.
|
|
263
691
|
|
|
264
|
-
###
|
|
692
|
+
### Native and managed hosting
|
|
265
693
|
|
|
266
694
|
`Server.start()` installs no process signal handlers, never closes its
|
|
267
695
|
environment, and shares only a caller-managed active generation. `Server.run()`
|
|
@@ -273,15 +701,15 @@ signal or explicit-close retry, and sets `process.exitCode` to `1` after a
|
|
|
273
701
|
signal-driven failure.
|
|
274
702
|
|
|
275
703
|
`ManagedServerApplication.run()` is the Node-only deployment entrypoint for a
|
|
276
|
-
complete
|
|
277
|
-
`ManagedServerApplication.start()` starts the same
|
|
704
|
+
group of complete server replicas when the framework handles process shutdown.
|
|
705
|
+
`ManagedServerApplication.start()` starts the same group for an embedding host
|
|
278
706
|
and installs no process signal handlers; that host closes the returned handle.
|
|
279
707
|
Both require a positive safe-integer
|
|
280
708
|
`processCount` and an ESM `moduleUrl`; it never derives a process count from
|
|
281
709
|
the machine. Its optional `host` and required nonzero `port` bind the public
|
|
282
|
-
Coordinator listener. Every child executes that same module and calls
|
|
283
|
-
`createServer({ host: "127.0.0.1", port: 0 })`. The child reports its
|
|
284
|
-
loopback endpoint only after its local readiness gates settle. The parent
|
|
710
|
+
Coordinator listener. Every child executes that same module and calls the
|
|
711
|
+
module's `createServer({ host: "127.0.0.1", port: 0 })`. The child reports its
|
|
712
|
+
actual loopback endpoint only after its local readiness gates settle. The parent
|
|
285
713
|
initially waits until every configured child is ready. Afterwards it is ready
|
|
286
714
|
while at least one child is ready; it remains alive and keeps replacing while
|
|
287
715
|
degraded, and reports unready if no child is ready. It replaces an unexpected
|
|
@@ -303,12 +731,10 @@ call on a sibling child. Subscription fan-out follows current READY members;
|
|
|
303
731
|
later and replacement members attach the retained subscription before managed
|
|
304
732
|
admission.
|
|
305
733
|
|
|
306
|
-
`ServerOptions
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
`
|
|
310
|
-
Connect/gRPC-Web listener. A public bind failure closes subscription resources
|
|
311
|
-
and the native backend; multiple rollback failures are aggregated.
|
|
734
|
+
`ServerOptions` is native-only: it configures contexts, resources, services,
|
|
735
|
+
and the native listener. It has no `browser` option and the root package does
|
|
736
|
+
not load `@spine-event-engine/auth`. Browser hosting belongs to the explicit
|
|
737
|
+
`@spine-event-engine/server/browser` entry point; see the [browser/auth guide](https://github.com/SpineEventEngine/spine-ts/blob/master/docs/BROWSER_CLIENT_AUTH_EXTENSION_GUIDE.md).
|
|
312
738
|
|
|
313
739
|
Browser unary gateway admission has a fixed 1 MiB (1,048,576-byte) request
|
|
314
740
|
limit. A larger unary gateway request is rejected with `ResourceExhausted`.
|
|
@@ -317,7 +743,7 @@ gRPC-Web transport message accepted by the public listener, so the effective
|
|
|
317
743
|
inbound limit is the stricter applicable limit. `writeMaxBytes` limits public
|
|
318
744
|
transport responses independently.
|
|
319
745
|
|
|
320
|
-
|
|
746
|
+
For `BrowserServer` from `@spine-event-engine/server/browser`, origins must be unique canonical HTTP(S) origins with no path, query,
|
|
321
747
|
fragment, or trailing slash. Requests without an allowed exact `Origin` receive
|
|
322
748
|
403 before RPC handling. Allowed responses include credentialed exact-origin
|
|
323
749
|
CORS, `Vary: Origin`, protocol request headers, and exposed gRPC status headers.
|
|
@@ -364,12 +790,12 @@ native backend. Concurrent calls share an attempt; a failed unfinished phase
|
|
|
364
790
|
can be retried without repeating completed native cleanup.
|
|
365
791
|
|
|
366
792
|
Browser subscription bindings are separate from service subscription records.
|
|
367
|
-
`BrowserAdmission`
|
|
793
|
+
`BrowserAdmission` determines who configures and manages them. Authenticated mode supplies
|
|
368
794
|
`sessions` and may supply `BrowserServerOptions.bindings`; production then
|
|
369
|
-
requires
|
|
795
|
+
requires `DurableSubscriptionBindings` from `@spine-event-engine/server/browser` and rejects a
|
|
370
796
|
missing or in-memory binding store before listener open. Public mode supplies
|
|
371
|
-
`publicAccess: true`, cannot supply bindings, and uses
|
|
372
|
-
|
|
797
|
+
`publicAccess: true`, cannot supply bindings, and uses process-local bindings
|
|
798
|
+
managed by the framework. The durable registry receives an explicit application
|
|
373
799
|
namespace, storage factory, identifier source, and cleanup callback. It closes
|
|
374
800
|
only its independently opened record-storage handle, not the application
|
|
375
801
|
storage factory or a Spine JVM/TS backend. It stores canonical public
|
|
@@ -383,7 +809,7 @@ inside the stored Topic and are checked for Activate and Cancel. Create assigns
|
|
|
383
809
|
the public ID directly; no quota, reservation, fingerprint, or lease is
|
|
384
810
|
persisted. Cleanup remains bounded and restart-safe at the record level. It
|
|
385
811
|
cleans the backend definition before deleting its record. It is not a cleaner
|
|
386
|
-
|
|
812
|
+
lock between Gateway processes: this store supports one Gateway process. Active streams do not resume,
|
|
387
813
|
updates are not replayed, and the registry provides neither exactly-once
|
|
388
814
|
delivery, global update ordering, nor cluster-complete notification delivery.
|
|
389
815
|
|
|
@@ -402,18 +828,23 @@ rows are direct records; a delivered row is the deduplication fact. A handler
|
|
|
402
828
|
effect and the exact pending-to-delivered compare-and-set are not one
|
|
403
829
|
transaction, so a lost acknowledgement can redeliver after restart. Downstream
|
|
404
830
|
signal handling must be idempotent. A customized `DeliveryWorkRegistry`
|
|
405
|
-
implements `validateOwnership()` so delivery can validate before dispatch,
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
storage.
|
|
831
|
+
implements `validateOwnership()` so delivery can validate before dispatch, before
|
|
832
|
+
the Entity transaction commit, and before acknowledgement. These checks stop a worker
|
|
833
|
+
after it detects that another worker has taken the shard. They do not make
|
|
834
|
+
delivery and handler storage part of one distributed transaction.
|
|
409
835
|
|
|
410
836
|
`keepUntil` is the optional deduplication-protection deadline, not a second
|
|
411
837
|
retention setting. A delivered row is cleanup-eligible when that deadline is
|
|
412
|
-
absent or elapsed
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
838
|
+
absent or elapsed (`keepUntil <= now`). Delivery skips per-record mutation checks
|
|
839
|
+
and deletion attempts for rows whose deadline is still in the future. Cleanup
|
|
840
|
+
and retained-row duplicate recognition use the same applicable clock. Eligible
|
|
841
|
+
rows still receive final time and shard checks before deletion.
|
|
842
|
+
Direct local storage atomically verifies ownership and
|
|
843
|
+
the exact delivered snapshot during removal. A remote adapter may instead read
|
|
844
|
+
and compare the snapshot before sending a separate best-effort removal request;
|
|
845
|
+
that sequence is not an atomic compare-and-delete guarantee. Pending, retryable,
|
|
846
|
+
non-delivered, and still-protected rows remain. There is no additional retention
|
|
847
|
+
configuration, timer, or scheduler.
|
|
417
848
|
|
|
418
849
|
`BoundedContextBuilder.withDeliveryStrategy(strategy)` snapshots a validated
|
|
419
850
|
immutable strategy for its Entity Inbox; the default is one shard. For example,
|
|
@@ -429,7 +860,6 @@ const context = BoundedContext.singleTenant("Tasks")
|
|
|
429
860
|
|
|
430
861
|
Aggregate commands and Process Manager commands/events derive their target shard
|
|
431
862
|
internally and persist their envelope in the target Entity Inbox before any
|
|
432
|
-
handler runs. Delivery is never a post-request callback: local and remote
|
|
433
863
|
handler runs. Local intake can directly drain the persisted Inbox in the
|
|
434
864
|
current request path. Attached `ServerEnvironment` ports acknowledge admission
|
|
435
865
|
and delivery workers replay it through the same path. Projection rows remain
|
|
@@ -443,12 +873,22 @@ before source client/storage. `DeliverySource` is not configured on
|
|
|
443
873
|
durable supervisor state, topology failover, exactly-once effects, or automatic
|
|
444
874
|
retry of unknown remote mutations.
|
|
445
875
|
|
|
446
|
-
Each
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
876
|
+
Each Inbox read is bounded to one page. That bound does not limit the total
|
|
877
|
+
pending work: an active drain continues from the last row read until that scan
|
|
878
|
+
ends. It restarts from the beginning only if the completed scan delivered
|
|
879
|
+
messages, allowing it to find work added during delivery. It finishes after a
|
|
880
|
+
scan delivers nothing; it does not restart after each productive page.
|
|
881
|
+
Delivery compares pending rows with delivered rows in the current page
|
|
882
|
+
and a process-local cache of the 1,000 most recent deliveries, then removes
|
|
883
|
+
duplicates. The 30-second Inbox deduplication window controls how long delivered
|
|
884
|
+
rows remain available as duplicate evidence; it is not a replay-retention
|
|
885
|
+
period. Persisted accepted rows follow their delivery lifecycle and may be
|
|
886
|
+
replayed according to that lifecycle after the deduplication window has elapsed.
|
|
887
|
+
|
|
888
|
+
Direct delivery uses the configured Inbox storage clock for both retained-row
|
|
889
|
+
duplicate checks and early cleanup filtering. Remote delivery uses local client
|
|
890
|
+
wall-clock time for these checks and does not assume it is synchronized with
|
|
891
|
+
the remote server.
|
|
452
892
|
|
|
453
893
|
`DeliveryMonitor` is an instantiable, customizable policy seam exposing
|
|
454
894
|
asynchronous continuation, start/completion, failed-reception, pickup-failure,
|
|
@@ -483,6 +923,15 @@ shard-update hints; when absent, the existing local source remains the fallback.
|
|
|
483
923
|
Applications configure and close only the delivery facility through the
|
|
484
924
|
environment: attachment supervisors and their source reads belong to that facility.
|
|
485
925
|
|
|
926
|
+
## Signal identity and Aggregate semantics
|
|
927
|
+
|
|
928
|
+
Projection handlers cannot use `@Assign`; explicit metadata rejects that unsupported
|
|
929
|
+
handler shape. Framework-created child Commands and Events receive fresh IDs. Existing
|
|
930
|
+
Command or Event envelopes retain their IDs through transport and storage, including
|
|
931
|
+
when returned or passed as envelopes. Aggregate domain Events use the loaded pre-dispatch
|
|
932
|
+
version, while persisted Aggregate state advances to the next version. History caching accepts descending retained
|
|
933
|
+
versions with gaps, while continuing to reject equal or newer continuations.
|
|
934
|
+
|
|
486
935
|
# Dynamic unary discovery
|
|
487
936
|
|
|
488
|
-
`BrowserServerOptions.discovery` may supply changing complete application-node snapshots alongside fixed `backend` configuration. Both unary routing and native subscription streams use the same current membership. Empty membership retains durable definitions and later nodes reactivate them; a new backend-dependent subscription is unavailable until a node exists.
|
|
937
|
+
`BrowserServerOptions.discovery` from `@spine-event-engine/server/browser` may supply changing complete application-node snapshots alongside fixed `backend` configuration. Both unary routing and native subscription streams use the same current membership. Empty membership retains durable definitions and later nodes reactivate them; a new backend-dependent subscription is unavailable until a node exists.
|