@boy-offi9-inc/reqkit 0.7.1 → 0.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/README.md +92 -66
- package/dist/reqkit.mjs +2136 -2014
- package/package.json +9 -5
- package/src/{createFetch.js → http/createFetch.js} +130 -22
- package/src/index.js +18 -18
- package/src/{paginate.js → pagination/paginate.js} +2 -2
- package/src/{createRateLimiter.js → resilience/rateLimiter.js} +7 -4
- package/src/{withRetry.js → resilience/retry.js} +2 -2
- package/src/{retryAfterAware.js → resilience/retryAfterAware.js} +3 -3
- package/src/{downloadResumable.js → streams/download.js} +5 -5
- package/src/{streamSSE.js → streams/streamSSE.js} +5 -5
- package/src/{createMockFetch.js → testing/createMockFetch.js} +1 -1
- package/types/errors.d.ts +24 -0
- package/types/http.d.ts +180 -0
- package/types/index.d.ts +7 -538
- package/types/pagination.d.ts +62 -0
- package/types/resilience.d.ts +171 -0
- package/types/streams.d.ts +105 -0
- package/types/testing.d.ts +44 -0
- /package/{RECIPES.md → docs/recipes.md} +0 -0
- /package/src/{HTTPError.js → errors/HTTPError.js} +0 -0
- /package/src/{normalizeError.js → errors/normalizeError.js} +0 -0
- /package/src/{buildQuery.js → http/buildQuery.js} +0 -0
- /package/src/{_cache.js → http/cache/entry.js} +0 -0
- /package/src/{createMemoryCache.js → http/cache/memoryCache.js} +0 -0
- /package/src/{parseJsonSafe.js → http/parseJsonSafe.js} +0 -0
- /package/src/{_http.js → internal/http.js} +0 -0
- /package/src/{_sleep.js → internal/sleep.js} +0 -0
- /package/src/{createCircuitBreaker.js → resilience/circuitBreaker.js} +0 -0
- /package/src/{withDedupe.js → resilience/dedupe.js} +0 -0
- /package/src/{createLimiter.js → resilience/limiter.js} +0 -0
- /package/src/{withTimeout.js → resilience/timeout.js} +0 -0
- /package/src/{withProgress.js → streams/progress.js} +0 -0
- /package/src/{streams.js → streams/readers.js} +0 -0
package/types/http.d.ts
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
// createFetch and the request/response helpers — src/http/
|
|
2
|
+
import type { CircuitBreaker, CircuitBreakerOptions, CircuitState, RateLimiter, RateLimiterOptions, RetryOptions } from "./resilience";
|
|
3
|
+
|
|
4
|
+
export interface JsonSafeResult<T = unknown> {
|
|
5
|
+
data: T | null;
|
|
6
|
+
error: Error | null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function parseJsonSafe<T = unknown>(input: Response | string): Promise<JsonSafeResult<T>>;
|
|
10
|
+
|
|
11
|
+
export function buildQuery(params: Record<string, unknown> | null | undefined): string;
|
|
12
|
+
|
|
13
|
+
export interface CreateFetchRetryOptions {
|
|
14
|
+
/** Default 2. */
|
|
15
|
+
retries?: number;
|
|
16
|
+
/** Default [408, 425, 429, 500, 502, 503, 504]. */
|
|
17
|
+
statusCodes?: number[];
|
|
18
|
+
/** Methods that may be retried. Default GET, HEAD, OPTIONS, PUT, DELETE. */
|
|
19
|
+
methods?: string[];
|
|
20
|
+
baseDelayMs?: number;
|
|
21
|
+
maxDelayMs?: number;
|
|
22
|
+
factor?: number;
|
|
23
|
+
jitter?: number | "full";
|
|
24
|
+
maxRetryTime?: number;
|
|
25
|
+
shouldRetry?: (err: unknown, attempt: number) => boolean;
|
|
26
|
+
calculateDelay?: RetryOptions["calculateDelay"];
|
|
27
|
+
onRetry?: RetryOptions["onRetry"];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface FetchHookContext {
|
|
31
|
+
url: string;
|
|
32
|
+
init: RequestInit & { headers: Headers };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface CreateFetchDefaults {
|
|
36
|
+
baseUrl?: string;
|
|
37
|
+
headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
|
|
38
|
+
/** Per attempt, in ms. false disables. Default 30000. */
|
|
39
|
+
timeout?: number | false;
|
|
40
|
+
/** false disables retrying. */
|
|
41
|
+
retry?: false | CreateFetchRetryOptions;
|
|
42
|
+
/**
|
|
43
|
+
* `true` only learns from X-RateLimit-* / Retry-After headers; `{ requests, perMs }`
|
|
44
|
+
* also enforces a budget; or share a createRateLimiter() instance.
|
|
45
|
+
*/
|
|
46
|
+
rateLimit?: boolean | RateLimiterOptions | RateLimiter;
|
|
47
|
+
/** Options for a per-origin breaker, a shared breaker instance, or `(url) => breaker`. */
|
|
48
|
+
circuitBreaker?: boolean | CircuitBreakerOptions | CircuitBreaker | ((url: string) => CircuitBreaker);
|
|
49
|
+
/**
|
|
50
|
+
* Extra origins ("https://cdn.example.com") that may receive the default credentials
|
|
51
|
+
* (`auth` and default Authorization/Cookie/X-API-Key headers), besides `baseUrl`'s.
|
|
52
|
+
* Absolute URLs to any other origin don't get them. With neither `baseUrl` nor
|
|
53
|
+
* `trustedOrigins` there's nothing to compare against and credentials go everywhere.
|
|
54
|
+
*/
|
|
55
|
+
trustedOrigins?: string[];
|
|
56
|
+
/** Attach a token and, on a 401, refresh it once (shared by all concurrent requests) and replay. */
|
|
57
|
+
auth?: AuthOptions;
|
|
58
|
+
/** In-memory GET cache with TTL, stale-while-revalidate and ETag/Last-Modified revalidation. `true` = 60s TTL. */
|
|
59
|
+
cache?: boolean | CacheOptions;
|
|
60
|
+
/** Merge identical concurrent GET/HEAD calls. Default false. */
|
|
61
|
+
dedupe?: boolean;
|
|
62
|
+
/** Throw HTTPError on non-2xx. Default true. */
|
|
63
|
+
throwHttpErrors?: boolean;
|
|
64
|
+
fetch?: typeof fetch;
|
|
65
|
+
onRequest?: (ctx: FetchHookContext) => void | Promise<void>;
|
|
66
|
+
onResponse?: (ctx: FetchHookContext & { response: Response }) => void | Promise<void>;
|
|
67
|
+
/** One callback for everything that happens. Errors thrown by the handler are ignored. */
|
|
68
|
+
onEvent?: (event: FetchEvent) => void | Promise<void>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface ReqkitRequestInit extends Omit<RequestInit, "headers" | "cache"> {
|
|
72
|
+
headers?: HeadersInit;
|
|
73
|
+
query?: Record<string, unknown>;
|
|
74
|
+
/** Serialized as the body with a JSON content-type. */
|
|
75
|
+
json?: unknown;
|
|
76
|
+
timeout?: number | false;
|
|
77
|
+
retry?: false | CreateFetchRetryOptions;
|
|
78
|
+
throwHttpErrors?: boolean;
|
|
79
|
+
/** `false`/"no-store" bypass the cache; "reload" skips the read but refreshes the entry. */
|
|
80
|
+
cache?: false | "no-store" | "reload";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface ReqkitFetch {
|
|
84
|
+
(input: string, init?: ReqkitRequestInit): Promise<Response>;
|
|
85
|
+
/** The `baseUrl` this instance was created with. */
|
|
86
|
+
readonly baseUrl: string | undefined;
|
|
87
|
+
/** Resolves to the parsed JSON body (null for an empty body). */
|
|
88
|
+
json<T = unknown>(input: string, init?: ReqkitRequestInit): Promise<T>;
|
|
89
|
+
/** New instance with these defaults merged over the current ones. */
|
|
90
|
+
extend(overrides?: CreateFetchDefaults): ReqkitFetch;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function createFetch(defaults?: CreateFetchDefaults): ReqkitFetch;
|
|
94
|
+
|
|
95
|
+
export interface CacheStore {
|
|
96
|
+
get(key: string): any | Promise<any>;
|
|
97
|
+
set(key: string, value: any): void | Promise<void>;
|
|
98
|
+
delete(key: string): any;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface MemoryCache extends CacheStore {
|
|
102
|
+
clear(): void;
|
|
103
|
+
readonly size: number;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function createMemoryCache(opts?: { maxEntries?: number }): MemoryCache;
|
|
107
|
+
|
|
108
|
+
export interface CacheEvent {
|
|
109
|
+
type: "hit" | "stale" | "revalidated" | "miss" | "store" | "stale-if-error";
|
|
110
|
+
url: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface CacheOptions {
|
|
114
|
+
/** How long an entry is served without asking the server, in ms. Default 60000. */
|
|
115
|
+
ttl?: number;
|
|
116
|
+
/** After the TTL, keep serving the stale copy for this long while refreshing in the background. Default 0. */
|
|
117
|
+
staleWhileRevalidate?: number;
|
|
118
|
+
/**
|
|
119
|
+
* How long past the TTL a cached copy may still be served when the origin is failing
|
|
120
|
+
* (network error, timeout, open circuit, or a 5xx). 4xx responses and aborts are never masked. Default 0 (off).
|
|
121
|
+
*/
|
|
122
|
+
staleIfError?: number;
|
|
123
|
+
/** For the built-in memory store. Default 100. */
|
|
124
|
+
maxEntries?: number;
|
|
125
|
+
/** Responses larger than this aren't cached. Default 1 MiB. */
|
|
126
|
+
maxBodyBytes?: number;
|
|
127
|
+
/** Bring your own store (Redis, IndexedDB, a Map...). */
|
|
128
|
+
store?: CacheStore;
|
|
129
|
+
onEvent?: (event: CacheEvent) => void;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface AuthOptions {
|
|
133
|
+
/** The current token (sync or async). Falsy = send no auth header. */
|
|
134
|
+
getToken(): string | null | undefined | Promise<string | null | undefined>;
|
|
135
|
+
/**
|
|
136
|
+
* Called ONCE when requests are rejected (401 by default), no matter how many
|
|
137
|
+
* are waiting. Return the new token, or update your store and return nothing
|
|
138
|
+
* (reqkit then calls getToken() again). Throwing rejects every waiting request.
|
|
139
|
+
*/
|
|
140
|
+
refresh(ctx: { token: string; response: Response }): string | void | undefined | Promise<string | void | undefined>;
|
|
141
|
+
/** Default "authorization". */
|
|
142
|
+
header?: string;
|
|
143
|
+
/** Default "Bearer". Use "" to send the bare token (e.g. for an X-API-Key header). */
|
|
144
|
+
scheme?: string;
|
|
145
|
+
/** Default: `res.status === 401`. */
|
|
146
|
+
shouldRefresh?: (response: Response) => boolean;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Events delivered to `createFetch({ onEvent })`. */
|
|
150
|
+
export type FetchEvent =
|
|
151
|
+
| { type: "request"; url: string; method: string }
|
|
152
|
+
| { type: "attempt"; url: string; method: string; /** 1-based */ attempt: number }
|
|
153
|
+
| {
|
|
154
|
+
type: "retry";
|
|
155
|
+
url: string;
|
|
156
|
+
method: string;
|
|
157
|
+
/** The attempt that failed (1-based). */
|
|
158
|
+
attempt: number;
|
|
159
|
+
delayMs: number;
|
|
160
|
+
reason: "error" | "status";
|
|
161
|
+
status?: number;
|
|
162
|
+
error?: unknown;
|
|
163
|
+
}
|
|
164
|
+
| {
|
|
165
|
+
type: "response";
|
|
166
|
+
url: string;
|
|
167
|
+
method: string;
|
|
168
|
+
status: number;
|
|
169
|
+
ok: boolean;
|
|
170
|
+
durationMs: number;
|
|
171
|
+
/** Network attempts made (0 for a cache hit). */
|
|
172
|
+
attempts: number;
|
|
173
|
+
/** How the cache was involved, if it was. */
|
|
174
|
+
cache?: "hit" | "stale" | "revalidated" | "miss" | "stale-if-error";
|
|
175
|
+
}
|
|
176
|
+
| { type: "error"; url?: string; method?: string; error: unknown; durationMs: number; attempts: number }
|
|
177
|
+
| { type: "cache"; url: string; method: "GET"; event: CacheEvent["type"] }
|
|
178
|
+
| { type: "ratelimit"; url: string; method: string; waitedMs: number }
|
|
179
|
+
| { type: "circuit"; origin: string; state: CircuitState; previous: CircuitState }
|
|
180
|
+
| { type: "auth"; url: string; method: string; outcome: "refreshed" | "reused" | "failed"; error?: unknown };
|