@joystick.js/db-canary 0.0.0-canary.2250 → 0.0.0-canary.2252

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 (49) hide show
  1. package/dist/client/database.js +1 -1
  2. package/dist/client/index.js +1 -1
  3. package/dist/server/cluster/master.js +4 -4
  4. package/dist/server/cluster/worker.js +1 -1
  5. package/dist/server/index.js +1 -1
  6. package/dist/server/lib/auto_index_manager.js +1 -1
  7. package/dist/server/lib/backup_manager.js +1 -1
  8. package/dist/server/lib/index_manager.js +1 -1
  9. package/dist/server/lib/operation_dispatcher.js +1 -1
  10. package/dist/server/lib/operations/admin.js +1 -1
  11. package/dist/server/lib/operations/bulk_write.js +1 -1
  12. package/dist/server/lib/operations/create_index.js +1 -1
  13. package/dist/server/lib/operations/delete_many.js +1 -1
  14. package/dist/server/lib/operations/delete_one.js +1 -1
  15. package/dist/server/lib/operations/find.js +1 -1
  16. package/dist/server/lib/operations/find_one.js +1 -1
  17. package/dist/server/lib/operations/insert_one.js +1 -1
  18. package/dist/server/lib/operations/update_one.js +1 -1
  19. package/dist/server/lib/send_response.js +1 -1
  20. package/dist/server/lib/tcp_protocol.js +1 -1
  21. package/package.json +2 -2
  22. package/src/client/database.js +92 -119
  23. package/src/client/index.js +279 -345
  24. package/src/server/cluster/master.js +265 -156
  25. package/src/server/cluster/worker.js +26 -18
  26. package/src/server/index.js +553 -330
  27. package/src/server/lib/auto_index_manager.js +85 -23
  28. package/src/server/lib/backup_manager.js +117 -70
  29. package/src/server/lib/index_manager.js +63 -25
  30. package/src/server/lib/operation_dispatcher.js +339 -168
  31. package/src/server/lib/operations/admin.js +343 -205
  32. package/src/server/lib/operations/bulk_write.js +458 -194
  33. package/src/server/lib/operations/create_index.js +127 -34
  34. package/src/server/lib/operations/delete_many.js +204 -67
  35. package/src/server/lib/operations/delete_one.js +164 -52
  36. package/src/server/lib/operations/find.js +563 -201
  37. package/src/server/lib/operations/find_one.js +544 -188
  38. package/src/server/lib/operations/insert_one.js +147 -52
  39. package/src/server/lib/operations/update_one.js +334 -93
  40. package/src/server/lib/send_response.js +37 -17
  41. package/src/server/lib/tcp_protocol.js +158 -53
  42. package/tests/server/cluster/master_read_write_operations.test.js +5 -14
  43. package/tests/server/integration/authentication_integration.test.js +18 -10
  44. package/tests/server/integration/backup_integration.test.js +35 -27
  45. package/tests/server/lib/api_key_manager.test.js +88 -32
  46. package/tests/server/lib/development_mode.test.js +2 -2
  47. package/tests/server/lib/operations/admin.test.js +20 -12
  48. package/tests/server/lib/operations/delete_one.test.js +10 -4
  49. package/tests/server/lib/operations/find_array_queries.test.js +261 -0
@@ -35,6 +35,131 @@ import { performance_monitor } from '../performance_monitor.js';
35
35
 
36
36
  const { create_context_logger } = create_logger('admin');
37
37
 
