opusdev/vector-similarity-api
1
1import { type BSONSerializeOptions, type Document, resolveBSONOptions } from './bson';2import type {3 AnyBulkWriteOperation,4 BulkOperationBase,5 BulkWriteOptions,6 BulkWriteResult7} from './bulk/common';8import { OrderedBulkOperation } from './bulk/ordered';9import { UnorderedBulkOperation } from './bulk/unordered';10import { ChangeStream, type ChangeStreamDocument, type ChangeStreamOptions } from './change_stream';11import { AggregationCursor } from './cursor/aggregation_cursor';12import { FindCursor } from './cursor/find_cursor';13import { ListIndexesCursor } from './cursor/list_indexes_cursor';14import {15 ListSearchIndexesCursor,16 type ListSearchIndexesOptions17} from './cursor/list_search_indexes_cursor';18import type { Db } from './db';19import { MongoAPIError, MongoInvalidArgumentError, MongoOperationTimeoutError } from './error';20import { type ExplainCommandOptions, type ExplainVerbosityLike } from './explain';21import type { MongoClient, PkFactory } from './mongo_client';22import type {23 Abortable,24 Filter,25 Flatten,26 OptionalUnlessRequiredId,27 TODO_NODE_3286,28 UpdateFilter,29 WithId,30 WithoutId31} from './mongo_types';32import type { AggregateOptions } from './operations/aggregate';33import { CountOperation, type CountOptions } from './operations/count';34import {35 DeleteManyOperation,36 DeleteOneOperation,37 type DeleteOptions,38 type DeleteResult39} from './operations/delete';40import { DistinctOperation, type DistinctOptions } from './operations/distinct';41import { type DropCollectionOptions } from './operations/drop';42import {43 EstimatedDocumentCountOperation,44 type EstimatedDocumentCountOptions45} from './operations/estimated_document_count';46import { autoConnect, executeOperation } from './operations/execute_operation';47import { type FindOneOptions, type FindOptions } from './operations/find';48import {49 FindOneAndDeleteOperation,50 type FindOneAndDeleteOptions,51 FindOneAndReplaceOperation,52 type FindOneAndReplaceOptions,53 FindOneAndUpdateOperation,54 type FindOneAndUpdateOptions55} from './operations/find_and_modify';56import {57 CreateIndexesOperation,58 type CreateIndexesOptions,59 type DropIndexesOptions,60 DropIndexOperation,61 type IndexDescription,62 type IndexDescriptionCompact,63 type IndexDescriptionInfo,64 type IndexInformationOptions,65 type IndexSpecification,66 type ListIndexesOptions67} from './operations/indexes';68import {69 type InsertManyResult,70 InsertOneOperation,71 type InsertOneOptions,72 type InsertOneResult73} from './operations/insert';74import type { Hint, OperationOptions } from './operations/operation';75import { RenameOperation, type RenameOptions } from './operations/rename';76import {77 CreateSearchIndexesOperation,78 type SearchIndexDescription79} from './operations/search_indexes/create';80import { DropSearchIndexOperation } from './operations/search_indexes/drop';81import { UpdateSearchIndexOperation } from './operations/search_indexes/update';82import {83 ReplaceOneOperation,84 type ReplaceOptions,85 UpdateManyOperation,86 UpdateOneOperation,87 type UpdateOptions,88 type UpdateResult89} from './operations/update';90import { ReadConcern, type ReadConcernLike } from './read_concern';91import { ReadPreference, type ReadPreferenceLike } from './read_preference';92import { type Sort } from './sort';93import {94 DEFAULT_PK_FACTORY,95 MongoDBCollectionNamespace,96 normalizeHintField,97 resolveOptions98} from './utils';99import { WriteConcern, type WriteConcernOptions } from './write_concern';100 101/** @public */102export interface ModifyResult<TSchema = Document> {103 value: WithId<TSchema> | null;104 lastErrorObject?: Document;105 ok: 0 | 1;106}107 108/** @public */109export interface CountDocumentsOptions extends AggregateOptions {110 /** The number of documents to skip. */111 skip?: number;112 /** The maximum amount of documents to consider. */113 limit?: number;114}115 116/** @public */117export interface CollectionOptions extends BSONSerializeOptions, WriteConcernOptions {118 /** Specify a read concern for the collection. (only MongoDB 3.2 or higher supported) */119 readConcern?: ReadConcernLike;120 /** The preferred read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). */121 readPreference?: ReadPreferenceLike;122 /**123 * @experimental124 * Specifies the time an operation will run until it throws a timeout error125 */126 timeoutMS?: number;127}128 129/** @internal */130export interface CollectionPrivate {131 pkFactory: PkFactory;132 db: Db;133 options: any;134 namespace: MongoDBCollectionNamespace;135 readPreference?: ReadPreference;136 bsonOptions: BSONSerializeOptions;137 collectionHint?: Hint;138 readConcern?: ReadConcern;139 writeConcern?: WriteConcern;140}141 142/**143 * The **Collection** class is an internal class that embodies a MongoDB collection144 * allowing for insert/find/update/delete and other command operation on that MongoDB collection.145 *146 * **COLLECTION Cannot directly be instantiated**147 * @public148 *149 * @example150 * ```ts151 * import { MongoClient } from 'mongodb';152 *153 * interface Pet {154 * name: string;155 * kind: 'dog' | 'cat' | 'fish';156 * }157 *158 * const client = new MongoClient('mongodb://localhost:27017');159 * const pets = client.db().collection<Pet>('pets');160 *161 * const petCursor = pets.find();162 *163 * for await (const pet of petCursor) {164 * console.log(`${pet.name} is a ${pet.kind}!`);165 * }166 * ```167 */168export class Collection<TSchema extends Document = Document> {169 /** @internal */170 s: CollectionPrivate;171 172 /** @internal */173 client: MongoClient;174 175 /**176 * Get the database object for the collection.177 */178 readonly db: Db;179 180 /**181 * Create a new Collection instance182 * @internal183 */184 constructor(db: Db, name: string, options?: CollectionOptions) {185 this.db = db;186 // Internal state187 this.s = {188 db,189 options,190 namespace: new MongoDBCollectionNamespace(db.databaseName, name),191 pkFactory: db.options?.pkFactory ?? DEFAULT_PK_FACTORY,192 readPreference: ReadPreference.fromOptions(options),193 bsonOptions: resolveBSONOptions(options, db),194 readConcern: ReadConcern.fromOptions(options),195 writeConcern: WriteConcern.fromOptions(options)196 };197 198 this.client = db.client;199 }200 201 /**202 * The name of the database this collection belongs to203 */204 get dbName(): string {205 return this.s.namespace.db;206 }207 208 /**209 * The name of this collection210 */211 get collectionName(): string {212 return this.s.namespace.collection;213 }214 215 /**216 * The namespace of this collection, in the format `${this.dbName}.${this.collectionName}`217 */218 get namespace(): string {219 return this.fullNamespace.toString();220 }221 222 /**223 * @internal224 *225 * The `MongoDBNamespace` for the collection.226 */227 get fullNamespace(): MongoDBCollectionNamespace {228 return this.s.namespace;229 }230 231 /**232 * The current readConcern of the collection. If not explicitly defined for233 * this collection, will be inherited from the parent DB234 */235 get readConcern(): ReadConcern | undefined {236 if (this.s.readConcern == null) {237 return this.db.readConcern;238 }239 return this.s.readConcern;240 }241 242 /**243 * The current readPreference of the collection. If not explicitly defined for244 * this collection, will be inherited from the parent DB245 */246 get readPreference(): ReadPreference | undefined {247 if (this.s.readPreference == null) {248 return this.db.readPreference;249 }250 251 return this.s.readPreference;252 }253 254 get bsonOptions(): BSONSerializeOptions {255 return this.s.bsonOptions;256 }257 258 /**259 * The current writeConcern of the collection. If not explicitly defined for260 * this collection, will be inherited from the parent DB261 */262 get writeConcern(): WriteConcern | undefined {263 if (this.s.writeConcern == null) {264 return this.db.writeConcern;265 }266 return this.s.writeConcern;267 }268 269 /** The current index hint for the collection */270 get hint(): Hint | undefined {271 return this.s.collectionHint;272 }273 274 set hint(v: Hint | undefined) {275 this.s.collectionHint = normalizeHintField(v);276 }277 278 public get timeoutMS(): number | undefined {279 return this.s.options.timeoutMS;280 }281 282 /**283 * Inserts a single document into MongoDB. If documents passed in do not contain the **_id** field,284 * one will be added to each of the documents missing it by the driver, mutating the document. This behavior285 * can be overridden by setting the **forceServerObjectId** flag.286 *287 * @param doc - The document to insert288 * @param options - Optional settings for the command289 */290 async insertOne(291 doc: OptionalUnlessRequiredId<TSchema>,292 options?: InsertOneOptions293 ): Promise<InsertOneResult<TSchema>> {294 return await executeOperation(295 this.client,296 new InsertOneOperation(297 this as TODO_NODE_3286,298 doc,299 resolveOptions(this, options)300 ) as TODO_NODE_3286301 );302 }303 304 /**305 * Inserts an array of documents into MongoDB. If documents passed in do not contain the **_id** field,306 * one will be added to each of the documents missing it by the driver, mutating the document. This behavior307 * can be overridden by setting the **forceServerObjectId** flag.308 *309 * @param docs - The documents to insert310 * @param options - Optional settings for the command311 */312 async insertMany(313 docs: ReadonlyArray<OptionalUnlessRequiredId<TSchema>>,314 options?: BulkWriteOptions315 ): Promise<InsertManyResult<TSchema>> {316 if (!Array.isArray(docs)) {317 throw new MongoInvalidArgumentError('Argument "docs" must be an array of documents');318 }319 options = resolveOptions(this, options ?? {});320 321 const acknowledged = WriteConcern.fromOptions(options)?.w !== 0;322 323 try {324 const res = await this.bulkWrite(325 docs.map(doc => ({ insertOne: { document: doc } })),326 options327 );328 return {329 acknowledged,330 insertedCount: res.insertedCount,331 insertedIds: res.insertedIds332 };333 } catch (err) {334 if (err && err.message === 'Operation must be an object with an operation key') {335 throw new MongoInvalidArgumentError(336 'Collection.insertMany() cannot be called with an array that has null/undefined values'337 );338 }339 throw err;340 }341 }342 343 /**344 * Perform a bulkWrite operation without a fluent API345 *346 * Legal operation types are347 * - `insertOne`348 * - `replaceOne`349 * - `updateOne`350 * - `updateMany`351 * - `deleteOne`352 * - `deleteMany`353 *354 * If documents passed in do not contain the **_id** field,355 * one will be added to each of the documents missing it by the driver, mutating the document. This behavior356 * can be overridden by setting the **forceServerObjectId** flag.357 *358 * @param operations - Bulk operations to perform359 * @param options - Optional settings for the command360 * @throws MongoDriverError if operations is not an array361 */362 async bulkWrite(363 operations: ReadonlyArray<AnyBulkWriteOperation<TSchema>>,364 options?: BulkWriteOptions365 ): Promise<BulkWriteResult> {366 if (!Array.isArray(operations)) {367 throw new MongoInvalidArgumentError('Argument "operations" must be an array of documents');368 }369 370 options = resolveOptions(this, options ?? {});371 372 // TODO(NODE-7071): remove once the client doesn't need to be connected to construct373 // bulk operations374 const isConnected = this.client.topology != null;375 if (!isConnected) {376 await autoConnect(this.client);377 }378 379 // Create the bulk operation380 const bulk: BulkOperationBase =381 options.ordered === false382 ? this.initializeUnorderedBulkOp(options)383 : this.initializeOrderedBulkOp(options);384 385 // for each op go through and add to the bulk386 for (const operation of operations) {387 bulk.raw(operation);388 }389 390 // Execute the bulk391 return await bulk.execute({ ...options });392 }393 394 /**395 * Update a single document in a collection396 *397 * The value of `update` can be either:398 * - UpdateFilter<TSchema> - A document that contains update operator expressions,399 * - Document[] - an aggregation pipeline.400 *401 * @param filter - The filter used to select the document to update402 * @param update - The modifications to apply403 * @param options - Optional settings for the command404 */405 async updateOne(406 filter: Filter<TSchema>,407 update: UpdateFilter<TSchema> | Document[],408 options?: UpdateOptions & { sort?: Sort }409 ): Promise<UpdateResult<TSchema>> {410 return await executeOperation(411 this.client,412 new UpdateOneOperation(this.s.namespace, filter, update, resolveOptions(this, options))413 );414 }415 416 /**417 * Replace a document in a collection with another document418 *419 * @param filter - The filter used to select the document to replace420 * @param replacement - The Document that replaces the matching document421 * @param options - Optional settings for the command422 */423 async replaceOne(424 filter: Filter<TSchema>,425 replacement: WithoutId<TSchema>,426 options?: ReplaceOptions427 ): Promise<UpdateResult<TSchema>> {428 return await executeOperation(429 this.client,430 new ReplaceOneOperation(this.s.namespace, filter, replacement, resolveOptions(this, options))431 );432 }433 434 /**435 * Update multiple documents in a collection436 *437 * The value of `update` can be either:438 * - UpdateFilter<TSchema> - A document that contains update operator expressions,439 * - Document[] - an aggregation pipeline.440 *441 * @param filter - The filter used to select the document to update442 * @param update - The modifications to apply443 * @param options - Optional settings for the command444 */445 async updateMany(446 filter: Filter<TSchema>,447 update: UpdateFilter<TSchema> | Document[],448 options?: UpdateOptions449 ): Promise<UpdateResult<TSchema>> {450 return await executeOperation(451 this.client,452 new UpdateManyOperation(this.s.namespace, filter, update, resolveOptions(this, options))453 );454 }455 456 /**457 * Delete a document from a collection458 *459 * @param filter - The filter used to select the document to remove460 * @param options - Optional settings for the command461 */462 async deleteOne(463 filter: Filter<TSchema> = {},464 options: DeleteOptions = {}465 ): Promise<DeleteResult> {466 return await executeOperation(467 this.client,468 new DeleteOneOperation(this.s.namespace, filter, resolveOptions(this, options))469 );470 }471 472 /**473 * Delete multiple documents from a collection474 *475 * @param filter - The filter used to select the documents to remove476 * @param options - Optional settings for the command477 */478 async deleteMany(479 filter: Filter<TSchema> = {},480 options: DeleteOptions = {}481 ): Promise<DeleteResult> {482 return await executeOperation(483 this.client,484 new DeleteManyOperation(this.s.namespace, filter, resolveOptions(this, options))485 );486 }487 488 /**489 * Rename the collection.490 *491 * @remarks492 * This operation does not inherit options from the Db or MongoClient.493 *494 * @param newName - New name of of the collection.495 * @param options - Optional settings for the command496 */497 async rename(newName: string, options?: RenameOptions): Promise<Collection> {498 // Intentionally, we do not inherit options from parent for this operation.499 return await executeOperation(500 this.client,501 new RenameOperation(502 this as TODO_NODE_3286,503 newName,504 resolveOptions(undefined, {505 ...options,506 readPreference: ReadPreference.PRIMARY507 })508 )509 );510 }511 512 /**513 * Drop the collection from the database, removing it permanently. New accesses will create a new collection.514 *515 * @param options - Optional settings for the command516 */517 async drop(options?: DropCollectionOptions): Promise<boolean> {518 return await this.db.dropCollection(this.collectionName, options);519 }520 521 /**522 * Fetches the first document that matches the filter523 *524 * @param filter - Query for find Operation525 * @param options - Optional settings for the command526 */527 async findOne(): Promise<WithId<TSchema> | null>;528 async findOne(filter: Filter<TSchema>): Promise<WithId<TSchema> | null>;529 async findOne(530 filter: Filter<TSchema>,531 options: Omit<FindOneOptions, 'timeoutMode'> & Abortable532 ): Promise<WithId<TSchema> | null>;533 534 // allow an override of the schema.535 async findOne<T = TSchema>(): Promise<T | null>;536 async findOne<T = TSchema>(filter: Filter<TSchema>): Promise<T | null>;537 async findOne<T = TSchema>(538 filter: Filter<TSchema>,539 options?: Omit<FindOneOptions, 'timeoutMode'> & Abortable540 ): Promise<T | null>;541 542 async findOne(543 filter: Filter<TSchema> = {},544 options: Omit<FindOneOptions, 'timeoutMode'> & Abortable = {}545 ): Promise<WithId<TSchema> | null> {546 // Explicitly set the limit to 1 and singleBatch to true for all commands, per the spec.547 // noCursorTimeout must be unset as well as batchSize.548 // See: https://github.com/mongodb/specifications/blob/master/source/crud/crud.md#findone-api-details549 const { batchSize: _batchSize, noCursorTimeout: _noCursorTimeout, ...opts } = options;550 opts.singleBatch = true;551 const cursor = this.find(filter, opts).limit(1);552 const result = await cursor.next();553 await cursor.close();554 return result;555 }556 557 /**558 * Creates a cursor for a filter that can be used to iterate over results from MongoDB559 *560 * @param filter - The filter predicate. If unspecified, then all documents in the collection will match the predicate561 */562 find(): FindCursor<WithId<TSchema>>;563 find(filter: Filter<TSchema>, options?: FindOptions & Abortable): FindCursor<WithId<TSchema>>;564 find<T extends Document>(565 filter: Filter<TSchema>,566 options?: FindOptions & Abortable567 ): FindCursor<T>;568 find(569 filter: Filter<TSchema> = {},570 options: FindOptions & Abortable = {}571 ): FindCursor<WithId<TSchema>> {572 return new FindCursor<WithId<TSchema>>(573 this.client,574 this.s.namespace,575 filter,576 resolveOptions(this, options)577 );578 }579 580 /**581 * Returns the options of the collection.582 *583 * @param options - Optional settings for the command584 */585 async options(options?: OperationOptions): Promise<Document> {586 options = resolveOptions(this, options);587 const [collection] = await this.db588 .listCollections({ name: this.collectionName }, { ...options, nameOnly: false })589 .toArray();590 591 if (collection == null || collection.options == null) {592 throw new MongoAPIError(`collection ${this.namespace} not found`);593 }594 595 return collection.options;596 }597 598 /**599 * Returns if the collection is a capped collection600 *601 * @param options - Optional settings for the command602 */603 async isCapped(options?: OperationOptions): Promise<boolean> {604 const { capped } = await this.options(options);605 return Boolean(capped);606 }607 608 /**609 * Creates an index on the db and collection collection.610 *611 * @param indexSpec - The field name or index specification to create an index for612 * @param options - Optional settings for the command613 *614 * @example615 * ```ts616 * const collection = client.db('foo').collection('bar');617 *618 * await collection.createIndex({ a: 1, b: -1 });619 *620 * // Alternate syntax for { c: 1, d: -1 } that ensures order of indexes621 * await collection.createIndex([ [c, 1], [d, -1] ]);622 *623 * // Equivalent to { e: 1 }624 * await collection.createIndex('e');625 *626 * // Equivalent to { f: 1, g: 1 }627 * await collection.createIndex(['f', 'g'])628 *629 * // Equivalent to { h: 1, i: -1 }630 * await collection.createIndex([ { h: 1 }, { i: -1 } ]);631 *632 * // Equivalent to { j: 1, k: -1, l: 2d }633 * await collection.createIndex(['j', ['k', -1], { l: '2d' }])634 * ```635 */636 async createIndex(637 indexSpec: IndexSpecification,638 options?: CreateIndexesOptions639 ): Promise<string> {640 const indexes = await executeOperation(641 this.client,642 CreateIndexesOperation.fromIndexSpecification(643 this,644 this.collectionName,645 indexSpec,646 resolveOptions(this, options)647 )648 );649 650 return indexes[0];651 }652 653 /**654 * Creates multiple indexes in the collection, this method is only supported for655 * MongoDB 2.6 or higher. Earlier version of MongoDB will throw a command not supported656 * error.657 *658 * **Note**: Unlike {@link Collection#createIndex| createIndex}, this function takes in raw index specifications.659 * Index specifications are defined {@link https://www.mongodb.com/docs/manual/reference/command/createIndexes/| here}.660 *661 * @param indexSpecs - An array of index specifications to be created662 * @param options - Optional settings for the command663 *664 * @example665 * ```ts666 * const collection = client.db('foo').collection('bar');667 * await collection.createIndexes([668 * // Simple index on field fizz669 * {670 * key: { fizz: 1 },671 * }672 * // wildcard index673 * {674 * key: { '$**': 1 }675 * },676 * // named index on darmok and jalad677 * {678 * key: { darmok: 1, jalad: -1 }679 * name: 'tanagra'680 * }681 * ]);682 * ```683 */684 async createIndexes(685 indexSpecs: IndexDescription[],686 options?: CreateIndexesOptions687 ): Promise<string[]> {688 return await executeOperation(689 this.client,690 CreateIndexesOperation.fromIndexDescriptionArray(691 this,692 this.collectionName,693 indexSpecs,694 resolveOptions(this, { ...options, maxTimeMS: undefined })695 )696 );697 }698 699 /**700 * Drops an index from this collection.701 *702 * @param indexName - Name of the index to drop.703 * @param options - Optional settings for the command704 */705 async dropIndex(indexName: string, options?: DropIndexesOptions): Promise<Document> {706 return await executeOperation(707 this.client,708 new DropIndexOperation(this as TODO_NODE_3286, indexName, {709 ...resolveOptions(this, options),710 readPreference: ReadPreference.primary711 })712 );713 }714 715 /**716 * Drops all indexes from this collection.717 *718 * @param options - Optional settings for the command719 */720 async dropIndexes(options?: DropIndexesOptions): Promise<boolean> {721 try {722 await executeOperation(723 this.client,724 new DropIndexOperation(this as TODO_NODE_3286, '*', resolveOptions(this, options))725 );726 return true;727 } catch (error) {728 // TODO(NODE-6517): Driver should only filter for namespace not found error. Other errors should be thrown.729 if (error instanceof MongoOperationTimeoutError) throw error;730 return false;731 }732 }733 734 /**735 * Get the list of all indexes information for the collection.736 *737 * @param options - Optional settings for the command738 */739 listIndexes(options?: ListIndexesOptions): ListIndexesCursor {740 return new ListIndexesCursor(this as TODO_NODE_3286, resolveOptions(this, options));741 }742 743 /**744 * Checks if one or more indexes exist on the collection, fails on first non-existing index745 *746 * @param indexes - One or more index names to check.747 * @param options - Optional settings for the command748 */749 async indexExists(indexes: string | string[], options?: ListIndexesOptions): Promise<boolean> {750 const indexNames: string[] = Array.isArray(indexes) ? indexes : [indexes];751 const allIndexes: Set<string> = new Set(752 await this.listIndexes(options)753 .map(({ name }) => name)754 .toArray()755 );756 return indexNames.every(name => allIndexes.has(name));757 }758 759 /**760 * Retrieves this collections index info.761 *762 * @param options - Optional settings for the command763 */764 indexInformation(765 options: IndexInformationOptions & { full: true }766 ): Promise<IndexDescriptionInfo[]>;767 indexInformation(768 options: IndexInformationOptions & { full?: false }769 ): Promise<IndexDescriptionCompact>;770 indexInformation(771 options: IndexInformationOptions772 ): Promise<IndexDescriptionCompact | IndexDescriptionInfo[]>;773 indexInformation(): Promise<IndexDescriptionCompact>;774 async indexInformation(775 options?: IndexInformationOptions776 ): Promise<IndexDescriptionCompact | IndexDescriptionInfo[]> {777 return await this.indexes({778 ...options,779 full: options?.full ?? false780 });781 }782 783 /**784 * Gets an estimate of the count of documents in a collection using collection metadata.785 * This will always run a count command on all server versions.786 *787 * due to an oversight in versions 5.0.0-5.0.8 of MongoDB, the count command,788 * which estimatedDocumentCount uses in its implementation, was not included in v1 of789 * the Stable API, and so users of the Stable API with estimatedDocumentCount are790 * recommended to upgrade their server version to 5.0.9+ or set apiStrict: false to avoid791 * encountering errors.792 *793 * @see {@link https://www.mongodb.com/docs/manual/reference/command/count/#behavior|Count: Behavior}794 * @param options - Optional settings for the command795 */796 async estimatedDocumentCount(options?: EstimatedDocumentCountOptions): Promise<number> {797 return await executeOperation(798 this.client,799 new EstimatedDocumentCountOperation(this as TODO_NODE_3286, resolveOptions(this, options))800 );801 }802 803 /**804 * Gets the number of documents matching the filter.805 * For a fast count of the total documents in a collection see {@link Collection#estimatedDocumentCount| estimatedDocumentCount}.806 *807 * Due to countDocuments using the $match aggregation pipeline stage, certain query operators cannot be used in countDocuments. This includes the $where and $near query operators, among others. Details can be found in the documentation for the $match aggregation pipeline stage.808 *809 * **Note**: When migrating from {@link Collection#count| count} to {@link Collection#countDocuments| countDocuments}810 * the following query operators must be replaced:811 *812 * | Operator | Replacement |813 * | -------- | ----------- |814 * | `$where` | [`$expr`][1] |815 * | `$near` | [`$geoWithin`][2] with [`$center`][3] |816 * | `$nearSphere` | [`$geoWithin`][2] with [`$centerSphere`][4] |817 *818 * [1]: https://www.mongodb.com/docs/manual/reference/operator/query/expr/819 * [2]: https://www.mongodb.com/docs/manual/reference/operator/query/geoWithin/820 * [3]: https://www.mongodb.com/docs/manual/reference/operator/query/center/#op._S_center821 * [4]: https://www.mongodb.com/docs/manual/reference/operator/query/centerSphere/#op._S_centerSphere822 *823 * @param filter - The filter for the count824 * @param options - Optional settings for the command825 *826 * @see https://www.mongodb.com/docs/manual/reference/operator/query/expr/827 * @see https://www.mongodb.com/docs/manual/reference/operator/query/geoWithin/828 * @see https://www.mongodb.com/docs/manual/reference/operator/query/center/#op._S_center829 * @see https://www.mongodb.com/docs/manual/reference/operator/query/centerSphere/#op._S_centerSphere830 */831 async countDocuments(832 filter: Filter<TSchema> = {},833 options: CountDocumentsOptions & Abortable = {}834 ): Promise<number> {835 const pipeline = [];836 pipeline.push({ $match: filter });837 838 if (typeof options.skip === 'number') {839 pipeline.push({ $skip: options.skip });840 }841 842 if (typeof options.limit === 'number') {843 pipeline.push({ $limit: options.limit });844 }845 846 pipeline.push({ $group: { _id: 1, n: { $sum: 1 } } });847 848 const cursor = this.aggregate<{ n: number }>(pipeline, options);849 const doc = await cursor.next();850 await cursor.close();851 return doc?.n ?? 0;852 }853 854 /**855 * The distinct command returns a list of distinct values for the given key across a collection.856 *857 * @param key - Field of the document to find distinct values for858 * @param filter - The filter for filtering the set of documents to which we apply the distinct filter.859 * @param options - Optional settings for the command860 */861 distinct<Key extends keyof WithId<TSchema>>(862 key: Key863 ): Promise<Array<Flatten<WithId<TSchema>[Key]>>>;864 distinct<Key extends keyof WithId<TSchema>>(865 key: Key,866 filter: Filter<TSchema>867 ): Promise<Array<Flatten<WithId<TSchema>[Key]>>>;868 distinct<Key extends keyof WithId<TSchema>>(869 key: Key,870 filter: Filter<TSchema>,871 options: DistinctOptions872 ): Promise<Array<Flatten<WithId<TSchema>[Key]>>>;873 distinct<Key extends keyof WithId<TSchema>>(874 key: Key,875 filter: Filter<TSchema>,876 options: DistinctOptions & { explain: ExplainVerbosityLike | ExplainCommandOptions }877 ): Promise<Document>;878 879 // Embedded documents overload880 distinct(key: string): Promise<any[]>;881 distinct(key: string, filter: Filter<TSchema>): Promise<any[]>;882 distinct(key: string, filter: Filter<TSchema>, options: DistinctOptions): Promise<any[]>;883 884 async distinct<Key extends keyof WithId<TSchema>>(885 key: Key,886 filter: Filter<TSchema> = {},887 options: DistinctOptions = {}888 ): Promise<any[]> {889 return await executeOperation(890 this.client,891 new DistinctOperation(892 this as TODO_NODE_3286,893 key as TODO_NODE_3286,894 filter,895 resolveOptions(this, options)896 )897 );898 }899 900 /**901 * Retrieve all the indexes on the collection.902 *903 * @param options - Optional settings for the command904 */905 indexes(options: IndexInformationOptions & { full?: true }): Promise<IndexDescriptionInfo[]>;906 indexes(options: IndexInformationOptions & { full: false }): Promise<IndexDescriptionCompact>;907 indexes(908 options: IndexInformationOptions909 ): Promise<IndexDescriptionCompact | IndexDescriptionInfo[]>;910 indexes(options?: ListIndexesOptions): Promise<IndexDescriptionInfo[]>;911 async indexes(912 options?: IndexInformationOptions913 ): Promise<IndexDescriptionCompact | IndexDescriptionInfo[]> {914 const indexes: IndexDescriptionInfo[] = await this.listIndexes(options).toArray();915 const full = options?.full ?? true;916 if (full) {917 return indexes;918 }919 920 const object: IndexDescriptionCompact = Object.fromEntries(921 indexes.map(({ name, key }) => [name, Object.entries(key)])922 );923 924 return object;925 }926 927 /**928 * Find a document and delete it in one atomic operation. Requires a write lock for the duration of the operation.929 *930 * @param filter - The filter used to select the document to remove931 * @param options - Optional settings for the command932 */933 async findOneAndDelete(934 filter: Filter<TSchema>,935 options: FindOneAndDeleteOptions & { includeResultMetadata: true }936 ): Promise<ModifyResult<TSchema>>;937 async findOneAndDelete(938 filter: Filter<TSchema>,939 options: FindOneAndDeleteOptions & { includeResultMetadata: false }940 ): Promise<WithId<TSchema> | null>;941 async findOneAndDelete(942 filter: Filter<TSchema>,943 options: FindOneAndDeleteOptions944 ): Promise<WithId<TSchema> | null>;945 async findOneAndDelete(filter: Filter<TSchema>): Promise<WithId<TSchema> | null>;946 async findOneAndDelete(947 filter: Filter<TSchema>,948 options?: FindOneAndDeleteOptions949 ): Promise<WithId<TSchema> | ModifyResult<TSchema> | null> {950 return await executeOperation(951 this.client,952 new FindOneAndDeleteOperation(953 this as TODO_NODE_3286,954 filter,955 resolveOptions(this, options)956 ) as TODO_NODE_3286957 );958 }959 960 /**961 * Find a document and replace it in one atomic operation. Requires a write lock for the duration of the operation.962 *963 * @param filter - The filter used to select the document to replace964 * @param replacement - The Document that replaces the matching document965 * @param options - Optional settings for the command966 */967 async findOneAndReplace(968 filter: Filter<TSchema>,969 replacement: WithoutId<TSchema>,970 options: FindOneAndReplaceOptions & { includeResultMetadata: true }971 ): Promise<ModifyResult<TSchema>>;972 async findOneAndReplace(973 filter: Filter<TSchema>,974 replacement: WithoutId<TSchema>,975 options: FindOneAndReplaceOptions & { includeResultMetadata: false }976 ): Promise<WithId<TSchema> | null>;977 async findOneAndReplace(978 filter: Filter<TSchema>,979 replacement: WithoutId<TSchema>,980 options: FindOneAndReplaceOptions981 ): Promise<WithId<TSchema> | null>;982 async findOneAndReplace(983 filter: Filter<TSchema>,984 replacement: WithoutId<TSchema>985 ): Promise<WithId<TSchema> | null>;986 async findOneAndReplace(987 filter: Filter<TSchema>,988 replacement: WithoutId<TSchema>,989 options?: FindOneAndReplaceOptions990 ): Promise<WithId<TSchema> | ModifyResult<TSchema> | null> {991 return await executeOperation(992 this.client,993 new FindOneAndReplaceOperation(994 this as TODO_NODE_3286,995 filter,996 replacement,997 resolveOptions(this, options)998 ) as TODO_NODE_3286999 );1000 }1001 1002 /**1003 * Find a document and update it in one atomic operation. Requires a write lock for the duration of the operation.1004 *1005 * The value of `update` can be either:1006 * - UpdateFilter<TSchema> - A document that contains update operator expressions,1007 * - Document[] - an aggregation pipeline consisting of the following stages:1008 * - $addFields and its alias $set1009 * - $project and its alias $unset1010 * - $replaceRoot and its alias $replaceWith.1011 * See the [findAndModify command documentation](https://www.mongodb.com/docs/manual/reference/command/findAndModify) for details.1012 *1013 * @param filter - The filter used to select the document to update1014 * @param update - The modifications to apply1015 * @param options - Optional settings for the command1016 */1017 async findOneAndUpdate(1018 filter: Filter<TSchema>,1019 update: UpdateFilter<TSchema> | Document[],1020 options: FindOneAndUpdateOptions & { includeResultMetadata: true }1021 ): Promise<ModifyResult<TSchema>>;1022 async findOneAndUpdate(1023 filter: Filter<TSchema>,1024 update: UpdateFilter<TSchema> | Document[],1025 options: FindOneAndUpdateOptions & { includeResultMetadata: false }1026 ): Promise<WithId<TSchema> | null>;1027 async findOneAndUpdate(1028 filter: Filter<TSchema>,1029 update: UpdateFilter<TSchema> | Document[],1030 options: FindOneAndUpdateOptions1031 ): Promise<WithId<TSchema> | null>;1032 async findOneAndUpdate(1033 filter: Filter<TSchema>,1034 update: UpdateFilter<TSchema> | Document[]1035 ): Promise<WithId<TSchema> | null>;1036 async findOneAndUpdate(1037 filter: Filter<TSchema>,1038 update: UpdateFilter<TSchema> | Document[],1039 options?: FindOneAndUpdateOptions1040 ): Promise<WithId<TSchema> | ModifyResult<TSchema> | null> {1041 return await executeOperation(1042 this.client,1043 new FindOneAndUpdateOperation(1044 this as TODO_NODE_3286,1045 filter,1046 update,1047 resolveOptions(this, options)1048 ) as TODO_NODE_32861049 );1050 }1051 1052 /**1053 * Execute an aggregation framework pipeline against the collection, needs MongoDB \>= 2.21054 *1055 * @param pipeline - An array of aggregation pipelines to execute1056 * @param options - Optional settings for the command1057 */1058 aggregate<T extends Document = Document>(1059 pipeline: Document[] = [],1060 options?: AggregateOptions & Abortable1061 ): AggregationCursor<T> {1062 if (!Array.isArray(pipeline)) {1063 throw new MongoInvalidArgumentError(1064 'Argument "pipeline" must be an array of aggregation stages'1065 );1066 }1067 1068 return new AggregationCursor(1069 this.client,1070 this.s.namespace,1071 pipeline,1072 resolveOptions(this, options)1073 );1074 }1075 1076 /**1077 * Create a new Change Stream, watching for new changes (insertions, updates, replacements, deletions, and invalidations) in this collection.1078 *1079 * @remarks1080 * watch() accepts two generic arguments for distinct use cases:1081 * - The first is to override the schema that may be defined for this specific collection1082 * - The second is to override the shape of the change stream document entirely, if it is not provided the type will default to ChangeStreamDocument of the first argument1083 * @example1084 * By just providing the first argument I can type the change to be `ChangeStreamDocument<{ _id: number }>`1085 * ```ts1086 * collection.watch<{ _id: number }>()1087 * .on('change', change => console.log(change._id.toFixed(4)));1088 * ```1089 *1090 * @example1091 * Passing a second argument provides a way to reflect the type changes caused by an advanced pipeline.1092 * Here, we are using a pipeline to have MongoDB filter for insert changes only and add a comment.1093 * No need start from scratch on the ChangeStreamInsertDocument type!1094 * By using an intersection we can save time and ensure defaults remain the same type!1095 * ```ts1096 * collection1097 * .watch<Schema, ChangeStreamInsertDocument<Schema> & { comment: string }>([1098 * { $addFields: { comment: 'big changes' } },1099 * { $match: { operationType: 'insert' } }1100 * ])1101 * .on('change', change => {1102 * change.comment.startsWith('big');1103 * change.operationType === 'insert';1104 * // No need to narrow in code because the generics did that for us!1105 * expectType<Schema>(change.fullDocument);1106 * });1107 * ```1108 *1109 * @remarks1110 * When `timeoutMS` is configured for a change stream, it will have different behaviour depending1111 * on whether the change stream is in iterator mode or emitter mode. In both cases, a change1112 * stream will time out if it does not receive a change event within `timeoutMS` of the last change1113 * event.1114 *1115 * Note that if a change stream is consistently timing out when watching a collection, database or1116 * client that is being changed, then this may be due to the server timing out before it can finish1117 * processing the existing oplog. To address this, restart the change stream with a higher1118 * `timeoutMS`.1119 *1120 * If the change stream times out the initial aggregate operation to establish the change stream on1121 * the server, then the client will close the change stream. If the getMore calls to the server1122 * time out, then the change stream will be left open, but will throw a MongoOperationTimeoutError1123 * when in iterator mode and emit an error event that returns a MongoOperationTimeoutError in1124 * emitter mode.1125 *1126 * To determine whether or not the change stream is still open following a timeout, check the1127 * {@link ChangeStream.closed} getter.1128 *1129 * @example1130 * In iterator mode, if a next() call throws a timeout error, it will attempt to resume the change stream.1131 * The next call can just be retried after this succeeds.1132 * ```ts1133 * const changeStream = collection.watch([], { timeoutMS: 100 });1134 * try {1135 * await changeStream.next();1136 * } catch (e) {1137 * if (e instanceof MongoOperationTimeoutError && !changeStream.closed) {1138 * await changeStream.next();1139 * }1140 * throw e;1141 * }1142 * ```1143 *1144 * @example1145 * In emitter mode, if the change stream goes `timeoutMS` without emitting a change event, it will1146 * emit an error event that returns a MongoOperationTimeoutError, but will not close the change1147 * stream unless the resume attempt fails. There is no need to re-establish change listeners as1148 * this will automatically continue emitting change events once the resume attempt completes.1149 *1150 * ```ts1151 * const changeStream = collection.watch([], { timeoutMS: 100 });1152 * changeStream.on('change', console.log);1153 * changeStream.on('error', e => {1154 * if (e instanceof MongoOperationTimeoutError && !changeStream.closed) {1155 * // do nothing1156 * } else {1157 * changeStream.close();1158 * }1159 * });1160 * ```1161 *1162 * @param pipeline - An array of {@link https://www.mongodb.com/docs/manual/reference/operator/aggregation-pipeline/|aggregation pipeline stages} through which to pass change stream documents. This allows for filtering (using $match) and manipulating the change stream documents.1163 * @param options - Optional settings for the command1164 * @typeParam TLocal - Type of the data being detected by the change stream1165 * @typeParam TChange - Type of the whole change stream document emitted1166 */1167 watch<TLocal extends Document = TSchema, TChange extends Document = ChangeStreamDocument<TLocal>>(1168 pipeline: Document[] = [],1169 options: ChangeStreamOptions = {}1170 ): ChangeStream<TLocal, TChange> {1171 // Allow optionally not specifying a pipeline1172 if (!Array.isArray(pipeline)) {1173 options = pipeline;1174 pipeline = [];1175 }1176 1177 return new ChangeStream<TLocal, TChange>(this, pipeline, resolveOptions(this, options));1178 }1179 1180 /**1181 * Initiate an Out of order batch write operation. All operations will be buffered into insert/update/remove commands executed out of order.1182 *1183 * @throws MongoNotConnectedError1184 * @remarks1185 * **NOTE:** MongoClient must be connected prior to calling this method due to a known limitation in this legacy implementation.1186 * However, `collection.bulkWrite()` provides an equivalent API that does not require prior connecting.1187 */1188 initializeUnorderedBulkOp(options?: BulkWriteOptions): UnorderedBulkOperation {1189 return new UnorderedBulkOperation(this as TODO_NODE_3286, resolveOptions(this, options));1190 }1191 1192 /**1193 * Initiate an In order bulk write operation. Operations will be serially executed in the order they are added, creating a new operation for each switch in types.1194 *1195 * @throws MongoNotConnectedError1196 * @remarks1197 * **NOTE:** MongoClient must be connected prior to calling this method due to a known limitation in this legacy implementation.1198 * However, `collection.bulkWrite()` provides an equivalent API that does not require prior connecting.1199 */1200 initializeOrderedBulkOp(options?: BulkWriteOptions): OrderedBulkOperation {