0
# Cursor Operations
1
2
Chainable query interface for sorting, pagination, projection, and result processing with lazy evaluation and MongoDB-style query building.
3
4
## Capabilities
5
6
### Cursor Creation
7
8
Cursors are automatically created when calling find methods without a callback parameter, enabling method chaining.
9
10
```javascript { .api }
11
/**
12
* Cursor creation through find methods without callback
13
* @returns {Cursor} Chainable cursor instance
14
*/
15
interface CursorCreation {
16
find(query: object, projection?: object): Cursor;
17
findOne(query: object, projection?: object): Cursor;
18
count(query: object): Cursor;
19
}
20
21
/**
22
* Cursor interface for chaining operations
23
*/
24
interface Cursor {
25
limit(limit: number): Cursor;
26
skip(skip: number): Cursor;
27
sort(sortQuery: object): Cursor;
28
projection(projection: object): Cursor;
29
exec(callback: function): void;
30
}
31
```
32
33
**Usage Examples:**
34
35
```javascript
36
const db = new Datastore({ filename: './products.db', autoload: true });
37
38
// Create cursor from find (no callback)
39
const cursor = db.find({ category: 'electronics' });
40
41
// Create cursor from findOne
42
const singleCursor = db.findOne({ sku: 'ABC123' });
43
44
// Create cursor from count
45
const countCursor = db.count({ status: 'active' });
46
47
// Cursors are chainable
48
const chainedCursor = db.find({ price: { $gte: 100 } })
49
.sort({ price: 1 })
50
.skip(10)
51
.limit(5);
52
```
53
54
### Result Limiting
55
56
Control the maximum number of documents returned by the query.
57
58
```javascript { .api }
59
/**
60
* Limit the number of results returned
61
* @param {number} limit - Maximum number of documents to return
62
* @returns {Cursor} Cursor instance for chaining
63
*/
64
limit(limit);
65
```
66
67
**Usage Examples:**
68
69
```javascript
70
const db = new Datastore({ filename: './products.db', autoload: true });
71
72
// Get top 10 products
73
db.find({})
74
.limit(10)
75
.exec((err, products) => {
76
console.log('Top 10 products:', products);
77
});
78
79
// Get 5 most expensive products
80
db.find({})
81
.sort({ price: -1 })
82
.limit(5)
83
.exec((err, products) => {
84
console.log('5 most expensive products:', products);
85
});
86
87
// Pagination - first page (5 items)
88
db.find({ category: 'books' })
89
.sort({ title: 1 })
90
.limit(5)
91
.exec((err, books) => {
92
console.log('First 5 books:', books);
93
});
94
95
// Single result using limit
96
db.find({ featured: true })
97
.sort({ priority: -1 })
98
.limit(1)
99
.exec((err, products) => {
100
console.log('Top featured product:', products[0]);
101
});
102
```
103
104
### Result Skipping
105
106
Skip a specified number of documents from the beginning of the result set, useful for pagination.
107
108
```javascript { .api }
109
/**
110
* Skip specified number of documents from the beginning
111
* @param {number} skip - Number of documents to skip
112
* @returns {Cursor} Cursor instance for chaining
113
*/
114
skip(skip);
115
```
116
117
**Usage Examples:**
118
119
```javascript
120
const db = new Datastore({ filename: './users.db', autoload: true });
121
122
// Pagination - skip first 10 results
123
db.find({})
124
.sort({ createdAt: -1 })
125
.skip(10)
126
.limit(10)
127
.exec((err, users) => {
128
console.log('Users 11-20 (page 2):', users);
129
});
130
131
// Skip first page for second page
132
const pageSize = 20;
133
const pageNumber = 2; // 0-indexed: page 2 = third page
134
const skipCount = pageNumber * pageSize;
135
136
db.find({ status: 'active' })
137
.sort({ name: 1 })
138
.skip(skipCount)
139
.limit(pageSize)
140
.exec((err, users) => {
141
console.log(`Page ${pageNumber + 1} users:`, users);
142
});
143
144
// Skip recent entries to get older data
145
db.find({ type: 'log' })
146
.sort({ timestamp: -1 })
147
.skip(100) // Skip 100 most recent
148
.limit(50) // Get next 50
149
.exec((err, logs) => {
150
console.log('Older log entries:', logs);
151
});
152
153
// Advanced pagination with total count
154
function paginateUsers(page, pageSize, callback) {
155
const skip = page * pageSize;
156
157
// Get total count
158
db.count({}, (err, total) => {
159
if (err) return callback(err);
160
161
// Get page data
162
db.find({})
163
.sort({ name: 1 })
164
.skip(skip)
165
.limit(pageSize)
166
.exec((err, users) => {
167
if (err) return callback(err);
168
169
callback(null, {
170
users: users,
171
totalCount: total,
172
currentPage: page,
173
totalPages: Math.ceil(total / pageSize),
174
hasNextPage: (page + 1) * pageSize < total,
175
hasPrevPage: page > 0
176
});
177
});
178
});
179
}
180
181
// Usage
182
paginateUsers(0, 10, (err, result) => {
183
if (!err) {
184
console.log('Pagination result:', result);
185
}
186
});
187
```
188
189
### Result Sorting
190
191
Sort query results by one or multiple fields in ascending or descending order.
192
193
```javascript { .api }
194
/**
195
* Sort results by specified fields
196
* @param {object} sortQuery - Sort specification object
197
* @returns {Cursor} Cursor instance for chaining
198
*/
199
sort(sortQuery);
200
201
/**
202
* Sort specification format
203
*/
204
interface SortQuery {
205
[fieldName: string]: 1 | -1; // 1 for ascending, -1 for descending
206
}
207
```
208
209
**Usage Examples:**
210
211
```javascript
212
const db = new Datastore({ filename: './products.db', autoload: true });
213
214
// Sort by single field - ascending
215
db.find({})
216
.sort({ name: 1 })
217
.exec((err, products) => {
218
console.log('Products sorted by name (A-Z):', products);
219
});
220
221
// Sort by single field - descending
222
db.find({})
223
.sort({ price: -1 })
224
.exec((err, products) => {
225
console.log('Products sorted by price (high to low):', products);
226
});
227
228
// Sort by multiple fields
229
db.find({})
230
.sort({ category: 1, price: -1 })
231
.exec((err, products) => {
232
console.log('Products sorted by category (A-Z) then price (high to low):', products);
233
});
234
235
// Sort by nested fields using dot notation
236
db.find({})
237
.sort({ 'details.weight': 1 })
238
.exec((err, products) => {
239
console.log('Products sorted by weight:', products);
240
});
241
242
// Sort by date fields
243
db.find({})
244
.sort({ createdAt: -1 }) // Most recent first
245
.exec((err, products) => {
246
console.log('Products sorted by creation date (newest first):', products);
247
});
248
249
// Combining sort with other operations
250
db.find({ category: 'electronics' })
251
.sort({ rating: -1, price: 1 }) // Best rated first, then cheapest
252
.limit(10)
253
.exec((err, products) => {
254
console.log('Top 10 electronics by rating and price:', products);
255
});
256
257
// Custom string comparison (if compareStrings option was set)
258
const customDb = new Datastore({
259
filename: './data.db',
260
compareStrings: (a, b) => a.localeCompare(b, 'en', { sensitivity: 'base' })
261
});
262
263
customDb.find({})
264
.sort({ name: 1 })
265
.exec((err, docs) => {
266
console.log('Documents sorted with custom string comparison:', docs);
267
});
268
```
269
270
### Field Projection
271
272
Select specific fields to include or exclude in the query results, reducing data transfer and memory usage.
273
274
```javascript { .api }
275
/**
276
* Set field projection for results
277
* @param {object} projection - Field projection specification
278
* @returns {Cursor} Cursor instance for chaining
279
*/
280
projection(projection);
281
282
/**
283
* Projection specification format
284
*/
285
interface ProjectionQuery {
286
[fieldName: string]: 1 | 0; // 1 to include, 0 to exclude (cannot mix except with _id)
287
}
288
```
289
290
**Usage Examples:**
291
292
```javascript
293
const db = new Datastore({ filename: './users.db', autoload: true });
294
295
// Include specific fields only
296
db.find({})
297
.projection({ name: 1, email: 1 })
298
.exec((err, users) => {
299
// Results contain only name, email, and _id fields
300
console.log('User names and emails:', users);
301
});
302
303
// Exclude specific fields
304
db.find({})
305
.projection({ password: 0, secretKey: 0 })
306
.exec((err, users) => {
307
// Results contain all fields except password and secretKey
308
console.log('Users without sensitive data:', users);
309
});
310
311
// Exclude _id field
312
db.find({})
313
.projection({ _id: 0, name: 1, email: 1 })
314
.exec((err, users) => {
315
// Results contain only name and email (no _id)
316
console.log('Users without IDs:', users);
317
});
318
319
// Nested field projection
320
db.find({})
321
.projection({ 'profile.name': 1, 'profile.email': 1 })
322
.exec((err, users) => {
323
console.log('User profile names and emails:', users);
324
});
325
326
// Projection with other cursor operations
327
db.find({ status: 'active' })
328
.sort({ lastLogin: -1 })
329
.limit(50)
330
.projection({ name: 1, lastLogin: 1, status: 1 })
331
.exec((err, users) => {
332
console.log('Recent active users (limited fields):', users);
333
});
334
335
// Projection for performance optimization
336
db.find({ category: 'electronics' })
337
.projection({ name: 1, price: 1, rating: 1 }) // Only essential fields
338
.sort({ rating: -1 })
339
.limit(100)
340
.exec((err, products) => {
341
console.log('Electronics summary data:', products);
342
});
343
344
// Complex projection example
345
db.find({
346
$and: [
347
{ role: { $in: ['admin', 'moderator'] } },
348
{ active: true }
349
]
350
})
351
.projection({
352
name: 1,
353
role: 1,
354
'permissions.read': 1,
355
'permissions.write': 1,
356
lastLogin: 1,
357
password: 0, // Explicitly exclude sensitive data
358
apiKeys: 0 // Explicitly exclude sensitive data
359
})
360
.sort({ lastLogin: -1 })
361
.exec((err, staff) => {
362
console.log('Staff members with permissions:', staff);
363
});
364
```
365
366
### Cursor Execution
367
368
Execute the constructed cursor query and retrieve results through a callback function.
369
370
```javascript { .api }
371
/**
372
* Execute the cursor query and return results
373
* @param {function} callback - Callback function (err, results) => {}
374
*/
375
exec(callback);
376
377
/**
378
* Execution callback signatures vary by operation type
379
*/
380
interface ExecutionCallbacks {
381
find: (err: Error, docs: object[]) => void; // Array of documents
382
findOne: (err: Error, doc: object) => void; // Single document or null
383
count: (err: Error, count: number) => void; // Number of matching documents
384
}
385
```
386
387
**Usage Examples:**
388
389
```javascript
390
const db = new Datastore({ filename: './orders.db', autoload: true });
391
392
// Execute find cursor
393
db.find({ status: 'pending' })
394
.sort({ createdAt: -1 })
395
.limit(20)
396
.exec((err, orders) => {
397
if (err) {
398
console.error('Query failed:', err);
399
return;
400
}
401
console.log('Found', orders.length, 'pending orders');
402
orders.forEach(order => {
403
console.log('- Order', order._id, 'for', order.customerName);
404
});
405
});
406
407
// Execute findOne cursor
408
db.findOne({ orderId: 'ORD_12345' })
409
.projection({ customerName: 1, total: 1, status: 1 })
410
.exec((err, order) => {
411
if (err) {
412
console.error('Query failed:', err);
413
return;
414
}
415
if (order) {
416
console.log('Order found:', order);
417
} else {
418
console.log('Order not found');
419
}
420
});
421
422
// Execute count cursor
423
db.count({ status: 'completed', total: { $gte: 100 } })
424
.exec((err, count) => {
425
if (err) {
426
console.error('Count failed:', err);
427
return;
428
}
429
console.log('High-value completed orders:', count);
430
});
431
432
// Error handling patterns
433
db.find({ invalidField: { $invalidOperator: 'value' } })
434
.exec((err, results) => {
435
if (err) {
436
console.error('Query error:', err.message);
437
console.error('Error type:', err.name);
438
return;
439
}
440
console.log('Results:', results);
441
});
442
443
// Promise-style wrapper for modern async/await usage
444
function promisifyQuery(cursor) {
445
return new Promise((resolve, reject) => {
446
cursor.exec((err, result) => {
447
if (err) reject(err);
448
else resolve(result);
449
});
450
});
451
}
452
453
// Usage with async/await
454
async function getTopProducts() {
455
try {
456
const products = await promisifyQuery(
457
db.find({ featured: true })
458
.sort({ rating: -1 })
459
.limit(10)
460
.projection({ name: 1, rating: 1, price: 1 })
461
);
462
console.log('Top featured products:', products);
463
return products;
464
} catch (err) {
465
console.error('Failed to get top products:', err);
466
throw err;
467
}
468
}
469
```
470
471
### Cursor Chaining Patterns
472
473
Common patterns for combining cursor operations effectively.
474
475
```javascript
476
const db = new Datastore({ filename: './data.db', autoload: true });
477
478
// Pattern 1: Pagination with sorting
479
function getPaginatedData(page, pageSize, sortField = 'createdAt', sortOrder = -1) {
480
return db.find({})
481
.sort({ [sortField]: sortOrder })
482
.skip(page * pageSize)
483
.limit(pageSize);
484
}
485
486
// Usage
487
getPaginatedData(2, 10, 'name', 1).exec((err, data) => {
488
console.log('Page 3 data:', data);
489
});
490
491
// Pattern 2: Search with relevance sorting
492
function searchProducts(searchTerm, maxResults = 20) {
493
return db.find({
494
$or: [
495
{ name: { $regex: new RegExp(searchTerm, 'i') } },
496
{ description: { $regex: new RegExp(searchTerm, 'i') } }
497
]
498
})
499
.sort({ featured: -1, rating: -1 }) // Featured first, then by rating
500
.limit(maxResults)
501
.projection({ name: 1, description: 1, price: 1, rating: 1, featured: 1 });
502
}
503
504
// Pattern 3: Recent items with exclusions
505
function getRecentItems(excludeIds = [], limit = 50) {
506
const query = excludeIds.length > 0
507
? { _id: { $nin: excludeIds } }
508
: {};
509
510
return db.find(query)
511
.sort({ updatedAt: -1 })
512
.limit(limit)
513
.projection({ title: 1, updatedAt: 1, status: 1 });
514
}
515
516
// Pattern 4: Conditional sorting and projection
517
function getUsers(role = null, includeDetails = false) {
518
const query = role ? { role: role } : {};
519
const projection = includeDetails
520
? { password: 0, apiKey: 0 } // Exclude sensitive fields
521
: { name: 1, email: 1, role: 1 }; // Include only basic fields
522
523
return db.find(query)
524
.sort({ name: 1 })
525
.projection(projection);
526
}
527
528
// Pattern 5: Complex filtering with performance optimization
529
function getFilteredProducts(filters) {
530
let cursor = db.find(filters.query || {});
531
532
if (filters.sort) {
533
cursor = cursor.sort(filters.sort);
534
}
535
536
if (filters.skip) {
537
cursor = cursor.skip(filters.skip);
538
}
539
540
if (filters.limit) {
541
cursor = cursor.limit(filters.limit);
542
}
543
544
if (filters.fields) {
545
cursor = cursor.projection(filters.fields);
546
}
547
548
return cursor;
549
}
550
551
// Usage
552
const filters = {
553
query: { category: 'electronics', price: { $lte: 500 } },
554
sort: { rating: -1, price: 1 },
555
skip: 20,
556
limit: 10,
557
fields: { name: 1, price: 1, rating: 1 }
558
};
559
560
getFilteredProducts(filters).exec((err, products) => {
561
console.log('Filtered products:', products);
562
});
563
```
564
565
## Cursor Performance Considerations
566
567
- Cursors use lazy evaluation - operations are not executed until `exec()` is called
568
- Sorting is performed in memory, so large result sets may impact performance
569
- Use `limit()` to reduce memory usage for large queries
570
- Combine `projection()` with queries to minimize data transfer
571
- Index frequently sorted fields for better performance
572
- Use `skip()` sparingly with large offsets as it still processes skipped documents