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.
Files changed (72) hide show
  1. package/.idea/FastFetch-Smart-API-Fetcher.iml +12 -0
  2. package/.idea/modules.xml +8 -0
  3. package/.idea/vcs.xml +6 -0
  4. package/LICENSE +21 -0
  5. package/README.md +101 -0
  6. package/__tests__/demo.test.ts +216 -0
  7. package/__tests__/test_database.json +5002 -0
  8. package/coverage/clover.xml +463 -0
  9. package/coverage/coverage-final.json +11 -0
  10. package/coverage/lcov-report/base.css +224 -0
  11. package/coverage/lcov-report/block-navigation.js +87 -0
  12. package/coverage/lcov-report/circuit-breaker.ts.html +547 -0
  13. package/coverage/lcov-report/client.ts.html +1858 -0
  14. package/coverage/lcov-report/errors.ts.html +415 -0
  15. package/coverage/lcov-report/fastFetch.ts.html +1045 -0
  16. package/coverage/lcov-report/favicon.png +0 -0
  17. package/coverage/lcov-report/index.html +251 -0
  18. package/coverage/lcov-report/index.ts.html +241 -0
  19. package/coverage/lcov-report/metrics.ts.html +685 -0
  20. package/coverage/lcov-report/middleware.ts.html +403 -0
  21. package/coverage/lcov-report/offline-queue.ts.html +535 -0
  22. package/coverage/lcov-report/prettify.css +1 -0
  23. package/coverage/lcov-report/prettify.js +2 -0
  24. package/coverage/lcov-report/queue.ts.html +421 -0
  25. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  26. package/coverage/lcov-report/sorter.js +196 -0
  27. package/coverage/lcov-report/streaming.ts.html +466 -0
  28. package/coverage/lcov.info +908 -0
  29. package/dist/circuit-breaker.d.ts +61 -0
  30. package/dist/circuit-breaker.d.ts.map +1 -0
  31. package/dist/circuit-breaker.js +106 -0
  32. package/dist/client.d.ts +215 -0
  33. package/dist/client.d.ts.map +1 -0
  34. package/dist/client.js +391 -0
  35. package/dist/errors.d.ts +56 -0
  36. package/dist/errors.d.ts.map +1 -0
  37. package/dist/errors.js +91 -0
  38. package/dist/fastFetch.d.ts +65 -0
  39. package/dist/fastFetch.d.ts.map +1 -0
  40. package/dist/fastFetch.js +209 -0
  41. package/dist/index.d.ts +18 -0
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +18 -0
  44. package/dist/metrics.d.ts +71 -0
  45. package/dist/metrics.d.ts.map +1 -0
  46. package/dist/metrics.js +131 -0
  47. package/dist/middleware.d.ts +66 -0
  48. package/dist/middleware.d.ts.map +1 -0
  49. package/dist/middleware.js +45 -0
  50. package/dist/offline-queue.d.ts +65 -0
  51. package/dist/offline-queue.d.ts.map +1 -0
  52. package/dist/offline-queue.js +120 -0
  53. package/dist/queue.d.ts +33 -0
  54. package/dist/queue.d.ts.map +1 -0
  55. package/dist/queue.js +76 -0
  56. package/dist/streaming.d.ts +40 -0
  57. package/dist/streaming.d.ts.map +1 -0
  58. package/dist/streaming.js +98 -0
  59. package/index.d.ts +167 -0
  60. package/jest.config.js +16 -0
  61. package/package.json +55 -0
  62. package/src/circuit-breaker.ts +154 -0
  63. package/src/client.ts +591 -0
  64. package/src/errors.ts +110 -0
  65. package/src/fastFetch.ts +320 -0
  66. package/src/index.ts +52 -0
  67. package/src/metrics.ts +200 -0
  68. package/src/middleware.ts +106 -0
  69. package/src/offline-queue.ts +150 -0
  70. package/src/queue.ts +112 -0
  71. package/src/streaming.ts +127 -0
  72. package/tsconfig.json +18 -0
