sehawq.db 2.4.2 → 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 +172 -237
  3. package/src/index.js +371 -106
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sehawq.db",
3
- "version": "2.4.2",
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,306 +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!
9
11
 
10
12
  ---
11
13
 
12
- ## 🚀 Features
13
-
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.
19
- - **🔥 NEW: Advanced Query System** — Filter, sort, and paginate your data.
20
- - **🔥 NEW: Aggregation Functions** — Calculate sum, average, min, max, and more.
21
- - **🔥 NEW: Method Chaining** — Chain operations for complex queries.
22
-
23
- ### 🔍 Query System
24
- - `find(filter)` — Find all entries matching a filter function.
25
- - `findOne(filter)` — Find the first entry matching a filter.
26
- - `where(field, operator, value)` — Filter by field with operators (`>`, `<`, `>=`, `<=`, `=`, `!=`, `in`, `contains`, `startsWith`, `endsWith`).
27
-
28
- ### 📊 Aggregation Functions
29
- - `count(filter)` — Count entries (with optional filter).
30
- - `sum(field)` — Sum numeric values by field.
31
- - `avg(field)` — Calculate average of numeric values.
32
- - `min(field)` / `max(field)` — Find minimum/maximum values.
33
- - `groupBy(field)` — Group entries by field value.
34
-
35
- ### ⛓️ Method Chaining & Pagination
36
- - `sort(field, direction)` — Sort results by field (`'asc'` or `'desc'`).
37
- - `limit(count)` — Limit number of results.
38
- - `skip(count)` — Skip number of results for pagination.
39
- - `first()` / `last()` — Get first or last result.
40
- - `values()` / `keys()` — Extract values or keys only.
41
-
42
- ### 🔧 Array Helpers
43
- - `push(key, value)` — Add an element to an array.
44
- - `pull(key, value)` — Remove an element from an array.
45
-
46
- ### ➗ Math Helpers
47
- - `add(key, number)` — Increment a numeric value.
48
- - `subtract(key, number)` — Decrement a numeric value.
49
-
50
- ### 💾 Backup & Restore
51
- - `backup(filePath)` — Save a backup of the database.
52
- - `restore(filePath)` — Restore database from a backup.
53
-
54
- ### 📡 Event Emitter
55
- Hooks into database operations:
56
- - `set` — Triggered when data is added or updated.
57
- - `delete` — Triggered when a key is removed.
58
- - `clear` — Triggered when all data is cleared.
59
- - `push` / `pull` — Triggered on array modification.
60
- - `add` — Triggered on numeric increment.
61
- - `backup` / `restore` — Triggered on backup or restore.
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!**
62
21
 
63
22
  ---
64
23
 
65
- ## 📦 Installation
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
66
55
 
67
56
  ```bash
68
- npm install sehawq.db
57
+ npm install sehawq.db express socket.io socket.io-client cors
69
58
  ```
70
59
 
71
60
  ---
72
61
 
73
- ## ⚡ Quick Start (30 seconds)
62
+ ## ⚡ Quick Start (Local Database)
74
63
 
75
64
  ```javascript
76
- const db = require('sehawq.db')();
65
+ const SehawqDB = require('sehawq.db');
66
+ const db = new SehawqDB();
77
67
 
78
- // Store data
79
- db.set('user', 'John Doe');
80
- db.set('score', 100);
68
+ // Basic operations
69
+ db.set('user', { name: 'John', age: 25 });
70
+ console.log(db.get('user')); // { name: 'John', age: 25 }
81
71
 
82
- // Get data
83
- console.log(db.get('user')); // John Doe
84
- console.log(db.get('score')); // 100
72
+ // Query system
73
+ db.set('user1', { name: 'Alice', score: 95 });
74
+ db.set('user2', { name: 'Bob', score: 87 });
85
75
 
86
- // That's it! 🎉
76
+ const topUsers = db.find()
77
+ .sort('score', 'desc')
78
+ .limit(2)
79
+ .values();
87
80
  ```
88
81
 
89
82
  ---
90
83
 
91
- ## 🔧 Detailed Usage
84
+ ## 🌐 REST API Server
92
85
 
93
- ### Basic Operations
86
+ ### Start Server
94
87
 
