@legendapp/state 3.0.0-alpha.3 → 3.0.0-alpha.30

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 (78) 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/config/enable$GetSet.js +2 -1
  7. package/config/enable$GetSet.mjs +2 -1
  8. package/config/enableReactTracking.js +2 -1
  9. package/config/enableReactTracking.mjs +2 -1
  10. package/config/enableReactUse.js +2 -1
  11. package/config/enableReactUse.mjs +2 -1
  12. package/config/enable_PeekAssign.js +2 -1
  13. package/config/enable_PeekAssign.mjs +2 -1
  14. package/config.d.mts +13 -0
  15. package/config.d.ts +13 -0
  16. package/config.js +2052 -0
  17. package/config.mjs +2050 -0
  18. package/helpers/trackHistory.js +2 -2
  19. package/helpers/trackHistory.mjs +2 -2
  20. package/index.d.mts +21 -302
  21. package/index.d.ts +21 -302
  22. package/index.js +274 -318
  23. package/index.mjs +275 -317
  24. package/observableInterfaces-Dilj6F92.d.mts +282 -0
  25. package/observableInterfaces-Dilj6F92.d.ts +282 -0
  26. package/package.json +11 -1
  27. package/persist-plugins/async-storage.d.mts +6 -3
  28. package/persist-plugins/async-storage.d.ts +6 -3
  29. package/persist-plugins/async-storage.js +12 -4
  30. package/persist-plugins/async-storage.mjs +12 -5
  31. package/persist-plugins/indexeddb.d.mts +6 -4
  32. package/persist-plugins/indexeddb.d.ts +6 -4
  33. package/persist-plugins/indexeddb.js +16 -6
  34. package/persist-plugins/indexeddb.mjs +16 -7
  35. package/persist-plugins/mmkv.d.mts +5 -1
  36. package/persist-plugins/mmkv.d.ts +5 -1
  37. package/persist-plugins/mmkv.js +14 -5
  38. package/persist-plugins/mmkv.mjs +14 -6
  39. package/react.d.mts +18 -14
  40. package/react.d.ts +18 -14
  41. package/react.js +57 -32
  42. package/react.mjs +58 -33
  43. package/sync-plugins/_transformObjectFields.d.mts +31 -0
  44. package/sync-plugins/_transformObjectFields.d.ts +31 -0
  45. package/sync-plugins/_transformObjectFields.js +114 -0
  46. package/sync-plugins/_transformObjectFields.mjs +110 -0
  47. package/sync-plugins/crud.d.mts +15 -23
  48. package/sync-plugins/crud.d.ts +15 -23
  49. package/sync-plugins/crud.js +213 -134
  50. package/sync-plugins/crud.mjs +214 -135
  51. package/sync-plugins/fetch.js +12 -8
  52. package/sync-plugins/fetch.mjs +13 -9
  53. package/sync-plugins/firebase.d.mts +26 -0
  54. package/sync-plugins/firebase.d.ts +26 -0
  55. package/sync-plugins/firebase.js +373 -0
  56. package/sync-plugins/firebase.mjs +368 -0
  57. package/sync-plugins/keel.d.mts +27 -10
  58. package/sync-plugins/keel.d.ts +27 -10
  59. package/sync-plugins/keel.js +40 -21
  60. package/sync-plugins/keel.mjs +40 -21
  61. package/sync-plugins/supabase.d.mts +12 -7
  62. package/sync-plugins/supabase.d.ts +12 -7
  63. package/sync-plugins/supabase.js +24 -13
  64. package/sync-plugins/supabase.mjs +25 -14
  65. package/sync-plugins/tanstack-query.d.mts +2 -2
  66. package/sync-plugins/tanstack-query.d.ts +2 -2
  67. package/sync-plugins/tanstack-query.js +3 -2
  68. package/sync-plugins/tanstack-query.mjs +3 -2
  69. package/sync-plugins/tanstack-react-query.d.mts +1 -1
  70. package/sync-plugins/tanstack-react-query.d.ts +1 -1
  71. package/sync.d.mts +68 -197
  72. package/sync.d.ts +68 -197
  73. package/sync.js +448 -283
  74. package/sync.mjs +454 -289
  75. package/types/babel.d.ts +12 -1
  76. package/.DS_Store +0 -0
  77. /package/config/{enable_GetSet.d.mts → enable$GetSet.d.mts} +0 -0
  78. /package/config/{enable_GetSet.d.ts → enable$GetSet.d.ts} +0 -0
