fetchvault 1.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/README.md ADDED
@@ -0,0 +1,273 @@
1
+ # FetchVault
2
+
3
+ > A tiny, zero-dependency fetch wrapper with retries, timeouts, deduplication, circuit breaking, mocking, and typed results.
4
+
5
+ ![npm version](https://img.shields.io/npm/v/fetchvault)
6
+ ![bundle size <3kb](https://img.shields.io/badge/bundle-%3C3kb-brightgreen)
7
+ ![zero dependencies](https://img.shields.io/badge/dependencies-0-success)
8
+ ![TypeScript](https://img.shields.io/badge/TypeScript-Ready-3178c6)
9
+ ![MIT](https://img.shields.io/badge/license-MIT-blue)
10
+
11
+ ---
12
+
13
+ ## Why FetchVault?
14
+
15
+ FetchVault gives you a safer default for HTTP calls without pulling in a heavy client.
16
+
17
+ - Shielded results: every request resolves to a predictable `{ data, error, statusCode }` shape
18
+ - Lightning fast: tiny footprint with zero runtime dependencies
19
+ - Retry built in: automatic retries for network failures and `5xx` responses
20
+ - Human-friendly timeouts: use values like `"500ms"` or `"5s"`
21
+ - Smart deduplication: concurrent `GET` requests reuse the same in-flight call
22
+ - Circuit breaker protection: repeated upstream failures stop hammering unhealthy services
23
+ - Middleware hooks: intercept and transform requests or responses
24
+ - Built-in mocking: return local mock data without touching the network
25
+ - Schema validation: plug in Zod or any `parse(data)` schema
26
+ - TypeScript-ready: typed results and typed client helpers out of the box
27
+
28
+ ---
29
+
30
+ ## Quick Start
31
+
32
+ ```bash
33
+ npm install fetchvault
34
+ ```
35
+
36
+ ```ts
37
+ import { fetchvault } from 'fetchvault'
38
+
39
+ const { data, error, statusCode } = await fetchvault<{ id: string }>(
40
+ 'https://api.example.com/users/1'
41
+ )
42
+
43
+ if (error) {
44
+ console.error(error.code, error.message)
45
+ } else {
46
+ console.log(statusCode, data.id)
47
+ }
48
+ ```
49
+
50
+ ### Why teams like it
51
+
52
+ - Cleaner async code with fewer hidden failure paths
53
+ - Easier UI and API handling because success and failure share one shape
54
+ - Useful resilience features without adding Axios-sized overhead
55
+ - Good fit for browser apps, Node services, CLIs, and SDKs
56
+
57
+ ---
58
+
59
+ ## The Result Pattern
60
+
61
+ FetchVault always resolves with the same object shape:
62
+
63
+ ```ts
64
+ type VaultResult<T> = {
65
+ data: T | null
66
+ error: VaultError | null
67
+ statusCode: number | null
68
+ }
69
+ ```
70
+
71
+ That means you can handle API calls with a simple `if (error)` branch instead of mixing `try/catch` into normal control flow.
72
+
73
+ Before (Axios):
74
+
75
+ ```ts
76
+ try {
77
+ const response = await axios.get('/users/1')
78
+ console.log(response.data)
79
+ } catch (error) {
80
+ console.error(error)
81
+ }
82
+ ```
83
+
84
+ After (FetchVault):
85
+
86
+ ```ts
87
+ const { data, error } = await fetchvault<{ id: string }>('/users/1')
88
+
89
+ if (error) {
90
+ console.error(error.code, error.message)
91
+ } else {
92
+ console.log(data.id)
93
+ }
94
+ ```
95
+
96
+ `fetchvault()` always returns the same shape, so TypeScript nudges you toward explicit and predictable error handling.
97
+
98
+ ---
99
+
100
+ ## API Reference
101
+
102
+ ### fetchvault(url, options)
103
+
104
+ ```ts
105
+ fetchvault<T, S extends VaultSchema<T> = never>(
106
+ url: string,
107
+ options?: VaultOptions<T, S>
108
+ ): Promise<VaultResult<S extends never ? T : InferSchema<S>>>
109
+ ```
110
+
111
+ `VaultOptions` fields:
112
+
113
+ - `method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'` HTTP method (`GET` default)
114
+ - `headers?: Record<string, string>` request headers
115
+ - `body?: unknown` request body (objects are JSON-serialized)
116
+ - `timeout?: string | number` timeout like `"5s"`, `"500ms"`, or `300`
117
+ - `retries?: number` retries for network and `5xx` failures (default `3`)
118
+ - `schema?: S` structural validator with `parse(data)`
119
+ - `signal?: AbortSignal` external abort signal merged with timeout signal
120
+ - `deduplicate?: boolean` deduplicate concurrent `GET` calls (default `true`)
121
+ - `mock?: VaultMockConfig` bypass network and return mock data
122
+ - `beforeRequest?: VaultRequestHook[]` ordered request middleware
123
+ - `afterResponse?: VaultResponseHook[]` ordered response middleware
124
+
125
+ ### createClient(config)
126
+
127
+ ```ts
128
+ import { createClient } from 'fetchvault'
129
+
130
+ const api = createClient({
131
+ baseURL: 'https://api.example.com',
132
+ headers: { Authorization: 'Bearer token' },
133
+ timeout: '10s',
134
+ retries: 2,
135
+ beforeRequest: [
136
+ async (req) => ({
137
+ ...req,
138
+ headers: { ...req.headers, 'x-client': 'web' }
139
+ })
140
+ ]
141
+ })
142
+
143
+ const result = await api.get<{ id: string }>('/users/1')
144
+ ```
145
+
146
+ ### Features with code examples
147
+
148
+ - Retries with jitter
149
+
150
+ ```ts
151
+ const result = await fetchvault('https://api.example.com/unstable', { retries: 3 })
152
+ ```
153
+
154
+ - Circuit breaker
155
+
156
+ ```ts
157
+ const api = createClient({ baseURL: 'https://upstream.example.com' })
158
+ const result = await api.get('/health')
159
+ ```
160
+
161
+ - Request deduplication
162
+
163
+ ```ts
164
+ const a = fetchvault('https://api.example.com/users')
165
+ const b = fetchvault('https://api.example.com/users')
166
+ const [first, second] = await Promise.all([a, b])
167
+ ```
168
+
169
+ Both calls reuse the same in-flight `GET` request by default.
170
+
171
+ - Human-readable timeouts
172
+
173
+ ```ts
174
+ await fetchvault('https://api.example.com/slow', { timeout: '1.5s' })
175
+ ```
176
+
177
+ - Middleware / interceptors
178
+
179
+ ```ts
180
+ await fetchvault('https://api.example.com/users', {
181
+ beforeRequest: [
182
+ (req) => ({
183
+ ...req,
184
+ headers: { ...req.headers, Authorization: 'Bearer token' }
185
+ })
186
+ ],
187
+ afterResponse: [
188
+ (res) => ({ ...res, data: { payload: res.data } })
189
+ ]
190
+ })
191
+ ```
192
+
193
+ - Built-in mocking
194
+
195
+ ```ts
196
+ await fetchvault('https://api.example.com/users', {
197
+ mock: { data: [{ id: '1' }], status: 200, delay: '200ms' }
198
+ })
199
+ ```
200
+
201
+ - Schema validation with Zod
202
+
203
+ ```ts
204
+ import { z } from 'zod'
205
+
206
+ const User = z.object({ id: z.string() })
207
+ const result = await fetchvault('https://api.example.com/users/1', { schema: User })
208
+ ```
209
+
210
+ Any schema object with a `parse(data)` method works, so you are not locked into one validation library.
211
+
212
+ ---
213
+
214
+ ## Error Codes
215
+
216
+ FetchVault keeps errors machine-readable so your app logic can branch safely.
217
+
218
+ ```ts
219
+ if (error?.code === 'TIMEOUT') {
220
+ showRetryToast()
221
+ }
222
+
223
+ if (error?.code === 'HTTP_ERROR' && statusCode === 401) {
224
+ redirectToLogin()
225
+ }
226
+ ```
227
+
228
+ Common error codes include:
229
+
230
+ - `HTTP_ERROR`
231
+ - `NETWORK_ERROR`
232
+ - `TIMEOUT`
233
+ - `VALIDATION_ERROR`
234
+ - `MIDDLEWARE_ERROR`
235
+ - `CIRCUIT_OPEN`
236
+
237
+ ---
238
+
239
+ ## How It Works
240
+
241
+ ### Circuit Breaker State Machine
242
+
243
+ ```text
244
+ CLOSED --(5 failures)--> OPEN
245
+ ^ |
246
+ | (30 sec)
247
+ | v
248
+ (success) HALF_OPEN
249
+ +------(probe)----------+
250
+ ```
251
+
252
+ ### Retry + Jitter
253
+
254
+ FetchVault retries only network failures and HTTP `5xx` responses.
255
+
256
+ `delay = (2^attempt * 1000ms) + jitter(0..500ms)`
257
+
258
+ Jitter prevents synchronized retries and reduces thundering herd spikes.
259
+
260
+ ---
261
+
262
+ ## Contributing
263
+
264
+ Issues and pull requests are welcome. Before opening a PR, run:
265
+
266
+ ```bash
267
+ npm test
268
+ npm run build
269
+ ```
270
+
271
+ ## License
272
+
273
+ MIT
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Available circuit breaker states.
3
+ *
4
+ * @example
5
+ * const state: VaultCircuitState = 'CLOSED'
6
+ */
7
+ export type VaultCircuitState = 'CLOSED' | 'OPEN' | 'HALF_OPEN';
8
+ /**
9
+ * Circuit breaker that protects unstable upstream services.
10
+ *
11
+ * @example
12
+ * const breaker = new VaultCircuitBreaker()
13
+ * if (breaker.canRequest()) { ... }
14
+ */
15
+ export declare class VaultCircuitBreaker {
16
+ private state;
17
+ private consecutiveFailures;
18
+ private probeInFlight;
19
+ private resetTimer;
20
+ /**
21
+ * Returns the current circuit state.
22
+ *
23
+ * @returns Current state of the breaker.
24
+ * @example
25
+ * const state = breaker.getState()
26
+ */
27
+ getState(): VaultCircuitState;
28
+ /**
29
+ * Determines whether a request is allowed to execute.
30
+ *
31
+ * @returns `true` if request may proceed.
32
+ * @example
33
+ * if (breaker.canRequest()) {
34
+ * // execute request
35
+ * }
36
+ */
37
+ canRequest(): boolean;
38
+ /**
39
+ * Records a successful request and restores healthy state.
40
+ *
41
+ * @returns Nothing.
42
+ * @example
43
+ * breaker.recordSuccess()
44
+ */
45
+ recordSuccess(): void;
46
+ /**
47
+ * Records a failed request and updates state machine transitions.
48
+ *
49
+ * @returns Nothing.
50
+ * @example
51
+ * breaker.recordFailure()
52
+ */
53
+ recordFailure(): void;
54
+ private openCircuit;
55
+ private scheduleHalfOpen;
56
+ private transitionTo;
57
+ }
58
+ /**
59
+ * Gets or creates a circuit breaker for a base URL key.
60
+ *
61
+ * @param registry Circuit map to read/write.
62
+ * @param baseUrl Base URL key.
63
+ * @returns Existing or new `VaultCircuitBreaker`.
64
+ * @example
65
+ * const breaker = getCircuitBreaker(circuitMap, 'https://api.example.com')
66
+ */
67
+ export declare function getCircuitBreaker(registry: Map<string, VaultCircuitBreaker>, baseUrl: string): VaultCircuitBreaker;
68
+ //# sourceMappingURL=circuit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"circuit.d.ts","sourceRoot":"","sources":["../src/circuit.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAA;AAE/D;;;;;;GAMG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,KAAK,CAA8B;IAC3C,OAAO,CAAC,mBAAmB,CAAI;IAC/B,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,UAAU,CAA6C;IAE/D;;;;;;OAMG;IACH,QAAQ,IAAI,iBAAiB;IAI7B;;;;;;;;OAQG;IACH,UAAU,IAAI,OAAO;IAiBrB;;;;;;OAMG;IACH,aAAa,IAAI,IAAI;IAMrB;;;;;;OAMG;IACH,aAAa,IAAI,IAAI;IAarB,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,YAAY;CAOrB;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,EAC1C,OAAO,EAAE,MAAM,GACd,mBAAmB,CASrB"}
@@ -0,0 +1,119 @@
1
+ const MAX_FAILURES = 5;
2
+ const RESET_TIMEOUT_MS = 30000;
3
+ /**
4
+ * Circuit breaker that protects unstable upstream services.
5
+ *
6
+ * @example
7
+ * const breaker = new VaultCircuitBreaker()
8
+ * if (breaker.canRequest()) { ... }
9
+ */
10
+ export class VaultCircuitBreaker {
11
+ constructor() {
12
+ this.state = 'CLOSED';
13
+ this.consecutiveFailures = 0;
14
+ this.probeInFlight = false;
15
+ this.resetTimer = null;
16
+ }
17
+ /**
18
+ * Returns the current circuit state.
19
+ *
20
+ * @returns Current state of the breaker.
21
+ * @example
22
+ * const state = breaker.getState()
23
+ */
24
+ getState() {
25
+ return this.state;
26
+ }
27
+ /**
28
+ * Determines whether a request is allowed to execute.
29
+ *
30
+ * @returns `true` if request may proceed.
31
+ * @example
32
+ * if (breaker.canRequest()) {
33
+ * // execute request
34
+ * }
35
+ */
36
+ canRequest() {
37
+ if (this.state === 'CLOSED') {
38
+ return true;
39
+ }
40
+ if (this.state === 'OPEN') {
41
+ return false;
42
+ }
43
+ if (this.probeInFlight) {
44
+ return false;
45
+ }
46
+ this.probeInFlight = true;
47
+ return true;
48
+ }
49
+ /**
50
+ * Records a successful request and restores healthy state.
51
+ *
52
+ * @returns Nothing.
53
+ * @example
54
+ * breaker.recordSuccess()
55
+ */
56
+ recordSuccess() {
57
+ this.consecutiveFailures = 0;
58
+ this.probeInFlight = false;
59
+ this.transitionTo('CLOSED');
60
+ }
61
+ /**
62
+ * Records a failed request and updates state machine transitions.
63
+ *
64
+ * @returns Nothing.
65
+ * @example
66
+ * breaker.recordFailure()
67
+ */
68
+ recordFailure() {
69
+ if (this.state === 'HALF_OPEN') {
70
+ this.probeInFlight = false;
71
+ this.openCircuit();
72
+ return;
73
+ }
74
+ this.consecutiveFailures += 1;
75
+ if (this.consecutiveFailures >= MAX_FAILURES) {
76
+ this.openCircuit();
77
+ }
78
+ }
79
+ openCircuit() {
80
+ this.transitionTo('OPEN');
81
+ this.probeInFlight = false;
82
+ this.scheduleHalfOpen();
83
+ }
84
+ scheduleHalfOpen() {
85
+ if (this.resetTimer) {
86
+ clearTimeout(this.resetTimer);
87
+ }
88
+ this.resetTimer = setTimeout(() => {
89
+ this.transitionTo('HALF_OPEN');
90
+ this.probeInFlight = false;
91
+ }, RESET_TIMEOUT_MS);
92
+ }
93
+ transitionTo(nextState) {
94
+ this.state = nextState;
95
+ if (nextState === 'CLOSED' && this.resetTimer) {
96
+ clearTimeout(this.resetTimer);
97
+ this.resetTimer = null;
98
+ }
99
+ }
100
+ }
101
+ /**
102
+ * Gets or creates a circuit breaker for a base URL key.
103
+ *
104
+ * @param registry Circuit map to read/write.
105
+ * @param baseUrl Base URL key.
106
+ * @returns Existing or new `VaultCircuitBreaker`.
107
+ * @example
108
+ * const breaker = getCircuitBreaker(circuitMap, 'https://api.example.com')
109
+ */
110
+ export function getCircuitBreaker(registry, baseUrl) {
111
+ const existing = registry.get(baseUrl);
112
+ if (existing) {
113
+ return existing;
114
+ }
115
+ const created = new VaultCircuitBreaker();
116
+ registry.set(baseUrl, created);
117
+ return created;
118
+ }
119
+ //# sourceMappingURL=circuit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"circuit.js","sourceRoot":"","sources":["../src/circuit.ts"],"names":[],"mappings":"AAAA,MAAM,YAAY,GAAG,CAAC,CAAA;AACtB,MAAM,gBAAgB,GAAG,KAAM,CAAA;AAU/B;;;;;;GAMG;AACH,MAAM,OAAO,mBAAmB;IAAhC;QACU,UAAK,GAAsB,QAAQ,CAAA;QACnC,wBAAmB,GAAG,CAAC,CAAA;QACvB,kBAAa,GAAG,KAAK,CAAA;QACrB,eAAU,GAAyC,IAAI,CAAA;IAgGjE,CAAC;IA9FC;;;;;;OAMG;IACH,QAAQ;QACN,OAAO,IAAI,CAAC,KAAK,CAAA;IACnB,CAAC;IAED;;;;;;;;OAQG;IACH,UAAU;QACR,IAAI,IAAI,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC5B,OAAO,IAAI,CAAA;QACb,CAAC;QAED,IAAI,IAAI,CAAC,KAAK,KAAK,MAAM,EAAE,CAAC;YAC1B,OAAO,KAAK,CAAA;QACd,CAAC;QAED,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACvB,OAAO,KAAK,CAAA;QACd,CAAC;QAED,IAAI,CAAC,aAAa,GAAG,IAAI,CAAA;QACzB,OAAO,IAAI,CAAA;IACb,CAAC;IAED;;;;;;OAMG;IACH,aAAa;QACX,IAAI,CAAC,mBAAmB,GAAG,CAAC,CAAA;QAC5B,IAAI,CAAC,aAAa,GAAG,KAAK,CAAA;QAC1B,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAA;IAC7B,CAAC;IAED;;;;;;OAMG;IACH,aAAa;QACX,IAAI,IAAI,CAAC,KAAK,KAAK,WAAW,EAAE,CAAC;YAC/B,IAAI,CAAC,aAAa,GAAG,KAAK,CAAA;YAC1B,IAAI,CAAC,WAAW,EAAE,CAAA;YAClB,OAAM;QACR,CAAC;QAED,IAAI,CAAC,mBAAmB,IAAI,CAAC,CAAA;QAC7B,IAAI,IAAI,CAAC,mBAAmB,IAAI,YAAY,EAAE,CAAC;YAC7C,IAAI,CAAC,WAAW,EAAE,CAAA;QACpB,CAAC;IACH,CAAC;IAEO,WAAW;QACjB,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAA;QACzB,IAAI,CAAC,aAAa,GAAG,KAAK,CAAA;QAC1B,IAAI,CAAC,gBAAgB,EAAE,CAAA;IACzB,CAAC;IAEO,gBAAgB;QACtB,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;QAC/B,CAAC;QAED,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC,GAAG,EAAE;YAChC,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,CAAA;YAC9B,IAAI,CAAC,aAAa,GAAG,KAAK,CAAA;QAC5B,CAAC,EAAE,gBAAgB,CAAC,CAAA;IACtB,CAAC;IAEO,YAAY,CAAC,SAA4B;QAC/C,IAAI,CAAC,KAAK,GAAG,SAAS,CAAA;QACtB,IAAI,SAAS,KAAK,QAAQ,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YAC9C,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;YAC7B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAA;QACxB,CAAC;IACH,CAAC;CACF;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAC/B,QAA0C,EAC1C,OAAe;IAEf,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;IACtC,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,QAAQ,CAAA;IACjB,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,mBAAmB,EAAE,CAAA;IACzC,QAAQ,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;IAC9B,OAAO,OAAO,CAAA;AAChB,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { VaultClient, VaultClientConfig } from './types.js';
2
+ /**
3
+ * Creates a pre-configured FetchVault client instance.
4
+ *
5
+ * @param config Client defaults including base URL, headers, timeout, retries, and hooks.
6
+ * @returns Client with HTTP verb helpers bound to the provided base URL.
7
+ * @example
8
+ * const api = createClient({ baseURL: 'https://api.example.com', headers: { Authorization: 'Bearer token' } })
9
+ */
10
+ export declare function createClient(config: VaultClientConfig): VaultClient;
11
+ export default createClient;
12
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,WAAW,EACX,iBAAiB,EAOlB,MAAM,YAAY,CAAA;AAEnB;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,iBAAiB,GAAG,WAAW,CAuBnE;AAiDD,eAAe,YAAY,CAAA"}
package/dist/client.js ADDED
@@ -0,0 +1,63 @@
1
+ import { createFetchVaultEngine } from './core.js';
2
+ /**
3
+ * Creates a pre-configured FetchVault client instance.
4
+ *
5
+ * @param config Client defaults including base URL, headers, timeout, retries, and hooks.
6
+ * @returns Client with HTTP verb helpers bound to the provided base URL.
7
+ * @example
8
+ * const api = createClient({ baseURL: 'https://api.example.com', headers: { Authorization: 'Bearer token' } })
9
+ */
10
+ export function createClient(config) {
11
+ const circuitMap = new Map();
12
+ const dedupMap = new Map();
13
+ const engine = createFetchVaultEngine({ circuitMap, dedupMap });
14
+ const methodFactory = (method) => {
15
+ return async function clientMethod(path, options) {
16
+ const mergedOptions = mergeOptions(config, options, method);
17
+ const target = joinUrl(config.baseURL, path);
18
+ return engine(target, mergedOptions);
19
+ };
20
+ };
21
+ return {
22
+ get: methodFactory('GET'),
23
+ post: methodFactory('POST'),
24
+ put: methodFactory('PUT'),
25
+ patch: methodFactory('PATCH'),
26
+ delete: methodFactory('DELETE')
27
+ };
28
+ }
29
+ function mergeOptions(config, options, method) {
30
+ const callOptions = options ?? {};
31
+ return {
32
+ ...callOptions,
33
+ method: callOptions.method ?? method,
34
+ headers: mergeHeaders(config.headers, callOptions.headers),
35
+ timeout: callOptions.timeout ?? config.timeout,
36
+ retries: callOptions.retries ?? config.retries,
37
+ beforeRequest: mergeHooks(config.beforeRequest, callOptions.beforeRequest),
38
+ afterResponse: mergeHooks(config.afterResponse, callOptions.afterResponse)
39
+ };
40
+ }
41
+ function mergeHeaders(instanceHeaders, callHeaders) {
42
+ return {
43
+ ...(instanceHeaders ?? {}),
44
+ ...(callHeaders ?? {})
45
+ };
46
+ }
47
+ function mergeHooks(instanceHooks, callHooks) {
48
+ return [...(instanceHooks ?? []), ...(callHooks ?? [])];
49
+ }
50
+ function joinUrl(baseURL, path) {
51
+ if (!path) {
52
+ return baseURL;
53
+ }
54
+ if (baseURL.endsWith('/') && path.startsWith('/')) {
55
+ return `${baseURL.slice(0, -1)}${path}`;
56
+ }
57
+ if (!baseURL.endsWith('/') && !path.startsWith('/')) {
58
+ return `${baseURL}/${path}`;
59
+ }
60
+ return `${baseURL}${path}`;
61
+ }
62
+ export default createClient;
63
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/dist/core.d.ts ADDED
@@ -0,0 +1,29 @@
1
+ import { VaultCircuitBreaker } from './circuit.js';
2
+ import type { InferSchema, VaultOptions, VaultResult, VaultSchema } from './types.js';
3
+ interface EngineContext {
4
+ circuitMap: Map<string, VaultCircuitBreaker>;
5
+ dedupMap: Map<string, Promise<VaultResult<unknown>>>;
6
+ }
7
+ type EngineResult<T, S extends VaultSchema<T> = never> = VaultResult<S extends never ? T : InferSchema<S>>;
8
+ type EngineFunction = <T, S extends VaultSchema<T> = never>(url: string, options?: VaultOptions<T, S>) => Promise<EngineResult<T, S>>;
9
+ /**
10
+ * Creates an isolated FetchVault engine.
11
+ *
12
+ * @param context Optional per-engine circuit and dedup stores.
13
+ * @returns Request function that follows the FetchVault result pattern.
14
+ * @example
15
+ * const engine = createFetchVaultEngine({ circuitMap: new Map(), dedupMap: new Map() })
16
+ */
17
+ export declare function createFetchVaultEngine(context?: Partial<EngineContext>): EngineFunction;
18
+ /**
19
+ * Executes a resilient fetch call using FetchVault defaults.
20
+ *
21
+ * @param url Target URL.
22
+ * @param options Optional request configuration.
23
+ * @returns Discriminated result union with data or structured error.
24
+ * @example
25
+ * const result = await fetchvault<{ id: string }>('/users/1')
26
+ */
27
+ export declare function fetchvault<T, S extends VaultSchema<T> = never>(url: string, options?: VaultOptions<T, S>): Promise<VaultResult<S extends never ? T : InferSchema<S>>>;
28
+ export default fetchvault;
29
+ //# sourceMappingURL=core.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAMrE,OAAO,KAAK,EACV,WAAW,EAEX,YAAY,EAEZ,WAAW,EAEX,WAAW,EACZ,MAAM,YAAY,CAAA;AAKnB,UAAU,aAAa;IACrB,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAA;IAC5C,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;CACrD;AAED,KAAK,YAAY,CAAC,CAAC,EAAE,CAAC,SAAS,WAAW,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,WAAW,CAAC,CAAC,SAAS,KAAK,GAAG,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,CAAA;AAE1G,KAAK,cAAc,GAAG,CAAC,CAAC,EAAE,CAAC,SAAS,WAAW,CAAC,CAAC,CAAC,GAAG,KAAK,EACxD,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,KACzB,OAAO,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;AAchC;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,OAAO,CAAC,aAAa,CAAM,GAAG,cAAc,CAsB3F;AAID;;;;;;;;GAQG;AACH,wBAAsB,UAAU,CAAC,CAAC,EAAE,CAAC,SAAS,WAAW,CAAC,CAAC,CAAC,GAAG,KAAK,EAClE,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,GAC3B,OAAO,CAAC,WAAW,CAAC,CAAC,SAAS,KAAK,GAAG,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAE5D;AAuUD,eAAe,UAAU,CAAA"}