@legendapp/state 3.0.0-beta.3 → 3.0.0-beta.31

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 (74) hide show
  1. package/.DS_Store +0 -0
  2. package/config/enableReactComponents.js +3 -1
  3. package/config/enableReactComponents.mjs +3 -1
  4. package/config/enableReactTracking.d.mts +2 -1
  5. package/config/enableReactTracking.d.ts +2 -1
  6. package/config/enableReactTracking.js +32 -13
  7. package/config/enableReactTracking.mjs +32 -13
  8. package/index.d.mts +38 -5
  9. package/index.d.ts +38 -5
  10. package/index.js +202 -31
  11. package/index.mjs +202 -31
  12. package/package.json +35 -1
  13. package/persist-plugins/async-storage.js +17 -9
  14. package/persist-plugins/async-storage.mjs +17 -9
  15. package/persist-plugins/expo-sqlite.d.mts +19 -0
  16. package/persist-plugins/expo-sqlite.d.ts +19 -0
  17. package/persist-plugins/expo-sqlite.js +72 -0
  18. package/persist-plugins/expo-sqlite.mjs +69 -0
  19. package/react-native.d.mts +4 -0
  20. package/react-native.d.ts +4 -0
  21. package/react-native.js +53 -0
  22. package/react-native.mjs +40 -0
  23. package/react-reactive/Components.d.mts +19 -0
  24. package/react-reactive/Components.d.ts +19 -0
  25. package/react-reactive/Components.js +53 -0
  26. package/react-reactive/Components.mjs +40 -0
  27. package/react-reactive/enableReactComponents.d.mts +3 -2
  28. package/react-reactive/enableReactComponents.d.ts +3 -2
  29. package/react-reactive/enableReactComponents.js +10 -3
  30. package/react-reactive/enableReactComponents.mjs +10 -3
  31. package/react-reactive/enableReactNativeComponents.d.mts +3 -20
  32. package/react-reactive/enableReactNativeComponents.d.ts +3 -20
  33. package/react-reactive/enableReactNativeComponents.js +8 -3
  34. package/react-reactive/enableReactNativeComponents.mjs +8 -3
  35. package/react-reactive/enableReactive.js +10 -3
  36. package/react-reactive/enableReactive.mjs +10 -3
  37. package/react-reactive/enableReactive.native.js +8 -3
  38. package/react-reactive/enableReactive.native.mjs +8 -3
  39. package/react-reactive/enableReactive.web.js +8 -3
  40. package/react-reactive/enableReactive.web.mjs +8 -3
  41. package/react-web.d.mts +6 -0
  42. package/react-web.d.ts +6 -0
  43. package/react-web.js +39 -0
  44. package/react-web.mjs +37 -0
  45. package/react.d.mts +41 -21
  46. package/react.d.ts +41 -21
  47. package/react.js +86 -63
  48. package/react.mjs +87 -65
  49. package/sync-plugins/crud.d.mts +24 -9
  50. package/sync-plugins/crud.d.ts +24 -9
  51. package/sync-plugins/crud.js +250 -116
  52. package/sync-plugins/crud.mjs +251 -117
  53. package/sync-plugins/firebase.d.mts +7 -3
  54. package/sync-plugins/firebase.d.ts +7 -3
  55. package/sync-plugins/firebase.js +4 -2
  56. package/sync-plugins/firebase.mjs +4 -2
  57. package/sync-plugins/keel.d.mts +12 -13
  58. package/sync-plugins/keel.d.ts +12 -13
  59. package/sync-plugins/keel.js +60 -52
  60. package/sync-plugins/keel.mjs +61 -48
  61. package/sync-plugins/supabase.d.mts +7 -3
  62. package/sync-plugins/supabase.d.ts +7 -3
  63. package/sync-plugins/supabase.js +90 -33
  64. package/sync-plugins/supabase.mjs +91 -34
  65. package/sync-plugins/tanstack-query.d.mts +3 -3
  66. package/sync-plugins/tanstack-query.d.ts +3 -3
  67. package/sync.d.mts +16 -8
  68. package/sync.d.ts +16 -8
  69. package/sync.js +324 -215
  70. package/sync.mjs +323 -215
  71. package/trace.js +5 -6
  72. package/trace.mjs +5 -6
  73. package/types/reactive-native.d.ts +19 -0
  74. package/types/reactive-web.d.ts +7 -0