95
88
  ```javascript
96
89
  const SehawqDB = require('sehawq.db');
90
+
97
91
  const db = new SehawqDB({
98
- path: './mydata.json',
99
- autoSaveInterval: 5000 // Auto-save every 5 seconds
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
100
97
  });
101
98
 
102
- // Set and get data
103
- db.set('user.123.name', 'John Doe');
104
- db.set('user.123.balance', 1000);
105
- console.log(db.get('user.123')); // { name: 'John Doe', balance: 1000 }
106
-
107
- // Check if key exists
108
- if (db.has('user.123')) {
109
- console.log('User exists!');
110
- }
99
+ // Or start manually:
100
+ // await db.startServer(3000);
111
101
 
112
- // Delete data
113
- db.delete('user.123.balance');
102
+ // 🚀 Server is now running on http://localhost:3000
114
103
  ```
115
104
 
116
- ### Array Operations
117
-
118
- ```javascript
119
- // Initialize an array
120
- db.set('users', []);
121
-
122
- // Add items
123
- db.push('users', { id: 1, name: 'Alice' });
124
- db.push('users', { id: 2, name: 'Bob' });
125
-
126
- // Remove items
127
- db.pull('users', { id: 1, name: 'Alice' });
105
+ ### API Endpoints
128
106
 
129
- console.log(db.get('users')); // [{ id: 2, name: 'Bob' }]
107
+ #### Health Check
108
+ ```bash
109
+ GET /api/health
130
110
  ```
131
111
 
132
- ### Math Operations
133
-
134
- ```javascript
135
- db.set('score', 100);
136
- db.add('score', 50); // score = 150
137
- db.subtract('score', 20); // score = 130
138
- console.log(db.get('score')); // 130
112
+ #### Get All Data
113
+ ```bash
114
+ GET /api/data
139
115
  ```
140
116
 
141
- ### Advanced Queries
142
-
143
- ```javascript
144
- // Sample data
145
- db.set('user1', { name: 'Alice', age: 25, active: true, score: 95 });
146
- db.set('user2', { name: 'Bob', age: 30, active: false, score: 87 });
147
- db.set('user3', { name: 'Charlie', age: 22, active: true, score: 92 });
148
-
149
- // Find all active users
150
- const activeUsers = db.find(user => user.active).values();
151
- console.log(activeUsers);
152
-
153
- // Find users older than 24
154
- const olderUsers = db.where('age', '>', 24).values();
155
-
156
- // Complex query with chaining
157
- const topActiveUsers = db
158
- .find(user => user.active)
159
- .sort('score', 'desc')
160
- .limit(2)
161
- .values();
162
-
163
- console.log(topActiveUsers); // Top 2 active users by score
117
+ #### Get by Key
118
+ ```bash
119
+ GET /api/data/:key
164
120
  ```
165
121
 
166
- ### Aggregation
122
+ #### Set Data
123
+ ```bash
124
+ POST /api/data/:key
125
+ Content-Type: application/json
167
126
 
168
- ```javascript
169
- // Count total users
170
- const totalUsers = db.count();
127
+ {
128
+ "value": { "name": "John", "age": 25 }
129
+ }
130
+ ```
171
131
 
172
- // Count active users
173
- const activeCount = db.count(user => user.active);
132
+ #### Update Data
133
+ ```bash
134
+ PUT /api/data/:key
135
+ Content-Type: application/json
174
136
 
175
- // Calculate average age
176
- const avgAge = db.avg('age');
137
+ {
138
+ "value": { "name": "John", "age": 26 }
139
+ }
140
+ ```
177
141
 
178
- // Find highest score
179
- const highestScore = db.max('score');
142
+ #### Delete Data
143
+ ```bash
144
+ DELETE /api/data/:key
145
+ ```
180
146
 
181
- // Group users by active status
182
- const grouped = db.groupBy('active');
183
- console.log(grouped);
184
- // {
185
- // 'true': [{ name: 'Alice', ... }, { name: 'Charlie', ... }],
186
- // 'false': [{ name: 'Bob', ... }]
187
- // }
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
+ }
188
158
  ```
189
159
 
190
- ### Pagination
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
+ ```
191
168
 
192
- ```javascript
193
- // Get users with pagination (page 2, 10 items per page)
194
- const page2Users = db
195
- .find()
196
- .skip(10)
197
- .limit(10)
198
- .values();
169
+ #### Array Operations
170
+ ```bash
171
+ POST /api/array/:key/push
172
+ POST /api/array/:key/pull
173
+ ```
199
174
 
200
- // Sort and paginate
201
- const sortedPage = db
202
- .find()
203
- .sort('name', 'asc')
204
- .skip(20)
205
- .limit(5)
206
- .values();
175
+ #### Math Operations
176
+ ```bash
177
+ POST /api/math/:key/add
178
+ POST /api/math/:key/subtract
207
179
  ```
208
180
 
209
- ### Event Handling
181
+ ### API Authentication
210
182
 
211
183
  ```javascript
