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,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache Adapter - Service Worker Cache API implementation
|
|
3
|
+
* Provides network-aware storage for offline support
|
|
4
|
+
*/
|
|
5
|
+
import { BaseAdapter } from '@/core/BaseAdapter';
|
|
6
|
+
import { getObjectSize } from '@/utils';
|
|
7
|
+
import { StorageError, QuotaExceededError, NotSupportedError } from '@/utils/errors';
|
|
8
|
+
/**
|
|
9
|
+
* Cache API adapter for Service Worker environments
|
|
10
|
+
*/
|
|
11
|
+
export class CacheAdapter extends BaseAdapter {
|
|
12
|
+
name = 'cache';
|
|
13
|
+
capabilities = {
|
|
14
|
+
persistent: true,
|
|
15
|
+
synchronous: false,
|
|
16
|
+
observable: false, // No native change events
|
|
17
|
+
transactional: false,
|
|
18
|
+
queryable: true,
|
|
19
|
+
maxSize: -1, // Browser dependent, typically GBs
|
|
20
|
+
binary: true, // Supports binary data via Response
|
|
21
|
+
encrypted: false,
|
|
22
|
+
crossTab: true, // Shared across tabs via Service Worker
|
|
23
|
+
};
|
|
24
|
+
cacheName;
|
|
25
|
+
baseUrl;
|
|
26
|
+
cache;
|
|
27
|
+
constructor(cacheName = 'strata-storage-v1', baseUrl = 'https://strata.local/') {
|
|
28
|
+
super();
|
|
29
|
+
this.cacheName = cacheName;
|
|
30
|
+
this.baseUrl = baseUrl;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Check if Cache API is available
|
|
34
|
+
*/
|
|
35
|
+
async isAvailable() {
|
|
36
|
+
return typeof window !== 'undefined' && 'caches' in window && typeof caches.open === 'function';
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Initialize the adapter
|
|
40
|
+
*/
|
|
41
|
+
async initialize(config) {
|
|
42
|
+
if (config?.cacheName)
|
|
43
|
+
this.cacheName = config.cacheName;
|
|
44
|
+
if (config?.baseUrl)
|
|
45
|
+
this.baseUrl = config.baseUrl;
|
|
46
|
+
await this.openCache();
|
|
47
|
+
this.startTTLCleanup();
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Open cache
|
|
51
|
+
*/
|
|
52
|
+
async openCache() {
|
|
53
|
+
if (this.cache)
|
|
54
|
+
return this.cache;
|
|
55
|
+
if (!('caches' in window)) {
|
|
56
|
+
throw new NotSupportedError('Cache API not available');
|
|
57
|
+
}
|
|
58
|
+
this.cache = await caches.open(this.cacheName);
|
|
59
|
+
return this.cache;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Create URL for key
|
|
63
|
+
*/
|
|
64
|
+
keyToUrl(key) {
|
|
65
|
+
return new URL(encodeURIComponent(key), this.baseUrl).href;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Extract key from URL
|
|
69
|
+
*/
|
|
70
|
+
urlToKey(url) {
|
|
71
|
+
const urlObj = new URL(url);
|
|
72
|
+
const pathname = urlObj.pathname;
|
|
73
|
+
const lastSegment = pathname.split('/').pop() || '';
|
|
74
|
+
return decodeURIComponent(lastSegment);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Get a value from cache
|
|
78
|
+
*/
|
|
79
|
+
async get(key) {
|
|
80
|
+
const cache = await this.openCache();
|
|
81
|
+
const url = this.keyToUrl(key);
|
|
82
|
+
try {
|
|
83
|
+
const response = await cache.match(url);
|
|
84
|
+
if (!response)
|
|
85
|
+
return null;
|
|
86
|
+
const data = (await response.json());
|
|
87
|
+
// Check TTL
|
|
88
|
+
if (this.isExpired(data)) {
|
|
89
|
+
await this.remove(key);
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
return data;
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
console.error(`Failed to get key ${key} from cache:`, error);
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Set a value in cache
|
|
101
|
+
*/
|
|
102
|
+
async set(key, value) {
|
|
103
|
+
const cache = await this.openCache();
|
|
104
|
+
const url = this.keyToUrl(key);
|
|
105
|
+
const oldValue = await this.get(key);
|
|
106
|
+
try {
|
|
107
|
+
// Create Response with the data
|
|
108
|
+
const response = new Response(JSON.stringify(value), {
|
|
109
|
+
headers: {
|
|
110
|
+
'Content-Type': 'application/json',
|
|
111
|
+
'X-Strata-Created': value.created.toString(),
|
|
112
|
+
'X-Strata-Updated': value.updated.toString(),
|
|
113
|
+
'X-Strata-Expires': value.expires?.toString() || '',
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
await cache.put(url, response);
|
|
117
|
+
this.emitChange(key, oldValue?.value, value.value, 'local');
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
if (this.isQuotaError(error)) {
|
|
121
|
+
throw new QuotaExceededError('Cache quota exceeded', { key, size: getObjectSize(value) });
|
|
122
|
+
}
|
|
123
|
+
throw new StorageError(`Failed to set key ${key} in cache: ${error}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Remove a value from cache
|
|
128
|
+
*/
|
|
129
|
+
async remove(key) {
|
|
130
|
+
const cache = await this.openCache();
|
|
131
|
+
const url = this.keyToUrl(key);
|
|
132
|
+
const oldValue = await this.get(key);
|
|
133
|
+
const deleted = await cache.delete(url);
|
|
134
|
+
if (deleted && oldValue) {
|
|
135
|
+
this.emitChange(key, oldValue.value, undefined, 'local');
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Clear cache
|
|
140
|
+
*/
|
|
141
|
+
async clear(options) {
|
|
142
|
+
if (!options || (!options.pattern && !options.tags && !options.expiredOnly)) {
|
|
143
|
+
// Delete and recreate cache
|
|
144
|
+
await caches.delete(this.cacheName);
|
|
145
|
+
this.cache = await caches.open(this.cacheName);
|
|
146
|
+
this.emitChange('*', undefined, undefined, 'local');
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
// Use base implementation for filtered clear
|
|
150
|
+
await super.clear(options);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Get all keys
|
|
154
|
+
*/
|
|
155
|
+
async keys(pattern) {
|
|
156
|
+
const cache = await this.openCache();
|
|
157
|
+
const requests = await cache.keys();
|
|
158
|
+
const keys = [];
|
|
159
|
+
for (const request of requests) {
|
|
160
|
+
const key = this.urlToKey(request.url);
|
|
161
|
+
// Check if not expired
|
|
162
|
+
const value = await this.get(key);
|
|
163
|
+
if (value) {
|
|
164
|
+
keys.push(key);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return this.filterKeys(keys, pattern);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Query cache with conditions
|
|
171
|
+
*/
|
|
172
|
+
async query(condition) {
|
|
173
|
+
const cache = await this.openCache();
|
|
174
|
+
const requests = await cache.keys();
|
|
175
|
+
const results = [];
|
|
176
|
+
for (const request of requests) {
|
|
177
|
+
const key = this.urlToKey(request.url);
|
|
178
|
+
const value = await this.get(key);
|
|
179
|
+
if (value && this.queryEngine.matches(value.value, condition)) {
|
|
180
|
+
results.push({ key, value: value.value });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return results;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Get storage size
|
|
187
|
+
*/
|
|
188
|
+
async size(detailed) {
|
|
189
|
+
const cache = await this.openCache();
|
|
190
|
+
const requests = await cache.keys();
|
|
191
|
+
let total = 0;
|
|
192
|
+
let count = 0;
|
|
193
|
+
let keySize = 0;
|
|
194
|
+
let valueSize = 0;
|
|
195
|
+
let metadataSize = 0;
|
|
196
|
+
for (const request of requests) {
|
|
197
|
+
const response = await cache.match(request);
|
|
198
|
+
if (response) {
|
|
199
|
+
count++;
|
|
200
|
+
const key = this.urlToKey(request.url);
|
|
201
|
+
const blob = await response.blob();
|
|
202
|
+
const contentSize = blob.size;
|
|
203
|
+
total += key.length * 2 + contentSize;
|
|
204
|
+
if (detailed) {
|
|
205
|
+
keySize += key.length * 2;
|
|
206
|
+
valueSize += contentSize;
|
|
207
|
+
// Headers contribute to metadata
|
|
208
|
+
const headers = response.headers;
|
|
209
|
+
headers.forEach((value, key) => {
|
|
210
|
+
metadataSize += (key.length + value.length) * 2;
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const result = { total, count };
|
|
216
|
+
if (detailed) {
|
|
217
|
+
result.detailed = {
|
|
218
|
+
keys: keySize,
|
|
219
|
+
values: valueSize,
|
|
220
|
+
metadata: metadataSize,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
return result;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Store binary data
|
|
227
|
+
*/
|
|
228
|
+
async setBinary(key, data, metadata) {
|
|
229
|
+
const cache = await this.openCache();
|
|
230
|
+
const url = this.keyToUrl(key);
|
|
231
|
+
const now = Date.now();
|
|
232
|
+
// Create storage value for metadata
|
|
233
|
+
const storageMetadata = {
|
|
234
|
+
value: metadata || {},
|
|
235
|
+
created: now,
|
|
236
|
+
updated: now,
|
|
237
|
+
metadata: { binary: true, size: data instanceof ArrayBuffer ? data.byteLength : data.size },
|
|
238
|
+
};
|
|
239
|
+
// Create Response with binary data
|
|
240
|
+
const response = new Response(data, {
|
|
241
|
+
headers: {
|
|
242
|
+
'Content-Type': 'application/octet-stream',
|
|
243
|
+
'X-Strata-Metadata': JSON.stringify(storageMetadata),
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
await cache.put(url, response);
|
|
247
|
+
this.emitChange(key, undefined, metadata || {}, 'local');
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Get binary data
|
|
251
|
+
*/
|
|
252
|
+
async getBinary(key) {
|
|
253
|
+
const cache = await this.openCache();
|
|
254
|
+
const url = this.keyToUrl(key);
|
|
255
|
+
try {
|
|
256
|
+
const response = await cache.match(url);
|
|
257
|
+
if (!response)
|
|
258
|
+
return null;
|
|
259
|
+
const metadataHeader = response.headers.get('X-Strata-Metadata');
|
|
260
|
+
const metadata = metadataHeader ? JSON.parse(metadataHeader) : null;
|
|
261
|
+
// Check if it's binary data
|
|
262
|
+
if (response.headers.get('Content-Type') !== 'application/octet-stream') {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
const data = await response.arrayBuffer();
|
|
266
|
+
return { data, metadata: metadata?.value };
|
|
267
|
+
}
|
|
268
|
+
catch (error) {
|
|
269
|
+
console.error(`Failed to get binary data for key ${key}:`, error);
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Close the adapter
|
|
275
|
+
*/
|
|
276
|
+
async close() {
|
|
277
|
+
this.cache = undefined;
|
|
278
|
+
if (super.close) {
|
|
279
|
+
await super.close();
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Check if error is quota exceeded
|
|
284
|
+
*/
|
|
285
|
+
isQuotaError(error) {
|
|
286
|
+
if (error instanceof Error || error instanceof DOMException) {
|
|
287
|
+
return error.name === 'QuotaExceededError' || error.message.toLowerCase().includes('quota');
|
|
288
|
+
}
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cookie Adapter - Browser cookie implementation
|
|
3
|
+
* Provides limited storage with 4KB per cookie limit
|
|
4
|
+
*/
|
|
5
|
+
import { BaseAdapter } from '@/core/BaseAdapter';
|
|
6
|
+
import type { StorageType, StorageCapabilities, StorageValue, ClearOptions, SizeInfo } from '@/types';
|
|
7
|
+
/**
|
|
8
|
+
* Cookie options
|
|
9
|
+
*/
|
|
10
|
+
interface CookieOptions {
|
|
11
|
+
domain?: string;
|
|
12
|
+
path?: string;
|
|
13
|
+
secure?: boolean;
|
|
14
|
+
sameSite?: 'strict' | 'lax' | 'none';
|
|
15
|
+
maxAge?: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Browser cookie adapter
|
|
19
|
+
*/
|
|
20
|
+
export declare class CookieAdapter extends BaseAdapter {
|
|
21
|
+
readonly name: StorageType;
|
|
22
|
+
readonly capabilities: StorageCapabilities;
|
|
23
|
+
private prefix;
|
|
24
|
+
private cookieOptions;
|
|
25
|
+
private maxCookieSize;
|
|
26
|
+
constructor(prefix?: string, options?: CookieOptions);
|
|
27
|
+
/**
|
|
28
|
+
* Check if cookies are available
|
|
29
|
+
*/
|
|
30
|
+
isAvailable(): Promise<boolean>;
|
|
31
|
+
/**
|
|
32
|
+
* Initialize the adapter
|
|
33
|
+
*/
|
|
34
|
+
initialize(config?: CookieOptions): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Get a value from cookies
|
|
37
|
+
*/
|
|
38
|
+
get<T = unknown>(key: string): Promise<StorageValue<T> | null>;
|
|
39
|
+
/**
|
|
40
|
+
* Set a value in cookies
|
|
41
|
+
*/
|
|
42
|
+
set<T = unknown>(key: string, value: StorageValue<T>): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Remove a value from cookies
|
|
45
|
+
*/
|
|
46
|
+
remove(key: string): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Clear cookies
|
|
49
|
+
*/
|
|
50
|
+
clear(options?: ClearOptions): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Get all keys
|
|
53
|
+
*/
|
|
54
|
+
keys(pattern?: string | RegExp): Promise<string[]>;
|
|
55
|
+
/**
|
|
56
|
+
* Get storage size
|
|
57
|
+
*/
|
|
58
|
+
size(detailed?: boolean): Promise<SizeInfo>;
|
|
59
|
+
/**
|
|
60
|
+
* Get a cookie value
|
|
61
|
+
*/
|
|
62
|
+
private getCookie;
|
|
63
|
+
/**
|
|
64
|
+
* Set a cookie
|
|
65
|
+
*/
|
|
66
|
+
private setCookie;
|
|
67
|
+
/**
|
|
68
|
+
* Delete a cookie
|
|
69
|
+
*/
|
|
70
|
+
private deleteCookie;
|
|
71
|
+
/**
|
|
72
|
+
* Get all cookies as key-value pairs
|
|
73
|
+
*/
|
|
74
|
+
private getAllCookies;
|
|
75
|
+
}
|
|
76
|
+
export {};
|
|
77
|
+
//# sourceMappingURL=CookieAdapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CookieAdapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/web/CookieAdapter.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,EACT,MAAM,SAAS,CAAC;AAGjB;;GAEG;AACH,UAAU,aAAa;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,qBAAa,aAAc,SAAQ,WAAW;IAC5C,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAa;IACvC,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAUxC;IAEF,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,aAAa,CAAQ;gBAEjB,MAAM,SAAY,EAAE,OAAO,GAAE,aAAkB;IAS3D;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAiBrC;;OAEG;IACG,UAAU,CAAC,MAAM,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAOvD;;OAEG;IACG,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAuBpE;;OAEG;IACG,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAqC1E;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWxC;;OAEG;IACG,KAAK,CAAC,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBlD;;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;IAmCjD;;OAEG;IACH,OAAO,CAAC,SAAS;IAgBjB;;OAEG;IACH,OAAO,CAAC,SAAS;IA4BjB;;OAEG;IACH,OAAO,CAAC,YAAY;IAIpB;;OAEG;IACH,OAAO,CAAC,aAAa;CAmBtB"}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cookie Adapter - Browser cookie implementation
|
|
3
|
+
* Provides limited storage with 4KB per cookie limit
|
|
4
|
+
*/
|
|
5
|
+
import { BaseAdapter } from '@/core/BaseAdapter';
|
|
6
|
+
import { StorageError, QuotaExceededError } from '@/utils/errors';
|
|
7
|
+
/**
|
|
8
|
+
* Browser cookie adapter
|
|
9
|
+
*/
|
|
10
|
+
export class CookieAdapter extends BaseAdapter {
|
|
11
|
+
name = 'cookies';
|
|
12
|
+
capabilities = {
|
|
13
|
+
persistent: true,
|
|
14
|
+
synchronous: false,
|
|
15
|
+
observable: false, // No native change events
|
|
16
|
+
transactional: false,
|
|
17
|
+
queryable: true,
|
|
18
|
+
maxSize: 4096, // 4KB per cookie
|
|
19
|
+
binary: false,
|
|
20
|
+
encrypted: false,
|
|
21
|
+
crossTab: true, // Cookies are shared across tabs
|
|
22
|
+
};
|
|
23
|
+
prefix;
|
|
24
|
+
cookieOptions;
|
|
25
|
+
maxCookieSize = 4096; // 4KB limit per cookie
|
|
26
|
+
constructor(prefix = 'strata_', options = {}) {
|
|
27
|
+
super();
|
|
28
|
+
this.prefix = prefix;
|
|
29
|
+
this.cookieOptions = {
|
|
30
|
+
path: '/',
|
|
31
|
+
...options,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Check if cookies are available
|
|
36
|
+
*/
|
|
37
|
+
async isAvailable() {
|
|
38
|
+
if (typeof window === 'undefined' || !navigator.cookieEnabled) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
// Test if we can actually set cookies
|
|
42
|
+
try {
|
|
43
|
+
const testKey = `${this.prefix}test`;
|
|
44
|
+
this.setCookie(testKey, 'test', { maxAge: 1 });
|
|
45
|
+
const result = this.getCookie(testKey) === 'test';
|
|
46
|
+
this.deleteCookie(testKey);
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Initialize the adapter
|
|
55
|
+
*/
|
|
56
|
+
async initialize(config) {
|
|
57
|
+
if (config) {
|
|
58
|
+
this.cookieOptions = { ...this.cookieOptions, ...config };
|
|
59
|
+
}
|
|
60
|
+
this.startTTLCleanup();
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Get a value from cookies
|
|
64
|
+
*/
|
|
65
|
+
async get(key) {
|
|
66
|
+
const cookieKey = this.prefix + key;
|
|
67
|
+
const value = this.getCookie(cookieKey);
|
|
68
|
+
if (!value)
|
|
69
|
+
return null;
|
|
70
|
+
try {
|
|
71
|
+
const decoded = decodeURIComponent(value);
|
|
72
|
+
const parsed = JSON.parse(decoded);
|
|
73
|
+
// Check TTL
|
|
74
|
+
if (this.isExpired(parsed)) {
|
|
75
|
+
await this.remove(key);
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
return parsed;
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
console.error(`Failed to parse cookie ${key}:`, error);
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Set a value in cookies
|
|
87
|
+
*/
|
|
88
|
+
async set(key, value) {
|
|
89
|
+
const cookieKey = this.prefix + key;
|
|
90
|
+
const oldValue = await this.get(key);
|
|
91
|
+
try {
|
|
92
|
+
const serialized = JSON.stringify(value);
|
|
93
|
+
const encoded = encodeURIComponent(serialized);
|
|
94
|
+
// Check size limit
|
|
95
|
+
if (encoded.length > this.maxCookieSize) {
|
|
96
|
+
throw new QuotaExceededError(`Cookie size ${encoded.length} exceeds limit ${this.maxCookieSize}`, { key, size: encoded.length });
|
|
97
|
+
}
|
|
98
|
+
// Set cookie with options
|
|
99
|
+
const options = { ...this.cookieOptions };
|
|
100
|
+
// Handle TTL
|
|
101
|
+
if (value.expires) {
|
|
102
|
+
const maxAge = Math.floor((value.expires - Date.now()) / 1000);
|
|
103
|
+
if (maxAge > 0) {
|
|
104
|
+
options.maxAge = maxAge;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
this.setCookie(cookieKey, encoded, options);
|
|
108
|
+
this.emitChange(key, oldValue?.value, value.value, 'local');
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
if (error instanceof QuotaExceededError) {
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
throw new StorageError(`Failed to set cookie ${key}: ${error}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Remove a value from cookies
|
|
119
|
+
*/
|
|
120
|
+
async remove(key) {
|
|
121
|
+
const oldValue = await this.get(key);
|
|
122
|
+
const cookieKey = this.prefix + key;
|
|
123
|
+
this.deleteCookie(cookieKey);
|
|
124
|
+
if (oldValue) {
|
|
125
|
+
this.emitChange(key, oldValue.value, undefined, 'local');
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Clear cookies
|
|
130
|
+
*/
|
|
131
|
+
async clear(options) {
|
|
132
|
+
if (!options || (!options.pattern && !options.tags && !options.expiredOnly)) {
|
|
133
|
+
// Clear all cookies with our prefix
|
|
134
|
+
const cookies = this.getAllCookies();
|
|
135
|
+
for (const [cookieKey] of cookies) {
|
|
136
|
+
if (cookieKey.startsWith(this.prefix)) {
|
|
137
|
+
this.deleteCookie(cookieKey);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
this.emitChange('*', undefined, undefined, 'local');
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
// Use base implementation for filtered clear
|
|
144
|
+
await super.clear(options);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Get all keys
|
|
148
|
+
*/
|
|
149
|
+
async keys(pattern) {
|
|
150
|
+
const cookies = this.getAllCookies();
|
|
151
|
+
const keys = [];
|
|
152
|
+
for (const [cookieKey] of cookies) {
|
|
153
|
+
if (cookieKey.startsWith(this.prefix)) {
|
|
154
|
+
const key = cookieKey.substring(this.prefix.length);
|
|
155
|
+
// Check if not expired
|
|
156
|
+
const value = await this.get(key);
|
|
157
|
+
if (value) {
|
|
158
|
+
keys.push(key);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return this.filterKeys(keys, pattern);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Get storage size
|
|
166
|
+
*/
|
|
167
|
+
async size(detailed) {
|
|
168
|
+
const cookies = this.getAllCookies();
|
|
169
|
+
let total = 0;
|
|
170
|
+
let count = 0;
|
|
171
|
+
let keySize = 0;
|
|
172
|
+
let valueSize = 0;
|
|
173
|
+
for (const [cookieKey, cookieValue] of cookies) {
|
|
174
|
+
if (cookieKey.startsWith(this.prefix)) {
|
|
175
|
+
count++;
|
|
176
|
+
const itemSize = (cookieKey.length + cookieValue.length) * 2; // UTF-16
|
|
177
|
+
total += itemSize;
|
|
178
|
+
if (detailed) {
|
|
179
|
+
keySize += cookieKey.length * 2;
|
|
180
|
+
valueSize += cookieValue.length * 2;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const result = { total, count };
|
|
185
|
+
if (detailed) {
|
|
186
|
+
result.detailed = {
|
|
187
|
+
keys: keySize,
|
|
188
|
+
values: valueSize,
|
|
189
|
+
metadata: 0,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
return result;
|
|
193
|
+
}
|
|
194
|
+
// Cookie manipulation helpers
|
|
195
|
+
/**
|
|
196
|
+
* Get a cookie value
|
|
197
|
+
*/
|
|
198
|
+
getCookie(name) {
|
|
199
|
+
if (typeof document === 'undefined')
|
|
200
|
+
return null;
|
|
201
|
+
const nameEQ = name + '=';
|
|
202
|
+
const cookies = document.cookie.split(';');
|
|
203
|
+
for (const cookie of cookies) {
|
|
204
|
+
const trimmed = cookie.trim();
|
|
205
|
+
if (trimmed.indexOf(nameEQ) === 0) {
|
|
206
|
+
return trimmed.substring(nameEQ.length);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Set a cookie
|
|
213
|
+
*/
|
|
214
|
+
setCookie(name, value, options = {}) {
|
|
215
|
+
if (typeof document === 'undefined')
|
|
216
|
+
return;
|
|
217
|
+
let cookie = `${name}=${value}`;
|
|
218
|
+
if (options.maxAge !== undefined) {
|
|
219
|
+
cookie += `; max-age=${options.maxAge}`;
|
|
220
|
+
}
|
|
221
|
+
if (options.domain) {
|
|
222
|
+
cookie += `; domain=${options.domain}`;
|
|
223
|
+
}
|
|
224
|
+
if (options.path) {
|
|
225
|
+
cookie += `; path=${options.path}`;
|
|
226
|
+
}
|
|
227
|
+
if (options.secure) {
|
|
228
|
+
cookie += '; secure';
|
|
229
|
+
}
|
|
230
|
+
if (options.sameSite) {
|
|
231
|
+
cookie += `; samesite=${options.sameSite}`;
|
|
232
|
+
}
|
|
233
|
+
document.cookie = cookie;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Delete a cookie
|
|
237
|
+
*/
|
|
238
|
+
deleteCookie(name) {
|
|
239
|
+
this.setCookie(name, '', { ...this.cookieOptions, maxAge: 0 });
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Get all cookies as key-value pairs
|
|
243
|
+
*/
|
|
244
|
+
getAllCookies() {
|
|
245
|
+
if (typeof document === 'undefined')
|
|
246
|
+
return [];
|
|
247
|
+
const cookies = [];
|
|
248
|
+
const cookieStrings = document.cookie.split(';');
|
|
249
|
+
for (const cookie of cookieStrings) {
|
|
250
|
+
const trimmed = cookie.trim();
|
|
251
|
+
const eqIndex = trimmed.indexOf('=');
|
|
252
|
+
if (eqIndex > 0) {
|
|
253
|
+
const name = trimmed.substring(0, eqIndex);
|
|
254
|
+
const value = trimmed.substring(eqIndex + 1);
|
|
255
|
+
cookies.push([name, value]);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return cookies;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
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 type { StorageType, StorageCapabilities, StorageValue, ClearOptions, SizeInfo, QueryCondition, Transaction } from '@/types';
|
|
7
|
+
/**
|
|
8
|
+
* Browser IndexedDB adapter
|
|
9
|
+
*/
|
|
10
|
+
export declare class IndexedDBAdapter extends BaseAdapter {
|
|
11
|
+
readonly name: StorageType;
|
|
12
|
+
readonly capabilities: StorageCapabilities;
|
|
13
|
+
private dbName;
|
|
14
|
+
private storeName;
|
|
15
|
+
private version;
|
|
16
|
+
private db?;
|
|
17
|
+
constructor(dbName?: string, storeName?: string, version?: number);
|
|
18
|
+
/**
|
|
19
|
+
* Check if IndexedDB is available
|
|
20
|
+
*/
|
|
21
|
+
isAvailable(): Promise<boolean>;
|
|
22
|
+
/**
|
|
23
|
+
* Initialize the adapter
|
|
24
|
+
*/
|
|
25
|
+
initialize(config?: {
|
|
26
|
+
dbName?: string;
|
|
27
|
+
storeName?: string;
|
|
28
|
+
version?: number;
|
|
29
|
+
}): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Open IndexedDB database
|
|
32
|
+
*/
|
|
33
|
+
private openDatabase;
|
|
34
|
+
/**
|
|
35
|
+
* Get a value from IndexedDB
|
|
36
|
+
*/
|
|
37
|
+
get<T = unknown>(key: string): Promise<StorageValue<T> | null>;
|
|
38
|
+
/**
|
|
39
|
+
* Set a value in IndexedDB
|
|
40
|
+
*/
|
|
41
|
+
set<T = unknown>(key: string, value: StorageValue<T>): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Remove a value from IndexedDB
|
|
44
|
+
*/
|
|
45
|
+
remove(key: string): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Clear IndexedDB
|
|
48
|
+
*/
|
|
49
|
+
clear(options?: ClearOptions): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Get all keys
|
|
52
|
+
*/
|
|
53
|
+
keys(pattern?: string | RegExp): Promise<string[]>;
|
|
54
|
+
/**
|
|
55
|
+
* Query IndexedDB with conditions
|
|
56
|
+
*/
|
|
57
|
+
query<T = unknown>(condition: QueryCondition): Promise<Array<{
|
|
58
|
+
key: string;
|
|
59
|
+
value: T;
|
|
60
|
+
}>>;
|
|
61
|
+
/**
|
|
62
|
+
* Get storage size
|
|
63
|
+
*/
|
|
64
|
+
size(detailed?: boolean): Promise<SizeInfo>;
|
|
65
|
+
/**
|
|
66
|
+
* Begin a transaction
|
|
67
|
+
*/
|
|
68
|
+
transaction(): Promise<Transaction>;
|
|
69
|
+
/**
|
|
70
|
+
* Close the adapter
|
|
71
|
+
*/
|
|
72
|
+
close(): Promise<void>;
|
|
73
|
+
/**
|
|
74
|
+
* Check if error is quota exceeded
|
|
75
|
+
*/
|
|
76
|
+
private isQuotaError;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=IndexedDBAdapter.d.ts.map
|