diff --git a/src/cursor/abstract_cursor.ts b/src/cursor/abstract_cursor.ts index 8b85b5ea7e3..c6e45a91fc5 100644 --- a/src/cursor/abstract_cursor.ts +++ b/src/cursor/abstract_cursor.ts @@ -21,29 +21,6 @@ import type { Server } from '../sdam/server'; import { ClientSession, maybeClearPinnedConnection } from '../sessions'; import { List, type MongoDBNamespace, ns, squashError } from '../utils'; -/** @internal */ -const kId = Symbol('id'); -/** @internal */ -const kDocuments = Symbol('documents'); -/** @internal */ -const kServer = Symbol('server'); -/** @internal */ -const kNamespace = Symbol('namespace'); -/** @internal */ -const kClient = Symbol('client'); -/** @internal */ -const kSession = Symbol('session'); -/** @internal */ -const kOptions = Symbol('options'); -/** @internal */ -const kTransform = Symbol('transform'); -/** @internal */ -const kInitialized = Symbol('initialized'); -/** @internal */ -const kClosed = Symbol('closed'); -/** @internal */ -const kKilled = Symbol('killed'); - /** @public */ export const CURSOR_FLAGS = [ 'tailable', @@ -133,15 +110,15 @@ export abstract class AbstractCursor< CursorEvents extends AbstractCursorEvents = AbstractCursorEvents > extends TypedEventEmitter { /** @internal */ - private [kId]: Long | null; + private cursorId: Long | null; /** @internal */ - private [kSession]: ClientSession; + private cursorSession: ClientSession; /** @internal */ - private [kServer]?: Server; + private selectedServer?: Server; /** @internal */ - private [kNamespace]: MongoDBNamespace; + private cursorNamespace: MongoDBNamespace; /** @internal */ - [kDocuments]: { + private documents: { length: number; shift(bsonOptions?: any): TSchema | null; clear(): void; @@ -149,17 +126,17 @@ export abstract class AbstractCursor< push(item: TSchema): void; }; /** @internal */ - private [kClient]: MongoClient; + private cursorClient: MongoClient; /** @internal */ - private [kTransform]?: (doc: TSchema) => any; + private transform?: (doc: TSchema) => any; /** @internal */ - private [kInitialized]: boolean; + private initialized: boolean; /** @internal */ - private [kClosed]: boolean; + private isClosed: boolean; /** @internal */ - private [kKilled]: boolean; + private isKilled: boolean; /** @internal */ - private [kOptions]: InternalAbstractCursorOptions; + protected readonly cursorOptions: InternalAbstractCursorOptions; /** @event */ static readonly CLOSE = 'close' as const; @@ -175,121 +152,116 @@ export abstract class AbstractCursor< if (!client.s.isMongoClient) { throw new MongoRuntimeError('Cursor must be constructed with MongoClient'); } - this[kClient] = client; - this[kNamespace] = namespace; - this[kId] = null; - this[kDocuments] = new List(); - this[kInitialized] = false; - this[kClosed] = false; - this[kKilled] = false; - this[kOptions] = { + this.cursorClient = client; + this.cursorNamespace = namespace; + this.cursorId = null; + this.documents = new List(); + this.initialized = false; + this.isClosed = false; + this.isKilled = false; + this.cursorOptions = { readPreference: options.readPreference && options.readPreference instanceof ReadPreference ? options.readPreference : ReadPreference.primary, ...pluckBSONSerializeOptions(options) }; - this[kOptions].timeoutMS = options.timeoutMS; + this.cursorOptions.timeoutMS = options.timeoutMS; const readConcern = ReadConcern.fromOptions(options); if (readConcern) { - this[kOptions].readConcern = readConcern; + this.cursorOptions.readConcern = readConcern; } if (typeof options.batchSize === 'number') { - this[kOptions].batchSize = options.batchSize; + this.cursorOptions.batchSize = options.batchSize; } // we check for undefined specifically here to allow falsy values // eslint-disable-next-line no-restricted-syntax if (options.comment !== undefined) { - this[kOptions].comment = options.comment; + this.cursorOptions.comment = options.comment; } if (typeof options.maxTimeMS === 'number') { - this[kOptions].maxTimeMS = options.maxTimeMS; + this.cursorOptions.maxTimeMS = options.maxTimeMS; } if (typeof options.maxAwaitTimeMS === 'number') { - this[kOptions].maxAwaitTimeMS = options.maxAwaitTimeMS; + this.cursorOptions.maxAwaitTimeMS = options.maxAwaitTimeMS; } if (options.session instanceof ClientSession) { - this[kSession] = options.session; + this.cursorSession = options.session; } else { - this[kSession] = this[kClient].startSession({ owner: this, explicit: false }); + this.cursorSession = this.cursorClient.startSession({ owner: this, explicit: false }); } } get id(): Long | undefined { - return this[kId] ?? undefined; + return this.cursorId ?? undefined; } /** @internal */ get isDead() { - return (this[kId]?.isZero() ?? false) || this[kClosed] || this[kKilled]; + return (this.cursorId?.isZero() ?? false) || this.isClosed || this.isKilled; } /** @internal */ get client(): MongoClient { - return this[kClient]; + return this.cursorClient; } /** @internal */ get server(): Server | undefined { - return this[kServer]; + return this.selectedServer; } get namespace(): MongoDBNamespace { - return this[kNamespace]; + return this.cursorNamespace; } get readPreference(): ReadPreference { - return this[kOptions].readPreference; + return this.cursorOptions.readPreference; } get readConcern(): ReadConcern | undefined { - return this[kOptions].readConcern; + return this.cursorOptions.readConcern; } /** @internal */ get session(): ClientSession { - return this[kSession]; + return this.cursorSession; } set session(clientSession: ClientSession) { - this[kSession] = clientSession; - } - - /** @internal */ - get cursorOptions(): InternalAbstractCursorOptions { - return this[kOptions]; + this.cursorSession = clientSession; } get closed(): boolean { - return this[kClosed]; + return this.isClosed; } get killed(): boolean { - return this[kKilled]; + return this.isKilled; } get loadBalanced(): boolean { - return !!this[kClient].topology?.loadBalanced; + return !!this.cursorClient.topology?.loadBalanced; } /** Returns current buffered documents length */ bufferedCount(): number { - return this[kDocuments].length; + return this.documents.length; } /** Returns current buffered documents */ readBufferedDocuments(number?: number): TSchema[] { const bufferedDocs: TSchema[] = []; - const documentsToRead = Math.min(number ?? this[kDocuments].length, this[kDocuments].length); + const documentsToRead = Math.min(number ?? this.documents.length, this.documents.length); for (let count = 0; count < documentsToRead; count++) { - const document = this[kDocuments].shift(this[kOptions]); + const document = this.documents.shift(this.cursorOptions); if (document != null) { bufferedDocs.push(document); } @@ -299,21 +271,21 @@ export abstract class AbstractCursor< } async *[Symbol.asyncIterator](): AsyncGenerator { - if (this.closed) { + if (this.isClosed) { return; } try { while (true) { - if (this.killed) { + if (this.isKilled) { return; } - if (this.closed && this[kDocuments].length === 0) { + if (this.isClosed && this.documents.length === 0) { return; } - if (this[kId] != null && this.isDead && this[kDocuments].length === 0) { + if (this.cursorId != null && this.isDead && this.documents.length === 0) { return; } @@ -329,7 +301,7 @@ export abstract class AbstractCursor< } finally { // Only close the cursor if it has not already been closed. This finally clause handles // the case when a user would break out of a for await of loop early. - if (!this.closed) { + if (!this.isClosed) { try { await this.close(); } catch (error) { @@ -370,34 +342,34 @@ export abstract class AbstractCursor< } async hasNext(): Promise { - if (this[kId] === Long.ZERO) { + if (this.cursorId === Long.ZERO) { return false; } do { - if (this[kDocuments].length !== 0) { + if (this.documents.length !== 0) { return true; } await this.fetchBatch(); - } while (!this.isDead || this[kDocuments].length !== 0); + } while (!this.isDead || this.documents.length !== 0); return false; } /** Get the next available document from the cursor, returns null if no more documents are available. */ async next(): Promise { - if (this[kId] === Long.ZERO) { + if (this.cursorId === Long.ZERO) { throw new MongoCursorExhaustedError(); } do { - const doc = this[kDocuments].shift(); + const doc = this.documents.shift(); if (doc != null) { - if (this[kTransform] != null) return await this.transformDocument(doc); + if (this.transform != null) return await this.transformDocument(doc); return doc; } await this.fetchBatch(); - } while (!this.isDead || this[kDocuments].length !== 0); + } while (!this.isDead || this.documents.length !== 0); return null; } @@ -406,21 +378,21 @@ export abstract class AbstractCursor< * Try to get the next available document from the cursor or `null` if an empty batch is returned */ async tryNext(): Promise { - if (this[kId] === Long.ZERO) { + if (this.cursorId === Long.ZERO) { throw new MongoCursorExhaustedError(); } - let doc = this[kDocuments].shift(); + let doc = this.documents.shift(); if (doc != null) { - if (this[kTransform] != null) return await this.transformDocument(doc); + if (this.transform != null) return await this.transformDocument(doc); return doc; } await this.fetchBatch(); - doc = this[kDocuments].shift(); + doc = this.documents.shift(); if (doc != null) { - if (this[kTransform] != null) return await this.transformDocument(doc); + if (this.transform != null) return await this.transformDocument(doc); return doc; } @@ -481,7 +453,7 @@ export abstract class AbstractCursor< throw new MongoInvalidArgumentError(`Flag ${flag} must be a boolean value`); } - this[kOptions][flag] = value; + this.cursorOptions[flag] = value; return this; } @@ -529,13 +501,13 @@ export abstract class AbstractCursor< */ map(transform: (doc: TSchema) => T): AbstractCursor { this.throwIfInitialized(); - const oldTransform = this[kTransform] as (doc: TSchema) => TSchema; // TODO(NODE-3283): Improve transform typing + const oldTransform = this.transform; if (oldTransform) { - this[kTransform] = doc => { + this.transform = doc => { return transform(oldTransform(doc)); }; } else { - this[kTransform] = transform; + this.transform = transform; } return this as unknown as AbstractCursor; @@ -549,9 +521,9 @@ export abstract class AbstractCursor< withReadPreference(readPreference: ReadPreferenceLike): this { this.throwIfInitialized(); if (readPreference instanceof ReadPreference) { - this[kOptions].readPreference = readPreference; + this.cursorOptions.readPreference = readPreference; } else if (typeof readPreference === 'string') { - this[kOptions].readPreference = ReadPreference.fromString(readPreference); + this.cursorOptions.readPreference = ReadPreference.fromString(readPreference); } else { throw new MongoInvalidArgumentError(`Invalid read preference: ${readPreference}`); } @@ -568,7 +540,7 @@ export abstract class AbstractCursor< this.throwIfInitialized(); const resolvedReadConcern = ReadConcern.fromOptions({ readConcern }); if (resolvedReadConcern) { - this[kOptions].readConcern = resolvedReadConcern; + this.cursorOptions.readConcern = resolvedReadConcern; } return this; @@ -585,7 +557,7 @@ export abstract class AbstractCursor< throw new MongoInvalidArgumentError('Argument for maxTimeMS must be a number'); } - this[kOptions].maxTimeMS = value; + this.cursorOptions.maxTimeMS = value; return this; } @@ -596,7 +568,7 @@ export abstract class AbstractCursor< */ batchSize(value: number): this { this.throwIfInitialized(); - if (this[kOptions].tailable) { + if (this.cursorOptions.tailable) { throw new MongoTailableCursorError('Tailable cursor does not support batchSize'); } @@ -604,7 +576,7 @@ export abstract class AbstractCursor< throw new MongoInvalidArgumentError('Operation "batchSize" requires an integer'); } - this[kOptions].batchSize = value; + this.cursorOptions.batchSize = value; return this; } @@ -614,17 +586,17 @@ export abstract class AbstractCursor< * if the resultant data has already been retrieved by this cursor. */ rewind(): void { - if (!this[kInitialized]) { + if (!this.initialized) { return; } - this[kId] = null; - this[kDocuments].clear(); - this[kClosed] = false; - this[kKilled] = false; - this[kInitialized] = false; + this.cursorId = null; + this.documents.clear(); + this.isClosed = false; + this.isKilled = false; + this.initialized = false; - const session = this[kSession]; + const session = this.cursorSession; if (session) { // We only want to end this session if we created it, and it hasn't ended yet if (session.explicit === false) { @@ -632,7 +604,7 @@ export abstract class AbstractCursor< // eslint-disable-next-line github/no-then session.endSession().then(undefined, squashError); } - this[kSession] = this.client.startSession({ owner: this, explicit: false }); + this.cursorSession = this.cursorClient.startSession({ owner: this, explicit: false }); } } } @@ -647,15 +619,29 @@ export abstract class AbstractCursor< /** @internal */ async getMore(batchSize: number, useCursorResponse = false): Promise { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const getMoreOperation = new GetMoreOperation(this[kNamespace], this[kId]!, this[kServer]!, { - ...this[kOptions], - session: this[kSession], - batchSize, - useCursorResponse - }); + if (this.cursorId == null) { + throw new MongoRuntimeError( + 'Unexpected null cursor id. A cursor creating command should have set this' + ); + } + if (this.selectedServer == null) { + throw new MongoRuntimeError( + 'Unexpected null selectedServer. A cursor creating command should have set this' + ); + } + const getMoreOperation = new GetMoreOperation( + this.cursorNamespace, + this.cursorId, + this.selectedServer, + { + ...this.cursorOptions, + session: this.cursorSession, + batchSize, + useCursorResponse + } + ); - return await executeOperation(this[kClient], getMoreOperation); + return await executeOperation(this.cursorClient, getMoreOperation); } /** @@ -667,33 +653,33 @@ export abstract class AbstractCursor< */ private async cursorInit(): Promise { try { - const state = await this._initialize(this[kSession]); + const state = await this._initialize(this.cursorSession); const response = state.response; - this[kServer] = state.server; + this.selectedServer = state.server; if (CursorResponse.is(response)) { - this[kId] = response.id; - if (response.ns) this[kNamespace] = response.ns; - this[kDocuments] = response; + this.cursorId = response.id; + if (response.ns) this.cursorNamespace = response.ns; + this.documents = response; } else if (response.cursor) { // TODO(NODE-2674): Preserve int64 sent from MongoDB - this[kId] = getCursorId(response); - if (response.cursor.ns) this[kNamespace] = ns(response.cursor.ns); - this[kDocuments].pushMany(response.cursor.firstBatch); + this.cursorId = getCursorId(response); + if (response.cursor.ns) this.cursorNamespace = ns(response.cursor.ns); + this.documents.pushMany(response.cursor.firstBatch); } - if (this[kId] == null) { + if (this.cursorId == null) { // When server responses return without a cursor document, we close this cursor // and return the raw server response. This is the case for explain commands - this[kId] = Long.ZERO; + this.cursorId = Long.ZERO; // TODO(NODE-3286): ExecutionResult needs to accept a generic parameter - this[kDocuments].push(state.response as TODO_NODE_3286); + this.documents.push(state.response as TODO_NODE_3286); } // the cursor is now initialized, even if it is dead - this[kInitialized] = true; + this.initialized = true; } catch (error) { // the cursor is now initialized, even if an error occurred - this[kInitialized] = true; + this.initialized = true; await this.cleanup(error); throw error; } @@ -707,7 +693,7 @@ export abstract class AbstractCursor< /** @internal Attempt to obtain more documents */ private async fetchBatch(): Promise { - if (this.closed) { + if (this.isClosed) { return; } @@ -719,27 +705,27 @@ export abstract class AbstractCursor< return; } - if (this[kId] == null) { + if (this.cursorId == null) { await this.cursorInit(); // If the cursor died or returned documents, return - if (this[kDocuments].length !== 0 || this.isDead) return; + if (this.documents.length !== 0 || this.isDead) return; // Otherwise, run a getMore } // otherwise need to call getMore - const batchSize = this[kOptions].batchSize || 1000; + const batchSize = this.cursorOptions.batchSize || 1000; try { const response = await this.getMore(batchSize); // CursorResponse is disabled in this PR // however the special `emptyGetMore` can be returned from find cursors if (CursorResponse.is(response)) { - this[kId] = response.id; - this[kDocuments] = response; + this.cursorId = response.id; + this.documents = response; } else if (response?.cursor) { const cursorId = getCursorId(response); - this[kDocuments].pushMany(response.cursor.nextBatch); - this[kId] = cursorId; + this.documents.pushMany(response.cursor.nextBatch); + this.cursorId = cursorId; } } catch (error) { try { @@ -765,21 +751,23 @@ export abstract class AbstractCursor< /** @internal */ private async cleanup(error?: Error) { - this[kClosed] = true; - const session = this[kSession]; + this.isClosed = true; + const session = this.cursorSession; try { if ( - !this[kKilled] && - this[kId] && - !this[kId].isZero() && - this[kNamespace] && - this[kServer] && + !this.isKilled && + this.cursorId && + !this.cursorId.isZero() && + this.cursorNamespace && + this.selectedServer && !session.hasEnded ) { - this[kKilled] = true; + this.isKilled = true; await executeOperation( - this[kClient], - new KillCursorsOperation(this[kId], this[kNamespace], this[kServer], { session }) + this.cursorClient, + new KillCursorsOperation(this.cursorId, this.cursorNamespace, this.selectedServer, { + session + }) ); } } catch (error) { @@ -801,7 +789,7 @@ export abstract class AbstractCursor< /** @internal */ private emitClose() { try { - if (!this.hasEmittedClose && (this[kDocuments].length === 0 || this[kClosed])) { + if (!this.hasEmittedClose && (this.documents.length === 0 || this.isClosed)) { // @ts-expect-error: CursorEvents is generic so Parameters may not be assignable to `[]`. Not sure how to require extenders do not add parameters. this.emit('close'); } @@ -812,11 +800,10 @@ export abstract class AbstractCursor< /** @internal */ private async transformDocument(document: NonNullable): Promise { - const transform = this[kTransform]; - if (transform == null) return document; + if (this.transform == null) return document; try { - const transformedDocument = transform(document); + const transformedDocument = this.transform(document); // eslint-disable-next-line no-restricted-syntax if (transformedDocument === null) { const TRANSFORM_TO_NULL_ERROR = @@ -836,7 +823,7 @@ export abstract class AbstractCursor< /** @internal */ protected throwIfInitialized() { - if (this[kInitialized]) throw new MongoCursorInUseError(); + if (this.initialized) throw new MongoCursorInUseError(); } } @@ -879,7 +866,7 @@ class ReadableCursorStream extends Readable { } private _readNext() { - if (this._cursor[kId] === Long.ZERO) { + if (this._cursor.id === Long.ZERO) { this.push(null); return; } diff --git a/src/cursor/aggregation_cursor.ts b/src/cursor/aggregation_cursor.ts index a36bee85582..1c23b3e1a4b 100644 --- a/src/cursor/aggregation_cursor.ts +++ b/src/cursor/aggregation_cursor.ts @@ -13,11 +13,6 @@ import { AbstractCursor } from './abstract_cursor'; /** @public */ export interface AggregationCursorOptions extends AbstractCursorOptions, AggregateOptions {} -/** @internal */ -const kPipeline = Symbol('pipeline'); -/** @internal */ -const kOptions = Symbol('options'); - /** * The **AggregationCursor** class is an internal class that embodies an aggregation cursor on MongoDB * allowing for iteration over the results returned from the underlying query. It supports @@ -26,10 +21,9 @@ const kOptions = Symbol('options'); * @public */ export class AggregationCursor extends AbstractCursor { + public readonly pipeline: Document[]; /** @internal */ - [kPipeline]: Document[]; - /** @internal */ - [kOptions]: AggregateOptions; + private aggregateOptions: AggregateOptions; /** @internal */ constructor( @@ -40,18 +34,14 @@ export class AggregationCursor extends AbstractCursor { ) { super(client, namespace, options); - this[kPipeline] = pipeline; - this[kOptions] = options; - } - - get pipeline(): Document[] { - return this[kPipeline]; + this.pipeline = pipeline; + this.aggregateOptions = options; } clone(): AggregationCursor { - const clonedOptions = mergeOptions({}, this[kOptions]); + const clonedOptions = mergeOptions({}, this.aggregateOptions); delete clonedOptions.session; - return new AggregationCursor(this.client, this.namespace, this[kPipeline], { + return new AggregationCursor(this.client, this.namespace, this.pipeline, { ...clonedOptions }); } @@ -62,8 +52,8 @@ export class AggregationCursor extends AbstractCursor { /** @internal */ async _initialize(session: ClientSession): Promise { - const aggregateOperation = new AggregateOperation(this.namespace, this[kPipeline], { - ...this[kOptions], + const aggregateOperation = new AggregateOperation(this.namespace, this.pipeline, { + ...this.aggregateOptions, ...this.cursorOptions, session }); @@ -78,8 +68,8 @@ export class AggregationCursor extends AbstractCursor { async explain(verbosity?: ExplainVerbosityLike): Promise { return await executeOperation( this.client, - new AggregateOperation(this.namespace, this[kPipeline], { - ...this[kOptions], // NOTE: order matters here, we may need to refine this + new AggregateOperation(this.namespace, this.pipeline, { + ...this.aggregateOptions, // NOTE: order matters here, we may need to refine this ...this.cursorOptions, explain: verbosity ?? true }) @@ -102,7 +92,7 @@ export class AggregationCursor extends AbstractCursor { addStage(stage: Document): AggregationCursor; addStage(stage: Document): AggregationCursor { this.throwIfInitialized(); - this[kPipeline].push(stage); + this.pipeline.push(stage); return this as unknown as AggregationCursor; } diff --git a/src/cursor/change_stream_cursor.ts b/src/cursor/change_stream_cursor.ts index 31fda3308b4..df4e4895cf4 100644 --- a/src/cursor/change_stream_cursor.ts +++ b/src/cursor/change_stream_cursor.ts @@ -43,15 +43,12 @@ export class ChangeStreamCursor< TSchema extends Document = Document, TChange extends Document = ChangeStreamDocument > extends AbstractCursor { - _resumeToken: ResumeToken; - startAtOperationTime?: OperationTime; - hasReceived?: boolean; - resumeAfter: ResumeToken; - startAfter: ResumeToken; - options: ChangeStreamCursorOptions; - - postBatchResumeToken?: ResumeToken; - pipeline: Document[]; + private _resumeToken: ResumeToken; + private startAtOperationTime?: OperationTime; + private hasReceived?: boolean; + private readonly changeStreamCursorOptions: ChangeStreamCursorOptions; + private postBatchResumeToken?: ResumeToken; + private readonly pipeline: Document[]; /** * @internal @@ -69,7 +66,7 @@ export class ChangeStreamCursor< super(client, namespace, options); this.pipeline = pipeline; - this.options = options; + this.changeStreamCursorOptions = options; this._resumeToken = null; this.startAtOperationTime = options.startAtOperationTime; @@ -91,7 +88,7 @@ export class ChangeStreamCursor< get resumeOptions(): ChangeStreamCursorOptions { const options: ChangeStreamCursorOptions = { - ...this.options + ...this.changeStreamCursorOptions }; for (const key of ['resumeAfter', 'startAfter', 'startAtOperationTime'] as const) { @@ -99,7 +96,7 @@ export class ChangeStreamCursor< } if (this.resumeToken != null) { - if (this.options.startAfter && !this.hasReceived) { + if (this.changeStreamCursorOptions.startAfter && !this.hasReceived) { options.startAfter = this.resumeToken; } else { options.resumeAfter = this.resumeToken; @@ -142,7 +139,7 @@ export class ChangeStreamCursor< async _initialize(session: ClientSession): Promise { const aggregateOperation = new AggregateOperation(this.namespace, this.pipeline, { ...this.cursorOptions, - ...this.options, + ...this.changeStreamCursorOptions, session }); @@ -156,8 +153,8 @@ export class ChangeStreamCursor< if ( this.startAtOperationTime == null && - this.resumeAfter == null && - this.startAfter == null && + this.changeStreamCursorOptions.resumeAfter == null && + this.changeStreamCursorOptions.startAfter == null && this.maxWireVersion >= 7 ) { this.startAtOperationTime = response.operationTime; diff --git a/src/cursor/find_cursor.ts b/src/cursor/find_cursor.ts index bac7675eacd..e779af69cb1 100644 --- a/src/cursor/find_cursor.ts +++ b/src/cursor/find_cursor.ts @@ -13,13 +13,6 @@ import { formatSort, type Sort, type SortDirection } from '../sort'; import { emitWarningOnce, mergeOptions, type MongoDBNamespace, squashError } from '../utils'; import { AbstractCursor } from './abstract_cursor'; -/** @internal */ -const kFilter = Symbol('filter'); -/** @internal */ -const kNumReturned = Symbol('numReturned'); -/** @internal */ -const kBuiltOptions = Symbol('builtOptions'); - /** @public Flags allowed for cursor */ export const FLAGS = [ 'tailable', @@ -33,11 +26,11 @@ export const FLAGS = [ /** @public */ export class FindCursor extends AbstractCursor { /** @internal */ - [kFilter]: Document; + private cursorFilter: Document; /** @internal */ - [kNumReturned] = 0; + private numReturned = 0; /** @internal */ - [kBuiltOptions]: FindOptions; + private readonly findOptions: FindOptions; /** @internal */ constructor( @@ -48,18 +41,18 @@ export class FindCursor extends AbstractCursor { ) { super(client, namespace, options); - this[kFilter] = filter; - this[kBuiltOptions] = options; + this.cursorFilter = filter; + this.findOptions = options; if (options.sort != null) { - this[kBuiltOptions].sort = formatSort(options.sort); + this.findOptions.sort = formatSort(options.sort); } } clone(): FindCursor { - const clonedOptions = mergeOptions({}, this[kBuiltOptions]); + const clonedOptions = mergeOptions({}, this.findOptions); delete clonedOptions.session; - return new FindCursor(this.client, this.namespace, this[kFilter], { + return new FindCursor(this.client, this.namespace, this.cursorFilter, { ...clonedOptions }); } @@ -70,8 +63,8 @@ export class FindCursor extends AbstractCursor { /** @internal */ async _initialize(session: ClientSession): Promise { - const findOperation = new FindOperation(this.namespace, this[kFilter], { - ...this[kBuiltOptions], // NOTE: order matters here, we may need to refine this + const findOperation = new FindOperation(this.namespace, this.cursorFilter, { + ...this.findOptions, // NOTE: order matters here, we may need to refine this ...this.cursorOptions, session }); @@ -80,10 +73,10 @@ export class FindCursor extends AbstractCursor { // the response is not a cursor when `explain` is enabled if (CursorResponse.is(response)) { - this[kNumReturned] = response.batchSize; + this.numReturned = response.batchSize; } else { // Can be an explain response, hence the ?. on everything - this[kNumReturned] = this[kNumReturned] + (response?.cursor?.firstBatch?.length ?? 0); + this.numReturned = this.numReturned + (response?.cursor?.firstBatch?.length ?? 0); } // TODO: NODE-2882 @@ -92,10 +85,10 @@ export class FindCursor extends AbstractCursor { /** @internal */ override async getMore(batchSize: number): Promise { - const numReturned = this[kNumReturned]; + const numReturned = this.numReturned; if (numReturned) { // TODO(DRIVERS-1448): Remove logic to enforce `limit` in the driver - const limit = this[kBuiltOptions].limit; + const limit = this.findOptions.limit; batchSize = limit && limit > 0 && numReturned + batchSize > limit ? limit - numReturned : batchSize; @@ -120,9 +113,9 @@ export class FindCursor extends AbstractCursor { const response = await super.getMore(batchSize, false); // TODO: wrap this in some logic to prevent it from happening if we don't need this support if (CursorResponse.is(response)) { - this[kNumReturned] = this[kNumReturned] + response.batchSize; + this.numReturned = this.numReturned + response.batchSize; } else { - this[kNumReturned] = this[kNumReturned] + (response?.cursor?.nextBatch?.length ?? 0); + this.numReturned = this.numReturned + (response?.cursor?.nextBatch?.length ?? 0); } return response; @@ -141,8 +134,8 @@ export class FindCursor extends AbstractCursor { } return await executeOperation( this.client, - new CountOperation(this.namespace, this[kFilter], { - ...this[kBuiltOptions], // NOTE: order matters here, we may need to refine this + new CountOperation(this.namespace, this.cursorFilter, { + ...this.findOptions, // NOTE: order matters here, we may need to refine this ...this.cursorOptions, ...options }) @@ -153,8 +146,8 @@ export class FindCursor extends AbstractCursor { async explain(verbosity?: ExplainVerbosityLike): Promise { return await executeOperation( this.client, - new FindOperation(this.namespace, this[kFilter], { - ...this[kBuiltOptions], // NOTE: order matters here, we may need to refine this + new FindOperation(this.namespace, this.cursorFilter, { + ...this.findOptions, // NOTE: order matters here, we may need to refine this ...this.cursorOptions, explain: verbosity ?? true }) @@ -164,7 +157,7 @@ export class FindCursor extends AbstractCursor { /** Set the cursor query */ filter(filter: Document): this { this.throwIfInitialized(); - this[kFilter] = filter; + this.cursorFilter = filter; return this; } @@ -175,7 +168,7 @@ export class FindCursor extends AbstractCursor { */ hint(hint: Hint): this { this.throwIfInitialized(); - this[kBuiltOptions].hint = hint; + this.findOptions.hint = hint; return this; } @@ -186,7 +179,7 @@ export class FindCursor extends AbstractCursor { */ min(min: Document): this { this.throwIfInitialized(); - this[kBuiltOptions].min = min; + this.findOptions.min = min; return this; } @@ -197,7 +190,7 @@ export class FindCursor extends AbstractCursor { */ max(max: Document): this { this.throwIfInitialized(); - this[kBuiltOptions].max = max; + this.findOptions.max = max; return this; } @@ -210,7 +203,7 @@ export class FindCursor extends AbstractCursor { */ returnKey(value: boolean): this { this.throwIfInitialized(); - this[kBuiltOptions].returnKey = value; + this.findOptions.returnKey = value; return this; } @@ -221,7 +214,7 @@ export class FindCursor extends AbstractCursor { */ showRecordId(value: boolean): this { this.throwIfInitialized(); - this[kBuiltOptions].showRecordId = value; + this.findOptions.showRecordId = value; return this; } @@ -243,43 +236,43 @@ export class FindCursor extends AbstractCursor { // NOTE: consider some TS magic for this switch (field) { case 'comment': - this[kBuiltOptions].comment = value as string | Document; + this.findOptions.comment = value as string | Document; break; case 'explain': - this[kBuiltOptions].explain = value as boolean; + this.findOptions.explain = value as boolean; break; case 'hint': - this[kBuiltOptions].hint = value as string | Document; + this.findOptions.hint = value as string | Document; break; case 'max': - this[kBuiltOptions].max = value as Document; + this.findOptions.max = value as Document; break; case 'maxTimeMS': - this[kBuiltOptions].maxTimeMS = value as number; + this.findOptions.maxTimeMS = value as number; break; case 'min': - this[kBuiltOptions].min = value as Document; + this.findOptions.min = value as Document; break; case 'orderby': - this[kBuiltOptions].sort = formatSort(value as string | Document); + this.findOptions.sort = formatSort(value as string | Document); break; case 'query': - this[kFilter] = value as Document; + this.cursorFilter = value as Document; break; case 'returnKey': - this[kBuiltOptions].returnKey = value as boolean; + this.findOptions.returnKey = value as boolean; break; case 'showDiskLoc': - this[kBuiltOptions].showRecordId = value as boolean; + this.findOptions.showRecordId = value as boolean; break; default: @@ -296,7 +289,7 @@ export class FindCursor extends AbstractCursor { */ comment(value: string): this { this.throwIfInitialized(); - this[kBuiltOptions].comment = value; + this.findOptions.comment = value; return this; } @@ -311,7 +304,7 @@ export class FindCursor extends AbstractCursor { throw new MongoInvalidArgumentError('Argument for maxAwaitTimeMS must be a number'); } - this[kBuiltOptions].maxAwaitTimeMS = value; + this.findOptions.maxAwaitTimeMS = value; return this; } @@ -326,7 +319,7 @@ export class FindCursor extends AbstractCursor { throw new MongoInvalidArgumentError('Argument for maxTimeMS must be a number'); } - this[kBuiltOptions].maxTimeMS = value; + this.findOptions.maxTimeMS = value; return this; } @@ -372,7 +365,7 @@ export class FindCursor extends AbstractCursor { */ project(value: Document): FindCursor { this.throwIfInitialized(); - this[kBuiltOptions].projection = value; + this.findOptions.projection = value; return this as unknown as FindCursor; } @@ -384,11 +377,11 @@ export class FindCursor extends AbstractCursor { */ sort(sort: Sort | string, direction?: SortDirection): this { this.throwIfInitialized(); - if (this[kBuiltOptions].tailable) { + if (this.findOptions.tailable) { throw new MongoTailableCursorError('Tailable cursor does not support sorting'); } - this[kBuiltOptions].sort = formatSort(sort, direction); + this.findOptions.sort = formatSort(sort, direction); return this; } @@ -401,17 +394,17 @@ export class FindCursor extends AbstractCursor { allowDiskUse(allow = true): this { this.throwIfInitialized(); - if (!this[kBuiltOptions].sort) { + if (!this.findOptions.sort) { throw new MongoInvalidArgumentError('Option "allowDiskUse" requires a sort specification'); } // As of 6.0 the default is true. This allows users to get back to the old behavior. if (!allow) { - this[kBuiltOptions].allowDiskUse = false; + this.findOptions.allowDiskUse = false; return this; } - this[kBuiltOptions].allowDiskUse = true; + this.findOptions.allowDiskUse = true; return this; } @@ -422,7 +415,7 @@ export class FindCursor extends AbstractCursor { */ collation(value: CollationOptions): this { this.throwIfInitialized(); - this[kBuiltOptions].collation = value; + this.findOptions.collation = value; return this; } @@ -433,7 +426,7 @@ export class FindCursor extends AbstractCursor { */ limit(value: number): this { this.throwIfInitialized(); - if (this[kBuiltOptions].tailable) { + if (this.findOptions.tailable) { throw new MongoTailableCursorError('Tailable cursor does not support limit'); } @@ -441,7 +434,7 @@ export class FindCursor extends AbstractCursor { throw new MongoInvalidArgumentError('Operation "limit" requires an integer'); } - this[kBuiltOptions].limit = value; + this.findOptions.limit = value; return this; } @@ -452,7 +445,7 @@ export class FindCursor extends AbstractCursor { */ skip(value: number): this { this.throwIfInitialized(); - if (this[kBuiltOptions].tailable) { + if (this.findOptions.tailable) { throw new MongoTailableCursorError('Tailable cursor does not support skip'); } @@ -460,7 +453,7 @@ export class FindCursor extends AbstractCursor { throw new MongoInvalidArgumentError('Operation "skip" requires an integer'); } - this[kBuiltOptions].skip = value; + this.findOptions.skip = value; return this; } } diff --git a/test/integration/change-streams/change_stream.test.ts b/test/integration/change-streams/change_stream.test.ts index a6824432d48..92682887825 100644 --- a/test/integration/change-streams/change_stream.test.ts +++ b/test/integration/change-streams/change_stream.test.ts @@ -2018,7 +2018,7 @@ describe('ChangeStream resumability', function () { } as FailPoint); expect(changeStream.cursor) - .to.have.property('options') + .to.have.property('changeStreamCursorOptions') .that.containSubset(changeStreamResumeOptions); await collection.insertOne({ name: 'bailey' }); @@ -2026,7 +2026,7 @@ describe('ChangeStream resumability', function () { await changeStream.next(); expect(changeStream.cursor) - .to.have.property('options') + .to.have.property('changeStreamCursorOptions') .that.containSubset(changeStreamResumeOptions); } ); @@ -2182,7 +2182,7 @@ describe('ChangeStream resumability', function () { } as FailPoint); expect(changeStream.cursor) - .to.have.property('options') + .to.have.property('changeStreamCursorOptions') .that.containSubset(changeStreamResumeOptions); await collection.insertOne({ name: 'bailey' }); @@ -2190,7 +2190,7 @@ describe('ChangeStream resumability', function () { await changeStream.hasNext(); expect(changeStream.cursor) - .to.have.property('options') + .to.have.property('changeStreamCursorOptions') .that.containSubset(changeStreamResumeOptions); } ); @@ -2360,7 +2360,7 @@ describe('ChangeStream resumability', function () { } as FailPoint); expect(changeStream.cursor) - .to.have.property('options') + .to.have.property('changeStreamCursorOptions') .that.containSubset(changeStreamResumeOptions); await collection.insertOne({ name: 'bailey' }); @@ -2368,7 +2368,7 @@ describe('ChangeStream resumability', function () { await changeStream.tryNext(); expect(changeStream.cursor) - .to.have.property('options') + .to.have.property('changeStreamCursorOptions') .that.containSubset(changeStreamResumeOptions); } ); @@ -2504,14 +2504,14 @@ describe('ChangeStream resumability', function () { } as FailPoint); expect(changeStream.cursor) - .to.have.property('options') + .to.have.property('changeStreamCursorOptions') .that.containSubset(changeStreamResumeOptions); await collection.insertOne({ city: 'New York City' }); await changeStreamIterator.next(); expect(changeStream.cursor) - .to.have.property('options') + .to.have.property('changeStreamCursorOptions') .that.containSubset(changeStreamResumeOptions); } ); @@ -2643,7 +2643,7 @@ describe('ChangeStream resumability', function () { } as FailPoint); expect(changeStream.cursor) - .to.have.property('options') + .to.have.property('changeStreamCursorOptions') .that.containSubset(changeStreamResumeOptions); const changes = once(changeStream, 'change'); @@ -2654,7 +2654,7 @@ describe('ChangeStream resumability', function () { await changes; expect(changeStream.cursor) - .to.have.property('options') + .to.have.property('changeStreamCursorOptions') .that.containSubset(changeStreamResumeOptions); } ); diff --git a/test/integration/crud/maxTimeMS.test.ts b/test/integration/crud/maxTimeMS.test.ts index ba27dcb0aaa..6acc67a1291 100644 --- a/test/integration/crud/maxTimeMS.test.ts +++ b/test/integration/crud/maxTimeMS.test.ts @@ -9,7 +9,6 @@ import { MongoCursorExhaustedError, MongoServerError } from '../../mongodb'; -import { getSymbolFrom } from '../../tools/utils'; describe('MaxTimeMS', function () { let client: MongoClient; @@ -30,8 +29,8 @@ describe('MaxTimeMS', function () { const col = client.db().collection('max_time_ms'); await col.insertMany([{ agg_pipe: 1 }], { writeConcern: { w: 1 } }); const cursor = col.find({ $where: 'sleep(100) || true' }).maxTimeMS(50); - const kBuiltOptions = getSymbolFrom(cursor, 'builtOptions'); - expect(cursor[kBuiltOptions]).to.have.property('maxTimeMS', 50); + // @ts-expect-error: findOptions are private + expect(cursor.findOptions).to.have.property('maxTimeMS', 50); const error = await cursor.count().catch(error => error); expect(error).to.be.instanceOf(MongoServerError); @@ -44,8 +43,8 @@ describe('MaxTimeMS', function () { const col = client.db().collection('max_time_ms'); await col.insertMany([{ agg_pipe: 1 }], { writeConcern: { w: 1 } }); const cursor = col.find({ $where: 'sleep(100) || true' }).maxTimeMS(50); - const kBuiltOptions = getSymbolFrom(cursor, 'builtOptions'); - expect(cursor[kBuiltOptions]).to.have.property('maxTimeMS', 50); + // @ts-expect-error: findOptions are private + expect(cursor.findOptions).to.have.property('maxTimeMS', 50); const error = await cursor.toArray().catch(error => error); expect(error).to.be.instanceOf(MongoServerError);