@@ -0,0 +1,368 @@
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 (fieldId && !val[fieldId]) {
234
+ val[fieldId] = key;
235
+ }
236
+ if (saving$[key].get()) {
237
+ pendingIncoming$[key].set(val);
238
+ } else {
239
+ update2({
240
+ value: [val],
241
+ mode: "assign"
242
+ });
243
+ }
244
+ };
245
+ const onChildDelete = (snap) => {
246
+ if (!didList)
247
+ return;
248
+ const key = snap.key;
249
+ const val = snap.val();
250
+ if (fieldId && !val[fieldId]) {
251
+ val[fieldId] = key;
252
+ }
253
+ val[symbolDelete] = true;
254
+ update2({
255
+ value: [val],
256
+ mode: "assign"
257
+ });
258
+ };
259
+ unsubscribes = [
260
+ fns.onChildAdded(ref, onChildChange, onError),
261
+ fns.onChildChanged(ref, onChildChange, onError),
262
+ fns.onChildRemoved(ref, onChildDelete, onError)
263
+ ];
264
+ }
265
+ return () => {
266
+ unsubscribes.forEach((fn) => fn());
267
+ };
268
+ } : void 0;
269
+ const addUpdatedAt = (input) => {
270
+ if (fieldUpdatedAt) {
271
+ input[fieldUpdatedAt] = serverTimestamp();
272
+ }
273
+ };
274
+ const addCreatedAt = (input) => {
275
+ if (fieldCreatedAt && !input[fieldCreatedAt]) {
276
+ input[fieldCreatedAt] = serverTimestamp();
277
+ }
278
+ return addUpdatedAt(input);
279
+ };
280
+ const upsert = async (input) => {
281
+ const id = fieldId ? input[fieldId] : "";
282
+ if (saving$[id].get()) {
283
+ pendingOutgoing$[id].set(input);
284
+ } else {
285
+ saving$[id].set(true);
286
+ const path = joinPaths(refPath(fns.getCurrentUser()), fieldId ? id : "");
287
+ await fns.update(fns.ref(path), input);
288
+ saving$[id].set(false);
289
+ flushAfterSave();
290
+ }
291
+ return when(
292
+ () => !pendingOutgoing$[id].get(),
293
+ () => {
294
+ const value = pendingIncoming$[id].get();
295
+ if (value) {
296
+ pendingIncoming$[id].delete();
297
+ return value;
298
+ }
299
+ }
300
+ );
301
+ };
302
+ const flushAfterSave = () => {
303
+ const outgoing = pendingOutgoing$.get();
304
+ Object.values(outgoing).forEach((value) => {
305
+ upsert(value);
306
+ });
307
+ pendingOutgoing$.set({});
308
+ };
309
+ const create = readonly ? void 0 : (input) => {
310
+ addCreatedAt(input);
311
+ return upsert(input);
312
+ };
313
+ const update = readonly ? void 0 : (input) => {
314
+ addUpdatedAt(input);
315
+ return upsert(input);
316
+ };
317
+ const deleteFn = readonly ? void 0 : (input) => {
318
+ const path = joinPaths(
319
+ refPath(fns.getCurrentUser()),
320
+ fieldId && asType !== "value" ? input[fieldId] : ""
321
+ );
322
+ return fns.remove(fns.ref(path));
323
+ };
324
+ let isAuthedIfRequired$;
325
+ if (requireAuth) {
326
+ if (fns.isInitialized()) {
327
+ isAuthedIfRequired$ = observable(false);
328
+ fns.onAuthStateChanged((user) => {
329
+ isAuthedIfRequired$.set(!!user);
330
+ });
331
+ }
332
+ }
333
+ let transform = transformProp;
334
+ if (fieldTransforms) {
335
+ const inverted = invertFieldMap(fieldTransforms);
336
+ transform = {
337
+ load(value, method) {
338
+ const fieldTransformed = transformObjectFields(value, inverted);
339
+ return (transformProp == null ? void 0 : transformProp.load) ? transformProp.load(fieldTransformed, method) : fieldTransformed;
340
+ },
341
+ save(value) {
342
+ const transformed = (transformProp == null ? void 0 : transformProp.save) ? transformProp.save(value) : value;
343
+ if (isPromise(transformed)) {
344
+ return transformed.then((transformedValue) => {
345
+ return transformObjectFields(transformedValue, fieldTransforms);
346
+ });
347
+ } else {
348
+ return transformObjectFields(transformed, fieldTransforms);
349
+ }
350
+ }
351
+ };
352
+ }
353
+ return syncedCrud({
354
+ ...rest,
355
+ list,
356
+ subscribe,
357
+ create,
358
+ update,
359
+ delete: deleteFn,
360
+ waitFor: () => isEnabled$.get() && (isAuthedIfRequired$ ? isAuthedIfRequired$.get() : true) && (waitFor ? computeSelector(waitFor) : true),
361
+ waitForSet: (params) => isEnabled$.get() && (isAuthedIfRequired$ ? isAuthedIfRequired$.get() : true) && (waitForSet ? isFunction(waitForSet) ? waitForSet(params) : waitForSet : true),
362
+ generateId: fns.generateId,
363
+ transform,
364
+ as: asType
365
+ });
366
+ }
367
+
368
+ 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,14 +39,14 @@ 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'> {
46
46
  client: {
47
47
  auth: {
48
- refresh: () => Promise<boolean>;
49
- isAuthenticated: () => Promise<boolean>;
48
+ refresh: () => Promise<APIResult<boolean>>;
49
+ isAuthenticated: () => Promise<APIResult<boolean>>;
50
50
  };
51
51
  api: {
52
52
  queries: Record<string, (i: any) => Promise<any>>;
@@ -55,20 +55,33 @@ 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;
65
+ refreshAuth?: () => void | Promise<void>;
59
66
  }
60
- interface SyncedKeelPropsManyBase<TRemote, TLocal, AOption extends CrudAsOption> extends Omit<SyncedCrudPropsMany<TRemote, TLocal, AOption>, 'list'> {
67
+ interface SyncedKeelPropsManyBase<TRemote extends {
68
+ id: string;
69
+ }, TLocal, AOption extends CrudAsOption> extends Omit<SyncedCrudPropsMany<TRemote, TLocal, AOption>, 'list'> {
61
70
  first?: number;
62
71
  get?: never;
63
72
  }
64
- interface SyncedKeelPropsManyWhere<TRemote, TLocal, AOption extends CrudAsOption, Where extends Record<string, any>> extends SyncedKeelPropsManyBase<TRemote, TLocal, AOption> {
73
+ interface SyncedKeelPropsManyWhere<TRemote extends {
74
+ id: string;
75
+ }, TLocal, AOption extends CrudAsOption, Where extends Record<string, any>> extends SyncedKeelPropsManyBase<TRemote, TLocal, AOption> {
65
76
  list?: (params: KeelListParams<NoInfer<Where>>) => Promise<CrudResult<APIResult<{
66
77
  results: TRemote[];
67
78
  pageInfo: any;
68
79
  }>>>;
69
80
  where?: Where | (() => Where);
70
81
  }
71
- interface SyncedKeelPropsManyNoWhere<TRemote, TLocal, AOption extends CrudAsOption> extends SyncedKeelPropsManyBase<TRemote, TLocal, AOption> {
82
+ interface SyncedKeelPropsManyNoWhere<TRemote extends {
83
+ id: string;
84
+ }, TLocal, AOption extends CrudAsOption> extends SyncedKeelPropsManyBase<TRemote, TLocal, AOption> {
72
85
  list?: (params: KeelListParams<{}>) => Promise<CrudResult<APIResult<{
73
86
  results: TRemote[];
74
87
  pageInfo: any;
@@ -76,8 +89,12 @@ interface SyncedKeelPropsManyNoWhere<TRemote, TLocal, AOption extends CrudAsOpti
76
89
  where?: never | {};
77
90
  }
78
91
  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'> {
92
+ type SyncedKeelPropsMany<TRemote extends {
93
+ id: string;
94
+ }, TLocal, AOption extends CrudAsOption, Where extends Record<string, any>> = HasAnyKeys<Where> extends true ? SyncedKeelPropsManyWhere<TRemote, TLocal, AOption, Where> : SyncedKeelPropsManyNoWhere<TRemote, TLocal, AOption>;
95
+ interface SyncedKeelPropsSingle<TRemote extends {
96
+ id: string;
97
+ }, TLocal> extends Omit<SyncedCrudPropsSingle<TRemote, TLocal>, 'get'> {
81
98
  get?: (params: KeelGetParams) => Promise<APIResult<TRemote>>;
82
99
  first?: never;
83
100
  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,14 +39,14 @@ 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'> {
46
46
  client: {
47
47
  auth: {
48
- refresh: () => Promise<boolean>;
49
- isAuthenticated: () => Promise<boolean>;
48
+ refresh: () => Promise<APIResult<boolean>>;
49
+ isAuthenticated: () => Promise<APIResult<boolean>>;
50
50
  };
51
51
  api: {
52
52
  queries: Record<string, (i: any) => Promise<any>>;
@@ -55,20 +55,33 @@ 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;
65
+ refreshAuth?: () => void | Promise<void>;
59
66
  }
60
- interface SyncedKeelPropsManyBase<TRemote, TLocal, AOption extends CrudAsOption> extends Omit<SyncedCrudPropsMany<TRemote, TLocal, AOption>, 'list'> {
67
+ interface SyncedKeelPropsManyBase<TRemote extends {
68
+ id: string;
69
+ }, TLocal, AOption extends CrudAsOption> extends Omit<SyncedCrudPropsMany<TRemote, TLocal, AOption>, 'list'> {
61
70
  first?: number;
62
71
  get?: never;
63
72
  }
64
- interface SyncedKeelPropsManyWhere<TRemote, TLocal, AOption extends CrudAsOption, Where extends Record<string, any>> extends SyncedKeelPropsManyBase<TRemote, TLocal, AOption> {
73
+ interface SyncedKeelPropsManyWhere<TRemote extends {
74
+ id: string;
75
+ }, TLocal, AOption extends CrudAsOption, Where extends Record<string, any>> extends SyncedKeelPropsManyBase<TRemote, TLocal, AOption> {
65
76
  list?: (params: KeelListParams<NoInfer<Where>>) => Promise<CrudResult<APIResult<{
66
77
  results: TRemote[];
67
78
  pageInfo: any;
68
79
  }>>>;
69
80
  where?: Where | (() => Where);
70
81
  }
71
- interface SyncedKeelPropsManyNoWhere<TRemote, TLocal, AOption extends CrudAsOption> extends SyncedKeelPropsManyBase<TRemote, TLocal, AOption> {
82
+ interface SyncedKeelPropsManyNoWhere<TRemote extends {
83
+ id: string;
84
+ }, TLocal, AOption extends CrudAsOption> extends SyncedKeelPropsManyBase<TRemote, TLocal, AOption> {
72
85
  list?: (params: KeelListParams<{}>) => Promise<CrudResult<APIResult<{
73
86
  results: TRemote[];
74
87
  pageInfo: any;
@@ -76,8 +89,12 @@ interface SyncedKeelPropsManyNoWhere<TRemote, TLocal, AOption extends CrudAsOpti
76
89
  where?: never | {};
77
90
  }
78
91
  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'> {
92
+ type SyncedKeelPropsMany<TRemote extends {
93
+ id: string;
94
+ }, TLocal, AOption extends CrudAsOption, Where extends Record<string, any>> = HasAnyKeys<Where> extends true ? SyncedKeelPropsManyWhere<TRemote, TLocal, AOption, Where> : SyncedKeelPropsManyNoWhere<TRemote, TLocal, AOption>;
95
+ interface SyncedKeelPropsSingle<TRemote extends {
96
+ id: string;
97
+ }, TLocal> extends Omit<SyncedCrudPropsSingle<TRemote, TLocal>, 'get'> {
81
98
  get?: (params: KeelGetParams) => Promise<APIResult<TRemote>>;
82
99
  first?: never;
83
100
  where?: never;
@@ -20,6 +20,9 @@ var modifiedClients = /* @__PURE__ */ new WeakSet();
20
20
  var isEnabled$ = state.observable(true);
21
21
  async function ensureAuthToken() {
22
22
  await state.when(isEnabled$.get());
23
+ if (keelConfig.refreshAuth) {
24
+ await keelConfig.refreshAuth();
25
+ }
23
26
  let isAuthed = await keelConfig.client.auth.isAuthenticated();
24
27
  if (!isAuthed) {
25
28
  isAuthed = await keelConfig.client.auth.refresh();
@@ -65,9 +68,9 @@ function configureSyncedKeel(config) {
65
68
  const oldFn = queries[key];
66
69
  queries[key] = (i) => {
67
70
  const realtimeKey = [key, ...Object.values(i.where || {})].filter((value) => value && typeof value !== "object").join("/");
68
- const subscribe = ({ refresh }) => {
71
+ const subscribe = (params) => {
69
72
  if (realtimeKey) {
70
- return realtimePlugin.subscribe(realtimeKey, refresh);
73
+ return realtimePlugin.subscribe(realtimeKey, params);
71
74
  }
72
75
  };
73
76
  return oldFn(i).then((ret) => {
@@ -128,7 +131,9 @@ function syncedKeel(props) {
128
131
  first,
129
132
  where: whereParam,
130
133
  waitFor,
134
+ waitForSet,
131
135
  fieldDeleted,
136
+ mode,
132
137
  ...rest
133
138
  } = props;
134
139
  const { changesSince } = props;
@@ -137,8 +142,15 @@ function syncedKeel(props) {
137
142
  const subscribeFnKey$ = state.observable("");
138
143
  const fieldCreatedAt = "createdAt";
139
144
  const fieldUpdatedAt = "updatedAt";
145
+ const setupSubscribe = (doSubscribe, subscribeKey, lastSync) => {
146
+ subscribeFn = doSubscribe;
147
+ subscribeFnKey$.set(subscribeKey);
148
+ if (realtimePlugin && lastSync) {
149
+ realtimePlugin.setLatestChange(subscribeKey, new Date(lastSync));
150
+ }
151
+ };
140
152
  const list = listParam ? async (listParams) => {
141
- const { lastSync, refresh } = listParams;
153
+ const { lastSync } = listParams;
142
154
  const queryBySync = !!lastSync && changesSince === "last-sync";
143
155
  const where = Object.assign(
144
156
  queryBySync ? { updatedAt: { after: new Date(lastSync + 1) } } : {},
@@ -147,8 +159,7 @@ function syncedKeel(props) {
147
159
  const params = { where, first };
148
160
  const { results, subscribe: subscribe2, subscribeKey } = await getAllPages(listParam, params);
149
161
  if (subscribe2) {
150
- subscribeFn = () => subscribe2({ refresh });
151
- subscribeFnKey$.set(subscribeKey);
162
+ setupSubscribe(() => subscribe2(listParams), subscribeKey, lastSync);
152
163
  }
153
164
  return results;
154
165
  } : void 0;
@@ -156,8 +167,7 @@ function syncedKeel(props) {
156
167
  const { refresh } = getParams;
157
168
  const { data, error, subscribe: subscribe2, subscribeKey } = await getParam({ refresh });
158
169
  if (subscribe2) {
159
- subscribeFn = () => subscribe2({ refresh });
160
- subscribeFnKey$.set(subscribeKey);
170
+ setupSubscribe(() => subscribe2(getParams), subscribeKey);
161
171
  }
162
172
  if (error) {
163
173
  throw new Error(error.message);
@@ -176,32 +186,40 @@ function syncedKeel(props) {
176
186
  }
177
187
  }
178
188
  };
179
- const handleSetError = async (error, params, isCreate) => {
189
+ const handleSetError = async (error, params, input, fn, from) => {
180
190
  var _a, _b, _c;
181
- 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"))) {
191
+ const { retryNum, update: update2 } = params;
192
+ 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
193
  if (__DEV__) {
184
194
  console.log("Creating duplicate data already saved, just ignore.");
185
195
  }
196
+ params.cancelRetry = true;
186
197
  update2({
187
198
  value: {},
188
199
  mode: "assign"
189
200
  });
201
+ } else if (from === "delete") {
202
+ if (error.message === "record not found") {
203
+ if (__DEV__) {
204
+ console.log("Deleting non-existing data, just ignore.");
205
+ }
206
+ params.cancelRetry = true;
207
+ }
190
208
  } else if (error.type === "bad_request") {
191
- (_c = keelConfig.onError) == null ? void 0 : _c.call(keelConfig, error);
209
+ (_c = keelConfig.onError) == null ? void 0 : _c.call(keelConfig, { error, params, input, type: from, action: fn.name || fn.toString() });
192
210
  if (retryNum > 4) {
193
- cancelRetry();
211
+ params.cancelRetry = true;
194
212
  }
195
- throw new Error(error.message);
213
+ throw new Error(error.message, { cause: { input } });
196
214
  } else {
197
215
  await handleApiError(error);
198
- throw new Error(error.message);
216
+ throw new Error(error.message, { cause: { input } });
199
217
  }
200
218
  };
201
219
  const create = createParam ? async (input, params) => {
202
220
  const { data, error } = await createParam(convertObjectToCreate(input));
203
221
  if (error) {
204
- handleSetError(error, params, true);
222
+ await handleSetError(error, params, input, createParam, "create");
205
223
  }
206
224
  return data;
207
225
  } : void 0;
@@ -214,15 +232,15 @@ function syncedKeel(props) {
214
232
  if (!state.isEmpty(values)) {
215
233
  const { data, error } = await updateParam({ where: { id }, values });
216
234
  if (error) {
217
- handleSetError(error, params, false);
235
+ await handleSetError(error, params, input, updateParam, "update");
218
236
  }
219
237
  return data;
220
238
  }
221
239
  } : void 0;
222
- const deleteFn = deleteParam ? async ({ id }, params) => {
223
- const { data, error } = await deleteParam({ id });
240
+ const deleteFn = deleteParam ? async (value, params) => {
241
+ const { data, error } = await deleteParam({ id: value.id });
224
242
  if (error) {
225
- handleSetError(error, params, false);
243
+ await handleSetError(error, params, value, deleteParam, "delete");
226
244
  }
227
245
  return data;
228
246
  } : void 0;
@@ -238,16 +256,17 @@ function syncedKeel(props) {
238
256
  return crud.syncedCrud({
239
257
  ...rest,
240
258
  as: asType,
259
+ mode: mode || "merge",
241
260
  list,
242
261
  create,
243
262
  update,
244
263
  delete: deleteFn,
245
264
  waitFor: () => isEnabled$.get() && (waitFor ? state.computeSelector(waitFor) : true),
265
+ waitForSet: (params) => isEnabled$.get() && (waitForSet ? state.isFunction(waitForSet) ? waitForSet(params) : waitForSet : true),
246
266
  onSaved,
247
- onSavedUpdate: "createdUpdatedAt",
248
267
  fieldCreatedAt,
249
268
  fieldUpdatedAt,
250
- fieldDeleted: fieldDeleted || "deleted",
269
+ fieldDeleted,
251
270
  changesSince,
252
271
  updatePartial: true,
253
272
  subscribe,