@legendapp/state 3.0.0-alpha.2 → 3.0.0-alpha.20

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 (54) hide show
  1. package/CHANGELOG.md +831 -1
  2. package/LICENSE +21 -1
  3. package/README.md +141 -1
  4. package/babel.js +0 -2
  5. package/babel.mjs +0 -2
  6. package/helpers/trackHistory.js +2 -2
  7. package/helpers/trackHistory.mjs +2 -2
  8. package/index.d.mts +45 -32
  9. package/index.d.ts +45 -32
  10. package/index.js +234 -156
  11. package/index.mjs +234 -156
  12. package/package.json +6 -1
  13. package/persist-plugins/async-storage.d.mts +2 -2
  14. package/persist-plugins/async-storage.d.ts +2 -2
  15. package/persist-plugins/indexeddb.js +1 -1
  16. package/persist-plugins/indexeddb.mjs +1 -1
  17. package/react.d.mts +17 -14
  18. package/react.d.ts +17 -14
  19. package/react.js +55 -30
  20. package/react.mjs +56 -31
  21. package/sync-plugins/_transformObjectFields.d.mts +31 -0
  22. package/sync-plugins/_transformObjectFields.d.ts +31 -0
  23. package/sync-plugins/_transformObjectFields.js +114 -0
  24. package/sync-plugins/_transformObjectFields.mjs +110 -0
  25. package/sync-plugins/crud.d.mts +14 -23
  26. package/sync-plugins/crud.d.ts +14 -23
  27. package/sync-plugins/crud.js +194 -129
  28. package/sync-plugins/crud.mjs +195 -130
  29. package/sync-plugins/firebase.d.mts +26 -0
  30. package/sync-plugins/firebase.d.ts +26 -0
  31. package/sync-plugins/firebase.js +365 -0
  32. package/sync-plugins/firebase.mjs +360 -0
  33. package/sync-plugins/keel.d.mts +24 -8
  34. package/sync-plugins/keel.d.ts +24 -8
  35. package/sync-plugins/keel.js +32 -16
  36. package/sync-plugins/keel.mjs +32 -16
  37. package/sync-plugins/supabase.d.mts +1 -1
  38. package/sync-plugins/supabase.d.ts +1 -1
  39. package/sync-plugins/supabase.js +5 -4
  40. package/sync-plugins/supabase.mjs +6 -5
  41. package/sync-plugins/tanstack-query.d.mts +2 -2
  42. package/sync-plugins/tanstack-query.d.ts +2 -2
  43. package/sync-plugins/tanstack-query.js +3 -2
  44. package/sync-plugins/tanstack-query.mjs +3 -2
  45. package/sync-plugins/tanstack-react-query.d.mts +1 -1
  46. package/sync-plugins/tanstack-react-query.d.ts +1 -1
  47. package/sync.d.mts +38 -185
  48. package/sync.d.ts +38 -185
  49. package/sync.js +354 -296
  50. package/sync.mjs +356 -297
  51. package/types/babel.d.ts +12 -1
  52. package/.DS_Store +0 -0
  53. /package/config/{enable_GetSet.d.mts → enable$GetSet.d.mts} +0 -0
  54. /package/config/{enable_GetSet.d.ts → enable$GetSet.d.ts} +0 -0
