strata-storage 1.0.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 (102) hide show
  1. package/Readme.md +113 -0
  2. package/android/src/main/java/com/strata/storage/EncryptedStorage.java +65 -0
  3. package/android/src/main/java/com/strata/storage/SQLiteStorage.java +147 -0
  4. package/android/src/main/java/com/strata/storage/SharedPreferencesStorage.java +74 -0
  5. package/android/src/main/java/com/stratastorage/StrataStoragePlugin.java +256 -0
  6. package/dist/adapters/capacitor/FilesystemAdapter.d.ts +46 -0
  7. package/dist/adapters/capacitor/FilesystemAdapter.d.ts.map +1 -0
  8. package/dist/adapters/capacitor/FilesystemAdapter.js +162 -0
  9. package/dist/adapters/capacitor/PreferencesAdapter.d.ts +46 -0
  10. package/dist/adapters/capacitor/PreferencesAdapter.d.ts.map +1 -0
  11. package/dist/adapters/capacitor/PreferencesAdapter.js +162 -0
  12. package/dist/adapters/capacitor/SecureAdapter.d.ts +69 -0
  13. package/dist/adapters/capacitor/SecureAdapter.d.ts.map +1 -0
  14. package/dist/adapters/capacitor/SecureAdapter.js +214 -0
  15. package/dist/adapters/capacitor/SqliteAdapter.d.ts +68 -0
  16. package/dist/adapters/capacitor/SqliteAdapter.d.ts.map +1 -0
  17. package/dist/adapters/capacitor/SqliteAdapter.js +277 -0
  18. package/dist/adapters/capacitor/index.d.ts +9 -0
  19. package/dist/adapters/capacitor/index.d.ts.map +1 -0
  20. package/dist/adapters/capacitor/index.js +8 -0
  21. package/dist/adapters/web/CacheAdapter.d.ts +91 -0
  22. package/dist/adapters/web/CacheAdapter.d.ts.map +1 -0
  23. package/dist/adapters/web/CacheAdapter.js +291 -0
  24. package/dist/adapters/web/CookieAdapter.d.ts +77 -0
  25. package/dist/adapters/web/CookieAdapter.d.ts.map +1 -0
  26. package/dist/adapters/web/CookieAdapter.js +260 -0
  27. package/dist/adapters/web/IndexedDBAdapter.d.ts +78 -0
  28. package/dist/adapters/web/IndexedDBAdapter.d.ts.map +1 -0
  29. package/dist/adapters/web/IndexedDBAdapter.js +371 -0
  30. package/dist/adapters/web/LocalStorageAdapter.d.ts +63 -0
  31. package/dist/adapters/web/LocalStorageAdapter.d.ts.map +1 -0
  32. package/dist/adapters/web/LocalStorageAdapter.js +238 -0
  33. package/dist/adapters/web/MemoryAdapter.d.ts +69 -0
  34. package/dist/adapters/web/MemoryAdapter.d.ts.map +1 -0
  35. package/dist/adapters/web/MemoryAdapter.js +165 -0
  36. package/dist/adapters/web/SessionStorageAdapter.d.ts +53 -0
  37. package/dist/adapters/web/SessionStorageAdapter.d.ts.map +1 -0
  38. package/dist/adapters/web/SessionStorageAdapter.js +180 -0
  39. package/dist/adapters/web/index.d.ts +10 -0
  40. package/dist/adapters/web/index.d.ts.map +1 -0
  41. package/dist/adapters/web/index.js +9 -0
  42. package/dist/core/AdapterRegistry.d.ts +52 -0
  43. package/dist/core/AdapterRegistry.d.ts.map +1 -0
  44. package/dist/core/AdapterRegistry.js +102 -0
  45. package/dist/core/BaseAdapter.d.ts +79 -0
  46. package/dist/core/BaseAdapter.d.ts.map +1 -0
  47. package/dist/core/BaseAdapter.js +197 -0
  48. package/dist/core/StorageStrategy.d.ts +55 -0
  49. package/dist/core/StorageStrategy.d.ts.map +1 -0
  50. package/dist/core/StorageStrategy.js +199 -0
  51. package/dist/core/Strata.d.ts +122 -0
  52. package/dist/core/Strata.d.ts.map +1 -0
  53. package/dist/core/Strata.js +568 -0
  54. package/dist/features/compression.d.ts +65 -0
  55. package/dist/features/compression.d.ts.map +1 -0
  56. package/dist/features/compression.js +205 -0
  57. package/dist/features/encryption.d.ts +68 -0
  58. package/dist/features/encryption.d.ts.map +1 -0
  59. package/dist/features/encryption.js +172 -0
  60. package/dist/features/migration.d.ts +17 -0
  61. package/dist/features/migration.d.ts.map +1 -0
  62. package/dist/features/migration.js +43 -0
  63. package/dist/features/query.d.ts +75 -0
  64. package/dist/features/query.d.ts.map +1 -0
  65. package/dist/features/query.js +305 -0
  66. package/dist/features/sync.d.ts +87 -0
  67. package/dist/features/sync.d.ts.map +1 -0
  68. package/dist/features/sync.js +233 -0
  69. package/dist/features/ttl.d.ts +124 -0
  70. package/dist/features/ttl.d.ts.map +1 -0
  71. package/dist/features/ttl.js +236 -0
  72. package/dist/index.d.ts +44 -0
  73. package/dist/index.d.ts.map +1 -0
  74. package/dist/index.js +46 -0
  75. package/dist/package.json +60 -0
  76. package/dist/plugin/definitions.d.ts +219 -0
  77. package/dist/plugin/definitions.d.ts.map +1 -0
  78. package/dist/plugin/definitions.js +5 -0
  79. package/dist/plugin/index.d.ts +8 -0
  80. package/dist/plugin/index.d.ts.map +1 -0
  81. package/dist/plugin/index.js +27 -0
  82. package/dist/plugin/web.d.ts +24 -0
  83. package/dist/plugin/web.d.ts.map +1 -0
  84. package/dist/plugin/web.js +35 -0
  85. package/dist/types/index.d.ts +558 -0
  86. package/dist/types/index.d.ts.map +1 -0
  87. package/dist/types/index.js +14 -0
  88. package/dist/utils/errors.d.ts +92 -0
  89. package/dist/utils/errors.d.ts.map +1 -0
  90. package/dist/utils/errors.js +153 -0
  91. package/dist/utils/index.d.ts +105 -0
  92. package/dist/utils/index.d.ts.map +1 -0
  93. package/dist/utils/index.js +329 -0
  94. package/ios/Plugin/KeychainStorage.swift +87 -0
  95. package/ios/Plugin/SQLiteStorage.swift +167 -0
  96. package/ios/Plugin/StrataStoragePlugin.swift +204 -0
  97. package/ios/Plugin/UserDefaultsStorage.swift +44 -0
  98. package/package.json +126 -0
  99. package/scripts/build.js +52 -0
  100. package/scripts/cli.js +60 -0
  101. package/scripts/configure.js +444 -0
  102. package/scripts/postinstall.js +33 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IndexedDBAdapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/web/IndexedDBAdapter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EACV,WAAW,EACX,mBAAmB,EACnB,YAAY,EACZ,YAAY,EACZ,QAAQ,EACR,cAAc,EACd,WAAW,EACZ,MAAM,SAAS,CAAC;AAIjB;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,WAAW;IAC/C,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAe;IACzC,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAUxC;IAEF,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,EAAE,CAAC,CAAc;gBAEb,MAAM,SAAkB,EAAE,SAAS,SAAY,EAAE,OAAO,SAAI;IAOxE;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAIrC;;OAEG;IACG,UAAU,CAAC,MAAM,CAAC,EAAE;QACxB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC,IAAI,CAAC;IASjB;;OAEG;YACW,YAAY;IAgC1B;;OAEG;IACG,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAiCpE;;OAEG;IACG,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IA8B1E;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAsBxC;;OAEG;IACG,KAAK,CAAC,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAwBlD;;OAEG;IACG,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IA4BxD;;OAEG;IACG,KAAK,CAAC,CAAC,GAAG,OAAO,EAAE,SAAS,EAAE,cAAc,GAAG,OAAO,CAAC,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,CAAC,CAAA;KAAE,CAAC,CAAC;IAkC9F;;OAEG;IACG,IAAI,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;IAwDjD;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC;IAQzC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAW5B;;OAEG;IACH,OAAO,CAAC,YAAY;CAMrB"}
