diff --git a/docs/api/model.md b/docs/api/model.md index c0d2ed7cc..b952bdd45 100644 --- a/docs/api/model.md +++ b/docs/api/model.md @@ -260,6 +260,33 @@ userProjected.firstName; // TypeScript error userProjected.lastName; // valid ``` +## `findCursor` + +Calls the MongoDB [`find()`](https://mongodb.github.io/node-mongodb-native/5.0/classes/Collection.html#find) method and returns the cursor. + +Useful when you want to process many records without loading them all into +memory at once. + +**Parameters:** + +| Name | Type | Attribute | +| --------- | ---------------------- | --------- | +| `filter` | `PaprFilter` | required | +| `options` | `FindOptions` | optional | + +**Example:** + +```ts +const cursor = await User.findCursor( + { active: true, email: { $exists: true } }, + { projection: { email: 1 } } +); + +for await (const user of cursor) { + await notify(user.email); +} +``` + ## `findOne` Calls the MongoDB [`findOne()`](https://mongodb.github.io/node-mongodb-native/5.0/classes/Collection.html#findOne) method. diff --git a/src/__tests__/model.test.ts b/src/__tests__/model.test.ts index 1203c9e7e..ddb115094 100644 --- a/src/__tests__/model.test.ts +++ b/src/__tests__/model.test.ts @@ -1,5 +1,5 @@ import { beforeEach, describe, expect, jest, test } from '@jest/globals'; -import { Collection, MongoError, ObjectId } from 'mongodb'; +import { Collection, FindCursor, MongoError, ObjectId } from 'mongodb'; import { expectType } from 'ts-expect'; import { Hooks } from '../hooks'; import { abstract, build, Model } from '../model'; @@ -902,6 +902,37 @@ describe('model', () => { }); }); + describe('findCursor', () => { + test('default', async () => { + const cursor = await simpleModel.findCursor({}); + + expectType>(cursor); + }); + + test('with projection', async () => { + const cursor = await simpleModel.findCursor( + {}, + { + projection: { + ...projection, + 'nested.direct': 1, + }, + } + ); + + expectType< + FindCursor<{ + _id: ObjectId; + foo: string; + ham?: Date; + nested?: { + direct: string; + }; + }> + >(cursor); + }); + }); + describe('findById', () => { test('default', async () => { const result = await simpleModel.findById('123456789012345678901234'); diff --git a/src/model.ts b/src/model.ts index 3ac5b3de4..fe8469d49 100644 --- a/src/model.ts +++ b/src/model.ts @@ -10,6 +10,7 @@ import type { DeleteResult, DistinctOptions, Filter, + FindCursor, FindOneAndDeleteOptions, FindOneAndUpdateOptions, FindOptions, @@ -86,6 +87,11 @@ export interface Model, 'projection'> & { projection?: TProjection } ) => Promise | null>; + findCursor: | undefined>( + filter: PaprFilter, + options?: Omit, 'projection'> & { projection?: TProjection } + ) => Promise>>; + findOne: | undefined>( filter: PaprFilter, options?: Omit, 'projection'> & { projection?: TProjection } @@ -158,6 +164,7 @@ export function abstract} + * @param [options] {FindOptions} + * + * @example + * const cursor = await User.findCursor( + * { active: true, email: { $exists: true } }, + * { projection: { email: 1 } } + * ) + * + * for await (const user of cursor) { + * await notify(user.email); + * } + */ + model.findCursor = wrap(model, async function findCursor< + TProjection extends Projection | undefined + >(filter: PaprFilter, options?: Omit, 'projection'> & { projection?: TProjection }): Promise< + FindCursor> + > { + return model.collection.find( + filter as Filter, + { + ...model.defaultOptions, + ...options, + } as FindOptions + ) as FindCursor>; + }); + /** * @description * Calls the MongoDB [`findOne()`](https://mongodb.github.io/node-mongodb-native/5.0/classes/Collection.html#findOne) method.