"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.FindCursor = exports.FLAGS = void 0; const error_1 = require("../error"); const count_1 = require("../operations/count"); const execute_operation_1 = require("../operations/execute_operation"); const find_1 = require("../operations/find"); const sort_1 = require("../sort"); const utils_1 = require("../utils"); const abstract_cursor_1 = require("./abstract_cursor"); /** @internal */ const kFilter = Symbol('filter'); /** @internal */ const kNumReturned = Symbol('numReturned'); /** @internal */ const kBuiltOptions = Symbol('builtOptions'); /** @public Flags allowed for cursor */ exports.FLAGS = [ 'tailable', 'oplogReplay', 'noCursorTimeout', 'awaitData', 'exhaust', 'partial' ]; /** @public */ class FindCursor extends abstract_cursor_1.AbstractCursor { /** @internal */ constructor(topology, namespace, filter, options = {}) { super(topology, namespace, options); this[kFilter] = filter || {}; this[kBuiltOptions] = options; if (options.sort != null) { this[kBuiltOptions].sort = (0, sort_1.formatSort)(options.sort); } } clone() { const clonedOptions = (0, utils_1.mergeOptions)({}, this[kBuiltOptions]); delete clonedOptions.session; return new FindCursor(this.topology, this.namespace, this[kFilter], { ...clonedOptions }); } map(transform) { return super.map(transform); } /** @internal */ _initialize(session, callback) { const findOperation = new find_1.FindOperation(undefined, this.namespace, this[kFilter], { ...this[kBuiltOptions], ...this.cursorOptions, session }); (0, execute_operation_1.executeOperation)(this.topology, findOperation, (err, response) => { if (err || response == null) return callback(err); // TODO: We only need this for legacy queries that do not support `limit`, maybe // the value should only be saved in those cases. if (response.cursor) { this[kNumReturned] = response.cursor.firstBatch.length; } else { this[kNumReturned] = response.documents ? response.documents.length : 0; } // TODO: NODE-2882 callback(undefined, { server: findOperation.server, session, response }); }); } /** @internal */ _getMore(batchSize, callback) { // NOTE: this is to support client provided limits in pre-command servers const numReturned = this[kNumReturned]; if (numReturned) { const limit = this[kBuiltOptions].limit; batchSize = limit && limit > 0 && numReturned + batchSize > limit ? limit - numReturned : batchSize; if (batchSize <= 0) { return this.close(callback); } } super._getMore(batchSize, (err, response) => { if (err) return callback(err); // TODO: wrap this in some logic to prevent it from happening if we don't need this support if (response) { this[kNumReturned] = this[kNumReturned] + response.cursor.nextBatch.length; } callback(undefined, response); }); } count(options, callback) { if (typeof options === 'boolean') { throw new error_1.MongoInvalidArgumentError('Invalid first parameter to count'); } if (typeof options === 'function') (callback = options), (options = {}); options = options !== null && options !== void 0 ? options : {}; return (0, execute_operation_1.executeOperation)(this.topology, new count_1.CountOperation(this.namespace, this[kFilter], { ...this[kBuiltOptions], ...this.cursorOptions, ...options }), callback); } explain(verbosity, callback) { if (typeof verbosity === 'function') (callback = verbosity), (verbosity = true); if (verbosity == null) verbosity = true; return (0, execute_operation_1.executeOperation)(this.topology, new find_1.FindOperation(undefined, this.namespace, this[kFilter], { ...this[kBuiltOptions], ...this.cursorOptions, explain: verbosity }), callback); } /** Set the cursor query */ filter(filter) { (0, abstract_cursor_1.assertUninitialized)(this); this[kFilter] = filter; return this; } /** * Set the cursor hint * * @param hint - If specified, then the query system will only consider plans using the hinted index. */ hint(hint) { (0, abstract_cursor_1.assertUninitialized)(this); this[kBuiltOptions].hint = hint; return this; } /** * Set the cursor min * * @param min - Specify a $min value to specify the inclusive lower bound for a specific index in order to constrain the results of find(). The $min specifies the lower bound for all keys of a specific index in order. */ min(min) { (0, abstract_cursor_1.assertUninitialized)(this); this[kBuiltOptions].min = min; return this; } /** * Set the cursor max * * @param max - Specify a $max value to specify the exclusive upper bound for a specific index in order to constrain the results of find(). The $max specifies the upper bound for all keys of a specific index in order. */ max(max) { (0, abstract_cursor_1.assertUninitialized)(this); this[kBuiltOptions].max = max; return this; } /** * Set the cursor returnKey. * If set to true, modifies the cursor to only return the index field or fields for the results of the query, rather than documents. * If set to true and the query does not use an index to perform the read operation, the returned documents will not contain any fields. * * @param value - the returnKey value. */ returnKey(value) { (0, abstract_cursor_1.assertUninitialized)(this); this[kBuiltOptions].returnKey = value; return this; } /** * Modifies the output of a query by adding a field $recordId to matching documents. $recordId is the internal key which uniquely identifies a document in a collection. * * @param value - The $showDiskLoc option has now been deprecated and replaced with the showRecordId field. $showDiskLoc will still be accepted for OP_QUERY stye find. */ showRecordId(value) { (0, abstract_cursor_1.assertUninitialized)(this); this[kBuiltOptions].showRecordId = value; return this; } /** * Add a query modifier to the cursor query * * @param name - The query modifier (must start with $, such as $orderby etc) * @param value - The modifier value. */ addQueryModifier(name, value) { (0, abstract_cursor_1.assertUninitialized)(this); if (name[0] !== '$') { throw new error_1.MongoInvalidArgumentError(`${name} is not a valid query modifier`); } // Strip of the $ const field = name.substr(1); // NOTE: consider some TS magic for this switch (field) { case 'comment': this[kBuiltOptions].comment = value; break; case 'explain': this[kBuiltOptions].explain = value; break; case 'hint': this[kBuiltOptions].hint = value; break; case 'max': this[kBuiltOptions].max = value; break; case 'maxTimeMS': this[kBuiltOptions].maxTimeMS = value; break; case 'min': this[kBuiltOptions].min = value; break; case 'orderby': this[kBuiltOptions].sort = (0, sort_1.formatSort)(value); break; case 'query': this[kFilter] = value; break; case 'returnKey': this[kBuiltOptions].returnKey = value; break; case 'showDiskLoc': this[kBuiltOptions].showRecordId = value; break; default: throw new error_1.MongoInvalidArgumentError(`Invalid query modifier: ${name}`); } return this; } /** * Add a comment to the cursor query allowing for tracking the comment in the log. * * @param value - The comment attached to this query. */ comment(value) { (0, abstract_cursor_1.assertUninitialized)(this); this[kBuiltOptions].comment = value; return this; } /** * Set a maxAwaitTimeMS on a tailing cursor query to allow to customize the timeout value for the option awaitData (Only supported on MongoDB 3.2 or higher, ignored otherwise) * * @param value - Number of milliseconds to wait before aborting the tailed query. */ maxAwaitTimeMS(value) { (0, abstract_cursor_1.assertUninitialized)(this); if (typeof value !== 'number') { throw new error_1.MongoInvalidArgumentError('Argument for maxAwaitTimeMS must be a number'); } this[kBuiltOptions].maxAwaitTimeMS = value; return this; } /** * Set a maxTimeMS on the cursor query, allowing for hard timeout limits on queries (Only supported on MongoDB 2.6 or higher) * * @param value - Number of milliseconds to wait before aborting the query. */ maxTimeMS(value) { (0, abstract_cursor_1.assertUninitialized)(this); if (typeof value !== 'number') { throw new error_1.MongoInvalidArgumentError('Argument for maxTimeMS must be a number'); } this[kBuiltOptions].maxTimeMS = value; return this; } /** * Add a project stage to the aggregation pipeline * * @remarks * In order to strictly type this function you must provide an interface * that represents the effect of your projection on the result documents. * * By default chaining a projection to your cursor changes the returned type to the generic * {@link Document} type. * You should specify a parameterized type to have assertions on your final results. * * @example * ```typescript * // Best way * const docs: FindCursor<{ a: number }> = cursor.project<{ a: number }>({ _id: 0, a: true }); * // Flexible way * const docs: FindCursor = cursor.project({ _id: 0, a: true }); * ``` * * @remarks * * **Note for Typescript Users:** adding a transform changes the return type of the iteration of this cursor, * it **does not** return a new instance of a cursor. This means when calling project, * you should always assign the result to a new variable in order to get a correctly typed cursor variable. * Take note of the following example: * * @example * ```typescript * const cursor: FindCursor<{ a: number; b: string }> = coll.find(); * const projectCursor = cursor.project<{ a: number }>({ _id: 0, a: true }); * const aPropOnlyArray: {a: number}[] = await projectCursor.toArray(); * * // or always use chaining and save the final cursor * * const cursor = coll.find().project<{ a: string }>({ * _id: 0, * a: { $convert: { input: '$a', to: 'string' } * }}); * ``` */ project(value) { (0, abstract_cursor_1.assertUninitialized)(this); this[kBuiltOptions].projection = value; return this; } /** * Sets the sort order of the cursor query. * * @param sort - The key or keys set for the sort. * @param direction - The direction of the sorting (1 or -1). */ sort(sort, direction) { (0, abstract_cursor_1.assertUninitialized)(this); if (this[kBuiltOptions].tailable) { throw new error_1.MongoTailableCursorError('Tailable cursor does not support sorting'); } this[kBuiltOptions].sort = (0, sort_1.formatSort)(sort, direction); return this; } /** * Allows disk use for blocking sort operations exceeding 100MB memory. (MongoDB 3.2 or higher) * * @remarks * {@link https://docs.mongodb.com/manual/reference/command/find/#find-cmd-allowdiskuse | find command allowDiskUse documentation} */ allowDiskUse() { (0, abstract_cursor_1.assertUninitialized)(this); if (!this[kBuiltOptions].sort) { throw new error_1.MongoInvalidArgumentError('Option "allowDiskUse" requires a sort specification'); } this[kBuiltOptions].allowDiskUse = true; return this; } /** * Set the collation options for the cursor. * * @param value - The cursor collation options (MongoDB 3.4 or higher) settings for update operation (see 3.4 documentation for available fields). */ collation(value) { (0, abstract_cursor_1.assertUninitialized)(this); this[kBuiltOptions].collation = value; return this; } /** * Set the limit for the cursor. * * @param value - The limit for the cursor query. */ limit(value) { (0, abstract_cursor_1.assertUninitialized)(this); if (this[kBuiltOptions].tailable) { throw new error_1.MongoTailableCursorError('Tailable cursor does not support limit'); } if (typeof value !== 'number') { throw new error_1.MongoInvalidArgumentError('Operation "limit" requires an integer'); } this[kBuiltOptions].limit = value; return this; } /** * Set the skip for the cursor. * * @param value - The skip for the cursor query. */ skip(value) { (0, abstract_cursor_1.assertUninitialized)(this); if (this[kBuiltOptions].tailable) { throw new error_1.MongoTailableCursorError('Tailable cursor does not support skip'); } if (typeof value !== 'number') { throw new error_1.MongoInvalidArgumentError('Operation "skip" requires an integer'); } this[kBuiltOptions].skip = value; return this; } } exports.FindCursor = FindCursor; //# sourceMappingURL=find_cursor.js.map