sehawq.db 4.0.3 → 4.0.5

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.
@@ -1,448 +1,448 @@
1
- /**
2
- * Query Engine - Makes data searching fast and intuitive
3
- *
4
- * Went from simple filters to a mini-query language
5
- * Because scanning everything is for beginners 😎
6
- */
7
-
8
- const { performance } = require('perf_hooks');
9
-
10
- class QueryEngine {
11
- constructor(database) {
12
- this.db = database;
13
- this.stats = {
14
- queries: 0,
15
- fullScans: 0,
16
- indexScans: 0,
17
- avgQueryTime: 0,
18
- queryTimes: []
19
- };
20
-
21
- // Query operators - because who remembers syntax?
22
- this.operators = {
23
- '=': (a, b) => a === b,
24
- '!=': (a, b) => a !== b,
25
- '>': (a, b) => a > b,
26
- '<': (a, b) => a < b,
27
- '>=': (a, b) => a >= b,
28
- '<=': (a, b) => a <= b,
29
- 'in': (a, b) => Array.isArray(b) && b.includes(a),
30
- 'contains': (a, b) => String(a).includes(String(b)),
31
- 'startsWith': (a, b) => String(a).startsWith(String(b)),
32
- 'endsWith': (a, b) => String(a).endsWith(String(b)),
33
- 'matches': (a, b) => new RegExp(b).test(String(a))
34
- };
35
-
36
- // Cache for compiled filter functions
37
- this.filterCache = new Map();
38
- this.cacheHits = 0;
39
- this.cacheMisses = 0;
40
- }
41
-
42
- /**
43
- * Find records using a filter function
44
- * The OG method - simple but powerful
45
- */
46
- find(filterFn, options = {}) {
47
- const startTime = performance.now();
48
- this.stats.queries++;
49
-
50
- const results = [];
51
- const data = this.db.data;
52
-
53
- // Use index if available and applicable
54
- if (options.useIndex !== false && this._canUseIndex(filterFn)) {
55
- results.push(...this._findWithIndex(filterFn));
56
- this.stats.indexScans++;
57
- } else {
58
- // Full table scan - reliable but slower
59
- for (const [key, value] of data) {
60
- if (filterFn(value, key)) {
61
- results.push({ key, value });
62
- }
63
- }
64
- this.stats.fullScans++;
65
- }
66
-
67
- const queryTime = performance.now() - startTime;
68
- this._recordQueryTime(queryTime);
69
-
70
- return new QueryResult(results, this, {
71
- queryTime,
72
- usedIndex: this.stats.indexScans > 0
73
- });
74
- }
75
-
76
- /**
77
- * MongoDB-style where queries
78
- * Because sometimes you want to feel professional
79
- */
80
- where(field, operator, value) {
81
- const filterFn = this._compileWhereClause(field, operator, value);
82
- return this.find(filterFn, { useIndex: true });
83
- }
84
-
85
- /**
86
- * Compile where clause into efficient filter function
87
- * With caching because compiling is expensive
88
- */
89
- _compileWhereClause(field, operator, value) {
90
- const cacheKey = `${field}|${operator}|${JSON.stringify(value)}`;
91
-
92
- // Check cache first
93
- if (this.filterCache.has(cacheKey)) {
94
- this.cacheHits++;
95
- return this.filterCache.get(cacheKey);
96
- }
97
-
98
- this.cacheMisses++;
99
-
100
- // Get the operator function
101
- const opFunc = this.operators[operator];
102
- if (!opFunc) {
103
- throw new Error(`Unknown operator: ${operator}. Available: ${Object.keys(this.operators).join(', ')}`);
104
- }
105
-
106
- // Compile the filter function
107
- let filterFn;
108
-
109
- if (field.includes('.')) {
110
- // Dot notation - user.profile.name
111
- const fieldParts = field.split('.');
112
- filterFn = (item) => {
113
- let fieldValue = item;
114
- for (const part of fieldParts) {
115
- fieldValue = fieldValue?.[part];
116
- if (fieldValue === undefined) break;
117
- }
118
- return opFunc(fieldValue, value);
119
- };
120
- } else {
121
- // Simple field access
122
- filterFn = (item) => opFunc(item[field], value);
123
- }
124
-
125
- // Cache the compiled function
126
- if (this.filterCache.size < 1000) { // Prevent memory leaks
127
- this.filterCache.set(cacheKey, filterFn);
128
- }
129
-
130
- return filterFn;
131
- }
132
-
133
- /**
134
- * Check if we can use indexes for this query
135
- */
136
- _canUseIndex(filterFn) {
137
- // TODO: Implement index detection logic
138
- // For now, we'll use full scans until IndexManager is ready
139
- return false;
140
- }
141
-
142
- /**
143
- * Use indexes for faster queries
144
- */
145
- _findWithIndex(filterFn) {
146
- // TODO: Implement index-based search
147
- // This will make large datasets queryable in milliseconds
148
- return [];
149
- }
150
-
151
- /**
152
- * Aggregation functions - for when you need answers
153
- */
154
-
155
- count(filterFn = null) {
156
- if (filterFn) {
157
- return this.find(filterFn).count();
158
- }
159
-
160
- // Fast path for total count
161
- return this.db.data.size;
162
- }
163
-
164
- // sum() function optimized to avoid multiple iterations
165
- sum(field, filterFn = null) {
166
- const results = filterFn ? this.find(filterFn) : this.findAll();
167
- const resultsArray = results.toArray();
168
- let total = 0;
169
-
170
- for (const item of resultsArray) {
171
- const value = this._getFieldValue(item.value, field);
172
- if (typeof value === 'number') {
173
- total += value;
174
- }
175
- }
176
- return total;
177
- }
178
-
179
- avg(field, filterFn = null) {
180
- const results = filterFn ? this.find(filterFn) : this.findAll();
181
- let total = 0;
182
- let count = 0;
183
-
184
- // results.toArray() used to avoid multiple iterations
185
- const resultsArray = results.toArray();
186
-
187
- for (const item of resultsArray) {
188
- const value = this._getFieldValue(item.value, field);
189
- if (typeof value === 'number') {
190
- total += value;
191
- count++;
192
- }
193
- }
194
-
195
- return count > 0 ? total / count : 0;
196
- }
197
-
198
- min(field, filterFn = null) {
199
- const results = filterFn ? this.find(filterFn) : this.findAll();
200
- const resultsArray = results.toArray();
201
- let min = Infinity;
202
-
203
- for (const item of resultsArray) {
204
- const value = this._getFieldValue(item.value, field);
205
- if (typeof value === 'number' && value < min) {
206
- min = value;
207
- }
208
- }
209
- return min !== Infinity ? min : null;
210
- }
211
-
212
- max(field, filterFn = null) {
213
- const results = filterFn ? this.find(filterFn) : this.findAll();
214
- const resultsArray = results.toArray();
215
- let max = -Infinity;
216
-
217
- for (const item of resultsArray) {
218
- const value = this._getFieldValue(item.value, field);
219
- if (typeof value === 'number' && value > max) {
220
- max = value;
221
- }
222
- }
223
- return max !== -Infinity ? max : null;
224
- }
225
-
226
- /**
227
- * Get all records as QueryResult
228
- */
229
- findAll() {
230
- const results = [];
231
- for (const [key, value] of this.db.data) {
232
- results.push({ key, value });
233
- }
234
- return new QueryResult(results, this);
235
- }
236
-
237
- /**
238
- * Get nested field value using dot notation
239
- */
240
- _getFieldValue(obj, fieldPath) {
241
- if (!fieldPath.includes('.')) {
242
- return obj[fieldPath];
243
- }
244
-
245
- const parts = fieldPath.split('.');
246
- let value = obj;
247
-
248
- for (const part of parts) {
249
- value = value?.[part];
250
- if (value === undefined) break;
251
- }
252
-
253
- return value;
254
- }
255
-
256
- /**
257
- * Group by field - SQL-like power
258
- */
259
- groupBy(field, aggregateFn = null) {
260
- const groups = new Map();
261
-
262
- for (const [key, value] of this.db.data) {
263
- const groupKey = this._getFieldValue(value, field);
264
-
265
- if (!groups.has(groupKey)) {
266
- groups.set(groupKey, []);
267
- }
268
-
269
- groups.get(groupKey).push({ key, value });
270
- }
271
-
272
- // Apply aggregation if provided
273
- if (aggregateFn) {
274
- const result = {};
275
- for (const [groupKey, items] of groups) {
276
- result[groupKey] = aggregateFn(items);
277
- }
278
- return result;
279
- }
280
-
281
- return Object.fromEntries(groups);
282
- }
283
-
284
- /**
285
- * Performance tracking
286
- */
287
- _recordQueryTime(queryTime) {
288
- this.stats.queryTimes.push(queryTime);
289
-
290
- // Keep only last 100 times
291
- if (this.stats.queryTimes.length > 100) {
292
- this.stats.queryTimes.shift();
293
- }
294
-
295
- this.stats.avgQueryTime = this.stats.queryTimes.reduce((a, b) => a + b, 0) / this.stats.queryTimes.length;
296
- }
297
-
298
- /**
299
- * Get query statistics
300
- */
301
- getStats() {
302
- return {
303
- ...this.stats,
304
- cacheHitRate: this.cacheHits + this.cacheMisses > 0
305
- ? ((this.cacheHits / (this.cacheHits + this.cacheMisses)) * 100).toFixed(2) + '%'
306
- : '0%',
307
- filterCacheSize: this.filterCache.size
308
- };
309
- }
310
-
311
- /**
312
- * Clear filter cache
313
- */
314
- clearCache() {
315
- this.filterCache.clear();
316
- this.cacheHits = 0;
317
- this.cacheMisses = 0;
318
- }
319
- }
320
-
321
- /**
322
- * QueryResult - Enables method chaining
323
- * Because .find().where().sort().limit() looks cool 😎
324
- */
325
- class QueryResult {
326
- constructor(results, queryEngine, meta = {}) {
327
- this.results = results;
328
- this.queryEngine = queryEngine;
329
- this.meta = meta;
330
- }
331
-
332
- /**
333
- * Sort results by field
334
- */
335
- sort(field, direction = 'asc') {
336
- const sorted = [...this.results].sort((a, b) => {
337
- const aVal = this.queryEngine._getFieldValue(a.value, field);
338
- const bVal = this.queryEngine._getFieldValue(b.value, field);
339
-
340
- if (aVal === bVal) return 0;
341
-
342
- if (direction === 'asc') {
343
- return aVal < bVal ? -1 : 1;
344
- } else {
345
- return aVal > bVal ? -1 : 1;
346
- }
347
- });
348
-
349
- return new QueryResult(sorted, this.queryEngine, this.meta);
350
- }
351
-
352
- /**
353
- * Limit number of results
354
- */
355
- limit(count) {
356
- const limited = this.results.slice(0, count);
357
- return new QueryResult(limited, this.queryEngine, this.meta);
358
- }
359
-
360
- /**
361
- * Skip number of results
362
- */
363
- skip(count) {
364
- const skipped = this.results.slice(count);
365
- return new QueryResult(skipped, this.queryEngine, this.meta);
366
- }
367
-
368
- /**
369
- * Get first result
370
- */
371
- first() {
372
- return this.results[0] || null;
373
- }
374
-
375
- /**
376
- * Get last result
377
- */
378
- last() {
379
- return this.results[this.results.length - 1] || null;
380
- }
381
-
382
- /**
383
- * Get result count
384
- */
385
- count() {
386
- return this.results.length;
387
- }
388
-
389
- /**
390
- * Get only values
391
- */
392
- values() {
393
- return this.results.map(item => item.value);
394
- }
395
-
396
- /**
397
- * Get only keys
398
- */
399
- keys() {
400
- return this.results.map(item => item.key);
401
- }
402
-
403
- /**
404
- * Convert to array
405
- */
406
- toArray() {
407
- return this.results;
408
- }
409
-
410
- /**
411
- * Get query performance info
412
- */
413
- getMeta() {
414
- return this.meta;
415
- }
416
-
417
- /**
418
- * Execute another query on these results
419
- */
420
- find(filterFn) {
421
- const filtered = this.results.filter(item => filterFn(item.value, item.key));
422
- return new QueryResult(filtered, this.queryEngine, this.meta);
423
- }
424
-
425
- /**
426
- * Map over results
427
- */
428
- map(fn) {
429
- return this.results.map((item, index) => fn(item.value, item.key, index));
430
- }
431
-
432
- /**
433
- * Filter results
434
- */
435
- filter(fn) {
436
- const filtered = this.results.filter((item, index) => fn(item.value, item.key, index));
437
- return new QueryResult(filtered, this.queryEngine, this.meta);
438
- }
439
-
440
- /**
441
- * ForEach loop
442
- */
443
- forEach(fn) {
444
- this.results.forEach((item, index) => fn(item.value, item.key, index));
445
- }
446
- }
447
-
1
+ /**
2
+ * Query Engine - Makes data searching fast and intuitive
3
+ *
4
+ * Went from simple filters to a mini-query language
5
+ * Because scanning everything is for beginners 😎
6
+ */
7
+
8
+ const { performance } = require('perf_hooks');
9
+
10
+ class QueryEngine {
11
+ constructor(database) {
12
+ this.db = database;
13
+ this.stats = {
14
+ queries: 0,
15
+ fullScans: 0,
16
+ indexScans: 0,
17
+ avgQueryTime: 0,
18
+ queryTimes: []
19
+ };
20
+
21
+ // Query operators - because who remembers syntax?
22
+ this.operators = {
23
+ '=': (a, b) => a === b,
24
+ '!=': (a, b) => a !== b,
25
+ '>': (a, b) => a > b,
26
+ '<': (a, b) => a < b,
27
+ '>=': (a, b) => a >= b,
28
+ '<=': (a, b) => a <= b,
29
+ 'in': (a, b) => Array.isArray(b) && b.includes(a),
30
+ 'contains': (a, b) => String(a).includes(String(b)),
31
+ 'startsWith': (a, b) => String(a).startsWith(String(b)),
32
+ 'endsWith': (a, b) => String(a).endsWith(String(b)),
33
+ 'matches': (a, b) => new RegExp(b).test(String(a))
34
+ };
35
+
36
+ // Cache for compiled filter functions
37
+ this.filterCache = new Map();
38
+ this.cacheHits = 0;
39
+ this.cacheMisses = 0;
40
+ }
41
+
42
+ /**
43
+ * Find records using a filter function
44
+ * The OG method - simple but powerful
45
+ */
46
+ find(filterFn, options = {}) {
47
+ const startTime = performance.now();
48
+ this.stats.queries++;
49
+
50
+ const results = [];
51
+ const data = this.db.data;
52
+
53
+ // Use index if available and applicable
54
+ if (options.useIndex !== false && this._canUseIndex(filterFn)) {
55
+ results.push(...this._findWithIndex(filterFn));
56
+ this.stats.indexScans++;
57
+ } else {
58
+ // Full table scan - reliable but slower
59
+ for (const [key, value] of data) {
60
+ if (filterFn(value, key)) {
61
+ results.push({ key, value });
62
+ }
63
+ }
64
+ this.stats.fullScans++;
65
+ }
66
+
67
+ const queryTime = performance.now() - startTime;
68
+ this._recordQueryTime(queryTime);
69
+
70
+ return new QueryResult(results, this, {
71
+ queryTime,
72
+ usedIndex: this.stats.indexScans > 0
73
+ });
74
+ }
75
+
76
+ /**
77
+ * MongoDB-style where queries
78
+ * Because sometimes you want to feel professional
79
+ */
80
+ where(field, operator, value) {
81
+ const filterFn = this._compileWhereClause(field, operator, value);
82
+ return this.find(filterFn, { useIndex: true });
83
+ }
84
+
85
+ /**
86
+ * Compile where clause into efficient filter function
87
+ * With caching because compiling is expensive
88
+ */
89
+ _compileWhereClause(field, operator, value) {
90
+ const cacheKey = `${field}|${operator}|${JSON.stringify(value)}`;
91
+
92
+ // Check cache first
93
+ if (this.filterCache.has(cacheKey)) {
94
+ this.cacheHits++;
95
+ return this.filterCache.get(cacheKey);
96
+ }
97
+
98
+ this.cacheMisses++;
99
+
100
+ // Get the operator function
101
+ const opFunc = this.operators[operator];
102
+ if (!opFunc) {
103
+ throw new Error(`Unknown operator: ${operator}. Available: ${Object.keys(this.operators).join(', ')}`);
104
+ }
105
+
106
+ // Compile the filter function
107
+ let filterFn;
108
+
109
+ if (field.includes('.')) {
110
+ // Dot notation - user.profile.name
111
+ const fieldParts = field.split('.');
112
+ filterFn = (item) => {
113
+ let fieldValue = item;
114
+ for (const part of fieldParts) {
115
+ fieldValue = fieldValue?.[part];
116
+ if (fieldValue === undefined) break;
117
+ }
118
+ return opFunc(fieldValue, value);
119
+ };
120
+ } else {
121
+ // Simple field access
122
+ filterFn = (item) => opFunc(item[field], value);
123
+ }
124
+
125
+ // Cache the compiled function
126
+ if (this.filterCache.size < 1000) { // Prevent memory leaks
127
+ this.filterCache.set(cacheKey, filterFn);
128
+ }
129
+
130
+ return filterFn;
131
+ }
132
+
133
+ /**
134
+ * Check if we can use indexes for this query
135
+ */
136
+ _canUseIndex(filterFn) {
137
+ // TODO: Implement index detection logic
138
+ // For now, we'll use full scans until IndexManager is ready
139
+ return false;
140
+ }
141
+
142
+ /**
143
+ * Use indexes for faster queries
144
+ */
145
+ _findWithIndex(filterFn) {
146
+ // TODO: Implement index-based search
147
+ // This will make large datasets queryable in milliseconds
148
+ return [];
149
+ }
150
+
151
+ /**
152
+ * Aggregation functions - for when you need answers
153
+ */
154
+
155
+ count(filterFn = null) {
156
+ if (filterFn) {
157
+ return this.find(filterFn).count();
158
+ }
159
+
160
+ // Fast path for total count
161
+ return this.db.data.size;
162
+ }
163
+
164
+ // sum() function optimized to avoid multiple iterations
165
+ sum(field, filterFn = null) {
166
+ const results = filterFn ? this.find(filterFn) : this.findAll();
167
+ const resultsArray = results.toArray();
168
+ let total = 0;
169
+
170
+ for (const item of resultsArray) {
171
+ const value = this._getFieldValue(item.value, field);
172
+ if (typeof value === 'number') {
173
+ total += value;
174
+ }
175
+ }
176
+ return total;
177
+ }
178
+
179
+ avg(field, filterFn = null) {
180
+ const results = filterFn ? this.find(filterFn) : this.findAll();
181
+ let total = 0;
182
+ let count = 0;
183
+
184
+ // results.toArray() used to avoid multiple iterations
185
+ const resultsArray = results.toArray();
186
+
187
+ for (const item of resultsArray) {
188
+ const value = this._getFieldValue(item.value, field);
189
+ if (typeof value === 'number') {
190
+ total += value;
191
+ count++;
192
+ }
193
+ }
194
+
195
+ return count > 0 ? total / count : 0;
196
+ }
197
+
198
+ min(field, filterFn = null) {
199
+ const results = filterFn ? this.find(filterFn) : this.findAll();
200
+ const resultsArray = results.toArray();
201
+ let min = Infinity;
202
+
203
+ for (const item of resultsArray) {
204
+ const value = this._getFieldValue(item.value, field);
205
+ if (typeof value === 'number' && value < min) {
206
+ min = value;
207
+ }
208
+ }
209
+ return min !== Infinity ? min : null;
210
+ }
211
+
212
+ max(field, filterFn = null) {
213
+ const results = filterFn ? this.find(filterFn) : this.findAll();
214
+ const resultsArray = results.toArray();
215
+ let max = -Infinity;
216
+
217
+ for (const item of resultsArray) {
218
+ const value = this._getFieldValue(item.value, field);
219
+ if (typeof value === 'number' && value > max) {
220
+ max = value;
221
+ }
222
+ }
223
+ return max !== -Infinity ? max : null;
224
+ }
225
+
226
+ /**
227
+ * Get all records as QueryResult
228
+ */
229
+ findAll() {
230
+ const results = [];
231
+ for (const [key, value] of this.db.data) {
232
+ results.push({ key, value });
233
+ }
234
+ return new QueryResult(results, this);
235
+ }
236
+
237
+ /**
238
+ * Get nested field value using dot notation
239
+ */
240
+ _getFieldValue(obj, fieldPath) {
241
+ if (!fieldPath.includes('.')) {
242
+ return obj[fieldPath];
243
+ }
244
+
245
+ const parts = fieldPath.split('.');
246
+ let value = obj;
247
+
248
+ for (const part of parts) {
249
+ value = value?.[part];
250
+ if (value === undefined) break;
251
+ }
252
+
253
+ return value;
254
+ }
255
+
256
+ /**
257
+ * Group by field - SQL-like power
258
+ */
259
+ groupBy(field, aggregateFn = null) {
260
+ const groups = new Map();
261
+
262
+ for (const [key, value] of this.db.data) {
263
+ const groupKey = this._getFieldValue(value, field);
264
+
265
+ if (!groups.has(groupKey)) {
266
+ groups.set(groupKey, []);
267
+ }
268
+
269
+ groups.get(groupKey).push({ key, value });
270
+ }
271
+
272
+ // Apply aggregation if provided
273
+ if (aggregateFn) {
274
+ const result = {};
275
+ for (const [groupKey, items] of groups) {
276
+ result[groupKey] = aggregateFn(items);
277
+ }
278
+ return result;
279
+ }
280
+
281
+ return Object.fromEntries(groups);
282
+ }
283
+
284
+ /**
285
+ * Performance tracking
286
+ */
287
+ _recordQueryTime(queryTime) {
288
+ this.stats.queryTimes.push(queryTime);
289
+
290
+ // Keep only last 100 times
291
+ if (this.stats.queryTimes.length > 100) {
292
+ this.stats.queryTimes.shift();
293
+ }
294
+
295
+ this.stats.avgQueryTime = this.stats.queryTimes.reduce((a, b) => a + b, 0) / this.stats.queryTimes.length;
296
+ }
297
+
298
+ /**
299
+ * Get query statistics
300
+ */
301
+ getStats() {
302
+ return {
303
+ ...this.stats,
304
+ cacheHitRate: this.cacheHits + this.cacheMisses > 0
305
+ ? ((this.cacheHits / (this.cacheHits + this.cacheMisses)) * 100).toFixed(2) + '%'
306
+ : '0%',
307
+ filterCacheSize: this.filterCache.size
308
+ };
309
+ }
310
+
311
+ /**
312
+ * Clear filter cache
313
+ */
314
+ clearCache() {
315
+ this.filterCache.clear();
316
+ this.cacheHits = 0;
317
+ this.cacheMisses = 0;
318
+ }
319
+ }
320
+
321
+ /**
322
+ * QueryResult - Enables method chaining
323
+ * Because .find().where().sort().limit() looks cool 😎
324
+ */
325
+ class QueryResult {
326
+ constructor(results, queryEngine, meta = {}) {
327
+ this.results = results;
328
+ this.queryEngine = queryEngine;
329
+ this.meta = meta;
330
+ }
331
+
332
+ /**
333
+ * Sort results by field
334
+ */
335
+ sort(field, direction = 'asc') {
336
+ const sorted = [...this.results].sort((a, b) => {
337
+ const aVal = this.queryEngine._getFieldValue(a.value, field);
338
+ const bVal = this.queryEngine._getFieldValue(b.value, field);
339
+
340
+ if (aVal === bVal) return 0;
341
+
342
+ if (direction === 'asc') {
343
+ return aVal < bVal ? -1 : 1;
344
+ } else {
345
+ return aVal > bVal ? -1 : 1;
346
+ }
347
+ });
348
+
349
+ return new QueryResult(sorted, this.queryEngine, this.meta);
350
+ }
351
+
352
+ /**
353
+ * Limit number of results
354
+ */
355
+ limit(count) {
356
+ const limited = this.results.slice(0, count);
357
+ return new QueryResult(limited, this.queryEngine, this.meta);
358
+ }
359
+
360
+ /**
361
+ * Skip number of results
362
+ */
363
+ skip(count) {
364
+ const skipped = this.results.slice(count);
365
+ return new QueryResult(skipped, this.queryEngine, this.meta);
366
+ }
367
+
368
+ /**
369
+ * Get first result
370
+ */
371
+ first() {
372
+ return this.results[0] || null;
373
+ }
374
+
375
+ /**
376
+ * Get last result
377
+ */
378
+ last() {
379
+ return this.results[this.results.length - 1] || null;
380
+ }
381
+
382
+ /**
383
+ * Get result count
384
+ */
385
+ count() {
386
+ return this.results.length;
387
+ }
388
+
389
+ /**
390
+ * Get only values
391
+ */
392
+ values() {
393
+ return this.results.map(item => item.value);
394
+ }
395
+
396
+ /**
397
+ * Get only keys
398
+ */
399
+ keys() {
400
+ return this.results.map(item => item.key);
401
+ }
402
+
403
+ /**
404
+ * Convert to array
405
+ */
406
+ toArray() {
407
+ return this.results;
408
+ }
409
+
410
+ /**
411
+ * Get query performance info
412
+ */
413
+ getMeta() {
414
+ return this.meta;
415
+ }
416
+
417
+ /**
418
+ * Execute another query on these results
419
+ */
420
+ find(filterFn) {
421
+ const filtered = this.results.filter(item => filterFn(item.value, item.key));
422
+ return new QueryResult(filtered, this.queryEngine, this.meta);
423
+ }
424
+
425
+ /**
426
+ * Map over results
427
+ */
428
+ map(fn) {
429
+ return this.results.map((item, index) => fn(item.value, item.key, index));
430
+ }
431
+
432
+ /**
433
+ * Filter results
434
+ */
435
+ filter(fn) {
436
+ const filtered = this.results.filter((item, index) => fn(item.value, item.key, index));
437
+ return new QueryResult(filtered, this.queryEngine, this.meta);
438
+ }
439
+
440
+ /**
441
+ * ForEach loop
442
+ */
443
+ forEach(fn) {
444
+ this.results.forEach((item, index) => fn(item.value, item.key, index));
445
+ }
446
+ }
447
+
448
448
  module.exports = QueryEngine;