@sincpro/client-framework 1.0.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.
Files changed (78) hide show
  1. package/AGENTS.md +53 -12
  2. package/MANIFESTO.md +56 -13
  3. package/README.md +329 -33
  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 +6 -3
  26. package/dist/codegen/services/scaffold.js +14 -13
  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/core/domain/bus.d.ts +16 -7
  33. package/dist/core/domain/bus.js +14 -6
  34. package/dist/core/domain/dto.d.ts +32 -2
  35. package/dist/core/domain/dto.js +23 -0
  36. package/dist/core/domain/pipeline.d.ts +89 -0
  37. package/dist/core/domain/pipeline.js +1 -0
  38. package/dist/core/domain/services.d.ts +14 -4
  39. package/dist/core/domain/services.js +4 -1
  40. package/dist/core/entrypoint/bounded-context.d.ts +79 -10
  41. package/dist/core/entrypoint/bounded-context.js +207 -33
  42. package/dist/core/index.d.ts +4 -2
  43. package/dist/core/index.js +3 -2
  44. package/dist/core/infrastructure/listeners.d.ts +28 -0
  45. package/dist/core/infrastructure/listeners.js +34 -0
  46. package/dist/core/services/binding.d.ts +2 -2
  47. package/dist/core/services/binding.js +6 -3
  48. package/dist/core/services/execution.d.ts +37 -6
  49. package/dist/core/services/execution.js +86 -7
  50. package/dist/core/services/interceptors.d.ts +15 -0
  51. package/dist/core/services/interceptors.js +25 -0
  52. package/dist/core/services/listening.d.ts +17 -0
  53. package/dist/core/services/listening.js +25 -0
  54. package/dist/event_driven/adapters/memory-event-store.d.ts +10 -0
  55. package/dist/event_driven/adapters/memory-event-store.js +23 -0
  56. package/dist/event_driven/adapters/stored-queue.d.ts +67 -0
  57. package/dist/event_driven/adapters/stored-queue.js +192 -0
  58. package/dist/event_driven/adapters/sync-queue.d.ts +11 -0
  59. package/dist/event_driven/adapters/sync-queue.js +19 -0
  60. package/dist/event_driven/domain/envelope.d.ts +28 -0
  61. package/dist/event_driven/domain/envelope.js +19 -0
  62. package/dist/event_driven/domain/queue.d.ts +40 -0
  63. package/dist/event_driven/domain/queue.js +1 -0
  64. package/dist/event_driven/domain/store.d.ts +56 -0
  65. package/dist/event_driven/domain/store.js +1 -0
  66. package/dist/event_driven/entrypoint/publisher.d.ts +26 -0
  67. package/dist/event_driven/entrypoint/publisher.js +35 -0
  68. package/dist/event_driven/entrypoint/subscriber.d.ts +34 -0
  69. package/dist/event_driven/entrypoint/subscriber.js +51 -0
  70. package/dist/event_driven/index.d.ts +19 -0
  71. package/dist/event_driven/index.js +17 -0
  72. package/dist/index.d.ts +7 -2
  73. package/dist/index.js +7 -2
  74. package/dist/observability/domain/logging.d.ts +2 -0
  75. package/dist/observability/services/logging.js +1 -0
  76. package/dist/observability/services/outcome.d.ts +6 -6
  77. package/dist/observability/services/outcome.js +10 -13
  78. package/package.json +1 -1
