on-zero 0.6.3 → 0.6.4

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 (82) hide show
  1. package/dist/cjs/createZeroClient.cjs +91 -12
  2. package/dist/cjs/createZeroClient.connection.test.cjs +163 -0
  3. package/dist/cjs/createZeroClient.connection.test.native.js +242 -0
  4. package/dist/cjs/createZeroClient.connection.test.native.js.map +1 -0
  5. package/dist/cjs/createZeroClient.native.js +99 -12
  6. package/dist/cjs/createZeroClient.native.js.map +1 -1
  7. package/dist/cjs/createZeroClient.recovery.test.cjs +52 -0
  8. package/dist/cjs/createZeroClient.recovery.test.native.js +52 -0
  9. package/dist/cjs/createZeroClient.recovery.test.native.js.map +1 -1
  10. package/dist/cjs/helpers/recoverZeroClient.cjs +140 -14
  11. package/dist/cjs/helpers/recoverZeroClient.native.js +155 -22
  12. package/dist/cjs/helpers/recoverZeroClient.native.js.map +1 -1
  13. package/dist/cjs/helpers/recoverZeroClient.test.cjs +181 -0
  14. package/dist/cjs/helpers/recoverZeroClient.test.native.js +209 -0
  15. package/dist/cjs/helpers/recoverZeroClient.test.native.js.map +1 -1
  16. package/dist/cjs/index.cjs +1 -0
  17. package/dist/cjs/index.native.js +1 -0
  18. package/dist/cjs/index.native.js.map +1 -1
  19. package/dist/cjs/multi.cjs +13 -0
  20. package/dist/cjs/multi.native.js +35 -0
  21. package/dist/cjs/multi.native.js.map +1 -1
  22. package/dist/cjs/multiPartition.test.cjs +42 -0
  23. package/dist/cjs/multiPartition.test.native.js +51 -0
  24. package/dist/cjs/multiPartition.test.native.js.map +1 -0
  25. package/dist/esm/createZeroClient.connection.test.mjs +164 -0
  26. package/dist/esm/createZeroClient.connection.test.mjs.map +1 -0
  27. package/dist/esm/createZeroClient.connection.test.native.js +240 -0
  28. package/dist/esm/createZeroClient.connection.test.native.js.map +1 -0
  29. package/dist/esm/createZeroClient.mjs +92 -13
  30. package/dist/esm/createZeroClient.mjs.map +1 -1
  31. package/dist/esm/createZeroClient.native.js +100 -13
  32. package/dist/esm/createZeroClient.native.js.map +1 -1
  33. package/dist/esm/createZeroClient.recovery.test.mjs +52 -0
  34. package/dist/esm/createZeroClient.recovery.test.mjs.map +1 -1
  35. package/dist/esm/createZeroClient.recovery.test.native.js +52 -0
  36. package/dist/esm/createZeroClient.recovery.test.native.js.map +1 -1
  37. package/dist/esm/helpers/recoverZeroClient.mjs +140 -15
  38. package/dist/esm/helpers/recoverZeroClient.mjs.map +1 -1
  39. package/dist/esm/helpers/recoverZeroClient.native.js +155 -23
  40. package/dist/esm/helpers/recoverZeroClient.native.js.map +1 -1
  41. package/dist/esm/helpers/recoverZeroClient.test.mjs +182 -1
  42. package/dist/esm/helpers/recoverZeroClient.test.mjs.map +1 -1
  43. package/dist/esm/helpers/recoverZeroClient.test.native.js +210 -1
  44. package/dist/esm/helpers/recoverZeroClient.test.native.js.map +1 -1
  45. package/dist/esm/index.js +2 -2
  46. package/dist/esm/index.js.map +1 -1
  47. package/dist/esm/index.mjs +2 -2
  48. package/dist/esm/index.mjs.map +1 -1
  49. package/dist/esm/index.native.js +2 -2
  50. package/dist/esm/index.native.js.map +1 -1
  51. package/dist/esm/multi.mjs +13 -1
  52. package/dist/esm/multi.mjs.map +1 -1
  53. package/dist/esm/multi.native.js +35 -1
  54. package/dist/esm/multi.native.js.map +1 -1
  55. package/dist/esm/multiPartition.test.mjs +43 -0
  56. package/dist/esm/multiPartition.test.mjs.map +1 -0
  57. package/dist/esm/multiPartition.test.native.js +49 -0
  58. package/dist/esm/multiPartition.test.native.js.map +1 -0
  59. package/lint/ssr-guards.js +164 -0
  60. package/package.json +2 -2
  61. package/readme.md +126 -0
  62. package/src/createZeroClient.connection.test.tsx +162 -0
  63. package/src/createZeroClient.recovery.test.tsx +68 -0
  64. package/src/createZeroClient.tsx +159 -13
  65. package/src/helpers/recoverZeroClient.test.ts +204 -1
  66. package/src/helpers/recoverZeroClient.ts +263 -42
  67. package/src/index.ts +3 -0
  68. package/src/multi.ts +33 -0
  69. package/src/multiPartition.test.ts +38 -0
  70. package/types/createZeroClient.connection.test.d.ts +5 -0
  71. package/types/createZeroClient.connection.test.d.ts.map +1 -0
  72. package/types/createZeroClient.d.ts +17 -0
  73. package/types/createZeroClient.d.ts.map +1 -1
  74. package/types/createZeroServer.d.ts +4 -4
  75. package/types/helpers/recoverZeroClient.d.ts +15 -1
  76. package/types/helpers/recoverZeroClient.d.ts.map +1 -1
  77. package/types/index.d.ts +1 -1
  78. package/types/index.d.ts.map +1 -1
  79. package/types/multi.d.ts +1 -0
  80. package/types/multi.d.ts.map +1 -1
  81. package/types/multiPartition.test.d.ts +2 -0
  82. package/types/multiPartition.test.d.ts.map +1 -0
