sehawq.db 2.3.0 → 3.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.
Files changed (3) hide show
  1. package/package.json +7 -2
  2. package/readme.md +223 -28
  3. package/src/index.js +588 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sehawq.db",
3
- "version": "2.3.0",
3
+ "version": "3.0.0",
4
4
  "description": "Lightweight JSON-based key-value database with namespaces, array & math helpers.",
5
5
  "main": "src/index.js",
6
6
  "keywords": [
@@ -11,5 +11,10 @@
11
11
  "lightweight"
12
12
  ],
13
13
  "author": "Omer (sehawq)",
14
- "license": "MIT"
14
+ "license": "MIT",
15
+ "dependencies": {
16
+ "cors": "^2.8.5",
17
+ "express": "^5.1.0",
18
+ "socket.io": "^4.8.1"
19
+ }
15
20
  }
package/readme.md CHANGED
@@ -1,46 +1,241 @@
1
- # sehawq.db
1
+ # sehawq.db 🚀
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/sehawq.db.svg)](https://www.npmjs.com/package/sehawq.db)
4
4
  [![npm downloads](https://img.shields.io/npm/dt/sehawq.db.svg)](https://www.npmjs.com/package/sehawq.db)
5
5
  [![license](https://img.shields.io/github/license/sehawq/sehawq.db.svg)](LICENSE)
6
6
 
7
- **Lightweight JSON-based key-value database for Node.js**
8
- Minimal, dependency-free, and easy-to-use. Perfect for small projects, bots, CLIs, and prototyping.
7
+ **The most powerful JSON-based database for Node.js**
8
+ Local database + REST API + Real-time Sync = **Firebase Alternative in One Package!**
9
+
10
+ Perfect for: APIs, Real-time apps, Chat apps, Collaborative tools, Prototypes, and Production!
11
+
12
+ ---
13
+
14
+ ## 🎯 Why SehawqDB?
15
+
16
+ ❌ **Firebase**: Expensive, vendor lock-in, complex pricing
17
+ ❌ **MongoDB**: Heavy, requires separate server setup
18
+ ❌ **Redis**: In-memory only, no persistence by default
19
+
20
+ ✅ **SehawqDB**: Lightweight, local-first, REST API built-in, real-time sync, **ZERO configuration!**
9
21
 
10
22
  ---
11
23
 
12
- ## 🚀 Features
24
+ ## 🔥 Features
25
+
26
+ ### 💾 Core Database
27
+ - **JSON-based storage** — Simple, readable, git-friendly
28
+ - **Query System** — MongoDB-like queries with `find()`, `where()`, filtering
29
+ - **Aggregations** — `sum()`, `avg()`, `min()`, `max()`, `groupBy()`
30
+ - **Method Chaining** — Fluent API for complex queries
31
+ - **Dot notation** — Access nested data easily
32
+
33
+ ### 🌐 Built-in REST API (NEW!)
34
+ - **Zero configuration** — Call `.startServer()` and you're live!
35
+ - **Full CRUD** — GET, POST, PUT, DELETE endpoints
36
+ - **Query API** — Filter, sort, paginate via HTTP
37
+ - **Authentication** — Optional API key protection
38
+
39
+ ### ⚡ Real-time Sync (NEW!)
40
+ - **WebSocket integration** — Powered by Socket.io
41
+ - **Live updates** — All clients sync instantly
42
+ - **Event-driven** — Listen to data changes in real-time
43
+ - **Cross-platform** — Works with React, Vue, Angular, mobile apps
44
+
45
+ ### 🔧 Developer Experience
46
+ - **TypeScript ready** — Full type definitions
47
+ - **Events** — Hook into all database operations
48
+ - **Backup & Restore** — Easy data management
49
+ - **Auto-save** — Configurable intervals
50
+ - **Array & Math helpers** — Built-in utilities
51
+
52
+ ---
53
+
54
+ ## 📦 Installation
55
+
56
+ ```bash
57
+ npm install sehawq.db express socket.io socket.io-client cors
58
+ ```
59
+
60
+ ---
13
61
 
14
- - **JSON-based lightweight storage** No extra dependencies, works with pure Node.js.
15
- - **Key-Value structure** — Simple `set`, `get`, `delete` logic.
16
- - **Dot-notation namespace** — Access nested data with `user.123.balance`.
17
- - **Sync & Async API** — Choose blocking or non-blocking file operations.
18
- - **Auto-save** — Writes changes to disk at regular intervals.
62
+ ## Quick Start (Local Database)
19
63
 
20
- ### 🔧 Array Helpers
21
- - `push(key, value)` — Add an element to an array.
22
- - `pull(key, value)` Remove an element from an array.
64
+ ```javascript
65
+ const SehawqDB = require('sehawq.db');
66
+ const db = new SehawqDB();
23
67
 
24
- ### Math Helpers
25
- - `add(key, number)` Increment a numeric value.
26
- - `subtract(key, number)` Decrement a numeric value.
68
+ // Basic operations
69
+ db.set('user', { name: 'John', age: 25 });
70
+ console.log(db.get('user')); // { name: 'John', age: 25 }
27
71
 
28
- ### 💾 Backup & Restore
29
- - `backup(filePath)` Save a backup of the database.
30
- - `restore(filePath)` Restore database from a backup.
72
+ // Query system
73
+ db.set('user1', { name: 'Alice', score: 95 });
74
+ db.set('user2', { name: 'Bob', score: 87 });
31
75
 
32
- ### 📡 Event Emitter
33
- Hooks into database operations:
34
- - `set` — Triggered when data is added or updated.
35
- - `delete` — Triggered when a key is removed.
36
- - `clear` — Triggered when all data is cleared.
37
- - `push` / `pull` — Triggered on array modification.
38
- - `add` — Triggered on numeric increment.
39
- - `backup` / `restore` — Triggered on backup or restore.
76
+ const topUsers = db.find()
77
+ .sort('score', 'desc')
78
+ .limit(2)
79
+ .values();
80
+ ```
40
81
 
41
82
  ---
42
83
 
43
- ## 📦 Installation
84
+ ## 🌐 REST API Server
85
+
86
+ ### Start Server
87
+
88
+ ```javascript
89
+ const SehawqDB = require('sehawq.db');
90
+
91
+ const db = new SehawqDB({
92
+ path: './database.json',
93
+ enableServer: true, // Enable REST API
94
+ serverPort: 3000,
95
+ enableRealtime: true, // Enable WebSocket
96
+ apiKey: 'your-secret-key' // Optional authentication
97
+ });
98
+
99
+ // Or start manually:
100
+ // await db.startServer(3000);
101
+
102
+ // 🚀 Server is now running on http://localhost:3000
103
+ ```
104
+
105
+ ### API Endpoints
44
106
 
107
+ #### Health Check
45
108
  ```bash
46
- npm install sehawq.db
109
+ GET /api/health
110
+ ```
111
+
112
+ #### Get All Data
113
+ ```bash
114
+ GET /api/data
115
+ ```
116
+
117
+ #### Get by Key
118
+ ```bash
119
+ GET /api/data/:key
120
+ ```
121
+
122
+ #### Set Data
123
+ ```bash
124
+ POST /api/data/:key
125
+ Content-Type: application/json
126
+
127
+ {
128
+ "value": { "name": "John", "age": 25 }
129
+ }
130
+ ```
131
+
132
+ #### Update Data
133
+ ```bash
134
+ PUT /api/data/:key
135
+ Content-Type: application/json
136
+
137
+ {
138
+ "value": { "name": "John", "age": 26 }
139
+ }
140
+ ```
141
+
142
+ #### Delete Data
143
+ ```bash
144
+ DELETE /api/data/:key
145
+ ```
146
+
147
+ #### Query with Filters
148
+ ```bash
149
+ POST /api/query
150
+ Content-Type: application/json
151
+
152
+ {
153
+ "filter": {},
154
+ "sort": { "field": "age", "direction": "desc" },
155
+ "limit": 10,
156
+ "skip": 0
157
+ }
158
+ ```
159
+
160
+ #### Aggregations
161
+ ```bash
162
+ GET /api/aggregate/count
163
+ GET /api/aggregate/sum?field=score
164
+ GET /api/aggregate/avg?field=age
165
+ GET /api/aggregate/min?field=price
166
+ GET /api/aggregate/max?field=rating
167
+ ```
168
+
169
+ #### Array Operations
170
+ ```bash
171
+ POST /api/array/:key/push
172
+ POST /api/array/:key/pull
173
+ ```
174
+
175
+ #### Math Operations
176
+ ```bash
177
+ POST /api/math/:key/add
178
+ POST /api/math/:key/subtract
179
+ ```
180
+
181
+ ### API Authentication
182
+
183
+ ```javascript
184
+ // Server side
185
+ const db = new SehawqDB({
186
+ apiKey: 'my-secret-key-123'
187
+ });
188
+
189
+ // Client side
190
+ fetch('http://localhost:3000/api/data', {
191
+ headers: {
192
+ 'X-API-Key': 'my-secret-key-123'
193
+ }
194
+ });
195
+ ```
196
+
197
+ ---
198
+
199
+ ## ⚡ Real-time Sync
200
+
201
+ ### Server Setup
202
+
203
+ ```javascript
204
+ const db = new SehawqDB({
205
+ enableServer: true,
206
+ enableRealtime: true,
207
+ serverPort: 3000
208
+ });
209
+
210
+ // Listen to client events
211
+ db.on('client:connected', ({ socketId }) => {
212
+ console.log('Client connected:', socketId);
213
+ });
214
+
215
+ db.on('client:disconnected', ({ socketId }) => {
216
+ console.log('Client disconnected:', socketId);
217
+ });
218
+ ```
219
+
220
+ ### Frontend (React Example)
221
+
222
+ ```javascript
223
+ import { io } from 'socket.io-client';
224
+ import { useEffect, useState } from 'react';
225
+
226
+ function App() {
227
+ const [data, setData] = useState({});
228
+ const socket = io('http://localhost:3000');
229
+
230
+ useEffect(() => {
231
+ // Receive initial data
232
+ socket.on('data:init', (initialData) => {
233
+ setData(initialData);
234
+ });
235
+
236
+ // Listen to real-time changes
237
+ socket.on('data:changed', ({ action, key, value }) => {
238
+ console.log(`Data ${action}:`, key, value);
239
+
240
+ if (action === 'set') {
241
+ setData(prev => ({ ...prev, [key]:
package/src/index.js CHANGED
@@ -1,6 +1,10 @@
1
1
  const fs = require("fs").promises;
2
2
  const path = require("path");
3
3
  const EventEmitter = require("events");
4
+ const http = require("http");
5
+ const express = require("express");
6
+ const { Server } = require("socket.io");
7
+ const cors = require("cors");
4
8
 
5
9
  class SehawqDB extends EventEmitter {
6
10
  /**
@@ -8,18 +12,38 @@ class SehawqDB extends EventEmitter {
8
12
  * @param {Object} options
9
13
  * @param {string} [options.path="sehawq.json"] File path for storage.
10
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
11
19
  */
12
20
  constructor(options = {}) {
13
21
  super();
14
22
  this.filePath = path.resolve(options.path || "sehawq.json");
15
23
  this.autoSaveInterval = options.autoSaveInterval || 0;
16
24
  this.data = {};
25
+
26
+ // Server options
27
+ this.enableServer = options.enableServer || false;
28
+ this.serverPort = options.serverPort || 3000;
29
+ this.enableRealtime = options.enableRealtime !== false;
30
+ this.apiKey = options.apiKey || null;
31
+
32
+ // Server instances
33
+ this.app = null;
34
+ this.server = null;
35
+ this.io = null;
36
+ this.isServerRunning = false;
17
37
 
18
38
  this._init();
19
39
 
20
40
  if (this.autoSaveInterval > 0) {
21
41
  this._interval = setInterval(() => this.save(), this.autoSaveInterval);
22
42
  }
43
+
44
+ if (this.enableServer) {
45
+ this.startServer(this.serverPort);
46
+ }
23
47
  }
24
48
 
25
49
  async _init() {
@@ -41,6 +65,17 @@ class SehawqDB extends EventEmitter {
41
65
  set(key, value) {
42
66
  this._setByPath(key, value);
43
67
  this.emit("set", { key, value });
68
+
69
+ // Real-time broadcast
70
+ if (this.io) {
71
+ this.io.emit("data:changed", {
72
+ action: "set",
73
+ key,
74
+ value,
75
+ timestamp: Date.now()
76
+ });
77
+ }
78
+
44
79
  this.save();
45
80
  return value;
46
81
  }
@@ -52,6 +87,16 @@ class SehawqDB extends EventEmitter {
52
87
  delete(key) {
53
88
  this._deleteByPath(key);
54
89
  this.emit("delete", { key });
90
+
91
+ // Real-time broadcast
92
+ if (this.io) {
93
+ this.io.emit("data:changed", {
94
+ action: "delete",
95
+ key,
96
+ timestamp: Date.now()
97
+ });
98
+ }
99
+
55
100
  this.save();
56
101
  }
57
102
 
@@ -66,6 +111,15 @@ class SehawqDB extends EventEmitter {
66
111
  clear() {
67
112
  this.data = {};
68
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
+
69
123
  this.save();
70
124
  }
71
125
 
@@ -84,6 +138,17 @@ class SehawqDB extends EventEmitter {
84
138
  arr.push(value);
85
139
  this._setByPath(key, arr);
86
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
+
87
152
  this.save();
88
153
  return arr;
89
154
  }
@@ -94,6 +159,17 @@ class SehawqDB extends EventEmitter {
94
159
  arr = arr.filter(v => v !== value);
95
160
  this._setByPath(key, arr);
96
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
+
97
173
  this.save();
98
174
  return arr;
99
175
  }
@@ -105,6 +181,18 @@ class SehawqDB extends EventEmitter {
105
181
  val += number;
106
182
  this._setByPath(key, val);
107
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
+
108
196
  this.save();
109
197
  return val;
110
198
  }
@@ -113,6 +201,415 @@ class SehawqDB extends EventEmitter {
113
201
  return this.add(key, -number);
114
202
  }
115
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
+
116
613
  // ---------------- Backup & Restore ----------------
117
614
  async backup(backupPath) {
118
615
  await fs.writeFile(backupPath, JSON.stringify(this.data, null, 2), "utf8");
@@ -168,6 +665,96 @@ class SehawqDB extends EventEmitter {
168
665
  }
169
666
  delete obj[keys[0]];
170
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
+
700
+ return this;
701
+ }
702
+
703
+ limit(count) {
704
+ this.results = this.results.slice(0, count);
705
+ return this;
706
+ }
707
+
708
+ skip(count) {
709
+ this.results = this.results.slice(count);
710
+ return this;
711
+ }
712
+
713
+ count() {
714
+ return this.results.length;
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;
757
+ }
171
758
  }
172
759
 
173
- module.exports = SehawqDB;
760
+ module.exports = SehawqDB;