@anfenn/dync 1.0.33 → 1.1.1

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/README.md CHANGED
@@ -45,7 +45,7 @@ And see how Dync compares to the alternatives [below](#hasnt-this-already-been-d
45
45
  ```
46
46
 
47
47
  - Sync some or all tables with any backend in 2 ways:
48
- - Option 1: Map remote api CRUD urls to a local collection:
48
+ - Option 1: Map remote api CRUD urls to a local table:
49
49
 
50
50
  ```ts
51
51
  const db = new Dync({
@@ -58,6 +58,8 @@ And see how Dync compares to the alternatives [below](#hasnt-this-already-been-d
58
58
  update: (id, changes) => fetch(`/api/items/${id}`),
59
59
  remove: (id) => fetch(`/api/items/${id}`),
60
60
  list: (since) => fetch(`/api/items?since=${since}`),
61
+ // Optional: Delay calling this endpoint during a pull if slow changing data, to reduce server load
62
+ listExtraIntervalMs: 7 * 24 * 60 * 60 * 1000, // 1 week
61
63
  },
62
64
  },
63
65
  });
package/dist/index.cjs CHANGED
@@ -169,7 +169,7 @@ var SYNC_STATE_KEY = "sync_state";
169
169
  var DEFAULT_STATE = {
170
170
  firstLoadDone: false,
171
171
  pendingChanges: [],
172
- lastPulled: {}
172
+ newestServerUpdatedAt: {}
173
173
  };
174
174
  var StateManager = class {
175
175
  persistedState;
@@ -339,7 +339,8 @@ function clonePersistedState(state) {
339
339
  before: cloneRecord(change.before),
340
340
  after: cloneRecord(change.after)
341
341
  })),
342
- lastPulled: { ...state.lastPulled },
342
+ newestServerUpdatedAt: { ...state.newestServerUpdatedAt },
343
+ lastPulledAt: state.lastPulledAt ? { ...state.lastPulledAt } : void 0,
343
344
  conflicts: cloneConflicts(state.conflicts)
344
345
  };
345
346
  }