@@ -0,0 +1,12 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="WEB_MODULE" version="4">
3
+ <component name="NewModuleRootManager">
4
+ <content url="file://$MODULE_DIR$">
5
+ <excludeFolder url="file://$MODULE_DIR$/.tmp" />
6
+ <excludeFolder url="file://$MODULE_DIR$/temp" />
7
+ <excludeFolder url="file://$MODULE_DIR$/tmp" />
8
+ </content>
9
+ <orderEntry type="inheritedJdk" />
10
+ <orderEntry type="sourceFolder" forTests="false" />
11
+ </component>
12
+ </module>
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/FastFetch-Smart-API-Fetcher.iml" filepath="$PROJECT_DIR$/.idea/FastFetch-Smart-API-Fetcher.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
package/.idea/vcs.xml ADDED
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="$PROJECT_DIR$" vcs="Git" />
5
+ </component>
6
+ </project>
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Keshav-005
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,101 @@
1
+ # FastFetch API Enhancer
2
+
3
+ FastFetch is a production-grade, enterprise-ready HTTP client built around the native `fetch` API. It transforms simple HTTP requests into robust network architecture by introducing advanced resilience patterns, high-concurrency request management, and real-time streaming capabilities without excessive boilerplate.
4
+
5
+ ## Architecture & Features
6
+
7
+ FastFetch v2 provides seven core execution pipelines directly into your fetch workflow:
8
+
9
+ - **Strict Typed Error Hierarchy:** Explicit errors for `TimeoutError`, `NetworkError`, `HttpError`, and `CircuitOpenError` enabling robust try/catch control flow.
10
+ - **Koa-Style Middleware Pipeline:** A powerful `.use(async (ctx, next) => {})` onion-model interceptor architecture for modifying request metadata or observing response execution duration.
11
+ - **Finite State Machine Circuit Breaker:** Stops cascading failures to downstream services by automatically failing fast when failure thresholds are breached, entering a `HALF_OPEN` state after the configured timeout.
12
+ - **Priority-Aware Concurrency Queue:** Protects API rate limits by enforcing a strict `maxConcurrent` ceiling across all ongoing requests while maintaining execution priority.
13
+ - **Offline Mutation Buffering:** Automatically buffers mutating requests (`POST`, `PUT`, `PATCH`, `DELETE`) internally if the browser detects an offline state, sequentially replaying them upon reconnection.
14
+ - **Real-Time Telemetry & Metrics:** Maintains rolling windows on p50, p95, and p99 latency percentiles alongside exact endpoint success and failure ratios.
15
+ - **Server-Sent Events (SSE) Native Streaming:** A built-in high-performance stream consumer utilizing `api.stream()` for interacting with AI models and event-driven backends natively over HTTP.
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install fastfetch-api-fetch-enhancer
21
+ ```
22
+
23
+ ## Basic Usage
24
+
25
+ A minimal example utilizing the `createClient` factory method:
26
+
27
+ ```typescript
28
+ import { createClient } from 'fastfetch-api-fetch-enhancer';
29
+
30
+ // Instantiate your global HTTP client
31
+ const api = createClient({
32
+ baseURL: 'https://api.example.com/v1',
33
+ headers: { 'Authorization': 'Bearer <token>' },
34
+ timeout: 10000,
35
+ retries: 3,
36
+ retryDelay: 1000,
37
+ maxConcurrent: 50,
38
+ circuitBreaker: {
39
+ threshold: 5,
40
+ timeout: 30000
41
+ }
42
+ });
43
+
44
+ // Koa-style middleware injection
45
+ api.use(async (ctx, next) => {
46
+ ctx.init.headers['X-Request-Id'] = crypto.randomUUID();
47
+ await next();
48
+ console.log(`[HTTP ${ctx.response?.status}] ${ctx.method} ${ctx.url} - ${ctx.duration}ms`);
49
+ });
50
+
51
+ // Start making requests
52
+ async function execute() {
53
+ try {
54
+ // Standard JSON shorthand
55
+ const user = await api.json('/users/me');
56
+
57
+ // Auto-serialization for payload delivery
58
+ await api.post('/events', { action: 'login', timestamp: Date.now() });
59
+
60
+ } catch (error) {
61
+ // Typed catch boundaries
62
+ if (error instanceof HttpError) {
63
+ console.error('API Rejected Request:', error.status);
64
+ } else if (error instanceof TimeoutError) {
65
+ console.error('Request timed out at:', error.timeout);
66
+ } else {
67
+ console.error('Catastrophic failure:', error);
68
+ }
69
+ }
70
+ }
71
+ ```
72
+
73
+ ## Advanced Streaming (SSE)
74
+
75
+ Consume AI generation pipelines directly over HTTP using the `stream` handler:
76
+
77
+ ```typescript
78
+ const signal = new AbortController();
79
+
80
+ await api.stream(
81
+ '/ai/generate-text',
82
+ (event) => {
83
+ process.stdout.write(event.data);
84
+ },
85
+ { signal: signal.signal }
86
+ );
87
+ ```
88
+
89
+ ## Monitoring & Telemetry
90
+
91
+ Evaluate application stability at any time by accessing the built-in system telemetry map:
92
+
93
+ ```typescript
94
+ const telemetry = api.metrics.snapshot();
95
+ console.table(telemetry.byEndpoint);
96
+ console.log('p99 global latency:', telemetry.globalLatencies.p99);
97
+ ```
98
+
99
+ ## License
100
+
101
+ FastFetch is open-source software licensed under the MIT License.
@@ -0,0 +1,216 @@
1
+ import {
2
+ fastFetch,
3
+ clearCache,
4
+ createClient,
5
+ HttpError,
6
+ TimeoutError,
7
+ CircuitOpenError,
8
+ } from "../src/index.js";
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Helpers
12
+ // ---------------------------------------------------------------------------
13
+ const POKE_BASE = "https://pokeapi.co/api/v2";
14
+ const DITTO_URL = `${POKE_BASE}/pokemon/ditto`;
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // 1. Core fastFetch & v1.x capabilities
18
+ // ---------------------------------------------------------------------------
19
+ describe("fastFetch — core & TTL Cache & Dedup", () => {
20
+ beforeEach(() => clearCache());
21
+
22
+ it("makes a basic GET and returns ok response", async () => {
23
+ const res = await fastFetch(DITTO_URL);
24
+ expect(res.ok).toBe(true);
25
+ const data = await res.json();
26
+ expect(data).toHaveProperty("name", "ditto");
27
+ });
28
+
29
+ it("deduplicates identical in-flight requests", async () => {
30
+ const [res1, res2] = await Promise.all([
31
+ fastFetch(DITTO_URL),
32
+ fastFetch(DITTO_URL),
33
+ ]);
34
+ const [d1, d2] = await Promise.all([res1.json(), res2.json()]);
35
+ expect(d1.name).toBe("ditto");
36
+ expect(d2.name).toBe("ditto");
37
+ });
38
+
39
+ it("returns a cached response on second call", async () => {
40
+ const res1 = await fastFetch(DITTO_URL, { fastCache: true, cacheTTL: 60_000 });
41
+ expect(res1.ok).toBe(true);
42
+
43
+ const res2 = await fastFetch(DITTO_URL, { fastCache: true, cacheTTL: 60_000 });
44
+ expect(res2.ok).toBe(true);
45
+ });
46
+ });
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // 2. Typed Error Hierarchy
50
+ // ---------------------------------------------------------------------------
51
+ describe("FastFetch v2.0 — Typed Errors", () => {
52
+ it("throws TimeoutError on abort", async () => {
53
+ let error: any;
54
+ try {
55
+ await fastFetch(DITTO_URL, { timeout: 1 });
56
+ } catch (e) {
57
+ error = e;
58
+ }
59
+ expect(error).toBeDefined();
60
+ expect(error).toBeInstanceOf(TimeoutError);
61
+ expect((error as TimeoutError).timeout).toBe(1);
62
+ });
63
+
64
+ it("throws HttpError when throwOnError is enabled via Client", async () => {
65
+ const api = createClient({ baseURL: POKE_BASE, throwOnError: true, retries: 0 });
66
+ let error: any;
67
+ try {
68
+ await api.get("/pokemon/invalid_pokemon_xyz123");
69
+ } catch (e) {
70
+ error = e;
71
+ }
72
+ expect(error).toBeDefined();
73
+ expect(error).toBeInstanceOf(HttpError);
74
+ expect((error as HttpError).status).toBe(404);
75
+ expect((error as HttpError).response).toBeDefined();
76
+ });
77
+ });
78
+
79
+ // ---------------------------------------------------------------------------
80
+ // 3. Koa-style Middleware
81
+ // ---------------------------------------------------------------------------
82
+ describe("FastFetch v2.0 — Middleware Pipeline", () => {
83
+ it("executes in onion model order (down then up)", async () => {
84
+ const api = createClient({ baseURL: POKE_BASE });
85
+ const order: string[] = [];
86
+
87
+ api.use(async (ctx, next) => {
88
+ order.push("mw1_down");
89
+ ctx.init.headers = { ...ctx.init.headers, "X-Trace": "1" };
90
+ await next();
91
+ order.push("mw1_up");
92
+ });
93
+
94
+ api.use(async (ctx, next) => {
95
+ order.push("mw2_down");
96
+ expect((ctx.init.headers as any)["X-Trace"]).toBe("1");
97
+ await next();
98
+ order.push("mw2_up");
99
+ expect(ctx.response?.status).toBe(200);
100
+ });
101
+
102
+ await api.get("/pokemon/ditto");
103
+
104
+ expect(order).toEqual(["mw1_down", "mw2_down", "mw2_up", "mw1_up"]);
105
+ });
106
+ });
107
+
108
+ // ---------------------------------------------------------------------------
109
+ // 4. Circuit Breaker
110
+ // ---------------------------------------------------------------------------
111
+ describe("FastFetch v2.0 — Circuit Breaker", () => {
112
+ it("transitions CLOSED -> OPEN -> HALF_OPEN -> CLOSED", async () => {
113
+ const api = createClient({
114
+ baseURL: "http://localhost:1", // guaranteed network failure
115
+ circuitBreaker: { threshold: 2, timeout: 50 },
116
+ retries: 0,
117
+ });
118
+
119
+ const cb = api.circuitBreaker!;
120
+ expect(cb.state).toBe("CLOSED");
121
+
122
+ // Failure 1
123
+ await expect(api.get("/")).rejects.toThrow();
124
+ expect(cb.state).toBe("CLOSED");
125
+ expect(cb.failures).toBe(1);
126
+
127
+ // Failure 2 -> Opens circuit
128
+ await expect(api.get("/")).rejects.toThrow();
129
+ expect(cb.state).toBe("OPEN");
130
+
131
+ // Circuit is OPEN, requests immediately throw (no network delay)
132
+ let error: any;
133
+ try {
134
+ await api.get("/");
135
+ } catch (e) {
136
+ error = e;
137
+ }
138
+ expect(error).toBeDefined();
139
+ expect(error).toBeInstanceOf(CircuitOpenError);
140
+
141
+ // Wait for timeout -> HALF_OPEN
142
+ await new Promise(r => setTimeout(r, 60));
143
+ expect(cb.state).toBe("HALF_OPEN");
144
+ });
145
+ });
146
+
147
+ // ---------------------------------------------------------------------------
148
+ // 5. Concurrency Queue
149
+ // ---------------------------------------------------------------------------
150
+ describe("FastFetch v2.0 — Concurrency Queue", () => {
151
+ it("limits active in-flight requests", async () => {
152
+ const maxConcurrent = 2;
153
+ const api = createClient({ baseURL: POKE_BASE, maxConcurrent });
154
+
155
+ // Fire 6 requests simultaneously
156
+ const p1 = api.get("/pokemon/1");
157
+ const p2 = api.get("/pokemon/2");
158
+ const p3 = api.get("/pokemon/3");
159
+ const p4 = api.get("/pokemon/4");
160
+ const p5 = api.get("/pokemon/5");
161
+ const p6 = api.get("/pokemon/6");
162
+
163
+ // Immediately check queue stats (2 active, 4 pending)
164
+ expect(api.queue!.active).toBe(2);
165
+ expect(api.queue!.pending).toBe(4);
166
+
167
+ await Promise.all([p1, p2, p3, p4, p5, p6]);
168
+
169
+ // All done
170
+ expect(api.queue!.active).toBe(0);
171
+ expect(api.queue!.pending).toBe(0);
172
+ });
173
+ });
174
+
175
+ // ---------------------------------------------------------------------------
176
+ // 6. Metrics
177
+ // ---------------------------------------------------------------------------
178
+ describe("FastFetch v2.0 — Metrics", () => {
179
+ it("records success rates and normalises URLs", async () => {
180
+ const api = createClient({ baseURL: POKE_BASE });
181
+
182
+ await api.get("/pokemon/1");
183
+ await api.get("/pokemon/2");
184
+
185
+ const snap = api.metrics.snapshot();
186
+ expect(snap.totalRequests).toBe(2);
187
+
188
+ // Normalisation replaces numeric segments with /:id
189
+ const keys = Object.keys(snap.byEndpoint);
190
+ expect(keys[0]).toContain("/pokemon/:id");
191
+ expect(snap.byEndpoint[keys[0]].count).toBe(2);
192
+ expect(snap.byEndpoint[keys[0]].successRate).toBe(1);
193
+ });
194
+ });
195
+
196
+ // ---------------------------------------------------------------------------
197
+ // 7. Auto Body Serialization
198
+ // ---------------------------------------------------------------------------
199
+ describe("FastFetch v2.0 — Auto Body Serialization", () => {
200
+ it("serialises plain objects to JSON automatically", async () => {
201
+ const api = createClient({ baseURL: POKE_BASE });
202
+
203
+ api.use(async (ctx, next) => {
204
+ // Intercept and assert the mutation before it goes out
205
+ expect(ctx.init.body).toBe('{"name":"bulbasaur"}');
206
+ expect((ctx.init.headers as any)["Content-Type"]).toBe("application/json");
207
+
208
+ // Stub the response so we don't actually POST to PokeAPI
209
+ ctx.response = new Response("ok", { status: 200 });
210
+ // do not await next() so we skip the network call
211
+ });
212
+
213
+ const res = await api.post("/pokemon", { name: "bulbasaur" });
214
+ expect(res.status).toBe(200);
215
+ });
216
+ });