38
+ /**
39
+ * Safely retrieves database settings with fallback.
40
+ * @returns {Object} Settings object with defaults
41
+ */
42
+ const get_safe_settings = () => {
43
+ try {
44
+ return get_settings();
45
+ } catch (settings_error) {
46
+ return { port: 1983 };
47
+ }
48
+ };
49
+
50
+ /**
51
+ * Safely retrieves database statistics.
52
+ * @param {Object} db - Database instance
53
+ * @returns {Object} Database statistics object
54
+ */
55
+ const get_safe_database_stats = (db) => {
56
+ try {
57
+ const raw_stats = db.getStats ? db.getStats() : {};
58
+ return {
59
+ pageSize: raw_stats.pageSize || 0,
60
+ treeDepth: raw_stats.treeDepth || 0,
61
+ treeBranchPages: raw_stats.treeBranchPages || 0,
62
+ treeLeafPages: raw_stats.treeLeafPages || 0,
63
+ entryCount: raw_stats.entryCount || 0,
64
+ mapSize: raw_stats.mapSize || 0,
65
+ lastPageNumber: raw_stats.lastPageNumber || 0
66
+ };
67
+ } catch (stats_error) {
68
+ return { error: 'Could not retrieve database stats' };
69
+ }
70
+ };
71
+
72
+ /**
73
+ * Counts collections and documents in database.
74
+ * @param {Object} db - Database instance
75
+ * @param {Object} log - Logger instance
76
+ * @returns {Object} Collections count and total documents
77
+ */
78
+ const count_collections_and_documents = (db, log) => {
79
+ const collections = {};
80
+ let total_documents = 0;
81
+
82
+ try {
83
+ for (const { key } of db.getRange()) {
84
+ if (typeof key === 'string' && key.includes(':') && !key.startsWith('_')) {
85
+ const collection_name = key.split(':')[0];
86
+ collections[collection_name] = (collections[collection_name] || 0) + 1;
87
+ total_documents++;
88
+ }
89
+ }
90
+ } catch (range_error) {
91
+ log.warn('Could not iterate database range for stats', { error: range_error.message });
92
+ }
93
+
94
+ return { collections, total_documents };
95
+ };
96
+
97
+ /**
98
+ * Calculates memory usage in megabytes.
99
+ * @returns {Object} Memory usage statistics
100
+ */
101
+ const calculate_memory_usage = () => {
102
+ const memory_usage = process.memoryUsage();
103
+ return {
104
+ rss: Math.round(memory_usage.rss / 1024 / 1024),
105
+ heapTotal: Math.round(memory_usage.heapTotal / 1024 / 1024),
106
+ heapUsed: Math.round(memory_usage.heapUsed / 1024 / 1024),
107
+ external: Math.round(memory_usage.external / 1024 / 1024)
108
+ };
109
+ };
110
+
111
+ /**
112
+ * Calculates database size usage percentage.
113
+ * @param {Object} db_stats - Database statistics
114
+ * @returns {number} Usage percentage
115
+ */
116
+ const calculate_database_usage_percent = (db_stats) => {
117
+ return db_stats.mapSize > 0
118
+ ? Math.round((db_stats.lastPageNumber * db_stats.pageSize / db_stats.mapSize) * 100)
119
+ : 0;
120
+ };
121
+
122
+ /**
123
+ * Creates server statistics object.
124
+ * @param {Object} memory_usage_mb - Memory usage in MB
125
+ * @returns {Object} Server statistics
126
+ */
127
+ const create_server_stats = (memory_usage_mb) => {
128
+ return {
129
+ uptime: Math.floor(process.uptime()),
130
+ uptime_formatted: format_uptime(process.uptime()),
131
+ memory_usage: memory_usage_mb,
132
+ memory_usage_raw: process.memoryUsage(),
133
+ node_version: process.version,
134
+ platform: process.platform,
135
+ arch: process.arch,
136
+ pid: process.pid,
137
+ cpu_usage: process.cpuUsage()
138
+ };
139
+ };
140
+
141
+ /**
142
+ * Creates database statistics object.
143
+ * @param {number} total_documents - Total document count
144
+ * @param {Object} collections - Collections object
145
+ * @param {Object} db_stats - Database statistics
146
+ * @param {number} map_size_usage_percent - Usage percentage
147
+ * @returns {Object} Database statistics
148
+ */
149
+ const create_database_stats = (total_documents, collections, db_stats, map_size_usage_percent) => {
150
+ return {
151
+ total_documents,
152
+ total_collections: Object.keys(collections).length,
153
+ collections,
154
+ stats: db_stats,
155
+ map_size_usage_percent,
156
+ disk_usage: {
157
+ map_size_mb: Math.round((db_stats.mapSize || 0) / 1024 / 1024),
158
+ used_space_mb: Math.round(((db_stats.lastPageNumber || 0) * (db_stats.pageSize || 0)) / 1024 / 1024)
159
+ }
160
+ };
161
+ };
162
+
38
163
  /**
39
164
  * Gets comprehensive server and database statistics.
40
165
  * @returns {Object} Enhanced statistics including server, database, and performance metrics
@@ -45,85 +170,15 @@ const get_enhanced_stats = () => {
45
170
 
46
171
  try {
47
172
  const db = get_database();
48
-
49
- // Handle settings gracefully - use defaults if not loaded
50
- let settings;
51
- try {
52
- settings = get_settings();
53
- } catch (settings_error) {
54
- settings = { port: 1983 };
55
- }
56
-
57
- // Safely get database stats
58
- let db_stats = {};
59
- try {
60
- const raw_stats = db.getStats ? db.getStats() : {};
61
- db_stats = {
62
- pageSize: raw_stats.pageSize || 0,
63
- treeDepth: raw_stats.treeDepth || 0,
64
- treeBranchPages: raw_stats.treeBranchPages || 0,
65
- treeLeafPages: raw_stats.treeLeafPages || 0,
66
- entryCount: raw_stats.entryCount || 0,
67
- mapSize: raw_stats.mapSize || 0,
68
- lastPageNumber: raw_stats.lastPageNumber || 0
69
- };
70
- } catch (stats_error) {
71
- db_stats = { error: 'Could not retrieve database stats' };
72
- }
73
-
74
- // Count collections and documents safely
75
- const collections = {};
76
- let total_documents = 0;
77
-
78
- try {
79
- for (const { key } of db.getRange()) {
80
- if (typeof key === 'string' && key.includes(':') && !key.startsWith('_')) {
81
- const collection_name = key.split(':')[0];
82
- collections[collection_name] = (collections[collection_name] || 0) + 1;
83
- total_documents++;
84
- }
85
- }
86
- } catch (range_error) {
87
- log.warn('Could not iterate database range for stats', { error: range_error.message });
88
- }
89
-
90
- // Calculate memory usage percentage
91
- const memory_usage = process.memoryUsage();
92
- const memory_usage_mb = {
93
- rss: Math.round(memory_usage.rss / 1024 / 1024),
94
- heapTotal: Math.round(memory_usage.heapTotal / 1024 / 1024),
95
- heapUsed: Math.round(memory_usage.heapUsed / 1024 / 1024),
96
- external: Math.round(memory_usage.external / 1024 / 1024)
97
- };
98
-
99
- // Calculate database size percentage
100
- const map_size_usage_percent = db_stats.mapSize > 0
101
- ? Math.round((db_stats.lastPageNumber * db_stats.pageSize / db_stats.mapSize) * 100)
102
- : 0;
173
+ const settings = get_safe_settings();
174
+ const db_stats = get_safe_database_stats(db);
175
+ const { collections, total_documents } = count_collections_and_documents(db, log);
176
+ const memory_usage_mb = calculate_memory_usage();
177
+ const map_size_usage_percent = calculate_database_usage_percent(db_stats);
103
178
 
104
179
  return {
105
- server: {
106
- uptime: Math.floor(process.uptime()),
107
- uptime_formatted: format_uptime(process.uptime()),
108
- memory_usage: memory_usage_mb,
109
- memory_usage_raw: memory_usage,
110
- node_version: process.version,
111
- platform: process.platform,
112
- arch: process.arch,
113
- pid: process.pid,
114
- cpu_usage: process.cpuUsage()
115
- },
116
- database: {
117
- total_documents,
118
- total_collections: Object.keys(collections).length,
119
- collections,
120
- stats: db_stats,
121
- map_size_usage_percent,
122
- disk_usage: {
123
- map_size_mb: Math.round((db_stats.mapSize || 0) / 1024 / 1024),
124
- used_space_mb: Math.round(((db_stats.lastPageNumber || 0) * (db_stats.pageSize || 0)) / 1024 / 1024)
125
- }
126
- },
180
+ server: create_server_stats(memory_usage_mb),
181
+ database: create_database_stats(total_documents, collections, db_stats, map_size_usage_percent),
127
182
  performance: {
128
183
  ops_per_second: calculate_ops_per_second(),
129
184
  avg_response_time_ms: calculate_avg_response_time()
@@ -189,100 +244,147 @@ export const track_operation = (duration_ms) => {
189
244
  };
190
245
 
191
246
  /**
192
- * Lists all collections in the database with metadata.
193
- * @param {string} [database_name='default'] - Database name to list collections from
194
- * @returns {Object} Object containing collections array, total counts
195
- * @throws {Error} When collection listing fails
247
+ * Creates initial collection metadata object.
248
+ * @param {string} collection_name - Collection name
249
+ * @returns {Object} Collection metadata object
196
250
  */
