sehawq.db 2.3.0 → 2.4.2

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 +1 -1
  2. package/readme.md +260 -0
  3. package/src/index.js +323 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sehawq.db",
3
- "version": "2.3.0",
3
+ "version": "2.4.2",
4
4
  "description": "Lightweight JSON-based key-value database with namespaces, array & math helpers.",
5
5
  "main": "src/index.js",
6
6
  "keywords": [
package/readme.md CHANGED
@@ -16,6 +16,28 @@ Minimal, dependency-free, and easy-to-use. Perfect for small projects, bots, CLI
16
16
  - **Dot-notation namespace** — Access nested data with `user.123.balance`.
17
17
  - **Sync & Async API** — Choose blocking or non-blocking file operations.
18
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.
19
41
 
20
42
  ### 🔧 Array Helpers
21
43
  - `push(key, value)` — Add an element to an array.
@@ -44,3 +66,241 @@ Hooks into database operations:
44
66
 
45
67
  ```bash
46
68
  npm install sehawq.db
69
+ ```
70
+
71
+ ---
72
+
73
+ ## ⚡ Quick Start (30 seconds)
74
+
75
+ ```javascript
76
+ const db = require('sehawq.db')();
77
+
78
+ // Store data
79
+ db.set('user', 'John Doe');
80
+ db.set('score', 100);
81
+
82
+ // Get data
83
+ console.log(db.get('user')); // John Doe
84
+ console.log(db.get('score')); // 100
85
+
86
+ // That's it! 🎉
87
+ ```
88
+
89
+ ---
90
+
91
+ ## 🔧 Detailed Usage
92
+
93
+ ### Basic Operations
94
+
95
+ ```javascript
96
+ const SehawqDB = require('sehawq.db');
97
+ const db = new SehawqDB({
98
+ path: './mydata.json',
99
+ autoSaveInterval: 5000 // Auto-save every 5 seconds
100
+ });
101
+
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
+ }
111
+
112
+ // Delete data
113
+ db.delete('user.123.balance');
114
+ ```
115
+
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' });
128
+
129
+ console.log(db.get('users')); // [{ id: 2, name: 'Bob' }]
130
+ ```
131
+
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
139
+ ```
140
+
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
164
+ ```
165
+
166
+ ### Aggregation
167
+
168
+ ```javascript
169
+ // Count total users
170
+ const totalUsers = db.count();
171
+
172
+ // Count active users
173
+ const activeCount = db.count(user => user.active);
174
+
175
+ // Calculate average age
176
+ const avgAge = db.avg('age');
177
+
178
+ // Find highest score
179
+ const highestScore = db.max('score');
180
+
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
+ // }
188
+ ```
189
+
190
+ ### Pagination
191
+
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();
199
+
200
+ // Sort and paginate
201
+ const sortedPage = db
202
+ .find()
203
+ .sort('name', 'asc')
204
+ .skip(20)
205
+ .limit(5)
206
+ .values();
207
+ ```
208
+
209
+ ### Event Handling
210
+
211
+ ```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}`);
219
+ });
220
+
221
+ db.on('backup', (data) => {
222
+ console.log(`Backup created: ${data.backupPath}`);
223
+ });
224
+ ```
225
+
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
+ ---
237
+
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
+ ---
295
+
296
+ ## 📄 License
297
+
298
+ MIT License - see [LICENSE](https://github.com/sehawq/sehawq.db/blob/main/LICENSE) file for details.
299
+
300
+ ## 🤝 Contributing
301
+
302
+ Contributions are welcome! Please feel free to submit a Pull Request.
303
+
304
+ ## 🐛 Issues
305
+
306
+ Found a bug? Please report it on [GitHub Issues](https://github.com/sehawq/sehawq.db/issues).
package/src/index.js CHANGED
@@ -113,6 +113,188 @@ class SehawqDB extends EventEmitter {
113
113
  return this.add(key, -number);
114
114
  }
115
115
 
116
+ // ---------------- NEW: Query System ----------------
117
+
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
+ find(filter) {
124
+ const results = [];
125
+
126
+ if (typeof filter === 'function') {
127
+ for (const [key, value] of Object.entries(this.data)) {
128
+ if (filter(value, key)) {
129
+ results.push({ key, value });
130
+ }
131
+ }
132
+ } else {
133
+ // Eğer filter verilmezse tüm dataları döndür
134
+ for (const [key, value] of Object.entries(this.data)) {
135
+ results.push({ key, value });
136
+ }
137
+ }
138
+
139
+ return new QueryResult(results);
140
+ }
141
+
142
+ /**
143
+ * Find first entry that matches the filter
144
+ * @param {Function} filter - Filter function
145
+ * @returns {Object|undefined} First matching item
146
+ */
147
+ findOne(filter) {
148
+ if (typeof filter === 'function') {
149
+ for (const [key, value] of Object.entries(this.data)) {
150
+ if (filter(value, key)) {
151
+ return { key, value };
152
+ }
153
+ }
154
+ }
155
+ return undefined;
156
+ }
157
+
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
+ where(field, operator, value) {
166
+ return this.find((item, key) => {
167
+ const fieldValue = this._getValueByPath(item, field);
168
+
169
+ switch (operator) {
170
+ case '=':
171
+ case '==':
172
+ return fieldValue === value;
173
+ case '!=':
174
+ return fieldValue !== value;
175
+ case '>':
176
+ return fieldValue > value;
177
+ case '<':
178
+ return fieldValue < value;
179
+ case '>=':
180
+ return fieldValue >= value;
181
+ case '<=':
182
+ return fieldValue <= value;
183
+ case 'in':
184
+ return Array.isArray(value) && value.includes(fieldValue);
185
+ case 'contains':
186
+ return typeof fieldValue === 'string' && fieldValue.includes(value);
187
+ case 'startsWith':
188
+ return typeof fieldValue === 'string' && fieldValue.startsWith(value);
189
+ case 'endsWith':
190
+ return typeof fieldValue === 'string' && fieldValue.endsWith(value);
191
+ default:
192
+ return false;
193
+ }
194
+ });
195
+ }
196
+
197
+ // ---------------- NEW: Aggregation System ----------------
198
+
199
+ /**
200
+ * Count total entries
201
+ * @param {Function} [filter] - Optional filter function
202
+ * @returns {number} Count of entries
203
+ */
204
+ count(filter) {
205
+ if (filter) {
206
+ return this.find(filter).count();
207
+ }
208
+ return Object.keys(this.data).length;
209
+ }
210
+
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
+ sum(field, filter) {
218
+ const items = filter ? this.find(filter).toArray() : this.find().toArray();
219
+ return items.reduce((sum, item) => {
220
+ const val = this._getValueByPath(item.value, field);
221
+ return sum + (typeof val === 'number' ? val : 0);
222
+ }, 0);
223
+ }
224
+
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
+ avg(field, filter) {
232
+ const items = filter ? this.find(filter).toArray() : this.find().toArray();
233
+ if (items.length === 0) return 0;
234
+
235
+ const sum = items.reduce((total, item) => {
236
+ const val = this._getValueByPath(item.value, field);
237
+ return total + (typeof val === 'number' ? val : 0);
238
+ }, 0);
239
+
240
+ return sum / items.length;
241
+ }
242
+
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
+ min(field, filter) {
250
+ const items = filter ? this.find(filter).toArray() : this.find().toArray();
251
+ if (items.length === 0) return undefined;
252
+
253
+ return Math.min(...items.map(item => {
254
+ const val = this._getValueByPath(item.value, field);
255
+ return typeof val === 'number' ? val : Infinity;
256
+ }).filter(val => val !== Infinity));
257
+ }
258
+
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
+ max(field, filter) {
266
+ const items = filter ? this.find(filter).toArray() : this.find().toArray();
267
+ if (items.length === 0) return undefined;
268
+
269
+ return Math.max(...items.map(item => {
270
+ const val = this._getValueByPath(item.value, field);
271
+ return typeof val === 'number' ? val : -Infinity;
272
+ }).filter(val => val !== -Infinity));
273
+ }
274
+
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
+ groupBy(field, filter) {
282
+ const items = filter ? this.find(filter).toArray() : this.find().toArray();
283
+ const groups = {};
284
+
285
+ items.forEach(item => {
286
+ const groupKey = this._getValueByPath(item.value, field);
287
+ const key = groupKey !== undefined ? String(groupKey) : 'undefined';
288
+
289
+ if (!groups[key]) {
290
+ groups[key] = [];
291
+ }
292
+ groups[key].push(item);
293
+ });
294
+
295
+ return groups;
296
+ }
297
+
116
298
  // ---------------- Backup & Restore ----------------
117
299
  async backup(backupPath) {
118
300
  await fs.writeFile(backupPath, JSON.stringify(this.data, null, 2), "utf8");
@@ -168,6 +350,146 @@ class SehawqDB extends EventEmitter {
168
350
  }
169
351
  delete obj[keys[0]];
170
352
  }
353
+
354
+ _getValueByPath(obj, pathStr) {
355
+ const keys = pathStr.split(".");
356
+ let result = obj;
357
+ for (const key of keys) {
358
+ if (result && Object.prototype.hasOwnProperty.call(result, key)) {
359
+ result = result[key];
360
+ } else {
361
+ return undefined;
362
+ }
363
+ }
364
+ return result;
365
+ }
366
+ }
367
+
368
+ // ---------------- QueryResult Class (for method chaining) ----------------
369
+ class QueryResult {
370
+ constructor(results) {
371
+ this.results = results || [];
372
+ }
373
+
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
+ sort(field, direction = 'asc') {
381
+ this.results.sort((a, b) => {
382
+ const aVal = this._getValueByPath(a.value, field);
383
+ const bVal = this._getValueByPath(b.value, field);
384
+
385
+ if (aVal === bVal) return 0;
386
+
387
+ const comparison = aVal > bVal ? 1 : -1;
388
+ return direction === 'desc' ? -comparison : comparison;
389
+ });
390
+
391
+ return this;
392
+ }
393
+
394
+ /**
395
+ * Limit number of results
396
+ * @param {number} count - Maximum number of results
397
+ * @returns {QueryResult} Chainable
398
+ */
399
+ limit(count) {
400
+ this.results = this.results.slice(0, count);
401
+ return this;
402
+ }
403
+
404
+ /**
405
+ * Skip number of results
406
+ * @param {number} count - Number of results to skip
407
+ * @returns {QueryResult} Chainable
408
+ */
409
+ skip(count) {
410
+ this.results = this.results.slice(count);
411
+ return this;
412
+ }
413
+
414
+ /**
415
+ * Get count of results
416
+ * @returns {number} Count
417
+ */
418
+ count() {
419
+ return this.results.length;
420
+ }
421
+
422
+ /**
423
+ * Get first result
424
+ * @returns {Object|undefined} First result
425
+ */
426
+ first() {
427
+ return this.results[0];
428
+ }
429
+
430
+ /**
431
+ * Get last result
432
+ * @returns {Object|undefined} Last result
433
+ */
434
+ last() {
435
+ return this.results[this.results.length - 1];
436
+ }
437
+
438
+ /**
439
+ * Convert to array
440
+ * @returns {Array} Results array
441
+ */
442
+ toArray() {
443
+ return this.results;
444
+ }
445
+
446
+ /**
447
+ * Get only values (without keys)
448
+ * @returns {Array} Values array
449
+ */
450
+ values() {
451
+ return this.results.map(item => item.value);
452
+ }
453
+
454
+ /**
455
+ * Get only keys
456
+ * @returns {Array} Keys array
457
+ */
458
+ keys() {
459
+ return this.results.map(item => item.key);
460
+ }
461
+
462
+ /**
463
+ * Apply additional filter
464
+ * @param {Function} filter - Filter function
465
+ * @returns {QueryResult} Chainable
466
+ */
467
+ filter(filter) {
468
+ this.results = this.results.filter(item => filter(item.value, item.key));
469
+ return this;
470
+ }
471
+
472
+ /**
473
+ * Map over results
474
+ * @param {Function} mapper - Map function
475
+ * @returns {Array} Mapped results
476
+ */
477
+ map(mapper) {
478
+ return this.results.map(item => mapper(item.value, item.key));
479
+ }
480
+
481
+ _getValueByPath(obj, pathStr) {
482
+ const keys = pathStr.split(".");
483
+ let result = obj;
484
+ for (const key of keys) {
485
+ if (result && Object.prototype.hasOwnProperty.call(result, key)) {
486
+ result = result[key];
487
+ } else {
488
+ return undefined;
489
+ }
490
+ }
491
+ return result;
492
+ }
171
493
  }
172
494
 
173
- module.exports = SehawqDB;
495
+ module.exports = SehawqDB;