@vielzeug/fetchit 1.0.4 → 1.1.1
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 +511 -0
- package/dist/fetchit.cjs +1 -1
- package/dist/fetchit.cjs.map +1 -1
- package/dist/fetchit.js +344 -164
- package/dist/fetchit.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +140 -66
- package/dist/index.js +4 -6
- package/dist/toolkit/dist/async/retry.cjs +2 -0
- package/dist/toolkit/dist/async/retry.cjs.map +1 -0
- package/dist/toolkit/dist/async/retry.js +25 -0
- package/dist/toolkit/dist/async/retry.js.map +1 -0
- package/dist/toolkit/dist/async/sleep.cjs +2 -0
- package/dist/toolkit/dist/async/sleep.cjs.map +1 -0
- package/dist/toolkit/dist/async/sleep.js +12 -0
- package/dist/toolkit/dist/async/sleep.js.map +1 -0
- package/dist/toolkit/dist/function/assert.cjs +3 -0
- package/dist/toolkit/dist/function/assert.cjs.map +1 -0
- package/dist/toolkit/dist/function/assert.js +12 -0
- package/dist/toolkit/dist/function/assert.js.map +1 -0
- package/dist/toolkit/dist/logit/dist/logit.cjs +2 -0
- package/dist/toolkit/dist/logit/dist/logit.cjs.map +1 -0
- package/dist/toolkit/dist/logit/dist/logit.js +263 -0
- package/dist/toolkit/dist/logit/dist/logit.js.map +1 -0
- package/package.json +3 -2
- package/dist/logit/dist/logit.cjs +0 -2
- package/dist/logit/dist/logit.cjs.map +0 -1
- package/dist/logit/dist/logit.js +0 -180
- package/dist/logit/dist/logit.js.map +0 -1
package/README.md
ADDED
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
# @vielzeug/fetchit
|
|
2
|
+
|
|
3
|
+
Modern, type-safe HTTP client with intelligent caching, request deduplication, and query management for TypeScript.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ✅ **Type-Safe** - Full TypeScript support with automatic type inference
|
|
8
|
+
- ✅ **Zero Dependencies** - Only requires `@vielzeug/toolkit` for retry logic
|
|
9
|
+
- ✅ **Lightweight** - ~3 KB gzipped
|
|
10
|
+
- ✅ **Smart Caching** - TanStack Query-inspired caching with stale-while-revalidate
|
|
11
|
+
- ✅ **Request Deduplication** - Prevents duplicate in-flight requests
|
|
12
|
+
- ✅ **Async Validation** - Built-in retry logic with exponential backoff
|
|
13
|
+
- ✅ **Abort Support** - Cancel requests with AbortController
|
|
14
|
+
- ✅ **Framework Agnostic** - Works anywhere JavaScript runs
|
|
15
|
+
- ✅ **Stable Keys** - Property order doesn't matter for cache matching
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# pnpm
|
|
21
|
+
pnpm add @vielzeug/fetchit
|
|
22
|
+
|
|
23
|
+
# npm
|
|
24
|
+
npm install @vielzeug/fetchit
|
|
25
|
+
|
|
26
|
+
# yarn
|
|
27
|
+
yarn add @vielzeug/fetchit
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
### Simple HTTP Client
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { createHttpClient } from '@vielzeug/fetchit';
|
|
36
|
+
|
|
37
|
+
const http = createHttpClient({
|
|
38
|
+
baseUrl: 'https://api.example.com',
|
|
39
|
+
timeout: 5000,
|
|
40
|
+
headers: { 'Authorization': 'Bearer token' }
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Make requests
|
|
44
|
+
const user = await http.get('/users/1');
|
|
45
|
+
const created = await http.post('/users', {
|
|
46
|
+
body: { name: 'Alice', email: 'alice@example.com' }
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Update headers dynamically
|
|
50
|
+
http.setHeaders({ 'Authorization': 'Bearer new-token' });
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Query Client with Caching
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { createQueryClient, createHttpClient } from '@vielzeug/fetchit';
|
|
57
|
+
|
|
58
|
+
const http = createHttpClient({ baseUrl: 'https://api.example.com' });
|
|
59
|
+
const queryClient = createQueryClient({
|
|
60
|
+
cache: {
|
|
61
|
+
staleTime: 5000, // 5 seconds
|
|
62
|
+
gcTime: 300000 // 5 minutes
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Fetch with caching
|
|
67
|
+
const user = await queryClient.fetch({
|
|
68
|
+
queryKey: ['users', 1],
|
|
69
|
+
queryFn: () => http.get('/users/1'),
|
|
70
|
+
staleTime: 5000,
|
|
71
|
+
retry: 3
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Same request reuses cache
|
|
75
|
+
const sameUser = await queryClient.fetch({
|
|
76
|
+
queryKey: ['users', 1],
|
|
77
|
+
queryFn: () => http.get('/users/1')
|
|
78
|
+
}); // ✅ Returns cached data instantly
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Using Both Together
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { createHttpClient, createQueryClient } from '@vielzeug/fetchit';
|
|
85
|
+
|
|
86
|
+
// Create HTTP client for requests
|
|
87
|
+
const http = createHttpClient({
|
|
88
|
+
baseUrl: 'https://api.example.com',
|
|
89
|
+
headers: { 'Authorization': 'Bearer token' }
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Create query client for caching
|
|
93
|
+
const queryClient = createQueryClient({
|
|
94
|
+
cache: { staleTime: 5000 }
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Use HTTP client for simple requests
|
|
98
|
+
await http.post('/analytics', { body: { event: 'click' } });
|
|
99
|
+
|
|
100
|
+
// Use query client for cached data fetching
|
|
101
|
+
await queryClient.fetch({
|
|
102
|
+
queryKey: ['users'],
|
|
103
|
+
queryFn: () => http.get('/users')
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Mutations with cache invalidation
|
|
107
|
+
await queryClient.mutate({
|
|
108
|
+
mutationFn: (data) => http.post('/users', { body: data }),
|
|
109
|
+
onSuccess: () => queryClient.invalidate(['users'])
|
|
110
|
+
}, { name: 'Charlie' });
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## API Reference
|
|
114
|
+
|
|
115
|
+
### HTTP Client
|
|
116
|
+
|
|
117
|
+
#### `createHttpClient(options)`
|
|
118
|
+
|
|
119
|
+
Creates a simple HTTP client for making requests without caching overhead.
|
|
120
|
+
|
|
121
|
+
**Options:**
|
|
122
|
+
- `baseUrl?: string` - Base URL for all requests
|
|
123
|
+
- `headers?: Record<string, string>` - Default headers
|
|
124
|
+
- `timeout?: number` - Request timeout in milliseconds (default: 30000)
|
|
125
|
+
- `dedupe?: boolean` - Enable request deduplication (default: true)
|
|
126
|
+
- `logger?: (level, msg, meta) => void` - Custom logger function
|
|
127
|
+
|
|
128
|
+
**Methods:**
|
|
129
|
+
- `get(url, config?)` - GET request
|
|
130
|
+
- `post(url, config?)` - POST request
|
|
131
|
+
- `put(url, config?)` - PUT request
|
|
132
|
+
- `patch(url, config?)` - PATCH request
|
|
133
|
+
- `delete(url, config?)` - DELETE request
|
|
134
|
+
- `request(method, url, config?)` - Custom method
|
|
135
|
+
- `setHeaders(headers)` - Update default headers
|
|
136
|
+
- `getHeaders()` - Get current headers
|
|
137
|
+
|
|
138
|
+
**Example:**
|
|
139
|
+
```typescript
|
|
140
|
+
const http = createHttpClient({
|
|
141
|
+
baseUrl: 'https://api.example.com',
|
|
142
|
+
timeout: 5000,
|
|
143
|
+
headers: { 'Authorization': 'Bearer token' }
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// GET request
|
|
147
|
+
const users = await http.get<User[]>('/users', {
|
|
148
|
+
params: { page: 1, limit: 10 }
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// POST with body
|
|
152
|
+
const created = await http.post<User>('/users', {
|
|
153
|
+
body: { name: 'Alice', email: 'alice@example.com' }
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Custom headers per request
|
|
157
|
+
await http.get('/protected', {
|
|
158
|
+
headers: { 'X-Custom-Header': 'value' }
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
### Query Client
|
|
165
|
+
|
|
166
|
+
#### `createQueryClient(options)`
|
|
167
|
+
|
|
168
|
+
Creates a query client with intelligent caching and state management.
|
|
169
|
+
|
|
170
|
+
**Options:**
|
|
171
|
+
- `staleTime?: number` - Time in ms before data is considered stale (default: 0)
|
|
172
|
+
- `gcTime?: number` - Time in ms before unused cache is garbage collected (default: 300000)
|
|
173
|
+
- `cache?: { staleTime?, gcTime? }` - Nested cache configuration
|
|
174
|
+
- `refetch?: { onFocus?, onReconnect? }` - Auto-refetch configuration
|
|
175
|
+
|
|
176
|
+
**Methods:**
|
|
177
|
+
- `fetch(options)` - Fetch data with caching
|
|
178
|
+
- `prefetch(options)` - Prefetch data (swallows errors)
|
|
179
|
+
- `mutate(options, variables)` - Execute mutations
|
|
180
|
+
- `invalidate(queryKey)` - Invalidate cached queries
|
|
181
|
+
- `setData(queryKey, data)` - Manually set cache data
|
|
182
|
+
- `getData(queryKey)` - Get cached data
|
|
183
|
+
- `getState(queryKey)` - Get query state
|
|
184
|
+
- `subscribe(queryKey, listener)` - Subscribe to query changes
|
|
185
|
+
- `unsubscribe(queryKey, listener)` - Unsubscribe from changes
|
|
186
|
+
- `clearCache()` - Clear all cached data
|
|
187
|
+
- `getCacheSize()` - Get number of cached entries
|
|
188
|
+
|
|
189
|
+
**Example:**
|
|
190
|
+
```typescript
|
|
191
|
+
const queryClient = createQueryClient({
|
|
192
|
+
cache: { staleTime: 5000, gcTime: 300000 }
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Fetch with caching
|
|
196
|
+
const user = await queryClient.fetch({
|
|
197
|
+
queryKey: ['users', 1],
|
|
198
|
+
queryFn: () => fetch('/users/1').then(r => r.json()),
|
|
199
|
+
staleTime: 5000,
|
|
200
|
+
retry: 3,
|
|
201
|
+
onSuccess: (data) => console.log('Loaded:', data),
|
|
202
|
+
onError: (err) => console.error('Failed:', err)
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// Subscribe to changes
|
|
206
|
+
const unsubscribe = queryClient.subscribe(['users', 1], (state) => {
|
|
207
|
+
console.log('State:', state.status, state.data);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Manually update cache
|
|
211
|
+
queryClient.setData(['users', 1], (old) => ({
|
|
212
|
+
...old,
|
|
213
|
+
name: 'Updated Name'
|
|
214
|
+
}));
|
|
215
|
+
|
|
216
|
+
// Invalidate cache
|
|
217
|
+
queryClient.invalidate(['users']); // Invalidates all user queries
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Advanced Features
|
|
223
|
+
|
|
224
|
+
### Request Deduplication
|
|
225
|
+
|
|
226
|
+
Automatically prevents duplicate in-flight requests.
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
const http = createHttpClient({ dedupe: true });
|
|
230
|
+
|
|
231
|
+
// These run concurrently but only make ONE request
|
|
232
|
+
const [user1, user2, user3] = await Promise.all([
|
|
233
|
+
http.get('/users/1'),
|
|
234
|
+
http.get('/users/1'),
|
|
235
|
+
http.get('/users/1')
|
|
236
|
+
]);
|
|
237
|
+
|
|
238
|
+
// All three get the same response
|
|
239
|
+
console.log(user1 === user2 && user2 === user3); // true
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Retry Logic
|
|
243
|
+
|
|
244
|
+
Built-in retry with exponential backoff.
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
await queryClient.fetch({
|
|
248
|
+
queryKey: ['users'],
|
|
249
|
+
queryFn: () => fetchUsers(),
|
|
250
|
+
retry: 3, // Retry 3 times (4 attempts total)
|
|
251
|
+
retryDelay: (attempt) => Math.min(1000 * 2 ** attempt, 30000)
|
|
252
|
+
});
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Abort Requests
|
|
256
|
+
|
|
257
|
+
Cancel requests with AbortController.
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
const controller = new AbortController();
|
|
261
|
+
|
|
262
|
+
const promise = http.get('/slow-endpoint', {
|
|
263
|
+
signal: controller.signal
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// Cancel after 1 second
|
|
267
|
+
setTimeout(() => controller.abort(), 1000);
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
await promise;
|
|
271
|
+
} catch (err) {
|
|
272
|
+
console.log('Request aborted');
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Cache Invalidation
|
|
277
|
+
|
|
278
|
+
Smart cache invalidation with prefix matching.
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
// Cache some data
|
|
282
|
+
await queryClient.fetch({
|
|
283
|
+
queryKey: ['users', 1],
|
|
284
|
+
queryFn: () => fetchUser(1)
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
await queryClient.fetch({
|
|
288
|
+
queryKey: ['users', 2],
|
|
289
|
+
queryFn: () => fetchUser(2)
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// Invalidate all user queries
|
|
293
|
+
queryClient.invalidate(['users']);
|
|
294
|
+
|
|
295
|
+
// Or invalidate specific user
|
|
296
|
+
queryClient.invalidate(['users', 1]);
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Stable Query Keys
|
|
300
|
+
|
|
301
|
+
Property order doesn't matter for cache matching.
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
// These are treated as the same query
|
|
305
|
+
const key1 = ['users', { page: 1, filter: 'active' }];
|
|
306
|
+
const key2 = ['users', { filter: 'active', page: 1 }];
|
|
307
|
+
|
|
308
|
+
// Both use the same cache entry
|
|
309
|
+
await queryClient.fetch({ queryKey: key1, queryFn: fetchUsers });
|
|
310
|
+
await queryClient.fetch({ queryKey: key2, queryFn: fetchUsers }); // Uses cache
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Mutations
|
|
314
|
+
|
|
315
|
+
Execute mutations with optimistic updates and cache invalidation.
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
await queryClient.mutate({
|
|
319
|
+
mutationFn: async (data) => {
|
|
320
|
+
return await http.post('/users', { body: data });
|
|
321
|
+
},
|
|
322
|
+
onSuccess: (newUser, variables) => {
|
|
323
|
+
// Update cache optimistically
|
|
324
|
+
queryClient.setData(['users'], (old = []) => [...old, newUser]);
|
|
325
|
+
},
|
|
326
|
+
onError: (error, variables) => {
|
|
327
|
+
console.error('Mutation failed:', error);
|
|
328
|
+
},
|
|
329
|
+
onSettled: (data, error, variables) => {
|
|
330
|
+
// Refetch to ensure consistency
|
|
331
|
+
queryClient.invalidate(['users']);
|
|
332
|
+
}
|
|
333
|
+
}, { name: 'Alice', email: 'alice@example.com' });
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### Subscriptions
|
|
337
|
+
|
|
338
|
+
Subscribe to query state changes.
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
const unsubscribe = queryClient.subscribe(['users', 1], (state) => {
|
|
342
|
+
console.log('Status:', state.status);
|
|
343
|
+
console.log('Data:', state.data);
|
|
344
|
+
console.log('Error:', state.error);
|
|
345
|
+
console.log('Loading:', state.isLoading);
|
|
346
|
+
console.log('Success:', state.isSuccess);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// Later, unsubscribe
|
|
350
|
+
unsubscribe();
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
## TypeScript Support
|
|
356
|
+
|
|
357
|
+
Full TypeScript support with automatic type inference.
|
|
358
|
+
|
|
359
|
+
```typescript
|
|
360
|
+
import { createHttpClient, type Infer } from '@vielzeug/fetchit';
|
|
361
|
+
|
|
362
|
+
interface User {
|
|
363
|
+
id: number;
|
|
364
|
+
name: string;
|
|
365
|
+
email: string;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const http = createHttpClient({ baseUrl: 'https://api.example.com' });
|
|
369
|
+
|
|
370
|
+
// Type inference
|
|
371
|
+
const user = await http.get<User>('/users/1');
|
|
372
|
+
console.log(user.name); // ✅ Type-safe
|
|
373
|
+
|
|
374
|
+
// Mutation types
|
|
375
|
+
await queryClient.mutate<User, { name: string; email: string }>({
|
|
376
|
+
mutationFn: async (vars) => {
|
|
377
|
+
return await http.post<User>('/users', { body: vars });
|
|
378
|
+
},
|
|
379
|
+
onSuccess: (data) => {
|
|
380
|
+
console.log(data.id); // ✅ Type-safe
|
|
381
|
+
}
|
|
382
|
+
}, { name: 'Alice', email: 'alice@example.com' });
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
387
|
+
## Error Handling
|
|
388
|
+
|
|
389
|
+
Custom error class with detailed information.
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
import { HttpError } from '@vielzeug/fetchit';
|
|
393
|
+
|
|
394
|
+
try {
|
|
395
|
+
await http.get('/not-found');
|
|
396
|
+
} catch (err) {
|
|
397
|
+
if (err instanceof HttpError) {
|
|
398
|
+
console.log('URL:', err.url); // '/not-found'
|
|
399
|
+
console.log('Method:', err.method); // 'GET'
|
|
400
|
+
console.log('Status:', err.status); // 404
|
|
401
|
+
console.log('Original:', err.original); // Original error
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
## Comparison with Alternatives
|
|
409
|
+
|
|
410
|
+
| Feature | fetchit | TanStack Query | SWR | axios |
|
|
411
|
+
|---------|---------|----------------|-----|-------|
|
|
412
|
+
| Bundle Size | **~3 KB** | ~15 KB | ~5 KB | ~13 KB |
|
|
413
|
+
| Dependencies | 1 (@vielzeug/toolkit) | 0 | 0 | Many |
|
|
414
|
+
| TypeScript | Native | Native | Good | Good |
|
|
415
|
+
| Caching | ✅ | ✅ | ✅ | ❌ |
|
|
416
|
+
| Request Deduplication | ✅ | ✅ | ✅ | ❌ |
|
|
417
|
+
| Stable Keys | ✅ | ❌ | ❌ | N/A |
|
|
418
|
+
| Retry Logic | ✅ | ✅ | ✅ | ✅ |
|
|
419
|
+
| Framework Agnostic | ✅ | ✅ | ❌ (React) | ✅ |
|
|
420
|
+
| Separate HTTP Client | ✅ | ❌ | ❌ | ✅ |
|
|
421
|
+
| Query Management | ✅ | ✅ | ✅ | ❌ |
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## Best Practices
|
|
426
|
+
|
|
427
|
+
### Use HTTP Client for Simple Requests
|
|
428
|
+
|
|
429
|
+
When you don't need caching, use the HTTP client:
|
|
430
|
+
|
|
431
|
+
```typescript
|
|
432
|
+
const http = createHttpClient({ baseUrl: 'https://api.example.com' });
|
|
433
|
+
|
|
434
|
+
// Simple one-off requests
|
|
435
|
+
await http.post('/analytics/event', { body: { event: 'click' } });
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### Use Query Client for Data Fetching
|
|
439
|
+
|
|
440
|
+
When you need caching and state management:
|
|
441
|
+
|
|
442
|
+
```typescript
|
|
443
|
+
const queryClient = createQueryClient({ cache: { staleTime: 5000 } });
|
|
444
|
+
const http = createHttpClient({ baseUrl: 'https://api.example.com' });
|
|
445
|
+
|
|
446
|
+
// Fetch and cache user data
|
|
447
|
+
await queryClient.fetch({
|
|
448
|
+
queryKey: ['users', userId],
|
|
449
|
+
queryFn: () => http.get(`/users/${userId}`)
|
|
450
|
+
});
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
### Combine Both for Full-Featured Apps
|
|
454
|
+
|
|
455
|
+
Use HTTP client and query client together:
|
|
456
|
+
|
|
457
|
+
```typescript
|
|
458
|
+
const http = createHttpClient({
|
|
459
|
+
baseUrl: 'https://api.example.com',
|
|
460
|
+
headers: { 'Authorization': 'Bearer token' }
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
const queryClient = createQueryClient({
|
|
464
|
+
cache: { staleTime: 5000 }
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
// HTTP client for simple requests
|
|
468
|
+
await http.post('/events', { body: event });
|
|
469
|
+
|
|
470
|
+
// Query client for cached data
|
|
471
|
+
await queryClient.fetch({
|
|
472
|
+
queryKey: ['users'],
|
|
473
|
+
queryFn: () => http.get('/users')
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
// Mutations with cache invalidation
|
|
477
|
+
await queryClient.mutate({
|
|
478
|
+
mutationFn: (data) => http.post('/users', { body: data }),
|
|
479
|
+
onSuccess: () => queryClient.invalidate(['users'])
|
|
480
|
+
}, userData);
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### Optimize Cache Settings
|
|
484
|
+
|
|
485
|
+
```typescript
|
|
486
|
+
const queryClient = createQueryClient({
|
|
487
|
+
cache: {
|
|
488
|
+
staleTime: 5000, // 5 seconds - how long data is fresh
|
|
489
|
+
gcTime: 300000 // 5 minutes - how long to keep unused data
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
---
|
|
495
|
+
|
|
496
|
+
## License
|
|
497
|
+
|
|
498
|
+
MIT
|
|
499
|
+
|
|
500
|
+
## Contributing
|
|
501
|
+
|
|
502
|
+
Contributions are welcome! Please read the [Contributing Guide](https://github.com/helmuthdu/vielzeug/blob/main/CONTRIBUTING.md).
|
|
503
|
+
|
|
504
|
+
## Credits
|
|
505
|
+
|
|
506
|
+
Inspired by [TanStack Query](https://tanstack.com/query) and [SWR](https://swr.vercel.app/).
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
|
package/dist/fetchit.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});require("./toolkit/dist/logit/dist/logit.cjs");const J=require("./toolkit/dist/async/retry.cjs"),V=0,Y=5*6e4,W=3e4,K=3,x=!0,v="application/json",z="content-type";class j extends Error{url;method;status;original;constructor(c,p,a,h,i){super(c),this.name="HttpError",this.url=p,this.method=a,this.status=h,this.original=i}}function _(t){return t instanceof Error?t:new Error(String(t))}function X(t,c,p){const a=(t||"").replace(/\/+$/,""),h=c.replace(/^\/+/,""),i=a?`${a}/${h}`:h;if(!p)return i;const m=Object.entries(p).filter(([,g])=>g!==void 0).map(([g,w])=>`${encodeURIComponent(g)}=${encodeURIComponent(String(w))}`).join("&");return m?`${i}${i.includes("?")?"&":"?"}${m}`:i}function Z(t){return typeof FormData<"u"&&t instanceof FormData||typeof Blob<"u"&&t instanceof Blob||typeof URLSearchParams<"u"&&t instanceof URLSearchParams||typeof t=="string"||t instanceof ArrayBuffer||t&&typeof t=="object"&&Object.prototype.toString.call(t)==="[object ArrayBuffer]"?!0:!!ArrayBuffer.isView?.(t)}function F(t){return t===null?"null":t===void 0?"undefined":typeof t!="object"?JSON.stringify(t):Array.isArray(t)?`[${t.map(a=>F(a)).join(",")}]`:`{${Object.keys(t).sort().map(a=>`${JSON.stringify(a)}:${F(t[a])}`).join(",")}}`}function tt(t){if(t==null)return"null";if(typeof FormData<"u"&&t instanceof FormData)return"[FormData]";if(typeof Blob<"u"&&t instanceof Blob)return`[Blob:${t.size}:${t.type}]`;if(typeof URLSearchParams<"u"&&t instanceof URLSearchParams)return`[URLSearchParams:${t.toString()}]`;if(t instanceof ArrayBuffer)return`[ArrayBuffer:${t.byteLength}]`;if(ArrayBuffer.isView?.(t))return`[ArrayBufferView:${t.byteLength}]`;if(typeof t=="string")return t;if(typeof t=="object")try{return F(t)}catch{return"[Object]"}try{return JSON.stringify(t)}catch{return"[Unknown]"}}function et(t,c){if(t===0||t===Number.POSITIVE_INFINITY){if(c)return{clear:()=>{},signal:c};const i=new AbortController;return{clear:()=>{},signal:i.signal}}if(typeof AbortSignal<"u"&&"timeout"in AbortSignal&&!c){const i=AbortSignal.timeout(t);return{clear:()=>{},signal:i}}const p=new AbortController,a=()=>p.abort();c&&(c.aborted?p.abort():c.addEventListener("abort",a,{once:!0}));const h=setTimeout(()=>p.abort(),t);return{clear:()=>{clearTimeout(h),c&&c.removeEventListener("abort",a)},signal:p.signal}}async function rt(t){if(t.status===204)return;const c=t.headers.get(z)??"";if(c.includes(v))return await t.json();if(c.startsWith("text/"))return await t.text();try{return await t.blob()}catch{return await t.text()}}function nt(t={}){const{baseUrl:c="",headers:p={},timeout:a=W,dedupe:h=x,logger:i}=t;let m={...p};const g=new Map;function w(f,d,C){i&&i(f,d,C)}async function A(f,d,C={}){const T=X(c,d,C.params),E=(f||"GET").toUpperCase(),{body:S,headers:R,dedupe:B,signal:N,...P}=C,$=B!==!1&&h,e=$?JSON.stringify({body:tt(S),full:T,m:E}):"";if($&&g.has(e))return g.get(e);const{signal:n,clear:r}=et(a,N??null),s={method:E,...P,headers:{...m,...R},signal:n};S!==void 0&&!Z(S)?(s.body=JSON.stringify(S),s.headers={[z]:v,...s.headers}):S!==void 0&&(s.body=S);const y=(async()=>{const l=Date.now();try{const u=await fetch(T,s),b=await rt(u);if(w("info",`${E} ${T} - ${u.status} (${Date.now()-l}ms)`,{req:s,res:b}),!u.ok)throw new j("Non-OK response",T,E,u.status,b);return b}catch(u){throw w("error",`${E} ${T} - ERROR`,u),u instanceof j?u:new j(_(u).message,T,E,void 0,u)}finally{r(),$&&g.delete(e)}})();return $&&g.set(e,y),y}return{delete:(f,d)=>A("DELETE",f,d),get:(f,d)=>A("GET",f,d),getHeaders:()=>({...m}),patch:(f,d)=>A("PATCH",f,d),post:(f,d)=>A("POST",f,d),put:(f,d)=>A("PUT",f,d),request:A,setHeaders(f){m=Object.fromEntries(Object.entries({...m,...f}).filter(([,d])=>d!==void 0))}}}function ot(t){const c=t?.cache?.staleTime??t?.staleTime??V,p=t?.cache?.gcTime??t?.gcTime??Y,a=new Map,h=new Map;function i(e){return F(e)}function m(e){const n=i(e);let r=a.get(n);return r||(r={abortController:null,data:void 0,dataUpdatedAt:0,error:null,errorUpdatedAt:0,fetchedAt:0,gcTimer:null,observers:new Set,promise:null,status:"idle"},a.set(n,r),h.set(n,n)),r}function g(e){const n={data:e.data,dataUpdatedAt:e.dataUpdatedAt,error:e.error,errorUpdatedAt:e.errorUpdatedAt,fetchedAt:e.fetchedAt,isError:e.status==="error",isIdle:e.status==="idle",isLoading:e.status==="pending",isSuccess:e.status==="success",status:e.status};e.observers.forEach(r=>{try{r(n)}catch{}})}function w(e,n,r){n.gcTimer&&(clearTimeout(n.gcTimer),n.gcTimer=null),r>0&&(n.gcTimer=setTimeout(()=>{a.delete(e),h.delete(e)},r))}function A(e){e.abortController?.abort(),e.gcTimer&&(clearTimeout(e.gcTimer),e.gcTimer=null)}function f(e,n,r=K){const s=e===!1?1:(e??r)+1;let y,l;return typeof n=="function"?(y=void 0,l=u=>n(u-1)):typeof n=="number"?(y=n,l=void 0):(y=1e3,l=(u,b)=>Math.min(b*2,3e4)),{backoff:l,delay:y,times:s}}async function d(e){const{queryKey:n,queryFn:r,staleTime:s=c,gcTime:y=p,enabled:l=!0,retry:u=K,retryDelay:b,onSuccess:L,onError:I}=e;if(!l)throw new Error("Query disabled");const H=i(n),o=m(n);if(o.status==="success"&&Date.now()-o.dataUpdatedAt<s)return o.data;if(o.promise)return o.promise;const U=new AbortController;o.abortController=U,o.status="pending",g(o);const{times:G,delay:M,backoff:Q}=f(u,b),k=(async()=>{try{const O=await J.retry(()=>r(),{backoff:Q,delay:M,signal:U.signal,times:G}),D=Date.now();o.data=O,o.status="success",o.dataUpdatedAt=D,o.fetchedAt=D,o.error=null,o.promise=null,o.abortController=null,w(H,o,y);try{L?.(O)}catch{}return g(o),O}catch(O){const D=_(O),q=U.signal.aborted||D.name==="AbortError";q?(o.status="idle",o.error=null):(o.status="error",o.error=D,o.errorUpdatedAt=Date.now()),o.promise=null,o.abortController=null;try{q||I?.(D)}catch{}throw g(o),D}})();return o.promise=k,k}async function C(e){return d({...e,enabled:!0}).catch(()=>{})}function T(e){const n=i(e),r=a.get(n);if(r){A(r),a.delete(n),h.delete(n);return}const s=[],y=n.slice(0,-1);for(const[l,u]of h.entries())if(u===n||u.startsWith(`${y},`)){const L=a.get(l);L&&A(L),s.push(l)}for(const l of s)a.delete(l),h.delete(l)}function E(){a.forEach(A),a.clear(),h.clear()}function S(e,n){const r=i(e),s=m(e);s.data=typeof n=="function"?n(s.data):n,s.dataUpdatedAt=Date.now(),s.fetchedAt=s.fetchedAt||Date.now(),s.status="success",w(r,s,p),g(s)}function R(e){const n=i(e);return a.get(n)?.data??void 0}function B(e){const n=i(e),r=a.get(n);return r?{data:r.data,dataUpdatedAt:r.dataUpdatedAt,error:r.error,errorUpdatedAt:r.errorUpdatedAt,fetchedAt:r.fetchedAt,isError:r.status==="error",isIdle:r.status==="idle",isLoading:r.status==="pending",isSuccess:r.status==="success",status:r.status}:null}function N(e,n){const r=m(e);r.observers.add(n);const s={data:r.data,dataUpdatedAt:r.dataUpdatedAt,error:r.error,errorUpdatedAt:r.errorUpdatedAt,fetchedAt:r.fetchedAt,isError:r.status==="error",isIdle:r.status==="idle",isLoading:r.status==="pending",isSuccess:r.status==="success",status:r.status};return n(s),()=>r.observers.delete(n)}function P(e,n){const r=i(e),s=a.get(r);s&&s.observers.delete(n)}async function $(e,n){const{mutationFn:r,onSuccess:s,onError:y,onSettled:l,retry:u=!1,retryDelay:b}=e,{times:L,delay:I,backoff:H}=f(u,b,0);try{const o=await J.retry(()=>r(n),{backoff:H,delay:I,times:L});try{s?.(o,n)}catch{}try{l?.(o,null,n)}catch{}return o}catch(o){const U=_(o);try{y?.(U,n)}catch{}try{l?.(void 0,U,n)}catch{}throw U}}return{clearCache:E,fetch:d,getCacheSize:()=>a.size,getData:R,getState:B,invalidate:T,mutate:$,prefetch:C,setData:S,subscribe:N,unsubscribe:P}}exports.HttpError=j;exports.createHttpClient=nt;exports.createQueryClient=ot;
|
|
2
2
|
//# sourceMappingURL=fetchit.cjs.map
|