swr-catalyst 0.1.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/LICENSE +21 -0
- package/README.md +457 -0
- package/dist/errors/MutationErrors/index.d.ts +120 -0
- package/dist/errors/MutationErrors/types.d.ts +98 -0
- package/dist/errors/index.d.ts +2 -0
- package/dist/hooks/index.d.ts +4 -0
- package/dist/hooks/shared/helpers/applyOptimisticUpdate/index.d.ts +46 -0
- package/dist/hooks/shared/helpers/createMutationError/index.d.ts +46 -0
- package/dist/hooks/shared/helpers/executeMutation/index.d.ts +51 -0
- package/dist/hooks/shared/helpers/index.d.ts +4 -0
- package/dist/hooks/shared/helpers/rollbackOptimisticUpdate/index.d.ts +31 -0
- package/dist/hooks/shared/index.d.ts +1 -0
- package/dist/hooks/useSWRCreate/index.d.ts +89 -0
- package/dist/hooks/useSWRCreate/index.test.d.ts +1 -0
- package/dist/hooks/useSWRCreate/types.d.ts +27 -0
- package/dist/hooks/useSWRDelete/index.d.ts +103 -0
- package/dist/hooks/useSWRDelete/index.test.d.ts +1 -0
- package/dist/hooks/useSWRDelete/types.d.ts +45 -0
- package/dist/hooks/useSWRUpdate/index.d.ts +121 -0
- package/dist/hooks/useSWRUpdate/index.test.d.ts +1 -0
- package/dist/hooks/useSWRUpdate/types.d.ts +41 -0
- package/dist/hooks/useStableKey/index.d.ts +52 -0
- package/dist/hooks/useStableKey/index.test.d.ts +1 -0
- package/dist/hooks/useStableKey/utils/deepEqual/index.d.ts +11 -0
- package/dist/hooks/useStableKey/utils/index.d.ts +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/swr-catalyst.js +465 -0
- package/dist/swr-catalyst.umd.cjs +1 -0
- package/dist/types/index.d.ts +103 -0
- package/dist/utils/extractSWRKey/index.d.ts +40 -0
- package/dist/utils/extractSWRKey/index.test.d.ts +1 -0
- package/dist/utils/index.d.ts +7 -0
- package/dist/utils/mutateByGroup/index.d.ts +51 -0
- package/dist/utils/mutateByGroup/index.test.d.ts +1 -0
- package/dist/utils/mutateById/index.d.ts +50 -0
- package/dist/utils/mutateById/index.test.d.ts +1 -0
- package/dist/utils/resetCache/index.d.ts +48 -0
- package/dist/utils/resetCache/index.test.d.ts +1 -0
- package/dist/utils/swrGetCache/index.d.ts +49 -0
- package/dist/utils/swrGetCache/index.test.d.ts +1 -0
- package/dist/utils/swrMutate/index.d.ts +47 -0
- package/dist/utils/swrMutate/index.test.d.ts +1 -0
- package/dist/utils/to/index.d.ts +49 -0
- package/dist/utils/to/index.test.d.ts +1 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Pedro Barbosa
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
# swr-catalyst
|
|
2
|
+
|
|
3
|
+
<div align="center">
|
|
4
|
+
|
|
5
|
+
**A lightweight, type-safe library for effortless data mutations with SWR**
|
|
6
|
+
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[](https://www.typescriptlang.org/)
|
|
9
|
+
[](https://bundlephobia.com/package/swr-catalyst)
|
|
10
|
+
|
|
11
|
+
[Features](#features) • [Installation](#installation) • [Quick Start](#quick-start) • [API Reference](#api-reference)
|
|
12
|
+
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Why swr-catalyst?
|
|
18
|
+
|
|
19
|
+
SWR excels at data fetching, but mutations (create, update, delete) require significant boilerplate—especially with optimistic updates. **swr-catalyst** eliminates this repetition with declarative hooks that handle:
|
|
20
|
+
|
|
21
|
+
- ✅ **Optimistic UI updates** with automatic rollback
|
|
22
|
+
- ✅ **Loading and error states** out of the box
|
|
23
|
+
- ✅ **Type-safe mutations** with full TypeScript support
|
|
24
|
+
- ✅ **Advanced cache management** utilities
|
|
25
|
+
- ✅ **Zero configuration** required
|
|
26
|
+
|
|
27
|
+
## Features
|
|
28
|
+
|
|
29
|
+
- 🎯 **Three core hooks**: `useSWRCreate`, `useSWRUpdate`, `useSWRDelete`
|
|
30
|
+
- 🚀 **Optimistic updates**: Instant UI feedback with automatic error rollback
|
|
31
|
+
- 📦 **Tiny footprint**: ~3KB minified + gzipped
|
|
32
|
+
- 🔒 **Type-safe**: Full TypeScript support with generics
|
|
33
|
+
- 🛠️ **Cache utilities**: Batch operations with `mutateById`, `mutateByGroup`, `resetCache`
|
|
34
|
+
- 🔑 **Structured keys**: Uses typed `SWRKey` objects for enhanced cache management
|
|
35
|
+
- ⚡ **Smart error handling**: Custom `MutationError` class with helpful context
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install swr-catalyst
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pnpm add swr-catalyst
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
yarn add swr-catalyst
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
> **Note:** This library requires `react` and `swr` as peer dependencies. Make sure you have them installed in your project.
|
|
52
|
+
|
|
53
|
+
## Quick Start
|
|
54
|
+
|
|
55
|
+
### Basic CRUD Operations
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
import { useSWRCreate, useSWRUpdate, useSWRDelete } from 'swr-catalyst';
|
|
59
|
+
|
|
60
|
+
// Create
|
|
61
|
+
const { trigger: createTodo, isMutating, error } = useSWRCreate(
|
|
62
|
+
{ id: 'todos', data: '/api/todos' },
|
|
63
|
+
async (newTodo) => api.post('/todos', newTodo)
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
await createTodo({ title: 'Buy milk' });
|
|
67
|
+
|
|
68
|
+
// Update
|
|
69
|
+
const { trigger: updateTodo } = useSWRUpdate(
|
|
70
|
+
{ id: 'todos', data: '/api/todos' },
|
|
71
|
+
async (id, data) => api.patch(`/todos/${id}`, data)
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
await updateTodo(1, { completed: true });
|
|
75
|
+
|
|
76
|
+
// Delete
|
|
77
|
+
const { trigger: deleteTodo } = useSWRDelete(
|
|
78
|
+
{ id: 'todos', data: '/api/todos' },
|
|
79
|
+
async (id) => api.delete(`/todos/${id}`)
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
await deleteTodo(1);
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Optimistic Updates
|
|
86
|
+
|
|
87
|
+
Make your UI feel instant with optimistic updates:
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
import { useSWRCreate } from 'swr-catalyst';
|
|
91
|
+
|
|
92
|
+
const { trigger: addTodo } = useSWRCreate(
|
|
93
|
+
{ id: 'todos', data: '/api/todos' },
|
|
94
|
+
createTodoAPI,
|
|
95
|
+
{
|
|
96
|
+
optimisticUpdate: (currentTodos, newTodo) => [
|
|
97
|
+
...(currentTodos || []),
|
|
98
|
+
{ ...newTodo, id: `temp-${Date.now()}` }
|
|
99
|
+
],
|
|
100
|
+
rollbackOnError: true // Automatically reverts on failure
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// UI updates immediately, syncs with server in background
|
|
105
|
+
await addTodo({ title: 'New task' });
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Loading and Error States
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
import { useSWRCreate } from 'swr-catalyst';
|
|
112
|
+
|
|
113
|
+
function AddTodoForm() {
|
|
114
|
+
const { trigger: addTodo, isMutating, error } = useSWRCreate(
|
|
115
|
+
{ id: 'todos', data: '/api/todos' },
|
|
116
|
+
createTodoAPI
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
120
|
+
e.preventDefault();
|
|
121
|
+
const formData = new FormData(e.currentTarget);
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
await addTodo({ title: formData.get('title') });
|
|
125
|
+
e.currentTarget.reset();
|
|
126
|
+
} catch (err) {
|
|
127
|
+
// Error state is automatically updated
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<form onSubmit={handleSubmit}>
|
|
133
|
+
<input name="title" type="text" disabled={isMutating} />
|
|
134
|
+
<button type="submit" disabled={isMutating}>
|
|
135
|
+
{isMutating ? 'Adding...' : 'Add Todo'}
|
|
136
|
+
</button>
|
|
137
|
+
{error && <p className="error">Failed to add todo: {error.message}</p>}
|
|
138
|
+
</form>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Error Handling
|
|
144
|
+
|
|
145
|
+
All hooks return a custom `MutationError` when mutations fail. This error class provides rich context and helpful methods for handling failures gracefully.
|
|
146
|
+
|
|
147
|
+
#### Properties
|
|
148
|
+
|
|
149
|
+
- `name`: Always `"MutationError"`
|
|
150
|
+
- `message`: Human-readable error description
|
|
151
|
+
- `context`: Object containing mutation details:
|
|
152
|
+
- `operation`: The type of mutation (`"create"`, `"update"`, or `"delete"`)
|
|
153
|
+
- `key`: The `SWRKey` that was being mutated
|
|
154
|
+
- `data`: The payload data (for create/update operations)
|
|
155
|
+
- `id`: The resource ID (for update/delete operations)
|
|
156
|
+
- `timestamp`: When the error occurred (milliseconds since epoch)
|
|
157
|
+
- `originalError`: The underlying error that caused the failure
|
|
158
|
+
- `stack`: Error stack trace
|
|
159
|
+
|
|
160
|
+
#### Methods
|
|
161
|
+
|
|
162
|
+
**`getUserMessage(): string`**
|
|
163
|
+
|
|
164
|
+
Returns a user-friendly error message suitable for displaying in UI.
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
const error = mutationError.getUserMessage();
|
|
168
|
+
// Returns: "Failed to add todos. Please try again."
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**`isNetworkError(): boolean`**
|
|
172
|
+
|
|
173
|
+
Checks if the error was caused by network-related issues (connection failures, timeouts, etc.).
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
if (error.isNetworkError()) {
|
|
177
|
+
toast.error('Network error. Check your connection and try again.', {
|
|
178
|
+
action: { label: 'Retry', onClick: handleRetry }
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**`isValidationError(): boolean`**
|
|
184
|
+
|
|
185
|
+
Checks if the error was caused by validation failures (invalid data, required fields, etc.).
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
if (error.isValidationError()) {
|
|
189
|
+
// Show validation-specific message
|
|
190
|
+
toast.error('Please check your input and try again.');
|
|
191
|
+
} else {
|
|
192
|
+
// Show generic error with retry option
|
|
193
|
+
toast.error('Something went wrong', { action: 'Retry' });
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**`toJSON(): object`**
|
|
198
|
+
|
|
199
|
+
Serializes the error to a JSON-compatible object for logging and error tracking services.
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
// Send to error tracking service
|
|
203
|
+
Sentry.captureException(mutationError.toJSON());
|
|
204
|
+
|
|
205
|
+
// Log for debugging
|
|
206
|
+
console.error('Mutation failed:', mutationError.toJSON());
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
#### Complete Example
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
import { useSWRCreate, MutationError } from 'swr-catalyst';
|
|
213
|
+
|
|
214
|
+
function TodoForm() {
|
|
215
|
+
const { trigger, error } = useSWRCreate(
|
|
216
|
+
{ id: 'todos', data: '/api/todos' },
|
|
217
|
+
createTodoAPI
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
const handleSubmit = async (data) => {
|
|
221
|
+
try {
|
|
222
|
+
await trigger(data);
|
|
223
|
+
} catch (err) {
|
|
224
|
+
if (err instanceof MutationError) {
|
|
225
|
+
// Use helper methods for better UX
|
|
226
|
+
if (err.isNetworkError()) {
|
|
227
|
+
toast.error('Network error. Please check your connection.');
|
|
228
|
+
} else if (err.isValidationError()) {
|
|
229
|
+
toast.error(err.getUserMessage());
|
|
230
|
+
} else {
|
|
231
|
+
// Generic error handling
|
|
232
|
+
toast.error('Something went wrong. Please try again.');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Log detailed error for debugging
|
|
236
|
+
console.error('Mutation context:', err.context);
|
|
237
|
+
console.error('Original error:', err.originalError);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
// Or use the error state directly
|
|
243
|
+
if (error) {
|
|
244
|
+
return <Alert>{error.getUserMessage()}</Alert>;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return <form onSubmit={handleSubmit}>...</form>;
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Key Structure
|
|
252
|
+
|
|
253
|
+
**Important:** swr-catalyst requires a specific key structure for all hooks and utilities to work properly.
|
|
254
|
+
|
|
255
|
+
### SWRKey Type
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
type SWRKey<T = unknown> = {
|
|
259
|
+
id: string; // Required: Unique identifier for the cache entry
|
|
260
|
+
group?: string; // Optional: Group name for batch operations
|
|
261
|
+
data: T; // Required: The actual SWR key (URL, array, etc.)
|
|
262
|
+
} | null;
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Why Structured Keys?
|
|
266
|
+
|
|
267
|
+
The structured key format enables powerful cache management features:
|
|
268
|
+
|
|
269
|
+
- **`id`**: Allows `mutateById()` to update specific cache entries
|
|
270
|
+
- **`group`**: Enables `mutateByGroup()` to batch-update related caches
|
|
271
|
+
- **`data`**: The actual key passed to SWR's fetcher function
|
|
272
|
+
|
|
273
|
+
## Key Management
|
|
274
|
+
|
|
275
|
+
The library handles key stability internally using deep equality comparison, so you don't need to wrap the key object yourself. The `useStableKey` hook automatically detects when values change, even for complex nested objects.
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
// ✅ Simple primitive value for data
|
|
279
|
+
const { trigger: createTodo } = useSWRCreate(
|
|
280
|
+
{ id: 'todos', data: '/api/todos' },
|
|
281
|
+
createTodoAPI
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
// ✅ Complex object data - no useMemo needed!
|
|
285
|
+
// The library handles deep equality automatically
|
|
286
|
+
const { trigger: createTodo } = useSWRCreate(
|
|
287
|
+
{ id: 'todos', data: { url: '/api/todos', userId: user.id } },
|
|
288
|
+
createTodoAPI
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
// ✅ Even deeply nested objects work automatically
|
|
292
|
+
const { trigger: createTodo } = useSWRCreate(
|
|
293
|
+
{
|
|
294
|
+
id: 'todos',
|
|
295
|
+
data: {
|
|
296
|
+
url: '/api/todos',
|
|
297
|
+
params: { userId: user.id, filter: 'active' }
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
createTodoAPI
|
|
301
|
+
);
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
## API Reference
|
|
305
|
+
|
|
306
|
+
This library exports a set of hooks and utility functions to streamline your data mutation workflow.
|
|
307
|
+
|
|
308
|
+
### Hooks
|
|
309
|
+
|
|
310
|
+
#### `useSWRCreate(key, createFunction, options)`
|
|
311
|
+
|
|
312
|
+
A hook for creating new data. It handles optimistic updates, loading states, and revalidates the cache upon success.
|
|
313
|
+
|
|
314
|
+
* **`key`**: A `SWRKey` object that will be revalidated after creation.
|
|
315
|
+
* **`createFunction`**: An async function `(data) => Promise<NewData>` that performs the API call.
|
|
316
|
+
* **`options`** (optional): Configuration for optimistic updates (`optimisticUpdate`, `rollbackOnError`).
|
|
317
|
+
|
|
318
|
+
**Returns:** `{ trigger, isMutating, error }`
|
|
319
|
+
|
|
320
|
+
#### `useSWRUpdate(key, updateFunction, options)`
|
|
321
|
+
|
|
322
|
+
A hook for updating existing data.
|
|
323
|
+
|
|
324
|
+
* **`key`**: A `SWRKey` object that will be revalidated after the update.
|
|
325
|
+
* **`updateFunction`**: An async function `(id, data) => Promise<UpdatedData>` that performs the API call.
|
|
326
|
+
* **`options`** (optional): Configuration for optimistic updates.
|
|
327
|
+
|
|
328
|
+
**Returns:** `{ trigger, isMutating, error }`
|
|
329
|
+
|
|
330
|
+
#### `useSWRDelete(key, deleteFunction, options)`
|
|
331
|
+
|
|
332
|
+
A hook for deleting data.
|
|
333
|
+
|
|
334
|
+
* **`key`**: A `SWRKey` object that will be revalidated after deletion.
|
|
335
|
+
* **`deleteFunction`**: An async function `(id) => Promise<void>` that performs the API call.
|
|
336
|
+
* **`options`** (optional): Configuration for optimistic updates.
|
|
337
|
+
|
|
338
|
+
**Returns:** `{ trigger, isMutating, error }`
|
|
339
|
+
|
|
340
|
+
#### `useStableKey(key)`
|
|
341
|
+
|
|
342
|
+
A utility hook that memoizes an `SWRKey` using deep equality comparison. This prevents unnecessary re-renders in child components when a key's object reference changes but its values do not. It is used internally by all mutation hooks.
|
|
343
|
+
|
|
344
|
+
* **`key`**: The `SWRKey` object to stabilize.
|
|
345
|
+
|
|
346
|
+
**Returns:** A memoized `SWRKey` with a stable reference.
|
|
347
|
+
|
|
348
|
+
### Utilities
|
|
349
|
+
|
|
350
|
+
#### `mutateById(ids, newData, options)`
|
|
351
|
+
|
|
352
|
+
Mutates all SWR cache entries whose key `id` matches one or more provided IDs.
|
|
353
|
+
|
|
354
|
+
* **`ids`**: A single ID string or an array of IDs.
|
|
355
|
+
* **`newData`** (optional): The new data to set for the matched keys. If omitted, matching keys are revalidated.
|
|
356
|
+
* **`options`** (optional): SWR mutator options (`revalidate`, `populateCache`, etc.).
|
|
357
|
+
|
|
358
|
+
**Example:**
|
|
359
|
+
```typescript
|
|
360
|
+
// Revalidate all caches related to 'user' and 'profile'
|
|
361
|
+
await mutateById(['user', 'profile']);
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
#### `mutateByGroup(groups, newData, options)`
|
|
365
|
+
|
|
366
|
+
Mutates all SWR cache entries whose key `group` matches one or more provided group names.
|
|
367
|
+
|
|
368
|
+
* **`groups`**: A single group string or an array of groups.
|
|
369
|
+
* **`newData`** (optional): The new data to set for the matched keys. If omitted, matching keys are revalidated.
|
|
370
|
+
* **`options`** (optional): SWR mutator options.
|
|
371
|
+
|
|
372
|
+
**Example:**
|
|
373
|
+
```typescript
|
|
374
|
+
// Update all caches in the 'user-data' group without revalidating
|
|
375
|
+
await mutateByGroup('user-data', updatedData, { revalidate: false });
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
#### `resetCache(preservedKeys)`
|
|
379
|
+
|
|
380
|
+
Clears the entire SWR cache, with an option to preserve specific entries by their `id`.
|
|
381
|
+
|
|
382
|
+
* **`preservedKeys`** (optional): A single ID string or an array of IDs to exclude from the reset.
|
|
383
|
+
|
|
384
|
+
**Example:**
|
|
385
|
+
```typescript
|
|
386
|
+
// On logout, clear all data except for public content
|
|
387
|
+
await resetCache(['public-posts', 'app-config']);
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
#### `to(promise)`
|
|
391
|
+
|
|
392
|
+
Wraps any promise and converts it into a `[data, error]` tuple, inspired by Go's error handling style. This avoids `try/catch` blocks for cleaner async code.
|
|
393
|
+
|
|
394
|
+
* **Returns:** A promise that always resolves with `[data, null]` on success or `[null, error]` on failure.
|
|
395
|
+
|
|
396
|
+
**Example:**
|
|
397
|
+
```tsx
|
|
398
|
+
import { to } from 'swr-catalyst';
|
|
399
|
+
|
|
400
|
+
const [result, error] = await to(createTodo({ title: 'New item' }));
|
|
401
|
+
|
|
402
|
+
if (error) {
|
|
403
|
+
console.error('Mutation failed:', error);
|
|
404
|
+
}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
#### `swrMutate(mutate, key, data, shouldRevalidate)`
|
|
408
|
+
|
|
409
|
+
A type-safe wrapper around SWR's `mutate` function that correctly handles the structured `SWRKey` object.
|
|
410
|
+
|
|
411
|
+
* **`mutate`**: The `mutate` function from `useSWRConfig()`.
|
|
412
|
+
* **`key`**: The `SWRKey` to mutate.
|
|
413
|
+
* **`data`** (optional): The new data to update the cache with.
|
|
414
|
+
* **`shouldRevalidate`** (optional): Whether to revalidate after mutation.
|
|
415
|
+
|
|
416
|
+
**Example:**
|
|
417
|
+
```typescript
|
|
418
|
+
// Perform an optimistic update without revalidation
|
|
419
|
+
await swrMutate(mutate, key, optimisticData, false);
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
#### `swrGetCache(cache, key)`
|
|
423
|
+
|
|
424
|
+
A type-safe wrapper around SWR's `cache.get()` method that correctly handles the structured `SWRKey` object.
|
|
425
|
+
|
|
426
|
+
* **`cache`**: The `cache` object from `useSWRConfig()`.
|
|
427
|
+
* **`key`**: The `SWRKey` to look up.
|
|
428
|
+
|
|
429
|
+
**Returns:** The cached data or `undefined`.
|
|
430
|
+
|
|
431
|
+
**Example:**
|
|
432
|
+
```typescript
|
|
433
|
+
const { cache } = useSWRConfig();
|
|
434
|
+
const cachedTodos = swrGetCache(cache, { id: 'todos', data: '/api/todos' });
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
## Bundle Size
|
|
438
|
+
|
|
439
|
+
| Package | Minified | Minified + Gzipped |
|
|
440
|
+
|---------|----------|-------------------|
|
|
441
|
+
| swr-catalyst | ~11.7KB | ~3.2KB |
|
|
442
|
+
|
|
443
|
+
Zero dependencies beyond peer dependencies (`react` and `swr`).
|
|
444
|
+
|
|
445
|
+
## Contributing
|
|
446
|
+
|
|
447
|
+
Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details.
|
|
448
|
+
|
|
449
|
+
## License
|
|
450
|
+
|
|
451
|
+
MIT © [Pedro Barbosa](https://github.com/pedroab0)
|
|
452
|
+
|
|
453
|
+
---
|
|
454
|
+
|
|
455
|
+
<div align="center">
|
|
456
|
+
Made with ❤️ for the SWR community
|
|
457
|
+
</div>
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { MutationErrorContext } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Custom error class for mutation operations.
|
|
4
|
+
* Provides consistent error handling with context across all hooks and utilities.
|
|
5
|
+
*/
|
|
6
|
+
export declare class MutationError extends Error {
|
|
7
|
+
readonly name = "MutationError";
|
|
8
|
+
readonly context: MutationErrorContext;
|
|
9
|
+
readonly originalError: unknown;
|
|
10
|
+
/**
|
|
11
|
+
* Creates a new MutationError with context and original error information.
|
|
12
|
+
*
|
|
13
|
+
* @param message - Human-readable error message describing what failed
|
|
14
|
+
* @param context - Contextual information about the mutation operation
|
|
15
|
+
* @param context.operation - Type of mutation: 'create', 'update', or 'delete'
|
|
16
|
+
* @param context.key - The SWR cache key that was being mutated
|
|
17
|
+
* @param context.data - The data that was being sent (for create/update)
|
|
18
|
+
* @param context.id - The ID of the resource (for update/delete)
|
|
19
|
+
* @param context.timestamp - When the error occurred (milliseconds since epoch)
|
|
20
|
+
* @param originalError - The underlying error that caused the mutation to fail
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* throw new MutationError(
|
|
24
|
+
* 'Failed to create todo',
|
|
25
|
+
* {
|
|
26
|
+
* operation: 'create',
|
|
27
|
+
* key: { id: 'todos', data: '/api/todos' },
|
|
28
|
+
* data: { title: 'New todo' },
|
|
29
|
+
* timestamp: Date.now()
|
|
30
|
+
* },
|
|
31
|
+
* new Error('Network request failed')
|
|
32
|
+
* );
|
|
33
|
+
*/
|
|
34
|
+
constructor(message: string, context: MutationErrorContext, originalError: unknown);
|
|
35
|
+
/**
|
|
36
|
+
* Returns a user-friendly error message suitable for displaying in UI.
|
|
37
|
+
*
|
|
38
|
+
* Converts technical operation names to user-friendly verbs and includes
|
|
39
|
+
* the resource name from the cache key.
|
|
40
|
+
*
|
|
41
|
+
* @returns A formatted error message like "Failed to add todos. Please try again."
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* const error = new MutationError(
|
|
45
|
+
* 'Failed to create resource "todos"',
|
|
46
|
+
* { operation: 'create', key: { id: 'todos' }, timestamp: Date.now() },
|
|
47
|
+
* originalErr
|
|
48
|
+
* );
|
|
49
|
+
* console.log(error.getUserMessage()); // "Failed to add todos. Please try again."
|
|
50
|
+
*/
|
|
51
|
+
getUserMessage(): string;
|
|
52
|
+
/**
|
|
53
|
+
* Serializes the error to a JSON-compatible object for logging and monitoring.
|
|
54
|
+
*
|
|
55
|
+
* Useful for sending error details to error tracking services like Sentry,
|
|
56
|
+
* LogRocket, or custom logging systems.
|
|
57
|
+
*
|
|
58
|
+
* @returns An object containing all error details including context and original error
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* const errorData = mutationError.toJSON();
|
|
62
|
+
* Sentry.captureException(errorData);
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* // Returns:
|
|
66
|
+
* // {
|
|
67
|
+
* // name: 'MutationError',
|
|
68
|
+
* // message: 'Failed to create resource "todos"',
|
|
69
|
+
* // context: { operation: 'create', key: {...}, ... },
|
|
70
|
+
* // originalError: { name: 'Error', message: '...', stack: '...' },
|
|
71
|
+
* // stack: '...'
|
|
72
|
+
* // }
|
|
73
|
+
*/
|
|
74
|
+
toJSON(): {
|
|
75
|
+
name: string;
|
|
76
|
+
message: string;
|
|
77
|
+
context: MutationErrorContext;
|
|
78
|
+
originalError: string | {
|
|
79
|
+
name: string;
|
|
80
|
+
message: string;
|
|
81
|
+
stack: string | undefined;
|
|
82
|
+
};
|
|
83
|
+
stack: string | undefined;
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Determines if the error was caused by network-related issues.
|
|
87
|
+
*
|
|
88
|
+
* Checks the original error's name and message for common network error indicators
|
|
89
|
+
* like 'NetworkError', 'fetch', 'network', or 'timeout'.
|
|
90
|
+
*
|
|
91
|
+
* @returns `true` if the error appears to be network-related, `false` otherwise
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* if (error.isNetworkError()) {
|
|
95
|
+
* // Show retry button
|
|
96
|
+
* toast.error('Network error. Please try again.', {
|
|
97
|
+
* action: { label: 'Retry', onClick: handleRetry }
|
|
98
|
+
* });
|
|
99
|
+
* }
|
|
100
|
+
*/
|
|
101
|
+
isNetworkError(): boolean;
|
|
102
|
+
/**
|
|
103
|
+
* Determines if the error was caused by validation failures.
|
|
104
|
+
*
|
|
105
|
+
* Checks the original error's message for common validation error indicators
|
|
106
|
+
* like 'validation', 'invalid', or 'required'.
|
|
107
|
+
*
|
|
108
|
+
* @returns `true` if the error appears to be validation-related, `false` otherwise
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* if (error.isValidationError()) {
|
|
112
|
+
* // Show validation message without retry option
|
|
113
|
+
* toast.error(error.getUserMessage());
|
|
114
|
+
* } else {
|
|
115
|
+
* // Show generic error with retry
|
|
116
|
+
* toast.error('Something went wrong', { action: 'Retry' });
|
|
117
|
+
* }
|
|
118
|
+
*/
|
|
119
|
+
isValidationError(): boolean;
|
|
120
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { SWRKey } from '../../types';
|
|
2
|
+
/**
|
|
3
|
+
* The type of mutation operation that was being performed.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* Used in error context to identify what operation failed and generate appropriate error messages.
|
|
7
|
+
*/
|
|
8
|
+
export type MutationOperation = "create" | "update" | "delete";
|
|
9
|
+
/**
|
|
10
|
+
* Contextual information about a failed mutation operation.
|
|
11
|
+
*
|
|
12
|
+
* This context is attached to every MutationError to provide detailed information
|
|
13
|
+
* about what operation failed, which resource was involved, and when it happened.
|
|
14
|
+
* This data is useful for error tracking, debugging, and user feedback.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* // Context for a failed create operation
|
|
18
|
+
* {
|
|
19
|
+
* operation: 'create',
|
|
20
|
+
* key: { id: 'todos', data: '/api/todos' },
|
|
21
|
+
* data: { title: 'New todo' },
|
|
22
|
+
* timestamp: 1698345600000
|
|
23
|
+
* }
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* // Context for a failed update operation
|
|
27
|
+
* {
|
|
28
|
+
* operation: 'update',
|
|
29
|
+
* key: { id: 'todos', data: '/api/todos' },
|
|
30
|
+
* data: { title: 'Updated title' },
|
|
31
|
+
* id: 123,
|
|
32
|
+
* timestamp: 1698345600000
|
|
33
|
+
* }
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* // Context for a failed delete operation
|
|
37
|
+
* {
|
|
38
|
+
* operation: 'delete',
|
|
39
|
+
* key: { id: 'todos', data: '/api/todos' },
|
|
40
|
+
* id: 123,
|
|
41
|
+
* timestamp: 1698345600000
|
|
42
|
+
* }
|
|
43
|
+
*/
|
|
44
|
+
export type MutationErrorContext = {
|
|
45
|
+
/**
|
|
46
|
+
* The type of mutation that failed: 'create', 'update', or 'delete'.
|
|
47
|
+
*/
|
|
48
|
+
operation: MutationOperation;
|
|
49
|
+
/**
|
|
50
|
+
* The structured SWR cache key that was being mutated.
|
|
51
|
+
* Contains the resource ID, optional group, and data endpoint.
|
|
52
|
+
* Can be null if the error occurred outside of a specific cache context.
|
|
53
|
+
*/
|
|
54
|
+
key: SWRKey | null;
|
|
55
|
+
/**
|
|
56
|
+
* The data that was being sent to the server.
|
|
57
|
+
* Present for 'create' and 'update' operations.
|
|
58
|
+
*
|
|
59
|
+
* @example { title: 'New todo', completed: false }
|
|
60
|
+
*/
|
|
61
|
+
data?: unknown;
|
|
62
|
+
/**
|
|
63
|
+
* The ID of the resource being modified.
|
|
64
|
+
* Present for 'update' and 'delete' operations.
|
|
65
|
+
*
|
|
66
|
+
* @example 123, "todo-abc", "user-456"
|
|
67
|
+
*/
|
|
68
|
+
id?: string | number;
|
|
69
|
+
/**
|
|
70
|
+
* Timestamp (milliseconds since Unix epoch) when the error occurred.
|
|
71
|
+
* Useful for error tracking and debugging timing-related issues.
|
|
72
|
+
*
|
|
73
|
+
* @example 1698345600000
|
|
74
|
+
*/
|
|
75
|
+
timestamp: number;
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Extended Error constructor type with V8's captureStackTrace method.
|
|
79
|
+
*
|
|
80
|
+
* This type is used to properly type the Error constructor when calling
|
|
81
|
+
* the V8-specific `captureStackTrace` method, which is not part of the
|
|
82
|
+
* standard ECMAScript Error type but is available in V8-based environments
|
|
83
|
+
* (Node.js, Chrome, Edge, etc.).
|
|
84
|
+
*
|
|
85
|
+
* @remarks
|
|
86
|
+
* This is an internal type used for better stack trace handling in MutationError.
|
|
87
|
+
* The type assertion is necessary because TypeScript's built-in Error type doesn't
|
|
88
|
+
* include V8-specific extensions.
|
|
89
|
+
*/
|
|
90
|
+
export type ErrorConstructorWithStackTrace = typeof Error & {
|
|
91
|
+
/**
|
|
92
|
+
* V8-specific method to capture a stack trace.
|
|
93
|
+
*
|
|
94
|
+
* @param targetObject - The object to attach the stack trace to
|
|
95
|
+
* @param constructorOpt - Optional constructor to exclude from the stack trace
|
|
96
|
+
*/
|
|
97
|
+
captureStackTrace(targetObject: object, constructorOpt?: unknown): void;
|
|
98
|
+
};
|