@anfenn/zync 0.1.17 → 0.1.18
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 +89 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,10 +6,12 @@ Unopinionated, bullet-proof, offline-first sync middleware for Zustand.
|
|
|
6
6
|
|
|
7
7
|
## Benefits
|
|
8
8
|
|
|
9
|
+
- Simple to sync any state with a backend
|
|
9
10
|
- Uses the official persist middleware as the local storage (localStorage, IndexedDB, etc.)
|
|
10
11
|
- Zync's persistWithSync() is a drop-in replacement for Zustand's persist()
|
|
11
12
|
- Allows for idiomatic use of Zustand
|
|
12
13
|
- Leaves the api requests up to you (RESTful, GraphQL, etc.), just provide add(), update(), remove() and list()
|
|
14
|
+
- **_Coming soon_**: Customisable conflict resolution. Currently last-write-wins.
|
|
13
15
|
|
|
14
16
|
## Requirements
|
|
15
17
|
|
|
@@ -123,6 +125,89 @@ useStore.sync.enable(true | false);
|
|
|
123
125
|
useStore.sync.startFirstLoad();
|
|
124
126
|
```
|
|
125
127
|
|
|
128
|
+
### In your API:
|
|
129
|
+
*(Supabase example, but could be fetch, GraphQL, etc.)*
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
import { ApiFunctions } from '@anfenn/zync';
|
|
133
|
+
import { supabase } from './supabase';
|
|
134
|
+
|
|
135
|
+
export type Fact = {
|
|
136
|
+
_localId: string;
|
|
137
|
+
fact: string;
|
|
138
|
+
// Server assigned fields
|
|
139
|
+
id?: number;
|
|
140
|
+
updated_at?: string;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export const factApi: ApiFunctions = { add, update, remove, list, firstLoad };
|
|
144
|
+
|
|
145
|
+
async function add(item: any): Promise<any | undefined> {
|
|
146
|
+
const { data, error } = await supabase.from('fact').insert(item).select();
|
|
147
|
+
|
|
148
|
+
if (error) {
|
|
149
|
+
// Throw errors to cause Zync to retry
|
|
150
|
+
throw new Error(error.message);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (data && data.length > 0) {
|
|
154
|
+
// Must return server id, and any other fields you want merged in
|
|
155
|
+
return { id: data[0].id };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function update(id: number, changes: any): Promise<boolean> {
|
|
160
|
+
const { status, statusText, data } = await supabase.from('fact').update(changes).eq('id', id).select();
|
|
161
|
+
|
|
162
|
+
if (status !== 200) {
|
|
163
|
+
throw new Error(statusText);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Must return success boolean to tell Zync to dequeue update
|
|
167
|
+
const changed = !!data?.[0];
|
|
168
|
+
return changed;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Soft delete
|
|
172
|
+
async function remove(id: number) {
|
|
173
|
+
const payload = {
|
|
174
|
+
deleted: true,
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const { status, statusText } = await supabase.from('fact').update(payload).eq('id', id);
|
|
178
|
+
|
|
179
|
+
if (status !== 204) {
|
|
180
|
+
throw new Error(statusText);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function list(lastUpdatedAt: Date) {
|
|
185
|
+
const { data, error } = await supabase.from('fact').select().gt('updated_at', lastUpdatedAt.toISOString());
|
|
186
|
+
|
|
187
|
+
if (error) {
|
|
188
|
+
throw new Error(error.message);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return data;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Optional, for if you want to download all data when your app is first used
|
|
195
|
+
// Called until no more records are returned
|
|
196
|
+
async function firstLoad(lastId: any) {
|
|
197
|
+
// Initially undefined, so you can choose the datatype (e.g. numeric or string id)
|
|
198
|
+
// Zync will remember the last id returned, having sorted in ascending order, and passes it in as lastId next time
|
|
199
|
+
if (!lastId) lastId = 0;
|
|
200
|
+
|
|
201
|
+
const { data, error } = await supabase.from('fact').select().limit(1000).order('id', { ascending: true }).gt('id', lastId);
|
|
202
|
+
|
|
203
|
+
if (error) {
|
|
204
|
+
throw new Error(error.message);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return data;
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
126
211
|
## Optional IndexedDB storage
|
|
127
212
|
|
|
128
213
|
When using IndexedDB Zustand saves the whole store under one key, which means indexes cannot be used to accelerate querying. However, if this becomes a performance issue due to the size of the store, then libraries like dexie.js instead of Zustand would be a better solution and provide the syntax for high performance queries.
|
|
@@ -142,3 +227,7 @@ npm install --save-optional idb
|
|
|
142
227
|
```
|
|
143
228
|
|
|
144
229
|
The library will throw a helpful runtime error if `idb` isn't installed when `createIndexedDBStorage()` is invoked.
|
|
230
|
+
|
|
231
|
+
## Community
|
|
232
|
+
|
|
233
|
+
PRs are welcome! [pnpm](https://pnpm.io) is used as a package manager. Run `pnpm install` to install local dependencies. Thank you for contributing!
|