@@ -0,0 +1,28 @@
1
+ import type { DomainEvent } from "../../core/domain/dto.js";
2
+ import type { Cause } from "../../core/domain/pipeline.js";
3
+ /**
4
+ * What travels beside a published event: who it is, which flow it belongs to and what caused it
5
+ * — the backend's `Envelope` and the chain of its `DomainEvent` (`correlation_id`,
6
+ * `causation_id`), kept beside the event rather than in it, so an event class carries only what
7
+ * happened.
8
+ *
9
+ * { name: "shop.order.v1.paid", id: "0193…", correlationId: "0193…", causationId: "0192…",
10
+ * publishedAt: "2026-11-10T23:31:53.000Z" }
11
+ *
12
+ * Context: a queue stores it with the event and hands it back on delivery
13
+ * (`context.execute(event, envelope)`): the run that executes the event continues the flow —
14
+ * the same `correlationId`, caused by this event's `id` —, three services and two devices later.
15
+ */
16
+ export type Envelope = Cause & {
17
+ /** The event's wire name (`eventName`). */
18
+ readonly name: string;
19
+ /** What directly caused it: the run that published it; `null` for the head of a flow. */
20
+ readonly causationId: string | null;
21
+ /** When it was published, ISO. */
22
+ readonly publishedAt: string;
23
+ };
24
+ /**
25
+ * The envelope of `event`, published by the run `cause` names — or the head of its own flow when
26
+ * nothing caused it (its `correlationId` is its own `id`).
27
+ */
28
+ export declare function envelopeOf(event: DomainEvent, cause?: Cause): Envelope;
@@ -0,0 +1,19 @@
1
+ import { newEntityId } from "../../common/ids.js";
2
+ /**
3
+ * The envelope of `event`, published by the run `cause` names — or the head of its own flow when
4
+ * nothing caused it (its `correlationId` is its own `id`).
5
+ */
6
+ export function envelopeOf(event, cause) {
7
+ const name = event.name;
8
+ if (name.trim() === "") {
9
+ throw new Error(`${event.constructor.name} has no eventName: an event is published by its wire name`);
10
+ }
11
+ const id = newEntityId();
12
+ return {
13
+ name,
14
+ id,
15
+ correlationId: cause?.correlationId ?? id,
16
+ causationId: cause?.id ?? null,
17
+ publishedAt: new Date().toISOString(),
18
+ };
19
+ }
@@ -0,0 +1,40 @@
1
+ import type { DomainEvent, EventClass } from "../../core/domain/dto.js";
2
+ import type { Envelope } from "./envelope.js";
3
+ /**
4
+ * What carries a published event to whoever hears it — the port each platform implements with
5
+ * its own technology, as the backend's `Queue`:
6
+ *
7
+ * - `syncQueue(subscriber)` — this package's: in the call, in memory (the web's default, tests).
8
+ * - a phone: SQLite, so an event outlives a restart, with retries, a dead-letter queue and the
9
+ * connectivity it waits for.
10
+ * - a browser: IndexedDB to work offline, `BroadcastChannel` to tell the other tabs.
11
+ * - a server's broker, through the backend.
12
+ *
13
+ * Context: delivery is the platform's — when, how often, how durably —; what it delivers and to
14
+ * whom is the same everywhere: an event, its envelope, and a `Subscribes` that executes it in
15
+ * every bounded context that hears it.
16
+ */
17
+ export interface EventQueue {
18
+ put(event: DomainEvent, envelope: Envelope): void | Promise<void>;
19
+ }
20
+ /** What one bounded context answered, or failed with, for a delivered event. */
21
+ export type Handled = {
22
+ readonly boundedContext: string;
23
+ readonly answer: unknown;
24
+ } | {
25
+ readonly boundedContext: string;
26
+ readonly error: unknown;
27
+ };
28
+ /**
29
+ * What a queue hands an event to — the backend's `Subscribes`: which class a stored name is
30
+ * rebuilt as, and the event executed by every subscriber.
31
+ */
32
+ export interface Subscribes {
33
+ /** The class an event of that wire name is rebuilt as, or `undefined` when nobody hears it. */
34
+ eventTypeOf(name: string): EventClass<DomainEvent> | undefined;
35
+ /**
36
+ * The event executed in every bounded context that hears it — but those `skip` names, which
37
+ * already have it — and what each answered.
38
+ */
39
+ handle(event: DomainEvent, envelope?: Envelope, skip?: readonly string[]): Promise<Handled[]>;
40
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,56 @@
1
+ import type { JsonValue } from "../../common/serialization.js";
2
+ import type { Envelope } from "./envelope.js";
3
+ import type { FailureKind } from "@sincpro/criteria";
4
+ /**
5
+ * Where a stored queue keeps what it was handed until every bounded context that hears it has
6
+ * executed it — the port each platform implements with its own technology:
7
+ *
8
+ * - `memoryEventStore()` — this package's: the process's memory (a browser tab, a test).
9
+ * - a phone: a SQLite table, so an event outlives a restart; the set-aside ones are its
10
+ * dead-letter queue.
11
+ * - a browser: IndexedDB, to work offline.
12
+ *
13
+ * Context: storing is the platform's, deciding what to do with what is stored is not — when to
14
+ * try again, what to set aside, which contexts already have it (`storedQueue`). So the port only
15
+ * keeps records, in the order they came, and finds the next one waiting.
16
+ */
17
+ export interface EventStore {
18
+ /** Keeps a published event, waiting. */
19
+ add(stored: StoredEvent): void | Promise<void>;
20
+ /**
21
+ * The oldest event still waiting (`pending`), in the order added — but those `skip` names by
22
+ * envelope id: not ready yet (no connection, no session), they wait while the rest go on.
23
+ */
24
+ next(skip: ReadonlySet<string>): StoredEvent | undefined | Promise<StoredEvent | undefined>;
25
+ /** What changed of a stored event — found by its envelope's id. */
26
+ save(stored: StoredEvent): void | Promise<void>;
27
+ }
28
+ /**
29
+ * - `pending`: waiting to be delivered — again, after `retryAt`, when it failed in a way a retry
30
+ * may fix.
31
+ * - `delivered`: every bounded context that hears it executed it.
32
+ * - `set_aside`: a context failed in a way every attempt would repeat (`PERMANENT_KINDS`) — the
33
+ * dead-letter queue; it never holds back what comes after.
34
+ */
35
+ export type StoredState = "pending" | "delivered" | "set_aside";
36
+ /** Why a bounded context did not execute it. */
37
+ export type DeliveryFailure = {
38
+ readonly boundedContext: string;
39
+ readonly kind: FailureKind;
40
+ readonly reason: string;
41
+ readonly message: string;
42
+ };
43
+ /** A published event as a store keeps it: its envelope, its values and how its delivery goes. */
44
+ export type StoredEvent = {
45
+ readonly envelope: Envelope;
46
+ /** The event's values (`valuesOf`): rebuilt as its class on delivery. */
47
+ readonly payload: JsonValue;
48
+ readonly state: StoredState;
49
+ readonly attempts: number;
50
+ /** When it may be tried again, ISO; `null` when it need not wait. */
51
+ readonly retryAt: string | null;
52
+ /** The bounded contexts done with it — executed, or set aside —: a retry never runs them again. */
53
+ readonly reached: readonly string[];
54
+ /** Why the last attempt did not reach every context. */
55
+ readonly failures: readonly DeliveryFailure[];
56
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,26 @@
1
+ import { BIND, type Execution } from "../../context/domain/execution.js";
2
+ import type { DomainEvent } from "../../core/domain/dto.js";
3
+ import type { Cause } from "../../core/domain/pipeline.js";
4
+ import { type Envelope } from "../domain/envelope.js";
5
+ import type { EventQueue } from "../domain/queue.js";
6
+ /**
7
+ * Puts events into a queue — what a Feature holds to tell whoever listens, as the backend's
8
+ * `Publisher`:
9
+ *
10
+ * shop.addDependency("publisher", new Publisher(queue));
11
+ * // in a Feature:
12
+ * await this.publisher.publish(new OrderPaid(order.id)); // its Envelope
13
+ *
14
+ * Context: a dependency of a bounded context is bound to each run (`BIND`), so what a Feature
15
+ * publishes joins that run's flow — the same `correlationId`, caused by the run — with nothing
16
+ * passed by hand. Outside a run, an event is the head of its own flow.
17
+ */
18
+ export declare class Publisher {
19
+ private readonly queue;
20
+ private readonly cause?;
21
+ constructor(queue: EventQueue, cause?: Cause | undefined);
22
+ /** `event` into the queue, with its envelope; answers the envelope once the queue took it. */
23
+ publish(event: DomainEvent): Promise<Envelope>;
24
+ /** The same publisher, publishing as `execution`: its events join that run's flow. */
25
+ [BIND](execution: Execution): Publisher;
26
+ }
@@ -0,0 +1,35 @@
1
+ import { BIND } from "../../context/domain/execution.js";
2
+ import { envelopeOf, } from "../domain/envelope.js";
3
+ /**
4
+ * Puts events into a queue — what a Feature holds to tell whoever listens, as the backend's
5
+ * `Publisher`:
6
+ *
7
+ * shop.addDependency("publisher", new Publisher(queue));
8
+ * // in a Feature:
9
+ * await this.publisher.publish(new OrderPaid(order.id)); // its Envelope
10
+ *
11
+ * Context: a dependency of a bounded context is bound to each run (`BIND`), so what a Feature
12
+ * publishes joins that run's flow — the same `correlationId`, caused by the run — with nothing
13
+ * passed by hand. Outside a run, an event is the head of its own flow.
14
+ */
15
+ export class Publisher {
16
+ queue;
17
+ cause;
18
+ constructor(queue, cause) {
19
+ this.queue = queue;
20
+ this.cause = cause;
21
+ }
22
+ /** `event` into the queue, with its envelope; answers the envelope once the queue took it. */
23
+ async publish(event) {
24
+ const envelope = envelopeOf(event, this.cause);
25
+ await this.queue.put(event, envelope);
26
+ return envelope;
27
+ }
28
+ /** The same publisher, publishing as `execution`: its events join that run's flow. */
29
+ [BIND](execution) {
30
+ return new Publisher(this.queue, {
31
+ id: execution.id,
32
+ correlationId: execution.correlationId,
33
+ });
34
+ }
35
+ }
@@ -0,0 +1,34 @@
1
+ import type { DomainEvent, EventClass } from "../../core/domain/dto.js";
2
+ import type { Cause } from "../../core/domain/pipeline.js";
3
+ import type { Envelope } from "../domain/envelope.js";
4
+ import type { Handled, Subscribes } from "../domain/queue.js";
5
+ /** What the subscriber needs of a bounded context: whether it hears an event, and running it. */
6
+ export type HearingContext = {
7
+ readonly name: string;
8
+ handlerOf(dto: abstract new (...args: never[]) => DomainEvent): unknown;
9
+ eventTypeOf(name: string): EventClass<DomainEvent> | undefined;
10
+ execute(event: DomainEvent, cause?: Cause): Promise<unknown>;
11
+ };
12
+ /**
13
+ * Hands an event to every bounded context that hears it — the backend's `Subscriber(buses)`:
14
+ *
15
+ * const subscriber = new Subscriber(shop, mail, stock);
16
+ * const queue = syncQueue(subscriber); // or a platform's durable queue
17
+ *
18
+ * Context: one subscriber per bounded context (its Feature or ApplicationService registered for
19
+ * the event class); several contexts hearing one event is several executions, one each, in the
20
+ * order named. A context that fails does not stop the others: each answer, or failure, is said.
21
+ */
22
+ export declare class Subscriber implements Subscribes {
23
+ private readonly contexts;
24
+ constructor(...contexts: HearingContext[]);
25
+ eventTypeOf(name: string): EventClass<DomainEvent> | undefined;
26
+ /**
27
+ * 1. Every bounded context registered for the event's class, in the order named — but those
28
+ * `skip` names, which already have it.
29
+ * 2. Each executes it — continuing the envelope's flow when there is one —, and its answer or
30
+ * failure is kept; a failure does not stop the next.
31
+ * Final: what each answered, or failed with.
32
+ */
33
+ handle(event: DomainEvent, envelope?: Envelope, skip?: readonly string[]): Promise<Handled[]>;
34
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Hands an event to every bounded context that hears it — the backend's `Subscriber(buses)`:
3
+ *
4
+ * const subscriber = new Subscriber(shop, mail, stock);
5
+ * const queue = syncQueue(subscriber); // or a platform's durable queue
6
+ *
7
+ * Context: one subscriber per bounded context (its Feature or ApplicationService registered for
8
+ * the event class); several contexts hearing one event is several executions, one each, in the
9
+ * order named. A context that fails does not stop the others: each answer, or failure, is said.
10
+ */
11
+ export class Subscriber {
12
+ contexts;
13
+ constructor(...contexts) {
14
+ this.contexts = contexts;
15
+ }
16
+ eventTypeOf(name) {
17
+ for (const context of this.contexts) {
18
+ const found = context.eventTypeOf(name);
19
+ if (found)
20
+ return found;
21
+ }
22
+ return undefined;
23
+ }
24
+ /**
25
+ * 1. Every bounded context registered for the event's class, in the order named — but those
26
+ * `skip` names, which already have it.
27
+ * 2. Each executes it — continuing the envelope's flow when there is one —, and its answer or
28
+ * failure is kept; a failure does not stop the next.
29
+ * Final: what each answered, or failed with.
30
+ */
31
+ async handle(event, envelope, skip = []) {
32
+ const type = Object.getPrototypeOf(event)?.constructor;
33
+ const handled = [];
34
+ for (const context of this.contexts) {
35
+ if (type === undefined || context.handlerOf(type) === undefined)
36
+ continue;
37
+ if (skip.includes(context.name))
38
+ continue;
39
+ try {
40
+ handled.push({
41
+ boundedContext: context.name,
42
+ answer: await context.execute(event, envelope),
43
+ });
44
+ }
45
+ catch (error) {
46
+ handled.push({ boundedContext: context.name, error });
47
+ }
48
+ }
49
+ return handled;
50
+ }
51
+ }
@@ -0,0 +1,19 @@
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 (every bounded context that hears)
11
+ */
12
+ export { type MemoryEventStore, memoryEventStore, } from "./adapters/memory-event-store.js";
13
+ export { type StoredQueue, storedQueue, type StoredQueueOptions, } from "./adapters/stored-queue.js";
14
+ export { syncQueue } from "./adapters/sync-queue.js";
15
+ export { type Envelope, envelopeOf, } from "./domain/envelope.js";
16
+ export type { EventQueue, Handled, Subscribes, } from "./domain/queue.js";
17
+ export type { DeliveryFailure, EventStore, StoredEvent, StoredState, } from "./domain/store.js";
18
+ export { Publisher } from "./entrypoint/publisher.js";
19
+ export { type HearingContext, Subscriber, } from "./entrypoint/subscriber.js";
@@ -0,0 +1,17 @@
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 (every bounded context that hears)
11
+ */
12
+ export { memoryEventStore, } from "./adapters/memory-event-store.js";
13
+ export { storedQueue, } from "./adapters/stored-queue.js";
14
+ export { syncQueue } from "./adapters/sync-queue.js";
15
+ export { envelopeOf, } from "./domain/envelope.js";
16
+ export { Publisher } from "./entrypoint/publisher.js";
17
+ export { Subscriber, } from "./entrypoint/subscriber.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";
@@ -24,6 +24,8 @@ export type LogRecord = {
24
24
  readonly boundedContext?: string;
25
25
  readonly useCase?: string;
26
26
  readonly executionId?: string;
27
+ /** The chain the run belongs to: every run and event of one flow share it. */
28
+ readonly correlationId?: string;
27
29
  readonly traceId?: string;
28
30
  readonly spanId?: string;
29
31
  readonly outcome?: Outcome;
@@ -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
  };
@@ -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.1.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",