@@ -738,12 +739,25 @@ async function pullAll(ctx) {
738
739
  const changedTables = [];
739
740
  for (const [tableName, api] of Object.entries(ctx.syncApis)) {
740
741
  try {
741
- const lastPulled = ctx.state.getState().lastPulled[tableName];
742
- const since = lastPulled ? new Date(lastPulled) : /* @__PURE__ */ new Date(0);
742
+ const now = Date.now();
743
+ if (beforeListExtraInterval(api, tableName, ctx, now)) {
744
+ continue;
745
+ }
746
+ const newestServerUpdatedAt = ctx.state.getState().newestServerUpdatedAt[tableName];
747
+ const since = newestServerUpdatedAt ? new Date(newestServerUpdatedAt) : /* @__PURE__ */ new Date(0);
743
748
  ctx.logger.debug(`[dync] pull:start tableName=${tableName} since=${since.toISOString()}`);
744
749
  const serverData = await api.list(since);
745
750
  const changed = await processPullData(tableName, serverData, since, ctx);
746
751
  if (changed) changedTables.push(tableName);
752
+ if (hasListExtraInterval(api)) {
753
+ await ctx.state.setState((syncState) => ({
754
+ ...syncState,
755
+ lastPulledAt: {
756
+ ...syncState.lastPulledAt,
757
+ [tableName]: now
758
+ }
759
+ }));
760
+ }
747
761
  } catch (err) {
748
762
  firstSyncError = firstSyncError ?? err;
749
763
  ctx.logger.error(`[dync] pull:error tableName=${tableName}`, err);
@@ -751,6 +765,22 @@ async function pullAll(ctx) {
751
765
  }
752
766
  return { error: firstSyncError, changedTables };
753
767
  }
768
+ function hasListExtraInterval(api) {
769
+ return Number.isFinite(api.listExtraIntervalMs) && api.listExtraIntervalMs > 0;
770
+ }
771
+ function beforeListExtraInterval(api, tableName, ctx, now) {
772
+ if (!hasListExtraInterval(api)) {
773
+ return false;
774
+ }
775
+ const lastPulledAt = ctx.state.getState().lastPulledAt?.[tableName] ?? 0;
776
+ if (now - lastPulledAt < api.listExtraIntervalMs) {
777
+ ctx.logger.debug(
778
+ `[dync] pull:skip-interval tableName=${tableName} lastPulledAt=${new Date(lastPulledAt).toISOString()} nextAllowed=${new Date(lastPulledAt + api.listExtraIntervalMs).toISOString()}`
779
+ );
780
+ return true;
781
+ }
782
+ return false;
783
+ }
754
784
  async function handleRemoteItemUpdate(table, tableName, localItem, remote, ctx) {
755
785
  const pendingChange = ctx.state.getState().pendingChanges.find((p) => p.tableName === tableName && p.localId === localItem._localId);
756
786
  const conflictStrategy = ctx.conflictResolutionStrategy;
@@ -807,8 +837,8 @@ async function pullAllBatch(ctx) {
807
837
  try {
808
838
  const sinceMap = {};
809
839
  for (const tableName of ctx.batchSync.syncTables) {
810
- const lastPulled = ctx.state.getState().lastPulled[tableName];
811
- sinceMap[tableName] = lastPulled ? new Date(lastPulled) : /* @__PURE__ */ new Date(0);
840
+ const newestServerUpdatedAt = ctx.state.getState().newestServerUpdatedAt[tableName];
841
+ sinceMap[tableName] = newestServerUpdatedAt ? new Date(newestServerUpdatedAt) : /* @__PURE__ */ new Date(0);
812
842
  }
813
843
  ctx.logger.debug(`[dync] pull:batch:start tables=${[...ctx.batchSync.syncTables].join(",")}`, sinceMap);
814
844
  const serverDataByTable = await ctx.batchSync.pull(sinceMap);
@@ -870,8 +900,8 @@ async function processPullData(tableName, serverData, since, ctx) {
870
900
  }
871
901
  await ctx.state.setState((syncState) => ({
872
902
  ...syncState,
873
- lastPulled: {
874
- ...syncState.lastPulled,
903
+ newestServerUpdatedAt: {
904
+ ...syncState.newestServerUpdatedAt,
875
905
  [tableName]: newest.toISOString()
876
906
  }
877
907
  }));
@@ -1153,7 +1183,7 @@ async function startFirstLoad(ctx) {
1153
1183
  ctx.logger.debug("[dync] First load completed");
1154
1184
  }
1155
1185
  async function processBatchInChunks(ctx, tableName, batch, isEmptyTable, isFirstBatch) {
1156
- let newest = new Date(ctx.state.getState().lastPulled[tableName] || 0);
1186
+ let newest = new Date(ctx.state.getState().newestServerUpdatedAt[tableName] || 0);
1157
1187
  return ctx.withTransaction("rw", [tableName, DYNC_STATE_TABLE], async (tables) => {
1158
1188
  const txTable = tables[tableName];
1159
1189
  let tableIsEmpty = isEmptyTable;
@@ -1188,8 +1218,8 @@ async function processBatchInChunks(ctx, tableName, batch, isEmptyTable, isFirst
1188
1218
  }
1189
1219
  await ctx.state.setState((syncState) => ({
1190
1220
  ...syncState,
1191
- lastPulled: {
1192
- ...syncState.lastPulled,
1221
+ newestServerUpdatedAt: {
1222
+ ...syncState.newestServerUpdatedAt,
1193
1223
  [tableName]: newest.toISOString()
1194
1224
  }
1195
1225
  }));
@@ -1363,7 +1393,7 @@ var DyncBase = class {
1363
1393
  this.adapter = storageAdapter;
1364
1394
  this.name = databaseName;
1365
1395
  this.syncOptions = {
1366
- syncInterval: DEFAULT_SYNC_INTERVAL_MILLIS,
1396
+ syncIntervalMs: DEFAULT_SYNC_INTERVAL_MILLIS,
1367
1397
  logger: DEFAULT_LOGGER,
1368
1398
  minLogLevel: DEFAULT_MIN_LOG_LEVEL,
1369
1399
  missingRemoteRecordDuringUpdateStrategy: DEFAULT_MISSING_REMOTE_RECORD_STRATEGY,
@@ -1614,7 +1644,8 @@ var DyncBase = class {
1614
1644
  }
1615
1645
  return pullAll({
1616
1646
  ...baseContext,
1617
- syncApis: this.syncApis
1647
+ syncApis: this.syncApis,
1648
+ syncIntervalMs: this.syncOptions.syncIntervalMs
1618
1649
  });
1619
1650
  }
1620
1651
  async pushAll() {
@@ -1649,7 +1680,7 @@ var DyncBase = class {
1649
1680
  while (this.syncTimerStarted) {
1650
1681
  this.sleepAbortController = new AbortController();
1651
1682
  await this.syncOnce();
1652
- await sleep(this.syncOptions.syncInterval, this.sleepAbortController.signal);
1683
+ await sleep(this.syncOptions.syncIntervalMs, this.sleepAbortController.signal);
1653
1684
  }
1654
1685
  this.syncStatus = "disabled";
1655
1686
  this.disableSyncPromiseResolver?.();