use-abcd 1.4.3 → 1.6.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.
Files changed (48) hide show
  1. package/README.md +225 -406
  2. package/dist/chunks/client-lHRDCo64.js +30 -0
  3. package/dist/chunks/client-lHRDCo64.js.map +1 -0
  4. package/dist/chunks/utils-C2wXstxP.js +828 -0
  5. package/dist/chunks/utils-C2wXstxP.js.map +1 -0
  6. package/dist/collection.d.ts +13 -5
  7. package/dist/examples/Benchmark.d.ts +2 -0
  8. package/dist/examples/LocalNotes.d.ts +2 -0
  9. package/dist/index.d.ts +5 -4
  10. package/dist/index.js +1071 -1789
  11. package/dist/index.js.map +1 -1
  12. package/dist/item.d.ts +3 -2
  13. package/dist/mocks/handlers.d.ts +0 -1
  14. package/dist/node.d.ts +5 -1
  15. package/dist/runtime/client.d.ts +10 -48
  16. package/dist/runtime/client.js +5 -13
  17. package/dist/runtime/client.js.map +1 -1
  18. package/dist/runtime/index.d.ts +5 -5
  19. package/dist/runtime/local.d.ts +32 -0
  20. package/dist/runtime/server.d.ts +25 -35
  21. package/dist/runtime/server.js +58 -107
  22. package/dist/runtime/server.js.map +1 -1
  23. package/dist/runtime/types.d.ts +28 -63
  24. package/dist/sync-queue.d.ts +7 -44
  25. package/dist/types.d.ts +22 -18
  26. package/dist/useCrud.d.ts +5 -3
  27. package/dist/useCrudTree.d.ts +3 -3
  28. package/dist/useItem.d.ts +3 -1
  29. package/dist/useNode.d.ts +3 -0
  30. package/dist/useSyncState.d.ts +2 -2
  31. package/dist/utils.d.ts +3 -4
  32. package/package.json +4 -2
  33. package/dist/cache.test.d.ts +0 -1
  34. package/dist/chunks/client-BfugfaiH.js +0 -144
  35. package/dist/chunks/client-BfugfaiH.js.map +0 -1
  36. package/dist/chunks/types-Dy4rYb2N.js +0 -19
  37. package/dist/chunks/types-Dy4rYb2N.js.map +0 -1
  38. package/dist/collection.e2e.test.d.ts +0 -1
  39. package/dist/fetch-handler.test.d.ts +0 -1
  40. package/dist/item-cache.test.d.ts +0 -1
  41. package/dist/node.test.d.ts +0 -1
  42. package/dist/runtime/client.test.d.ts +0 -1
  43. package/dist/runtime/server.test.d.ts +0 -1
  44. package/dist/sync-queue.test.d.ts +0 -1
  45. package/dist/useCrud.test.d.ts +0 -1
  46. package/dist/useCrudTree.test.d.ts +0 -1
  47. /package/dist/{App.d.ts → examples/App.d.ts} +0 -0
  48. /package/dist/{main.d.ts → examples/main.d.ts} +0 -0
package/README.md CHANGED
@@ -1,487 +1,306 @@
1
- # use-abcd (alpha)
1
+ # use-abcd
2
2
 
