sehawq.db 3.0.0 → 4.0.1

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.
@@ -0,0 +1,376 @@
1
+ /**
2
+ * Persistence Layer - Handles data storage and retrieval 💾
3
+ *
4
+ * The bridge between memory and permanent storage
5
+ * Because RAM is great, but it forgets everything when you blink
6
+ */
7
+
8
+ const fs = require('fs').promises;
9
+ const path = require('path');
10
+ const { performance } = require('perf_hooks');
11
+
12
+ class Persistence {
13
+ constructor(filePath, options = {}) {
14
+ this.filePath = filePath;
15
+ this.options = {
16
+ autoSave: true,
17
+ saveInterval: 5000,
18
+ compression: false,
19
+ encryption: false,
20
+ encryptionKey: null,
21
+ ...options
22
+ };
23
+
24
+ this.data = new Map();
25
+ this.isSaving = false;
26
+ this.saveQueue = [];
27
+ this.stats = {
28
+ reads: 0,
29
+ writes: 0,
30
+ saves: 0,
31
+ loads: 0,
32
+ errors: 0
33
+ };
34
+
35
+ this._ensureDirectory();
36
+ }
37
+
38
+ /**
39
+ * Ensure data directory exists
40
+ */
41
+ async _ensureDirectory() {
42
+ try {
43
+ const dir = path.dirname(this.filePath);
44
+ await fs.access(dir);
45
+ } catch (error) {
46
+ await fs.mkdir(path.dirname(this.filePath), { recursive: true });
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Load data from storage
52
+ */
53
+ async load() {
54
+ const startTime = performance.now();
55
+ this.stats.loads++;
56
+
57
+ try {
58
+ // Check if file exists
59
+ try {
60
+ await fs.access(this.filePath);
61
+ } catch (error) {
62
+ // File doesn't exist - start with empty data
63
+ this.data.clear();
64
+ return new Map();
65
+ }
66
+
67
+ // Read and parse data
68
+ const fileData = await fs.readFile(this.filePath, 'utf8');
69
+
70
+ let parsedData;
71
+ if (this.options.compression) {
72
+ parsedData = await this._decompress(fileData);
73
+ } else if (this.options.encryption) {
74
+ parsedData = await this._decrypt(fileData);
75
+ } else {
76
+ parsedData = JSON.parse(fileData);
77
+ }
78
+
79
+ // Convert to Map
80
+ this.data.clear();
81
+ for (const [key, value] of Object.entries(parsedData)) {
82
+ this.data.set(key, value);
83
+ }
84
+
85
+ const loadTime = performance.now() - startTime;
86
+
87
+ if (this.options.debug) {
88
+ console.log(`📁 Loaded ${this.data.size} records in ${loadTime.toFixed(2)}ms`);
89
+ }
90
+
91
+ return this.data;
92
+ } catch (error) {
93
+ this.stats.errors++;
94
+ console.error('🚨 Persistence load error:', error);
95
+
96
+ // Try to recover from backup
97
+ return await this._recoverFromBackup();
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Save data to storage
103
+ */
104
+ async save(data = null) {
105
+ if (this.isSaving) {
106
+ // Queue the save request
107
+ return new Promise((resolve, reject) => {
108
+ this.saveQueue.push({ data, resolve, reject });
109
+ });
110
+ }
111
+
112
+ this.isSaving = true;
113
+ const startTime = performance.now();
114
+ this.stats.saves++;
115
+
116
+ try {
117
+ const saveData = data || this.data;
118
+
119
+ // Convert Map to object for JSON serialization
120
+ const serializableData = Object.fromEntries(saveData);
121
+
122
+ let dataToSave;
123
+ if (this.options.compression) {
124
+ dataToSave = await this._compress(serializableData);
125
+ } else if (this.options.encryption) {
126
+ dataToSave = await this._encrypt(serializableData);
127
+ } else {
128
+ dataToSave = JSON.stringify(serializableData, null, 2);
129
+ }
130
+
131
+ // Atomic write: write to temp file then rename
132
+ const tempPath = this.filePath + '.tmp';
133
+ await fs.writeFile(tempPath, dataToSave, 'utf8');
134
+ await fs.rename(tempPath, this.filePath);
135
+
136
+ const saveTime = performance.now() - startTime;
137
+
138
+ if (this.options.debug) {
139
+ console.log(`💾 Saved ${saveData.size} records in ${saveTime.toFixed(2)}ms`);
140
+ }
141
+
142
+ this.stats.writes++;
143
+ return true;
144
+ } catch (error) {
145
+ this.stats.errors++;
146
+ console.error('🚨 Persistence save error:', error);
147
+ throw error;
148
+ } finally {
149
+ this.isSaving = false;
150
+
151
+ // Process next item in queue
152
+ if (this.saveQueue.length > 0) {
153
+ const next = this.saveQueue.shift();
154
+ this.save(next.data)
155
+ .then(next.resolve)
156
+ .catch(next.reject);
157
+ }
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Set value in persistence
163
+ */
164
+ async set(key, value) {
165
+ this.data.set(key, value);
166
+
167
+ if (this.options.autoSave) {
168
+ await this.save();
169
+ }
170
+
171
+ return true;
172
+ }
173
+
174
+ /**
175
+ * Get value from persistence
176
+ */
177
+ async get(key) {
178
+ this.stats.reads++;
179
+ return this.data.get(key);
180
+ }
181
+
182
+ /**
183
+ * Delete value from persistence
184
+ */
185
+ async delete(key) {
186
+ const deleted = this.data.delete(key);
187
+
188
+ if (deleted && this.options.autoSave) {
189
+ await this.save();
190
+ }
191
+
192
+ return deleted;
193
+ }
194
+
195
+ /**
196
+ * Check if key exists
197
+ */
198
+ async has(key) {
199
+ return this.data.has(key);
200
+ }
201
+
202
+ /**
203
+ * Get all data
204
+ */
205
+ async getAll() {
206
+ return new Map(this.data);
207
+ }
208
+
209
+ /**
210
+ * Clear all data
211
+ */
212
+ async clear() {
213
+ this.data.clear();
214
+
215
+ if (this.options.autoSave) {
216
+ await this.save();
217
+ }
218
+
219
+ return true;
220
+ }
221
+
222
+ /**
223
+ * Compression methods (placeholder - would use zlib in real implementation)
224
+ */
225
+ async _compress(data) {
226
+ // In a real implementation, this would use zlib or similar
227
+ // For now, just return stringified data
228
+ return JSON.stringify(data);
229
+ }
230
+
231
+ async _decompress(data) {
232
+ // In a real implementation, this would decompress
233
+ // For now, just parse JSON
234
+ return JSON.parse(data);
235
+ }
236
+
237
+ /**
238
+ * Encryption methods (placeholder)
239
+ */
240
+ async _encrypt(data) {
241
+ if (!this.options.encryptionKey) {
242
+ throw new Error('Encryption key required for encryption');
243
+ }
244
+
245
+ // In a real implementation, this would use crypto
246
+ // For now, just return stringified data
247
+ return JSON.stringify(data);
248
+ }
249
+
250
+ async _decrypt(data) {
251
+ if (!this.options.encryptionKey) {
252
+ throw new Error('Encryption key required for decryption');
253
+ }
254
+
255
+ // In a real implementation, this would decrypt
256
+ // For now, just parse JSON
257
+ return JSON.parse(data);
258
+ }
259
+
260
+ /**
261
+ * Backup and recovery
262
+ */
263
+ async backup(backupPath = null) {
264
+ const path = backupPath || `${this.filePath}.backup_${Date.now()}`;
265
+
266
+ try {
267
+ await this.save();
268
+ await fs.copyFile(this.filePath, path);
269
+
270
+ if (this.options.debug) {
271
+ console.log(`💾 Backup created: ${path}`);
272
+ }
273
+
274
+ return path;
275
+ } catch (error) {
276
+ console.error('🚨 Backup failed:', error);
277
+ throw error;
278
+ }
279
+ }
280
+
281
+ async _recoverFromBackup() {
282
+ try {
283
+ const dir = path.dirname(this.filePath);
284
+ const files = await fs.readdir(dir);
285
+ const backupFiles = files
286
+ .filter(file => file.startsWith(path.basename(this.filePath) + '.backup_'))
287
+ .sort()
288
+ .reverse();
289
+
290
+ for (const backupFile of backupFiles) {
291
+ try {
292
+ const backupPath = path.join(dir, backupFile);
293
+ await fs.copyFile(backupPath, this.filePath);
294
+
295
+ console.log(`🔧 Recovered from backup: ${backupFile}`);
296
+ return await this.load();
297
+ } catch (error) {
298
+ // Try next backup
299
+ continue;
300
+ }
301
+ }
302
+
303
+ throw new Error('No valid backup found');
304
+ } catch (error) {
305
+ console.error('🚨 Recovery failed:', error);
306
+ // Return empty data as last resort
307
+ this.data.clear();
308
+ return new Map();
309
+ }
310
+ }
311
+
312
+ /**
313
+ * Get persistence statistics
314
+ */
315
+ getStats() {
316
+ return {
317
+ ...this.stats,
318
+ dataSize: this.data.size,
319
+ filePath: this.filePath,
320
+ isSaving: this.isSaving,
321
+ queueLength: this.saveQueue.length
322
+ };
323
+ }
324
+
325
+ /**
326
+ * Start auto-save interval
327
+ */
328
+ startAutoSave() {
329
+ if (this.autoSaveInterval) {
330
+ clearInterval(this.autoSaveInterval);
331
+ }
332
+
333
+ this.autoSaveInterval = setInterval(() => {
334
+ if (this.data.size > 0) {
335
+ this.save().catch(error => {
336
+ console.error('🚨 Auto-save failed:', error);
337
+ });
338
+ }
339
+ }, this.options.saveInterval);
340
+ }
341
+
342
+ /**
343
+ * Stop auto-save
344
+ */
345
+ stopAutoSave() {
346
+ if (this.autoSaveInterval) {
347
+ clearInterval(this.autoSaveInterval);
348
+ this.autoSaveInterval = null;
349
+ }
350
+ }
351
+
352
+ /**
353
+ * Close persistence (cleanup)
354
+ */
355
+ async close() {
356
+ this.stopAutoSave();
357
+
358
+ // Process remaining save queue
359
+ while (this.saveQueue.length > 0) {
360
+ const { data, resolve, reject } = this.saveQueue.shift();
361
+ try {
362
+ await this.save(data);
363
+ resolve();
364
+ } catch (error) {
365
+ reject(error);
366
+ }
367
+ }
368
+
369
+ // Final save
370
+ if (this.data.size > 0) {
371
+ await this.save();
372
+ }
373
+ }
374
+ }
375
+
376
+ module.exports = Persistence;