fastfetch-api-fetch-enhancer 2.0.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/.idea/FastFetch-Smart-API-Fetcher.iml +12 -0
- package/.idea/modules.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/LICENSE +21 -0
- package/README.md +101 -0
- package/__tests__/demo.test.ts +216 -0
- package/__tests__/test_database.json +5002 -0
- package/coverage/clover.xml +463 -0
- package/coverage/coverage-final.json +11 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/circuit-breaker.ts.html +547 -0
- package/coverage/lcov-report/client.ts.html +1858 -0
- package/coverage/lcov-report/errors.ts.html +415 -0
- package/coverage/lcov-report/fastFetch.ts.html +1045 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +251 -0
- package/coverage/lcov-report/index.ts.html +241 -0
- package/coverage/lcov-report/metrics.ts.html +685 -0
- package/coverage/lcov-report/middleware.ts.html +403 -0
- package/coverage/lcov-report/offline-queue.ts.html +535 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/queue.ts.html +421 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +196 -0
- package/coverage/lcov-report/streaming.ts.html +466 -0
- package/coverage/lcov.info +908 -0
- package/dist/circuit-breaker.d.ts +61 -0
- package/dist/circuit-breaker.d.ts.map +1 -0
- package/dist/circuit-breaker.js +106 -0
- package/dist/client.d.ts +215 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +391 -0
- package/dist/errors.d.ts +56 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +91 -0
- package/dist/fastFetch.d.ts +65 -0
- package/dist/fastFetch.d.ts.map +1 -0
- package/dist/fastFetch.js +209 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/metrics.d.ts +71 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +131 -0
- package/dist/middleware.d.ts +66 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +45 -0
- package/dist/offline-queue.d.ts +65 -0
- package/dist/offline-queue.d.ts.map +1 -0
- package/dist/offline-queue.js +120 -0
- package/dist/queue.d.ts +33 -0
- package/dist/queue.d.ts.map +1 -0
- package/dist/queue.js +76 -0
- package/dist/streaming.d.ts +40 -0
- package/dist/streaming.d.ts.map +1 -0
- package/dist/streaming.js +98 -0
- package/index.d.ts +167 -0
- package/jest.config.js +16 -0
- package/package.json +55 -0
- package/src/circuit-breaker.ts +154 -0
- package/src/client.ts +591 -0
- package/src/errors.ts +110 -0
- package/src/fastFetch.ts +320 -0
- package/src/index.ts +52 -0
- package/src/metrics.ts +200 -0
- package/src/middleware.ts +106 -0
- package/src/offline-queue.ts +150 -0
- package/src/queue.ts +112 -0
- package/src/streaming.ts +127 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FastFetch Circuit Breaker — Finite State Machine implementation.
|
|
3
|
+
*
|
|
4
|
+
* Prevents cascading failures by "opening" the circuit after a configurable
|
|
5
|
+
* number of consecutive failures, then probing with a single request after
|
|
6
|
+
* the reset timeout before fully re-closing.
|
|
7
|
+
*
|
|
8
|
+
* States:
|
|
9
|
+
* CLOSED — normal operation; every request goes through
|
|
10
|
+
* OPEN — requests are immediately rejected (CircuitOpenError)
|
|
11
|
+
* HALF_OPEN — one probe request is allowed; success → CLOSED, failure → OPEN
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { CircuitOpenError } from "./errors.js";
|
|
15
|
+
|
|
16
|
+
export type CircuitState = "CLOSED" | "OPEN" | "HALF_OPEN";
|
|
17
|
+
|
|
18
|
+
export interface CircuitBreakerOptions {
|
|
19
|
+
/**
|
|
20
|
+
* Number of consecutive failures before the circuit opens (default: 5).
|
|
21
|
+
*/
|
|
22
|
+
threshold?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Milliseconds to wait in the OPEN state before transitioning to HALF_OPEN
|
|
25
|
+
* and allowing a single probe request (default: 30 000 ms = 30 s).
|
|
26
|
+
*/
|
|
27
|
+
timeout?: number;
|
|
28
|
+
/** Called when the circuit transitions CLOSED → OPEN. */
|
|
29
|
+
onOpen?: () => void;
|
|
30
|
+
/** Called when the circuit transitions HALF_OPEN → CLOSED (recovered). */
|
|
31
|
+
onClose?: () => void;
|
|
32
|
+
/** Called when the circuit transitions OPEN → HALF_OPEN (probing). */
|
|
33
|
+
onHalfOpen?: () => void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class CircuitBreaker {
|
|
37
|
+
private _state: CircuitState = "CLOSED";
|
|
38
|
+
private consecutiveFailures = 0;
|
|
39
|
+
private openedAt = 0;
|
|
40
|
+
private probeInFlight = false;
|
|
41
|
+
|
|
42
|
+
private readonly threshold: number;
|
|
43
|
+
private readonly timeoutMs: number;
|
|
44
|
+
private readonly onOpen?: () => void;
|
|
45
|
+
private readonly onClose?: () => void;
|
|
46
|
+
private readonly onHalfOpen?: () => void;
|
|
47
|
+
|
|
48
|
+
constructor(options: CircuitBreakerOptions = {}) {
|
|
49
|
+
this.threshold = options.threshold ?? 5;
|
|
50
|
+
this.timeoutMs = options.timeout ?? 30_000;
|
|
51
|
+
this.onOpen = options.onOpen;
|
|
52
|
+
this.onClose = options.onClose;
|
|
53
|
+
this.onHalfOpen = options.onHalfOpen;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ── State accessor ───────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Current circuit state. Reading this property can trigger an automatic
|
|
60
|
+
* OPEN → HALF_OPEN transition when the reset timeout has elapsed.
|
|
61
|
+
*/
|
|
62
|
+
get state(): CircuitState {
|
|
63
|
+
if (
|
|
64
|
+
this._state === "OPEN" &&
|
|
65
|
+
Date.now() - this.openedAt >= this.timeoutMs
|
|
66
|
+
) {
|
|
67
|
+
this._state = "HALF_OPEN";
|
|
68
|
+
this.probeInFlight = false;
|
|
69
|
+
this.onHalfOpen?.();
|
|
70
|
+
}
|
|
71
|
+
return this._state;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ── Core execute ─────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Execute `fn` through the circuit breaker.
|
|
78
|
+
* - CLOSED → run `fn`, track success/failure
|
|
79
|
+
* - OPEN → throw `CircuitOpenError` immediately (no network call)
|
|
80
|
+
* - HALF_OPEN → allow one probe; success closes, failure re-opens
|
|
81
|
+
*/
|
|
82
|
+
async execute<T>(fn: () => Promise<T>): Promise<T> {
|
|
83
|
+
const currentState = this.state; // may trigger OPEN→HALF_OPEN
|
|
84
|
+
|
|
85
|
+
if (currentState === "OPEN") {
|
|
86
|
+
throw new CircuitOpenError();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (currentState === "HALF_OPEN") {
|
|
90
|
+
// Reject parallel probes — only one probe allowed at a time
|
|
91
|
+
if (this.probeInFlight) {
|
|
92
|
+
throw new CircuitOpenError();
|
|
93
|
+
}
|
|
94
|
+
this.probeInFlight = true;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const result = await fn();
|
|
99
|
+
this.recordSuccess();
|
|
100
|
+
return result;
|
|
101
|
+
} catch (err) {
|
|
102
|
+
this.recordFailure();
|
|
103
|
+
throw err;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ── Private FSM transitions ──────────────────────────────────────────────
|
|
108
|
+
|
|
109
|
+
private recordSuccess(): void {
|
|
110
|
+
this.consecutiveFailures = 0;
|
|
111
|
+
this.probeInFlight = false;
|
|
112
|
+
|
|
113
|
+
if (this._state === "HALF_OPEN") {
|
|
114
|
+
this._state = "CLOSED";
|
|
115
|
+
this.onClose?.();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private recordFailure(): void {
|
|
120
|
+
this.consecutiveFailures++;
|
|
121
|
+
this.probeInFlight = false;
|
|
122
|
+
|
|
123
|
+
if (this._state === "HALF_OPEN") {
|
|
124
|
+
// Probe failed — re-open the circuit
|
|
125
|
+
this._state = "OPEN";
|
|
126
|
+
this.openedAt = Date.now();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (
|
|
131
|
+
this._state === "CLOSED" &&
|
|
132
|
+
this.consecutiveFailures >= this.threshold
|
|
133
|
+
) {
|
|
134
|
+
this._state = "OPEN";
|
|
135
|
+
this.openedAt = Date.now();
|
|
136
|
+
this.onOpen?.();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ── Manual controls ──────────────────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
/** Manually reset the circuit to CLOSED. Useful for testing. */
|
|
143
|
+
reset(): void {
|
|
144
|
+
this._state = "CLOSED";
|
|
145
|
+
this.consecutiveFailures = 0;
|
|
146
|
+
this.openedAt = 0;
|
|
147
|
+
this.probeInFlight = false;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Number of consecutive failures recorded since last success. */
|
|
151
|
+
get failures(): number {
|
|
152
|
+
return this.consecutiveFailures;
|
|
153
|
+
}
|
|
154
|
+
}
|