@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.
Files changed (84) hide show
  1. package/AGENTS.md +71 -13
  2. package/MANIFESTO.md +59 -14
  3. package/README.md +456 -55
  4. package/dist/circuit_breaker/domain/circuit.d.ts +29 -0
  5. package/dist/circuit_breaker/domain/circuit.js +37 -0
  6. package/dist/circuit_breaker/entrypoint/circuit-breaker.d.ts +56 -0
  7. package/dist/circuit_breaker/entrypoint/circuit-breaker.js +94 -0
  8. package/dist/circuit_breaker/index.d.ts +9 -0
  9. package/dist/circuit_breaker/index.js +9 -0
  10. package/dist/client/adapters/json-rpc.d.ts +40 -0
  11. package/dist/client/adapters/json-rpc.js +97 -0
  12. package/dist/client/adapters/rest-operations.d.ts +19 -0
  13. package/dist/client/adapters/rest-operations.js +66 -0
  14. package/dist/client/domain/operation.d.ts +73 -0
  15. package/dist/client/domain/operation.js +19 -0
  16. package/dist/client/index.d.ts +5 -1
  17. package/dist/client/index.js +5 -1
  18. package/dist/client/services/guarded.d.ts +17 -0
  19. package/dist/client/services/guarded.js +19 -0
  20. package/dist/client/services/propagation.d.ts +20 -7
  21. package/dist/client/services/propagation.js +44 -15
  22. package/dist/codegen/entrypoint/cli.js +7 -7
  23. package/dist/codegen/services/client.d.ts +7 -8
  24. package/dist/codegen/services/client.js +50 -148
  25. package/dist/codegen/services/scaffold.d.ts +7 -4
  26. package/dist/codegen/services/scaffold.js +23 -27
  27. package/dist/common/failures.d.ts +83 -0
  28. package/dist/common/failures.js +212 -0
  29. package/dist/common/index.d.ts +1 -0
  30. package/dist/common/index.js +1 -0
  31. package/dist/context/domain/execution.d.ts +12 -0
  32. package/dist/context/entrypoint/context.d.ts +5 -4
  33. package/dist/core/domain/bus.d.ts +16 -7
  34. package/dist/core/domain/bus.js +14 -6
  35. package/dist/core/domain/dto.d.ts +33 -3
  36. package/dist/core/domain/dto.js +24 -1
  37. package/dist/core/domain/errors.d.ts +2 -2
  38. package/dist/core/domain/pipeline.d.ts +89 -0
  39. package/dist/core/domain/pipeline.js +1 -0
  40. package/dist/core/domain/services.d.ts +16 -5
  41. package/dist/core/domain/services.js +5 -2
  42. package/dist/core/entrypoint/bounded-context.d.ts +96 -16
  43. package/dist/core/entrypoint/bounded-context.js +244 -39
  44. package/dist/core/index.d.ts +4 -2
  45. package/dist/core/index.js +3 -2
  46. package/dist/core/infrastructure/listeners.d.ts +28 -0
  47. package/dist/core/infrastructure/listeners.js +34 -0
  48. package/dist/core/services/binding.d.ts +2 -2
  49. package/dist/core/services/binding.js +6 -3
  50. package/dist/core/services/execution.d.ts +59 -12
  51. package/dist/core/services/execution.js +114 -19
  52. package/dist/core/services/interceptors.d.ts +15 -0
  53. package/dist/core/services/interceptors.js +25 -0
  54. package/dist/core/services/listening.d.ts +17 -0
  55. package/dist/core/services/listening.js +25 -0
  56. package/dist/event_driven/adapters/memory-event-store.d.ts +10 -0
  57. package/dist/event_driven/adapters/memory-event-store.js +23 -0
  58. package/dist/event_driven/adapters/stored-queue.d.ts +68 -0
  59. package/dist/event_driven/adapters/stored-queue.js +193 -0
  60. package/dist/event_driven/adapters/sync-queue.d.ts +11 -0
  61. package/dist/event_driven/adapters/sync-queue.js +19 -0
  62. package/dist/event_driven/domain/envelope.d.ts +28 -0
  63. package/dist/event_driven/domain/envelope.js +19 -0
  64. package/dist/event_driven/domain/queue.d.ts +40 -0
  65. package/dist/event_driven/domain/queue.js +1 -0
  66. package/dist/event_driven/domain/store.d.ts +56 -0
  67. package/dist/event_driven/domain/store.js +1 -0
  68. package/dist/event_driven/entrypoint/publisher.d.ts +26 -0
  69. package/dist/event_driven/entrypoint/publisher.js +35 -0
  70. package/dist/event_driven/entrypoint/subscriber.d.ts +42 -0
  71. package/dist/event_driven/entrypoint/subscriber.js +62 -0
  72. package/dist/event_driven/entrypoint/subscriptions.d.ts +110 -0
  73. package/dist/event_driven/entrypoint/subscriptions.js +184 -0
  74. package/dist/event_driven/index.d.ts +22 -0
  75. package/dist/event_driven/index.js +20 -0
  76. package/dist/index.d.ts +7 -2
  77. package/dist/index.js +7 -2
  78. package/dist/observability/adapters/memory.d.ts +1 -1
  79. package/dist/observability/domain/logging.d.ts +10 -4
  80. package/dist/observability/services/logging.d.ts +7 -4
  81. package/dist/observability/services/logging.js +3 -2
  82. package/dist/observability/services/outcome.d.ts +6 -6
  83. package/dist/observability/services/outcome.js +10 -13
  84. 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, Feature/ApplicationService, error handlers; pipeline; `BoundedContext`; the process |
36
- | `src/codegen/` | The generator and its CLI (`sincpro-client generate`) — the only place Node is allowed |
37
- | `src/testing/` | The public entry of the in-memory observability adapters |
38
- | `tests/` | vitest in Node, one folder per module; `tests/support/` holds fakes and forge's contracts |
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 identity.
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 `project.logger`; a run
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 command enters one internal execution pipeline. Context, tracing,
32
- outcomes and error reporting are supplied there, not handwritten by each service.
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
- `project.execute(new Command(...))`. The bounded context is the bus. An application is the set
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 `Transport` port and its implementations (REST, memory), `entity()` with
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, and the backend's error handlers — global, feature and
66
- application service — with the errors a bounded context expects. The internal engine uses
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 an event delivery system.** The command dispatcher in `./core` executes use cases.
88
- An event contract does not supply a publisher, queue, outbox, broker or synchronization engine.
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 per wire REST reaches; the
149
- entities screens use. What is generated is types, never classes: the remote contract describes
150
- the wire and never reaches the local bus.
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
- **Next:** web and mobile adopt it in their own releases; generated readers that validate and
156
- convert what arrives, when a use needs them; durable delivery, designed apart from
157
- instrumentation.
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