strata-storage 2.7.0 → 2.8.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.
Files changed (115) hide show
  1. package/AI-INTEGRATION-GUIDE.md +46 -2
  2. package/LICENSE +21 -35
  3. package/README.md +15 -13
  4. package/dist/LICENSE +21 -35
  5. package/dist/README.md +15 -13
  6. package/dist/adapters/capacitor/FilesystemAdapter.d.ts +0 -1
  7. package/dist/adapters/capacitor/PreferencesAdapter.d.ts +0 -1
  8. package/dist/adapters/capacitor/SecureAdapter.d.ts +0 -1
  9. package/dist/adapters/capacitor/SqliteAdapter.d.ts +0 -1
  10. package/dist/adapters/capacitor/index.d.ts +0 -1
  11. package/dist/adapters/web/CacheAdapter.d.ts +0 -1
  12. package/dist/adapters/web/CacheAdapter.js +20 -5
  13. package/dist/adapters/web/CookieAdapter.d.ts +0 -1
  14. package/dist/adapters/web/CookieAdapter.js +27 -3
  15. package/dist/adapters/web/IndexedDBAdapter.d.ts +0 -1
  16. package/dist/adapters/web/IndexedDBAdapter.js +5 -1
  17. package/dist/adapters/web/LocalStorageAdapter.d.ts +0 -1
  18. package/dist/adapters/web/LocalStorageAdapter.js +15 -1
  19. package/dist/adapters/web/MemoryAdapter.d.ts +7 -1
  20. package/dist/adapters/web/MemoryAdapter.js +22 -5
  21. package/dist/adapters/web/SessionStorageAdapter.d.ts +0 -1
  22. package/dist/adapters/web/SessionStorageAdapter.js +14 -1
  23. package/dist/adapters/web/URLAdapter.d.ts +0 -1
  24. package/dist/adapters/web/index.d.ts +0 -1
  25. package/dist/capacitor.d.ts +0 -1
  26. package/dist/config/support.d.ts +0 -1
  27. package/dist/core/AdapterRegistry.d.ts +0 -1
  28. package/dist/core/BaseAdapter.d.ts +0 -1
  29. package/dist/core/Strata.d.ts +12 -1
  30. package/dist/core/Strata.js +119 -21
  31. package/dist/features/compression/index.d.ts +0 -1
  32. package/dist/features/compression.d.ts +23 -9
  33. package/dist/features/compression.js +164 -77
  34. package/dist/features/encryption/index.d.ts +0 -1
  35. package/dist/features/encryption.d.ts +0 -1
  36. package/dist/features/integrity.d.ts +0 -1
  37. package/dist/features/migration.d.ts +0 -1
  38. package/dist/features/migration.js +7 -2
  39. package/dist/features/observer/index.d.ts +0 -1
  40. package/dist/features/observer.d.ts +0 -1
  41. package/dist/features/query/index.d.ts +0 -1
  42. package/dist/features/query.d.ts +1 -1
  43. package/dist/features/query.js +29 -6
  44. package/dist/features/sync/index.d.ts +0 -1
  45. package/dist/features/sync.d.ts +0 -1
  46. package/dist/features/sync.js +18 -3
  47. package/dist/features/ttl/index.d.ts +0 -1
  48. package/dist/features/ttl.d.ts +0 -1
  49. package/dist/features/ttl.js +4 -2
  50. package/dist/firebase.d.ts +0 -1
  51. package/dist/firebase.js +39 -2
  52. package/dist/index.d.ts +2 -3
  53. package/dist/integrations/angular/index.d.ts +0 -1
  54. package/dist/integrations/index.d.ts +0 -1
  55. package/dist/integrations/react/index.d.ts +0 -1
  56. package/dist/integrations/vue/index.d.ts +0 -1
  57. package/dist/package.json +9 -4
  58. package/dist/plugin/definitions.d.ts +0 -1
  59. package/dist/plugin/index.d.ts +0 -1
  60. package/dist/plugin/web.d.ts +0 -1
  61. package/dist/types/index.d.ts +0 -1
  62. package/dist/utils/errors.d.ts +0 -1
  63. package/dist/utils/index.d.ts +0 -1
  64. package/dist/utils/logger.d.ts +0 -1
  65. package/package.json +15 -5
  66. package/scripts/build.js +30 -13
  67. package/android/AGENTS.md +0 -66
  68. package/android/CLAUDE.md +0 -122
  69. package/dist/adapters/capacitor/FilesystemAdapter.d.ts.map +0 -1
  70. package/dist/adapters/capacitor/PreferencesAdapter.d.ts.map +0 -1
  71. package/dist/adapters/capacitor/SecureAdapter.d.ts.map +0 -1
  72. package/dist/adapters/capacitor/SqliteAdapter.d.ts.map +0 -1
  73. package/dist/adapters/capacitor/index.d.ts.map +0 -1
  74. package/dist/adapters/web/CacheAdapter.d.ts.map +0 -1
  75. package/dist/adapters/web/CookieAdapter.d.ts.map +0 -1
  76. package/dist/adapters/web/IndexedDBAdapter.d.ts.map +0 -1
  77. package/dist/adapters/web/LocalStorageAdapter.d.ts.map +0 -1
  78. package/dist/adapters/web/MemoryAdapter.d.ts.map +0 -1
  79. package/dist/adapters/web/SessionStorageAdapter.d.ts.map +0 -1
  80. package/dist/adapters/web/URLAdapter.d.ts.map +0 -1
  81. package/dist/adapters/web/index.d.ts.map +0 -1
  82. package/dist/capacitor.d.ts.map +0 -1
  83. package/dist/config/support.d.ts.map +0 -1
  84. package/dist/core/AdapterRegistry.d.ts.map +0 -1
  85. package/dist/core/BaseAdapter.d.ts.map +0 -1
  86. package/dist/core/Strata.d.ts.map +0 -1
  87. package/dist/features/compression/index.d.ts.map +0 -1
  88. package/dist/features/compression.d.ts.map +0 -1
  89. package/dist/features/encryption/index.d.ts.map +0 -1
  90. package/dist/features/encryption.d.ts.map +0 -1
  91. package/dist/features/integrity.d.ts.map +0 -1
  92. package/dist/features/migration.d.ts.map +0 -1
  93. package/dist/features/observer/index.d.ts.map +0 -1
  94. package/dist/features/observer.d.ts.map +0 -1
  95. package/dist/features/query/index.d.ts.map +0 -1
  96. package/dist/features/query.d.ts.map +0 -1
  97. package/dist/features/sync/index.d.ts.map +0 -1
  98. package/dist/features/sync.d.ts.map +0 -1
  99. package/dist/features/ttl/index.d.ts.map +0 -1
  100. package/dist/features/ttl.d.ts.map +0 -1
  101. package/dist/firebase.d.ts.map +0 -1
  102. package/dist/index.d.ts.map +0 -1
  103. package/dist/integrations/angular/index.d.ts.map +0 -1
  104. package/dist/integrations/index.d.ts.map +0 -1
  105. package/dist/integrations/react/index.d.ts.map +0 -1
  106. package/dist/integrations/vue/index.d.ts.map +0 -1
  107. package/dist/plugin/definitions.d.ts.map +0 -1
  108. package/dist/plugin/index.d.ts.map +0 -1
  109. package/dist/plugin/web.d.ts.map +0 -1
  110. package/dist/types/index.d.ts.map +0 -1
  111. package/dist/utils/errors.d.ts.map +0 -1
  112. package/dist/utils/index.d.ts.map +0 -1
  113. package/dist/utils/logger.d.ts.map +0 -1
  114. package/ios/AGENTS.md +0 -59
  115. package/ios/CLAUDE.md +0 -106
