@legendapp/state 2.2.0-next.74 → 2.2.0-next.76

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 (60) hide show
  1. package/helpers/time.d.ts +2 -2
  2. package/index.d.ts +1 -1
  3. package/index.js +82 -31
  4. package/index.js.map +1 -1
  5. package/index.mjs +81 -32
  6. package/index.mjs.map +1 -1
  7. package/package.json +16 -1
  8. package/persist.js +122 -129
  9. package/persist.js.map +1 -1
  10. package/persist.mjs +122 -129
  11. package/persist.mjs.map +1 -1
  12. package/react.js +5 -5
  13. package/react.js.map +1 -1
  14. package/react.mjs +6 -6
  15. package/react.mjs.map +1 -1
  16. package/src/ObservableObject.ts +34 -15
  17. package/src/batching.ts +9 -3
  18. package/src/computed.ts +4 -2
  19. package/src/globals.ts +17 -7
  20. package/src/helpers.ts +3 -3
  21. package/src/history/undoRedo.ts +111 -0
  22. package/src/is.ts +7 -0
  23. package/src/observableInterfaces.ts +6 -5
  24. package/src/observableTypes.ts +5 -0
  25. package/src/observe.ts +1 -1
  26. package/src/react/For.tsx +6 -6
  27. package/src/sync/activateSyncedNode.ts +9 -25
  28. package/src/sync/syncHelpers.ts +53 -12
  29. package/src/sync/syncObservable.ts +117 -101
  30. package/src/sync-plugins/crud.ts +384 -0
  31. package/src/sync-plugins/fetch.ts +57 -27
  32. package/src/sync-plugins/keel.ts +447 -0
  33. package/src/sync-plugins/supabase.ts +225 -0
  34. package/src/syncTypes.ts +12 -6
  35. package/src/when.ts +6 -1
  36. package/sync-plugins/crud.d.ts +40 -0
  37. package/sync-plugins/crud.js +275 -0
  38. package/sync-plugins/crud.js.map +1 -0
  39. package/sync-plugins/crud.mjs +271 -0
  40. package/sync-plugins/crud.mjs.map +1 -0
  41. package/sync-plugins/fetch.d.ts +8 -7
  42. package/sync-plugins/fetch.js +34 -12
  43. package/sync-plugins/fetch.js.map +1 -1
  44. package/sync-plugins/fetch.mjs +35 -13
  45. package/sync-plugins/fetch.mjs.map +1 -1
  46. package/sync-plugins/keel.d.ts +91 -0
  47. package/sync-plugins/keel.js +278 -0
  48. package/sync-plugins/keel.js.map +1 -0
  49. package/sync-plugins/keel.mjs +274 -0
  50. package/sync-plugins/keel.mjs.map +1 -0
  51. package/sync-plugins/supabase.d.ts +32 -0
  52. package/sync-plugins/supabase.js +134 -0
  53. package/sync-plugins/supabase.js.map +1 -0
  54. package/sync-plugins/supabase.mjs +131 -0
  55. package/sync-plugins/supabase.mjs.map +1 -0
  56. package/sync.d.ts +1 -0
  57. package/sync.js +157 -127
  58. package/sync.js.map +1 -1
  59. package/sync.mjs +156 -129
  60. package/sync.mjs.map +1 -1
package/persist.mjs CHANGED
@@ -134,19 +134,14 @@ if (process.env.NODE_ENV === 'development') {
134
134
  };
135
135
  }
136
136
 
