sehawq.db 3.0.0 → 4.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.
@@ -0,0 +1,295 @@
1
+ /**
2
+ * SehawqDB - The main database class
3
+ *
4
+ * Started as a simple JSON store, now with performance optimizations
5
+ * Because waiting for databases to load is boring 😴
6
+ */
7
+
8
+ const EventEmitter = require('events');
9
+ const path = require('path');
10
+ const fs = require('fs').promises;
11
+
12
+ class SehawqDB extends EventEmitter {
13
+ constructor(options = {}) {
14
+ super();
15
+
16
+ // Config with some sensible defaults
17
+ this.config = {
18
+ path: './sehawq-data.json',
19
+ autoSave: true,
20
+ saveInterval: 5000,
21
+ cacheEnabled: true,
22
+ cacheSize: 1000,
23
+ ...options
24
+ };
25
+
26
+ // Performance optimizations
27
+ this.data = new Map(); // Using Map for better performance
28
+ this.cache = new Map(); // Hot data cache
29
+ this.indexes = new Map(); // Query indexes
30
+
31
+ // Runtime stats (for debugging)
32
+ this.stats = {
33
+ reads: 0,
34
+ writes: 0,
35
+ cacheHits: 0,
36
+ cacheMisses: 0
37
+ };
38
+
39
+ this._initialized = false;
40
+ this._saveTimeout = null;
41
+
42
+ // Initialize - but don't block the constructor
43
+ this._init().catch(error => {
44
+ console.error('🚨 SehawqDB init failed:', error);
45
+ });
46
+ }
47
+
48
+ /**
49
+ * Async initialization
50
+ * Because sometimes we need to wait for things...
51
+ */
52
+ async _init() {
53
+ if (this._initialized) return;
54
+
55
+ try {
56
+ // Try to load existing data
57
+ await this._loadFromDisk();
58
+
59
+ // Start auto-save if enabled
60
+ if (this.config.autoSave) {
61
+ this._startAutoSave();
62
+ }
63
+
64
+ this._initialized = true;
65
+ this.emit('ready');
66
+
67
+ if (this.config.debug) {
68
+ console.log('✅ SehawqDB ready - Performance mode: ON');
69
+ }
70
+ } catch (error) {
71
+ this.emit('error', error);
72
+ throw error;
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Set a value - the bread and butter
78
+ */
79
+ set(key, value) {
80
+ if (!this._initialized) {
81
+ throw new Error('Database not initialized. Wait for ready event.');
82
+ }
83
+
84
+ const oldValue = this.data.get(key);
85
+ this.data.set(key, value);
86
+
87
+ // Cache the hot data
88
+ if (this.config.cacheEnabled) {
89
+ this._updateCache(key, value);
90
+ }
91
+
92
+ // Update indexes if any
93
+ this._updateIndexes(key, value, oldValue);
94
+
95
+ this.stats.writes++;
96
+ this.emit('set', { key, value, oldValue });
97
+
98
+ // Immediate save for important data, otherwise batch it
99
+ if (this.config.autoSave) {
100
+ this._queueSave();
101
+ }
102
+
103
+ return this;
104
+ }
105
+
106
+ /**
107
+ * Get a value - faster than your morning coffee
108
+ */
109
+ get(key) {
110
+ if (!this._initialized) {
111
+ throw new Error('Database not initialized.');
112
+ }
113
+
114
+ this.stats.reads++;
115
+
116
+ // Check cache first (hot path)
117
+ if (this.config.cacheEnabled && this.cache.has(key)) {
118
+ this.stats.cacheHits++;
119
+ return this.cache.get(key);
120
+ }
121
+
122
+ this.stats.cacheMisses++;
123
+ const value = this.data.get(key);
124
+
125
+ // Cache it for next time
126
+ if (this.config.cacheEnabled && value !== undefined) {
127
+ this._updateCache(key, value);
128
+ }
129
+
130
+ return value;
131
+ }
132
+
133
+ /**
134
+ * Delete a key - poof! gone.
135
+ */
136
+ delete(key) {
137
+ if (!this.data.has(key)) return false;
138
+
139
+ const oldValue = this.data.get(key);
140
+ this.data.delete(key);
141
+ this.cache.delete(key);
142
+ this._removeFromIndexes(key, oldValue);
143
+
144
+ this.emit('delete', { key, oldValue });
145
+ this._queueSave();
146
+
147
+ return true;
148
+ }
149
+
150
+ /**
151
+ * Check if key exists - no guessing games
152
+ */
153
+ has(key) {
154
+ return this.data.has(key);
155
+ }
156
+
157
+ /**
158
+ * Get all data - use carefully!
159
+ */
160
+ all() {
161
+ return Object.fromEntries(this.data);
162
+ }
163
+
164
+ /**
165
+ * Clear everything - the nuclear option
166
+ */
167
+ clear() {
168
+ const size = this.data.size;
169
+ this.data.clear();
170
+ this.cache.clear();
171
+ this.indexes.clear();
172
+
173
+ this.emit('clear', { size });
174
+ this._queueSave();
175
+
176
+ return this;
177
+ }
178
+
179
+ /**
180
+ * Smart cache management
181
+ */
182
+ _updateCache(key, value) {
183
+ // Simple LRU-like cache eviction
184
+ if (this.cache.size >= this.config.cacheSize) {
185
+ const firstKey = this.cache.keys().next().value;
186
+ this.cache.delete(firstKey);
187
+ }
188
+
189
+ this.cache.set(key, value);
190
+ }
191
+
192
+ /**
193
+ * Index management for faster queries
194
+ */
195
+ _updateIndexes(key, newValue, oldValue) {
196
+ // TODO: Implement in IndexManager.js
197
+ // This will make queries lightning fast ⚡
198
+ }
199
+
200
+ _removeFromIndexes(key, oldValue) {
201
+ // TODO: Remove from indexes
202
+ }
203
+
204
+ /**
205
+ * File operations with error handling
206
+ */
207
+ async _loadFromDisk() {
208
+ try {
209
+ const data = await fs.readFile(this.config.path, 'utf8');
210
+ const parsed = JSON.parse(data);
211
+
212
+ // Convert object to Map for better performance
213
+ for (const [key, value] of Object.entries(parsed)) {
214
+ this.data.set(key, value);
215
+ }
216
+
217
+ if (this.config.debug) {
218
+ console.log(`📁 Loaded ${this.data.size} records from disk`);
219
+ }
220
+ } catch (error) {
221
+ if (error.code === 'ENOENT') {
222
+ // File doesn't exist yet - that's fine
223
+ if (this.config.debug) {
224
+ console.log('📁 No existing data file - starting fresh');
225
+ }
226
+ } else {
227
+ throw error;
228
+ }
229
+ }
230
+ }
231
+
232
+ async _saveToDisk() {
233
+ try {
234
+ const data = JSON.stringify(Object.fromEntries(this.data), null, 2);
235
+
236
+ // Atomic write - prevent corruption
237
+ const tempPath = this.config.path + '.tmp';
238
+ await fs.writeFile(tempPath, data);
239
+ await fs.rename(tempPath, this.config.path);
240
+
241
+ if (this.config.debug) {
242
+ console.log(`💾 Saved ${this.data.size} records to disk`);
243
+ }
244
+
245
+ this.emit('save', { recordCount: this.data.size });
246
+ } catch (error) {
247
+ this.emit('error', error);
248
+ }
249
+ }
250
+
251
+ /**
252
+ * Batch save operations for performance
253
+ */
254
+ _queueSave() {
255
+ if (this._saveTimeout) clearTimeout(this._saveTimeout);
256
+
257
+ this._saveTimeout = setTimeout(() => {
258
+ this._saveToDisk();
259
+ }, 100); // Batch saves within 100ms window
260
+ }
261
+
262
+ _startAutoSave() {
263
+ setInterval(() => {
264
+ if (this._initialized) {
265
+ this._saveToDisk();
266
+ }
267
+ }, this.config.saveInterval);
268
+ }
269
+
270
+ /**
271
+ * Performance monitoring
272
+ */
273
+ getStats() {
274
+ return {
275
+ ...this.stats,
276
+ cacheHitRate: this.stats.reads > 0
277
+ ? (this.stats.cacheHits / this.stats.reads * 100).toFixed(2) + '%'
278
+ : '0%',
279
+ totalRecords: this.data.size,
280
+ cacheSize: this.cache.size
281
+ };
282
+ }
283
+
284
+ /**
285
+ * Clean shutdown - be nice to your data
286
+ */
287
+ async close() {
288
+ if (this._saveTimeout) clearTimeout(this._saveTimeout);
289
+ await this._saveToDisk();
290
+ this._initialized = false;
291
+ this.emit('close');
292
+ }
293
+ }
294
+
295
+ module.exports = SehawqDB;
@@ -0,0 +1,286 @@
1
+ /**
2
+ * Event System - Makes everything reactive ⚡
3
+ *
4
+ * Listen to database changes, system events, anything!
5
+ * Because sometimes you need to know when stuff happens
6
+ */
7
+
8
+ class Events {
9
+ constructor() {
10
+ this.events = new Map();
11
+ this.maxListeners = 100;
12
+ this.stats = {
13
+ eventsEmitted: 0,
14
+ listenersCalled: 0,
15
+ errors: 0
16
+ };
17
+ }
18
+
19
+ /**
20
+ * Add event listener
21
+ */
22
+ on(event, listener, options = {}) {
23
+ if (typeof listener !== 'function') {
24
+ throw new Error('Listener must be a function');
25
+ }
26
+
27
+ if (!this.events.has(event)) {
28
+ this.events.set(event, []);
29
+ }
30
+
31
+ const listeners = this.events.get(event);
32
+
33
+ // Check max listeners
34
+ if (listeners.length >= this.maxListeners) {
35
+ console.warn(`⚠️ Event '${event}' has ${listeners.length} listeners, possible memory leak`);
36
+ }
37
+
38
+ // Add listener with options
39
+ listeners.push({
40
+ fn: listener,
41
+ once: options.once || false,
42
+ async: options.async || false,
43
+ id: options.id || this._generateId()
44
+ });
45
+
46
+ return this;
47
+ }
48
+
49
+ /**
50
+ * Add one-time event listener
51
+ */
52
+ once(event, listener, options = {}) {
53
+ return this.on(event, listener, { ...options, once: true });
54
+ }
55
+
56
+ /**
57
+ * Remove event listener
58
+ */
59
+ off(event, listener) {
60
+ if (!this.events.has(event)) {
61
+ return this;
62
+ }
63
+
64
+ const listeners = this.events.get(event);
65
+
66
+ if (listener) {
67
+ // Remove specific listener
68
+ const index = listeners.findIndex(l => l.fn === listener);
69
+ if (index > -1) {
70
+ listeners.splice(index, 1);
71
+ }
72
+ } else {
73
+ // Remove all listeners for event
74
+ this.events.delete(event);
75
+ }
76
+
77
+ return this;
78
+ }
79
+
80
+ /**
81
+ * Emit event
82
+ */
83
+ emit(event, ...args) {
84
+ this.stats.eventsEmitted++;
85
+
86
+ if (!this.events.has(event)) {
87
+ return false;
88
+ }
89
+
90
+ const listeners = this.events.get(event).slice(); // Copy array
91
+ let hasListeners = false;
92
+
93
+ for (const listener of listeners) {
94
+ hasListeners = true;
95
+
96
+ // Remove one-time listeners
97
+ if (listener.once) {
98
+ this.off(event, listener.fn);
99
+ }
100
+
101
+ // Call listener
102
+ this._callListener(listener, event, args);
103
+ }
104
+
105
+ return hasListeners;
106
+ }
107
+
108
+ /**
109
+ * Call listener with error handling
110
+ */
111
+ _callListener(listener, event, args) {
112
+ const callListener = () => {
113
+ try {
114
+ listener.fn.apply(this, args);
115
+ this.stats.listenersCalled++;
116
+ } catch (error) {
117
+ this.stats.errors++;
118
+ console.error(`🚨 Event listener error for '${event}':`, error);
119
+
120
+ // Emit error event
121
+ if (this.events.has('error')) {
122
+ this.emit('error', error, event, listener);
123
+ }
124
+ }
125
+ };
126
+
127
+ if (listener.async) {
128
+ setImmediate(callListener);
129
+ } else {
130
+ callListener();
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Get all event names
136
+ */
137
+ eventNames() {
138
+ return Array.from(this.events.keys());
139
+ }
140
+
141
+ /**
142
+ * Get listeners for event
143
+ */
144
+ listeners(event) {
145
+ return this.events.has(event)
146
+ ? this.events.get(event).map(l => l.fn)
147
+ : [];
148
+ }
149
+
150
+ /**
151
+ * Get listener count for event
152
+ */
153
+ listenerCount(event) {
154
+ return this.events.has(event) ? this.events.get(event).length : 0;
155
+ }
156
+
157
+ /**
158
+ * Remove all listeners
159
+ */
160
+ removeAllListeners(event = null) {
161
+ if (event) {
162
+ this.events.delete(event);
163
+ } else {
164
+ this.events.clear();
165
+ }
166
+ return this;
167
+ }
168
+
169
+ /**
170
+ * Set max listeners
171
+ */
172
+ setMaxListeners(n) {
173
+ this.maxListeners = n;
174
+ return this;
175
+ }
176
+
177
+ /**
178
+ * Get max listeners
179
+ */
180
+ getMaxListeners() {
181
+ return this.maxListeners;
182
+ }
183
+
184
+ /**
185
+ * Generate unique ID for listener
186
+ */
187
+ _generateId() {
188
+ return `listener_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
189
+ }
190
+
191
+ /**
192
+ * Wait for event (Promise-based)
193
+ */
194
+ waitFor(event, timeout = 0) {
195
+ return new Promise((resolve, reject) => {
196
+ let timeoutId;
197
+
198
+ const listener = (...args) => {
199
+ if (timeoutId) clearTimeout(timeoutId);
200
+ resolve(args);
201
+ };
202
+
203
+ this.once(event, listener);
204
+
205
+ if (timeout > 0) {
206
+ timeoutId = setTimeout(() => {
207
+ this.off(event, listener);
208
+ reject(new Error(`Event '${event}' timeout after ${timeout}ms`));
209
+ }, timeout);
210
+ }
211
+ });
212
+ }
213
+
214
+ /**
215
+ * Pipe events to another emitter
216
+ */
217
+ pipe(event, targetEmitter, targetEvent = null) {
218
+ const targetEventName = targetEvent || event;
219
+
220
+ return this.on(event, (...args) => {
221
+ targetEmitter.emit(targetEventName, ...args);
222
+ });
223
+ }
224
+
225
+ /**
226
+ * Create namespaced event emitter
227
+ */
228
+ namespace(namespace) {
229
+ const namespaced = new Events();
230
+
231
+ // Pipe all events with namespace prefix
232
+ this.on('*', (event, ...args) => {
233
+ namespaced.emit(event, ...args);
234
+ });
235
+
236
+ // Pipe namespaced events back to parent
237
+ namespaced.on('*', (event, ...args) => {
238
+ this.emit(`${namespace}.${event}`, ...args);
239
+ });
240
+
241
+ return namespaced;
242
+ }
243
+
244
+ /**
245
+ * Get event statistics
246
+ */
247
+ getStats() {
248
+ const events = {};
249
+
250
+ for (const [event, listeners] of this.events) {
251
+ events[event] = {
252
+ listeners: listeners.length,
253
+ once: listeners.filter(l => l.once).length,
254
+ async: listeners.filter(l => l.async).length
255
+ };
256
+ }
257
+
258
+ return {
259
+ ...this.stats,
260
+ totalEvents: this.events.size,
261
+ totalListeners: Array.from(this.events.values()).reduce((sum, listeners) => sum + listeners.length, 0),
262
+ events
263
+ };
264
+ }
265
+
266
+ /**
267
+ * Debug: log all events
268
+ */
269
+ debug(enable = true) {
270
+ if (enable) {
271
+ this.on('*', (event, ...args) => {
272
+ console.log(`🎯 Event: ${event}`, args);
273
+ });
274
+ } else {
275
+ this.off('*');
276
+ }
277
+ return this;
278
+ }
279
+ }
280
+
281
+ // Wildcard event support
282
+ Events.prototype.on('*', function(event, ...args) {
283
+ // This will be called for every event
284
+ });
285
+
286
+ module.exports = Events;