sehawq.db 2.4.2 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.js CHANGED
@@ -1,495 +1,116 @@
1
- const fs = require("fs").promises;
2
- const path = require("path");
3
- const EventEmitter = require("events");
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');
4
6
 
5
- class SehawqDB extends EventEmitter {
6
- /**
7
- * Create a new SehawqDB instance.
8
- * @param {Object} options
9
- * @param {string} [options.path="sehawq.json"] File path for storage.
10
- * @param {number} [options.autoSaveInterval=0] Autosave interval in ms (0 disables autosave).
11
- */
7
+ class SehawqDB {
12
8
  constructor(options = {}) {
13
- super();
14
- this.filePath = path.resolve(options.path || "sehawq.json");
15
- this.autoSaveInterval = options.autoSaveInterval || 0;
16
- this.data = {};
17
-
18
- this._init();
19
-
20
- if (this.autoSaveInterval > 0) {
21
- this._interval = setInterval(() => this.save(), this.autoSaveInterval);
22
- }
23
- }
24
-
25
- async _init() {
26
- try {
27
- await fs.access(this.filePath);
28
- } catch {
29
- await fs.writeFile(this.filePath, JSON.stringify({}), "utf8");
30
- }
31
-
32
- try {
33
- const content = await fs.readFile(this.filePath, "utf8");
34
- this.data = JSON.parse(content);
35
- } catch {
36
- this.data = {};
37
- }
38
- }
39
-
40
- // ---------------- Core methods ----------------
41
- set(key, value) {
42
- this._setByPath(key, value);
43
- this.emit("set", { key, value });
44
- this.save();
45
- return value;
46
- }
47
-
48
- get(key) {
49
- return this._getByPath(key);
50
- }
51
-
52
- delete(key) {
53
- this._deleteByPath(key);
54
- this.emit("delete", { key });
55
- this.save();
56
- }
57
-
58
- has(key) {
59
- return this._getByPath(key) !== undefined;
60
- }
61
-
62
- all() {
63
- return this.data;
64
- }
65
-
66
- clear() {
67
- this.data = {};
68
- this.emit("clear");
69
- this.save();
70
- }
71
-
72
- keys() {
73
- return Object.keys(this.data);
74
- }
75
-
76
- values() {
77
- return Object.values(this.data);
78
- }
79
-
80
- // ---------------- Array helpers ----------------
81
- push(key, value) {
82
- let arr = this._getByPath(key);
83
- if (!Array.isArray(arr)) arr = [];
84
- arr.push(value);
85
- this._setByPath(key, arr);
86
- this.emit("push", { key, value });
87
- this.save();
88
- return arr;
89
- }
90
-
91
- pull(key, value) {
92
- let arr = this._getByPath(key);
93
- if (!Array.isArray(arr)) return [];
94
- arr = arr.filter(v => v !== value);
95
- this._setByPath(key, arr);
96
- this.emit("pull", { key, value });
97
- this.save();
98
- return arr;
99
- }
100
-
101
- // ---------------- Math helpers ----------------
102
- add(key, number) {
103
- let val = this._getByPath(key);
104
- if (typeof val !== "number") val = 0;
105
- val += number;
106
- this._setByPath(key, val);
107
- this.emit("add", { key, number });
108
- this.save();
109
- return val;
110
- }
111
-
112
- subtract(key, number) {
113
- return this.add(key, -number);
114
- }
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;
9
+ this.database = new Database(options);
10
+ this.queryEngine = new QueryEngine(this.database);
11
+ this.indexManager = new IndexManager(this.database, options);
234
12
 
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;
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);
252
20
 
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;
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);
268
31
 
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 = {};
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);
284
36
 
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
- });
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);
294
42
 
295
- return groups;
296
- }
297
-
298
- // ---------------- Backup & Restore ----------------
299
- async backup(backupPath) {
300
- await fs.writeFile(backupPath, JSON.stringify(this.data, null, 2), "utf8");
301
- this.emit("backup", { backupPath });
302
- }
303
-
304
- async restore(backupPath) {
305
- const content = await fs.readFile(backupPath, "utf8");
306
- this.data = JSON.parse(content);
307
- await this.save();
308
- this.emit("restore", { backupPath });
309
- }
310
-
311
- // ---------------- Save ----------------
312
- async save() {
313
- const tmpPath = `${this.filePath}.tmp`;
314
- await fs.writeFile(tmpPath, JSON.stringify(this.data, null, 2), "utf8");
315
- await fs.rename(tmpPath, this.filePath);
316
- }
317
-
318
- // ---------------- Internal utilities ----------------
319
- _getByPath(pathStr) {
320
- const keys = pathStr.split(".");
321
- let obj = this.data;
322
- for (const k of keys) {
323
- if (obj && Object.prototype.hasOwnProperty.call(obj, k)) {
324
- obj = obj[k];
325
- } else {
326
- return undefined;
327
- }
328
- }
329
- return obj;
330
- }
331
-
332
- _setByPath(pathStr, value) {
333
- const keys = pathStr.split(".");
334
- let obj = this.data;
335
- while (keys.length > 1) {
336
- const k = keys.shift();
337
- if (!obj[k] || typeof obj[k] !== "object") obj[k] = {};
338
- obj = obj[k];
339
- }
340
- obj[keys[0]] = value;
341
- }
342
-
343
- _deleteByPath(pathStr) {
344
- const keys = pathStr.split(".");
345
- let obj = this.data;
346
- while (keys.length > 1) {
347
- const k = keys.shift();
348
- if (!obj[k]) return;
349
- obj = obj[k];
350
- }
351
- delete obj[keys[0]];
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
- }
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;
363
63
  }
364
- return result;
365
- }
366
- }
367
-
368
- // ---------------- QueryResult Class (for method chaining) ----------------
369
- class QueryResult {
370
- constructor(results) {
371
- this.results = results || [];
64
+ return false;
372
65
  }
373
66
 
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;
67
+ _fallbackAdd(key, number) {
68
+ const current = this.get(key) || 0;
69
+ const newValue = current + number;
70
+ this.set(key, newValue);
71
+ return newValue;
392
72
  }
393
73
 
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;
74
+ _fallbackSubtract(key, number) {
75
+ return this._fallbackAdd(key, -number);
402
76
  }
403
77
 
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;
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;
412
85
  }
413
86
 
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);
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;
460
95
  }
461
96
 
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));
97
+ async start() {
98
+ await new Promise(resolve => this.database.on('ready', resolve));
469
99
  return this;
470
100
  }
471
101
 
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));
102
+ async stop() {
103
+ await this.database.close();
479
104
  }
480
105
 
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;
106
+ // 🔥 STATS Methods
107
+ getStats() {
108
+ return {
109
+ database: this.database.getStats?.(),
110
+ query: this.queryEngine.getStats?.(),
111
+ indexes: this.indexManager.getStats?.()
112
+ };
492
113
  }
493
114
  }
494
115
 
495
- module.exports = SehawqDB;
116
+ module.exports = { SehawqDB };