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.
- package/.github/workflows/npm-publish.yml +1 -1
- package/index.js +2 -0
- package/package.json +25 -9
- package/readme.md +342 -170
- 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 +106 -750
- 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
package/src/index.js
CHANGED
|
@@ -1,760 +1,116 @@
|
|
|
1
|
-
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const { Server } = require("socket.io");
|
|
7
|
-
const cors = require("cors");
|
|
1
|
+
// src/index.js - The heart of SehawqDB v4.0.0
|
|
2
|
+
const Database = require('./core/Database');
|
|
3
|
+
const QueryEngine = require('./core/QueryEngine');
|
|
4
|
+
const IndexManager = require('./core/IndexManager');
|
|
5
|
+
const Storage = require('./core/Storage');
|
|
8
6
|
|
|
9
|
-
class SehawqDB
|
|
10
|
-
/**
|
|
11
|
-
* Create a new SehawqDB instance.
|
|
12
|
-
* @param {Object} options
|
|
13
|
-
* @param {string} [options.path="sehawq.json"] File path for storage.
|
|
14
|
-
* @param {number} [options.autoSaveInterval=0] Autosave interval in ms (0 disables autosave).
|
|
15
|
-
* @param {boolean} [options.enableServer=false] Enable REST API server
|
|
16
|
-
* @param {number} [options.serverPort=3000] Server port
|
|
17
|
-
* @param {boolean} [options.enableRealtime=true] Enable real-time sync via WebSocket
|
|
18
|
-
* @param {string} [options.apiKey] Optional API key for authentication
|
|
19
|
-
*/
|
|
7
|
+
class SehawqDB {
|
|
20
8
|
constructor(options = {}) {
|
|
21
|
-
|
|
22
|
-
this.
|
|
23
|
-
this.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
this.
|
|
28
|
-
this.
|
|
29
|
-
this.
|
|
30
|
-
this.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
this.
|
|
35
|
-
this.
|
|
36
|
-
this.
|
|
37
|
-
|
|
38
|
-
this.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
clear() {
|
|
112
|
-
this.data = {};
|
|
113
|
-
this.emit("clear");
|
|
114
|
-
|
|
115
|
-
// Real-time broadcast
|
|
116
|
-
if (this.io) {
|
|
117
|
-
this.io.emit("data:changed", {
|
|
118
|
-
action: "clear",
|
|
119
|
-
timestamp: Date.now()
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
this.save();
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
keys() {
|
|
127
|
-
return Object.keys(this.data);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
values() {
|
|
131
|
-
return Object.values(this.data);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// ---------------- Array helpers ----------------
|
|
135
|
-
push(key, value) {
|
|
136
|
-
let arr = this._getByPath(key);
|
|
137
|
-
if (!Array.isArray(arr)) arr = [];
|
|
138
|
-
arr.push(value);
|
|
139
|
-
this._setByPath(key, arr);
|
|
140
|
-
this.emit("push", { key, value });
|
|
141
|
-
|
|
142
|
-
// Real-time broadcast
|
|
143
|
-
if (this.io) {
|
|
144
|
-
this.io.emit("data:changed", {
|
|
145
|
-
action: "push",
|
|
146
|
-
key,
|
|
147
|
-
value,
|
|
148
|
-
timestamp: Date.now()
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
this.save();
|
|
153
|
-
return arr;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
pull(key, value) {
|
|
157
|
-
let arr = this._getByPath(key);
|
|
158
|
-
if (!Array.isArray(arr)) return [];
|
|
159
|
-
arr = arr.filter(v => v !== value);
|
|
160
|
-
this._setByPath(key, arr);
|
|
161
|
-
this.emit("pull", { key, value });
|
|
162
|
-
|
|
163
|
-
// Real-time broadcast
|
|
164
|
-
if (this.io) {
|
|
165
|
-
this.io.emit("data:changed", {
|
|
166
|
-
action: "pull",
|
|
167
|
-
key,
|
|
168
|
-
value,
|
|
169
|
-
timestamp: Date.now()
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
this.save();
|
|
174
|
-
return arr;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// ---------------- Math helpers ----------------
|
|
178
|
-
add(key, number) {
|
|
179
|
-
let val = this._getByPath(key);
|
|
180
|
-
if (typeof val !== "number") val = 0;
|
|
181
|
-
val += number;
|
|
182
|
-
this._setByPath(key, val);
|
|
183
|
-
this.emit("add", { key, number });
|
|
184
|
-
|
|
185
|
-
// Real-time broadcast
|
|
186
|
-
if (this.io) {
|
|
187
|
-
this.io.emit("data:changed", {
|
|
188
|
-
action: "add",
|
|
189
|
-
key,
|
|
190
|
-
number,
|
|
191
|
-
newValue: val,
|
|
192
|
-
timestamp: Date.now()
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
this.save();
|
|
197
|
-
return val;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
subtract(key, number) {
|
|
201
|
-
return this.add(key, -number);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// ---------------- Query System ----------------
|
|
205
|
-
|
|
206
|
-
find(filter) {
|
|
207
|
-
const results = [];
|
|
208
|
-
|
|
209
|
-
if (typeof filter === 'function') {
|
|
210
|
-
for (const [key, value] of Object.entries(this.data)) {
|
|
211
|
-
if (filter(value, key)) {
|
|
212
|
-
results.push({ key, value });
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
} else {
|
|
216
|
-
for (const [key, value] of Object.entries(this.data)) {
|
|
217
|
-
results.push({ key, value });
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
return new QueryResult(results);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
findOne(filter) {
|
|
225
|
-
if (typeof filter === 'function') {
|
|
226
|
-
for (const [key, value] of Object.entries(this.data)) {
|
|
227
|
-
if (filter(value, key)) {
|
|
228
|
-
return { key, value };
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
return undefined;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
where(field, operator, value) {
|
|
236
|
-
return this.find((item, key) => {
|
|
237
|
-
const fieldValue = this._getValueByPath(item, field);
|
|
238
|
-
|
|
239
|
-
switch (operator) {
|
|
240
|
-
case '=':
|
|
241
|
-
case '==':
|
|
242
|
-
return fieldValue === value;
|
|
243
|
-
case '!=':
|
|
244
|
-
return fieldValue !== value;
|
|
245
|
-
case '>':
|
|
246
|
-
return fieldValue > value;
|
|
247
|
-
case '<':
|
|
248
|
-
return fieldValue < value;
|
|
249
|
-
case '>=':
|
|
250
|
-
return fieldValue >= value;
|
|
251
|
-
case '<=':
|
|
252
|
-
return fieldValue <= value;
|
|
253
|
-
case 'in':
|
|
254
|
-
return Array.isArray(value) && value.includes(fieldValue);
|
|
255
|
-
case 'contains':
|
|
256
|
-
return typeof fieldValue === 'string' && fieldValue.includes(value);
|
|
257
|
-
case 'startsWith':
|
|
258
|
-
return typeof fieldValue === 'string' && fieldValue.startsWith(value);
|
|
259
|
-
case 'endsWith':
|
|
260
|
-
return typeof fieldValue === 'string' && fieldValue.endsWith(value);
|
|
261
|
-
default:
|
|
262
|
-
return false;
|
|
263
|
-
}
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// ---------------- Aggregation System ----------------
|
|
268
|
-
|
|
269
|
-
count(filter) {
|
|
270
|
-
if (filter) {
|
|
271
|
-
return this.find(filter).count();
|
|
272
|
-
}
|
|
273
|
-
return Object.keys(this.data).length;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
sum(field, filter) {
|
|
277
|
-
const items = filter ? this.find(filter).toArray() : this.find().toArray();
|
|
278
|
-
return items.reduce((sum, item) => {
|
|
279
|
-
const val = this._getValueByPath(item.value, field);
|
|
280
|
-
return sum + (typeof val === 'number' ? val : 0);
|
|
281
|
-
}, 0);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
avg(field, filter) {
|
|
285
|
-
const items = filter ? this.find(filter).toArray() : this.find().toArray();
|
|
286
|
-
if (items.length === 0) return 0;
|
|
287
|
-
|
|
288
|
-
const sum = items.reduce((total, item) => {
|
|
289
|
-
const val = this._getValueByPath(item.value, field);
|
|
290
|
-
return total + (typeof val === 'number' ? val : 0);
|
|
291
|
-
}, 0);
|
|
292
|
-
|
|
293
|
-
return sum / items.length;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
min(field, filter) {
|
|
297
|
-
const items = filter ? this.find(filter).toArray() : this.find().toArray();
|
|
298
|
-
if (items.length === 0) return undefined;
|
|
299
|
-
|
|
300
|
-
return Math.min(...items.map(item => {
|
|
301
|
-
const val = this._getValueByPath(item.value, field);
|
|
302
|
-
return typeof val === 'number' ? val : Infinity;
|
|
303
|
-
}).filter(val => val !== Infinity));
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
max(field, filter) {
|
|
307
|
-
const items = filter ? this.find(filter).toArray() : this.find().toArray();
|
|
308
|
-
if (items.length === 0) return undefined;
|
|
309
|
-
|
|
310
|
-
return Math.max(...items.map(item => {
|
|
311
|
-
const val = this._getValueByPath(item.value, field);
|
|
312
|
-
return typeof val === 'number' ? val : -Infinity;
|
|
313
|
-
}).filter(val => val !== -Infinity));
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
groupBy(field, filter) {
|
|
317
|
-
const items = filter ? this.find(filter).toArray() : this.find().toArray();
|
|
318
|
-
const groups = {};
|
|
319
|
-
|
|
320
|
-
items.forEach(item => {
|
|
321
|
-
const groupKey = this._getValueByPath(item.value, field);
|
|
322
|
-
const key = groupKey !== undefined ? String(groupKey) : 'undefined';
|
|
323
|
-
|
|
324
|
-
if (!groups[key]) {
|
|
325
|
-
groups[key] = [];
|
|
326
|
-
}
|
|
327
|
-
groups[key].push(item);
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
return groups;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
// ---------------- REST API Server ----------------
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* Start REST API server with real-time sync
|
|
337
|
-
* @param {number} port - Server port
|
|
338
|
-
* @returns {Promise<void>}
|
|
339
|
-
*/
|
|
340
|
-
async startServer(port = 3000) {
|
|
341
|
-
if (this.isServerRunning) {
|
|
342
|
-
console.log(`⚠️ Server is already running on port ${this.serverPort}`);
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
this.app = express();
|
|
347
|
-
this.server = http.createServer(this.app);
|
|
348
|
-
|
|
349
|
-
// Enable real-time if requested
|
|
350
|
-
if (this.enableRealtime) {
|
|
351
|
-
this.io = new Server(this.server, {
|
|
352
|
-
cors: {
|
|
353
|
-
origin: "*",
|
|
354
|
-
methods: ["GET", "POST", "PUT", "DELETE"]
|
|
355
|
-
}
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
this._setupWebSocket();
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
// Middleware
|
|
362
|
-
this.app.use(cors());
|
|
363
|
-
this.app.use(express.json());
|
|
364
|
-
|
|
365
|
-
// API Key middleware
|
|
366
|
-
if (this.apiKey) {
|
|
367
|
-
this.app.use((req, res, next) => {
|
|
368
|
-
const key = req.headers['x-api-key'];
|
|
369
|
-
if (key !== this.apiKey) {
|
|
370
|
-
return res.status(401).json({ error: 'Unauthorized' });
|
|
371
|
-
}
|
|
372
|
-
next();
|
|
373
|
-
});
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
this._setupRoutes();
|
|
377
|
-
|
|
378
|
-
return new Promise((resolve, reject) => {
|
|
379
|
-
this.server.listen(port, () => {
|
|
380
|
-
this.isServerRunning = true;
|
|
381
|
-
this.serverPort = port;
|
|
382
|
-
console.log(`🚀 SehawqDB Server running on http://localhost:${port}`);
|
|
383
|
-
console.log(`📡 Real-time sync: ${this.enableRealtime ? 'ENABLED' : 'DISABLED'}`);
|
|
384
|
-
console.log(`🔒 API Key: ${this.apiKey ? 'ENABLED' : 'DISABLED'}`);
|
|
385
|
-
this.emit('server:started', { port });
|
|
386
|
-
resolve();
|
|
387
|
-
}).on('error', reject);
|
|
388
|
-
});
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
/**
|
|
392
|
-
* Stop the REST API server
|
|
393
|
-
*/
|
|
394
|
-
stopServer() {
|
|
395
|
-
if (!this.isServerRunning) return;
|
|
396
|
-
|
|
397
|
-
if (this.io) this.io.close();
|
|
398
|
-
if (this.server) this.server.close();
|
|
399
|
-
|
|
400
|
-
this.isServerRunning = false;
|
|
401
|
-
console.log('🛑 SehawqDB Server stopped');
|
|
402
|
-
this.emit('server:stopped');
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
_setupRoutes() {
|
|
406
|
-
const router = express.Router();
|
|
407
|
-
|
|
408
|
-
// Health check
|
|
409
|
-
router.get('/health', (req, res) => {
|
|
410
|
-
res.json({
|
|
411
|
-
status: 'ok',
|
|
412
|
-
uptime: process.uptime(),
|
|
413
|
-
realtime: this.enableRealtime,
|
|
414
|
-
dataSize: Object.keys(this.data).length
|
|
415
|
-
});
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
// Get all data
|
|
419
|
-
router.get('/data', (req, res) => {
|
|
420
|
-
res.json({ success: true, data: this.data });
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
// Get by key
|
|
424
|
-
router.get('/data/:key', (req, res) => {
|
|
425
|
-
const { key } = req.params;
|
|
426
|
-
const value = this.get(key);
|
|
427
|
-
|
|
428
|
-
if (value === undefined) {
|
|
429
|
-
return res.status(404).json({ success: false, error: 'Key not found' });
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
res.json({ success: true, key, value });
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
// Set data
|
|
436
|
-
router.post('/data/:key', (req, res) => {
|
|
437
|
-
const { key } = req.params;
|
|
438
|
-
const { value } = req.body;
|
|
439
|
-
|
|
440
|
-
if (value === undefined) {
|
|
441
|
-
return res.status(400).json({ success: false, error: 'Value is required' });
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
const result = this.set(key, value);
|
|
445
|
-
res.json({ success: true, key, value: result });
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
// Update data (alias for set)
|
|
449
|
-
router.put('/data/:key', (req, res) => {
|
|
450
|
-
const { key } = req.params;
|
|
451
|
-
const { value } = req.body;
|
|
452
|
-
|
|
453
|
-
if (value === undefined) {
|
|
454
|
-
return res.status(400).json({ success: false, error: 'Value is required' });
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
const result = this.set(key, value);
|
|
458
|
-
res.json({ success: true, key, value: result });
|
|
459
|
-
});
|
|
460
|
-
|
|
461
|
-
// Delete data
|
|
462
|
-
router.delete('/data/:key', (req, res) => {
|
|
463
|
-
const { key } = req.params;
|
|
464
|
-
|
|
465
|
-
if (!this.has(key)) {
|
|
466
|
-
return res.status(404).json({ success: false, error: 'Key not found' });
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
this.delete(key);
|
|
470
|
-
res.json({ success: true, key });
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
// Query with find
|
|
474
|
-
router.post('/query', (req, res) => {
|
|
475
|
-
try {
|
|
476
|
-
const { filter, sort, limit, skip } = req.body;
|
|
477
|
-
|
|
478
|
-
let query = this.find();
|
|
479
|
-
|
|
480
|
-
// Apply sorting
|
|
481
|
-
if (sort && sort.field) {
|
|
482
|
-
query = query.sort(sort.field, sort.direction || 'asc');
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
// Apply pagination
|
|
486
|
-
if (skip) query = query.skip(skip);
|
|
487
|
-
if (limit) query = query.limit(limit);
|
|
488
|
-
|
|
489
|
-
const results = query.toArray();
|
|
490
|
-
|
|
491
|
-
res.json({
|
|
492
|
-
success: true,
|
|
493
|
-
results,
|
|
494
|
-
count: results.length
|
|
495
|
-
});
|
|
496
|
-
} catch (error) {
|
|
497
|
-
res.status(400).json({ success: false, error: error.message });
|
|
498
|
-
}
|
|
499
|
-
});
|
|
500
|
-
|
|
501
|
-
// Aggregation
|
|
502
|
-
router.get('/aggregate/:operation', (req, res) => {
|
|
503
|
-
try {
|
|
504
|
-
const { operation } = req.params;
|
|
505
|
-
const { field } = req.query;
|
|
506
|
-
|
|
507
|
-
let result;
|
|
508
|
-
|
|
509
|
-
switch (operation) {
|
|
510
|
-
case 'count':
|
|
511
|
-
result = this.count();
|
|
512
|
-
break;
|
|
513
|
-
case 'sum':
|
|
514
|
-
if (!field) return res.status(400).json({ error: 'Field is required' });
|
|
515
|
-
result = this.sum(field);
|
|
516
|
-
break;
|
|
517
|
-
case 'avg':
|
|
518
|
-
if (!field) return res.status(400).json({ error: 'Field is required' });
|
|
519
|
-
result = this.avg(field);
|
|
520
|
-
break;
|
|
521
|
-
case 'min':
|
|
522
|
-
if (!field) return res.status(400).json({ error: 'Field is required' });
|
|
523
|
-
result = this.min(field);
|
|
524
|
-
break;
|
|
525
|
-
case 'max':
|
|
526
|
-
if (!field) return res.status(400).json({ error: 'Field is required' });
|
|
527
|
-
result = this.max(field);
|
|
528
|
-
break;
|
|
529
|
-
default:
|
|
530
|
-
return res.status(400).json({ error: 'Invalid operation' });
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
res.json({ success: true, operation, field, result });
|
|
534
|
-
} catch (error) {
|
|
535
|
-
res.status(400).json({ success: false, error: error.message });
|
|
536
|
-
}
|
|
537
|
-
});
|
|
538
|
-
|
|
539
|
-
// Array operations
|
|
540
|
-
router.post('/array/:key/push', (req, res) => {
|
|
541
|
-
const { key } = req.params;
|
|
542
|
-
const { value } = req.body;
|
|
543
|
-
|
|
544
|
-
const result = this.push(key, value);
|
|
545
|
-
res.json({ success: true, key, value: result });
|
|
546
|
-
});
|
|
547
|
-
|
|
548
|
-
router.post('/array/:key/pull', (req, res) => {
|
|
549
|
-
const { key } = req.params;
|
|
550
|
-
const { value } = req.body;
|
|
551
|
-
|
|
552
|
-
const result = this.pull(key, value);
|
|
553
|
-
res.json({ success: true, key, value: result });
|
|
554
|
-
});
|
|
555
|
-
|
|
556
|
-
// Math operations
|
|
557
|
-
router.post('/math/:key/add', (req, res) => {
|
|
558
|
-
const { key } = req.params;
|
|
559
|
-
const { number } = req.body;
|
|
560
|
-
|
|
561
|
-
if (typeof number !== 'number') {
|
|
562
|
-
return res.status(400).json({ error: 'Number is required' });
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
const result = this.add(key, number);
|
|
566
|
-
res.json({ success: true, key, value: result });
|
|
567
|
-
});
|
|
568
|
-
|
|
569
|
-
router.post('/math/:key/subtract', (req, res) => {
|
|
570
|
-
const { key } = req.params;
|
|
571
|
-
const { number } = req.body;
|
|
572
|
-
|
|
573
|
-
if (typeof number !== 'number') {
|
|
574
|
-
return res.status(400).json({ error: 'Number is required' });
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
const result = this.subtract(key, number);
|
|
578
|
-
res.json({ success: true, key, value: result });
|
|
579
|
-
});
|
|
580
|
-
|
|
581
|
-
this.app.use('/api', router);
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
_setupWebSocket() {
|
|
585
|
-
this.io.on('connection', (socket) => {
|
|
586
|
-
console.log(`✅ Client connected: ${socket.id}`);
|
|
587
|
-
this.emit('client:connected', { socketId: socket.id });
|
|
588
|
-
|
|
589
|
-
// Send current data on connection
|
|
590
|
-
socket.emit('data:init', this.data);
|
|
591
|
-
|
|
592
|
-
// Handle client operations
|
|
593
|
-
socket.on('data:set', ({ key, value }) => {
|
|
594
|
-
this.set(key, value);
|
|
595
|
-
});
|
|
596
|
-
|
|
597
|
-
socket.on('data:delete', ({ key }) => {
|
|
598
|
-
this.delete(key);
|
|
599
|
-
});
|
|
600
|
-
|
|
601
|
-
socket.on('data:get', ({ key }, callback) => {
|
|
602
|
-
const value = this.get(key);
|
|
603
|
-
callback({ success: true, value });
|
|
604
|
-
});
|
|
605
|
-
|
|
606
|
-
socket.on('disconnect', () => {
|
|
607
|
-
console.log(`❌ Client disconnected: ${socket.id}`);
|
|
608
|
-
this.emit('client:disconnected', { socketId: socket.id });
|
|
609
|
-
});
|
|
610
|
-
});
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
// ---------------- Backup & Restore ----------------
|
|
614
|
-
async backup(backupPath) {
|
|
615
|
-
await fs.writeFile(backupPath, JSON.stringify(this.data, null, 2), "utf8");
|
|
616
|
-
this.emit("backup", { backupPath });
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
async restore(backupPath) {
|
|
620
|
-
const content = await fs.readFile(backupPath, "utf8");
|
|
621
|
-
this.data = JSON.parse(content);
|
|
622
|
-
await this.save();
|
|
623
|
-
this.emit("restore", { backupPath });
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
// ---------------- Save ----------------
|
|
627
|
-
async save() {
|
|
628
|
-
const tmpPath = `${this.filePath}.tmp`;
|
|
629
|
-
await fs.writeFile(tmpPath, JSON.stringify(this.data, null, 2), "utf8");
|
|
630
|
-
await fs.rename(tmpPath, this.filePath);
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
// ---------------- Internal utilities ----------------
|
|
634
|
-
_getByPath(pathStr) {
|
|
635
|
-
const keys = pathStr.split(".");
|
|
636
|
-
let obj = this.data;
|
|
637
|
-
for (const k of keys) {
|
|
638
|
-
if (obj && Object.prototype.hasOwnProperty.call(obj, k)) {
|
|
639
|
-
obj = obj[k];
|
|
640
|
-
} else {
|
|
641
|
-
return undefined;
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
return obj;
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
_setByPath(pathStr, value) {
|
|
648
|
-
const keys = pathStr.split(".");
|
|
649
|
-
let obj = this.data;
|
|
650
|
-
while (keys.length > 1) {
|
|
651
|
-
const k = keys.shift();
|
|
652
|
-
if (!obj[k] || typeof obj[k] !== "object") obj[k] = {};
|
|
653
|
-
obj = obj[k];
|
|
654
|
-
}
|
|
655
|
-
obj[keys[0]] = value;
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
_deleteByPath(pathStr) {
|
|
659
|
-
const keys = pathStr.split(".");
|
|
660
|
-
let obj = this.data;
|
|
661
|
-
while (keys.length > 1) {
|
|
662
|
-
const k = keys.shift();
|
|
663
|
-
if (!obj[k]) return;
|
|
664
|
-
obj = obj[k];
|
|
665
|
-
}
|
|
666
|
-
delete obj[keys[0]];
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
_getValueByPath(obj, pathStr) {
|
|
670
|
-
const keys = pathStr.split(".");
|
|
671
|
-
let result = obj;
|
|
672
|
-
for (const key of keys) {
|
|
673
|
-
if (result && Object.prototype.hasOwnProperty.call(result, key)) {
|
|
674
|
-
result = result[key];
|
|
675
|
-
} else {
|
|
676
|
-
return undefined;
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
return result;
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
// ---------------- QueryResult Class ----------------
|
|
684
|
-
class QueryResult {
|
|
685
|
-
constructor(results) {
|
|
686
|
-
this.results = results || [];
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
sort(field, direction = 'asc') {
|
|
690
|
-
this.results.sort((a, b) => {
|
|
691
|
-
const aVal = this._getValueByPath(a.value, field);
|
|
692
|
-
const bVal = this._getValueByPath(b.value, field);
|
|
693
|
-
|
|
694
|
-
if (aVal === bVal) return 0;
|
|
695
|
-
|
|
696
|
-
const comparison = aVal > bVal ? 1 : -1;
|
|
697
|
-
return direction === 'desc' ? -comparison : comparison;
|
|
698
|
-
});
|
|
699
|
-
|
|
9
|
+
this.database = new Database(options);
|
|
10
|
+
this.queryEngine = new QueryEngine(this.database);
|
|
11
|
+
this.indexManager = new IndexManager(this.database, options);
|
|
12
|
+
|
|
13
|
+
// Database methods
|
|
14
|
+
this.set = this.database.set.bind(this.database);
|
|
15
|
+
this.get = this.database.get.bind(this.database);
|
|
16
|
+
this.delete = this.database.delete.bind(this.database);
|
|
17
|
+
this.has = this.database.has.bind(this.database);
|
|
18
|
+
this.all = this.database.all.bind(this.database);
|
|
19
|
+
this.clear = this.database.clear.bind(this.database);
|
|
20
|
+
|
|
21
|
+
// 🔥 Query methods
|
|
22
|
+
this.find = this.queryEngine.find.bind(this.queryEngine);
|
|
23
|
+
this.where = this.queryEngine.where.bind(this.queryEngine);
|
|
24
|
+
this.findAll = this.queryEngine.findAll.bind(this.queryEngine);
|
|
25
|
+
this.count = this.queryEngine.count.bind(this.queryEngine);
|
|
26
|
+
this.sum = this.queryEngine.sum.bind(this.queryEngine);
|
|
27
|
+
this.avg = this.queryEngine.avg.bind(this.queryEngine);
|
|
28
|
+
this.min = this.queryEngine.min.bind(this.queryEngine);
|
|
29
|
+
this.max = this.queryEngine.max.bind(this.queryEngine);
|
|
30
|
+
this.groupBy = this.queryEngine.groupBy.bind(this.queryEngine);
|
|
31
|
+
|
|
32
|
+
// 🔥 Index methods
|
|
33
|
+
this.createIndex = this.indexManager.createIndex.bind(this.indexManager);
|
|
34
|
+
this.dropIndex = this.indexManager.dropIndex.bind(this.indexManager);
|
|
35
|
+
this.getIndexes = this.indexManager.getIndexes.bind(this.indexManager);
|
|
36
|
+
|
|
37
|
+
// 🔥 ARRAY & MATH Methods
|
|
38
|
+
this.push = this.database.push?.bind(this.database) || this._fallbackPush.bind(this);
|
|
39
|
+
this.pull = this.database.pull?.bind(this.database) || this._fallbackPull.bind(this);
|
|
40
|
+
this.add = this.database.add?.bind(this.database) || this._fallbackAdd.bind(this);
|
|
41
|
+
this.subtract = this.database.subtract?.bind(this.database) || this._fallbackSubtract.bind(this);
|
|
42
|
+
|
|
43
|
+
// 🔥 BACKUP & RESTORE Methods
|
|
44
|
+
this.backup = this.database.backup?.bind(this.database) || this._fallbackBackup.bind(this);
|
|
45
|
+
this.restore = this.database.restore?.bind(this.database) || this._fallbackRestore.bind(this);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 🔥 FALLBACK Methods
|
|
49
|
+
_fallbackPush(key, value) {
|
|
50
|
+
const array = this.get(key) || [];
|
|
51
|
+
array.push(value);
|
|
52
|
+
this.set(key, array);
|
|
53
|
+
return array.length;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
_fallbackPull(key, value) {
|
|
57
|
+
const array = this.get(key) || [];
|
|
58
|
+
const index = array.indexOf(value);
|
|
59
|
+
if (index > -1) {
|
|
60
|
+
array.splice(index, 1);
|
|
61
|
+
this.set(key, array);
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
_fallbackAdd(key, number) {
|
|
68
|
+
const current = this.get(key) || 0;
|
|
69
|
+
const newValue = current + number;
|
|
70
|
+
this.set(key, newValue);
|
|
71
|
+
return newValue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
_fallbackSubtract(key, number) {
|
|
75
|
+
return this._fallbackAdd(key, -number);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 🔥 BACKUP FALLBACK Methods
|
|
79
|
+
async _fallbackBackup(backupPath = null) {
|
|
80
|
+
const path = backupPath || `./sehawq-backup-${Date.now()}.json`;
|
|
81
|
+
const storage = new Storage(path);
|
|
82
|
+
const data = this.all();
|
|
83
|
+
await storage.write(data);
|
|
84
|
+
return path;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async _fallbackRestore(backupPath) {
|
|
88
|
+
const storage = new Storage(backupPath);
|
|
89
|
+
const data = await storage.read();
|
|
90
|
+
this.clear();
|
|
91
|
+
for (const [key, value] of Object.entries(data)) {
|
|
92
|
+
this.set(key, value);
|
|
93
|
+
}
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async start() {
|
|
98
|
+
await new Promise(resolve => this.database.on('ready', resolve));
|
|
700
99
|
return this;
|
|
701
100
|
}
|
|
702
101
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
return this;
|
|
102
|
+
async stop() {
|
|
103
|
+
await this.database.close();
|
|
706
104
|
}
|
|
707
105
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
return
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
first() {
|
|
718
|
-
return this.results[0];
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
last() {
|
|
722
|
-
return this.results[this.results.length - 1];
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
toArray() {
|
|
726
|
-
return this.results;
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
values() {
|
|
730
|
-
return this.results.map(item => item.value);
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
keys() {
|
|
734
|
-
return this.results.map(item => item.key);
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
filter(filter) {
|
|
738
|
-
this.results = this.results.filter(item => filter(item.value, item.key));
|
|
739
|
-
return this;
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
map(mapper) {
|
|
743
|
-
return this.results.map(item => mapper(item.value, item.key));
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
_getValueByPath(obj, pathStr) {
|
|
747
|
-
const keys = pathStr.split(".");
|
|
748
|
-
let result = obj;
|
|
749
|
-
for (const key of keys) {
|
|
750
|
-
if (result && Object.prototype.hasOwnProperty.call(result, key)) {
|
|
751
|
-
result = result[key];
|
|
752
|
-
} else {
|
|
753
|
-
return undefined;
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
return result;
|
|
106
|
+
// 🔥 STATS Methods
|
|
107
|
+
getStats() {
|
|
108
|
+
return {
|
|
109
|
+
database: this.database.getStats?.(),
|
|
110
|
+
query: this.queryEngine.getStats?.(),
|
|
111
|
+
indexes: this.indexManager.getStats?.()
|
|
112
|
+
};
|
|
757
113
|
}
|
|
758
114
|
}
|
|
759
115
|
|
|
760
|
-
module.exports = SehawqDB;
|
|
116
|
+
module.exports = { SehawqDB };
|