sehawq.db 4.0.3 โ†’ 4.0.5

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.
@@ -1,355 +1,355 @@
1
- /**
2
- * LazyLoader - Loads data only when needed, saves memory like a boss ๐Ÿ’พ
3
- *
4
- * Why load everything when you only need some things?
5
- * This made our memory usage drop faster than my grades in college ๐Ÿ˜…
6
- */
7
-
8
- class LazyLoader {
9
- constructor(storage, options = {}) {
10
- this.storage = storage;
11
- this.options = {
12
- chunkSize: 100, // Items per chunk
13
- prefetch: true, // Load next chunk in background
14
- maxLoadedChunks: 5, // Keep this many chunks in memory
15
- autoUnload: true, // Unload old chunks automatically
16
- ...options
17
- };
18
-
19
- // Chunk management
20
- this.chunks = new Map(); // chunkIndex -> data
21
- this.chunkIndex = new Map(); // key -> chunkIndex
22
- this.accessHistory = []; // LRU for chunks
23
- this.loadedChunksCount = 0;
24
-
25
- // Performance tracking
26
- this.stats = {
27
- chunksLoaded: 0,
28
- chunksUnloaded: 0,
29
- keysLoaded: 0,
30
- memorySaved: 0,
31
- cacheHits: 0,
32
- cacheMisses: 0,
33
- prefetchHits: 0
34
- };
35
-
36
- this._initialized = false;
37
- }
38
-
39
- /**
40
- * Initialize the lazy loader - build chunk index
41
- */
42
- async initialize(allKeys) {
43
- if (this._initialized) return;
44
-
45
- // Build chunk index from all keys
46
- let chunkIndex = 0;
47
- let currentChunkSize = 0;
48
-
49
- for (const key of allKeys) {
50
- this.chunkIndex.set(key, chunkIndex);
51
- currentChunkSize++;
52
-
53
- if (currentChunkSize >= this.options.chunkSize) {
54
- chunkIndex++;
55
- currentChunkSize = 0;
56
- }
57
- }
58
-
59
- this.totalChunks = chunkIndex + 1;
60
- this._initialized = true;
61
-
62
- if (this.options.debug) {
63
- console.log(`๐Ÿ“Š LazyLoader: ${allKeys.length} keys in ${this.totalChunks} chunks`);
64
- }
65
- }
66
-
67
- /**
68
- * Get value by key - loads chunk if needed
69
- */
70
- async get(key) {
71
- if (!this._initialized) {
72
- throw new Error('LazyLoader not initialized. Call initialize() first.');
73
- }
74
-
75
- const chunkIndex = this.chunkIndex.get(key);
76
-
77
- if (chunkIndex === undefined) {
78
- this.stats.cacheMisses++;
79
- return undefined; // Key not found
80
- }
81
-
82
- // Check if chunk is already loaded
83
- if (this.chunks.has(chunkIndex)) {
84
- this.stats.cacheHits++;
85
- this._updateAccessHistory(chunkIndex);
86
- return this.chunks.get(chunkIndex).get(key);
87
- }
88
-
89
- this.stats.cacheMisses++;
90
-
91
- // Load the chunk
92
- await this._loadChunk(chunkIndex);
93
-
94
- // Prefetch adjacent chunks in background
95
- if (this.options.prefetch) {
96
- this._prefetchAdjacentChunks(chunkIndex);
97
- }
98
-
99
- const chunk = this.chunks.get(chunkIndex);
100
- return chunk ? chunk.get(key) : undefined;
101
- }
102
-
103
- /**
104
- * Set value - updates chunk if loaded, or defers to storage
105
- */
106
- async set(key, value) {
107
- const chunkIndex = this.chunkIndex.get(key);
108
-
109
- if (chunkIndex !== undefined && this.chunks.has(chunkIndex)) {
110
- // Update in loaded chunk
111
- this.chunks.get(chunkIndex).set(key, value);
112
- this._updateAccessHistory(chunkIndex);
113
- }
114
-
115
- // Always update in storage
116
- await this.storage.set(key, value);
117
- }
118
-
119
- /**
120
- * Check if key exists (without loading chunk)
121
- */
122
- has(key) {
123
- return this.chunkIndex.has(key);
124
- }
125
-
126
- /**
127
- * Get all keys (without loading data)
128
- */
129
- getAllKeys() {
130
- return Array.from(this.chunkIndex.keys());
131
- }
132
-
133
- /**
134
- * Load a specific chunk into memory
135
- */
136
- async _loadChunk(chunkIndex) {
137
- // Unload least recently used chunks if we're at the limit
138
- if (this.loadedChunksCount >= this.options.maxLoadedChunks) {
139
- await this._unloadLRUChunk();
140
- }
141
-
142
- // Get all keys in this chunk
143
- const chunkKeys = [];
144
- for (const [key, index] of this.chunkIndex) {
145
- if (index === chunkIndex) {
146
- chunkKeys.push(key);
147
- }
148
- }
149
-
150
- // Load data for these keys
151
- const chunkData = new Map();
152
- for (const key of chunkKeys) {
153
- const value = await this.storage.get(key);
154
- if (value !== undefined) {
155
- chunkData.set(key, value);
156
- }
157
- }
158
-
159
- // Store the chunk
160
- this.chunks.set(chunkIndex, chunkData);
161
- this._updateAccessHistory(chunkIndex);
162
- this.loadedChunksCount++;
163
- this.stats.chunksLoaded++;
164
- this.stats.keysLoaded += chunkData.size;
165
-
166
- if (this.options.debug) {
167
- console.log(`๐Ÿ“ Loaded chunk ${chunkIndex} with ${chunkData.size} items`);
168
- }
169
-
170
- return chunkData;
171
- }
172
-
173
- /**
174
- * Unload least recently used chunk
175
- */
176
- async _unloadLRUChunk() {
177
- if (this.accessHistory.length === 0) return;
178
-
179
- const lruChunkIndex = this.accessHistory[0];
180
- await this._unloadChunk(lruChunkIndex);
181
- }
182
-
183
- /**
184
- * Unload specific chunk
185
- */
186
- async _unloadChunk(chunkIndex) {
187
- const chunk = this.chunks.get(chunkIndex);
188
- if (!chunk) return;
189
-
190
- // Calculate memory saved (rough estimate)
191
- let chunkSize = 0;
192
- for (const [key, value] of chunk) {
193
- chunkSize += this._estimateSize(key) + this._estimateSize(value);
194
- }
195
-
196
- this.chunks.delete(chunkIndex);
197
- this.accessHistory = this.accessHistory.filter(idx => idx !== chunkIndex);
198
- this.loadedChunksCount--;
199
- this.stats.chunksUnloaded++;
200
- this.stats.memorySaved += chunkSize;
201
-
202
- if (this.options.debug) {
203
- console.log(`๐Ÿ—‘๏ธ Unloaded chunk ${chunkIndex} (saved ~${chunkSize} bytes)`);
204
- }
205
- }
206
-
207
- /**
208
- * Prefetch chunks around the currently loaded one
209
- */
210
- _prefetchAdjacentChunks(currentChunkIndex) {
211
- const prefetchIndices = [
212
- currentChunkIndex + 1, // Next chunk
213
- currentChunkIndex - 1 // Previous chunk
214
- ].filter(index => index >= 0 && index < this.totalChunks && !this.chunks.has(index));
215
-
216
- // Prefetch in background (don't await)
217
- for (const index of prefetchIndices) {
218
- this._loadChunk(index).then(() => {
219
- this.stats.prefetchHits++;
220
- }).catch(error => {
221
- console.error(`Prefetch failed for chunk ${index}:`, error);
222
- });
223
- }
224
- }
225
-
226
- /**
227
- * Update access history for LRU tracking
228
- */
229
- _updateAccessHistory(chunkIndex) {
230
- // Remove existing entry
231
- this.accessHistory = this.accessHistory.filter(idx => idx !== chunkIndex);
232
- // Add to end (most recently used)
233
- this.accessHistory.push(chunkIndex);
234
- }
235
-
236
- /**
237
- * Estimate size of an object in bytes
238
- */
239
- _estimateSize(obj) {
240
- if (obj === null || obj === undefined) return 0;
241
-
242
- switch (typeof obj) {
243
- case 'string':
244
- return obj.length * 2; // 2 bytes per character
245
- case 'number':
246
- return 8; // 8 bytes for number
247
- case 'boolean':
248
- return 4; // 4 bytes for boolean
249
- case 'object':
250
- if (Array.isArray(obj)) {
251
- return obj.reduce((size, item) => size + this._estimateSize(item), 0);
252
- } else {
253
- let size = 0;
254
- for (const key in obj) {
255
- if (obj.hasOwnProperty(key)) {
256
- size += this._estimateSize(key) + this._estimateSize(obj[key]);
257
- }
258
- }
259
- return size;
260
- }
261
- default:
262
- return 0;
263
- }
264
- }
265
-
266
- /**
267
- * Manually load a chunk (for eager loading)
268
- */
269
- async loadChunk(chunkIndex) {
270
- return await this._loadChunk(chunkIndex);
271
- }
272
-
273
- /**
274
- * Manually unload a chunk
275
- */
276
- async unloadChunk(chunkIndex) {
277
- return await this._unloadChunk(chunkIndex);
278
- }
279
-
280
- /**
281
- * Get currently loaded chunks
282
- */
283
- getLoadedChunks() {
284
- return Array.from(this.chunks.keys());
285
- }
286
-
287
- /**
288
- * Get chunk information for a key
289
- */
290
- getChunkInfo(key) {
291
- const chunkIndex = this.chunkIndex.get(key);
292
- if (chunkIndex === undefined) return null;
293
-
294
- const isLoaded = this.chunks.has(chunkIndex);
295
- const keysInChunk = Array.from(this.chunkIndex.entries())
296
- .filter(([k, idx]) => idx === chunkIndex)
297
- .map(([k]) => k);
298
-
299
- return {
300
- chunkIndex,
301
- isLoaded,
302
- keysInChunk,
303
- loadedChunks: this.loadedChunksCount,
304
- totalChunks: this.totalChunks
305
- };
306
- }
307
-
308
- /**
309
- * Get performance statistics
310
- */
311
- getStats() {
312
- const totalAccesses = this.stats.cacheHits + this.stats.cacheMisses;
313
- const hitRate = totalAccesses > 0
314
- ? (this.stats.cacheHits / totalAccesses * 100).toFixed(2)
315
- : 0;
316
-
317
- return {
318
- ...this.stats,
319
- hitRate: `${hitRate}%`,
320
- loadedChunks: this.loadedChunksCount,
321
- totalChunks: this.totalChunks,
322
- memorySaved: `${(this.stats.memorySaved / 1024 / 1024).toFixed(2)} MB`,
323
- prefetchEffectiveness: this.stats.prefetchHits > 0
324
- ? `${((this.stats.prefetchHits / this.stats.chunksLoaded) * 100).toFixed(1)}%`
325
- : '0%'
326
- };
327
- }
328
-
329
- /**
330
- * Clear all loaded chunks
331
- */
332
- clear() {
333
- this.chunks.clear();
334
- this.accessHistory = [];
335
- this.loadedChunksCount = 0;
336
-
337
- if (this.options.debug) {
338
- console.log('๐Ÿงน Cleared all loaded chunks');
339
- }
340
- }
341
-
342
- /**
343
- * Preload specific chunks (for startup optimization)
344
- */
345
- async preloadChunks(chunkIndices) {
346
- const loadPromises = chunkIndices.map(index => this._loadChunk(index));
347
- await Promise.all(loadPromises);
348
-
349
- if (this.options.debug) {
350
- console.log(`๐Ÿ”ฅ Preloaded ${chunkIndices.length} chunks`);
351
- }
352
- }
353
- }
354
-
1
+ /**
2
+ * LazyLoader - Loads data only when needed, saves memory like a boss ๐Ÿ’พ
3
+ *
4
+ * Why load everything when you only need some things?
5
+ * This made our memory usage drop faster than my grades in college ๐Ÿ˜…
6
+ */
7
+
8
+ class LazyLoader {
9
+ constructor(storage, options = {}) {
10
+ this.storage = storage;
11
+ this.options = {
12
+ chunkSize: 100, // Items per chunk
13
+ prefetch: true, // Load next chunk in background
14
+ maxLoadedChunks: 5, // Keep this many chunks in memory
15
+ autoUnload: true, // Unload old chunks automatically
16
+ ...options
17
+ };
18
+
19
+ // Chunk management
20
+ this.chunks = new Map(); // chunkIndex -> data
21
+ this.chunkIndex = new Map(); // key -> chunkIndex
22
+ this.accessHistory = []; // LRU for chunks
23
+ this.loadedChunksCount = 0;
24
+
25
+ // Performance tracking
26
+ this.stats = {
27
+ chunksLoaded: 0,
28
+ chunksUnloaded: 0,
29
+ keysLoaded: 0,
30
+ memorySaved: 0,
31
+ cacheHits: 0,
32
+ cacheMisses: 0,
33
+ prefetchHits: 0
34
+ };
35
+
36
+ this._initialized = false;
37
+ }
38
+
39
+ /**
40
+ * Initialize the lazy loader - build chunk index
41
+ */
42
+ async initialize(allKeys) {
43
+ if (this._initialized) return;
44
+
45
+ // Build chunk index from all keys
46
+ let chunkIndex = 0;
47
+ let currentChunkSize = 0;
48
+
49
+ for (const key of allKeys) {
50
+ this.chunkIndex.set(key, chunkIndex);
51
+ currentChunkSize++;
52
+
53
+ if (currentChunkSize >= this.options.chunkSize) {
54
+ chunkIndex++;
55
+ currentChunkSize = 0;
56
+ }
57
+ }
58
+
59
+ this.totalChunks = chunkIndex + 1;
60
+ this._initialized = true;
61
+
62
+ if (this.options.debug) {
63
+ console.log(`๐Ÿ“Š LazyLoader: ${allKeys.length} keys in ${this.totalChunks} chunks`);
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Get value by key - loads chunk if needed
69
+ */
70
+ async get(key) {
71
+ if (!this._initialized) {
72
+ throw new Error('LazyLoader not initialized. Call initialize() first.');
73
+ }
74
+
75
+ const chunkIndex = this.chunkIndex.get(key);
76
+
77
+ if (chunkIndex === undefined) {
78
+ this.stats.cacheMisses++;
79
+ return undefined; // Key not found
80
+ }
81
+
82
+ // Check if chunk is already loaded
83
+ if (this.chunks.has(chunkIndex)) {
84
+ this.stats.cacheHits++;
85
+ this._updateAccessHistory(chunkIndex);
86
+ return this.chunks.get(chunkIndex).get(key);
87
+ }
88
+
89
+ this.stats.cacheMisses++;
90
+
91
+ // Load the chunk
92
+ await this._loadChunk(chunkIndex);
93
+
94
+ // Prefetch adjacent chunks in background
95
+ if (this.options.prefetch) {
96
+ this._prefetchAdjacentChunks(chunkIndex);
97
+ }
98
+
99
+ const chunk = this.chunks.get(chunkIndex);
100
+ return chunk ? chunk.get(key) : undefined;
101
+ }
102
+
103
+ /**
104
+ * Set value - updates chunk if loaded, or defers to storage
105
+ */
106
+ async set(key, value) {
107
+ const chunkIndex = this.chunkIndex.get(key);
108
+
109
+ if (chunkIndex !== undefined && this.chunks.has(chunkIndex)) {
110
+ // Update in loaded chunk
111
+ this.chunks.get(chunkIndex).set(key, value);
112
+ this._updateAccessHistory(chunkIndex);
113
+ }
114
+
115
+ // Always update in storage
116
+ await this.storage.set(key, value);
117
+ }
118
+
119
+ /**
120
+ * Check if key exists (without loading chunk)
121
+ */
122
+ has(key) {
123
+ return this.chunkIndex.has(key);
124
+ }
125
+
126
+ /**
127
+ * Get all keys (without loading data)
128
+ */
129
+ getAllKeys() {
130
+ return Array.from(this.chunkIndex.keys());
131
+ }
132
+
133
+ /**
134
+ * Load a specific chunk into memory
135
+ */
136
+ async _loadChunk(chunkIndex) {
137
+ // Unload least recently used chunks if we're at the limit
138
+ if (this.loadedChunksCount >= this.options.maxLoadedChunks) {
139
+ await this._unloadLRUChunk();
140
+ }
141
+
142
+ // Get all keys in this chunk
143
+ const chunkKeys = [];
144
+ for (const [key, index] of this.chunkIndex) {
145
+ if (index === chunkIndex) {
146
+ chunkKeys.push(key);
147
+ }
148
+ }
149
+
150
+ // Load data for these keys
151
+ const chunkData = new Map();
152
+ for (const key of chunkKeys) {
153
+ const value = await this.storage.get(key);
154
+ if (value !== undefined) {
155
+ chunkData.set(key, value);
156
+ }
157
+ }
158
+
159
+ // Store the chunk
160
+ this.chunks.set(chunkIndex, chunkData);
161
+ this._updateAccessHistory(chunkIndex);
162
+ this.loadedChunksCount++;
163
+ this.stats.chunksLoaded++;
164
+ this.stats.keysLoaded += chunkData.size;
165
+
166
+ if (this.options.debug) {
167
+ console.log(`๐Ÿ“ Loaded chunk ${chunkIndex} with ${chunkData.size} items`);
168
+ }
169
+
170
+ return chunkData;
171
+ }
172
+
173
+ /**
174
+ * Unload least recently used chunk
175
+ */
176
+ async _unloadLRUChunk() {
177
+ if (this.accessHistory.length === 0) return;
178
+
179
+ const lruChunkIndex = this.accessHistory[0];
180
+ await this._unloadChunk(lruChunkIndex);
181
+ }
182
+
183
+ /**
184
+ * Unload specific chunk
185
+ */
186
+ async _unloadChunk(chunkIndex) {
187
+ const chunk = this.chunks.get(chunkIndex);
188
+ if (!chunk) return;
189
+
190
+ // Calculate memory saved (rough estimate)
191
+ let chunkSize = 0;
192
+ for (const [key, value] of chunk) {
193
+ chunkSize += this._estimateSize(key) + this._estimateSize(value);
194
+ }
195
+
196
+ this.chunks.delete(chunkIndex);
197
+ this.accessHistory = this.accessHistory.filter(idx => idx !== chunkIndex);
198
+ this.loadedChunksCount--;
199
+ this.stats.chunksUnloaded++;
200
+ this.stats.memorySaved += chunkSize;
201
+
202
+ if (this.options.debug) {
203
+ console.log(`๐Ÿ—‘๏ธ Unloaded chunk ${chunkIndex} (saved ~${chunkSize} bytes)`);
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Prefetch chunks around the currently loaded one
209
+ */
210
+ _prefetchAdjacentChunks(currentChunkIndex) {
211
+ const prefetchIndices = [
212
+ currentChunkIndex + 1, // Next chunk
213
+ currentChunkIndex - 1 // Previous chunk
214
+ ].filter(index => index >= 0 && index < this.totalChunks && !this.chunks.has(index));
215
+
216
+ // Prefetch in background (don't await)
217
+ for (const index of prefetchIndices) {
218
+ this._loadChunk(index).then(() => {
219
+ this.stats.prefetchHits++;
220
+ }).catch(error => {
221
+ console.error(`Prefetch failed for chunk ${index}:`, error);
222
+ });
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Update access history for LRU tracking
228
+ */
229
+ _updateAccessHistory(chunkIndex) {
230
+ // Remove existing entry
231
+ this.accessHistory = this.accessHistory.filter(idx => idx !== chunkIndex);
232
+ // Add to end (most recently used)
233
+ this.accessHistory.push(chunkIndex);
234
+ }
235
+
236
+ /**
237
+ * Estimate size of an object in bytes
238
+ */
239
+ _estimateSize(obj) {
240
+ if (obj === null || obj === undefined) return 0;
241
+
242
+ switch (typeof obj) {
243
+ case 'string':
244
+ return obj.length * 2; // 2 bytes per character
245
+ case 'number':
246
+ return 8; // 8 bytes for number
247
+ case 'boolean':
248
+ return 4; // 4 bytes for boolean
249
+ case 'object':
250
+ if (Array.isArray(obj)) {
251
+ return obj.reduce((size, item) => size + this._estimateSize(item), 0);
252
+ } else {
253
+ let size = 0;
254
+ for (const key in obj) {
255
+ if (obj.hasOwnProperty(key)) {
256
+ size += this._estimateSize(key) + this._estimateSize(obj[key]);
257
+ }
258
+ }
259
+ return size;
260
+ }
261
+ default:
262
+ return 0;
263
+ }
264
+ }
265
+
266
+ /**
267
+ * Manually load a chunk (for eager loading)
268
+ */
269
+ async loadChunk(chunkIndex) {
270
+ return await this._loadChunk(chunkIndex);
271
+ }
272
+
273
+ /**
274
+ * Manually unload a chunk
275
+ */
276
+ async unloadChunk(chunkIndex) {
277
+ return await this._unloadChunk(chunkIndex);
278
+ }
279
+
280
+ /**
281
+ * Get currently loaded chunks
282
+ */
283
+ getLoadedChunks() {
284
+ return Array.from(this.chunks.keys());
285
+ }
286
+
287
+ /**
288
+ * Get chunk information for a key
289
+ */
290
+ getChunkInfo(key) {
291
+ const chunkIndex = this.chunkIndex.get(key);
292
+ if (chunkIndex === undefined) return null;
293
+
294
+ const isLoaded = this.chunks.has(chunkIndex);
295
+ const keysInChunk = Array.from(this.chunkIndex.entries())
296
+ .filter(([k, idx]) => idx === chunkIndex)
297
+ .map(([k]) => k);
298
+
299
+ return {
300
+ chunkIndex,
301
+ isLoaded,
302
+ keysInChunk,
303
+ loadedChunks: this.loadedChunksCount,
304
+ totalChunks: this.totalChunks
305
+ };
306
+ }
307
+
308
+ /**
309
+ * Get performance statistics
310
+ */
311
+ getStats() {
312
+ const totalAccesses = this.stats.cacheHits + this.stats.cacheMisses;
313
+ const hitRate = totalAccesses > 0
314
+ ? (this.stats.cacheHits / totalAccesses * 100).toFixed(2)
315
+ : 0;
316
+
317
+ return {
318
+ ...this.stats,
319
+ hitRate: `${hitRate}%`,
320
+ loadedChunks: this.loadedChunksCount,
321
+ totalChunks: this.totalChunks,
322
+ memorySaved: `${(this.stats.memorySaved / 1024 / 1024).toFixed(2)} MB`,
323
+ prefetchEffectiveness: this.stats.prefetchHits > 0
324
+ ? `${((this.stats.prefetchHits / this.stats.chunksLoaded) * 100).toFixed(1)}%`
325
+ : '0%'
326
+ };
327
+ }
328
+
329
+ /**
330
+ * Clear all loaded chunks
331
+ */
332
+ clear() {
333
+ this.chunks.clear();
334
+ this.accessHistory = [];
335
+ this.loadedChunksCount = 0;
336
+
337
+ if (this.options.debug) {
338
+ console.log('๐Ÿงน Cleared all loaded chunks');
339
+ }
340
+ }
341
+
342
+ /**
343
+ * Preload specific chunks (for startup optimization)
344
+ */
345
+ async preloadChunks(chunkIndices) {
346
+ const loadPromises = chunkIndices.map(index => this._loadChunk(index));
347
+ await Promise.all(loadPromises);
348
+
349
+ if (this.options.debug) {
350
+ console.log(`๐Ÿ”ฅ Preloaded ${chunkIndices.length} chunks`);
351
+ }
352
+ }
353
+ }
354
+
355
355
  module.exports = LazyLoader;