@sincpro/client-framework 0.6.0 → 1.1.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 +95 -25
- package/MANIFESTO.md +103 -40
- package/README.md +608 -333
- 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/{memory.d.ts → adapters/memory.d.ts} +1 -1
- package/dist/client/{memory.js → adapters/memory.js} +2 -2
- package/dist/{adapters → client/adapters}/parquet.d.ts +1 -1
- package/dist/client/adapters/rest-operations.d.ts +19 -0
- package/dist/client/adapters/rest-operations.js +66 -0
- package/dist/client/{rest.d.ts → adapters/rest.d.ts} +1 -1
- package/dist/client/{rest.js → adapters/rest.js} +2 -2
- package/dist/client/{entity.d.ts → domain/entity.d.ts} +6 -10
- package/dist/client/domain/operation.d.ts +73 -0
- package/dist/client/domain/operation.js +19 -0
- package/dist/client/index.d.ts +20 -0
- package/dist/client/index.js +20 -0
- package/dist/client/services/guarded.d.ts +17 -0
- package/dist/client/services/guarded.js +19 -0
- package/dist/client/services/propagation.d.ts +45 -0
- package/dist/{services/execution.js → client/services/propagation.js} +49 -36
- package/dist/client/{retry.js → services/retry.js} +1 -1
- package/dist/codegen/{metamodel.d.ts → domain/metamodel.d.ts} +17 -2
- package/dist/codegen/{names.d.ts → domain/names.d.ts} +2 -2
- package/dist/codegen/{names.js → domain/names.js} +3 -3
- package/dist/codegen/{cli.js → entrypoint/cli.js} +15 -14
- package/dist/codegen/index.d.ts +7 -7
- package/dist/codegen/index.js +5 -10
- package/dist/codegen/services/client.d.ts +15 -0
- package/dist/codegen/services/client.js +111 -0
- package/dist/codegen/services/domain.d.ts +20 -0
- package/dist/codegen/{domain.js → services/domain.js} +82 -25
- package/dist/codegen/{entities.d.ts → services/entities.d.ts} +4 -3
- package/dist/codegen/{entities.js → services/entities.js} +18 -4
- package/dist/codegen/{rest.d.ts → services/rest.d.ts} +1 -1
- package/dist/codegen/{scaffold.d.ts → services/scaffold.d.ts} +8 -5
- package/dist/codegen/{scaffold.js → services/scaffold.js} +39 -43
- package/dist/common/failures.d.ts +83 -0
- package/dist/common/failures.js +212 -0
- package/dist/common/index.d.ts +11 -0
- package/dist/common/index.js +11 -0
- package/dist/context/adapters/memory.d.ts +6 -0
- package/dist/context/adapters/memory.js +34 -0
- package/dist/context/adapters/plain-codec.d.ts +3 -0
- package/dist/context/adapters/plain-codec.js +16 -0
- package/dist/context/{codec.d.ts → domain/codec.d.ts} +0 -1
- package/dist/context/{codec.js → domain/codec.js} +0 -14
- package/dist/context/domain/execution.d.ts +45 -0
- package/dist/context/domain/execution.js +11 -0
- package/dist/context/{store.d.ts → domain/store.d.ts} +0 -5
- package/dist/context/{store.js → domain/store.js} +1 -34
- package/dist/context/{context.d.ts → entrypoint/context.d.ts} +1 -1
- package/dist/context/index.d.ts +17 -0
- package/dist/context/index.js +15 -0
- package/dist/context/infrastructure/process.d.ts +13 -0
- package/dist/context/infrastructure/process.js +35 -0
- package/dist/core/domain/bus.d.ts +27 -0
- package/dist/core/domain/bus.js +26 -0
- package/dist/core/domain/dto.d.ts +62 -0
- package/dist/core/domain/dto.js +43 -0
- package/dist/core/domain/errors.d.ts +18 -0
- package/dist/core/domain/errors.js +1 -0
- package/dist/core/domain/pipeline.d.ts +89 -0
- package/dist/core/domain/pipeline.js +1 -0
- package/dist/core/domain/services.d.ts +47 -0
- package/dist/core/domain/services.js +31 -0
- package/dist/core/entrypoint/bounded-context.d.ts +139 -0
- package/dist/core/entrypoint/bounded-context.js +426 -0
- package/dist/core/entrypoint/screens.d.ts +6 -0
- package/dist/core/entrypoint/screens.js +136 -0
- package/dist/core/index.d.ts +19 -0
- package/dist/core/index.js +17 -0
- package/dist/core/infrastructure/listeners.d.ts +28 -0
- package/dist/core/infrastructure/listeners.js +34 -0
- package/dist/core/infrastructure/process.d.ts +56 -0
- package/dist/core/infrastructure/process.js +35 -0
- package/dist/core/services/binding.d.ts +6 -0
- package/dist/core/services/binding.js +16 -0
- package/dist/core/services/error-handlers.d.ts +11 -0
- package/dist/core/services/error-handlers.js +21 -0
- package/dist/core/services/execution.d.ts +66 -0
- package/dist/core/services/execution.js +216 -0
- 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 +67 -0
- package/dist/event_driven/adapters/stored-queue.js +192 -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 +34 -0
- package/dist/event_driven/entrypoint/subscriber.js +51 -0
- package/dist/event_driven/index.d.ts +19 -0
- package/dist/event_driven/index.js +17 -0
- package/dist/index.d.ts +21 -38
- package/dist/index.js +21 -38
- package/dist/model/index.d.ts +10 -0
- package/dist/model/index.js +10 -0
- package/dist/model/{metas.d.ts → infrastructure/metas.d.ts} +1 -1
- package/dist/model/{schema.d.ts → services/schema.d.ts} +1 -1
- package/dist/model/{schema.js → services/schema.js} +1 -1
- package/dist/observability/adapters/console.d.ts +12 -0
- package/dist/observability/adapters/console.js +35 -0
- package/dist/observability/adapters/memory.d.ts +41 -0
- package/dist/observability/adapters/memory.js +49 -0
- package/dist/{adapters → observability/adapters}/opentelemetry.d.ts +1 -1
- package/dist/{adapters → observability/adapters}/sentry.d.ts +1 -1
- package/dist/observability/domain/errors.d.ts +23 -0
- package/dist/observability/domain/errors.js +8 -0
- package/dist/observability/domain/logging.d.ts +44 -0
- package/dist/observability/domain/logging.js +1 -0
- package/dist/observability/domain/tracing.d.ts +49 -0
- package/dist/observability/domain/tracing.js +20 -0
- package/dist/observability/index.d.ts +16 -0
- package/dist/observability/index.js +4 -0
- package/dist/observability/infrastructure/registry.d.ts +21 -0
- package/dist/observability/infrastructure/registry.js +18 -0
- package/dist/observability/services/logging.d.ts +27 -0
- package/dist/observability/services/logging.js +106 -0
- package/dist/observability/services/outcome.d.ts +12 -0
- package/dist/observability/services/outcome.js +17 -0
- package/dist/{services/report.js → observability/services/reporting.js} +4 -3
- package/dist/testing/index.d.ts +3 -33
- package/dist/testing/index.js +5 -37
- package/package.json +16 -8
- package/dist/codegen/client.d.ts +0 -14
- package/dist/codegen/client.js +0 -201
- package/dist/codegen/domain.d.ts +0 -15
- package/dist/observability/ports.d.ts +0 -65
- package/dist/observability/ports.js +0 -21
- package/dist/services/bounded-context.d.ts +0 -79
- package/dist/services/bounded-context.js +0 -261
- package/dist/services/execution.d.ts +0 -63
- package/dist/services/feature.d.ts +0 -85
- package/dist/services/feature.js +0 -86
- package/dist/services/process.d.ts +0 -60
- package/dist/services/process.js +0 -57
- package/dist/services/screens.d.ts +0 -2
- package/dist/services/screens.js +0 -76
- /package/dist/{adapters → client/adapters}/parquet.js +0 -0
- /package/dist/client/{contract.d.ts → domain/contract.d.ts} +0 -0
- /package/dist/client/{contract.js → domain/contract.js} +0 -0
- /package/dist/client/{entity.js → domain/entity.js} +0 -0
- /package/dist/client/{transport.d.ts → domain/transport.d.ts} +0 -0
- /package/dist/client/{transport.js → domain/transport.js} +0 -0
- /package/dist/client/{discovery.d.ts → services/discovery.d.ts} +0 -0
- /package/dist/client/{discovery.js → services/discovery.js} +0 -0
- /package/dist/client/{retry.d.ts → services/retry.d.ts} +0 -0
- /package/dist/codegen/{metamodel.js → domain/metamodel.js} +0 -0
- /package/dist/codegen/{cli.d.ts → entrypoint/cli.d.ts} +0 -0
- /package/dist/codegen/{check.d.ts → services/check.d.ts} +0 -0
- /package/dist/codegen/{check.js → services/check.js} +0 -0
- /package/dist/codegen/{rest.js → services/rest.js} +0 -0
- /package/dist/context/{context.js → entrypoint/context.js} +0 -0
- /package/dist/model/{meta.d.ts → domain/meta.d.ts} +0 -0
- /package/dist/model/{meta.js → domain/meta.js} +0 -0
- /package/dist/model/{metas.js → infrastructure/metas.js} +0 -0
- /package/dist/{adapters → observability/adapters}/opentelemetry.js +0 -0
- /package/dist/{adapters → observability/adapters}/sentry.js +0 -0
- /package/dist/{services/report.d.ts → observability/services/reporting.d.ts} +0 -0
package/AGENTS.md
CHANGED
|
@@ -20,18 +20,23 @@ it says what this package is responsible for and what it is not. This file is ho
|
|
|
20
20
|
|
|
21
21
|
## Where things go
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
|
29
|
-
|
|
|
30
|
-
| `src/
|
|
31
|
-
| `src/
|
|
32
|
-
| `src/
|
|
33
|
-
| `src/
|
|
34
|
-
| `
|
|
23
|
+
Each module is laid out as sincpro_framework's are: `domain/` (ports, types and rules),
|
|
24
|
+
`adapters/` (implementations of the ports), `services/` (what uses them), `infrastructure/`
|
|
25
|
+
(the process's state and technical support) and `entrypoint/` (the doors a caller uses). A
|
|
26
|
+
module has only the layers it needs, and says what it offers in its own `index.ts`.
|
|
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 and events, bus, services, handlers, extension points; pipeline; `BoundedContext`; process |
|
|
36
|
+
| `src/event_driven/` | `Envelope`, `EventQueue`/`Subscribes` ports; `syncQueue`; `Publisher`, `Subscriber` |
|
|
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 |
|
|
35
40
|
|
|
36
41
|
## Hard rules (the build enforces them — `make doctor`)
|
|
37
42
|
|
|
@@ -42,6 +47,11 @@ it says what this package is responsible for and what it is not. This file is ho
|
|
|
42
47
|
and nowhere else; nothing an application runs imports `codegen/`.
|
|
43
48
|
- **Self-imports, no extensions.** Inside `src/`, import `@sincpro/client-framework/<path>` —
|
|
44
49
|
never a relative path, never `.ts`.
|
|
50
|
+
- **Domain inward.** A `domain/` folder imports only other `domain/` folders: no adapter,
|
|
51
|
+
service, infrastructure or entrypoint.
|
|
52
|
+
- **Vendors in their entry.** A vendor's SDK is imported only in its optional entry
|
|
53
|
+
(`observability/adapters/opentelemetry.ts`, `observability/adapters/sentry.ts`,
|
|
54
|
+
`client/adapters/parquet.ts`), and nothing in `src/` imports one.
|
|
45
55
|
|
|
46
56
|
## Conventions
|
|
47
57
|
|
|
@@ -55,17 +65,53 @@ it says what this package is responsible for and what it is not. This file is ho
|
|
|
55
65
|
## Known traps
|
|
56
66
|
|
|
57
67
|
- **A run sees the instance through a view of its own.** Inside a method, each dependency
|
|
58
|
-
(`this.client`), `this.context` and `this.
|
|
68
|
+
(`this.client`), `this.context` and `this.featureBus` are that run's — bound to its execution. So a
|
|
59
69
|
service declares no `#private` fields (they refuse the view); use TypeScript's `private`.
|
|
60
|
-
- **A service is a singleton of its
|
|
61
|
-
instance, shared by the next call, the call beside it and every `
|
|
70
|
+
- **A service is a singleton of its generation.** What a method writes on `this` lands on the
|
|
71
|
+
instance, shared by the next call, the call beside it and every `scoped` variant — as in
|
|
62
72
|
sincpro_framework. What belongs to one request goes in its arguments or its context.
|
|
63
|
-
- **A span
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
73
|
+
- **A span uses stable service metadata, not constructor.name.** `serviceName = "Cancel"` gives
|
|
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 subscriber
|
|
82
|
+
per bounded context; a `Subscriber` executes an event in each. `storedQueue` holds the rules —
|
|
83
|
+
store first, deliver in order, retry what may succeed, set aside what never will, never run a
|
|
84
|
+
context twice — over an `EventStore` the platform plugs (SQLite, IndexedDB, memory). A store
|
|
85
|
+
only keeps records; it never decides.
|
|
86
|
+
- **A circuit belongs where the dependency is.** `CircuitBreaker` (in the process's memory, the
|
|
87
|
+
backend's semantics: `COUNTED_KINDS`, half-open with one probe) guards a transport
|
|
88
|
+
(`guardedTransport`) or an adapter; its `CircuitOpen` declares `unavailable` and a wait, so
|
|
89
|
+
nothing else needs to know a circuit exists.
|
|
90
|
+
- **Delivery is at least once.** A stored event may run twice in a context after a crash; a
|
|
91
|
+
subscriber keys its effect by `this.execution.causationId` (the envelope's id). `maxAttempts`
|
|
92
|
+
sets aside what keeps failing. One process drains a store. `execution` is reserved.
|
|
93
|
+
- **A failure says its kind, whatever the wire.** `failureOf` reads a declared `failureKind`, the
|
|
94
|
+
`Problem` (the same over REST, JSON-RPC and gRPC), then a REST status. A transport of another
|
|
95
|
+
wire, or another backend's adapter, declares the kind with `kindOfGrpcCode` /
|
|
96
|
+
`kindOfJsonRpcCode` / `kindOfHttpStatus` — never a table of its own. Retrying, setting aside,
|
|
97
|
+
reporting and the outcome all follow from it.
|
|
98
|
+
- **A run can be cancelled.** `execute(…, { signal })`: it ends `aborted` at once, its Features
|
|
99
|
+
inherit the signal, `this.signal` reads it, and a bound transport sends it with every request.
|
|
100
|
+
`signal` is reserved, like `context` and `logger`.
|
|
101
|
+
- **No variable named `process`.** `make doctor` reads `process.` as Node outside `codegen/`.
|
|
102
|
+
- **A run belongs to a flow.** `correlationId` is shared by every run and event of one flow;
|
|
103
|
+
`causationId` names what directly caused it. A dependency carrying `BIND` (the `Publisher`)
|
|
104
|
+
is bound to the run, so what it publishes joins that flow with nothing passed by hand.
|
|
105
|
+
- **The pipeline is closed for modification, open for extension.** Change the flow in
|
|
106
|
+
`executeService` only for every run; what one application needs goes through an interceptor
|
|
107
|
+
(control, inside the run, keeps the class), a listener (observation, never changes the
|
|
108
|
+
answer, its failure ignored) or `extend`.
|
|
109
|
+
- **Dependency declarations do not register anything.** Service bases declare typed attributes
|
|
110
|
+
from a shared dependency interface. Only `addDependency` supplies runtime values; a missing
|
|
111
|
+
dependency fails explicitly.
|
|
112
|
+
- **Decorators are optional.** Function registration must work without any decorator
|
|
113
|
+
transform. Standard and legacy (`experimentalDecorators`) fixtures are compiled explicitly in
|
|
114
|
+
tests; do not claim Metro (Babel) or web bundler support without testing those configurations.
|
|
69
115
|
- **The trace reaches the wire through the dependencies.** A dependency is bound to the run when it
|
|
70
116
|
is a `Transport` or carries `BIND` (every generated client does, through `bindable`); anything
|
|
71
117
|
else is handed as it is. A client built elsewhere and closed over sends no trace.
|
|
@@ -76,20 +122,44 @@ it says what this package is responsible for and what it is not. This file is ho
|
|
|
76
122
|
- **A store's `load` may answer a new object each time** (one that parses JSON does): `Context`
|
|
77
123
|
keeps one object per `version`, so a screen can hand `values` to `useSyncExternalStore`. A store
|
|
78
124
|
that changes without moving the version is a broken store.
|
|
125
|
+
- **The generated client never names a wire.** A method is `transport.call(operations.X, input,
|
|
126
|
+
options)`: no route, no verb, no RPC method. The contract's bindings (`rest`, `rpc`, `grpc`,
|
|
127
|
+
`mcp`) are data in `operations`; an `OperationTransport` adapter reads the one of its wire
|
|
128
|
+
(`restOperations`, `jsonRpcOperations`, a platform's gRPC) and throws `OperationNotPublished`
|
|
129
|
+
for the rest. A run binds an `OperationTransport` as it binds a `Transport`: headers and signal.
|
|
130
|
+
The REST `Transport` stays for `restOperations` and for screens (`entity()`, pages, drafts).
|
|
131
|
+
- **What is generated is types, never classes.** A generated DTO is the remote contract: a
|
|
132
|
+
class extending `Command` could be registered on the local bus, which is what keeps a remote
|
|
133
|
+
operation and a local use case apart. Runtime knowledge goes in data (`operations`, `events`);
|
|
134
|
+
validating or converting what arrives would be a generated reader, not a class.
|
|
135
|
+
- **Events come only from the contract.** The generator writes event types and `DomainEvent`
|
|
136
|
+
only when the backend exported its contract with `--events`; a contract without them writes
|
|
137
|
+
the same bytes as before.
|
|
79
138
|
- **What the generator writes is compared byte for byte** (`tests/support/forge-contract/`, out
|
|
80
139
|
of prettier and eslint). After changing the generator on purpose, run `make codegen-fixtures`
|
|
81
140
|
and read the diff.
|
|
82
|
-
- **A module name the generator writes is built in parts** (`codegen/names.ts`, `FRAMEWORK`): the
|
|
141
|
+
- **A module name the generator writes is built in parts** (`codegen/domain/names.ts`, `FRAMEWORK`): the
|
|
83
142
|
build's `tsc-alias` rewrites this package's specifiers inside strings too.
|
|
84
143
|
- **An adapter wraps what the application built, never builds it.** `openTelemetryTracer` takes
|
|
85
144
|
the app's tracer, `sentryReporter` the app's client: no `register()`, no `Sentry.init()`, no
|
|
86
145
|
endpoint or DSN here, nothing read from the environment. A vendor's SDK is imported only in
|
|
87
|
-
|
|
146
|
+
its optional entry, and nothing else imports one (`make doctor`).
|
|
88
147
|
- **A span nothing records claims no trace.** OpenTelemetry's API with no provider answers an
|
|
89
148
|
all-zeros span: the adapter gives no ids, so no `traceparent` of zeros reaches the server.
|
|
90
149
|
- **Reported means unexpected.** A `TransportError` the server answered with a 4xx is `refused`,
|
|
91
|
-
an `AbortError` is `aborted`; neither is reported (`outcomeOf`).
|
|
92
|
-
`
|
|
150
|
+
an `AbortError` is `aborted`; neither is reported (`outcomeOf`). A bounded context adds its own
|
|
151
|
+
with `expectErrors(...)`; a process replaces the rule with `ProcessOptions.outcomeOf`. Never
|
|
152
|
+
catch in a Feature to hide a failure.
|
|
153
|
+
- **An error handler answers; it does not observe.** The failure is recorded where it happened —
|
|
154
|
+
span, log line, report — before the layer's handlers run, as in sincpro_framework. A handler
|
|
155
|
+
returns the answer or throws to the next; the global chain wraps only `execute` and `scoped`.
|
|
156
|
+
- **The application layer imports no logger.** A bounded context has `project.logger`; a run
|
|
157
|
+
hands `this.logger`, bound to its execution. Only `logger` (infrastructure, the global one) and
|
|
158
|
+
`loggerAdapter` are predefined; injected as a dependency they bind to the run (`BIND`), imported
|
|
159
|
+
at module level they do not. No logger is predefined for an application layer. Console is the
|
|
160
|
+
default engine; `configureProcess({ logger })` and `configureLogger(...)` take the same
|
|
161
|
+
`LoggerConfiguration`. The host chooses production explicitly. Never log DTOs, results, full
|
|
162
|
+
session context or raw errors automatically.
|
|
93
163
|
|
|
94
164
|
- **Only what is transversal comes here.** A value's wire form (a decimal as text, a day never
|
|
95
165
|
an instant, a money with its currency) is the backend's contract, the same on every platform,
|
package/MANIFESTO.md
CHANGED
|
@@ -25,28 +25,34 @@ server-side render, in a script and in a test, unchanged.**
|
|
|
25
25
|
|
|
26
26
|
## What it is responsible for
|
|
27
27
|
|
|
28
|
-
1. **Services.** A `Feature`
|
|
29
|
-
|
|
30
|
-
ApplicationService.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
28
|
+
1. **Services.** A `Feature` executes a registered Command through `execute(dto)`;
|
|
29
|
+
an `ApplicationService` composes its context's Features through `featureBus`, never another
|
|
30
|
+
ApplicationService. Dependencies are typed attributes supplied by the bounded context.
|
|
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.
|
|
36
|
+
3. **Observability, as ports.** `Tracer`, `ErrorReporter` and `Logger` are the contracts: one span per use
|
|
37
|
+
case, named `<bounded context>/<serviceName>`; the unexpected reported
|
|
38
|
+
with its trace id; what the server already explained (a validation, a conflict), or what a
|
|
39
|
+
bounded context declares expected, not reported.
|
|
38
40
|
The platform plugs the implementation — OpenTelemetry, GlitchTip, a queue on the phone, memory
|
|
39
|
-
in a test — once, for the process (`configureProcess`).
|
|
41
|
+
in a test — once, for the process (`configureProcess`). Structured logs are tagged by layer
|
|
42
|
+
(infrastructure, adapter, bounded context, ApplicationService, Feature) and by execution;
|
|
43
|
+
console filtering never controls another sink's delivery.
|
|
40
44
|
4. **Context.** The levels a call sees — the process, its bounded context, a scope — read once
|
|
41
45
|
where a gesture starts and inherited by everything it runs, and what travels to the server with
|
|
42
46
|
it (`traceparent`, `sincpro-context`). Where the values live is a port (`ContextStore`) each
|
|
43
47
|
platform implements with its own technology; the execution is carried by each call, never kept
|
|
44
48
|
in a global stack: two calls at once never mix their parents.
|
|
45
49
|
5. **Bounded contexts.** Set up like the backend's — its dependencies (`addDependency`), its
|
|
46
|
-
Features and ApplicationServices, registered
|
|
47
|
-
`project.
|
|
48
|
-
never import each other's insides.
|
|
49
|
-
6. **The client.** The `
|
|
50
|
+
Features and ApplicationServices, registered for class DTOs — and called as
|
|
51
|
+
`project.execute(new Command(...))`. The bounded context is the bus. An application is the set
|
|
52
|
+
of bounded contexts it loads; they never import each other's insides.
|
|
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
|
|
50
56
|
typed commands and answers, the service's client contract, model definitions read once from
|
|
51
57
|
the server and the server's `Problem`.
|
|
52
58
|
7. **The values of the wire.** How every value travels, as the backend writes it — a decimal as
|
|
@@ -54,9 +60,25 @@ server-side render, in a script and in a test, unchanged.**
|
|
|
54
60
|
and the exact arithmetic a client needs on them (`Decimal`, `Money`). Only the wire form and
|
|
55
61
|
what follows from it: showing a value to a person is each platform's.
|
|
56
62
|
8. **The generator.** From a service's client contract to each domain's types, client and
|
|
57
|
-
entities —
|
|
63
|
+
entities — and its domain events when the contract carries them —, generated as types,
|
|
64
|
+
never edited, and as platform-neutral as everything else here.
|
|
58
65
|
9. **Testing.** An in-memory transport, tracer and reporter, so a use case is proven in Node with
|
|
59
66
|
nothing else.
|
|
67
|
+
10. **The command core (`./core`).** The single model matching the backend's application
|
|
68
|
+
abstractions: class DTOs and Responses, a bounded context that is a bus, Features and
|
|
69
|
+
ApplicationServices registered for DTO classes, attribute-injected dependencies and a
|
|
70
|
+
feature bus for composition, domain events executed by their subscribers, 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
|
|
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, a
|
|
77
|
+
`Subscriber` over the bounded contexts that hear, the queue that delivers in the call, and
|
|
78
|
+
the stored queue's rules over an `EventStore` the platform plugs. The guidelines, the rules
|
|
79
|
+
and the contracts are here; where events are kept is the platform's.
|
|
80
|
+
12. **Resilience.** What a failure is (`failureOf`, the backend's kinds on every wire) and the
|
|
81
|
+
backend's circuit breaker, guarding any transport or adapter.
|
|
60
82
|
|
|
61
83
|
## What it is not
|
|
62
84
|
|
|
@@ -73,9 +95,13 @@ server-side render, in a script and in a test, unchanged.**
|
|
|
73
95
|
application built — its provider, its client, with its endpoint and its DSN. It initialises
|
|
74
96
|
nothing and registers nothing global; an application that does not import them installs
|
|
75
97
|
nothing and ships nothing, and without a tracer or a reporter everything runs the same.
|
|
76
|
-
- **Not where business rules live.** The
|
|
77
|
-
|
|
78
|
-
|
|
98
|
+
- **Not where application-specific business rules live.** The package supplies execution
|
|
99
|
+
abstractions; a registered use case may delegate to a server or use a local domain. A remote
|
|
100
|
+
adapter does not authorize reimplementing the server's rules.
|
|
101
|
+
- **Not a durable event delivery system.** It defines how events are published, carried and
|
|
102
|
+
executed — the ports, the envelope, the in-call queue — so web and mobile do it alike; storing
|
|
103
|
+
them, retrying, a dead-letter queue, waiting for a connection, other tabs, a broker are the
|
|
104
|
+
platform's adapters behind `EventQueue`.
|
|
79
105
|
|
|
80
106
|
## What we believe
|
|
81
107
|
|
|
@@ -85,17 +111,18 @@ server-side render, in a script and in a test, unchanged.**
|
|
|
85
111
|
2. **The backend's words.** Bounded context, Feature, ApplicationService, dependencies, context,
|
|
86
112
|
process, the span name — the same as sincpro_framework, so whoever knows the backend already
|
|
87
113
|
knows the client.
|
|
88
|
-
3. **Nothing escapes
|
|
89
|
-
context
|
|
90
|
-
|
|
114
|
+
3. **Nothing escapes execution.** Reusing a use case means dispatching its Command through the
|
|
115
|
+
bounded context or feature bus. Directly constructing and calling a service bypasses its
|
|
116
|
+
execution context and instrumentation.
|
|
91
117
|
4. **Context travels with the call.** Never in a global stack: clients run many things at once.
|
|
92
118
|
5. **A port where there are two implementations, a class where there is one.** `Transport` has
|
|
93
119
|
REST and memory; `Tracer` has a platform's and a test's; `ContextStore` has the browser's, the
|
|
94
120
|
phone's and memory.
|
|
95
121
|
6. **Generated, never edited.** Applications extend by composition; regenerating never loses
|
|
96
122
|
work.
|
|
97
|
-
7. **
|
|
98
|
-
|
|
123
|
+
7. **Authority is explicit.** A generated client reads the server's model and refusal and never
|
|
124
|
+
authorizes duplicating server rules. A local domain can own local rules; synchronization and
|
|
125
|
+
conflict policy are separate from command delivery.
|
|
99
126
|
8. **Small, and paid for only when used.** No side effects at import, tree-shakable, and no
|
|
100
127
|
dependency it can avoid.
|
|
101
128
|
9. **Grow by evidence.** Nothing before its second use; every change ships with a test that fails
|
|
@@ -107,8 +134,8 @@ server-side render, in a script and in a test, unchanged.**
|
|
|
107
134
|
@sincpro/criteria the contract with the server: Criteria, Meta, Page, Problem
|
|
108
135
|
▲
|
|
109
136
|
@sincpro/client-framework this package — platform-neutral
|
|
110
|
-
│
|
|
111
|
-
│ the process · the context · Tracer, ErrorReporter and ContextStore ports
|
|
137
|
+
│ BoundedContext · Command · Feature · ApplicationService · execution
|
|
138
|
+
│ the process · the context · Tracer, ErrorReporter, Logger and ContextStore ports
|
|
112
139
|
│ Transport · entity() · client contract · model definitions · ids
|
|
113
140
|
│ the generator · in-memory doubles for tests
|
|
114
141
|
▲
|
|
@@ -119,22 +146,58 @@ server-side render, in a script and in a test, unchanged.**
|
|
|
119
146
|
|
|
120
147
|
Dependencies point down only. This package knows nothing above it.
|
|
121
148
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
149
|
+
**What goes where.** client-framework + web-framework is to a web application what
|
|
150
|
+
sincpro_framework is to a backend; client-framework + @sincpro/mobile, the same for a phone. A
|
|
151
|
+
piece web and mobile can both use, and that needs no platform, comes here — as a port, its rules
|
|
152
|
+
and a memory adapter. A piece only one platform needs, or that needs a platform's technology, is
|
|
153
|
+
that platform's — an adapter of a port. An application's business is the application's. Nothing
|
|
154
|
+
is replicated: what differs by nature (a phone's SQLite domain, a browser's entity screens) stays
|
|
155
|
+
different, and the platforms meet here only through the ports.
|
|
129
156
|
|
|
130
|
-
|
|
131
|
-
service's `this.client`, `addDependency`, the services registered by name and used as
|
|
132
|
-
`project.Issues.close(input)` and `this.feature.Issues`; the process configured once in `main`
|
|
133
|
-
(`configureProcess`); and the context as a port, so the browser and the phone keep it each with
|
|
134
|
-
their own technology behind the same door.
|
|
157
|
+
## Where it stands
|
|
135
158
|
|
|
136
|
-
**
|
|
137
|
-
|
|
159
|
+
**The application layer is the backend's.** A bounded context executes class Commands through
|
|
160
|
+
one pipeline — context, span, log lines, outcome, report — with its dependencies injected,
|
|
161
|
+
its ApplicationServices composing Features through `featureBus`, and the backend's error
|
|
162
|
+
handlers: a failure is observed where it happened, then answered or handed on. Screen requests
|
|
163
|
+
and scaffolded applications run through the same pipeline.
|
|
164
|
+
|
|
165
|
+
**Observability is ports, plugged once.** Tracing, error reporting and logging are ports the
|
|
166
|
+
process configures; the console is the default log engine, OpenTelemetry and Sentry optional
|
|
167
|
+
entries that wrap what the application built. The application layer imports no logger: a
|
|
168
|
+
bounded context has its own, and a run hands `this.logger`, bound to its execution; only the
|
|
169
|
+
infrastructure and the adapters have a predefined one.
|
|
170
|
+
|
|
171
|
+
**The client and the generator read the backend's contract.** Types per model, operation and —
|
|
172
|
+
when the contract is exported with them — domain event; a client whose methods call each
|
|
173
|
+
operation by who it is, over the `OperationTransport` the application injects — REST, JSON-RPC,
|
|
174
|
+
a platform's gRPC —; the entities screens use. What is generated is types, never classes, and
|
|
175
|
+
never names a wire: the contract's bindings are data, read by the adapter of each wire.
|
|
176
|
+
|
|
177
|
+
**Each module is laid out as the backend's.** `domain/`, `adapters/`, `services/`,
|
|
178
|
+
`infrastructure/`, `entrypoint/`, only those a module needs; `domain/` depends only on `domain/`.
|
|
179
|
+
|
|
180
|
+
**The pipeline is the core's, and open.** The flow of a run is defined once; interceptors,
|
|
181
|
+
listeners and extensions change or hear it from outside, without touching a service.
|
|
182
|
+
|
|
183
|
+
**Events travel the same way on every platform.** A Feature publishes through a `Publisher`;
|
|
184
|
+
a `Subscriber` executes the event in every bounded context that hears it, in the same flow
|
|
185
|
+
(`correlationId`, caused by the run that published it). In the call (`syncQueue`), or stored
|
|
186
|
+
first and delivered by the backend's store-and-forward rules (`storedQueue`) over the
|
|
187
|
+
`EventStore` each platform plugs — a SQLite table, IndexedDB, memory.
|
|
188
|
+
|
|
189
|
+
**A failure means the same thing everywhere.** `failureOf` reads any error — whatever the wire,
|
|
190
|
+
whatever backend — in the backend's kinds and next steps; retrying, setting aside, reporting and
|
|
191
|
+
a screen's answer follow from it. A run can be cancelled, and its requests with it. The
|
|
192
|
+
backend's circuit breaker guards any transport.
|
|
193
|
+
|
|
194
|
+
**Consumers have a guide.** The `sincpro-client-framework` skill says, for an application and for
|
|
195
|
+
a platform's framework, what goes where and how each thing is done.
|
|
196
|
+
|
|
197
|
+
**Next:** web and mobile adopt it in their own releases — mobile's SQLite `EventStore` and gRPC or
|
|
198
|
+
JSON-RPC wire, web's in-memory or IndexedDB store and its hooks —, declaring this package as a
|
|
199
|
+
peer dependency; events from the server live, with the backend's server-sent events; generated
|
|
200
|
+
readers that validate and convert what arrives, when a use needs them.
|
|
138
201
|
|
|
139
202
|
## How we decide
|
|
140
203
|
|