@@ -74,4 +74,3 @@ export declare class SessionStorageAdapter extends LocalStorageAdapter {
74
74
  */
75
75
  subscribe(callback: import("../../types/index.js").SubscriptionCallback): import("../../types/index.js").UnsubscribeFunction;
76
76
  }
77
- //# sourceMappingURL=SessionStorageAdapter.d.ts.map
@@ -115,7 +115,20 @@ export class SessionStorageAdapter extends LocalStorageAdapter {
115
115
  * Remove a value from sessionStorage (synchronous)
116
116
  */
117
117
  removeSync(key) {
118
- const oldValue = this.getSync(key);
118
+ // Read raw — NOT via getSync(), which deletes expired entries by calling
119
+ // removeSync() and would recurse here forever. Only read with a listener.
120
+ let oldValue = null;
121
+ if (this.hasChangeListeners()) {
122
+ const item = window.sessionStorage.getItem(this.prefix + key);
123
+ if (item) {
124
+ try {
125
+ oldValue = deserialize(item);
126
+ }
127
+ catch {
128
+ oldValue = null;
129
+ }
130
+ }
131
+ }
119
132
  window.sessionStorage.removeItem(this.prefix + key);
120
133
  if (oldValue) {
121
134
  this.emitChange(key, oldValue.value, undefined, 'local');
@@ -56,4 +56,3 @@ export declare class URLAdapter extends BaseAdapter {
56
56
  private snapshotParams;
57
57
  private handleExternalChange;
58
58
  }
59
- //# sourceMappingURL=URLAdapter.d.ts.map
@@ -8,4 +8,3 @@ export { IndexedDBAdapter } from "./IndexedDBAdapter.js";
8
8
  export { CookieAdapter } from "./CookieAdapter.js";
9
9
  export { CacheAdapter } from "./CacheAdapter.js";
10
10
  export { URLAdapter, type URLAdapterConfig } from "./URLAdapter.js";
11
- //# sourceMappingURL=index.d.ts.map
@@ -17,4 +17,3 @@ export declare function isCapacitorEnvironment(): boolean;
17
17
  * Get Capacitor-specific storage types
18
18
  */
19
19
  export declare function getCapacitorStorageTypes(): string[];
20
- //# sourceMappingURL=capacitor.d.ts.map
@@ -7,4 +7,3 @@ export declare const SUPPORT_CONFIG: {
7
7
  readonly label: "Support the Project";
8
8
  readonly description: "Help us keep strata-storage free and improving";
9
9
  };
10
- //# sourceMappingURL=support.d.ts.map
@@ -49,4 +49,3 @@ export declare class AdapterRegistry {
49
49
  */
50
50
  closeAll(): Promise<void>;
51
51
  }
52
- //# sourceMappingURL=AdapterRegistry.d.ts.map
@@ -100,4 +100,3 @@ export declare abstract class BaseAdapter implements StorageAdapter {
100
100
  abstract remove(key: string): Promise<void>;
101
101
  abstract keys(pattern?: string | RegExp): Promise<string[]>;
102
102
  }
103
- //# sourceMappingURL=BaseAdapter.d.ts.map
@@ -336,6 +336,18 @@ export declare class Strata {
336
336
  /** Effective namespace for an operation: a per-call value wins over the
337
337
  * instance default (`config.namespace`). Empty strings are treated as none. */
338
338
  private effectiveNamespace;
339
+ /**
340
+ * Encode a namespace into an unambiguous physical-key prefix component.
341
+ * `:` is a legal character in user keys, so a bare `${ns}:${key}` join lets
342
+ * namespace `a` + key `b:c` collide with namespace `a:b` + key `c` (both
343
+ * `a:b:c`), leaking keys across namespaces. Percent-encoding `:` (and the `%`
344
+ * escape itself) in the namespace component removes the ambiguity while
345
+ * leaving the user key — including any `:` — untouched. Namespaces without a
346
+ * `:` are unchanged, so existing namespaced data keeps the same physical key.
347
+ */
348
+ private encodeNamespace;
349
+ /** The physical-key prefix (with trailing `:`) for a namespace. */
350
+ private namespacePrefix;
339
351
  /** Map a logical key to the physical key actually stored (namespace-prefixed). */
340
352
  private resolveKey;
341
353
  /** Reduce physical keys to the logical keys owned by `ns`, then apply pattern. */
@@ -363,4 +375,3 @@ export declare class Strata {
363
375
  private startAutoBackup;
364
376
  private selectAdapter;
365
377
  }
366
- //# sourceMappingURL=Strata.d.ts.map
@@ -63,7 +63,13 @@ export class Strata {
63
63
  */
64
64
  async initialize() {
65
65
  if (!this._readyPromise) {
66
- this._readyPromise = this._performInitialization();
66
+ // On failure, clear the cached promise so a later call can retry instead
67
+ // of permanently re-awaiting a rejected promise (e.g. a transient adapter
68
+ // error during init should not brick the instance forever).
69
+ this._readyPromise = this._performInitialization().catch((error) => {
70
+ this._readyPromise = undefined;
71
+ throw error;
72
+ });
67
73
  }
68
74
  return this._readyPromise;
69
75
  }
@@ -441,12 +447,42 @@ export class Strata {
441
447
  // Fast path: an unfiltered, un-namespaced clear wipes the whole adapter.
442
448
  if (!needsInspection) {
443
449
  await adapter.clear();
450
+ // Propagate the full clear cross-tab so other tabs converge, matching
451
+ // the set/remove broadcasts. ONLY full clears broadcast — a filtered or
452
+ // namespaced clear must not wipe the whole adapter elsewhere.
453
+ // localStorage/sessionStorage propagate natively and are skipped on the
454
+ // receiving side; applyRemoteChange clears the adapter directly (no
455
+ // re-broadcast), so there is no sync loop.
456
+ if (this.syncManager) {
457
+ this.syncManager.broadcast({
458
+ type: 'clear',
459
+ storage: adapter.name,
460
+ timestamp: Date.now(),
461
+ });
462
+ }
463
+ continue;
464
+ }
465
+ // `expiredOnly` with no other narrowing filter: delegate to the adapter's
466
+ // bulk expired-reclamation when it has one (SQLite reaps in a single
467
+ // DELETE). The key-inspection loop below cannot reach expired rows on
468
+ // backends whose keys() already filters them out (SQLite filters in SQL),
469
+ // so without this it would be a silent no-op there.
470
+ if (options?.expiredOnly &&
471
+ !ns &&
472
+ !options?.prefix &&
473
+ !options?.pattern &&
474
+ !options?.tags &&
475
+ olderThanTs === undefined &&
476
+ typeof adapter.cleanupExpired === 'function') {
477
+ await adapter.cleanupExpired();
444
478
  continue;
445
479
  }
446
480
  // Filtered/namespaced clear: inspect keys and remove only the matches.
447
- const scopedPrefix = ns ? `${ns}:${options?.prefix ?? ''}` : options?.prefix;
481
+ const scopedPrefix = ns
482
+ ? `${this.namespacePrefix(ns)}${options?.prefix ?? ''}`
483
+ : options?.prefix;
448
484
  for (const physicalKey of await adapter.keys()) {
449
- if (ns && !physicalKey.startsWith(`${ns}:`))
485
+ if (ns && !physicalKey.startsWith(this.namespacePrefix(ns)))
450
486
  continue;
451
487
  if (scopedPrefix && !physicalKey.startsWith(scopedPrefix))
452
488
  continue;
@@ -641,11 +677,22 @@ export class Strata {
641
677
  try {
642
678
  if (!needsInspection) {
643
679
  adapter.clearSync();
680
+ // Propagate the full clear cross-tab (see clear()). Only full clears
681
+ // broadcast; localStorage/sessionStorage are skipped on receive.
682
+ if (this.syncManager) {
683
+ this.syncManager.broadcast({
684
+ type: 'clear',
685
+ storage: adapter.name,
686
+ timestamp: Date.now(),
687
+ });
688
+ }
644
689
  continue;
645
690
  }
646
- const scopedPrefix = ns ? `${ns}:${options?.prefix ?? ''}` : options?.prefix;
691
+ const scopedPrefix = ns
692
+ ? `${this.namespacePrefix(ns)}${options?.prefix ?? ''}`
693
+ : options?.prefix;
647
694
  for (const physicalKey of adapter.keysSync()) {
648
- if (ns && !physicalKey.startsWith(`${ns}:`))
695
+ if (ns && !physicalKey.startsWith(this.namespacePrefix(ns)))
649
696
  continue;
650
697
  if (scopedPrefix && !physicalKey.startsWith(scopedPrefix))
651
698
  continue;
@@ -720,11 +767,21 @@ export class Strata {
720
767
  computeExpiration(options) {
721
768
  if (this.ttlManager)
722
769
  return this.ttlManager.calculateExpiration(options);
723
- if (typeof options?.ttl === 'number')
724
- return Date.now() + options.ttl;
770
+ // Pre-init fallback: mirror TTLManager.calculateExpiration's precedence
771
+ // (expireAt > expireAfter > ttl > defaultTTL) and past-guarding, so a write
772
+ // issued before initialize() expires identically to one issued after.
725
773
  if (options?.expireAt !== undefined) {
726
- return typeof options.expireAt === 'number' ? options.expireAt : options.expireAt.getTime();
774
+ const t = options.expireAt instanceof Date ? options.expireAt.getTime() : options.expireAt;
775
+ return t > Date.now() ? t : undefined;
727
776
  }
777
+ if (options?.expireAfter !== undefined) {
778
+ const t = options.expireAfter instanceof Date ? options.expireAfter.getTime() : options.expireAfter;
779
+ return t > Date.now() ? t : undefined;
780
+ }
781
+ if (typeof options?.ttl === 'number')
782
+ return Date.now() + options.ttl;
783
+ if (this.config.ttl?.defaultTTL)
784
+ return Date.now() + this.config.ttl.defaultTTL;
728
785
  return undefined;
729
786
  }
730
787
  /**
@@ -752,7 +809,7 @@ export class Strata {
752
809
  continue;
753
810
  }
754
811
  let storageTotal = 0;
755
- const prefix = `${ns}:`;
812
+ const prefix = this.namespacePrefix(ns);
756
813
  for (const physicalKey of await adapter.keys()) {
757
814
  if (!physicalKey.startsWith(prefix))
758
815
  continue;
@@ -797,7 +854,7 @@ export class Strata {
797
854
  callback(change);
798
855
  return;
799
856
  }
800
- const prefix = `${ns}:`;
857
+ const prefix = this.namespacePrefix(ns);
801
858
  if (!change.key.startsWith(prefix))
802
859
  return;
803
860
  callback({ ...change, key: change.key.slice(prefix.length) });
@@ -847,9 +904,9 @@ export class Strata {
847
904
  // integrity repair, decryption, and decompression.
848
905
  const decoded = [];
849
906
  for (const physicalKey of rawKeys) {
850
- if (ns && !physicalKey.startsWith(`${ns}:`))
907
+ if (ns && !physicalKey.startsWith(this.namespacePrefix(ns)))
851
908
  continue;
852
- const logicalKey = ns ? physicalKey.slice(ns.length + 1) : physicalKey;
909
+ const logicalKey = ns ? physicalKey.slice(this.namespacePrefix(ns).length) : physicalKey;
853
910
  if (!isValidKey(logicalKey))
854
911
  continue;
855
912
  const value = await this.get(logicalKey, options);
@@ -1136,8 +1193,8 @@ export class Strata {
1136
1193
  const items = await this.ttlManager.getExpiring(timeWindow, () => adapter.keys(), (physicalKey) => adapter.get(physicalKey));
1137
1194
  return ns
1138
1195
  ? items
1139
- .filter((item) => item.key.startsWith(`${ns}:`))
1140
- .map((item) => ({ ...item, key: item.key.slice(ns.length + 1) }))
1196
+ .filter((item) => item.key.startsWith(this.namespacePrefix(ns)))
1197
+ .map((item) => ({ ...item, key: item.key.slice(this.namespacePrefix(ns).length) }))
1141
1198
  : items;
1142
1199
  }
1143
1200
  /**
@@ -1158,7 +1215,7 @@ export class Strata {
1158
1215
  const ns = this.effectiveNamespace(options);
1159
1216
  const expired = await this.ttlManager.cleanup(async () => {
1160
1217
  const keys = await adapter.keys();
1161
- return ns ? keys.filter((key) => key.startsWith(`${ns}:`)) : keys;
1218
+ return ns ? keys.filter((key) => key.startsWith(this.namespacePrefix(ns))) : keys;
1162
1219
  }, (physicalKey) => adapter.get(physicalKey), (physicalKey) => adapter.remove(physicalKey));
1163
1220
  return expired.length;
1164
1221
  }
@@ -1248,7 +1305,14 @@ export class Strata {
1248
1305
  : [config.defaultStorage, ...existing];
1249
1306
  }
1250
1307
  if (!normalized.defaultStorages || normalized.defaultStorages.length === 0) {
1251
- normalized.defaultStorages = ['memory'];
1308
+ // Persistent-first, matching the default `storage` singleton: prefer
1309
+ // durable backends so writes survive a reload, with sync-capable
1310
+ // localStorage ahead of async-only indexedDB, and volatile `memory` as the
1311
+ // last-resort fallback (SSR / no web storage). selectDefaultAdapter only
1312
+ // picks from adapters that are actually available, so unusable entries are
1313
+ // skipped. (A bare instance previously defaulted to memory-only, silently
1314
+ // contradicting the documented persistent-first behavior.)
1315
+ normalized.defaultStorages = ['localStorage', 'indexedDB', 'sessionStorage', 'memory'];
1252
1316
  }
1253
1317
  return normalized;
1254
1318
  }
@@ -1266,14 +1330,30 @@ export class Strata {
1266
1330
  const ns = options?.namespace ?? this.config.namespace;
1267
1331
  return ns && ns.length > 0 ? ns : undefined;
1268
1332
  }
1333
+ /**
1334
+ * Encode a namespace into an unambiguous physical-key prefix component.
1335
+ * `:` is a legal character in user keys, so a bare `${ns}:${key}` join lets
1336
+ * namespace `a` + key `b:c` collide with namespace `a:b` + key `c` (both
1337
+ * `a:b:c`), leaking keys across namespaces. Percent-encoding `:` (and the `%`
1338
+ * escape itself) in the namespace component removes the ambiguity while
1339
+ * leaving the user key — including any `:` — untouched. Namespaces without a
1340
+ * `:` are unchanged, so existing namespaced data keeps the same physical key.
1341
+ */
1342
+ encodeNamespace(ns) {
1343
+ return ns.replace(/%/g, '%25').replace(/:/g, '%3A');
1344
+ }
1345
+ /** The physical-key prefix (with trailing `:`) for a namespace. */
1346
+ namespacePrefix(ns) {
1347
+ return `${this.encodeNamespace(ns)}:`;
1348
+ }
1269
1349
  /** Map a logical key to the physical key actually stored (namespace-prefixed). */
1270
1350
  resolveKey(key, options) {
1271
1351
  const ns = this.effectiveNamespace(options);
1272
- return ns ? `${ns}:${key}` : key;
1352
+ return ns ? `${this.encodeNamespace(ns)}:${key}` : key;
1273
1353
  }
1274
1354
  /** Reduce physical keys to the logical keys owned by `ns`, then apply pattern. */
1275
1355
  scopeAndStripKeys(physicalKeys, ns, pattern) {
1276
- const prefix = `${ns}:`;
1356
+ const prefix = `${this.encodeNamespace(ns)}:`;
1277
1357
  const logical = physicalKeys
1278
1358
  .filter((key) => key.startsWith(prefix))
1279
1359
  .map((key) => key.slice(prefix.length));
@@ -1296,8 +1376,9 @@ export class Strata {
1296
1376
  getDefaultStorages() {
1297
1377
  // Only return adapters that are actually registered
1298
1378
  const registered = Array.from(this.registry.getAll().keys()).map((key) => String(key));
1299
- // Prefer these storages in order if available
1300
- const preferredOrder = ['indexedDB', 'localStorage', 'sessionStorage', 'memory'];
1379
+ // Prefer these storages in order if available (sync-capable localStorage
1380
+ // ahead of async-only indexedDB, mirroring normalizeConfig's default).
1381
+ const preferredOrder = ['localStorage', 'indexedDB', 'sessionStorage', 'memory'];
1301
1382
  const available = preferredOrder.filter((storage) => registered.includes(storage));
1302
1383
  // Always include memory as fallback if registered
1303
1384
  if (available.length === 0 && registered.includes('memory')) {
@@ -1362,8 +1443,25 @@ export class Strata {
1362
1443
  }
1363
1444
  }
1364
1445
  async applyRemoteChange(change) {
1365
- if (change.source !== 'remote' || !change.key || change.key === '*')
1446
+ if (change.source !== 'remote' || !change.key)
1447
+ return;
1448
+ // A full-clear broadcast arrives as a keyless ('*') change. Apply it to the
1449
+ // matching adapter so cleared state stays consistent cross-tab. Clearing the
1450
+ // adapter directly (not this.clear()) avoids re-broadcasting, so no loop.
1451
+ if (change.key === '*') {
1452
+ if (change.storage === 'localStorage' || change.storage === 'sessionStorage')
1453
+ return;
1454
+ const clearTarget = this.adapters.get(change.storage);
1455
+ if (clearTarget) {
1456
+ try {
1457
+ await clearTarget.clear();
1458
+ }
1459
+ catch (error) {
1460
+ logger.warn(`Failed to apply remote clear for "${change.storage}":`, error);
1461
+ }
1462
+ }
1366
1463
  return;
1464
+ }
1367
1465
  if (change.storage === 'localStorage' || change.storage === 'sessionStorage')
1368
1466
  return;
1369
1467
  const adapter = this.adapters.get(change.storage);
@@ -1,2 +1 @@
1
1
  export * from "../compression.js";
2
- //# sourceMappingURL=index.d.ts.map
@@ -41,22 +41,37 @@ export declare class CompressionManager {
41
41
  * Check if data is compressed
42
42
  */
43
43
  isCompressedData(data: unknown): data is CompressedData;
44
+ private static readonly FORMAT;
45
+ private static readonly LZW_MAX_CODE;
44
46
  /**
45
- * LZ-string compression implementation
47
+ * LZ compression over UTF-8 bytes.
48
+ *
49
+ * The alphabet is bytes (0–255), so the literal-vs-dictionary-code
50
+ * discriminator (`< 256`) on decompress is correct for ALL input — including
51
+ * CJK / emoji / symbols whose code points are ≥ 256. (Operating on raw UTF-16
52
+ * chars previously misclassified any such literal as a dictionary code and
53
+ * silently corrupted the stored value, or threw on read.)
46
54
  */
47
55
  private lzCompress;
48
56
  /**
49
- * LZ-string decompression implementation
57
+ * LZ decompression — inverse of {@link lzCompress}, reconstructing UTF-8 bytes
58
+ * then decoding them back to the original string.
50
59
  */
51
60
  private lzDecompress;
61
+ private static readonly B64_CHARS;
62
+ /** Base64-encode raw bytes (btoa when available, else a pure-JS fallback). */
63
+ private bytesToBase64;
64
+ /** Decode a base64 string back to raw bytes (atob when available, else pure-JS). */
65
+ private base64ToBytes;
52
66
  /**
53
- * Base64 encoding for compressed data
67
+ * Legacy decoder for payloads written by <= 2.7.0 (algorithm 'lz'): the
68
+ * original UTF-16-char LZW over the raw string with a `decodeURIComponent`
69
+ * base64 layer. Kept read-only so existing compressed-at-rest values still
70
+ * decode after upgrading to the current byte-LZW codec. (That codec corrupted
71
+ * non-Latin1 input, so legacy payloads are Latin1 in practice.)
54
72
  */
55
- private encodeToBase64;
56
- /**
57
- * Base64 decoding for compressed data
58
- */
59
- private decodeFromBase64;
73
+ private legacyLzDecompress;
74
+ private legacyDecodeFromBase64;
60
75
  /**
61
76
  * Get compression ratio
62
77
  */
@@ -66,4 +81,3 @@ export declare class CompressionManager {
66
81
  */
67
82
  getSavingsPercentage(compressedData: CompressedData): number;
68
83
  }
69
- //# sourceMappingURL=compression.d.ts.map