@@ -26,6 +26,7 @@ var recoverZeroClient_exports = {};
26
26
  __export(recoverZeroClient_exports, {
27
27
  classifyZeroRecoveryLog: () => classifyZeroRecoveryLog,
28
28
  composeRecoveryLogSink: () => composeRecoveryLogSink,
29
+ isRecoverableZeroStalePokeMessage: () => isRecoverableZeroStalePokeMessage,
29
30
  makeZeroRecovery: () => makeZeroRecovery,
30
31
  resetRecoveryStateForTests: () => resetRecoveryStateForTests
31
32
  });
@@ -43,6 +44,7 @@ function _type_of(obj) {
43
44
  return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj;
44
45
  }
45
46
  var RECOVER_GUARD_MS = 6e4;
47
+ var RELOAD_LATCH_TIMEOUT_MS = 15e3;
46
48
  var LOCAL_STORE_LOST = "Expected IndexedDB not found";
47
49
  var SQLITE_ERROR_NAME = "SqliteError";
48
50
  var SQLITE_STATEMENT_FINALIZED = "This statement has been finalized";
@@ -50,26 +52,83 @@ var STORE_CLOSED = "Store is closed";
50
52
  var STORE_CLOSED_REPEAT_MIN_MS = 2e3;
51
53
  var STORE_CLOSED_REPEAT_MAX_MS = 6e4;
52
54
  var reloadScheduled = false;
55
+ var reloadInProgress = false;
56
+ var reloadLatchTimer;
53
57
  var pendingDeletes = [];
