@fabricorg/platform 1.6.0 → 1.8.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/CHANGELOG.md +79 -0
- package/README.md +216 -3
- package/dist/actions/index.cjs +6 -6
- package/dist/actions/index.d.cts +85 -3
- package/dist/actions/index.d.ts +85 -3
- package/dist/actions/index.js +1 -1
- package/dist/adapters/index.cjs +33 -9
- package/dist/adapters/index.d.cts +388 -1
- package/dist/adapters/index.d.ts +388 -1
- package/dist/adapters/index.js +1 -1
- package/dist/certification/index.cjs +6 -6
- package/dist/certification/index.js +3 -3
- package/dist/{chunk-VZ6C4LXC.cjs → chunk-45OKKLNY.cjs} +17 -9
- package/dist/chunk-45OKKLNY.cjs.map +1 -0
- package/dist/chunk-5FOCPRTR.js +511 -0
- package/dist/chunk-5FOCPRTR.js.map +1 -0
- package/dist/{chunk-USDLP4E7.cjs → chunk-5IVFPWK6.cjs} +4 -4
- package/dist/{chunk-USDLP4E7.cjs.map → chunk-5IVFPWK6.cjs.map} +1 -1
- package/dist/chunk-6HY35TKB.cjs +491 -0
- package/dist/chunk-6HY35TKB.cjs.map +1 -0
- package/dist/chunk-6UGEIDLC.cjs +26 -0
- package/dist/chunk-6UGEIDLC.cjs.map +1 -0
- package/dist/chunk-BJGZE3GE.js +20 -0
- package/dist/chunk-BJGZE3GE.js.map +1 -0
- package/dist/{chunk-X3GHYMAJ.cjs → chunk-CRZXTZ36.cjs} +2 -22
- package/dist/chunk-CRZXTZ36.cjs.map +1 -0
- package/dist/{chunk-TMMP4U2D.cjs → chunk-DBTAUI6C.cjs} +5 -2
- package/dist/chunk-DBTAUI6C.cjs.map +1 -0
- package/dist/{chunk-UYCQV65U.js → chunk-EMSAFNPX.js} +3 -3
- package/dist/{chunk-UYCQV65U.js.map → chunk-EMSAFNPX.js.map} +1 -1
- package/dist/{chunk-DCC3UIUJ.js → chunk-GFU7BGEJ.js} +3 -3
- package/dist/{chunk-DCC3UIUJ.js.map → chunk-GFU7BGEJ.js.map} +1 -1
- package/dist/{chunk-JFVEPAED.js → chunk-GU3LZWJD.js} +17 -9
- package/dist/chunk-GU3LZWJD.js.map +1 -0
- package/dist/chunk-HEYMMVDP.js +480 -0
- package/dist/chunk-HEYMMVDP.js.map +1 -0
- package/dist/chunk-IF6NJORI.cjs +526 -0
- package/dist/chunk-IF6NJORI.cjs.map +1 -0
- package/dist/{chunk-XS7EUS4T.cjs → chunk-MCWSUGB3.cjs} +4 -4
- package/dist/{chunk-XS7EUS4T.cjs.map → chunk-MCWSUGB3.cjs.map} +1 -1
- package/dist/{chunk-QHV6LOS6.js → chunk-PDN5DOLQ.js} +3 -3
- package/dist/{chunk-QHV6LOS6.js.map → chunk-PDN5DOLQ.js.map} +1 -1
- package/dist/{chunk-OZWUNGJE.js → chunk-PIDZQAEL.js} +5 -2
- package/dist/chunk-PIDZQAEL.js.map +1 -0
- package/dist/{chunk-ATWWZTRC.js → chunk-TYNIFC7J.js} +3 -18
- package/dist/chunk-TYNIFC7J.js.map +1 -0
- package/dist/{chunk-DDX5FKOM.cjs → chunk-UGWGHTNC.cjs} +5 -2
- package/dist/chunk-UGWGHTNC.cjs.map +1 -0
- package/dist/{chunk-ZF5HDNSU.js → chunk-WNZP53GI.js} +5 -2
- package/dist/chunk-WNZP53GI.js.map +1 -0
- package/dist/{chunk-2URHPZXL.cjs → chunk-WQ3BYI7T.cjs} +6 -6
- package/dist/{chunk-2URHPZXL.cjs.map → chunk-WQ3BYI7T.cjs.map} +1 -1
- package/dist/evidence/index.cjs +45 -1
- package/dist/evidence/index.d.cts +187 -1
- package/dist/evidence/index.d.ts +187 -1
- package/dist/evidence/index.js +3 -1
- package/dist/governance/index.d.cts +1 -0
- package/dist/governance/index.d.ts +1 -0
- package/dist/index.cjs +153 -88
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +11 -10
- package/dist/manifest/index.cjs +8 -8
- package/dist/manifest/index.js +2 -2
- package/dist/modules/index.cjs +9 -9
- package/dist/modules/index.js +1 -1
- package/dist/policies/index.cjs +9 -9
- package/dist/policies/index.d.cts +14 -1
- package/dist/policies/index.d.ts +14 -1
- package/dist/policies/index.js +1 -1
- package/dist/privacy/index.cjs +19 -18
- package/dist/privacy/index.js +2 -1
- package/dist/testing/index.cjs +5 -5
- package/dist/testing/index.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-ATWWZTRC.js.map +0 -1
- package/dist/chunk-DDX5FKOM.cjs.map +0 -1
- package/dist/chunk-FAG2RTPM.js +0 -120
- package/dist/chunk-FAG2RTPM.js.map +0 -1
- package/dist/chunk-JFVEPAED.js.map +0 -1
- package/dist/chunk-OZLZHISS.js +0 -3
- package/dist/chunk-OZLZHISS.js.map +0 -1
- package/dist/chunk-OZWUNGJE.js.map +0 -1
- package/dist/chunk-PG5LYTUX.cjs +0 -4
- package/dist/chunk-PG5LYTUX.cjs.map +0 -1
- package/dist/chunk-TMMP4U2D.cjs.map +0 -1
- package/dist/chunk-VZ6C4LXC.cjs.map +0 -1
- package/dist/chunk-X3GHYMAJ.cjs.map +0 -1
- package/dist/chunk-XZQZQ5SW.cjs +0 -129
- package/dist/chunk-XZQZQ5SW.cjs.map +0 -1
- package/dist/chunk-ZF5HDNSU.js.map +0 -1
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
// adapters/circuit-breaker.ts
|
|
2
|
+
function classifyCircuitBreakerOutcome(kind) {
|
|
3
|
+
switch (kind) {
|
|
4
|
+
case "success":
|
|
5
|
+
return "success";
|
|
6
|
+
case "permanent_failure":
|
|
7
|
+
return "ignored";
|
|
8
|
+
case "transient_failure":
|
|
9
|
+
case "timeout":
|
|
10
|
+
case "ambiguous":
|
|
11
|
+
return "failure";
|
|
12
|
+
default:
|
|
13
|
+
return "failure";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
var MANUAL_OVERRIDES = ["force_open", "force_closed"];
|
|
17
|
+
function assertAdapterCircuitBreakerConfig(config) {
|
|
18
|
+
if (!config || typeof config !== "object") {
|
|
19
|
+
throw new RangeError("Adapter circuit breaker config must be an object.");
|
|
20
|
+
}
|
|
21
|
+
if (config.mode !== "monitor" && config.mode !== "enforce") {
|
|
22
|
+
throw new RangeError(`Adapter circuit breaker mode must be "monitor" or "enforce"; got ${JSON.stringify(config.mode)}.`);
|
|
23
|
+
}
|
|
24
|
+
if (!Number.isInteger(config.failureThreshold) || config.failureThreshold < 1) {
|
|
25
|
+
throw new RangeError(`Adapter circuit breaker failureThreshold must be a positive integer; got ${String(config.failureThreshold)}.`);
|
|
26
|
+
}
|
|
27
|
+
if (typeof config.windowMs !== "number" || !Number.isFinite(config.windowMs) || config.windowMs <= 0) {
|
|
28
|
+
throw new RangeError(`Adapter circuit breaker windowMs must be a positive number of milliseconds; got ${String(config.windowMs)}.`);
|
|
29
|
+
}
|
|
30
|
+
if (typeof config.cooldownMs !== "number" || !Number.isFinite(config.cooldownMs) || config.cooldownMs < 0) {
|
|
31
|
+
throw new RangeError(`Adapter circuit breaker cooldownMs must be a non-negative number of milliseconds; got ${String(config.cooldownMs)}.`);
|
|
32
|
+
}
|
|
33
|
+
if (config.minimumSamples !== void 0 && (!Number.isInteger(config.minimumSamples) || config.minimumSamples < 1)) {
|
|
34
|
+
throw new RangeError(`Adapter circuit breaker minimumSamples must be a positive integer; got ${String(config.minimumSamples)}.`);
|
|
35
|
+
}
|
|
36
|
+
if (config.probeLeaseMs !== void 0 && (typeof config.probeLeaseMs !== "number" || !Number.isFinite(config.probeLeaseMs) || config.probeLeaseMs <= 0)) {
|
|
37
|
+
throw new RangeError(`Adapter circuit breaker probeLeaseMs must be a positive number of milliseconds; got ${String(config.probeLeaseMs)}.`);
|
|
38
|
+
}
|
|
39
|
+
if (config.manualOverride !== void 0 && !MANUAL_OVERRIDES.includes(config.manualOverride)) {
|
|
40
|
+
throw new RangeError(`Adapter circuit breaker manualOverride must be "force_open" or "force_closed"; got ${JSON.stringify(config.manualOverride)}.`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
var InMemoryAdapterCircuitBreaker = class {
|
|
44
|
+
key;
|
|
45
|
+
mode;
|
|
46
|
+
config;
|
|
47
|
+
now;
|
|
48
|
+
onEvent;
|
|
49
|
+
probeLeaseMs;
|
|
50
|
+
samples = [];
|
|
51
|
+
state = "closed";
|
|
52
|
+
openedAt = 0;
|
|
53
|
+
probeIssuedAt;
|
|
54
|
+
/** Advances on every state change, manual override change and probe grant. */
|
|
55
|
+
generation = 0;
|
|
56
|
+
manualOverride;
|
|
57
|
+
/** Telemetry waiting for the current operation to commit. */
|
|
58
|
+
outbox = [];
|
|
59
|
+
operationDepth = 0;
|
|
60
|
+
flushing = false;
|
|
61
|
+
constructor(options) {
|
|
62
|
+
assertAdapterCircuitBreakerConfig(options.config);
|
|
63
|
+
this.key = options.key;
|
|
64
|
+
this.config = { ...options.config };
|
|
65
|
+
this.mode = options.config.mode;
|
|
66
|
+
this.now = options.now ?? Date.now;
|
|
67
|
+
this.onEvent = options.onEvent;
|
|
68
|
+
this.manualOverride = options.config.manualOverride;
|
|
69
|
+
this.probeLeaseMs = options.config.probeLeaseMs ?? Math.max(options.config.cooldownMs, options.config.windowMs);
|
|
70
|
+
}
|
|
71
|
+
getState() {
|
|
72
|
+
this.settle(this.now());
|
|
73
|
+
return this.reportedState();
|
|
74
|
+
}
|
|
75
|
+
getManualOverride() {
|
|
76
|
+
return this.manualOverride;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Set (`force_open` / `force_closed`) or clear (`undefined`) the manual
|
|
80
|
+
* override. Clearing returns the breaker to `closed` with its window intact.
|
|
81
|
+
*/
|
|
82
|
+
setManualOverride(override) {
|
|
83
|
+
if (override !== void 0 && !MANUAL_OVERRIDES.includes(override)) {
|
|
84
|
+
throw new RangeError(`Adapter circuit breaker manualOverride must be "force_open" or "force_closed"; got ${JSON.stringify(override)}.`);
|
|
85
|
+
}
|
|
86
|
+
const now = this.now();
|
|
87
|
+
this.settle(now);
|
|
88
|
+
if (override === this.manualOverride) return;
|
|
89
|
+
this.operation(() => {
|
|
90
|
+
const from = this.reportedState();
|
|
91
|
+
this.manualOverride = override;
|
|
92
|
+
this.generation++;
|
|
93
|
+
if (override === void 0) {
|
|
94
|
+
this.state = "closed";
|
|
95
|
+
this.releaseProbe();
|
|
96
|
+
}
|
|
97
|
+
this.emit({
|
|
98
|
+
type: "state_change",
|
|
99
|
+
key: this.key,
|
|
100
|
+
mode: this.mode,
|
|
101
|
+
at: now,
|
|
102
|
+
from,
|
|
103
|
+
to: this.reportedState(),
|
|
104
|
+
reason: override === void 0 ? "manual_override_cleared" : "manual_override",
|
|
105
|
+
...override ? { manualOverride: override } : {}
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
check() {
|
|
110
|
+
return this.decide(false, this.now());
|
|
111
|
+
}
|
|
112
|
+
acquire() {
|
|
113
|
+
const now = this.now();
|
|
114
|
+
this.settle(now);
|
|
115
|
+
return this.operation(() => this.decide(true, now));
|
|
116
|
+
}
|
|
117
|
+
record(outcome, admission) {
|
|
118
|
+
const now = this.now();
|
|
119
|
+
this.settle(now);
|
|
120
|
+
this.operation(() => this.applyOutcome(outcome, admission, now));
|
|
121
|
+
}
|
|
122
|
+
applyOutcome(outcome, admission, now) {
|
|
123
|
+
const generation = admission?.generation;
|
|
124
|
+
if (generation !== void 0 && generation !== this.generation) return;
|
|
125
|
+
const resolvesProbe = this.state === "half_open" && this.probeIssuedAt !== void 0 && (generation === void 0 || admission?.probe === true);
|
|
126
|
+
if (outcome === "ignored") {
|
|
127
|
+
if (resolvesProbe) this.releaseProbe();
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
this.prune(now);
|
|
131
|
+
this.samples.push({ at: now, failure: outcome === "failure" });
|
|
132
|
+
if (this.manualOverride) return;
|
|
133
|
+
if (resolvesProbe) {
|
|
134
|
+
this.releaseProbe();
|
|
135
|
+
if (outcome === "success") {
|
|
136
|
+
this.samples = [];
|
|
137
|
+
this.transition("closed", "probe_succeeded", now);
|
|
138
|
+
} else {
|
|
139
|
+
this.openedAt = now;
|
|
140
|
+
this.transition("open", "probe_failed", now);
|
|
141
|
+
}
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (this.state === "closed" && outcome === "failure" && this.shouldTrip()) {
|
|
145
|
+
this.openedAt = now;
|
|
146
|
+
this.transition("open", "failure_threshold", now);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* `take` is false for {@link check}: a pure preview that sees an elapsed
|
|
151
|
+
* cooldown without committing it, takes no probe and emits nothing.
|
|
152
|
+
*/
|
|
153
|
+
decide(take, now) {
|
|
154
|
+
if (this.manualOverride === "force_closed") {
|
|
155
|
+
return { allowed: true, state: "closed", probe: false, manualOverride: "force_closed", generation: this.generation };
|
|
156
|
+
}
|
|
157
|
+
if (this.manualOverride === "force_open") {
|
|
158
|
+
return this.refuse(take, { state: "open", manualOverride: "force_open" });
|
|
159
|
+
}
|
|
160
|
+
const cooldownElapsed = this.state === "open" && now - this.openedAt >= this.config.cooldownMs;
|
|
161
|
+
if (take) this.advance(now);
|
|
162
|
+
const state = !take && cooldownElapsed ? "half_open" : this.state;
|
|
163
|
+
if (state === "closed") return { allowed: true, state: "closed", probe: false, generation: this.generation };
|
|
164
|
+
if (state === "open") {
|
|
165
|
+
return this.refuse(take, {
|
|
166
|
+
state: "open",
|
|
167
|
+
retryAfterMs: Math.max(0, this.openedAt + this.config.cooldownMs - now)
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
const probeFree = !take && cooldownElapsed || this.probeIssuedAt === void 0 || now - this.probeIssuedAt >= this.probeLeaseMs;
|
|
171
|
+
if (!probeFree) return this.refuse(take, { state: "half_open" });
|
|
172
|
+
if (!take) return { allowed: true, state: "half_open", probe: true, generation: this.generation };
|
|
173
|
+
this.probeIssuedAt = now;
|
|
174
|
+
this.generation++;
|
|
175
|
+
return { allowed: true, state: "half_open", probe: true, generation: this.generation };
|
|
176
|
+
}
|
|
177
|
+
refuse(take, detail) {
|
|
178
|
+
const enforce = this.mode === "enforce";
|
|
179
|
+
if (take) {
|
|
180
|
+
this.emit({
|
|
181
|
+
type: enforce ? "rejected" : "would_reject",
|
|
182
|
+
key: this.key,
|
|
183
|
+
mode: this.mode,
|
|
184
|
+
at: this.now(),
|
|
185
|
+
...detail
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
const generation = this.generation;
|
|
189
|
+
return enforce ? { allowed: false, probe: false, ...detail, generation } : { allowed: true, probe: false, wouldReject: true, ...detail, generation };
|
|
190
|
+
}
|
|
191
|
+
releaseProbe() {
|
|
192
|
+
this.probeIssuedAt = void 0;
|
|
193
|
+
}
|
|
194
|
+
/** Apply the time-based open → half_open transition. */
|
|
195
|
+
advance(now) {
|
|
196
|
+
if (this.state === "open" && now - this.openedAt >= this.config.cooldownMs) {
|
|
197
|
+
this.releaseProbe();
|
|
198
|
+
this.transition("half_open", "cooldown_elapsed", now);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
shouldTrip() {
|
|
202
|
+
const minimumSamples = this.config.minimumSamples ?? this.config.failureThreshold;
|
|
203
|
+
const total = this.samples.length;
|
|
204
|
+
if (total < minimumSamples) return false;
|
|
205
|
+
let consecutive = 0;
|
|
206
|
+
for (let index = total - 1; index >= 0 && this.samples[index].failure; index--) consecutive++;
|
|
207
|
+
if (consecutive >= this.config.failureThreshold) return true;
|
|
208
|
+
const failures = this.samples.reduce((count, sample) => count + (sample.failure ? 1 : 0), 0);
|
|
209
|
+
return failures / total >= this.config.failureThreshold / minimumSamples;
|
|
210
|
+
}
|
|
211
|
+
prune(now) {
|
|
212
|
+
const cutoff = now - this.config.windowMs;
|
|
213
|
+
let drop = 0;
|
|
214
|
+
while (drop < this.samples.length && this.samples[drop].at < cutoff) drop++;
|
|
215
|
+
if (drop > 0) this.samples.splice(0, drop);
|
|
216
|
+
}
|
|
217
|
+
transition(to, reason, at) {
|
|
218
|
+
const from = this.state;
|
|
219
|
+
if (from === to) return;
|
|
220
|
+
this.state = to;
|
|
221
|
+
this.generation++;
|
|
222
|
+
this.emit({ type: "state_change", key: this.key, mode: this.mode, at, from, to, reason });
|
|
223
|
+
}
|
|
224
|
+
reportedState() {
|
|
225
|
+
if (this.manualOverride) return this.manualOverride === "force_open" ? "open" : "closed";
|
|
226
|
+
return this.state;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Commit an elapsed cooldown as an operation of its own, delivering its
|
|
230
|
+
* event before the caller's operation reads state.
|
|
231
|
+
*/
|
|
232
|
+
settle(now) {
|
|
233
|
+
if (this.manualOverride) return;
|
|
234
|
+
this.operation(() => this.advance(now));
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Run `step` to completion, then deliver the telemetry it produced. The
|
|
238
|
+
* return value is fixed before any listener runs.
|
|
239
|
+
*/
|
|
240
|
+
operation(step) {
|
|
241
|
+
this.operationDepth++;
|
|
242
|
+
try {
|
|
243
|
+
return step();
|
|
244
|
+
} finally {
|
|
245
|
+
this.operationDepth--;
|
|
246
|
+
if (this.operationDepth === 0) this.flush();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
emit(event) {
|
|
250
|
+
if (this.onEvent) this.outbox.push(event);
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Deliver buffered events in order. A listener that calls back into the
|
|
254
|
+
* breaker queues its own events; this loop delivers them after it returns
|
|
255
|
+
* instead of recursing.
|
|
256
|
+
*/
|
|
257
|
+
flush() {
|
|
258
|
+
if (this.flushing) return;
|
|
259
|
+
this.flushing = true;
|
|
260
|
+
try {
|
|
261
|
+
for (let event = this.outbox.shift(); event; event = this.outbox.shift()) this.deliver(event);
|
|
262
|
+
} finally {
|
|
263
|
+
this.flushing = false;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
deliver(event) {
|
|
267
|
+
const listener = this.onEvent;
|
|
268
|
+
if (!listener) return;
|
|
269
|
+
try {
|
|
270
|
+
const result = listener(event);
|
|
271
|
+
if (result && typeof result.then === "function") {
|
|
272
|
+
result.then(void 0, () => void 0);
|
|
273
|
+
}
|
|
274
|
+
} catch {
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
var AdapterCircuitBreakerRegistry = class {
|
|
279
|
+
constructor(options = {}) {
|
|
280
|
+
this.options = options;
|
|
281
|
+
}
|
|
282
|
+
options;
|
|
283
|
+
breakers = /* @__PURE__ */ new Map();
|
|
284
|
+
forAdapter(adapter) {
|
|
285
|
+
if (!adapter.circuitBreaker) return void 0;
|
|
286
|
+
const key = `${adapter.adapterType}:${adapter.operation}`;
|
|
287
|
+
let breaker = this.breakers.get(key);
|
|
288
|
+
if (!breaker) {
|
|
289
|
+
breaker = new InMemoryAdapterCircuitBreaker({
|
|
290
|
+
key,
|
|
291
|
+
config: adapter.circuitBreaker,
|
|
292
|
+
...this.options.now ? { now: this.options.now } : {},
|
|
293
|
+
...this.options.onEvent ? { onEvent: this.options.onEvent } : {}
|
|
294
|
+
});
|
|
295
|
+
this.breakers.set(key, breaker);
|
|
296
|
+
}
|
|
297
|
+
return breaker;
|
|
298
|
+
}
|
|
299
|
+
/** The breaker already created for `adapterType:operation`, if any. */
|
|
300
|
+
get(adapterType, operation) {
|
|
301
|
+
return this.breakers.get(`${adapterType}:${operation}`);
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
var AdapterCircuitOpenError = class extends Error {
|
|
305
|
+
name = "AdapterCircuitOpenError";
|
|
306
|
+
code = "ADAPTER_CIRCUIT_OPEN";
|
|
307
|
+
reason = "circuit_open";
|
|
308
|
+
adapterKey;
|
|
309
|
+
state;
|
|
310
|
+
/** The attempt number that was refused (1 = refused before any call). */
|
|
311
|
+
attempt;
|
|
312
|
+
retryAfterMs;
|
|
313
|
+
manualOverride;
|
|
314
|
+
/** Error of the previous attempt, when the breaker opened mid-retry. */
|
|
315
|
+
lastError;
|
|
316
|
+
constructor(input) {
|
|
317
|
+
const after = input.retryAfterMs !== void 0 ? `; retry after ${input.retryAfterMs} ms` : "";
|
|
318
|
+
super(`Adapter circuit is ${input.state === "half_open" ? "half-open with its probe in flight" : "open"} for ${input.adapterKey}; the call was not attempted${after}.`);
|
|
319
|
+
this.adapterKey = input.adapterKey;
|
|
320
|
+
this.state = input.state;
|
|
321
|
+
this.attempt = input.attempt;
|
|
322
|
+
if (input.retryAfterMs !== void 0) this.retryAfterMs = input.retryAfterMs;
|
|
323
|
+
if (input.manualOverride !== void 0) this.manualOverride = input.manualOverride;
|
|
324
|
+
if (input.lastError !== void 0) this.lastError = input.lastError;
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
function isAdapterCircuitOpenError(error) {
|
|
328
|
+
if (error instanceof AdapterCircuitOpenError) return true;
|
|
329
|
+
return typeof error === "object" && error !== null && error.name === "AdapterCircuitOpenError" && error.code === "ADAPTER_CIRCUIT_OPEN";
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// adapters/index.ts
|
|
333
|
+
function adapterKey(adapterType, operation) {
|
|
334
|
+
return `${adapterType}:${operation}`;
|
|
335
|
+
}
|
|
336
|
+
var MissingAdapterRegistrationError = class extends Error {
|
|
337
|
+
constructor(adapterType, operation) {
|
|
338
|
+
super(`No adapter registered for adapterType "${adapterType}" and operation "${operation}"`);
|
|
339
|
+
this.name = "MissingAdapterRegistrationError";
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
var AdapterRegistry = class {
|
|
343
|
+
adapters = /* @__PURE__ */ new Map();
|
|
344
|
+
register(adapter) {
|
|
345
|
+
const key = adapterKey(adapter.adapterType, adapter.operation);
|
|
346
|
+
if (this.adapters.has(key)) {
|
|
347
|
+
throw new Error(
|
|
348
|
+
`Adapter already registered for adapterType "${adapter.adapterType}" and operation "${adapter.operation}"`
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
this.adapters.set(key, adapter);
|
|
352
|
+
}
|
|
353
|
+
resolve(adapterType, operation) {
|
|
354
|
+
return this.adapters.get(adapterKey(adapterType, operation));
|
|
355
|
+
}
|
|
356
|
+
require(adapterType, operation) {
|
|
357
|
+
const adapter = this.resolve(adapterType, operation);
|
|
358
|
+
if (!adapter) {
|
|
359
|
+
throw new MissingAdapterRegistrationError(adapterType, operation);
|
|
360
|
+
}
|
|
361
|
+
return adapter;
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
var DEFAULT_RETRY_POLICY = {
|
|
365
|
+
maxAttempts: 3,
|
|
366
|
+
initialDelayMs: 100,
|
|
367
|
+
backoffMultiplier: 2,
|
|
368
|
+
maxDelayMs: 1e3
|
|
369
|
+
};
|
|
370
|
+
var NO_RETRY_POLICY = {
|
|
371
|
+
idempotent: false,
|
|
372
|
+
maxAttempts: 1,
|
|
373
|
+
initialDelayMs: 0,
|
|
374
|
+
backoffMultiplier: 1,
|
|
375
|
+
maxDelayMs: 0
|
|
376
|
+
};
|
|
377
|
+
function resolveAdapterRetryPolicy(policy, defaultIdempotent) {
|
|
378
|
+
const idempotent = policy?.idempotent ?? defaultIdempotent;
|
|
379
|
+
if (!idempotent) {
|
|
380
|
+
return NO_RETRY_POLICY;
|
|
381
|
+
}
|
|
382
|
+
return {
|
|
383
|
+
idempotent: true,
|
|
384
|
+
maxAttempts: Math.max(1, Math.min(policy?.maxAttempts ?? DEFAULT_RETRY_POLICY.maxAttempts, 5)),
|
|
385
|
+
initialDelayMs: Math.max(0, policy?.initialDelayMs ?? DEFAULT_RETRY_POLICY.initialDelayMs),
|
|
386
|
+
backoffMultiplier: Math.max(
|
|
387
|
+
1,
|
|
388
|
+
policy?.backoffMultiplier ?? DEFAULT_RETRY_POLICY.backoffMultiplier
|
|
389
|
+
),
|
|
390
|
+
maxDelayMs: Math.max(0, policy?.maxDelayMs ?? DEFAULT_RETRY_POLICY.maxDelayMs)
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
function getRetryDelayMs(attempt, policy) {
|
|
394
|
+
const exponentialDelay = policy.initialDelayMs * policy.backoffMultiplier ** Math.max(0, attempt - 1);
|
|
395
|
+
return Math.min(exponentialDelay, policy.maxDelayMs);
|
|
396
|
+
}
|
|
397
|
+
function classifyAdapterOutcome(result) {
|
|
398
|
+
if (result.outcome) {
|
|
399
|
+
assertAdapterOutcomeConsistency(result);
|
|
400
|
+
return result.outcome;
|
|
401
|
+
}
|
|
402
|
+
return result.success ? "success" : "transient_failure";
|
|
403
|
+
}
|
|
404
|
+
function shouldRetryAdapterOutcome(kind, idempotent) {
|
|
405
|
+
return kind === "transient_failure" && idempotent;
|
|
406
|
+
}
|
|
407
|
+
function assertAdapterOutcomeConsistency(result) {
|
|
408
|
+
if (result.success && result.outcome && result.outcome !== "success") {
|
|
409
|
+
throw new Error(
|
|
410
|
+
`Adapter returned success=true with contradictory outcome "${result.outcome}"; a successful result must not declare a non-success outcome.`
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
function recordCircuitOutcome(breaker, outcome, admission) {
|
|
415
|
+
if (!breaker) return;
|
|
416
|
+
try {
|
|
417
|
+
breaker.record(outcome, admission);
|
|
418
|
+
} catch {
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
function circuitOpenError(breaker, admission, attempt, lastError) {
|
|
422
|
+
return new AdapterCircuitOpenError({
|
|
423
|
+
adapterKey: breaker.key,
|
|
424
|
+
state: admission.state,
|
|
425
|
+
attempt,
|
|
426
|
+
...admission.retryAfterMs !== void 0 ? { retryAfterMs: admission.retryAfterMs } : {},
|
|
427
|
+
...admission.manualOverride !== void 0 ? { manualOverride: admission.manualOverride } : {},
|
|
428
|
+
...lastError !== void 0 ? { lastError } : {}
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
async function executeWithAdapterRetry(options) {
|
|
432
|
+
const policy = resolveAdapterRetryPolicy(options.policy, options.defaultIdempotent);
|
|
433
|
+
const sleep = options.sleep ?? ((delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs)));
|
|
434
|
+
const breaker = options.circuitBreaker;
|
|
435
|
+
let lastError;
|
|
436
|
+
for (let attempt = 1; attempt <= policy.maxAttempts; attempt++) {
|
|
437
|
+
let admission;
|
|
438
|
+
let executed = false;
|
|
439
|
+
let recorded = false;
|
|
440
|
+
const record = (outcome) => {
|
|
441
|
+
if (!executed || recorded) return;
|
|
442
|
+
recorded = true;
|
|
443
|
+
recordCircuitOutcome(breaker, outcome, admission);
|
|
444
|
+
};
|
|
445
|
+
let refusal;
|
|
446
|
+
async function handleThrown(error) {
|
|
447
|
+
lastError = error instanceof Error ? error.message : String(error);
|
|
448
|
+
const thrownOutcome = options.classifyThrownError ? options.classifyThrownError(error) : "transient_failure";
|
|
449
|
+
record(classifyCircuitBreakerOutcome(thrownOutcome));
|
|
450
|
+
let willRetry = shouldRetryAdapterOutcome(thrownOutcome, policy.idempotent) && attempt < policy.maxAttempts;
|
|
451
|
+
const blocked = willRetry ? refusedRetry(breaker) : void 0;
|
|
452
|
+
if (blocked) willRetry = false;
|
|
453
|
+
const nextDelayMs = willRetry ? getRetryDelayMs(attempt, policy) : void 0;
|
|
454
|
+
await options.onAttempt?.({ attempt, error: lastError, willRetry, nextDelayMs });
|
|
455
|
+
if (blocked) return blocked;
|
|
456
|
+
if (!willRetry) return "rethrow";
|
|
457
|
+
await sleep(nextDelayMs ?? 0);
|
|
458
|
+
return "retry";
|
|
459
|
+
}
|
|
460
|
+
try {
|
|
461
|
+
if (options.prepareAttempt) await options.prepareAttempt(attempt, policy.maxAttempts);
|
|
462
|
+
} catch (error) {
|
|
463
|
+
const decision = await handleThrown(error);
|
|
464
|
+
if (decision === "rethrow") throw error;
|
|
465
|
+
if (decision === "retry") continue;
|
|
466
|
+
refusal = decision;
|
|
467
|
+
}
|
|
468
|
+
if (!refusal) {
|
|
469
|
+
if (breaker) {
|
|
470
|
+
admission = breaker.acquire();
|
|
471
|
+
if (!admission.allowed) throw circuitOpenError(breaker, admission, attempt, lastError);
|
|
472
|
+
}
|
|
473
|
+
try {
|
|
474
|
+
executed = true;
|
|
475
|
+
const result = await options.execute(attempt, policy.maxAttempts);
|
|
476
|
+
if (options.isSuccessful(result)) {
|
|
477
|
+
record("success");
|
|
478
|
+
await options.onAttempt?.({ attempt, result, willRetry: false });
|
|
479
|
+
return result;
|
|
480
|
+
}
|
|
481
|
+
lastError = options.getError(result) ?? "Adapter returned unsuccessful result";
|
|
482
|
+
const outcome = options.classifyOutcome ? options.classifyOutcome(result) : "transient_failure";
|
|
483
|
+
record(classifyCircuitBreakerOutcome(outcome));
|
|
484
|
+
let willRetry = shouldRetryAdapterOutcome(outcome, policy.idempotent) && attempt < policy.maxAttempts;
|
|
485
|
+
refusal = willRetry ? refusedRetry(breaker) : void 0;
|
|
486
|
+
if (refusal) willRetry = false;
|
|
487
|
+
const nextDelayMs = willRetry ? getRetryDelayMs(attempt, policy) : void 0;
|
|
488
|
+
await options.onAttempt?.({ attempt, result, error: lastError, willRetry, nextDelayMs });
|
|
489
|
+
if (!willRetry && !refusal) {
|
|
490
|
+
return result;
|
|
491
|
+
}
|
|
492
|
+
if (!refusal) await sleep(nextDelayMs ?? 0);
|
|
493
|
+
} catch (error) {
|
|
494
|
+
const decision = await handleThrown(error);
|
|
495
|
+
if (decision === "rethrow") throw error;
|
|
496
|
+
refusal = decision === "retry" ? void 0 : decision;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
if (refusal && breaker) throw circuitOpenError(breaker, refusal, attempt + 1, lastError);
|
|
500
|
+
}
|
|
501
|
+
throw new Error(lastError ?? "Adapter retry exhausted");
|
|
502
|
+
}
|
|
503
|
+
function refusedRetry(breaker) {
|
|
504
|
+
if (!breaker) return void 0;
|
|
505
|
+
const preview = breaker.check();
|
|
506
|
+
return preview.allowed ? void 0 : preview;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
export { AdapterCircuitBreakerRegistry, AdapterCircuitOpenError, AdapterRegistry, InMemoryAdapterCircuitBreaker, MissingAdapterRegistrationError, assertAdapterCircuitBreakerConfig, assertAdapterOutcomeConsistency, classifyAdapterOutcome, classifyCircuitBreakerOutcome, executeWithAdapterRetry, getRetryDelayMs, isAdapterCircuitOpenError, resolveAdapterRetryPolicy, shouldRetryAdapterOutcome };
|
|
510
|
+
//# sourceMappingURL=chunk-5FOCPRTR.js.map
|
|
511
|
+
//# sourceMappingURL=chunk-5FOCPRTR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../adapters/circuit-breaker.ts","../adapters/index.ts"],"names":[],"mappings":"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","file":"chunk-5FOCPRTR.js","sourcesContent":["import type {\n\tAdapterCircuitBreakerConfig,\n\tAdapterCircuitBreakerManualOverride,\n\tAdapterImplementation,\n\tAdapterOutcomeKind,\n} from \"./index\";\n\n/**\n * State of an adapter circuit breaker.\n *\n * - `closed`: calls flow and outcomes are counted.\n * - `open`: the dependency is presumed unhealthy. In `enforce` mode calls are\n * rejected without being attempted until `cooldownMs` has elapsed.\n * - `half_open`: the cooldown elapsed. One probe call is admitted; its outcome\n * closes the breaker (success) or reopens it (failure).\n */\nexport type AdapterCircuitState = \"closed\" | \"open\" | \"half_open\";\n\n/**\n * How a single adapter outcome counts toward the breaker.\n *\n * - `success`: the dependency answered and the call succeeded.\n * - `failure`: evidence the dependency is unhealthy.\n * - `ignored`: the dependency answered but rejected the request on its merits\n * (a validation or business rejection). It is neither a success nor a\n * dependency failure and does not enter the rolling window.\n */\nexport type AdapterCircuitOutcome = \"success\" | \"failure\" | \"ignored\";\n\n/**\n * Map an {@link AdapterOutcomeKind} to how it counts toward a circuit breaker.\n *\n * | Outcome kind | Counts as |\n * |---------------------|-----------|\n * | `success` | `success` |\n * | `transient_failure` | `failure` |\n * | `timeout` | `failure` |\n * | `ambiguous` | `failure` |\n * | `permanent_failure` | `ignored` |\n *\n * `ambiguous` counts as a failure: the dependency did not give a definite\n * answer, which is a health signal even though the Host routes the invocation\n * to reconciliation. `permanent_failure` (validation or rejection) is the\n * dependency working correctly and saying no, so it does not trip the breaker.\n * `executeWithAdapterRetry` classifies a thrown error with the caller's\n * `classifyThrownError` (default `transient_failure`) and maps that kind here,\n * so an unclassified thrown error counts as a failure.\n */\nexport function classifyCircuitBreakerOutcome(kind: AdapterOutcomeKind): AdapterCircuitOutcome {\n\tswitch (kind) {\n\t\tcase \"success\":\n\t\t\treturn \"success\";\n\t\tcase \"permanent_failure\":\n\t\t\treturn \"ignored\";\n\t\tcase \"transient_failure\":\n\t\tcase \"timeout\":\n\t\tcase \"ambiguous\":\n\t\t\treturn \"failure\";\n\t\tdefault:\n\t\t\t// An unknown kind is treated as a dependency failure (fail closed).\n\t\t\treturn \"failure\";\n\t}\n}\n\n/** The answer a breaker gives before a call. */\nexport interface AdapterCircuitAdmission {\n\t/**\n\t * Whether the call may proceed. Always `true` in `monitor` mode; `false`\n\t * only in `enforce` mode while the breaker is open, forced open, or\n\t * half-open with its probe already in flight.\n\t */\n\tallowed: boolean;\n\t/** State at the time of the decision. */\n\tstate: AdapterCircuitState;\n\t/** True when this admission is the half-open probe. Pass it back to `record`. */\n\tprobe: boolean;\n\t/** Monitor mode only: true when `enforce` mode would have rejected this call. */\n\twouldReject?: boolean;\n\t/**\n\t * Milliseconds until the breaker will admit a probe, when known. Absent while\n\t * a manual `force_open` override is set or a half-open probe is in flight.\n\t */\n\tretryAfterMs?: number;\n\t/** Set when a manual override decided the admission. */\n\tmanualOverride?: AdapterCircuitBreakerManualOverride;\n\t/**\n\t * The breaker epoch the admission was granted in; opaque to callers.\n\t * {@link InMemoryAdapterCircuitBreaker} lets an outcome change state only\n\t * when this still matches its current epoch. An admission without it is\n\t * treated as current.\n\t */\n\tgeneration?: number;\n}\n\n/**\n * Contract between a circuit breaker and `executeWithAdapterRetry`.\n * Implement it to plug in a shared or distributed breaker.\n */\nexport interface AdapterCircuitBreaker {\n\t/** `adapterType:operation` this breaker guards. */\n\treadonly key: string;\n\treadonly mode: \"monitor\" | \"enforce\";\n\t/** Current state, applying any elapsed cooldown or manual override. */\n\tgetState(): AdapterCircuitState;\n\t/**\n\t * Preview whether a call would be admitted now. Side-effect free: it does\n\t * not commit an elapsed cooldown, take the half-open probe or emit\n\t * telemetry.\n\t */\n\tcheck(): AdapterCircuitAdmission;\n\t/**\n\t * Decide whether a call may start now. Call it immediately before the\n\t * external effect and pass the returned admission to {@link record}.\n\t */\n\tacquire(): AdapterCircuitAdmission;\n\t/**\n\t * Record the outcome of a call that was admitted by {@link acquire}. Pass\n\t * that admission, not a {@link check} preview, so the breaker can tell a\n\t * stale outcome from a current one.\n\t */\n\trecord(outcome: AdapterCircuitOutcome, admission?: AdapterCircuitAdmission): void;\n}\n\nexport type AdapterCircuitBreakerEventReason =\n\t| \"failure_threshold\"\n\t| \"cooldown_elapsed\"\n\t| \"probe_succeeded\"\n\t| \"probe_failed\"\n\t| \"manual_override\"\n\t| \"manual_override_cleared\";\n\ninterface AdapterCircuitBreakerEventBase {\n\tkey: string;\n\tmode: \"monitor\" | \"enforce\";\n\t/** Epoch milliseconds from the breaker's clock. */\n\tat: number;\n}\n\n/** Telemetry reported by {@link InMemoryAdapterCircuitBreaker}. */\nexport type AdapterCircuitBreakerEvent =\n\t| (AdapterCircuitBreakerEventBase & {\n\t\t\ttype: \"state_change\";\n\t\t\tfrom: AdapterCircuitState;\n\t\t\tto: AdapterCircuitState;\n\t\t\treason: AdapterCircuitBreakerEventReason;\n\t\t\tmanualOverride?: AdapterCircuitBreakerManualOverride;\n\t })\n\t| (AdapterCircuitBreakerEventBase & {\n\t\t\t/** Enforce mode refused a call; it was not attempted. */\n\t\t\ttype: \"rejected\";\n\t\t\tstate: AdapterCircuitState;\n\t\t\tretryAfterMs?: number;\n\t\t\tmanualOverride?: AdapterCircuitBreakerManualOverride;\n\t })\n\t| (AdapterCircuitBreakerEventBase & {\n\t\t\t/** Monitor mode let a call through that enforce mode would have refused. */\n\t\t\ttype: \"would_reject\";\n\t\t\tstate: AdapterCircuitState;\n\t\t\tretryAfterMs?: number;\n\t\t\tmanualOverride?: AdapterCircuitBreakerManualOverride;\n\t });\n\n/**\n * Telemetry callback. Errors it throws and promises it rejects are caught\n * and discarded so telemetry cannot change an adapter call's outcome.\n *\n * {@link InMemoryAdapterCircuitBreaker} calls it only after the operation that\n * produced the event has committed, never part-way through a decision or a\n * record. A callback may call back into the breaker; it sees committed state,\n * and events its own calls produce are delivered after it returns, in order.\n */\nexport type AdapterCircuitBreakerEventListener = (\n\tevent: AdapterCircuitBreakerEvent,\n) => void | Promise<void>;\n\nexport interface InMemoryAdapterCircuitBreakerOptions {\n\t/** Usually `adapterType:operation`. */\n\tkey: string;\n\tconfig: AdapterCircuitBreakerConfig;\n\t/** Epoch-millisecond clock. Defaults to `Date.now`. */\n\tnow?: () => number;\n\tonEvent?: AdapterCircuitBreakerEventListener;\n}\n\nconst MANUAL_OVERRIDES: readonly AdapterCircuitBreakerManualOverride[] = [\"force_open\", \"force_closed\"];\n\n/**\n * Validate a circuit-breaker config. Throws a `RangeError` naming the first\n * invalid field: `mode` must be `monitor` or `enforce`; `failureThreshold` and\n * `minimumSamples` positive integers; `windowMs` and `probeLeaseMs` positive\n * finite numbers; `cooldownMs` a non-negative finite number; `manualOverride`\n * `force_open` or `force_closed` when present.\n */\nexport function assertAdapterCircuitBreakerConfig(config: AdapterCircuitBreakerConfig): void {\n\tif (!config || typeof config !== \"object\") {\n\t\tthrow new RangeError(\"Adapter circuit breaker config must be an object.\");\n\t}\n\tif (config.mode !== \"monitor\" && config.mode !== \"enforce\") {\n\t\tthrow new RangeError(`Adapter circuit breaker mode must be \"monitor\" or \"enforce\"; got ${JSON.stringify(config.mode)}.`);\n\t}\n\tif (!Number.isInteger(config.failureThreshold) || config.failureThreshold < 1) {\n\t\tthrow new RangeError(`Adapter circuit breaker failureThreshold must be a positive integer; got ${String(config.failureThreshold)}.`);\n\t}\n\tif (typeof config.windowMs !== \"number\" || !Number.isFinite(config.windowMs) || config.windowMs <= 0) {\n\t\tthrow new RangeError(`Adapter circuit breaker windowMs must be a positive number of milliseconds; got ${String(config.windowMs)}.`);\n\t}\n\tif (typeof config.cooldownMs !== \"number\" || !Number.isFinite(config.cooldownMs) || config.cooldownMs < 0) {\n\t\tthrow new RangeError(`Adapter circuit breaker cooldownMs must be a non-negative number of milliseconds; got ${String(config.cooldownMs)}.`);\n\t}\n\tif (\n\t\tconfig.minimumSamples !== undefined &&\n\t\t(!Number.isInteger(config.minimumSamples) || config.minimumSamples < 1)\n\t) {\n\t\tthrow new RangeError(`Adapter circuit breaker minimumSamples must be a positive integer; got ${String(config.minimumSamples)}.`);\n\t}\n\tif (\n\t\tconfig.probeLeaseMs !== undefined &&\n\t\t(typeof config.probeLeaseMs !== \"number\" || !Number.isFinite(config.probeLeaseMs) || config.probeLeaseMs <= 0)\n\t) {\n\t\tthrow new RangeError(`Adapter circuit breaker probeLeaseMs must be a positive number of milliseconds; got ${String(config.probeLeaseMs)}.`);\n\t}\n\tif (config.manualOverride !== undefined && !MANUAL_OVERRIDES.includes(config.manualOverride)) {\n\t\tthrow new RangeError(`Adapter circuit breaker manualOverride must be \"force_open\" or \"force_closed\"; got ${JSON.stringify(config.manualOverride)}.`);\n\t}\n}\n\ninterface Sample {\n\tat: number;\n\tfailure: boolean;\n}\n\n/**\n * Dependency-free, in-process circuit breaker for one adapter operation.\n *\n * State lives in this instance only: it is not shared between processes,\n * hosts, or replicas, and it is lost on restart. Each replica trips on the\n * failures it observes itself.\n *\n * Tripping rule (parity with the .NET `InMemoryAdapterCircuitBreaker`): after\n * a failure is recorded while `closed`, let `N` be the outcomes recorded within\n * the last `windowMs` and `M = minimumSamples ?? failureThreshold`. The breaker\n * opens when `N >= M` and either\n * 1. the most recent `failureThreshold` outcomes in the window are all\n * failures, or\n * 2. the failure ratio in the window is at least `failureThreshold / M`.\n *\n * With `minimumSamples` omitted this reads \"`failureThreshold` consecutive\n * failures\". With `minimumSamples: 20, failureThreshold: 10` it also opens at\n * a 50% failure rate over at least 20 calls.\n *\n * After `cooldownMs` the breaker turns `half_open` and admits one probe. A\n * probe success closes it and clears the window; a probe failure reopens it\n * and restarts the cooldown. At most one probe is outstanding at a time: it\n * holds a lease of `probeLeaseMs` (default `max(cooldownMs, windowMs)`, never\n * derived from the cooldown alone, so `cooldownMs: 0` still admits one probe).\n * A probe that has not recorded when its lease expires is considered lost and\n * the next call becomes the probe.\n *\n * Epochs: the breaker keeps a generation counter that advances on every state\n * change (including one committed by an elapsed cooldown), every manual\n * override change, and every probe grant. Each admission carries the\n * generation it was granted in. `record` first commits any elapsed cooldown,\n * then compares generations. An outcome from an earlier epoch is stale: it is\n * dropped entirely. It changes no state, does not resolve or free the probe\n * slot, and is not added to the rolling window, so it cannot contribute to a\n * later trip either. Because a probe grant starts a new epoch, the outstanding\n * probe is the only `probe: true` admission of the current epoch, which is how\n * a replaced probe's late outcome is told apart. An outcome recorded without an\n * admission, or with one that has no `generation`, is treated as current: it is\n * sampled, can trip a closed breaker and resolves an outstanding probe.\n *\n * Telemetry is buffered and delivered after each operation commits. An\n * elapsed cooldown is committed, and its `cooldown_elapsed` event delivered,\n * as a step of its own before `acquire`, `record`, `getState` or\n * `setManualOverride` continues, so a callback reacting to it (for example by\n * forcing the breaker open) governs the call that observed it. Every other\n * event is delivered once the method's decision or record is final, and a\n * callback never revokes an admission that is already committed. Two cases\n * follow from that: a breaker call made from inside a callback queues its own\n * `cooldown_elapsed` behind the current delivery and decides first; and with\n * `cooldownMs: 0`, a cooldown that falls due again during the decision (because\n * a callback reopened the breaker) is committed and admitted before its event\n * is delivered.\n *\n * `monitor` mode runs the same state machine and reports the same telemetry\n * but admits every call; calls enforce mode would have refused are reported\n * as `would_reject`.\n *\n * A manual override pins the reported state (`force_open` → `open`,\n * `force_closed` → `closed`) and suspends automatic transitions while set;\n * outcomes of calls admitted under the override are still recorded. Like an\n * automatic open, `force_open` blocks only in `enforce` mode. Clearing the\n * override returns the breaker to `closed` with its window intact.\n */\nexport class InMemoryAdapterCircuitBreaker implements AdapterCircuitBreaker {\n\treadonly key: string;\n\treadonly mode: \"monitor\" | \"enforce\";\n\tprivate readonly config: AdapterCircuitBreakerConfig;\n\tprivate readonly now: () => number;\n\tprivate readonly onEvent: AdapterCircuitBreakerEventListener | undefined;\n\tprivate readonly probeLeaseMs: number;\n\tprivate samples: Sample[] = [];\n\tprivate state: AdapterCircuitState = \"closed\";\n\tprivate openedAt = 0;\n\tprivate probeIssuedAt: number | undefined;\n\t/** Advances on every state change, manual override change and probe grant. */\n\tprivate generation = 0;\n\tprivate manualOverride: AdapterCircuitBreakerManualOverride | undefined;\n\t/** Telemetry waiting for the current operation to commit. */\n\tprivate readonly outbox: AdapterCircuitBreakerEvent[] = [];\n\tprivate operationDepth = 0;\n\tprivate flushing = false;\n\n\tconstructor(options: InMemoryAdapterCircuitBreakerOptions) {\n\t\tassertAdapterCircuitBreakerConfig(options.config);\n\t\tthis.key = options.key;\n\t\tthis.config = { ...options.config };\n\t\tthis.mode = options.config.mode;\n\t\tthis.now = options.now ?? Date.now;\n\t\tthis.onEvent = options.onEvent;\n\t\tthis.manualOverride = options.config.manualOverride;\n\t\tthis.probeLeaseMs = options.config.probeLeaseMs ?? Math.max(options.config.cooldownMs, options.config.windowMs);\n\t}\n\n\tgetState(): AdapterCircuitState {\n\t\tthis.settle(this.now());\n\t\treturn this.reportedState();\n\t}\n\n\tgetManualOverride(): AdapterCircuitBreakerManualOverride | undefined {\n\t\treturn this.manualOverride;\n\t}\n\n\t/**\n\t * Set (`force_open` / `force_closed`) or clear (`undefined`) the manual\n\t * override. Clearing returns the breaker to `closed` with its window intact.\n\t */\n\tsetManualOverride(override: AdapterCircuitBreakerManualOverride | undefined): void {\n\t\tif (override !== undefined && !MANUAL_OVERRIDES.includes(override)) {\n\t\t\tthrow new RangeError(`Adapter circuit breaker manualOverride must be \"force_open\" or \"force_closed\"; got ${JSON.stringify(override)}.`);\n\t\t}\n\t\tconst now = this.now();\n\t\tthis.settle(now);\n\t\t// Compared after settle: a telemetry callback may have set this override.\n\t\tif (override === this.manualOverride) return;\n\t\tthis.operation(() => {\n\t\t\tconst from = this.reportedState();\n\t\t\tthis.manualOverride = override;\n\t\t\tthis.generation++;\n\t\t\tif (override === undefined) {\n\t\t\t\tthis.state = \"closed\";\n\t\t\t\tthis.releaseProbe();\n\t\t\t}\n\t\t\tthis.emit({\n\t\t\t\ttype: \"state_change\",\n\t\t\t\tkey: this.key,\n\t\t\t\tmode: this.mode,\n\t\t\t\tat: now,\n\t\t\t\tfrom,\n\t\t\t\tto: this.reportedState(),\n\t\t\t\treason: override === undefined ? \"manual_override_cleared\" : \"manual_override\",\n\t\t\t\t...(override ? { manualOverride: override } : {}),\n\t\t\t});\n\t\t});\n\t}\n\n\tcheck(): AdapterCircuitAdmission {\n\t\treturn this.decide(false, this.now());\n\t}\n\n\tacquire(): AdapterCircuitAdmission {\n\t\tconst now = this.now();\n\t\tthis.settle(now);\n\t\treturn this.operation(() => this.decide(true, now));\n\t}\n\n\trecord(outcome: AdapterCircuitOutcome, admission?: AdapterCircuitAdmission): void {\n\t\tconst now = this.now();\n\t\t// Commit an elapsed cooldown first, so an outcome admitted while open\n\t\t// is judged against the half_open epoch it would otherwise influence.\n\t\tthis.settle(now);\n\t\tthis.operation(() => this.applyOutcome(outcome, admission, now));\n\t}\n\n\tprivate applyOutcome(outcome: AdapterCircuitOutcome, admission: AdapterCircuitAdmission | undefined, now: number): void {\n\t\tconst generation = admission?.generation;\n\t\t// Stale: admitted in an earlier epoch. No transition, no probe release, no sample.\n\t\tif (generation !== undefined && generation !== this.generation) return;\n\t\t// In the current epoch the only probe admission is the outstanding probe.\n\t\tconst resolvesProbe =\n\t\t\tthis.state === \"half_open\" &&\n\t\t\tthis.probeIssuedAt !== undefined &&\n\t\t\t(generation === undefined || admission?.probe === true);\n\t\tif (outcome === \"ignored\") {\n\t\t\t// The dependency answered; the probe proved nothing either way.\n\t\t\tif (resolvesProbe) this.releaseProbe();\n\t\t\treturn;\n\t\t}\n\t\tthis.prune(now);\n\t\tthis.samples.push({ at: now, failure: outcome === \"failure\" });\n\t\tif (this.manualOverride) return;\n\t\tif (resolvesProbe) {\n\t\t\tthis.releaseProbe();\n\t\t\tif (outcome === \"success\") {\n\t\t\t\tthis.samples = [];\n\t\t\t\tthis.transition(\"closed\", \"probe_succeeded\", now);\n\t\t\t} else {\n\t\t\t\tthis.openedAt = now;\n\t\t\t\tthis.transition(\"open\", \"probe_failed\", now);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (this.state === \"closed\" && outcome === \"failure\" && this.shouldTrip()) {\n\t\t\tthis.openedAt = now;\n\t\t\tthis.transition(\"open\", \"failure_threshold\", now);\n\t\t}\n\t}\n\n\t/**\n\t * `take` is false for {@link check}: a pure preview that sees an elapsed\n\t * cooldown without committing it, takes no probe and emits nothing.\n\t */\n\tprivate decide(take: boolean, now: number): AdapterCircuitAdmission {\n\t\tif (this.manualOverride === \"force_closed\") {\n\t\t\treturn { allowed: true, state: \"closed\", probe: false, manualOverride: \"force_closed\", generation: this.generation };\n\t\t}\n\t\tif (this.manualOverride === \"force_open\") {\n\t\t\treturn this.refuse(take, { state: \"open\", manualOverride: \"force_open\" });\n\t\t}\n\t\t// An elapsed cooldown releases any probe, so a pending open → half_open\n\t\t// transition always has its probe free.\n\t\tconst cooldownElapsed = this.state === \"open\" && now - this.openedAt >= this.config.cooldownMs;\n\t\t// Normally a no-op: acquire() settled first. A listener run by that\n\t\t// settle can reopen the breaker (with cooldownMs 0 it is due at once);\n\t\t// that cooldown is committed here and reported after the decision.\n\t\tif (take) this.advance(now);\n\t\tconst state: AdapterCircuitState = !take && cooldownElapsed ? \"half_open\" : this.state;\n\t\tif (state === \"closed\") return { allowed: true, state: \"closed\", probe: false, generation: this.generation };\n\t\tif (state === \"open\") {\n\t\t\treturn this.refuse(take, {\n\t\t\t\tstate: \"open\",\n\t\t\t\tretryAfterMs: Math.max(0, this.openedAt + this.config.cooldownMs - now),\n\t\t\t});\n\t\t}\n\t\t// half_open: one probe at a time, until it records or its lease expires.\n\t\tconst probeFree =\n\t\t\t(!take && cooldownElapsed) ||\n\t\t\tthis.probeIssuedAt === undefined ||\n\t\t\tnow - this.probeIssuedAt >= this.probeLeaseMs;\n\t\tif (!probeFree) return this.refuse(take, { state: \"half_open\" });\n\t\tif (!take) return { allowed: true, state: \"half_open\", probe: true, generation: this.generation };\n\t\t// Granting the probe starts a new epoch, so a replaced probe's token goes stale.\n\t\tthis.probeIssuedAt = now;\n\t\tthis.generation++;\n\t\treturn { allowed: true, state: \"half_open\", probe: true, generation: this.generation };\n\t}\n\n\tprivate refuse(\n\t\ttake: boolean,\n\t\tdetail: { state: AdapterCircuitState; retryAfterMs?: number; manualOverride?: AdapterCircuitBreakerManualOverride },\n\t): AdapterCircuitAdmission {\n\t\tconst enforce = this.mode === \"enforce\";\n\t\tif (take) {\n\t\t\tthis.emit({\n\t\t\t\ttype: enforce ? \"rejected\" : \"would_reject\",\n\t\t\t\tkey: this.key,\n\t\t\t\tmode: this.mode,\n\t\t\t\tat: this.now(),\n\t\t\t\t...detail,\n\t\t\t});\n\t\t}\n\t\tconst generation = this.generation;\n\t\treturn enforce\n\t\t\t? { allowed: false, probe: false, ...detail, generation }\n\t\t\t: { allowed: true, probe: false, wouldReject: true, ...detail, generation };\n\t}\n\n\tprivate releaseProbe(): void {\n\t\tthis.probeIssuedAt = undefined;\n\t}\n\n\t/** Apply the time-based open → half_open transition. */\n\tprivate advance(now: number): void {\n\t\tif (this.state === \"open\" && now - this.openedAt >= this.config.cooldownMs) {\n\t\t\tthis.releaseProbe();\n\t\t\tthis.transition(\"half_open\", \"cooldown_elapsed\", now);\n\t\t}\n\t}\n\n\tprivate shouldTrip(): boolean {\n\t\tconst minimumSamples = this.config.minimumSamples ?? this.config.failureThreshold;\n\t\tconst total = this.samples.length;\n\t\tif (total < minimumSamples) return false;\n\t\tlet consecutive = 0;\n\t\tfor (let index = total - 1; index >= 0 && this.samples[index]!.failure; index--) consecutive++;\n\t\tif (consecutive >= this.config.failureThreshold) return true;\n\t\tconst failures = this.samples.reduce((count, sample) => count + (sample.failure ? 1 : 0), 0);\n\t\treturn failures / total >= this.config.failureThreshold / minimumSamples;\n\t}\n\n\tprivate prune(now: number): void {\n\t\tconst cutoff = now - this.config.windowMs;\n\t\tlet drop = 0;\n\t\twhile (drop < this.samples.length && this.samples[drop]!.at < cutoff) drop++;\n\t\tif (drop > 0) this.samples.splice(0, drop);\n\t}\n\n\tprivate transition(to: AdapterCircuitState, reason: AdapterCircuitBreakerEventReason, at: number): void {\n\t\tconst from = this.state;\n\t\tif (from === to) return;\n\t\tthis.state = to;\n\t\tthis.generation++;\n\t\tthis.emit({ type: \"state_change\", key: this.key, mode: this.mode, at, from, to, reason });\n\t}\n\n\tprivate reportedState(): AdapterCircuitState {\n\t\tif (this.manualOverride) return this.manualOverride === \"force_open\" ? \"open\" : \"closed\";\n\t\treturn this.state;\n\t}\n\n\t/**\n\t * Commit an elapsed cooldown as an operation of its own, delivering its\n\t * event before the caller's operation reads state.\n\t */\n\tprivate settle(now: number): void {\n\t\tif (this.manualOverride) return;\n\t\tthis.operation(() => this.advance(now));\n\t}\n\n\t/**\n\t * Run `step` to completion, then deliver the telemetry it produced. The\n\t * return value is fixed before any listener runs.\n\t */\n\tprivate operation<T>(step: () => T): T {\n\t\tthis.operationDepth++;\n\t\ttry {\n\t\t\treturn step();\n\t\t} finally {\n\t\t\tthis.operationDepth--;\n\t\t\tif (this.operationDepth === 0) this.flush();\n\t\t}\n\t}\n\n\tprivate emit(event: AdapterCircuitBreakerEvent): void {\n\t\tif (this.onEvent) this.outbox.push(event);\n\t}\n\n\t/**\n\t * Deliver buffered events in order. A listener that calls back into the\n\t * breaker queues its own events; this loop delivers them after it returns\n\t * instead of recursing.\n\t */\n\tprivate flush(): void {\n\t\tif (this.flushing) return;\n\t\tthis.flushing = true;\n\t\ttry {\n\t\t\tfor (let event = this.outbox.shift(); event; event = this.outbox.shift()) this.deliver(event);\n\t\t} finally {\n\t\t\tthis.flushing = false;\n\t\t}\n\t}\n\n\tprivate deliver(event: AdapterCircuitBreakerEvent): void {\n\t\tconst listener = this.onEvent;\n\t\tif (!listener) return;\n\t\ttry {\n\t\t\tconst result = listener(event);\n\t\t\tif (result && typeof (result as PromiseLike<void>).then === \"function\") {\n\t\t\t\t(result as PromiseLike<void>).then(undefined, () => undefined);\n\t\t\t}\n\t\t} catch {\n\t\t\t// Telemetry must never change an adapter call's outcome.\n\t\t}\n\t}\n}\n\n/** Supplies the breaker, if any, that guards an adapter. */\nexport interface AdapterCircuitBreakerProvider {\n\tforAdapter(adapter: AdapterImplementation): AdapterCircuitBreaker | undefined;\n}\n\nexport interface AdapterCircuitBreakerRegistryOptions {\n\t/** Epoch-millisecond clock handed to every breaker. Defaults to `Date.now`. */\n\tnow?: () => number;\n\tonEvent?: AdapterCircuitBreakerEventListener;\n}\n\n/**\n * Lazily creates one {@link InMemoryAdapterCircuitBreaker} per\n * `adapterType:operation`, from that adapter's own `circuitBreaker` config.\n * Adapters without a config get no breaker. The first config seen for a key\n * wins; share a registry only between hosts that register the same adapters.\n */\nexport class AdapterCircuitBreakerRegistry implements AdapterCircuitBreakerProvider {\n\tprivate readonly breakers = new Map<string, InMemoryAdapterCircuitBreaker>();\n\n\tconstructor(private readonly options: AdapterCircuitBreakerRegistryOptions = {}) {}\n\n\tforAdapter(adapter: AdapterImplementation): InMemoryAdapterCircuitBreaker | undefined {\n\t\tif (!adapter.circuitBreaker) return undefined;\n\t\tconst key = `${adapter.adapterType}:${adapter.operation}`;\n\t\tlet breaker = this.breakers.get(key);\n\t\tif (!breaker) {\n\t\t\tbreaker = new InMemoryAdapterCircuitBreaker({\n\t\t\t\tkey,\n\t\t\t\tconfig: adapter.circuitBreaker,\n\t\t\t\t...(this.options.now ? { now: this.options.now } : {}),\n\t\t\t\t...(this.options.onEvent ? { onEvent: this.options.onEvent } : {}),\n\t\t\t});\n\t\t\tthis.breakers.set(key, breaker);\n\t\t}\n\t\treturn breaker;\n\t}\n\n\t/** The breaker already created for `adapterType:operation`, if any. */\n\tget(adapterType: string, operation: string): InMemoryAdapterCircuitBreaker | undefined {\n\t\treturn this.breakers.get(`${adapterType}:${operation}`);\n\t}\n}\n\n/**\n * Thrown by `executeWithAdapterRetry` when an `enforce`-mode breaker refuses\n * an attempt. The refused attempt never reached `execute`, so no external\n * effect happened for it.\n */\nexport class AdapterCircuitOpenError extends Error {\n\toverride readonly name = \"AdapterCircuitOpenError\";\n\treadonly code = \"ADAPTER_CIRCUIT_OPEN\" as const;\n\treadonly reason = \"circuit_open\" as const;\n\treadonly adapterKey: string;\n\treadonly state: AdapterCircuitState;\n\t/** The attempt number that was refused (1 = refused before any call). */\n\treadonly attempt: number;\n\treadonly retryAfterMs?: number;\n\treadonly manualOverride?: AdapterCircuitBreakerManualOverride;\n\t/** Error of the previous attempt, when the breaker opened mid-retry. */\n\treadonly lastError?: string;\n\n\tconstructor(input: {\n\t\tadapterKey: string;\n\t\tstate: AdapterCircuitState;\n\t\tattempt: number;\n\t\tretryAfterMs?: number;\n\t\tmanualOverride?: AdapterCircuitBreakerManualOverride;\n\t\tlastError?: string;\n\t}) {\n\t\tconst after = input.retryAfterMs !== undefined ? `; retry after ${input.retryAfterMs} ms` : \"\";\n\t\tsuper(`Adapter circuit is ${input.state === \"half_open\" ? \"half-open with its probe in flight\" : \"open\"} for ${input.adapterKey}; the call was not attempted${after}.`);\n\t\tthis.adapterKey = input.adapterKey;\n\t\tthis.state = input.state;\n\t\tthis.attempt = input.attempt;\n\t\tif (input.retryAfterMs !== undefined) this.retryAfterMs = input.retryAfterMs;\n\t\tif (input.manualOverride !== undefined) this.manualOverride = input.manualOverride;\n\t\tif (input.lastError !== undefined) this.lastError = input.lastError;\n\t}\n}\n\n/**\n * Recognise an {@link AdapterCircuitOpenError}, including one constructed by a\n * second copy of this package (for example an ESM and a CommonJS build loaded\n * side by side), where `instanceof` fails.\n */\nexport function isAdapterCircuitOpenError(error: unknown): error is AdapterCircuitOpenError {\n\tif (error instanceof AdapterCircuitOpenError) return true;\n\treturn (\n\t\ttypeof error === \"object\" &&\n\t\terror !== null &&\n\t\t(error as { name?: unknown }).name === \"AdapterCircuitOpenError\" &&\n\t\t(error as { code?: unknown }).code === \"ADAPTER_CIRCUIT_OPEN\"\n\t);\n}\n","import {\n\tAdapterCircuitOpenError,\n\tclassifyCircuitBreakerOutcome,\n\ttype AdapterCircuitAdmission,\n\ttype AdapterCircuitBreaker,\n\ttype AdapterCircuitOutcome,\n} from \"./circuit-breaker\";\n\nexport * from \"./circuit-breaker\";\n\nexport interface AdapterExecutionContext {\n\ttenantId: string;\n\tspaceId: string;\n\tactionInvocationId: string;\n\tadapterInvocationId: string;\n\tcorrelationId: string;\n\tcausationId?: string;\n\tattempt: number;\n\tmaxAttempts: number;\n\t/** Epoch-millis deadline after which the adapter should abandon the call. */\n\tdeadlineMs?: number;\n\t/** Set by the host when the invocation has been cancelled; adapters should stop work. */\n\tcancelled?: boolean;\n\t/** Non-secret reason the invocation was cancelled. */\n\tcancellationReason?: string;\n\t/**\n\t * AbortSignal the host aborts when the deadline expires or the invocation is\n\t * cancelled. Adapters should propagate this to underlying HTTP/database\n\t * clients so in-flight work is abandoned rather than left dangling.\n\t */\n\tsignal?: AbortSignal;\n}\n\n/**\n * Additive classification of an adapter outcome so the retry layer can\n * distinguish retryable transient failures from permanent rejections,\n * timeouts, and ambiguous results that require manual reconciliation.\n *\n * When `outcome` is absent on an {@link AdapterExecutionResult}, the retry\n * layer treats a non-success result as `transient_failure` for backward\n * compatibility.\n */\nexport type AdapterOutcomeKind =\n\t| \"success\"\n\t| \"transient_failure\"\n\t| \"permanent_failure\"\n\t| \"timeout\"\n\t| \"ambiguous\";\n\n/**\n * Durable, non-secret evidence attached to a classified adapter outcome.\n * Used by the host to record reconciliation evidence when an adapter returns\n * an ambiguous result whose external effect may or may not have been applied.\n */\nexport interface AdapterOutcomeEvidence {\n\tkind: AdapterOutcomeKind;\n\treason?: string;\n\t/** Opaque, non-secret reference to external operation state for reconciliation. */\n\texternalReference?: string;\n\t/** Whether the external effect may have been applied (true for ambiguous/timeout). */\n\teffectMayHaveApplied?: boolean;\n\tobservedAt: Date | string;\n}\n\n/**\n * An adapter handed the work to an external system that will finish it later.\n *\n * Explicit rather than inferred from evidence: a successful adapter call that\n * merely records a reference is not the same as one that has left the outcome\n * in someone else's hands. The host keeps the invocation running and expects a\n * completion carrying this reference through its completion ingress.\n */\nexport interface AcceptedExternalOperation {\n\t/** Opaque, non-secret reference the external system will complete by. */\n\texternalReference: string;\n\t/** When the external system said it expects to finish, if it said. */\n\texpectedBy?: Date | string;\n}\n\nexport interface AdapterExecutionResult {\n\tsuccess: boolean;\n\tcorrelationId?: string;\n\terror?: string;\n\t/** Additive outcome classification. Omitted = infer from `success`. */\n\toutcome?: AdapterOutcomeKind;\n\t/** Durable evidence for ambiguous or timeout outcomes requiring reconciliation. */\n\toutcomeEvidence?: AdapterOutcomeEvidence;\n\t/** Present when a successful call handed the work off to finish elsewhere. */\n\tacceptedExternalOperation?: AcceptedExternalOperation;\n}\n\nexport type AdapterExecutor = (\n\tinput: Record<string, unknown>,\n\tcontext: AdapterExecutionContext,\n) => Promise<AdapterExecutionResult>;\n\nexport interface AdapterRetryPolicy {\n\t/** External calls must explicitly opt into retry by being idempotent. */\n\tidempotent: boolean;\n\t/** Total attempts including the first call. Non-idempotent policies are forced to 1. */\n\tmaxAttempts?: number;\n\t/** First backoff delay in milliseconds. */\n\tinitialDelayMs?: number;\n\t/** Exponential multiplier applied after each failed attempt. */\n\tbackoffMultiplier?: number;\n\t/** Upper bound for any single retry delay in milliseconds. */\n\tmaxDelayMs?: number;\n}\n\nexport interface ResolvedAdapterRetryPolicy {\n\tidempotent: boolean;\n\tmaxAttempts: number;\n\tinitialDelayMs: number;\n\tbackoffMultiplier: number;\n\tmaxDelayMs: number;\n}\n\n/** Manual circuit-breaker override: pin the breaker open or closed. */\nexport type AdapterCircuitBreakerManualOverride = \"force_open\" | \"force_closed\";\n\n/**\n * Per-adapter circuit-breaker configuration, read by\n * {@link AdapterCircuitBreakerRegistry} (and so by Platform Host) to create one\n * in-process {@link InMemoryAdapterCircuitBreaker} per `adapterType:operation`.\n * Validated by {@link assertAdapterCircuitBreakerConfig}.\n */\nexport interface AdapterCircuitBreakerConfig {\n\t/**\n\t * `monitor` records outcomes, runs the state machine and reports what it\n\t * would have refused, but never blocks a call. `enforce` refuses calls\n\t * while the breaker is open, without attempting them.\n\t */\n\tmode: \"monitor\" | \"enforce\";\n\t/**\n\t * A failure count. Let `M = minimumSamples ?? failureThreshold`. Once at\n\t * least `M` outcomes fall within `windowMs`, the breaker opens when the most\n\t * recent `failureThreshold` of them are all failures, or when the failure\n\t * ratio in the window reaches `failureThreshold / M`. Without\n\t * `minimumSamples` this is \"`failureThreshold` consecutive failures\".\n\t * Matches the .NET port's `InMemoryAdapterCircuitBreaker`. Positive integer.\n\t */\n\tfailureThreshold: number;\n\t/** Rolling window, in milliseconds, of outcomes the thresholds consider. */\n\twindowMs: number;\n\t/** Milliseconds an open breaker waits before admitting one half-open probe. */\n\tcooldownMs: number;\n\t/** Outcomes required in the window before the breaker can open. Defaults to `failureThreshold`. */\n\tminimumSamples?: number;\n\t/**\n\t * How long, in milliseconds, the single half-open probe holds its slot\n\t * before it is presumed lost and another call may probe. Defaults to\n\t * `max(cooldownMs, windowMs)`; it is independent of `cooldownMs`, so a zero\n\t * or short cooldown never admits concurrent probes. Positive.\n\t */\n\tprobeLeaseMs?: number;\n\t/**\n\t * Optional initial manual override. `force_open` reports `open` (and blocks\n\t * only in `enforce` mode); `force_closed` reports `closed` and never blocks.\n\t * Change it at runtime with `InMemoryAdapterCircuitBreaker.setManualOverride`.\n\t */\n\tmanualOverride?: AdapterCircuitBreakerManualOverride;\n}\n\nexport interface AdapterImplementation {\n\tadapterType: string;\n\toperation: string;\n\tvendor: string;\n\t/** Whether this adapter operation can be safely retried by default. */\n\tidempotent: boolean;\n\tretryPolicy?: AdapterRetryPolicy;\n\t/** Defaults to monitor-only; enforce mode must be explicitly configured. */\n\tcircuitBreaker?: AdapterCircuitBreakerConfig;\n\texecute: AdapterExecutor;\n}\n\nexport interface AdapterAttempt<T> {\n\tattempt: number;\n\tresult?: T;\n\terror?: string;\n\twillRetry: boolean;\n\tnextDelayMs?: number;\n}\n\nexport interface ExecuteWithRetryOptions<T> {\n\tpolicy?: AdapterRetryPolicy;\n\tdefaultIdempotent: boolean;\n\texecute: (attempt: number, maxAttempts: number) => Promise<T>;\n\tisSuccessful: (result: T) => boolean;\n\tgetError: (result: T) => string | undefined;\n\t/**\n\t * Optional outcome classifier. When provided, the retry layer retries only\n\t * `transient_failure` outcomes (and only when the policy is idempotent).\n\t * `permanent_failure`, `timeout`, and `ambiguous` outcomes are never retried.\n\t * When omitted, all non-success results are treated as transient (backward compatible).\n\t */\n\tclassifyOutcome?: (result: T) => AdapterOutcomeKind;\n\t/**\n\t * Optional classifier for errors thrown by the execute callback (not returned\n\t * as results). When provided, the retry layer retries only `transient_failure`\n\t * classifications. When omitted, thrown errors are never retried — the caller\n\t * must explicitly opt into retrying a thrown failure by classifying it.\n\t */\n\tclassifyThrownError?: (error: unknown) => AdapterOutcomeKind;\n\tsleep?: (delayMs: number) => Promise<void>;\n\tonAttempt?: (attempt: AdapterAttempt<T>) => Promise<void> | void;\n\t/**\n\t * Optional per-attempt preparation, awaited before the circuit breaker is\n\t * consulted and before `execute` (for example, persisting the attempt\n\t * number). Put every await a caller needs before the call here, so that\n\t * nothing awaits between breaker admission and the external call. If it\n\t * throws, no admission is taken and no breaker outcome is recorded; the\n\t * error is classified with `classifyThrownError` and retried or rethrown\n\t * exactly like an error thrown by `execute`.\n\t */\n\tprepareAttempt?: (attempt: number, maxAttempts: number) => Promise<void> | void;\n\t/**\n\t * Optional circuit breaker. When present, `acquire()` is called\n\t * immediately before every `execute` call, with no await in between, and every attempt's outcome is\n\t * recorded through {@link classifyCircuitBreakerOutcome}. In `enforce` mode\n\t * a refused attempt is not executed and {@link AdapterCircuitOpenError} is\n\t * thrown; if the breaker opens between attempts, retrying stops and the\n\t * same error is thrown. When omitted, behaviour is unchanged.\n\t */\n\tcircuitBreaker?: AdapterCircuitBreaker;\n}\n\nfunction adapterKey(adapterType: string, operation: string): string {\n\treturn `${adapterType}:${operation}`;\n}\n\nexport class MissingAdapterRegistrationError extends Error {\n\tconstructor(adapterType: string, operation: string) {\n\t\tsuper(`No adapter registered for adapterType \"${adapterType}\" and operation \"${operation}\"`);\n\t\tthis.name = \"MissingAdapterRegistrationError\";\n\t}\n}\n\nexport class AdapterRegistry {\n\tprivate readonly adapters = new Map<string, AdapterImplementation>();\n\n\tregister(adapter: AdapterImplementation): void {\n\t\tconst key = adapterKey(adapter.adapterType, adapter.operation);\n\t\tif (this.adapters.has(key)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Adapter already registered for adapterType \"${adapter.adapterType}\" and operation \"${adapter.operation}\"`,\n\t\t\t);\n\t\t}\n\n\t\tthis.adapters.set(key, adapter);\n\t}\n\n\tresolve(adapterType: string, operation: string): AdapterImplementation | undefined {\n\t\treturn this.adapters.get(adapterKey(adapterType, operation));\n\t}\n\n\trequire(adapterType: string, operation: string): AdapterImplementation {\n\t\tconst adapter = this.resolve(adapterType, operation);\n\t\tif (!adapter) {\n\t\t\tthrow new MissingAdapterRegistrationError(adapterType, operation);\n\t\t}\n\n\t\treturn adapter;\n\t}\n}\n\nconst DEFAULT_RETRY_POLICY: ResolvedAdapterRetryPolicy = {\n\tidempotent: true,\n\tmaxAttempts: 3,\n\tinitialDelayMs: 100,\n\tbackoffMultiplier: 2,\n\tmaxDelayMs: 1_000,\n};\n\nconst NO_RETRY_POLICY: ResolvedAdapterRetryPolicy = {\n\tidempotent: false,\n\tmaxAttempts: 1,\n\tinitialDelayMs: 0,\n\tbackoffMultiplier: 1,\n\tmaxDelayMs: 0,\n};\n\nexport function resolveAdapterRetryPolicy(\n\tpolicy: AdapterRetryPolicy | undefined,\n\tdefaultIdempotent: boolean,\n): ResolvedAdapterRetryPolicy {\n\tconst idempotent = policy?.idempotent ?? defaultIdempotent;\n\n\tif (!idempotent) {\n\t\treturn NO_RETRY_POLICY;\n\t}\n\n\treturn {\n\t\tidempotent: true,\n\t\tmaxAttempts: Math.max(1, Math.min(policy?.maxAttempts ?? DEFAULT_RETRY_POLICY.maxAttempts, 5)),\n\t\tinitialDelayMs: Math.max(0, policy?.initialDelayMs ?? DEFAULT_RETRY_POLICY.initialDelayMs),\n\t\tbackoffMultiplier: Math.max(\n\t\t\t1,\n\t\t\tpolicy?.backoffMultiplier ?? DEFAULT_RETRY_POLICY.backoffMultiplier,\n\t\t),\n\t\tmaxDelayMs: Math.max(0, policy?.maxDelayMs ?? DEFAULT_RETRY_POLICY.maxDelayMs),\n\t};\n}\n\nexport function getRetryDelayMs(attempt: number, policy: ResolvedAdapterRetryPolicy): number {\n\tconst exponentialDelay =\n\t\tpolicy.initialDelayMs * policy.backoffMultiplier ** Math.max(0, attempt - 1);\n\n\treturn Math.min(exponentialDelay, policy.maxDelayMs);\n}\n\n/**\n * Classify an {@link AdapterExecutionResult} into an {@link AdapterOutcomeKind}.\n * When `outcome` is explicitly set, it is returned directly, after rejecting a\n * contradictory result where `success` is true but the outcome is non-success.\n * Otherwise a successful result maps to `success` and a failed result maps to\n * `transient_failure` (the historical default).\n */\nexport function classifyAdapterOutcome(result: AdapterExecutionResult): AdapterOutcomeKind {\n\tif (result.outcome) {\n\t\tassertAdapterOutcomeConsistency(result);\n\t\treturn result.outcome;\n\t}\n\treturn result.success ? \"success\" : \"transient_failure\";\n}\n\n/**\n * Determine whether an outcome kind is retryable. Only `transient_failure`\n * is retryable, and only when the adapter policy is idempotent. Permanent\n * failures, timeouts, and ambiguous outcomes are never retried.\n */\nexport function shouldRetryAdapterOutcome(\n\tkind: AdapterOutcomeKind,\n\tidempotent: boolean,\n): boolean {\n\treturn kind === \"transient_failure\" && idempotent;\n}\n\n/**\n * Reject a contradictory adapter result where `success` is true but the\n * explicit `outcome` is a non-success kind. A successful result claiming an\n * ambiguous, permanent, timeout, or transient outcome is a programming\n * error — the adapter cannot have both succeeded and require reconciliation.\n */\nexport function assertAdapterOutcomeConsistency(result: AdapterExecutionResult): void {\n\tif (result.success && result.outcome && result.outcome !== \"success\") {\n\t\tthrow new Error(\n\t\t\t`Adapter returned success=true with contradictory outcome \"${result.outcome}\"; a successful result must not declare a non-success outcome.`,\n\t\t);\n\t}\n}\n\n/**\n * Record an outcome on the breaker. A breaker that throws must not turn a\n * call that already ran (and may have had its external effect) into a failure.\n */\nfunction recordCircuitOutcome(\n\tbreaker: AdapterCircuitBreaker | undefined,\n\toutcome: AdapterCircuitOutcome,\n\tadmission: AdapterCircuitAdmission | undefined,\n): void {\n\tif (!breaker) return;\n\ttry {\n\t\tbreaker.record(outcome, admission);\n\t} catch {\n\t\t// Breaker bookkeeping is best effort once the call has happened.\n\t}\n}\n\nfunction circuitOpenError(\n\tbreaker: AdapterCircuitBreaker,\n\tadmission: AdapterCircuitAdmission,\n\tattempt: number,\n\tlastError: string | undefined,\n): AdapterCircuitOpenError {\n\treturn new AdapterCircuitOpenError({\n\t\tadapterKey: breaker.key,\n\t\tstate: admission.state,\n\t\tattempt,\n\t\t...(admission.retryAfterMs !== undefined ? { retryAfterMs: admission.retryAfterMs } : {}),\n\t\t...(admission.manualOverride !== undefined ? { manualOverride: admission.manualOverride } : {}),\n\t\t...(lastError !== undefined ? { lastError } : {}),\n\t});\n}\n\nexport async function executeWithAdapterRetry<T>(options: ExecuteWithRetryOptions<T>): Promise<T> {\n\tconst policy = resolveAdapterRetryPolicy(options.policy, options.defaultIdempotent);\n\tconst sleep =\n\t\toptions.sleep ?? ((delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs)));\n\tconst breaker = options.circuitBreaker;\n\tlet lastError: string | undefined;\n\n\tfor (let attempt = 1; attempt <= policy.maxAttempts; attempt++) {\n\t\tlet admission: AdapterCircuitAdmission | undefined;\n\t\t// One outcome per attempt reaches the breaker, and only for an attempt\n\t\t// that reached `execute`; a later step of this iteration (onAttempt,\n\t\t// sleep) that throws into the catch below does not record a second one.\n\t\tlet executed = false;\n\t\tlet recorded = false;\n\t\tconst record = (outcome: AdapterCircuitOutcome) => {\n\t\t\tif (!executed || recorded) return;\n\t\t\trecorded = true;\n\t\t\trecordCircuitOutcome(breaker, outcome, admission);\n\t\t};\n\t\tlet refusal: AdapterCircuitAdmission | undefined;\n\t\t/**\n\t\t * Classify an error thrown by `prepareAttempt`, `execute` or a later\n\t\t * step of this attempt. Returns \"retry\" after sleeping, \"rethrow\", or the\n\t\t * breaker admission that refuses the retry.\n\t\t */\n\t\tasync function handleThrown(error: unknown): Promise<\"retry\" | \"rethrow\" | AdapterCircuitAdmission> {\n\t\t\tlastError = error instanceof Error ? error.message : String(error);\n\t\t\t// Preserve the pre-classification contract for direct callers. The\n\t\t\t// governed Host supplies a classifier and therefore remains fail-closed\n\t\t\t// for ambiguous, timeout, and permanent failures.\n\t\t\tconst thrownOutcome = options.classifyThrownError\n\t\t\t\t? options.classifyThrownError(error)\n\t\t\t\t: \"transient_failure\" as const;\n\t\t\trecord(classifyCircuitBreakerOutcome(thrownOutcome));\n\t\t\tlet willRetry =\n\t\t\t\tshouldRetryAdapterOutcome(thrownOutcome, policy.idempotent) && attempt < policy.maxAttempts;\n\t\t\tconst blocked = willRetry ? refusedRetry(breaker) : undefined;\n\t\t\tif (blocked) willRetry = false;\n\t\t\tconst nextDelayMs = willRetry ? getRetryDelayMs(attempt, policy) : undefined;\n\t\t\tawait options.onAttempt?.({ attempt, error: lastError, willRetry, nextDelayMs });\n\n\t\t\tif (blocked) return blocked;\n\t\t\tif (!willRetry) return \"rethrow\";\n\t\t\tawait sleep(nextDelayMs ?? 0);\n\t\t\treturn \"retry\";\n\t\t}\n\n\t\ttry {\n\t\t\tif (options.prepareAttempt) await options.prepareAttempt(attempt, policy.maxAttempts);\n\t\t} catch (error) {\n\t\t\tconst decision = await handleThrown(error);\n\t\t\tif (decision === \"rethrow\") throw error;\n\t\t\tif (decision === \"retry\") continue;\n\t\t\trefusal = decision;\n\t\t}\n\t\tif (!refusal) {\n\t\t\t// Admission sits immediately before the external call: no await between.\n\t\t\tif (breaker) {\n\t\t\t\tadmission = breaker.acquire();\n\t\t\t\tif (!admission.allowed) throw circuitOpenError(breaker, admission, attempt, lastError);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\texecuted = true;\n\t\t\t\tconst result = await options.execute(attempt, policy.maxAttempts);\n\t\t\t\tif (options.isSuccessful(result)) {\n\t\t\t\t\trecord(\"success\");\n\t\t\t\t\tawait options.onAttempt?.({ attempt, result, willRetry: false });\n\t\t\t\t\treturn result;\n\t\t\t\t}\n\n\t\t\t\tlastError = options.getError(result) ?? \"Adapter returned unsuccessful result\";\n\t\t\t\tconst outcome = options.classifyOutcome\n\t\t\t\t\t? options.classifyOutcome(result)\n\t\t\t\t\t: \"transient_failure\" as const;\n\t\t\t\trecord(classifyCircuitBreakerOutcome(outcome));\n\t\t\t\tlet willRetry =\n\t\t\t\t\tshouldRetryAdapterOutcome(outcome, policy.idempotent) && attempt < policy.maxAttempts;\n\t\t\t\trefusal = willRetry ? refusedRetry(breaker) : undefined;\n\t\t\t\tif (refusal) willRetry = false;\n\t\t\t\tconst nextDelayMs = willRetry ? getRetryDelayMs(attempt, policy) : undefined;\n\t\t\t\tawait options.onAttempt?.({ attempt, result, error: lastError, willRetry, nextDelayMs });\n\n\t\t\t\tif (!willRetry && !refusal) {\n\t\t\t\t\treturn result;\n\t\t\t\t}\n\n\t\t\t\tif (!refusal) await sleep(nextDelayMs ?? 0);\n\t\t\t} catch (error) {\n\t\t\t\tconst decision = await handleThrown(error);\n\t\t\t\tif (decision === \"rethrow\") throw error;\n\t\t\t\trefusal = decision === \"retry\" ? undefined : decision;\n\t\t\t}\n\t\t}\n\t\t// The breaker opened on this attempt's outcome: stop instead of retrying.\n\t\tif (refusal && breaker) throw circuitOpenError(breaker, refusal, attempt + 1, lastError);\n\t}\n\n\tthrow new Error(lastError ?? \"Adapter retry exhausted\");\n}\n\n/**\n * Preview whether an `enforce`-mode breaker would refuse the next attempt.\n * Returns the refusing admission, or undefined when the retry may proceed.\n */\nfunction refusedRetry(breaker: AdapterCircuitBreaker | undefined): AdapterCircuitAdmission | undefined {\n\tif (!breaker) return undefined;\n\tconst preview = breaker.check();\n\treturn preview.allowed ? undefined : preview;\n}\n"]}
|