tanstack-cacher 1.0.0 → 1.2.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 CHANGED
@@ -1,518 +1,7 @@
1
- # React Query Cache Manager
2
-
3
1
  [![npm version](https://img.shields.io/npm/v/tanstack-cacher.svg)](https://www.npmjs.com/package/tanstack-cacher)
4
2
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
5
3
  [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](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 { QueryClient, QueryKey } from '@tanstack/react-query';
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
- export { type CacheConfig, type CacheHandlers, type InsertPosition, type PaginationConfig, QueryCacheManager };
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 { QueryClient, QueryKey } from '@tanstack/react-query';
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
- export { type CacheConfig, type CacheHandlers, type InsertPosition, type PaginationConfig, QueryCacheManager };
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
- export { QueryCacheManager };
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.0.0",
3
+ "version": "1.2.0",
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": {