@pixagram/lacerta-db 0.7.3 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.idea/lacerta-db.iml +8 -0
- package/.idea/modules.xml +8 -0
- package/.idea/php.xml +19 -0
- package/.idea/workspace.xml +61 -0
- package/dist/browser.min.js +16 -4
- package/dist/index.min.js +16 -4
- package/index (Copy).js +3718 -0
- package/index.js +571 -22
- package/package.json +7 -4
package/index.js
CHANGED
|
@@ -1,13 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* LacertaDB V0.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* LacertaDB V0.8.0 - Production Library with QuickStore
|
|
3
|
+
*
|
|
4
|
+
* A high-performance, browser-based document database with support for:
|
|
5
|
+
* - IndexedDB storage with connection pooling
|
|
6
|
+
* - Multiple caching strategies (LRU, LFU, TTL)
|
|
7
|
+
* - Full-text search and geospatial indexing
|
|
8
|
+
* - Document encryption and compression
|
|
9
|
+
* - Binary attachments via OPFS (Origin Private File System)
|
|
10
|
+
* - MongoDB-like query syntax and aggregation pipeline
|
|
11
|
+
* - Schema migrations and versioning
|
|
12
|
+
* - QuickStore for fast localStorage-based operations
|
|
13
|
+
*
|
|
14
|
+
* @module LacertaDB
|
|
15
|
+
* @version 0.8.0
|
|
5
16
|
* @license MIT
|
|
17
|
+
* @author Pixagram SA
|
|
6
18
|
*/
|
|
7
19
|
|
|
8
20
|
'use strict';
|
|
9
21
|
|
|
10
|
-
//
|
|
22
|
+
// ========================
|
|
23
|
+
// Polyfills
|
|
24
|
+
// ========================
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Polyfill for requestIdleCallback if not available in the browser.
|
|
28
|
+
* Falls back to setTimeout with 0ms delay for environments that don't support it.
|
|
29
|
+
*/
|
|
11
30
|
if (!window.requestIdleCallback) {
|
|
12
31
|
window.requestIdleCallback = function(callback) {
|
|
13
32
|
return setTimeout(callback, 0);
|
|
@@ -15,29 +34,72 @@ if (!window.requestIdleCallback) {
|
|
|
15
34
|
window.cancelIdleCallback = clearTimeout;
|
|
16
35
|
}
|
|
17
36
|
|
|
18
|
-
//
|
|
37
|
+
// ========================
|
|
38
|
+
// Dependencies
|
|
39
|
+
// ========================
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Import external serialization and encoding utilities.
|
|
43
|
+
* TurboSerial provides efficient binary serialization with compression and deduplication.
|
|
44
|
+
* TurboBase64 provides fast base64 encoding/decoding.
|
|
45
|
+
*/
|
|
19
46
|
import TurboSerial from "@pixagram/turboserial";
|
|
20
47
|
import TurboBase64 from "@pixagram/turbobase64";
|
|
21
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Global serializer instance configured for optimal performance.
|
|
51
|
+
* @type {TurboSerial}
|
|
52
|
+
*/
|
|
22
53
|
const serializer = new TurboSerial({
|
|
23
|
-
compression: true,
|
|
24
|
-
deduplication: true,
|
|
25
|
-
simdOptimization: true,
|
|
26
|
-
detectCircular: true
|
|
54
|
+
compression: true, // Enable built-in compression
|
|
55
|
+
deduplication: true, // Deduplicate repeated values
|
|
56
|
+
simdOptimization: true, // Use SIMD instructions when available
|
|
57
|
+
detectCircular: true // Handle circular references
|
|
27
58
|
});
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Global Base64 encoder/decoder instance.
|
|
62
|
+
* @type {TurboBase64}
|
|
63
|
+
*/
|
|
28
64
|
const base64 = new TurboBase64();
|
|
29
65
|
|
|
30
66
|
// ========================
|
|
31
67
|
// Quick Store (localStorage based)
|
|
32
68
|
// ========================
|
|
33
69
|
|
|
70
|
+
/**
|
|
71
|
+
* QuickStore provides fast, synchronous access to small amounts of data using localStorage.
|
|
72
|
+
* Ideal for frequently accessed metadata, user preferences, or small documents
|
|
73
|
+
* where the overhead of IndexedDB is not justified.
|
|
74
|
+
*
|
|
75
|
+
* Data is serialized using TurboSerial and encoded as Base64 for localStorage storage.
|
|
76
|
+
*
|
|
77
|
+
* @class QuickStore
|
|
78
|
+
* @example
|
|
79
|
+
* const store = new QuickStore('myDatabase');
|
|
80
|
+
* store.add('user1', { name: 'John', age: 30 });
|
|
81
|
+
* const user = store.get('user1');
|
|
82
|
+
* console.log(user.name); // 'John'
|
|
83
|
+
*/
|
|
34
84
|
class QuickStore {
|
|
85
|
+
/**
|
|
86
|
+
* Creates a new QuickStore instance.
|
|
87
|
+
* @param {string} dbName - The database name used to namespace localStorage keys
|
|
88
|
+
*/
|
|
35
89
|
constructor(dbName) {
|
|
90
|
+
/** @private @type {string} Database name */
|
|
36
91
|
this._dbName = dbName;
|
|
92
|
+
/** @private @type {string} Prefix for all localStorage keys */
|
|
37
93
|
this._keyPrefix = `lacertadb_${dbName}_quickstore_`;
|
|
94
|
+
/** @private @type {string} Key for storing the document index */
|
|
38
95
|
this._indexKey = `${this._keyPrefix}index`;
|
|
39
96
|
}
|
|
40
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Reads the index of all stored document IDs from localStorage.
|
|
100
|
+
* @private
|
|
101
|
+
* @returns {Array<string>} Array of document IDs
|
|
102
|
+
*/
|
|
41
103
|
_readIndex() {
|
|
42
104
|
const indexStr = localStorage.getItem(this._indexKey);
|
|
43
105
|
if (!indexStr) return [];
|
|
@@ -49,12 +111,24 @@ class QuickStore {
|
|
|
49
111
|
}
|
|
50
112
|
}
|
|
51
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Writes the document ID index to localStorage.
|
|
116
|
+
* @private
|
|
117
|
+
* @param {Array<string>} index - Array of document IDs to store
|
|
118
|
+
*/
|
|
52
119
|
_writeIndex(index) {
|
|
53
120
|
const serializedIndex = serializer.serialize(index);
|
|
54
121
|
const encodedIndex = base64.encode(serializedIndex);
|
|
55
122
|
localStorage.setItem(this._indexKey, encodedIndex);
|
|
56
123
|
}
|
|
57
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Adds or replaces a document in the QuickStore.
|
|
127
|
+
* @param {string} docId - Unique identifier for the document
|
|
128
|
+
* @param {Object} data - Document data to store
|
|
129
|
+
* @returns {boolean} True if successful, false otherwise
|
|
130
|
+
* @throws {LacertaDBError} If localStorage quota is exceeded
|
|
131
|
+
*/
|
|
58
132
|
add(docId, data) {
|
|
59
133
|
const key = `${this._keyPrefix}data_${docId}`;
|
|
60
134
|
try {
|
|
@@ -76,6 +150,11 @@ class QuickStore {
|
|
|
76
150
|
}
|
|
77
151
|
}
|
|
78
152
|
|
|
153
|
+
/**
|
|
154
|
+
* Retrieves a document from the QuickStore.
|
|
155
|
+
* @param {string} docId - Document ID to retrieve
|
|
156
|
+
* @returns {Object|null} The document data, or null if not found
|
|
157
|
+
*/
|
|
79
158
|
get(docId) {
|
|
80
159
|
const key = `${this._keyPrefix}data_${docId}`;
|
|
81
160
|
const stored = localStorage.getItem(key);
|
|
@@ -90,10 +169,20 @@ class QuickStore {
|
|
|
90
169
|
return null;
|
|
91
170
|
}
|
|
92
171
|
|
|
172
|
+
/**
|
|
173
|
+
* Updates an existing document (alias for add).
|
|
174
|
+
* @param {string} docId - Document ID to update
|
|
175
|
+
* @param {Object} data - New document data
|
|
176
|
+
* @returns {boolean} True if successful
|
|
177
|
+
*/
|
|
93
178
|
update(docId, data) {
|
|
94
179
|
return this.add(docId, data);
|
|
95
180
|
}
|
|
96
181
|
|
|
182
|
+
/**
|
|
183
|
+
* Deletes a document from the QuickStore.
|
|
184
|
+
* @param {string} docId - Document ID to delete
|
|
185
|
+
*/
|
|
97
186
|
delete(docId) {
|
|
98
187
|
const key = `${this._keyPrefix}data_${docId}`;
|
|
99
188
|
localStorage.removeItem(key);
|
|
@@ -106,6 +195,10 @@ class QuickStore {
|
|
|
106
195
|
}
|
|
107
196
|
}
|
|
108
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Retrieves all documents from the QuickStore.
|
|
200
|
+
* @returns {Array<Object>} Array of all documents with their _id fields
|
|
201
|
+
*/
|
|
109
202
|
getAll() {
|
|
110
203
|
const index = this._readIndex();
|
|
111
204
|
return index.map(docId => {
|
|
@@ -114,6 +207,12 @@ class QuickStore {
|
|
|
114
207
|
}).filter(Boolean);
|
|
115
208
|
}
|
|
116
209
|
|
|
210
|
+
/**
|
|
211
|
+
* Queries documents using MongoDB-like filter syntax.
|
|
212
|
+
* Uses the global queryEngine for evaluation.
|
|
213
|
+
* @param {Object} [filter={}] - Query filter object
|
|
214
|
+
* @returns {Array<Object>} Array of matching documents
|
|
215
|
+
*/
|
|
117
216
|
query(filter = {}) {
|
|
118
217
|
const allDocs = this.getAll();
|
|
119
218
|
if (Object.keys(filter).length === 0) return allDocs;
|
|
@@ -122,6 +221,9 @@ class QuickStore {
|
|
|
122
221
|
return allDocs.filter(doc => queryEngine.evaluate(doc, filter));
|
|
123
222
|
}
|
|
124
223
|
|
|
224
|
+
/**
|
|
225
|
+
* Clears all documents from the QuickStore.
|
|
226
|
+
*/
|
|
125
227
|
clear() {
|
|
126
228
|
const index = this._readIndex();
|
|
127
229
|
for (const docId of index) {
|
|
@@ -130,6 +232,10 @@ class QuickStore {
|
|
|
130
232
|
localStorage.removeItem(this._indexKey);
|
|
131
233
|
}
|
|
132
234
|
|
|
235
|
+
/**
|
|
236
|
+
* Gets the number of documents in the QuickStore.
|
|
237
|
+
* @type {number}
|
|
238
|
+
*/
|
|
133
239
|
get size() {
|
|
134
240
|
return this._readIndex().length;
|
|
135
241
|
}
|
|
@@ -139,12 +245,34 @@ class QuickStore {
|
|
|
139
245
|
// Global IndexedDB Connection Pool
|
|
140
246
|
// ========================
|
|
141
247
|
|
|
248
|
+
/**
|
|
249
|
+
* Manages a pool of IndexedDB connections to optimize database access.
|
|
250
|
+
* Connections are reused across multiple operations and reference-counted
|
|
251
|
+
* to ensure proper cleanup.
|
|
252
|
+
*
|
|
253
|
+
* @class IndexedDBConnectionPool
|
|
254
|
+
*/
|
|
142
255
|
class IndexedDBConnectionPool {
|
|
256
|
+
/**
|
|
257
|
+
* Creates a new connection pool instance.
|
|
258
|
+
*/
|
|
143
259
|
constructor() {
|
|
260
|
+
/** @private @type {Map<string, IDBDatabase>} Active database connections */
|
|
144
261
|
this._connections = new Map();
|
|
262
|
+
/** @private @type {Map<string, number>} Reference counts for each connection */
|
|
145
263
|
this._refCounts = new Map();
|
|
146
264
|
}
|
|
147
265
|
|
|
266
|
+
/**
|
|
267
|
+
* Gets or creates a connection to an IndexedDB database.
|
|
268
|
+
* If a connection already exists, increments its reference count.
|
|
269
|
+
*
|
|
270
|
+
* @param {string} dbName - Database name
|
|
271
|
+
* @param {number} [version=1] - Database version
|
|
272
|
+
* @param {Function} [upgradeCallback] - Callback for onupgradeneeded event
|
|
273
|
+
* @returns {Promise<IDBDatabase>} The database connection
|
|
274
|
+
* @throws {LacertaDBError} If database cannot be opened
|
|
275
|
+
*/
|
|
148
276
|
async getConnection(dbName, version = 1, upgradeCallback) {
|
|
149
277
|
const key = `${dbName}_v${version}`;
|
|
150
278
|
|
|
@@ -171,6 +299,13 @@ class IndexedDBConnectionPool {
|
|
|
171
299
|
return db;
|
|
172
300
|
}
|
|
173
301
|
|
|
302
|
+
/**
|
|
303
|
+
* Releases a database connection. If reference count reaches zero,
|
|
304
|
+
* the connection is closed.
|
|
305
|
+
*
|
|
306
|
+
* @param {string} dbName - Database name
|
|
307
|
+
* @param {number} [version=1] - Database version
|
|
308
|
+
*/
|
|
174
309
|
releaseConnection(dbName, version = 1) {
|
|
175
310
|
const key = `${dbName}_v${version}`;
|
|
176
311
|
const refCount = this._refCounts.get(key) || 0;
|
|
@@ -188,6 +323,10 @@ class IndexedDBConnectionPool {
|
|
|
188
323
|
}
|
|
189
324
|
}
|
|
190
325
|
|
|
326
|
+
/**
|
|
327
|
+
* Closes all connections in the pool.
|
|
328
|
+
* Should be called during application cleanup.
|
|
329
|
+
*/
|
|
191
330
|
closeAll() {
|
|
192
331
|
for (const db of this._connections.values()) {
|
|
193
332
|
db.close();
|
|
@@ -197,18 +336,34 @@ class IndexedDBConnectionPool {
|
|
|
197
336
|
}
|
|
198
337
|
}
|
|
199
338
|
|
|
339
|
+
/** @type {IndexedDBConnectionPool} Global connection pool instance */
|
|
200
340
|
const connectionPool = new IndexedDBConnectionPool();
|
|
201
341
|
|
|
202
342
|
// ========================
|
|
203
343
|
// Async Mutex for managing concurrent operations
|
|
204
344
|
// ========================
|
|
205
345
|
|
|
346
|
+
/**
|
|
347
|
+
* Provides mutual exclusion for asynchronous operations.
|
|
348
|
+
* Ensures that only one async operation can access a critical section at a time.
|
|
349
|
+
*
|
|
350
|
+
* @class AsyncMutex
|
|
351
|
+
*/
|
|
206
352
|
class AsyncMutex {
|
|
353
|
+
/**
|
|
354
|
+
* Creates a new mutex instance.
|
|
355
|
+
*/
|
|
207
356
|
constructor() {
|
|
357
|
+
/** @private @type {Array<Function>} Queue of waiting operations */
|
|
208
358
|
this._queue = [];
|
|
359
|
+
/** @private @type {boolean} Whether the mutex is currently locked */
|
|
209
360
|
this._locked = false;
|
|
210
361
|
}
|
|
211
362
|
|
|
363
|
+
/**
|
|
364
|
+
* Acquires the mutex lock. Returns a release function when the lock is acquired.
|
|
365
|
+
* @returns {Promise<Function>} A function to release the lock
|
|
366
|
+
*/
|
|
212
367
|
acquire() {
|
|
213
368
|
return new Promise(resolve => {
|
|
214
369
|
this._queue.push(resolve);
|
|
@@ -216,11 +371,21 @@ class AsyncMutex {
|
|
|
216
371
|
});
|
|
217
372
|
}
|
|
218
373
|
|
|
374
|
+
/**
|
|
375
|
+
* Releases the mutex lock.
|
|
376
|
+
*/
|
|
219
377
|
release() {
|
|
220
378
|
this._locked = false;
|
|
221
379
|
this._dispatch();
|
|
222
380
|
}
|
|
223
381
|
|
|
382
|
+
/**
|
|
383
|
+
* Runs a callback with exclusive access to the mutex.
|
|
384
|
+
* Automatically acquires and releases the lock.
|
|
385
|
+
*
|
|
386
|
+
* @param {Function} callback - Async function to run exclusively
|
|
387
|
+
* @returns {Promise<*>} Result of the callback
|
|
388
|
+
*/
|
|
224
389
|
async runExclusive(callback) {
|
|
225
390
|
const release = await this.acquire();
|
|
226
391
|
try {
|
|
@@ -230,6 +395,10 @@ class AsyncMutex {
|
|
|
230
395
|
}
|
|
231
396
|
}
|
|
232
397
|
|
|
398
|
+
/**
|
|
399
|
+
* Dispatches the next waiting operation if the mutex is unlocked.
|
|
400
|
+
* @private
|
|
401
|
+
*/
|
|
233
402
|
_dispatch() {
|
|
234
403
|
if (this._locked || this._queue.length === 0) {
|
|
235
404
|
return;
|
|
@@ -244,12 +413,29 @@ class AsyncMutex {
|
|
|
244
413
|
// Custom error class for LacertaDB
|
|
245
414
|
// ========================
|
|
246
415
|
|
|
416
|
+
/**
|
|
417
|
+
* Custom error class for LacertaDB operations.
|
|
418
|
+
* Includes error codes for programmatic error handling.
|
|
419
|
+
*
|
|
420
|
+
* @class LacertaDBError
|
|
421
|
+
* @extends Error
|
|
422
|
+
*/
|
|
247
423
|
class LacertaDBError extends Error {
|
|
424
|
+
/**
|
|
425
|
+
* Creates a new LacertaDBError.
|
|
426
|
+
* @param {string} message - Human-readable error message
|
|
427
|
+
* @param {string} code - Machine-readable error code
|
|
428
|
+
* @param {Error} [originalError] - Original error that caused this error
|
|
429
|
+
*/
|
|
248
430
|
constructor(message, code, originalError) {
|
|
249
431
|
super(message);
|
|
432
|
+
/** @type {string} Error name identifier */
|
|
250
433
|
this.name = 'LacertaDBError';
|
|
434
|
+
/** @type {string} Machine-readable error code */
|
|
251
435
|
this.code = code;
|
|
436
|
+
/** @type {Error|null} Original error if this is a wrapped error */
|
|
252
437
|
this.originalError = originalError || null;
|
|
438
|
+
/** @type {string} ISO timestamp when the error occurred */
|
|
253
439
|
this.timestamp = new Date().toISOString();
|
|
254
440
|
}
|
|
255
441
|
}
|
|
@@ -258,15 +444,38 @@ class LacertaDBError extends Error {
|
|
|
258
444
|
// LRU Cache Implementation
|
|
259
445
|
// ========================
|
|
260
446
|
|
|
447
|
+
/**
|
|
448
|
+
* Least Recently Used (LRU) cache implementation.
|
|
449
|
+
* Evicts the least recently accessed items when the cache reaches its maximum size.
|
|
450
|
+
* Optionally supports TTL (time-to-live) for cache entries.
|
|
451
|
+
*
|
|
452
|
+
* @class LRUCache
|
|
453
|
+
*/
|
|
261
454
|
class LRUCache {
|
|
455
|
+
/**
|
|
456
|
+
* Creates a new LRU cache.
|
|
457
|
+
* @param {number} [maxSize=100] - Maximum number of items to store
|
|
458
|
+
* @param {number|null} [ttl=null] - Time-to-live in milliseconds (null for no expiration)
|
|
459
|
+
*/
|
|
262
460
|
constructor(maxSize = 100, ttl = null) {
|
|
461
|
+
/** @private @type {number} Maximum cache size */
|
|
263
462
|
this._maxSize = maxSize;
|
|
463
|
+
/** @private @type {number|null} TTL in milliseconds */
|
|
264
464
|
this._ttl = ttl;
|
|
465
|
+
/** @private @type {Map<string, *>} Cache storage */
|
|
265
466
|
this._cache = new Map();
|
|
467
|
+
/** @private @type {Array<string>} Access order tracking (most recent at end) */
|
|
266
468
|
this._accessOrder = [];
|
|
469
|
+
/** @private @type {Map<string, number>} Timestamps for TTL */
|
|
267
470
|
this._timestamps = new Map();
|
|
268
471
|
}
|
|
269
472
|
|
|
473
|
+
/**
|
|
474
|
+
* Retrieves a value from the cache.
|
|
475
|
+
* Updates the access order and checks TTL expiration.
|
|
476
|
+
* @param {string} key - Cache key
|
|
477
|
+
* @returns {*|null} Cached value or null if not found/expired
|
|
478
|
+
*/
|
|
270
479
|
get(key) {
|
|
271
480
|
if (!this._cache.has(key)) {
|
|
272
481
|
return null;
|
|
@@ -289,6 +498,12 @@ class LRUCache {
|
|
|
289
498
|
return this._cache.get(key);
|
|
290
499
|
}
|
|
291
500
|
|
|
501
|
+
/**
|
|
502
|
+
* Stores a value in the cache.
|
|
503
|
+
* Evicts the oldest items if the cache exceeds maxSize.
|
|
504
|
+
* @param {string} key - Cache key
|
|
505
|
+
* @param {*} value - Value to store
|
|
506
|
+
*/
|
|
292
507
|
set(key, value) {
|
|
293
508
|
if (this._cache.has(key)) {
|
|
294
509
|
const index = this._accessOrder.indexOf(key);
|
|
@@ -308,6 +523,11 @@ class LRUCache {
|
|
|
308
523
|
}
|
|
309
524
|
}
|
|
310
525
|
|
|
526
|
+
/**
|
|
527
|
+
* Deletes an item from the cache.
|
|
528
|
+
* @param {string} key - Cache key to delete
|
|
529
|
+
* @returns {boolean} True if item was deleted
|
|
530
|
+
*/
|
|
311
531
|
delete(key) {
|
|
312
532
|
const index = this._accessOrder.indexOf(key);
|
|
313
533
|
if (index > -1) {
|
|
@@ -317,12 +537,20 @@ class LRUCache {
|
|
|
317
537
|
return this._cache.delete(key);
|
|
318
538
|
}
|
|
319
539
|
|
|
540
|
+
/**
|
|
541
|
+
* Clears all items from the cache.
|
|
542
|
+
*/
|
|
320
543
|
clear() {
|
|
321
544
|
this._cache.clear();
|
|
322
545
|
this._accessOrder = [];
|
|
323
546
|
this._timestamps.clear();
|
|
324
547
|
}
|
|
325
548
|
|
|
549
|
+
/**
|
|
550
|
+
* Checks if a key exists in the cache (respects TTL).
|
|
551
|
+
* @param {string} key - Cache key
|
|
552
|
+
* @returns {boolean} True if key exists and is not expired
|
|
553
|
+
*/
|
|
326
554
|
has(key) {
|
|
327
555
|
if (this._ttl && this._cache.has(key)) {
|
|
328
556
|
const timestamp = this._timestamps.get(key);
|
|
@@ -334,13 +562,30 @@ class LRUCache {
|
|
|
334
562
|
return this._cache.has(key);
|
|
335
563
|
}
|
|
336
564
|
|
|
565
|
+
/**
|
|
566
|
+
* Gets the current number of items in the cache.
|
|
567
|
+
* @type {number}
|
|
568
|
+
*/
|
|
337
569
|
get size() {
|
|
338
570
|
return this._cache.size;
|
|
339
571
|
}
|
|
340
572
|
}
|
|
341
573
|
|
|
342
|
-
//
|
|
574
|
+
// ========================
|
|
575
|
+
// LFU Cache Implementation
|
|
576
|
+
// ========================
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Least Frequently Used (LFU) cache implementation.
|
|
580
|
+
* Evicts the least frequently accessed items when the cache reaches its maximum size.
|
|
581
|
+
* @class LFUCache
|
|
582
|
+
*/
|
|
343
583
|
class LFUCache {
|
|
584
|
+
/**
|
|
585
|
+
* Creates a new LFU cache.
|
|
586
|
+
* @param {number} [maxSize=100] - Maximum number of items to store
|
|
587
|
+
* @param {number|null} [ttl=null] - Time-to-live in milliseconds
|
|
588
|
+
*/
|
|
344
589
|
constructor(maxSize = 100, ttl = null) {
|
|
345
590
|
this._maxSize = maxSize;
|
|
346
591
|
this._ttl = ttl;
|
|
@@ -349,6 +594,11 @@ class LFUCache {
|
|
|
349
594
|
this._timestamps = new Map();
|
|
350
595
|
}
|
|
351
596
|
|
|
597
|
+
/**
|
|
598
|
+
* Retrieves a value from the cache and increments its frequency.
|
|
599
|
+
* @param {string} key - Cache key
|
|
600
|
+
* @returns {*|null} Cached value or null if not found/expired
|
|
601
|
+
*/
|
|
352
602
|
get(key) {
|
|
353
603
|
if (!this._cache.has(key)) {
|
|
354
604
|
return null;
|
|
@@ -366,6 +616,11 @@ class LFUCache {
|
|
|
366
616
|
return this._cache.get(key);
|
|
367
617
|
}
|
|
368
618
|
|
|
619
|
+
/**
|
|
620
|
+
* Stores a value in the cache.
|
|
621
|
+
* @param {string} key - Cache key
|
|
622
|
+
* @param {*} value - Value to store
|
|
623
|
+
*/
|
|
369
624
|
set(key, value) {
|
|
370
625
|
if (this._cache.has(key)) {
|
|
371
626
|
this._cache.set(key, value);
|
|
@@ -391,39 +646,73 @@ class LFUCache {
|
|
|
391
646
|
}
|
|
392
647
|
}
|
|
393
648
|
|
|
649
|
+
/**
|
|
650
|
+
* Deletes an item from the cache.
|
|
651
|
+
* @param {string} key - Cache key to delete
|
|
652
|
+
* @returns {boolean} True if item was deleted
|
|
653
|
+
*/
|
|
394
654
|
delete(key) {
|
|
395
655
|
this._frequencies.delete(key);
|
|
396
656
|
this._timestamps.delete(key);
|
|
397
657
|
return this._cache.delete(key);
|
|
398
658
|
}
|
|
399
659
|
|
|
660
|
+
/** Clears all items from the cache. */
|
|
400
661
|
clear() {
|
|
401
662
|
this._cache.clear();
|
|
402
663
|
this._frequencies.clear();
|
|
403
664
|
this._timestamps.clear();
|
|
404
665
|
}
|
|
405
666
|
|
|
667
|
+
/**
|
|
668
|
+
* Checks if a key exists in the cache.
|
|
669
|
+
* @param {string} key - Cache key
|
|
670
|
+
* @returns {boolean} True if key exists
|
|
671
|
+
*/
|
|
406
672
|
has(key) {
|
|
407
673
|
return this._cache.has(key);
|
|
408
674
|
}
|
|
409
675
|
|
|
676
|
+
/** @type {number} Current number of items in the cache */
|
|
410
677
|
get size() {
|
|
411
678
|
return this._cache.size;
|
|
412
679
|
}
|
|
413
680
|
}
|
|
414
681
|
|
|
415
|
-
//
|
|
682
|
+
// ========================
|
|
683
|
+
// TTL Cache Implementation
|
|
684
|
+
// ========================
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* Time-To-Live (TTL) only cache implementation.
|
|
688
|
+
* Items automatically expire after a specified duration.
|
|
689
|
+
* @class TTLCache
|
|
690
|
+
*/
|
|
416
691
|
class TTLCache {
|
|
692
|
+
/**
|
|
693
|
+
* Creates a new TTL cache.
|
|
694
|
+
* @param {number} [ttl=60000] - Time-to-live in milliseconds (default 1 minute)
|
|
695
|
+
*/
|
|
417
696
|
constructor(ttl = 60000) {
|
|
418
697
|
this._ttl = ttl;
|
|
419
698
|
this._cache = new Map();
|
|
420
699
|
this._timers = new Map();
|
|
421
700
|
}
|
|
422
701
|
|
|
702
|
+
/**
|
|
703
|
+
* Retrieves a value from the cache.
|
|
704
|
+
* @param {string} key - Cache key
|
|
705
|
+
* @returns {*|null} Cached value or null if not found
|
|
706
|
+
*/
|
|
423
707
|
get(key) {
|
|
424
708
|
return this._cache.get(key) || null;
|
|
425
709
|
}
|
|
426
710
|
|
|
711
|
+
/**
|
|
712
|
+
* Stores a value in the cache with automatic expiration.
|
|
713
|
+
* @param {string} key - Cache key
|
|
714
|
+
* @param {*} value - Value to store
|
|
715
|
+
*/
|
|
427
716
|
set(key, value) {
|
|
428
717
|
if (this._timers.has(key)) {
|
|
429
718
|
clearTimeout(this._timers.get(key));
|
|
@@ -437,6 +726,11 @@ class TTLCache {
|
|
|
437
726
|
this._timers.set(key, timer);
|
|
438
727
|
}
|
|
439
728
|
|
|
729
|
+
/**
|
|
730
|
+
* Deletes an item from the cache.
|
|
731
|
+
* @param {string} key - Cache key to delete
|
|
732
|
+
* @returns {boolean} True if item was deleted
|
|
733
|
+
*/
|
|
440
734
|
delete(key) {
|
|
441
735
|
if (this._timers.has(key)) {
|
|
442
736
|
clearTimeout(this._timers.get(key));
|
|
@@ -445,6 +739,7 @@ class TTLCache {
|
|
|
445
739
|
return this._cache.delete(key);
|
|
446
740
|
}
|
|
447
741
|
|
|
742
|
+
/** Clears all items and timers from the cache. */
|
|
448
743
|
clear() {
|
|
449
744
|
for (const timer of this._timers.values()) {
|
|
450
745
|
clearTimeout(timer);
|
|
@@ -453,15 +748,21 @@ class TTLCache {
|
|
|
453
748
|
this._cache.clear();
|
|
454
749
|
}
|
|
455
750
|
|
|
751
|
+
/**
|
|
752
|
+
* Checks if a key exists in the cache.
|
|
753
|
+
* @param {string} key - Cache key
|
|
754
|
+
* @returns {boolean} True if key exists
|
|
755
|
+
*/
|
|
456
756
|
has(key) {
|
|
457
757
|
return this._cache.has(key);
|
|
458
758
|
}
|
|
459
759
|
|
|
760
|
+
/** @type {number} Current number of items in the cache */
|
|
460
761
|
get size() {
|
|
461
762
|
return this._cache.size;
|
|
462
763
|
}
|
|
463
764
|
|
|
464
|
-
|
|
765
|
+
/** Destroys the cache, clearing all timers and data. */
|
|
465
766
|
destroy() {
|
|
466
767
|
for (const timer of this._timers.values()) {
|
|
467
768
|
clearTimeout(timer);
|
|
@@ -475,7 +776,20 @@ class TTLCache {
|
|
|
475
776
|
// Cache Strategy System
|
|
476
777
|
// ========================
|
|
477
778
|
|
|
779
|
+
/**
|
|
780
|
+
* Unified cache strategy manager that supports multiple caching algorithms.
|
|
781
|
+
* Provides a consistent interface regardless of the underlying strategy.
|
|
782
|
+
* @class CacheStrategy
|
|
783
|
+
*/
|
|
478
784
|
class CacheStrategy {
|
|
785
|
+
/**
|
|
786
|
+
* Creates a new cache strategy.
|
|
787
|
+
* @param {Object} [config={}] - Configuration options
|
|
788
|
+
* @param {string} [config.type='lru'] - Cache type: 'lru', 'lfu', 'ttl', or 'none'
|
|
789
|
+
* @param {number} [config.maxSize=100] - Maximum cache size
|
|
790
|
+
* @param {number|null} [config.ttl=null] - Time-to-live in milliseconds
|
|
791
|
+
* @param {boolean} [config.enabled=true] - Whether caching is enabled
|
|
792
|
+
*/
|
|
479
793
|
constructor(config = {}) {
|
|
480
794
|
this._type = config.type || 'lru';
|
|
481
795
|
this._maxSize = config.maxSize || 100;
|
|
@@ -484,7 +798,7 @@ class CacheStrategy {
|
|
|
484
798
|
this._cache = null;
|
|
485
799
|
}
|
|
486
800
|
|
|
487
|
-
|
|
801
|
+
/** Gets the cache instance, creating it lazily if needed. */
|
|
488
802
|
get cache() {
|
|
489
803
|
if (!this._cache) {
|
|
490
804
|
this._cache = this._createCache();
|
|
@@ -492,6 +806,7 @@ class CacheStrategy {
|
|
|
492
806
|
return this._cache;
|
|
493
807
|
}
|
|
494
808
|
|
|
809
|
+
/** @private Creates the appropriate cache instance based on config type. */
|
|
495
810
|
_createCache() {
|
|
496
811
|
switch (this._type) {
|
|
497
812
|
case 'lru':
|
|
@@ -507,31 +822,51 @@ class CacheStrategy {
|
|
|
507
822
|
}
|
|
508
823
|
}
|
|
509
824
|
|
|
825
|
+
/**
|
|
826
|
+
* Retrieves a value from the cache.
|
|
827
|
+
* @param {string} key - Cache key
|
|
828
|
+
* @returns {*|null} Cached value or null
|
|
829
|
+
*/
|
|
510
830
|
get(key) {
|
|
511
831
|
if (!this._enabled || !this.cache) return null;
|
|
512
832
|
return this.cache.get(key);
|
|
513
833
|
}
|
|
514
834
|
|
|
835
|
+
/**
|
|
836
|
+
* Stores a value in the cache.
|
|
837
|
+
* @param {string} key - Cache key
|
|
838
|
+
* @param {*} value - Value to store
|
|
839
|
+
*/
|
|
515
840
|
set(key, value) {
|
|
516
841
|
if (!this._enabled || !this.cache) return;
|
|
517
842
|
this.cache.set(key, value);
|
|
518
843
|
}
|
|
519
844
|
|
|
845
|
+
/**
|
|
846
|
+
* Deletes an item from the cache.
|
|
847
|
+
* @param {string} key - Cache key to delete
|
|
848
|
+
*/
|
|
520
849
|
delete(key) {
|
|
521
850
|
if (!this._enabled || !this.cache) return;
|
|
522
851
|
this.cache.delete(key);
|
|
523
852
|
}
|
|
524
853
|
|
|
854
|
+
/** Clears all items from the cache. */
|
|
525
855
|
clear() {
|
|
526
856
|
if (!this._enabled || !this.cache) return;
|
|
527
857
|
this.cache.clear();
|
|
528
858
|
}
|
|
529
859
|
|
|
860
|
+
/**
|
|
861
|
+
* Updates the cache strategy configuration.
|
|
862
|
+
* @param {Object} newConfig - New configuration options
|
|
863
|
+
*/
|
|
530
864
|
updateStrategy(newConfig) {
|
|
531
865
|
Object.assign(this, newConfig);
|
|
532
866
|
this._cache = null; // Reset cache for lazy reinitialization
|
|
533
867
|
}
|
|
534
868
|
|
|
869
|
+
/** Destroys the cache and releases resources. */
|
|
535
870
|
destroy() {
|
|
536
871
|
if (this._cache && this._cache.destroy) {
|
|
537
872
|
this._cache.destroy();
|
|
@@ -543,10 +878,21 @@ class CacheStrategy {
|
|
|
543
878
|
}
|
|
544
879
|
|
|
545
880
|
// ========================
|
|
546
|
-
// Compression Utility
|
|
881
|
+
// Compression Utility
|
|
547
882
|
// ========================
|
|
548
883
|
|
|
884
|
+
/**
|
|
885
|
+
* Browser-based compression utility using the Compression Streams API.
|
|
886
|
+
* Provides deflate compression and decompression for data storage optimization.
|
|
887
|
+
* @class BrowserCompressionUtility
|
|
888
|
+
*/
|
|
549
889
|
class BrowserCompressionUtility {
|
|
890
|
+
/**
|
|
891
|
+
* Compresses data using the deflate algorithm.
|
|
892
|
+
* @param {Uint8Array} input - Data to compress
|
|
893
|
+
* @returns {Promise<Uint8Array>} Compressed data
|
|
894
|
+
* @throws {TypeError} If input is not a Uint8Array
|
|
895
|
+
*/
|
|
550
896
|
async compress(input) {
|
|
551
897
|
if (!(input instanceof Uint8Array)) {
|
|
552
898
|
throw new TypeError('Input must be Uint8Array');
|
|
@@ -562,6 +908,11 @@ class BrowserCompressionUtility {
|
|
|
562
908
|
}
|
|
563
909
|
}
|
|
564
910
|
|
|
911
|
+
/**
|
|
912
|
+
* Decompresses deflate-compressed data.
|
|
913
|
+
* @param {Uint8Array} compressedData - Compressed data to decompress
|
|
914
|
+
* @returns {Promise<Uint8Array>} Decompressed data
|
|
915
|
+
*/
|
|
565
916
|
async decompress(compressedData) {
|
|
566
917
|
if (!(compressedData instanceof Uint8Array)) {
|
|
567
918
|
throw new TypeError('Input must be Uint8Array');
|
|
@@ -577,6 +928,11 @@ class BrowserCompressionUtility {
|
|
|
577
928
|
}
|
|
578
929
|
}
|
|
579
930
|
|
|
931
|
+
/**
|
|
932
|
+
* Synchronous compression (pass-through, no actual compression).
|
|
933
|
+
* @param {Uint8Array} input - Data to compress
|
|
934
|
+
* @returns {Uint8Array} Original data (uncompressed)
|
|
935
|
+
*/
|
|
580
936
|
compressSync(input) {
|
|
581
937
|
if (!(input instanceof Uint8Array)) {
|
|
582
938
|
throw new TypeError('Input must be Uint8Array');
|
|
@@ -584,6 +940,11 @@ class BrowserCompressionUtility {
|
|
|
584
940
|
return input;
|
|
585
941
|
}
|
|
586
942
|
|
|
943
|
+
/**
|
|
944
|
+
* Synchronous decompression (pass-through).
|
|
945
|
+
* @param {Uint8Array} compressedData - Data to decompress
|
|
946
|
+
* @returns {Uint8Array} Original data
|
|
947
|
+
*/
|
|
587
948
|
decompressSync(compressedData) {
|
|
588
949
|
if (!(compressedData instanceof Uint8Array)) {
|
|
589
950
|
throw new TypeError('Input must be Uint8Array');
|
|
@@ -596,7 +957,18 @@ class BrowserCompressionUtility {
|
|
|
596
957
|
// Browser Encryption Utility
|
|
597
958
|
// ========================
|
|
598
959
|
|
|
960
|
+
/**
|
|
961
|
+
* Utility for encrypting and decrypting data using Web Crypto API.
|
|
962
|
+
* Uses AES-GCM for authenticated encryption with PBKDF2 for key derivation.
|
|
963
|
+
* @class BrowserEncryptionUtility
|
|
964
|
+
*/
|
|
599
965
|
class BrowserEncryptionUtility {
|
|
966
|
+
/**
|
|
967
|
+
* Encrypts data using AES-256-GCM with PBKDF2 key derivation.
|
|
968
|
+
* @param {Uint8Array} data - Data to encrypt
|
|
969
|
+
* @param {string} password - Password for encryption
|
|
970
|
+
* @returns {Promise<Uint8Array>} Encrypted data (salt + IV + ciphertext)
|
|
971
|
+
*/
|
|
600
972
|
async encrypt(data, password) {
|
|
601
973
|
const encoder = new TextEncoder();
|
|
602
974
|
const salt = crypto.getRandomValues(new Uint8Array(16));
|
|
@@ -638,6 +1010,12 @@ class BrowserEncryptionUtility {
|
|
|
638
1010
|
return result;
|
|
639
1011
|
}
|
|
640
1012
|
|
|
1013
|
+
/**
|
|
1014
|
+
* Decrypts AES-256-GCM encrypted data.
|
|
1015
|
+
* @param {Uint8Array} encryptedData - Encrypted data (salt + IV + ciphertext)
|
|
1016
|
+
* @param {string} password - Password for decryption
|
|
1017
|
+
* @returns {Promise<Uint8Array>} Decrypted data
|
|
1018
|
+
*/
|
|
641
1019
|
async decrypt(encryptedData, password) {
|
|
642
1020
|
const encoder = new TextEncoder();
|
|
643
1021
|
const salt = encryptedData.slice(0, 16);
|
|
@@ -680,7 +1058,21 @@ class BrowserEncryptionUtility {
|
|
|
680
1058
|
// Database-Level Encryption
|
|
681
1059
|
// ========================
|
|
682
1060
|
|
|
1061
|
+
/**
|
|
1062
|
+
* Advanced encryption system for database-level security.
|
|
1063
|
+
* Provides PIN-based encryption with HMAC verification for data integrity.
|
|
1064
|
+
* Suitable for securing entire databases or sensitive documents like private keys.
|
|
1065
|
+
* @class SecureDatabaseEncryption
|
|
1066
|
+
*/
|
|
683
1067
|
class SecureDatabaseEncryption {
|
|
1068
|
+
/**
|
|
1069
|
+
* Creates a new secure encryption instance.
|
|
1070
|
+
* @param {Object} [config={}] - Encryption configuration
|
|
1071
|
+
* @param {number} [config.iterations=100000] - PBKDF2 iteration count
|
|
1072
|
+
* @param {string} [config.hashAlgorithm='SHA-256'] - Hash algorithm for key derivation
|
|
1073
|
+
* @param {number} [config.keyLength=256] - AES key length in bits
|
|
1074
|
+
* @param {number} [config.saltLength=32] - Salt length in bytes
|
|
1075
|
+
*/
|
|
684
1076
|
constructor(config = {}) {
|
|
685
1077
|
this._masterKey = null;
|
|
686
1078
|
this._salt = null;
|
|
@@ -693,10 +1085,17 @@ class SecureDatabaseEncryption {
|
|
|
693
1085
|
this._hmacKey = null;
|
|
694
1086
|
}
|
|
695
1087
|
|
|
1088
|
+
/** @type {boolean} Whether encryption has been initialized */
|
|
696
1089
|
get initialized() {
|
|
697
1090
|
return this._initialized;
|
|
698
1091
|
}
|
|
699
1092
|
|
|
1093
|
+
/**
|
|
1094
|
+
* Initializes encryption with a PIN code.
|
|
1095
|
+
* @param {string} pin - PIN code (typically 4-8 digits)
|
|
1096
|
+
* @param {Uint8Array|null} [salt=null] - Salt for key derivation (generated if null)
|
|
1097
|
+
* @returns {Promise<string>} Base64-encoded salt
|
|
1098
|
+
*/
|
|
700
1099
|
async initialize(pin, salt = null) {
|
|
701
1100
|
if (this._initialized) {
|
|
702
1101
|
throw new Error('Database encryption already initialized');
|
|
@@ -752,6 +1151,11 @@ class SecureDatabaseEncryption {
|
|
|
752
1151
|
return base64.encode(this._salt);
|
|
753
1152
|
}
|
|
754
1153
|
|
|
1154
|
+
/**
|
|
1155
|
+
* Encrypts data with AES-GCM and adds HMAC for integrity.
|
|
1156
|
+
* @param {string|Object|Uint8Array} data - Data to encrypt
|
|
1157
|
+
* @returns {Promise<Uint8Array>} Encrypted data with IV and HMAC
|
|
1158
|
+
*/
|
|
755
1159
|
async encrypt(data) {
|
|
756
1160
|
if (!this._initialized) {
|
|
757
1161
|
throw new Error('Database encryption not initialized');
|
|
@@ -794,6 +1198,12 @@ class SecureDatabaseEncryption {
|
|
|
794
1198
|
return result;
|
|
795
1199
|
}
|
|
796
1200
|
|
|
1201
|
+
/**
|
|
1202
|
+
* Decrypts data and verifies HMAC integrity.
|
|
1203
|
+
* @param {Uint8Array} encryptedPackage - Encrypted data (IV + ciphertext + HMAC)
|
|
1204
|
+
* @returns {Promise<Uint8Array>} Decrypted data
|
|
1205
|
+
* @throws {Error} If HMAC verification fails
|
|
1206
|
+
*/
|
|
797
1207
|
async decrypt(encryptedPackage) {
|
|
798
1208
|
if (!this._initialized) {
|
|
799
1209
|
throw new Error('Database encryption not initialized');
|
|
@@ -831,6 +1241,12 @@ class SecureDatabaseEncryption {
|
|
|
831
1241
|
return new Uint8Array(decryptedData);
|
|
832
1242
|
}
|
|
833
1243
|
|
|
1244
|
+
/**
|
|
1245
|
+
* Encrypts a private key with additional authentication data.
|
|
1246
|
+
* @param {string|Uint8Array|Object} privateKey - Key to encrypt
|
|
1247
|
+
* @param {string} [additionalAuth=''] - Additional authentication data
|
|
1248
|
+
* @returns {Promise<string>} Base64-encoded encrypted key
|
|
1249
|
+
*/
|
|
834
1250
|
async encryptPrivateKey(privateKey, additionalAuth = '') {
|
|
835
1251
|
if (!this._initialized) {
|
|
836
1252
|
throw new Error('Database encryption not initialized');
|
|
@@ -874,6 +1290,12 @@ class SecureDatabaseEncryption {
|
|
|
874
1290
|
return base64.encode(result);
|
|
875
1291
|
}
|
|
876
1292
|
|
|
1293
|
+
/**
|
|
1294
|
+
* Decrypts a private key, verifying additional authentication data.
|
|
1295
|
+
* @param {string} encryptedKeyString - Base64-encoded encrypted key
|
|
1296
|
+
* @param {string} [additionalAuth=''] - Additional authentication data to verify
|
|
1297
|
+
* @returns {Promise<string>} Decrypted private key
|
|
1298
|
+
*/
|
|
877
1299
|
async decryptPrivateKey(encryptedKeyString, additionalAuth = '') {
|
|
878
1300
|
if (!this._initialized) {
|
|
879
1301
|
throw new Error('Database encryption not initialized');
|
|
@@ -908,6 +1330,12 @@ class SecureDatabaseEncryption {
|
|
|
908
1330
|
return new TextDecoder().decode(decryptedKey);
|
|
909
1331
|
}
|
|
910
1332
|
|
|
1333
|
+
/**
|
|
1334
|
+
* Generates a cryptographically secure random PIN.
|
|
1335
|
+
* @static
|
|
1336
|
+
* @param {number} [length=6] - PIN length (number of digits)
|
|
1337
|
+
* @returns {string} Random PIN
|
|
1338
|
+
*/
|
|
911
1339
|
static generateSecurePIN(length = 6) {
|
|
912
1340
|
const digits = new Uint8Array(length);
|
|
913
1341
|
crypto.getRandomValues(digits);
|
|
@@ -916,6 +1344,7 @@ class SecureDatabaseEncryption {
|
|
|
916
1344
|
.join('');
|
|
917
1345
|
}
|
|
918
1346
|
|
|
1347
|
+
/** Destroys the encryption instance and clears all keys from memory. */
|
|
919
1348
|
destroy() {
|
|
920
1349
|
this._masterKey = null;
|
|
921
1350
|
this._encKey = null;
|
|
@@ -924,6 +1353,7 @@ class SecureDatabaseEncryption {
|
|
|
924
1353
|
this._initialized = false;
|
|
925
1354
|
}
|
|
926
1355
|
|
|
1356
|
+
/** @private Compares two Uint8Arrays for equality. */
|
|
927
1357
|
_arrayEquals(a, b) {
|
|
928
1358
|
if (a.length !== b.length) return false;
|
|
929
1359
|
for (let i = 0; i < a.length; i++) {
|
|
@@ -932,6 +1362,12 @@ class SecureDatabaseEncryption {
|
|
|
932
1362
|
return true;
|
|
933
1363
|
}
|
|
934
1364
|
|
|
1365
|
+
/**
|
|
1366
|
+
* Changes the encryption PIN.
|
|
1367
|
+
* @param {string} oldPin - Current PIN
|
|
1368
|
+
* @param {string} newPin - New PIN to set
|
|
1369
|
+
* @returns {Promise<string>} Base64-encoded new salt
|
|
1370
|
+
*/
|
|
935
1371
|
async changePin(oldPin, newPin) {
|
|
936
1372
|
if (!this._initialized) {
|
|
937
1373
|
throw new Error('Database encryption not initialized');
|
|
@@ -956,6 +1392,10 @@ class SecureDatabaseEncryption {
|
|
|
956
1392
|
return newSalt;
|
|
957
1393
|
}
|
|
958
1394
|
|
|
1395
|
+
/**
|
|
1396
|
+
* Exports encryption metadata for persistence (does NOT include keys).
|
|
1397
|
+
* @returns {Object} Encryption metadata
|
|
1398
|
+
*/
|
|
959
1399
|
exportMetadata() {
|
|
960
1400
|
if (!this._salt) {
|
|
961
1401
|
throw new Error('No encryption metadata to export');
|
|
@@ -972,6 +1412,11 @@ class SecureDatabaseEncryption {
|
|
|
972
1412
|
};
|
|
973
1413
|
}
|
|
974
1414
|
|
|
1415
|
+
/**
|
|
1416
|
+
* Imports encryption metadata from persistence.
|
|
1417
|
+
* @param {Object} metadata - Encryption metadata
|
|
1418
|
+
* @returns {boolean} True if successful
|
|
1419
|
+
*/
|
|
975
1420
|
importMetadata(metadata) {
|
|
976
1421
|
if (!metadata.salt) {
|
|
977
1422
|
throw new Error('Invalid encryption metadata');
|
|
@@ -991,7 +1436,17 @@ class SecureDatabaseEncryption {
|
|
|
991
1436
|
// B-Tree Index Implementation with Self-Healing
|
|
992
1437
|
// ========================
|
|
993
1438
|
|
|
1439
|
+
/**
|
|
1440
|
+
* Node in a B-Tree index structure.
|
|
1441
|
+
* @class BTreeNode
|
|
1442
|
+
* @private
|
|
1443
|
+
*/
|
|
994
1444
|
class BTreeNode {
|
|
1445
|
+
/**
|
|
1446
|
+
* Creates a new B-Tree node.
|
|
1447
|
+
* @param {number} order - B-Tree order (determines branching factor)
|
|
1448
|
+
* @param {boolean} leaf - Whether this is a leaf node
|
|
1449
|
+
*/
|
|
995
1450
|
constructor(order, leaf) {
|
|
996
1451
|
this.keys = new Array(2 * order - 1);
|
|
997
1452
|
this.values = new Array(2 * order - 1);
|
|
@@ -1001,6 +1456,7 @@ class BTreeNode {
|
|
|
1001
1456
|
this.order = order;
|
|
1002
1457
|
}
|
|
1003
1458
|
|
|
1459
|
+
/** Searches for a key in this node and its subtree. */
|
|
1004
1460
|
search(key) {
|
|
1005
1461
|
let i = 0;
|
|
1006
1462
|
while (i < this.n && key > this.keys[i]) {
|
|
@@ -1018,6 +1474,7 @@ class BTreeNode {
|
|
|
1018
1474
|
return this.children[i] ? this.children[i].search(key) : null;
|
|
1019
1475
|
}
|
|
1020
1476
|
|
|
1477
|
+
/** Performs a range search within this node and its subtree. */
|
|
1021
1478
|
rangeSearch(min, max, results) {
|
|
1022
1479
|
let i = 0;
|
|
1023
1480
|
|
|
@@ -1040,6 +1497,7 @@ class BTreeNode {
|
|
|
1040
1497
|
}
|
|
1041
1498
|
}
|
|
1042
1499
|
|
|
1500
|
+
/** Inserts a key-value pair into a non-full node. */
|
|
1043
1501
|
insertNonFull(key, value) {
|
|
1044
1502
|
let i = this.n - 1;
|
|
1045
1503
|
|
|
@@ -1086,6 +1544,7 @@ class BTreeNode {
|
|
|
1086
1544
|
}
|
|
1087
1545
|
}
|
|
1088
1546
|
|
|
1547
|
+
/** Splits a full child node. */
|
|
1089
1548
|
splitChild(i, y) {
|
|
1090
1549
|
const z = new BTreeNode(this.order, y.leaf);
|
|
1091
1550
|
z.n = this.order - 1;
|
|
@@ -1119,6 +1578,7 @@ class BTreeNode {
|
|
|
1119
1578
|
this.n++;
|
|
1120
1579
|
}
|
|
1121
1580
|
|
|
1581
|
+
/** Removes a value from the key's value set. */
|
|
1122
1582
|
remove(key, value) {
|
|
1123
1583
|
let i = 0;
|
|
1124
1584
|
while (i < this.n && key > this.keys[i]) {
|
|
@@ -1141,6 +1601,7 @@ class BTreeNode {
|
|
|
1141
1601
|
}
|
|
1142
1602
|
}
|
|
1143
1603
|
|
|
1604
|
+
/** Verifies and repairs the node structure (self-healing). */
|
|
1144
1605
|
verify() {
|
|
1145
1606
|
const issues = [];
|
|
1146
1607
|
|
|
@@ -1170,7 +1631,16 @@ class BTreeNode {
|
|
|
1170
1631
|
}
|
|
1171
1632
|
}
|
|
1172
1633
|
|
|
1634
|
+
/**
|
|
1635
|
+
* B-Tree index for efficient ordered data access and range queries.
|
|
1636
|
+
* Supports multiple document IDs per key and includes self-healing capabilities.
|
|
1637
|
+
* @class BTreeIndex
|
|
1638
|
+
*/
|
|
1173
1639
|
class BTreeIndex {
|
|
1640
|
+
/**
|
|
1641
|
+
* Creates a new B-Tree index.
|
|
1642
|
+
* @param {number} [order=4] - B-Tree order (minimum degree)
|
|
1643
|
+
*/
|
|
1174
1644
|
constructor(order = 4) {
|
|
1175
1645
|
this._root = null;
|
|
1176
1646
|
this._order = order;
|
|
@@ -1179,6 +1649,11 @@ class BTreeIndex {
|
|
|
1179
1649
|
this._verificationInterval = 60000;
|
|
1180
1650
|
}
|
|
1181
1651
|
|
|
1652
|
+
/**
|
|
1653
|
+
* Inserts a key-value pair into the index.
|
|
1654
|
+
* @param {*} key - Index key
|
|
1655
|
+
* @param {string} value - Document ID
|
|
1656
|
+
*/
|
|
1182
1657
|
insert(key, value) {
|
|
1183
1658
|
if (Date.now() - this._lastVerification > this._verificationInterval) {
|
|
1184
1659
|
this.verify();
|
|
@@ -1207,17 +1682,33 @@ class BTreeIndex {
|
|
|
1207
1682
|
this._size++;
|
|
1208
1683
|
}
|
|
1209
1684
|
|
|
1685
|
+
/**
|
|
1686
|
+
* Finds all document IDs associated with a key.
|
|
1687
|
+
* @param {*} key - Key to search for
|
|
1688
|
+
* @returns {Array<string>} Array of document IDs
|
|
1689
|
+
*/
|
|
1210
1690
|
find(key) {
|
|
1211
1691
|
if (!this._root) return [];
|
|
1212
1692
|
const values = this._root.search(key);
|
|
1213
1693
|
return values ? Array.from(values) : [];
|
|
1214
1694
|
}
|
|
1215
1695
|
|
|
1696
|
+
/**
|
|
1697
|
+
* Checks if a key exists in the index.
|
|
1698
|
+
* @param {*} key - Key to check
|
|
1699
|
+
* @returns {boolean} True if key exists
|
|
1700
|
+
*/
|
|
1216
1701
|
contains(key) {
|
|
1217
1702
|
if (!this._root) return false;
|
|
1218
1703
|
return this._root.search(key) !== null;
|
|
1219
1704
|
}
|
|
1220
1705
|
|
|
1706
|
+
/**
|
|
1707
|
+
* Finds all document IDs within a key range.
|
|
1708
|
+
* @param {*} min - Minimum key (inclusive)
|
|
1709
|
+
* @param {*} max - Maximum key (inclusive)
|
|
1710
|
+
* @returns {Array<string>} Array of document IDs
|
|
1711
|
+
*/
|
|
1221
1712
|
range(min, max) {
|
|
1222
1713
|
if (!this._root) return [];
|
|
1223
1714
|
const results = [];
|
|
@@ -1225,6 +1716,7 @@ class BTreeIndex {
|
|
|
1225
1716
|
return results;
|
|
1226
1717
|
}
|
|
1227
1718
|
|
|
1719
|
+
/** Finds all document IDs with keys >= min. */
|
|
1228
1720
|
rangeFrom(min) {
|
|
1229
1721
|
if (!this._root) return [];
|
|
1230
1722
|
const results = [];
|
|
@@ -1232,6 +1724,7 @@ class BTreeIndex {
|
|
|
1232
1724
|
return results;
|
|
1233
1725
|
}
|
|
1234
1726
|
|
|
1727
|
+
/** Finds all document IDs with keys <= max. */
|
|
1235
1728
|
rangeTo(max) {
|
|
1236
1729
|
if (!this._root) return [];
|
|
1237
1730
|
const results = [];
|
|
@@ -1239,6 +1732,7 @@ class BTreeIndex {
|
|
|
1239
1732
|
return results;
|
|
1240
1733
|
}
|
|
1241
1734
|
|
|
1735
|
+
/** Removes a document ID from a key's value set. */
|
|
1242
1736
|
remove(key, value) {
|
|
1243
1737
|
if (!this._root) return;
|
|
1244
1738
|
this._root.remove(key, value);
|
|
@@ -1250,6 +1744,7 @@ class BTreeIndex {
|
|
|
1250
1744
|
this._size--;
|
|
1251
1745
|
}
|
|
1252
1746
|
|
|
1747
|
+
/** Verifies index integrity and performs self-healing repairs. */
|
|
1253
1748
|
verify() {
|
|
1254
1749
|
this._lastVerification = Date.now();
|
|
1255
1750
|
if (!this._root) return { healthy: true, issues: [] };
|
|
@@ -1267,18 +1762,34 @@ class BTreeIndex {
|
|
|
1267
1762
|
};
|
|
1268
1763
|
}
|
|
1269
1764
|
|
|
1765
|
+
/** @type {number} Total number of entries in the index */
|
|
1270
1766
|
get size() {
|
|
1271
1767
|
return this._size;
|
|
1272
1768
|
}
|
|
1273
1769
|
}
|
|
1274
1770
|
|
|
1275
|
-
//
|
|
1771
|
+
// ========================
|
|
1772
|
+
// Text Index for Full-Text Search
|
|
1773
|
+
// ========================
|
|
1774
|
+
|
|
1775
|
+
/**
|
|
1776
|
+
* Inverted index for full-text search capabilities.
|
|
1777
|
+
* Tokenizes text and builds an index for fast keyword lookups.
|
|
1778
|
+
* @class TextIndex
|
|
1779
|
+
*/
|
|
1276
1780
|
class TextIndex {
|
|
1277
1781
|
constructor() {
|
|
1782
|
+
/** @private Token to document IDs mapping */
|
|
1278
1783
|
this._invertedIndex = new Map();
|
|
1784
|
+
/** @private Document ID to text mapping */
|
|
1279
1785
|
this._documentTexts = new Map();
|
|
1280
1786
|
}
|
|
1281
1787
|
|
|
1788
|
+
/**
|
|
1789
|
+
* Adds a document to the text index.
|
|
1790
|
+
* @param {string} text - Document text content
|
|
1791
|
+
* @param {string} docId - Document ID
|
|
1792
|
+
*/
|
|
1282
1793
|
addDocument(text, docId) {
|
|
1283
1794
|
if (typeof text !== 'string') return;
|
|
1284
1795
|
|
|
@@ -1293,6 +1804,7 @@ class TextIndex {
|
|
|
1293
1804
|
}
|
|
1294
1805
|
}
|
|
1295
1806
|
|
|
1807
|
+
/** Removes a document from the text index. */
|
|
1296
1808
|
removeDocument(docId) {
|
|
1297
1809
|
const text = this._documentTexts.get(docId);
|
|
1298
1810
|
if (!text) return;
|
|
@@ -1311,11 +1823,17 @@ class TextIndex {
|
|
|
1311
1823
|
this._documentTexts.delete(docId);
|
|
1312
1824
|
}
|
|
1313
1825
|
|
|
1826
|
+
/** Updates a document's text in the index. */
|
|
1314
1827
|
updateDocument(docId, newText) {
|
|
1315
1828
|
this.removeDocument(docId);
|
|
1316
1829
|
this.addDocument(newText, docId);
|
|
1317
1830
|
}
|
|
1318
1831
|
|
|
1832
|
+
/**
|
|
1833
|
+
* Searches for documents containing all query terms.
|
|
1834
|
+
* @param {string} query - Search query
|
|
1835
|
+
* @returns {Array<string>} Array of matching document IDs
|
|
1836
|
+
*/
|
|
1319
1837
|
search(query) {
|
|
1320
1838
|
const tokens = this._tokenize(query);
|
|
1321
1839
|
if (tokens.length === 0) return [];
|
|
@@ -1337,6 +1855,7 @@ class TextIndex {
|
|
|
1337
1855
|
return results ? Array.from(results) : [];
|
|
1338
1856
|
}
|
|
1339
1857
|
|
|
1858
|
+
/** @private Tokenizes text into searchable terms. */
|
|
1340
1859
|
_tokenize(text) {
|
|
1341
1860
|
return text.toLowerCase()
|
|
1342
1861
|
.replace(/[^\w\s]/g, ' ')
|
|
@@ -1344,17 +1863,32 @@ class TextIndex {
|
|
|
1344
1863
|
.filter(token => token.length > 2);
|
|
1345
1864
|
}
|
|
1346
1865
|
|
|
1866
|
+
/** @type {number} Number of indexed documents */
|
|
1347
1867
|
get size() {
|
|
1348
1868
|
return this._documentTexts.size;
|
|
1349
1869
|
}
|
|
1350
1870
|
}
|
|
1351
1871
|
|
|
1352
|
-
//
|
|
1872
|
+
// ========================
|
|
1873
|
+
// Geo Index for Spatial Queries
|
|
1874
|
+
// ========================
|
|
1875
|
+
|
|
1876
|
+
/**
|
|
1877
|
+
* Geospatial index for location-based queries.
|
|
1878
|
+
* Supports proximity searches and bounding box queries.
|
|
1879
|
+
* @class GeoIndex
|
|
1880
|
+
*/
|
|
1353
1881
|
class GeoIndex {
|
|
1354
1882
|
constructor() {
|
|
1883
|
+
/** @private Document ID to coordinates mapping */
|
|
1355
1884
|
this._points = new Map();
|
|
1356
1885
|
}
|
|
1357
1886
|
|
|
1887
|
+
/**
|
|
1888
|
+
* Adds a geographic point to the index.
|
|
1889
|
+
* @param {Object} coords - Coordinates object with lat/lng properties
|
|
1890
|
+
* @param {string} docId - Document ID
|
|
1891
|
+
*/
|
|
1358
1892
|
addPoint(coords, docId) {
|
|
1359
1893
|
if (!coords || typeof coords.lat !== 'number' || typeof coords.lng !== 'number') {
|
|
1360
1894
|
return;
|
|
@@ -1362,14 +1896,22 @@ class GeoIndex {
|
|
|
1362
1896
|
this._points.set(docId, coords);
|
|
1363
1897
|
}
|
|
1364
1898
|
|
|
1899
|
+
/** Removes a point from the index. */
|
|
1365
1900
|
removePoint(docId) {
|
|
1366
1901
|
this._points.delete(docId);
|
|
1367
1902
|
}
|
|
1368
1903
|
|
|
1904
|
+
/** Updates a point's coordinates. */
|
|
1369
1905
|
updatePoint(docId, newCoords) {
|
|
1370
1906
|
this._points.set(docId, newCoords);
|
|
1371
1907
|
}
|
|
1372
1908
|
|
|
1909
|
+
/**
|
|
1910
|
+
* Finds all points within a distance from a center point.
|
|
1911
|
+
* @param {Object} center - Center coordinates
|
|
1912
|
+
* @param {number} maxDistance - Maximum distance in kilometers
|
|
1913
|
+
* @returns {Array<string>} Document IDs sorted by distance
|
|
1914
|
+
*/
|
|
1373
1915
|
findNear(center, maxDistance) {
|
|
1374
1916
|
const results = [];
|
|
1375
1917
|
|
|
@@ -1384,6 +1926,11 @@ class GeoIndex {
|
|
|
1384
1926
|
.map(r => r.docId);
|
|
1385
1927
|
}
|
|
1386
1928
|
|
|
1929
|
+
/**
|
|
1930
|
+
* Finds all points within a bounding box.
|
|
1931
|
+
* @param {Object} bounds - Bounding box with minLat, maxLat, minLng, maxLng
|
|
1932
|
+
* @returns {Array<string>} Document IDs within bounds
|
|
1933
|
+
*/
|
|
1387
1934
|
findWithin(bounds) {
|
|
1388
1935
|
const results = [];
|
|
1389
1936
|
|
|
@@ -1396,8 +1943,9 @@ class GeoIndex {
|
|
|
1396
1943
|
return results;
|
|
1397
1944
|
}
|
|
1398
1945
|
|
|
1946
|
+
/** @private Calculates distance using the Haversine formula. */
|
|
1399
1947
|
_haversine(coord1, coord2) {
|
|
1400
|
-
const R = 6371;
|
|
1948
|
+
const R = 6371; // Earth's radius in km
|
|
1401
1949
|
const dLat = this._toRad(coord2.lat - coord1.lat);
|
|
1402
1950
|
const dLng = this._toRad(coord2.lng - coord1.lng);
|
|
1403
1951
|
|
|
@@ -1409,24 +1957,25 @@ class GeoIndex {
|
|
|
1409
1957
|
return R * c;
|
|
1410
1958
|
}
|
|
1411
1959
|
|
|
1960
|
+
/** @private Converts degrees to radians. */
|
|
1412
1961
|
_toRad(deg) {
|
|
1413
1962
|
return deg * (Math.PI / 180);
|
|
1414
1963
|
}
|
|
1415
1964
|
|
|
1965
|
+
/** @private Checks if coordinates are within bounds. */
|
|
1416
1966
|
_isWithinBounds(coords, bounds) {
|
|
1417
1967
|
return coords.lat >= bounds.minLat && coords.lat <= bounds.maxLat &&
|
|
1418
1968
|
coords.lng >= bounds.minLng && coords.lng <= bounds.maxLng;
|
|
1419
1969
|
}
|
|
1420
1970
|
|
|
1971
|
+
/** @type {number} Number of indexed points */
|
|
1421
1972
|
get size() {
|
|
1422
1973
|
return this._points.size;
|
|
1423
1974
|
}
|
|
1424
1975
|
}
|
|
1425
|
-
|
|
1426
1976
|
// ========================
|
|
1427
|
-
// Index
|
|
1977
|
+
// B-Tree Index Implementation with Self-Healing
|
|
1428
1978
|
// ========================
|
|
1429
|
-
|
|
1430
1979
|
class IndexManager {
|
|
1431
1980
|
constructor(collection) {
|
|
1432
1981
|
this._collection = collection;
|
|
@@ -3459,7 +4008,7 @@ class Database {
|
|
|
3459
4008
|
|
|
3460
4009
|
async export(format = 'json', password = null) {
|
|
3461
4010
|
const data = {
|
|
3462
|
-
version: '0.
|
|
4011
|
+
version: '0.8.0',
|
|
3463
4012
|
database: this.name,
|
|
3464
4013
|
timestamp: Date.now(),
|
|
3465
4014
|
collections: {}
|
|
@@ -3626,7 +4175,7 @@ class LacertaDB {
|
|
|
3626
4175
|
|
|
3627
4176
|
async createBackup(password = null) {
|
|
3628
4177
|
const backup = {
|
|
3629
|
-
version: '0.
|
|
4178
|
+
version: '0.8.0',
|
|
3630
4179
|
timestamp: Date.now(),
|
|
3631
4180
|
databases: {}
|
|
3632
4181
|
};
|