diff --git a/src/collection.ts b/src/collection.ts index e66a0cc16f5..24469b56efa 100644 --- a/src/collection.ts +++ b/src/collection.ts @@ -115,7 +115,10 @@ export interface CollectionOptions extends BSONSerializeOptions, WriteConcernOpt readConcern?: ReadConcernLike; /** The preferred read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). */ readPreference?: ReadPreferenceLike; - /** @internal TODO(NODE-5688): make this public */ + /** + * @experimental + * Specifies the time an operation will run until it throws a timeout error + */ timeoutMS?: number; } @@ -262,8 +265,7 @@ export class Collection { this.s.collectionHint = normalizeHintField(v); } - /** @internal */ - get timeoutMS(): number | undefined { + public get timeoutMS(): number | undefined { return this.s.options.timeoutMS; } diff --git a/src/cursor/abstract_cursor.ts b/src/cursor/abstract_cursor.ts index 4eb5904f433..ab052dd11b2 100644 --- a/src/cursor/abstract_cursor.ts +++ b/src/cursor/abstract_cursor.ts @@ -61,15 +61,44 @@ export interface CursorStreamOptions { /** @public */ export type CursorFlag = (typeof CURSOR_FLAGS)[number]; -/** @public*/ +/** + * @public + * @experimental + * Specifies how `timeoutMS` is applied to the cursor. Can be either `'cursorLifeTime'` or `'iteration'` + * When set to `'iteration'`, the deadline specified by `timeoutMS` applies to each call of + * `cursor.next()`. + * When set to `'cursorLifetime'`, the deadline applies to the life of the entire cursor. + * + * Depending on the type of cursor being used, this option has different default values. + * For non-tailable cursors, this value defaults to `'cursorLifetime'` + * For tailable cursors, this value defaults to `'iteration'` since tailable cursors, by + * definition can have an arbitrarily long lifetime. + * + * @example + * ```ts + * const cursor = collection.find({}, {timeoutMS: 100, timeoutMode: 'iteration'}); + * for await (const doc of cursor) { + * // process doc + * // This will throw a timeout error if any of the iterator's `next()` calls takes more than 100ms, but + * // will continue to iterate successfully otherwise, regardless of the number of batches. + * } + * ``` + * + * @example + * ```ts + * const cursor = collection.find({}, { timeoutMS: 1000, timeoutMode: 'cursorLifetime' }); + * const docs = await cursor.toArray(); // This entire line will throw a timeout error if all batches are not fetched and returned within 1000ms. + * ``` + */ export const CursorTimeoutMode = Object.freeze({ ITERATION: 'iteration', LIFETIME: 'cursorLifetime' } as const); -/** @public - * TODO(NODE-5688): Document and release - * */ +/** + * @public + * @experimental + */ export type CursorTimeoutMode = (typeof CursorTimeoutMode)[keyof typeof CursorTimeoutMode]; /** @public */ @@ -116,9 +145,37 @@ export interface AbstractCursorOptions extends BSONSerializeOptions { */ awaitData?: boolean; noCursorTimeout?: boolean; - /** @internal TODO(NODE-5688): make this public */ + /** Specifies the time an operation will run until it throws a timeout error. See {@link AbstractCursorOptions.timeoutMode} for more details on how this option applies to cursors. */ timeoutMS?: number; - /** @internal TODO(NODE-5688): make this public */ + /** + * @public + * @experimental + * Specifies how `timeoutMS` is applied to the cursor. Can be either `'cursorLifeTime'` or `'iteration'` + * When set to `'iteration'`, the deadline specified by `timeoutMS` applies to each call of + * `cursor.next()`. + * When set to `'cursorLifetime'`, the deadline applies to the life of the entire cursor. + * + * Depending on the type of cursor being used, this option has different default values. + * For non-tailable cursors, this value defaults to `'cursorLifetime'` + * For tailable cursors, this value defaults to `'iteration'` since tailable cursors, by + * definition can have an arbitrarily long lifetime. + * + * @example + * ```ts + * const cursor = collection.find({}, {timeoutMS: 100, timeoutMode: 'iteration'}); + * for await (const doc of cursor) { + * // process doc + * // This will throw a timeout error if any of the iterator's `next()` calls takes more than 100ms, but + * // will continue to iterate successfully otherwise, regardless of the number of batches. + * } + * ``` + * + * @example + * ```ts + * const cursor = collection.find({}, { timeoutMS: 1000, timeoutMode: 'cursorLifetime' }); + * const docs = await cursor.toArray(); // This entire line will throw a timeout error if all batches are not fetched and returned within 1000ms. + * ``` + */ timeoutMode?: CursorTimeoutMode; /** diff --git a/src/cursor/run_command_cursor.ts b/src/cursor/run_command_cursor.ts index 15f95042c7f..3f6dd6d34e0 100644 --- a/src/cursor/run_command_cursor.ts +++ b/src/cursor/run_command_cursor.ts @@ -19,9 +19,42 @@ import { export type RunCursorCommandOptions = { readPreference?: ReadPreferenceLike; session?: ClientSession; - /** @internal */ + /** + * @experimental + * Specifies the time an operation will run until it throws a timeout error. Note that if + * `maxTimeMS` is provided in the command in addition to setting `timeoutMS` in the options, then + * the original value of `maxTimeMS` will be overwritten. + */ timeoutMS?: number; - /** @internal */ + /** + * @public + * @experimental + * Specifies how `timeoutMS` is applied to the cursor. Can be either `'cursorLifeTime'` or `'iteration'` + * When set to `'iteration'`, the deadline specified by `timeoutMS` applies to each call of + * `cursor.next()`. + * When set to `'cursorLifetime'`, the deadline applies to the life of the entire cursor. + * + * Depending on the type of cursor being used, this option has different default values. + * For non-tailable cursors, this value defaults to `'cursorLifetime'` + * For tailable cursors, this value defaults to `'iteration'` since tailable cursors, by + * definition can have an arbitrarily long lifetime. + * + * @example + * ```ts + * const cursor = collection.find({}, {timeoutMS: 100, timeoutMode: 'iteration'}); + * for await (const doc of cursor) { + * // process doc + * // This will throw a timeout error if any of the iterator's `next()` calls takes more than 100ms, but + * // will continue to iterate successfully otherwise, regardless of the number of batches. + * } + * ``` + * + * @example + * ```ts + * const cursor = collection.find({}, { timeoutMS: 1000, timeoutMode: 'cursorLifetime' }); + * const docs = await cursor.toArray(); // This entire line will throw a timeout error if all batches are not fetched and returned within 1000ms. + * ``` + */ timeoutMode?: CursorTimeoutMode; tailable?: boolean; awaitData?: boolean; diff --git a/src/db.ts b/src/db.ts index d62a719382e..121d6fc4f1e 100644 --- a/src/db.ts +++ b/src/db.ts @@ -97,7 +97,10 @@ export interface DbOptions extends BSONSerializeOptions, WriteConcernOptions { readConcern?: ReadConcern; /** Should retry failed writes */ retryWrites?: boolean; - /** @internal TODO(NODE-5688): make this public */ + /** + * @experimental + * Specifies the time an operation will run until it throws a timeout error + */ timeoutMS?: number; } @@ -222,8 +225,7 @@ export class Db { return this.s.namespace.toString(); } - /** @internal */ - get timeoutMS(): number | undefined { + public get timeoutMS(): number | undefined { return this.s.options?.timeoutMS; } diff --git a/src/error.ts b/src/error.ts index 2f59f688c92..9beda0f3664 100644 --- a/src/error.ts +++ b/src/error.ts @@ -865,7 +865,6 @@ export class MongoUnexpectedServerResponseError extends MongoRuntimeError { * @category Error * * This error is thrown when an operation could not be completed within the specified `timeoutMS`. - * TODO(NODE-5688): expand this documentation. * * @example * ```ts diff --git a/src/gridfs/index.ts b/src/gridfs/index.ts index 67df4548cb0..70f154431cf 100644 --- a/src/gridfs/index.ts +++ b/src/gridfs/index.ts @@ -38,7 +38,11 @@ export interface GridFSBucketOptions extends WriteConcernOptions { chunkSizeBytes?: number; /** Read preference to be passed to read operations */ readPreference?: ReadPreference; - /** @internal TODO(NODE-5688): make this public */ + /** + * @experimental + * Specifies the lifetime duration of a gridFS stream. If any async operations are in progress + * when this timeout expires, the stream will throw a timeout error. + */ timeoutMS?: number; } diff --git a/src/index.ts b/src/index.ts index 65f9ec7ccb7..dd4d8a21d95 100644 --- a/src/index.ts +++ b/src/index.ts @@ -37,7 +37,11 @@ export { Timestamp, UUID } from './bson'; -export { AnyBulkWriteOperation, BulkWriteOptions, MongoBulkWriteError } from './bulk/common'; +export { + type AnyBulkWriteOperation, + type BulkWriteOptions, + MongoBulkWriteError +} from './bulk/common'; export { ClientEncryption } from './client-side-encryption/client_encryption'; export { ChangeStreamCursor } from './cursor/change_stream_cursor'; export { @@ -111,7 +115,7 @@ export { AutoEncryptionLoggerLevel } from './client-side-encryption/auto_encrypt export { GSSAPICanonicalizationValue } from './cmap/auth/gssapi'; export { AuthMechanism } from './cmap/auth/providers'; export { Compressor } from './cmap/wire_protocol/compression'; -export { CURSOR_FLAGS, type CursorTimeoutMode } from './cursor/abstract_cursor'; +export { CURSOR_FLAGS, CursorTimeoutMode } from './cursor/abstract_cursor'; export { MongoErrorLabel } from './error'; export { ExplainVerbosity } from './explain'; export { ServerApiVersion } from './mongo_client'; diff --git a/src/mongo_client.ts b/src/mongo_client.ts index 1c9d7843796..f16d165a236 100644 --- a/src/mongo_client.ts +++ b/src/mongo_client.ts @@ -130,7 +130,10 @@ export type SupportedNodeConnectionOptions = SupportedTLSConnectionOptions & export interface MongoClientOptions extends BSONSerializeOptions, SupportedNodeConnectionOptions { /** Specifies the name of the replica set, if the mongod is a member of a replica set. */ replicaSet?: string; - /** @internal TODO(NODE-5688): This option is in development and currently has no behaviour. */ + /** + * @experimental + * Specifies the time an operation will run until it throws a timeout error + */ timeoutMS?: number; /** Enables or disables TLS/SSL for the connection. */ tls?: boolean; @@ -488,7 +491,6 @@ export class MongoClient extends TypedEventEmitter implements return this.s.bsonOptions; } - /** @internal */ get timeoutMS(): number | undefined { return this.s.options.timeoutMS; } @@ -1029,6 +1031,5 @@ export interface MongoOptions * TODO: NODE-5671 - remove internal flag */ mongodbLogPath?: 'stderr' | 'stdout' | MongoDBLogWritable; - /** @internal TODO(NODE-5688): make this public */ timeoutMS?: number; } diff --git a/src/operations/indexes.ts b/src/operations/indexes.ts index 220d438d834..afd05f5be36 100644 --- a/src/operations/indexes.ts +++ b/src/operations/indexes.ts @@ -1,7 +1,7 @@ import type { Document } from '../bson'; import { CursorResponse } from '../cmap/wire_protocol/responses'; import type { Collection } from '../collection'; -import { type AbstractCursorOptions, type CursorTimeoutMode } from '../cursor/abstract_cursor'; +import { type AbstractCursorOptions } from '../cursor/abstract_cursor'; import { MongoCompatibilityError } from '../error'; import { type OneOrMore } from '../mongo_types'; import type { Server } from '../sdam/server'; @@ -361,8 +361,6 @@ export class DropIndexOperation extends CommandOperation { /** @public */ export type ListIndexesOptions = AbstractCursorOptions & { - /** @internal TODO(NODE-5688): make this public */ - timeoutMode?: CursorTimeoutMode; /** @internal */ omitMaxTimeMS?: boolean; }; diff --git a/src/operations/operation.ts b/src/operations/operation.ts index 80cb552fcb6..1c5be203516 100644 --- a/src/operations/operation.ts +++ b/src/operations/operation.ts @@ -2,7 +2,7 @@ import { type BSONSerializeOptions, type Document, resolveBSONOptions } from '.. import { ReadPreference, type ReadPreferenceLike } from '../read_preference'; import type { Server } from '../sdam/server'; import type { ClientSession } from '../sessions'; -import { type Timeout, type TimeoutContext } from '../timeout'; +import { type TimeoutContext } from '../timeout'; import type { MongoDBNamespace } from '../utils'; export const Aspect = { @@ -35,7 +35,10 @@ export interface OperationOptions extends BSONSerializeOptions { /** @internal Hint to `executeOperation` to omit maxTimeMS */ omitMaxTimeMS?: boolean; - /** @internal TODO(NODE-5688): make this public */ + /** + * @experimental + * Specifies the time an operation will run until it throws a timeout error + */ timeoutMS?: number; } @@ -61,9 +64,7 @@ export abstract class AbstractOperation { options: OperationOptions; - /** @internal */ - timeout?: Timeout; - /** @internal */ + /** Specifies the time an operation will run until it throws a timeout error. */ timeoutMS?: number; [kSession]: ClientSession | undefined; diff --git a/src/operations/run_command.ts b/src/operations/run_command.ts index 1747f10c8be..db5c5a7c169 100644 --- a/src/operations/run_command.ts +++ b/src/operations/run_command.ts @@ -15,7 +15,10 @@ export type RunCommandOptions = { session?: ClientSession; /** The read preference */ readPreference?: ReadPreferenceLike; - /** @internal */ + /** + * @experimental + * Specifies the time an operation will run until it throws a timeout error + */ timeoutMS?: number; /** @internal */ omitMaxTimeMS?: boolean; diff --git a/src/sessions.ts b/src/sessions.ts index 9ada6124d5a..f323d1a93be 100644 --- a/src/sessions.ts +++ b/src/sessions.ts @@ -103,7 +103,7 @@ export interface EndSessionOptions { force?: boolean; forceClear?: boolean; - /** @internal */ + /** Specifies the time an operation will run until it throws a timeout error */ timeoutMS?: number; } @@ -145,7 +145,10 @@ export class ClientSession [kPinnedConnection]?: Connection; /** @internal */ [kTxnNumberIncrement]: number; - /** @internal */ + /** + * @experimental + * Specifies the time an operation in a given `ClientSession` will run until it throws a timeout error + */ timeoutMS?: number; /** @internal */ @@ -710,6 +713,9 @@ export class ClientSession * `Promise.allSettled`, `Promise.race`, etc to parallelize operations inside a transaction is * undefined behaviour. * + * **IMPORTANT:** When running an operation inside a `withTransaction` callback, if it is not + * provided the explicit session in its options, it will not be part of the transaction and it will not respect timeoutMS. + * * * @remarks * - If all operations successfully complete and the `commitTransaction` operation is successful, then the provided function will return the result of the provided function. diff --git a/test/integration/client-side-operations-timeout/node_csot.test.ts b/test/integration/client-side-operations-timeout/node_csot.test.ts index b63ffa8da22..ec69dcc1b7b 100644 --- a/test/integration/client-side-operations-timeout/node_csot.test.ts +++ b/test/integration/client-side-operations-timeout/node_csot.test.ts @@ -18,6 +18,7 @@ import { type CommandStartedEvent, type CommandSucceededEvent, Connection, + CursorTimeoutMode, type Db, type FindCursor, GridFSBucket, @@ -423,7 +424,7 @@ describe('CSOT driver tests', metadata, () => { const cursor = client .db('db') .collection('coll') - .find({}, { batchSize: 3, timeoutMode: 'iteration', timeoutMS: 10 }) + .find({}, { batchSize: 3, timeoutMode: CursorTimeoutMode.ITERATION, timeoutMS: 10 }) .limit(3); const maybeError = await cursor.next().then( diff --git a/test/unit/index.test.ts b/test/unit/index.test.ts index a76aff98d91..7b064b1078d 100644 --- a/test/unit/index.test.ts +++ b/test/unit/index.test.ts @@ -5,14 +5,7 @@ import { expect } from 'chai'; import * as mongodb from '../../src/index'; import { setDifference } from '../mongodb'; -/** - * TS-NODE Adds these keys but they are undefined, they are not present when you import from lib - * We did not think this strangeness was worth investigating so we just make sure they remain set to undefined - */ -const TS_NODE_EXPORTS = ['AnyBulkWriteOperation', 'BulkWriteOptions']; - const EXPECTED_EXPORTS = [ - ...TS_NODE_EXPORTS, 'AbstractCursor', 'Admin', 'AggregationCursor', @@ -31,11 +24,11 @@ const EXPECTED_EXPORTS = [ 'ClientSession', 'Code', 'Collection', - 'configureExplicitResourceManagement', 'CommandFailedEvent', 'CommandStartedEvent', 'CommandSucceededEvent', 'Compressor', + 'configureExplicitResourceManagement', 'ConnectionCheckedInEvent', 'ConnectionCheckedOutEvent', 'ConnectionCheckOutFailedEvent', @@ -49,12 +42,13 @@ const EXPECTED_EXPORTS = [ 'ConnectionPoolReadyEvent', 'ConnectionReadyEvent', 'CURSOR_FLAGS', + 'CursorTimeoutMode', 'Db', 'DBRef', 'Decimal128', 'Double', - 'ExplainVerbosity', 'ExplainableCursor', + 'ExplainVerbosity', 'FindCursor', 'GridFSBucket', 'GridFSBucketReadStream', @@ -102,6 +96,7 @@ const EXPECTED_EXPORTS = [ 'MongoNetworkTimeoutError', 'MongoNotConnectedError', 'MongoOIDCError', + 'MongoOperationTimeoutError', 'MongoParseError', 'MongoRuntimeError', 'MongoServerClosedError', @@ -111,10 +106,8 @@ const EXPECTED_EXPORTS = [ 'MongoTailableCursorError', 'MongoTopologyClosedError', 'MongoTransactionError', - 'MongoOperationTimeoutError', 'MongoUnexpectedServerResponseError', 'MongoWriteConcernError', - 'WriteConcernErrorResult', 'ObjectId', 'OrderedBulkOperation', 'ProfilingLevel', @@ -130,6 +123,10 @@ const EXPECTED_EXPORTS = [ 'ServerHeartbeatStartedEvent', 'ServerHeartbeatSucceededEvent', 'ServerOpeningEvent', + 'ServerSelectionEvent', + 'ServerSelectionFailedEvent', + 'ServerSelectionStartedEvent', + 'ServerSelectionSucceededEvent', 'ServerType', 'SrvPollingEvent', 'Timestamp', @@ -139,12 +136,9 @@ const EXPECTED_EXPORTS = [ 'TopologyType', 'UnorderedBulkOperation', 'UUID', + 'WaitingForSuitableServerEvent', 'WriteConcern', - 'ServerSelectionEvent', - 'ServerSelectionFailedEvent', - 'ServerSelectionStartedEvent', - 'ServerSelectionSucceededEvent', - 'WaitingForSuitableServerEvent' + 'WriteConcernErrorResult' ]; describe('mongodb entrypoint', () => { @@ -155,12 +149,4 @@ describe('mongodb entrypoint', () => { it('exports only the expected keys', () => { expect(setDifference(Object.keys(mongodb), EXPECTED_EXPORTS)).to.be.empty; }); - - it('should export keys added by ts-node as undefined', () => { - // If the array is empty, this test would be a no-op so we should remove it - expect(TS_NODE_EXPORTS).to.have.length.greaterThan(0); - for (const tsNodeExportKey of TS_NODE_EXPORTS) { - expect(mongodb).to.have.property(tsNodeExportKey, undefined); - } - }); });