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,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compression Feature - Pure JavaScript LZ-string implementation
|
|
3
|
+
* Zero-dependency compression/decompression for storage values
|
|
4
|
+
*/
|
|
5
|
+
import { CompressionError } from '@/utils/errors';
|
|
6
|
+
/**
|
|
7
|
+
* Compression manager using pure JavaScript LZ-string algorithm
|
|
8
|
+
*/
|
|
9
|
+
export class CompressionManager {
|
|
10
|
+
config;
|
|
11
|
+
constructor(config = {}) {
|
|
12
|
+
this.config = {
|
|
13
|
+
algorithm: config.algorithm || 'lz',
|
|
14
|
+
threshold: config.threshold || 1024, // 1KB default threshold
|
|
15
|
+
level: config.level || 6,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Compress data using LZ-string algorithm
|
|
20
|
+
*/
|
|
21
|
+
async compress(data) {
|
|
22
|
+
try {
|
|
23
|
+
const jsonStr = JSON.stringify(data);
|
|
24
|
+
const originalSize = new Blob([jsonStr]).size;
|
|
25
|
+
// Skip compression if below threshold
|
|
26
|
+
if (originalSize < this.config.threshold) {
|
|
27
|
+
return data;
|
|
28
|
+
}
|
|
29
|
+
// Compress using LZ algorithm
|
|
30
|
+
const compressed = this.lzCompress(jsonStr);
|
|
31
|
+
const compressedSize = new Blob([compressed]).size;
|
|
32
|
+
// Only use compression if it reduces size
|
|
33
|
+
if (compressedSize >= originalSize) {
|
|
34
|
+
return data;
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
data: compressed,
|
|
38
|
+
algorithm: this.config.algorithm,
|
|
39
|
+
originalSize,
|
|
40
|
+
compressedSize,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
throw new CompressionError(`Compression failed: ${error}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Decompress data
|
|
49
|
+
*/
|
|
50
|
+
async decompress(compressedData) {
|
|
51
|
+
try {
|
|
52
|
+
if (!this.isCompressedData(compressedData)) {
|
|
53
|
+
return compressedData;
|
|
54
|
+
}
|
|
55
|
+
const decompressed = this.lzDecompress(compressedData.data);
|
|
56
|
+
return JSON.parse(decompressed);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
throw new CompressionError(`Decompression failed: ${error}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Check if data is compressed
|
|
64
|
+
*/
|
|
65
|
+
isCompressedData(data) {
|
|
66
|
+
return (typeof data === 'object' &&
|
|
67
|
+
data !== null &&
|
|
68
|
+
'data' in data &&
|
|
69
|
+
'algorithm' in data &&
|
|
70
|
+
'originalSize' in data &&
|
|
71
|
+
'compressedSize' in data);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* LZ-string compression implementation
|
|
75
|
+
*/
|
|
76
|
+
lzCompress(uncompressed) {
|
|
77
|
+
if (uncompressed == null)
|
|
78
|
+
return '';
|
|
79
|
+
const context = new Map();
|
|
80
|
+
const out = [];
|
|
81
|
+
let currentChar;
|
|
82
|
+
let phrase = uncompressed.charAt(0);
|
|
83
|
+
let code = 256;
|
|
84
|
+
for (let i = 1; i < uncompressed.length; i++) {
|
|
85
|
+
currentChar = uncompressed.charAt(i);
|
|
86
|
+
if (context.has(phrase + currentChar)) {
|
|
87
|
+
phrase += currentChar;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
if (phrase.length > 1) {
|
|
91
|
+
out.push(String.fromCharCode(context.get(phrase)));
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
out.push(phrase);
|
|
95
|
+
}
|
|
96
|
+
context.set(phrase + currentChar, code);
|
|
97
|
+
code++;
|
|
98
|
+
phrase = currentChar;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (phrase.length > 1) {
|
|
102
|
+
out.push(String.fromCharCode(context.get(phrase)));
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
out.push(phrase);
|
|
106
|
+
}
|
|
107
|
+
return this.encodeToBase64(out.join(''));
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* LZ-string decompression implementation
|
|
111
|
+
*/
|
|
112
|
+
lzDecompress(compressed) {
|
|
113
|
+
if (compressed == null)
|
|
114
|
+
return '';
|
|
115
|
+
if (compressed === '')
|
|
116
|
+
return '';
|
|
117
|
+
const decoded = this.decodeFromBase64(compressed);
|
|
118
|
+
const dictionary = new Map();
|
|
119
|
+
let currentChar = decoded.charAt(0);
|
|
120
|
+
let oldPhrase = currentChar;
|
|
121
|
+
const out = [currentChar];
|
|
122
|
+
let code = 256;
|
|
123
|
+
let phrase;
|
|
124
|
+
for (let i = 1; i < decoded.length; i++) {
|
|
125
|
+
const currentCode = decoded.charCodeAt(i);
|
|
126
|
+
if (currentCode < 256) {
|
|
127
|
+
phrase = decoded.charAt(i);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
phrase = dictionary.get(currentCode) || oldPhrase + currentChar;
|
|
131
|
+
}
|
|
132
|
+
out.push(phrase);
|
|
133
|
+
currentChar = phrase.charAt(0);
|
|
134
|
+
dictionary.set(code, oldPhrase + currentChar);
|
|
135
|
+
code++;
|
|
136
|
+
oldPhrase = phrase;
|
|
137
|
+
}
|
|
138
|
+
return out.join('');
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Base64 encoding for compressed data
|
|
142
|
+
*/
|
|
143
|
+
encodeToBase64(input) {
|
|
144
|
+
if (typeof btoa !== 'undefined') {
|
|
145
|
+
// Browser environment
|
|
146
|
+
return btoa(unescape(encodeURIComponent(input)));
|
|
147
|
+
}
|
|
148
|
+
// Pure JS implementation for Node.js or other environments
|
|
149
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
|
|
150
|
+
let result = '';
|
|
151
|
+
let i = 0;
|
|
152
|
+
while (i < input.length) {
|
|
153
|
+
const a = input.charCodeAt(i++);
|
|
154
|
+
const b = i < input.length ? input.charCodeAt(i++) : 0;
|
|
155
|
+
const c = i < input.length ? input.charCodeAt(i++) : 0;
|
|
156
|
+
const bitmap = (a << 16) | (b << 8) | c;
|
|
157
|
+
result += chars.charAt((bitmap >> 18) & 63);
|
|
158
|
+
result += chars.charAt((bitmap >> 12) & 63);
|
|
159
|
+
result += i - 2 < input.length ? chars.charAt((bitmap >> 6) & 63) : '=';
|
|
160
|
+
result += i - 1 < input.length ? chars.charAt(bitmap & 63) : '=';
|
|
161
|
+
}
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Base64 decoding for compressed data
|
|
166
|
+
*/
|
|
167
|
+
decodeFromBase64(input) {
|
|
168
|
+
if (typeof atob !== 'undefined') {
|
|
169
|
+
// Browser environment
|
|
170
|
+
return decodeURIComponent(escape(atob(input)));
|
|
171
|
+
}
|
|
172
|
+
// Pure JS implementation
|
|
173
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
|
|
174
|
+
let result = '';
|
|
175
|
+
let i = 0;
|
|
176
|
+
input = input.replace(/[^A-Za-z0-9+/]/g, '');
|
|
177
|
+
while (i < input.length) {
|
|
178
|
+
const encoded1 = chars.indexOf(input.charAt(i++));
|
|
179
|
+
const encoded2 = chars.indexOf(input.charAt(i++));
|
|
180
|
+
const encoded3 = chars.indexOf(input.charAt(i++));
|
|
181
|
+
const encoded4 = chars.indexOf(input.charAt(i++));
|
|
182
|
+
const bitmap = (encoded1 << 18) | (encoded2 << 12) | (encoded3 << 6) | encoded4;
|
|
183
|
+
result += String.fromCharCode((bitmap >> 16) & 255);
|
|
184
|
+
if (encoded3 !== 64)
|
|
185
|
+
result += String.fromCharCode((bitmap >> 8) & 255);
|
|
186
|
+
if (encoded4 !== 64)
|
|
187
|
+
result += String.fromCharCode(bitmap & 255);
|
|
188
|
+
}
|
|
189
|
+
return result;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Get compression ratio
|
|
193
|
+
*/
|
|
194
|
+
getCompressionRatio(compressedData) {
|
|
195
|
+
return compressedData.compressedSize / compressedData.originalSize;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Get savings percentage
|
|
199
|
+
*/
|
|
200
|
+
getSavingsPercentage(compressedData) {
|
|
201
|
+
return (((compressedData.originalSize - compressedData.compressedSize) /
|
|
202
|
+
compressedData.originalSize) *
|
|
203
|
+
100);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encryption Feature - Web Crypto API implementation
|
|
3
|
+
* Zero-dependency encryption/decryption for storage values
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Encryption configuration
|
|
7
|
+
*/
|
|
8
|
+
export interface EncryptionConfig {
|
|
9
|
+
algorithm?: 'AES-GCM' | 'AES-CBC';
|
|
10
|
+
keyLength?: 128 | 192 | 256;
|
|
11
|
+
iterations?: number;
|
|
12
|
+
saltLength?: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Encrypted data structure
|
|
16
|
+
*/
|
|
17
|
+
export interface EncryptedData {
|
|
18
|
+
data: string;
|
|
19
|
+
salt: string;
|
|
20
|
+
iv: string;
|
|
21
|
+
algorithm: string;
|
|
22
|
+
iterations: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Encryption manager using Web Crypto API
|
|
26
|
+
*/
|
|
27
|
+
export declare class EncryptionManager {
|
|
28
|
+
private config;
|
|
29
|
+
private keyCache;
|
|
30
|
+
constructor(config?: EncryptionConfig);
|
|
31
|
+
/**
|
|
32
|
+
* Check if encryption is available
|
|
33
|
+
*/
|
|
34
|
+
isAvailable(): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Encrypt data
|
|
37
|
+
*/
|
|
38
|
+
encrypt(data: unknown, password: string): Promise<EncryptedData>;
|
|
39
|
+
/**
|
|
40
|
+
* Decrypt data
|
|
41
|
+
*/
|
|
42
|
+
decrypt<T = unknown>(encryptedData: EncryptedData, password: string): Promise<T>;
|
|
43
|
+
/**
|
|
44
|
+
* Derive encryption key from password
|
|
45
|
+
*/
|
|
46
|
+
private deriveKey;
|
|
47
|
+
/**
|
|
48
|
+
* Convert ArrayBuffer to base64
|
|
49
|
+
*/
|
|
50
|
+
private bufferToBase64;
|
|
51
|
+
/**
|
|
52
|
+
* Convert base64 to ArrayBuffer
|
|
53
|
+
*/
|
|
54
|
+
private base64ToBuffer;
|
|
55
|
+
/**
|
|
56
|
+
* Clear key cache
|
|
57
|
+
*/
|
|
58
|
+
clearCache(): void;
|
|
59
|
+
/**
|
|
60
|
+
* Generate a secure random password
|
|
61
|
+
*/
|
|
62
|
+
generatePassword(length?: number): string;
|
|
63
|
+
/**
|
|
64
|
+
* Hash data using SHA-256
|
|
65
|
+
*/
|
|
66
|
+
hash(data: string): Promise<string>;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=encryption.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encryption.d.ts","sourceRoot":"","sources":["../../src/features/encryption.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAClC,SAAS,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,QAAQ,CAAqC;gBAEzC,MAAM,GAAE,gBAAqB;IASzC;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;IACG,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAyCtE;;OAEG;IACG,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAiCtF;;OAEG;YACW,SAAS;IA0CvB;;OAEG;IACH,OAAO,CAAC,cAAc;IAStB;;OAEG;IACH,OAAO,CAAC,cAAc;IAStB;;OAEG;IACH,UAAU,IAAI,IAAI;IAIlB;;OAEG;IACH,gBAAgB,CAAC,MAAM,GAAE,MAAW,GAAG,MAAM;IAc7C;;OAEG;IACG,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAU1C"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encryption Feature - Web Crypto API implementation
|
|
3
|
+
* Zero-dependency encryption/decryption for storage values
|
|
4
|
+
*/
|
|
5
|
+
import { EncryptionError } from '@/utils/errors';
|
|
6
|
+
/**
|
|
7
|
+
* Encryption manager using Web Crypto API
|
|
8
|
+
*/
|
|
9
|
+
export class EncryptionManager {
|
|
10
|
+
config;
|
|
11
|
+
keyCache = new Map();
|
|
12
|
+
constructor(config = {}) {
|
|
13
|
+
this.config = {
|
|
14
|
+
algorithm: config.algorithm || 'AES-GCM',
|
|
15
|
+
keyLength: config.keyLength || 256,
|
|
16
|
+
iterations: config.iterations || 100000,
|
|
17
|
+
saltLength: config.saltLength || 16,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Check if encryption is available
|
|
22
|
+
*/
|
|
23
|
+
isAvailable() {
|
|
24
|
+
return typeof window !== 'undefined' && window.crypto && window.crypto.subtle !== undefined;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Encrypt data
|
|
28
|
+
*/
|
|
29
|
+
async encrypt(data, password) {
|
|
30
|
+
if (!this.isAvailable()) {
|
|
31
|
+
throw new EncryptionError('Web Crypto API not available');
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
// Convert data to string
|
|
35
|
+
const dataStr = JSON.stringify(data);
|
|
36
|
+
const encoder = new TextEncoder();
|
|
37
|
+
const dataBuffer = encoder.encode(dataStr);
|
|
38
|
+
// Generate salt and IV
|
|
39
|
+
const salt = crypto.getRandomValues(new Uint8Array(this.config.saltLength));
|
|
40
|
+
const iv = crypto.getRandomValues(new Uint8Array(12)); // 12 bytes for GCM
|
|
41
|
+
// Derive key from password
|
|
42
|
+
const key = await this.deriveKey(password, salt);
|
|
43
|
+
// Encrypt data
|
|
44
|
+
const encryptedBuffer = await crypto.subtle.encrypt({
|
|
45
|
+
name: this.config.algorithm,
|
|
46
|
+
iv: iv,
|
|
47
|
+
}, key, dataBuffer);
|
|
48
|
+
// Convert to base64 for storage
|
|
49
|
+
return {
|
|
50
|
+
data: this.bufferToBase64(encryptedBuffer),
|
|
51
|
+
salt: this.bufferToBase64(salt),
|
|
52
|
+
iv: this.bufferToBase64(iv),
|
|
53
|
+
algorithm: this.config.algorithm,
|
|
54
|
+
iterations: this.config.iterations,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
throw new EncryptionError(`Encryption failed: ${error}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Decrypt data
|
|
63
|
+
*/
|
|
64
|
+
async decrypt(encryptedData, password) {
|
|
65
|
+
if (!this.isAvailable()) {
|
|
66
|
+
throw new EncryptionError('Web Crypto API not available');
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
// Convert from base64
|
|
70
|
+
const dataBuffer = this.base64ToBuffer(encryptedData.data);
|
|
71
|
+
const salt = this.base64ToBuffer(encryptedData.salt);
|
|
72
|
+
const iv = this.base64ToBuffer(encryptedData.iv);
|
|
73
|
+
// Derive key from password
|
|
74
|
+
const key = await this.deriveKey(password, new Uint8Array(salt), encryptedData.iterations);
|
|
75
|
+
// Decrypt data
|
|
76
|
+
const decryptedBuffer = await crypto.subtle.decrypt({
|
|
77
|
+
name: encryptedData.algorithm,
|
|
78
|
+
iv: iv,
|
|
79
|
+
}, key, dataBuffer);
|
|
80
|
+
// Convert back to original data
|
|
81
|
+
const decoder = new TextDecoder();
|
|
82
|
+
const decryptedStr = decoder.decode(decryptedBuffer);
|
|
83
|
+
return JSON.parse(decryptedStr);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
throw new EncryptionError(`Decryption failed: ${error}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Derive encryption key from password
|
|
91
|
+
*/
|
|
92
|
+
async deriveKey(password, salt, iterations = this.config.iterations) {
|
|
93
|
+
// Check cache
|
|
94
|
+
const cacheKey = `${password}-${this.bufferToBase64(salt)}-${iterations}`;
|
|
95
|
+
if (this.keyCache.has(cacheKey)) {
|
|
96
|
+
return this.keyCache.get(cacheKey);
|
|
97
|
+
}
|
|
98
|
+
// Import password as key material
|
|
99
|
+
const encoder = new TextEncoder();
|
|
100
|
+
const passwordBuffer = encoder.encode(password);
|
|
101
|
+
const keyMaterial = await crypto.subtle.importKey('raw', passwordBuffer, 'PBKDF2', false, [
|
|
102
|
+
'deriveBits',
|
|
103
|
+
'deriveKey',
|
|
104
|
+
]);
|
|
105
|
+
// Derive key using PBKDF2
|
|
106
|
+
const key = await crypto.subtle.deriveKey({
|
|
107
|
+
name: 'PBKDF2',
|
|
108
|
+
salt: salt,
|
|
109
|
+
iterations: iterations,
|
|
110
|
+
hash: 'SHA-256',
|
|
111
|
+
}, keyMaterial, {
|
|
112
|
+
name: this.config.algorithm,
|
|
113
|
+
length: this.config.keyLength,
|
|
114
|
+
}, false, ['encrypt', 'decrypt']);
|
|
115
|
+
// Cache the key
|
|
116
|
+
this.keyCache.set(cacheKey, key);
|
|
117
|
+
return key;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Convert ArrayBuffer to base64
|
|
121
|
+
*/
|
|
122
|
+
bufferToBase64(buffer) {
|
|
123
|
+
const bytes = new Uint8Array(buffer);
|
|
124
|
+
let binary = '';
|
|
125
|
+
for (let i = 0; i < bytes.byteLength; i++) {
|
|
126
|
+
binary += String.fromCharCode(bytes[i]);
|
|
127
|
+
}
|
|
128
|
+
return btoa(binary);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Convert base64 to ArrayBuffer
|
|
132
|
+
*/
|
|
133
|
+
base64ToBuffer(base64) {
|
|
134
|
+
const binary = atob(base64);
|
|
135
|
+
const bytes = new Uint8Array(binary.length);
|
|
136
|
+
for (let i = 0; i < binary.length; i++) {
|
|
137
|
+
bytes[i] = binary.charCodeAt(i);
|
|
138
|
+
}
|
|
139
|
+
return bytes.buffer;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Clear key cache
|
|
143
|
+
*/
|
|
144
|
+
clearCache() {
|
|
145
|
+
this.keyCache.clear();
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Generate a secure random password
|
|
149
|
+
*/
|
|
150
|
+
generatePassword(length = 32) {
|
|
151
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-=[]{}|;:,.<>?';
|
|
152
|
+
const array = new Uint8Array(length);
|
|
153
|
+
crypto.getRandomValues(array);
|
|
154
|
+
let password = '';
|
|
155
|
+
for (let i = 0; i < length; i++) {
|
|
156
|
+
password += chars[array[i] % chars.length];
|
|
157
|
+
}
|
|
158
|
+
return password;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Hash data using SHA-256
|
|
162
|
+
*/
|
|
163
|
+
async hash(data) {
|
|
164
|
+
if (!this.isAvailable()) {
|
|
165
|
+
throw new EncryptionError('Web Crypto API not available');
|
|
166
|
+
}
|
|
167
|
+
const encoder = new TextEncoder();
|
|
168
|
+
const dataBuffer = encoder.encode(data);
|
|
169
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', dataBuffer);
|
|
170
|
+
return this.bufferToBase64(hashBuffer);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migration utilities for storage upgrades
|
|
3
|
+
*/
|
|
4
|
+
import type { StorageAdapter } from '@/types';
|
|
5
|
+
export interface Migration {
|
|
6
|
+
version: number;
|
|
7
|
+
up: (adapter: StorageAdapter) => Promise<void>;
|
|
8
|
+
down?: (adapter: StorageAdapter) => Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
export declare class MigrationManager {
|
|
11
|
+
private migrations;
|
|
12
|
+
register(migration: Migration): void;
|
|
13
|
+
migrate(adapter: StorageAdapter, targetVersion: number): Promise<void>;
|
|
14
|
+
private getCurrentVersion;
|
|
15
|
+
private setVersion;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=migration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migration.d.ts","sourceRoot":"","sources":["../../src/features/migration.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACnD;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,UAAU,CAAmB;IAErC,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI;IAK9B,OAAO,CAAC,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YA0B9D,iBAAiB;YAKjB,UAAU;CAOzB"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migration utilities for storage upgrades
|
|
3
|
+
*/
|
|
4
|
+
export class MigrationManager {
|
|
5
|
+
migrations = [];
|
|
6
|
+
register(migration) {
|
|
7
|
+
this.migrations.push(migration);
|
|
8
|
+
this.migrations.sort((a, b) => a.version - b.version);
|
|
9
|
+
}
|
|
10
|
+
async migrate(adapter, targetVersion) {
|
|
11
|
+
const currentVersion = await this.getCurrentVersion(adapter);
|
|
12
|
+
if (currentVersion === targetVersion)
|
|
13
|
+
return;
|
|
14
|
+
const migrationsToRun = this.migrations.filter((m) => currentVersion < targetVersion
|
|
15
|
+
? m.version > currentVersion && m.version <= targetVersion
|
|
16
|
+
: m.version <= currentVersion && m.version > targetVersion);
|
|
17
|
+
if (currentVersion < targetVersion) {
|
|
18
|
+
for (const migration of migrationsToRun) {
|
|
19
|
+
await migration.up(adapter);
|
|
20
|
+
await this.setVersion(adapter, migration.version);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
for (const migration of migrationsToRun.reverse()) {
|
|
25
|
+
if (migration.down) {
|
|
26
|
+
await migration.down(adapter);
|
|
27
|
+
}
|
|
28
|
+
await this.setVersion(adapter, migration.version - 1);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async getCurrentVersion(adapter) {
|
|
33
|
+
const versionData = await adapter.get('__strata_version__');
|
|
34
|
+
return versionData?.value || 0;
|
|
35
|
+
}
|
|
36
|
+
async setVersion(adapter, version) {
|
|
37
|
+
await adapter.set('__strata_version__', {
|
|
38
|
+
value: version,
|
|
39
|
+
created: Date.now(),
|
|
40
|
+
updated: Date.now(),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query Engine Feature
|
|
3
|
+
* Zero-dependency implementation of MongoDB-like query operators
|
|
4
|
+
*/
|
|
5
|
+
import type { QueryCondition } from '@/types';
|
|
6
|
+
/**
|
|
7
|
+
* Query engine for advanced data filtering
|
|
8
|
+
*/
|
|
9
|
+
export declare class QueryEngine {
|
|
10
|
+
/**
|
|
11
|
+
* Check if a value matches a query condition
|
|
12
|
+
*/
|
|
13
|
+
matches(value: unknown, condition: QueryCondition): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Check if object has query operators
|
|
16
|
+
*/
|
|
17
|
+
private hasOperators;
|
|
18
|
+
/**
|
|
19
|
+
* Match value against operators
|
|
20
|
+
*/
|
|
21
|
+
private matchesOperators;
|
|
22
|
+
/**
|
|
23
|
+
* Match single operator
|
|
24
|
+
*/
|
|
25
|
+
private matchesOperator;
|
|
26
|
+
/**
|
|
27
|
+
* Match object against condition
|
|
28
|
+
*/
|
|
29
|
+
private matchesObject;
|
|
30
|
+
/**
|
|
31
|
+
* Get nested value from object using dot notation
|
|
32
|
+
*/
|
|
33
|
+
private getNestedValue;
|
|
34
|
+
/**
|
|
35
|
+
* Check equality with proper type handling
|
|
36
|
+
*/
|
|
37
|
+
private equals;
|
|
38
|
+
/**
|
|
39
|
+
* Compare values
|
|
40
|
+
*/
|
|
41
|
+
private compare;
|
|
42
|
+
/**
|
|
43
|
+
* Match regex pattern
|
|
44
|
+
*/
|
|
45
|
+
private matchesRegex;
|
|
46
|
+
/**
|
|
47
|
+
* Get JavaScript type of value
|
|
48
|
+
*/
|
|
49
|
+
private getType;
|
|
50
|
+
/**
|
|
51
|
+
* Handle null/undefined matching
|
|
52
|
+
*/
|
|
53
|
+
private matchesNull;
|
|
54
|
+
/**
|
|
55
|
+
* Sort array of items by multiple fields
|
|
56
|
+
*/
|
|
57
|
+
sort<T>(items: T[], sortBy: Record<string, 1 | -1>): T[];
|
|
58
|
+
/**
|
|
59
|
+
* Project/transform objects based on projection spec
|
|
60
|
+
*/
|
|
61
|
+
project<T>(item: T, projection: Record<string, 0 | 1 | boolean>): Partial<T>;
|
|
62
|
+
/**
|
|
63
|
+
* Set nested value in object using dot notation
|
|
64
|
+
*/
|
|
65
|
+
private setNestedValue;
|
|
66
|
+
/**
|
|
67
|
+
* Delete nested value from object using dot notation
|
|
68
|
+
*/
|
|
69
|
+
private deleteNestedValue;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Create a query engine instance
|
|
73
|
+
*/
|
|
74
|
+
export declare function createQueryEngine(): QueryEngine;
|
|
75
|
+
//# sourceMappingURL=query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/features/query.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAkB,MAAM,SAAS,CAAC;AAE9D;;GAEG;AACH,qBAAa,WAAW;IACtB;;OAEG;IACH,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,cAAc,GAAG,OAAO;IAwB3D;;OAEG;IACH,OAAO,CAAC,YAAY;IAIpB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IASxB;;OAEG;IACH,OAAO,CAAC,eAAe;IAqDvB;;OAEG;IACH,OAAO,CAAC,aAAa;IAkBrB;;OAEG;IACH,OAAO,CAAC,cAAc;IAmBtB;;OAEG;IACH,OAAO,CAAC,MAAM;IA+Bd;;OAEG;IACH,OAAO,CAAC,OAAO;IAyBf;;OAEG;IACH,OAAO,CAAC,YAAY;IAOpB;;OAEG;IACH,OAAO,CAAC,OAAO;IASf;;OAEG;IACH,OAAO,CAAC,WAAW;IAcnB;;OAEG;IACH,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE;IAoBxD;;OAEG;IACH,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IA2B5E;;OAEG;IACH,OAAO,CAAC,cAAc;IAetB;;OAEG;IACH,OAAO,CAAC,iBAAiB;CAc1B;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,WAAW,CAE/C"}
|