197
- const list_collections = (database_name = 'default') => {
198
- const log = create_context_logger();
251
+ const create_collection_metadata = (collection_name) => {
252
+ return {
253
+ name: collection_name,
254
+ document_count: 0,
255
+ indexes: [],
256
+ estimated_size_bytes: 0
257
+ };
258
+ };
259
+
260
+ /**
261
+ * Scans database range for collections and documents.
262
+ * @param {Object} db - Database instance
263
+ * @param {string} database_name - Database name
264
+ * @param {Object} log - Logger instance
265
+ * @returns {Object} Collections map and total documents count
266
+ */
267
+ const scan_database_for_collections = (db, database_name, log) => {
268
+ const collections_map = {};
269
+ let total_documents = 0;
199
270
 
200
271
  try {
201
- const db = get_database();
202
- const collections_map = {};
203
- let total_documents = 0;
204
-
205
- // Look for database:collection:document_id format
206
- try {
207
- for (const { key } of db.getRange()) {
208
- if (typeof key === 'string' && key.includes(':') && !key.startsWith('_')) {
209
- const parts = key.split(':');
210
- if (parts.length >= 3) {
211
- const key_database = parts[0];
212
- const collection_name = parts[1];
213
-
214
- // Only count collections from the specified database
215
- if (key_database === database_name) {
216
- if (!collections_map[collection_name]) {
217
- collections_map[collection_name] = {
218
- name: collection_name,
219
- document_count: 0,
220
- indexes: [],
221
- estimated_size_bytes: 0
222
- };
223
- }
224
- collections_map[collection_name].document_count++;
225
- total_documents++;
272
+ for (const { key } of db.getRange()) {
273
+ if (typeof key === 'string' && key.includes(':') && !key.startsWith('_')) {
274
+ const parts = key.split(':');
275
+ if (parts.length >= 3) {
276
+ const key_database = parts[0];
277
+ const collection_name = parts[1];
278
+
279
+ if (key_database === database_name) {
280
+ if (!collections_map[collection_name]) {
281
+ collections_map[collection_name] = create_collection_metadata(collection_name);
226
282
  }
283
+ collections_map[collection_name].document_count++;
284
+ total_documents++;
227
285
  }
228
286
  }
229
287
  }
230
- } catch (range_error) {
231
- log.warn('Could not iterate database range for collections', { error: range_error.message });
288
+ }
289
+ } catch (range_error) {
290
+ log.warn('Could not iterate database range for collections', { error: range_error.message });
291
+ }
292
+
293
+ return { collections_map, total_documents };
294
+ };
295
+
296
+ /**
297
+ * Fallback method to find collections using known patterns.
298
+ * @param {Object} db - Database instance
299
+ * @param {string} database_name - Database name
300
+ * @param {Object} collections_map - Existing collections map
301
+ * @returns {number} Additional documents found
302
+ */
303
+ const fallback_collection_scan = (db, database_name, collections_map) => {
304
+ const potential_collections = [
305
+ 'admin_test', 'test_collection', 'queue_test', 'users', 'products',
306
+ 'orders', 'sessions', 'logs', 'analytics', 'settings', 'another_collection',
307
+ 'list_test', 'pagination_test', 'get_test', 'query_test', 'admin_insert_test',
308
+ 'admin_update_test', 'admin_delete_test'
309
+ ];
310
+
311
+ let additional_documents = 0;
312
+
313
+ for (const collection_name of potential_collections) {
314
+ try {
315
+ const prefix = `${database_name}:${collection_name}:`;
316
+ const range = db.getRange({ start: prefix, end: prefix + '\xFF' });
232
317
 
233
- // Fallback: try to find collections by checking for database-specific patterns
234
- const potential_collections = [
235
- 'admin_test', 'test_collection', 'queue_test', 'users', 'products',
236
- 'orders', 'sessions', 'logs', 'analytics', 'settings', 'another_collection',
237
- 'list_test', 'pagination_test', 'get_test', 'query_test', 'admin_insert_test',
238
- 'admin_update_test', 'admin_delete_test'
239
- ];
318
+ let document_count = 0;
319
+ for (const entry of range) {
320
+ document_count++;
321
+ additional_documents++;
322
+ }
240
323
 
241
- for (const collection_name of potential_collections) {
242
- try {
243
- // Check if this collection has any documents in the specified database
244
- const prefix = `${database_name}:${collection_name}:`;
245
- const range = db.getRange({ start: prefix, end: prefix + '\xFF' });
246
-
247
- let document_count = 0;
248
- for (const entry of range) {
249
- document_count++;
250
- total_documents++;
251
- }
252
-
253
- if (document_count > 0) {
254
- collections_map[collection_name] = {
255
- name: collection_name,
256
- document_count,
257
- indexes: [],
258
- estimated_size_bytes: document_count * 100 // Rough estimate
259
- };
260
- }
261
- } catch (collection_error) {
262
- // Skip collections that can't be accessed
263
- continue;
264
- }
324
+ if (document_count > 0) {
325
+ collections_map[collection_name] = {
326
+ name: collection_name,
327
+ document_count,
328
+ indexes: [],
329
+ estimated_size_bytes: document_count * 100
330
+ };
265
331
  }
332
+ } catch (collection_error) {
333
+ continue;
266
334
  }
335
+ }
336
+
337
+ return additional_documents;
338
+ };
339
+
340
+ /**
341
+ * Adds index information to collections.
342
+ * @param {Object} db - Database instance
343
+ * @param {string} database_name - Database name
344
+ * @param {Object} collections_map - Collections map to update
345
+ * @param {Object} log - Logger instance
346
+ */
347
+ const add_index_information = (db, database_name, collections_map, log) => {
348
+ try {
349
+ const index_prefix = `index:${database_name}:`;
350
+ const index_range = db.getRange({ start: index_prefix, end: index_prefix + '\xFF' });
267
351
 
268
- // Get index information for each collection safely
269
- try {
270
- const index_prefix = `index:${database_name}:`;
271
- const index_range = db.getRange({ start: index_prefix, end: index_prefix + '\xFF' });
272
- for (const { key, value } of index_range) {
273
- if (typeof key === 'string' && key.startsWith(index_prefix)) {
274
- const remaining_key = key.substring(index_prefix.length);
275
- const collection_name = remaining_key.split(':')[0];
276
- if (collections_map[collection_name]) {
277
- if (!collections_map[collection_name].indexes.includes(remaining_key.split(':')[1])) {
278
- collections_map[collection_name].indexes.push(remaining_key.split(':')[1]);
279
- }
352
+ for (const { key, value } of index_range) {
353
+ if (typeof key === 'string' && key.startsWith(index_prefix)) {
354
+ const remaining_key = key.substring(index_prefix.length);
355
+ const collection_name = remaining_key.split(':')[0];
356
+ const index_name = remaining_key.split(':')[1];
357
+
358
+ if (collections_map[collection_name] && index_name) {
359
+ if (!collections_map[collection_name].indexes.includes(index_name)) {
360
+ collections_map[collection_name].indexes.push(index_name);
280
361
  }
281
362
  }
282
363
  }
283
- } catch (index_range_error) {
284
- log.warn('Could not iterate index range', { error: index_range_error.message });
285
364
  }
365
+ } catch (index_range_error) {
366
+ log.warn('Could not iterate index range', { error: index_range_error.message });
367
+ }
368
+ };
369
+
370
+ /**
371
+ * Lists all collections in the database with metadata.
372
+ * @param {string} [database_name='default'] - Database name to list collections from
373
+ * @returns {Object} Object containing collections array, total counts
374
+ * @throws {Error} When collection listing fails
375
+ */
376
+ const list_collections = (database_name = 'default') => {
377
+ const log = create_context_logger();
378
+
379
+ try {
380
+ const db = get_database();
381
+ let { collections_map, total_documents } = scan_database_for_collections(db, database_name, log);
382
+
383
+ if (Object.keys(collections_map).length === 0) {
384
+ total_documents += fallback_collection_scan(db, database_name, collections_map);
385
+ }
386
+
387
+ add_index_information(db, database_name, collections_map, log);
286
388
 
287
389
  const collections_array = Object.values(collections_map);
288
390
 
@@ -416,6 +518,80 @@ const get_document = (collection, document_id, database = 'default') => {
416
518
  }
417
519
  };
418
520
 
521
+ /**
522
+ * Evaluates a single query operator against a document value.
523
+ * @param {string} operator - Query operator (e.g., '$gt', '$regex')
524
+ * @param {*} op_value - Operator value
525
+ * @param {*} doc_value - Document field value
526
+ * @param {Object} filter_value - Full filter value for context
527
+ * @returns {boolean} Whether the operator matches
528
+ */
529
+ const evaluate_query_operator = (operator, op_value, doc_value, filter_value) => {
530
+ switch (operator) {
531
+ case '$gt': return doc_value > op_value;
532
+ case '$gte': return doc_value >= op_value;
533
+ case '$lt': return doc_value < op_value;
534
+ case '$lte': return doc_value <= op_value;
535
+ case '$ne': return doc_value !== op_value;
536
+ case '$in': return Array.isArray(op_value) && op_value.includes(doc_value);
537
+ case '$regex':
538
+ const regex_options = filter_value.$options || '';
539
+ return new RegExp(op_value, regex_options).test(String(doc_value));
540
+ default: return doc_value === filter_value;
541
+ }
542
+ };
543
+
544
+ /**
545
+ * Checks if a document matches the query filter.
546
+ * @param {Object} document - Document to check
547
+ * @param {Object} filter - Query filter
548
+ * @returns {boolean} Whether document matches filter
549
+ */
550
+ const document_matches_filter = (document, filter) => {
551
+ return Object.keys(filter).every(field => {
552
+ const filter_value = filter[field];
553
+ const doc_value = document[field];
554
+
555
+ if (typeof filter_value === 'object' && filter_value !== null) {
556
+ return Object.keys(filter_value).every(operator => {
557
+ const op_value = filter_value[operator];
558
+ return evaluate_query_operator(operator, op_value, doc_value, filter_value);
559
+ });
560
+ } else {
561
+ return doc_value === filter_value;
562
+ }
563
+ });
564
+ };
565
+
566
+ /**
567
+ * Processes a single document during query iteration.
568
+ * @param {string} key - Document key
569
+ * @param {string} value - Document value
570
+ * @param {string} prefix - Collection prefix
571
+ * @param {Object} filter - Query filter
572
+ * @param {Object} log - Logger instance
573
+ * @returns {Object|null} Processed document or null if invalid
574
+ */
575
+ const process_query_document = (key, value, prefix, filter, log) => {
576
+ try {
577
+ const document = JSON.parse(value);
578
+ const document_id = key.substring(prefix.length);
579
+ const full_document = { _id: document_id, ...document };
580
+
581
+ if (document_matches_filter(full_document, filter)) {
582
+ return full_document;
583
+ }
584
+
585
+ return null;
586
+ } catch (parse_error) {
587
+ log.warn('Could not parse document during query', {
588
+ key,
589
+ error: parse_error.message
590
+ });
591
+ return null;
592
+ }
593
+ };
594
+
419
595
  /**
420
596
  * Queries documents in a collection with filtering and pagination.
421
597
  * @param {string} collection - Collection name
@@ -448,58 +624,20 @@ const query_documents = (collection, filter = {}, options = {}) => {
448
624
  if (typeof key === 'string' && key.startsWith(prefix)) {
449
625
  total_examined++;
450
626
 
451
- try {
452
- const document = JSON.parse(value);
453
- const document_id = key.substring(prefix.length);
454
- const full_document = { _id: document_id, ...document };
455
-
456
- // Simple filter matching
457
- const matches = Object.keys(filter).every(field => {
458
- const filter_value = filter[field];
459
- const doc_value = full_document[field];
460
-
461
- if (typeof filter_value === 'object' && filter_value !== null) {
462
- // Handle operators like { $gt: 5 }, { $regex: "pattern" }
463
- return Object.keys(filter_value).every(operator => {
464
- const op_value = filter_value[operator];
465
- switch (operator) {
466
- case '$gt': return doc_value > op_value;
467
- case '$gte': return doc_value >= op_value;
468
- case '$lt': return doc_value < op_value;
469
- case '$lte': return doc_value <= op_value;
470
- case '$ne': return doc_value !== op_value;
471
- case '$in': return Array.isArray(op_value) && op_value.includes(doc_value);
472
- case '$regex':
473
- // Handle $options parameter for regex flags
474
- const regex_options = filter_value.$options || '';
475
- return new RegExp(op_value, regex_options).test(String(doc_value));
476
- default: return doc_value === filter_value;
477
- }
478
- });
479
- } else {
480
- return doc_value === filter_value;
481
- }
482
- });
627
+ const processed_document = process_query_document(key, value, prefix, filter, log);
628
+
629
+ if (processed_document) {
630
+ if (skipped < skip) {
631
+ skipped++;
632
+ continue;
633
+ }
483
634
 
484
- if (matches) {
485
- if (skipped < skip) {
486
- skipped++;
487
- continue;
488
- }
489
-
490
- if (count >= limit) {
491
- break;
492
- }
493
-
494
- documents.push(full_document);
495
- count++;
635
+ if (count >= limit) {
636
+ break;
496
637
  }
497
- } catch (parse_error) {
498
- log.warn('Could not parse document during query', {
499
- collection,
500
- key,
501
- error: parse_error.message
502
- });
638
+
639
+ documents.push(processed_document);
640
+ count++;
503
641
  }
504
642
  }
505
643
  }