Skip to content

Commit c12979a

Browse files
authored
fix(NODE-3386): listCollections result type definition (#2866)
1 parent e0b3afe commit c12979a

File tree

7 files changed

+90
-10
lines changed

7 files changed

+90
-10
lines changed

src/db.ts

+24-3
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,11 @@ import {
3434
DropDatabaseOptions,
3535
DropCollectionOptions
3636
} from './operations/drop';
37-
import { ListCollectionsCursor, ListCollectionsOptions } from './operations/list_collections';
37+
import {
38+
CollectionInfo,
39+
ListCollectionsCursor,
40+
ListCollectionsOptions
41+
} from './operations/list_collections';
3842
import { ProfilingLevelOperation, ProfilingLevelOptions } from './operations/profiling_level';
3943
import { RemoveUserOperation, RemoveUserOptions } from './operations/remove_user';
4044
import { RenameOperation, RenameOptions } from './operations/rename';
@@ -358,8 +362,25 @@ export class Db {
358362
* @param filter - Query to filter collections by
359363
* @param options - Optional settings for the command
360364
*/
361-
listCollections(filter?: Document, options?: ListCollectionsOptions): ListCollectionsCursor {
362-
return new ListCollectionsCursor(this, filter || {}, resolveOptions(this, options));
365+
listCollections(
366+
filter: Document,
367+
options: Exclude<ListCollectionsOptions, 'nameOnly'> & { nameOnly: true }
368+
): ListCollectionsCursor<Pick<CollectionInfo, 'name' | 'type'>>;
369+
listCollections(
370+
filter: Document,
371+
options: Exclude<ListCollectionsOptions, 'nameOnly'> & { nameOnly: false }
372+
): ListCollectionsCursor<CollectionInfo>;
373+
listCollections<
374+
T extends Pick<CollectionInfo, 'name' | 'type'> | CollectionInfo =
375+
| Pick<CollectionInfo, 'name' | 'type'>
376+
| CollectionInfo
377+
>(filter?: Document, options?: ListCollectionsOptions): ListCollectionsCursor<T>;
378+
listCollections<
379+
T extends Pick<CollectionInfo, 'name' | 'type'> | CollectionInfo =
380+
| Pick<CollectionInfo, 'name' | 'type'>
381+
| CollectionInfo
382+
>(filter: Document = {}, options: ListCollectionsOptions = {}): ListCollectionsCursor<T> {
383+
return new ListCollectionsCursor<T>(this, filter, resolveOptions(this, options));
363384
}
364385

365386
/**

src/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ export type {
269269
IndexDirection
270270
} from './operations/indexes';
271271
export type { InsertOneResult, InsertOneOptions, InsertManyResult } from './operations/insert';
272-
export type { ListCollectionsOptions } from './operations/list_collections';
272+
export type { ListCollectionsOptions, CollectionInfo } from './operations/list_collections';
273273
export type { ListDatabasesResult, ListDatabasesOptions } from './operations/list_databases';
274274
export type {
275275
MapFunction,

src/operations/is_capped.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export class IsCappedOperation extends AbstractOperation<boolean> {
2222
coll.s.db
2323
.listCollections(
2424
{ name: coll.collectionName },
25-
{ ...this.options, readPreference: this.readPreference, session }
25+
{ ...this.options, nameOnly: false, readPreference: this.readPreference, session }
2626
)
2727
.toArray((err, collections) => {
2828
if (err || !collections) return callback(err);

src/operations/list_collections.ts

+17-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { CommandOperation, CommandOperationOptions } from './command';
22
import { Aspect, defineAspects } from './operation';
33
import { maxWireVersion, Callback, getTopology, MongoDBNamespace } from '../utils';
44
import * as CONSTANTS from '../constants';
5-
import type { Document } from '../bson';
5+
import type { Binary, Document } from '../bson';
66
import type { Server } from '../sdam/server';
77
import type { Db } from '../db';
88
import { AbstractCursor } from '../cursor/abstract_cursor';
@@ -105,7 +105,21 @@ export class ListCollectionsOperation extends CommandOperation<string[]> {
105105
}
106106

107107
/** @public */
108-
export class ListCollectionsCursor extends AbstractCursor {
108+
export interface CollectionInfo extends Document {
109+
name: string;
110+
type?: string;
111+
options?: Document;
112+
info?: {
113+
readOnly?: false;
114+
uuid?: Binary;
115+
};
116+
idIndex?: Document;
117+
}
118+
119+
/** @public */
120+
export class ListCollectionsCursor<
121+
T extends Pick<CollectionInfo, 'name' | 'type'> | CollectionInfo = CollectionInfo
122+
> extends AbstractCursor<T> {
109123
parent: Db;
110124
filter: Document;
111125
options?: ListCollectionsOptions;
@@ -117,7 +131,7 @@ export class ListCollectionsCursor extends AbstractCursor {
117131
this.options = options;
118132
}
119133

120-
clone(): ListCollectionsCursor {
134+
clone(): ListCollectionsCursor<T> {
121135
return new ListCollectionsCursor(this.parent, this.filter, {
122136
...this.options,
123137
...this.cursorOptions

src/operations/options_operation.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,15 @@ export class OptionsOperation extends AbstractOperation<Document> {
2323
coll.s.db
2424
.listCollections(
2525
{ name: coll.collectionName },
26-
{ ...this.options, readPreference: this.readPreference, session }
26+
{ ...this.options, nameOnly: false, readPreference: this.readPreference, session }
2727
)
2828
.toArray((err, collections) => {
2929
if (err || !collections) return callback(err);
3030
if (collections.length === 0) {
3131
return callback(new MongoDriverError(`collection ${coll.namespace} not found`));
3232
}
3333

34-
callback(err, collections[0].options || null);
34+
callback(err, collections[0].options);
3535
});
3636
}
3737
}

test/types/list_collections.test-d.ts

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { expectType, expectNotType } from 'tsd';
2+
3+
import { MongoClient } from '../../src/mongo_client';
4+
import type { CollectionInfo, ListCollectionsCursor } from '../../src/operations/list_collections';
5+
6+
const db = new MongoClient('').db();
7+
8+
type EitherCollectionInfoResult = CollectionInfo | Pick<CollectionInfo, 'name' | 'type'>;
9+
10+
// We default to the CollectionInfo result type
11+
expectType<ListCollectionsCursor<Pick<CollectionInfo, 'name' | 'type'> | CollectionInfo>>(
12+
db.listCollections()
13+
);
14+
// By default it isn't narrowed to either type
15+
expectNotType<ListCollectionsCursor<Pick<CollectionInfo, 'name' | 'type'>>>(db.listCollections());
16+
expectNotType<ListCollectionsCursor<CollectionInfo>>(db.listCollections());
17+
18+
// Testing each argument variation
19+
db.listCollections();
20+
db.listCollections({ a: 2 });
21+
db.listCollections({ a: 2 }, { batchSize: 2 });
22+
23+
const collections = await db.listCollections().toArray();
24+
expectType<EitherCollectionInfoResult[]>(collections);
25+
26+
const nameOnly = await db.listCollections({}, { nameOnly: true }).toArray();
27+
expectType<Pick<CollectionInfo, 'name' | 'type'>[]>(nameOnly);
28+
29+
const fullInfo = await db.listCollections({}, { nameOnly: false }).toArray();
30+
expectType<CollectionInfo[]>(fullInfo);
31+
32+
const couldBeEither = await db.listCollections({}, { nameOnly: Math.random() > 0.5 }).toArray();
33+
expectType<EitherCollectionInfoResult[]>(couldBeEither);
34+
35+
// Showing here that:
36+
// regardless of the option the generic parameter can be used to coerce the result if need be
37+
// note the nameOnly: false, yet strings are returned
38+
const overridden = await db
39+
.listCollections<Pick<CollectionInfo, 'name' | 'type'>>({}, { nameOnly: false })
40+
.toArray();
41+
expectType<Pick<CollectionInfo, 'name' | 'type'>[]>(overridden);
42+
const overriddenWithToArray = await db
43+
.listCollections({}, { nameOnly: false })
44+
.toArray<Pick<CollectionInfo, 'name' | 'type'>>();
45+
expectType<Pick<CollectionInfo, 'name' | 'type'>[]>(overriddenWithToArray);
File renamed without changes.

0 commit comments

Comments
 (0)