@sincpro/client-framework 1.1.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 (34) hide show
  1. package/AGENTS.md +27 -10
  2. package/MANIFESTO.md +8 -6
  3. package/README.md +163 -58
  4. package/dist/client/services/guarded.d.ts +1 -1
  5. package/dist/codegen/services/scaffold.d.ts +1 -1
  6. package/dist/codegen/services/scaffold.js +12 -17
  7. package/dist/context/entrypoint/context.d.ts +5 -4
  8. package/dist/core/domain/dto.d.ts +3 -3
  9. package/dist/core/domain/dto.js +3 -3
  10. package/dist/core/domain/errors.d.ts +2 -2
  11. package/dist/core/domain/pipeline.d.ts +3 -3
  12. package/dist/core/domain/services.d.ts +5 -4
  13. package/dist/core/domain/services.js +1 -1
  14. package/dist/core/entrypoint/bounded-context.d.ts +24 -13
  15. package/dist/core/entrypoint/bounded-context.js +46 -15
  16. package/dist/core/infrastructure/listeners.d.ts +1 -1
  17. package/dist/core/infrastructure/listeners.js +1 -1
  18. package/dist/core/services/execution.d.ts +33 -17
  19. package/dist/core/services/execution.js +54 -38
  20. package/dist/event_driven/adapters/stored-queue.d.ts +5 -4
  21. package/dist/event_driven/adapters/stored-queue.js +5 -4
  22. package/dist/event_driven/entrypoint/publisher.d.ts +1 -1
  23. package/dist/event_driven/entrypoint/publisher.js +1 -1
  24. package/dist/event_driven/entrypoint/subscriber.d.ts +14 -6
  25. package/dist/event_driven/entrypoint/subscriber.js +16 -5
  26. package/dist/event_driven/entrypoint/subscriptions.d.ts +110 -0
  27. package/dist/event_driven/entrypoint/subscriptions.js +184 -0
  28. package/dist/event_driven/index.d.ts +4 -1
  29. package/dist/event_driven/index.js +4 -1
  30. package/dist/observability/adapters/memory.d.ts +1 -1
  31. package/dist/observability/domain/logging.d.ts +8 -4
  32. package/dist/observability/services/logging.d.ts +7 -4
  33. package/dist/observability/services/logging.js +2 -2
  34. package/package.json +1 -1
@@ -1,16 +1,27 @@
1
1
  /**
2
- * Hands an event to every bounded context that hears it — the backend's `Subscriber(buses)`:
2
+ * The door of a queue: hands an event to everyone who hears it — the backend's
3
+ * `Subscriber(buses)`:
3
4
  *
4
- * const subscriber = new Subscriber(shop, mail, stock);
5
+ * const subscriber = new Subscriber(shopBoundedContext, mailBoundedContext, ticketsSubscriptions);
5
6
  * const queue = syncQueue(subscriber); // or a platform's durable queue
6
7
  *
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.
8
+ * Context: who hears an event is a bounded context with a use case of it (a Feature or an
9
+ * ApplicationService registered for its class) or `Subscriptions` with an `EventSubscriber` of
10
+ * it — the entrypoint that calls use cases. Several hearing one event is several executions, one
11
+ * each, in the order named; one that fails does not stop the others: each answer, or failure, is
12
+ * said under its name — which a stored queue keeps, so a redelivery never runs again one that
13
+ * already heard it. Two of them never share a name.
10
14
  */
