@spine-event-engine/server 2.0.0-snapshot.25 → 2.0.0-snapshot.28
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 +104 -16
- package/REFERENCE.md +486 -57
- package/dist/agent/agent-history.d.ts +19 -3
- package/dist/agent/agent-history.d.ts.map +1 -1
- package/dist/agent/agent-history.js +41 -10
- package/dist/agent/agent-history.js.map +1 -1
- package/dist/agent/agent-live-ai-runtime.d.ts +1 -1
- package/dist/agent/agent-live-ai-runtime.js +1 -1
- package/dist/agent/agent-live-budget.d.ts +1 -1
- package/dist/agent/agent-live-budget.js +1 -1
- package/dist/agent/agent-live-read-runtime.d.ts +13 -2
- package/dist/agent/agent-live-read-runtime.d.ts.map +1 -1
- package/dist/agent/agent-live-read-runtime.js +6 -5
- package/dist/agent/agent-live-read-runtime.js.map +1 -1
- package/dist/browser/browser-admission.d.ts +47 -0
- package/dist/browser/browser-admission.d.ts.map +1 -0
- package/dist/browser/browser-admission.js +200 -0
- package/dist/browser/browser-admission.js.map +1 -0
- package/dist/browser/browser-server.d.ts +171 -1
- package/dist/browser/browser-server.d.ts.map +1 -1
- package/dist/browser/browser-server.js +1027 -303
- package/dist/browser/browser-server.js.map +1 -1
- package/dist/browser/durable-subscription-bindings.d.ts +5 -0
- package/dist/browser/durable-subscription-bindings.d.ts.map +1 -1
- package/dist/browser/durable-subscription-bindings.js +226 -38
- package/dist/browser/durable-subscription-bindings.js.map +1 -1
- package/dist/browser/index.d.ts +1 -1
- package/dist/browser/index.d.ts.map +1 -1
- package/dist/browser/options.d.ts +53 -2
- package/dist/browser/options.d.ts.map +1 -1
- package/dist/bus/command-bus.d.ts +9 -1
- package/dist/bus/command-bus.d.ts.map +1 -1
- package/dist/bus/command-bus.js +75 -10
- package/dist/bus/command-bus.js.map +1 -1
- package/dist/bus/command-errors.d.ts +20 -4
- package/dist/bus/command-errors.d.ts.map +1 -1
- package/dist/bus/command-errors.js +47 -7
- package/dist/bus/command-errors.js.map +1 -1
- package/dist/bus/event-bus.d.ts +9 -1
- package/dist/bus/event-bus.d.ts.map +1 -1
- package/dist/bus/event-bus.js +136 -9
- package/dist/bus/event-bus.js.map +1 -1
- package/dist/context/bounded-context.d.ts +59 -39
- package/dist/context/bounded-context.d.ts.map +1 -1
- package/dist/context/bounded-context.js +277 -262
- package/dist/context/bounded-context.js.map +1 -1
- package/dist/context/entity-inbox.d.ts +12 -17
- package/dist/context/entity-inbox.d.ts.map +1 -1
- package/dist/context/entity-inbox.js +106 -119
- package/dist/context/entity-inbox.js.map +1 -1
- package/dist/context/local-inbox-handoff.d.ts +13 -13
- package/dist/context/local-inbox-handoff.d.ts.map +1 -1
- package/dist/context/local-inbox-handoff.js +60 -32
- package/dist/context/local-inbox-handoff.js.map +1 -1
- package/dist/context/persistent-tenant-index.d.ts +81 -0
- package/dist/context/persistent-tenant-index.d.ts.map +1 -0
- package/dist/context/persistent-tenant-index.js +270 -0
- package/dist/context/persistent-tenant-index.js.map +1 -0
- package/dist/context/projection-handoff.d.ts +9 -9
- package/dist/context/projection-handoff.d.ts.map +1 -1
- package/dist/context/projection-handoff.js +31 -23
- package/dist/context/projection-handoff.js.map +1 -1
- package/dist/context/tenant-index.d.ts +7 -6
- package/dist/context/tenant-index.d.ts.map +1 -1
- package/dist/context/tenant-index.js +2 -188
- package/dist/context/tenant-index.js.map +1 -1
- package/dist/context/tenant-unknown-error.d.ts +2 -2
- package/dist/context/tenant-unknown-error.d.ts.map +1 -1
- package/dist/context/tenant-unknown-error.js +4 -1
- package/dist/context/tenant-unknown-error.js.map +1 -1
- package/dist/delivery/delivery-supervisor.d.ts.map +1 -1
- package/dist/delivery/delivery-supervisor.js +12 -3
- package/dist/delivery/delivery-supervisor.js.map +1 -1
- package/dist/delivery/delivery.d.ts +4 -3
- package/dist/delivery/delivery.d.ts.map +1 -1
- package/dist/delivery/delivery.js +82 -34
- package/dist/delivery/delivery.js.map +1 -1
- package/dist/delivery/reception-observation.d.ts +3 -3
- package/dist/delivery/reception-observation.js +2 -2
- package/dist/entity/entity.d.ts +59 -0
- package/dist/entity/entity.d.ts.map +1 -1
- package/dist/entity/entity.js +60 -0
- package/dist/entity/entity.js.map +1 -1
- package/dist/handler/app-query-handler.d.ts +60 -0
- package/dist/handler/app-query-handler.d.ts.map +1 -0
- package/dist/handler/app-query-handler.js +23 -0
- package/dist/handler/app-query-handler.js.map +1 -0
- package/dist/handler/generated-registry-discovery.d.ts +6 -0
- package/dist/handler/generated-registry-discovery.d.ts.map +1 -1
- package/dist/handler/generated-registry-discovery.js +71 -2
- package/dist/handler/generated-registry-discovery.js.map +1 -1
- package/dist/handler/handler-decorators.d.ts +8 -29
- package/dist/handler/handler-decorators.d.ts.map +1 -1
- package/dist/handler/handler-decorators.js +33 -210
- package/dist/handler/handler-decorators.js.map +1 -1
- package/dist/handler/spine-metadata-inventory.d.ts +99 -0
- package/dist/handler/spine-metadata-inventory.d.ts.map +1 -0
- package/dist/handler/spine-metadata-inventory.js +162 -0
- package/dist/handler/spine-metadata-inventory.js.map +1 -0
- package/dist/index.d.ts +9 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/repository/command-routing.d.ts +1 -1
- package/dist/repository/command-routing.js +1 -1
- package/dist/repository/event-routing.d.ts +1 -1
- package/dist/repository/event-routing.js +1 -1
- package/dist/repository/repository.d.ts +61 -26
- package/dist/repository/repository.d.ts.map +1 -1
- package/dist/repository/repository.js +612 -306
- package/dist/repository/repository.js.map +1 -1
- package/dist/repository/state-update-routing.d.ts +1 -1
- package/dist/repository/state-update-routing.js +1 -1
- package/dist/runtime/signal-publisher.d.ts +23 -8
- package/dist/runtime/signal-publisher.d.ts.map +1 -1
- package/dist/runtime/signal-publisher.js +11 -8
- package/dist/runtime/signal-publisher.js.map +1 -1
- package/dist/server/managed-server-application.d.ts +12 -11
- package/dist/server/managed-server-application.d.ts.map +1 -1
- package/dist/server/managed-server-application.js +41 -25
- package/dist/server/managed-server-application.js.map +1 -1
- package/dist/server/node-coordinator.d.ts +18 -5
- package/dist/server/node-coordinator.d.ts.map +1 -1
- package/dist/server/node-coordinator.js +744 -108
- package/dist/server/node-coordinator.js.map +1 -1
- package/dist/server/reserved-spine-rpc-paths.json +1 -0
- package/dist/server/server-environment.d.ts +23 -5
- package/dist/server/server-environment.d.ts.map +1 -1
- package/dist/server/server-environment.js +89 -24
- package/dist/server/server-environment.js.map +1 -1
- package/dist/server/server.d.ts +45 -0
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +112 -3
- package/dist/server/server.js.map +1 -1
- package/dist/services/app-query-execution.d.ts +53 -0
- package/dist/services/app-query-execution.d.ts.map +1 -0
- package/dist/services/app-query-execution.js +408 -0
- package/dist/services/app-query-execution.js.map +1 -0
- package/dist/services/native-operation.d.ts +34 -0
- package/dist/services/native-operation.d.ts.map +1 -0
- package/dist/services/native-operation.js +77 -0
- package/dist/services/native-operation.js.map +1 -0
- package/dist/services/query-reader.d.ts +32 -7
- package/dist/services/query-reader.d.ts.map +1 -1
- package/dist/services/query-reader.js +22 -2
- package/dist/services/query-reader.js.map +1 -1
- package/dist/services/read-request-status.d.ts +65 -0
- package/dist/services/read-request-status.d.ts.map +1 -0
- package/dist/services/read-request-status.js +151 -0
- package/dist/services/read-request-status.js.map +1 -0
- package/dist/services/spine-services.d.ts +42 -7
- package/dist/services/spine-services.d.ts.map +1 -1
- package/dist/services/spine-services.js +1005 -175
- package/dist/services/spine-services.js.map +1 -1
- package/dist/services/trusted-reader-ingress.d.ts +110 -0
- package/dist/services/trusted-reader-ingress.d.ts.map +1 -0
- package/dist/services/trusted-reader-ingress.js +102 -0
- package/dist/services/trusted-reader-ingress.js.map +1 -0
- package/dist/spi/handler-registry.d.ts +2 -0
- package/dist/spi/handler-registry.d.ts.map +1 -1
- package/dist/spi/handler-registry.js +1 -1
- package/dist/spi/handler-registry.js.map +1 -1
- package/dist/stand/read-side-filter-engine.d.ts +46 -0
- package/dist/stand/read-side-filter-engine.d.ts.map +1 -0
- package/dist/stand/read-side-filter-engine.js +356 -0
- package/dist/stand/read-side-filter-engine.js.map +1 -0
- package/dist/stand/read-side-filter.d.ts +247 -0
- package/dist/stand/read-side-filter.d.ts.map +1 -0
- package/dist/stand/read-side-filter.js +39 -0
- package/dist/stand/read-side-filter.js.map +1 -0
- package/dist/stand/read-side-message.d.ts +66 -0
- package/dist/stand/read-side-message.d.ts.map +1 -0
- package/dist/stand/read-side-message.js +167 -0
- package/dist/stand/read-side-message.js.map +1 -0
- package/dist/stand/stand-direct-delivery.d.ts +161 -0
- package/dist/stand/stand-direct-delivery.d.ts.map +1 -0
- package/dist/stand/stand-direct-delivery.js +210 -0
- package/dist/stand/stand-direct-delivery.js.map +1 -0
- package/dist/stand/stand.d.ts +81 -43
- package/dist/stand/stand.d.ts.map +1 -1
- package/dist/stand/stand.js +411 -55
- package/dist/stand/stand.js.map +1 -1
- package/dist/stand/subscription-observer.d.ts.map +1 -1
- package/dist/stand/subscription-observer.js +103 -0
- package/dist/stand/subscription-observer.js.map +1 -1
- package/dist/stand/subscription-read-filters.d.ts +40 -0
- package/dist/stand/subscription-read-filters.d.ts.map +1 -0
- package/dist/stand/subscription-read-filters.js +91 -0
- package/dist/stand/subscription-read-filters.js.map +1 -0
- package/dist/stand/subscription-reader-binding.d.ts +20 -0
- package/dist/stand/subscription-reader-binding.d.ts.map +1 -0
- package/dist/stand/subscription-reader-binding.js +56 -0
- package/dist/stand/subscription-reader-binding.js.map +1 -0
- package/dist/stand/subscription-records.d.ts +30 -3
- package/dist/stand/subscription-records.d.ts.map +1 -1
- package/dist/stand/subscription-records.js +61 -16
- package/dist/stand/subscription-records.js.map +1 -1
- package/dist/stand/subscription-registry.d.ts +49 -14
- package/dist/stand/subscription-registry.d.ts.map +1 -1
- package/dist/stand/subscription-registry.js +150 -31
- package/dist/stand/subscription-registry.js.map +1 -1
- package/dist/stand/subscription-runtime.d.ts +7 -2
- package/dist/stand/subscription-runtime.d.ts.map +1 -1
- package/dist/stand/subscription-runtime.js +131 -25
- package/dist/stand/subscription-runtime.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +16 -14
package/REFERENCE.md
CHANGED
|
@@ -168,7 +168,7 @@ phase without implying that every Entity model has been prepared.
|
|
|
168
168
|
awaits both model preparation and the first subscription reconciliation by
|
|
169
169
|
default. Each option can be set to `false` independently. A deferred phase runs
|
|
170
170
|
before the first valid Command, Event, query, subscription operation, or Inbox
|
|
171
|
-
|
|
171
|
+
delivery for the receiving context; lazy multitenant preparation selects only that
|
|
172
172
|
input's currently registered tenant. A failed lazy attempt rejects its current
|
|
173
173
|
input and can be retried by a later input. Monitoring requires the shared
|
|
174
174
|
subscription registry, so `startSubscriptionMonitoring: true` prepares that
|
|
@@ -208,20 +208,33 @@ contexts share one in-memory factory, including in production. Local and test
|
|
|
208
208
|
environments default storage in memory and use the core registry only as a
|
|
209
209
|
fallback; those defaults are not production configuration.
|
|
210
210
|
|
|
211
|
-
`tenantIndex`
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
211
|
+
`tenantIndex` is optional and defaults to `SINGLE_TENANT`, a marker that creates
|
|
212
|
+
no directory and never consults a provider catalog. The environment getter
|
|
213
|
+
returns `TenantIndexSetting`, either that marker or an application-supplied
|
|
214
|
+
`TenantIndex`. Narrow against the marker before calling `contains()`, `keep()`,
|
|
215
|
+
`page()`, or `all()`. A multitenant Bounded Context requires a real environment
|
|
216
|
+
index before business storage opens. A single-tenant Bounded Context always returns the
|
|
217
|
+
marker, including inside a multitenant environment. Provider catalogs remain
|
|
218
|
+
separate storage-provider APIs and do not select application membership.
|
|
219
|
+
|
|
220
|
+
`PersistentTenantIndex.create({ storageFactory, group })` opens a separate,
|
|
221
|
+
storage-backed registration directory without constructing a Bounded Context or
|
|
222
|
+
`ServerEnvironment`. It creates and checks supported storage structures before
|
|
223
|
+
returning. Set `createMissingStructures: false` to skip preparation on creation
|
|
224
|
+
and on later reads or writes. A skipped directory needs structures prepared by
|
|
225
|
+
the application before data access; missing structures cause provider errors.
|
|
226
|
+
Memory storage remains process-local. The injected factory remains usable after the
|
|
227
|
+
index closes. `keep()` registers a complete `TenantId` atomically and retains
|
|
228
|
+
the first registration time; `contains()` reads its primary key. `page()` uses
|
|
229
|
+
finite keyset traversal with an abort signal and a continuation accepted only
|
|
230
|
+
by that index instance. `all()` traverses the complete current directory, so
|
|
231
|
+
its result can be large. Traversal does not promise a snapshot while other
|
|
232
|
+
writers register tenants. Registration does not provision tenant databases or
|
|
233
|
+
authorize users. The environment defaults to the explicit `SINGLE_TENANT` marker.
|
|
221
234
|
|
|
222
235
|
Multitenant receiving contexts check current index membership before Command,
|
|
223
236
|
Event, query, direct repository/history, and subscription work. Inbox persistence
|
|
224
|
-
and later
|
|
237
|
+
and later delivery check separately, so removing a tenant prevents saved work from
|
|
225
238
|
restoring it. Input does not call `keep()` implicitly. A confirmed missing member
|
|
226
239
|
raises `TenantUnknownError` with the complete tenant ID and context name; native
|
|
227
240
|
services return `NotFound` with a structured `spine.base.Error` detail whose type
|
|
@@ -249,8 +262,8 @@ Direct `repository.routeCommand()` and `repository.routeEvent()` calls return
|
|
|
249
262
|
Promises and must be awaited. Commands select exactly one ID; Events and state
|
|
250
263
|
updates may select any number of distinct IDs. After validation and stable
|
|
251
264
|
deduplication, routes above 1,000 recipients emit one warning and retain every
|
|
252
|
-
recipient. Inbox
|
|
253
|
-
|
|
265
|
+
recipient. Inbox delivery uses stored target IDs without rerouting. The local
|
|
266
|
+
`catchUpReadSide()` helper is unrelated to retry: it resets and dispatches stored Events through
|
|
254
267
|
the whole local read side, and is not Projection catch-up.
|
|
255
268
|
|
|
256
269
|
## Message-valued Entity IDs
|
|
@@ -282,7 +295,7 @@ const id: MessageId = create(CommandIdSchema, { uuid: "shipment-7" });
|
|
|
282
295
|
```
|
|
283
296
|
|
|
284
297
|
Admission validates a message ID against the schema declared by the state
|
|
285
|
-
field, including generated validation rules, before it is stored or
|
|
298
|
+
field, including generated validation rules, before it is stored or delivered.
|
|
286
299
|
When default routing finds a message but the Entity ID type is primitive, a
|
|
287
300
|
custom route returns the intended primitive ID.
|
|
288
301
|
|
|
@@ -585,9 +598,8 @@ await context.commandBus().post(
|
|
|
585
598
|
The generated record declares `ApproveProjectSchema` as the input and
|
|
586
599
|
`ScheduleProjectSchema` as the emitted Command schema; registry ingestion, not
|
|
587
600
|
manual decorator materialization, constructs the output-bearing metadata.
|
|
588
|
-
Application builds emit the record to
|
|
589
|
-
`generated/
|
|
590
|
-
`build()` with their compiled package root.
|
|
601
|
+
Application builds emit the record to `generated/spine-metadata.js` and import
|
|
602
|
+
`generated/spine-bootstrap.js` before `build()` selects registered classes.
|
|
591
603
|
|
|
592
604
|
One `@Where({ eventField, equals })` equality filter may narrow an event- or
|
|
593
605
|
rejection-consuming `@Subscribe`, `@React`, or `@Command` handler after type
|
|
@@ -597,9 +609,9 @@ repeated declarations fail closed. It is not an alternative routing mechanism.
|
|
|
597
609
|
Configure logging through `ServerEnvironment` when assembling the server. The
|
|
598
610
|
server installs that logger into its runtime components, where warnings and
|
|
599
611
|
failures carry operation context. Durable facts belong in events or storage.
|
|
600
|
-
Ordinary Aggregate, Process Manager, and Projection Inbox
|
|
601
|
-
accepted work through the normal handler path
|
|
602
|
-
|
|
612
|
+
Ordinary Aggregate, Process Manager, and Projection Inbox delivery sends persisted
|
|
613
|
+
accepted work through the normal handler path. Handler effects must tolerate
|
|
614
|
+
redelivery after an uncertain acknowledgement. An accepted Agent interaction is recorded by its receipt and is
|
|
603
615
|
never re-executed after interruption.
|
|
604
616
|
|
|
605
617
|
## Context assembly and entities
|
|
@@ -608,12 +620,14 @@ Create a context with `BoundedContext.singleTenant(name)` or
|
|
|
608
620
|
`BoundedContext.multitenant(name)`. Names are immutable. Use
|
|
609
621
|
`ServerEnvironment.when(type).use({ storageFactory: factory })` before explicit
|
|
610
622
|
environment setup to supply record/event storage to all contexts.
|
|
611
|
-
`withTenantIndex(index)` selects a different live membership directory for
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
`
|
|
615
|
-
|
|
616
|
-
|
|
623
|
+
`withTenantIndex(index)` selects a different live membership directory for a
|
|
624
|
+
multitenant Bounded Context after its environment has a real index. The Bounded Context getter
|
|
625
|
+
returns its captured selection; single-tenant Bounded Contexts return `SINGLE_TENANT`.
|
|
626
|
+
Both getters have type `TenantIndexSetting` and require narrowing before index
|
|
627
|
+
methods. Closing a Bounded Context does not close the configured directory.
|
|
628
|
+
The application imports its generated `spine-bootstrap.js` before registering
|
|
629
|
+
classes with `add(EntityClass)`; `build()` then selects passive descriptions
|
|
630
|
+
by exact constructor and performs assembly, including
|
|
617
631
|
explicit `Repository` registration. Await it before using the context. A built context contains `CommandBus`,
|
|
618
632
|
`EventBus`, `Stand`, repositories, and its storage lifecycle.
|
|
619
633
|
Trusted server-side code can call `context.getRepository(EntityClass)` to obtain
|
|
@@ -631,13 +645,12 @@ registry shapes with an instruction to regenerate.
|
|
|
631
645
|
Each built context also has one `StandSubscriptionRegistry`. By default the
|
|
632
646
|
builder creates a storage-backed registry from the resolved context
|
|
633
647
|
`StorageFactory` (including the `ServerEnvironment` factory used for a builder
|
|
634
|
-
added to a server). It stores one `spine.client.SubscriptionRecord` under each
|
|
648
|
+
added to a server). Its physical `StorageGroup` derives from the logical Bounded Context name, so two contexts using one factory keep distinct definitions. An injected `StorageSubscriptionRegistry` takes an explicit group as its third constructor argument and prepares its captured factory and context. It stores one `spine.client.SubscriptionRecord` under each
|
|
635
649
|
explicit `SubscriptionId`. A complete custom implementation can instead be
|
|
636
650
|
supplied with `withSubscriptionRegistry(registry)`; responsibility transfers to
|
|
637
651
|
the builder on the first build attempt.
|
|
638
652
|
|
|
639
|
-
`SubscriptionService` streams are live
|
|
640
|
-
ordering, or completeness contracts. A standalone Server defaults to the
|
|
653
|
+
`SubscriptionService` streams are live notifications. Each subscriber receives its admitted filtered updates in order, but there is no replay, exactly-once, or cluster-wide completeness guarantee. A standalone Server defaults to the
|
|
641
654
|
persistent registry. In a group of managed server replicas, the framework starts
|
|
642
655
|
and closes each child Server and accepts only every context's actual
|
|
643
656
|
`InMemorySubscriptionRegistry`; a persistent or custom registry is rejected
|
|
@@ -645,7 +658,12 @@ and the assembled child is closed before READY. Durable logical bindings remain
|
|
|
645
658
|
Gateway-only. The Coordinator fans each logical definition to ready children,
|
|
646
659
|
merges updates, propagates cancellation, and carries no subscription payload on
|
|
647
660
|
parent/child IPC. A late or replacement child synchronizes retained definitions
|
|
648
|
-
before it is eligible for normal requests.
|
|
661
|
+
before it is eligible for normal requests. The Coordinator resolves the same
|
|
662
|
+
`subscriptionQueueCapacity` before binding its HTTP/2 listener. Its activation
|
|
663
|
+
ACK is separate from the update queue, which reserves one active DATA update
|
|
664
|
+
plus the configured waiting count for each logical subscriber. Overflow closes
|
|
665
|
+
that subscriber's relay and aborts its child streams.
|
|
666
|
+
Active definitions attach their
|
|
649
667
|
child streams before admission; subscribed but inactive definitions do not
|
|
650
668
|
block readiness. Each child observes application-configured remote/shared
|
|
651
669
|
Delivery directly. During DRAINING, new unary and shard admission stops while
|
|
@@ -678,12 +696,21 @@ completed cycle. This is best-effort per-node convergence, not a
|
|
|
678
696
|
cluster-completeness, replay, ordering, gap-repair, or exactly-once guarantee.
|
|
679
697
|
|
|
680
698
|
The registry accepts only a generated `SubscriptionId` for activate, get, and
|
|
681
|
-
delete. `create(subscription)` returns `{ kind: "created", entry }` or
|
|
699
|
+
delete. `create(subscription, reader?)` returns `{ kind: "created", entry }` or
|
|
682
700
|
`{ kind: "existing", entry }`; the latter requires byte-equivalent canonical
|
|
683
|
-
content and
|
|
684
|
-
`activate(id)` returns `activated`,
|
|
685
|
-
`
|
|
686
|
-
durable records fail closed.
|
|
701
|
+
content and the same reader binding. A different definition or binding with the
|
|
702
|
+
same ID throws `StandConflictError`. `activate(id, reader?)` returns `activated`,
|
|
703
|
+
`active`, `missing`, `expired`, or `denied`. `delete(id, reader?)` returns `deleted`,
|
|
704
|
+
`missing`, or `denied`. Blank IDs or topics and malformed durable records fail closed.
|
|
705
|
+
|
|
706
|
+
Service requests always supply the admitted principal ID or an explicit anonymous
|
|
707
|
+
binding. When supplied, the registry checks the binding again after every failed
|
|
708
|
+
compare-and-set, before changing a reread record. The omitted argument is for
|
|
709
|
+
trusted internal lifecycle work; it is not a way to admit an unauthenticated RPC.
|
|
710
|
+
Reader identity is stored atomically with the subscription definition and phase.
|
|
711
|
+
Attributes, authority facts, and credentials are not part of the stored identity.
|
|
712
|
+
A missing legacy binding is not anonymous and cannot be claimed by a caller;
|
|
713
|
+
the client must create a new subscription.
|
|
687
714
|
|
|
688
715
|
Entries and nested subscriptions returned by create, activate, get, and
|
|
689
716
|
snapshot are clone-safe views. Protobuf byte arrays remain mutable runtime
|
|
@@ -840,6 +867,11 @@ The scope is explicit: pass `{}` in a single-tenant Bounded Context or
|
|
|
840
867
|
`{ tenantId }` in a multitenant Bounded Context. Reads use indexed storage
|
|
841
868
|
without restoring an Agent. The application bridge must authorize access;
|
|
842
869
|
actor metadata does not grant read permission.
|
|
870
|
+
For a trusted in-process caller, the scope can also include detached
|
|
871
|
+
`actorContext`, a verified `reader` with principal and authority facts, and a
|
|
872
|
+
`signal`. Read-side filters receive those facts for that history call; omitting
|
|
873
|
+
`reader` leaves principal and authority facts absent. The bridge supplies
|
|
874
|
+
verified facts explicitly and cannot derive them from `actorContext`.
|
|
843
875
|
|
|
844
876
|
If a selected model's `authorizeUse` callback explicitly returns `false`
|
|
845
877
|
before the handler, the turn records one `AgentInvocationTerminated` System
|
|
@@ -849,10 +881,15 @@ may correlate this fact with the original source through paged Agent history.
|
|
|
849
881
|
|
|
850
882
|
## Signals, validation, and rejection behavior
|
|
851
883
|
|
|
852
|
-
`CommandBus`
|
|
853
|
-
before
|
|
854
|
-
|
|
855
|
-
`
|
|
884
|
+
`CommandBus` checks the nonempty Command ID, nondefault message and context,
|
|
885
|
+
and declared payload constraints before admission or dispatcher code runs.
|
|
886
|
+
Annotated packed messages use generated Proto modules registered in the process.
|
|
887
|
+
`SpineServices` reports invalid Commands in an error Ack with type
|
|
888
|
+
`spine.core.CommandValidationError`, numeric `INVALID_COMMAND` code, and a
|
|
889
|
+
packed `spine.validation.ValidationError`. Unsupported Commands use the same
|
|
890
|
+
enum type and numeric `UNSUPPORTED_COMMAND` code.
|
|
891
|
+
Technical validator failures use a sanitized `COMMAND_POST_ERROR` Ack without
|
|
892
|
+
constraint details; they are distinct from invalid Command input.
|
|
856
893
|
Entity transition validation failures that escape service processing map to
|
|
857
894
|
`COMMAND_STATE_TRANSITION_VALIDATION_FAILED` with the same detail type. Managed
|
|
858
895
|
reception of ordinary Entity work sends post-handler transition rejection to `DeliveryMonitor`;
|
|
@@ -871,6 +908,8 @@ an internal diagnostic, not a retry guarantee.
|
|
|
871
908
|
Every domain context has an internal paired System Context. Domain events use
|
|
872
909
|
the domain `EventBus` and domain `EventStore`. System events use only the
|
|
873
910
|
System Context `EventBus`, so they never enter the domain EventStore.
|
|
911
|
+
Each EventBus validates the Event envelope and domain payload before admission,
|
|
912
|
+
storage, or delivery.
|
|
874
913
|
`persistSystemEvents()` enables the paired System Context's separate storage.
|
|
875
914
|
Bounded Contexts with Agents must enable it. For other Bounded Contexts it is optional; without
|
|
876
915
|
it, the bus validates, dispatches and notifies without appending. Schemas may come from an external dispatcher or
|
|
@@ -887,9 +926,213 @@ work to stop using dependencies, then closes contexts. Caller-managed `start()`
|
|
|
887
926
|
does not close global process facilities; close `ServerEnvironment.instance()`
|
|
888
927
|
separately when used. The final run-managed `run()` close closes its environment.
|
|
889
928
|
|
|
929
|
+
Native QueryService accepts `BytesValue` equality and range operands for annotated scalar `bytes`
|
|
930
|
+
columns and permits ascending or descending byte ordering. The shared evaluator uses unsigned
|
|
931
|
+
lexicographic comparison, including shorter equal prefixes first. This TypeScript query capability
|
|
932
|
+
does not assert JVM JDBC byte-range compatibility. `QUERY_SERVICE_MAX_RESULTS` from core is the
|
|
933
|
+
current capacity of 1,000 returned and evaluated records for one native read, not a collection-size limit.
|
|
934
|
+
|
|
935
|
+
`ServerOptions.appQueries` selects concrete generated `AppQueryHandler<Q, R>`
|
|
936
|
+
instances by exact constructor metadata before listening. Duplicate Q routes
|
|
937
|
+
or missing metadata fail startup. On direct app-node ingress,
|
|
938
|
+
`QueryService.Execute` validates a complete AppQuery envelope and Q, then
|
|
939
|
+
returns one packed paired R. Native and browser Gateways and managed Coordinators
|
|
940
|
+
forward Execute to an app node; `ClientRequest.query(generatedQ)` decodes its paired R.
|
|
941
|
+
The internal
|
|
942
|
+
`Server.query(generatedQ, executionContext)` accepts a generated Q and returns
|
|
943
|
+
its paired generated R, using a detached request and the effective actor,
|
|
944
|
+
verified reader facts, and cancellation signal supplied by the current
|
|
945
|
+
framework invocation. `ProcessManager.currentActorContext` and
|
|
946
|
+
`Agent.currentActorContext` expose only the current signal's detached actor and
|
|
947
|
+
tenant during a handler; neither establishes a verified principal. Constructor
|
|
948
|
+
injection can bind the query function to a Server. Scoped Entity reads require
|
|
949
|
+
query/full visibility and destination tenant membership. Direct calls do not
|
|
950
|
+
cross the Server's gRPC output-filter boundary.
|
|
951
|
+
|
|
952
|
+
`ReadSideFilter<typeof MessageSchema>` declares a typed policy for a registered
|
|
953
|
+
Proto message. Its protected constructor takes the generated schema and optional
|
|
954
|
+
`{ nested: true }`; `filter(message, context)` receives a detached mutable value
|
|
955
|
+
and may return the same type or `null` to remove that occurrence. The context
|
|
956
|
+
separates actor metadata from verified reader facts and identifies the current
|
|
957
|
+
operation, plus a detached containing message and field for nested calls.
|
|
958
|
+
Read-only packed bytes remain available to Buf decoding through a detached
|
|
959
|
+
`value.slice()` passed to `fromBinary`.
|
|
960
|
+
Call `addReadSideFilter(filter)` on a `BoundedContextBuilder` before `build()` to apply it to that
|
|
961
|
+
Bounded Context's outward Entity reads and retained histories. Pass `readSideFilters`
|
|
962
|
+
to `new Server(...)` to apply policies to Entity Query and App Query results
|
|
963
|
+
leaving that Server over gRPC. The same instance can be installed in both
|
|
964
|
+
places; each boundary invokes it once. Direct `Server.query()` runs BC source
|
|
965
|
+
policies but does not cross the Server output boundary. An Entity root returned
|
|
966
|
+
as `null` is omitted without another storage read; an App Query root returned
|
|
967
|
+
as `null` produces OK with zero outer `AppQueryResponse.message` entries. A
|
|
968
|
+
non-null response whose child field is absent remains one response entry.
|
|
969
|
+
History removals retain the
|
|
970
|
+
original continuation. Retained storage, hydration, and replay remain raw.
|
|
971
|
+
Direct Stand and retained-history callbacks identify the producing Bounded
|
|
972
|
+
Context. Protected history called from a handler carries its current signal
|
|
973
|
+
actor; an out-of-handler diagnostic read has no actor or authenticated reader.
|
|
974
|
+
Native SubscriptionService delivery filters already matched Entity states and
|
|
975
|
+
Event payloads through the producing Bounded Context policy and then the
|
|
976
|
+
Server policy. The `subscription` operation includes the original Topic and
|
|
977
|
+
admitted subscription; its context carries the current verified reader facts,
|
|
978
|
+
effective tenant, actor metadata, producing Bounded Context, and cancellation
|
|
979
|
+
signal. Removing a state emits `no_longer_matching` with its original Entity
|
|
980
|
+
ID; removing every Event payload suppresses that data batch. ACKs and existing
|
|
981
|
+
removal controls remain unchanged. Policy failures end only that reader stream
|
|
982
|
+
with a sanitized technical status.
|
|
983
|
+
|
|
984
|
+
The operation describes the call that produced the value. `query` includes the
|
|
985
|
+
actual Query and, during an App Query child read, the enclosing AppQuery. `app-query`
|
|
986
|
+
identifies the final application result. `direct` identifies either a typed
|
|
987
|
+
Stand state read by type URL and Entity ID, a caller-supplied RecordQuery, or a
|
|
988
|
+
normalized query plan. `history` distinguishes an Entity state-at timestamp,
|
|
989
|
+
Entity state/Event history depth, and an Agent view with its page request and
|
|
990
|
+
optional cursor. Direct callers supply actor metadata, verified reader facts,
|
|
991
|
+
and cancellation separately; they cannot override the operation.
|
|
992
|
+
Direct subscription delivery uses `kind: "direct"` with
|
|
993
|
+
`operation.subscription.typeUrl`, distinct from a one-off Stand read's
|
|
994
|
+
`operation.read`.
|
|
995
|
+
|
|
996
|
+
This filter uses the generated Project state and can be installed on the Bounded
|
|
997
|
+
Context builder that contains its Repository:
|
|
998
|
+
|
|
999
|
+
<!-- docs-snippet-path: packages/server/test/server/app-query-execution.test.ts -->
|
|
1000
|
+
|
|
1001
|
+
```ts
|
|
1002
|
+
import {
|
|
1003
|
+
type BoundedContextBuilder,
|
|
1004
|
+
ReadSideFilter,
|
|
1005
|
+
type ReadSideContext,
|
|
1006
|
+
} from "@spine-event-engine/server";
|
|
1007
|
+
import {
|
|
1008
|
+
type ProjectOverviewState,
|
|
1009
|
+
ProjectOverviewStateSchema,
|
|
1010
|
+
} from "../../test-fixtures/generated/repository-routing/project_states_pb.js";
|
|
1011
|
+
|
|
1012
|
+
/**
|
|
1013
|
+
* Replaces a project name in all ordinary Entity Query results.
|
|
1014
|
+
*/
|
|
1015
|
+
class PublicProjectName extends ReadSideFilter<typeof ProjectOverviewStateSchema> {
|
|
1016
|
+
/**
|
|
1017
|
+
* Registers the filter for project overview state.
|
|
1018
|
+
*/
|
|
1019
|
+
constructor() {
|
|
1020
|
+
super(ProjectOverviewStateSchema);
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
/**
|
|
1024
|
+
* Replaces the name only for ordinary Entity Query responses.
|
|
1025
|
+
*
|
|
1026
|
+
* @param state Project overview returned by the read side.
|
|
1027
|
+
* @param context Describes the read operation and verified reader.
|
|
1028
|
+
* @returns The project overview with its name replaced for ordinary Entity Queries.
|
|
1029
|
+
*/
|
|
1030
|
+
filter(state: ProjectOverviewState, context: ReadSideContext): ProjectOverviewState {
|
|
1031
|
+
if (context.operation.kind === "query") state.name = "Public project";
|
|
1032
|
+
return state;
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
const projectPolicy = new PublicProjectName();
|
|
1037
|
+
|
|
1038
|
+
/**
|
|
1039
|
+
* Registers the project name policy in its supplying Bounded Context.
|
|
1040
|
+
*
|
|
1041
|
+
* @param builder Configures the project Bounded Context.
|
|
1042
|
+
* @returns The builder with the read policy installed.
|
|
1043
|
+
*/
|
|
1044
|
+
function installProjectPolicy(builder: BoundedContextBuilder): BoundedContextBuilder {
|
|
1045
|
+
return builder.addReadSideFilter(projectPolicy);
|
|
1046
|
+
}
|
|
1047
|
+
```
|
|
1048
|
+
|
|
1049
|
+
The repository `onCreate` callback can inject `(q, context) => server.query(q, context)`
|
|
1050
|
+
into a Process Manager or Agent. Within an active handler, pass a generated Q
|
|
1051
|
+
with `{ actorContext: this.currentActorContext }`; the actor/tenant snapshot
|
|
1052
|
+
does not supply verified principal or provider facts. The
|
|
1053
|
+
[scoped integration test](https://github.com/SpineEventEngine/spine-ts/blob/master/packages/server/test/entity/process-manager-querying.test.ts) includes
|
|
1054
|
+
the full callback and handler invocation.
|
|
1055
|
+
|
|
1056
|
+
This example uses the generated Project query and the current Process Manager
|
|
1057
|
+
scope; pass `scopedOnCreate(server)` as the repository's `onCreate` callback.
|
|
1058
|
+
Ordinary generation and bootstrap register the decorated handler description:
|
|
1059
|
+
|
|
1060
|
+
<!-- docs-snippet-path: packages/server/test/entity/process-manager-querying.test.ts -->
|
|
1061
|
+
|
|
1062
|
+
```ts
|
|
1063
|
+
import { create } from "@bufbuild/protobuf";
|
|
1064
|
+
import {
|
|
1065
|
+
Assign,
|
|
1066
|
+
ProcessManager,
|
|
1067
|
+
type EntityOptions,
|
|
1068
|
+
type Server,
|
|
1069
|
+
type ServerQueryExecutionContext,
|
|
1070
|
+
} from "@spine-event-engine/server";
|
|
1071
|
+
import { ProcessManagerStateSchema } from "../../test-fixtures/generated/entity-metadata/visibility_pb.js";
|
|
1072
|
+
import { ProjectIdSchema } from "../../test-fixtures/generated/repository-routing/project_identifiers_pb.js";
|
|
1073
|
+
import {
|
|
1074
|
+
type ProjectCreated as ProjectionEvent,
|
|
1075
|
+
ProjectCreatedSchema as ProjectionEventSchema,
|
|
1076
|
+
} from "../../test-fixtures/generated/repository-routing/project_events_pb.js";
|
|
1077
|
+
import { type CreateReviewProject } from "../../test-fixtures/generated/validation-refusal/project_commands_pb.js";
|
|
1078
|
+
import {
|
|
1079
|
+
type GetProjectOverview_Query,
|
|
1080
|
+
type GetProjectOverview_Response,
|
|
1081
|
+
} from "../../test-fixtures/generated/repository-routing/project_queries_pb.js";
|
|
1082
|
+
import { GetProjectOverview } from "../../test-fixtures/generated/repository-routing/project_queries_app_query.js";
|
|
1083
|
+
|
|
1084
|
+
/**
|
|
1085
|
+
* Runs a typed App Query from an active Process Manager handler.
|
|
1086
|
+
*/
|
|
1087
|
+
class ScopedAppQueryManager extends ProcessManager<string, typeof ProcessManagerStateSchema> {
|
|
1088
|
+
/**
|
|
1089
|
+
* Captures the scoped query callback supplied during repository construction.
|
|
1090
|
+
*
|
|
1091
|
+
* @param options Configures this Process Manager instance.
|
|
1092
|
+
* @param ask Executes a generated App Query with trusted handler context.
|
|
1093
|
+
*/
|
|
1094
|
+
constructor(
|
|
1095
|
+
options: EntityOptions<string, typeof ProcessManagerStateSchema>,
|
|
1096
|
+
private readonly ask: (
|
|
1097
|
+
query: GetProjectOverview_Query,
|
|
1098
|
+
context: ServerQueryExecutionContext,
|
|
1099
|
+
) => Promise<GetProjectOverview_Response>,
|
|
1100
|
+
) {
|
|
1101
|
+
super(options);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
/**
|
|
1105
|
+
* Reads the current project overview while handling the creation Command.
|
|
1106
|
+
*
|
|
1107
|
+
* @param command Project creation request being assigned.
|
|
1108
|
+
* @returns Event carrying the selected overview name.
|
|
1109
|
+
*/
|
|
1110
|
+
@Assign
|
|
1111
|
+
async assign(command: CreateReviewProject): Promise<ProjectionEvent> {
|
|
1112
|
+
const result = await this.ask(
|
|
1113
|
+
create(GetProjectOverview.querySchema, {
|
|
1114
|
+
project: create(ProjectIdSchema, { value: command.id }),
|
|
1115
|
+
}),
|
|
1116
|
+
{ actorContext: this.currentActorContext },
|
|
1117
|
+
);
|
|
1118
|
+
return create(ProjectionEventSchema, {
|
|
1119
|
+
id: command.id,
|
|
1120
|
+
name: result.project?.name ?? "",
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
/**
|
|
1126
|
+
* Injects the Server's typed App Query route into new Process Manager instances.
|
|
1127
|
+
*/
|
|
1128
|
+
export const scopedOnCreate =
|
|
1129
|
+
(server: Server) => (options: EntityOptions<string, typeof ProcessManagerStateSchema>) =>
|
|
1130
|
+
new ScopedAppQueryManager(options, (q, context) => server.query(q, context));
|
|
1131
|
+
```
|
|
1132
|
+
|
|
890
1133
|
`Stand` registers entity state for point/list reads, updates current state, and
|
|
891
|
-
offers in-process subscriptions. The
|
|
892
|
-
`catchUpReadSide(options?)` clears every registered Projection state and
|
|
1134
|
+
offers in-process subscriptions. The local read-side reset helper
|
|
1135
|
+
`catchUpReadSide(options?)` clears every registered Projection state and dispatches
|
|
893
1136
|
the whole stored event history through matching Projection subscribers; it does
|
|
894
1137
|
not append events. Single-tenant contexts reject a tenant option; multitenant
|
|
895
1138
|
contexts require one. It cannot target a Projection repository, entity IDs, or
|
|
@@ -917,8 +1160,57 @@ authoritative queries from current state and Entity subscriptions observe
|
|
|
917
1160
|
committed `EntityStateChanged` messages on the paired System Context EventBus,
|
|
918
1161
|
so a direct Stand write alone does not produce a subscription update. Consumer
|
|
919
1162
|
callback failures are isolated from post-commit observation and other
|
|
920
|
-
consumers.
|
|
921
|
-
|
|
1163
|
+
consumers. Native subscription updates enter the existing bounded delivery
|
|
1164
|
+
queue before asynchronous read policies run; a full queue closes that stream.
|
|
1165
|
+
For a surviving Event, policy replaces only its domain payload and retains the original envelope.
|
|
1166
|
+
Direct `Stand.subscribe(schema, { next, error }, options)` applies only its
|
|
1167
|
+
producing Bounded Context's read policies, independently for each local
|
|
1168
|
+
subscriber. `next` receives either `{ kind: "state", state, previousState? }`
|
|
1169
|
+
or `{ kind: "removal", noLongerMatching: true }`; both retain the original type
|
|
1170
|
+
URL, Entity ID, version, and tenant metadata. A removed prior state is omitted
|
|
1171
|
+
from a surviving current state. `error` receives one terminal local policy,
|
|
1172
|
+
callback, or capacity fault. There is no raw callback overload. Options accept
|
|
1173
|
+
trusted `reader` facts, `actorContext` metadata, tenant, and cancellation;
|
|
1174
|
+
`ServerEnvironmentSettings.subscriptionQueueCapacity` sets the waiting count
|
|
1175
|
+
for direct and server-served subscriptions. Configure it before constructing
|
|
1176
|
+
the Stand or Bounded Context. It defaults to 100 waiting updates in addition to
|
|
1177
|
+
one active update; zero allows the active update with no waiting updates. Filter calls
|
|
1178
|
+
and `next` completion are sequential per subscriber. Overflow closes only that
|
|
1179
|
+
subscriber, clears waiting updates, invalidates the active result, and reports
|
|
1180
|
+
`StandSubscriptionCapacityError`; committed writes and other subscribers
|
|
1181
|
+
continue. Unsubscribe, cancellation, and Stand close invalidate outstanding
|
|
1182
|
+
results without waiting for a nonsettling policy. A producer does not await
|
|
1183
|
+
asynchronous policy or observer completion, although CPU-heavy synchronous
|
|
1184
|
+
policy work still runs on the producer's Node event loop.
|
|
1185
|
+
Direct subscribers do not cross a Server output-filter boundary. The package
|
|
1186
|
+
does not guarantee cluster-complete observations, subscription replay,
|
|
1187
|
+
event-gap repair, or exactly-once effects.
|
|
1188
|
+
|
|
1189
|
+
This typed local observer handles visible states, removals, and terminal faults.
|
|
1190
|
+
Configure `subscriptionQueueCapacity: 250` through `ServerEnvironment.when(...).use(...)`
|
|
1191
|
+
before creating the Stand to allow 250 waiting updates beyond the active one:
|
|
1192
|
+
|
|
1193
|
+
<!-- docs-snippet-path: packages/server/test/stand/stand-direct-subscription.test.ts -->
|
|
1194
|
+
|
|
1195
|
+
```ts
|
|
1196
|
+
import { type Stand, type StandSubscription } from "@spine-event-engine/server";
|
|
1197
|
+
import { ProjectOverviewStateSchema } from "../../test-fixtures/generated/repository-routing/project_states_pb.js";
|
|
1198
|
+
|
|
1199
|
+
export function observeProjects(
|
|
1200
|
+
stand: Stand,
|
|
1201
|
+
onState: (name: string) => void,
|
|
1202
|
+
onRemoval: (id: unknown) => void,
|
|
1203
|
+
onError: (error: Error) => void,
|
|
1204
|
+
): StandSubscription {
|
|
1205
|
+
return stand.subscribe(ProjectOverviewStateSchema, {
|
|
1206
|
+
next(update) {
|
|
1207
|
+
if (update.kind === "state") onState(update.state.name);
|
|
1208
|
+
else onRemoval(update.id);
|
|
1209
|
+
},
|
|
1210
|
+
error: onError,
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
```
|
|
922
1214
|
|
|
923
1215
|
### Native and managed hosting
|
|
924
1216
|
|
|
@@ -988,10 +1280,123 @@ gRPC-Web transport message accepted by the public listener, so the effective
|
|
|
988
1280
|
inbound limit is the stricter applicable limit. `writeMaxBytes` limits public
|
|
989
1281
|
transport responses independently.
|
|
990
1282
|
|
|
1283
|
+
### Native reader admission
|
|
1284
|
+
|
|
1285
|
+
Configure app-node admission at `ServerOptions.services.readerAdmission`:
|
|
1286
|
+
|
|
1287
|
+
- `direct.authenticate({ service, method, value, headers, peer, signal })` authenticates
|
|
1288
|
+
a direct native request. It returns `{ reader, invalidated? }` with verified
|
|
1289
|
+
`{ principal, authorityFacts }` in `reader`, `{ reader: {} }` for deliberate
|
|
1290
|
+
public access, or `undefined` to deny access. `signal` begins before
|
|
1291
|
+
authentication and ends on terminal unary or complete stream exit.
|
|
1292
|
+
The request value contains canonical Proto bytes; `peer` is the Connect
|
|
1293
|
+
`HandlerContext` for that invocation. `invalidated` is a separate local
|
|
1294
|
+
AbortSignal captured by the host; it never enters reader facts or proofs.
|
|
1295
|
+
- `forwarded.verify(bytes, proof, peer)` verifies an assertion from a trusted
|
|
1296
|
+
forwarding peer. It must authenticate that peer and validate the supplied
|
|
1297
|
+
proof over the exact bytes. `BrowserServerOptions.forwardingProof.issue(bytes)`
|
|
1298
|
+
supplies the corresponding gateway adapter.
|
|
1299
|
+
|
|
1300
|
+
Admission completes before service validation, routing, or subscription mutation.
|
|
1301
|
+
Principal IDs and authority facts are copied per invocation. `principal.attributes`
|
|
1302
|
+
remains a string map; `authorityFacts` accepts finite JSON values, including
|
|
1303
|
+
arrays and nested objects. ActorContext remains separate from authentication.
|
|
1304
|
+
Neither the framework nor these ports promise live provider information.
|
|
1305
|
+
|
|
1306
|
+
With no admission adapters, native requests remain public. Internal identity
|
|
1307
|
+
headers are still rejected without a configured verifier. With a forwarding
|
|
1308
|
+
verifier but no direct adapter, a request without a forwarding assertion is
|
|
1309
|
+
rejected as unauthenticated. An application that also permits public direct
|
|
1310
|
+
requests can explicitly return an empty reader from its direct adapter.
|
|
1311
|
+
Malformed assertions and denied authentication return `UNAUTHENTICATED`;
|
|
1312
|
+
unexpected adapter faults return sanitized `INTERNAL`. Explicit cancellation,
|
|
1313
|
+
availability, and permission statuses retain their meaning.
|
|
1314
|
+
|
|
1315
|
+
`ManagedServerApplication` accepts `readerAdmission` for its Coordinator listener
|
|
1316
|
+
and `forwardingProof` for Coordinator-to-child requests. Configure the receiving
|
|
1317
|
+
child Servers with their verifier as well. Each hop verifies its incoming
|
|
1318
|
+
assertion and issues a new proof for the actual outgoing operation and payload.
|
|
1319
|
+
Loopback placement alone does not authenticate a forwarding peer.
|
|
1320
|
+
|
|
1321
|
+
A standard session strategy accepts the host signal during `resolve` and
|
|
1322
|
+
returns an invalidation signal for local logout, its captured session deadline,
|
|
1323
|
+
and strategy close. A custom direct authenticator may omit invalidation, but
|
|
1324
|
+
then the host cannot stop an active stream on local logout or expiry. A
|
|
1325
|
+
successful unary request releases its operation even while the transport peer
|
|
1326
|
+
stays connected. Subscribe releases its operation after definition creation;
|
|
1327
|
+
Activate authenticates again and holds its operation through ACK and every
|
|
1328
|
+
update. Normal completion, host handler iterator return, cancellation, and host
|
|
1329
|
+
close stop the operation. Raw Connect clients must abort an explicit
|
|
1330
|
+
`AbortSignal` or call `SubscriptionService.Cancel` to stop remote stream work;
|
|
1331
|
+
returning from the client iterator alone does not signal transport cancellation.
|
|
1332
|
+
An active stream in another process receives no local signal.
|
|
1333
|
+
|
|
1334
|
+
```ts
|
|
1335
|
+
import type { SignedSessions } from "@spine-event-engine/auth";
|
|
1336
|
+
import { Server, type ReaderAdmissionResult, type ServerOptions } from "@spine-event-engine/server";
|
|
1337
|
+
|
|
1338
|
+
/**
|
|
1339
|
+
* Configures direct native bearer admission with the local session strategy.
|
|
1340
|
+
*
|
|
1341
|
+
* @param sessions Strategy also used by the application's logout action.
|
|
1342
|
+
* @param contexts Application Bounded Contexts exposed by this Server.
|
|
1343
|
+
* @returns Server configured for native authentication.
|
|
1344
|
+
*/
|
|
1345
|
+
function nativeServer(
|
|
1346
|
+
sessions: SignedSessions,
|
|
1347
|
+
contexts: NonNullable<ServerOptions["contexts"]>,
|
|
1348
|
+
): Server {
|
|
1349
|
+
return new Server({
|
|
1350
|
+
contexts,
|
|
1351
|
+
services: {
|
|
1352
|
+
readerAdmission: {
|
|
1353
|
+
direct: {
|
|
1354
|
+
/**
|
|
1355
|
+
* Resolves the bearer credential within the host operation.
|
|
1356
|
+
*
|
|
1357
|
+
* @param request Native peer and lifetime facts.
|
|
1358
|
+
* @returns Verified reader and invalidation, or denial.
|
|
1359
|
+
*/
|
|
1360
|
+
async authenticate(request): Promise<ReaderAdmissionResult | undefined> {
|
|
1361
|
+
const authorization = request.peer.requestHeader.get("authorization");
|
|
1362
|
+
const token = /^Bearer ([^\s,]+)$/i.exec(authorization ?? "")?.[1];
|
|
1363
|
+
if (token === undefined) return undefined;
|
|
1364
|
+
const session = await sessions.resolve(
|
|
1365
|
+
{ kind: "bearer", value: token },
|
|
1366
|
+
request.signal,
|
|
1367
|
+
);
|
|
1368
|
+
if (session?.invalidated === undefined) return undefined;
|
|
1369
|
+
return { reader: { principal: session.principal }, invalidated: session.invalidated };
|
|
1370
|
+
},
|
|
1371
|
+
},
|
|
1372
|
+
},
|
|
1373
|
+
},
|
|
1374
|
+
});
|
|
1375
|
+
}
|
|
1376
|
+
```
|
|
1377
|
+
|
|
1378
|
+
The admitted logout action checks `await sessions.logout(credential)` on that
|
|
1379
|
+
same strategy. It reports durable success only for `revoked`; `unavailable`
|
|
1380
|
+
and `expiryOnly` remain distinct. Application shutdown closes both Server and
|
|
1381
|
+
strategy. A custom authenticator still enforces its own credential and policy
|
|
1382
|
+
checks; a token in a header alone does not establish authority.
|
|
1383
|
+
|
|
1384
|
+
Authenticated forwarding now requires an issuer; configure both gateway and
|
|
1385
|
+
receiving node when updating an existing authenticated deployment. The native
|
|
1386
|
+
Server root still does not load the optional auth package. The forwarding ports
|
|
1387
|
+
do not make a JVM endpoint understand the TS internal reader assertion.
|
|
1388
|
+
|
|
1389
|
+
### Browser hosting
|
|
1390
|
+
|
|
991
1391
|
For `BrowserServer` from `@spine-event-engine/server/browser`, origins must be unique canonical HTTP(S) origins with no path, query,
|
|
992
1392
|
fragment, or trailing slash. Requests without an allowed exact `Origin` receive
|
|
993
1393
|
403 before RPC handling. Allowed responses include credentialed exact-origin
|
|
994
1394
|
CORS, `Vary: Origin`, protocol request headers, and exposed gRPC status headers.
|
|
1395
|
+
RPC and auth-route preflight completes before credential extraction. The host
|
|
1396
|
+
allows configured CSRF headers alongside Connect and gRPC-Web protocol headers;
|
|
1397
|
+
actual responses, including permitted-origin terminal errors, retain exact-origin
|
|
1398
|
+
credentialed CORS. Callback responses cannot replace that host policy with a
|
|
1399
|
+
wildcard origin.
|
|
995
1400
|
|
|
996
1401
|
Supplying `browser.backend.baseUrl` selects standalone mode. It is one canonical
|
|
997
1402
|
HTTP(S) origin for a Spine TS or JVM backend; the gateway never closes
|
|
@@ -1011,7 +1416,8 @@ re-query authoritative state after duplicate updates or generic loss notices.
|
|
|
1011
1416
|
Each registration has one exact `GET` or `POST` canonical path, one method per
|
|
1012
1417
|
path, exact canonical origins, finite request bytes, and a finite timeout. A
|
|
1013
1418
|
missing Origin is rejected unless that route explicitly permits it for an OAuth
|
|
1014
|
-
callback
|
|
1419
|
+
callback and validates the protocol state in its handler.
|
|
1420
|
+
Unknown paths, method/origin failures, body overflow, timeout, and
|
|
1015
1421
|
handler failure return fixed 404, 405, 403, 413, 504, and 500 responses without
|
|
1016
1422
|
calling application code on rejected input. The 404 unknown-path result applies
|
|
1017
1423
|
only after framework Origin admission. Auth admission is bounded to 64 active
|
|
@@ -1022,10 +1428,18 @@ transfer. Response bodies use `writeMaxBytes`; overflow returns 413 before any
|
|
|
1022
1428
|
application status or headers are committed. Listener close ultimately aborts
|
|
1023
1429
|
an active request through its disconnect signal.
|
|
1024
1430
|
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1431
|
+
Every credential reader and auth route declares CSRF header names, primary
|
|
1432
|
+
credential-cookie names, and supporting state-cookie names. The host snapshots
|
|
1433
|
+
these declarations before startup, rejects repeated raw Authorization, Origin,
|
|
1434
|
+
configured CSRF headers, and selected cookies, and checks simultaneous primary
|
|
1435
|
+
credentials only for the active reader or selected route. Legal split Cookie
|
|
1436
|
+
fields and repeated unrelated cookies remain available to the reader.
|
|
1437
|
+
`OpaqueSessionCookies` provides a strict reader for a sealed opaque session or
|
|
1438
|
+
bearer credential, including CSRF checks; a primary cookie and bearer field
|
|
1439
|
+
together are rejected. The legacy `cookies` option cannot be combined with
|
|
1440
|
+
`credentials`. Without either, the host reads one valid Bearer field. A reader
|
|
1441
|
+
returns a credential, absence, or terminal rejection; a thrown read is terminal
|
|
1442
|
+
unavailability. Credentials and raw
|
|
1029
1443
|
transport metadata do not reach the private backend. The application-supplied
|
|
1030
1444
|
context resolver replaces caller actor and tenant facts before forwarding.
|
|
1031
1445
|
|
|
@@ -1050,7 +1464,11 @@ never returns private native data through public subscription responses.
|
|
|
1050
1464
|
The durable registry preserves one approved `GatewayAuthenticatedSubscription`
|
|
1051
1465
|
per public subscription through a process restart. It validates the record ID,
|
|
1052
1466
|
full Subscription, and expiry before use. The trusted Actor and Tenant remain
|
|
1053
|
-
inside the stored Topic
|
|
1467
|
+
inside the stored Topic; a stable principal ID is stored beside it. Activate and
|
|
1468
|
+
Cancel check the newly admitted reader and Actor/Tenant before stream effects
|
|
1469
|
+
or record mutation. Changed authority facts do not change the principal ID.
|
|
1470
|
+
An explicit anonymous binding remains distinct from missing legacy identity;
|
|
1471
|
+
records with missing identity cannot be claimed by a caller. Create assigns
|
|
1054
1472
|
the public ID directly; no quota, reservation, fingerprint, or lease is
|
|
1055
1473
|
persisted. Cleanup remains bounded and restart-safe at the record level. It
|
|
1056
1474
|
cleans the backend definition before deleting its record. It is not a cleaner
|
|
@@ -1064,11 +1482,21 @@ delivery, global update ordering, nor cluster-complete notification delivery.
|
|
|
1064
1482
|
local durable-row abstraction. Inbox and shard storage require a single-tenant
|
|
1065
1483
|
Delivery storage context shared by all tenants; the packed signal supplies
|
|
1066
1484
|
the dispatch tenant. Read limits are 1–1000; work leases are
|
|
1067
|
-
1000–2147483647 ms. Framework
|
|
1068
|
-
updates,
|
|
1485
|
+
1000–2147483647 ms. Framework delivery supports command handling, projection
|
|
1486
|
+
updates, process-manager reactions, and Aggregate Event reactions. Callbacks must tolerate redelivery:
|
|
1069
1487
|
lost renewal can prevent stale finalization but cannot undo a callback already
|
|
1070
1488
|
run. The package exposes no general raw worker callback API.
|
|
1071
1489
|
|
|
1490
|
+
Repository-backed Command and Event routing awaits recipient Inbox writes and
|
|
1491
|
+
notifies Delivery immediately. A successful post through this routing means
|
|
1492
|
+
recipient Inbox writes completed; it does not wait for handler execution or row
|
|
1493
|
+
acknowledgement. Restart durability depends on the configured storage provider.
|
|
1494
|
+
Custom dispatchers define their own completion contract. After an Entity commit, the
|
|
1495
|
+
source delivery awaits one outgoing bus handoff attempt through recipient Inbox
|
|
1496
|
+
acceptance. A failed publication is reported and contained without re-executing
|
|
1497
|
+
a successful source Command. Entity storage, recipient Inbox writes, and source
|
|
1498
|
+
acknowledgement remain separate operations.
|
|
1499
|
+
|
|
1072
1500
|
Inbox reads return `InboxMessage` with a full-precision Protobuf `Timestamp` in
|
|
1073
1501
|
`whenReceived`. `InboxMessageInput` requires a precise `Timestamp` for new
|
|
1074
1502
|
receipts. Writes, acknowledgements, and exact removals use an `InboxMessage`
|
|
@@ -1124,7 +1552,8 @@ internally and persist their envelope in the target Entity Inbox before any
|
|
|
1124
1552
|
handler runs. Projection input uses the same environment Delivery and shared
|
|
1125
1553
|
Inbox, with its separate recipient identity.
|
|
1126
1554
|
|
|
1127
|
-
Local posting waits for
|
|
1555
|
+
Local repository-backed posting waits for recipient Inbox writes and scheduling,
|
|
1556
|
+
while handler execution and row acknowledgement continue through Delivery. For an
|
|
1128
1557
|
Agent, successful acceptance and Inbox acknowledgement let posting finish while
|
|
1129
1558
|
the live handler continues. If Agent execution capacity is unavailable, the input
|
|
1130
1559
|
stays pending and the local call reports the existing command-posting error.
|
|
@@ -1170,9 +1599,9 @@ scan delivers nothing; it does not restart after each productive page.
|
|
|
1170
1599
|
Delivery compares pending rows with delivered rows in the current page
|
|
1171
1600
|
and a process-local cache of the 1,000 most recent deliveries, then removes
|
|
1172
1601
|
duplicates. The 30-second Inbox deduplication window controls how long delivered
|
|
1173
|
-
rows remain available as duplicate evidence; it is not a
|
|
1602
|
+
rows remain available as duplicate evidence; it is not a delivery-retention
|
|
1174
1603
|
period. Persisted accepted rows follow their delivery lifecycle and may be
|
|
1175
|
-
|
|
1604
|
+
redelivered according to that lifecycle after the deduplication window has elapsed.
|
|
1176
1605
|
|
|
1177
1606
|
Direct delivery uses the configured Inbox storage clock for both retained-row
|
|
1178
1607
|
duplicate checks and early cleanup filtering. Remote delivery uses local client
|
|
@@ -1193,8 +1622,8 @@ reception monitor. A custom reception action that completes normally completes
|
|
|
1193
1622
|
delivery and the enclosing stage acknowledges the row. An action that rejects
|
|
1194
1623
|
propagates its error and leaves the row `TO_DELIVER`. `markDelivered()` does not
|
|
1195
1624
|
mean domain processing succeeded.
|
|
1196
|
-
Managed repository
|
|
1197
|
-
storage and
|
|
1625
|
+
Managed repository delivery sends actual handler failures to the monitor while
|
|
1626
|
+
storage and delivery infrastructure errors propagate. A direct `onMessage` callback
|
|
1198
1627
|
is treated as the endpoint. Handler effects and acknowledgement are separate,
|
|
1199
1628
|
so a crash between them may redeliver.
|
|
1200
1629
|
The framework adds no persisted attempts, quarantine, dead-letter, receipt, or
|