tanstack-cacher 1.0.0 → 1.2.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 +1 -512
- package/dist/index.d.mts +46 -2
- package/dist/index.d.ts +46 -2
- package/dist/index.js +136 -0
- package/dist/index.mjs +132 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,518 +1,7 @@
|
|
|
1
|
-
# React Query Cache Manager
|
|
2
|
-
|
|
3
1
|
[](https://www.npmjs.com/package/tanstack-cacher)
|
|
4
2
|
[](https://opensource.org/licenses/MIT)
|
|
5
3
|
[](https://www.typescriptlang.org/)
|
|
6
4
|
|
|
7
5
|
## Introduction
|
|
8
6
|
|
|
9
|
-
A robust, TypeScript-first cache manager for React Query that works with **ANY response structure**. Using simple path-based configuration, it handles simple arrays, deeply nested data, paginated responses, and any custom format you throw at it.
|
|
10
|
-
|
|
11
|
-
**Key Features:**
|
|
12
|
-
- Path-based configuration (e.g., `itemsPath: "data.content"`)
|
|
13
|
-
- Automatic handling of missing data and nested structures
|
|
14
|
-
- Built-in pagination metadata management
|
|
15
|
-
- Works with any response shape
|
|
16
|
-
- Full TypeScript support with type inference
|
|
17
|
-
- Zero dependencies (except React Query)
|
|
18
|
-
|
|
19
|
-
## Installation
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
npm install @your-scope/tanstack-cacher
|
|
23
|
-
# or
|
|
24
|
-
yarn add @your-scope/tanstack-cacher
|
|
25
|
-
# or
|
|
26
|
-
pnpm add @your-scope/tanstack-cacher
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
## Quick Start
|
|
30
|
-
|
|
31
|
-
### 1. Simple Array Response
|
|
32
|
-
|
|
33
|
-
```typescript
|
|
34
|
-
import { QueryCacheManager } from '@your-scope/tanstack-cacher';
|
|
35
|
-
import { useQueryClient, useMutation } from '@tanstack/react-query';
|
|
36
|
-
|
|
37
|
-
interface Todo {
|
|
38
|
-
id: string;
|
|
39
|
-
title: string;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// API returns: Todo[]
|
|
43
|
-
function TodoList() {
|
|
44
|
-
const queryClient = useQueryClient();
|
|
45
|
-
|
|
46
|
-
const cache = new QueryCacheManager<Todo[], Todo>({
|
|
47
|
-
queryClient,
|
|
48
|
-
queryKey: ['todos'],
|
|
49
|
-
itemsPath: '', // Empty string = data IS the array
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
const createMutation = useMutation({
|
|
53
|
-
mutationFn: createTodo,
|
|
54
|
-
onMutate: (newTodo) => cache.add(newTodo),
|
|
55
|
-
onError: () => cache.invalidate(),
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
const updateMutation = useMutation({
|
|
59
|
-
mutationFn: updateTodo,
|
|
60
|
-
onMutate: (todo) => cache.update(todo),
|
|
61
|
-
onError: () => cache.invalidate(),
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
const deleteMutation = useMutation({
|
|
65
|
-
mutationFn: deleteTodo,
|
|
66
|
-
onMutate: (id) => cache.delete(id),
|
|
67
|
-
onError: () => cache.invalidate(),
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
### 2. Nested Response
|
|
73
|
-
|
|
74
|
-
```typescript
|
|
75
|
-
interface ApiResponse {
|
|
76
|
-
success: true;
|
|
77
|
-
data: {
|
|
78
|
-
items: User[];
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const cache = new QueryCacheManager<ApiResponse, User>({
|
|
83
|
-
queryClient,
|
|
84
|
-
queryKey: ['users'],
|
|
85
|
-
itemsPath: 'data.items', // Path to the array
|
|
86
|
-
});
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
### 3. Paginated Response (Automatic Metadata Updates!)
|
|
90
|
-
|
|
91
|
-
```typescript
|
|
92
|
-
interface PaginatedResponse {
|
|
93
|
-
content: User[];
|
|
94
|
-
page: {
|
|
95
|
-
number: number;
|
|
96
|
-
size: number;
|
|
97
|
-
totalElements: number;
|
|
98
|
-
totalPages: number;
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const cache = new QueryCacheManager<PaginatedResponse, User>({
|
|
103
|
-
queryClient,
|
|
104
|
-
queryKey: ['users', { page: 0 }],
|
|
105
|
-
itemsPath: 'content', // Path to items
|
|
106
|
-
pagination: {
|
|
107
|
-
totalElementsPath: 'page.totalElements', // Auto-updates on add/delete
|
|
108
|
-
totalPagesPath: 'page.totalPages', // Auto-recalculated on add/delete
|
|
109
|
-
currentPagePath: 'page.number',
|
|
110
|
-
pageSizePath: 'page.size', // Required for totalPages calculation
|
|
111
|
-
},
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
// Now when you add/delete, totalElements AND totalPages update automatically!
|
|
115
|
-
cache.add(newUser); // totalElements++, totalPages recalculated
|
|
116
|
-
cache.delete(userId); // totalElements--, totalPages recalculated
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
### 4. Different Pagination Format
|
|
120
|
-
|
|
121
|
-
```typescript
|
|
122
|
-
interface Response {
|
|
123
|
-
items: Product[];
|
|
124
|
-
meta: {
|
|
125
|
-
total: number;
|
|
126
|
-
currentPage: number;
|
|
127
|
-
totalPages: number;
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const cache = new QueryCacheManager<Response, Product>({
|
|
132
|
-
queryClient,
|
|
133
|
-
queryKey: ['products'],
|
|
134
|
-
itemsPath: 'items',
|
|
135
|
-
pagination: {
|
|
136
|
-
totalElementsPath: 'meta.total',
|
|
137
|
-
totalPagesPath: 'meta.totalPages',
|
|
138
|
-
currentPagePath: 'meta.currentPage',
|
|
139
|
-
pageSizePath: 'meta.pageSize', // For auto totalPages calculation
|
|
140
|
-
},
|
|
141
|
-
});
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
### 5. Deeply Nested Response
|
|
145
|
-
|
|
146
|
-
```typescript
|
|
147
|
-
interface ComplexResponse {
|
|
148
|
-
status: 'ok';
|
|
149
|
-
result: {
|
|
150
|
-
data: {
|
|
151
|
-
users: User[];
|
|
152
|
-
};
|
|
153
|
-
metadata: {
|
|
154
|
-
count: number;
|
|
155
|
-
};
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const cache = new QueryCacheManager<ComplexResponse, User>({
|
|
160
|
-
queryClient,
|
|
161
|
-
queryKey: ['users'],
|
|
162
|
-
itemsPath: 'result.data.users', // Navigate with dots
|
|
163
|
-
pagination: {
|
|
164
|
-
totalElementsPath: 'result.metadata.count',
|
|
165
|
-
},
|
|
166
|
-
});
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
## API Reference
|
|
170
|
-
|
|
171
|
-
### Constructor
|
|
172
|
-
|
|
173
|
-
```typescript
|
|
174
|
-
new QueryCacheManager<TData, TItem>(config)
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
**Config Options:**
|
|
178
|
-
|
|
179
|
-
| Option | Type | Required | Description |
|
|
180
|
-
|--------|------|----------|-------------|
|
|
181
|
-
| `queryClient` | `QueryClient` | Yes | React Query client instance |
|
|
182
|
-
| `queryKey` | `QueryKey` | Yes | Query key for the cache |
|
|
183
|
-
| `itemsPath` | `string` | Yes | Dot-separated path to items array (empty string if data IS array) |
|
|
184
|
-
| `pagination` | `PaginationConfig` | No | Pagination metadata paths |
|
|
185
|
-
| `keyExtractor` | `(item: TItem) => string \| number` | No | Extract unique ID (default: `item.id`) |
|
|
186
|
-
| `initialData` | `TData` | No | Initial structure when cache is empty |
|
|
187
|
-
|
|
188
|
-
**PaginationConfig:**
|
|
189
|
-
|
|
190
|
-
| Option | Type | Description |
|
|
191
|
-
|--------|------|-------------|
|
|
192
|
-
| `totalElementsPath` | `string` | Path to total count (auto-updated on add/delete) |
|
|
193
|
-
| `totalPagesPath` | `string` | Path to total pages (auto-recalculated when pageSizePath provided) |
|
|
194
|
-
| `currentPagePath` | `string` | Path to current page number |
|
|
195
|
-
| `pageSizePath` | `string` | Path to page size (required for automatic totalPages recalculation) |
|
|
196
|
-
|
|
197
|
-
### Methods
|
|
198
|
-
|
|
199
|
-
#### `add(newItem, position?)`
|
|
200
|
-
Add new item to cache
|
|
201
|
-
```typescript
|
|
202
|
-
cache.add({ id: '1', name: 'John' }); // Adds at start (default)
|
|
203
|
-
cache.add({ id: '2', name: 'Jane' }, 'end'); // Adds at end
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
#### `update(updatedItem, matcher?)`
|
|
207
|
-
Update existing item
|
|
208
|
-
```typescript
|
|
209
|
-
// Update by ID (default)
|
|
210
|
-
cache.update({ id: '1', name: 'Johnny' });
|
|
211
|
-
|
|
212
|
-
// Custom matcher
|
|
213
|
-
cache.update(
|
|
214
|
-
{ status: 'active' },
|
|
215
|
-
(item) => item.email === 'user@example.com'
|
|
216
|
-
);
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
#### `delete(itemOrId, matcher?)`
|
|
220
|
-
Remove item from cache
|
|
221
|
-
```typescript
|
|
222
|
-
// Delete by ID
|
|
223
|
-
cache.delete('1');
|
|
224
|
-
|
|
225
|
-
// Delete by object
|
|
226
|
-
cache.delete({ id: '1', name: 'John' });
|
|
227
|
-
|
|
228
|
-
// Custom matcher
|
|
229
|
-
cache.delete(null, (item) => item.isDeleted);
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
#### `replace(newData)`
|
|
233
|
-
Replace entire cache data
|
|
234
|
-
```typescript
|
|
235
|
-
cache.replace(newFullData);
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
#### `clear()`
|
|
239
|
-
Clear all items (resets array to empty, updates pagination to 0)
|
|
240
|
-
```typescript
|
|
241
|
-
cache.clear();
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
#### `getItemsFromCache()`
|
|
245
|
-
Get current items array
|
|
246
|
-
```typescript
|
|
247
|
-
const items = cache.getItemsFromCache(); // returns TItem[]
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
#### `getDataFromCache()`
|
|
251
|
-
Get full cache data
|
|
252
|
-
```typescript
|
|
253
|
-
const fullData = cache.getDataFromCache(); // returns TData | undefined
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
#### `invalidate()`
|
|
257
|
-
Trigger refetch from server
|
|
258
|
-
```typescript
|
|
259
|
-
cache.invalidate();
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
#### `createHandlers()`
|
|
263
|
-
Get handlers for mutation callbacks
|
|
264
|
-
```typescript
|
|
265
|
-
const handlers = cache.createHandlers();
|
|
266
|
-
|
|
267
|
-
useMutation({
|
|
268
|
-
mutationFn: createUser,
|
|
269
|
-
onMutate: handlers.onAdd,
|
|
270
|
-
onError: () => cache.invalidate(),
|
|
271
|
-
});
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
## Real-World Example
|
|
275
|
-
|
|
276
|
-
```typescript
|
|
277
|
-
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
278
|
-
import { QueryCacheManager } from '@your-scope/tanstack-cacher';
|
|
279
|
-
|
|
280
|
-
interface Todo {
|
|
281
|
-
id: string;
|
|
282
|
-
title: string;
|
|
283
|
-
completed: boolean;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
function TodoApp() {
|
|
287
|
-
const queryClient = useQueryClient();
|
|
288
|
-
|
|
289
|
-
// Setup cache manager
|
|
290
|
-
const todoCache = new QueryCacheManager<Todo[], Todo>({
|
|
291
|
-
queryClient,
|
|
292
|
-
queryKey: ['todos'],
|
|
293
|
-
itemsPath: '', // Data IS the array
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
// Fetch todos
|
|
297
|
-
const { data: todos } = useQuery({
|
|
298
|
-
queryKey: ['todos'],
|
|
299
|
-
queryFn: () => fetch('/api/todos').then(r => r.json()),
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
// Mutations with optimistic updates
|
|
303
|
-
const createMutation = useMutation({
|
|
304
|
-
mutationFn: async (title: string) => {
|
|
305
|
-
const res = await fetch('/api/todos', {
|
|
306
|
-
method: 'POST',
|
|
307
|
-
body: JSON.stringify({ title }),
|
|
308
|
-
});
|
|
309
|
-
return res.json();
|
|
310
|
-
},
|
|
311
|
-
onMutate: (title) => {
|
|
312
|
-
// Optimistically add with temp ID
|
|
313
|
-
todoCache.add({
|
|
314
|
-
id: `temp-${Date.now()}`,
|
|
315
|
-
title,
|
|
316
|
-
completed: false,
|
|
317
|
-
});
|
|
318
|
-
},
|
|
319
|
-
onError: () => todoCache.invalidate(),
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
const toggleMutation = useMutation({
|
|
323
|
-
mutationFn: async (todo: Todo) => {
|
|
324
|
-
const res = await fetch(`/api/todos/${todo.id}`, {
|
|
325
|
-
method: 'PUT',
|
|
326
|
-
body: JSON.stringify({ ...todo, completed: !todo.completed }),
|
|
327
|
-
});
|
|
328
|
-
return res.json();
|
|
329
|
-
},
|
|
330
|
-
onMutate: (todo) => {
|
|
331
|
-
// Optimistically update
|
|
332
|
-
todoCache.update({ id: todo.id, completed: !todo.completed });
|
|
333
|
-
},
|
|
334
|
-
onError: () => todoCache.invalidate(),
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
const deleteMutation = useMutation({
|
|
338
|
-
mutationFn: (id: string) => fetch(`/api/todos/${id}`, { method: 'DELETE' }),
|
|
339
|
-
onMutate: (id) => {
|
|
340
|
-
// Optimistically remove
|
|
341
|
-
todoCache.delete(id);
|
|
342
|
-
},
|
|
343
|
-
onError: () => todoCache.invalidate(),
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
return (
|
|
347
|
-
<div>
|
|
348
|
-
<input
|
|
349
|
-
onKeyDown={(e) => {
|
|
350
|
-
if (e.key === 'Enter') {
|
|
351
|
-
createMutation.mutate(e.currentTarget.value);
|
|
352
|
-
e.currentTarget.value = '';
|
|
353
|
-
}
|
|
354
|
-
}}
|
|
355
|
-
placeholder="Add todo..."
|
|
356
|
-
/>
|
|
357
|
-
<ul>
|
|
358
|
-
{todos?.map((todo) => (
|
|
359
|
-
<li key={todo.id}>
|
|
360
|
-
<input
|
|
361
|
-
type="checkbox"
|
|
362
|
-
checked={todo.completed}
|
|
363
|
-
onChange={() => toggleMutation.mutate(todo)}
|
|
364
|
-
/>
|
|
365
|
-
<span>{todo.title}</span>
|
|
366
|
-
<button onClick={() => deleteMutation.mutate(todo.id)}>
|
|
367
|
-
Delete
|
|
368
|
-
</button>
|
|
369
|
-
</li>
|
|
370
|
-
))}
|
|
371
|
-
</ul>
|
|
372
|
-
</div>
|
|
373
|
-
);
|
|
374
|
-
}
|
|
375
|
-
```
|
|
376
|
-
|
|
377
|
-
## Next.js Usage
|
|
378
|
-
|
|
379
|
-
Works seamlessly with Next.js App Router:
|
|
380
|
-
|
|
381
|
-
```typescript
|
|
382
|
-
// app/users/page.tsx
|
|
383
|
-
'use client';
|
|
384
|
-
|
|
385
|
-
import { QueryCacheManager } from '@your-scope/tanstack-cacher';
|
|
386
|
-
|
|
387
|
-
export default function UsersPage() {
|
|
388
|
-
const queryClient = useQueryClient();
|
|
389
|
-
|
|
390
|
-
const cache = new QueryCacheManager({
|
|
391
|
-
queryClient,
|
|
392
|
-
queryKey: ['users'],
|
|
393
|
-
itemsPath: 'data.users',
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
// ... rest of component
|
|
397
|
-
}
|
|
398
|
-
```
|
|
399
|
-
|
|
400
|
-
## Advanced Examples
|
|
401
|
-
|
|
402
|
-
### Handling Missing Data
|
|
403
|
-
|
|
404
|
-
The manager automatically creates the structure if data is missing:
|
|
405
|
-
|
|
406
|
-
```typescript
|
|
407
|
-
const cache = new QueryCacheManager<ApiResponse, User>({
|
|
408
|
-
queryClient,
|
|
409
|
-
queryKey: ['users'],
|
|
410
|
-
itemsPath: 'data.content',
|
|
411
|
-
// If cache is empty, this structure is created:
|
|
412
|
-
initialData: {
|
|
413
|
-
data: {
|
|
414
|
-
content: [],
|
|
415
|
-
},
|
|
416
|
-
page: {
|
|
417
|
-
totalElements: 0,
|
|
418
|
-
totalPages: 0,
|
|
419
|
-
},
|
|
420
|
-
},
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
// Even if cache is empty, this works fine!
|
|
424
|
-
cache.add(newUser); // Creates structure + adds item
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
### Custom Key Extractor
|
|
428
|
-
|
|
429
|
-
```typescript
|
|
430
|
-
interface User {
|
|
431
|
-
userId: string; // Not "id"
|
|
432
|
-
name: string;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
const cache = new QueryCacheManager<User[], User>({
|
|
436
|
-
queryClient,
|
|
437
|
-
queryKey: ['users'],
|
|
438
|
-
itemsPath: '',
|
|
439
|
-
keyExtractor: (user) => user.userId, // Custom ID field
|
|
440
|
-
});
|
|
441
|
-
```
|
|
442
|
-
|
|
443
|
-
### Multiple Cache Managers
|
|
444
|
-
|
|
445
|
-
```typescript
|
|
446
|
-
function App() {
|
|
447
|
-
const queryClient = useQueryClient();
|
|
448
|
-
|
|
449
|
-
// One manager per query
|
|
450
|
-
const usersCache = new QueryCacheManager({
|
|
451
|
-
queryClient,
|
|
452
|
-
queryKey: ['users'],
|
|
453
|
-
itemsPath: 'data.users',
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
const postsCache = new QueryCacheManager({
|
|
457
|
-
queryClient,
|
|
458
|
-
queryKey: ['posts'],
|
|
459
|
-
itemsPath: 'posts',
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
// Use independently
|
|
463
|
-
usersCache.add(newUser);
|
|
464
|
-
postsCache.add(newPost);
|
|
465
|
-
}
|
|
466
|
-
```
|
|
467
|
-
|
|
468
|
-
## Best Practices
|
|
469
|
-
|
|
470
|
-
1. **Always handle errors** - Call `invalidate()` on mutation errors to refetch from server
|
|
471
|
-
2. **Use temporary IDs** - For optimistic creates, use `temp-${Date.now()}` or similar
|
|
472
|
-
3. **One manager per query** - Create separate instances for different queries
|
|
473
|
-
4. **Specify paths clearly** - Use dot notation for nested paths: `"data.result.items"`
|
|
474
|
-
5. **Type everything** - Provide TypeScript types for full type safety
|
|
475
|
-
|
|
476
|
-
## Why This Package?
|
|
477
|
-
|
|
478
|
-
Traditional cache managers assume your API structure. This package:
|
|
479
|
-
|
|
480
|
-
- **Works with ANY structure** - Just tell it the path to your data
|
|
481
|
-
- **Handles edge cases** - Missing data, nested objects, pagination metadata
|
|
482
|
-
- **Zero configuration overhead** - No complex setup or boilerplate
|
|
483
|
-
- **Type-safe** - Full TypeScript support with inference
|
|
484
|
-
- **Framework agnostic** - Works anywhere React Query works
|
|
485
|
-
|
|
486
|
-
## Migration from Old API
|
|
487
|
-
|
|
488
|
-
If you were using the old `getItems`/`setItems` approach:
|
|
489
|
-
|
|
490
|
-
```typescript
|
|
491
|
-
// Old way ❌
|
|
492
|
-
const cache = new QueryCacheManager({
|
|
493
|
-
getItems: (data) => data.content,
|
|
494
|
-
setItems: (data, items) => ({ ...data, content: items }),
|
|
495
|
-
onItemsAdd: (data, count) => ({
|
|
496
|
-
...data,
|
|
497
|
-
page: { ...data.page, totalElements: data.page.totalElements + count }
|
|
498
|
-
}),
|
|
499
|
-
});
|
|
500
|
-
|
|
501
|
-
// New way ✅
|
|
502
|
-
const cache = new QueryCacheManager({
|
|
503
|
-
itemsPath: 'content',
|
|
504
|
-
pagination: {
|
|
505
|
-
totalElementsPath: 'page.totalElements',
|
|
506
|
-
},
|
|
507
|
-
});
|
|
508
|
-
```
|
|
509
|
-
|
|
510
|
-
Much simpler and safer!
|
|
511
|
-
|
|
512
|
-
## License
|
|
513
|
-
|
|
514
|
-
MIT
|
|
515
|
-
|
|
516
|
-
## Contributing
|
|
517
|
-
|
|
518
|
-
Issues and PRs welcome!
|
|
7
|
+
A robust, simplified TypeScript-first cache manager for React Query that works with **ANY response structure**. Using simple path-based configuration, it handles simple arrays, deeply nested data, paginated responses, and any custom format you throw at it.
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as _tanstack_react_query from '@tanstack/react-query';
|
|
2
|
+
import { QueryClient, QueryKey, UseQueryOptions, UseMutationOptions, UseQueryResult } from '@tanstack/react-query';
|
|
2
3
|
export * from '@tanstack/react-query';
|
|
3
4
|
|
|
4
5
|
interface PaginationConfig {
|
|
@@ -40,4 +41,47 @@ declare class QueryCacheManager<TData, TItem> {
|
|
|
40
41
|
createHandlers(): CacheHandlers<TItem>;
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
type CacheOptions<TData = any, TItem = any> = Omit<CacheConfig<TData, TItem>, 'queryClient' | 'queryKey'>;
|
|
45
|
+
|
|
46
|
+
type CustomQueryOptions<TData, TError = unknown> = UseQueryOptions<TData, TError> & {
|
|
47
|
+
queryKey: QueryKey;
|
|
48
|
+
cacheType?: string;
|
|
49
|
+
queryFn: () => Promise<TData>;
|
|
50
|
+
cacheConfig?: CacheOptions;
|
|
51
|
+
};
|
|
52
|
+
type CustomMutationOptions<TData, TError, TVariables, TContext> = UseMutationOptions<TData, TError, TVariables, TContext> & {
|
|
53
|
+
notify?: boolean;
|
|
54
|
+
notifyError?: boolean;
|
|
55
|
+
errorMessage?: string;
|
|
56
|
+
notifySuccess?: boolean;
|
|
57
|
+
successMessage?: string;
|
|
58
|
+
notificationConfig?: NotificationOptions;
|
|
59
|
+
getErrorMessage?: (error: TError) => string;
|
|
60
|
+
};
|
|
61
|
+
interface NotificationOptions {
|
|
62
|
+
duration?: number;
|
|
63
|
+
[key: string]: any;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
declare function useCustomQuery<TData, TError = unknown, TItem = any>(options: CustomQueryOptions<TData, TError>): UseQueryResult<TData, TError> & {
|
|
67
|
+
cache?: QueryCacheManager<TData, TItem>;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
declare const useCustomMutation: <TData, TError, TVariables = void, TContext = unknown>(options: CustomMutationOptions<TData, TError, TVariables, TContext>) => _tanstack_react_query.UseMutationResult<TData, TError, TVariables, TContext>;
|
|
71
|
+
|
|
72
|
+
declare const useQueryCacheManagers: <T extends Record<string, QueryCacheManager<any, any>>>(configs: { [K in keyof T]: {
|
|
73
|
+
queryKey: readonly unknown[];
|
|
74
|
+
options?: Partial<Omit<CacheConfig<any, any>, "queryClient">>;
|
|
75
|
+
}; }) => T;
|
|
76
|
+
|
|
77
|
+
interface NotificationContextType {
|
|
78
|
+
showError: (message: string, options?: NotificationOptions) => void;
|
|
79
|
+
showSuccess: (message: string, options?: NotificationOptions) => void;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
declare const useNotificationContext: () => NotificationContextType;
|
|
83
|
+
|
|
84
|
+
declare const resetCacheManager: (queryKey: QueryKey) => void;
|
|
85
|
+
declare const resetAllCacheManagers: () => void;
|
|
86
|
+
|
|
87
|
+
export { type CacheConfig, type CacheHandlers, type CacheOptions, type CustomMutationOptions, type CustomQueryOptions, type InsertPosition, type PaginationConfig, QueryCacheManager, resetAllCacheManagers, resetCacheManager, useCustomMutation, useCustomQuery, useNotificationContext, useQueryCacheManagers };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as _tanstack_react_query from '@tanstack/react-query';
|
|
2
|
+
import { QueryClient, QueryKey, UseQueryOptions, UseMutationOptions, UseQueryResult } from '@tanstack/react-query';
|
|
2
3
|
export * from '@tanstack/react-query';
|
|
3
4
|
|
|
4
5
|
interface PaginationConfig {
|
|
@@ -40,4 +41,47 @@ declare class QueryCacheManager<TData, TItem> {
|
|
|
40
41
|
createHandlers(): CacheHandlers<TItem>;
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
type CacheOptions<TData = any, TItem = any> = Omit<CacheConfig<TData, TItem>, 'queryClient' | 'queryKey'>;
|
|
45
|
+
|
|
46
|
+
type CustomQueryOptions<TData, TError = unknown> = UseQueryOptions<TData, TError> & {
|
|
47
|
+
queryKey: QueryKey;
|
|
48
|
+
cacheType?: string;
|
|
49
|
+
queryFn: () => Promise<TData>;
|
|
50
|
+
cacheConfig?: CacheOptions;
|
|
51
|
+
};
|
|
52
|
+
type CustomMutationOptions<TData, TError, TVariables, TContext> = UseMutationOptions<TData, TError, TVariables, TContext> & {
|
|
53
|
+
notify?: boolean;
|
|
54
|
+
notifyError?: boolean;
|
|
55
|
+
errorMessage?: string;
|
|
56
|
+
notifySuccess?: boolean;
|
|
57
|
+
successMessage?: string;
|
|
58
|
+
notificationConfig?: NotificationOptions;
|
|
59
|
+
getErrorMessage?: (error: TError) => string;
|
|
60
|
+
};
|
|
61
|
+
interface NotificationOptions {
|
|
62
|
+
duration?: number;
|
|
63
|
+
[key: string]: any;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
declare function useCustomQuery<TData, TError = unknown, TItem = any>(options: CustomQueryOptions<TData, TError>): UseQueryResult<TData, TError> & {
|
|
67
|
+
cache?: QueryCacheManager<TData, TItem>;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
declare const useCustomMutation: <TData, TError, TVariables = void, TContext = unknown>(options: CustomMutationOptions<TData, TError, TVariables, TContext>) => _tanstack_react_query.UseMutationResult<TData, TError, TVariables, TContext>;
|
|
71
|
+
|
|
72
|
+
declare const useQueryCacheManagers: <T extends Record<string, QueryCacheManager<any, any>>>(configs: { [K in keyof T]: {
|
|
73
|
+
queryKey: readonly unknown[];
|
|
74
|
+
options?: Partial<Omit<CacheConfig<any, any>, "queryClient">>;
|
|
75
|
+
}; }) => T;
|
|
76
|
+
|
|
77
|
+
interface NotificationContextType {
|
|
78
|
+
showError: (message: string, options?: NotificationOptions) => void;
|
|
79
|
+
showSuccess: (message: string, options?: NotificationOptions) => void;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
declare const useNotificationContext: () => NotificationContextType;
|
|
83
|
+
|
|
84
|
+
declare const resetCacheManager: (queryKey: QueryKey) => void;
|
|
85
|
+
declare const resetAllCacheManagers: () => void;
|
|
86
|
+
|
|
87
|
+
export { type CacheConfig, type CacheHandlers, type CacheOptions, type CustomMutationOptions, type CustomQueryOptions, type InsertPosition, type PaginationConfig, QueryCacheManager, resetAllCacheManagers, resetCacheManager, useCustomMutation, useCustomQuery, useNotificationContext, useQueryCacheManagers };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var reactQuery = require('@tanstack/react-query');
|
|
4
|
+
var react = require('react');
|
|
4
5
|
|
|
5
6
|
// src/index.ts
|
|
6
7
|
|
|
@@ -283,7 +284,142 @@ var QueryCacheManager = class {
|
|
|
283
284
|
}
|
|
284
285
|
};
|
|
285
286
|
|
|
287
|
+
// src/utils/cacheRegistry.ts
|
|
288
|
+
var cacheConfigRegistry = /* @__PURE__ */ new Map();
|
|
289
|
+
var cacheRegistry = /* @__PURE__ */ new Map();
|
|
290
|
+
var defaultConfigs = {
|
|
291
|
+
paginated: {
|
|
292
|
+
itemsPath: "data.content",
|
|
293
|
+
pagination: {
|
|
294
|
+
totalElementsPath: "data.page.totalElements",
|
|
295
|
+
totalPagesPath: "data.page.totalPages",
|
|
296
|
+
currentPagePath: "data.page.number",
|
|
297
|
+
pageSizePath: "data.page.size"
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
nonPaginated: {
|
|
301
|
+
itemsPath: "data"
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
Object.entries(defaultConfigs).forEach(([key, config]) => {
|
|
305
|
+
cacheConfigRegistry.set(key, config);
|
|
306
|
+
});
|
|
307
|
+
var getOrCreateCacheManager = (queryKey, queryClient, options) => {
|
|
308
|
+
const key = JSON.stringify(queryKey);
|
|
309
|
+
if (cacheRegistry.has(key)) {
|
|
310
|
+
return cacheRegistry.get(key);
|
|
311
|
+
}
|
|
312
|
+
const cacheType = options?.cacheType;
|
|
313
|
+
const config = options?.cacheConfig || cacheType && cacheConfigRegistry.get(cacheType);
|
|
314
|
+
if (!config) {
|
|
315
|
+
throw new Error(
|
|
316
|
+
`[CacheManager] Unknown cacheType "${cacheType}". Available: ${[
|
|
317
|
+
...cacheConfigRegistry.keys()
|
|
318
|
+
].join(", ")}`
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
const manager = new QueryCacheManager({
|
|
322
|
+
queryClient,
|
|
323
|
+
queryKey,
|
|
324
|
+
...config
|
|
325
|
+
});
|
|
326
|
+
cacheRegistry.set(key, manager);
|
|
327
|
+
return manager;
|
|
328
|
+
};
|
|
329
|
+
var resetCacheManager = (queryKey) => {
|
|
330
|
+
cacheRegistry.delete(JSON.stringify(queryKey));
|
|
331
|
+
};
|
|
332
|
+
var resetAllCacheManagers = () => {
|
|
333
|
+
cacheRegistry.clear();
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// src/hooks/useCustomQuery.ts
|
|
337
|
+
function useCustomQuery(options) {
|
|
338
|
+
const queryClient = reactQuery.useQueryClient();
|
|
339
|
+
const { queryKey, cacheType, cacheConfig, ...rest } = options;
|
|
340
|
+
const queryResult = reactQuery.useQuery({
|
|
341
|
+
queryKey,
|
|
342
|
+
...rest
|
|
343
|
+
});
|
|
344
|
+
let cache;
|
|
345
|
+
if (cacheType) {
|
|
346
|
+
cache = getOrCreateCacheManager(queryKey, queryClient, {
|
|
347
|
+
cacheType,
|
|
348
|
+
cacheConfig
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
return {
|
|
352
|
+
...queryResult,
|
|
353
|
+
cache
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
var NotificationContext = react.createContext(
|
|
357
|
+
void 0
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
// src/hooks/useNotificationContext.ts
|
|
361
|
+
var useNotificationContext = () => {
|
|
362
|
+
const context = react.useContext(NotificationContext);
|
|
363
|
+
if (!context) {
|
|
364
|
+
throw new Error("useNotificationContext must be used within an NotificationProvider");
|
|
365
|
+
}
|
|
366
|
+
return context;
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
// src/hooks/useCustomMutation.ts
|
|
370
|
+
var useCustomMutation = (options) => {
|
|
371
|
+
const {
|
|
372
|
+
onError,
|
|
373
|
+
onSuccess,
|
|
374
|
+
notify = false,
|
|
375
|
+
notifyError = false,
|
|
376
|
+
notifySuccess = false,
|
|
377
|
+
errorMessage = "Operation failed!",
|
|
378
|
+
successMessage = "Operation successfull!",
|
|
379
|
+
notificationConfig = { duration: 2 },
|
|
380
|
+
getErrorMessage,
|
|
381
|
+
...rest
|
|
382
|
+
} = options;
|
|
383
|
+
const { showSuccess, showError } = useNotificationContext();
|
|
384
|
+
return reactQuery.useMutation({
|
|
385
|
+
...rest,
|
|
386
|
+
onSuccess: (data, variables, context, mutation) => {
|
|
387
|
+
if (notify || notifySuccess) {
|
|
388
|
+
showSuccess(successMessage, notificationConfig);
|
|
389
|
+
}
|
|
390
|
+
onSuccess?.(data, variables, context, mutation);
|
|
391
|
+
},
|
|
392
|
+
onError: (apiError, variables, context, mutation) => {
|
|
393
|
+
const message = getErrorMessage ? getErrorMessage(apiError) : apiError?.error?.message ?? errorMessage;
|
|
394
|
+
if (notify || notifyError) {
|
|
395
|
+
showError(message, notificationConfig);
|
|
396
|
+
}
|
|
397
|
+
onError?.(apiError, variables, context, mutation);
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
};
|
|
401
|
+
var useQueryCacheManagers = (configs) => {
|
|
402
|
+
const queryClient = reactQuery.useQueryClient();
|
|
403
|
+
const managers = {};
|
|
404
|
+
Object.entries(configs).forEach(([key, config]) => {
|
|
405
|
+
const options = config.options ?? {};
|
|
406
|
+
managers[key] = new QueryCacheManager({
|
|
407
|
+
queryKey: config.queryKey,
|
|
408
|
+
queryClient,
|
|
409
|
+
...options,
|
|
410
|
+
itemsPath: options.itemsPath ?? ""
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
return managers;
|
|
414
|
+
};
|
|
415
|
+
|
|
286
416
|
exports.QueryCacheManager = QueryCacheManager;
|
|
417
|
+
exports.resetAllCacheManagers = resetAllCacheManagers;
|
|
418
|
+
exports.resetCacheManager = resetCacheManager;
|
|
419
|
+
exports.useCustomMutation = useCustomMutation;
|
|
420
|
+
exports.useCustomQuery = useCustomQuery;
|
|
421
|
+
exports.useNotificationContext = useNotificationContext;
|
|
422
|
+
exports.useQueryCacheManagers = useQueryCacheManagers;
|
|
287
423
|
Object.keys(reactQuery).forEach(function (k) {
|
|
288
424
|
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
289
425
|
enumerable: true,
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { useQueryClient, useQuery, useMutation } from '@tanstack/react-query';
|
|
1
2
|
export * from '@tanstack/react-query';
|
|
3
|
+
import { createContext, useContext } from 'react';
|
|
2
4
|
|
|
3
5
|
// src/index.ts
|
|
4
6
|
|
|
@@ -281,4 +283,133 @@ var QueryCacheManager = class {
|
|
|
281
283
|
}
|
|
282
284
|
};
|
|
283
285
|
|
|
284
|
-
|
|
286
|
+
// src/utils/cacheRegistry.ts
|
|
287
|
+
var cacheConfigRegistry = /* @__PURE__ */ new Map();
|
|
288
|
+
var cacheRegistry = /* @__PURE__ */ new Map();
|
|
289
|
+
var defaultConfigs = {
|
|
290
|
+
paginated: {
|
|
291
|
+
itemsPath: "data.content",
|
|
292
|
+
pagination: {
|
|
293
|
+
totalElementsPath: "data.page.totalElements",
|
|
294
|
+
totalPagesPath: "data.page.totalPages",
|
|
295
|
+
currentPagePath: "data.page.number",
|
|
296
|
+
pageSizePath: "data.page.size"
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
nonPaginated: {
|
|
300
|
+
itemsPath: "data"
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
Object.entries(defaultConfigs).forEach(([key, config]) => {
|
|
304
|
+
cacheConfigRegistry.set(key, config);
|
|
305
|
+
});
|
|
306
|
+
var getOrCreateCacheManager = (queryKey, queryClient, options) => {
|
|
307
|
+
const key = JSON.stringify(queryKey);
|
|
308
|
+
if (cacheRegistry.has(key)) {
|
|
309
|
+
return cacheRegistry.get(key);
|
|
310
|
+
}
|
|
311
|
+
const cacheType = options?.cacheType;
|
|
312
|
+
const config = options?.cacheConfig || cacheType && cacheConfigRegistry.get(cacheType);
|
|
313
|
+
if (!config) {
|
|
314
|
+
throw new Error(
|
|
315
|
+
`[CacheManager] Unknown cacheType "${cacheType}". Available: ${[
|
|
316
|
+
...cacheConfigRegistry.keys()
|
|
317
|
+
].join(", ")}`
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
const manager = new QueryCacheManager({
|
|
321
|
+
queryClient,
|
|
322
|
+
queryKey,
|
|
323
|
+
...config
|
|
324
|
+
});
|
|
325
|
+
cacheRegistry.set(key, manager);
|
|
326
|
+
return manager;
|
|
327
|
+
};
|
|
328
|
+
var resetCacheManager = (queryKey) => {
|
|
329
|
+
cacheRegistry.delete(JSON.stringify(queryKey));
|
|
330
|
+
};
|
|
331
|
+
var resetAllCacheManagers = () => {
|
|
332
|
+
cacheRegistry.clear();
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
// src/hooks/useCustomQuery.ts
|
|
336
|
+
function useCustomQuery(options) {
|
|
337
|
+
const queryClient = useQueryClient();
|
|
338
|
+
const { queryKey, cacheType, cacheConfig, ...rest } = options;
|
|
339
|
+
const queryResult = useQuery({
|
|
340
|
+
queryKey,
|
|
341
|
+
...rest
|
|
342
|
+
});
|
|
343
|
+
let cache;
|
|
344
|
+
if (cacheType) {
|
|
345
|
+
cache = getOrCreateCacheManager(queryKey, queryClient, {
|
|
346
|
+
cacheType,
|
|
347
|
+
cacheConfig
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
return {
|
|
351
|
+
...queryResult,
|
|
352
|
+
cache
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
var NotificationContext = createContext(
|
|
356
|
+
void 0
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
// src/hooks/useNotificationContext.ts
|
|
360
|
+
var useNotificationContext = () => {
|
|
361
|
+
const context = useContext(NotificationContext);
|
|
362
|
+
if (!context) {
|
|
363
|
+
throw new Error("useNotificationContext must be used within an NotificationProvider");
|
|
364
|
+
}
|
|
365
|
+
return context;
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
// src/hooks/useCustomMutation.ts
|
|
369
|
+
var useCustomMutation = (options) => {
|
|
370
|
+
const {
|
|
371
|
+
onError,
|
|
372
|
+
onSuccess,
|
|
373
|
+
notify = false,
|
|
374
|
+
notifyError = false,
|
|
375
|
+
notifySuccess = false,
|
|
376
|
+
errorMessage = "Operation failed!",
|
|
377
|
+
successMessage = "Operation successfull!",
|
|
378
|
+
notificationConfig = { duration: 2 },
|
|
379
|
+
getErrorMessage,
|
|
380
|
+
...rest
|
|
381
|
+
} = options;
|
|
382
|
+
const { showSuccess, showError } = useNotificationContext();
|
|
383
|
+
return useMutation({
|
|
384
|
+
...rest,
|
|
385
|
+
onSuccess: (data, variables, context, mutation) => {
|
|
386
|
+
if (notify || notifySuccess) {
|
|
387
|
+
showSuccess(successMessage, notificationConfig);
|
|
388
|
+
}
|
|
389
|
+
onSuccess?.(data, variables, context, mutation);
|
|
390
|
+
},
|
|
391
|
+
onError: (apiError, variables, context, mutation) => {
|
|
392
|
+
const message = getErrorMessage ? getErrorMessage(apiError) : apiError?.error?.message ?? errorMessage;
|
|
393
|
+
if (notify || notifyError) {
|
|
394
|
+
showError(message, notificationConfig);
|
|
395
|
+
}
|
|
396
|
+
onError?.(apiError, variables, context, mutation);
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
};
|
|
400
|
+
var useQueryCacheManagers = (configs) => {
|
|
401
|
+
const queryClient = useQueryClient();
|
|
402
|
+
const managers = {};
|
|
403
|
+
Object.entries(configs).forEach(([key, config]) => {
|
|
404
|
+
const options = config.options ?? {};
|
|
405
|
+
managers[key] = new QueryCacheManager({
|
|
406
|
+
queryKey: config.queryKey,
|
|
407
|
+
queryClient,
|
|
408
|
+
...options,
|
|
409
|
+
itemsPath: options.itemsPath ?? ""
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
return managers;
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
export { QueryCacheManager, resetAllCacheManagers, resetCacheManager, useCustomMutation, useCustomQuery, useNotificationContext, useQueryCacheManagers };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tanstack-cacher",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "A lightweight cache management utility for TanStack Query that simplifies adding, updating, deleting, and synchronizing cached data",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"web": "http://github.com/hacagahasanli"
|
|
20
20
|
}
|
|
21
21
|
],
|
|
22
|
-
"homepage": "https://github.com/hacagahasanli/tanstack-cacher",
|
|
22
|
+
"homepage": "https://github.com/hacagahasanli/tanstack-cacher#readme",
|
|
23
23
|
"keywords": [
|
|
24
24
|
"react",
|
|
25
25
|
"react-query",
|