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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/CHANGELOG.md +831 -1
  2. package/LICENSE +21 -1
  3. package/README.md +141 -1
  4. package/babel.js +0 -2
  5. package/babel.mjs +0 -2
  6. package/helpers/trackHistory.js +2 -2
  7. package/helpers/trackHistory.mjs +2 -2
  8. package/index.d.mts +45 -32
  9. package/index.d.ts +45 -32
  10. package/index.js +234 -156
  11. package/index.mjs +234 -156
  12. package/package.json +6 -1
  13. package/persist-plugins/async-storage.d.mts +2 -2
  14. package/persist-plugins/async-storage.d.ts +2 -2
  15. package/persist-plugins/indexeddb.js +1 -1
  16. package/persist-plugins/indexeddb.mjs +1 -1
  17. package/react.d.mts +17 -14
  18. package/react.d.ts +17 -14
  19. package/react.js +55 -30
  20. package/react.mjs +56 -31
  21. package/sync-plugins/_transformObjectFields.d.mts +31 -0
  22. package/sync-plugins/_transformObjectFields.d.ts +31 -0
  23. package/sync-plugins/_transformObjectFields.js +114 -0
  24. package/sync-plugins/_transformObjectFields.mjs +110 -0
  25. package/sync-plugins/crud.d.mts +14 -23
  26. package/sync-plugins/crud.d.ts +14 -23
  27. package/sync-plugins/crud.js +205 -134
  28. package/sync-plugins/crud.mjs +206 -135
  29. package/sync-plugins/firebase.d.mts +26 -0
  30. package/sync-plugins/firebase.d.ts +26 -0
  31. package/sync-plugins/firebase.js +365 -0
  32. package/sync-plugins/firebase.mjs +360 -0
  33. package/sync-plugins/keel.d.mts +24 -8
  34. package/sync-plugins/keel.d.ts +24 -8
  35. package/sync-plugins/keel.js +32 -16
  36. package/sync-plugins/keel.mjs +32 -16
  37. package/sync-plugins/supabase.d.mts +1 -1
  38. package/sync-plugins/supabase.d.ts +1 -1
  39. package/sync-plugins/supabase.js +5 -4
  40. package/sync-plugins/supabase.mjs +6 -5
  41. package/sync-plugins/tanstack-query.d.mts +2 -2
  42. package/sync-plugins/tanstack-query.d.ts +2 -2
  43. package/sync-plugins/tanstack-query.js +3 -2
  44. package/sync-plugins/tanstack-query.mjs +3 -2
  45. package/sync-plugins/tanstack-react-query.d.mts +1 -1
  46. package/sync-plugins/tanstack-react-query.d.ts +1 -1
  47. package/sync.d.mts +38 -185
  48. package/sync.d.ts +38 -185
  49. package/sync.js +354 -296
  50. package/sync.mjs +356 -297
  51. package/types/babel.d.ts +12 -1
  52. package/.DS_Store +0 -0
  53. /package/config/{enable_GetSet.d.mts → enable$GetSet.d.mts} +0 -0
  54. /package/config/{enable_GetSet.d.ts → enable$GetSet.d.ts} +0 -0
package/sync.mjs CHANGED
@@ -1,6 +1,4 @@
1
- import { isObject, isDate, isNullOrUndefined, isString, endBatch, beginBatch, isFunction, observable, when, linked, internal, isPromise, mergeIntoObservable, isEmpty, shouldIgnoreUnobserved, whenReady, constructObjectWithPath, deconstructObjectWithPath, setAtPath, isArray } from '@legendapp/state';
2
-
3
- // sync.ts
1
+ import { isObject, isDate, isNullOrUndefined, isString, endBatch, beginBatch, isFunction, syncState, when, linked, internal, observable, isPromise, mergeIntoObservable, isEmpty, whenReady, trackSelector, shouldIgnoreUnobserved, constructObjectWithPath, setAtPath, isArray } from '@legendapp/state';
4
2
 
5
3
  // src/sync/configureObservableSync.ts
6
4
  var observableSyncConfiguration = {};
@@ -137,34 +135,19 @@ function transformStringifyDates(...args) {
137
135
  }
138
136
  };
139
137
  }
140
- function syncObservableAdapter({ get, set }) {
141
- const ret = {};
142
- if (get) {
143
- ret.get = async (params) => {
144
- try {
145
- let value = get(params);
146
- if (isPromise(value)) {
147
- value = await value;
148
- }
149
- params.onChange({
150
- value,
151
- lastSync: params.lastSync,
152
- mode: params.mode
153
- });
154
- params.onGet();
155
- } catch (e) {
156
- }
157
- };
158
- }
159
- if (set) {
160
- ret.set = set;
161
- }
162
- return ret;
163
- }
164
-
165
- // src/sync/syncObservable.ts
166
- var { createPreviousHandler, clone, getValueAtPath, globalState, symbolLinked, getNode, getNodeValue } = internal;
138
+ var {
139
+ clone,
140
+ deepMerge,
141
+ getNode,
142
+ getNodeValue,
143
+ getValueAtPath,
144
+ globalState,
145
+ runWithRetry,
146
+ symbolLinked,
147
+ createPreviousHandler
148
+ } = internal;
167
149
  var mapSyncPlugins = /* @__PURE__ */ new WeakMap();
150
+ var allSyncStates = /* @__PURE__ */ new Map();
168
151
  var metadatas = /* @__PURE__ */ new WeakMap();
169
152
  var promisesLocalSaves = /* @__PURE__ */ new Set();
