use-abcd 0.2.0 → 1.0.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 +459 -85
- package/dist/App.d.ts +2 -0
- package/dist/cache.d.ts +12 -0
- package/dist/cache.test.d.ts +1 -0
- package/dist/chunks/client-VrsFvEIA.js +144 -0
- package/dist/chunks/client-VrsFvEIA.js.map +1 -0
- package/dist/chunks/types-Dy4rYb2N.js +19 -0
- package/dist/chunks/types-Dy4rYb2N.js.map +1 -0
- package/dist/collection.d.ts +53 -0
- package/dist/collection.e2e.test.d.ts +1 -0
- package/dist/examples/OptimisticComments.d.ts +2 -0
- package/dist/examples/PaginatedUsers.d.ts +2 -0
- package/dist/examples/Products.d.ts +2 -0
- package/dist/fetch-handler.d.ts +34 -0
- package/dist/fetch-handler.test.d.ts +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +2263 -0
- package/dist/index.js.map +1 -0
- package/dist/item.d.ts +17 -0
- package/dist/main.d.ts +1 -0
- package/dist/mocks/browser.d.ts +1 -0
- package/dist/mocks/handlers.d.ts +2 -0
- package/dist/runtime/client.d.ts +51 -0
- package/dist/runtime/client.js +16 -0
- package/dist/runtime/client.js.map +1 -0
- package/dist/runtime/client.test.d.ts +1 -0
- package/dist/runtime/index.d.ts +5 -0
- package/dist/runtime/server.d.ts +33 -0
- package/dist/runtime/server.js +121 -0
- package/dist/runtime/server.js.map +1 -0
- package/dist/runtime/server.test.d.ts +1 -0
- package/dist/runtime/types.d.ts +70 -0
- package/dist/sync-queue.d.ts +29 -0
- package/dist/sync-queue.test.d.ts +1 -0
- package/dist/types.d.ts +61 -0
- package/dist/useCrud.d.ts +26 -169
- package/dist/useItem.d.ts +11 -0
- package/dist/utils.d.ts +4 -0
- package/package.json +33 -15
- package/dist/useCrud.js +0 -2135
package/README.md
CHANGED
|
@@ -8,13 +8,13 @@ A powerful React hook for managing ABCD (or CRUD) operations with optimistic upd
|
|
|
8
8
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
11
|
-
- 🔄 Automatic state management
|
|
12
|
-
- ⚡ Optimistic updates
|
|
13
|
-
- 🗄️ Built-in caching
|
|
14
|
-
- 🎯 Type-safe
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
11
|
+
- 🔄 Automatic state management with React 19 compatible hooks
|
|
12
|
+
- ⚡ Optimistic updates for instant UI feedback
|
|
13
|
+
- 🗄️ Built-in caching with configurable TTL and capacity
|
|
14
|
+
- 🎯 Type-safe with full TypeScript support
|
|
15
|
+
- ⏳ Debounced sync with configurable delays
|
|
16
|
+
- 🔄 Sync queue management with pause/resume/retry
|
|
17
|
+
- 🎨 Context-based filtering and pagination
|
|
18
18
|
|
|
19
19
|
## Installation
|
|
20
20
|
|
|
@@ -26,103 +26,477 @@ yarn add use-abcd
|
|
|
26
26
|
bun add use-abcd
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
##
|
|
29
|
+
## Package Exports
|
|
30
|
+
|
|
31
|
+
The package provides multiple entry points:
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
// Main package - React hooks and client-side sync utilities
|
|
35
|
+
import { useCrud, Collection, createSyncClient } from "use-abcd";
|
|
36
|
+
|
|
37
|
+
// Runtime client - Client & server sync utilities (for isomorphic code)
|
|
38
|
+
import { createSyncClient, createSyncServer } from "use-abcd/runtime/client";
|
|
39
|
+
|
|
40
|
+
// Runtime server - Server-side sync utilities only
|
|
41
|
+
import { createSyncServer, serverSyncSuccess } from "use-abcd/runtime/server";
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Quick Start
|
|
30
45
|
|
|
31
46
|
```typescript
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
import { map } from "lodash-es";
|
|
36
|
-
import { wait } from "../utils";
|
|
37
|
-
|
|
38
|
-
type Todo = {
|
|
39
|
-
userId: string;
|
|
47
|
+
import { useCrud, type Config } from "use-abcd";
|
|
48
|
+
|
|
49
|
+
interface Todo {
|
|
40
50
|
id: string;
|
|
41
51
|
title: string;
|
|
42
52
|
completed: boolean;
|
|
43
|
-
}
|
|
53
|
+
}
|
|
44
54
|
|
|
45
|
-
const
|
|
46
|
-
id: "
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return
|
|
54
|
-
items: await fetch("https://jsonplaceholder.typicode.com/todos")
|
|
55
|
-
.then((r) => r.json())
|
|
56
|
-
.then((items) => items.slice(0, 10)),
|
|
57
|
-
metadata: {},
|
|
58
|
-
};
|
|
55
|
+
const TodoConfig: Config<Todo, {}> = {
|
|
56
|
+
id: "todos",
|
|
57
|
+
initialContext: {},
|
|
58
|
+
getId: (item) => item.id,
|
|
59
|
+
|
|
60
|
+
onFetch: async (context, signal) => {
|
|
61
|
+
const response = await fetch("/api/todos", { signal });
|
|
62
|
+
const data = await response.json();
|
|
63
|
+
return data.items;
|
|
59
64
|
},
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
65
|
+
|
|
66
|
+
onSync: async (changes, signal) => {
|
|
67
|
+
// Handle create, update, delete operations
|
|
68
|
+
const results = [];
|
|
69
|
+
for (const change of changes) {
|
|
70
|
+
// Process each change and return results
|
|
71
|
+
results.push({ id: change.id, status: "success" });
|
|
72
|
+
}
|
|
73
|
+
return results;
|
|
63
74
|
},
|
|
64
75
|
};
|
|
65
76
|
|
|
66
|
-
|
|
67
|
-
const {
|
|
68
|
-
|
|
77
|
+
function TodoList() {
|
|
78
|
+
const { items, loading, create, update, remove } = useCrud(TodoConfig);
|
|
79
|
+
|
|
80
|
+
// Use items, create, update, remove in your UI
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Core Concepts
|
|
85
|
+
|
|
86
|
+
### Config
|
|
87
|
+
|
|
88
|
+
The `Config` object defines how your data is fetched, synced, and managed:
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
type Config<T, C> = {
|
|
92
|
+
id: string; // Unique identifier for this collection
|
|
93
|
+
initialContext: C; // Initial context (filters, pagination, etc.)
|
|
94
|
+
getId: (item: T) => string; // Extract ID from item
|
|
95
|
+
|
|
96
|
+
// Optional sync configuration
|
|
97
|
+
syncDebounce?: number; // Debounce delay for sync (default: 300ms)
|
|
98
|
+
syncRetries?: number; // Max retry attempts (default: 3)
|
|
99
|
+
|
|
100
|
+
// Optional cache configuration
|
|
101
|
+
cacheCapacity?: number; // Max cache entries (default: 10)
|
|
102
|
+
cacheTtl?: number; // Cache TTL in ms (default: 60000)
|
|
103
|
+
|
|
104
|
+
// Required handlers
|
|
105
|
+
onFetch: (context: C, signal: AbortSignal) => Promise<T[]>;
|
|
106
|
+
onSync: (changes: Change<T>[], signal: AbortSignal) => Promise<SyncResult[]>;
|
|
107
|
+
};
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Hook API
|
|
111
|
+
|
|
112
|
+
The `useCrud` hook returns:
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
{
|
|
116
|
+
// State
|
|
117
|
+
items: Map<string, T>; // Current items
|
|
118
|
+
context: C; // Current context
|
|
119
|
+
loading: boolean; // Fetch loading state
|
|
120
|
+
syncing: boolean; // Sync in progress
|
|
121
|
+
syncQueue: SyncQueueState<T>; // Sync queue state
|
|
122
|
+
syncState: SyncState; // Overall sync state
|
|
123
|
+
|
|
124
|
+
// Item operations (optimistic)
|
|
125
|
+
create: (item: T) => void;
|
|
126
|
+
update: (id: string, mutate: (draft: T) => void) => void;
|
|
127
|
+
remove: (id: string) => void;
|
|
128
|
+
getItem: (id: string) => Item<T, C>;
|
|
129
|
+
getItemStatus: (id: string) => ItemStatus | null;
|
|
130
|
+
|
|
131
|
+
// Context & refresh
|
|
132
|
+
setContext: (mutate: (draft: C) => void) => void;
|
|
133
|
+
refresh: () => Promise<void>;
|
|
134
|
+
|
|
135
|
+
// Sync controls
|
|
136
|
+
pauseSync: () => void;
|
|
137
|
+
resumeSync: () => void;
|
|
138
|
+
retrySync: (id?: string) => void;
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Examples
|
|
143
|
+
|
|
144
|
+
The repository includes comprehensive examples demonstrating various use cases:
|
|
145
|
+
|
|
146
|
+
### 1. Full CRUD Operations (Products Example)
|
|
147
|
+
|
|
148
|
+
Demonstrates complete CRUD functionality with:
|
|
149
|
+
- Create, read, update, delete operations
|
|
150
|
+
- Category filtering and search
|
|
151
|
+
- Sync queue management
|
|
152
|
+
- Error handling with retries
|
|
153
|
+
- Per-item status indicators
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
const ProductsConfig: Config<Product, ProductContext> = {
|
|
157
|
+
id: "products",
|
|
158
|
+
initialContext: { page: 1, limit: 10 },
|
|
159
|
+
getId: (item) => item.id,
|
|
69
160
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
161
|
+
onFetch: async (context, signal) => {
|
|
162
|
+
const params = new URLSearchParams({
|
|
163
|
+
page: String(context.page),
|
|
164
|
+
limit: String(context.limit),
|
|
73
165
|
});
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
166
|
+
if (context.category) params.append("category", context.category);
|
|
167
|
+
if (context.search) params.append("search", context.search);
|
|
168
|
+
|
|
169
|
+
const response = await fetch(`/api/products?${params}`, { signal });
|
|
170
|
+
return (await response.json()).items;
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
onSync: async (changes, signal) => {
|
|
174
|
+
// Handle batch sync operations
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Key features:**
|
|
180
|
+
- Filtering by category
|
|
181
|
+
- Text search
|
|
182
|
+
- Pause/resume sync
|
|
183
|
+
- Retry failed operations
|
|
184
|
+
- Visual status indicators
|
|
185
|
+
|
|
186
|
+
### 2. Pagination (Users Example)
|
|
187
|
+
|
|
188
|
+
Shows context-based pagination with:
|
|
189
|
+
- Dynamic page size selection
|
|
190
|
+
- Next/previous navigation
|
|
191
|
+
- Context updates trigger re-fetch
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
interface UserContext {
|
|
195
|
+
page: number;
|
|
196
|
+
limit: number;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const { items, context, setContext } = useCrud<User, UserContext>(UsersConfig);
|
|
200
|
+
|
|
201
|
+
// Change page
|
|
202
|
+
setContext((draft) => {
|
|
203
|
+
draft.page += 1;
|
|
99
204
|
});
|
|
100
205
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
206
|
+
// Change items per page
|
|
207
|
+
setContext((draft) => {
|
|
208
|
+
draft.limit = 20;
|
|
209
|
+
draft.page = 1; // Reset to first page
|
|
210
|
+
});
|
|
211
|
+
```
|
|
106
212
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
213
|
+
**Key features:**
|
|
214
|
+
- Configurable page size
|
|
215
|
+
- Context-based pagination
|
|
216
|
+
- Automatic re-fetch on context change
|
|
217
|
+
|
|
218
|
+
### 3. Optimistic Updates (Comments Example)
|
|
219
|
+
|
|
220
|
+
Demonstrates the power of optimistic updates:
|
|
221
|
+
- Instant UI feedback
|
|
222
|
+
- Background synchronization
|
|
223
|
+
- Sync queue visualization
|
|
224
|
+
- Manual retry controls
|
|
225
|
+
- Error state handling
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
const CommentsConfig: Config<Comment, CommentContext> = {
|
|
229
|
+
id: "comments-optimistic",
|
|
230
|
+
initialContext: { postId: "1" },
|
|
231
|
+
getId: (item) => item.id,
|
|
232
|
+
syncDebounce: 100, // Very short debounce for demo
|
|
233
|
+
|
|
234
|
+
// ... handlers
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// Create appears instantly in UI
|
|
238
|
+
create({
|
|
239
|
+
id: `temp-${Date.now()}`,
|
|
240
|
+
text: "New comment",
|
|
241
|
+
author: "You",
|
|
242
|
+
createdAt: new Date().toISOString(),
|
|
243
|
+
});
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**Key features:**
|
|
247
|
+
- Immediate UI updates
|
|
248
|
+
- Sync queue status display
|
|
249
|
+
- Pause/resume synchronization
|
|
250
|
+
- Per-item sync status
|
|
251
|
+
- Manual retry for errors
|
|
252
|
+
|
|
253
|
+
### 4. Original Examples
|
|
254
|
+
|
|
255
|
+
The repository also includes the original simpler examples:
|
|
256
|
+
- **Blog Post**: Single item editing with optimistic updates
|
|
257
|
+
- **Todo List**: Simple list with toggle completion
|
|
258
|
+
|
|
259
|
+
## Advanced Usage
|
|
260
|
+
|
|
261
|
+
### Custom Context for Filtering
|
|
262
|
+
|
|
263
|
+
Use context to manage filters, pagination, sorting, etc.:
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
interface ProductContext {
|
|
267
|
+
page: number;
|
|
268
|
+
limit: number;
|
|
269
|
+
category?: string;
|
|
270
|
+
search?: string;
|
|
271
|
+
sortBy?: "name" | "price";
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const { context, setContext } = useCrud<Product, ProductContext>(config);
|
|
275
|
+
|
|
276
|
+
// Update multiple context fields
|
|
277
|
+
setContext((draft) => {
|
|
278
|
+
draft.category = "electronics";
|
|
279
|
+
draft.page = 1;
|
|
280
|
+
});
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Monitoring Sync Queue
|
|
284
|
+
|
|
285
|
+
Track pending changes and errors:
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
const { syncQueue, pauseSync, resumeSync, retrySync } = useCrud(config);
|
|
110
289
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
))}
|
|
118
|
-
</div>
|
|
119
|
-
</div>
|
|
120
|
-
);
|
|
290
|
+
console.log({
|
|
291
|
+
pending: syncQueue.queue.size,
|
|
292
|
+
inFlight: syncQueue.inFlight.size,
|
|
293
|
+
errors: syncQueue.errors.size,
|
|
294
|
+
isPaused: syncQueue.isPaused,
|
|
295
|
+
isSyncing: syncQueue.isSyncing,
|
|
121
296
|
});
|
|
297
|
+
|
|
298
|
+
// Pause sync temporarily
|
|
299
|
+
pauseSync();
|
|
300
|
+
|
|
301
|
+
// Resume sync
|
|
302
|
+
resumeSync();
|
|
303
|
+
|
|
304
|
+
// Retry specific item
|
|
305
|
+
retrySync(itemId);
|
|
306
|
+
|
|
307
|
+
// Retry all failed items
|
|
308
|
+
retrySync();
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Per-Item Status
|
|
312
|
+
|
|
313
|
+
Track the sync status of individual items:
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
const { getItemStatus } = useCrud(config);
|
|
317
|
+
|
|
318
|
+
const status = getItemStatus(itemId);
|
|
319
|
+
if (status) {
|
|
320
|
+
console.log({
|
|
321
|
+
type: status.type, // "create" | "update" | "delete"
|
|
322
|
+
status: status.status, // "pending" | "syncing" | "success" | "error"
|
|
323
|
+
retries: status.retries, // Number of retry attempts
|
|
324
|
+
error: status.error, // Error message if failed
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### ID Remapping for Optimistic Creates
|
|
330
|
+
|
|
331
|
+
When creating items optimistically, you typically use a temporary ID (e.g., `temp-${Date.now()}`). After the server confirms the creation, it may assign a different permanent ID. The library automatically handles this ID remapping.
|
|
332
|
+
|
|
333
|
+
**In your `onSync` handler**, return the server-assigned `newId` for create operations:
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
onSync: async (changes, signal) => {
|
|
337
|
+
const results: SyncResult[] = [];
|
|
338
|
+
|
|
339
|
+
for (const change of changes) {
|
|
340
|
+
if (change.type === "create") {
|
|
341
|
+
const response = await fetch("/api/items", {
|
|
342
|
+
method: "POST",
|
|
343
|
+
headers: { "Content-Type": "application/json" },
|
|
344
|
+
body: JSON.stringify(change.data),
|
|
345
|
+
signal,
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
if (!response.ok) throw new Error("Failed to create");
|
|
349
|
+
|
|
350
|
+
const data = await response.json();
|
|
351
|
+
// Return newId to remap the temporary ID to the server-assigned ID
|
|
352
|
+
results.push({
|
|
353
|
+
id: change.id, // The temporary ID
|
|
354
|
+
status: "success",
|
|
355
|
+
newId: data.id, // The server-assigned permanent ID
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
// ... handle update and delete
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return results;
|
|
362
|
+
};
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
**What happens automatically:**
|
|
366
|
+
1. The item's key in the `items` Map is updated from `temp-123` to `server-456`
|
|
367
|
+
2. The item's `id` property is updated (assumes item has an `id` field)
|
|
368
|
+
3. Any `Item` references are updated to use the new ID
|
|
369
|
+
4. The UI re-renders with the correct permanent ID
|
|
370
|
+
|
|
371
|
+
**Custom ID field**: If your item uses a different property for the ID (not `id`), provide a `setId` function in your config:
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
const config: Config<MyItem, Context> = {
|
|
375
|
+
getId: (item) => item.itemId,
|
|
376
|
+
setId: (item, newId) => ({ ...item, itemId: newId }),
|
|
377
|
+
// ...
|
|
378
|
+
};
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### Cache Control
|
|
382
|
+
|
|
383
|
+
Control caching behavior:
|
|
384
|
+
|
|
385
|
+
```typescript
|
|
386
|
+
const config: Config<T, C> = {
|
|
387
|
+
// ...
|
|
388
|
+
cacheCapacity: 20, // Store up to 20 cache entries
|
|
389
|
+
cacheTtl: 30000, // Cache expires after 30 seconds
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
const { refresh } = useCrud(config);
|
|
393
|
+
|
|
394
|
+
// Force refresh (bypass cache)
|
|
395
|
+
await refresh();
|
|
122
396
|
```
|
|
123
397
|
|
|
124
|
-
|
|
398
|
+
## Running Examples Locally
|
|
399
|
+
|
|
400
|
+
The repository includes a development environment with MSW (Mock Service Worker) for testing:
|
|
401
|
+
|
|
402
|
+
```bash
|
|
403
|
+
# Clone the repository
|
|
404
|
+
git clone https://github.com/smtrd3/use-abcd
|
|
405
|
+
cd use-abcd
|
|
406
|
+
|
|
407
|
+
# Install dependencies
|
|
408
|
+
bun install # or npm install
|
|
409
|
+
|
|
410
|
+
# Start development server
|
|
411
|
+
bun run dev # or npm run dev
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
Visit `http://localhost:5173` to see the examples in action.
|
|
415
|
+
|
|
416
|
+
### Available Examples:
|
|
417
|
+
|
|
418
|
+
1. **Products (Full CRUD)** - Complete CRUD operations with filtering
|
|
419
|
+
2. **Pagination** - Context-based pagination with users
|
|
420
|
+
3. **Optimistic Updates** - Comments with sync queue visualization
|
|
421
|
+
4. **Blog Post (Original)** - Simple single-item editing
|
|
422
|
+
5. **Todo (Original)** - Basic list operations
|
|
423
|
+
|
|
424
|
+
## API Reference
|
|
425
|
+
|
|
426
|
+
### Types
|
|
427
|
+
|
|
428
|
+
```typescript
|
|
429
|
+
// Main configuration
|
|
430
|
+
type Config<T, C> = {
|
|
431
|
+
id: string;
|
|
432
|
+
initialContext: C;
|
|
433
|
+
getId: (item: T) => string;
|
|
434
|
+
setId?: (item: T, newId: string) => T; // Optional: for ID remapping on create
|
|
435
|
+
syncDebounce?: number;
|
|
436
|
+
syncRetries?: number;
|
|
437
|
+
cacheCapacity?: number;
|
|
438
|
+
cacheTtl?: number;
|
|
439
|
+
onFetch: (context: C, signal: AbortSignal) => Promise<T[]>;
|
|
440
|
+
onSync: (changes: Change<T>[], signal: AbortSignal) => Promise<SyncResult[]>;
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
// Change type for sync operations
|
|
444
|
+
type Change<T> = {
|
|
445
|
+
id: string;
|
|
446
|
+
type: "create" | "update" | "delete";
|
|
447
|
+
data: T;
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
// Sync result
|
|
451
|
+
type SyncResult = {
|
|
452
|
+
id: string;
|
|
453
|
+
status: "success" | "error";
|
|
454
|
+
error?: string;
|
|
455
|
+
newId?: string; // For create operations: server-assigned ID to replace temp ID
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
// Item status
|
|
459
|
+
type ItemStatus = {
|
|
460
|
+
type: "create" | "update" | "delete";
|
|
461
|
+
status: "pending" | "syncing" | "success" | "error";
|
|
462
|
+
retries: number;
|
|
463
|
+
error?: string;
|
|
464
|
+
} | null;
|
|
465
|
+
|
|
466
|
+
// Sync queue state
|
|
467
|
+
type SyncQueueState<T> = {
|
|
468
|
+
queue: Map<string, Change<T>>; // Pending changes
|
|
469
|
+
inFlight: Map<string, Change<T>>; // Currently syncing
|
|
470
|
+
errors: Map<string, { error: string; retries: number }>;
|
|
471
|
+
isPaused: boolean;
|
|
472
|
+
isSyncing: boolean;
|
|
473
|
+
};
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
## Best Practices
|
|
477
|
+
|
|
478
|
+
1. **Use Optimistic Updates**: Let users see changes immediately while syncing in the background
|
|
479
|
+
2. **Handle Errors Gracefully**: Show error states and provide retry mechanisms
|
|
480
|
+
3. **Configure Debouncing**: Adjust `syncDebounce` based on your use case
|
|
481
|
+
4. **Leverage Context**: Use context for filters, pagination, and search
|
|
482
|
+
5. **Monitor Sync Queue**: Display pending changes and errors to users
|
|
483
|
+
6. **Cache Wisely**: Configure `cacheTtl` and `cacheCapacity` based on your data freshness requirements
|
|
484
|
+
|
|
485
|
+
## Architecture
|
|
486
|
+
|
|
487
|
+
The library is built on several core concepts:
|
|
488
|
+
|
|
489
|
+
- **Collection**: Manages the item collection, sync queue, and fetch handler
|
|
490
|
+
- **SyncQueue**: Handles debounced synchronization with retry logic
|
|
491
|
+
- **FetchHandler**: Manages data fetching with caching
|
|
492
|
+
- **Item**: Represents individual items with their sync state
|
|
493
|
+
|
|
494
|
+
All state updates use [Mutative](https://github.com/unadlib/mutative) for immutable updates, ensuring React can efficiently detect changes.
|
|
495
|
+
|
|
496
|
+
## Contributing
|
|
497
|
+
|
|
498
|
+
This is an alpha release. Please read the source code for a deeper understanding of its implementation and capabilities. Contributions and feedback are welcome!
|
|
125
499
|
|
|
126
500
|
## License
|
|
127
501
|
|
|
128
|
-
MIT
|
|
502
|
+
MIT
|
package/dist/App.d.ts
ADDED
package/dist/cache.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare class Cache<T> {
|
|
2
|
+
private _cache;
|
|
3
|
+
private _capacity;
|
|
4
|
+
private _ttl;
|
|
5
|
+
constructor(capacity: number, ttl: number);
|
|
6
|
+
get(key: string): T | null;
|
|
7
|
+
set(key: string, value: T): void;
|
|
8
|
+
invalidate(key: string): void;
|
|
9
|
+
clear(): void;
|
|
10
|
+
has(key: string): boolean;
|
|
11
|
+
get size(): number;
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|