11
15
  export class Subscriber {
12
16
  contexts;
13
17
  constructor(...contexts) {
18
+ const names = new Set();
19
+ for (const context of contexts) {
20
+ if (names.has(context.name)) {
21
+ throw new Error(`Two of those who hear events are named ${context.name}: the queue keeps who heard an event by its name`);
22
+ }
23
+ names.add(context.name);
24
+ }
14
25
  this.contexts = contexts;
15
26
  }
16
27
  eventTypeOf(name) {
@@ -0,0 +1,110 @@
1
+ import type { Execution } from "../../context/domain/execution.js";
2
+ import type { ContextValues } from "../../context/domain/store.js";
3
+ import { DomainEvent, type EventClass } from "../../core/domain/dto.js";
4
+ import type { Cause, ExecuteOptions } from "../../core/domain/pipeline.js";
5
+ import type { LayerLogger } from "../../observability/domain/logging.js";
6
+ import type { Span } from "../../observability/domain/tracing.js";
7
+ /**
8
+ * An entrypoint that hears one domain event and calls the use cases it starts — as a cron calls
9
+ * them on a tick, or a screen on a gesture:
10
+ *
11
+ * class LoggedSuccessfully extends EventSubscriber<OdooLoggedIn> {
12
+ * static readonly subscriberName = "LoggedSuccessfully";
13
+ * declare readonly ticketsBoundedContext: BoundedContext<TicketsDependencies>;
14
+ *
15
+ * async handle(event: OdooLoggedIn) {
16
+ * await this.ticketsBoundedContext.execute(new CommandPrepareSession(event.uid));
17
+ * }
18
+ * }
19
+ *
20
+ * Context: a subscriber is not a use case. It reads the event, builds the Command — or the
21
+ * Commands — and executes them on a bounded context it was given; what to decide, and work of
22
+ * several steps, is a Feature or an ApplicationService it executes. While `handle` runs,
23
+ * `this.span`, `this.logger`, `this.signal`, `this.execution` and every dependency are that
24
+ * delivery's: a bounded context it was given executes as a child of it — the same flow, caused by
25
+ * it (`BIND`). `subscriberName` is stable metadata, never `constructor.name`. Like a service, a
26
+ * subscriber is one instance for every delivery: what belongs to one goes in its locals.
27
+ */
28
+ export declare abstract class EventSubscriber<E extends DomainEvent, Context extends object = ContextValues> {
29
+ /** What this delivery runs with: the process's context. */
30
+ readonly context: Readonly<Partial<Context>>;
31
+ /** This delivery's span: the Commands it executes are its children. */
32
+ readonly span: Span;
33
+ /** This delivery's logger: every line carries its flow and trace. */
34
+ readonly logger: LayerLogger;
35
+ /** What cancels this delivery, when it was executed with one. */
36
+ readonly signal: AbortSignal | undefined;
37
+ /**
38
+ * This delivery: its id, flow (`correlationId`) and cause — the envelope's id, the key that
39
+ * makes hearing an event twice harmless.
40
+ */
41
+ readonly execution: Execution;
42
+ abstract handle(event: E): unknown;
43
+ }
44
+ type SubscriberClass<E extends DomainEvent> = {
45
+ new (): EventSubscriber<E>;
46
+ readonly subscriberName: string;
47
+ };
48
+ /** What a subscription takes the place of: the subscriber registered now for the event. */
49
+ export type SubscriptionOptions = {
50
+ replaces?: abstract new (...args: never[]) => object;
51
+ };
52
+ /** What `subscribe(...)` answers: the function that registers the subscriber. */
53
+ export type Subscribing<E extends DomainEvent> = <S extends SubscriberClass<NoInfer<E>>>(Subscriber: S, decorator?: ClassDecoratorContext<S>) => S;
54
+ /**
55
+ * The subscribers of one part of an application — which `EventSubscriber` hears which event —,
56
+ * with the dependencies they call: what a queue's `Subscriber` hands an event to, beside the
57
+ * bounded contexts:
58
+ *
59
+ * export const ticketsSubscriptions = new Subscriptions<TicketsEntrypoints>("tickets-subscriptions");
60
+ * ticketsSubscriptions.addDependency("ticketsBoundedContext", ticketsBoundedContext);
61
+ * ticketsSubscriptions.subscribe(OdooLoggedIn)(LoggedSuccessfully);
62
+ *
63
+ * storedQueue({ store, subscriber: new Subscriber(odooBoundedContext, ticketsSubscriptions) });
64
+ *
65
+ * Context: the shape of a bounded context, and of the backend's `Crons`: a name that is its
66
+ * identity in spans, logs and the queue's record of who already heard an event — so it differs
67
+ * from every bounded context's —, dependencies by name, and a function (or a decorator) that
68
+ * registers. One subscriber per event here: several reactions to one event are one
69
+ * ApplicationService it executes, or subscribers of other registries. Each delivery is a run of
70
+ * its own (`<name>/<subscriberName>`, layer `entrypoint`), continuing the envelope's flow; it is
71
+ * not a use case, so interceptors and listeners do not see it — they see the use cases it calls.
72
+ */
73
+ export declare class Subscriptions<D extends object = Record<never, never>> {
74
+ readonly name: string;
75
+ private readonly registrations;
76
+ private readonly dependencyValues;
77
+ constructor(name: string);
78
+ /** A dependency its subscribers find on `this`, bound to each delivery; `replaces` as a bounded context's. */
79
+ addDependency<K extends keyof D & string>(name: K, dependency: D[K], options?: {
80
+ replaces: D[K];
81
+ }): this;
82
+ /**
83
+ * Registers the subscriber that hears `event` here. With `replaces`, it takes the place of the
84
+ * one registered now.
85
+ */
86
+ subscribe<E extends DomainEvent>(event: EventClass<E>, options?: SubscriptionOptions): Subscribing<E>;
87
+ /** The subscriber registered now for an event class — what a `replaces` names. */
88
+ handlerOf(event: abstract new (...args: never[]) => DomainEvent): (abstract new (...args: never[]) => object) | undefined;
89
+ /** The event class registered here under a wire name, or `undefined` when nobody here hears it. */
90
+ eventTypeOf(name: string): EventClass<DomainEvent> | undefined;
91
+ /**
92
+ * `event` heard by its subscriber here — continuing the envelope's flow when it is given
93
+ * (`execute(event, envelope)`) — and what `handle` returned.
94
+ *
95
+ * 1. The subscriber registered for the event's exact class; none: an error.
96
+ * 2. A run `<name>/<subscriberName>`, layer `entrypoint`: its span, log lines and — when it
97
+ * fails with a fault nothing reported yet — its report; its dependencies bound to it.
98
+ * Final: what `handle` returned; its failure, as it is, to the queue that delivered it.
99
+ */
100
+ execute(event: DomainEvent, options?: ExecuteOptions | Cause): Promise<unknown>;
101
+ /**
102
+ * 1. The subscriber is an `EventSubscriber`, for a domain event class with its `eventName`.
103
+ * 2. The event has no subscriber here yet — or, with `replaces`, the one it has is that one.
104
+ * 3. Its `subscriberName` is given and no other subscriber here has it; no other event class
105
+ * here has the event's name.
106
+ * Final: registered, said on its spans when it replaced another; the next delivery hears it.
107
+ */
108
+ private register;
109
+ }
110
+ export {};
@@ -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
+ }
@@ -7,7 +7,9 @@
7
7
  * domain/ the envelope and the ports: EventQueue, Subscribes, EventStore
8
8
  * adapters/ syncQueue (in the call), storedQueue (store, then deliver, retry, set aside),
9
9
  * memoryEventStore
10
- * entrypoint/ Publisher (what a Feature holds), Subscriber (every bounded context that hears)
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)
11
13
  */