212
- // Listen for database events
213
- db.on('set', (data) => {
214
- console.log(`Set: ${data.key} = ${data.value}`);
215
- });
216
-
217
- db.on('delete', (data) => {
218
- console.log(`Deleted: ${data.key}`);
184
+ // Server side
185
+ const db = new SehawqDB({
186
+ apiKey: 'my-secret-key-123'
219
187
  });
220
188
 
221
- db.on('backup', (data) => {
222
- console.log(`Backup created: ${data.backupPath}`);
189
+ // Client side
190
+ fetch('http://localhost:3000/api/data', {
191
+ headers: {
192
+ 'X-API-Key': 'my-secret-key-123'
193
+ }
223
194
  });
224
195
  ```
225
196
 
226
- ### Backup & Restore
227
-
228
- ```javascript
229
- // Create backup
230
- await db.backup('./backup.json');
231
-
232
- // Restore from backup
233
- await db.restore('./backup.json');
234
- ```
235
-
236
197
  ---
237
198
 
238
- ## 📝 Changelog
239
-
240
- ### Changes in 2.4.2 🔥
241
-
242
- - ✨ **Added Query System**
243
- - `find(filter)` — Filter entries with custom functions
244
- - `findOne(filter)` — Find first matching entry
245
- - `where(field, operator, value)` — Field-based filtering with operators
246
- - **Operators**: `>`, `<`, `>=`, `<=`, `=`, `!=`, `in`, `contains`, `startsWith`, `endsWith`
247
-
248
- - ✨ **Added Aggregation Functions**
249
- - `count(filter)` — Count entries with optional filtering
250
- - `sum(field)` — Sum numeric values by field
251
- - `avg(field)` — Calculate average of numeric values
252
- - `min(field)` / `max(field)` — Find minimum/maximum values
253
- - `groupBy(field)` — Group entries by field value
254
-
255
- - ✨ **Added Method Chaining Support**
256
- - New `QueryResult` class enables chaining operations
257
- - `sort(field, direction)` — Sort results ascending or descending
258
- - `limit(count)` / `skip(count)` — Pagination support
259
- - `first()` / `last()` — Get first or last result
260
- - `values()` / `keys()` — Extract values or keys only
261
- - `filter()` — Apply additional filtering
262
- - `map()` — Transform results
263
-
264
- - 🔧 **Enhanced Dot Notation**
265
- - Full support for nested queries and filtering
266
- - Deep object traversal for all query operations
267
-
268
- - 📊 **Advanced Query Examples**
269
- ```javascript
270
- // Complex chained queries
271
- db.find(user => user.active)
272
- .sort('score', 'desc')
273
- .limit(10)
274
- .values();
275
-
276
- // Field-based filtering
277
- db.where('age', '>=', 18)
278
- .where('status', 'in', ['premium', 'gold'])
279
- .count();
280
- ```
281
-
282
- ### Changes in 2.4.2x
283
-
284
- - ✨ Initial release with core features
285
- - 🔧 Basic CRUD operations (`set`, `get`, `delete`, `has`)
286
- - 🔧 Dot notation support for nested data
287
- - 🔧 Array helpers (`push`, `pull`)
288
- - 🔧 Math helpers (`add`, `subtract`)
289
- - 🔧 Auto-save functionality
290
- - 🔧 Event emitter system
291
- - 🔧 Backup & restore functionality
292
- - 🔧 Atomic file operations with temporary files
293
-
294
- ---
199
+ ## Real-time Sync
295
200
 
296
- ## 📄 License
201
+ ### Server Setup
297
202
 
298
- MIT License - see [LICENSE](https://github.com/sehawq/sehawq.db/blob/main/LICENSE) file for details.
203
+ ```javascript
204
+ const db = new SehawqDB({
205
+ enableServer: true,
206
+ enableRealtime: true,
207
+ serverPort: 3000
208
+ });
299
209
 
300
- ## 🤝 Contributing
210
+ // Listen to client events
211
+ db.on('client:connected', ({ socketId }) => {
212
+ console.log('Client connected:', socketId);
213
+ });
301
214
 
302
- Contributions are welcome! Please feel free to submit a Pull Request.
215
+ db.on('client:disconnected', ({ socketId }) => {
216
+ console.log('Client disconnected:', socketId);
217
+ });
218
+ ```
303
219
 
304
- ## 🐛 Issues
220
+ ### Frontend (React Example)
305
221
 
306
- Found a bug? Please report it on [GitHub Issues](https://github.com/sehawq/sehawq.db/issues).
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,13 +201,8 @@ class SehawqDB extends EventEmitter {
113
201
  return this.add(key, -number);
114
202
  }
115
203
 
116
- // ---------------- NEW: Query System ----------------
204
+ // ---------------- Query System ----------------
117
205
 
118
- /**
119
- * Find all entries that match the filter function
120
- * @param {Function} filter - Filter function (item, key) => boolean
121
- * @returns {QueryResult} Chainable query result
122
- */
123
206
  find(filter) {
124
207
  const results = [];
125
208
 
@@ -130,7 +213,6 @@ class SehawqDB extends EventEmitter {
130
213
  }
131
214
  }
132
215
  } else {
133
- // Eğer filter verilmezse tüm dataları döndür
134
216
  for (const [key, value] of Object.entries(this.data)) {
135
217
  results.push({ key, value });
136
218
  }
@@ -139,11 +221,6 @@ class SehawqDB extends EventEmitter {
139
221
  return new QueryResult(results);
140
222
  }
141
223
 
142
- /**
143
- * Find first entry that matches the filter
144
- * @param {Function} filter - Filter function
145
- * @returns {Object|undefined} First matching item
146
- */
147
224
  findOne(filter) {
148
225
  if (typeof filter === 'function') {
149
226
  for (const [key, value] of Object.entries(this.data)) {
@@ -155,13 +232,6 @@ class SehawqDB extends EventEmitter {
155
232
  return undefined;
156
233
  }
157
234
 
158
- /**
159
- * Filter by field value with operators
160
- * @param {string} field - Field name (supports dot notation)
161
- * @param {string} operator - Comparison operator (=, !=, >, <, >=, <=, in, contains)
162
- * @param {*} value - Value to compare
163
- * @returns {QueryResult} Chainable query result
164
- */
165
235
  where(field, operator, value) {
166
236
  return this.find((item, key) => {
167
237
  const fieldValue = this._getValueByPath(item, field);
@@ -194,13 +264,8 @@ class SehawqDB extends EventEmitter {
194
264
  });
195
265
  }
196
266
 
197
- // ---------------- NEW: Aggregation System ----------------
267
+ // ---------------- Aggregation System ----------------
198
268
 
199
- /**
200
- * Count total entries
201
- * @param {Function} [filter] - Optional filter function
202
- * @returns {number} Count of entries
203
- */
204
269
  count(filter) {
205
270
  if (filter) {
206
271
  return this.find(filter).count();
@@ -208,12 +273,6 @@ class SehawqDB extends EventEmitter {
208
273
  return Object.keys(this.data).length;
209
274
  }
210
275
 
211
- /**
212
- * Sum numeric values by field
213
- * @param {string} field - Field name to sum
214
- * @param {Function} [filter] - Optional filter function
215
- * @returns {number} Sum of values
216
- */
217
276
  sum(field, filter) {
218
277
  const items = filter ? this.find(filter).toArray() : this.find().toArray();
219
278
  return items.reduce((sum, item) => {
@@ -222,12 +281,6 @@ class SehawqDB extends EventEmitter {
222
281
  }, 0);
223
282
  }
224
283
 
225
- /**
226
- * Average of numeric values by field
227
- * @param {string} field - Field name to average
228
- * @param {Function} [filter] - Optional filter function
229
- * @returns {number} Average of values
230
- */
231
284
  avg(field, filter) {
232
285
  const items = filter ? this.find(filter).toArray() : this.find().toArray();
233
286
  if (items.length === 0) return 0;
@@ -240,12 +293,6 @@ class SehawqDB extends EventEmitter {
240
293
  return sum / items.length;
241
294
  }
242
295
 
243
- /**
244
- * Minimum value by field
245
- * @param {string} field - Field name
246
- * @param {Function} [filter] - Optional filter function
247
- * @returns {*} Minimum value
248
- */
249
296
  min(field, filter) {
250
297
  const items = filter ? this.find(filter).toArray() : this.find().toArray();
251
298
  if (items.length === 0) return undefined;
@@ -256,12 +303,6 @@ class SehawqDB extends EventEmitter {
256
303
  }).filter(val => val !== Infinity));
257
304
  }
258
305
 
259
- /**
260
- * Maximum value by field
261
- * @param {string} field - Field name
262
- * @param {Function} [filter] - Optional filter function
263
- * @returns {*} Maximum value
264
- */
265
306
  max(field, filter) {
266
307
  const items = filter ? this.find(filter).toArray() : this.find().toArray();
267
308
  if (items.length === 0) return undefined;
@@ -272,12 +313,6 @@ class SehawqDB extends EventEmitter {
272
313
  }).filter(val => val !== -Infinity));
273
314
  }
274
315
 
275
- /**
276
- * Group entries by field value
277
- * @param {string} field - Field name to group by
278
- * @param {Function} [filter] - Optional filter function
279
- * @returns {Object} Grouped results
280
- */
281
316
  groupBy(field, filter) {
282
317
  const items = filter ? this.find(filter).toArray() : this.find().toArray();
283
318
  const groups = {};
@@ -295,6 +330,286 @@ class SehawqDB extends EventEmitter {
295
330
  return groups;
296
331
  }
297
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
+
298
613
  // ---------------- Backup & Restore ----------------
299
614
  async backup(backupPath) {
300
615
  await fs.writeFile(backupPath, JSON.stringify(this.data, null, 2), "utf8");
@@ -365,18 +680,12 @@ class SehawqDB extends EventEmitter {
365
680
  }
366
681
  }
367
682
 
368
- // ---------------- QueryResult Class (for method chaining) ----------------
683
+ // ---------------- QueryResult Class ----------------
369
684
  class QueryResult {
370
685
  constructor(results) {
371
686
  this.results = results || [];
372
687
  }
373
688
 
374
- /**
375
- * Sort results by field
376
- * @param {string} field - Field name to sort by
377
- * @param {string} direction - 'asc' or 'desc'
378
- * @returns {QueryResult} Chainable
379
- */
380
689
  sort(field, direction = 'asc') {
381
690
  this.results.sort((a, b) => {
382
691
  const aVal = this._getValueByPath(a.value, field);
@@ -391,89 +700,45 @@ class QueryResult {
391
700
  return this;
392
701
  }
393
702
 
394
- /**
395
- * Limit number of results
396
- * @param {number} count - Maximum number of results
397
- * @returns {QueryResult} Chainable
398
- */
399
703
  limit(count) {
400
704
  this.results = this.results.slice(0, count);
401
705
  return this;
402
706
  }
403
707
 
404
- /**
405
- * Skip number of results
406
- * @param {number} count - Number of results to skip
407
- * @returns {QueryResult} Chainable
408
- */
409
708
  skip(count) {
410
709
  this.results = this.results.slice(count);
411
710
  return this;
412
711
  }
413
712
 
414
- /**
415
- * Get count of results
416
- * @returns {number} Count
417
- */
418
713
  count() {
419
714
  return this.results.length;
420
715
  }
421
716
 
422
- /**
423
- * Get first result
424
- * @returns {Object|undefined} First result
425
- */
426
717
  first() {
427
718
  return this.results[0];
428
719
  }
429
720
 
430
- /**
431
- * Get last result
432
- * @returns {Object|undefined} Last result
433
- */
434
721
  last() {
435
722
  return this.results[this.results.length - 1];
436
723
  }
437
724
 
438
- /**
439
- * Convert to array
440
- * @returns {Array} Results array
441
- */
442
725
  toArray() {
443
726
  return this.results;
444
727
  }
445
728
 
446
- /**
447
- * Get only values (without keys)
448
- * @returns {Array} Values array
449
- */
450
729
  values() {
451
730
  return this.results.map(item => item.value);
452
731
  }
453
732
 
454
- /**
455
- * Get only keys
456
- * @returns {Array} Keys array
457
- */
458
733
  keys() {
459
734
  return this.results.map(item => item.key);
460
735
  }
461
736
 
462
- /**
463
- * Apply additional filter
464
- * @param {Function} filter - Filter function
465
- * @returns {QueryResult} Chainable
466
- */
467
737
  filter(filter) {
468
738
  this.results = this.results.filter(item => filter(item.value, item.key));
469
739
  return this;
470
740
  }
471
741
 
472
- /**
473
- * Map over results
474
- * @param {Function} mapper - Map function
475
- * @returns {Array} Mapped results
476
- */
477
742
  map(mapper) {
478
743
  return this.results.map(item => mapper(item.value, item.key));
479
744
  }