@@ -0,0 +1,360 @@
1
+ import { observable, symbolDelete, isString, isArray, isObject, computeSelector, isFunction, isNullOrUndefined, isPromise, isNumber, when } from '@legendapp/state';
2
+ import { syncedCrud } from '@legendapp/state/sync-plugins/crud';
3
+ import { getAuth } from 'firebase/auth';
4
+ import { ref, getDatabase, query, orderByChild, startAt, update, onValue, onChildAdded, onChildChanged, onChildRemoved, serverTimestamp, remove, push } from 'firebase/database';
5
+
6
+ // src/sync-plugins/firebase.ts
7
+ var validateMap;
8
+ function transformObjectFields(dataIn, map) {
9
+ if (process.env.NODE_ENV === "development") {
10
+ validateMap(map);
11
+ }
12
+ let ret = dataIn;
13
+ if (dataIn) {
14
+ if (dataIn === symbolDelete)
15
+ return dataIn;
16
+ if (isString(dataIn)) {
17
+ return map[dataIn];
18
+ }
19
+ ret = {};
20
+ const dict = Object.keys(map).length === 1 && map["_dict"];
21
+ for (const key in dataIn) {
22
+ let v = dataIn[key];
23
+ if (dict) {
24
+ ret[key] = transformObjectFields(v, dict);
25
+ } else {
26
+ const mapped = map[key];
27
+ if (mapped === void 0) {
28
+ if (key !== "@") {
29
+ ret[key] = v;
30
+ if (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test") {
31
+ console.error("A fatal field transformation error has occurred", key, dataIn, map);
32
+ }
33
+ }
34
+ } else if (mapped !== null) {
35
+ if (v !== void 0 && v !== null) {
36
+ if (map[key + "_val"]) {
37
+ const mapChild = map[key + "_val"];
38
+ if (isArray(v)) {
39
+ v = v.map((vChild) => mapChild[vChild]);
40
+ } else {
41
+ v = mapChild[v];
42
+ }
43
+ } else if (map[key + "_arr"] && isArray(v)) {
44
+ const mapChild = map[key + "_arr"];
45
+ v = v.map((vChild) => transformObjectFields(vChild, mapChild));
46
+ } else if (isObject(v)) {
47
+ if (map[key + "_obj"]) {
48
+ v = transformObjectFields(v, map[key + "_obj"]);
49
+ } else if (map[key + "_dict"]) {
50
+ const mapChild = map[key + "_dict"];
51
+ const out = {};
52
+ for (const keyChild in v) {
53
+ out[keyChild] = transformObjectFields(v[keyChild], mapChild);
54
+ }
55
+ v = out;
56
+ }
57
+ }
58
+ }
59
+ ret[mapped] = v;
60
+ }
61
+ }
62
+ }
63
+ }
64
+ return ret;
65
+ }
66
+ var invertedMaps = /* @__PURE__ */ new WeakMap();
67
+ function invertFieldMap(obj) {
68
+ const existing = invertedMaps.get(obj);
69
+ if (existing)
70
+ return existing;
71
+ const target = {};
72
+ for (const key in obj) {
73
+ const val = obj[key];
74
+ if (key === "_dict") {
75
+ target[key] = invertFieldMap(val);
76
+ } else if (key.endsWith("_obj") || key.endsWith("_dict") || key.endsWith("_arr") || key.endsWith("_val")) {
77
+ const keyMapped = obj[key.replace(/_obj|_dict|_arr|_val$/, "")];
78
+ const suffix = key.match(/_obj|_dict|_arr|_val$/)[0];
79
+ target[keyMapped + suffix] = invertFieldMap(val);
80
+ } else if (typeof val === "string") {
81
+ target[val] = key;
82
+ }
83
+ }
84
+ invertedMaps.set(obj, target);
85
+ return target;
86
+ }
87
+ if (process.env.NODE_ENV === "development") {
88
+ validateMap = function(record) {
89
+ const values = Object.values(record).filter((value) => {
90
+ if (isObject(value)) {
91
+ validateMap(value);
92
+ } else {
93
+ return isString(value);
94
+ }
95
+ });
96
+ const uniques = Array.from(new Set(values));
97
+ if (values.length !== uniques.length) {
98
+ console.error("Field transform map has duplicate values", record, values.length, uniques.length);
99
+ }
100
+ return record;
101
+ };
102
+ }
103
+
104
+ // src/sync-plugins/firebase.ts
105
+ var isEnabled$ = observable(true);
106
+ var firebaseConfig = {};
107
+ function configureSyncedFirebase(config) {
108
+ const { enabled, ...rest } = config;
109
+ Object.assign(firebaseConfig, rest);
110
+ if (enabled !== void 0) {
111
+ isEnabled$.set(enabled);
112
+ }
113
+ }
114
+ function joinPaths(str1, str2) {
115
+ return str2 ? [str1, str2].join("/").replace(/\/\//g, "/") : str1;
116
+ }
117
+ var fns = {
118
+ isInitialized: () => {
119
+ try {
120
+ return !!getAuth().app;
121
+ } catch (e) {
122
+ return false;
123
+ }
124
+ },
125
+ getCurrentUser: () => {
126
+ var _a;
127
+ return (_a = getAuth().currentUser) == null ? void 0 : _a.uid;
128
+ },
129
+ ref: (path) => ref(getDatabase(), path),
130
+ orderByChild: (ref, child, start) => query(ref, orderByChild(child), startAt(start)),
131
+ update: (ref, object) => update(ref, object),
132
+ once: (ref, callback, callbackError) => {
133
+ let unsubscribe;
134
+ const cb = (snap) => {
135
+ if (unsubscribe) {
136
+ unsubscribe();
137
+ unsubscribe = void 0;
138
+ }
139
+ callback(snap);
140
+ };
141
+ unsubscribe = onValue(ref, cb, callbackError);
142
+ return unsubscribe;
143
+ },
144
+ onChildAdded,
145
+ onChildChanged,
146
+ onChildRemoved,
147
+ onValue,
148
+ serverTimestamp,
149
+ remove: remove,
150
+ onAuthStateChanged: (cb) => getAuth().onAuthStateChanged(cb),
151
+ generateId: () => push(ref(getDatabase())).key
152
+ };
153
+ function syncedFirebase(props) {
154
+ props = { ...firebaseConfig, ...props };
155
+ const saving$ = observable({});
156
+ const pendingOutgoing$ = observable({});
157
+ const pendingIncoming$ = observable({});
158
+ let didList = false;
159
+ const {
160
+ refPath,
161
+ query,
162
+ fieldId,
163
+ realtime,
164
+ requireAuth,
165
+ readonly,
166
+ transform: transformProp,
167
+ fieldTransforms,
168
+ waitFor,
169
+ waitForSet,
170
+ ...rest
171
+ } = props;
172
+ const { fieldCreatedAt, changesSince } = props;
173
+ const asType = props.as || "value";
174
+ const fieldUpdatedAt = props.fieldUpdatedAt || "@";
175
+ const computeRef = (lastSync) => {
176
+ const pathFirebase = refPath(fns.getCurrentUser());
177
+ let ref = fns.ref(pathFirebase);
178
+ if (query) {
179
+ ref = query(ref);
180
+ }
181
+ if (changesSince === "last-sync" && lastSync && fieldUpdatedAt && isNumber(lastSync)) {
182
+ ref = fns.orderByChild(ref, fieldUpdatedAt, lastSync + 1);
183
+ }
184
+ return ref;
185
+ };
186
+ const list = async ({ lastSync, onError }) => {
187
+ const ref = computeRef(lastSync);
188
+ return new Promise((resolve) => {
189
+ fns.once(
190
+ ref,
191
+ async (snap) => {
192
+ const val = snap.val();
193
+ let values = [];
194
+ if (!isNullOrUndefined(val)) {
195
+ values = asType === "value" ? [val] : Object.entries(val).map(([key, value]) => {
196
+ if (fieldId && !value[fieldId]) {
197
+ value[fieldId] = key;
198
+ }
199
+ return value;
200
+ });
201
+ }
202
+ didList = true;
203
+ resolve(values);
204
+ },
205
+ onError
206
+ );
207
+ });
208
+ };
209
+ const subscribe = realtime ? ({ lastSync, update: update2, onError }) => {
210
+ const ref = computeRef(lastSync);
211
+ let unsubscribes;
212
+ if (asType === "value") {
213
+ const onValue2 = (snap) => {
214
+ if (!didList)
215
+ return;
216
+ const val = snap.val();
217
+ if (saving$[""].get()) {
218
+ pendingIncoming$[""].set(val);
219
+ } else {
220
+ update2({
221
+ value: [val],
222
+ mode: "set"
223
+ });
224
+ }
225
+ };
226
+ unsubscribes = [fns.onValue(ref, onValue2, onError)];
227
+ } else {
228
+ const onChildChange = (snap) => {
229
+ if (!didList)
230
+ return;
231
+ const key = snap.key;
232
+ const val = snap.val();
233
+ if (saving$[key].get()) {
234
+ pendingIncoming$[key].set(val);
235
+ } else {
236
+ update2({
237
+ value: [val],
238
+ mode: "assign"
239
+ });
240
+ }
241
+ };
242
+ const onChildDelete = (snap) => {
243
+ if (!didList)
244
+ return;
245
+ const key = snap.key;
246
+ update2({
247
+ value: [{ [key]: symbolDelete }],
248
+ mode: "assign"
249
+ });
250
+ };
251
+ unsubscribes = [
252
+ fns.onChildAdded(ref, onChildChange, onError),
253
+ fns.onChildChanged(ref, onChildChange, onError),
254
+ fns.onChildRemoved(ref, onChildDelete, onError)
255
+ ];
256
+ }
257
+ return () => {
258
+ unsubscribes.forEach((fn) => fn());
259
+ };
260
+ } : void 0;
261
+ const addUpdatedAt = (input) => {
262
+ if (fieldUpdatedAt) {
263
+ input[fieldUpdatedAt] = serverTimestamp();
264
+ }
265
+ };
266
+ const addCreatedAt = (input) => {
267
+ if (fieldCreatedAt && !input[fieldCreatedAt]) {
268
+ input[fieldCreatedAt] = serverTimestamp();
269
+ }
270
+ return addUpdatedAt(input);
271
+ };
272
+ const upsert = async (input) => {
273
+ const id = fieldId ? input[fieldId] : "";
274
+ if (saving$[id].get()) {
275
+ pendingOutgoing$[id].set(input);
276
+ } else {
277
+ saving$[id].set(true);
278
+ const path = joinPaths(refPath(fns.getCurrentUser()), fieldId ? id : "");
279
+ await fns.update(fns.ref(path), input);
280
+ saving$[id].set(false);
281
+ flushAfterSave();
282
+ }
283
+ return when(
284
+ () => !pendingOutgoing$[id].get(),
285
+ () => {
286
+ const value = pendingIncoming$[id].get();
287
+ if (value) {
288
+ pendingIncoming$[id].delete();
289
+ return value;
290
+ }
291
+ }
292
+ );
293
+ };
294
+ const flushAfterSave = () => {
295
+ const outgoing = pendingOutgoing$.get();
296
+ Object.values(outgoing).forEach((value) => {
297
+ upsert(value);
298
+ });
299
+ pendingOutgoing$.set({});
300
+ };
301
+ const create = readonly ? void 0 : (input) => {
302
+ addCreatedAt(input);
303
+ return upsert(input);
304
+ };
305
+ const update = readonly ? void 0 : (input) => {
306
+ addUpdatedAt(input);
307
+ return upsert(input);
308
+ };
309
+ const deleteFn = readonly ? void 0 : (input) => {
310
+ const path = joinPaths(
311
+ refPath(fns.getCurrentUser()),
312
+ fieldId && asType !== "value" ? input[fieldId] : ""
313
+ );
314
+ return fns.remove(fns.ref(path));
315
+ };
316
+ let isAuthedIfRequired$;
317
+ if (requireAuth) {
318
+ if (fns.isInitialized()) {
319
+ isAuthedIfRequired$ = observable(false);
320
+ fns.onAuthStateChanged((user) => {
321
+ isAuthedIfRequired$.set(!!user);
322
+ });
323
+ }
324
+ }
325
+ let transform = transformProp;
326
+ if (fieldTransforms) {
327
+ const inverted = invertFieldMap(fieldTransforms);
328
+ transform = {
329
+ load(value, method) {
330
+ const fieldTransformed = transformObjectFields(value, inverted);
331
+ return (transformProp == null ? void 0 : transformProp.load) ? transformProp.load(fieldTransformed, method) : fieldTransformed;
332
+ },
333
+ save(value) {
334
+ const transformed = (transformProp == null ? void 0 : transformProp.save) ? transformProp.save(value) : value;
335
+ if (isPromise(transformed)) {
336
+ return transformed.then((transformedValue) => {
337
+ return transformObjectFields(transformedValue, fieldTransforms);
338
+ });
339
+ } else {
340
+ return transformObjectFields(transformed, fieldTransforms);
341
+ }
342
+ }
343
+ };
344
+ }
345
+ return syncedCrud({
346
+ ...rest,
347
+ list,
348
+ subscribe,
349
+ create,
350
+ update,
351
+ delete: deleteFn,
352
+ waitFor: () => isEnabled$.get() && (isAuthedIfRequired$ ? isAuthedIfRequired$.get() : true) && (waitFor ? computeSelector(waitFor) : true),
353
+ waitForSet: (params) => isEnabled$.get() && (isAuthedIfRequired$ ? isAuthedIfRequired$.get() : true) && (waitForSet ? isFunction(waitForSet) ? waitForSet(params) : waitForSet : true),
354
+ generateId: fns.generateId,
355
+ transform,
356
+ as: asType
357
+ });
358
+ }
359
+
360
+ export { configureSyncedFirebase, invertFieldMap, syncedFirebase, transformObjectFields };
@@ -1,4 +1,4 @@
1
- import { SyncedOptions } from '@legendapp/state/sync';
1
+ import { SyncedGetSetSubscribeBaseParams, SyncedOptions, SyncedSetParams } from '@legendapp/state/sync';
2
2
  import { SyncedCrudPropsBase, CrudAsOption, SyncedCrudReturnType, SyncedCrudPropsSingle, CrudResult, SyncedCrudPropsMany } from '@legendapp/state/sync-plugins/crud';
3
3
 
4
4
  interface KeelObjectBase {
@@ -39,7 +39,7 @@ interface KeelListParams<Where = {}> {
39
39
  before?: string;
40
40
  }
41
41
  interface KeelRealtimePlugin {
42
- subscribe: (realtimeKey: string, refresh: () => void) => void;
42
+ subscribe: (realtimeKey: string, params: SyncedGetSetSubscribeBaseParams) => void;
43
43
  setLatestChange: (realtimeKey: string, time: Date) => void;
44
44
  }
45
45
  interface SyncedKeelConfiguration extends Omit<SyncedCrudPropsBase<any>, keyof SyncedOptions | 'create' | 'update' | 'delete' | 'onSaved' | 'updatePartial' | 'fieldCreatedAt' | 'fieldUpdatedAt' | 'generateId'> {
@@ -55,20 +55,32 @@ interface SyncedKeelConfiguration extends Omit<SyncedCrudPropsBase<any>, keyof S
55
55
  realtimePlugin?: KeelRealtimePlugin;
56
56
  as?: Exclude<CrudAsOption, 'value'>;
57
57
  enabled?: boolean;
58
- onError?: (params: APIResult<any>['error']) => void;
58
+ onError?: (params: {
59
+ type: 'create' | 'update' | 'delete';
60
+ params: SyncedSetParams<any>;
61
+ input: any;
62
+ action: string;
63
+ error: APIResult<any>['error'];
64
+ }) => void;
59
65
  }
60
- interface SyncedKeelPropsManyBase<TRemote, TLocal, AOption extends CrudAsOption> extends Omit<SyncedCrudPropsMany<TRemote, TLocal, AOption>, 'list'> {
66
+ interface SyncedKeelPropsManyBase<TRemote extends {
67
+ id: string;
68
+ }, TLocal, AOption extends CrudAsOption> extends Omit<SyncedCrudPropsMany<TRemote, TLocal, AOption>, 'list'> {
61
69
  first?: number;
62
70
  get?: never;
63
71
  }
64
- interface SyncedKeelPropsManyWhere<TRemote, TLocal, AOption extends CrudAsOption, Where extends Record<string, any>> extends SyncedKeelPropsManyBase<TRemote, TLocal, AOption> {
72
+ interface SyncedKeelPropsManyWhere<TRemote extends {
73
+ id: string;
74
+ }, TLocal, AOption extends CrudAsOption, Where extends Record<string, any>> extends SyncedKeelPropsManyBase<TRemote, TLocal, AOption> {
65
75
  list?: (params: KeelListParams<NoInfer<Where>>) => Promise<CrudResult<APIResult<{
66
76
  results: TRemote[];
67
77
  pageInfo: any;
68
78
  }>>>;
69
79
  where?: Where | (() => Where);
70
80
  }
71
- interface SyncedKeelPropsManyNoWhere<TRemote, TLocal, AOption extends CrudAsOption> extends SyncedKeelPropsManyBase<TRemote, TLocal, AOption> {
81
+ interface SyncedKeelPropsManyNoWhere<TRemote extends {
82
+ id: string;
83
+ }, TLocal, AOption extends CrudAsOption> extends SyncedKeelPropsManyBase<TRemote, TLocal, AOption> {
72
84
  list?: (params: KeelListParams<{}>) => Promise<CrudResult<APIResult<{
73
85
  results: TRemote[];
74
86
  pageInfo: any;
@@ -76,8 +88,12 @@ interface SyncedKeelPropsManyNoWhere<TRemote, TLocal, AOption extends CrudAsOpti
76
88
  where?: never | {};
77
89
  }
78
90
  type HasAnyKeys<T> = keyof T extends never ? false : true;
79
- type SyncedKeelPropsMany<TRemote, TLocal, AOption extends CrudAsOption, Where extends Record<string, any>> = HasAnyKeys<Where> extends true ? SyncedKeelPropsManyWhere<TRemote, TLocal, AOption, Where> : SyncedKeelPropsManyNoWhere<TRemote, TLocal, AOption>;
80
- interface SyncedKeelPropsSingle<TRemote, TLocal> extends Omit<SyncedCrudPropsSingle<TRemote, TLocal>, 'get'> {
91
+ type SyncedKeelPropsMany<TRemote extends {
92
+ id: string;
93
+ }, TLocal, AOption extends CrudAsOption, Where extends Record<string, any>> = HasAnyKeys<Where> extends true ? SyncedKeelPropsManyWhere<TRemote, TLocal, AOption, Where> : SyncedKeelPropsManyNoWhere<TRemote, TLocal, AOption>;
94
+ interface SyncedKeelPropsSingle<TRemote extends {
95
+ id: string;
96
+ }, TLocal> extends Omit<SyncedCrudPropsSingle<TRemote, TLocal>, 'get'> {
81
97
  get?: (params: KeelGetParams) => Promise<APIResult<TRemote>>;
82
98
  first?: never;
83
99
  where?: never;
@@ -1,4 +1,4 @@
1
- import { SyncedOptions } from '@legendapp/state/sync';
1
+ import { SyncedGetSetSubscribeBaseParams, SyncedOptions, SyncedSetParams } from '@legendapp/state/sync';
2
2
  import { SyncedCrudPropsBase, CrudAsOption, SyncedCrudReturnType, SyncedCrudPropsSingle, CrudResult, SyncedCrudPropsMany } from '@legendapp/state/sync-plugins/crud';
3
3
 
4
4
  interface KeelObjectBase {
@@ -39,7 +39,7 @@ interface KeelListParams<Where = {}> {
39
39
  before?: string;
40
40
  }
41
41
  interface KeelRealtimePlugin {
42
- subscribe: (realtimeKey: string, refresh: () => void) => void;
42
+ subscribe: (realtimeKey: string, params: SyncedGetSetSubscribeBaseParams) => void;
43
43
  setLatestChange: (realtimeKey: string, time: Date) => void;
44
44
  }
45
45
  interface SyncedKeelConfiguration extends Omit<SyncedCrudPropsBase<any>, keyof SyncedOptions | 'create' | 'update' | 'delete' | 'onSaved' | 'updatePartial' | 'fieldCreatedAt' | 'fieldUpdatedAt' | 'generateId'> {
@@ -55,20 +55,32 @@ interface SyncedKeelConfiguration extends Omit<SyncedCrudPropsBase<any>, keyof S
55
55
  realtimePlugin?: KeelRealtimePlugin;
56
56
  as?: Exclude<CrudAsOption, 'value'>;
57
57
  enabled?: boolean;
58
- onError?: (params: APIResult<any>['error']) => void;
58
+ onError?: (params: {
59
+ type: 'create' | 'update' | 'delete';
60
+ params: SyncedSetParams<any>;
61
+ input: any;
62
+ action: string;
63
+ error: APIResult<any>['error'];
64
+ }) => void;
59
65
  }
60
- interface SyncedKeelPropsManyBase<TRemote, TLocal, AOption extends CrudAsOption> extends Omit<SyncedCrudPropsMany<TRemote, TLocal, AOption>, 'list'> {
66
+ interface SyncedKeelPropsManyBase<TRemote extends {
67
+ id: string;
68
+ }, TLocal, AOption extends CrudAsOption> extends Omit<SyncedCrudPropsMany<TRemote, TLocal, AOption>, 'list'> {
61
69
  first?: number;
62
70
  get?: never;
63
71
  }
64
- interface SyncedKeelPropsManyWhere<TRemote, TLocal, AOption extends CrudAsOption, Where extends Record<string, any>> extends SyncedKeelPropsManyBase<TRemote, TLocal, AOption> {
72
+ interface SyncedKeelPropsManyWhere<TRemote extends {
73
+ id: string;
74
+ }, TLocal, AOption extends CrudAsOption, Where extends Record<string, any>> extends SyncedKeelPropsManyBase<TRemote, TLocal, AOption> {
65
75
  list?: (params: KeelListParams<NoInfer<Where>>) => Promise<CrudResult<APIResult<{
66
76
  results: TRemote[];
67
77
  pageInfo: any;
68
78
  }>>>;
69
79
  where?: Where | (() => Where);
70
80
  }
71
- interface SyncedKeelPropsManyNoWhere<TRemote, TLocal, AOption extends CrudAsOption> extends SyncedKeelPropsManyBase<TRemote, TLocal, AOption> {
81
+ interface SyncedKeelPropsManyNoWhere<TRemote extends {
82
+ id: string;
83
+ }, TLocal, AOption extends CrudAsOption> extends SyncedKeelPropsManyBase<TRemote, TLocal, AOption> {
72
84
  list?: (params: KeelListParams<{}>) => Promise<CrudResult<APIResult<{
73
85
  results: TRemote[];
74
86
  pageInfo: any;
@@ -76,8 +88,12 @@ interface SyncedKeelPropsManyNoWhere<TRemote, TLocal, AOption extends CrudAsOpti
76
88
  where?: never | {};
77
89
  }
78
90
  type HasAnyKeys<T> = keyof T extends never ? false : true;
79
- type SyncedKeelPropsMany<TRemote, TLocal, AOption extends CrudAsOption, Where extends Record<string, any>> = HasAnyKeys<Where> extends true ? SyncedKeelPropsManyWhere<TRemote, TLocal, AOption, Where> : SyncedKeelPropsManyNoWhere<TRemote, TLocal, AOption>;
80
- interface SyncedKeelPropsSingle<TRemote, TLocal> extends Omit<SyncedCrudPropsSingle<TRemote, TLocal>, 'get'> {
91
+ type SyncedKeelPropsMany<TRemote extends {
92
+ id: string;
93
+ }, TLocal, AOption extends CrudAsOption, Where extends Record<string, any>> = HasAnyKeys<Where> extends true ? SyncedKeelPropsManyWhere<TRemote, TLocal, AOption, Where> : SyncedKeelPropsManyNoWhere<TRemote, TLocal, AOption>;
94
+ interface SyncedKeelPropsSingle<TRemote extends {
95
+ id: string;
96
+ }, TLocal> extends Omit<SyncedCrudPropsSingle<TRemote, TLocal>, 'get'> {
81
97
  get?: (params: KeelGetParams) => Promise<APIResult<TRemote>>;
82
98
  first?: never;
83
99
  where?: never;
@@ -65,9 +65,9 @@ function configureSyncedKeel(config) {
65
65
  const oldFn = queries[key];
66
66
  queries[key] = (i) => {
67
67
  const realtimeKey = [key, ...Object.values(i.where || {})].filter((value) => value && typeof value !== "object").join("/");
68
- const subscribe = ({ refresh }) => {
68
+ const subscribe = (params) => {
69
69
  if (realtimeKey) {
70
- return realtimePlugin.subscribe(realtimeKey, refresh);
70
+ return realtimePlugin.subscribe(realtimeKey, params);
71
71
  }
72
72
  };
73
73
  return oldFn(i).then((ret) => {
@@ -128,7 +128,9 @@ function syncedKeel(props) {
128
128
  first,
129
129
  where: whereParam,
130
130
  waitFor,
131
+ waitForSet,
131
132
  fieldDeleted,
133
+ mode,
132
134
  ...rest
133
135
  } = props;
134
136
  const { changesSince } = props;
@@ -137,8 +139,15 @@ function syncedKeel(props) {
137
139
  const subscribeFnKey$ = state.observable("");
138
140
  const fieldCreatedAt = "createdAt";
139
141
  const fieldUpdatedAt = "updatedAt";
142
+ const setupSubscribe = (doSubscribe, subscribeKey, lastSync) => {
143
+ subscribeFn = doSubscribe;
144
+ subscribeFnKey$.set(subscribeKey);
145
+ if (realtimePlugin && lastSync) {
146
+ realtimePlugin.setLatestChange(subscribeKey, new Date(lastSync));
147
+ }
148
+ };
140
149
  const list = listParam ? async (listParams) => {
141
- const { lastSync, refresh } = listParams;
150
+ const { lastSync } = listParams;
142
151
  const queryBySync = !!lastSync && changesSince === "last-sync";
143
152
  const where = Object.assign(
144
153
  queryBySync ? { updatedAt: { after: new Date(lastSync + 1) } } : {},
@@ -147,8 +156,7 @@ function syncedKeel(props) {
147
156
  const params = { where, first };
148
157
  const { results, subscribe: subscribe2, subscribeKey } = await getAllPages(listParam, params);
149
158
  if (subscribe2) {
150
- subscribeFn = () => subscribe2({ refresh });
151
- subscribeFnKey$.set(subscribeKey);
159
+ setupSubscribe(() => subscribe2(listParams), subscribeKey, lastSync);
152
160
  }
153
161
  return results;
154
162
  } : void 0;
@@ -156,8 +164,7 @@ function syncedKeel(props) {
156
164
  const { refresh } = getParams;
157
165
  const { data, error, subscribe: subscribe2, subscribeKey } = await getParam({ refresh });
158
166
  if (subscribe2) {
159
- subscribeFn = () => subscribe2({ refresh });
160
- subscribeFnKey$.set(subscribeKey);
167
+ setupSubscribe(() => subscribe2(getParams), subscribeKey);
161
168
  }
162
169
  if (error) {
163
170
  throw new Error(error.message);
@@ -176,19 +183,27 @@ function syncedKeel(props) {
176
183
  }
177
184
  }
178
185
  };
179
- const handleSetError = async (error, params, isCreate) => {
186
+ const handleSetError = async (error, params, input, fn, from) => {
180
187
  var _a, _b, _c;
181
188
  const { retryNum, cancelRetry, update: update2 } = params;
182
- if (isCreate && ((_a = error.message) == null ? void 0 : _a.includes("for the unique")) && ((_b = error.message) == null ? void 0 : _b.includes("must be unique"))) {
189
+ if (from === "create" && ((_a = error.message) == null ? void 0 : _a.includes("for the unique")) && ((_b = error.message) == null ? void 0 : _b.includes("must be unique"))) {
183
190
  if (__DEV__) {
184
191
  console.log("Creating duplicate data already saved, just ignore.");
185
192
  }
193
+ cancelRetry();
186
194
  update2({
187
195
  value: {},
188
196
  mode: "assign"
189
197
  });
198
+ } else if (from === "delete") {
199
+ if (error.message === "record not found") {
200
+ if (__DEV__) {
201
+ console.log("Deleting non-existing data, just ignore.");
202
+ }
203
+ cancelRetry();
204
+ }
190
205
  } else if (error.type === "bad_request") {
191
- (_c = keelConfig.onError) == null ? void 0 : _c.call(keelConfig, error);
206
+ (_c = keelConfig.onError) == null ? void 0 : _c.call(keelConfig, { error, params, input, type: from, action: fn.name || fn.toString() });
192
207
  if (retryNum > 4) {
193
208
  cancelRetry();
194
209
  }
@@ -201,7 +216,7 @@ function syncedKeel(props) {
201
216
  const create = createParam ? async (input, params) => {
202
217
  const { data, error } = await createParam(convertObjectToCreate(input));
203
218
  if (error) {
204
- handleSetError(error, params, true);
219
+ await handleSetError(error, params, input, createParam, "create");
205
220
  }
206
221
  return data;
207
222
  } : void 0;
@@ -214,15 +229,15 @@ function syncedKeel(props) {
214
229
  if (!state.isEmpty(values)) {
215
230
  const { data, error } = await updateParam({ where: { id }, values });
216
231
  if (error) {
217
- handleSetError(error, params, false);
232
+ await handleSetError(error, params, input, updateParam, "update");
218
233
  }
219
234
  return data;
220
235
  }
221
236
  } : void 0;
222
- const deleteFn = deleteParam ? async ({ id }, params) => {
223
- const { data, error } = await deleteParam({ id });
237
+ const deleteFn = deleteParam ? async (value, params) => {
238
+ const { data, error } = await deleteParam({ id: value.id });
224
239
  if (error) {
225
- handleSetError(error, params, false);
240
+ await handleSetError(error, params, value, deleteParam, "delete");
226
241
  }
227
242
  return data;
228
243
  } : void 0;
@@ -238,13 +253,14 @@ function syncedKeel(props) {
238
253
  return crud.syncedCrud({
239
254
  ...rest,
240
255
  as: asType,
256
+ mode: mode || "merge",
241
257
  list,
242
258
  create,
243
259
  update,
244
260
  delete: deleteFn,
245
261
  waitFor: () => isEnabled$.get() && (waitFor ? state.computeSelector(waitFor) : true),
262
+ waitForSet: (params) => isEnabled$.get() && (waitForSet ? state.isFunction(waitForSet) ? waitForSet(params) : waitForSet : true),
246
263
  onSaved,
247
- onSavedUpdate: "createdUpdatedAt",
248
264
  fieldCreatedAt,
249
265
  fieldUpdatedAt,
250
266
  fieldDeleted: fieldDeleted || "deleted",