fix(cursor): Improve cursor usage documentation and refactor getCursor API to support native cursor modifiers
This commit is contained in:
parent
e7c0951786
commit
43f9033ccc
@ -1,5 +1,12 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-04-24 - 5.15.1 - fix(cursor)
|
||||||
|
Improve cursor usage documentation and refactor getCursor API to support native cursor modifiers
|
||||||
|
|
||||||
|
- Updated examples in readme.md to demonstrate manual iteration using cursor.next() and proper cursor closing.
|
||||||
|
- Refactored the getCursor method in classes.doc.ts to accept session and modifier options, consolidating cursor handling.
|
||||||
|
- Added new tests in test/test.cursor.ts to verify cursor operations, including limits, sorting, and skipping.
|
||||||
|
|
||||||
## 2025-04-24 - 5.15.0 - feat(svDb)
|
## 2025-04-24 - 5.15.0 - feat(svDb)
|
||||||
Enhance svDb decorator to support custom serialization and deserialization options
|
Enhance svDb decorator to support custom serialization and deserialization options
|
||||||
|
|
||||||
|
39
readme.md
39
readme.md
@ -133,31 +133,34 @@ const user = await User.getInstance({ username: 'myUsername' });
|
|||||||
// Fetch multiple users that match criteria
|
// Fetch multiple users that match criteria
|
||||||
const users = await User.getInstances({ email: 'myEmail@example.com' });
|
const users = await User.getInstances({ email: 'myEmail@example.com' });
|
||||||
|
|
||||||
// Using a cursor for large collections
|
// Obtain a cursor for large result sets
|
||||||
const cursor = await User.getCursor({ active: true });
|
const cursor = await User.getCursor({ active: true });
|
||||||
|
|
||||||
// Process documents one at a time (memory efficient)
|
// Stream each document efficiently
|
||||||
await cursor.forEach(async (user, index) => {
|
await cursor.forEach(async (user) => {
|
||||||
// Process each user with its position
|
console.log(`Processing user: ${user.username}`);
|
||||||
console.log(`Processing user ${index}: ${user.username}`);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Chain cursor methods like in the MongoDB native driver
|
// Manually iterate using next()
|
||||||
const paginatedCursor = await User.getCursor({ active: true })
|
let nextUser;
|
||||||
.limit(10) // Limit results
|
while ((nextUser = await cursor.next())) {
|
||||||
.skip(20) // Skip first 20 results
|
console.log(`Next user: ${nextUser.username}`);
|
||||||
.sort({ createdAt: -1 }); // Sort by creation date descending
|
}
|
||||||
|
|
||||||
// Convert cursor to array (when you know the result set is small)
|
// Convert to array when the result set is small
|
||||||
const userArray = await paginatedCursor.toArray();
|
const userArray = await cursor.toArray();
|
||||||
|
|
||||||
// Other cursor operations
|
// Close the cursor to free resources
|
||||||
const nextUser = await cursor.next(); // Get the next document
|
|
||||||
const hasMoreUsers = await cursor.hasNext(); // Check if more documents exist
|
|
||||||
const count = await cursor.count(); // Get the count of documents in the cursor
|
|
||||||
|
|
||||||
// Always close cursors when done with them
|
|
||||||
await cursor.close();
|
await cursor.close();
|
||||||
|
|
||||||
|
// For native cursor modifiers (sort, skip, limit), use getCursor with modifier option:
|
||||||
|
const paginatedCursor = await User.getCursor(
|
||||||
|
{ active: true },
|
||||||
|
{ modifier: (c) => c.sort({ createdAt: -1 }).skip(20).limit(10) }
|
||||||
|
);
|
||||||
|
await paginatedCursor.forEach((user) => {
|
||||||
|
console.log(`Paginated user: ${user.username}`);
|
||||||
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Update
|
#### Update
|
||||||
|
97
test/test.cursor.ts
Normal file
97
test/test.cursor.ts
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import { tap, expect } from '@push.rocks/tapbundle';
|
||||||
|
import * as smartmongo from '@push.rocks/smartmongo';
|
||||||
|
import { smartunique } from '../ts/plugins.js';
|
||||||
|
import * as smartdata from '../ts/index.js';
|
||||||
|
|
||||||
|
// Set up database connection
|
||||||
|
let smartmongoInstance: smartmongo.SmartMongo;
|
||||||
|
let testDb: smartdata.SmartdataDb;
|
||||||
|
|
||||||
|
// Define a simple document model for cursor tests
|
||||||
|
@smartdata.Collection(() => testDb)
|
||||||
|
class CursorTest extends smartdata.SmartDataDbDoc<CursorTest, CursorTest> {
|
||||||
|
@smartdata.unI()
|
||||||
|
public id: string = smartunique.shortId();
|
||||||
|
|
||||||
|
@smartdata.svDb()
|
||||||
|
public name: string;
|
||||||
|
|
||||||
|
@smartdata.svDb()
|
||||||
|
public order: number;
|
||||||
|
|
||||||
|
constructor(name: string, order: number) {
|
||||||
|
super();
|
||||||
|
this.name = name;
|
||||||
|
this.order = order;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the in-memory MongoDB and SmartdataDB
|
||||||
|
tap.test('cursor init: start Mongo and SmartdataDb', async () => {
|
||||||
|
smartmongoInstance = await smartmongo.SmartMongo.createAndStart();
|
||||||
|
testDb = new smartdata.SmartdataDb(
|
||||||
|
await smartmongoInstance.getMongoDescriptor(),
|
||||||
|
);
|
||||||
|
await testDb.init();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Insert sample documents
|
||||||
|
tap.test('cursor insert: save 5 test documents', async () => {
|
||||||
|
for (let i = 1; i <= 5; i++) {
|
||||||
|
const doc = new CursorTest(`item${i}`, i);
|
||||||
|
await doc.save();
|
||||||
|
}
|
||||||
|
const count = await CursorTest.getCount({});
|
||||||
|
expect(count).toEqual(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test that toArray returns all documents
|
||||||
|
tap.test('cursor toArray: retrieves all documents', async () => {
|
||||||
|
const cursor = await CursorTest.getCursor({});
|
||||||
|
const all = await cursor.toArray();
|
||||||
|
expect(all.length).toEqual(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test iteration via forEach
|
||||||
|
tap.test('cursor forEach: iterates through all documents', async () => {
|
||||||
|
const names: string[] = [];
|
||||||
|
const cursor = await CursorTest.getCursor({});
|
||||||
|
await cursor.forEach(async (item) => {
|
||||||
|
names.push(item.name);
|
||||||
|
});
|
||||||
|
expect(names.length).toEqual(5);
|
||||||
|
expect(names).toContain('item3');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test native cursor modifiers: limit
|
||||||
|
tap.test('cursor modifier limit: only two documents', async () => {
|
||||||
|
const cursor = await CursorTest.getCursor({}, { modifier: (c) => c.limit(2) });
|
||||||
|
const limited = await cursor.toArray();
|
||||||
|
expect(limited.length).toEqual(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test native cursor modifiers: sort and skip
|
||||||
|
tap.test('cursor modifier sort & skip: returns correct order', async () => {
|
||||||
|
const cursor = await CursorTest.getCursor({}, {
|
||||||
|
modifier: (c) => c.sort({ order: -1 }).skip(1),
|
||||||
|
});
|
||||||
|
const results = await cursor.toArray();
|
||||||
|
// Skipped the first (order 5), next should be 4,3,2,1
|
||||||
|
expect(results.length).toEqual(4);
|
||||||
|
expect(results[0].order).toEqual(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cleanup: drop database, close connections, stop Mongo
|
||||||
|
tap.test('cursor cleanup: drop DB and stop', async () => {
|
||||||
|
await testDb.mongoDb.dropDatabase();
|
||||||
|
await testDb.close();
|
||||||
|
if (smartmongoInstance) {
|
||||||
|
await smartmongoInstance.stopAndDumpToDir(
|
||||||
|
`.nogit/dbdump/test.cursor.ts`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Ensure process exits after cleanup
|
||||||
|
setTimeout(() => process.exit(), 2000);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default tap.start();
|
@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartdata',
|
name: '@push.rocks/smartdata',
|
||||||
version: '5.15.0',
|
version: '5.15.1',
|
||||||
description: 'An advanced library for NoSQL data organization and manipulation using TypeScript with support for MongoDB, data validation, collections, and custom data types.'
|
description: 'An advanced library for NoSQL data organization and manipulation using TypeScript with support for MongoDB, data validation, collections, and custom data types.'
|
||||||
}
|
}
|
||||||
|
@ -276,38 +276,27 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get cursor
|
* Get a cursor for streaming results, with optional session and native cursor modifiers.
|
||||||
* @returns
|
* @param filterArg Partial filter to apply
|
||||||
*/
|
* @param opts Optional session and modifier for the raw MongoDB cursor
|
||||||
/**
|
|
||||||
* Get a cursor for streaming results, with optional session
|
|
||||||
*/
|
*/
|
||||||
public static async getCursor<T>(
|
public static async getCursor<T>(
|
||||||
this: plugins.tsclass.typeFest.Class<T>,
|
this: plugins.tsclass.typeFest.Class<T>,
|
||||||
filterArg: plugins.tsclass.typeFest.PartialDeep<T>,
|
filterArg: plugins.tsclass.typeFest.PartialDeep<T>,
|
||||||
opts?: { session?: plugins.mongodb.ClientSession }
|
opts?: {
|
||||||
) {
|
session?: plugins.mongodb.ClientSession;
|
||||||
const collection: SmartdataCollection<T> = (this as any).collection;
|
modifier?: (cursorArg: plugins.mongodb.FindCursor<plugins.mongodb.WithId<plugins.mongodb.BSON.Document>>) => plugins.mongodb.FindCursor<plugins.mongodb.WithId<plugins.mongodb.BSON.Document>>;
|
||||||
const cursor: SmartdataDbCursor<T> = await collection.getCursor(
|
|
||||||
convertFilterForMongoDb(filterArg),
|
|
||||||
this as any as typeof SmartDataDbDoc,
|
|
||||||
{ session: opts?.session },
|
|
||||||
);
|
|
||||||
return cursor;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getCursorExtended<T>(
|
|
||||||
this: plugins.tsclass.typeFest.Class<T>,
|
|
||||||
filterArg: plugins.tsclass.typeFest.PartialDeep<T>,
|
|
||||||
modifierFunction = (cursorArg: plugins.mongodb.FindCursor<plugins.mongodb.WithId<plugins.mongodb.BSON.Document>>) => cursorArg,
|
|
||||||
): Promise<SmartdataDbCursor<T>> {
|
): Promise<SmartdataDbCursor<T>> {
|
||||||
const collection: SmartdataCollection<T> = (this as any).collection;
|
const collection: SmartdataCollection<T> = (this as any).collection;
|
||||||
|
const { session, modifier } = opts || {};
|
||||||
await collection.init();
|
await collection.init();
|
||||||
let cursor: plugins.mongodb.FindCursor<any> = collection.mongoDbCollection.find(
|
let rawCursor: plugins.mongodb.FindCursor<any> =
|
||||||
convertFilterForMongoDb(filterArg),
|
collection.mongoDbCollection.find(convertFilterForMongoDb(filterArg), { session });
|
||||||
);
|
if (modifier) {
|
||||||
cursor = modifierFunction(cursor);
|
rawCursor = modifier(rawCursor);
|
||||||
return new SmartdataDbCursor<T>(cursor, this as any as typeof SmartDataDbDoc);
|
}
|
||||||
|
return new SmartdataDbCursor<T>(rawCursor, this as any as typeof SmartDataDbDoc);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
x
Reference in New Issue
Block a user