@sincpro/client-framework 1.0.0 → 1.2.0
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/AGENTS.md +71 -13
- package/MANIFESTO.md +59 -14
- package/README.md +456 -55
- package/dist/circuit_breaker/domain/circuit.d.ts +29 -0
- package/dist/circuit_breaker/domain/circuit.js +37 -0
- package/dist/circuit_breaker/entrypoint/circuit-breaker.d.ts +56 -0
- package/dist/circuit_breaker/entrypoint/circuit-breaker.js +94 -0
- package/dist/circuit_breaker/index.d.ts +9 -0
- package/dist/circuit_breaker/index.js +9 -0
- package/dist/client/adapters/json-rpc.d.ts +40 -0
- package/dist/client/adapters/json-rpc.js +97 -0
- package/dist/client/adapters/rest-operations.d.ts +19 -0
- package/dist/client/adapters/rest-operations.js +66 -0
- package/dist/client/domain/operation.d.ts +73 -0
- package/dist/client/domain/operation.js +19 -0
- package/dist/client/index.d.ts +5 -1
- package/dist/client/index.js +5 -1
- package/dist/client/services/guarded.d.ts +17 -0
- package/dist/client/services/guarded.js +19 -0
- package/dist/client/services/propagation.d.ts +20 -7
- package/dist/client/services/propagation.js +44 -15
- package/dist/codegen/entrypoint/cli.js +7 -7
- package/dist/codegen/services/client.d.ts +7 -8
- package/dist/codegen/services/client.js +50 -148
- package/dist/codegen/services/scaffold.d.ts +7 -4
- package/dist/codegen/services/scaffold.js +23 -27
- package/dist/common/failures.d.ts +83 -0
- package/dist/common/failures.js +212 -0
- package/dist/common/index.d.ts +1 -0
- package/dist/common/index.js +1 -0
- package/dist/context/domain/execution.d.ts +12 -0
- package/dist/context/entrypoint/context.d.ts +5 -4
- package/dist/core/domain/bus.d.ts +16 -7
- package/dist/core/domain/bus.js +14 -6
- package/dist/core/domain/dto.d.ts +33 -3
- package/dist/core/domain/dto.js +24 -1
- package/dist/core/domain/errors.d.ts +2 -2
- package/dist/core/domain/pipeline.d.ts +89 -0
- package/dist/core/domain/pipeline.js +1 -0
- package/dist/core/domain/services.d.ts +16 -5
- package/dist/core/domain/services.js +5 -2
- package/dist/core/entrypoint/bounded-context.d.ts +96 -16
- package/dist/core/entrypoint/bounded-context.js +244 -39
- package/dist/core/index.d.ts +4 -2
- package/dist/core/index.js +3 -2
- package/dist/core/infrastructure/listeners.d.ts +28 -0
- package/dist/core/infrastructure/listeners.js +34 -0
- package/dist/core/services/binding.d.ts +2 -2
- package/dist/core/services/binding.js +6 -3
- package/dist/core/services/execution.d.ts +59 -12
- package/dist/core/services/execution.js +114 -19
- package/dist/core/services/interceptors.d.ts +15 -0
- package/dist/core/services/interceptors.js +25 -0
- package/dist/core/services/listening.d.ts +17 -0
- package/dist/core/services/listening.js +25 -0
- package/dist/event_driven/adapters/memory-event-store.d.ts +10 -0
- package/dist/event_driven/adapters/memory-event-store.js +23 -0
- package/dist/event_driven/adapters/stored-queue.d.ts +68 -0
- package/dist/event_driven/adapters/stored-queue.js +193 -0
- package/dist/event_driven/adapters/sync-queue.d.ts +11 -0
- package/dist/event_driven/adapters/sync-queue.js +19 -0
- package/dist/event_driven/domain/envelope.d.ts +28 -0
- package/dist/event_driven/domain/envelope.js +19 -0
- package/dist/event_driven/domain/queue.d.ts +40 -0
- package/dist/event_driven/domain/queue.js +1 -0
- package/dist/event_driven/domain/store.d.ts +56 -0
- package/dist/event_driven/domain/store.js +1 -0
- package/dist/event_driven/entrypoint/publisher.d.ts +26 -0
- package/dist/event_driven/entrypoint/publisher.js +35 -0
- package/dist/event_driven/entrypoint/subscriber.d.ts +42 -0
- package/dist/event_driven/entrypoint/subscriber.js +62 -0
- package/dist/event_driven/entrypoint/subscriptions.d.ts +110 -0
- package/dist/event_driven/entrypoint/subscriptions.js +184 -0
- package/dist/event_driven/index.d.ts +22 -0
- package/dist/event_driven/index.js +20 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.js +7 -2
- package/dist/observability/adapters/memory.d.ts +1 -1
- package/dist/observability/domain/logging.d.ts +10 -4
- package/dist/observability/services/logging.d.ts +7 -4
- package/dist/observability/services/logging.js +3 -2
- package/dist/observability/services/outcome.d.ts +6 -6
- package/dist/observability/services/outcome.js +10 -13
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -25,17 +25,18 @@ Each module is laid out as sincpro_framework's are: `domain/` (ports, types and
|
|
|
25
25
|
(the process's state and technical support) and `entrypoint/` (the doors a caller uses). A
|
|
26
26
|
module has only the layers it needs, and says what it offers in its own `index.ts`.
|
|
27
27
|
|
|
28
|
-
| Folder | What
|
|
29
|
-
| -------------------- |
|
|
30
|
-
| `src/common/` | The values of the wire: ids, a day and an instant, `Decimal`, `Money`, `valuesOf` / `rebuilt`
|
|
31
|
-
| `src/context/` | `ContextStore`, codec, `Execution` (domain); memory and plain codec; `Context`; the process's
|
|
32
|
-
| `src/observability/` | Tracer/ErrorReporter/Logger ports; console, memory, OpenTelemetry, Sentry; loggers, reporting
|
|
33
|
-
| `src/client/` | `Transport`, `entity()`, the contract (domain); REST, memory, Parquet; discovery, propagation
|
|
34
|
-
| `src/model/` | Model definitions: `defineMeta`, from a JSON Schema, the store of known ones
|
|
35
|
-
| `src/core/` | DTOs, bus,
|
|
36
|
-
| `src/
|
|
37
|
-
| `src/
|
|
38
|
-
| `
|
|
28
|
+
| Folder | What |
|
|
29
|
+
| -------------------- | ----------------------------------------------------------------------------------------------- |
|
|
30
|
+
| `src/common/` | The values of the wire: ids, a day and an instant, `Decimal`, `Money`, `valuesOf` / `rebuilt` |
|
|
31
|
+
| `src/context/` | `ContextStore`, codec, `Execution` (domain); memory and plain codec; `Context`; the process's |
|
|
32
|
+
| `src/observability/` | Tracer/ErrorReporter/Logger ports; console, memory, OpenTelemetry, Sentry; loggers, reporting |
|
|
33
|
+
| `src/client/` | `Transport`, `entity()`, the contract (domain); REST, memory, Parquet; discovery, propagation |
|
|
34
|
+
| `src/model/` | Model definitions: `defineMeta`, from a JSON Schema, the store of known ones |
|
|
35
|
+
| `src/core/` | DTOs and events, bus, services, handlers, extension points; pipeline; `BoundedContext`; process |
|
|
36
|
+
| `src/event_driven/` | `Envelope`, `EventQueue`/`Subscribes` ports; queues; `Publisher`, `Subscriber`, `Subscriptions` |
|
|
37
|
+
| `src/codegen/` | The generator and its CLI (`sincpro-client generate`) — the only place Node is allowed |
|
|
38
|
+
| `src/testing/` | The public entry of the in-memory observability adapters |
|
|
39
|
+
| `tests/` | vitest in Node, one folder per module; `tests/support/` holds fakes and forge's contracts |
|
|
39
40
|
|
|
40
41
|
## Hard rules (the build enforces them — `make doctor`)
|
|
41
42
|
|
|
@@ -70,7 +71,58 @@ module has only the layers it needs, and says what it offers in its own `index.t
|
|
|
70
71
|
instance, shared by the next call, the call beside it and every `scoped` variant — as in
|
|
71
72
|
sincpro_framework. What belongs to one request goes in its arguments or its context.
|
|
72
73
|
- **A span uses stable service metadata, not constructor.name.** `serviceName = "Cancel"` gives
|
|
73
|
-
`project/Cancel`, identity `project.Cancel`. Commands route by exact constructor
|
|
74
|
+
`project/Cancel`, identity `project.Cancel`. Commands and events route by exact constructor
|
|
75
|
+
identity. An event declares `static eventName` (its wire name) for the same reason; `name` is
|
|
76
|
+
reserved for it.
|
|
77
|
+
- **A replacement names what it replaces.** `feature(dto, { replaces: Service })` is refused
|
|
78
|
+
unless `Service` is registered now for `dto`; its spans carry `sincpro.replaces`. A service
|
|
79
|
+
meant to be subclassed declares `static readonly serviceName: string = "…"`, or the literal
|
|
80
|
+
type stops a subclass from renaming itself.
|
|
81
|
+
- **How events are carried is here; where they are kept is the platform's.** One use case of an
|
|
82
|
+
event per bounded context, one `EventSubscriber` per event per `Subscriptions`; a `Subscriber`
|
|
83
|
+
executes an event in each. `storedQueue` holds the rules — store first, deliver in order,
|
|
84
|
+
retry what may succeed, set aside what never will, never run one who heard it twice — over an
|
|
85
|
+
`EventStore` the platform plugs (SQLite, IndexedDB, memory). A store only keeps records; it
|
|
86
|
+
never decides.
|
|
87
|
+
- **A subscriber is an entrypoint, not a use case.** An `EventSubscriber` reads the event and
|
|
88
|
+
executes Commands on a bounded context it was given, as a cron does on a tick; what to decide,
|
|
89
|
+
and a workflow of several use cases, is a Feature or an ApplicationService it executes. Its run
|
|
90
|
+
is layer `entrypoint`: no interceptor wraps it, no listener hears it, no error handler answers
|
|
91
|
+
it. An event may instead be the input of a use case (`feature(Event)(Service)`) when the use
|
|
92
|
+
case exists because of it. A `Subscriptions` is named apart from every bounded context: the
|
|
93
|
+
queue keeps who heard an event by name, and `Subscriber` refuses two of one name.
|
|
94
|
+
- **A bounded context injected into a run is bound to it** (`BIND`): what it executes is a child
|
|
95
|
+
of the caller — span, flow, cause, signal, one report — with its own dependencies, context and
|
|
96
|
+
handlers. A failure keeps the outcome where it first failed, so a caller does not report what
|
|
97
|
+
the callee expected.
|
|
98
|
+
- **A bounded context is named for what it is.** `salesBoundedContext`, never `sales` (an
|
|
99
|
+
entity?) and never `ctx` or `bc`: `context` is already the run's. The scaffold writes it so.
|
|
100
|
+
- **A circuit belongs where the dependency is.** `CircuitBreaker` (in the process's memory, the
|
|
101
|
+
backend's semantics: `COUNTED_KINDS`, half-open with one probe) guards a transport
|
|
102
|
+
(`guardedTransport`) or an adapter; its `CircuitOpen` declares `unavailable` and a wait, so
|
|
103
|
+
nothing else needs to know a circuit exists.
|
|
104
|
+
- **Delivery is at least once.** A stored event may run twice after a crash. A use case of the
|
|
105
|
+
event keys its effect by `this.execution.causationId` (the envelope's id); an `EventSubscriber`
|
|
106
|
+
finds the same id in its own run and hands it in the Command it executes — the use case's
|
|
107
|
+
`causationId` is the subscriber's run —, or the effect is keyed by the entity the event names.
|
|
108
|
+
`maxAttempts` sets aside what keeps failing. One process drains a store. `execution` is
|
|
109
|
+
reserved.
|
|
110
|
+
- **A failure says its kind, whatever the wire.** `failureOf` reads a declared `failureKind`, the
|
|
111
|
+
`Problem` (the same over REST, JSON-RPC and gRPC), then a REST status. A transport of another
|
|
112
|
+
wire, or another backend's adapter, declares the kind with `kindOfGrpcCode` /
|
|
113
|
+
`kindOfJsonRpcCode` / `kindOfHttpStatus` — never a table of its own. Retrying, setting aside,
|
|
114
|
+
reporting and the outcome all follow from it.
|
|
115
|
+
- **A run can be cancelled.** `execute(…, { signal })`: it ends `aborted` at once, its Features
|
|
116
|
+
inherit the signal, `this.signal` reads it, and a bound transport sends it with every request.
|
|
117
|
+
`signal` is reserved, like `context` and `logger`.
|
|
118
|
+
- **No variable named `process`.** `make doctor` reads `process.` as Node outside `codegen/`.
|
|
119
|
+
- **A run belongs to a flow.** `correlationId` is shared by every run and event of one flow;
|
|
120
|
+
`causationId` names what directly caused it. A dependency carrying `BIND` (the `Publisher`)
|
|
121
|
+
is bound to the run, so what it publishes joins that flow with nothing passed by hand.
|
|
122
|
+
- **The pipeline is closed for modification, open for extension.** Change the flow in
|
|
123
|
+
`executeService` only for every run; what one application needs goes through an interceptor
|
|
124
|
+
(control, inside the run, keeps the class), a listener (observation, never changes the
|
|
125
|
+
answer, its failure ignored) or `extend`.
|
|
74
126
|
- **Dependency declarations do not register anything.** Service bases declare typed attributes
|
|
75
127
|
from a shared dependency interface. Only `addDependency` supplies runtime values; a missing
|
|
76
128
|
dependency fails explicitly.
|
|
@@ -87,6 +139,12 @@ module has only the layers it needs, and says what it offers in its own `index.t
|
|
|
87
139
|
- **A store's `load` may answer a new object each time** (one that parses JSON does): `Context`
|
|
88
140
|
keeps one object per `version`, so a screen can hand `values` to `useSyncExternalStore`. A store
|
|
89
141
|
that changes without moving the version is a broken store.
|
|
142
|
+
- **The generated client never names a wire.** A method is `transport.call(operations.X, input,
|
|
143
|
+
options)`: no route, no verb, no RPC method. The contract's bindings (`rest`, `rpc`, `grpc`,
|
|
144
|
+
`mcp`) are data in `operations`; an `OperationTransport` adapter reads the one of its wire
|
|
145
|
+
(`restOperations`, `jsonRpcOperations`, a platform's gRPC) and throws `OperationNotPublished`
|
|
146
|
+
for the rest. A run binds an `OperationTransport` as it binds a `Transport`: headers and signal.
|
|
147
|
+
The REST `Transport` stays for `restOperations` and for screens (`entity()`, pages, drafts).
|
|
90
148
|
- **What is generated is types, never classes.** A generated DTO is the remote contract: a
|
|
91
149
|
class extending `Command` could be registered on the local bus, which is what keeps a remote
|
|
92
150
|
operation and a local use case apart. Runtime knowledge goes in data (`operations`, `events`);
|
|
@@ -112,7 +170,7 @@ module has only the layers it needs, and says what it offers in its own `index.t
|
|
|
112
170
|
- **An error handler answers; it does not observe.** The failure is recorded where it happened —
|
|
113
171
|
span, log line, report — before the layer's handlers run, as in sincpro_framework. A handler
|
|
114
172
|
returns the answer or throws to the next; the global chain wraps only `execute` and `scoped`.
|
|
115
|
-
- **The application layer imports no logger.** A bounded context has `
|
|
173
|
+
- **The application layer imports no logger.** A bounded context has `projectBoundedContext.logger`; a run
|
|
116
174
|
hands `this.logger`, bound to its execution. Only `logger` (infrastructure, the global one) and
|
|
117
175
|
`loggerAdapter` are predefined; injected as a dependency they bind to the run (`BIND`), imported
|
|
118
176
|
at module level they do not. No logger is predefined for an application layer. Console is the
|
package/MANIFESTO.md
CHANGED
|
@@ -28,8 +28,11 @@ server-side render, in a script and in a test, unchanged.**
|
|
|
28
28
|
1. **Services.** A `Feature` executes a registered Command through `execute(dto)`;
|
|
29
29
|
an `ApplicationService` composes its context's Features through `featureBus`, never another
|
|
30
30
|
ApplicationService. Dependencies are typed attributes supplied by the bounded context.
|
|
31
|
-
2. **Execution.** Every
|
|
32
|
-
|
|
31
|
+
2. **Execution.** Every Command and event enters one execution pipeline, defined here: context,
|
|
32
|
+
chain, span, log lines, outcome, report, error handlers — not handwritten by each service. An
|
|
33
|
+
application changes it from outside through its extension points, as the backend's
|
|
34
|
+
`bus_pipeline`: interceptors control a run (look, veto, adjust, answer); listeners hear it
|
|
35
|
+
start, answer or fail, and never change it; an extension wires a component in one place.
|
|
33
36
|
3. **Observability, as ports.** `Tracer`, `ErrorReporter` and `Logger` are the contracts: one span per use
|
|
34
37
|
case, named `<bounded context>/<serviceName>`; the unexpected reported
|
|
35
38
|
with its trace id; what the server already explained (a validation, a conflict), or what a
|
|
@@ -45,9 +48,11 @@ server-side render, in a script and in a test, unchanged.**
|
|
|
45
48
|
in a global stack: two calls at once never mix their parents.
|
|
46
49
|
5. **Bounded contexts.** Set up like the backend's — its dependencies (`addDependency`), its
|
|
47
50
|
Features and ApplicationServices, registered for class DTOs — and called as
|
|
48
|
-
`
|
|
51
|
+
`projectBoundedContext.execute(new Command(...))`. The bounded context is the bus. An application is the set
|
|
49
52
|
of bounded contexts it loads; they never import each other's insides.
|
|
50
|
-
6. **The client.** The `
|
|
53
|
+
6. **The client.** The `OperationTransport` port — an operation called by who it is, over any
|
|
54
|
+
wire — with its REST and JSON-RPC adapters (a platform adds gRPC or its own); the `Transport`
|
|
55
|
+
port and its implementations (REST, memory), `entity()` with
|
|
51
56
|
typed commands and answers, the service's client contract, model definitions read once from
|
|
52
57
|
the server and the server's `Problem`.
|
|
53
58
|
7. **The values of the wire.** How every value travels, as the backend writes it — a decimal as
|
|
@@ -62,9 +67,20 @@ server-side render, in a script and in a test, unchanged.**
|
|
|
62
67
|
10. **The command core (`./core`).** The single model matching the backend's application
|
|
63
68
|
abstractions: class DTOs and Responses, a bounded context that is a bus, Features and
|
|
64
69
|
ApplicationServices registered for DTO classes, attribute-injected dependencies and a
|
|
65
|
-
feature bus for composition,
|
|
66
|
-
|
|
70
|
+
feature bus for composition, domain events as the input of a use case, replacements,
|
|
71
|
+
and the backend's error handlers — global, feature and application service — with the
|
|
72
|
+
errors a bounded context expects. The internal engine uses
|
|
67
73
|
the injected process ports. Remote clients and mapping remain distinct.
|
|
74
|
+
11. **Event-driven, as ports (`event_driven`).** How every platform publishes domain events and
|
|
75
|
+
subscribes to them the same way: the envelope that travels beside an event (its flow and
|
|
76
|
+
its cause), the `EventQueue` and `Subscribes` ports, a `Publisher` a Feature holds, the
|
|
77
|
+
`EventSubscriber` — the entrypoint that hears an event and calls use cases, as a cron does on
|
|
78
|
+
a tick — and its `Subscriptions`, a `Subscriber` over everyone who hears, the queue that
|
|
79
|
+
delivers in the call, and the stored queue's rules over an `EventStore` the platform plugs.
|
|
80
|
+
The guidelines, the rules and the contracts are here; where events are kept is the
|
|
81
|
+
platform's.
|
|
82
|
+
12. **Resilience.** What a failure is (`failureOf`, the backend's kinds on every wire) and the
|
|
83
|
+
backend's circuit breaker, guarding any transport or adapter.
|
|
68
84
|
|
|
69
85
|
## What it is not
|
|
70
86
|
|
|
@@ -84,8 +100,10 @@ server-side render, in a script and in a test, unchanged.**
|
|
|
84
100
|
- **Not where application-specific business rules live.** The package supplies execution
|
|
85
101
|
abstractions; a registered use case may delegate to a server or use a local domain. A remote
|
|
86
102
|
adapter does not authorize reimplementing the server's rules.
|
|
87
|
-
- **Not
|
|
88
|
-
|
|
103
|
+
- **Not a durable event delivery system.** It defines how events are published, carried and
|
|
104
|
+
executed — the ports, the envelope, the in-call queue — so web and mobile do it alike; storing
|
|
105
|
+
them, retrying, a dead-letter queue, waiting for a connection, other tabs, a broker are the
|
|
106
|
+
platform's adapters behind `EventQueue`.
|
|
89
107
|
|
|
90
108
|
## What we believe
|
|
91
109
|
|
|
@@ -130,6 +148,14 @@ server-side render, in a script and in a test, unchanged.**
|
|
|
130
148
|
|
|
131
149
|
Dependencies point down only. This package knows nothing above it.
|
|
132
150
|
|
|
151
|
+
**What goes where.** client-framework + web-framework is to a web application what
|
|
152
|
+
sincpro_framework is to a backend; client-framework + @sincpro/mobile, the same for a phone. A
|
|
153
|
+
piece web and mobile can both use, and that needs no platform, comes here — as a port, its rules
|
|
154
|
+
and a memory adapter. A piece only one platform needs, or that needs a platform's technology, is
|
|
155
|
+
that platform's — an adapter of a port. An application's business is the application's. Nothing
|
|
156
|
+
is replicated: what differs by nature (a phone's SQLite domain, a browser's entity screens) stays
|
|
157
|
+
different, and the platforms meet here only through the ports.
|
|
158
|
+
|
|
133
159
|
## Where it stands
|
|
134
160
|
|
|
135
161
|
**The application layer is the backend's.** A bounded context executes class Commands through
|
|
@@ -145,16 +171,35 @@ bounded context has its own, and a run hands `this.logger`, bound to its executi
|
|
|
145
171
|
infrastructure and the adapters have a predefined one.
|
|
146
172
|
|
|
147
173
|
**The client and the generator read the backend's contract.** Types per model, operation and —
|
|
148
|
-
when the contract is exported with them — domain event; a client
|
|
149
|
-
|
|
150
|
-
the
|
|
174
|
+
when the contract is exported with them — domain event; a client whose methods call each
|
|
175
|
+
operation by who it is, over the `OperationTransport` the application injects — REST, JSON-RPC,
|
|
176
|
+
a platform's gRPC —; the entities screens use. What is generated is types, never classes, and
|
|
177
|
+
never names a wire: the contract's bindings are data, read by the adapter of each wire.
|
|
151
178
|
|
|
152
179
|
**Each module is laid out as the backend's.** `domain/`, `adapters/`, `services/`,
|
|
153
180
|
`infrastructure/`, `entrypoint/`, only those a module needs; `domain/` depends only on `domain/`.
|
|
154
181
|
|
|
155
|
-
**
|
|
156
|
-
|
|
157
|
-
|
|
182
|
+
**The pipeline is the core's, and open.** The flow of a run is defined once; interceptors,
|
|
183
|
+
listeners and extensions change or hear it from outside, without touching a service.
|
|
184
|
+
|
|
185
|
+
**Events travel the same way on every platform.** A Feature publishes through a `Publisher`;
|
|
186
|
+
a `Subscriber` executes the event in every bounded context that hears it, in the same flow
|
|
187
|
+
(`correlationId`, caused by the run that published it). In the call (`syncQueue`), or stored
|
|
188
|
+
first and delivered by the backend's store-and-forward rules (`storedQueue`) over the
|
|
189
|
+
`EventStore` each platform plugs — a SQLite table, IndexedDB, memory.
|
|
190
|
+
|
|
191
|
+
**A failure means the same thing everywhere.** `failureOf` reads any error — whatever the wire,
|
|
192
|
+
whatever backend — in the backend's kinds and next steps; retrying, setting aside, reporting and
|
|
193
|
+
a screen's answer follow from it. A run can be cancelled, and its requests with it. The
|
|
194
|
+
backend's circuit breaker guards any transport.
|
|
195
|
+
|
|
196
|
+
**Consumers have a guide.** The `sincpro-client-framework` skill says, for an application and for
|
|
197
|
+
a platform's framework, what goes where and how each thing is done.
|
|
198
|
+
|
|
199
|
+
**Next:** web and mobile adopt it in their own releases — mobile's SQLite `EventStore` and gRPC or
|
|
200
|
+
JSON-RPC wire, web's in-memory or IndexedDB store and its hooks —, declaring this package as a
|
|
201
|
+
peer dependency; events from the server live, with the backend's server-sent events; generated
|
|
202
|
+
readers that validate and convert what arrives, when a use needs them.
|
|
158
203
|
|
|
159
204
|
## How we decide
|
|
160
205
|
|