12
14
  export { type MemoryEventStore, memoryEventStore, } from "./adapters/memory-event-store.js";
13
15
  export { type StoredQueue, storedQueue, type StoredQueueOptions, } from "./adapters/stored-queue.js";
@@ -17,3 +19,4 @@ export type { EventQueue, Handled, Subscribes, } from "./domain/queue.js";
17
19
  export type { DeliveryFailure, EventStore, StoredEvent, StoredState, } from "./domain/store.js";
18
20
  export { Publisher } from "./entrypoint/publisher.js";
19
21
  export { type HearingContext, Subscriber, } from "./entrypoint/subscriber.js";
22
+ export { EventSubscriber, type Subscribing, type SubscriptionOptions, Subscriptions, } from "./entrypoint/subscriptions.js";
@@ -7,7 +7,9 @@
7
7
  * domain/ the envelope and the ports: EventQueue, Subscribes, EventStore
8
8
  * adapters/ syncQueue (in the call), storedQueue (store, then deliver, retry, set aside),
9
9
  * memoryEventStore
10
- * entrypoint/ Publisher (what a Feature holds), Subscriber (every bounded context that hears)
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)
11
13
  */
12
14
  export { memoryEventStore, } from "./adapters/memory-event-store.js";
13
15
  export { storedQueue, } from "./adapters/stored-queue.js";
@@ -15,3 +17,4 @@ export { syncQueue } from "./adapters/sync-queue.js";
15
17
  export { envelopeOf, } from "./domain/envelope.js";
16
18
  export { Publisher } from "./entrypoint/publisher.js";
17
19
  export { Subscriber, } from "./entrypoint/subscriber.js";
20
+ export { EventSubscriber, Subscriptions, } from "./entrypoint/subscriptions.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;
@@ -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 {};
@@ -51,11 +51,11 @@ function layerLogger(layer, sink, correlation = {}) {
51
51
  export const logger = layerLogger("infrastructure", processSink);
52
52
  /** The adapters' logger: what talks to a printer, a database, another system. */
53
53
  export const loggerAdapter = layerLogger("adapter", processSink);
54
- /** @internal — a bounded context's own logger (`project.logger`). */
54
+ /** @internal — a bounded context's own logger (`projectBoundedContext.logger`). */
55
55
  export function boundedContextLogger(name) {
56
56
  return layerLogger("bounded_context", processSink, { boundedContext: name });
57
57
  }
58
- /** @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. */
59
59
  export function executionLogger(layer, execution) {
60
60
  return layerLogger(layer, processSink, correlationOf(execution));
61
61
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sincpro/client-framework",
3
- "version": "1.1.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",