sehawq.db 2.4.2 → 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.
- package/.github/workflows/npm-publish.yml +1 -1
- package/index.js +2 -0
- package/package.json +28 -7
- package/readme.md +342 -235
- package/src/core/Database.js +295 -0
- package/src/core/Events.js +286 -0
- package/src/core/IndexManager.js +814 -0
- package/src/core/Persistence.js +376 -0
- package/src/core/QueryEngine.js +448 -0
- package/src/core/Storage.js +322 -0
- package/src/core/Validator.js +325 -0
- package/src/index.js +90 -469
- package/src/performance/Cache.js +339 -0
- package/src/performance/LazyLoader.js +355 -0
- package/src/performance/MemoryManager.js +496 -0
- package/src/server/api.js +688 -0
- package/src/server/websocket.js +528 -0
- package/src/utils/benchmark.js +52 -0
- package/src/utils/dot-notation.js +248 -0
- package/src/utils/helpers.js +276 -0
- package/src/utils/profiler.js +71 -0
- package/src/version.js +38 -0
|
@@ -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;
|