@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
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { failureOf } from "../../common/failures.js";
|
|
2
|
+
import { readValues, rebuilt, valuesOf, } from "../../common/serialization.js";
|
|
3
|
+
import { loggerAdapter } from "../../observability/services/logging.js";
|
|
4
|
+
function rebuildEvent(type, payload) {
|
|
5
|
+
const values = readValues(payload);
|
|
6
|
+
if (typeof type.fromJSON === "function")
|
|
7
|
+
return rebuilt(values, type);
|
|
8
|
+
return Object.assign(Object.create(type.prototype), values);
|
|
9
|
+
}
|
|
10
|
+
function described(boundedContext, error) {
|
|
11
|
+
const failure = failureOf(error);
|
|
12
|
+
return {
|
|
13
|
+
boundedContext,
|
|
14
|
+
kind: failure.kind,
|
|
15
|
+
reason: failure.reason,
|
|
16
|
+
message: error instanceof Error ? error.message : String(error),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The queue that stores an event before delivering it, and delivers it until every bounded
|
|
21
|
+
* context that hears it has it — the backend's store-and-forward (`Forwarding`), over a store
|
|
22
|
+
* each platform plugs.
|
|
23
|
+
*
|
|
24
|
+
* const queue = storedQueue({ store: sqliteEventStore(db), subscriber, ready: online });
|
|
25
|
+
* shop.addDependency("publisher", new Publisher(queue));
|
|
26
|
+
* connectivity.onBack(() => queue.drain());
|
|
27
|
+
*
|
|
28
|
+
* Context: what a phone did by hand in its event bus, the same on every platform. `publish`
|
|
29
|
+
* returns once the event is stored — nobody waits for the delivery. Then, one at a time and in
|
|
30
|
+
* the order stored:
|
|
31
|
+
*
|
|
32
|
+
* 1. An event not ready (`ready`) waits while the rest go on.
|
|
33
|
+
* 2. Nobody hears its name: delivered — a fact recorded, nobody's to act on.
|
|
34
|
+
* 3. Rebuilt as its class; one that cannot be is set aside.
|
|
35
|
+
* 4. Executed in every bounded context that hears it and does not have it yet (`reached`).
|
|
36
|
+
* 5. A failure every attempt would repeat (`PERMANENT_KINDS`: a refusal, invalid, not found) sets
|
|
37
|
+
* that context aside; any other is tried again after the backoff — or what the failure asks
|
|
38
|
+
* for (`retryAfter`, an open circuit's time left) —, and what comes after waits, so the
|
|
39
|
+
* order holds; after `maxAttempts`, it is set aside too.
|
|
40
|
+
* Final: delivered when every context has it, set aside — the dead-letter queue — when one
|
|
41
|
+
* refused it for good or ran out of attempts; `onSettled` hears either.
|
|
42
|
+
*
|
|
43
|
+
* Delivery is at least once: a process that stops after a context executed an event and before
|
|
44
|
+
* the store saved it executes it there again on the next drain. A subscriber that must not act
|
|
45
|
+
* twice keys what it does by the envelope's id — `this.execution.causationId` in its run. And one
|
|
46
|
+
* process drains a store: two tabs over one IndexedDB claim it first (the Web Locks API).
|
|
47
|
+
*/
|
|
48
|
+
export function storedQueue(options) {
|
|
49
|
+
const { store, subscriber } = options;
|
|
50
|
+
const firstWait = options.wait ?? 1000;
|
|
51
|
+
const longestWait = options.longestWait ?? 60_000;
|
|
52
|
+
const maxAttempts = options.maxAttempts ?? 10;
|
|
53
|
+
if (!(maxAttempts >= 1))
|
|
54
|
+
throw new TypeError("A stored queue needs one attempt at least");
|
|
55
|
+
const rebuild = options.rebuild ?? rebuildEvent;
|
|
56
|
+
let draining;
|
|
57
|
+
let again = false;
|
|
58
|
+
let timer;
|
|
59
|
+
const settle = async (stored) => {
|
|
60
|
+
await store.save(stored);
|
|
61
|
+
try {
|
|
62
|
+
options.onSettled?.(stored);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// Telling a screen never changes a delivery.
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
const wakeAt = (at) => {
|
|
69
|
+
if (timer !== undefined)
|
|
70
|
+
clearTimeout(timer);
|
|
71
|
+
timer = setTimeout(() => {
|
|
72
|
+
timer = undefined;
|
|
73
|
+
void queue.drain();
|
|
74
|
+
}, Math.max(0, at - Date.now()));
|
|
75
|
+
// A timer of its own must not keep a process alive that has nothing else to do.
|
|
76
|
+
timer.unref?.();
|
|
77
|
+
};
|
|
78
|
+
/** One pass over what waits; it stops at an event that must wait its turn. */
|
|
79
|
+
const pass = async () => {
|
|
80
|
+
const skip = new Set();
|
|
81
|
+
for (;;) {
|
|
82
|
+
const head = await store.next(skip);
|
|
83
|
+
if (head === undefined)
|
|
84
|
+
return;
|
|
85
|
+
const id = head.envelope.id;
|
|
86
|
+
if (head.retryAt !== null && Date.parse(head.retryAt) > Date.now()) {
|
|
87
|
+
wakeAt(Date.parse(head.retryAt));
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (options.ready && !(await options.ready(head))) {
|
|
91
|
+
skip.add(id);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const attempts = head.attempts + 1;
|
|
95
|
+
const type = subscriber.eventTypeOf(head.envelope.name);
|
|
96
|
+
if (type === undefined) {
|
|
97
|
+
await settle({ ...head, state: "delivered", attempts, retryAt: null, failures: [] });
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
let event;
|
|
101
|
+
try {
|
|
102
|
+
event = rebuild(type, head.payload);
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
await settle({
|
|
106
|
+
...head,
|
|
107
|
+
state: "set_aside",
|
|
108
|
+
attempts,
|
|
109
|
+
retryAt: null,
|
|
110
|
+
failures: [described("", error)],
|
|
111
|
+
});
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const handled = await subscriber.handle(event, head.envelope, head.reached);
|
|
115
|
+
const reached = [...head.reached];
|
|
116
|
+
const failures = [];
|
|
117
|
+
let retryAfter = 0;
|
|
118
|
+
let repeat = false;
|
|
119
|
+
for (const one of handled) {
|
|
120
|
+
if (!("error" in one)) {
|
|
121
|
+
reached.push(one.boundedContext);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
failures.push(described(one.boundedContext, one.error));
|
|
125
|
+
const failure = failureOf(one.error);
|
|
126
|
+
if (failure.permanent)
|
|
127
|
+
reached.push(one.boundedContext);
|
|
128
|
+
else {
|
|
129
|
+
repeat = true;
|
|
130
|
+
retryAfter = Math.max(retryAfter, (failure.retryAfter ?? 0) * 1000);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (repeat && attempts < maxAttempts) {
|
|
134
|
+
const backoff = Math.min(longestWait, firstWait * 2 ** (attempts - 1));
|
|
135
|
+
const at = Date.now() + Math.max(backoff, retryAfter);
|
|
136
|
+
await settle({
|
|
137
|
+
...head,
|
|
138
|
+
state: "pending",
|
|
139
|
+
attempts,
|
|
140
|
+
retryAt: new Date(at).toISOString(),
|
|
141
|
+
reached,
|
|
142
|
+
failures,
|
|
143
|
+
});
|
|
144
|
+
wakeAt(at);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
await settle({
|
|
148
|
+
...head,
|
|
149
|
+
state: failures.length === 0 ? "delivered" : "set_aside",
|
|
150
|
+
attempts,
|
|
151
|
+
retryAt: null,
|
|
152
|
+
reached,
|
|
153
|
+
failures,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
const queue = {
|
|
158
|
+
async put(event, envelope) {
|
|
159
|
+
await store.add({
|
|
160
|
+
envelope,
|
|
161
|
+
payload: valuesOf(event),
|
|
162
|
+
state: "pending",
|
|
163
|
+
attempts: 0,
|
|
164
|
+
retryAt: null,
|
|
165
|
+
reached: [],
|
|
166
|
+
failures: [],
|
|
167
|
+
});
|
|
168
|
+
queue.drain().catch((error) => loggerAdapter.error("The stored queue could not deliver", {
|
|
169
|
+
reason: failureOf(error).reason,
|
|
170
|
+
}));
|
|
171
|
+
},
|
|
172
|
+
drain() {
|
|
173
|
+
if (draining !== undefined) {
|
|
174
|
+
again = true;
|
|
175
|
+
return draining;
|
|
176
|
+
}
|
|
177
|
+
draining = (async () => {
|
|
178
|
+
try {
|
|
179
|
+
do {
|
|
180
|
+
again = false;
|
|
181
|
+
await pass();
|
|
182
|
+
} while (again);
|
|
183
|
+
}
|
|
184
|
+
finally {
|
|
185
|
+
draining = undefined;
|
|
186
|
+
}
|
|
187
|
+
})();
|
|
188
|
+
return draining;
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
return queue;
|
|
192
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { EventQueue, Subscribes } from "../domain/queue.js";
|
|
2
|
+
/**
|
|
3
|
+
* The queue that delivers in the call, in memory — the backend's `SyncQueue`: `publish` returns
|
|
4
|
+
* once every bounded context that hears the event executed it. What a web application starts
|
|
5
|
+
* with, and what a test proves a flow with.
|
|
6
|
+
*
|
|
7
|
+
* Context: nothing is stored, so nothing outlives the process — a phone's queue is durable and
|
|
8
|
+
* the platform's. Every subscriber runs; then the first failure, if any, reaches the publisher,
|
|
9
|
+
* as an in-process call would.
|
|
10
|
+
*/
|
|
11
|
+
export declare function syncQueue(subscriber: Subscribes): EventQueue;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The queue that delivers in the call, in memory — the backend's `SyncQueue`: `publish` returns
|
|
3
|
+
* once every bounded context that hears the event executed it. What a web application starts
|
|
4
|
+
* with, and what a test proves a flow with.
|
|
5
|
+
*
|
|
6
|
+
* Context: nothing is stored, so nothing outlives the process — a phone's queue is durable and
|
|
7
|
+
* the platform's. Every subscriber runs; then the first failure, if any, reaches the publisher,
|
|
8
|
+
* as an in-process call would.
|
|
9
|
+
*/
|
|
10
|
+
export function syncQueue(subscriber) {
|
|
11
|
+
return {
|
|
12
|
+
async put(event, envelope) {
|
|
13
|
+
const handled = await subscriber.handle(event, envelope);
|
|
14
|
+
const failed = handled.find((one) => "error" in one);
|
|
15
|
+
if (failed && "error" in failed)
|
|
16
|
+
throw failed.error;
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -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
|
@@ -3,45 +3,28 @@
|
|
|
3
3
|
* service needs, on any platform.
|
|
4
4
|
*
|
|
5
5
|
* Context: one entry, not one per area — the package has no side effects at import
|
|
6
|
-
* (`sideEffects: false`), so an application pays only for what it uses
|
|
7
|
-
*
|
|
6
|
+
* (`sideEffects: false`), so an application pays only for what it uses. Each module is laid out
|
|
7
|
+
* as sincpro_framework's are — `domain/` (ports and rules), `adapters/` (their implementations),
|
|
8
|
+
* `services/`, `infrastructure/`, `entrypoint/` — and says what it offers in its own `index.ts`:
|
|
8
9
|
*
|
|
9
|
-
* common/
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* model/
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* observability/ the ports a platform plugs: a tracer and an error reporter
|
|
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
|
|
12
|
+
* context/ the context's levels, where they live, what travels, the execution
|
|
13
|
+
* observability/ tracing, error reporting and logging: ports, console, loggers
|
|
14
|
+
* client/ the seam with a server: transports, entities, the client contract
|
|
15
|
+
* model/ model definitions
|
|
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
|
|
19
19
|
*
|
|
20
|
-
* The generator is `@sincpro/client-framework/codegen`, and the in-memory
|
|
20
|
+
* The generator is `@sincpro/client-framework/codegen`, and the in-memory observability
|
|
21
21
|
* `@sincpro/client-framework/testing`: neither is part of what an application runs.
|
|
22
22
|
*/
|
|
23
|
-
export
|
|
24
|
-
export
|
|
25
|
-
export
|
|
26
|
-
export
|
|
27
|
-
export
|
|
28
|
-
export
|
|
29
|
-
export
|
|
30
|
-
export
|
|
31
|
-
export { isEntityId, momentOf, newEntityId } from "./common/ids.js";
|
|
32
|
-
export { Money } from "./common/money.js";
|
|
33
|
-
export { BYTES, type JsonValue, pack, readValues, type Rebuild, rebuilt, unpack, valuesOf, } from "./common/serialization.js";
|
|
34
|
-
export { dayOf, installTimeZone, instantOf, isDay, timeZoneNow, wallTimeOf, } from "./common/time.js";
|
|
35
|
-
export { CONTEXT_HEADER, type ContextCodec, plainCodec, travelling, travels, } from "./context/codec.js";
|
|
36
|
-
export { type Context } from "./context/context.js";
|
|
37
|
-
export { type ContextStore, type ContextValues, memoryContexts, } from "./context/store.js";
|
|
38
|
-
export { defineMeta, field, type FieldOptions, type MetaSpec, type Words, } from "./model/meta.js";
|
|
39
|
-
export { type MetaStore, metaStore } from "./model/metas.js";
|
|
40
|
-
export { completeMeta, type JsonSchema, metaFromSchema, type SchemaComponents, } from "./model/schema.js";
|
|
41
|
-
export { type Attributes, type ErrorAbout, type ErrorReporter, NO_TRACER, type Outcome, type Span, type Tracer, } from "./observability/ports.js";
|
|
42
|
-
export { type BoundedContext, UseBoundedContext, } from "./services/bounded-context.js";
|
|
43
|
-
export { BIND, type Bindable, bindable, bindDependency, bindTransport, type Execution, headersOf, } from "./services/execution.js";
|
|
44
|
-
export { ApplicationService, Feature, type ServiceClasses, type ServiceMethods, type Services, } from "./services/feature.js";
|
|
45
|
-
export { configureProcess, outcomeOf, processContext, type ProcessOptions, } from "./services/process.js";
|
|
46
|
-
export { reportError } from "./services/report.js";
|
|
47
|
-
export { screensTransport } from "./services/screens.js";
|
|
23
|
+
export * from "./circuit_breaker/index.js";
|
|
24
|
+
export * from "./client/index.js";
|
|
25
|
+
export * from "./common/index.js";
|
|
26
|
+
export * from "./context/index.js";
|
|
27
|
+
export * from "./core/index.js";
|
|
28
|
+
export * from "./event_driven/index.js";
|
|
29
|
+
export * from "./model/index.js";
|
|
30
|
+
export * from "./observability/index.js";
|