@@ -0,0 +1,371 @@
1
+ /**
2
+ * IndexedDB Adapter - Browser IndexedDB implementation
3
+ * Provides large-scale persistent storage with advanced features
4
+ */
5
+ import { BaseAdapter } from '@/core/BaseAdapter';
6
+ import { createDeferred, getObjectSize } from '@/utils';
7
+ import { StorageError, QuotaExceededError, TransactionError } from '@/utils/errors';
8
+ /**
9
+ * Browser IndexedDB adapter
10
+ */
11
+ export class IndexedDBAdapter extends BaseAdapter {
12
+ name = 'indexedDB';
13
+ capabilities = {
14
+ persistent: true,
15
+ synchronous: false,
16
+ observable: false, // No native change events
17
+ transactional: true,
18
+ queryable: true,
19
+ maxSize: -1, // Browser dependent, typically GBs
20
+ binary: true,
21
+ encrypted: false,
22
+ crossTab: false, // No built-in cross-tab sync
23
+ };
24
+ dbName;
25
+ storeName;
26
+ version;
27
+ db;
28
+ constructor(dbName = 'StrataStorage', storeName = 'storage', version = 1) {
29
+ super();
30
+ this.dbName = dbName;
31
+ this.storeName = storeName;
32
+ this.version = version;
33
+ }
34
+ /**
35
+ * Check if IndexedDB is available
36
+ */
37
+ async isAvailable() {
38
+ return typeof window !== 'undefined' && 'indexedDB' in window && window.indexedDB !== null;
39
+ }
40
+ /**
41
+ * Initialize the adapter
42
+ */
43
+ async initialize(config) {
44
+ if (config?.dbName)
45
+ this.dbName = config.dbName;
46
+ if (config?.storeName)
47
+ this.storeName = config.storeName;
48
+ if (config?.version)
49
+ this.version = config.version;
50
+ await this.openDatabase();
51
+ this.startTTLCleanup();
52
+ }
53
+ /**
54
+ * Open IndexedDB database
55
+ */
56
+ async openDatabase() {
57
+ if (this.db)
58
+ return this.db;
59
+ const { promise, resolve, reject } = createDeferred();
60
+ const request = window.indexedDB.open(this.dbName, this.version);
61
+ request.onerror = () => reject(new StorageError(`Failed to open IndexedDB: ${request.error}`));
62
+ request.onsuccess = () => {
63
+ this.db = request.result;
64
+ resolve(request.result);
65
+ };
66
+ request.onupgradeneeded = (event) => {
67
+ const db = event.target.result;
68
+ // Create object store if it doesn't exist
69
+ if (!db.objectStoreNames.contains(this.storeName)) {
70
+ const store = db.createObjectStore(this.storeName, { keyPath: 'key' });
71
+ // Create indexes for efficient querying
72
+ store.createIndex('expires', 'expires', { unique: false });
73
+ store.createIndex('tags', 'tags', { unique: false, multiEntry: true });
74
+ store.createIndex('created', 'created', { unique: false });
75
+ store.createIndex('updated', 'updated', { unique: false });
76
+ }
77
+ };
78
+ return promise;
79
+ }
80
+ /**
81
+ * Get a value from IndexedDB
82
+ */
83
+ async get(key) {
84
+ const db = await this.openDatabase();
85
+ const { promise, resolve, reject } = createDeferred();
86
+ const transaction = db.transaction([this.storeName], 'readonly');
87
+ const store = transaction.objectStore(this.storeName);
88
+ const request = store.get(key);
89
+ request.onsuccess = () => {
90
+ const result = request.result;
91
+ if (!result) {
92
+ resolve(null);
93
+ return;
94
+ }
95
+ // Remove the key property as it's not part of StorageValue
96
+ const { key: _, ...value } = result;
97
+ // Check TTL
98
+ if (this.isExpired(value)) {
99
+ // Don't wait for removal
100
+ this.remove(key).catch(console.error);
101
+ resolve(null);
102
+ }
103
+ else {
104
+ resolve(value);
105
+ }
106
+ };
107
+ request.onerror = () => reject(new StorageError(`Failed to get key ${key}: ${request.error}`));
108
+ return promise;
109
+ }
110
+ /**
111
+ * Set a value in IndexedDB
112
+ */
113
+ async set(key, value) {
114
+ const db = await this.openDatabase();
115
+ const oldValue = await this.get(key);
116
+ const { promise, resolve, reject } = createDeferred();
117
+ const transaction = db.transaction([this.storeName], 'readwrite');
118
+ const store = transaction.objectStore(this.storeName);
119
+ // Add key to the value for IndexedDB storage
120
+ const record = { key, ...value };
121
+ const request = store.put(record);
122
+ request.onsuccess = () => {
123
+ resolve();
124
+ this.emitChange(key, oldValue?.value, value.value, 'local');
125
+ };
126
+ request.onerror = () => {
127
+ if (this.isQuotaError(request.error)) {
128
+ reject(new QuotaExceededError('IndexedDB quota exceeded', { key, size: getObjectSize(value) }));
129
+ }
130
+ else {
131
+ reject(new StorageError(`Failed to set key ${key}: ${request.error}`));
132
+ }
133
+ };
134
+ return promise;
135
+ }
136
+ /**
137
+ * Remove a value from IndexedDB
138
+ */
139
+ async remove(key) {
140
+ const db = await this.openDatabase();
141
+ const oldValue = await this.get(key);
142
+ const { promise, resolve, reject } = createDeferred();
143
+ const transaction = db.transaction([this.storeName], 'readwrite');
144
+ const store = transaction.objectStore(this.storeName);
145
+ const request = store.delete(key);
146
+ request.onsuccess = () => {
147
+ resolve();
148
+ if (oldValue) {
149
+ this.emitChange(key, oldValue.value, undefined, 'local');
150
+ }
151
+ };
152
+ request.onerror = () => reject(new StorageError(`Failed to remove key ${key}: ${request.error}`));
153
+ return promise;
154
+ }
155
+ /**
156
+ * Clear IndexedDB
157
+ */
158
+ async clear(options) {
159
+ if (!options || (!options.pattern && !options.tags && !options.expiredOnly)) {
160
+ // Clear everything
161
+ const db = await this.openDatabase();
162
+ const { promise, resolve, reject } = createDeferred();
163
+ const transaction = db.transaction([this.storeName], 'readwrite');
164
+ const store = transaction.objectStore(this.storeName);
165
+ const request = store.clear();
166
+ request.onsuccess = () => {
167
+ resolve();
168
+ this.emitChange('*', undefined, undefined, 'local');
169
+ };
170
+ request.onerror = () => reject(new StorageError(`Failed to clear store: ${request.error}`));
171
+ return promise;
172
+ }
173
+ // Use base implementation for filtered clear
174
+ await super.clear(options);
175
+ }
176
+ /**
177
+ * Get all keys
178
+ */
179
+ async keys(pattern) {
180
+ const db = await this.openDatabase();
181
+ const { promise, resolve, reject } = createDeferred();
182
+ const transaction = db.transaction([this.storeName], 'readonly');
183
+ const store = transaction.objectStore(this.storeName);
184
+ const request = store.getAllKeys();
185
+ request.onsuccess = async () => {
186
+ const allKeys = request.result;
187
+ // Filter expired keys
188
+ const validKeys = [];
189
+ for (const key of allKeys) {
190
+ const value = await this.get(key);
191
+ if (value) {
192
+ validKeys.push(key);
193
+ }
194
+ }
195
+ resolve(this.filterKeys(validKeys, pattern));
196
+ };
197
+ request.onerror = () => reject(new StorageError(`Failed to get keys: ${request.error}`));
198
+ return promise;
199
+ }
200
+ /**
201
+ * Query IndexedDB with conditions
202
+ */
203
+ async query(condition) {
204
+ const db = await this.openDatabase();
205
+ const { promise, resolve, reject } = createDeferred();
206
+ const transaction = db.transaction([this.storeName], 'readonly');
207
+ const store = transaction.objectStore(this.storeName);
208
+ const results = [];
209
+ // For now, we'll do a full scan and filter
210
+ // In the future, we can optimize using indexes
211
+ const request = store.openCursor();
212
+ request.onsuccess = (event) => {
213
+ const cursor = event.target.result;
214
+ if (cursor) {
215
+ const record = cursor.value;
216
+ const { key, ...value } = record;
217
+ if (!this.isExpired(value) && this.queryEngine.matches(value.value, condition)) {
218
+ results.push({ key, value: value.value });
219
+ }
220
+ cursor.continue();
221
+ }
222
+ else {
223
+ resolve(results);
224
+ }
225
+ };
226
+ request.onerror = () => reject(new StorageError(`Query failed: ${request.error}`));
227
+ return promise;
228
+ }
229
+ /**
230
+ * Get storage size
231
+ */
232
+ async size(detailed) {
233
+ const db = await this.openDatabase();
234
+ const { promise, resolve, reject } = createDeferred();
235
+ const transaction = db.transaction([this.storeName], 'readonly');
236
+ const store = transaction.objectStore(this.storeName);
237
+ let total = 0;
238
+ let count = 0;
239
+ let keySize = 0;
240
+ let valueSize = 0;
241
+ let metadataSize = 0;
242
+ const request = store.openCursor();
243
+ request.onsuccess = (event) => {
244
+ const cursor = event.target.result;
245
+ if (cursor) {
246
+ const record = cursor.value;
247
+ const { key, value, ...metadata } = record;
248
+ count++;
249
+ const recordKeySize = key.length * 2;
250
+ const recordValueSize = getObjectSize(value);
251
+ const recordMetadataSize = getObjectSize(metadata);
252
+ total += recordKeySize + recordValueSize + recordMetadataSize;
253
+ if (detailed) {
254
+ keySize += recordKeySize;
255
+ valueSize += recordValueSize;
256
+ metadataSize += recordMetadataSize;
257
+ }
258
+ cursor.continue();
259
+ }
260
+ else {
261
+ const result = { total, count };
262
+ if (detailed) {
263
+ result.detailed = {
264
+ keys: keySize,
265
+ values: valueSize,
266
+ metadata: metadataSize,
267
+ };
268
+ }
269
+ resolve(result);
270
+ }
271
+ };
272
+ request.onerror = () => reject(new StorageError(`Failed to calculate size: ${request.error}`));
273
+ return promise;
274
+ }
275
+ /**
276
+ * Begin a transaction
277
+ */
278
+ async transaction() {
279
+ const db = await this.openDatabase();
280
+ const txn = db.transaction([this.storeName], 'readwrite');
281
+ const store = txn.objectStore(this.storeName);
282
+ return new IndexedDBTransaction(store, txn);
283
+ }
284
+ /**
285
+ * Close the adapter
286
+ */
287
+ async close() {
288
+ if (this.db) {
289
+ this.db.close();
290
+ this.db = undefined;
291
+ }
292
+ if (super.close) {
293
+ await super.close();
294
+ }
295
+ }
296
+ /**
297
+ * Check if error is quota exceeded
298
+ */
299
+ isQuotaError(error) {
300
+ if (error instanceof Error || error instanceof DOMException) {
301
+ return error.name === 'QuotaExceededError' || error.message.toLowerCase().includes('quota');
302
+ }
303
+ return false;
304
+ }
305
+ }
306
+ /**
307
+ * IndexedDB transaction implementation
308
+ */
309
+ class IndexedDBTransaction {
310
+ store;
311
+ txn;
312
+ committed = false;
313
+ aborted = false;
314
+ constructor(store, txn) {
315
+ this.store = store;
316
+ this.txn = txn;
317
+ }
318
+ async get(key) {
319
+ if (this.aborted)
320
+ throw new TransactionError('Transaction already aborted');
321
+ const { promise, resolve, reject } = createDeferred();
322
+ const request = this.store.get(key);
323
+ request.onsuccess = () => {
324
+ const result = request.result;
325
+ resolve(result ? result.value : null);
326
+ };
327
+ request.onerror = () => reject(new StorageError(`Transaction get failed: ${request.error}`));
328
+ return promise;
329
+ }
330
+ async set(key, value) {
331
+ if (this.aborted)
332
+ throw new TransactionError('Transaction already aborted');
333
+ const { promise, resolve, reject } = createDeferred();
334
+ const now = Date.now();
335
+ const record = {
336
+ key,
337
+ value,
338
+ created: now,
339
+ updated: now,
340
+ };
341
+ const request = this.store.put(record);
342
+ request.onsuccess = () => resolve();
343
+ request.onerror = () => reject(new StorageError(`Transaction set failed: ${request.error}`));
344
+ return promise;
345
+ }
346
+ async remove(key) {
347
+ if (this.aborted)
348
+ throw new TransactionError('Transaction already aborted');
349
+ const { promise, resolve, reject } = createDeferred();
350
+ const request = this.store.delete(key);
351
+ request.onsuccess = () => resolve();
352
+ request.onerror = () => reject(new StorageError(`Transaction remove failed: ${request.error}`));
353
+ return promise;
354
+ }
355
+ async commit() {
356
+ if (this.aborted)
357
+ throw new TransactionError('Cannot commit aborted transaction');
358
+ if (this.committed)
359
+ throw new TransactionError('Transaction already committed');
360
+ this.committed = true;
361
+ // IndexedDB auto-commits when transaction completes
362
+ }
363
+ async rollback() {
364
+ if (this.committed)
365
+ throw new TransactionError('Cannot rollback committed transaction');
366
+ if (this.aborted)
367
+ return;
368
+ this.aborted = true;
369
+ this.txn.abort();
370
+ }
371
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * LocalStorage Adapter - Browser localStorage implementation
3
+ * Provides persistent storage with 5-10MB limit
4
+ */
5
+ import { BaseAdapter } from '@/core/BaseAdapter';
6
+ import type { StorageType, StorageCapabilities, StorageValue, ClearOptions, SizeInfo, SubscriptionCallback, UnsubscribeFunction } from '@/types';
7
+ /**
8
+ * Browser localStorage adapter
9
+ */
10
+ export declare class LocalStorageAdapter extends BaseAdapter {
11
+ readonly name: StorageType;
12
+ readonly capabilities: StorageCapabilities;
13
+ protected prefix: string;
14
+ protected listeners: Map<SubscriptionCallback, (event: StorageEvent) => void>;
15
+ constructor(prefix?: string);
16
+ /**
17
+ * Check if localStorage is available
18
+ */
19
+ isAvailable(): Promise<boolean>;
20
+ /**
21
+ * Initialize the adapter
22
+ */
23
+ initialize(config?: {
24
+ prefix?: string;
25
+ }): Promise<void>;
26
+ /**
27
+ * Get a value from localStorage
28
+ */
29
+ get<T = unknown>(key: string): Promise<StorageValue<T> | null>;
30
+ /**
31
+ * Set a value in localStorage
32
+ */
33
+ set<T = unknown>(key: string, value: StorageValue<T>): Promise<void>;
34
+ /**
35
+ * Remove a value from localStorage
36
+ */
37
+ remove(key: string): Promise<void>;
38
+ /**
39
+ * Clear localStorage
40
+ */
41
+ clear(options?: ClearOptions): Promise<void>;
42
+ /**
43
+ * Get all keys
44
+ */
45
+ keys(pattern?: string | RegExp): Promise<string[]>;
46
+ /**
47
+ * Get storage size
48
+ */
49
+ size(detailed?: boolean): Promise<SizeInfo>;
50
+ /**
51
+ * Subscribe to storage changes
52
+ */
53
+ subscribe(callback: SubscriptionCallback): UnsubscribeFunction;
54
+ /**
55
+ * Close the adapter
56
+ */
57
+ close(): Promise<void>;
58
+ /**
59
+ * Check if error is quota exceeded
60
+ */
61
+ protected isQuotaError(error: unknown): boolean;
62
+ }
63
+ //# sourceMappingURL=LocalStorageAdapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LocalStorageAdapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/web/LocalStorageAdapter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EACV,WAAW,EACX,mBAAmB,EACnB,YAAY,EACZ,YAAY,EACZ,QAAQ,EACR,oBAAoB,EACpB,mBAAmB,EACpB,MAAM,SAAS,CAAC;AAIjB;;GAEG;AACH,qBAAa,mBAAoB,SAAQ,WAAW;IAClD,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAkB;IAC5C,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAUxC;IAEF,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,SAAS,EAAE,GAAG,CAAC,oBAAoB,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC,CAAa;gBAE9E,MAAM,SAAY;IAK9B;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAgBrC;;OAEG;IACG,UAAU,CAAC,MAAM,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAO7D;;OAEG;IACG,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAoBpE;;OAEG;IACG,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAqB1E;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASxC;;OAEG;IACG,KAAK,CAAC,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBlD;;OAEG;IACG,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAmBxD;;OAEG;IACG,IAAI,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;IAoCjD;;OAEG;IACH,SAAS,CAAC,QAAQ,EAAE,oBAAoB,GAAG,mBAAmB;IAkC9D;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAY5B;;OAEG;IACH,SAAS,CAAC,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO;CAUhD"}
@@ -0,0 +1,238 @@
1
+ /**
2
+ * LocalStorage Adapter - Browser localStorage implementation
3
+ * Provides persistent storage with 5-10MB limit
4
+ */
5
+ import { BaseAdapter } from '@/core/BaseAdapter';
6
+ import { serialize, deserialize, getObjectSize } from '@/utils';
7
+ import { QuotaExceededError, SerializationError } from '@/utils/errors';
8
+ /**
9
+ * Browser localStorage adapter
10
+ */
11
+ export class LocalStorageAdapter extends BaseAdapter {
12
+ name = 'localStorage';
13
+ capabilities = {
14
+ persistent: true,
15
+ synchronous: false, // We use async for consistency
16
+ observable: true, // Via storage events
17
+ transactional: false,
18
+ queryable: true,
19
+ maxSize: 10 * 1024 * 1024, // Typically 5-10MB
20
+ binary: false, // Only strings
21
+ encrypted: false,
22
+ crossTab: true, // Storage events work across tabs
23
+ };
24
+ prefix;
25
+ listeners = new Map();
26
+ constructor(prefix = 'strata:') {
27
+ super();
28
+ this.prefix = prefix;
29
+ }
30
+ /**
31
+ * Check if localStorage is available
32
+ */
33
+ async isAvailable() {
34
+ try {
35
+ if (typeof window === 'undefined' || !window.localStorage) {
36
+ return false;
37
+ }
38
+ // Test if we can actually use it
39
+ const testKey = `${this.prefix}__test__`;
40
+ window.localStorage.setItem(testKey, 'test');
41
+ window.localStorage.removeItem(testKey);
42
+ return true;
43
+ }
44
+ catch {
45
+ return false;
46
+ }
47
+ }
48
+ /**
49
+ * Initialize the adapter
50
+ */
51
+ async initialize(config) {
52
+ if (config?.prefix) {
53
+ this.prefix = config.prefix;
54
+ }
55
+ this.startTTLCleanup();
56
+ }
57
+ /**
58
+ * Get a value from localStorage
59
+ */
60
+ async get(key) {
61
+ try {
62
+ const item = window.localStorage.getItem(this.prefix + key);
63
+ if (!item)
64
+ return null;
65
+ const value = deserialize(item);
66
+ // Check TTL
67
+ if (this.isExpired(value)) {
68
+ await this.remove(key);
69
+ return null;
70
+ }
71
+ return value;
72
+ }
73
+ catch (error) {
74
+ console.error(`Failed to get key ${key} from localStorage:`, error);
75
+ return null;
76
+ }
77
+ }
78
+ /**
79
+ * Set a value in localStorage
80
+ */
81
+ async set(key, value) {
82
+ const fullKey = this.prefix + key;
83
+ const oldValue = await this.get(key);
84
+ try {
85
+ const serialized = serialize(value);
86
+ window.localStorage.setItem(fullKey, serialized);
87
+ }
88
+ catch (error) {
89
+ if (this.isQuotaError(error)) {
90
+ throw new QuotaExceededError('LocalStorage quota exceeded', {
91
+ key,
92
+ size: getObjectSize(value),
93
+ });
94
+ }
95
+ throw new SerializationError(`Failed to store key ${key} in localStorage`, error);
96
+ }
97
+ // Emit change event (storage events don't fire in same window)
98
+ this.emitChange(key, oldValue?.value, value.value, 'local');
99
+ }
100
+ /**
101
+ * Remove a value from localStorage
102
+ */
103
+ async remove(key) {
104
+ const oldValue = await this.get(key);
105
+ window.localStorage.removeItem(this.prefix + key);
106
+ if (oldValue) {
107
+ this.emitChange(key, oldValue.value, undefined, 'local');
108
+ }
109
+ }
110
+ /**
111
+ * Clear localStorage
112
+ */
113
+ async clear(options) {
114
+ if (!options || (!options.pattern && !options.tags && !options.expiredOnly)) {
115
+ // Clear all with our prefix
116
+ const keysToRemove = [];
117
+ for (let i = 0; i < window.localStorage.length; i++) {
118
+ const key = window.localStorage.key(i);
119
+ if (key?.startsWith(this.prefix)) {
120
+ keysToRemove.push(key);
121
+ }
122
+ }
123
+ keysToRemove.forEach((key) => window.localStorage.removeItem(key));
124
+ this.emitChange('*', undefined, undefined, 'local');
125
+ return;
126
+ }
127
+ // Use base implementation for filtered clear
128
+ await super.clear(options);
129
+ }
130
+ /**
131
+ * Get all keys
132
+ */
133
+ async keys(pattern) {
134
+ const keys = [];
135
+ for (let i = 0; i < window.localStorage.length; i++) {
136
+ const fullKey = window.localStorage.key(i);
137
+ if (fullKey?.startsWith(this.prefix)) {
138
+ const key = fullKey.substring(this.prefix.length);
139
+ // Check if not expired
140
+ const value = await this.get(key);
141
+ if (value) {
142
+ keys.push(key);
143
+ }
144
+ }
145
+ }
146
+ return this.filterKeys(keys, pattern);
147
+ }
148
+ /**
149
+ * Get storage size
150
+ */
151
+ async size(detailed) {
152
+ let total = 0;
153
+ let count = 0;
154
+ let keySize = 0;
155
+ let valueSize = 0;
156
+ for (let i = 0; i < window.localStorage.length; i++) {
157
+ const fullKey = window.localStorage.key(i);
158
+ if (fullKey?.startsWith(this.prefix)) {
159
+ const item = window.localStorage.getItem(fullKey);
160
+ if (item) {
161
+ count++;
162
+ const itemSize = (fullKey.length + item.length) * 2; // UTF-16
163
+ total += itemSize;
164
+ if (detailed) {
165
+ keySize += fullKey.length * 2;
166
+ valueSize += item.length * 2;
167
+ }
168
+ }
169
+ }
170
+ }
171
+ const result = { total, count };
172
+ if (detailed) {
173
+ result.detailed = {
174
+ keys: keySize,
175
+ values: valueSize,
176
+ metadata: 0, // Metadata is included in values for localStorage
177
+ };
178
+ }
179
+ return result;
180
+ }
181
+ /**
182
+ * Subscribe to storage changes
183
+ */
184
+ subscribe(callback) {
185
+ const listener = (event) => {
186
+ // Only process events from other windows/tabs
187
+ if (event.storageArea !== window.localStorage)
188
+ return;
189
+ // Check if the key belongs to us
190
+ if (!event.key || !event.key.startsWith(this.prefix))
191
+ return;
192
+ const key = event.key.substring(this.prefix.length);
193
+ const oldValue = event.oldValue ? deserialize(event.oldValue) : null;
194
+ const newValue = event.newValue ? deserialize(event.newValue) : null;
195
+ callback({
196
+ key,
197
+ oldValue: oldValue?.value ?? undefined,
198
+ newValue: newValue?.value ?? undefined,
199
+ source: 'remote',
200
+ storage: this.name,
201
+ timestamp: Date.now(),
202
+ });
203
+ };
204
+ window.addEventListener('storage', listener);
205
+ this.listeners.set(callback, listener);
206
+ return () => {
207
+ const storedListener = this.listeners.get(callback);
208
+ if (storedListener) {
209
+ window.removeEventListener('storage', storedListener);
210
+ this.listeners.delete(callback);
211
+ }
212
+ };
213
+ }
214
+ /**
215
+ * Close the adapter
216
+ */
217
+ async close() {
218
+ // Remove all storage event listeners
219
+ this.listeners.forEach((listener) => {
220
+ window.removeEventListener('storage', listener);
221
+ });
222
+ this.listeners.clear();
223
+ if (super.close) {
224
+ await super.close();
225
+ }
226
+ }
227
+ /**
228
+ * Check if error is quota exceeded
229
+ */
230
+ isQuotaError(error) {
231
+ if (error instanceof Error) {
232
+ return (error.name === 'QuotaExceededError' ||
233
+ error.name === 'NS_ERROR_DOM_QUOTA_REACHED' ||
234
+ error.message.toLowerCase().includes('quota'));
235
+ }
236
+ return false;
237
+ }
238
+ }