@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.js CHANGED
@@ -136,19 +136,14 @@ if (process.env.NODE_ENV === 'development') {
136
136
  };
137
137
  }
138
138
 
139
- function removeNullUndefined(val) {
140
- if (val) {
141
- Object.keys(val).forEach((key) => {
142
- const v = val[key];
143
- if (v === null || v === undefined) {
144
- delete val[key];
145
- }
146
- else if (state.isObject(v)) {
147
- removeNullUndefined(v);
148
- }
149
- });
150
- }
151
- return val;
139
+ function removeNullUndefined(a, recursive) {
140
+ const out = {};
141
+ Object.keys(a).forEach((key) => {
142
+ if (a[key] !== null && a[key] !== undefined) {
143
+ out[key] = recursive && state.isObject(a[key]) ? removeNullUndefined(a[key]) : a[key];
144
+ }
145
+ });
146
+ return out;
152
147
  }
153
148
 
154
149
  function observablePersistRemoteFunctionsAdapter({ get, set, }) {
@@ -1279,8 +1274,7 @@ async function doChangeLocal(changeInfo) {
1279
1274
  const { pluginPersist } = localState;
1280
1275
  const persist = syncOptions.persist;
1281
1276
  const { table, config: configLocal } = parseLocalConfig(persist);
1282
- const configRemote = syncOptions;
1283
- const shouldSaveMetadata = persist && (configRemote === null || configRemote === void 0 ? void 0 : configRemote.offlineBehavior) === 'retry';
1277
+ const shouldSaveMetadata = persist === null || persist === void 0 ? void 0 : persist.retrySync;
1284
1278
  if (saveRemote && shouldSaveMetadata) {
1285
1279
  // First save pending changes before saving local or remote
1286
1280
  await updateMetadataImmediate(obs, localState, syncState, syncOptions, {
@@ -1311,8 +1305,8 @@ async function doChangeRemote(changeInfo) {
1311
1305
  const { pluginPersist, pluginSync } = localState;
1312
1306
  const persist = syncOptions.persist;
1313
1307
  const { table, config: configLocal } = parseLocalConfig(persist);
1314
- const { offlineBehavior, allowSetIfGetError, onBeforeSet, onSetError, waitForSet, onAfterSet } = syncOptions || {};
1315
- const shouldSaveMetadata = persist && offlineBehavior === 'retry';
1308
+ const { allowSetIfGetError, onBeforeSet, onSetError, waitForSet, onAfterSet } = syncOptions || {};
1309
+ const shouldSaveMetadata = persist === null || persist === void 0 ? void 0 : persist.retrySync;
1316
1310
  if (changesRemote.length > 0) {
1317
1311
  // Wait for remote to be ready before saving
1318
1312
  await state.when(() => syncState.isLoaded.get() || (allowSetIfGetError && syncState.error.get()));
@@ -1351,11 +1345,11 @@ async function doChangeRemote(changeInfo) {
1351
1345
  const pathStrs = Array.from(new Set(changesRemote.map((change) => change.pathStr)));
1352
1346
  const { changes, lastSync } = saved;
1353
1347
  if (pathStrs.length > 0) {
1348
+ let transformedChanges = undefined;
1349
+ const metadata = {};
1354
1350
  if (persist) {
1355
- const metadata = {};
1356
1351
  const pendingMetadata = (_b = pluginPersist.getMetadata(table, configLocal)) === null || _b === void 0 ? void 0 : _b.pending;
1357
1352
  const pending = localState.pendingChanges;
1358
- let transformedChanges = undefined;
1359
1353
  for (let i = 0; i < pathStrs.length; i++) {
1360
1354
  const pathStr = pathStrs[i];
1361
1355
  // Clear pending for this path
@@ -1374,32 +1368,34 @@ async function doChangeRemote(changeInfo) {
1374
1368
  if (lastSync) {
1375
1369
  metadata.lastSync = lastSync;
1376
1370
  }
1377
- // Remote can optionally have data that needs to be merged back into the observable,
1378
- // for example Firebase may update dateModified with the server timestamp
1379
- if (changes && !state.isEmpty(changes)) {
1380
- transformedChanges = transformLoadData(changes, syncOptions, false);
1381
- }
1382
- if (localState.numSavesOutstanding > 0) {
1383
- if (transformedChanges) {
1384
- if (!localState.pendingSaveResults) {
1385
- localState.pendingSaveResults = [];
1386
- }
1387
- localState.pendingSaveResults.push(transformedChanges);
1371
+ }
1372
+ // Remote can optionally have data that needs to be merged back into the observable,
1373
+ // for example Firebase may update dateModified with the server timestamp
1374
+ if (changes && !state.isEmpty(changes)) {
1375
+ transformedChanges = transformLoadData(changes, syncOptions, false);
1376
+ }
1377
+ if (localState.numSavesOutstanding > 0) {
1378
+ if (transformedChanges) {
1379
+ if (!localState.pendingSaveResults) {
1380
+ localState.pendingSaveResults = [];
1388
1381
  }
1382
+ localState.pendingSaveResults.push(transformedChanges);
1389
1383
  }
1390
- else {
1391
- let allChanges = [...(localState.pendingSaveResults || []), transformedChanges].filter((v) => v !== undefined);
1392
- if (allChanges.length > 0) {
1393
- if (allChanges.some((change) => state.isPromise(change))) {
1394
- allChanges = await Promise.all(allChanges);
1395
- }
1396
- onChangeRemote(() => state.mergeIntoObservable(obs, ...allChanges));
1384
+ }
1385
+ else {
1386
+ let allChanges = [...(localState.pendingSaveResults || []), transformedChanges].filter((v) => v !== undefined);
1387
+ if (allChanges.length > 0) {
1388
+ if (allChanges.some((change) => state.isPromise(change))) {
1389
+ allChanges = await Promise.all(allChanges);
1397
1390
  }
1391
+ onChangeRemote(() => state.mergeIntoObservable(obs, ...allChanges));
1392
+ }
1393
+ if (persist) {
1398
1394
  if (shouldSaveMetadata && !state.isEmpty(metadata)) {
1399
1395
  updateMetadata(obs, localState, syncState, syncOptions, metadata);
1400
1396
  }
1401
- localState.pendingSaveResults = [];
1402
1397
  }
1398
+ localState.pendingSaveResults = [];
1403
1399
  }
1404
1400
  onAfterSet === null || onAfterSet === void 0 ? void 0 : onAfterSet();
1405
1401
  }
@@ -1535,6 +1531,77 @@ function syncObservable(obs$, syncOptionsOrSynced) {
1535
1531
  const get = (_b = localState.pluginSync.get) === null || _b === void 0 ? void 0 : _b.bind(localState.pluginSync);
1536
1532
  if (get) {
1537
1533
  const runGet = () => {
1534
+ const onChange = async ({ value, mode, lastSync }) => {
1535
+ mode = mode || syncOptions.mode || 'set';
1536
+ if (value !== undefined) {
1537
+ value = transformLoadData(value, syncOptions, true);
1538
+ if (state.isPromise(value)) {
1539
+ value = await value;
1540
+ }
1541
+ const pending = localState.pendingChanges;
1542
+ const currentValue = obs$.peek();
1543
+ if (pending) {
1544
+ let didChangeMetadata = false;
1545
+ Object.keys(pending).forEach((key) => {
1546
+ const p = key.split('/').filter((p) => p !== '');
1547
+ const { v, t } = pending[key];
1548
+ if (t.length === 0 || !value) {
1549
+ if (state.isObject(value) && state.isObject(v)) {
1550
+ Object.assign(value, v);
1551
+ }
1552
+ else {
1553
+ value = v;
1554
+ }
1555
+ }
1556
+ else if (value[p[0]] !== undefined) {
1557
+ const curValue = getValueAtPath(currentValue, p);
1558
+ const newValue = getValueAtPath(value, p);
1559
+ if (JSON.stringify(curValue) === JSON.stringify(newValue)) {
1560
+ delete pending[key];
1561
+ didChangeMetadata = true;
1562
+ }
1563
+ else {
1564
+ value = state.setAtPath(value, p, t, v, 'merge', obs$.peek(), (path, value) => {
1565
+ delete pending[key];
1566
+ pending[path.join('/')] = {
1567
+ p: null,
1568
+ v: value,
1569
+ t: t.slice(0, path.length),
1570
+ };
1571
+ });
1572
+ }
1573
+ }
1574
+ });
1575
+ if (didChangeMetadata) {
1576
+ updateMetadata(obs$, localState, syncState, syncOptions, {
1577
+ pending,
1578
+ });
1579
+ }
1580
+ }
1581
+ onChangeRemote(() => {
1582
+ if (mode === 'assign' && state.isObject(value)) {
1583
+ obs$.assign(value);
1584
+ }
1585
+ else if (mode === 'append' && state.isArray(value)) {
1586
+ obs$.push(...value);
1587
+ }
1588
+ else if (mode === 'prepend' && state.isArray(value)) {
1589
+ obs$.splice(0, 0, ...value);
1590
+ }
1591
+ else if (mode === 'merge') {
1592
+ state.mergeIntoObservable(obs$, value);
1593
+ }
1594
+ else {
1595
+ obs$.set(value);
1596
+ }
1597
+ });
1598
+ }
1599
+ if (lastSync && syncOptions.persist) {
1600
+ updateMetadata(obs$, localState, syncState, syncOptions, {
1601
+ lastSync,
1602
+ });
1603
+ }
1604
+ };
1538
1605
  get({
1539
1606
  state: syncState,
1540
1607
  obs: obs$,
@@ -1546,82 +1613,23 @@ function syncObservable(obs$, syncOptionsOrSynced) {
1546
1613
  (_a = syncOptions.onGetError) === null || _a === void 0 ? void 0 : _a.call(syncOptions, error);
1547
1614
  },
1548
1615
  onGet: () => {
1616
+ const isFirstLoad = !node.state.isLoaded.peek();
1549
1617
  node.state.assign({
1550
1618
  isLoaded: true,
1551
1619
  error: undefined,
1552
1620
  });
1553
- },
1554
- onChange: async ({ value, mode, lastSync }) => {
1555
- mode = mode || syncOptions.mode || 'set';
1556
- if (value !== undefined) {
1557
- value = transformLoadData(value, syncOptions, true);
1558
- if (state.isPromise(value)) {
1559
- value = await value;
1560
- }
1561
- const pending = localState.pendingChanges;
1562
- const currentValue = obs$.peek();
1563
- if (pending) {
1564
- let didChangeMetadata = false;
1565
- Object.keys(pending).forEach((key) => {
1566
- const p = key.split('/').filter((p) => p !== '');
1567
- const { v, t } = pending[key];
1568
- if (t.length === 0 || !value) {
1569
- if (state.isObject(value) && state.isObject(v)) {
1570
- Object.assign(value, v);
1571
- }
1572
- else {
1573
- value = v;
1574
- }
1575
- }
1576
- else if (value[p[0]] !== undefined) {
1577
- const curValue = getValueAtPath(currentValue, p);
1578
- const newValue = getValueAtPath(value, p);
1579
- if (JSON.stringify(curValue) === JSON.stringify(newValue)) {
1580
- delete pending[key];
1581
- didChangeMetadata = true;
1582
- }
1583
- else {
1584
- value = state.setAtPath(value, p, t, v, 'merge', obs$.peek(), (path, value) => {
1585
- delete pending[key];
1586
- pending[path.join('/')] = {
1587
- p: null,
1588
- v: value,
1589
- t: t.slice(0, path.length),
1590
- };
1591
- });
1592
- }
1593
- }
1594
- });
1595
- if (didChangeMetadata) {
1596
- updateMetadata(obs$, localState, syncState, syncOptions, {
1597
- pending,
1598
- });
1599
- }
1600
- }
1601
- onChangeRemote(() => {
1602
- if (mode === 'assign' && state.isObject(value)) {
1603
- obs$.assign(value);
1604
- }
1605
- else if (mode === 'append' && state.isArray(value)) {
1606
- obs$.push(...value);
1607
- }
1608
- else if (mode === 'prepend' && state.isArray(value)) {
1609
- obs$.splice(0, 0, ...value);
1610
- }
1611
- else if (mode === 'merge') {
1612
- state.mergeIntoObservable(obs$, value);
1613
- }
1614
- else {
1615
- obs$.set(value);
1616
- }
1617
- });
1618
- }
1619
- if (lastSync && syncOptions.persist) {
1620
- updateMetadata(obs$, localState, syncState, syncOptions, {
1621
- lastSync,
1621
+ if (isFirstLoad && syncOptions.subscribe) {
1622
+ syncOptions.subscribe({
1623
+ node,
1624
+ update: (params) => {
1625
+ params.mode || (params.mode = syncOptions.mode || 'merge');
1626
+ onChange(params);
1627
+ },
1628
+ refresh: sync,
1622
1629
  });
1623
1630
  }
1624
1631
  },
1632
+ onChange,
1625
1633
  });
1626
1634
  };
1627
1635
  runGet();
@@ -1691,7 +1699,7 @@ function enableActivateSyncedNode() {
1691
1699
  const obs$ = getProxy(node);
1692
1700
  if (node.activationState) {
1693
1701
  // If it is a Synced
1694
- const { get, initial, set, subscribe } = node.activationState;
1702
+ const { get, initial, set } = node.activationState;
1695
1703
  let onChange = undefined;
1696
1704
  const pluginRemote = {};
1697
1705
  let promiseReturn = undefined;
@@ -1703,16 +1711,18 @@ function enableActivateSyncedNode() {
1703
1711
  pluginRemote.get = (params) => {
1704
1712
  onChange = params.onChange;
1705
1713
  const updateLastSync = (lastSync) => (params.lastSync = lastSync);
1706
- const setMode = (mode) => (params.mode = mode);
1707
1714
  const existingValue = getNodeValue(node);
1708
1715
  const value = runWithRetry(node, { attemptNum: 0 }, () => {
1709
- return get({
1716
+ const paramsToGet = {
1710
1717
  value: state.isFunction(existingValue) || (existingValue === null || existingValue === void 0 ? void 0 : existingValue[symbolLinked$1]) ? undefined : existingValue,
1711
1718
  lastSync: params.lastSync,
1712
1719
  updateLastSync,
1713
- setMode,
1720
+ mode: params.mode,
1714
1721
  refresh,
1715
- });
1722
+ };
1723
+ const ret = get(paramsToGet);
1724
+ params.mode = paramsToGet.mode;
1725
+ return ret;
1716
1726
  });
1717
1727
  promiseReturn = value;
1718
1728
  return value;
@@ -1764,23 +1774,6 @@ function enableActivateSyncedNode() {
1764
1774
  setNodeValue(node, promiseReturn ? undefined : newValue);
1765
1775
  // @ts-expect-error TODO fix these types
1766
1776
  syncState = syncObservable(obs$, { ...node.activationState, ...pluginRemote });
1767
- if (subscribe) {
1768
- state.when(promiseReturn || true, () => {
1769
- subscribe({
1770
- node,
1771
- update: (params) => {
1772
- if (!onChange) {
1773
- // TODO: Make this message better
1774
- console.log('[legend-state] Cannot update immediately before the first return');
1775
- }
1776
- else {
1777
- onChange(params);
1778
- }
1779
- },
1780
- refresh,
1781
- });
1782
- });
1783
- }
1784
1777
  return { update: onChange, value: newValue };
1785
1778
  }
1786
1779
  else {