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 +273 -0
- package/dist/circuit.d.ts +68 -0
- package/dist/circuit.d.ts.map +1 -0
- package/dist/circuit.js +119 -0
- package/dist/circuit.js.map +1 -0
- package/dist/client.d.ts +12 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +63 -0
- package/dist/client.js.map +1 -0
- package/dist/core.d.ts +29 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +280 -0
- package/dist/core.js.map +1 -0
- package/dist/dedup.d.ts +29 -0
- package/dist/dedup.d.ts.map +1 -0
- package/dist/dedup.js +70 -0
- package/dist/dedup.js.map +1 -0
- package/dist/index.cjs +29 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware.d.ts +31 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +48 -0
- package/dist/middleware.js.map +1 -0
- package/dist/mock.d.ts +30 -0
- package/dist/mock.d.ts.map +1 -0
- package/dist/mock.js +56 -0
- package/dist/mock.js.map +1 -0
- package/dist/retry.d.ts +31 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +51 -0
- package/dist/retry.js.map +1 -0
- package/dist/timeout.d.ts +34 -0
- package/dist/timeout.d.ts.map +1 -0
- package/dist/timeout.js +87 -0
- package/dist/timeout.js.map +1 -0
- package/dist/types.d.ts +214 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +38 -0
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
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

|
|
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"}
|
package/dist/circuit.js
ADDED
|
@@ -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"}
|
package/dist/client.d.ts
ADDED
|
@@ -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"}
|