@anfenn/zync 0.1.11 → 0.1.12

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 (2) hide show
  1. package/README.md +122 -2
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,126 @@
1
- # Zqync
1
+ # Zync
2
2
 
3
- Sync middleware for zustand.
3
+ [![npm version](https://img.shields.io/npm/v/@anfenn/zync.svg)](https://www.npmjs.com/package/@anfenn/zync)
4
+
5
+ Unopinionated, bullet-proof, offline-first sync middleware for Zustand.
6
+
7
+ ## Benefits
8
+
9
+ - Uses the official persist middleware as the local storage (localStorage, IndexedDB, etc.)
10
+ - Zync's persistWithSync() is a drop-in replacement for Zustand's persist()
11
+ - Allows for idiomatic use of Zustand
12
+ - Leaves the api requests up to you (RESTful, GraphQL, etc.), just provide add(), update(), remove() and list()
13
+
14
+ ## Requirements
15
+
16
+ - Client records will have a `_localId` field which is stable and never sent to the server. It is ideal for use as a key in JSX. The provided helper function `nextLocalId()` returns a UUID, but you could use any unique value
17
+ - Server records must have:
18
+ - `id`: Server assigned unique identifier (any datatype)
19
+ - `updated_at`: Server assigned (trigger or api layer). The client will never send this as the client clock is unlikely to be in sync with the server, so is never used for change detection
20
+ - `deleted`: Boolean, used for soft deletes, to allow other clients to download deleted records to keep their local records in sync
21
+
22
+ ## Quickstart
23
+
24
+ ```bash
25
+ npm install @anfenn/zync
26
+ ```
27
+
28
+ ### Zustand store creation:
29
+
30
+ ```ts
31
+ import { SyncAction, UseStoreWithSync, persistWithSync } from '@anfenn/zync';
32
+ import { create } from 'zustand';
33
+ import { createJSONStorage } from 'zustand/middleware';
34
+ import { useShallow } from 'zustand/react/shallow';
35
+
36
+ type Store = {
37
+ facts: Fact[];
38
+ addFact: (item: Fact) => void;
39
+ updateFact: (localId: string, changes: Partial<Fact>) => void;
40
+ removeFact: (localId: string) => void;
41
+ };
42
+
43
+ const useStore = create<any>()(
44
+ persistWithSync<Store>(
45
+ (set, get, queueToSync) => ({
46
+ // Standard Zustand state and mutation functions with new queueToSync() function
47
+
48
+ facts: [],
49
+ addFact: (item: Fact) => {
50
+ const updated_at = new Date().toISOString();
51
+ const newItem = { ...item, created_at: updated_at, updated_at };
52
+
53
+ set((state: Store) => ({
54
+ facts: [...state.facts, newItem],
55
+ }));
56
+
57
+ queueToSync(SyncAction.CreateOrUpdate, item._localId, 'facts');
58
+ },
59
+ updateFact: (localId: string, changes: Partial<Fact>) => {
60
+ set((state: Store) => ({
61
+ facts: state.facts.map((item) => (item._localId === localId ? { ...item, ...changes } : item)),
62
+ }));
63
+
64
+ queueToSync(SyncAction.CreateOrUpdate, localId, 'facts');
65
+ },
66
+ removeFact: (localId: string) => {
67
+ queueToSync(SyncAction.Remove, 'facts', localId);
68
+
69
+ set((state: Store) => ({
70
+ facts: state.facts.filter((item) => item._localId !== localId),
71
+ }));
72
+ },
73
+ }),
74
+ {
75
+ // Standard Zustand persist options
76
+
77
+ name: 'store',
78
+ storage: createJSONStorage(() => localStorage),
79
+ },
80
+ {
81
+ // State-to-API map to enable syncing. Must implement the full CRUD API:
82
+ //
83
+ // add: (item: any) => Promise<any | undefined>
84
+ // update: (id: any, changes: any) => Promise<boolean>
85
+ // remove: (id: any) => Promise<void>
86
+ // list: (lastUpdatedAt: Date) => Promise<any[]>
87
+ // firstLoad: (lastId: any) => Promise<any[]> (Optional)
88
+
89
+ facts: factApi,
90
+ },
91
+ ),
92
+ ) as UseStoreWithSync<Store>;
93
+
94
+ const useFacts = () =>
95
+ useStore(
96
+ useShallow(({ facts, addFact, updateFact, removeFact }) => ({
97
+ facts,
98
+ addFact,
99
+ updateFact,
100
+ removeFact,
101
+ })),
102
+ );
103
+ ```
104
+
105
+ ### In your component:
106
+
107
+ ```ts
108
+ // Your state
109
+ const { facts, addFact } = useFacts();
110
+
111
+ // Zync's internal sync state
112
+ const syncState = useStore((state) => state.syncState);
113
+ // syncState.status // 'hydrating' | 'syncing' | 'idle'
114
+ // syncState.error
115
+ // syncState.enabled
116
+ // syncState.firstLoadDone
117
+ // syncState.pendingChanges
118
+ // syncState.lastPulled
119
+
120
+ // Zync's control api
121
+ useStore.sync.enable(true | false);
122
+ useStore.sync.startFirstLoad();
123
+ ```
4
124
 
5
125
  ## Optional IndexedDB storage
6
126
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anfenn/zync",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "private": false,
5
5
  "description": "Sync middleware for zustand",
6
6
  "keywords": [],