@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
@@ -0,0 +1,184 @@
1
+ import { processContext } from "../../context/infrastructure/process.js";
2
+ import { optionsOf } from "../../core/domain/bus.js";
3
+ import { DomainEvent } from "../../core/domain/dto.js";
4
+ import { RESERVED_MEMBERS } from "../../core/domain/services.js";
5
+ import { executeService } from "../../core/services/execution.js";
6
+ /**
7
+ * An entrypoint that hears one domain event and calls the use cases it starts — as a cron calls
8
+ * them on a tick, or a screen on a gesture:
9
+ *
10
+ * class LoggedSuccessfully extends EventSubscriber<OdooLoggedIn> {
11
+ * static readonly subscriberName = "LoggedSuccessfully";
12
+ * declare readonly ticketsBoundedContext: BoundedContext<TicketsDependencies>;
13
+ *
14
+ * async handle(event: OdooLoggedIn) {
15
+ * await this.ticketsBoundedContext.execute(new CommandPrepareSession(event.uid));
16
+ * }
17
+ * }
18
+ *
19
+ * Context: a subscriber is not a use case. It reads the event, builds the Command — or the
20
+ * Commands — and executes them on a bounded context it was given; what to decide, and work of
21
+ * several steps, is a Feature or an ApplicationService it executes. While `handle` runs,
22
+ * `this.span`, `this.logger`, `this.signal`, `this.execution` and every dependency are that
23
+ * delivery's: a bounded context it was given executes as a child of it — the same flow, caused by
24
+ * it (`BIND`). `subscriberName` is stable metadata, never `constructor.name`. Like a service, a
25
+ * subscriber is one instance for every delivery: what belongs to one goes in its locals.
26
+ */
27
+ export class EventSubscriber {
28
+ }
29
+ function isEventClass(value) {
30
+ return typeof value === "function" && value.prototype instanceof DomainEvent;
31
+ }
32
+ function nameOf(Subscriber) {
33
+ const named = Subscriber;
34
+ return String(named?.subscriberName ?? named?.name ?? Subscriber);
35
+ }
36
+ /**
37
+ * The subscribers of one part of an application — which `EventSubscriber` hears which event —,
38
+ * with the dependencies they call: what a queue's `Subscriber` hands an event to, beside the
39
+ * bounded contexts:
40
+ *
41
+ * export const ticketsSubscriptions = new Subscriptions<TicketsEntrypoints>("tickets-subscriptions");
42
+ * ticketsSubscriptions.addDependency("ticketsBoundedContext", ticketsBoundedContext);
43
+ * ticketsSubscriptions.subscribe(OdooLoggedIn)(LoggedSuccessfully);
44
+ *
45
+ * storedQueue({ store, subscriber: new Subscriber(odooBoundedContext, ticketsSubscriptions) });
46
+ *
47
+ * Context: the shape of a bounded context, and of the backend's `Crons`: a name that is its
48
+ * identity in spans, logs and the queue's record of who already heard an event — so it differs
49
+ * from every bounded context's —, dependencies by name, and a function (or a decorator) that
50
+ * registers. One subscriber per event here: several reactions to one event are one
51
+ * ApplicationService it executes, or subscribers of other registries. Each delivery is a run of
52
+ * its own (`<name>/<subscriberName>`, layer `entrypoint`), continuing the envelope's flow; it is
53
+ * not a use case, so interceptors and listeners do not see it — they see the use cases it calls.
54
+ */
55
+ export class Subscriptions {
56
+ name;
57
+ registrations = new Map();
58
+ dependencyValues = new Map();
59
+ constructor(name) {
60
+ this.name = name;
61
+ if (name.trim() === "" || name.includes("/")) {
62
+ throw new TypeError("Subscriptions need a nonempty name without '/'");
63
+ }
64
+ }
65
+ /** A dependency its subscribers find on `this`, bound to each delivery; `replaces` as a bounded context's. */
66
+ addDependency(name, dependency, options) {
67
+ if (RESERVED_MEMBERS.has(name))
68
+ throw new Error(`Reserved dependency name: ${name}`);
69
+ const registered = this.dependencyValues.has(name);
70
+ if (registered && options === undefined) {
71
+ throw new Error(`Dependency already registered: ${name}`);
72
+ }
73
+ if (options && (!registered || this.dependencyValues.get(name) !== options.replaces)) {
74
+ throw new Error(`Dependency replacement does not match the registration: ${name}`);
75
+ }
76
+ this.dependencyValues.set(name, dependency);
77
+ return this;
78
+ }
79
+ /**
80
+ * Registers the subscriber that hears `event` here. With `replaces`, it takes the place of the
81
+ * one registered now.
82
+ */
83
+ subscribe(event, options = {}) {
84
+ return (Subscriber, decorator) => {
85
+ const register = () => this.register(event, Subscriber, options.replaces);
86
+ // Standard decorators run before static fields have initialized.
87
+ if (decorator)
88
+ decorator.addInitializer(register);
89
+ else
90
+ register();
91
+ return Subscriber;
92
+ };
93
+ }
94
+ /** The subscriber registered now for an event class — what a `replaces` names. */
95
+ handlerOf(event) {
96
+ return this.registrations.get(event)?.Subscriber;
97
+ }
98
+ /** The event class registered here under a wire name, or `undefined` when nobody here hears it. */
99
+ eventTypeOf(name) {
100
+ for (const event of this.registrations.keys()) {
101
+ if (event.eventName === name)
102
+ return event;
103
+ }
104
+ return undefined;
105
+ }
106
+ /**
107
+ * `event` heard by its subscriber here — continuing the envelope's flow when it is given
108
+ * (`execute(event, envelope)`) — and what `handle` returned.
109
+ *
110
+ * 1. The subscriber registered for the event's exact class; none: an error.
111
+ * 2. A run `<name>/<subscriberName>`, layer `entrypoint`: its span, log lines and — when it
112
+ * fails with a fault nothing reported yet — its report; its dependencies bound to it.
113
+ * Final: what `handle` returned; its failure, as it is, to the queue that delivered it.
114
+ */
115
+ execute(event, options) {
116
+ const type = Object.getPrototypeOf(event)?.constructor;
117
+ const registration = this.registrations.get(type);
118
+ if (registration === undefined) {
119
+ return Promise.reject(new Error(`${this.name}: no subscriber hears ${event.name}`));
120
+ }
121
+ registration.instance ??= new registration.Subscriber();
122
+ const given = optionsOf(options);
123
+ const attributes = {
124
+ "sincpro.event": event.name,
125
+ ...(registration.replaces ? { "sincpro.replaces": registration.replaces } : {}),
126
+ };
127
+ return executeService(registration.instance, event, {
128
+ boundedContext: this.name,
129
+ name: registration.name,
130
+ layer: "entrypoint",
131
+ method: "handle",
132
+ attributes,
133
+ dependencies: new Map(this.dependencyValues),
134
+ context: () => ({ ...processContext.values }),
135
+ ...(given.cause ? { cause: given.cause } : {}),
136
+ ...(given.signal ? { signal: given.signal } : {}),
137
+ interceptors: [],
138
+ expected: () => false,
139
+ handlers: [],
140
+ });
141
+ }
142
+ /**
143
+ * 1. The subscriber is an `EventSubscriber`, for a domain event class with its `eventName`.
144
+ * 2. The event has no subscriber here yet — or, with `replaces`, the one it has is that one.
145
+ * 3. Its `subscriberName` is given and no other subscriber here has it; no other event class
146
+ * here has the event's name.
147
+ * Final: registered, said on its spans when it replaced another; the next delivery hears it.
148
+ */
149
+ register(event, Subscriber, replaces) {
150
+ if (!(Subscriber.prototype instanceof EventSubscriber)) {
151
+ throw new TypeError("The subscriber is not an EventSubscriber");
152
+ }
153
+ if (!isEventClass(event)) {
154
+ throw new TypeError("A subscriber is registered for a DomainEvent class");
155
+ }
156
+ if (typeof event.eventName !== "string" || event.eventName.trim() === "") {
157
+ throw new Error("A domain event needs a nonempty eventName");
158
+ }
159
+ const current = this.registrations.get(event);
160
+ if (replaces !== undefined && current?.Subscriber !== replaces) {
161
+ throw new Error(`Replacement does not match the subscription: ${nameOf(replaces)}`);
162
+ }
163
+ if (replaces === undefined && current) {
164
+ throw new Error(`Event already subscribed in ${this.name}: one subscriber per event — several reactions are one ApplicationService`);
165
+ }
166
+ const name = Subscriber.subscriberName;
167
+ if (typeof name !== "string" || name.trim() === "" || name.includes("/")) {
168
+ throw new Error("A subscriber needs a nonempty subscriberName without '/'");
169
+ }
170
+ for (const [other, registration] of this.registrations) {
171
+ if (registration !== current && registration.name === name) {
172
+ throw new Error(`Subscriber name already registered: ${name}`);
173
+ }
174
+ if (other !== event && other.eventName === event.eventName) {
175
+ throw new Error(`Event name already registered: ${event.eventName}`);
176
+ }
177
+ }
178
+ this.registrations.set(event, {
179
+ name,
180
+ Subscriber,
181
+ ...(current ? { replaces: current.name } : {}),
182
+ });
183
+ }
184
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Event-driven: the ports and the in-memory pieces that let every platform publish domain events
3
+ * and subscribe to them the same way — as sincpro_framework's `event_driven`. Delivering them
4
+ * durably (SQLite on a phone, IndexedDB in a browser, a broker) is each platform's, behind
5
+ * `EventQueue`.
6
+ *
7
+ * domain/ the envelope and the ports: EventQueue, Subscribes, EventStore
8
+ * adapters/ syncQueue (in the call), storedQueue (store, then deliver, retry, set aside),
9
+ * memoryEventStore
10
+ * entrypoint/ Publisher (what a Feature holds), Subscriber (the queue's door: everyone who
11
+ * hears), Subscriptions and EventSubscriber (the entrypoint that hears an event and
12
+ * calls use cases)
13
+ */
14
+ export { type MemoryEventStore, memoryEventStore, } from "./adapters/memory-event-store.js";
15
+ export { type StoredQueue, storedQueue, type StoredQueueOptions, } from "./adapters/stored-queue.js";
16
+ export { syncQueue } from "./adapters/sync-queue.js";
17
+ export { type Envelope, envelopeOf, } from "./domain/envelope.js";
18
+ export type { EventQueue, Handled, Subscribes, } from "./domain/queue.js";
19
+ export type { DeliveryFailure, EventStore, StoredEvent, StoredState, } from "./domain/store.js";
20
+ export { Publisher } from "./entrypoint/publisher.js";
21
+ export { type HearingContext, Subscriber, } from "./entrypoint/subscriber.js";
22
+ export { EventSubscriber, type Subscribing, type SubscriptionOptions, Subscriptions, } from "./entrypoint/subscriptions.js";
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Event-driven: the ports and the in-memory pieces that let every platform publish domain events
3
+ * and subscribe to them the same way — as sincpro_framework's `event_driven`. Delivering them
4
+ * durably (SQLite on a phone, IndexedDB in a browser, a broker) is each platform's, behind
5
+ * `EventQueue`.
6
+ *
7
+ * domain/ the envelope and the ports: EventQueue, Subscribes, EventStore
8
+ * adapters/ syncQueue (in the call), storedQueue (store, then deliver, retry, set aside),
9
+ * memoryEventStore
10
+ * entrypoint/ Publisher (what a Feature holds), Subscriber (the queue's door: everyone who
11
+ * hears), Subscriptions and EventSubscriber (the entrypoint that hears an event and
12
+ * calls use cases)
13
+ */
14
+ export { memoryEventStore, } from "./adapters/memory-event-store.js";
15
+ export { storedQueue, } from "./adapters/stored-queue.js";
16
+ export { syncQueue } from "./adapters/sync-queue.js";
17
+ export { envelopeOf, } from "./domain/envelope.js";
18
+ export { Publisher } from "./entrypoint/publisher.js";
19
+ export { Subscriber, } from "./entrypoint/subscriber.js";
20
+ export { EventSubscriber, Subscriptions, } from "./entrypoint/subscriptions.js";
package/dist/index.d.ts CHANGED
@@ -7,19 +7,24 @@
7
7
  * as sincpro_framework's are — `domain/` (ports and rules), `adapters/` (their implementations),
8
8
  * `services/`, `infrastructure/`, `entrypoint/` — and says what it offers in its own `index.ts`:
9
9
  *
10
- * common/ the values of the wire, as the backend writes them
10
+ * common/ the values of the wire, as the backend writes them; what a failure is
11
+ * circuit_breaker/ stop calling what keeps failing; try it again once it may be back
11
12
  * context/ the context's levels, where they live, what travels, the execution
12
13
  * observability/ tracing, error reporting and logging: ports, console, loggers
13
14
  * client/ the seam with a server: transports, entities, the client contract
14
15
  * model/ model definitions
15
- * core/ Commands, Features, ApplicationServices, BoundedContext, the process
16
+ * core/ Commands, events, Features, ApplicationServices, BoundedContext, the
17
+ * pipeline's extension points, the process
18
+ * event_driven/ publishing and subscribing to domain events: ports, Publisher, Subscriber
16
19
  *
17
20
  * The generator is `@sincpro/client-framework/codegen`, and the in-memory observability
18
21
  * `@sincpro/client-framework/testing`: neither is part of what an application runs.
19
22
  */
23
+ export * from "./circuit_breaker/index.js";
20
24
  export * from "./client/index.js";
21
25
  export * from "./common/index.js";
22
26
  export * from "./context/index.js";
23
27
  export * from "./core/index.js";
28
+ export * from "./event_driven/index.js";
24
29
  export * from "./model/index.js";
25
30
  export * from "./observability/index.js";
package/dist/index.js CHANGED
@@ -7,19 +7,24 @@
7
7
  * as sincpro_framework's are — `domain/` (ports and rules), `adapters/` (their implementations),
8
8
  * `services/`, `infrastructure/`, `entrypoint/` — and says what it offers in its own `index.ts`:
9
9
  *
10
- * common/ the values of the wire, as the backend writes them
10
+ * common/ the values of the wire, as the backend writes them; what a failure is
11
+ * circuit_breaker/ stop calling what keeps failing; try it again once it may be back
11
12
  * context/ the context's levels, where they live, what travels, the execution
12
13
  * observability/ tracing, error reporting and logging: ports, console, loggers
13
14
  * client/ the seam with a server: transports, entities, the client contract
14
15
  * model/ model definitions
15
- * core/ Commands, Features, ApplicationServices, BoundedContext, the process
16
+ * core/ Commands, events, Features, ApplicationServices, BoundedContext, the
17
+ * pipeline's extension points, the process
18
+ * event_driven/ publishing and subscribing to domain events: ports, Publisher, Subscriber
16
19
  *
17
20
  * The generator is `@sincpro/client-framework/codegen`, and the in-memory observability
18
21
  * `@sincpro/client-framework/testing`: neither is part of what an application runs.
19
22
  */
23
+ export * from "./circuit_breaker/index.js";
20
24
  export * from "./client/index.js";
21
25
  export * from "./common/index.js";
22
26
  export * from "./context/index.js";
23
27
  export * from "./core/index.js";
28
+ export * from "./event_driven/index.js";
24
29
  export * from "./model/index.js";
25
30
  export * from "./observability/index.js";
@@ -6,7 +6,7 @@ import type { Outcome, Tracer } from "../domain/tracing.js";
6
6
  *
7
7
  * const tracer = memoryTracer();
8
8
  * configureProcess({ tracer });
9
- * await project.execute(new CommandCloseIssue("issue-1"));
9
+ * await projectBoundedContext.execute(new CommandCloseIssue("issue-1"));
10
10
  * tracer.spans.map((one) => one.name); // ["project/CloseIssue", "project/Store", …]
11
11
  *
12
12
  * Context: a use case is proven in Node with nothing of a platform; these are the platform's
@@ -5,14 +5,18 @@ import type { Attributes, Outcome } from "./tracing.js";
5
5
  *
6
6
  * Context: a line says which layer wrote it, the way sincpro_framework's architecture names
7
7
  * them: the application layer (a bounded context and its ApplicationServices and Features,
8
- * through `project.logger` and `this.logger`), its adapters (`loggerAdapter`) and the
9
- * infrastructure around them (`logger`). A line written inside a use case carries that
8
+ * through `projectBoundedContext.logger` and `this.logger`), the entrypoints that call it (an
9
+ * `EventSubscriber`'s `this.logger`), its adapters (`loggerAdapter`) and the infrastructure
10
+ * around them (`logger`). A line written inside a use case carries that
10
11
  * execution's identity; nothing writes a DTO, a response, an error body or the session's
11
12
  * context on its own.
12
13
  */
13
14
  export type LogLevel = "debug" | "info" | "warn" | "error";
14
- /** The layer a line comes from. */
15
- export type LogLayer = "infrastructure" | "adapter" | "bounded_context" | "application_service" | "feature";
15
+ /**
16
+ * The layer a line comes from: the infrastructure, an adapter, an entrypoint that calls use cases
17
+ * (an `EventSubscriber`), a bounded context, an ApplicationService, a Feature.
18
+ */
19
+ export type LogLayer = "infrastructure" | "adapter" | "entrypoint" | "bounded_context" | "application_service" | "feature";
16
20
  /** Structured logging never implicitly includes a DTO, response, error body or session context. */
17
21
  export type LogRecord = {
18
22
  readonly timestamp: string;
@@ -24,6 +28,8 @@ export type LogRecord = {
24
28
  readonly boundedContext?: string;
25
29
  readonly useCase?: string;
26
30
  readonly executionId?: string;
31
+ /** The chain the run belongs to: every run and event of one flow share it. */
32
+ readonly correlationId?: string;
27
33
  readonly traceId?: string;
28
34
  readonly spanId?: string;
29
35
  readonly outcome?: Outcome;
@@ -5,14 +5,16 @@ import type { Outcome } from "../domain/tracing.js";
5
5
  export declare const logger: LayerLogger;
6
6
  /** The adapters' logger: what talks to a printer, a database, another system. */
7
7
  export declare const loggerAdapter: LayerLogger;
8
- /** @internal — a bounded context's own logger (`project.logger`). */
8
+ /** @internal — a bounded context's own logger (`projectBoundedContext.logger`). */
9
9
  export declare function boundedContextLogger(name: string): LayerLogger;
10
- /** @internal — what a Feature or ApplicationService finds as `this.logger` while it runs. */
11
- export declare function executionLogger(layer: "feature" | "application_service", execution: Execution): LayerLogger;
10
+ /** A run's layer: an entrypoint's (an `EventSubscriber`), a Feature's or an ApplicationService's. */
11
+ type RunLogLayer = Extract<LogLayer, "entrypoint" | "feature" | "application_service">;
12
+ /** @internal — what a run (a use case, an entrypoint) finds as `this.logger` while it runs. */
13
+ export declare function executionLogger(layer: RunLogLayer, execution: Execution): LayerLogger;
12
14
  /** Each destination keeps its own policy; one failing sink does not hold back the others. */
13
15
  export declare function combineLoggers(...loggers: readonly Logger[]): Logger;
14
16
  /** @internal — lifecycle records contain correlation and outcome, never business payloads. */
15
- export declare function logExecution(layer: "feature" | "application_service", execution: Execution, completed?: {
17
+ export declare function logExecution(layer: RunLogLayer, execution: Execution, completed?: {
16
18
  outcome: Outcome;
17
19
  durationMs: number;
18
20
  }): void;
@@ -25,3 +27,4 @@ export declare function logHandled(layer: LogLayer, correlation: {
25
27
  } | {
26
28
  boundedContext: string;
27
29
  }): void;
30
+ export {};
@@ -19,6 +19,7 @@ function correlationOf(execution) {
19
19
  boundedContext: execution.boundedContext,
20
20
  useCase: execution.useCase,
21
21
  executionId: execution.id,
22
+ correlationId: execution.correlationId,
22
23
  ...(execution.span.traceId ? { traceId: execution.span.traceId } : {}),
23
24
  ...(execution.span.spanId ? { spanId: execution.span.spanId } : {}),
24
25
  };
@@ -50,11 +51,11 @@ function layerLogger(layer, sink, correlation = {}) {
50
51
  export const logger = layerLogger("infrastructure", processSink);
51
52
  /** The adapters' logger: what talks to a printer, a database, another system. */
52
53
  export const loggerAdapter = layerLogger("adapter", processSink);
53
- /** @internal — a bounded context's own logger (`project.logger`). */
54
+ /** @internal — a bounded context's own logger (`projectBoundedContext.logger`). */
54
55
  export function boundedContextLogger(name) {
55
56
  return layerLogger("bounded_context", processSink, { boundedContext: name });
56
57
  }
57
- /** @internal — what a Feature or ApplicationService finds as `this.logger` while it runs. */
58
+ /** @internal — what a run (a use case, an entrypoint) finds as `this.logger` while it runs. */
58
59
  export function executionLogger(layer, execution) {
59
60
  return layerLogger(layer, processSink, correlationOf(execution));
60
61
  }
@@ -1,12 +1,12 @@
1
1
  import type { Outcome } from "../domain/tracing.js";
2
2
  /**
3
- * How an error ended a use case, by default.
3
+ * How an error ended a use case, by default — read off the failure it is (`failureOf`).
4
4
  *
5
5
  * Context: the backend reports what nobody expected and only that — a refusal it explained is
6
- * the business answering, not a fault. Here the same: a `TransportError` the server answered
7
- * with a 4xx is `refused`; a request the caller stopped waiting for is `aborted`; everything
8
- * else — a bug, a 5xx, a server nobody could reach — is `failed`, and reported. A bounded
9
- * context adds its own expected errors (`expectErrors`); a process replaces this whole
10
- * (`configureProcess({ outcomeOf })`).
6
+ * the business answering, not a fault. Here the same: a failure that is the caller's answer (a
7
+ * 4xx, an error that declares a kind such as `domain` or `not_found`) is `refused`; a request
8
+ * the caller stopped waiting for is `aborted`; a fault — a bug, a 5xx, a server nobody could
9
+ * reach — is `failed`, and reported. A bounded context adds its own expected errors
10
+ * (`expectErrors`); a process replaces this whole (`configureProcess({ outcomeOf })`).
11
11
  */
12
12
  export declare function outcomeOf(error: unknown): Outcome;
@@ -1,20 +1,17 @@
1
- import { TransportError } from "../../client/domain/transport.js";
1
+ import { failureOf } from "../../common/failures.js";
2
2
  /**
3
- * How an error ended a use case, by default.
3
+ * How an error ended a use case, by default — read off the failure it is (`failureOf`).
4
4
  *
5
5
  * Context: the backend reports what nobody expected and only that — a refusal it explained is
6
- * the business answering, not a fault. Here the same: a `TransportError` the server answered
7
- * with a 4xx is `refused`; a request the caller stopped waiting for is `aborted`; everything
8
- * else — a bug, a 5xx, a server nobody could reach — is `failed`, and reported. A bounded
9
- * context adds its own expected errors (`expectErrors`); a process replaces this whole
10
- * (`configureProcess({ outcomeOf })`).
6
+ * the business answering, not a fault. Here the same: a failure that is the caller's answer (a
7
+ * 4xx, an error that declares a kind such as `domain` or `not_found`) is `refused`; a request
8
+ * the caller stopped waiting for is `aborted`; a fault — a bug, a 5xx, a server nobody could
9
+ * reach — is `failed`, and reported. A bounded context adds its own expected errors
10
+ * (`expectErrors`); a process replaces this whole (`configureProcess({ outcomeOf })`).
11
11
  */
12
12
  export function outcomeOf(error) {
13
- if (error instanceof TransportError && error.status !== null)
14
- return error.status >= 400 && error.status < 500 ? "refused" : "failed";
15
- if (error !== null &&
16
- typeof error === "object" &&
17
- error.name === "AbortError")
13
+ const failure = failureOf(error);
14
+ if (failure.aborted)
18
15
  return "aborted";
19
- return "failed";
16
+ return failure.fault ? "failed" : "refused";
20
17
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sincpro/client-framework",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "The client side of sincpro_framework, for every platform: Feature and ApplicationService traced and reported by default, applications made of domains, and the typed client generated from a service's contract. No React, no DOM.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",