Skip to content

Commit

Permalink
fix(NODE-3386): listCollections result type definition (#2866)
Browse files Browse the repository at this point in the history
  • Loading branch information
nbbeeken committed Jul 9, 2021
1 parent e0b3afe commit c12979a
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 10 deletions.
27 changes: 24 additions & 3 deletions src/db.ts
Expand Up @@ -34,7 +34,11 @@ import {
DropDatabaseOptions,
DropCollectionOptions
} from './operations/drop';
import { ListCollectionsCursor, ListCollectionsOptions } from './operations/list_collections';
import {
CollectionInfo,
ListCollectionsCursor,
ListCollectionsOptions
} from './operations/list_collections';
import { ProfilingLevelOperation, ProfilingLevelOptions } from './operations/profiling_level';
import { RemoveUserOperation, RemoveUserOptions } from './operations/remove_user';
import { RenameOperation, RenameOptions } from './operations/rename';
Expand Down Expand Up @@ -358,8 +362,25 @@ export class Db {
* @param filter - Query to filter collections by
* @param options - Optional settings for the command
*/
listCollections(filter?: Document, options?: ListCollectionsOptions): ListCollectionsCursor {
return new ListCollectionsCursor(this, filter || {}, resolveOptions(this, options));
listCollections(
filter: Document,
options: Exclude<ListCollectionsOptions, 'nameOnly'> & { nameOnly: true }
): ListCollectionsCursor<Pick<CollectionInfo, 'name' | 'type'>>;
listCollections(
filter: Document,
options: Exclude<ListCollectionsOptions, 'nameOnly'> & { nameOnly: false }
): ListCollectionsCursor<CollectionInfo>;
listCollections<
T extends Pick<CollectionInfo, 'name' | 'type'> | CollectionInfo =
| Pick<CollectionInfo, 'name' | 'type'>
| CollectionInfo
>(filter?: Document, options?: ListCollectionsOptions): ListCollectionsCursor<T>;
listCollections<
T extends Pick<CollectionInfo, 'name' | 'type'> | CollectionInfo =
| Pick<CollectionInfo, 'name' | 'type'>
| CollectionInfo
>(filter: Document = {}, options: ListCollectionsOptions = {}): ListCollectionsCursor<T> {
return new ListCollectionsCursor<T>(this, filter, resolveOptions(this, options));
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Expand Up @@ -269,7 +269,7 @@ export type {
IndexDirection
} from './operations/indexes';
export type { InsertOneResult, InsertOneOptions, InsertManyResult } from './operations/insert';
export type { ListCollectionsOptions } from './operations/list_collections';
export type { ListCollectionsOptions, CollectionInfo } from './operations/list_collections';
export type { ListDatabasesResult, ListDatabasesOptions } from './operations/list_databases';
export type {
MapFunction,
Expand Down
2 changes: 1 addition & 1 deletion src/operations/is_capped.ts
Expand Up @@ -22,7 +22,7 @@ export class IsCappedOperation extends AbstractOperation<boolean> {
coll.s.db
.listCollections(
{ name: coll.collectionName },
{ ...this.options, readPreference: this.readPreference, session }
{ ...this.options, nameOnly: false, readPreference: this.readPreference, session }
)
.toArray((err, collections) => {
if (err || !collections) return callback(err);
Expand Down
20 changes: 17 additions & 3 deletions src/operations/list_collections.ts
Expand Up @@ -2,7 +2,7 @@ import { CommandOperation, CommandOperationOptions } from './command';
import { Aspect, defineAspects } from './operation';
import { maxWireVersion, Callback, getTopology, MongoDBNamespace } from '../utils';
import * as CONSTANTS from '../constants';
import type { Document } from '../bson';
import type { Binary, Document } from '../bson';
import type { Server } from '../sdam/server';
import type { Db } from '../db';
import { AbstractCursor } from '../cursor/abstract_cursor';
Expand Down Expand Up @@ -105,7 +105,21 @@ export class ListCollectionsOperation extends CommandOperation<string[]> {
}

/** @public */
export class ListCollectionsCursor extends AbstractCursor {
export interface CollectionInfo extends Document {
name: string;
type?: string;
options?: Document;
info?: {
readOnly?: false;
uuid?: Binary;
};
idIndex?: Document;
}

/** @public */
export class ListCollectionsCursor<
T extends Pick<CollectionInfo, 'name' | 'type'> | CollectionInfo = CollectionInfo
> extends AbstractCursor<T> {
parent: Db;
filter: Document;
options?: ListCollectionsOptions;
Expand All @@ -117,7 +131,7 @@ export class ListCollectionsCursor extends AbstractCursor {
this.options = options;
}

clone(): ListCollectionsCursor {
clone(): ListCollectionsCursor<T> {
return new ListCollectionsCursor(this.parent, this.filter, {
...this.options,
...this.cursorOptions
Expand Down
4 changes: 2 additions & 2 deletions src/operations/options_operation.ts
Expand Up @@ -23,15 +23,15 @@ export class OptionsOperation extends AbstractOperation<Document> {
coll.s.db
.listCollections(
{ name: coll.collectionName },
{ ...this.options, readPreference: this.readPreference, session }
{ ...this.options, nameOnly: false, readPreference: this.readPreference, session }
)
.toArray((err, collections) => {
if (err || !collections) return callback(err);
if (collections.length === 0) {
return callback(new MongoDriverError(`collection ${coll.namespace} not found`));
}

callback(err, collections[0].options || null);
callback(err, collections[0].options);
});
}
}
45 changes: 45 additions & 0 deletions test/types/list_collections.test-d.ts
@@ -0,0 +1,45 @@
import { expectType, expectNotType } from 'tsd';

import { MongoClient } from '../../src/mongo_client';
import type { CollectionInfo, ListCollectionsCursor } from '../../src/operations/list_collections';

const db = new MongoClient('').db();

type EitherCollectionInfoResult = CollectionInfo | Pick<CollectionInfo, 'name' | 'type'>;

// We default to the CollectionInfo result type
expectType<ListCollectionsCursor<Pick<CollectionInfo, 'name' | 'type'> | CollectionInfo>>(
db.listCollections()
);
// By default it isn't narrowed to either type
expectNotType<ListCollectionsCursor<Pick<CollectionInfo, 'name' | 'type'>>>(db.listCollections());
expectNotType<ListCollectionsCursor<CollectionInfo>>(db.listCollections());

// Testing each argument variation
db.listCollections();
db.listCollections({ a: 2 });
db.listCollections({ a: 2 }, { batchSize: 2 });

const collections = await db.listCollections().toArray();
expectType<EitherCollectionInfoResult[]>(collections);

const nameOnly = await db.listCollections({}, { nameOnly: true }).toArray();
expectType<Pick<CollectionInfo, 'name' | 'type'>[]>(nameOnly);

const fullInfo = await db.listCollections({}, { nameOnly: false }).toArray();
expectType<CollectionInfo[]>(fullInfo);

const couldBeEither = await db.listCollections({}, { nameOnly: Math.random() > 0.5 }).toArray();
expectType<EitherCollectionInfoResult[]>(couldBeEither);

// Showing here that:
// regardless of the option the generic parameter can be used to coerce the result if need be
// note the nameOnly: false, yet strings are returned
const overridden = await db
.listCollections<Pick<CollectionInfo, 'name' | 'type'>>({}, { nameOnly: false })
.toArray();
expectType<Pick<CollectionInfo, 'name' | 'type'>[]>(overridden);
const overriddenWithToArray = await db
.listCollections({}, { nameOnly: false })
.toArray<Pick<CollectionInfo, 'name' | 'type'>>();
expectType<Pick<CollectionInfo, 'name' | 'type'>[]>(overriddenWithToArray);
File renamed without changes.

0 comments on commit c12979a

Please sign in to comment.