@drakkar.software/starfish-client 3.0.0-alpha.26 → 3.0.0-alpha.28

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.
@@ -79,6 +79,20 @@ export interface CreateStarfishStoreOptions {
79
79
  * ```
80
80
  */
81
81
  onRemoteUpdate?: (data: Record<string, unknown>) => void;
82
+ /**
83
+ * Auto re-attempt a failed flush with exponential backoff while the store
84
+ * stays dirty + online. Omit to keep the current no-retry behavior.
85
+ *
86
+ * Defaults when the option is present: `maxRetries: 5`, `initialDelayMs: 500`,
87
+ * `maxDelayMs: 30_000`. Backoff is `min(initial * 2^attempt, max) + jitter(100ms)`.
88
+ * A successful flush resets the counter. Going offline cancels any pending retry.
89
+ * `AbortError`s are never retried.
90
+ */
91
+ flushRetry?: {
92
+ maxRetries?: number;
93
+ initialDelayMs?: number;
94
+ maxDelayMs?: number;
95
+ };
82
96
  }
83
97
  export type { DevtoolsOptions };
84
98
  export declare function createStarfishStore(options: CreateStarfishStoreOptions): StoreApi<StarfishStore>;
@@ -679,7 +679,20 @@ var StarfishClient = class {
679
679
  if (!res.ok) {
680
680
  throw new StarfishHttpError(res.status, await res.text());
681
681
  }
682
- return res.json();
682
+ const result = await res.json();
683
+ if (this.cache) {
684
+ const pullPath = sendPath.replace("/push/", "/pull/");
685
+ const cacheKey = pullCacheKey(pullPath);
686
+ const snapshot = {
687
+ data,
688
+ hash: result.hash,
689
+ timestamp: result.timestamp,
690
+ cachedAt: Date.now()
691
+ };
692
+ void this.cache.set(cacheKey, JSON.stringify(snapshot)).catch(() => {
693
+ });
694
+ }
695
+ return result;
683
696
  }
684
697
  /**
685
698
  * Append an element to an appendOnly (`by_timestamp`) collection.
@@ -1069,6 +1082,30 @@ function createStarfishStore(options) {
1069
1082
  const { name, syncManager, storage } = options;
1070
1083
  const storeCreator = (rawSet, get) => {
1071
1084
  const set = rawSet;
1085
+ let retryTimer;
1086
+ let retryAttempt = 0;
1087
+ const scheduleFlushRetry = () => {
1088
+ const retryOpts = options.flushRetry;
1089
+ if (!retryOpts) return;
1090
+ const maxRetries = retryOpts.maxRetries ?? 5;
1091
+ if (retryAttempt >= maxRetries) return;
1092
+ const initialMs = retryOpts.initialDelayMs ?? 500;
1093
+ const maxMs = retryOpts.maxDelayMs ?? 3e4;
1094
+ const delayMs = Math.min(initialMs * Math.pow(2, retryAttempt), maxMs) + Math.random() * 100;
1095
+ retryAttempt++;
1096
+ clearTimeout(retryTimer);
1097
+ retryTimer = setTimeout(() => {
1098
+ if (get().dirty && get().online && !get().syncing) {
1099
+ get().flush().catch(() => {
1100
+ });
1101
+ }
1102
+ }, delayMs);
1103
+ };
1104
+ const cancelFlushRetry = () => {
1105
+ clearTimeout(retryTimer);
1106
+ retryTimer = void 0;
1107
+ retryAttempt = 0;
1108
+ };
1072
1109
  return {
1073
1110
  data: {},
1074
1111
  syncing: false,
@@ -1103,6 +1140,8 @@ function createStarfishStore(options) {
1103
1140
  set: (modifier) => {
1104
1141
  try {
1105
1142
  const next = options.produce ? options.produce(get().data, modifier) : modifier(get().data);
1143
+ retryAttempt = 0;
1144
+ clearTimeout(retryTimer);
1106
1145
  set({ data: next, dirty: true, error: null }, false, "set");
1107
1146
  if (get().online) get().flush().catch(() => {
1108
1147
  });
@@ -1118,15 +1157,22 @@ function createStarfishStore(options) {
1118
1157
  set({ syncing: true, error: null }, false, "flush/start");
1119
1158
  try {
1120
1159
  await syncManager.push(get().data);
1160
+ cancelFlushRetry();
1121
1161
  set({ data: syncManager.getData(), syncing: false, dirty: false, hash: syncManager.getHash(), stale: false }, false, "flush/success");
1122
1162
  } catch (err) {
1163
+ const isAbort = err instanceof Error && (err.name === "AbortError" || typeof DOMException !== "undefined" && err instanceof DOMException && err.name === "AbortError");
1123
1164
  set({ syncing: false, error: err instanceof Error ? err.message : String(err) }, false, "flush/error");
1165
+ if (!isAbort) scheduleFlushRetry();
1124
1166
  }
1125
1167
  },
1126
1168
  setOnline: (online) => {
1127
1169
  set({ online }, false, "setOnline");
1128
- if (online && get().dirty) get().flush().catch(() => {
1129
- });
1170
+ if (online && get().dirty) {
1171
+ get().flush().catch(() => {
1172
+ });
1173
+ } else if (!online) {
1174
+ cancelFlushRetry();
1175
+ }
1130
1176
  }
1131
1177
  };
1132
1178
  };