54
- function recoveryGuardOpen(reasonKey) {
58
+ var inMemoryGuard = /* @__PURE__ */new Map();
59
+ function defaultGuardStorage() {
55
60
  try {
56
- var key = `on-zero-recover-${reasonKey}`;
57
- var rawLast = window.sessionStorage.getItem(key);
58
- var now = Date.now();
59
- var last = rawLast ? Number(rawLast) : 0;
60
- if (last > 0 && now - last < RECOVER_GUARD_MS) return false;
61
- window.sessionStorage.setItem(key, String(now));
62
- } catch (e) {}
61
+ if (typeof window === "undefined" || !window.sessionStorage) return void 0;
62
+ return window.sessionStorage;
63
+ } catch (e) {
64
+ return void 0;
65
+ }
66
+ }
67
+ function recoveryGuardOpen(reasonKey, guardStorage) {
68
+ var key = `on-zero-recover-${reasonKey}`;
69
+ var now = Date.now();
70
+ var _inMemoryGuard_get;
71
+ var memLast = (_inMemoryGuard_get = inMemoryGuard.get(key)) !== null && _inMemoryGuard_get !== void 0 ? _inMemoryGuard_get : 0;
72
+ if (memLast > 0 && now - memLast < RECOVER_GUARD_MS) return false;
73
+ var storage = guardStorage !== null && guardStorage !== void 0 ? guardStorage : defaultGuardStorage();
74
+ if (storage) {
75
+ try {
76
+ var rawLast = storage.getItem(key);
77
+ var last = rawLast ? Number(rawLast) : 0;
78
+ if (last > 0 && now - last < RECOVER_GUARD_MS) return false;
79
+ storage.setItem(key, String(now));
80
+ } catch (e) {}
81
+ }
82
+ inMemoryGuard.set(key, now);
63
83
  return true;
64
84
  }
65
85
  var lastStoreClosedAtMs = 0;
