Skip to content

Commit 1c219ef

Browse files
nbbeekenljhaywar
authored andcommitted
fix(NODE-3546): revert findOne not found result type to null (#2945)
1 parent 33d16cb commit 1c219ef

File tree

6 files changed

+61
-44
lines changed

6 files changed

+61
-44
lines changed

src/collection.ts

+27-30
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { DEFAULT_PK_FACTORY, emitWarningOnce, maybePromise, resolveOptions } from './utils';
1+
import { DEFAULT_PK_FACTORY, emitWarningOnce, resolveOptions } from './utils';
22
import { ReadPreference, ReadPreferenceLike } from './read_preference';
33
import {
44
normalizeHintField,
@@ -99,7 +99,7 @@ import type {
9999

100100
/** @public */
101101
export interface ModifyResult<TSchema = Document> {
102-
value?: TSchema;
102+
value: TSchema | null;
103103
lastErrorObject?: Document;
104104
ok: 0 | 1;
105105
}
@@ -676,51 +676,48 @@ export class Collection<TSchema extends Document = Document> {
676676
* @param options - Optional settings for the command
677677
* @param callback - An optional callback, a Promise will be returned if none is provided
678678
*/
679-
findOne(): Promise<TSchema | undefined>;
680-
findOne(callback: Callback<TSchema | undefined>): void;
681-
findOne(filter: Filter<TSchema>): Promise<TSchema | undefined>;
682-
findOne(filter: Filter<TSchema>, callback: Callback<TSchema | undefined>): void;
683-
findOne(filter: Filter<TSchema>, options: FindOptions): Promise<TSchema | undefined>;
684-
findOne(
685-
filter: Filter<TSchema>,
686-
options: FindOptions,
687-
callback: Callback<TSchema | undefined>
688-
): void;
679+
findOne(): Promise<TSchema | null>;
680+
findOne(callback: Callback<TSchema | null>): void;
681+
findOne(filter: Filter<TSchema>): Promise<TSchema | null>;
682+
findOne(filter: Filter<TSchema>, callback: Callback<TSchema | null>): void;
683+
findOne(filter: Filter<TSchema>, options: FindOptions): Promise<TSchema | null>;
684+
findOne(filter: Filter<TSchema>, options: FindOptions, callback: Callback<TSchema | null>): void;
689685

690686
// allow an override of the schema.
691-
findOne<T = TSchema>(): Promise<T | undefined>;
692-
findOne<T = TSchema>(callback: Callback<T | undefined>): void;
693-
findOne<T = TSchema>(filter: Filter<TSchema>): Promise<T | undefined>;
694-
findOne<T = TSchema>(filter: Filter<TSchema>, options?: FindOptions): Promise<T | undefined>;
687+
findOne<T = TSchema>(): Promise<T | null>;
688+
findOne<T = TSchema>(callback: Callback<T | null>): void;
689+
findOne<T = TSchema>(filter: Filter<TSchema>): Promise<T | null>;
690+
findOne<T = TSchema>(filter: Filter<TSchema>, options?: FindOptions): Promise<T | null>;
695691
findOne<T = TSchema>(
696692
filter: Filter<TSchema>,
697693
options?: FindOptions,
698-
callback?: Callback<T | undefined>
694+
callback?: Callback<T | null>
699695
): void;
700696

701697
findOne(
702-
filter?: Filter<TSchema> | Callback<TSchema | undefined>,
703-
options?: FindOptions | Callback<TSchema | undefined>,
704-
callback?: Callback<TSchema>
705-
): Promise<TSchema | undefined> | void {
698+
filter?: Filter<TSchema> | Callback<TSchema | null>,
699+
options?: FindOptions | Callback<TSchema | null>,
700+
callback?: Callback<TSchema | null>
701+
): Promise<TSchema | null> | void {
706702
if (callback != null && typeof callback !== 'function') {
707703
throw new MongoInvalidArgumentError(
708704
'Third parameter to `findOne()` must be a callback or undefined'
709705
);
710706
}
711707

712-
if (typeof filter === 'function')
713-
(callback = filter as Callback<Document | undefined>), (filter = {}), (options = {});
714-
if (typeof options === 'function') (callback = options), (options = {});
708+
if (typeof filter === 'function') {
709+
callback = filter as Callback<TSchema | null>;
710+
filter = {};
711+
options = {};
712+
}
713+
if (typeof options === 'function') {
714+
callback = options;
715+
options = {};
716+
}
715717

716718
const finalFilter = filter ?? {};
717719
const finalOptions = options ?? {};
718-
return maybePromise(callback, callback =>
719-
this.find(finalFilter, finalOptions)
720-
.limit(-1)
721-
.batchSize(1)
722-
.next((error, result) => callback(error, result ?? undefined))
723-
);
720+
return this.find(finalFilter, finalOptions).limit(-1).batchSize(1).next(callback);
724721
}
725722

726723
/**

src/cursor/abstract_cursor.ts

+1
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ export abstract class AbstractCursor<
297297
/** Get the next available document from the cursor, returns null if no more documents are available. */
298298
next(): Promise<TSchema | null>;
299299
next(callback: Callback<TSchema | null>): void;
300+
next(callback?: Callback<TSchema | null>): Promise<TSchema | null> | void;
300301
next(callback?: Callback<TSchema | null>): Promise<TSchema | null> | void {
301302
return maybePromise(callback, done => {
302303
if (this[kId] === Long.ZERO) {

test/functional/crud_api.test.js

+22-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use strict';
22
const test = require('./shared').assert;
33
const { expect } = require('chai');
4-
const { ReturnDocument } = require('../../src');
4+
const { ReturnDocument, ObjectId } = require('../../src');
55
const setupDatabase = require('./shared').setupDatabase;
66

77
// instanceof cannot be use reliably to detect the new models in js due to scoping and new
@@ -14,6 +14,27 @@ describe('CRUD API', function () {
1414
return setupDatabase(this.configuration);
1515
});
1616

17+
it('should correctly execute findOne method using crud api', async function () {
18+
const configuration = this.configuration;
19+
const client = configuration.newClient();
20+
await client.connect();
21+
const db = client.db(configuration.db);
22+
const collection = db.collection('t');
23+
24+
await collection.insertOne({ findOneTest: 1 });
25+
26+
const findOneResult = await collection.findOne({ findOneTest: 1 });
27+
28+
expect(findOneResult).to.have.property('findOneTest', 1);
29+
expect(findOneResult).to.have.property('_id').that.is.instanceOf(ObjectId);
30+
31+
const findNoneResult = await collection.findOne({ findOneTest: 2 });
32+
expect(findNoneResult).to.be.null;
33+
34+
await collection.drop();
35+
await client.close();
36+
});
37+
1738
it('should correctly execute find method using crud api', {
1839
// Add a tag that our runner can trigger on
1940
// in this case we are setting that node needs to be higher than 0.10.X to run

test/functional/sessions.test.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -301,8 +301,8 @@ describe('Sessions', function () {
301301
controlSession = client.startSession();
302302

303303
// set up sessions with two sets of cluster times
304-
expect(await collection.findOne({}, { session: controlSession })).to.be.undefined;
305-
expect(await collection.findOne({}, { session: testSession })).to.be.undefined;
304+
expect(await collection.findOne({}, { session: controlSession })).to.be.null;
305+
expect(await collection.findOne({}, { session: testSession })).to.be.null;
306306
await collection.insertOne({ apple: 'green' });
307307
expect(await collection.findOne({}, { session: otherSession }))
308308
.property('apple')

test/types/community/collection/findX.test-d.ts

+7-9
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,7 @@ cursor.forEach(
8787
}
8888
);
8989

90-
expectType<Bag | undefined>(
91-
await collectionBag.findOne({ color: 'red' }, { projection: { cost: 1 } })
92-
);
90+
expectType<Bag | null>(await collectionBag.findOne({ color: 'red' }, { projection: { cost: 1 } }));
9391

9492
const overrideFind = await collectionBag.findOne<{ cost: number }>(
9593
{ color: 'white' },
@@ -98,7 +96,7 @@ const overrideFind = await collectionBag.findOne<{ cost: number }>(
9896
expectType<PropExists<typeof overrideFind, 'color'>>(false);
9997

10098
// Overriding findOne, makes the return that exact type
101-
expectType<{ cost: number } | undefined>(
99+
expectType<{ cost: number } | null>(
102100
await collectionBag.findOne<{ cost: number }>({ color: 'red' }, { projection: { cost: 1 } })
103101
);
104102

@@ -121,13 +119,13 @@ interface House {
121119

122120
const car = db.collection<Car>('car');
123121

124-
expectNotType<House | undefined>(await car.findOne({}));
122+
expectNotType<House | null>(await car.findOne({}));
125123

126124
interface Car {
127125
make: string;
128126
}
129127

130-
function printCar(car: Car | undefined) {
128+
function printCar(car: Car | null) {
131129
console.log(car ? `A car of ${car.make} make` : 'No car');
132130
}
133131

@@ -232,12 +230,12 @@ interface TypedDb extends Db {
232230
const typedDb = client.db('test2') as TypedDb;
233231

234232
const person = typedDb.collection('people').findOne({});
235-
expectType<Promise<Person | undefined>>(person);
233+
expectType<Promise<Person | null>>(person);
236234

237235
typedDb.collection('people').findOne({}, function (_err, person) {
238-
expectType<Person | undefined>(person);
236+
expectType<Person | null | undefined>(person); // null is if nothing is found, undefined is when there is an error defined
239237
});
240238

241239
typedDb.collection('things').findOne({}, function (_err, thing) {
242-
expectType<Thing | undefined>(thing);
240+
expectType<Thing | null | undefined>(thing);
243241
});

test/types/union_schema.test-d.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ expectAssignable<ShapeInsert>({ height: 4, width: 4 });
3131
expectAssignable<ShapeInsert>({ radius: 4 });
3232

3333
const c: Collection<Shape> = null as never;
34-
expectType<Promise<Shape | undefined>>(c.findOne({ height: 4, width: 4 }));
34+
expectType<Promise<Shape | null>>(c.findOne({ height: 4, width: 4 }));
3535
// collection API can only respect TSchema given, cannot pick a type inside a union
36-
expectNotType<Promise<Rectangle | undefined>>(c.findOne({ height: 4, width: 4 }));
36+
expectNotType<Promise<Rectangle | null>>(c.findOne({ height: 4, width: 4 }));
3737

3838
interface A {
3939
_id: number;

0 commit comments

Comments
 (0)