137
- function removeNullUndefined(val) {
138
- if (val) {
139
- Object.keys(val).forEach((key) => {
140
- const v = val[key];
141
- if (v === null || v === undefined) {
142
- delete val[key];
143
- }
144
- else if (isObject(v)) {
145
- removeNullUndefined(v);
146
- }
147
- });
148
- }
149
- return val;
137
+ function removeNullUndefined(a, recursive) {
138
+ const out = {};
139
+ Object.keys(a).forEach((key) => {
140
+ if (a[key] !== null && a[key] !== undefined) {
141
+ out[key] = recursive && isObject(a[key]) ? removeNullUndefined(a[key]) : a[key];
142
+ }
143
+ });
144
+ return out;
150
145
  }
151
146
 
152
147
  function observablePersistRemoteFunctionsAdapter({ get, set, }) {
@@ -1277,8 +1272,7 @@ async function doChangeLocal(changeInfo) {
1277
1272
  const { pluginPersist } = localState;
1278
1273
  const persist = syncOptions.persist;
1279
1274
  const { table, config: configLocal } = parseLocalConfig(persist);
1280
- const configRemote = syncOptions;
1281
- const shouldSaveMetadata = persist && (configRemote === null || configRemote === void 0 ? void 0 : configRemote.offlineBehavior) === 'retry';
1275
+ const shouldSaveMetadata = persist === null || persist === void 0 ? void 0 : persist.retrySync;
1282
1276
  if (saveRemote && shouldSaveMetadata) {
1283
1277
  // First save pending changes before saving local or remote
1284
1278
  await updateMetadataImmediate(obs, localState, syncState, syncOptions, {
@@ -1309,8 +1303,8 @@ async function doChangeRemote(changeInfo) {
1309
1303
  const { pluginPersist, pluginSync } = localState;
1310
1304
  const persist = syncOptions.persist;
1311
1305
  const { table, config: configLocal } = parseLocalConfig(persist);
1312
- const { offlineBehavior, allowSetIfGetError, onBeforeSet, onSetError, waitForSet, onAfterSet } = syncOptions || {};
1313
- const shouldSaveMetadata = persist && offlineBehavior === 'retry';
1306
+ const { allowSetIfGetError, onBeforeSet, onSetError, waitForSet, onAfterSet } = syncOptions || {};
1307
+ const shouldSaveMetadata = persist === null || persist === void 0 ? void 0 : persist.retrySync;
1314
1308
  if (changesRemote.length > 0) {
1315
1309
  // Wait for remote to be ready before saving
1316
1310
  await when(() => syncState.isLoaded.get() || (allowSetIfGetError && syncState.error.get()));
@@ -1349,11 +1343,11 @@ async function doChangeRemote(changeInfo) {
1349
1343
  const pathStrs = Array.from(new Set(changesRemote.map((change) => change.pathStr)));
1350
1344
  const { changes, lastSync } = saved;
1351
1345
  if (pathStrs.length > 0) {
1346
+ let transformedChanges = undefined;
1347
+ const metadata = {};
1352
1348
  if (persist) {
1353
- const metadata = {};
1354
1349
  const pendingMetadata = (_b = pluginPersist.getMetadata(table, configLocal)) === null || _b === void 0 ? void 0 : _b.pending;
1355
1350
  const pending = localState.pendingChanges;
1356
- let transformedChanges = undefined;
1357
1351
  for (let i = 0; i < pathStrs.length; i++) {
1358
1352
  const pathStr = pathStrs[i];
1359
1353
  // Clear pending for this path
@@ -1372,32 +1366,34 @@ async function doChangeRemote(changeInfo) {
1372
1366
  if (lastSync) {
1373
1367
  metadata.lastSync = lastSync;
1374
1368
  }
1375
- // Remote can optionally have data that needs to be merged back into the observable,
1376
- // for example Firebase may update dateModified with the server timestamp
1377
- if (changes && !isEmpty(changes)) {
1378
- transformedChanges = transformLoadData(changes, syncOptions, false);
1379
- }
1380
- if (localState.numSavesOutstanding > 0) {
1381
- if (transformedChanges) {
1382
- if (!localState.pendingSaveResults) {
1383
- localState.pendingSaveResults = [];
1384
- }
1385
- localState.pendingSaveResults.push(transformedChanges);
1369
+ }
1370
+ // Remote can optionally have data that needs to be merged back into the observable,
1371
+ // for example Firebase may update dateModified with the server timestamp
1372
+ if (changes && !isEmpty(changes)) {
1373
+ transformedChanges = transformLoadData(changes, syncOptions, false);
1374
+ }
1375
+ if (localState.numSavesOutstanding > 0) {
1376
+ if (transformedChanges) {
1377
+ if (!localState.pendingSaveResults) {
1378
+ localState.pendingSaveResults = [];
1386
1379
  }
1380
+ localState.pendingSaveResults.push(transformedChanges);
1387
1381
  }
1388
- else {
1389
- let allChanges = [...(localState.pendingSaveResults || []), transformedChanges].filter((v) => v !== undefined);
1390
- if (allChanges.length > 0) {
1391
- if (allChanges.some((change) => isPromise(change))) {
1392
- allChanges = await Promise.all(allChanges);
1393
- }
1394
- onChangeRemote(() => mergeIntoObservable(obs, ...allChanges));
1382
+ }
1383
+ else {
1384
+ let allChanges = [...(localState.pendingSaveResults || []), transformedChanges].filter((v) => v !== undefined);
1385
+ if (allChanges.length > 0) {
1386
+ if (allChanges.some((change) => isPromise(change))) {
1387
+ allChanges = await Promise.all(allChanges);
1395
1388
  }
1389
+ onChangeRemote(() => mergeIntoObservable(obs, ...allChanges));
1390
+ }
1391
+ if (persist) {
1396
1392
  if (shouldSaveMetadata && !isEmpty(metadata)) {
1397
1393
  updateMetadata(obs, localState, syncState, syncOptions, metadata);
1398
1394
  }
1399
- localState.pendingSaveResults = [];
1400
1395
  }
1396
+ localState.pendingSaveResults = [];
1401
1397
  }
1402
1398
  onAfterSet === null || onAfterSet === void 0 ? void 0 : onAfterSet();
1403
1399
  }
@@ -1533,6 +1529,77 @@ function syncObservable(obs$, syncOptionsOrSynced) {
1533
1529
  const get = (_b = localState.pluginSync.get) === null || _b === void 0 ? void 0 : _b.bind(localState.pluginSync);
1534
1530
  if (get) {
1535
1531
  const runGet = () => {
1532
+ const onChange = async ({ value, mode, lastSync }) => {
1533
+ mode = mode || syncOptions.mode || 'set';
1534
+ if (value !== undefined) {
1535
+ value = transformLoadData(value, syncOptions, true);
1536
+ if (isPromise(value)) {
1537
+ value = await value;
1538
+ }
1539
+ const pending = localState.pendingChanges;
1540
+ const currentValue = obs$.peek();
1541
+ if (pending) {
1542
+ let didChangeMetadata = false;
1543
+ Object.keys(pending).forEach((key) => {
1544
+ const p = key.split('/').filter((p) => p !== '');
1545
+ const { v, t } = pending[key];
1546
+ if (t.length === 0 || !value) {
1547
+ if (isObject(value) && isObject(v)) {
1548
+ Object.assign(value, v);
1549
+ }
1550
+ else {
1551
+ value = v;
1552
+ }
1553
+ }
1554
+ else if (value[p[0]] !== undefined) {
1555
+ const curValue = getValueAtPath(currentValue, p);
1556
+ const newValue = getValueAtPath(value, p);
1557
+ if (JSON.stringify(curValue) === JSON.stringify(newValue)) {
1558
+ delete pending[key];
1559
+ didChangeMetadata = true;
1560
+ }
1561
+ else {
1562
+ value = setAtPath(value, p, t, v, 'merge', obs$.peek(), (path, value) => {
1563
+ delete pending[key];
1564
+ pending[path.join('/')] = {
1565
+ p: null,
1566
+ v: value,
1567
+ t: t.slice(0, path.length),
1568
+ };
1569
+ });
1570
+ }
1571
+ }
1572
+ });
1573
+ if (didChangeMetadata) {
1574
+ updateMetadata(obs$, localState, syncState, syncOptions, {
1575
+ pending,
1576
+ });
1577
+ }
1578
+ }
1579
+ onChangeRemote(() => {
1580
+ if (mode === 'assign' && isObject(value)) {
1581
+ obs$.assign(value);
1582
+ }
1583
+ else if (mode === 'append' && isArray(value)) {
1584
+ obs$.push(...value);
1585
+ }
1586
+ else if (mode === 'prepend' && isArray(value)) {
1587
+ obs$.splice(0, 0, ...value);
1588
+ }
1589
+ else if (mode === 'merge') {
1590
+ mergeIntoObservable(obs$, value);
1591
+ }
1592
+ else {
1593
+ obs$.set(value);
1594
+ }
1595
+ });
1596
+ }
1597
+ if (lastSync && syncOptions.persist) {
1598
+ updateMetadata(obs$, localState, syncState, syncOptions, {
1599
+ lastSync,
1600
+ });
1601
+ }
1602
+ };
1536
1603
  get({
1537
1604
  state: syncState,
1538
1605
  obs: obs$,
@@ -1544,82 +1611,23 @@ function syncObservable(obs$, syncOptionsOrSynced) {
1544
1611
  (_a = syncOptions.onGetError) === null || _a === void 0 ? void 0 : _a.call(syncOptions, error);
1545
1612
  },
1546
1613
  onGet: () => {
1614
+ const isFirstLoad = !node.state.isLoaded.peek();
1547
1615
  node.state.assign({
1548
1616
  isLoaded: true,
1549
1617
  error: undefined,
1550
1618
  });
1551
- },
1552
- onChange: async ({ value, mode, lastSync }) => {
1553
- mode = mode || syncOptions.mode || 'set';
1554
- if (value !== undefined) {
1555
- value = transformLoadData(value, syncOptions, true);
1556
- if (isPromise(value)) {
1557
- value = await value;
1558
- }
1559
- const pending = localState.pendingChanges;
1560
- const currentValue = obs$.peek();
1561
- if (pending) {
1562
- let didChangeMetadata = false;
1563
- Object.keys(pending).forEach((key) => {
1564
- const p = key.split('/').filter((p) => p !== '');
1565
- const { v, t } = pending[key];
1566
- if (t.length === 0 || !value) {
1567
- if (isObject(value) && isObject(v)) {
1568
- Object.assign(value, v);
1569
- }
1570
- else {
1571
- value = v;
1572
- }
1573
- }
1574
- else if (value[p[0]] !== undefined) {
1575
- const curValue = getValueAtPath(currentValue, p);
1576
- const newValue = getValueAtPath(value, p);
1577
- if (JSON.stringify(curValue) === JSON.stringify(newValue)) {
1578
- delete pending[key];
1579
- didChangeMetadata = true;
1580
- }
1581
- else {
1582
- value = setAtPath(value, p, t, v, 'merge', obs$.peek(), (path, value) => {
1583
- delete pending[key];
1584
- pending[path.join('/')] = {
1585
- p: null,
1586
- v: value,
1587
- t: t.slice(0, path.length),
1588
- };
1589
- });
1590
- }
1591
- }
1592
- });
1593
- if (didChangeMetadata) {
1594
- updateMetadata(obs$, localState, syncState, syncOptions, {
1595
- pending,
1596
- });
1597
- }
1598
- }
1599
- onChangeRemote(() => {
1600
- if (mode === 'assign' && isObject(value)) {
1601
- obs$.assign(value);
1602
- }
1603
- else if (mode === 'append' && isArray(value)) {
1604
- obs$.push(...value);
1605
- }
1606
- else if (mode === 'prepend' && isArray(value)) {
1607
- obs$.splice(0, 0, ...value);
1608
- }
1609
- else if (mode === 'merge') {
1610
- mergeIntoObservable(obs$, value);
1611
- }
1612
- else {
1613
- obs$.set(value);
1614
- }
1615
- });
1616
- }
1617
- if (lastSync && syncOptions.persist) {
1618
- updateMetadata(obs$, localState, syncState, syncOptions, {
1619
- lastSync,
1619
+ if (isFirstLoad && syncOptions.subscribe) {
1620
+ syncOptions.subscribe({
1621
+ node,
1622
+ update: (params) => {
1623
+ params.mode || (params.mode = syncOptions.mode || 'merge');
1624
+ onChange(params);
1625
+ },
1626
+ refresh: sync,
1620
1627
  });
1621
1628
  }
1622
1629
  },
1630
+ onChange,
1623
1631
  });
1624
1632
  };
1625
1633
  runGet();
@@ -1689,7 +1697,7 @@ function enableActivateSyncedNode() {
1689
1697
  const obs$ = getProxy(node);
1690
1698
  if (node.activationState) {
1691
1699
  // If it is a Synced
1692
- const { get, initial, set, subscribe } = node.activationState;
1700
+ const { get, initial, set } = node.activationState;
1693
1701
  let onChange = undefined;
1694
1702
  const pluginRemote = {};
1695
1703
  let promiseReturn = undefined;
@@ -1701,16 +1709,18 @@ function enableActivateSyncedNode() {
1701
1709
  pluginRemote.get = (params) => {
1702
1710
  onChange = params.onChange;
1703
1711
  const updateLastSync = (lastSync) => (params.lastSync = lastSync);
1704
- const setMode = (mode) => (params.mode = mode);
1705
1712
  const existingValue = getNodeValue(node);
1706
1713
  const value = runWithRetry(node, { attemptNum: 0 }, () => {
1707
- return get({
1714
+ const paramsToGet = {
1708
1715
  value: isFunction(existingValue) || (existingValue === null || existingValue === void 0 ? void 0 : existingValue[symbolLinked$1]) ? undefined : existingValue,
1709
1716
  lastSync: params.lastSync,
1710
1717
  updateLastSync,
1711
- setMode,
1718
+ mode: params.mode,
1712
1719
  refresh,
1713
- });
1720
+ };
1721
+ const ret = get(paramsToGet);
1722
+ params.mode = paramsToGet.mode;
1723
+ return ret;
1714
1724
  });
1715
1725
  promiseReturn = value;
1716
1726
  return value;
@@ -1762,23 +1772,6 @@ function enableActivateSyncedNode() {
1762
1772
  setNodeValue(node, promiseReturn ? undefined : newValue);
1763
1773
  // @ts-expect-error TODO fix these types
1764
1774
  syncState = syncObservable(obs$, { ...node.activationState, ...pluginRemote });
1765
- if (subscribe) {
1766
- when(promiseReturn || true, () => {
1767
- subscribe({
1768
- node,
1769
- update: (params) => {
1770
- if (!onChange) {
1771
- // TODO: Make this message better
1772
- console.log('[legend-state] Cannot update immediately before the first return');
1773
- }
1774
- else {
1775
- onChange(params);
1776
- }
1777
- },
1778
- refresh,
1779
- });
1780
- });
1781
- }
1782
1775
  return { update: onChange, value: newValue };
1783
1776
  }
1784
1777
  else {