86
+ function armReloadLatchTimeout() {
87
+ if (typeof setTimeout !== "function") return;
88
+ if (reloadLatchTimer) clearTimeout(reloadLatchTimer);
89
+ reloadLatchTimer = setTimeout(function () {
90
+ reloadLatchTimer = void 0;
91
+ reloadScheduled = false;
92
+ }, RELOAD_LATCH_TIMEOUT_MS);
93
+ if (reloadLatchTimer && (typeof reloadLatchTimer === "undefined" ? "undefined" : _type_of(reloadLatchTimer)) === "object" && "unref" in reloadLatchTimer) {
94
+ reloadLatchTimer.unref();
95
+ }
96
+ }
97
+ function disarmReloadLatchTimeout() {
98
+ if (reloadLatchTimer) {
99
+ clearTimeout(reloadLatchTimer);
100
+ reloadLatchTimer = void 0;
101
+ }
102
+ }
103
+ function performReload(deps) {
104
+ if (reloadInProgress) return Promise.resolve();
105
+ reloadInProgress = true;
106
+ disarmReloadLatchTimeout();
107
+ var _deps_reload;
108
+ var doReload = (_deps_reload = deps.reload) !== null && _deps_reload !== void 0 ? _deps_reload : function () {
109
+ var _globalThis_location_reload, _globalThis_location;
110
+ return (_globalThis_location = globalThis.location) === null || _globalThis_location === void 0 ? void 0 : (_globalThis_location_reload = _globalThis_location.reload) === null || _globalThis_location_reload === void 0 ? void 0 : _globalThis_location_reload.call(_globalThis_location);
111
+ };
112
+ return Promise.resolve().then(function () {
113
+ return Promise.allSettled(pendingDeletes.splice(0).map(function (run) {
114
+ return run();
115
+ }));
116
+ }).then(function () {
117
+ var _deps_beforeReload;
118
+ return (_deps_beforeReload = deps.beforeReload) === null || _deps_beforeReload === void 0 ? void 0 : _deps_beforeReload.call(deps);
119
+ }).catch(function () {}).then(function () {
120
+ doReload();
121
+ });
122
+ }
66
123
  function recover(deps, reasonKey, message, dropLocalState) {
67
124
  if (typeof window === "undefined") return;
68
125
  if (dropLocalState) {
69
- pendingDeletes.push(Promise.resolve().then(deps.deleteLocalState).catch(function () {}));
126
+ pendingDeletes.push(function () {
127
+ return Promise.resolve().then(deps.deleteLocalState);
128
+ });
70
129
  }
71
130
  if (reloadScheduled) return;
72
- if (!recoveryGuardOpen(reasonKey)) {
131
+ if (!recoveryGuardOpen(reasonKey, deps.guardStorage)) {
73
132
  console.error(`[on-zero] ${message} \u2014 already recovered once, not reloading`);
74
133
  deps.zeroEvents.emit({
75
134
  type: "fatal",
@@ -78,22 +137,25 @@ function recover(deps, reasonKey, message, dropLocalState) {
78
137
  return;
79
138
  }
80
139
  reloadScheduled = true;
140
+ armReloadLatchTimeout();
81
141
  console.warn(`[on-zero] ${message} \u2014 recovering`);
82
142
  deps.zeroEvents.emit({
83
143
  type: "recovering",
84
144
  reason: message
85
145
  });
86
- var _deps_reload;
87
- var doReload = (_deps_reload = deps.reload) !== null && _deps_reload !== void 0 ? _deps_reload : function () {
88
- var _globalThis_location_reload, _globalThis_location;
89
- return (_globalThis_location = globalThis.location) === null || _globalThis_location === void 0 ? void 0 : (_globalThis_location_reload = _globalThis_location.reload) === null || _globalThis_location_reload === void 0 ? void 0 : _globalThis_location_reload.call(_globalThis_location);
146
+ var runReload = function () {
147
+ return performReload(deps);
90
148
  };
91
- Promise.resolve().then(function () {
92
- return Promise.allSettled(pendingDeletes);
93
- }).then(function () {
94
- var _deps_beforeReload;
95
- return (_deps_beforeReload = deps.beforeReload) === null || _deps_beforeReload === void 0 ? void 0 : _deps_beforeReload.call(deps);
96
- }).catch(function () {}).finally(doReload);
149
+ if (deps.scheduleReload) {
150
+ deps.scheduleReload({
151
+ reason: message,
152
+ reasonKey,
153
+ dropLocalState,
154
+ performReload: runReload
155
+ });
156
+ } else {
157
+ void runReload();
158
+ }
97
159
  }
98
160
  function makeZeroRecovery(deps) {
99
161
  return {
@@ -132,6 +194,58 @@ function isBenignStoreClosedLog(text) {
132
194
  if (/Mutator\s+".*"\s+app error on client/i.test(text)) return true;
133
195
  return false;
134
196
  }
197
+ function classifyMutationDesync(text) {
198
+ if (text.includes("sent mutation ID") && text.includes("but expected")) {
199
+ return {
200
+ reason: "mutation-desync",
201
+ reasonKey: "mutation-desync",
202
+ message: "mutation id desync"
203
+ };
204
+ }
205
+ if (text.includes("oooMutation") || text.includes("Server reported an out-of-order mutation")) {
206
+ return {
207
+ reason: "mutation-desync",
208
+ reasonKey: "mutation-desync",
209
+ message: "out-of-order mutation"
210
+ };
211
+ }
212
+ if (text.includes("already processed")) {
213
+ return {
214
+ reason: "mutation-desync",
215
+ reasonKey: "mutation-desync",
216
+ message: "mutation already processed"
217
+ };
218
+ }
219
+ if (text.includes("InvalidConnectionRequestLastMutationID")) {
220
+ return {
221
+ reason: "mutation-desync",
222
+ reasonKey: "mutation-desync",
223
+ message: "invalid connection last mutation id"
224
+ };
225
+ }
226
+ if (text.includes("InvalidConnectionRequestBaseCookie")) {
227
+ return {
228
+ reason: "connection-cookie-invalid",
229
+ reasonKey: "connection-cookie-invalid",
230
+ message: "invalid connection base cookie"
231
+ };
232
+ }
233
+ if (text.includes("ClientNotFound") || text.includes("Client not found")) {
234
+ return {
235
+ reason: "client-not-found",
236
+ reasonKey: "client-not-found",
237
+ message: "client not found"
238
+ };
239
+ }
240
+ if (text.includes("connection userID mismatch")) {
241
+ return {
242
+ reason: "connection-userid-mismatch",
243
+ reasonKey: "connection-userid-mismatch",
244
+ message: "connection user id mismatch"
245
+ };
246
+ }
247
+ return void 0;
248
+ }
135
249
  function classifyZeroRecoveryLog(level, args) {
136
250
  var nowMs = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : Date.now();
137
251
  if (level !== "error") return void 0;
@@ -152,6 +266,13 @@ function classifyZeroRecoveryLog(level, args) {
152
266
  dropLocalState: true
153
267
  };
154
268
  }
269
+ var desync = classifyMutationDesync(text);
270
+ if (desync) {
271
+ return {
272
+ ...desync,
273
+ dropLocalState: true
274
+ };
275
+ }
155
276
  if (text.includes(STORE_CLOSED) && !isBenignStoreClosedLog(text)) {
156
277
  var prevMs = lastStoreClosedAtMs;
157
278
  lastStoreClosedAtMs = nowMs;
@@ -175,9 +296,12 @@ function composeRecoveryLogSink(deps, consumerLogSink) {
175
296
  }
176
297
  if (consumerLogSink) consumerLogSink.log(level, context, ...args);else logToConsole(level, context, ...args);
177
298
  var recovery = classifyZeroRecoveryLog(level, args);
178
- if (recovery) {
179
- recover(deps, recovery.reasonKey, recovery.message, recovery.dropLocalState);
299
+ if (!recovery) return;
300
+ if (deps.benignLogFilter) {
301
+ var text = args.map(logArgText).join(" ");
302
+ if (deps.benignLogFilter(text)) return;
180
303
  }
304
+ recover(deps, recovery.reasonKey, recovery.message, recovery.dropLocalState);
181
305
  },
182
306
  // call through the consumer sink so a class-based sink keeps its `this`,
183
307
  // rather than handing Zero a detached method reference.
@@ -186,9 +310,18 @@ function composeRecoveryLogSink(deps, consumerLogSink) {
186
310
  } : void 0
187
311
  };
188
312
  }
313
+ function isRecoverableZeroStalePokeMessage(message) {
314
+ return message.includes("Server returned unexpected base cookie during sync") || message.includes("Received cookie") && message.includes("is < than last snapshot cookie") && message.includes("ignoring client view");
315
+ }
189
316
  function resetRecoveryStateForTests() {
190
317
  reloadScheduled = false;
318
+ reloadInProgress = false;
319
+ if (reloadLatchTimer) {
320
+ clearTimeout(reloadLatchTimer);
321
+ reloadLatchTimer = void 0;
322
+ }
191
323
  pendingDeletes.length = 0;
192
324
  lastStoreClosedAtMs = 0;
325
+ inMemoryGuard.clear();
193
326
  }
194
327
  //# sourceMappingURL=recoverZeroClient.native.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["__toCommonJS","mod","__copyProps","__defProp","value","recoverZeroClient_exports","__export","classifyZeroRecoveryLog","composeRecoveryLogSink","makeZeroRecovery","resetRecoveryStateForTests","module","exports","_instanceof","left","right","Symbol","hasInstance","_type_of","obj","constructor","RECOVER_GUARD_MS","LOCAL_STORE_LOST","SQLITE_ERROR_NAME","SQLITE_STATEMENT_FINALIZED","STORE_CLOSED","STORE_CLOSED_REPEAT_MIN_MS","STORE_CLOSED_REPEAT_MAX_MS","reloadScheduled","pendingDeletes","recoveryGuardOpen","reasonKey","key","rawLast","window","sessionStorage","getItem","now","Date","last","Number","setItem","String","e","lastStoreClosedAtMs","recover","deps","message","dropLocalState","push","Promise","resolve","then","deleteLocalState","catch","console","error","zeroEvents","emit","type","reason","warn","_deps_reload","doReload","reload","_globalThis_location_reload","_globalThis_location","globalThis","location","call","allSettled","_deps_beforeReload","beforeReload","finally","onUpdateNeeded","onClientStateNotFound","logToConsole","level","context","_len","arguments","length","args","Array","_key","prefix","Object","entries","map","param","join","method","logArgText","arg","Error","name","stack","isBenignStoreClosedLog","text","test","nowMs","includes","prevMs"],"sources":["../../../src/helpers/recoverZeroClient.ts"],"sourcesContent":[null],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["__toCommonJS","mod","__copyProps","__defProp","value","recoverZeroClient_exports","__export","classifyZeroRecoveryLog","composeRecoveryLogSink","isRecoverableZeroStalePokeMessage","makeZeroRecovery","resetRecoveryStateForTests","module","exports","_instanceof","left","right","Symbol","hasInstance","_type_of","obj","constructor","RECOVER_GUARD_MS","RELOAD_LATCH_TIMEOUT_MS","LOCAL_STORE_LOST","SQLITE_ERROR_NAME","SQLITE_STATEMENT_FINALIZED","STORE_CLOSED","STORE_CLOSED_REPEAT_MIN_MS","STORE_CLOSED_REPEAT_MAX_MS","reloadScheduled","reloadInProgress","reloadLatchTimer","pendingDeletes","inMemoryGuard","Map","defaultGuardStorage","window","sessionStorage","e","recoveryGuardOpen","reasonKey","guardStorage","key","now","Date","_inMemoryGuard_get","memLast","get","storage","rawLast","getItem","last","Number","setItem","String","set","lastStoreClosedAtMs","armReloadLatchTimeout","setTimeout","clearTimeout","unref","disarmReloadLatchTimeout","performReload","deps","Promise","resolve","_deps_reload","doReload","reload","_globalThis_location_reload","_globalThis_location","globalThis","location","call","then","allSettled","splice","map","run","_deps_beforeReload","beforeReload","catch","recover","message","dropLocalState","push","deleteLocalState","console","error","zeroEvents","emit","type","reason","warn","runReload","scheduleReload","onUpdateNeeded","onClientStateNotFound","logToConsole","level","context","_len","arguments","length","args","Array","_key","prefix","Object","entries","param","join","method","logArgText","arg","Error","name","stack","isBenignStoreClosedLog","text","test","classifyMutationDesync","includes","nowMs","desync","prevMs"],"sources":["../../../src/helpers/recoverZeroClient.ts"],"sourcesContent":[null],"mappings":"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","ignoreList":[]}
@@ -289,4 +289,185 @@ async function flush() {
289
289
  reason: "client state not found"
290
290
  });
291
291
  });
292
+ (0, import_vitest.test)("scheduleReload defers the reload AND the store delete until performReload runs", async () => {
293
+ const {
294
+ deps,
295
+ deleteLocalState,
296
+ reload
297
+ } = setup();
298
+ let captured;
299
+ deps.scheduleReload = ctx => {
300
+ captured = ctx;
301
+ };
302
+ (0, import_recoverZeroClient.makeZeroRecovery)(deps).onClientStateNotFound();
303
+ await flush();
304
+ (0, import_vitest.expect)(reload).not.toHaveBeenCalled();
305
+ (0, import_vitest.expect)(deleteLocalState).not.toHaveBeenCalled();
306
+ (0, import_vitest.expect)(captured).toMatchObject({
307
+ reasonKey: "client-state-not-found",
308
+ dropLocalState: true
309
+ });
310
+ await captured.performReload();
311
+ await flush();
312
+ (0, import_vitest.expect)(deleteLocalState).toHaveBeenCalledTimes(1);
313
+ (0, import_vitest.expect)(reload).toHaveBeenCalledTimes(1);
314
+ });
315
+ (0, import_vitest.test)("scheduleReload that reloads directly skips the delete and beforeReload", async () => {
316
+ const {
317
+ deps,
318
+ deleteLocalState,
319
+ reload
320
+ } = setup();
321
+ const nativeReload = import_vitest.vi.fn();
322
+ const beforeReload = import_vitest.vi.fn(async () => {});
323
+ deps.beforeReload = beforeReload;
324
+ deps.scheduleReload = () => {
325
+ nativeReload();
326
+ };
327
+ (0, import_recoverZeroClient.makeZeroRecovery)(deps).onClientStateNotFound();
328
+ await flush();
329
+ (0, import_vitest.expect)(nativeReload).toHaveBeenCalledTimes(1);
330
+ (0, import_vitest.expect)(deleteLocalState).not.toHaveBeenCalled();
331
+ (0, import_vitest.expect)(beforeReload).not.toHaveBeenCalled();
332
+ (0, import_vitest.expect)(reload).not.toHaveBeenCalled();
333
+ });
334
+ (0, import_vitest.test)("scheduleReload performReload is idempotent (one reload even if called twice)", async () => {
335
+ const {
336
+ deps,
337
+ deleteLocalState,
338
+ reload
339
+ } = setup();
340
+ let captured;
341
+ deps.scheduleReload = ctx => {
342
+ captured = ctx;
343
+ };
344
+ (0, import_recoverZeroClient.makeZeroRecovery)(deps).onClientStateNotFound();
345
+ await flush();
346
+ await captured.performReload();
347
+ await captured.performReload();
348
+ await flush();
349
+ (0, import_vitest.expect)(deleteLocalState).toHaveBeenCalledTimes(1);
350
+ (0, import_vitest.expect)(reload).toHaveBeenCalledTimes(1);
351
+ });
352
+ (0, import_vitest.test)("scheduleReload deferred PAST the latch timeout still deletes and reloads exactly once", async () => {
353
+ import_vitest.vi.useFakeTimers();
354
+ try {
355
+ const {
356
+ deps,
357
+ deleteLocalState,
358
+ reload
359
+ } = setup();
360
+ let captured;
361
+ deps.scheduleReload = ctx => {
362
+ captured = ctx;
363
+ };
364
+ (0, import_recoverZeroClient.makeZeroRecovery)(deps).onClientStateNotFound();
365
+ (0, import_vitest.expect)(captured).toBeDefined();
366
+ await import_vitest.vi.advanceTimersByTimeAsync(2e4);
367
+ (0, import_vitest.expect)(deleteLocalState).not.toHaveBeenCalled();
368
+ (0, import_vitest.expect)(reload).not.toHaveBeenCalled();
369
+ await captured.performReload();
370
+ (0, import_vitest.expect)(deleteLocalState).toHaveBeenCalledTimes(1);
371
+ (0, import_vitest.expect)(reload).toHaveBeenCalledTimes(1);
372
+ } finally {
373
+ import_vitest.vi.useRealTimers();
374
+ }
375
+ });
376
+ (0, import_vitest.test)("a slow beforeReload holds the latch through the timeout window (no double reload)", async () => {
377
+ import_vitest.vi.useFakeTimers();
378
+ try {
379
+ const {
380
+ deps,
381
+ reload
382
+ } = setup();
383
+ let releaseBeforeReload = () => {};
384
+ deps.beforeReload = () => new Promise(resolve => {
385
+ releaseBeforeReload = resolve;
386
+ });
387
+ const recovery = (0, import_recoverZeroClient.makeZeroRecovery)(deps);
388
+ recovery.onClientStateNotFound();
389
+ await import_vitest.vi.advanceTimersByTimeAsync(2e4);
390
+ recovery.onClientStateNotFound();
391
+ await import_vitest.vi.advanceTimersByTimeAsync(0);
392
+ (0, import_vitest.expect)(reload).not.toHaveBeenCalled();
393
+ releaseBeforeReload();
394
+ await import_vitest.vi.advanceTimersByTimeAsync(0);
395
+ (0, import_vitest.expect)(reload).toHaveBeenCalledTimes(1);
396
+ } finally {
397
+ import_vitest.vi.useRealTimers();
398
+ }
399
+ });
400
+ (0, import_vitest.test)("injectable guardStorage gives cross-reload loop protection (Hermes has no sessionStorage)", async () => {
401
+ const store = /* @__PURE__ */new Map();
402
+ const {
403
+ deps,
404
+ reload,
405
+ events
406
+ } = setup();
407
+ deps.guardStorage = {
408
+ getItem: key => store.get(key) ?? null,
409
+ setItem: (key, value) => {
410
+ store.set(key, value);
411
+ }
412
+ };
413
+ const recovery = (0, import_recoverZeroClient.makeZeroRecovery)(deps);
414
+ recovery.onClientStateNotFound();
415
+ await flush();
416
+ (0, import_vitest.expect)(reload).toHaveBeenCalledTimes(1);
417
+ (0, import_recoverZeroClient.resetRecoveryStateForTests)();
418
+ recovery.onClientStateNotFound();
419
+ await flush();
420
+ (0, import_vitest.expect)(reload).toHaveBeenCalledTimes(1);
421
+ (0, import_vitest.expect)(events.some(event => event.type === "fatal")).toBe(true);
422
+ });
423
+ (0, import_vitest.test)("a guardStorage that throws never crashes recovery (in-memory is the floor)", async () => {
424
+ const {
425
+ deps,
426
+ reload
427
+ } = setup();
428
+ deps.guardStorage = {
429
+ getItem: () => {
430
+ throw new Error("no storage on this platform");
431
+ },
432
+ setItem: () => {
433
+ throw new Error("no storage on this platform");
434
+ }
435
+ };
436
+ (0, import_recoverZeroClient.makeZeroRecovery)(deps).onClientStateNotFound();
437
+ await flush();
438
+ (0, import_vitest.expect)(reload).toHaveBeenCalledTimes(1);
439
+ });
440
+ (0, import_vitest.test)("classifier recognizes the mutation / connection desync class", () => {
441
+ const cases = [[["sent mutation ID 5 but expected 4"], "mutation-desync"], [["oooMutation detected"], "mutation-desync"], [["Server reported an out-of-order mutation"], "mutation-desync"], [["Ignoring mutation 3, already processed. Expected: 4"], "mutation-desync"], [["InvalidConnectionRequestLastMutationID"], "mutation-desync"], [["InvalidConnectionRequestBaseCookie"], "connection-cookie-invalid"], [["ClientNotFound: client gone"], "client-not-found"], [["connection userID mismatch"], "connection-userid-mismatch"]];
442
+ for (const [args, reason] of cases) {
443
+ (0, import_vitest.expect)((0, import_recoverZeroClient.classifyZeroRecoveryLog)("error", args)).toMatchObject({
444
+ reason,
445
+ dropLocalState: true
446
+ });
447
+ }
448
+ (0, import_vitest.expect)((0, import_recoverZeroClient.classifyZeroRecoveryLog)("error", ["consecutive server-ack timeouts"])).toBeUndefined();
449
+ (0, import_vitest.expect)((0, import_recoverZeroClient.classifyZeroRecoveryLog)("error", ["Connection attempt timed out after 10 seconds"])).toBeUndefined();
450
+ (0, import_vitest.expect)((0, import_recoverZeroClient.classifyZeroRecoveryLog)("warn", ["ClientNotFound"])).toBeUndefined();
451
+ });
452
+ (0, import_vitest.test)("benignLogFilter suppresses recovery for a matching classified log", async () => {
453
+ const {
454
+ deps,
455
+ deleteLocalState,
456
+ reload
457
+ } = setup();
458
+ deps.benignLogFilter = message => message.includes("ClientNotFound");
459
+ const sink = (0, import_recoverZeroClient.composeRecoveryLogSink)(deps);
460
+ sink.log("error", void 0, "ClientNotFound: gone");
461
+ await flush();
462
+ (0, import_vitest.expect)(deleteLocalState).not.toHaveBeenCalled();
463
+ (0, import_vitest.expect)(reload).not.toHaveBeenCalled();
464
+ sink.log("error", void 0, "sent mutation ID 5 but expected 4");
465
+ await flush();
466
+ (0, import_vitest.expect)(reload).toHaveBeenCalledTimes(1);
467
+ });
468
+ (0, import_vitest.test)("isRecoverableZeroStalePokeMessage matches only the stale-cookie signatures", () => {
469
+ (0, import_vitest.expect)((0, import_recoverZeroClient.isRecoverableZeroStalePokeMessage)("Server returned unexpected base cookie during sync")).toBe(true);
470
+ (0, import_vitest.expect)((0, import_recoverZeroClient.isRecoverableZeroStalePokeMessage)("Received cookie 5 is < than last snapshot cookie 9, ignoring client view")).toBe(true);
471
+ (0, import_vitest.expect)((0, import_recoverZeroClient.isRecoverableZeroStalePokeMessage)("client state not found")).toBe(false);
472
+ });
292
473
  });