Skip to content

docs(NODE-6765): sync types for findOneAndUpdate #4423

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
20 changes: 14 additions & 6 deletions src/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -966,32 +966,40 @@ export class Collection<TSchema extends Document = Document> {
/**
* Find a document and update it in one atomic operation. Requires a write lock for the duration of the operation.
*
* The value of `update` can be either:
* - UpdateFilter<TSchema> - A document that contains update operator expressions,
* - Document[] - an aggregation pipeline consisting of the following stages:
* - $addFields and its alias $set
* - $project and its alias $unset
* - $replaceRoot and its alias $replaceWith.
* See the [findAndModify command documentation](https://www.mongodb.com/docs/manual/reference/command/findAndModify) for details.
*
* @param filter - The filter used to select the document to update
* @param update - Update operations to be performed on the document
* @param update - The modifications to apply
* @param options - Optional settings for the command
*/
async findOneAndUpdate(
filter: Filter<TSchema>,
update: UpdateFilter<TSchema>,
update: UpdateFilter<TSchema> | Document[],
options: FindOneAndUpdateOptions & { includeResultMetadata: true }
): Promise<ModifyResult<TSchema>>;
async findOneAndUpdate(
filter: Filter<TSchema>,
update: UpdateFilter<TSchema>,
update: UpdateFilter<TSchema> | Document[],
options: FindOneAndUpdateOptions & { includeResultMetadata: false }
): Promise<WithId<TSchema> | null>;
async findOneAndUpdate(
filter: Filter<TSchema>,
update: UpdateFilter<TSchema>,
update: UpdateFilter<TSchema> | Document[],
options: FindOneAndUpdateOptions
): Promise<WithId<TSchema> | null>;
async findOneAndUpdate(
filter: Filter<TSchema>,
update: UpdateFilter<TSchema>
update: UpdateFilter<TSchema> | Document[]
): Promise<WithId<TSchema> | null>;
async findOneAndUpdate(
filter: Filter<TSchema>,
update: UpdateFilter<TSchema>,
update: UpdateFilter<TSchema> | Document[],
options?: FindOneAndUpdateOptions
): Promise<WithId<TSchema> | ModifyResult<TSchema> | null> {
return await executeOperation(
Expand Down
81 changes: 80 additions & 1 deletion test/integration/crud/find_and_modify.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { expect } from 'chai';

import { type CommandStartedEvent, MongoServerError, ObjectId } from '../../mongodb';
import {
type Collection,
type CommandStartedEvent,
type MongoClient,
MongoServerError,
ObjectId
} from '../../mongodb';
import { setupDatabase } from '../shared';

describe('Collection (#findOneAnd...)', function () {
Expand Down Expand Up @@ -324,6 +330,79 @@ describe('Collection (#findOneAnd...)', function () {
});
});
});

context('when updating with an aggregation pipeline', function () {
context('when passing includeResultMetadata: true', function () {
let client: MongoClient;
let collection: Collection<{ a: number; b: number }>;

beforeEach(async function () {
client = this.configuration.newClient({}, { maxPoolSize: 1 });
collection = client.db('test').collection('findAndModifyTest');
await collection.insertMany([{ a: 1, b: 1 }], { writeConcern: { w: 1 } });
});

afterEach(async function () {
await collection.drop();
await client?.close();
});

it(
'the aggregation pipeline updates the matching document',
{
requires: {
mongodb: '>4.0'
}
},
async function () {
const {
value: { _id, ...document }
} = await collection.findOneAndUpdate(
{ a: 1 },
[{ $set: { a: { $add: [1, '$a'] } } }],
{
includeResultMetadata: true,
returnDocument: 'after'
}
);
expect(document).to.deep.equal({ a: 2, b: 1 });
}
);
});

context('when passing includeResultMetadata: false', function () {
let client: MongoClient;
let collection: Collection<{ a: number; b: number }>;

beforeEach(async function () {
client = this.configuration.newClient({}, { maxPoolSize: 1 });
collection = client.db('test').collection('findAndModifyTest');
await collection.insertMany([{ a: 1, b: 1 }], { writeConcern: { w: 1 } });
});

afterEach(async function () {
await collection.drop();
await client?.close();
});

it(
'the aggregation pipeline updates the matching document',
{
requires: {
mongodb: '>4.0'
}
},
async function () {
const { _id, ...document } = await collection.findOneAndUpdate(
{ a: 1 },
[{ $set: { a: { $add: [1, '$a'] } } }],
{ returnDocument: 'after' }
);
expect(document).to.deep.equal({ a: 2, b: 1 });
}
);
});
});
});

describe('#findOneAndReplace', function () {
Expand Down
11 changes: 11 additions & 0 deletions test/types/community/collection/findX.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,3 +388,14 @@ expectType<WithId<{ a: number; b: string }> | null>(
}
)
);

// the update operator can be an aggregation pipeline
expectType<WithId<{ a: number; b: string }> | null>(
await coll.findOneAndUpdate({ a: 3 }, [
{
$set: {
a: 5
}
}
])
);