@mushi-mushi/core 1.17.0 → 1.18.0

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/dist/index.cjs CHANGED
@@ -842,6 +842,8 @@ var LS_KEY = "mushi_offline_queue";
842
842
  var BATCH_SIZE = 10;
843
843
  var MAX_BACKOFF_MS = 6e4;
844
844
  var AUTO_FLUSH_INTERVAL_MS = 3e4;
845
+ var MAX_DELIVERY_ATTEMPTS = 8;
846
+ var MAX_QUEUE_AGE_MS = 24 * 60 * 60 * 1e3;
845
847
  function createOfflineQueue(config = {}) {
846
848
  const { enabled = true, maxQueueSize = 50, syncOnReconnect = true, encryptAtRest = true } = config;
847
849
  let syncCleanup = null;
@@ -874,6 +876,17 @@ function createOfflineQueue(config = {}) {
874
876
  function isEncryptedRecord(row) {
875
877
  return !!row.payload && isEncryptedPayload(row.payload);
876
878
  }
879
+ function rowAttempts(row) {
880
+ const n = row.attempts;
881
+ return typeof n === "number" && Number.isFinite(n) ? n : 0;
882
+ }
883
+ function isExpired(row, now) {
884
+ const queuedAt = row.queuedAt;
885
+ if (!queuedAt) return false;
886
+ const ts = Date.parse(queuedAt);
887
+ if (Number.isNaN(ts)) return false;
888
+ return now - ts > MAX_QUEUE_AGE_MS;
889
+ }
877
890
  function detectBackend() {
878
891
  if (backendType) return backendType;
879
892
  if (typeof indexedDB !== "undefined") {
@@ -929,6 +942,15 @@ function createOfflineQueue(config = {}) {
929
942
  tx.onerror = () => reject(tx.error);
930
943
  });
931
944
  }
945
+ async function idbPutRow(row) {
946
+ const db = await openDb();
947
+ return new Promise((resolve, reject) => {
948
+ const tx = db.transaction(STORE_NAME, "readwrite");
949
+ tx.objectStore(STORE_NAME).put(row);
950
+ tx.oncomplete = () => resolve();
951
+ tx.onerror = () => reject(tx.error);
952
+ });
953
+ }
932
954
  async function idbSize() {
933
955
  const db = await openDb();
934
956
  return new Promise((resolve, reject) => {
@@ -970,6 +992,14 @@ function createOfflineQueue(config = {}) {
970
992
  const rows = lsRead().filter((r) => r.id !== id);
971
993
  lsWrite(rows);
972
994
  }
995
+ function lsUpdateRow(row) {
996
+ const rows = lsRead();
997
+ const idx = rows.findIndex((r) => r.id === row.id);
998
+ if (idx >= 0) {
999
+ rows[idx] = row;
1000
+ lsWrite(rows);
1001
+ }
1002
+ }
973
1003
  async function enqueue(report) {
974
1004
  if (!enabled) return;
975
1005
  const currentSize = await size();
@@ -1010,6 +1040,23 @@ function createOfflineQueue(config = {}) {
1010
1040
  } else {
1011
1041
  rows = lsRead();
1012
1042
  }
1043
+ const now = Date.now();
1044
+ const fresh = [];
1045
+ for (const row of rows) {
1046
+ if (isExpired(row, now)) {
1047
+ const rowId = row.id;
1048
+ try {
1049
+ if (backend === "indexeddb") await idbDelete(rowId);
1050
+ else lsDelete(rowId);
1051
+ } catch {
1052
+ lsDelete(rowId);
1053
+ }
1054
+ queueLog.debug("Offline queue: evicting stale report", { id: rowId });
1055
+ } else {
1056
+ fresh.push(row);
1057
+ }
1058
+ }
1059
+ rows = fresh;
1013
1060
  const batch = rows.slice(0, BATCH_SIZE);
1014
1061
  let sent = 0;
1015
1062
  let failed = 0;
@@ -1053,10 +1100,33 @@ function createOfflineQueue(config = {}) {
1053
1100
  lsDelete(rowId);
1054
1101
  }
1055
1102
  } else if (transient) {
1056
- queueLog.debug("Offline queue: transient failure, will retry", {
1057
- id: rowId,
1058
- code: result.error?.code
1059
- });
1103
+ const nextAttempts = rowAttempts(row) + 1;
1104
+ if (nextAttempts >= MAX_DELIVERY_ATTEMPTS) {
1105
+ try {
1106
+ if (backend === "indexeddb") await idbDelete(rowId);
1107
+ else lsDelete(rowId);
1108
+ } catch {
1109
+ lsDelete(rowId);
1110
+ }
1111
+ queueLog.warn("Offline queue: giving up on report after repeated failures", {
1112
+ id: rowId,
1113
+ attempts: nextAttempts,
1114
+ code: result.error?.code
1115
+ });
1116
+ } else {
1117
+ row.attempts = nextAttempts;
1118
+ try {
1119
+ if (backend === "indexeddb") await idbPutRow(row);
1120
+ else lsUpdateRow(row);
1121
+ } catch {
1122
+ lsUpdateRow(row);
1123
+ }
1124
+ queueLog.debug("Offline queue: transient failure, will retry", {
1125
+ id: rowId,
1126
+ attempts: nextAttempts,
1127
+ code: result.error?.code
1128
+ });
1129
+ }
1060
1130
  }
1061
1131
  failed++;
1062
1132
  if (i < batch.length - 1) {