3
- [![Build Status](https://github.com/smtrd3/common-state/workflows/CI/badge.svg)](https://github.com/smtrd3/common-state/actions)
3
+ State management library purpose-built for CRUD applications. Manages collections of records with optimistic mutations, automatic syncing, and built-in retry logic. Includes a fullstack component for implementing state synchronization between client and server.
4
4
 
5
- Most apps are just CRUD operations in disguise. Stop fighting complex state management frameworks and reinventing patterns. This library gives you optimized CRUD state management with built-in sync and offline-first support. Get zero-latency updates, no loading screens, and effortless API integration. Your code stays simple, your users get instant responses.
6
-
7
- A powerful React hook for managing ABCD (or CRUD) operations with optimistic updates, caching, and automatic state management.
8
-
9
- > **Note on Package Name**: The package is published as `use-abcd` on npm due to naming availability, where ABCD stands for Add, Browse, Change, and Delete - which maps directly to the traditional CRUD (Create, Read, Update, Delete) operations. While the package name uses ABCD, all internal APIs and documentation use CRUD terminology for familiarity and consistency with common programming patterns.
10
-
11
- ## Features
12
-
13
- - 🔄 Automatic state management with React 19 compatible hooks
14
- - ⚡ Optimistic updates for instant UI feedback
15
- - 🗄️ Built-in caching with configurable TTL and capacity
16
- - 🎯 Type-safe with full TypeScript support
17
- - ⏳ Debounced sync with configurable delays
18
- - 🔄 Sync queue management with pause/resume/retry
19
- - 🎨 Context-based filtering and pagination
20
- - 🔌 End-to-end type-safe client-server sync utilities
21
-
22
- ## Installation
23
-
24
- ```bash
5
+ ```
25
6
  npm install use-abcd
26
- # or
27
- yarn add use-abcd
28
- # or
29
- bun add use-abcd
30
7
  ```
31
8
 
32
- ## Package Exports
33
-
34
- The package provides multiple entry points:
35
-
36
- ```typescript
37
- // Main package - React hooks and client-side sync utilities
38
- import { useCrud, Collection, createSyncClient } from "use-abcd";
39
-
40
- // Runtime client - Client & server sync utilities (for isomorphic code)
41
- import { createSyncClient, createSyncServer } from "use-abcd/runtime/client";
9
+ ## Basic Usage
42
10
 
43
- // Runtime server - Server-side sync utilities only
44
- import { createSyncServer, serverSyncSuccess } from "use-abcd/runtime/server";
45
- ```
46
-
47
- ## Quick Start
11
+ A collection is defined by a `Config` object with an `id`, an `initialContext` (the query parameters), and a `handler` that fetches and syncs data.
48
12
 
49
- ```typescript
50
- import { useCrud, type Config } from "use-abcd";
13
+ ```ts
14
+ import { useCrud, createSyncClient } from "use-abcd";
51
15
 
52
16
  interface Todo {
53
17
  id: string;
54
18
  title: string;
55
- completed: boolean;
19
+ done: boolean;
56
20
  }
57
21
 
58
- const TodoConfig: Config<Todo, {}> = {
59
- id: "todos",
60
- initialContext: {},
61
- getId: (item) => item.id,
62
-
63
- onFetch: async (context, signal) => {
64
- const response = await fetch("/api/todos", { signal });
65
- const data = await response.json();
66
- return data.items;
67
- },
68
-
69
- onSync: async (changes, signal) => {
70
- // Handle create, update, delete operations
71
- const results = [];
72
- for (const change of changes) {
73
- // Process each change and return results
74
- results.push({ id: change.id, status: "success" });
75
- }
76
- return results;
77
- },
78
- };
79
-
80
- function TodoList() {
81
- const { items, loading, create, update, remove } = useCrud(TodoConfig);
82
-
83
- // Use items, create, update, remove in your UI
22
+ interface Query {
23
+ status: "all" | "active" | "done";
84
24
  }
85
- ```
86
-
87
- ## Core Concepts
88
-
89
- ### Config
90
25
 
91
- The `Config` object defines how your data is fetched, synced, and managed:
92
-
93
- ```typescript
94
- type Config<T extends object, C> = {
95
- id: string; // Unique identifier for this collection
96
- initialContext: C; // Initial context (filters, pagination, etc.)
97
- getId: (item: T) => string; // Extract ID from item
98
- setId?: (item: T, newId: string) => T; // Optional: for ID remapping on create
99
-
100
- // Optional sync configuration
101
- syncDebounce?: number; // Debounce delay for sync (default: 300ms)
102
- syncRetries?: number; // Max retry attempts (default: 3)
103
-
104
- // Optional cache configuration
105
- cacheCapacity?: number; // Max cache entries (default: 10)
106
- cacheTtl?: number; // Cache TTL in ms (default: 60000)
107
-
108
- // Required handlers
109
- onFetch: (context: C, signal: AbortSignal) => Promise<T[]>;
110
- onSync?: (changes: Change<T>[], signal: AbortSignal) => Promise<SyncResult[]>;
26
+ const config = {
27
+ id: "todos",
28
+ initialContext: { status: "all" } as Query,
29
+ handler: createSyncClient<Todo, Query>("/api/todos"),
111
30
  };
112
31
  ```
113
32
 
114
- ### Hook API
115
-
116
- The `useCrud` hook returns:
117
-
118
- ```typescript
119
- {
120
- // State
121
- items: Map<string, T>; // Current items
122
- context: C; // Current context
123
- loading: boolean; // Fetch loading state
124
- syncing: boolean; // Sync in progress
125
- syncQueue: SyncQueueState<T>; // Sync queue state
126
-
127
- // Item operations (optimistic)
128
- create: (item: T) => void;
129
- update: (id: string, mutate: (draft: T) => void) => void;
130
- remove: (id: string) => void;
131
- getItem: (id: string) => Item<T, C>;
132
- getItemStatus: (id: string) => ItemStatus | null;
133
-
134
- // Context & refresh
135
- setContext: (mutate: (draft: C) => void) => void;
136
- refresh: () => Promise<void>;
137
-
138
- // Sync controls
139
- pauseSync: () => void;
140
- resumeSync: () => void;
141
- retrySync: (id?: string) => void;
33
+ The `handler` is a single function that serves both fetching and syncing. When the collection needs data it calls the handler with `{ query }`. When local mutations need to be pushed it calls with `{ changes }`. `createSyncClient` creates a handler that talks to a remote endpoint over HTTP.
34
+
35
+ ### `useCrud`
36
+
37
+ The main hook. Returns the collection's items, state flags, and mutation functions.
38
+
39
+ ```tsx
40
+ function TodoApp() {
41
+ const {
42
+ items, // Map<string, Todo>
43
+ loading, // true during initial fetch
44
+ syncing, // true while pushing changes
45
+ context, // current query context
46
+ create, // (item: Omit<Todo, "id">) => string
47
+ update, // (id, (draft) => void) => void
48
+ remove, // (id) => void
49
+ setContext, // (mutator) => void — triggers refetch
50
+ getItem, // (id) => Item<Todo>
51
+ getItemStatus,// (id) => ItemStatus | null
52
+ refresh, // () => Promise<void>
53
+ pauseSync, // () => void
54
+ resumeSync, // () => void
55
+ retrySync, // (id?) => void
56
+ } = useCrud(config);
57
+
58
+ // ...render
142
59
  }
143
60
  ```
144
61
 
145
- ## Examples
146
-
147
- The repository includes examples demonstrating various use cases:
148
-
149
- - **Products** - Full CRUD with filtering, search, and error handling
150
- - **Pagination** - Context-based pagination with dynamic page size
151
- - **Optimistic Updates** - Comments with instant UI feedback and sync queue visualization
152
- - **Blog Post** - Simple single-item editing
153
- - **Todo List** - Basic list operations
62
+ Mutations are optimistic — `create`, `update`, and `remove` update local state immediately and queue changes for sync in the background. The sync queue batches changes, debounces flushes, and retries on failure.
154
63
 
155
- Run locally: `bun run dev` or `npm run dev` and visit `http://localhost:5173`
64
+ ### `useItem`
156
65
 
157
- ## Advanced Usage
66
+ Subscribe to a single item without re-rendering on unrelated changes. Takes an `Item` reference from `getItem`.
158
67
 
159
- ### Individual Item Management
68
+ ```tsx
69
+ function TodoRow({ item }: { item: Item<Todo> }) {
70
+ const { data, status, update, remove, exists } = useItem(item);
160
71
 
161
- Use `getItem()` with `useItem()` for managing individual items:
72
+ // ...render
73
+ }
74
+ ```
162
75
 
163
- ```typescript
164
- import { useCrud, useItem } from "use-abcd";
76
+ ### Context
165
77
 
166
- function ProductList() {
167
- const { items, getItem } = useCrud(ProductsConfig);
78
+ Context drives the query sent to the handler on fetch. Changing it triggers a refetch.
168
79
 
169
- return (
170
- <div>
171
- {Array.from(items.keys()).map((id) => (
172
- <ProductItem key={id} item={getItem(id)} />
173
- ))}
174
- </div>
175
- );
176
- }
80
+ ```tsx
81
+ setContext((draft) => {
82
+ draft.status = "active";
83
+ });
84
+ ```
177
85
 
178
- function ProductItem({ item }: { item: Item<Product, ProductContext> }) {
179
- const { data, status, update, remove, exists } = useItem(item);
86
+ ### Config Options
180
87
 
181
- if (!exists) return null;
182
-
183
- return (
184
- <div>
185
- <h3>{data?.name}</h3>
186
- <p>Status: {status?.status || "synced"}</p>
187
- <button onClick={() => update((draft) => { draft.stock += 1; })}>
188
- Add Stock
189
- </button>
190
- <button onClick={() => remove()}>Delete</button>
191
- </div>
192
- );
88
+ ```ts
89
+ {
90
+ id: string; // unique collection identifier
91
+ initialContext: C; // starting query state
92
+ handler?: CrudHandler; // fetch + sync function
93
+ serverItems?: T[]; // initial items for SSR hydration
94
+
95
+ // Sync
96
+ syncDebounce?: number; // ms, default 300
97
+ syncRetries?: number; // default 3
98
+ refetchOnMutation?: boolean; // refetch after create/delete, default false
99
+
100
+ // Cache
101
+ cacheCapacity?: number; // context cache slots, default 10
102
+ cacheTtl?: number; // ms, default 60000
103
+
104
+ // Fetch
105
+ fetchRetries?: number; // default 0
193
106
  }
194
107
  ```
195
108
 
196
- **Benefits:**
109
+ ## Tree State
197
110
 
198
- - `useItem()` subscribes only to that specific item's changes
199
- - React re-renders only when that item's data changes (automatic optimization via WeakMap cache)
200
- - Clean separation of list and item concerns
111
+ The library supports tree-shaped state using `useCrudTree`. Nodes are stored as a flat key-value map internally, with parent-child relationships encoded in the node IDs using a separator (default `.`). A node with id `root.settings.theme` is a child of `root.settings`.
201
112
 
202
- ### Context-Based Filtering & Pagination
113
+ ```ts
114
+ import { useCrudTree, type TreeConfig } from "use-abcd";
203
115
 
204
- Use context to manage filters, pagination, sorting:
205
-
206
- ```typescript
207
- interface ProductContext {
208
- page: number;
209
- limit: number;
210
- category?: string;
211
- search?: string;
116
+ interface FieldValue {
117
+ label: string;
212
118
  }
213
119
 
214
- const { items, context, setContext } = useCrud<Product, ProductContext>(config);
120
+ type NodeType = "object" | "array" | "primitive";
215
121
 
216
- // Update context to refetch
217
- setContext((draft) => {
218
- draft.category = "electronics";
219
- draft.page = 1;
220
- });
122
+ const config: TreeConfig<FieldValue, {}, NodeType> = {
123
+ id: "tree-editor",
124
+ initialContext: {},
125
+ rootId: "root",
126
+ // nodeSeparator: ".", // default
127
+ handler: createSyncClient("/api/tree"),
128
+ };
221
129
  ```
222
130
 
223
- ### Sync Queue Monitoring
131
+ ### `useCrudTree`
224
132
 
225
- Track pending changes and errors:
133
+ Returns the root node, selection state, serialization, and all the standard sync controls.
226
134
 
227
- ```typescript
228
- const { syncQueue, pauseSync, resumeSync, retrySync } = useCrud(config);
135
+ ```tsx
136
+ function TreeEditor() {
137
+ const {
138
+ rootNode, // Node | null
139
+ selectedNode, // Node | null
140
+ selectedNodeId, // string | null
141
+ selectNode, // (id) => void
142
+ deselectNode, // () => void
143
+ getNode, // (id) => Node
144
+ toJson, // () => object | null
145
+ // ...same sync controls as useCrud
146
+ } = useCrudTree(config);
229
147
 
230
- // Check queue state
231
- console.log({
232
- pending: syncQueue.queue.size,
233
- inFlight: syncQueue.inFlight.size,
234
- errors: syncQueue.errors.size,
235
- });
236
-
237
- // Control sync
238
- pauseSync();
239
- resumeSync();
240
- retrySync(); // Retry all failed items
241
- retrySync(itemId); // Retry specific item
148
+ // ...render
149
+ }
242
150
  ```
243
151
 
244
- ### ID Remapping for Optimistic Creates
245
-
246
- Handle temporary IDs that get replaced by server-assigned IDs:
247
-
248
- ```typescript
249
- onSync: async (changes, signal) => {
250
- for (const change of changes) {
251
- if (change.type === "create") {
252
- const response = await fetch("/api/items", {
253
- method: "POST",
254
- body: JSON.stringify(change.data),
255
- signal,
256
- });
257
- const data = await response.json();
258
-
259
- // Return newId to remap temp ID to server ID
260
- return {
261
- id: change.id, // Temporary ID (e.g., "temp-123")
262
- status: "success",
263
- newId: data.id, // Server-assigned ID (e.g., "456")
264
- };
265
- }
266
- // ... handle update and delete
267
- }
268
- };
152
+ ### `useNode`
153
+
154
+ Subscribe to a single tree node. Provides navigation, mutation, and reordering operations.
155
+
156
+ ```tsx
157
+ function TreeNodeRow({ node }: { node: Node<FieldValue, {}, NodeType> }) {
158
+ const {
159
+ data, // TreeNode<FieldValue, NodeType> | undefined
160
+ children, // Node[]
161
+ depth, // nesting level
162
+ exists, // boolean
163
+ isSelected, // boolean
164
+ status, // ItemStatus
165
+
166
+ // Tree mutations
167
+ append, // (value, type?) => string add child at end
168
+ prepend, // (value, type?) => string — add child at start
169
+ moveUp, // () => void
170
+ moveDown, // () => void
171
+ move, // (position, targetParent?) => void reparent
172
+ clone, // () => Map — deep clone subtree
173
+
174
+ // Node mutations
175
+ updateProp, // (draft => void) => void — update value
176
+ remove, // () => void — remove node and descendants
177
+
178
+ // Selection
179
+ select, // () => void
180
+ deselect, // () => void
181
+
182
+ // Navigation
183
+ getParent, // () => Node | null
184
+ } = useNode(node);
185
+
186
+ // ...render
187
+ }
269
188
  ```
270
189
 
271
- The library automatically:
190
+ Each `TreeNode` stored in the collection has this shape:
272
191
 
273
- 1. Updates the item's key in the `items` Map
274
- 2. Updates the item's `id` property
275
- 3. Updates any `Item` references
276
- 4. Triggers UI re-render
277
-
278
- ## End-to-End Type-Safe CRUD with createSyncClient & createSyncServer
192
+ ```ts
193
+ {
194
+ id: string; // e.g. "root.settings.theme"
195
+ position: number; // sort order among siblings
196
+ value: T; // the node's data
197
+ type: NodeType; // e.g. "object" | "array" | "primitive"
198
+ }
199
+ ```
279
200
 
280
- Build a complete type-safe CRUD solution with minimal boilerplate:
201
+ ### `useSelectedNode`
281
202
 
282
- ### Client Setup
203
+ Convenience hook to access the currently selected node from anywhere, by collection ID.
283
204
 
284
- ```typescript
285
- import { useCrud, createSyncClientFromEndpoint } from "use-abcd";
205
+ ```tsx
206
+ function Inspector() {
207
+ const { data, isPresent } = useSelectedNode<FieldValue, {}, NodeType>("tree-editor");
286
208
 
287
- interface User {
288
- id: string;
289
- name: string;
290
- email: string;
209
+ // ...render
291
210
  }
211
+ ```
292
212
 
293
- interface UserQuery {
294
- page: number;
295
- limit: number;
296
- search?: string;
297
- }
213
+ ## Server Contract
298
214
 
299
- const UserConfig: Config<User, UserQuery> = {
300
- id: "users",
301
- initialContext: { page: 1, limit: 10 },
302
- getId: (user) => user.id,
215
+ The library communicates with the server through a single `POST` endpoint. Every request and response follows a fixed shape.
303
216
 
304
- // Use createSyncClientFromEndpoint for unified fetch + sync
305
- ...createSyncClientFromEndpoint<User, UserQuery>("/api/users"),
306
- };
217
+ ### Request body
307
218
 
308
- function UserList() {
309
- const { items, loading, create, update, remove, setContext } = useCrud(UserConfig);
310
-
311
- return (
312
- <div>
313
- {loading ? <p>Loading...</p> : (
314
- <>
315
- {Array.from(items.values()).map((user) => (
316
- <div key={user.id}>
317
- <span>{user.name} - {user.email}</span>
318
- <button onClick={() => update(user.id, (draft) => {
319
- draft.name = "Updated Name";
320
- })}>
321
- Update
322
- </button>
323
- <button onClick={() => remove(user.id)}>Delete</button>
324
- </div>
325
- ))}
326
- <button onClick={() => create({
327
- id: `temp-${Date.now()}`,
328
- name: "New User",
329
- email: "new@example.com",
330
- })}>
331
- Add User
332
- </button>
333
- </>
334
- )}
335
- </div>
336
- );
219
+ ```ts
220
+ {
221
+ scope?: string; // optional namespace
222
+ query?: Q; // present on fetch requests
223
+ changes?: Change<T>[]; // present on sync requests
337
224
  }
338
225
  ```
339
226
 
340
- ### Server Setup
341
-
342
- ```typescript
343
- import { createSyncServer, serverSyncSuccess } from "use-abcd/runtime/server";
344
-
345
- // Define your handlers
346
- const usersHandler = createSyncServer<User, UserQuery>({
347
- fetch: async (query) => {
348
- // Handle pagination and search
349
- return db.users.findMany({
350
- skip: (query.page - 1) * query.limit,
351
- take: query.limit,
352
- where: query.search ? { name: { contains: query.search } } : undefined,
353
- });
354
- },
355
-
356
- create: async (data) => {
357
- const user = await db.users.create({ data });
358
- return serverSyncSuccess({ newId: user.id });
359
- },
360
-
361
- update: async (id, data) => {
362
- await db.users.update({ where: { id }, data });
363
- return serverSyncSuccess();
364
- },
365
-
366
- delete: async (id) => {
367
- await db.users.delete({ where: { id } });
368
- return serverSyncSuccess();
369
- },
370
- });
371
-
372
- // Use with your framework
373
- // Next.js App Router
374
- export const POST = usersHandler.handler;
375
-
376
- // Hono
377
- app.post("/api/users", (c) => usersHandler.handler(c.req.raw));
227
+ A request contains `query` (to fetch data), `changes` (to push mutations), or both.
378
228
 
379
- // Bun.serve
380
- Bun.serve({
381
- fetch(req) {
382
- if (new URL(req.url).pathname === "/api/users") {
383
- return usersHandler.handler(req);
384
- }
385
- },
386
- });
229
+ Each change:
230
+ ```ts
231
+ { id: string; type: "create" | "update" | "delete"; data: T }
387
232
  ```
388
233
 
389
- ### What You Get
390
-
391
- - **Type safety**: Full TypeScript inference from data types to API calls
392
- - **Automatic ID remapping**: Temporary IDs are replaced with server-assigned IDs
393
- - **Batch operations**: Multiple changes are sent in a single request
394
- - **Optimistic updates**: UI updates instantly, syncs in background
395
- - **Error handling**: Failed operations are tracked and can be retried
396
- - **Unified endpoint**: Single POST endpoint handles fetch + create/update/delete
397
-
398
- ### Request/Response Format
399
-
400
- ```typescript
401
- // Fetch + Sync in one request
402
- POST /api/users
403
- Body: {
404
- query: { page: 1, limit: 10, search: "john" },
405
- changes: [
406
- { id: "temp-123", type: "create", data: { ... } },
407
- { id: "456", type: "update", data: { ... } },
408
- { id: "789", type: "delete", data: { ... } }
409
- ]
410
- }
234
+ ### Response body
411
235
 
412
- Response: {
413
- results: [...users], // Fetched items
414
- syncResults: [ // Sync results
415
- { id: "temp-123", status: "success", newId: "999" },
416
- { id: "456", status: "success" },
417
- { id: "789", status: "success" }
418
- ]
236
+ ```ts
237
+ {
238
+ serverSyncedAt: string; // required — server timestamp (ULID)
239
+ items?: T[]; // returned items from a fetch
240
+ syncResults?: Result[]; // per-change results from a sync
241
+ serverState?: S; // optional server-side metadata
419
242
  }
420
243
  ```
421
244
 
422
- ## API Reference
245
+ Each result:
246
+ ```ts
247
+ { id: string; type: ChangeType; status: "success" | "error"; serverSyncedAt: string; error?: string }
248
+ ```
423
249
 
424
- ### Types
250
+ ### Using the built-in runtime
425
251
 
426
- ```typescript
427
- type Config<T extends object, C> = {
428
- id: string;
429
- initialContext: C;
430
- getId: (item: T) => string;
431
- setId?: (item: T, newId: string) => T;
432
- syncDebounce?: number;
433
- syncRetries?: number;
434
- cacheCapacity?: number;
435
- cacheTtl?: number;
436
- onFetch: (context: C, signal: AbortSignal) => Promise<T[]>;
437
- onSync?: (changes: Change<T>[], signal: AbortSignal) => Promise<SyncResult[]>;
438
- };
252
+ The library ships client and server helpers that implement this contract.
439
253
 
440
- type Change<T> = {
441
- id: string;
442
- type: "create" | "update" | "delete";
443
- data: T;
444
- };
254
+ **Client** creates a `handler` for your config:
445
255
 
446
- type SyncResult = {
447
- id: string;
448
- status: "success" | "error";
449
- error?: string;
450
- newId?: string; // For creates: server-assigned ID
451
- };
256
+ ```ts
257
+ import { createSyncClient } from "use-abcd";
452
258
 
453
- type ItemStatus = {
454
- type: "create" | "update" | "delete";
455
- status: "pending" | "syncing" | "success" | "error";
456
- retries: number;
457
- error?: string;
458
- } | null;
259
+ const handler = createSyncClient<Todo, Query>("/api/todos");
260
+ // or with options:
261
+ const handler = createSyncClient<Todo, Query>({
262
+ endpoint: "/api/todos",
263
+ headers: { Authorization: "Bearer ..." },
264
+ scope: "workspace-123",
265
+ });
459
266
  ```
460
267
 
461
- ## Best Practices
462
-
463
- 1. **Use Optimistic Updates** - Let users see changes immediately while syncing in the background
464
- 2. **Handle Errors Gracefully** - Show error states and provide retry mechanisms
465
- 3. **Leverage Context** - Use context for filters, pagination, and search to trigger automatic refetches
466
- 4. **Use getItem() + useItem()** - For individual item management with automatic React optimization
467
- 5. **Monitor Sync Queue** - Display pending changes and errors to users for transparency
468
- 6. **Use createSyncClient/Server** - For end-to-end type-safe CRUD with minimal boilerplate
469
-
470
- ## Architecture
471
-
472
- The library is built on several core concepts:
268
+ **Server** creates a request handler from CRUD callbacks:
269
+
270
+ ```ts
271
+ import { createCrudHandler, createSyncServer } from "use-abcd/runtime/server";
272
+
273
+ const handler = createSyncServer(
274
+ createCrudHandler<Todo, Query>({
275
+ fetch: ({ scope, query }) => {
276
+ return db.todos.findMany({ where: { status: query.status } });
277
+ },
278
+ create: (record) => {
279
+ db.todos.insert(record.data);
280
+ },
281
+ update: (record) => {
282
+ db.todos.update(record.data.id, record.data);
283
+ },
284
+ remove: (record) => {
285
+ db.todos.delete(record.data.id);
286
+ },
287
+ }),
288
+ );
289
+ ```
473
290
 
474
- - **Collection** - Manages the item collection, sync queue, and fetch handler
475
- - **SyncQueue** - Handles debounced synchronization with retry logic
476
- - **FetchHandler** - Manages data fetching with caching
477
- - **Item** - Represents individual items with WeakMap-based caching for React optimization
291
+ `createSyncServer` returns a `(Request) => Promise<Response>` function compatible with any server that uses the Web Request/Response API (Bun, Deno, Cloudflare Workers, Next.js route handlers, etc.).
478
292
 
479
- All state updates use [Mutative](https://github.com/unadlib/mutative) for immutable updates, ensuring React can efficiently detect changes.
293
+ Each callback receives a `ServerRecord<T>`:
294
+ ```ts
295
+ { id: string; data: T; serverSyncedAt: string; deleted: boolean }
296
+ ```
480
297
 
481
- ## Contributing
298
+ The `fetch` callback can return a plain array or an object with `items` and optional `serverState` for passing metadata (totals, pagination cursors, etc.) back to the client.
482
299
 
483
- This is an alpha release. Please read the source code for a deeper understanding of its implementation and capabilities. Contributions and feedback are welcome!
300
+ ### Custom backend
484
301
 
485
- ## License
302
+ If you are not using the built-in runtime, implement the POST endpoint yourself following the request/response shapes above. The key requirements:
486
303
 
487
- MIT
304
+ 1. Always return `serverSyncedAt` — a monotonically increasing string (ULIDs recommended). The client uses this for ordering and conflict detection.
305
+ 2. Return `syncResults` for each change in the request. Each result must include the change's `id`, `type`, and a `status` of `"success"` or `"error"`. Missing results cause the sync queue to stall.
306
+ 3. Return `items` when the request contains a `query`.