@fedify/fedify 2.3.0-dev.994 → 2.3.0-pr.809.36
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/README.md +16 -7
- package/dist/{assert-DikXweDx.mjs → assert-OguE97r2.mjs} +1 -1
- package/dist/{assert_instance_of-C4Ri6VuN.mjs → assert_instance_of-DBC5X09g.mjs} +1 -1
- package/dist/{assert_not_equals--wG9hV7u.mjs → assert_not_equals-DkVK8oqV.mjs} +1 -1
- package/dist/{assert_rejects-B-qJtC9Z.mjs → assert_rejects-DN60FHPX.mjs} +28 -3
- package/dist/{assert_strict_equals-Dmjbg-bA.mjs → assert_strict_equals-XEgZAlrj.mjs} +1 -1
- package/dist/{assert_throws-4NwKEy2q.mjs → assert_throws-BOkhLGYc.mjs} +1 -1
- package/dist/{builder-yAlpiIqP.mjs → builder-DSZ7FZmg.mjs} +96 -42
- package/dist/{chunk-nlSIicah.js → chunk-CRNNMoPX.js} +2 -2
- package/dist/circuit-breaker-CSWsyoef.mjs +337 -0
- package/dist/{client-z-8dc-e1.d.cts → client-CAM_bQXx.d.cts} +1 -0
- package/dist/{client-AtlibPOU.d.ts → client-CSddvgWN.d.ts} +1 -2
- package/dist/compat/mod.d.cts +2 -1
- package/dist/compat/mod.d.ts +2 -3
- package/dist/compat/mod.js +3 -3
- package/dist/compat/outgoing-jsonld.test.mjs +4 -4
- package/dist/compat/public-audience.test.mjs +4 -4
- package/dist/compat/transformers.test.mjs +5 -5
- package/dist/{context-BzH2-ajs.d.ts → context-BBVLF7lx.d.cts} +342 -269
- package/dist/{context-DJGagtNd.d.cts → context-BU6jSQdo.d.ts} +344 -266
- package/dist/{context-Dk_tacqz.mjs → context-DVoTs_wM.mjs} +64 -5
- package/dist/{deno-XBVlKnOX.mjs → deno-DCdM7d93.mjs} +1 -1
- package/dist/{docloader-DPp-X6gk.mjs → docloader-B7mVwA5_.mjs} +4 -4
- package/dist/{esm-DVILvP5e.mjs → esm-vrlUxr60.mjs} +4 -7
- package/dist/federation/builder.test.mjs +163 -11
- package/dist/federation/circuit-breaker.test.d.mts +2 -0
- package/dist/federation/circuit-breaker.test.mjs +446 -0
- package/dist/federation/collection.test.mjs +3 -3
- package/dist/federation/handler.test.mjs +1770 -29
- package/dist/federation/idempotency.test.mjs +5 -5
- package/dist/federation/inbox.test.mjs +4 -4
- package/dist/federation/keycache.test.mjs +5 -5
- package/dist/federation/kv.test.mjs +2 -2
- package/dist/federation/metrics.test.d.mts +2 -0
- package/dist/federation/metrics.test.mjs +634 -0
- package/dist/federation/middleware.test.mjs +4036 -146
- package/dist/federation/mod.cjs +195 -14
- package/dist/federation/mod.d.cts +6 -4
- package/dist/federation/mod.d.ts +6 -6
- package/dist/federation/mod.js +192 -14
- package/dist/federation/mq.test.mjs +176 -15
- package/dist/federation/negotiation.test.mjs +4 -4
- package/dist/federation/retry.test.mjs +3 -3
- package/dist/federation/router.test.mjs +190 -9
- package/dist/federation/send.test.mjs +153 -15
- package/dist/federation/temporal.test.d.mts +2 -0
- package/dist/federation/temporal.test.mjs +71 -0
- package/dist/federation/webfinger.test.mjs +150 -5
- package/dist/{http-CSlLA54v.mjs → http-BufbkLuW.mjs} +146 -47
- package/dist/{http-B4rrzxtg.js → http-Dh18nwJg.js} +994 -64
- package/dist/{http-aQzN9Ayi.d.ts → http-VyDTd4G3.d.cts} +15 -3
- package/dist/{http-BrGqJDXL.cjs → http-gOiudB3g.cjs} +1099 -61
- package/dist/{http-CrGuipxe.d.cts → http-lf8Hsd91.d.ts} +15 -1
- package/dist/{key-Bzx--sHD.mjs → key-DZdKLJXT.mjs} +43 -18
- package/dist/{kv-CbLNp3zQ.d.cts → kv-D6hNiMTK.d.ts} +1 -0
- package/dist/{kv-cache-DI-Sk7-I.cjs → kv-cache-BuK_nlpY.cjs} +20 -3
- package/dist/{kv-cache-U__xU4qR.mjs → kv-cache-C7SQnkGI.mjs} +21 -3
- package/dist/{kv-cache-EMIqoIuB.js → kv-cache-DNDSb6Po.js} +20 -3
- package/dist/{kv-GFYnFoOl.d.ts → kv-gJ8LYbxX.d.cts} +1 -3
- package/dist/ld-0Lsznacf.mjs +626 -0
- package/dist/metrics-BHeWd24f.mjs +815 -0
- package/dist/{middleware-zjZ6AFCW.mjs → middleware-BNANvDh2.mjs} +1 -1
- package/dist/{middleware-nidH7VZH.js → middleware-BgzRkDcm.js} +2260 -556
- package/dist/{middleware-An4DjES4.cjs → middleware-D-iES1nQ.cjs} +2318 -623
- package/dist/{middleware-K1g6bEkV.mjs → middleware-EV6J_eAc.mjs} +1639 -373
- package/dist/{mod-CR8soWa9.d.ts → mod-B0hW12_O.d.cts} +26 -4
- package/dist/mod-C0F6kvgS.d.cts +182 -0
- package/dist/{mod-Cr3f-ACa.d.cts → mod-COIAjwRS.d.ts} +26 -2
- package/dist/{mod-CMEbIaNh.d.cts → mod-DFvNJcNb.d.ts} +56 -4
- package/dist/mod-vPYVoa5n.d.ts +182 -0
- package/dist/{mod-CLgIXe9w.d.ts → mod-yvIXFAEi.d.cts} +56 -6
- package/dist/mod.cjs +9 -6
- package/dist/mod.d.cts +12 -10
- package/dist/mod.d.ts +12 -12
- package/dist/mod.js +11 -11
- package/dist/mq-D-nlpY04.d.ts +208 -0
- package/dist/mq-D8uSFzxe.d.cts +208 -0
- package/dist/{negotiation-SQvQgUqe.mjs → negotiation-DDstyBvc.mjs} +29 -0
- package/dist/nodeinfo/client.test.mjs +4 -4
- package/dist/nodeinfo/handler.test.mjs +4 -4
- package/dist/nodeinfo/mod.d.cts +2 -1
- package/dist/nodeinfo/mod.d.ts +2 -3
- package/dist/nodeinfo/mod.js +3 -3
- package/dist/nodeinfo/types.test.mjs +3 -3
- package/dist/otel/exporter.test.mjs +28 -25
- package/dist/otel/mod.cjs +6 -5
- package/dist/otel/mod.d.cts +5 -3
- package/dist/otel/mod.d.ts +5 -5
- package/dist/otel/mod.js +8 -7
- package/dist/{outgoing-jsonld-CNmZLixq.mjs → outgoing-jsonld-L_DbOaFe.mjs} +2 -2
- package/dist/{owner-C-tqfvxn.mjs → owner-Cl-1cAR8.mjs} +2 -2
- package/dist/{owner-CptqhsOy.d.cts → owner-CnngXDNJ.d.ts} +2 -1
- package/dist/{owner-74ARJ5TL.d.ts → owner-DEvZuyOE.d.cts} +2 -3
- package/dist/{proof-Bbbwf8_x.js → proof-CyXndf6-.js} +378 -15
- package/dist/{proof-BlmRPzrK.mjs → proof-Dnz8jtiQ.mjs} +26 -8
- package/dist/{proof-BW89QMVB.cjs → proof-tz35unOj.cjs} +432 -15
- package/dist/runtime/mod.d.cts +1 -0
- package/dist/runtime/mod.d.ts +1 -2
- package/dist/runtime/mod.js +3 -3
- package/dist/{send-05pJLcb6.mjs → send-DIbBYN5P.mjs} +72 -26
- package/dist/sig/http.test.mjs +356 -33
- package/dist/sig/key.test.mjs +103 -6
- package/dist/sig/ld.test.mjs +696 -7
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.d.cts +4 -3
- package/dist/sig/mod.d.ts +4 -5
- package/dist/sig/mod.js +4 -4
- package/dist/sig/owner.test.mjs +6 -6
- package/dist/sig/proof.test.mjs +169 -8
- package/dist/{std__assert-CRDpx_HF.mjs → std__assert-BBjXFNOb.mjs} +5 -30
- package/dist/temporal-BEwob1Vg.mjs +95 -0
- package/dist/testing/mod.d.mts +110 -10
- package/dist/testing/mod.mjs +1 -2
- package/dist/{transformers-ve6e2xcg.js → transformers-BGMIq1cs.js} +2 -2
- package/dist/{types-hvL8ElAs.js → types-CAY3OdLq.js} +2 -2
- package/dist/utils/docloader.test.mjs +6 -6
- package/dist/utils/kv-cache.test.mjs +67 -2
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.d.cts +2 -1
- package/dist/utils/mod.d.ts +2 -3
- package/dist/utils/mod.js +3 -3
- package/dist/vocab/cjs.test.mjs +1 -1
- package/dist/vocab/mod.d.cts +1 -0
- package/dist/vocab/mod.d.ts +1 -2
- package/dist/vocab/mod.js +2 -2
- package/package.json +16 -16
- package/dist/ld-DR78beiv.mjs +0 -279
- package/dist/middleware-BFBaR0mF.cjs +0 -4
- package/dist/mod-2d12ffz3.d.ts +0 -64
- package/dist/mod-D35TRn09.d.cts +0 -62
- package/dist/router-CrMLXoOr.mjs +0 -114
- package/dist/{activity-listener-ell7W1s9.mjs → activity-listener-tztVvlNb.mjs} +0 -0
- package/dist/{assert_equals-Ew3jOFa3.mjs → assert_equals-C-ZRDbaf.mjs} +0 -0
- package/dist/{client-D_1QpnWt.mjs → client-ByXmQhYD.mjs} +1 -1
- package/dist/{collection-D-HqUuA2.mjs → collection-Cc3DVAhE.mjs} +0 -0
- package/dist/{keycache-EGATflN-.mjs → keycache-BeU0LCII.mjs} +0 -0
- package/dist/{public-audience-DYFHzm_c.mjs → public-audience-Cvbr2Gzt.mjs} +1 -1
- /package/dist/{retry-bMXBL97A.mjs → retry-CXg_MBI-.mjs} +0 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import { Temporal } from "@js-temporal/polyfill";
|
|
2
|
+
import "urlpattern-polyfill";
|
|
3
|
+
globalThis.addEventListener = () => {};
|
|
4
|
+
import { getLogger } from "@logtape/logtape";
|
|
5
|
+
//#region src/federation/circuit-breaker.ts
|
|
6
|
+
const MAX_CUSTOM_FAILURE_HISTORY = 100;
|
|
7
|
+
/**
|
|
8
|
+
* Tracks reachability state for remote outbox delivery hosts.
|
|
9
|
+
* @since 2.3.0
|
|
10
|
+
*/
|
|
11
|
+
var CircuitBreaker = class {
|
|
12
|
+
#kv;
|
|
13
|
+
#prefix;
|
|
14
|
+
#options;
|
|
15
|
+
#now;
|
|
16
|
+
#stateChangeObserver;
|
|
17
|
+
constructor(options) {
|
|
18
|
+
this.#kv = options.kv;
|
|
19
|
+
this.#prefix = options.prefix;
|
|
20
|
+
this.#options = normalizeCircuitBreakerOptions(options.options ?? {});
|
|
21
|
+
this.#now = options.now ?? (() => Temporal.Now.instant());
|
|
22
|
+
this.#stateChangeObserver = options.stateChangeObserver;
|
|
23
|
+
}
|
|
24
|
+
get options() {
|
|
25
|
+
return this.#options;
|
|
26
|
+
}
|
|
27
|
+
capHeldDelay(heldSince, delay) {
|
|
28
|
+
const now = this.#now();
|
|
29
|
+
return now.until(this.#capHeldRetryAt(now, heldSince, now.add(delay)));
|
|
30
|
+
}
|
|
31
|
+
async beforeSend(remoteHost, message) {
|
|
32
|
+
const heldSince = parseHeldSince(message.circuitHeldSince);
|
|
33
|
+
const now = this.#now();
|
|
34
|
+
if (heldSince != null && Temporal.Instant.compare(heldSince.add(this.#options.heldActivityTtl), now) <= 0) return {
|
|
35
|
+
type: "drop",
|
|
36
|
+
heldSince
|
|
37
|
+
};
|
|
38
|
+
let lastConflictingState;
|
|
39
|
+
for (let attempt = 0; attempt < 10; attempt++) {
|
|
40
|
+
const oldState = await this.#get(remoteHost);
|
|
41
|
+
if (oldState == null || oldState.state === "closed") return {
|
|
42
|
+
type: "send",
|
|
43
|
+
probe: false
|
|
44
|
+
};
|
|
45
|
+
if (oldState.state === "half-open") {
|
|
46
|
+
const halfOpened = oldState.halfOpened == null ? void 0 : Temporal.Instant.from(oldState.halfOpened);
|
|
47
|
+
if (halfOpened != null) {
|
|
48
|
+
const staleAt = halfOpened.add(this.#options.recoveryDelay);
|
|
49
|
+
if (Temporal.Instant.compare(now, staleAt) < 0) {
|
|
50
|
+
const releaseAt = now.add(this.#options.releaseInterval);
|
|
51
|
+
const retryAt = Temporal.Instant.compare(releaseAt, staleAt) < 0 ? releaseAt : staleAt;
|
|
52
|
+
const cappedRetryAt = this.#capHeldRetryAt(now, heldSince, retryAt);
|
|
53
|
+
return {
|
|
54
|
+
type: "hold",
|
|
55
|
+
state: "half-open",
|
|
56
|
+
delay: now.until(cappedRetryAt),
|
|
57
|
+
heldSince: heldSince ?? now
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const newState = {
|
|
62
|
+
...oldState,
|
|
63
|
+
state: "half-open",
|
|
64
|
+
halfOpened: now.toString()
|
|
65
|
+
};
|
|
66
|
+
if (await this.#replace(remoteHost, oldState, newState)) return {
|
|
67
|
+
type: "send",
|
|
68
|
+
probe: true
|
|
69
|
+
};
|
|
70
|
+
lastConflictingState = "half-open";
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const probeAt = (oldState.opened == null ? now : Temporal.Instant.from(oldState.opened)).add(this.#options.recoveryDelay);
|
|
74
|
+
if (Temporal.Instant.compare(now, probeAt) < 0) {
|
|
75
|
+
const retryAt = this.#capHeldRetryAt(now, heldSince, probeAt);
|
|
76
|
+
return {
|
|
77
|
+
type: "hold",
|
|
78
|
+
state: "open",
|
|
79
|
+
delay: now.until(retryAt),
|
|
80
|
+
heldSince: heldSince ?? now
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
const newState = {
|
|
84
|
+
...oldState,
|
|
85
|
+
state: "half-open",
|
|
86
|
+
halfOpened: now.toString()
|
|
87
|
+
};
|
|
88
|
+
if (await this.#replace(remoteHost, oldState, newState)) {
|
|
89
|
+
await this.#notifyStateChange(remoteHost, "open", "half-open");
|
|
90
|
+
return {
|
|
91
|
+
type: "send",
|
|
92
|
+
probe: true,
|
|
93
|
+
stateChange: {
|
|
94
|
+
previousState: "open",
|
|
95
|
+
newState: "half-open"
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
lastConflictingState = "open";
|
|
100
|
+
}
|
|
101
|
+
if (lastConflictingState != null) {
|
|
102
|
+
const retryAt = this.#capHeldRetryAt(now, heldSince, now.add(this.#options.releaseInterval));
|
|
103
|
+
return {
|
|
104
|
+
type: "hold",
|
|
105
|
+
state: lastConflictingState,
|
|
106
|
+
delay: now.until(retryAt),
|
|
107
|
+
heldSince: heldSince ?? now
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
throw new Error(`Failed to update circuit breaker state for ${remoteHost}`);
|
|
111
|
+
}
|
|
112
|
+
async recordSuccess(remoteHost) {
|
|
113
|
+
for (let attempt = 0; attempt < 10; attempt++) {
|
|
114
|
+
const oldState = await this.#get(remoteHost);
|
|
115
|
+
if (oldState == null) return void 0;
|
|
116
|
+
if (await this.#replace(remoteHost, oldState, void 0)) {
|
|
117
|
+
if (oldState.state !== "closed") {
|
|
118
|
+
await this.#notifyStateChange(remoteHost, oldState.state, "closed");
|
|
119
|
+
return {
|
|
120
|
+
previousState: oldState.state,
|
|
121
|
+
newState: "closed"
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
throw new Error(`Failed to update circuit breaker state for ${remoteHost}`);
|
|
128
|
+
}
|
|
129
|
+
async recordReachableFailure(remoteHost) {
|
|
130
|
+
return await this.recordSuccess(remoteHost);
|
|
131
|
+
}
|
|
132
|
+
async recordFailure(remoteHost) {
|
|
133
|
+
const now = this.#now();
|
|
134
|
+
for (let attempt = 0; attempt < 10; attempt++) {
|
|
135
|
+
const oldState = await this.#get(remoteHost);
|
|
136
|
+
if (oldState?.state === "open") return void 0;
|
|
137
|
+
const oldFailures = oldState?.failures.map(Temporal.Instant.from) ?? [];
|
|
138
|
+
const failures = this.#options.pruneFailures([...oldFailures, now], now);
|
|
139
|
+
let newState;
|
|
140
|
+
let transition;
|
|
141
|
+
if (oldState?.state === "half-open" || this.#options.failure(failures)) {
|
|
142
|
+
newState = {
|
|
143
|
+
state: "open",
|
|
144
|
+
failures: failures.map((t) => t.toString()),
|
|
145
|
+
opened: now.toString()
|
|
146
|
+
};
|
|
147
|
+
transition = [oldState?.state ?? "closed", "open"];
|
|
148
|
+
} else newState = {
|
|
149
|
+
state: "closed",
|
|
150
|
+
failures: failures.map((t) => t.toString())
|
|
151
|
+
};
|
|
152
|
+
if (await this.#replace(remoteHost, oldState, newState)) {
|
|
153
|
+
if (transition != null) {
|
|
154
|
+
await this.#notifyStateChange(remoteHost, transition[0], transition[1]);
|
|
155
|
+
return {
|
|
156
|
+
previousState: transition[0],
|
|
157
|
+
newState: transition[1]
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
throw new Error(`Failed to update circuit breaker state for ${remoteHost}`);
|
|
164
|
+
}
|
|
165
|
+
async dropActivity(remoteHost, details) {
|
|
166
|
+
try {
|
|
167
|
+
await this.#options.onActivityDrop?.(remoteHost, details);
|
|
168
|
+
} catch (error) {
|
|
169
|
+
getLogger([
|
|
170
|
+
"fedify",
|
|
171
|
+
"federation",
|
|
172
|
+
"circuit"
|
|
173
|
+
]).error("An unexpected error occurred in circuit breaker activity drop handler:\n{error}", {
|
|
174
|
+
remoteHost,
|
|
175
|
+
error
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async getState(remoteHost) {
|
|
180
|
+
return await this.#get(remoteHost);
|
|
181
|
+
}
|
|
182
|
+
#key(remoteHost) {
|
|
183
|
+
return [...this.#prefix, remoteHost];
|
|
184
|
+
}
|
|
185
|
+
#capHeldRetryAt(now, heldSince, retryAt) {
|
|
186
|
+
const expiresAt = (heldSince ?? now).add(this.#options.heldActivityTtl);
|
|
187
|
+
return Temporal.Instant.compare(expiresAt, retryAt) < 0 ? expiresAt : retryAt;
|
|
188
|
+
}
|
|
189
|
+
async #get(remoteHost) {
|
|
190
|
+
return parseCircuitBreakerKvState(await this.#kv.get(this.#key(remoteHost)));
|
|
191
|
+
}
|
|
192
|
+
async #replace(remoteHost, oldState, newState) {
|
|
193
|
+
const key = this.#key(remoteHost);
|
|
194
|
+
if (this.#kv.cas == null) {
|
|
195
|
+
if (newState == null) await this.#kv.delete(key);
|
|
196
|
+
else await this.#kv.set(key, newState);
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
return await this.#kv.cas(key, oldState, newState);
|
|
200
|
+
}
|
|
201
|
+
async #notifyStateChange(remoteHost, previousState, newState) {
|
|
202
|
+
try {
|
|
203
|
+
await this.#options.onStateChange?.(remoteHost, previousState, newState);
|
|
204
|
+
} catch (error) {
|
|
205
|
+
getLogger([
|
|
206
|
+
"fedify",
|
|
207
|
+
"federation",
|
|
208
|
+
"circuit"
|
|
209
|
+
]).error("An unexpected error occurred in circuit breaker state change handler:\n{error}", {
|
|
210
|
+
remoteHost,
|
|
211
|
+
previousState,
|
|
212
|
+
newState,
|
|
213
|
+
error
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
await this.#stateChangeObserver?.(remoteHost, previousState, newState);
|
|
218
|
+
} catch (error) {
|
|
219
|
+
getLogger([
|
|
220
|
+
"fedify",
|
|
221
|
+
"federation",
|
|
222
|
+
"circuit"
|
|
223
|
+
]).error("An unexpected error occurred in circuit breaker state change observer:\n{error}", {
|
|
224
|
+
remoteHost,
|
|
225
|
+
previousState,
|
|
226
|
+
newState,
|
|
227
|
+
error
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
/**
|
|
233
|
+
* Normalizes user-provided circuit breaker options into the internal policy
|
|
234
|
+
* shape used while processing queued outbox deliveries.
|
|
235
|
+
*
|
|
236
|
+
* @param options The public circuit breaker options supplied to Fedify.
|
|
237
|
+
* @returns The normalized failure predicate, failure pruning function,
|
|
238
|
+
* duration values, and optional callbacks with defaults applied.
|
|
239
|
+
* @throws {RangeError} If any configured duration is not positive.
|
|
240
|
+
* @throws {TypeError} If `failureThreshold` is not a positive integer.
|
|
241
|
+
*/
|
|
242
|
+
function normalizeCircuitBreakerOptions(options) {
|
|
243
|
+
const recoveryDelay = toInstantDuration(options.recoveryDelay ?? { minutes: 30 });
|
|
244
|
+
const heldActivityTtl = toInstantDuration(options.heldActivityTtl ?? { hours: 168 });
|
|
245
|
+
const releaseInterval = toInstantDuration(options.releaseInterval ?? { seconds: 1 });
|
|
246
|
+
assertPositiveDuration(recoveryDelay, "recoveryDelay");
|
|
247
|
+
assertPositiveDuration(heldActivityTtl, "heldActivityTtl");
|
|
248
|
+
assertPositiveDuration(releaseInterval, "releaseInterval");
|
|
249
|
+
let failure;
|
|
250
|
+
let pruneFailures;
|
|
251
|
+
if (options.failure == null) {
|
|
252
|
+
const failureThreshold = options.failureThreshold ?? 5;
|
|
253
|
+
if (!Number.isInteger(failureThreshold) || failureThreshold <= 0) throw new TypeError("failureThreshold must be a positive integer.");
|
|
254
|
+
const failureWindow = toInstantDuration(options.failureWindow ?? { minutes: 10 });
|
|
255
|
+
assertPositiveDuration(failureWindow, "failureWindow");
|
|
256
|
+
pruneFailures = (timestamps, now) => {
|
|
257
|
+
const earliest = now.subtract(failureWindow);
|
|
258
|
+
return timestamps.filter((timestamp) => Temporal.Instant.compare(timestamp, earliest) >= 0).slice(-failureThreshold);
|
|
259
|
+
};
|
|
260
|
+
failure = (timestamps) => {
|
|
261
|
+
if (timestamps.length < failureThreshold) return false;
|
|
262
|
+
const first = timestamps[timestamps.length - failureThreshold];
|
|
263
|
+
const last = timestamps[timestamps.length - 1];
|
|
264
|
+
return Temporal.Duration.compare(first.until(last), failureWindow) <= 0;
|
|
265
|
+
};
|
|
266
|
+
} else {
|
|
267
|
+
failure = options.failure;
|
|
268
|
+
pruneFailures = (timestamps) => timestamps.slice(-MAX_CUSTOM_FAILURE_HISTORY);
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
failure,
|
|
272
|
+
pruneFailures,
|
|
273
|
+
recoveryDelay,
|
|
274
|
+
heldActivityTtl,
|
|
275
|
+
releaseInterval,
|
|
276
|
+
onStateChange: options.onStateChange,
|
|
277
|
+
onActivityDrop: options.onActivityDrop
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function toInstantDuration(duration) {
|
|
281
|
+
const parsed = Temporal.Duration.from(duration);
|
|
282
|
+
return Temporal.Duration.from({ milliseconds: Math.trunc(parsed.total({
|
|
283
|
+
unit: "millisecond",
|
|
284
|
+
relativeTo: Temporal.PlainDateTime.from("2026-01-01T00:00:00")
|
|
285
|
+
})) });
|
|
286
|
+
}
|
|
287
|
+
function assertPositiveDuration(duration, name) {
|
|
288
|
+
if (Temporal.Duration.compare(duration, { seconds: 0 }) <= 0) throw new RangeError(`${name} must be a positive duration.`);
|
|
289
|
+
}
|
|
290
|
+
function parseHeldSince(value) {
|
|
291
|
+
if (value == null) return void 0;
|
|
292
|
+
try {
|
|
293
|
+
return Temporal.Instant.from(value);
|
|
294
|
+
} catch (error) {
|
|
295
|
+
getLogger([
|
|
296
|
+
"fedify",
|
|
297
|
+
"federation",
|
|
298
|
+
"circuit"
|
|
299
|
+
]).warn("Invalid circuitHeldSince value in queued outbox message: {value}", {
|
|
300
|
+
value,
|
|
301
|
+
error
|
|
302
|
+
});
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Parses a value loaded from the circuit breaker KV store.
|
|
308
|
+
*
|
|
309
|
+
* @param value The raw KV value to validate.
|
|
310
|
+
* @returns A circuit breaker state when `value` has a recognized state and
|
|
311
|
+
* valid instant strings, or `undefined` when the stored value is malformed.
|
|
312
|
+
*/
|
|
313
|
+
function parseCircuitBreakerKvState(value) {
|
|
314
|
+
const isInstantString = (v) => {
|
|
315
|
+
if (typeof v !== "string") return false;
|
|
316
|
+
try {
|
|
317
|
+
Temporal.Instant.from(v);
|
|
318
|
+
return true;
|
|
319
|
+
} catch {
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
if (typeof value !== "object" || value == null) return void 0;
|
|
324
|
+
const record = value;
|
|
325
|
+
if (record.state !== "closed" && record.state !== "open" && record.state !== "half-open") return;
|
|
326
|
+
if (!Array.isArray(record.failures) || !record.failures.every((failure) => isInstantString(failure))) return;
|
|
327
|
+
if (record.opened != null && !isInstantString(record.opened)) return;
|
|
328
|
+
if (record.halfOpened != null && !isInstantString(record.halfOpened)) return;
|
|
329
|
+
return {
|
|
330
|
+
state: record.state,
|
|
331
|
+
failures: record.failures,
|
|
332
|
+
...record.opened == null ? {} : { opened: record.opened },
|
|
333
|
+
...record.halfOpened == null ? {} : { halfOpened: record.halfOpened }
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
//#endregion
|
|
337
|
+
export { normalizeCircuitBreakerOptions as n, parseCircuitBreakerKvState as r, CircuitBreaker as t };
|
package/dist/compat/mod.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
/// <reference lib="esnext.temporal" />
|
|
2
|
+
import { jt as ActivityTransformer, n as Context } from "../context-BBVLF7lx.cjs";
|
|
2
3
|
import { Activity } from "@fedify/vocab";
|
|
3
4
|
|
|
4
5
|
//#region src/compat/transformers.d.ts
|
package/dist/compat/mod.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import { Ot as ActivityTransformer, n as Context } from "../context-BzH2-ajs.js";
|
|
1
|
+
/// <reference lib="esnext.temporal" />
|
|
2
|
+
import { jt as ActivityTransformer, n as Context } from "../context-BU6jSQdo.js";
|
|
4
3
|
import { Activity } from "@fedify/vocab";
|
|
5
4
|
|
|
6
5
|
//#region src/compat/transformers.d.ts
|
package/dist/compat/mod.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import "@js-temporal/polyfill";
|
|
2
|
-
import "urlpattern-polyfill";
|
|
3
|
-
import { n as autoIdAssigner, r as getDefaultActivityTransformers, t as actorDehydrator } from "../transformers-
|
|
1
|
+
import { Temporal } from "@js-temporal/polyfill";
|
|
2
|
+
import { URLPattern } from "urlpattern-polyfill";
|
|
3
|
+
import { n as autoIdAssigner, r as getDefaultActivityTransformers, t as actorDehydrator } from "../transformers-BGMIq1cs.js";
|
|
4
4
|
export { actorDehydrator, autoIdAssigner, getDefaultActivityTransformers };
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { t as assertEquals } from "../assert_equals-
|
|
5
|
-
import { t as assertStrictEquals } from "../assert_strict_equals-
|
|
6
|
-
import { n as normalizeAttachmentArrays, r as normalizeOutgoingActivityJsonLd, t as isPreloadedContextAttachmentSafe } from "../outgoing-jsonld-
|
|
7
|
-
import { mockDocumentLoader, test } from "@fedify/fixture";
|
|
4
|
+
import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
|
|
5
|
+
import { t as assertStrictEquals } from "../assert_strict_equals-XEgZAlrj.mjs";
|
|
6
|
+
import { n as normalizeAttachmentArrays, r as normalizeOutgoingActivityJsonLd, t as isPreloadedContextAttachmentSafe } from "../outgoing-jsonld-L_DbOaFe.mjs";
|
|
8
7
|
import { Create, Document, Note, PUBLIC_COLLECTION } from "@fedify/vocab";
|
|
8
|
+
import { mockDocumentLoader, test } from "@fedify/fixture";
|
|
9
9
|
//#region src/compat/outgoing-jsonld.test.ts
|
|
10
10
|
test("normalizeAttachmentArrays() wraps scalar attachments", async () => {
|
|
11
11
|
const object = (await normalizeAttachmentArrays({
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { t as assertEquals } from "../assert_equals-
|
|
5
|
-
import { t as assertNotEquals } from "../assert_not_equals
|
|
6
|
-
import { t as normalizePublicAudience } from "../public-audience-
|
|
7
|
-
import { test } from "@fedify/fixture";
|
|
4
|
+
import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
|
|
5
|
+
import { t as assertNotEquals } from "../assert_not_equals-DkVK8oqV.mjs";
|
|
6
|
+
import { t as normalizePublicAudience } from "../public-audience-Cvbr2Gzt.mjs";
|
|
8
7
|
import { Create, Note, PUBLIC_COLLECTION } from "@fedify/vocab";
|
|
8
|
+
import { test } from "@fedify/fixture";
|
|
9
9
|
import { getDocumentLoader, preloadedContexts } from "@fedify/vocab-runtime";
|
|
10
10
|
//#region src/compat/public-audience.test.ts
|
|
11
11
|
const PUBLIC_URI = PUBLIC_COLLECTION.href;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { t as assertEquals } from "../assert_equals-
|
|
5
|
-
import { t as assertInstanceOf } from "../assert_instance_of-
|
|
6
|
-
import { t as assert } from "../assert-
|
|
4
|
+
import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
|
|
5
|
+
import { t as assertInstanceOf } from "../assert_instance_of-DBC5X09g.mjs";
|
|
6
|
+
import { t as assert } from "../assert-OguE97r2.mjs";
|
|
7
7
|
import { t as MemoryKvStore } from "../kv-rV3vodCc.mjs";
|
|
8
|
-
import {
|
|
9
|
-
import { test } from "@fedify/fixture";
|
|
8
|
+
import { b as autoIdAssigner, n as FederationImpl, y as actorDehydrator } from "../middleware-EV6J_eAc.mjs";
|
|
10
9
|
import { Follow, Person } from "@fedify/vocab";
|
|
10
|
+
import { test } from "@fedify/fixture";
|
|
11
11
|
//#region src/compat/transformers.test.ts
|
|
12
12
|
const context = new FederationImpl({ kv: new MemoryKvStore() }).createContext(new URL("http://example.com/"));
|
|
13
13
|
test("autoIdAssigner", async () => {
|