@pureq/pureq 1.0.1 → 1.1.3
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 +268 -39
- package/dist/src/adapters/storage/encryptedStorage.d.ts +16 -0
- package/dist/src/adapters/storage/encryptedStorage.d.ts.map +1 -0
- package/dist/src/adapters/storage/encryptedStorage.js +77 -0
- package/dist/src/adapters/storage/encryptedStorage.js.map +1 -0
- package/dist/src/adapters/storage/indexedDBAdapter.d.ts +16 -0
- package/dist/src/adapters/storage/indexedDBAdapter.d.ts.map +1 -0
- package/dist/src/adapters/storage/indexedDBAdapter.js +75 -0
- package/dist/src/adapters/storage/indexedDBAdapter.js.map +1 -0
- package/dist/src/client/createClient.d.ts.map +1 -1
- package/dist/src/client/createClient.js +15 -14
- package/dist/src/client/createClient.js.map +1 -1
- package/dist/src/executor/execute.d.ts.map +1 -1
- package/dist/src/executor/execute.js +108 -27
- package/dist/src/executor/execute.js.map +1 -1
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +6 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/middleware/authRefresh.d.ts +26 -0
- package/dist/src/middleware/authRefresh.d.ts.map +1 -0
- package/dist/src/middleware/authRefresh.js +53 -0
- package/dist/src/middleware/authRefresh.js.map +1 -0
- package/dist/src/middleware/circuitBreaker.d.ts +19 -2
- package/dist/src/middleware/circuitBreaker.d.ts.map +1 -1
- package/dist/src/middleware/circuitBreaker.js +64 -26
- package/dist/src/middleware/circuitBreaker.js.map +1 -1
- package/dist/src/middleware/compose.d.ts.map +1 -1
- package/dist/src/middleware/compose.js +2 -1
- package/dist/src/middleware/compose.js.map +1 -1
- package/dist/src/middleware/diagnostics.d.ts.map +1 -1
- package/dist/src/middleware/diagnostics.js +1 -4
- package/dist/src/middleware/diagnostics.js.map +1 -1
- package/dist/src/middleware/fallback.d.ts +27 -0
- package/dist/src/middleware/fallback.d.ts.map +1 -0
- package/dist/src/middleware/fallback.js +42 -0
- package/dist/src/middleware/fallback.js.map +1 -0
- package/dist/src/middleware/hedge.js +3 -3
- package/dist/src/middleware/hedge.js.map +1 -1
- package/dist/src/middleware/offlineQueue.d.ts +9 -0
- package/dist/src/middleware/offlineQueue.d.ts.map +1 -1
- package/dist/src/middleware/offlineQueue.js +62 -23
- package/dist/src/middleware/offlineQueue.js.map +1 -1
- package/dist/src/middleware/retry.d.ts +17 -1
- package/dist/src/middleware/retry.d.ts.map +1 -1
- package/dist/src/middleware/retry.js +39 -14
- package/dist/src/middleware/retry.js.map +1 -1
- package/dist/src/middleware/validation.d.ts +24 -0
- package/dist/src/middleware/validation.d.ts.map +1 -0
- package/dist/src/middleware/validation.js +52 -0
- package/dist/src/middleware/validation.js.map +1 -0
- package/dist/src/node/fsAdapter.d.ts +16 -0
- package/dist/src/node/fsAdapter.d.ts.map +1 -0
- package/dist/src/node/fsAdapter.js +72 -0
- package/dist/src/node/fsAdapter.js.map +1 -0
- package/dist/src/node/index.d.ts +6 -0
- package/dist/src/node/index.d.ts.map +1 -0
- package/dist/src/node/index.js +6 -0
- package/dist/src/node/index.js.map +1 -0
- package/dist/src/policy/guardrails.d.ts +1 -1
- package/dist/src/policy/guardrails.d.ts.map +1 -1
- package/dist/src/types/http.d.ts +8 -0
- package/dist/src/types/http.d.ts.map +1 -1
- package/dist/src/types/internal.d.ts +5 -5
- package/dist/src/types/internal.d.ts.map +1 -1
- package/dist/src/types/internal.js +4 -1
- package/dist/src/types/internal.js.map +1 -1
- package/dist/src/types/result.d.ts +3 -1
- package/dist/src/types/result.d.ts.map +1 -1
- package/dist/src/types/result.js +27 -6
- package/dist/src/types/result.js.map +1 -1
- package/dist/src/utils/crypto.d.ts +9 -0
- package/dist/src/utils/crypto.d.ts.map +1 -1
- package/dist/src/utils/crypto.js +44 -0
- package/dist/src/utils/crypto.js.map +1 -1
- package/dist/src/utils/policyTrace.d.ts +7 -9
- package/dist/src/utils/policyTrace.d.ts.map +1 -1
- package/dist/src/utils/policyTrace.js +31 -12
- package/dist/src/utils/policyTrace.js.map +1 -1
- package/dist/tests/circuit-breaker.test.js +6 -6
- package/dist/tests/circuit-breaker.test.js.map +1 -1
- package/dist/tests/compose.test.js +2 -1
- package/dist/tests/compose.test.js.map +1 -1
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Functional, immutable, type-safe HTTP transport layer for TypeScript.
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
**pureq** is not another fetch wrapper. It's a **policy-first transport layer** that makes HTTP behavior explicit, composable, and observable
|
|
9
|
+
**pureq** is not another fetch wrapper. It's a **policy-first transport layer** that makes HTTP behavior explicit, composable, and observable across frontend, BFF, backend, and edge runtimes.
|
|
10
10
|
|
|
11
11
|
```ts
|
|
12
12
|
import { createClient, retry, circuitBreaker, dedupe } from "pureq";
|
|
@@ -19,7 +19,7 @@ const api = createClient({ baseURL: "https://api.example.com" })
|
|
|
19
19
|
const user = await api.getJson<User>("/users/:id", { params: { id: "42" } });
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
Zero runtime dependencies. Works everywhere `fetch` works.
|
|
22
|
+
Zero runtime dependencies in the core package. Works everywhere `fetch` works.
|
|
23
23
|
|
|
24
24
|
## Install
|
|
25
25
|
|
|
@@ -27,57 +27,113 @@ Zero runtime dependencies. Works everywhere `fetch` works.
|
|
|
27
27
|
npm install @pureq/pureq
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
### Node.js (with FileSystem support)
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
If using Node.js specific adapters (like `FileSystemQueueStorageAdapter`), use the node subpath:
|
|
33
33
|
|
|
34
34
|
```ts
|
|
35
|
-
import {
|
|
35
|
+
import { FileSystemQueueStorageAdapter } from "pureq/node";
|
|
36
|
+
```
|
|
36
37
|
|
|
37
|
-
|
|
38
|
+
> [!NOTE]
|
|
39
|
+
> Core package (`pureq`) is zero-dependency and runtime-agnostic.
|
|
40
|
+
> Node-only adapters (for example `FileSystemQueueStorageAdapter`) live behind `pureq/node` so browser bundles stay clean and small.
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
console.log(response.status); // 200
|
|
41
|
-
```
|
|
42
|
+
## Quick Start
|
|
42
43
|
|
|
43
|
-
###
|
|
44
|
+
### A quicker start than raw fetch
|
|
44
45
|
|
|
45
46
|
```ts
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
import { createClient } from "pureq";
|
|
48
|
+
|
|
49
|
+
type User = { id: string; name: string };
|
|
50
|
+
|
|
51
|
+
const client = createClient({ baseURL: "https://api.example.com" });
|
|
52
|
+
|
|
53
|
+
// GET + status check + JSON parse + typed path params
|
|
54
|
+
const user = await client.getJson<User>("/users/:userId", {
|
|
55
|
+
params: { userId: "42" },
|
|
49
56
|
});
|
|
50
57
|
|
|
51
|
-
|
|
58
|
+
console.log(user.name);
|
|
52
59
|
```
|
|
53
60
|
|
|
54
|
-
###
|
|
61
|
+
### Type-safe path parameters (Template Literal Types)
|
|
55
62
|
|
|
56
63
|
```ts
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
|
|
64
|
+
// Param keys are derived from the URL template at compile time.
|
|
65
|
+
|
|
66
|
+
// ✅ Compiles
|
|
67
|
+
await client.get("/users/:userId/posts/:postId", {
|
|
68
|
+
params: { userId: "1", postId: "42" },
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// ❌ TypeScript error: missing postId
|
|
72
|
+
await client.get("/users/:userId/posts/:postId", {
|
|
73
|
+
params: { userId: "1" },
|
|
60
74
|
});
|
|
61
75
|
```
|
|
62
76
|
|
|
63
|
-
###
|
|
77
|
+
### Result API to prevent missed error handling
|
|
64
78
|
|
|
65
79
|
```ts
|
|
66
|
-
const result = await client.
|
|
67
|
-
params: {
|
|
80
|
+
const result = await client.getJsonResult<User>("/users/:userId", {
|
|
81
|
+
params: { userId: "42" },
|
|
68
82
|
});
|
|
69
83
|
|
|
70
84
|
if (!result.ok) {
|
|
71
|
-
//
|
|
72
|
-
|
|
85
|
+
// Switching on kind gives explicit, exhaustive transport handling.
|
|
86
|
+
switch (result.error.kind) {
|
|
87
|
+
case "network":
|
|
88
|
+
showOfflineBanner();
|
|
89
|
+
break;
|
|
90
|
+
case "timeout":
|
|
91
|
+
showRetryToast();
|
|
92
|
+
break;
|
|
93
|
+
case "http":
|
|
94
|
+
if (result.error.status === 401) redirectToLogin();
|
|
95
|
+
break;
|
|
96
|
+
case "circuit-open":
|
|
97
|
+
switchToFallbackMode();
|
|
98
|
+
break;
|
|
99
|
+
case "aborted":
|
|
100
|
+
case "storage-error":
|
|
101
|
+
case "auth-error":
|
|
102
|
+
case "validation-error":
|
|
103
|
+
case "unknown":
|
|
104
|
+
reportTransportError(result.error);
|
|
105
|
+
break;
|
|
106
|
+
default: {
|
|
107
|
+
const exhaustive: never = result.error.kind;
|
|
108
|
+
throw new Error(`Unhandled error kind: ${exhaustive}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
73
111
|
return;
|
|
74
112
|
}
|
|
75
113
|
|
|
76
|
-
|
|
114
|
+
// result.data is strongly typed JSON
|
|
115
|
+
renderUser(result.data);
|
|
77
116
|
```
|
|
78
117
|
|
|
79
118
|
Every request method has a `*Result` variant that never throws — transport failures become values you can pattern-match on.
|
|
80
119
|
|
|
120
|
+
### Systematic Error Codes
|
|
121
|
+
|
|
122
|
+
For enterprise-grade observability, `PureqError` exposes both `kind` and `code` on the same error object. They represent the same category in two formats:
|
|
123
|
+
|
|
124
|
+
- `kind`: human-friendly lowercase string (e.g. `"timeout"`)
|
|
125
|
+
- `code`: machine-readable Screaming Snake Case (e.g. `"PUREQ_TIMEOUT"`)
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
if (!result.ok) {
|
|
129
|
+
if (result.error.kind === "timeout" && result.error.code === "PUREQ_TIMEOUT") {
|
|
130
|
+
// Handle specifically...
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Common codes: `PUREQ_TIMEOUT`, `PUREQ_NETWORK_ERROR`, `PUREQ_OFFLINE_QUEUE_FULL`, `PUREQ_AUTH_REFRESH_FAILED`, `PUREQ_VALIDATION_ERROR`.
|
|
136
|
+
|
|
81
137
|
---
|
|
82
138
|
|
|
83
139
|
## Why pureq
|
|
@@ -215,16 +271,56 @@ const withAuth = withRetry.useRequestInterceptor((req) => ({
|
|
|
215
271
|
|
|
216
272
|
This makes it trivial to share a base client while customizing per-dependency behavior.
|
|
217
273
|
|
|
274
|
+
### The "Why" of Immutability
|
|
275
|
+
|
|
276
|
+
Unlike mutable interceptor stacks, pureq clients are branchable snapshots.
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
import { authRefresh, createClient, dedupe, retry } from "pureq";
|
|
280
|
+
|
|
281
|
+
const base = createClient({ baseURL: "https://api.example.com" })
|
|
282
|
+
.use(retry({ maxRetries: 2, delay: 200 }));
|
|
283
|
+
|
|
284
|
+
// Public API: cache-friendly and lightweight
|
|
285
|
+
const publicApi = base.use(dedupe({ methods: ["GET", "HEAD"] }));
|
|
286
|
+
|
|
287
|
+
// Auth API: isolated auth policy without mutating publicApi
|
|
288
|
+
const authedApi = base
|
|
289
|
+
.use(authRefresh({ refresh: refreshToken }))
|
|
290
|
+
.useRequestInterceptor((req) => ({
|
|
291
|
+
...req,
|
|
292
|
+
headers: { ...req.headers, Authorization: `Bearer ${getToken()}` },
|
|
293
|
+
}));
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
This pattern is especially useful in BFFs: you keep shared defaults, then safely branch per upstream or trust boundary.
|
|
297
|
+
|
|
218
298
|
### Middleware: the Onion Model
|
|
219
299
|
|
|
220
300
|
Middleware wraps the entire request lifecycle. Each middleware can intercept the request before it happens, await the result, and post-process the response:
|
|
221
301
|
|
|
222
302
|
```text
|
|
223
|
-
Request
|
|
224
|
-
|
|
225
|
-
|
|
303
|
+
Request
|
|
304
|
+
-> [dedupe]
|
|
305
|
+
-> [retry]
|
|
306
|
+
-> [circuitBreaker]
|
|
307
|
+
-> [adapter/fetch]
|
|
308
|
+
<- [circuitBreaker]
|
|
309
|
+
<- [retry]
|
|
310
|
+
<- [dedupe]
|
|
311
|
+
Response
|
|
226
312
|
```
|
|
227
313
|
|
|
314
|
+
Think of `retry` and `circuitBreaker` as wrappers around `next()`.
|
|
315
|
+
|
|
316
|
+
- Outer wrappers see the whole execution envelope.
|
|
317
|
+
- Inner wrappers only see what reaches them.
|
|
318
|
+
|
|
319
|
+
That is why order changes behavior. Example:
|
|
320
|
+
|
|
321
|
+
- `retry` outside `circuitBreaker`: each retry attempt passes through breaker logic.
|
|
322
|
+
- `circuitBreaker` outside `retry`: breaker sees one wrapped operation that may include internal retries.
|
|
323
|
+
|
|
228
324
|
Middleware can:
|
|
229
325
|
|
|
230
326
|
- Transform the request before `next()`
|
|
@@ -300,9 +396,28 @@ const client = createClient({ baseURL: "https://api.example.com" })
|
|
|
300
396
|
.use(dedupe()) // collapse duplicate in-flight GETs
|
|
301
397
|
.use(httpCache({ ttlMs: 10_000, maxEntries: 200 })) // in-memory cache with LRU eviction
|
|
302
398
|
.use(retry({ maxRetries: 3, delay: 200, backoff: true })) // exponential backoff
|
|
303
|
-
.use(circuitBreaker({ failureThreshold: 5, cooldownMs: 30_000 }))
|
|
399
|
+
.use(circuitBreaker({ failureThreshold: 5, cooldownMs: 30_000 }))
|
|
400
|
+
.use(validation({ validate: (data) => !!data })) // zero-dependency schema validation
|
|
401
|
+
.use(fallback({ value: { body: "default", status: 200 } as any })); // graceful degradation
|
|
304
402
|
```
|
|
305
403
|
|
|
404
|
+
### Standard Order (recommended)
|
|
405
|
+
|
|
406
|
+
For most production stacks, this order is a safe default:
|
|
407
|
+
|
|
408
|
+
```text
|
|
409
|
+
[dedupe] -> [httpCache] -> [retry] -> [circuitBreaker] -> [validation] -> [adapter]
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
Why this order:
|
|
413
|
+
|
|
414
|
+
- `dedupe` outermost to collapse duplicate in-flight work early.
|
|
415
|
+
- `httpCache` before retry to short-circuit cache hits.
|
|
416
|
+
- `retry` before `circuitBreaker` so each attempt is still guarded by breaker state.
|
|
417
|
+
- `validation` near the inner edge to validate final payloads before returning to callers.
|
|
418
|
+
|
|
419
|
+
If order is reversed accidentally, behavior can drift (for example, retrying inside a layer you expected to run once).
|
|
420
|
+
|
|
306
421
|
### Retry
|
|
307
422
|
|
|
308
423
|
Full-featured retry with exponential backoff, Retry-After header respect, and retry budget.
|
|
@@ -313,15 +428,19 @@ import { retry } from "pureq";
|
|
|
313
428
|
client.use(retry({
|
|
314
429
|
maxRetries: 3,
|
|
315
430
|
delay: 200,
|
|
431
|
+
methods: ["GET", "PUT", "DELETE"], // Safe defaults: idempotent methods only
|
|
316
432
|
backoff: true,
|
|
317
|
-
maxDelay: 5_000,
|
|
318
|
-
retryBudgetMs: 2000, // total time cap across all retries
|
|
319
|
-
retryOnStatus: [429, 500, 502, 503, 504],
|
|
320
|
-
respectRetryAfter: true,
|
|
321
433
|
onRetry: ({ attempt, error }) => console.warn(`Retry #${attempt}`, error),
|
|
322
434
|
}));
|
|
323
435
|
```
|
|
324
436
|
|
|
437
|
+
**Safety First**: By default, `retry` only targets idempotent methods. To retry `POST` or `PATCH`, you must explicitly add them to `methods` and ensure the backend supports idempotency keys.
|
|
438
|
+
|
|
439
|
+
```text
|
|
440
|
+
POST /api/orders HTTP/1.1
|
|
441
|
+
Idempotency-Key: abc-123
|
|
442
|
+
```
|
|
443
|
+
|
|
325
444
|
### Deadline Propagation
|
|
326
445
|
|
|
327
446
|
Enforces a total request budget across all retry attempts — different from a per-request timeout.
|
|
@@ -407,20 +526,37 @@ Queues mutation requests when offline and replays them when connectivity restore
|
|
|
407
526
|
```ts
|
|
408
527
|
import { createOfflineQueue, idempotencyKey } from "pureq";
|
|
409
528
|
|
|
529
|
+
// 1. Create durable storage (IndexedDB for browser, FS for Node)
|
|
530
|
+
const storage = new IndexedDBQueueStorageAdapter();
|
|
531
|
+
|
|
532
|
+
// 2. (Optional) Wrap with encryption for enterprise security
|
|
533
|
+
// myCryptoKey can come from crypto.subtle.generateKey(...) or importKey/deriveKey from a password.
|
|
534
|
+
const encryptedStorage = new EncryptedQueueStorageAdapter(storage, myCryptoKey);
|
|
535
|
+
|
|
410
536
|
const queue = createOfflineQueue({
|
|
537
|
+
storage: encryptedStorage,
|
|
411
538
|
methods: ["POST", "PUT", "PATCH"],
|
|
412
|
-
|
|
539
|
+
ttlMs: 24 * 60 * 60 * 1000, // 24h expiration
|
|
540
|
+
lockName: "my-app-offline-lock", // Multi-tab coordination
|
|
413
541
|
});
|
|
414
542
|
|
|
415
543
|
const client = createClient()
|
|
416
|
-
.use(idempotencyKey())
|
|
544
|
+
.use(idempotencyKey())
|
|
417
545
|
.use(queue.middleware);
|
|
418
546
|
|
|
419
547
|
// Later, when back online:
|
|
420
|
-
await queue.flush(
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
548
|
+
await queue.flush((req) => client.post(req.url, req.body));
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
**Durable Adapters**:
|
|
552
|
+
|
|
553
|
+
- `IndexedDBQueueStorageAdapter`: Standard browser persistence.
|
|
554
|
+
- `FileSystemQueueStorageAdapter`: Node.js persistence (import from `pureq/node`).
|
|
555
|
+
- `EncryptedQueueStorageAdapter`: Wrapper that encrypts data at rest using AES-GCM.
|
|
556
|
+
|
|
557
|
+
```ts
|
|
558
|
+
// Example: Creating an encrypted storage
|
|
559
|
+
const encrypted = new EncryptedQueueStorageAdapter(new IndexedDBQueueStorageAdapter(), key);
|
|
424
560
|
```
|
|
425
561
|
|
|
426
562
|
### Idempotency Keys
|
|
@@ -436,6 +572,64 @@ client.use(idempotencyKey({
|
|
|
436
572
|
}));
|
|
437
573
|
```
|
|
438
574
|
|
|
575
|
+
### Auth Refresh
|
|
576
|
+
|
|
577
|
+
Automatically handles 401 Unauthorized errors by refreshing the token and retrying the request. Includes built-in "thundering herd" prevention to ensure only one refresh request is in-flight at a time.
|
|
578
|
+
|
|
579
|
+
```ts
|
|
580
|
+
import { authRefresh } from "pureq";
|
|
581
|
+
|
|
582
|
+
client.use(authRefresh({
|
|
583
|
+
refresh: async () => {
|
|
584
|
+
// Multiple 401s will only trigger ONE refresh call.
|
|
585
|
+
const res = await fetch("/api/refresh", { method: "POST" });
|
|
586
|
+
return (await res.json()).token;
|
|
587
|
+
},
|
|
588
|
+
// Optional: customize how the request is updated
|
|
589
|
+
updateRequest: (req, token) => ({
|
|
590
|
+
...req,
|
|
591
|
+
headers: { ...req.headers, Authorization: `Bearer ${token}` }
|
|
592
|
+
})
|
|
593
|
+
}));
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
### Validation
|
|
597
|
+
|
|
598
|
+
A zero-dependency bridge to any schema validation library (Zod, Valibot, etc.) or custom type guards.
|
|
599
|
+
|
|
600
|
+
```ts
|
|
601
|
+
import { validation } from "pureq";
|
|
602
|
+
import { z } from "zod";
|
|
603
|
+
|
|
604
|
+
const UserSchema = z.object({ id: z.string(), name: z.string() });
|
|
605
|
+
|
|
606
|
+
client.use(validation({
|
|
607
|
+
validate: (data) => UserSchema.parse(data), // Throws PUREQ_VALIDATION_ERROR on failure
|
|
608
|
+
message: "Invalid API response schema"
|
|
609
|
+
}));
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
### Fallback
|
|
613
|
+
|
|
614
|
+
Enables "Graceful Degradation" by returning a default value or cached data when a request fails.
|
|
615
|
+
|
|
616
|
+
```ts
|
|
617
|
+
import { fallback, HttpResponse, type PureqError } from "pureq";
|
|
618
|
+
|
|
619
|
+
const isPureqError = (value: unknown): value is PureqError => {
|
|
620
|
+
return typeof value === "object" && value !== null && "code" in value && "kind" in value;
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
client.use(fallback({
|
|
624
|
+
value: new HttpResponse(new Response(JSON.stringify({ items: [] }), { status: 200 })),
|
|
625
|
+
when: (trigger) =>
|
|
626
|
+
trigger.type === "error" &&
|
|
627
|
+
isPureqError(trigger.error) &&
|
|
628
|
+
trigger.error.code === "PUREQ_TIMEOUT" &&
|
|
629
|
+
trigger.error.kind === "timeout"
|
|
630
|
+
}));
|
|
631
|
+
```
|
|
632
|
+
|
|
439
633
|
### Policy Guardrails
|
|
440
634
|
|
|
441
635
|
pureq validates your middleware stack at client creation time and rejects invalid combinations:
|
|
@@ -479,7 +673,7 @@ for (const mw of backendPreset()) backend = backend.use(mw);
|
|
|
479
673
|
| `frontendPreset()` | 5s | 1 | GET/HEAD | 4 failures / 10s cooldown | ✅ |
|
|
480
674
|
| `bffPreset()` | 3s | 2 | GET/HEAD | 5 failures / 20s cooldown | ✅ body-only |
|
|
481
675
|
| `backendPreset()` | 2.5s | 3 | off | 6 failures / 30s cooldown | ✅ body-only |
|
|
482
|
-
| `resilientPreset()` |
|
|
676
|
+
| `resilientPreset()` | none (pair with `defaultTimeout()` if needed) | 2 | GET/HEAD | 5 failures / 30s cooldown | ✅ |
|
|
483
677
|
|
|
484
678
|
All presets are built from the same public middleware. You can inspect and override any parameter.
|
|
485
679
|
|
|
@@ -523,6 +717,24 @@ const snap = diagnostics.snapshot();
|
|
|
523
717
|
console.log(snap.p50, snap.p95, snap.total, snap.success, snap.failed);
|
|
524
718
|
```
|
|
525
719
|
|
|
720
|
+
### Policy Tracing (Debuggability)
|
|
721
|
+
|
|
722
|
+
Ever wonder *why* a request was retried or why the circuit opened? `pureq` records a detailed decision trace for every request.
|
|
723
|
+
|
|
724
|
+
```ts
|
|
725
|
+
import { explainPolicyTrace } from "pureq";
|
|
726
|
+
|
|
727
|
+
try {
|
|
728
|
+
await client.get("/flakey-endpoint");
|
|
729
|
+
} catch (err) {
|
|
730
|
+
// Prints exactly what happened:
|
|
731
|
+
// [2026-04-10T10:00:00Z] RETRY: RETRY (status=503)
|
|
732
|
+
// [2026-04-10T10:00:01Z] RETRY: RETRY (status=503)
|
|
733
|
+
// [2026-04-10T10:00:02Z] CIRCUIT-BREAKER: TRIP (reason="failure threshold exceeded")
|
|
734
|
+
console.log(explainPolicyTrace(err.request));
|
|
735
|
+
}
|
|
736
|
+
```
|
|
737
|
+
|
|
526
738
|
### OpenTelemetry integration
|
|
527
739
|
|
|
528
740
|
Map transport events to OTel-compatible attributes:
|
|
@@ -789,6 +1001,20 @@ More details: [Runtime compatibility matrix](./docs/runtime_compatibility_matrix
|
|
|
789
1001
|
|
|
790
1002
|
---
|
|
791
1003
|
|
|
1004
|
+
## Production Readiness Checklist
|
|
1005
|
+
|
|
1006
|
+
If you are adopting pureq in a production service, start with these defaults:
|
|
1007
|
+
|
|
1008
|
+
- Set a request time budget (`defaultTimeout()` or `deadline()`) first.
|
|
1009
|
+
- Add `retry()` with explicit status/method policy.
|
|
1010
|
+
- Add `validation()` for critical response payloads.
|
|
1011
|
+
- Add `circuitBreaker()` for unstable upstreams.
|
|
1012
|
+
- Enable diagnostics hooks (`onRequestStart/onRequestSuccess/onRequestError`).
|
|
1013
|
+
- Prefer `*Result` APIs on boundary layers to avoid unhandled transport errors.
|
|
1014
|
+
- Use `idempotencyKey()` before retrying write operations.
|
|
1015
|
+
|
|
1016
|
+
---
|
|
1017
|
+
|
|
792
1018
|
## API Reference
|
|
793
1019
|
|
|
794
1020
|
### Client
|
|
@@ -828,12 +1054,15 @@ More details: [Runtime compatibility matrix](./docs/runtime_compatibility_matrix
|
|
|
828
1054
|
| Middleware | Purpose |
|
|
829
1055
|
| --------- | ------- |
|
|
830
1056
|
| `retry(options)` | Exponential backoff, Retry-After, budget |
|
|
1057
|
+
| `authRefresh(options)` | Automatic token refresh (thundering herd prevention) |
|
|
831
1058
|
| `deadline(options)` | Total request budget across retries |
|
|
832
1059
|
| `defaultTimeout(ms)` | Default per-request timeout |
|
|
833
1060
|
| `circuitBreaker(options)` | Fail-fast on repeated failures |
|
|
834
1061
|
| `concurrencyLimit(options)` | Cap in-flight requests |
|
|
835
1062
|
| `dedupe(options?)` | Collapse duplicate concurrent requests |
|
|
836
1063
|
| `hedge(options)` | Duplicate request for tail latency |
|
|
1064
|
+
| `validation(options)` | Schema validation bridge (Zod/Valibot ready) |
|
|
1065
|
+
| `fallback(options)` | Graceful degradation with fallback values |
|
|
837
1066
|
| `httpCache(options)` | In-memory cache with ETag/stale-if-error |
|
|
838
1067
|
| `createOfflineQueue(options?)` | Offline mutation queue with replay |
|
|
839
1068
|
| `idempotencyKey(options?)` | Auto-inject idempotency headers |
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { OfflineQueueStorageAdapter, QueuedRequest } from "../../middleware/offlineQueue";
|
|
2
|
+
/**
|
|
3
|
+
* A wrapper for OfflineQueueStorageAdapter that encrypts request data at rest.
|
|
4
|
+
* Crucial for enterprise compliance when storing PII or auth tokens in local storage.
|
|
5
|
+
*/
|
|
6
|
+
export declare class EncryptedQueueStorageAdapter implements OfflineQueueStorageAdapter {
|
|
7
|
+
private readonly inner;
|
|
8
|
+
private readonly key;
|
|
9
|
+
constructor(inner: OfflineQueueStorageAdapter, key: CryptoKey);
|
|
10
|
+
push(item: QueuedRequest): Promise<void>;
|
|
11
|
+
getAll(): Promise<readonly QueuedRequest[]>;
|
|
12
|
+
remove(id: number): Promise<void>;
|
|
13
|
+
clear(): Promise<void>;
|
|
14
|
+
size(): Promise<number>;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=encryptedStorage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encryptedStorage.d.ts","sourceRoot":"","sources":["../../../../src/adapters/storage/encryptedStorage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAiB/F;;;GAGG;AACH,qBAAa,4BAA6B,YAAW,0BAA0B;IAE3E,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,GAAG;gBADH,KAAK,EAAE,0BAA0B,EACjC,GAAG,EAAE,SAAS;IAG3B,IAAI,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IASxC,MAAM,IAAI,OAAO,CAAC,SAAS,aAAa,EAAE,CAAC;IA0D3C,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIjC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAItB,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;CAG9B"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { encrypt, decrypt } from "../../utils/crypto";
|
|
2
|
+
let decryptionErrorCounter = 0;
|
|
3
|
+
function createDecryptionErrorId() {
|
|
4
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
5
|
+
return crypto.randomUUID();
|
|
6
|
+
}
|
|
7
|
+
decryptionErrorCounter += 1;
|
|
8
|
+
return `decrypt-${Date.now()}-${decryptionErrorCounter}`;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A wrapper for OfflineQueueStorageAdapter that encrypts request data at rest.
|
|
12
|
+
* Crucial for enterprise compliance when storing PII or auth tokens in local storage.
|
|
13
|
+
*/
|
|
14
|
+
export class EncryptedQueueStorageAdapter {
|
|
15
|
+
constructor(inner, key) {
|
|
16
|
+
this.inner = inner;
|
|
17
|
+
this.key = key;
|
|
18
|
+
}
|
|
19
|
+
async push(item) {
|
|
20
|
+
const encryptedReq = await encrypt(JSON.stringify(item.req), this.key);
|
|
21
|
+
await this.inner.push({
|
|
22
|
+
...item,
|
|
23
|
+
req: encryptedReq,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
async getAll() {
|
|
27
|
+
const rawItems = await this.inner.getAll();
|
|
28
|
+
const results = [];
|
|
29
|
+
const failures = [];
|
|
30
|
+
for (let index = 0; index < rawItems.length; index++) {
|
|
31
|
+
const item = rawItems[index];
|
|
32
|
+
try {
|
|
33
|
+
if (typeof item.req !== "string") {
|
|
34
|
+
throw new Error("encrypted req payload is not a string");
|
|
35
|
+
}
|
|
36
|
+
const decryptedReqJson = await decrypt(item.req, this.key);
|
|
37
|
+
const decryptedReq = JSON.parse(decryptedReqJson);
|
|
38
|
+
const encryptedItem = item;
|
|
39
|
+
results.push({
|
|
40
|
+
id: encryptedItem.id,
|
|
41
|
+
queuedAt: encryptedItem.queuedAt,
|
|
42
|
+
...(encryptedItem.expiresAt !== undefined ? { expiresAt: encryptedItem.expiresAt } : {}),
|
|
43
|
+
req: decryptedReq,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
const errorId = createDecryptionErrorId();
|
|
48
|
+
const safeName = error instanceof Error ? error.name : "UnknownError";
|
|
49
|
+
console.warn(`pureq: failed to decrypt queued request - errorId=${errorId} code=DECRYPTION_FAILURE name=${safeName}`);
|
|
50
|
+
failures.push({
|
|
51
|
+
index,
|
|
52
|
+
id: item.id,
|
|
53
|
+
errorId,
|
|
54
|
+
code: "DECRYPTION_FAILURE",
|
|
55
|
+
name: safeName,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (failures.length > 0) {
|
|
60
|
+
const aggregate = new AggregateError(failures, `pureq: failed to decrypt ${failures.length} queued request(s)`);
|
|
61
|
+
aggregate.details = failures;
|
|
62
|
+
aggregate.code = "PUREQ_DECRYPTION_FAILURE";
|
|
63
|
+
throw aggregate;
|
|
64
|
+
}
|
|
65
|
+
return results;
|
|
66
|
+
}
|
|
67
|
+
async remove(id) {
|
|
68
|
+
await this.inner.remove(id);
|
|
69
|
+
}
|
|
70
|
+
async clear() {
|
|
71
|
+
await this.inner.clear();
|
|
72
|
+
}
|
|
73
|
+
async size() {
|
|
74
|
+
return this.inner.size();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=encryptedStorage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encryptedStorage.js","sourceRoot":"","sources":["../../../../src/adapters/storage/encryptedStorage.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { OfflineQueueStorageAdapter, QueuedRequest } from "../../middleware/offlineQueue";
|
|
2
|
+
/**
|
|
3
|
+
* Standard IndexedDB-based storage for the offline queue.
|
|
4
|
+
* Survives page refreshes and browser restarts.
|
|
5
|
+
*/
|
|
6
|
+
export declare class IndexedDBQueueStorageAdapter implements OfflineQueueStorageAdapter {
|
|
7
|
+
private dbPromise;
|
|
8
|
+
private readonly storeName;
|
|
9
|
+
constructor(dbName?: string);
|
|
10
|
+
push(item: QueuedRequest): Promise<void>;
|
|
11
|
+
getAll(): Promise<readonly QueuedRequest[]>;
|
|
12
|
+
remove(id: number): Promise<void>;
|
|
13
|
+
clear(): Promise<void>;
|
|
14
|
+
size(): Promise<number>;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=indexedDBAdapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexedDBAdapter.d.ts","sourceRoot":"","sources":["../../../../src/adapters/storage/indexedDBAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAE/F;;;GAGG;AACH,qBAAa,4BAA6B,YAAW,0BAA0B;IAC7E,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAc;gBAE5B,MAAM,SAAwB;IAmBpC,IAAI,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAWxC,MAAM,IAAI,OAAO,CAAC,SAAS,aAAa,EAAE,CAAC;IAW3C,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWjC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAWtB,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;CAU9B"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standard IndexedDB-based storage for the offline queue.
|
|
3
|
+
* Survives page refreshes and browser restarts.
|
|
4
|
+
*/
|
|
5
|
+
export class IndexedDBQueueStorageAdapter {
|
|
6
|
+
constructor(dbName = "pureq-offline-queue") {
|
|
7
|
+
this.storeName = "requests";
|
|
8
|
+
this.dbPromise = new Promise((resolve, reject) => {
|
|
9
|
+
if (typeof indexedDB === "undefined") {
|
|
10
|
+
reject(new Error("pureq: IndexedDB is not available in this environment"));
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const request = indexedDB.open(dbName, 1);
|
|
14
|
+
request.onupgradeneeded = () => {
|
|
15
|
+
const db = request.result;
|
|
16
|
+
if (!db.objectStoreNames.contains(this.storeName)) {
|
|
17
|
+
db.createObjectStore(this.storeName, { keyPath: "id" });
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
request.onsuccess = () => resolve(request.result);
|
|
21
|
+
request.onerror = () => reject(request.error);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
async push(item) {
|
|
25
|
+
const db = await this.dbPromise;
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
const transaction = db.transaction(this.storeName, "readwrite");
|
|
28
|
+
const store = transaction.objectStore(this.storeName);
|
|
29
|
+
const request = store.put(item);
|
|
30
|
+
request.onsuccess = () => resolve();
|
|
31
|
+
request.onerror = () => reject(request.error);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
async getAll() {
|
|
35
|
+
const db = await this.dbPromise;
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
const transaction = db.transaction(this.storeName, "readonly");
|
|
38
|
+
const store = transaction.objectStore(this.storeName);
|
|
39
|
+
const request = store.getAll();
|
|
40
|
+
request.onsuccess = () => resolve(request.result);
|
|
41
|
+
request.onerror = () => reject(request.error);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
async remove(id) {
|
|
45
|
+
const db = await this.dbPromise;
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
const transaction = db.transaction(this.storeName, "readwrite");
|
|
48
|
+
const store = transaction.objectStore(this.storeName);
|
|
49
|
+
const request = store.delete(id);
|
|
50
|
+
request.onsuccess = () => resolve();
|
|
51
|
+
request.onerror = () => reject(request.error);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
async clear() {
|
|
55
|
+
const db = await this.dbPromise;
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
const transaction = db.transaction(this.storeName, "readwrite");
|
|
58
|
+
const store = transaction.objectStore(this.storeName);
|
|
59
|
+
const request = store.clear();
|
|
60
|
+
request.onsuccess = () => resolve();
|
|
61
|
+
request.onerror = () => reject(request.error);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
async size() {
|
|
65
|
+
const db = await this.dbPromise;
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
const transaction = db.transaction(this.storeName, "readonly");
|
|
68
|
+
const store = transaction.objectStore(this.storeName);
|
|
69
|
+
const request = store.count();
|
|
70
|
+
request.onsuccess = () => resolve(request.result);
|
|
71
|
+
request.onerror = () => reject(request.error);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=indexedDBAdapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexedDBAdapter.js","sourceRoot":"","sources":["../../../../src/adapters/storage/indexedDBAdapter.ts"],"names":[],"mappings":"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"}
|