@@ -1,9 +1,9 @@
1
- import { isPromise, applyChanges, isNullOrUndefined, setAtPath, symbolDelete, isArray, internal, getNodeValue } from '@legendapp/state';
1
+ import { isPromise, isNullOrUndefined, applyChanges, setAtPath, symbolDelete, isArray, internal, getNodeValue } from '@legendapp/state';
2
2
  import { synced, deepEqual, internal as internal$1, diffObjects } from '@legendapp/state/sync';
3
3
 
4
4
  // src/sync-plugins/crud.ts
5
- var { clone } = internal;
6
- var { waitForSet } = internal$1;
5
+ var { clone, getKeys } = internal;
6
+ var { waitForSet, runWithRetry } = internal$1;
7
7
  function transformOut(data, transform) {
8
8
  return transform ? transform(clone(data)) : data;
9
9
  }
@@ -23,6 +23,47 @@ function computeLastSync(data, fieldUpdatedAt, fieldCreatedAt) {
23
23
  }
24
24
  return newLastSync;
25
25
  }
26
+ function arrayToRecord(arr, keyField) {
27
+ const record = {};
28
+ if (arr == null ? void 0 : arr.length) {
29
+ for (let i = 0; i < arr.length; i++) {
30
+ const v = arr[i];
31
+ const key = v[keyField];
32
+ record[key] = v;
33
+ }
34
+ }
35
+ return record;
36
+ }
37
+ function retrySet(params, retry, action, itemKey, itemValue, change, queuedRetries, itemValueFull, actionFn, saveResult) {
38
+ if (action === "delete") {
39
+ if (queuedRetries.create.has(itemKey)) {
40
+ queuedRetries.create.delete(itemKey);
41
+ }
42
+ if (queuedRetries.update.has(itemKey)) {
43
+ queuedRetries.update.delete(itemKey);
44
+ }
45
+ } else {
46
+ if (queuedRetries.delete.has(itemKey)) {
47
+ queuedRetries.delete.delete(itemKey);
48
+ }
49
+ }
50
+ const queuedRetry = queuedRetries[action].get(itemKey);
51
+ if (queuedRetry) {
52
+ itemValue = Object.assign(queuedRetry, itemValue);
53
+ }
54
+ queuedRetries[action].set(itemKey, itemValue);
55
+ const clonedValue = clone(itemValueFull);
56
+ const paramsWithChanges = { ...params, changes: [change] };
57
+ return runWithRetry(
58
+ paramsWithChanges,
59
+ retry,
60
+ "create_" + itemKey,
61
+ () => actionFn(itemValue, paramsWithChanges).then((result) => {
62
+ queuedRetries[action].delete(itemKey);
63
+ return saveResult(itemKey, clonedValue, result, true, change);
64
+ })
65
+ );
66
+ }
26
67
  function syncedCrud(props) {
27
68
  const {
28
69
  get: getFn,
@@ -43,10 +84,16 @@ function syncedCrud(props) {
43
84
  changesSince,
44
85
  generateId,
45
86
  waitForSet: waitForSetParam,
87
+ retry,
46
88
  ...rest
47
89
  } = props;
48
90
  const fieldId = fieldIdProp || "id";
49
91
  const pendingCreates = /* @__PURE__ */ new Set();
92
+ const queuedRetries = {
93
+ create: /* @__PURE__ */ new Map(),
94
+ update: /* @__PURE__ */ new Map(),
95
+ delete: /* @__PURE__ */ new Map()
96
+ };
50
97
  let asType = props.as;
51
98
  if (!asType) {
52
99
  asType = getFn ? "value" : "object";
@@ -75,69 +122,76 @@ function syncedCrud(props) {
75
122
  return out;
76
123
  };
77
124
  const transformRows = (data) => {
78
- return Promise.all(
125
+ return data.length ? Promise.all(
79
126
  data.map(
80
127
  (value) => (
81
128
  // Skip transforming any children with symbolDelete or fieldDeleted because they'll get deleted by resultsToOutType
82
129
  value[symbolDelete] || fieldDeleted && value[fieldDeleted] || fieldDeletedList && value[fieldDeletedList] ? value : transform.load(value, "get")
83
130
  )
84
131
  )
85
- );
132
+ ) : [];
86
133
  };
87
134
  const get = getFn || listFn ? (getParams) => {
88
- const { updateLastSync, lastSync, value } = getParams;
89
- if (listFn) {
90
- const isLastSyncMode = changesSince === "last-sync";
91
- if (isLastSyncMode && lastSync) {
92
- getParams.mode = modeParam || (asType === "array" ? "append" : asType === "value" ? "set" : "assign");
93
- }
94
- const listPromise = listFn(getParams);
95
- const toOut = (transformed) => {
96
- var _a;
97
- if (asType === "value") {
98
- return transformed.length > 0 ? transformed[0] : (_a = (isLastSyncMode && lastSync || fieldDeleted) && value) != null ? _a : null;
99
- } else {
100
- return resultsToOutType(transformed);
135
+ return runWithRetry(getParams, retry, getFn || listFn, () => {
136
+ const { updateLastSync, lastSync, value } = getParams;
137
+ if (listFn) {
138
+ const isLastSyncMode = changesSince === "last-sync";
139
+ if (isLastSyncMode && lastSync) {
140
+ getParams.mode = modeParam || (asType === "array" ? "append" : asType === "value" ? "set" : "assign");
101
141
  }
102
- };
103
- const processResults = (data) => {
104
- data || (data = []);
105
- if (fieldUpdatedAt) {
106
- const newLastSync = computeLastSync(data, fieldUpdatedAt, fieldCreatedAt);
107
- if (newLastSync && newLastSync !== lastSync) {
108
- updateLastSync(newLastSync);
142
+ const listPromise = listFn(getParams);
143
+ const toOut = (transformed) => {
144
+ if (asType === "value") {
145
+ if (transformed.length > 0) {
146
+ return transformed[0];
147
+ } else {
148
+ return value ? void 0 : null;
149
+ }
150
+ } else {
151
+ return resultsToOutType(transformed);
109
152
  }
110
- }
111
- let transformed = data;
112
- if (transform == null ? void 0 : transform.load) {
113
- transformed = transformRows(data);
114
- }
115
- return isPromise(transformed) ? transformed.then(toOut) : toOut(transformed);
116
- };
117
- return isPromise(listPromise) ? listPromise.then(processResults) : processResults(listPromise);
118
- } else if (getFn) {
119
- const dataPromise = getFn(getParams);
120
- const processData = (data) => {
121
- let transformed = data;
122
- if (data) {
123
- const newLastSync = data[fieldUpdatedAt] || data[fieldCreatedAt];
124
- if (newLastSync && newLastSync !== lastSync) {
125
- updateLastSync(newLastSync);
153
+ };
154
+ const processResults = (data) => {
155
+ data || (data = []);
156
+ if (fieldUpdatedAt) {
157
+ const newLastSync = computeLastSync(data, fieldUpdatedAt, fieldCreatedAt);
158
+ if (newLastSync && newLastSync !== lastSync) {
159
+ updateLastSync(newLastSync);
160
+ }
126
161
  }
162
+ let transformed = data;
127
163
  if (transform == null ? void 0 : transform.load) {
128
- transformed = transform.load(data, "get");
164
+ transformed = transformRows(data);
129
165
  }
130
- }
131
- return transformed;
132
- };
133
- return isPromise(dataPromise) ? dataPromise.then(processData) : processData(dataPromise);
134
- }
166
+ return isPromise(transformed) ? transformed.then(toOut) : toOut(transformed);
167
+ };
168
+ return isPromise(listPromise) ? listPromise.then(processResults) : processResults(listPromise);
169
+ } else if (getFn) {
170
+ const dataPromise = getFn(getParams);
171
+ const processData = (data) => {
172
+ let transformed = data;
173
+ if (data) {
174
+ const newLastSync = data[fieldUpdatedAt] || data[fieldCreatedAt];
175
+ if (newLastSync && newLastSync !== lastSync) {
176
+ updateLastSync(newLastSync);
177
+ }
178
+ if (transform == null ? void 0 : transform.load) {
179
+ transformed = transform.load(data, "get");
180
+ }
181
+ }
182
+ return transformed;
183
+ };
184
+ return isPromise(dataPromise) ? dataPromise.then(processData) : processData(dataPromise);
185
+ }
186
+ });
135
187
  } : void 0;
136
188
  const set = createFn || updateFn || deleteFn ? async (params) => {
137
189
  const { value, changes, update, retryAsCreate, node } = params;
138
190
  const creates = /* @__PURE__ */ new Map();
139
191
  const updates = /* @__PURE__ */ new Map();
192
+ const updateFullValues = /* @__PURE__ */ new Map();
140
193
  const deletes = /* @__PURE__ */ new Set();
194
+ const changesById = /* @__PURE__ */ new Map();
141
195
  const getUpdateValue = (itemValue, prev) => {
142
196
  return updatePartial ? Object.assign(
143
197
  diffObjects(
@@ -146,19 +200,21 @@ function syncedCrud(props) {
146
200
  /*deep*/
147
201
  true
148
202
  ),
149
- itemValue[fieldId] ? { [fieldId]: itemValue[fieldId] } : {}
203
+ !isNullOrUndefined(itemValue[fieldId]) ? { [fieldId]: itemValue[fieldId] } : {}
150
204
  ) : itemValue;
151
205
  };
152
206
  changes.forEach((change) => {
207
+ var _a, _b;
153
208
  const { path, prevAtPath, valueAtPath, pathTypes } = change;
154
209
  if (asType === "value") {
155
210
  if (value) {
156
211
  let id = value == null ? void 0 : value[fieldId];
157
212
  let isCreate = fieldCreatedAt ? !value[fieldCreatedAt] : !prevAtPath;
158
- if (!id && generateId) {
213
+ if (isNullOrUndefined(id) && generateId) {
159
214
  id = ensureId(value, fieldId, generateId);
160
215
  }
161
- if (id) {
216
+ if (!isNullOrUndefined(id)) {
217
+ changesById.set(id, change);
162
218
  if (pendingCreates.has(id)) {
163
219
  isCreate = false;
164
220
  }
@@ -171,6 +227,7 @@ function syncedCrud(props) {
171
227
  } else if (path.length === 0) {
172
228
  if (valueAtPath) {
173
229
  updates.set(id, getUpdateValue(valueAtPath, prevAtPath));
230
+ updateFullValues.set(id, valueAtPath);
174
231
  } else if (prevAtPath) {
175
232
  deletes.add(prevAtPath);
176
233
  }
@@ -182,27 +239,42 @@ function syncedCrud(props) {
182
239
  true
183
240
  );
184
241
  updates.set(id, getUpdateValue(value, previous));
242
+ updateFullValues.set(id, value);
185
243
  }
186
244
  } else {
187
245
  console.error("[legend-state]: added synced item without an id");
188
246
  }
189
247
  } else if (path.length === 0) {
190
248
  deletes.add(prevAtPath);
249
+ changesById.set(prevAtPath[fieldId], change);
191
250
  }
192
251
  } else {
193
252
  let itemsChanged = [];
194
253
  if (path.length === 0) {
195
- const changed = asMap ? Array.from(valueAtPath.entries()) : Object.entries(valueAtPath);
196
- for (let i = 0; i < changed.length; i++) {
197
- const [key, value2] = changed[i];
198
- const prev = asMap ? prevAtPath.get(key) : prevAtPath[key];
254
+ const valueAsObject = asArray ? arrayToRecord(valueAtPath, fieldId) : valueAtPath;
255
+ const prevAsObject = asArray ? arrayToRecord(prevAtPath, fieldId) : prevAtPath;
256
+ const keys = getKeys(valueAsObject, false, asMap, false);
257
+ const keysPrev = getKeys(prevAsObject, false, asMap, false);
258
+ const keysSet = new Set(keys);
259
+ const length = ((_a = keys || valueAsObject) == null ? void 0 : _a.length) || 0;
260
+ const lengthPrev = ((_b = keysPrev || prevAsObject) == null ? void 0 : _b.length) || 0;
261
+ for (let i = 0; i < lengthPrev; i++) {
262
+ const key = keysPrev[i];
263
+ if (!keysSet.has(key)) {
264
+ deletes.add(prevAsObject[key]);
265
+ }
266
+ }
267
+ for (let i = 0; i < length; i++) {
268
+ const key = keys[i];
269
+ const value2 = asMap ? valueAsObject.get(key) : valueAsObject[key];
270
+ const prev = prevAsObject ? asMap ? prevAsObject.get(key) : prevAsObject[key] : void 0;
199
271
  if (isNullOrUndefined(value2) && !isNullOrUndefined(prev)) {
200
272
  deletes.add(prev);
201
273
  return false;
202
274
  } else {
203
- const isDiff = !prevAtPath || !deepEqual(value2, prev);
275
+ const isDiff = !prevAsObject || !deepEqual(value2, prev);
204
276
  if (isDiff) {
205
- itemsChanged.push([getUpdateValue(value2, prev), prev]);
277
+ itemsChanged.push([getUpdateValue(value2, prev), prev, value2]);
206
278
  }
207
279
  }
208
280
  }
@@ -212,6 +284,7 @@ function syncedCrud(props) {
212
284
  if (!itemValue) {
213
285
  if (path.length === 1 && prevAtPath) {
214
286
  deletes.add(prevAtPath);
287
+ changesById.set(prevAtPath[fieldId], change);
215
288
  }
216
289
  } else {
217
290
  const previous = setAtPath(
@@ -220,10 +293,10 @@ function syncedCrud(props) {
220
293
  pathTypes.slice(1),
221
294
  prevAtPath
222
295
  );
223
- itemsChanged = [[getUpdateValue(itemValue, previous), previous]];
296
+ itemsChanged = [[getUpdateValue(itemValue, previous), previous, itemValue]];
224
297
  }
225
298
  }
226
- itemsChanged == null ? void 0 : itemsChanged.forEach(([item, prev]) => {
299
+ itemsChanged == null ? void 0 : itemsChanged.forEach(([item, prev, fullValue]) => {
227
300
  const isCreate = !pendingCreates.has(item[fieldId]) && (fieldCreatedAt ? !item[fieldCreatedAt] && !(prev == null ? void 0 : prev[fieldCreatedAt]) : fieldUpdatedAt ? !item[fieldUpdatedAt] && !(prev == null ? void 0 : prev[fieldCreatedAt]) : isNullOrUndefined(prev));
228
301
  if (isCreate) {
229
302
  if (generateId) {
@@ -233,16 +306,21 @@ function syncedCrud(props) {
233
306
  console.error("[legend-state]: added item without an id");
234
307
  }
235
308
  if (createFn) {
236
- pendingCreates.add(item[fieldId]);
237
- creates.set(item[fieldId], item);
309
+ const id = item[fieldId];
310
+ changesById.set(id, change);
311
+ pendingCreates.add(id);
312
+ creates.set(id, item);
238
313
  } else {
239
314
  console.warn("[legend-state] missing create function");
240
315
  }
241
316
  } else {
242
317
  if (updateFn) {
243
- updates.set(
244
- item[fieldId],
245
- updates.has(item[fieldId]) ? Object.assign(updates.get(item[fieldId]), item) : item
318
+ const id = item[fieldId];
319
+ changesById.set(id, change);
320
+ updates.set(id, updates.has(id) ? Object.assign(updates.get(id), item) : item);
321
+ updateFullValues.set(
322
+ id,
323
+ updateFullValues.has(id) ? Object.assign(updateFullValues.get(id), fullValue) : fullValue
246
324
  );
247
325
  } else {
248
326
  console.warn("[legend-state] missing update function");
@@ -251,93 +329,149 @@ function syncedCrud(props) {
251
329
  });
252
330
  }
253
331
  });
254
- const saveResult = async (itemKey, input, data, isCreate) => {
332
+ const saveResult = async (itemKey, input, data, isCreate, change) => {
255
333
  var _a;
256
334
  if (data) {
257
- const saved = (transform == null ? void 0 : transform.load) ? await transform.load(data, "set") : data;
335
+ let saved = (transform == null ? void 0 : transform.load) ? await transform.load(data, "set") : data;
258
336
  const isChild = itemKey !== "undefined" && asType !== "value";
259
337
  const currentPeeked = getNodeValue(node);
260
- const currentValue = isChild ? (_a = asType === "array" && isArray(currentPeeked) ? currentPeeked.find((v) => v[fieldId] === itemKey) : void 0) != null ? _a : currentPeeked[itemKey] : currentPeeked;
261
- const dataOnSaved = {
262
- saved,
263
- input,
264
- currentValue,
265
- isCreate,
266
- props
267
- };
268
- let savedOut = saved;
269
- if (savedOut && !isNullOrUndefined(currentValue)) {
270
- savedOut = clone(savedOut);
271
- Object.keys(savedOut).forEach((key) => {
338
+ const currentValue = isChild ? (_a = asType === "array" && isArray(currentPeeked) ? currentPeeked.find((v) => v[fieldId] === itemKey) : void 0) != null ? _a : asType === "Map" ? currentPeeked.get(itemKey) : currentPeeked[itemKey] : currentPeeked;
339
+ if (saved && !isNullOrUndefined(currentValue)) {
340
+ if (onSaved) {
341
+ const ret = onSaved({
342
+ saved,
343
+ input,
344
+ currentValue,
345
+ isCreate,
346
+ props
347
+ });
348
+ if (ret) {
349
+ saved = ret;
350
+ }
351
+ }
352
+ saved = clone(saved);
353
+ Object.keys(saved).forEach((key) => {
272
354
  const i = input[key];
273
355
  const c = currentValue[key];
274
356
  if (
275
357
  // value is already the new value, can ignore
276
- savedOut[key] === c || // user has changed local value
277
- key !== "id" && i !== c
358
+ saved[key] === c || // user has changed local value
359
+ key !== fieldId && i !== void 0 && i !== c
278
360
  ) {
279
- delete savedOut[key];
361
+ delete saved[key];
280
362
  }
281
363
  });
282
- if (onSaved) {
283
- const ret = onSaved(dataOnSaved);
284
- if (ret) {
285
- savedOut = ret;
364
+ let value2;
365
+ if (asType === "array") {
366
+ const index = currentPeeked.findIndex(
367
+ (cur) => cur[fieldId] === itemKey
368
+ );
369
+ if (index < 0) {
370
+ console.warn("[legend-state] Item saved that does not exist in array", saved);
371
+ } else {
372
+ value2 = { [index < 0 ? 0 : index]: saved };
286
373
  }
374
+ } else {
375
+ value2 = itemKey !== "undefined" && asType !== "value" ? { [itemKey]: saved } : saved;
376
+ }
377
+ if (value2 !== void 0) {
378
+ update({
379
+ value: value2,
380
+ mode: "merge",
381
+ changes: [change]
382
+ });
287
383
  }
288
- const createdAt = fieldCreatedAt ? savedOut[fieldCreatedAt] : void 0;
289
- const updatedAt = fieldUpdatedAt ? savedOut[fieldUpdatedAt] : void 0;
290
- const value2 = itemKey !== "undefined" && asType !== "value" ? { [itemKey]: savedOut } : savedOut;
291
- update({
292
- value: value2,
293
- lastSync: updatedAt || createdAt ? +new Date(updatedAt || createdAt) : void 0,
294
- mode: "merge"
295
- });
296
384
  }
297
385
  }
298
386
  };
299
387
  return Promise.all([
388
+ // Handle creates
300
389
  ...Array.from(creates).map(async ([itemKey, itemValue]) => {
301
390
  if (waitForSetParam) {
302
391
  await waitForSet(waitForSetParam, changes, itemValue, { type: "create" });
303
392
  }
304
393
  const createObj = await transformOut(itemValue, transform == null ? void 0 : transform.save);
305
- return createFn(createObj, params).then((result) => {
306
- return saveResult(itemKey, createObj, result, true);
307
- }).finally(() => {
394
+ return retrySet(
395
+ params,
396
+ retry,
397
+ "create",
398
+ itemKey,
399
+ createObj,
400
+ changesById.get(itemKey),
401
+ queuedRetries,
402
+ createObj,
403
+ createFn,
404
+ saveResult
405
+ ).then(() => {
308
406
  pendingCreates.delete(itemKey);
309
407
  });
310
408
  }),
409
+ // Handle updates
311
410
  ...Array.from(updates).map(async ([itemKey, itemValue]) => {
411
+ const fullValue = updateFullValues.get(itemKey);
312
412
  if (waitForSetParam) {
313
- await waitForSet(waitForSetParam, changes, itemValue, { type: "update" });
413
+ await waitForSet(waitForSetParam, changes, fullValue, { type: "update" });
314
414
  }
315
- const toSave = itemValue;
316
- const changed = await transformOut(toSave, transform == null ? void 0 : transform.save);
415
+ const changed = await transformOut(itemValue, transform == null ? void 0 : transform.save);
416
+ const fullValueTransformed = await transformOut(
417
+ fullValue,
418
+ transform == null ? void 0 : transform.save
419
+ );
317
420
  if (Object.keys(changed).length > 0) {
318
- return updateFn(changed, params).then(
319
- (result) => result && saveResult(itemKey, changed, result, false)
421
+ return retrySet(
422
+ params,
423
+ retry,
424
+ "update",
425
+ itemKey,
426
+ changed,
427
+ changesById.get(itemKey),
428
+ queuedRetries,
429
+ fullValueTransformed,
430
+ updateFn,
431
+ saveResult
320
432
  );
321
433
  }
322
434
  }),
323
- ...Array.from(deletes).map(async (valuePrevious) => {
324
- if (valuePrevious !== symbolDelete) {
325
- if (waitForSetParam) {
326
- await waitForSet(waitForSetParam, changes, valuePrevious, { type: "delete" });
327
- }
328
- if (deleteFn) {
329
- deleteFn(valuePrevious, params);
330
- } else if (fieldDeleted && updateFn) {
331
- const valueId = valuePrevious[fieldId];
332
- if (valueId) {
333
- updateFn({ ...{ [fieldId]: valueId }, [fieldDeleted]: true }, params);
334
- } else {
335
- console.error("[legend-state]: deleting item without an id");
336
- }
337
- } else {
338
- console.warn("[legend-state] missing delete function");
339
- }
435
+ // Handle deletes
436
+ ...Array.from(deletes).filter((val) => val !== symbolDelete).map(async (valuePrevious) => {
437
+ if (waitForSetParam) {
438
+ await waitForSet(waitForSetParam, changes, valuePrevious, { type: "delete" });
439
+ }
440
+ const itemKey = valuePrevious[fieldId];
441
+ if (!itemKey) {
442
+ console.error("[legend-state]: deleting item without an id");
443
+ return;
444
+ }
445
+ if (deleteFn) {
446
+ return retrySet(
447
+ params,
448
+ retry,
449
+ "delete",
450
+ itemKey,
451
+ valuePrevious,
452
+ changesById.get(itemKey),
453
+ queuedRetries,
454
+ valuePrevious,
455
+ deleteFn,
456
+ saveResult
457
+ );
458
+ }
459
+ if (fieldDeleted && updateFn) {
460
+ const value2 = { [fieldId]: itemKey, [fieldDeleted]: true };
461
+ return retrySet(
462
+ params,
463
+ retry,
464
+ "delete",
465
+ itemKey,
466
+ value2,
467
+ changesById.get(itemKey),
468
+ queuedRetries,
469
+ value2,
470
+ updateFn,
471
+ saveResult
472
+ );
340
473
  }
474
+ console.warn("[legend-state] missing delete function");
341
475
  })
342
476
  ]);
343
477
  } : void 0;
@@ -1,4 +1,4 @@
1
- import { FieldTransforms } from '@legendapp/state/sync';
1
+ import { FieldTransforms, SyncedErrorParams } from '@legendapp/state/sync';
2
2
  export { FieldTransforms } from '@legendapp/state/sync';
3
3
  import { CrudAsOption, SyncedCrudPropsMany, SyncedCrudPropsBase, SyncedCrudReturnType } from '@legendapp/state/sync-plugins/crud';
4
4
  import { DatabaseReference, Query } from 'firebase/database';
@@ -6,11 +6,12 @@ import { DatabaseReference, Query } from 'firebase/database';
6
6
  declare function transformObjectFields(dataIn: Record<string, any>, map: Record<string, any>): any;
7
7
  declare function invertFieldMap(obj: Record<string, any>): any;
8
8
 
9
- interface SyncedFirebaseProps<TRemote extends object, TLocal, TAs extends CrudAsOption = 'value'> extends Omit<SyncedCrudPropsMany<TRemote, TLocal, TAs>, 'list' | 'retry'>, SyncedCrudPropsBase<TRemote, TLocal> {
9
+ interface SyncedFirebaseProps<TRemote extends object, TLocal, TAs extends CrudAsOption = 'value'> extends Omit<SyncedCrudPropsMany<TRemote, TLocal, TAs>, 'list' | 'retry'>, Omit<SyncedCrudPropsBase<TRemote, TLocal>, 'onError'> {
10
10
  refPath: (uid: string | undefined) => string;
11
11
  query?: (ref: DatabaseReference) => DatabaseReference | Query;
12
12
  fieldId?: string;
13
13
  fieldTransforms?: FieldTransforms<TRemote>;
14
+ onError?: (error: Error, params: FirebaseErrorParams) => void;
14
15
  realtime?: boolean;
15
16
  requireAuth?: boolean;
16
17
  readonly?: boolean;
@@ -22,6 +23,9 @@ interface SyncedFirebaseConfiguration {
22
23
  enabled?: boolean;
23
24
  }
24
25
  declare function configureSyncedFirebase(config: SyncedFirebaseConfiguration): void;
26
+ interface FirebaseErrorParams extends Omit<SyncedErrorParams, 'source'> {
27
+ source: 'list' | 'get' | 'create' | 'update' | 'delete';
28
+ }
25
29
  declare function syncedFirebase<TRemote extends object, TLocal = TRemote, TAs extends CrudAsOption = 'object'>(props: SyncedFirebaseProps<TRemote, TLocal, TAs>): SyncedCrudReturnType<TLocal, TAs>;
26
30
 
27
- export { type SyncedFirebaseProps, configureSyncedFirebase, invertFieldMap, syncedFirebase, transformObjectFields };
31
+ export { type FirebaseErrorParams, type SyncedFirebaseProps, configureSyncedFirebase, invertFieldMap, syncedFirebase, transformObjectFields };
@@ -1,4 +1,4 @@
1
- import { FieldTransforms } from '@legendapp/state/sync';
1
+ import { FieldTransforms, SyncedErrorParams } from '@legendapp/state/sync';
2
2
  export { FieldTransforms } from '@legendapp/state/sync';
3
3
  import { CrudAsOption, SyncedCrudPropsMany, SyncedCrudPropsBase, SyncedCrudReturnType } from '@legendapp/state/sync-plugins/crud';
4
4
  import { DatabaseReference, Query } from 'firebase/database';
@@ -6,11 +6,12 @@ import { DatabaseReference, Query } from 'firebase/database';
6
6
  declare function transformObjectFields(dataIn: Record<string, any>, map: Record<string, any>): any;
7
7
  declare function invertFieldMap(obj: Record<string, any>): any;
8
8
 
9
- interface SyncedFirebaseProps<TRemote extends object, TLocal, TAs extends CrudAsOption = 'value'> extends Omit<SyncedCrudPropsMany<TRemote, TLocal, TAs>, 'list' | 'retry'>, SyncedCrudPropsBase<TRemote, TLocal> {
9
+ interface SyncedFirebaseProps<TRemote extends object, TLocal, TAs extends CrudAsOption = 'value'> extends Omit<SyncedCrudPropsMany<TRemote, TLocal, TAs>, 'list' | 'retry'>, Omit<SyncedCrudPropsBase<TRemote, TLocal>, 'onError'> {
10
10
  refPath: (uid: string | undefined) => string;
11
11
  query?: (ref: DatabaseReference) => DatabaseReference | Query;
12
12
  fieldId?: string;
13
13
  fieldTransforms?: FieldTransforms<TRemote>;
14
+ onError?: (error: Error, params: FirebaseErrorParams) => void;
14
15
  realtime?: boolean;
15
16
  requireAuth?: boolean;
16
17
  readonly?: boolean;
@@ -22,6 +23,9 @@ interface SyncedFirebaseConfiguration {
22
23
  enabled?: boolean;
23
24
  }
24
25
  declare function configureSyncedFirebase(config: SyncedFirebaseConfiguration): void;
26
+ interface FirebaseErrorParams extends Omit<SyncedErrorParams, 'source'> {
27
+ source: 'list' | 'get' | 'create' | 'update' | 'delete';
28
+ }
25
29
  declare function syncedFirebase<TRemote extends object, TLocal = TRemote, TAs extends CrudAsOption = 'object'>(props: SyncedFirebaseProps<TRemote, TLocal, TAs>): SyncedCrudReturnType<TLocal, TAs>;
26
30
 
27
- export { type SyncedFirebaseProps, configureSyncedFirebase, invertFieldMap, syncedFirebase, transformObjectFields };
31
+ export { type FirebaseErrorParams, type SyncedFirebaseProps, configureSyncedFirebase, invertFieldMap, syncedFirebase, transformObjectFields };
@@ -185,7 +185,8 @@ function syncedFirebase(props) {
185
185
  }
186
186
  return ref;
187
187
  };
188
- const list = async ({ lastSync, onError }) => {
188
+ const list = async (getParams) => {
189
+ const { lastSync, onError } = getParams;
189
190
  const ref = computeRef(lastSync);
190
191
  return new Promise((resolve) => {
191
192
  fns.once(
@@ -204,7 +205,7 @@ function syncedFirebase(props) {
204
205
  didList = true;
205
206
  resolve(values);
206
207
  },
207
- onError
208
+ (error) => onError(error, { source: "list", type: "get", retry: getParams })
208
209
  );
209
210
  });
210
211
  };
@@ -354,6 +355,7 @@ function syncedFirebase(props) {
354
355
  }
355
356
  return crud.syncedCrud({
356
357
  ...rest,
358
+ // Workaround for type errors
357
359
  list,
358
360
  subscribe,
359
361
  create,
@@ -183,7 +183,8 @@ function syncedFirebase(props) {
183
183
  }
184
184
  return ref;
185
185
  };
186
- const list = async ({ lastSync, onError }) => {
186
+ const list = async (getParams) => {
187
+ const { lastSync, onError } = getParams;
187
188
  const ref = computeRef(lastSync);
188
189
  return new Promise((resolve) => {
189
190
  fns.once(
@@ -202,7 +203,7 @@ function syncedFirebase(props) {
202
203
  didList = true;
203
204
  resolve(values);
204
205
  },
205
- onError
206
+ (error) => onError(error, { source: "list", type: "get", retry: getParams })
206
207
  );
207
208
  });
208
209
  };
@@ -352,6 +353,7 @@ function syncedFirebase(props) {
352
353
  }
353
354
  return syncedCrud({
354
355
  ...rest,
356
+ // Workaround for type errors
355
357
  list,
356
358
  subscribe,
357
359
  create,