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.
- package/Readme.md +113 -0
- package/android/src/main/java/com/strata/storage/EncryptedStorage.java +65 -0
- package/android/src/main/java/com/strata/storage/SQLiteStorage.java +147 -0
- package/android/src/main/java/com/strata/storage/SharedPreferencesStorage.java +74 -0
- package/android/src/main/java/com/stratastorage/StrataStoragePlugin.java +256 -0
- package/dist/adapters/capacitor/FilesystemAdapter.d.ts +46 -0
- package/dist/adapters/capacitor/FilesystemAdapter.d.ts.map +1 -0
- package/dist/adapters/capacitor/FilesystemAdapter.js +162 -0
- package/dist/adapters/capacitor/PreferencesAdapter.d.ts +46 -0
- package/dist/adapters/capacitor/PreferencesAdapter.d.ts.map +1 -0
- package/dist/adapters/capacitor/PreferencesAdapter.js +162 -0
- package/dist/adapters/capacitor/SecureAdapter.d.ts +69 -0
- package/dist/adapters/capacitor/SecureAdapter.d.ts.map +1 -0
- package/dist/adapters/capacitor/SecureAdapter.js +214 -0
- package/dist/adapters/capacitor/SqliteAdapter.d.ts +68 -0
- package/dist/adapters/capacitor/SqliteAdapter.d.ts.map +1 -0
- package/dist/adapters/capacitor/SqliteAdapter.js +277 -0
- package/dist/adapters/capacitor/index.d.ts +9 -0
- package/dist/adapters/capacitor/index.d.ts.map +1 -0
- package/dist/adapters/capacitor/index.js +8 -0
- package/dist/adapters/web/CacheAdapter.d.ts +91 -0
- package/dist/adapters/web/CacheAdapter.d.ts.map +1 -0
- package/dist/adapters/web/CacheAdapter.js +291 -0
- package/dist/adapters/web/CookieAdapter.d.ts +77 -0
- package/dist/adapters/web/CookieAdapter.d.ts.map +1 -0
- package/dist/adapters/web/CookieAdapter.js +260 -0
- package/dist/adapters/web/IndexedDBAdapter.d.ts +78 -0
- package/dist/adapters/web/IndexedDBAdapter.d.ts.map +1 -0
- package/dist/adapters/web/IndexedDBAdapter.js +371 -0
- package/dist/adapters/web/LocalStorageAdapter.d.ts +63 -0
- package/dist/adapters/web/LocalStorageAdapter.d.ts.map +1 -0
- package/dist/adapters/web/LocalStorageAdapter.js +238 -0
- package/dist/adapters/web/MemoryAdapter.d.ts +69 -0
- package/dist/adapters/web/MemoryAdapter.d.ts.map +1 -0
- package/dist/adapters/web/MemoryAdapter.js +165 -0
- package/dist/adapters/web/SessionStorageAdapter.d.ts +53 -0
- package/dist/adapters/web/SessionStorageAdapter.d.ts.map +1 -0
- package/dist/adapters/web/SessionStorageAdapter.js +180 -0
- package/dist/adapters/web/index.d.ts +10 -0
- package/dist/adapters/web/index.d.ts.map +1 -0
- package/dist/adapters/web/index.js +9 -0
- package/dist/core/AdapterRegistry.d.ts +52 -0
- package/dist/core/AdapterRegistry.d.ts.map +1 -0
- package/dist/core/AdapterRegistry.js +102 -0
- package/dist/core/BaseAdapter.d.ts +79 -0
- package/dist/core/BaseAdapter.d.ts.map +1 -0
- package/dist/core/BaseAdapter.js +197 -0
- package/dist/core/StorageStrategy.d.ts +55 -0
- package/dist/core/StorageStrategy.d.ts.map +1 -0
- package/dist/core/StorageStrategy.js +199 -0
- package/dist/core/Strata.d.ts +122 -0
- package/dist/core/Strata.d.ts.map +1 -0
- package/dist/core/Strata.js +568 -0
- package/dist/features/compression.d.ts +65 -0
- package/dist/features/compression.d.ts.map +1 -0
- package/dist/features/compression.js +205 -0
- package/dist/features/encryption.d.ts +68 -0
- package/dist/features/encryption.d.ts.map +1 -0
- package/dist/features/encryption.js +172 -0
- package/dist/features/migration.d.ts +17 -0
- package/dist/features/migration.d.ts.map +1 -0
- package/dist/features/migration.js +43 -0
- package/dist/features/query.d.ts +75 -0
- package/dist/features/query.d.ts.map +1 -0
- package/dist/features/query.js +305 -0
- package/dist/features/sync.d.ts +87 -0
- package/dist/features/sync.d.ts.map +1 -0
- package/dist/features/sync.js +233 -0
- package/dist/features/ttl.d.ts +124 -0
- package/dist/features/ttl.d.ts.map +1 -0
- package/dist/features/ttl.js +236 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +46 -0
- package/dist/package.json +60 -0
- package/dist/plugin/definitions.d.ts +219 -0
- package/dist/plugin/definitions.d.ts.map +1 -0
- package/dist/plugin/definitions.js +5 -0
- package/dist/plugin/index.d.ts +8 -0
- package/dist/plugin/index.d.ts.map +1 -0
- package/dist/plugin/index.js +27 -0
- package/dist/plugin/web.d.ts +24 -0
- package/dist/plugin/web.d.ts.map +1 -0
- package/dist/plugin/web.js +35 -0
- package/dist/types/index.d.ts +558 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +14 -0
- package/dist/utils/errors.d.ts +92 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +153 -0
- package/dist/utils/index.d.ts +105 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +329 -0
- package/ios/Plugin/KeychainStorage.swift +87 -0
- package/ios/Plugin/SQLiteStorage.swift +167 -0
- package/ios/Plugin/StrataStoragePlugin.swift +204 -0
- package/ios/Plugin/UserDefaultsStorage.swift +44 -0
- package/package.json +126 -0
- package/scripts/build.js +52 -0
- package/scripts/cli.js +60 -0
- package/scripts/configure.js +444 -0
- package/scripts/postinstall.js +33 -0
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strata Storage - Main entry point
|
|
3
|
+
* Zero-dependency universal storage solution
|
|
4
|
+
*/
|
|
5
|
+
import { AdapterRegistry } from './AdapterRegistry';
|
|
6
|
+
import { isBrowser, isNode, isCapacitor } from '@/utils';
|
|
7
|
+
import { StorageError, EncryptionError } from '@/utils/errors';
|
|
8
|
+
import { EncryptionManager } from '@/features/encryption';
|
|
9
|
+
import { CompressionManager } from '@/features/compression';
|
|
10
|
+
import { SyncManager } from '@/features/sync';
|
|
11
|
+
import { TTLManager } from '@/features/ttl';
|
|
12
|
+
/**
|
|
13
|
+
* Main Strata class - unified storage interface
|
|
14
|
+
*/
|
|
15
|
+
export class Strata {
|
|
16
|
+
config;
|
|
17
|
+
registry;
|
|
18
|
+
defaultAdapter;
|
|
19
|
+
adapters = new Map();
|
|
20
|
+
platform;
|
|
21
|
+
encryptionManager;
|
|
22
|
+
compressionManager;
|
|
23
|
+
syncManager;
|
|
24
|
+
ttlManager;
|
|
25
|
+
constructor(config = {}) {
|
|
26
|
+
this.config = this.normalizeConfig(config);
|
|
27
|
+
this.platform = this.detectPlatform();
|
|
28
|
+
this.registry = new AdapterRegistry();
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Initialize Strata with available adapters
|
|
32
|
+
*/
|
|
33
|
+
async initialize() {
|
|
34
|
+
// Register all adapters based on platform
|
|
35
|
+
await this.registerAdapters();
|
|
36
|
+
// Find and set default adapter
|
|
37
|
+
await this.selectDefaultAdapter();
|
|
38
|
+
// Initialize configured adapters
|
|
39
|
+
await this.initializeAdapters();
|
|
40
|
+
// Initialize encryption if enabled
|
|
41
|
+
if (this.config.encryption?.enabled) {
|
|
42
|
+
this.encryptionManager = new EncryptionManager(this.config.encryption);
|
|
43
|
+
if (!this.encryptionManager.isAvailable()) {
|
|
44
|
+
console.warn('Encryption enabled but Web Crypto API not available');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Initialize compression if enabled
|
|
48
|
+
if (this.config.compression?.enabled) {
|
|
49
|
+
this.compressionManager = new CompressionManager(this.config.compression);
|
|
50
|
+
}
|
|
51
|
+
// Initialize sync if enabled
|
|
52
|
+
if (this.config.sync?.enabled) {
|
|
53
|
+
this.syncManager = new SyncManager(this.config.sync);
|
|
54
|
+
await this.syncManager.initialize();
|
|
55
|
+
// Subscribe to sync events
|
|
56
|
+
this.syncManager.subscribe((_change) => {
|
|
57
|
+
// Forward sync events to subscribers
|
|
58
|
+
// The adapters will handle their own change events
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
// Initialize TTL manager
|
|
62
|
+
this.ttlManager = new TTLManager(this.config.ttl);
|
|
63
|
+
// Set up TTL cleanup for default adapter
|
|
64
|
+
if (this.defaultAdapter && this.config.ttl?.autoCleanup !== false) {
|
|
65
|
+
this.ttlManager.startAutoCleanup(() => this.defaultAdapter.keys(), (key) => this.defaultAdapter.get(key), (key) => this.defaultAdapter.remove(key));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Get a value from storage
|
|
70
|
+
*/
|
|
71
|
+
async get(key, options) {
|
|
72
|
+
const adapter = await this.selectAdapter(options?.storage);
|
|
73
|
+
const value = await adapter.get(key);
|
|
74
|
+
if (!value)
|
|
75
|
+
return null;
|
|
76
|
+
// Handle TTL
|
|
77
|
+
if (this.ttlManager && this.ttlManager.isExpired(value)) {
|
|
78
|
+
await adapter.remove(key);
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
// Update sliding TTL if configured
|
|
82
|
+
if (options?.sliding && value.expires && this.ttlManager) {
|
|
83
|
+
const updatedValue = this.ttlManager.updateExpiration(value, options);
|
|
84
|
+
if (updatedValue !== value) {
|
|
85
|
+
await adapter.set(key, updatedValue);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Handle decryption if needed
|
|
89
|
+
if (value.encrypted && this.encryptionManager) {
|
|
90
|
+
try {
|
|
91
|
+
if (!options?.skipDecryption) {
|
|
92
|
+
const password = options?.encryptionPassword || this.config.encryption?.password;
|
|
93
|
+
if (!password) {
|
|
94
|
+
throw new EncryptionError('Encrypted value requires password for decryption');
|
|
95
|
+
}
|
|
96
|
+
const decrypted = await this.encryptionManager.decrypt(value.value, password);
|
|
97
|
+
return decrypted;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
if (options?.ignoreDecryptionErrors) {
|
|
102
|
+
console.warn(`Failed to decrypt key ${key}:`, error);
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Handle decompression if needed
|
|
109
|
+
if (value.compressed && this.compressionManager) {
|
|
110
|
+
try {
|
|
111
|
+
const decompressed = await this.compressionManager.decompress(value.value);
|
|
112
|
+
return decompressed;
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
console.warn(`Failed to decompress key ${key}:`, error);
|
|
116
|
+
return value.value;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return value.value;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Set a value in storage
|
|
123
|
+
*/
|
|
124
|
+
async set(key, value, options) {
|
|
125
|
+
const adapter = await this.selectAdapter(options?.storage);
|
|
126
|
+
const now = Date.now();
|
|
127
|
+
let processedValue = value;
|
|
128
|
+
let compressed = false;
|
|
129
|
+
// Handle compression if needed
|
|
130
|
+
const shouldCompress = options?.compress ?? this.config.compression?.enabled;
|
|
131
|
+
if (shouldCompress && this.compressionManager) {
|
|
132
|
+
const compressedResult = await this.compressionManager.compress(value);
|
|
133
|
+
if (this.compressionManager.isCompressedData(compressedResult)) {
|
|
134
|
+
processedValue = compressedResult;
|
|
135
|
+
compressed = true;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Handle encryption if needed
|
|
139
|
+
const shouldEncrypt = options?.encrypt ?? this.config.encryption?.enabled;
|
|
140
|
+
let encrypted = false;
|
|
141
|
+
if (shouldEncrypt && this.encryptionManager) {
|
|
142
|
+
const password = options?.encryptionPassword || this.config.encryption?.password;
|
|
143
|
+
if (!password) {
|
|
144
|
+
throw new EncryptionError('Encryption enabled but no password provided');
|
|
145
|
+
}
|
|
146
|
+
processedValue = await this.encryptionManager.encrypt(value, password);
|
|
147
|
+
encrypted = true;
|
|
148
|
+
}
|
|
149
|
+
const storageValue = {
|
|
150
|
+
value: processedValue,
|
|
151
|
+
created: now,
|
|
152
|
+
updated: now,
|
|
153
|
+
expires: this.ttlManager ? this.ttlManager.calculateExpiration(options) : undefined,
|
|
154
|
+
tags: options?.tags,
|
|
155
|
+
metadata: options?.metadata,
|
|
156
|
+
encrypted: encrypted,
|
|
157
|
+
compressed: compressed,
|
|
158
|
+
};
|
|
159
|
+
await adapter.set(key, storageValue);
|
|
160
|
+
// Broadcast change for sync
|
|
161
|
+
if (this.syncManager) {
|
|
162
|
+
this.syncManager.broadcast({
|
|
163
|
+
type: 'set',
|
|
164
|
+
key,
|
|
165
|
+
value: storageValue,
|
|
166
|
+
storage: adapter.name,
|
|
167
|
+
timestamp: now,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Remove a value from storage
|
|
173
|
+
*/
|
|
174
|
+
async remove(key, options) {
|
|
175
|
+
const adapter = await this.selectAdapter(options?.storage);
|
|
176
|
+
await adapter.remove(key);
|
|
177
|
+
// Broadcast removal for sync
|
|
178
|
+
if (this.syncManager) {
|
|
179
|
+
this.syncManager.broadcast({
|
|
180
|
+
type: 'remove',
|
|
181
|
+
key,
|
|
182
|
+
storage: adapter.name,
|
|
183
|
+
timestamp: Date.now(),
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Check if a key exists
|
|
189
|
+
*/
|
|
190
|
+
async has(key, options) {
|
|
191
|
+
const adapter = await this.selectAdapter(options?.storage);
|
|
192
|
+
return adapter.has(key);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Clear storage
|
|
196
|
+
*/
|
|
197
|
+
async clear(options) {
|
|
198
|
+
if (options?.storage) {
|
|
199
|
+
const adapter = await this.selectAdapter(options.storage);
|
|
200
|
+
await adapter.clear(options);
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
// Clear all adapters
|
|
204
|
+
for (const adapter of this.adapters.values()) {
|
|
205
|
+
await adapter.clear(options);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Get all keys
|
|
211
|
+
*/
|
|
212
|
+
async keys(pattern, options) {
|
|
213
|
+
if (options?.storage) {
|
|
214
|
+
const adapter = await this.selectAdapter(options.storage);
|
|
215
|
+
return adapter.keys(pattern);
|
|
216
|
+
}
|
|
217
|
+
// Get keys from all adapters and deduplicate
|
|
218
|
+
const allKeys = new Set();
|
|
219
|
+
for (const adapter of this.adapters.values()) {
|
|
220
|
+
const keys = await adapter.keys(pattern);
|
|
221
|
+
keys.forEach((key) => allKeys.add(key));
|
|
222
|
+
}
|
|
223
|
+
return Array.from(allKeys);
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Get storage size information
|
|
227
|
+
*/
|
|
228
|
+
async size(detailed) {
|
|
229
|
+
let total = 0;
|
|
230
|
+
let count = 0;
|
|
231
|
+
const byStorage = {};
|
|
232
|
+
for (const [type, adapter] of this.adapters.entries()) {
|
|
233
|
+
const sizeInfo = await adapter.size(detailed);
|
|
234
|
+
total += sizeInfo.total;
|
|
235
|
+
count += sizeInfo.count;
|
|
236
|
+
byStorage[type] = sizeInfo.total;
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
total,
|
|
240
|
+
count,
|
|
241
|
+
byStorage: byStorage,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Subscribe to storage changes
|
|
246
|
+
*/
|
|
247
|
+
subscribe(callback, options) {
|
|
248
|
+
const unsubscribers = [];
|
|
249
|
+
if (options?.storage) {
|
|
250
|
+
const adapter = this.adapters.get(options.storage);
|
|
251
|
+
if (adapter?.subscribe) {
|
|
252
|
+
unsubscribers.push(adapter.subscribe(callback));
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
// Subscribe to all adapters that support it
|
|
257
|
+
for (const adapter of this.adapters.values()) {
|
|
258
|
+
if (adapter.subscribe) {
|
|
259
|
+
unsubscribers.push(adapter.subscribe(callback));
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// Return function to unsubscribe from all
|
|
264
|
+
return () => {
|
|
265
|
+
unsubscribers.forEach((unsub) => unsub());
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Query storage (if supported)
|
|
270
|
+
*/
|
|
271
|
+
async query(condition, options) {
|
|
272
|
+
const adapter = await this.selectAdapter(options?.storage);
|
|
273
|
+
if (!adapter.query) {
|
|
274
|
+
throw new StorageError(`Adapter ${adapter.name} does not support queries`);
|
|
275
|
+
}
|
|
276
|
+
return adapter.query(condition);
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Export storage data
|
|
280
|
+
*/
|
|
281
|
+
async export(options) {
|
|
282
|
+
const data = {};
|
|
283
|
+
const keys = options?.keys || (await this.keys());
|
|
284
|
+
for (const key of keys) {
|
|
285
|
+
const value = await this.get(key);
|
|
286
|
+
if (value !== null) {
|
|
287
|
+
if (options?.includeMetadata) {
|
|
288
|
+
const adapter = await this.selectAdapter();
|
|
289
|
+
const storageValue = await adapter.get(key);
|
|
290
|
+
data[key] = storageValue;
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
data[key] = value;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
const format = options?.format || 'json';
|
|
298
|
+
if (format === 'json') {
|
|
299
|
+
return JSON.stringify(data, null, options?.pretty ? 2 : 0);
|
|
300
|
+
}
|
|
301
|
+
throw new StorageError(`Export format ${format} not supported`);
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Import storage data
|
|
305
|
+
*/
|
|
306
|
+
async import(data, options) {
|
|
307
|
+
const format = options?.format || 'json';
|
|
308
|
+
if (format !== 'json') {
|
|
309
|
+
throw new StorageError(`Import format ${format} not supported`);
|
|
310
|
+
}
|
|
311
|
+
const parsed = JSON.parse(data);
|
|
312
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
313
|
+
const exists = await this.has(key);
|
|
314
|
+
if (!exists || options?.overwrite) {
|
|
315
|
+
await this.set(key, value);
|
|
316
|
+
}
|
|
317
|
+
else if (options?.merge) {
|
|
318
|
+
const existing = await this.get(key);
|
|
319
|
+
if (options.merge === 'deep' && typeof existing === 'object' && typeof value === 'object') {
|
|
320
|
+
// Deep merge will be implemented with utils
|
|
321
|
+
await this.set(key, {
|
|
322
|
+
...existing,
|
|
323
|
+
...value,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
await this.set(key, value);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Get available storage types
|
|
334
|
+
*/
|
|
335
|
+
getAvailableStorageTypes() {
|
|
336
|
+
return Array.from(this.adapters.keys());
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Get adapter capabilities
|
|
340
|
+
*/
|
|
341
|
+
getCapabilities(storage) {
|
|
342
|
+
if (storage) {
|
|
343
|
+
const adapter = this.adapters.get(storage);
|
|
344
|
+
return adapter ? adapter.capabilities : {};
|
|
345
|
+
}
|
|
346
|
+
// Return capabilities of all adapters
|
|
347
|
+
const capabilities = {};
|
|
348
|
+
for (const [type, adapter] of this.adapters.entries()) {
|
|
349
|
+
capabilities[type] = adapter.capabilities;
|
|
350
|
+
}
|
|
351
|
+
return capabilities;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Generate a secure password for encryption
|
|
355
|
+
*/
|
|
356
|
+
generatePassword(length) {
|
|
357
|
+
if (!this.encryptionManager) {
|
|
358
|
+
throw new EncryptionError('Encryption not initialized');
|
|
359
|
+
}
|
|
360
|
+
return this.encryptionManager.generatePassword(length);
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Hash data using SHA-256
|
|
364
|
+
*/
|
|
365
|
+
async hash(data) {
|
|
366
|
+
if (!this.encryptionManager) {
|
|
367
|
+
throw new EncryptionError('Encryption not initialized');
|
|
368
|
+
}
|
|
369
|
+
return this.encryptionManager.hash(data);
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Get TTL (time to live) for a key
|
|
373
|
+
*/
|
|
374
|
+
async getTTL(key, options) {
|
|
375
|
+
if (!this.ttlManager)
|
|
376
|
+
return null;
|
|
377
|
+
const adapter = await this.selectAdapter(options?.storage);
|
|
378
|
+
const value = await adapter.get(key);
|
|
379
|
+
if (!value)
|
|
380
|
+
return null;
|
|
381
|
+
return this.ttlManager.getTimeToLive(value);
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Extend TTL for a key
|
|
385
|
+
*/
|
|
386
|
+
async extendTTL(key, extension, options) {
|
|
387
|
+
if (!this.ttlManager) {
|
|
388
|
+
throw new StorageError('TTL manager not initialized');
|
|
389
|
+
}
|
|
390
|
+
const adapter = await this.selectAdapter(options?.storage);
|
|
391
|
+
const value = await adapter.get(key);
|
|
392
|
+
if (!value) {
|
|
393
|
+
throw new StorageError(`Key ${key} not found`);
|
|
394
|
+
}
|
|
395
|
+
const updated = this.ttlManager.extendTTL(value, extension);
|
|
396
|
+
await adapter.set(key, updated);
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Make a key persistent (remove TTL)
|
|
400
|
+
*/
|
|
401
|
+
async persist(key, options) {
|
|
402
|
+
if (!this.ttlManager) {
|
|
403
|
+
throw new StorageError('TTL manager not initialized');
|
|
404
|
+
}
|
|
405
|
+
const adapter = await this.selectAdapter(options?.storage);
|
|
406
|
+
const value = await adapter.get(key);
|
|
407
|
+
if (!value) {
|
|
408
|
+
throw new StorageError(`Key ${key} not found`);
|
|
409
|
+
}
|
|
410
|
+
const persisted = this.ttlManager.persist(value);
|
|
411
|
+
await adapter.set(key, persisted);
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Get items expiring within a time window
|
|
415
|
+
*/
|
|
416
|
+
async getExpiring(timeWindow, options) {
|
|
417
|
+
if (!this.ttlManager)
|
|
418
|
+
return [];
|
|
419
|
+
const adapter = await this.selectAdapter(options?.storage);
|
|
420
|
+
return this.ttlManager.getExpiring(timeWindow, () => adapter.keys(), (key) => adapter.get(key));
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Manually trigger TTL cleanup
|
|
424
|
+
*/
|
|
425
|
+
async cleanupExpired(options) {
|
|
426
|
+
if (!this.ttlManager)
|
|
427
|
+
return 0;
|
|
428
|
+
const adapter = await this.selectAdapter(options?.storage);
|
|
429
|
+
const expired = await this.ttlManager.cleanup(() => adapter.keys(), (key) => adapter.get(key), (key) => adapter.remove(key));
|
|
430
|
+
return expired.length;
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Close all adapters
|
|
434
|
+
*/
|
|
435
|
+
async close() {
|
|
436
|
+
for (const adapter of this.adapters.values()) {
|
|
437
|
+
if (adapter.close) {
|
|
438
|
+
await adapter.close();
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
this.adapters.clear();
|
|
442
|
+
// Clear encryption cache
|
|
443
|
+
if (this.encryptionManager) {
|
|
444
|
+
this.encryptionManager.clearCache();
|
|
445
|
+
}
|
|
446
|
+
// Close sync manager
|
|
447
|
+
if (this.syncManager) {
|
|
448
|
+
this.syncManager.close();
|
|
449
|
+
}
|
|
450
|
+
// Clear TTL manager
|
|
451
|
+
if (this.ttlManager) {
|
|
452
|
+
this.ttlManager.clear();
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
// Private methods
|
|
456
|
+
normalizeConfig(config) {
|
|
457
|
+
return {
|
|
458
|
+
platform: config.platform || this.detectPlatform(),
|
|
459
|
+
defaultStorages: config.defaultStorages || this.getDefaultStorages(),
|
|
460
|
+
...config,
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
detectPlatform() {
|
|
464
|
+
if (isCapacitor())
|
|
465
|
+
return 'web'; // Capacitor runs in web context
|
|
466
|
+
if (isBrowser())
|
|
467
|
+
return 'web';
|
|
468
|
+
if (isNode())
|
|
469
|
+
return 'node';
|
|
470
|
+
return 'web'; // Default to web
|
|
471
|
+
}
|
|
472
|
+
getDefaultStorages() {
|
|
473
|
+
switch (this.platform) {
|
|
474
|
+
case 'web':
|
|
475
|
+
if (isCapacitor()) {
|
|
476
|
+
return ['preferences', 'sqlite', 'indexedDB', 'localStorage', 'memory'];
|
|
477
|
+
}
|
|
478
|
+
return ['indexedDB', 'localStorage', 'memory'];
|
|
479
|
+
case 'node':
|
|
480
|
+
return ['filesystem', 'memory'];
|
|
481
|
+
case 'ios':
|
|
482
|
+
case 'android':
|
|
483
|
+
return ['preferences', 'sqlite', 'secure', 'memory'];
|
|
484
|
+
default:
|
|
485
|
+
return ['memory'];
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
async registerAdapters() {
|
|
489
|
+
// Register adapters based on platform
|
|
490
|
+
if (this.platform === 'web') {
|
|
491
|
+
// Dynamically import and register web adapters
|
|
492
|
+
const { MemoryAdapter } = await import('@/adapters/web/MemoryAdapter');
|
|
493
|
+
const { LocalStorageAdapter } = await import('@/adapters/web/LocalStorageAdapter');
|
|
494
|
+
const { SessionStorageAdapter } = await import('@/adapters/web/SessionStorageAdapter');
|
|
495
|
+
const { IndexedDBAdapter } = await import('@/adapters/web/IndexedDBAdapter');
|
|
496
|
+
const { CookieAdapter } = await import('@/adapters/web/CookieAdapter');
|
|
497
|
+
const { CacheAdapter } = await import('@/adapters/web/CacheAdapter');
|
|
498
|
+
this.registry.register(new MemoryAdapter());
|
|
499
|
+
this.registry.register(new LocalStorageAdapter());
|
|
500
|
+
this.registry.register(new SessionStorageAdapter());
|
|
501
|
+
this.registry.register(new IndexedDBAdapter());
|
|
502
|
+
this.registry.register(new CookieAdapter());
|
|
503
|
+
this.registry.register(new CacheAdapter());
|
|
504
|
+
// If running in Capacitor, also register native adapters
|
|
505
|
+
if (isCapacitor()) {
|
|
506
|
+
const { PreferencesAdapter } = await import('@/adapters/capacitor/PreferencesAdapter');
|
|
507
|
+
const { SqliteAdapter } = await import('@/adapters/capacitor/SqliteAdapter');
|
|
508
|
+
const { SecureAdapter } = await import('@/adapters/capacitor/SecureAdapter');
|
|
509
|
+
const { FilesystemAdapter } = await import('@/adapters/capacitor/FilesystemAdapter');
|
|
510
|
+
this.registry.register(new PreferencesAdapter());
|
|
511
|
+
this.registry.register(new SqliteAdapter());
|
|
512
|
+
this.registry.register(new SecureAdapter());
|
|
513
|
+
this.registry.register(new FilesystemAdapter());
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
// Additional adapters will be registered as they are implemented
|
|
517
|
+
}
|
|
518
|
+
async selectDefaultAdapter() {
|
|
519
|
+
const storages = this.config.defaultStorages || this.getDefaultStorages();
|
|
520
|
+
for (const storage of storages) {
|
|
521
|
+
try {
|
|
522
|
+
const adapter = await this.registry.getInitialized(storage, this.config.adapters?.[storage]);
|
|
523
|
+
this.defaultAdapter = adapter;
|
|
524
|
+
this.adapters.set(storage, adapter);
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
catch {
|
|
528
|
+
// Continue to next adapter
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
throw new StorageError('No available storage adapters found');
|
|
532
|
+
}
|
|
533
|
+
async initializeAdapters() {
|
|
534
|
+
for (const [type, adapter] of this.adapters.entries()) {
|
|
535
|
+
const config = this.config.adapters?.[type];
|
|
536
|
+
if (config && typeof config === 'object') {
|
|
537
|
+
await adapter.initialize(config);
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
await adapter.initialize();
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
async selectAdapter(storage) {
|
|
545
|
+
if (!storage) {
|
|
546
|
+
if (!this.defaultAdapter) {
|
|
547
|
+
throw new StorageError('No default adapter available');
|
|
548
|
+
}
|
|
549
|
+
return this.defaultAdapter;
|
|
550
|
+
}
|
|
551
|
+
const storages = Array.isArray(storage) ? storage : [storage];
|
|
552
|
+
for (const s of storages) {
|
|
553
|
+
const adapter = this.adapters.get(s);
|
|
554
|
+
if (adapter)
|
|
555
|
+
return adapter;
|
|
556
|
+
}
|
|
557
|
+
// Try to load adapter if not already loaded
|
|
558
|
+
for (const s of storages) {
|
|
559
|
+
const adapter = this.registry.get(s);
|
|
560
|
+
if (adapter && (await adapter.isAvailable())) {
|
|
561
|
+
await adapter.initialize();
|
|
562
|
+
this.adapters.set(s, adapter);
|
|
563
|
+
return adapter;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
throw new StorageError(`No available adapter found for storage types: ${storages.join(', ')}`);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compression Feature - Pure JavaScript LZ-string implementation
|
|
3
|
+
* Zero-dependency compression/decompression for storage values
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Compression configuration
|
|
7
|
+
*/
|
|
8
|
+
export interface CompressionConfig {
|
|
9
|
+
algorithm?: 'lz' | 'gzip';
|
|
10
|
+
threshold?: number;
|
|
11
|
+
level?: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Compressed data structure
|
|
15
|
+
*/
|
|
16
|
+
export interface CompressedData {
|
|
17
|
+
data: string;
|
|
18
|
+
algorithm: string;
|
|
19
|
+
originalSize: number;
|
|
20
|
+
compressedSize: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Compression manager using pure JavaScript LZ-string algorithm
|
|
24
|
+
*/
|
|
25
|
+
export declare class CompressionManager {
|
|
26
|
+
private config;
|
|
27
|
+
constructor(config?: CompressionConfig);
|
|
28
|
+
/**
|
|
29
|
+
* Compress data using LZ-string algorithm
|
|
30
|
+
*/
|
|
31
|
+
compress(data: unknown): Promise<CompressedData | unknown>;
|
|
32
|
+
/**
|
|
33
|
+
* Decompress data
|
|
34
|
+
*/
|
|
35
|
+
decompress<T = unknown>(compressedData: CompressedData): Promise<T>;
|
|
36
|
+
/**
|
|
37
|
+
* Check if data is compressed
|
|
38
|
+
*/
|
|
39
|
+
isCompressedData(data: unknown): data is CompressedData;
|
|
40
|
+
/**
|
|
41
|
+
* LZ-string compression implementation
|
|
42
|
+
*/
|
|
43
|
+
private lzCompress;
|
|
44
|
+
/**
|
|
45
|
+
* LZ-string decompression implementation
|
|
46
|
+
*/
|
|
47
|
+
private lzDecompress;
|
|
48
|
+
/**
|
|
49
|
+
* Base64 encoding for compressed data
|
|
50
|
+
*/
|
|
51
|
+
private encodeToBase64;
|
|
52
|
+
/**
|
|
53
|
+
* Base64 decoding for compressed data
|
|
54
|
+
*/
|
|
55
|
+
private decodeFromBase64;
|
|
56
|
+
/**
|
|
57
|
+
* Get compression ratio
|
|
58
|
+
*/
|
|
59
|
+
getCompressionRatio(compressedData: CompressedData): number;
|
|
60
|
+
/**
|
|
61
|
+
* Get savings percentage
|
|
62
|
+
*/
|
|
63
|
+
getSavingsPercentage(compressedData: CompressedData): number;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=compression.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compression.d.ts","sourceRoot":"","sources":["../../src/features/compression.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,MAAM,CAA8B;gBAEhC,MAAM,GAAE,iBAAsB;IAQ1C;;OAEG;IACG,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,cAAc,GAAG,OAAO,CAAC;IA8BhE;;OAEG;IACG,UAAU,CAAC,CAAC,GAAG,OAAO,EAAE,cAAc,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;IAazE;;OAEG;IACH,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,cAAc;IAWvD;;OAEG;IACH,OAAO,CAAC,UAAU;IAmClB;;OAEG;IACH,OAAO,CAAC,YAAY;IA+BpB;;OAEG;IACH,OAAO,CAAC,cAAc;IA2BtB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IA6BxB;;OAEG;IACH,mBAAmB,CAAC,cAAc,EAAE,cAAc,GAAG,MAAM;IAI3D;;OAEG;IACH,oBAAoB,CAAC,cAAc,EAAE,cAAc,GAAG,MAAM;CAO7D"}
|