@legendapp/state 3.0.0-beta.15 → 3.0.0-beta.17

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/index.d.mts CHANGED
@@ -248,6 +248,7 @@ interface UpdateFnParams<T = any> {
248
248
  value: T;
249
249
  mode?: GetMode;
250
250
  lastSync?: number | undefined;
251
+ changes?: Change[];
251
252
  }
252
253
  interface UpdateSetFnParams<T = any> extends UpdateFnParams<T> {
253
254
  lastSync?: never;
package/index.d.ts CHANGED
@@ -248,6 +248,7 @@ interface UpdateFnParams<T = any> {
248
248
  value: T;
249
249
  mode?: GetMode;
250
250
  lastSync?: number | undefined;
251
+ changes?: Change[];
251
252
  }
252
253
  interface UpdateSetFnParams<T = any> extends UpdateFnParams<T> {
253
254
  lastSync?: never;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@legendapp/state",
3
- "version": "3.0.0-beta.15",
3
+ "version": "3.0.0-beta.17",
4
4
  "description": "legend-state",
5
5
  "sideEffects": false,
6
6
  "private": false,
@@ -25,12 +25,7 @@ function computeLastSync(data, fieldUpdatedAt, fieldCreatedAt) {
25
25
  }
26
26
  return newLastSync;
27
27
  }
28
- var queuedRetries = {
29
- create: /* @__PURE__ */ new Map(),
30
- update: /* @__PURE__ */ new Map(),
31
- delete: /* @__PURE__ */ new Map()
32
- };
33
- function retrySet(params, retry, action, itemKey, itemValue, actionFn, saveResult) {
28
+ function retrySet(params, retry, action, itemKey, itemValue, change, queuedRetries, actionFn, saveResult) {
34
29
  if (action === "delete") {
35
30
  if (queuedRetries.create.has(itemKey)) {
36
31
  queuedRetries.create.delete(itemKey);
@@ -48,13 +43,14 @@ function retrySet(params, retry, action, itemKey, itemValue, actionFn, saveResul
48
43
  itemValue = Object.assign(queuedRetry, itemValue);
49
44
  }
50
45
  queuedRetries[action].set(itemKey, itemValue);
46
+ const paramsWithChanges = { ...params, changes: [change] };
51
47
  return runWithRetry(
52
- params,
48
+ paramsWithChanges,
53
49
  retry,
54
50
  "create_" + itemKey,
55
- () => actionFn(itemValue, params).then((result) => {
51
+ () => actionFn(itemValue, paramsWithChanges).then((result) => {
56
52
  queuedRetries[action].delete(itemKey);
57
- return saveResult(itemKey, itemValue, result, true);
53
+ return saveResult(itemKey, itemValue, result, true, change);
58
54
  })
59
55
  );
60
56
  }
@@ -83,6 +79,11 @@ function syncedCrud(props) {
83
79
  } = props;
84
80
  const fieldId = fieldIdProp || "id";
85
81
  const pendingCreates = /* @__PURE__ */ new Set();
82
+ const queuedRetries = {
83
+ create: /* @__PURE__ */ new Map(),
84
+ update: /* @__PURE__ */ new Map(),
85
+ delete: /* @__PURE__ */ new Map()
86
+ };
86
87
  let asType = props.as;
87
88
  if (!asType) {
88
89
  asType = getFn ? "value" : "object";
@@ -179,6 +180,7 @@ function syncedCrud(props) {
179
180
  const creates = /* @__PURE__ */ new Map();
180
181
  const updates = /* @__PURE__ */ new Map();
181
182
  const deletes = /* @__PURE__ */ new Set();
183
+ const changesById = /* @__PURE__ */ new Map();
182
184
  const getUpdateValue = (itemValue, prev) => {
183
185
  return updatePartial ? Object.assign(
184
186
  sync.diffObjects(
@@ -200,6 +202,7 @@ function syncedCrud(props) {
200
202
  id = ensureId(value, fieldId, generateId);
201
203
  }
202
204
  if (id) {
205
+ changesById.set(id, change);
203
206
  if (pendingCreates.has(id)) {
204
207
  isCreate = false;
205
208
  }
@@ -229,6 +232,7 @@ function syncedCrud(props) {
229
232
  }
230
233
  } else if (path.length === 0) {
231
234
  deletes.add(prevAtPath);
235
+ changesById.set(prevAtPath[fieldId], change);
232
236
  }
233
237
  } else {
234
238
  let itemsChanged = [];
@@ -253,6 +257,7 @@ function syncedCrud(props) {
253
257
  if (!itemValue) {
254
258
  if (path.length === 1 && prevAtPath) {
255
259
  deletes.add(prevAtPath);
260
+ changesById.set(prevAtPath[fieldId], change);
256
261
  }
257
262
  } else {
258
263
  const previous = state.setAtPath(
@@ -274,17 +279,18 @@ function syncedCrud(props) {
274
279
  console.error("[legend-state]: added item without an id");
275
280
  }
276
281
  if (createFn) {
277
- pendingCreates.add(item[fieldId]);
278
- creates.set(item[fieldId], item);
282
+ const id = item[fieldId];
283
+ changesById.set(id, change);
284
+ pendingCreates.add(id);
285
+ creates.set(id, item);
279
286
  } else {
280
287
  console.warn("[legend-state] missing create function");
281
288
  }
282
289
  } else {
283
290
  if (updateFn) {
284
- updates.set(
285
- item[fieldId],
286
- updates.has(item[fieldId]) ? Object.assign(updates.get(item[fieldId]), item) : item
287
- );
291
+ const id = item[fieldId];
292
+ changesById.set(id, change);
293
+ updates.set(id, updates.has(id) ? Object.assign(updates.get(id), item) : item);
288
294
  } else {
289
295
  console.warn("[legend-state] missing update function");
290
296
  }
@@ -292,7 +298,7 @@ function syncedCrud(props) {
292
298
  });
293
299
  }
294
300
  });
295
- const saveResult = async (itemKey, input, data, isCreate) => {
301
+ const saveResult = async (itemKey, input, data, isCreate, change) => {
296
302
  var _a;
297
303
  if (data) {
298
304
  let saved = (transform == null ? void 0 : transform.load) ? await transform.load(data, "set") : data;
@@ -319,7 +325,7 @@ function syncedCrud(props) {
319
325
  if (
320
326
  // value is already the new value, can ignore
321
327
  saved[key] === c || // user has changed local value
322
- key !== fieldId && i !== c
328
+ key !== fieldId && i !== void 0 && i !== c
323
329
  ) {
324
330
  delete saved[key];
325
331
  }
@@ -340,7 +346,8 @@ function syncedCrud(props) {
340
346
  if (value2 !== void 0) {
341
347
  update({
342
348
  value: value2,
343
- mode: "merge"
349
+ mode: "merge",
350
+ changes: [change]
344
351
  });
345
352
  }
346
353
  }
@@ -353,11 +360,19 @@ function syncedCrud(props) {
353
360
  await waitForSet(waitForSetParam, changes, itemValue, { type: "create" });
354
361
  }
355
362
  const createObj = await transformOut(itemValue, transform == null ? void 0 : transform.save);
356
- return retrySet(params, retry, "create", itemKey, createObj, createFn, saveResult).then(
357
- () => {
358
- pendingCreates.delete(itemKey);
359
- }
360
- );
363
+ return retrySet(
364
+ params,
365
+ retry,
366
+ "create",
367
+ itemKey,
368
+ createObj,
369
+ changesById.get(itemKey),
370
+ queuedRetries,
371
+ createFn,
372
+ saveResult
373
+ ).then(() => {
374
+ pendingCreates.delete(itemKey);
375
+ });
361
376
  }),
362
377
  // Handle updates
363
378
  ...Array.from(updates).map(async ([itemKey, itemValue]) => {
@@ -366,7 +381,17 @@ function syncedCrud(props) {
366
381
  }
367
382
  const changed = await transformOut(itemValue, transform == null ? void 0 : transform.save);
368
383
  if (Object.keys(changed).length > 0) {
369
- return retrySet(params, retry, "update", itemKey, changed, updateFn, saveResult);
384
+ return retrySet(
385
+ params,
386
+ retry,
387
+ "update",
388
+ itemKey,
389
+ changed,
390
+ changesById.get(itemKey),
391
+ queuedRetries,
392
+ updateFn,
393
+ saveResult
394
+ );
370
395
  }
371
396
  }),
372
397
  // Handle deletes
@@ -374,8 +399,8 @@ function syncedCrud(props) {
374
399
  if (waitForSetParam) {
375
400
  await waitForSet(waitForSetParam, changes, valuePrevious, { type: "delete" });
376
401
  }
377
- const valueId = valuePrevious[fieldId];
378
- if (!valueId) {
402
+ const itemKey = valuePrevious[fieldId];
403
+ if (!itemKey) {
379
404
  console.error("[legend-state]: deleting item without an id");
380
405
  return;
381
406
  }
@@ -384,8 +409,10 @@ function syncedCrud(props) {
384
409
  params,
385
410
  retry,
386
411
  "delete",
387
- valueId,
412
+ itemKey,
388
413
  valuePrevious,
414
+ changesById.get(itemKey),
415
+ queuedRetries,
389
416
  deleteFn,
390
417
  saveResult
391
418
  );
@@ -395,8 +422,10 @@ function syncedCrud(props) {
395
422
  params,
396
423
  retry,
397
424
  "delete",
398
- valueId,
399
- { [fieldId]: valueId, [fieldDeleted]: true },
425
+ itemKey,
426
+ { [fieldId]: itemKey, [fieldDeleted]: true },
427
+ changesById.get(itemKey),
428
+ queuedRetries,
400
429
  updateFn,
401
430
  saveResult
402
431
  );
@@ -23,12 +23,7 @@ function computeLastSync(data, fieldUpdatedAt, fieldCreatedAt) {
23
23
  }
24
24
  return newLastSync;
25
25
  }
26
- var queuedRetries = {
27
- create: /* @__PURE__ */ new Map(),
28
- update: /* @__PURE__ */ new Map(),
29
- delete: /* @__PURE__ */ new Map()
30
- };
31
- function retrySet(params, retry, action, itemKey, itemValue, actionFn, saveResult) {
26
+ function retrySet(params, retry, action, itemKey, itemValue, change, queuedRetries, actionFn, saveResult) {
32
27
  if (action === "delete") {
33
28
  if (queuedRetries.create.has(itemKey)) {
34
29
  queuedRetries.create.delete(itemKey);
@@ -46,13 +41,14 @@ function retrySet(params, retry, action, itemKey, itemValue, actionFn, saveResul
46
41
  itemValue = Object.assign(queuedRetry, itemValue);
47
42
  }
48
43
  queuedRetries[action].set(itemKey, itemValue);
44
+ const paramsWithChanges = { ...params, changes: [change] };
49
45
  return runWithRetry(
50
- params,
46
+ paramsWithChanges,
51
47
  retry,
52
48
  "create_" + itemKey,
53
- () => actionFn(itemValue, params).then((result) => {
49
+ () => actionFn(itemValue, paramsWithChanges).then((result) => {
54
50
  queuedRetries[action].delete(itemKey);
55
- return saveResult(itemKey, itemValue, result, true);
51
+ return saveResult(itemKey, itemValue, result, true, change);
56
52
  })
57
53
  );
58
54
  }
@@ -81,6 +77,11 @@ function syncedCrud(props) {
81
77
  } = props;
82
78
  const fieldId = fieldIdProp || "id";
83
79
  const pendingCreates = /* @__PURE__ */ new Set();
80
+ const queuedRetries = {
81
+ create: /* @__PURE__ */ new Map(),
82
+ update: /* @__PURE__ */ new Map(),
83
+ delete: /* @__PURE__ */ new Map()
84
+ };
84
85
  let asType = props.as;
85
86
  if (!asType) {
86
87
  asType = getFn ? "value" : "object";
@@ -177,6 +178,7 @@ function syncedCrud(props) {
177
178
  const creates = /* @__PURE__ */ new Map();
178
179
  const updates = /* @__PURE__ */ new Map();
179
180
  const deletes = /* @__PURE__ */ new Set();
181
+ const changesById = /* @__PURE__ */ new Map();
180
182
  const getUpdateValue = (itemValue, prev) => {
181
183
  return updatePartial ? Object.assign(
182
184
  diffObjects(
@@ -198,6 +200,7 @@ function syncedCrud(props) {
198
200
  id = ensureId(value, fieldId, generateId);
199
201
  }
200
202
  if (id) {
203
+ changesById.set(id, change);
201
204
  if (pendingCreates.has(id)) {
202
205
  isCreate = false;
203
206
  }
@@ -227,6 +230,7 @@ function syncedCrud(props) {
227
230
  }
228
231
  } else if (path.length === 0) {
229
232
  deletes.add(prevAtPath);
233
+ changesById.set(prevAtPath[fieldId], change);
230
234
  }
231
235
  } else {
232
236
  let itemsChanged = [];
@@ -251,6 +255,7 @@ function syncedCrud(props) {
251
255
  if (!itemValue) {
252
256
  if (path.length === 1 && prevAtPath) {
253
257
  deletes.add(prevAtPath);
258
+ changesById.set(prevAtPath[fieldId], change);
254
259
  }
255
260
  } else {
256
261
  const previous = setAtPath(
@@ -272,17 +277,18 @@ function syncedCrud(props) {
272
277
  console.error("[legend-state]: added item without an id");
273
278
  }
274
279
  if (createFn) {
275
- pendingCreates.add(item[fieldId]);
276
- creates.set(item[fieldId], item);
280
+ const id = item[fieldId];
281
+ changesById.set(id, change);
282
+ pendingCreates.add(id);
283
+ creates.set(id, item);
277
284
  } else {
278
285
  console.warn("[legend-state] missing create function");
279
286
  }
280
287
  } else {
281
288
  if (updateFn) {
282
- updates.set(
283
- item[fieldId],
284
- updates.has(item[fieldId]) ? Object.assign(updates.get(item[fieldId]), item) : item
285
- );
289
+ const id = item[fieldId];
290
+ changesById.set(id, change);
291
+ updates.set(id, updates.has(id) ? Object.assign(updates.get(id), item) : item);
286
292
  } else {
287
293
  console.warn("[legend-state] missing update function");
288
294
  }
@@ -290,7 +296,7 @@ function syncedCrud(props) {
290
296
  });
291
297
  }
292
298
  });
293
- const saveResult = async (itemKey, input, data, isCreate) => {
299
+ const saveResult = async (itemKey, input, data, isCreate, change) => {
294
300
  var _a;
295
301
  if (data) {
296
302
  let saved = (transform == null ? void 0 : transform.load) ? await transform.load(data, "set") : data;
@@ -317,7 +323,7 @@ function syncedCrud(props) {
317
323
  if (
318
324
  // value is already the new value, can ignore
319
325
  saved[key] === c || // user has changed local value
320
- key !== fieldId && i !== c
326
+ key !== fieldId && i !== void 0 && i !== c
321
327
  ) {
322
328
  delete saved[key];
323
329
  }
@@ -338,7 +344,8 @@ function syncedCrud(props) {
338
344
  if (value2 !== void 0) {
339
345
  update({
340
346
  value: value2,
341
- mode: "merge"
347
+ mode: "merge",
348
+ changes: [change]
342
349
  });
343
350
  }
344
351
  }
@@ -351,11 +358,19 @@ function syncedCrud(props) {
351
358
  await waitForSet(waitForSetParam, changes, itemValue, { type: "create" });
352
359
  }
353
360
  const createObj = await transformOut(itemValue, transform == null ? void 0 : transform.save);
354
- return retrySet(params, retry, "create", itemKey, createObj, createFn, saveResult).then(
355
- () => {
356
- pendingCreates.delete(itemKey);
357
- }
358
- );
361
+ return retrySet(
362
+ params,
363
+ retry,
364
+ "create",
365
+ itemKey,
366
+ createObj,
367
+ changesById.get(itemKey),
368
+ queuedRetries,
369
+ createFn,
370
+ saveResult
371
+ ).then(() => {
372
+ pendingCreates.delete(itemKey);
373
+ });
359
374
  }),
360
375
  // Handle updates
361
376
  ...Array.from(updates).map(async ([itemKey, itemValue]) => {
@@ -364,7 +379,17 @@ function syncedCrud(props) {
364
379
  }
365
380
  const changed = await transformOut(itemValue, transform == null ? void 0 : transform.save);
366
381
  if (Object.keys(changed).length > 0) {
367
- return retrySet(params, retry, "update", itemKey, changed, updateFn, saveResult);
382
+ return retrySet(
383
+ params,
384
+ retry,
385
+ "update",
386
+ itemKey,
387
+ changed,
388
+ changesById.get(itemKey),
389
+ queuedRetries,
390
+ updateFn,
391
+ saveResult
392
+ );
368
393
  }
369
394
  }),
370
395
  // Handle deletes
@@ -372,8 +397,8 @@ function syncedCrud(props) {
372
397
  if (waitForSetParam) {
373
398
  await waitForSet(waitForSetParam, changes, valuePrevious, { type: "delete" });
374
399
  }
375
- const valueId = valuePrevious[fieldId];
376
- if (!valueId) {
400
+ const itemKey = valuePrevious[fieldId];
401
+ if (!itemKey) {
377
402
  console.error("[legend-state]: deleting item without an id");
378
403
  return;
379
404
  }
@@ -382,8 +407,10 @@ function syncedCrud(props) {
382
407
  params,
383
408
  retry,
384
409
  "delete",
385
- valueId,
410
+ itemKey,
386
411
  valuePrevious,
412
+ changesById.get(itemKey),
413
+ queuedRetries,
387
414
  deleteFn,
388
415
  saveResult
389
416
  );
@@ -393,8 +420,10 @@ function syncedCrud(props) {
393
420
  params,
394
421
  retry,
395
422
  "delete",
396
- valueId,
397
- { [fieldId]: valueId, [fieldDeleted]: true },
423
+ itemKey,
424
+ { [fieldId]: itemKey, [fieldDeleted]: true },
425
+ changesById.get(itemKey),
426
+ queuedRetries,
398
427
  updateFn,
399
428
  saveResult
400
429
  );
@@ -13,7 +13,7 @@ type APIError = {
13
13
  type: string;
14
14
  message: string;
15
15
  requestId?: string;
16
- error?: Error;
16
+ error?: unknown;
17
17
  };
18
18
  type APIResult<T> = Result<T, APIError>;
19
19
  type Data<T> = {
@@ -13,7 +13,7 @@ type APIError = {
13
13
  type: string;
14
14
  message: string;
15
15
  requestId?: string;
16
- error?: Error;
16
+ error?: unknown;
17
17
  };
18
18
  type APIResult<T> = Result<T, APIError>;
19
19
  type Data<T> = {
package/sync.js CHANGED
@@ -36,12 +36,12 @@ function diffObjects(obj1, obj2, deep = false) {
36
36
  return diff;
37
37
  }
38
38
  function deepEqual(a, b, ignoreFields, nullVsUndefined) {
39
- if (a === b) {
39
+ if (a === b)
40
40
  return true;
41
- }
42
- if (state.isNullOrUndefined(a) !== state.isNullOrUndefined(b)) {
41
+ if (state.isNullOrUndefined(a) !== state.isNullOrUndefined(b))
43
42
  return false;
44
- }
43
+ if (!state.isObject(a) || !state.isObject(b))
44
+ return a === b;
45
45
  if (nullVsUndefined) {
46
46
  a = removeNullUndefined(
47
47
  a,
@@ -54,8 +54,18 @@ function deepEqual(a, b, ignoreFields, nullVsUndefined) {
54
54
  true
55
55
  );
56
56
  }
57
- const replacer = ignoreFields ? (key, value) => ignoreFields.includes(key) ? void 0 : value : void 0;
58
- return JSON.stringify(a, replacer) === JSON.stringify(b, replacer);
57
+ const keysA = Object.keys(a).filter((key) => !(ignoreFields == null ? void 0 : ignoreFields.includes(key)));
58
+ const keysB = Object.keys(b).filter((key) => !(ignoreFields == null ? void 0 : ignoreFields.includes(key)));
59
+ if (keysA.length !== keysB.length)
60
+ return false;
61
+ return keysA.every((key) => {
62
+ if (!Object.prototype.hasOwnProperty.call(b, key))
63
+ return false;
64
+ if (state.isDate(a[key]) && state.isDate(b[key])) {
65
+ return a[key].getTime() === b[key].getTime();
66
+ }
67
+ return deepEqual(a[key], b[key], ignoreFields, nullVsUndefined);
68
+ });
59
69
  }
60
70
  function combineTransforms(...transforms) {
61
71
  return {
@@ -287,10 +297,10 @@ function updateMetadata(value$, localState, syncState2, syncOptions, newMetadata
287
297
  if (localState.timeoutSaveMetadata) {
288
298
  clearTimeout(localState.timeoutSaveMetadata);
289
299
  }
290
- localState.timeoutSaveMetadata = setTimeout(
291
- () => updateMetadataImmediate(value$, localState, syncState2, syncOptions, newMetadata),
292
- 0
293
- );
300
+ metadatas.set(value$, { ...metadatas.get(value$) || {}, ...newMetadata });
301
+ localState.timeoutSaveMetadata = setTimeout(() => {
302
+ updateMetadataImmediate(value$, localState, syncState2, syncOptions, metadatas.get(value$));
303
+ }, 0);
294
304
  }
295
305
  var _queuedChanges = [];
296
306
  var _queuedRemoteChanges = /* @__PURE__ */ new Map();
@@ -309,8 +319,25 @@ function mergeChanges(changes) {
309
319
  existing.valueAtPath = change.valueAtPath;
310
320
  }
311
321
  } else {
312
- changesByPath.set(pathStr, change);
313
- changesOut.push(change);
322
+ let found = false;
323
+ for (let u = 0; u < change.path.length; u++) {
324
+ const path = change.path.slice(0, u).join("/");
325
+ if (changesByPath.has(path)) {
326
+ const remaining = change.path.slice(u);
327
+ state.setAtPath(
328
+ changesByPath.get(path).valueAtPath,
329
+ remaining,
330
+ change.pathTypes.slice(u),
331
+ change.valueAtPath
332
+ );
333
+ found = true;
334
+ break;
335
+ }
336
+ }
337
+ if (!found) {
338
+ changesByPath.set(pathStr, change);
339
+ changesOut.push(change);
340
+ }
314
341
  }
315
342
  }
316
343
  return changesOut;
@@ -635,10 +662,11 @@ async function doChangeRemote(changeInfo) {
635
662
  onError: onSetError,
636
663
  update: (params) => {
637
664
  if (updateResult) {
638
- const { value: value2, mode } = params;
665
+ const { value: value2, mode, changes } = params;
639
666
  updateResult = {
640
667
  value: deepMerge(updateResult.value, value2),
641
- mode
668
+ mode,
669
+ changes: changes ? [...updateResult.changes || [], ...changes] : updateResult.changes
642
670
  };
643
671
  } else {
644
672
  updateResult = params;
@@ -660,9 +688,11 @@ async function doChangeRemote(changeInfo) {
660
688
  }
661
689
  });
662
690
  }
663
- if (!didError) {
664
- const pathStrs = Array.from(new Set(changesRemote.map((change) => change.pathStr)));
665
- const { value: changes } = updateResult || {};
691
+ if (!didError || (updateResult == null ? void 0 : updateResult.changes)) {
692
+ const { value: updateValue, changes: updateChanges = changesRemote } = updateResult || {};
693
+ const pathStrs = Array.from(
694
+ new Set(updateChanges.map((change) => change.pathStr))
695
+ );
666
696
  if (pathStrs.length > 0) {
667
697
  let transformedChanges = void 0;
668
698
  const metadata = {};
@@ -680,8 +710,8 @@ async function doChangeRemote(changeInfo) {
680
710
  }
681
711
  }
682
712
  }
683
- if (changes && !state.isEmpty(changes)) {
684
- transformedChanges = transformLoadData(changes, syncOptions, false, "set");
713
+ if (updateValue && !state.isEmpty(updateValue)) {
714
+ transformedChanges = transformLoadData(updateValue, syncOptions, false, "set");
685
715
  }
686
716
  if (transformedChanges !== void 0) {
687
717
  if (state.isPromise(transformedChanges)) {
@@ -857,27 +887,33 @@ function syncObservable(obs$, syncOptionsOrSynced) {
857
887
  syncState$.isLoaded.set(!syncState$.numPendingRemoteLoads.peek());
858
888
  let isSynced = false;
859
889
  let isSubscribed = false;
890
+ let isApplyingPendingAfterSync = false;
860
891
  let unsubscribe = void 0;
861
892
  const applyPending = (pending) => {
862
893
  if (pending && !state.isEmpty(pending)) {
863
- localState.isApplyingPending = true;
864
894
  const keys = Object.keys(pending);
895
+ const value = getNodeValue(node);
865
896
  const changes = [];
866
897
  for (let i = 0; i < keys.length; i++) {
867
898
  const key = keys[i];
868
899
  const path = key.split("/").filter((p2) => p2 !== "");
869
- const { p, v, t } = pending[key];
870
- changes.push({ path, valueAtPath: v, prevAtPath: p, pathTypes: t });
900
+ const { p, t, v } = pending[key];
901
+ const valueAtPath = getValueAtPath(value, path);
902
+ if (isApplyingPendingAfterSync || !deepEqual(valueAtPath, v)) {
903
+ changes.push({ path, valueAtPath: v, prevAtPath: p, pathTypes: t });
904
+ }
905
+ }
906
+ if (changes.length > 0) {
907
+ localState.isApplyingPending = true;
908
+ onObsChange(obs$, syncState$, localState, syncOptions, {
909
+ value,
910
+ isFromPersist: false,
911
+ isFromSync: false,
912
+ getPrevious: createPreviousHandler(value, changes),
913
+ changes
914
+ });
915
+ localState.isApplyingPending = false;
871
916
  }
872
- const value = getNodeValue(node);
873
- onObsChange(obs$, syncState$, localState, syncOptions, {
874
- value,
875
- isFromPersist: false,
876
- isFromSync: false,
877
- getPrevious: createPreviousHandler(value, changes),
878
- changes
879
- });
880
- localState.isApplyingPending = false;
881
917
  }
882
918
  };
883
919
  const { get, subscribe } = syncOptions;
@@ -914,10 +950,10 @@ function syncObservable(obs$, syncOptionsOrSynced) {
914
950
  const { v, t } = pending2[key];
915
951
  if (t.length === 0 || !value) {
916
952
  const oldValue = clone2(value);
917
- pending2[key].p = oldValue;
953
+ pending2[key].p = key ? oldValue[key] : oldValue;
918
954
  if (state.isObject(value) && state.isObject(v)) {
919
- Object.assign(value, v);
920
- } else {
955
+ Object.assign(value, key ? { [key]: v } : v);
956
+ } else if (!key) {
921
957
  value = v;
922
958
  }
923
959
  } else if (value[p[0]] !== void 0) {
@@ -929,6 +965,7 @@ function syncObservable(obs$, syncOptionsOrSynced) {
929
965
  } else {
930
966
  const oldValue = clone2(value);
931
967
  pending2[key].p = getValueAtPath(oldValue, p);
968
+ didChangeMetadata = true;
932
969
  value = state.setAtPath(
933
970
  value,
934
971
  p,
@@ -949,7 +986,7 @@ function syncObservable(obs$, syncOptionsOrSynced) {
949
986
  }
950
987
  });
951
988
  if (didChangeMetadata && syncOptions.persist) {
952
- updateMetadata(obs$, localState, syncState$, syncOptions, {
989
+ updateMetadataImmediate(obs$, localState, syncState$, syncOptions, {
953
990
  pending: pending2
954
991
  });
955
992
  }
@@ -1126,14 +1163,17 @@ function syncObservable(obs$, syncOptionsOrSynced) {
1126
1163
  }
1127
1164
  if (!isSynced) {
1128
1165
  isSynced = true;
1129
- await state.when(syncState$.isLoaded);
1166
+ isApplyingPendingAfterSync = true;
1130
1167
  applyPending(pending);
1168
+ isApplyingPendingAfterSync = false;
1131
1169
  }
1132
1170
  };
1133
1171
  syncStateValue.sync = sync;
1134
1172
  } else {
1135
1173
  if (!isSynced) {
1174
+ isApplyingPendingAfterSync = true;
1136
1175
  applyPending(localState.pendingChanges);
1176
+ isApplyingPendingAfterSync = false;
1137
1177
  }
1138
1178
  }
1139
1179
  syncStateValue.reset = async () => {
package/sync.mjs CHANGED
@@ -34,12 +34,12 @@ function diffObjects(obj1, obj2, deep = false) {
34
34
  return diff;
35
35
  }
36
36
  function deepEqual(a, b, ignoreFields, nullVsUndefined) {
37
- if (a === b) {
37
+ if (a === b)
38
38
  return true;
39
- }
40
- if (isNullOrUndefined(a) !== isNullOrUndefined(b)) {
39
+ if (isNullOrUndefined(a) !== isNullOrUndefined(b))
41
40
  return false;
42
- }
41
+ if (!isObject(a) || !isObject(b))
42
+ return a === b;
43
43
  if (nullVsUndefined) {
44
44
  a = removeNullUndefined(
45
45
  a,
@@ -52,8 +52,18 @@ function deepEqual(a, b, ignoreFields, nullVsUndefined) {
52
52
  true
53
53
  );
54
54
  }
55
- const replacer = ignoreFields ? (key, value) => ignoreFields.includes(key) ? void 0 : value : void 0;
56
- return JSON.stringify(a, replacer) === JSON.stringify(b, replacer);
55
+ const keysA = Object.keys(a).filter((key) => !(ignoreFields == null ? void 0 : ignoreFields.includes(key)));
56
+ const keysB = Object.keys(b).filter((key) => !(ignoreFields == null ? void 0 : ignoreFields.includes(key)));
57
+ if (keysA.length !== keysB.length)
58
+ return false;
59
+ return keysA.every((key) => {
60
+ if (!Object.prototype.hasOwnProperty.call(b, key))
61
+ return false;
62
+ if (isDate(a[key]) && isDate(b[key])) {
63
+ return a[key].getTime() === b[key].getTime();
64
+ }
65
+ return deepEqual(a[key], b[key], ignoreFields, nullVsUndefined);
66
+ });
57
67
  }
58
68
  function combineTransforms(...transforms) {
59
69
  return {
@@ -285,10 +295,10 @@ function updateMetadata(value$, localState, syncState2, syncOptions, newMetadata
285
295
  if (localState.timeoutSaveMetadata) {
286
296
  clearTimeout(localState.timeoutSaveMetadata);
287
297
  }
288
- localState.timeoutSaveMetadata = setTimeout(
289
- () => updateMetadataImmediate(value$, localState, syncState2, syncOptions, newMetadata),
290
- 0
291
- );
298
+ metadatas.set(value$, { ...metadatas.get(value$) || {}, ...newMetadata });
299
+ localState.timeoutSaveMetadata = setTimeout(() => {
300
+ updateMetadataImmediate(value$, localState, syncState2, syncOptions, metadatas.get(value$));
301
+ }, 0);
292
302
  }
293
303
  var _queuedChanges = [];
294
304
  var _queuedRemoteChanges = /* @__PURE__ */ new Map();
@@ -307,8 +317,25 @@ function mergeChanges(changes) {
307
317
  existing.valueAtPath = change.valueAtPath;
308
318
  }
309
319
  } else {
310
- changesByPath.set(pathStr, change);
311
- changesOut.push(change);
320
+ let found = false;
321
+ for (let u = 0; u < change.path.length; u++) {
322
+ const path = change.path.slice(0, u).join("/");
323
+ if (changesByPath.has(path)) {
324
+ const remaining = change.path.slice(u);
325
+ setAtPath(
326
+ changesByPath.get(path).valueAtPath,
327
+ remaining,
328
+ change.pathTypes.slice(u),
329
+ change.valueAtPath
330
+ );
331
+ found = true;
332
+ break;
333
+ }
334
+ }
335
+ if (!found) {
336
+ changesByPath.set(pathStr, change);
337
+ changesOut.push(change);
338
+ }
312
339
  }
313
340
  }
314
341
  return changesOut;
@@ -633,10 +660,11 @@ async function doChangeRemote(changeInfo) {
633
660
  onError: onSetError,
634
661
  update: (params) => {
635
662
  if (updateResult) {
636
- const { value: value2, mode } = params;
663
+ const { value: value2, mode, changes } = params;
637
664
  updateResult = {
638
665
  value: deepMerge(updateResult.value, value2),
639
- mode
666
+ mode,
667
+ changes: changes ? [...updateResult.changes || [], ...changes] : updateResult.changes
640
668
  };
641
669
  } else {
642
670
  updateResult = params;
@@ -658,9 +686,11 @@ async function doChangeRemote(changeInfo) {
658
686
  }
659
687
  });
660
688
  }
661
- if (!didError) {
662
- const pathStrs = Array.from(new Set(changesRemote.map((change) => change.pathStr)));
663
- const { value: changes } = updateResult || {};
689
+ if (!didError || (updateResult == null ? void 0 : updateResult.changes)) {
690
+ const { value: updateValue, changes: updateChanges = changesRemote } = updateResult || {};
691
+ const pathStrs = Array.from(
692
+ new Set(updateChanges.map((change) => change.pathStr))
693
+ );
664
694
  if (pathStrs.length > 0) {
665
695
  let transformedChanges = void 0;
666
696
  const metadata = {};
@@ -678,8 +708,8 @@ async function doChangeRemote(changeInfo) {
678
708
  }
679
709
  }
680
710
  }
681
- if (changes && !isEmpty(changes)) {
682
- transformedChanges = transformLoadData(changes, syncOptions, false, "set");
711
+ if (updateValue && !isEmpty(updateValue)) {
712
+ transformedChanges = transformLoadData(updateValue, syncOptions, false, "set");
683
713
  }
684
714
  if (transformedChanges !== void 0) {
685
715
  if (isPromise$1(transformedChanges)) {
@@ -855,27 +885,33 @@ function syncObservable(obs$, syncOptionsOrSynced) {
855
885
  syncState$.isLoaded.set(!syncState$.numPendingRemoteLoads.peek());
856
886
  let isSynced = false;
857
887
  let isSubscribed = false;
888
+ let isApplyingPendingAfterSync = false;
858
889
  let unsubscribe = void 0;
859
890
  const applyPending = (pending) => {
860
891
  if (pending && !isEmpty(pending)) {
861
- localState.isApplyingPending = true;
862
892
  const keys = Object.keys(pending);
893
+ const value = getNodeValue(node);
863
894
  const changes = [];
864
895
  for (let i = 0; i < keys.length; i++) {
865
896
  const key = keys[i];
866
897
  const path = key.split("/").filter((p2) => p2 !== "");
867
- const { p, v, t } = pending[key];
868
- changes.push({ path, valueAtPath: v, prevAtPath: p, pathTypes: t });
898
+ const { p, t, v } = pending[key];
899
+ const valueAtPath = getValueAtPath(value, path);
900
+ if (isApplyingPendingAfterSync || !deepEqual(valueAtPath, v)) {
901
+ changes.push({ path, valueAtPath: v, prevAtPath: p, pathTypes: t });
902
+ }
903
+ }
904
+ if (changes.length > 0) {
905
+ localState.isApplyingPending = true;
906
+ onObsChange(obs$, syncState$, localState, syncOptions, {
907
+ value,
908
+ isFromPersist: false,
909
+ isFromSync: false,
910
+ getPrevious: createPreviousHandler(value, changes),
911
+ changes
912
+ });
913
+ localState.isApplyingPending = false;
869
914
  }
870
- const value = getNodeValue(node);
871
- onObsChange(obs$, syncState$, localState, syncOptions, {
872
- value,
873
- isFromPersist: false,
874
- isFromSync: false,
875
- getPrevious: createPreviousHandler(value, changes),
876
- changes
877
- });
878
- localState.isApplyingPending = false;
879
915
  }
880
916
  };
881
917
  const { get, subscribe } = syncOptions;
@@ -912,10 +948,10 @@ function syncObservable(obs$, syncOptionsOrSynced) {
912
948
  const { v, t } = pending2[key];
913
949
  if (t.length === 0 || !value) {
914
950
  const oldValue = clone2(value);
915
- pending2[key].p = oldValue;
951
+ pending2[key].p = key ? oldValue[key] : oldValue;
916
952
  if (isObject(value) && isObject(v)) {
917
- Object.assign(value, v);
918
- } else {
953
+ Object.assign(value, key ? { [key]: v } : v);
954
+ } else if (!key) {
919
955
  value = v;
920
956
  }
921
957
  } else if (value[p[0]] !== void 0) {
@@ -927,6 +963,7 @@ function syncObservable(obs$, syncOptionsOrSynced) {
927
963
  } else {
928
964
  const oldValue = clone2(value);
929
965
  pending2[key].p = getValueAtPath(oldValue, p);
966
+ didChangeMetadata = true;
930
967
  value = setAtPath(
931
968
  value,
932
969
  p,
@@ -947,7 +984,7 @@ function syncObservable(obs$, syncOptionsOrSynced) {
947
984
  }
948
985
  });
949
986
  if (didChangeMetadata && syncOptions.persist) {
950
- updateMetadata(obs$, localState, syncState$, syncOptions, {
987
+ updateMetadataImmediate(obs$, localState, syncState$, syncOptions, {
951
988
  pending: pending2
952
989
  });
953
990
  }
@@ -1124,14 +1161,17 @@ function syncObservable(obs$, syncOptionsOrSynced) {
1124
1161
  }
1125
1162
  if (!isSynced) {
1126
1163
  isSynced = true;
1127
- await when(syncState$.isLoaded);
1164
+ isApplyingPendingAfterSync = true;
1128
1165
  applyPending(pending);
1166
+ isApplyingPendingAfterSync = false;
1129
1167
  }
1130
1168
  };
1131
1169
  syncStateValue.sync = sync;
1132
1170
  } else {
1133
1171
  if (!isSynced) {
1172
+ isApplyingPendingAfterSync = true;
1134
1173
  applyPending(localState.pendingChanges);
1174
+ isApplyingPendingAfterSync = false;
1135
1175
  }
1136
1176
  }
1137
1177
  syncStateValue.reset = async () => {