170
153
  function parseLocalConfig(config) {
@@ -181,13 +164,19 @@ function onChangeRemote(cb) {
181
164
  globalState.isLoadingRemote = false;
182
165
  endBatch(true);
183
166
  }
184
- function transformSaveData(value, path, pathTypes, { transform }) {
167
+ async function transformSaveData(value, path, pathTypes, { transform }) {
185
168
  if (transform == null ? void 0 : transform.save) {
186
169
  const constructed = constructObjectWithPath(path, pathTypes, value);
187
- const saved = transform.save(constructed);
188
- value = deconstructObjectWithPath(path, pathTypes, saved);
170
+ const saved = await transform.save(constructed);
171
+ value = saved;
172
+ const outPath = [];
173
+ for (let i = 0; i < path.length; i++) {
174
+ outPath[i] = Object.keys(value)[0];
175
+ value = value[outPath[i]];
176
+ }
177
+ path = outPath;
189
178
  }
190
- return value;
179
+ return { value, path };
191
180
  }
192
181
  function transformLoadData(value, { transform }, doUserTransform, method) {
193
182
  if (doUserTransform && (transform == null ? void 0 : transform.load)) {
@@ -195,7 +184,7 @@ function transformLoadData(value, { transform }, doUserTransform, method) {
195
184
  }
196
185
  return value;
197
186
  }
198
- async function updateMetadataImmediate(value$, localState, syncState, syncOptions, newMetadata) {
187
+ async function updateMetadataImmediate(value$, localState, syncState2, syncOptions, newMetadata) {
199
188
  const saves = Array.from(promisesLocalSaves);
200
189
  if (saves.length > 0) {
201
190
  await Promise.all(saves);
@@ -203,27 +192,24 @@ async function updateMetadataImmediate(value$, localState, syncState, syncOption
203
192
  const { pluginPersist } = localState;
204
193
  const { table, config } = parseLocalConfig(syncOptions == null ? void 0 : syncOptions.persist);
205
194
  const oldMetadata = metadatas.get(value$);
206
- const { lastSync, pending } = newMetadata;
207
- const needsUpdate = pending || lastSync && (!oldMetadata || lastSync !== oldMetadata.lastSync);
208
- if (needsUpdate) {
209
- const metadata = Object.assign({}, oldMetadata, newMetadata);
210
- metadatas.set(value$, metadata);
211
- if (pluginPersist) {
212
- await pluginPersist.setMetadata(table, metadata, config);
213
- }
214
- if (lastSync) {
215
- syncState.assign({
216
- lastSync
217
- });
218
- }
195
+ const { lastSync } = newMetadata;
196
+ const metadata = Object.assign({}, oldMetadata, newMetadata);
197
+ metadatas.set(value$, metadata);
198
+ if (pluginPersist) {
199
+ await pluginPersist.setMetadata(table, metadata, config);
200
+ }
201
+ if (lastSync) {
202
+ syncState2.assign({
203
+ lastSync
204
+ });
219
205
  }
220
206
  }
221
- function updateMetadata(value$, localState, syncState, syncOptions, newMetadata) {
207
+ function updateMetadata(value$, localState, syncState2, syncOptions, newMetadata) {
222
208
  if (localState.timeoutSaveMetadata) {
223
209
  clearTimeout(localState.timeoutSaveMetadata);
224
210
  }
225
211
  localState.timeoutSaveMetadata = setTimeout(
226
- () => updateMetadataImmediate(value$, localState, syncState, syncOptions, newMetadata),
212
+ () => updateMetadataImmediate(value$, localState, syncState2, syncOptions, newMetadata),
227
213
  0
228
214
  );
229
215
  }
@@ -251,26 +237,25 @@ function mergeChanges(changes) {
251
237
  return changesOut;
252
238
  }
253
239
  function mergeQueuedChanges(allChanges) {
254
- const changesByObsRemote = /* @__PURE__ */ new Map();
255
- const changesByObsLocal = /* @__PURE__ */ new Map();
256
- const previousByObs = /* @__PURE__ */ new Map();
240
+ const changesByOptionsRemote = /* @__PURE__ */ new Map();
241
+ const changesByOptionsLocal = /* @__PURE__ */ new Map();
242
+ const previousByOptions = /* @__PURE__ */ new Map();
257
243
  const outRemote = /* @__PURE__ */ new Map();
258
244
  const outLocal = /* @__PURE__ */ new Map();
259
245
  for (let i = 0; i < allChanges.length; i++) {
260
246
  const value = allChanges[i];
261
- const { value$: obs, changes, inRemoteChange, getPrevious } = value;
247
+ const { changes, inRemoteChange, getPrevious, syncOptions } = value;
262
248
  const targetMap = inRemoteChange ? outRemote : outLocal;
263
- const changesMap = inRemoteChange ? changesByObsRemote : changesByObsLocal;
264
- const existing = changesMap.get(obs);
249
+ const changesMap = inRemoteChange ? changesByOptionsRemote : changesByOptionsLocal;
250
+ const existing = changesMap.get(syncOptions);
265
251
  const newChanges = existing ? [...existing, ...changes] : changes;
266
252
  const merged = mergeChanges(newChanges);
267
- changesMap.set(obs, merged);
253
+ changesMap.set(syncOptions, merged);
268
254
  value.changes = merged;
269
- if (!previousByObs.has(obs)) {
270
- previousByObs.set(obs, getPrevious());
255
+ if (!previousByOptions.has(syncOptions)) {
256
+ previousByOptions.set(syncOptions, getPrevious());
271
257
  }
272
- value.valuePrevious = previousByObs.get(obs);
273
- targetMap.set(obs, value);
258
+ targetMap.set(syncOptions, value);
274
259
  }
275
260
  return Array.from(outRemote.values()).concat(Array.from(outLocal.values()));
276
261
  }
@@ -316,15 +301,13 @@ async function processQueuedRemoteChanges(syncOptions) {
316
301
  }
317
302
  }
318
303
  async function prepChangeLocal(queuedChange) {
319
- const { syncState, changes, localState, syncOptions, inRemoteChange, isApplyingPending } = queuedChange;
304
+ const { syncState: syncState2, changes, syncOptions, inRemoteChange, isApplyingPending } = queuedChange;
320
305
  const persist = syncOptions.persist;
321
- const { pluginSync } = localState;
322
306
  const { config: configLocal } = parseLocalConfig(persist);
323
- const configRemote = syncOptions;
324
- const saveLocal = (persist == null ? void 0 : persist.name) && !configLocal.readonly && !isApplyingPending && syncState.isPersistEnabled.peek();
325
- const saveRemote = !!(!inRemoteChange && (pluginSync == null ? void 0 : pluginSync.set) && (configRemote == null ? void 0 : configRemote.enableSync) !== false && syncState.isSyncEnabled.peek());
307
+ const saveLocal = (persist == null ? void 0 : persist.name) && !configLocal.readonly && !isApplyingPending && syncState2.isPersistEnabled.peek();
308
+ const saveRemote = !!(!inRemoteChange && (syncOptions == null ? void 0 : syncOptions.set) && syncState2.isSyncEnabled.peek());
326
309
  if (saveLocal || saveRemote) {
327
- if (saveLocal && !syncState.isPersistLoaded.peek()) {
310
+ if (saveLocal && !syncState2.isPersistLoaded.peek()) {
328
311
  console.error(
329
312
  "[legend-state] WARNING: An observable was changed before being loaded from persist",
330
313
  persist
@@ -357,13 +340,13 @@ async function prepChangeLocal(queuedChange) {
357
340
  configLocal
358
341
  );
359
342
  promisesTransform.push(
360
- doInOrder(promiseTransformLocal, (valueTransformed) => {
343
+ doInOrder(promiseTransformLocal, ({ value: valueTransformed, path: pathTransformed }) => {
361
344
  changesLocal.push({
362
- path,
345
+ path: pathTransformed,
363
346
  pathTypes,
364
347
  prevAtPath,
365
348
  valueAtPath: valueTransformed,
366
- pathStr
349
+ pathStr: path === pathTransformed ? pathStr : pathTransformed.join("/")
367
350
  });
368
351
  })
369
352
  );
@@ -379,22 +362,19 @@ async function prepChangeLocal(queuedChange) {
379
362
  }
380
363
  async function prepChangeRemote(queuedChange) {
381
364
  const {
382
- syncState,
365
+ syncState: syncState2,
383
366
  changes,
384
367
  localState,
385
368
  syncOptions,
386
369
  inRemoteChange,
387
- isApplyingPending,
388
- valuePrevious
370
+ isApplyingPending
389
371
  } = queuedChange;
390
372
  const persist = syncOptions.persist;
391
- const { pluginSync } = localState;
392
373
  const { config: configLocal } = parseLocalConfig(persist);
393
- const configRemote = syncOptions;
394
- const saveLocal = persist && !configLocal.readonly && !isApplyingPending && syncState.isPersistEnabled.peek();
395
- const saveRemote = !inRemoteChange && (pluginSync == null ? void 0 : pluginSync.set) && (configRemote == null ? void 0 : configRemote.enableSync) !== false && syncState.isSyncEnabled.peek();
374
+ const saveLocal = persist && !configLocal.readonly && !isApplyingPending && syncState2.isPersistEnabled.peek();
375
+ const saveRemote = !inRemoteChange && (syncOptions == null ? void 0 : syncOptions.set) && syncState2.isSyncEnabled.peek();
396
376
  if (saveLocal || saveRemote) {
397
- if (saveLocal && !syncState.isPersistLoaded.peek()) {
377
+ if (saveLocal && !syncState2.isPersistLoaded.peek()) {
398
378
  console.error(
399
379
  "[legend-state] WARNING: An observable was changed before being loaded from persist",
400
380
  persist
@@ -424,20 +404,20 @@ async function prepChangeRemote(queuedChange) {
424
404
  valueAtPath,
425
405
  path,
426
406
  pathTypes,
427
- configRemote || {}
407
+ syncOptions || {}
428
408
  );
429
409
  promisesTransform.push(
430
- doInOrder(promiseTransformRemote, (valueTransformed) => {
410
+ doInOrder(promiseTransformRemote, ({ value: valueTransformed, path: pathTransformed }) => {
431
411
  var _a;
432
412
  if (!localState.pendingChanges) {
433
413
  localState.pendingChanges = {};
434
414
  }
435
415
  let found2 = false;
436
- for (let i2 = 0; !found2 && i2 < path.length - 1; i2++) {
437
- const pathParent = path.slice(0, i2 + 1).join("/");
416
+ for (let i2 = 0; !found2 && i2 < pathTransformed.length - 1; i2++) {
417
+ const pathParent = pathTransformed.slice(0, i2 + 1).join("/");
438
418
  if ((_a = localState.pendingChanges[pathParent]) == null ? void 0 : _a.v) {
439
419
  found2 = true;
440
- const pathChild = path.slice(i2 + 1);
420
+ const pathChild = pathTransformed.slice(i2 + 1);
441
421
  const pathTypesChild = pathTypes.slice(i2 + 1);
442
422
  setAtPath(
443
423
  localState.pendingChanges[pathParent].v,
@@ -459,12 +439,11 @@ async function prepChangeRemote(queuedChange) {
459
439
  localState.pendingChanges[pathStr].v = valueAtPath;
460
440
  }
461
441
  changesRemote.push({
462
- path,
442
+ path: pathTransformed,
463
443
  pathTypes,
464
444
  prevAtPath,
465
445
  valueAtPath: valueTransformed,
466
- pathStr,
467
- valuePrevious
446
+ pathStr
468
447
  });
469
448
  })
470
449
  );
@@ -482,24 +461,27 @@ async function doChangeLocal(changeInfo) {
482
461
  if (!changeInfo)
483
462
  return;
484
463
  const { queuedChange, changesLocal, saveRemote } = changeInfo;
485
- const { value$: obs, syncState, localState, syncOptions } = queuedChange;
464
+ const { value$: obs, syncState: syncState2, localState, syncOptions } = queuedChange;
486
465
  const { pluginPersist } = localState;
487
466
  const persist = syncOptions.persist;
488
- const { table, config: configLocal } = parseLocalConfig(persist);
489
- const shouldSaveMetadata = persist == null ? void 0 : persist.retrySync;
490
- if (saveRemote && shouldSaveMetadata) {
491
- await updateMetadataImmediate(obs, localState, syncState, syncOptions, {
492
- pending: localState.pendingChanges
493
- });
494
- }
495
- if (changesLocal.length > 0) {
496
- let promiseSet = pluginPersist.set(table, changesLocal, configLocal);
497
- if (promiseSet) {
498
- promiseSet = promiseSet.then(() => {
499
- promisesLocalSaves.delete(promiseSet);
467
+ const saveLocal = !!(persist == null ? void 0 : persist.name);
468
+ if (saveLocal) {
469
+ const { table, config: configLocal } = parseLocalConfig(persist);
470
+ const shouldSaveMetadata = persist == null ? void 0 : persist.retrySync;
471
+ if (saveRemote && shouldSaveMetadata) {
472
+ await updateMetadataImmediate(obs, localState, syncState2, syncOptions, {
473
+ pending: localState.pendingChanges
500
474
  });
501
- promisesLocalSaves.add(promiseSet);
502
- await promiseSet;
475
+ }
476
+ if (changesLocal.length > 0) {
477
+ let promiseSet = pluginPersist.set(table, changesLocal, configLocal);
478
+ if (promiseSet) {
479
+ promiseSet = promiseSet.then(() => {
480
+ promisesLocalSaves.delete(promiseSet);
481
+ });
482
+ promisesLocalSaves.add(promiseSet);
483
+ await promiseSet;
484
+ }
503
485
  }
504
486
  }
505
487
  }
@@ -508,47 +490,87 @@ async function doChangeRemote(changeInfo) {
508
490
  if (!changeInfo)
509
491
  return;
510
492
  const { queuedChange, changesRemote } = changeInfo;
511
- const { value$: obs, syncState, localState, syncOptions, valuePrevious: previous } = queuedChange;
512
- const { pluginPersist, pluginSync } = localState;
493
+ const { value$: obs$, syncState: syncState2, localState, syncOptions } = queuedChange;
494
+ const { pluginPersist } = localState;
495
+ const node = getNode(obs$);
496
+ const state$ = node.state;
513
497
  const persist = syncOptions.persist;
514
498
  const { table, config: configLocal } = parseLocalConfig(persist);
515
- const { allowSetIfGetError, onBeforeSet, onSetError, waitForSet, onAfterSet } = syncOptions || {};
499
+ const { onBeforeSet, waitForSet, onAfterSet } = syncOptions || {};
516
500
  const shouldSaveMetadata = persist == null ? void 0 : persist.retrySync;
501
+ const saveLocal = !!(persist == null ? void 0 : persist.name);
517
502
  if (changesRemote.length > 0) {
518
- await when(() => syncState.isLoaded.get() || allowSetIfGetError && syncState.error.get());
503
+ if (!syncState2.isLoaded.peek()) {
504
+ await when(syncState2.isLoaded);
505
+ const pending = localState.pendingChanges;
506
+ if (pending) {
507
+ changesRemote.forEach((change) => {
508
+ const key = change.pathStr;
509
+ const pendingAtPath = pending[key];
510
+ if (!isNullOrUndefined(pendingAtPath)) {
511
+ const { p } = pendingAtPath;
512
+ change.prevAtPath = p;
513
+ }
514
+ });
515
+ }
516
+ }
519
517
  if (waitForSet) {
520
- const waitFor = isFunction(waitForSet) ? waitForSet({ changes: changesRemote, value: obs.peek() }) : waitForSet;
521
- if (waitFor) {
522
- await when(waitFor);
518
+ const waitFn = isFunction(waitForSet) ? waitForSet({ changes: changesRemote, value: obs$.peek() }) : waitForSet;
519
+ if (waitFn) {
520
+ await when(waitFn);
523
521
  }
524
522
  }
525
- let value = obs.peek();
523
+ let value = clone(obs$.peek());
526
524
  const transformSave = (_a = syncOptions == null ? void 0 : syncOptions.transform) == null ? void 0 : _a.save;
527
525
  if (transformSave) {
528
- value = transformSave(clone(value));
526
+ value = transformSave(value);
529
527
  }
528
+ state$.numPendingSets.set((v) => (v || 0) + 1);
529
+ state$.isSetting.set(true);
530
530
  onBeforeSet == null ? void 0 : onBeforeSet();
531
- localState.numSavesOutstanding = (localState.numSavesOutstanding || 0) + 1;
532
- let savedPromise = pluginSync.set({
533
- value$: obs,
534
- syncState,
535
- options: syncOptions,
531
+ let updateResult = void 0;
532
+ const onError = (error) => {
533
+ var _a2;
534
+ state$.error.set(error);
535
+ (_a2 = syncOptions.onSetError) == null ? void 0 : _a2.call(syncOptions, error, setParams);
536
+ };
537
+ const setParams = {
538
+ node,
539
+ value$: obs$,
536
540
  changes: changesRemote,
537
541
  value,
538
- valuePrevious: previous
542
+ onError,
543
+ update: (params) => {
544
+ if (updateResult) {
545
+ const { value: value2, lastSync, mode } = params;
546
+ updateResult = {
547
+ lastSync: Math.max(updateResult.lastSync || 0, lastSync || 0),
548
+ value: deepMerge(updateResult.value, value2),
549
+ mode
550
+ };
551
+ } else {
552
+ updateResult = params;
553
+ }
554
+ },
555
+ refresh: syncState2.sync
556
+ };
557
+ let savedPromise = runWithRetry({ retryNum: 0, retry: syncOptions.retry }, async (retryEvent) => {
558
+ const params = setParams;
559
+ params.cancelRetry = retryEvent.cancelRetry;
560
+ params.retryNum = retryEvent.retryNum;
561
+ return syncOptions.set(params);
539
562
  });
540
563
  if (isPromise(savedPromise)) {
541
- savedPromise = savedPromise.catch((err) => onSetError == null ? void 0 : onSetError(err));
564
+ savedPromise = savedPromise.catch(onError);
542
565
  }
543
- const saved = await savedPromise;
544
- localState.numSavesOutstanding--;
545
- if (saved !== void 0) {
566
+ await savedPromise;
567
+ if (!state$.error.peek()) {
546
568
  const pathStrs = Array.from(new Set(changesRemote.map((change) => change.pathStr)));
547
- const { changes, lastSync } = saved;
569
+ const { value: changes, lastSync } = updateResult || {};
548
570
  if (pathStrs.length > 0) {
549
571
  let transformedChanges = void 0;
550
572
  const metadata = {};
551
- if (persist) {
573
+ if (saveLocal) {
552
574
  const pendingMetadata = (_b = pluginPersist.getMetadata(table, configLocal)) == null ? void 0 : _b.pending;
553
575
  const pending = localState.pendingChanges;
554
576
  for (let i = 0; i < pathStrs.length; i++) {
@@ -568,42 +590,31 @@ async function doChangeRemote(changeInfo) {
568
590
  if (changes && !isEmpty(changes)) {
569
591
  transformedChanges = transformLoadData(changes, syncOptions, false, "set");
570
592
  }
571
- if (localState.numSavesOutstanding > 0) {
572
- if (transformedChanges) {
573
- if (!localState.pendingSaveResults) {
574
- localState.pendingSaveResults = [];
575
- }
576
- localState.pendingSaveResults.push(transformedChanges);
577
- }
578
- } else {
579
- let allChanges = [...localState.pendingSaveResults || [], transformedChanges].filter(
580
- (v) => v !== void 0
581
- );
582
- if (allChanges.length > 0) {
583
- if (allChanges.some((change) => isPromise(change))) {
584
- allChanges = await Promise.all(allChanges);
585
- }
586
- onChangeRemote(() => mergeIntoObservable(obs, ...allChanges));
593
+ if (transformedChanges !== void 0) {
594
+ if (isPromise(transformedChanges)) {
595
+ transformedChanges = await transformedChanges;
587
596
  }
588
- if (persist) {
589
- if (shouldSaveMetadata && !isEmpty(metadata)) {
590
- updateMetadata(obs, localState, syncState, syncOptions, metadata);
591
- }
597
+ onChangeRemote(() => mergeIntoObservable(obs$, transformedChanges));
598
+ }
599
+ if (saveLocal) {
600
+ if (shouldSaveMetadata && !isEmpty(metadata)) {
601
+ updateMetadata(obs$, localState, syncState2, syncOptions, metadata);
592
602
  }
593
- localState.pendingSaveResults = [];
594
603
  }
595
- onAfterSet == null ? void 0 : onAfterSet();
596
604
  }
605
+ state$.numPendingSets.set((v) => v - 1);
606
+ state$.isSetting.set(state$.numPendingSets.peek() > 0);
607
+ onAfterSet == null ? void 0 : onAfterSet();
597
608
  }
598
609
  }
599
610
  }
600
- function onObsChange(value$, syncState, localState, syncOptions, { changes, loading, remote, getPrevious }) {
601
- if (!loading) {
602
- const inRemoteChange = remote;
611
+ function onObsChange(value$, syncState2, localState, syncOptions, { changes, isFromPersist, isFromSync, getPrevious }) {
612
+ if (!isFromPersist) {
613
+ const inRemoteChange = isFromSync;
603
614
  const isApplyingPending = localState.isApplyingPending;
604
615
  _queuedChanges.push({
605
616
  value$,
606
- syncState,
617
+ syncState: syncState2,
607
618
  localState,
608
619
  syncOptions,
609
620
  changes,
@@ -616,13 +627,17 @@ function onObsChange(value$, syncState, localState, syncOptions, { changes, load
616
627
  }
617
628
  }
618
629
  }
619
- async function loadLocal(value$, syncOptions, syncState, localState) {
630
+ async function loadLocal(value$, syncOptions, syncState$, localState) {
620
631
  var _a, _b, _c;
621
632
  const { persist } = syncOptions;
622
- if (persist) {
633
+ const node = getNode(value$);
634
+ const nodeValue = getNodeValue(getNode(node.state));
635
+ const syncStateValue = syncState$.peek();
636
+ const prevClearPersist = nodeValue.clearPersist;
637
+ if (persist == null ? void 0 : persist.name) {
623
638
  const PersistPlugin = persist.plugin || ((_a = observableSyncConfiguration.persist) == null ? void 0 : _a.plugin);
624
639
  const { table, config } = parseLocalConfig(persist);
625
- const node = getNode(value$);
640
+ syncStateValue.numPendingLocalLoads = (syncStateValue.numPendingLocalLoads || 0) + 1;
626
641
  if (!PersistPlugin) {
627
642
  throw new Error("Local persist is not configured");
628
643
  }
@@ -656,7 +671,7 @@ async function loadLocal(value$, syncOptions, syncState, localState) {
656
671
  if (metadata) {
657
672
  metadatas.set(value$, metadata);
658
673
  localState.pendingChanges = metadata.pending;
659
- syncState.assign({
674
+ syncState$.assign({
660
675
  lastSync: metadata.lastSync
661
676
  });
662
677
  }
@@ -674,12 +689,18 @@ async function loadLocal(value$, syncOptions, syncState, localState) {
674
689
  }
675
690
  internal.globalState.isLoadingLocal = false;
676
691
  }
677
- getNodeValue(getNode(node.state)).clearPersist = () => Promise.all([
678
- persistPlugin.deleteTable(table, config),
679
- persistPlugin.deleteMetadata(table, config)
680
- ]);
692
+ syncStateValue.numPendingLocalLoads--;
693
+ nodeValue.clearPersist = () => Promise.all(
694
+ [
695
+ prevClearPersist,
696
+ persistPlugin.deleteTable(table, config),
697
+ persistPlugin.deleteMetadata(table, config)
698
+ ].filter(Boolean)
699
+ );
700
+ } else {
701
+ nodeValue.clearPersist = () => prevClearPersist == null ? void 0 : prevClearPersist();
681
702
  }
682
- syncState.isPersistLoaded.set(true);
703
+ syncState$.isPersistLoaded.set(!(syncStateValue.numPendingLocalLoads > 0));
683
704
  }
684
705
  function syncObservable(obs$, syncOptionsOrSynced) {
685
706
  let syncOptions = syncOptionsOrSynced;
@@ -690,31 +711,59 @@ function syncObservable(obs$, syncOptionsOrSynced) {
690
711
  if ((process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test") && (!obs$ || !node)) {
691
712
  throw new Error("[legend-state] syncObservable called with undefined observable");
692
713
  }
693
- syncOptions = {
694
- syncMode: "auto",
695
- ...observableSyncConfiguration,
696
- ...removeNullUndefined(syncOptions || {})
697
- };
714
+ syncOptions = deepMerge(
715
+ {
716
+ syncMode: "auto"
717
+ },
718
+ observableSyncConfiguration,
719
+ removeNullUndefined(syncOptions || {})
720
+ );
698
721
  const localState = {};
699
722
  let sync;
700
- const syncState = node.state = observable({
701
- isPersistLoaded: false,
702
- isLoaded: !syncOptions.get,
703
- isPersistEnabled: true,
704
- isSyncEnabled: true,
705
- clearPersist: void 0,
706
- sync: () => Promise.resolve(),
707
- getPendingChanges: () => localState.pendingChanges
708
- });
709
- loadLocal(obs$, syncOptions, syncState, localState);
710
- localState.pluginSync = syncObservableAdapter(syncOptions);
723
+ const syncState$ = syncState(obs$);
724
+ const syncStateValue = getNodeValue(getNode(syncState$));
725
+ allSyncStates.set(syncState$, node);
726
+ syncStateValue.getPendingChanges = () => localState.pendingChanges;
727
+ const onError = (error, getParams, source) => {
728
+ var _a;
729
+ syncState$.error.set(error);
730
+ (_a = syncOptions.onGetError) == null ? void 0 : _a.call(syncOptions, error, getParams, source);
731
+ };
732
+ loadLocal(obs$, syncOptions, syncState$, localState);
733
+ let isWaitingForLoad = !!syncOptions.get;
734
+ if (isWaitingForLoad) {
735
+ syncStateValue.numPendingRemoteLoads = (syncStateValue.numPendingRemoteLoads || 0) + 1;
736
+ }
737
+ syncState$.isLoaded.set(!syncState$.numPendingRemoteLoads.peek());
738
+ let isSynced = false;
739
+ const applyPending = (pending) => {
740
+ if (pending && !isEmpty(pending)) {
741
+ localState.isApplyingPending = true;
742
+ const keys = Object.keys(pending);
743
+ const changes = [];
744
+ for (let i = 0; i < keys.length; i++) {
745
+ const key = keys[i];
746
+ const path = key.split("/").filter((p2) => p2 !== "");
747
+ const { p, v, t } = pending[key];
748
+ changes.push({ path, valueAtPath: v, prevAtPath: p, pathTypes: t });
749
+ }
750
+ const value = getNodeValue(node);
751
+ onObsChange(obs$, syncState$, localState, syncOptions, {
752
+ value,
753
+ isFromPersist: false,
754
+ isFromSync: false,
755
+ getPrevious: createPreviousHandler(value, changes),
756
+ changes
757
+ });
758
+ localState.isApplyingPending = false;
759
+ }
760
+ };
711
761
  if (syncOptions.get) {
712
- let isSynced = false;
713
762
  let isSubscribed = false;
714
763
  let unsubscribe = void 0;
715
764
  sync = async () => {
716
- var _a, _b;
717
- if (isSynced && shouldIgnoreUnobserved(node, sync)) {
765
+ var _a;
766
+ if (isSynced && (!getNodeValue(getNode(syncState$)).isSyncEnabled || shouldIgnoreUnobserved(node, sync))) {
718
767
  if (unsubscribe) {
719
768
  isSubscribed = false;
720
769
  unsubscribe();
@@ -724,9 +773,10 @@ function syncObservable(obs$, syncOptionsOrSynced) {
724
773
  }
725
774
  const lastSync = (_a = metadatas.get(obs$)) == null ? void 0 : _a.lastSync;
726
775
  const pending = localState.pendingChanges;
727
- const get = (_b = localState.pluginSync.get) == null ? void 0 : _b.bind(localState.pluginSync);
776
+ const get = syncOptions.get;
728
777
  if (get) {
729
778
  const runGet = () => {
779
+ var _a2;
730
780
  const onChange = async ({ value, mode, lastSync: lastSync2 }) => {
731
781
  mode = mode || syncOptions.mode || "set";
732
782
  if (value !== void 0) {
@@ -739,9 +789,11 @@ function syncObservable(obs$, syncOptionsOrSynced) {
739
789
  if (pending2) {
740
790
  let didChangeMetadata = false;
741
791
  Object.keys(pending2).forEach((key) => {
742
- const p = key.split("/").filter((p2) => p2 !== "");
792
+ const p = key.split("/").filter((k) => k !== "");
743
793
  const { v, t } = pending2[key];
744
794
  if (t.length === 0 || !value) {
795
+ const oldValue = clone(value);
796
+ pending2[key].p = oldValue;
745
797
  if (isObject(value) && isObject(v)) {
746
798
  Object.assign(value, v);
747
799
  } else {
@@ -754,6 +806,8 @@ function syncObservable(obs$, syncOptionsOrSynced) {
754
806
  delete pending2[key];
755
807
  didChangeMetadata = true;
756
808
  } else {
809
+ const oldValue = clone(value);
810
+ pending2[key].p = getValueAtPath(oldValue, p);
757
811
  value = setAtPath(
758
812
  value,
759
813
  p,
@@ -773,18 +827,24 @@ function syncObservable(obs$, syncOptionsOrSynced) {
773
827
  }
774
828
  }
775
829
  });
776
- if (didChangeMetadata) {
777
- updateMetadata(obs$, localState, syncState, syncOptions, {
830
+ if (didChangeMetadata && syncOptions.persist) {
831
+ updateMetadata(obs$, localState, syncState$, syncOptions, {
778
832
  pending: pending2
779
833
  });
780
834
  }
781
835
  }
782
836
  onChangeRemote(() => {
783
- if (mode === "assign" && isObject(value)) {
837
+ if (mode === "assign") {
784
838
  obs$.assign(value);
785
- } else if (mode === "append" && isArray(value)) {
839
+ } else if (mode === "append") {
840
+ if ((process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test") && !isArray(value)) {
841
+ console.error("[legend-state] mode:append expects the value to be an array");
842
+ }
786
843
  obs$.push(...value);
787
- } else if (mode === "prepend" && isArray(value)) {
844
+ } else if (mode === "prepend") {
845
+ if ((process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test") && !isArray(value)) {
846
+ console.error("[legend-state] mode:prepend expects the value to be an array");
847
+ }
788
848
  obs$.splice(0, 0, ...value);
789
849
  } else if (mode === "merge") {
790
850
  mergeIntoObservable(obs$, value);
@@ -794,77 +854,129 @@ function syncObservable(obs$, syncOptionsOrSynced) {
794
854
  });
795
855
  }
796
856
  if (lastSync2 && syncOptions.persist) {
797
- updateMetadata(obs$, localState, syncState, syncOptions, {
857
+ updateMetadata(obs$, localState, syncState$, syncOptions, {
798
858
  lastSync: lastSync2
799
859
  });
800
860
  }
801
861
  };
802
- get({
803
- state: syncState,
804
- value$: obs$,
805
- options: syncOptions,
806
- lastSync,
807
- dateModified: lastSync,
808
- onError: (error) => {
809
- var _a2;
810
- (_a2 = syncOptions.onGetError) == null ? void 0 : _a2.call(syncOptions, error);
811
- },
812
- onGet: () => {
813
- node.state.assign({
814
- isLoaded: true,
815
- error: void 0
816
- });
817
- },
818
- onChange
819
- });
862
+ if (node.activationState) {
863
+ node.activationState.onChange = onChange;
864
+ }
820
865
  if (!isSubscribed && syncOptions.subscribe) {
821
866
  isSubscribed = true;
822
867
  unsubscribe = syncOptions.subscribe({
823
868
  node,
824
869
  value$: obs$,
870
+ lastSync,
825
871
  update: (params) => {
826
- when(node.state.isLoaded, () => {
827
- params.mode || (params.mode = syncOptions.mode || "merge");
828
- onChange(params);
872
+ when(syncState$.isLoaded, () => {
873
+ when(waitFor || true, () => {
874
+ params.mode || (params.mode = syncOptions.mode || "merge");
875
+ onChange(params);
876
+ });
829
877
  });
830
878
  },
831
- refresh: () => when(node.state.isLoaded, sync)
879
+ refresh: () => when(syncState$.isLoaded, sync),
880
+ onError: (error) => onError(error, void 0, "subscribe")
832
881
  });
833
882
  }
883
+ const existingValue = getNodeValue(node);
884
+ const getParams = {
885
+ node,
886
+ value$: obs$,
887
+ value: isFunction(existingValue) || (existingValue == null ? void 0 : existingValue[symbolLinked]) ? void 0 : existingValue,
888
+ mode: syncOptions.mode,
889
+ refresh: sync,
890
+ options: syncOptions,
891
+ lastSync,
892
+ updateLastSync: (lastSync2) => getParams.lastSync = lastSync2,
893
+ onError: (error) => onError(error, getParams, "get")
894
+ };
895
+ let modeBeforeReset = void 0;
896
+ (_a2 = syncOptions.onBeforeGet) == null ? void 0 : _a2.call(syncOptions, {
897
+ value: getParams.value,
898
+ lastSync,
899
+ pendingChanges: pending && !isEmpty(pending) ? pending : void 0,
900
+ clearPendingChanges: async () => {
901
+ localState.pendingChanges = {};
902
+ await updateMetadataImmediate(obs$, localState, syncState$, syncOptions, {
903
+ pending: localState.pendingChanges
904
+ });
905
+ },
906
+ resetCache: () => {
907
+ var _a3;
908
+ modeBeforeReset = getParams.mode;
909
+ getParams.mode = "set";
910
+ return (_a3 = syncStateValue.clearPersist) == null ? void 0 : _a3.call(syncStateValue);
911
+ }
912
+ });
913
+ syncState$.assign({
914
+ numPendingGets: (syncStateValue.numPendingGets || 0) + 1,
915
+ isGetting: true
916
+ });
917
+ const got = runWithRetry({ retryNum: 0, retry: syncOptions.retry }, (retryEvent) => {
918
+ const params = getParams;
919
+ params.cancelRetry = retryEvent.cancelRetry;
920
+ params.retryNum = retryEvent.retryNum;
921
+ return get(params);
922
+ });
923
+ const numGets = node.numGets = (node.numGets || 0) + 1;
924
+ const handle = (value) => {
925
+ syncState$.numPendingGets.set((v) => v - 1);
926
+ if (isWaitingForLoad) {
927
+ isWaitingForLoad = false;
928
+ syncStateValue.numPendingRemoteLoads--;
929
+ }
930
+ if (numGets >= (node.getNumResolved || 0)) {
931
+ node.getNumResolved = node.numGets;
932
+ onChange({
933
+ value,
934
+ lastSync: getParams.lastSync,
935
+ mode: getParams.mode
936
+ });
937
+ }
938
+ if (modeBeforeReset) {
939
+ getParams.mode = modeBeforeReset;
940
+ modeBeforeReset = void 0;
941
+ }
942
+ syncState$.assign({
943
+ isLoaded: syncStateValue.numPendingRemoteLoads < 1,
944
+ error: void 0,
945
+ isGetting: syncStateValue.numPendingGets > 0
946
+ });
947
+ };
948
+ if (isPromise(got)) {
949
+ got.then(handle);
950
+ } else {
951
+ handle(got);
952
+ }
834
953
  };
835
- runGet();
954
+ const { waitFor } = syncOptions;
955
+ if (waitFor) {
956
+ if (node.activationState) {
957
+ node.activationState.waitFor = void 0;
958
+ }
959
+ whenReady(waitFor, () => trackSelector(runGet, sync));
960
+ } else {
961
+ trackSelector(runGet, sync);
962
+ }
836
963
  } else {
837
- node.state.assign({
964
+ syncState$.assign({
838
965
  isLoaded: true,
839
966
  error: void 0
840
967
  });
841
968
  }
842
969
  if (!isSynced) {
843
970
  isSynced = true;
844
- await when(() => syncState.isLoaded.get() || syncOptions.allowSetIfGetError && syncState.error.get());
845
- if (pending && !isEmpty(pending)) {
846
- localState.isApplyingPending = true;
847
- const keys = Object.keys(pending);
848
- const changes = [];
849
- for (let i = 0; i < keys.length; i++) {
850
- const key = keys[i];
851
- const path = key.split("/").filter((p2) => p2 !== "");
852
- const { p, v, t } = pending[key];
853
- changes.push({ path, valueAtPath: v, prevAtPath: p, pathTypes: t });
854
- }
855
- const value = getNodeValue(node);
856
- onObsChange(obs$, syncState, localState, syncOptions, {
857
- value,
858
- loading: false,
859
- remote: false,
860
- getPrevious: createPreviousHandler(value, changes),
861
- changes
862
- });
863
- localState.isApplyingPending = false;
864
- }
971
+ await when(syncState$.isLoaded);
972
+ applyPending(pending);
865
973
  }
866
974
  };
867
- syncState.assign({ sync });
975
+ syncStateValue.sync = sync;
976
+ } else {
977
+ if (!isSynced) {
978
+ applyPending(localState.pendingChanges);
979
+ }
868
980
  }
869
981
  const onAllPersistLoaded = () => {
870
982
  var _a, _b;
@@ -883,77 +995,27 @@ function syncObservable(obs$, syncOptionsOrSynced) {
883
995
  }
884
996
  if ((syncOptions == null ? void 0 : syncOptions.set) || (syncOptions == null ? void 0 : syncOptions.persist)) {
885
997
  obs$.onChange(
886
- onObsChange.bind(this, obs$, syncState, localState, syncOptions)
998
+ onObsChange.bind(this, obs$, syncState$, localState, syncOptions)
887
999
  );
888
1000
  }
889
1001
  });
890
- return syncState;
1002
+ return syncState$;
891
1003
  }
892
- var { getProxy, globalState: globalState2, runWithRetry, symbolLinked: symbolLinked2, setNodeValue, getNodeValue: getNodeValue2 } = internal;
1004
+ var { getProxy, globalState: globalState2, setNodeValue, getNodeValue: getNodeValue2 } = internal;
893
1005
  function enableActivateSyncedNode() {
894
1006
  globalState2.activateSyncedNode = function activateSyncedNode(node, newValue) {
895
1007
  const obs$ = getProxy(node);
896
1008
  if (node.activationState) {
897
- const { get, initial, set, retry } = node.activationState;
898
- let onChange = void 0;
899
- const pluginRemote = {};
1009
+ const {
1010
+ get: getOrig,
1011
+ initial,
1012
+ set,
1013
+ onChange
1014
+ } = node.activationState;
900
1015
  let promiseReturn = void 0;
901
- let syncState;
902
- const refresh = () => syncState == null ? void 0 : syncState.sync();
903
- if (get) {
904
- pluginRemote.get = (params) => {
905
- var _a;
906
- onChange = params.onChange;
907
- const updateLastSync = (lastSync) => params.lastSync = lastSync;
908
- const existingValue = getNodeValue2(node);
909
- const value = runWithRetry(node, { attemptNum: 0, retry: retry || ((_a = params.options) == null ? void 0 : _a.retry) }, () => {
910
- const paramsToGet = {
911
- value: isFunction(existingValue) || (existingValue == null ? void 0 : existingValue[symbolLinked2]) ? void 0 : existingValue,
912
- lastSync: params.lastSync,
913
- updateLastSync,
914
- mode: params.mode,
915
- refresh
916
- };
917
- const ret = get(paramsToGet);
918
- params.mode = paramsToGet.mode;
919
- return ret;
920
- });
921
- promiseReturn = value;
922
- return value;
923
- };
924
- }
925
- if (set) {
926
- pluginRemote.set = async (params) => {
927
- var _a, _b;
928
- if ((_a = node.state) == null ? void 0 : _a.isLoaded.get()) {
929
- const retryAttempts = { attemptNum: 0, retry: retry || ((_b = params.options) == null ? void 0 : _b.retry) };
930
- return runWithRetry(node, retryAttempts, async (retryEvent) => {
931
- let changes = {};
932
- let maxModified = 0;
933
- if (!node.state.isLoaded.peek()) {
934
- await whenReady(node.state.isLoaded);
935
- }
936
- const cancelRetry = () => {
937
- retryEvent.cancel = true;
938
- };
939
- await set({
940
- ...params,
941
- node,
942
- update: (params2) => {
943
- const { value, lastSync } = params2;
944
- maxModified = Math.max(lastSync || 0, maxModified);
945
- changes = mergeIntoObservable(changes, value);
946
- },
947
- retryNum: retryAttempts.attemptNum,
948
- cancelRetry,
949
- refresh,
950
- fromSubscribe: false
951
- });
952
- return { changes, lastSync: maxModified || void 0 };
953
- });
954
- }
955
- };
956
- }
1016
+ const get = getOrig ? (params) => {
1017
+ return promiseReturn = getOrig(params);
1018
+ } : void 0;
957
1019
  const nodeVal = getNodeValue2(node);
958
1020
  if (promiseReturn !== void 0) {
959
1021
  newValue = promiseReturn;
@@ -963,7 +1025,7 @@ function enableActivateSyncedNode() {
963
1025
  newValue = initial;
964
1026
  }
965
1027
  setNodeValue(node, promiseReturn ? void 0 : newValue);
966
- syncState = syncObservable(obs$, { ...node.activationState, ...pluginRemote });
1028
+ syncObservable(obs$, { ...node.activationState, get, set });
967
1029
  return { update: onChange, value: newValue };
968
1030
  } else {
969
1031
  let update = void 0;
@@ -1002,11 +1064,8 @@ function installPersistActivateNode() {
1002
1064
  }
1003
1065
 
1004
1066
  // sync.ts
1005
- function isInRemoteChange() {
1006
- return internal.globalState.isLoadingRemote;
1007
- }
1008
1067
  var internal3 = {
1009
1068
  observableSyncConfiguration
1010
1069
  };
1011
1070
 
1012
- export { combineTransforms, configureObservableSync, deepEqual, diffObjects, internal3 as internal, isInRemoteChange, mapSyncPlugins, onChangeRemote, removeNullUndefined, syncObservable, synced, transformStringifyDates, transformStringifyKeys };
1071
+ export { combineTransforms, configureObservableSync, deepEqual, diffObjects, internal3 as internal, mapSyncPlugins, onChangeRemote, removeNullUndefined, syncObservable, synced, transformStringifyDates, transformStringifyKeys };