mongodb 6.10.0-dev.20241102.sha.2f3fb466 → 6.10.0-dev.20241107.sha.e5582ed7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/lib/admin.js +3 -2
  2. package/lib/admin.js.map +1 -1
  3. package/lib/beta.d.ts +558 -38
  4. package/lib/bulk/common.js +4 -4
  5. package/lib/bulk/common.js.map +1 -1
  6. package/lib/change_stream.js +111 -51
  7. package/lib/change_stream.js.map +1 -1
  8. package/lib/client-side-encryption/auto_encrypter.js +8 -5
  9. package/lib/client-side-encryption/auto_encrypter.js.map +1 -1
  10. package/lib/client-side-encryption/client_encryption.js +48 -18
  11. package/lib/client-side-encryption/client_encryption.js.map +1 -1
  12. package/lib/client-side-encryption/state_machine.js +43 -29
  13. package/lib/client-side-encryption/state_machine.js.map +1 -1
  14. package/lib/cmap/connection.js +78 -6
  15. package/lib/cmap/connection.js.map +1 -1
  16. package/lib/cmap/connection_pool.js +14 -9
  17. package/lib/cmap/connection_pool.js.map +1 -1
  18. package/lib/cmap/wire_protocol/on_data.js +5 -1
  19. package/lib/cmap/wire_protocol/on_data.js.map +1 -1
  20. package/lib/cmap/wire_protocol/responses.js +30 -0
  21. package/lib/cmap/wire_protocol/responses.js.map +1 -1
  22. package/lib/collection.js +62 -3
  23. package/lib/collection.js.map +1 -1
  24. package/lib/connection_string.js +2 -0
  25. package/lib/connection_string.js.map +1 -1
  26. package/lib/cursor/abstract_cursor.js +218 -38
  27. package/lib/cursor/abstract_cursor.js.map +1 -1
  28. package/lib/cursor/aggregation_cursor.js +29 -7
  29. package/lib/cursor/aggregation_cursor.js.map +1 -1
  30. package/lib/cursor/change_stream_cursor.js +2 -2
  31. package/lib/cursor/change_stream_cursor.js.map +1 -1
  32. package/lib/cursor/client_bulk_write_cursor.js +1 -1
  33. package/lib/cursor/client_bulk_write_cursor.js.map +1 -1
  34. package/lib/cursor/find_cursor.js +18 -8
  35. package/lib/cursor/find_cursor.js.map +1 -1
  36. package/lib/cursor/list_collections_cursor.js +1 -1
  37. package/lib/cursor/list_collections_cursor.js.map +1 -1
  38. package/lib/cursor/list_indexes_cursor.js +1 -1
  39. package/lib/cursor/list_indexes_cursor.js.map +1 -1
  40. package/lib/cursor/run_command_cursor.js +6 -4
  41. package/lib/cursor/run_command_cursor.js.map +1 -1
  42. package/lib/db.js +63 -3
  43. package/lib/db.js.map +1 -1
  44. package/lib/error.js +27 -2
  45. package/lib/error.js.map +1 -1
  46. package/lib/explain.js +57 -1
  47. package/lib/explain.js.map +1 -1
  48. package/lib/gridfs/download.js +31 -3
  49. package/lib/gridfs/download.js.map +1 -1
  50. package/lib/gridfs/index.js +49 -14
  51. package/lib/gridfs/index.js.map +1 -1
  52. package/lib/gridfs/upload.js +80 -22
  53. package/lib/gridfs/upload.js.map +1 -1
  54. package/lib/index.js +9 -5
  55. package/lib/index.js.map +1 -1
  56. package/lib/mongo_client.js +70 -1
  57. package/lib/mongo_client.js.map +1 -1
  58. package/lib/operations/aggregate.js +2 -2
  59. package/lib/operations/aggregate.js.map +1 -1
  60. package/lib/operations/bulk_write.js +7 -2
  61. package/lib/operations/bulk_write.js.map +1 -1
  62. package/lib/operations/client_bulk_write/client_bulk_write.js +3 -3
  63. package/lib/operations/client_bulk_write/client_bulk_write.js.map +1 -1
  64. package/lib/operations/client_bulk_write/executor.js +14 -3
  65. package/lib/operations/client_bulk_write/executor.js.map +1 -1
  66. package/lib/operations/command.js +5 -2
  67. package/lib/operations/command.js.map +1 -1
  68. package/lib/operations/count.js +2 -2
  69. package/lib/operations/count.js.map +1 -1
  70. package/lib/operations/create_collection.js +8 -7
  71. package/lib/operations/create_collection.js.map +1 -1
  72. package/lib/operations/delete.js +6 -6
  73. package/lib/operations/delete.js.map +1 -1
  74. package/lib/operations/distinct.js +2 -2
  75. package/lib/operations/distinct.js.map +1 -1
  76. package/lib/operations/drop.js +8 -8
  77. package/lib/operations/drop.js.map +1 -1
  78. package/lib/operations/estimated_document_count.js +2 -2
  79. package/lib/operations/estimated_document_count.js.map +1 -1
  80. package/lib/operations/execute_operation.js +16 -10
  81. package/lib/operations/execute_operation.js.map +1 -1
  82. package/lib/operations/find.js +6 -3
  83. package/lib/operations/find.js.map +1 -1
  84. package/lib/operations/find_and_modify.js +2 -2
  85. package/lib/operations/find_and_modify.js.map +1 -1
  86. package/lib/operations/get_more.js +2 -1
  87. package/lib/operations/get_more.js.map +1 -1
  88. package/lib/operations/indexes.js +6 -6
  89. package/lib/operations/indexes.js.map +1 -1
  90. package/lib/operations/insert.js +6 -6
  91. package/lib/operations/insert.js.map +1 -1
  92. package/lib/operations/kill_cursors.js +5 -2
  93. package/lib/operations/kill_cursors.js.map +1 -1
  94. package/lib/operations/list_collections.js +2 -2
  95. package/lib/operations/list_collections.js.map +1 -1
  96. package/lib/operations/list_databases.js +2 -2
  97. package/lib/operations/list_databases.js.map +1 -1
  98. package/lib/operations/operation.js.map +1 -1
  99. package/lib/operations/profiling_level.js +2 -2
  100. package/lib/operations/profiling_level.js.map +1 -1
  101. package/lib/operations/remove_user.js +2 -2
  102. package/lib/operations/remove_user.js.map +1 -1
  103. package/lib/operations/rename.js +2 -2
  104. package/lib/operations/rename.js.map +1 -1
  105. package/lib/operations/run_command.js +6 -4
  106. package/lib/operations/run_command.js.map +1 -1
  107. package/lib/operations/search_indexes/create.js +5 -2
  108. package/lib/operations/search_indexes/create.js.map +1 -1
  109. package/lib/operations/search_indexes/drop.js +2 -2
  110. package/lib/operations/search_indexes/drop.js.map +1 -1
  111. package/lib/operations/search_indexes/update.js +2 -2
  112. package/lib/operations/search_indexes/update.js.map +1 -1
  113. package/lib/operations/set_profiling_level.js +2 -2
  114. package/lib/operations/set_profiling_level.js.map +1 -1
  115. package/lib/operations/stats.js +2 -2
  116. package/lib/operations/stats.js.map +1 -1
  117. package/lib/operations/update.js +8 -8
  118. package/lib/operations/update.js.map +1 -1
  119. package/lib/operations/validate_collection.js +2 -2
  120. package/lib/operations/validate_collection.js.map +1 -1
  121. package/lib/sdam/server.js +4 -1
  122. package/lib/sdam/server.js.map +1 -1
  123. package/lib/sdam/server_description.js +2 -0
  124. package/lib/sdam/server_description.js.map +1 -1
  125. package/lib/sdam/topology.js +38 -11
  126. package/lib/sdam/topology.js.map +1 -1
  127. package/lib/sessions.js +145 -74
  128. package/lib/sessions.js.map +1 -1
  129. package/lib/timeout.js +217 -16
  130. package/lib/timeout.js.map +1 -1
  131. package/lib/utils.js +31 -17
  132. package/lib/utils.js.map +1 -1
  133. package/lib/write_concern.js.map +1 -1
  134. package/mongodb.d.ts +558 -38
  135. package/package.json +2 -2
  136. package/src/admin.ts +6 -2
  137. package/src/bulk/common.ts +17 -5
  138. package/src/change_stream.ts +127 -52
  139. package/src/client-side-encryption/auto_encrypter.ts +12 -5
  140. package/src/client-side-encryption/client_encryption.ts +103 -20
  141. package/src/client-side-encryption/state_machine.ts +66 -32
  142. package/src/cmap/connection.ts +105 -8
  143. package/src/cmap/connection_pool.ts +14 -14
  144. package/src/cmap/wire_protocol/on_data.ts +11 -1
  145. package/src/cmap/wire_protocol/responses.ts +35 -1
  146. package/src/collection.ts +81 -9
  147. package/src/connection_string.ts +2 -0
  148. package/src/cursor/abstract_cursor.ts +286 -39
  149. package/src/cursor/aggregation_cursor.ts +54 -8
  150. package/src/cursor/change_stream_cursor.ts +6 -2
  151. package/src/cursor/client_bulk_write_cursor.ts +6 -2
  152. package/src/cursor/find_cursor.ts +40 -9
  153. package/src/cursor/list_collections_cursor.ts +1 -1
  154. package/src/cursor/list_indexes_cursor.ts +1 -1
  155. package/src/cursor/run_command_cursor.ts +50 -5
  156. package/src/db.ts +75 -7
  157. package/src/error.ts +26 -1
  158. package/src/explain.ts +85 -0
  159. package/src/gridfs/download.ts +43 -4
  160. package/src/gridfs/index.ts +64 -16
  161. package/src/gridfs/upload.ts +152 -45
  162. package/src/index.ts +26 -4
  163. package/src/mongo_client.ts +75 -3
  164. package/src/operations/aggregate.ts +10 -2
  165. package/src/operations/bulk_write.ts +9 -2
  166. package/src/operations/client_bulk_write/client_bulk_write.ts +11 -3
  167. package/src/operations/client_bulk_write/executor.ts +15 -3
  168. package/src/operations/command.ts +18 -8
  169. package/src/operations/count.ts +10 -3
  170. package/src/operations/create_collection.ts +14 -7
  171. package/src/operations/delete.ts +15 -6
  172. package/src/operations/distinct.ts +7 -2
  173. package/src/operations/drop.ts +18 -8
  174. package/src/operations/estimated_document_count.ts +7 -2
  175. package/src/operations/execute_operation.ts +22 -13
  176. package/src/operations/find.ts +17 -5
  177. package/src/operations/find_and_modify.ts +7 -2
  178. package/src/operations/get_more.ts +4 -1
  179. package/src/operations/indexes.ts +20 -7
  180. package/src/operations/insert.ts +13 -6
  181. package/src/operations/kill_cursors.ts +10 -2
  182. package/src/operations/list_collections.ts +10 -1
  183. package/src/operations/list_databases.ts +9 -2
  184. package/src/operations/operation.ts +16 -2
  185. package/src/operations/profiling_level.ts +7 -2
  186. package/src/operations/remove_user.ts +7 -2
  187. package/src/operations/rename.ts +7 -2
  188. package/src/operations/run_command.ts +23 -4
  189. package/src/operations/search_indexes/create.ts +10 -2
  190. package/src/operations/search_indexes/drop.ts +7 -2
  191. package/src/operations/search_indexes/update.ts +7 -2
  192. package/src/operations/set_profiling_level.ts +4 -2
  193. package/src/operations/stats.ts +7 -2
  194. package/src/operations/update.ts +16 -8
  195. package/src/operations/validate_collection.ts +7 -2
  196. package/src/sdam/server.ts +14 -4
  197. package/src/sdam/server_description.ts +4 -0
  198. package/src/sdam/topology.ts +43 -18
  199. package/src/sessions.ts +193 -89
  200. package/src/timeout.ts +310 -23
  201. package/src/transactions.ts +1 -1
  202. package/src/utils.ts +42 -28
  203. package/src/write_concern.ts +6 -3
@@ -1,7 +1,13 @@
1
1
  import { type Document } from '../bson';
2
2
  import { CursorResponse } from '../cmap/wire_protocol/responses';
3
- import { MongoInvalidArgumentError, MongoTailableCursorError } from '../error';
4
- import { type ExplainCommandOptions, type ExplainVerbosityLike } from '../explain';
3
+ import { MongoAPIError, MongoInvalidArgumentError, MongoTailableCursorError } from '../error';
4
+ import {
5
+ Explain,
6
+ ExplainableCursor,
7
+ type ExplainCommandOptions,
8
+ type ExplainVerbosityLike,
9
+ validateExplainTimeoutOptions
10
+ } from '../explain';
5
11
  import type { MongoClient } from '../mongo_client';
6
12
  import type { CollationOptions } from '../operations/command';
7
13
  import { CountOperation, type CountOptions } from '../operations/count';
@@ -11,7 +17,7 @@ import type { Hint } from '../operations/operation';
11
17
  import type { ClientSession } from '../sessions';
12
18
  import { formatSort, type Sort, type SortDirection } from '../sort';
13
19
  import { emitWarningOnce, mergeOptions, type MongoDBNamespace, squashError } from '../utils';
14
- import { AbstractCursor, type InitialCursorResponse } from './abstract_cursor';
20
+ import { type InitialCursorResponse } from './abstract_cursor';
15
21
 
16
22
  /** @public Flags allowed for cursor */
17
23
  export const FLAGS = [
@@ -24,7 +30,7 @@ export const FLAGS = [
24
30
  ] as const;
25
31
 
26
32
  /** @public */
27
- export class FindCursor<TSchema = any> extends AbstractCursor<TSchema> {
33
+ export class FindCursor<TSchema = any> extends ExplainableCursor<TSchema> {
28
34
  /** @internal */
29
35
  private cursorFilter: Document;
30
36
  /** @internal */
@@ -63,13 +69,25 @@ export class FindCursor<TSchema = any> extends AbstractCursor<TSchema> {
63
69
 
64
70
  /** @internal */
65
71
  async _initialize(session: ClientSession): Promise<InitialCursorResponse> {
66
- const findOperation = new FindOperation(this.namespace, this.cursorFilter, {
72
+ const options = {
67
73
  ...this.findOptions, // NOTE: order matters here, we may need to refine this
68
74
  ...this.cursorOptions,
69
75
  session
70
- });
76
+ };
77
+
78
+ if (options.explain) {
79
+ try {
80
+ validateExplainTimeoutOptions(options, Explain.fromOptions(options));
81
+ } catch {
82
+ throw new MongoAPIError(
83
+ 'timeoutMS cannot be used with explain when explain is specified in findOptions'
84
+ );
85
+ }
86
+ }
71
87
 
72
- const response = await executeOperation(this.client, findOperation);
88
+ const findOperation = new FindOperation(this.namespace, this.cursorFilter, options);
89
+
90
+ const response = await executeOperation(this.client, findOperation, this.timeoutContext);
73
91
 
74
92
  // the response is not a cursor when `explain` is enabled
75
93
  this.numReturned = response.batchSize;
@@ -133,14 +151,27 @@ export class FindCursor<TSchema = any> extends AbstractCursor<TSchema> {
133
151
  }
134
152
 
135
153
  /** Execute the explain for the cursor */
136
- async explain(verbosity?: ExplainVerbosityLike | ExplainCommandOptions): Promise<Document> {
154
+ async explain(): Promise<Document>;
155
+ async explain(verbosity: ExplainVerbosityLike | ExplainCommandOptions): Promise<Document>;
156
+ async explain(options: { timeoutMS?: number }): Promise<Document>;
157
+ async explain(
158
+ verbosity: ExplainVerbosityLike | ExplainCommandOptions,
159
+ options: { timeoutMS?: number }
160
+ ): Promise<Document>;
161
+ async explain(
162
+ verbosity?: ExplainVerbosityLike | ExplainCommandOptions | { timeoutMS?: number },
163
+ options?: { timeoutMS?: number }
164
+ ): Promise<Document> {
165
+ const { explain, timeout } = this.resolveExplainTimeoutOptions(verbosity, options);
166
+
137
167
  return (
138
168
  await executeOperation(
139
169
  this.client,
140
170
  new FindOperation(this.namespace, this.cursorFilter, {
141
171
  ...this.findOptions, // NOTE: order matters here, we may need to refine this
142
172
  ...this.cursorOptions,
143
- explain: verbosity ?? true
173
+ ...timeout,
174
+ explain: explain ?? true
144
175
  })
145
176
  )
146
177
  ).shift(this.deserializationOptions);
@@ -41,7 +41,7 @@ export class ListCollectionsCursor<
41
41
  session
42
42
  });
43
43
 
44
- const response = await executeOperation(this.parent.client, operation);
44
+ const response = await executeOperation(this.parent.client, operation, this.timeoutContext);
45
45
 
46
46
  return { server: operation.server, session, response };
47
47
  }
@@ -30,7 +30,7 @@ export class ListIndexesCursor extends AbstractCursor {
30
30
  session
31
31
  });
32
32
 
33
- const response = await executeOperation(this.parent.client, operation);
33
+ const response = await executeOperation(this.parent.client, operation, this.timeoutContext);
34
34
 
35
35
  return { server: operation.server, session, response };
36
36
  }
@@ -9,12 +9,55 @@ import type { ReadConcernLike } from '../read_concern';
9
9
  import type { ReadPreferenceLike } from '../read_preference';
10
10
  import type { ClientSession } from '../sessions';
11
11
  import { ns } from '../utils';
12
- import { AbstractCursor, type InitialCursorResponse } from './abstract_cursor';
12
+ import {
13
+ AbstractCursor,
14
+ type CursorTimeoutMode,
15
+ type InitialCursorResponse
16
+ } from './abstract_cursor';
13
17
 
14
18
  /** @public */
15
19
  export type RunCursorCommandOptions = {
16
20
  readPreference?: ReadPreferenceLike;
17
21
  session?: ClientSession;
22
+ /**
23
+ * @experimental
24
+ * Specifies the time an operation will run until it throws a timeout error. Note that if
25
+ * `maxTimeMS` is provided in the command in addition to setting `timeoutMS` in the options, then
26
+ * the original value of `maxTimeMS` will be overwritten.
27
+ */
28
+ timeoutMS?: number;
29
+ /**
30
+ * @public
31
+ * @experimental
32
+ * Specifies how `timeoutMS` is applied to the cursor. Can be either `'cursorLifeTime'` or `'iteration'`
33
+ * When set to `'iteration'`, the deadline specified by `timeoutMS` applies to each call of
34
+ * `cursor.next()`.
35
+ * When set to `'cursorLifetime'`, the deadline applies to the life of the entire cursor.
36
+ *
37
+ * Depending on the type of cursor being used, this option has different default values.
38
+ * For non-tailable cursors, this value defaults to `'cursorLifetime'`
39
+ * For tailable cursors, this value defaults to `'iteration'` since tailable cursors, by
40
+ * definition can have an arbitrarily long lifetime.
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * const cursor = collection.find({}, {timeoutMS: 100, timeoutMode: 'iteration'});
45
+ * for await (const doc of cursor) {
46
+ * // process doc
47
+ * // This will throw a timeout error if any of the iterator's `next()` calls takes more than 100ms, but
48
+ * // will continue to iterate successfully otherwise, regardless of the number of batches.
49
+ * }
50
+ * ```
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * const cursor = collection.find({}, { timeoutMS: 1000, timeoutMode: 'cursorLifetime' });
55
+ * const docs = await cursor.toArray(); // This entire line will throw a timeout error if all batches are not fetched and returned within 1000ms.
56
+ * ```
57
+ */
58
+ timeoutMode?: CursorTimeoutMode;
59
+ tailable?: boolean;
60
+ awaitData?: boolean;
18
61
  } & BSONSerializeOptions;
19
62
 
20
63
  /** @public */
@@ -46,7 +89,7 @@ export class RunCommandCursor extends AbstractCursor {
46
89
 
47
90
  /**
48
91
  * Controls the `getMore.batchSize` field
49
- * @param maxTimeMS - the number documents to return in the `nextBatch`
92
+ * @param batchSize - the number documents to return in the `nextBatch`
50
93
  */
51
94
  public setBatchSize(batchSize: number): this {
52
95
  this.getMoreOptions.batchSize = batchSize;
@@ -72,7 +115,9 @@ export class RunCommandCursor extends AbstractCursor {
72
115
  );
73
116
  }
74
117
 
75
- /** Unsupported for RunCommandCursor: maxTimeMS must be configured directly on command document */
118
+ /**
119
+ * Unsupported for RunCommandCursor: maxTimeMS must be configured directly on command document
120
+ */
76
121
  public override maxTimeMS(_: number): never {
77
122
  throw new MongoAPIError(
78
123
  'maxTimeMS must be configured on the command document directly, to configure getMore.maxTimeMS use cursor.setMaxTimeMS()'
@@ -105,7 +150,7 @@ export class RunCommandCursor extends AbstractCursor {
105
150
  responseType: CursorResponse
106
151
  });
107
152
 
108
- const response = await executeOperation(this.client, operation);
153
+ const response = await executeOperation(this.client, operation, this.timeoutContext);
109
154
 
110
155
  return {
111
156
  server: operation.server,
@@ -123,6 +168,6 @@ export class RunCommandCursor extends AbstractCursor {
123
168
  ...this.getMoreOptions
124
169
  });
125
170
 
126
- return await executeOperation(this.client, getMoreOperation);
171
+ return await executeOperation(this.client, getMoreOperation, this.timeoutContext);
127
172
  }
128
173
  }
package/src/db.ts CHANGED
@@ -97,7 +97,10 @@ export interface DbOptions extends BSONSerializeOptions, WriteConcernOptions {
97
97
  readConcern?: ReadConcern;
98
98
  /** Should retry failed writes */
99
99
  retryWrites?: boolean;
100
- /** @internal TODO(NODE-5688): make this public */
100
+ /**
101
+ * @experimental
102
+ * Specifies the time an operation will run until it throws a timeout error
103
+ */
101
104
  timeoutMS?: number;
102
105
  }
103
106
 
@@ -222,6 +225,10 @@ export class Db {
222
225
  return this.s.namespace.toString();
223
226
  }
224
227
 
228
+ public get timeoutMS(): number | undefined {
229
+ return this.s.options?.timeoutMS;
230
+ }
231
+
225
232
  /**
226
233
  * Create a new collection on a server with the specified options. Use this to create capped collections.
227
234
  * More information about command options available at https://www.mongodb.com/docs/manual/reference/command/create/
@@ -270,11 +277,16 @@ export class Db {
270
277
  // Intentionally, we do not inherit options from parent for this operation.
271
278
  return await executeOperation(
272
279
  this.client,
273
- new RunCommandOperation(this, command, {
274
- ...resolveBSONOptions(options),
275
- session: options?.session,
276
- readPreference: options?.readPreference
277
- })
280
+ new RunCommandOperation(
281
+ this,
282
+ command,
283
+ resolveOptions(undefined, {
284
+ ...resolveBSONOptions(options),
285
+ timeoutMS: options?.timeoutMS ?? this.timeoutMS,
286
+ session: options?.session,
287
+ readPreference: options?.readPreference
288
+ })
289
+ )
278
290
  );
279
291
  }
280
292
 
@@ -379,7 +391,11 @@ export class Db {
379
391
  new RenameOperation(
380
392
  this.collection<TSchema>(fromCollection) as TODO_NODE_3286,
381
393
  toCollection,
382
- { ...options, new_collection: true, readPreference: ReadPreference.primary }
394
+ resolveOptions(undefined, {
395
+ ...options,
396
+ new_collection: true,
397
+ readPreference: ReadPreference.primary
398
+ })
383
399
  ) as TODO_NODE_3286
384
400
  );
385
401
  }
@@ -517,6 +533,58 @@ export class Db {
517
533
  * - The first is to provide the schema that may be defined for all the collections within this database
518
534
  * - 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 argument
519
535
  *
536
+ * @remarks
537
+ * When `timeoutMS` is configured for a change stream, it will have different behaviour depending
538
+ * on whether the change stream is in iterator mode or emitter mode. In both cases, a change
539
+ * stream will time out if it does not receive a change event within `timeoutMS` of the last change
540
+ * event.
541
+ *
542
+ * Note that if a change stream is consistently timing out when watching a collection, database or
543
+ * client that is being changed, then this may be due to the server timing out before it can finish
544
+ * processing the existing oplog. To address this, restart the change stream with a higher
545
+ * `timeoutMS`.
546
+ *
547
+ * If the change stream times out the initial aggregate operation to establish the change stream on
548
+ * the server, then the client will close the change stream. If the getMore calls to the server
549
+ * time out, then the change stream will be left open, but will throw a MongoOperationTimeoutError
550
+ * when in iterator mode and emit an error event that returns a MongoOperationTimeoutError in
551
+ * emitter mode.
552
+ *
553
+ * To determine whether or not the change stream is still open following a timeout, check the
554
+ * {@link ChangeStream.closed} getter.
555
+ *
556
+ * @example
557
+ * In iterator mode, if a next() call throws a timeout error, it will attempt to resume the change stream.
558
+ * The next call can just be retried after this succeeds.
559
+ * ```ts
560
+ * const changeStream = collection.watch([], { timeoutMS: 100 });
561
+ * try {
562
+ * await changeStream.next();
563
+ * } catch (e) {
564
+ * if (e instanceof MongoOperationTimeoutError && !changeStream.closed) {
565
+ * await changeStream.next();
566
+ * }
567
+ * throw e;
568
+ * }
569
+ * ```
570
+ *
571
+ * @example
572
+ * In emitter mode, if the change stream goes `timeoutMS` without emitting a change event, it will
573
+ * emit an error event that returns a MongoOperationTimeoutError, but will not close the change
574
+ * stream unless the resume attempt fails. There is no need to re-establish change listeners as
575
+ * this will automatically continue emitting change events once the resume attempt completes.
576
+ *
577
+ * ```ts
578
+ * const changeStream = collection.watch([], { timeoutMS: 100 });
579
+ * changeStream.on('change', console.log);
580
+ * changeStream.on('error', e => {
581
+ * if (e instanceof MongoOperationTimeoutError && !changeStream.closed) {
582
+ * // do nothing
583
+ * } else {
584
+ * changeStream.close();
585
+ * }
586
+ * });
587
+ * ```
520
588
  * @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.
521
589
  * @param options - Optional settings for the command
522
590
  * @typeParam TSchema - Type of the data being detected by the change stream
package/src/error.ts CHANGED
@@ -311,7 +311,7 @@ export class MongoAPIError extends MongoDriverError {
311
311
 
312
312
  /**
313
313
  * An error generated when the driver encounters unexpected input
314
- * or reaches an unexpected/invalid internal state
314
+ * or reaches an unexpected/invalid internal state.
315
315
  *
316
316
  * @privateRemarks
317
317
  * Should **never** be directly instantiated.
@@ -857,6 +857,31 @@ export class MongoUnexpectedServerResponseError extends MongoRuntimeError {
857
857
  }
858
858
  }
859
859
 
860
+ /**
861
+ * @public
862
+ * @category Error
863
+ *
864
+ * The `MongoOperationTimeoutError` class represents an error that occurs when an operation could not be completed within the specified `timeoutMS`.
865
+ * It is generated by the driver in support of the "client side operation timeout" feature so inherits from `MongoDriverError`.
866
+ * When `timeoutMS` is enabled `MongoServerError`s relating to `MaxTimeExpired` errors will be converted to `MongoOperationTimeoutError`
867
+ *
868
+ * @example
869
+ * ```ts
870
+ * try {
871
+ * await blogs.insertOne(blogPost, { timeoutMS: 60_000 })
872
+ * } catch (error) {
873
+ * if (error instanceof MongoOperationTimeoutError) {
874
+ * console.log(`Oh no! writer's block!`, error);
875
+ * }
876
+ * }
877
+ * ```
878
+ */
879
+ export class MongoOperationTimeoutError extends MongoDriverError {
880
+ override get name(): string {
881
+ return 'MongoOperationTimeoutError';
882
+ }
883
+ }
884
+
860
885
  /**
861
886
  * An error thrown when the user attempts to add options to a cursor that has already been
862
887
  * initialized
package/src/explain.ts CHANGED
@@ -1,3 +1,7 @@
1
+ import { type Document } from './bson';
2
+ import { AbstractCursor } from './cursor/abstract_cursor';
3
+ import { MongoAPIError } from './error';
4
+
1
5
  /** @public */
2
6
  export const ExplainVerbosity = Object.freeze({
3
7
  queryPlanner: 'queryPlanner',
@@ -86,3 +90,84 @@ export class Explain {
86
90
  return new Explain(verbosity, maxTimeMS);
87
91
  }
88
92
  }
93
+
94
+ export function validateExplainTimeoutOptions(options: Document, explain?: Explain) {
95
+ const { maxTimeMS, timeoutMS } = options;
96
+ if (timeoutMS != null && (maxTimeMS != null || explain?.maxTimeMS != null)) {
97
+ throw new MongoAPIError('Cannot use maxTimeMS with timeoutMS for explain commands.');
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Applies an explain to a given command.
103
+ * @internal
104
+ *
105
+ * @param command - the command on which to apply the explain
106
+ * @param options - the options containing the explain verbosity
107
+ */
108
+ export function decorateWithExplain(
109
+ command: Document,
110
+ explain: Explain
111
+ ): {
112
+ explain: Document;
113
+ verbosity: ExplainVerbosity;
114
+ maxTimeMS?: number;
115
+ } {
116
+ type ExplainCommand = ReturnType<typeof decorateWithExplain>;
117
+ const { verbosity, maxTimeMS } = explain;
118
+ const baseCommand: ExplainCommand = { explain: command, verbosity };
119
+
120
+ if (typeof maxTimeMS === 'number') {
121
+ baseCommand.maxTimeMS = maxTimeMS;
122
+ }
123
+
124
+ return baseCommand;
125
+ }
126
+
127
+ /**
128
+ * @public
129
+ *
130
+ * A base class for any cursors that have `explain()` methods.
131
+ */
132
+ export abstract class ExplainableCursor<TSchema> extends AbstractCursor<TSchema> {
133
+ /** Execute the explain for the cursor */
134
+ abstract explain(): Promise<Document>;
135
+ abstract explain(verbosity: ExplainVerbosityLike | ExplainCommandOptions): Promise<Document>;
136
+ abstract explain(options: { timeoutMS?: number }): Promise<Document>;
137
+ abstract explain(
138
+ verbosity: ExplainVerbosityLike | ExplainCommandOptions,
139
+ options: { timeoutMS?: number }
140
+ ): Promise<Document>;
141
+ abstract explain(
142
+ verbosity?: ExplainVerbosityLike | ExplainCommandOptions | { timeoutMS?: number },
143
+ options?: { timeoutMS?: number }
144
+ ): Promise<Document>;
145
+
146
+ protected resolveExplainTimeoutOptions(
147
+ verbosity?: ExplainVerbosityLike | ExplainCommandOptions | { timeoutMS?: number },
148
+ options?: { timeoutMS?: number }
149
+ ): { timeout?: { timeoutMS?: number }; explain?: ExplainVerbosityLike | ExplainCommandOptions } {
150
+ let explain: ExplainVerbosityLike | ExplainCommandOptions | undefined;
151
+ let timeout: { timeoutMS?: number } | undefined;
152
+
153
+ if (verbosity == null && options == null) {
154
+ explain = undefined;
155
+ timeout = undefined;
156
+ } else if (verbosity != null && options == null) {
157
+ explain =
158
+ typeof verbosity !== 'object'
159
+ ? verbosity
160
+ : 'verbosity' in verbosity
161
+ ? verbosity
162
+ : undefined;
163
+
164
+ timeout = typeof verbosity === 'object' && 'timeoutMS' in verbosity ? verbosity : undefined;
165
+ } else {
166
+ // @ts-expect-error TS isn't smart enough to determine that if both options are provided, the first is explain options
167
+ explain = verbosity;
168
+ timeout = options;
169
+ }
170
+
171
+ return { timeout, explain };
172
+ }
173
+ }
@@ -2,6 +2,7 @@ import { Readable } from 'stream';
2
2
 
3
3
  import type { Document, ObjectId } from '../bson';
4
4
  import type { Collection } from '../collection';
5
+ import { CursorTimeoutMode } from '../cursor/abstract_cursor';
5
6
  import type { FindCursor } from '../cursor/find_cursor';
6
7
  import {
7
8
  MongoGridFSChunkError,
@@ -12,6 +13,7 @@ import {
12
13
  import type { FindOptions } from '../operations/find';
13
14
  import type { ReadPreference } from '../read_preference';
14
15
  import type { Sort } from '../sort';
16
+ import { CSOTTimeoutContext } from '../timeout';
15
17
  import type { Callback } from '../utils';
16
18
  import type { GridFSChunk } from './upload';
17
19
 
@@ -28,7 +30,10 @@ export interface GridFSBucketReadStreamOptions {
28
30
  * to be returned by the stream. `end` is non-inclusive
29
31
  */
30
32
  end?: number;
31
- /** @internal TODO(NODE-5688): make this public */
33
+ /**
34
+ * @experimental
35
+ * Specifies the time an operation will run until it throws a timeout error
36
+ */
32
37
  timeoutMS?: number;
33
38
  }
34
39
 
@@ -98,8 +103,10 @@ export interface GridFSBucketReadStreamPrivate {
98
103
  skip?: number;
99
104
  start: number;
100
105
  end: number;
106
+ timeoutMS?: number;
101
107
  };
102
108
  readPreference?: ReadPreference;
109
+ timeoutContext?: CSOTTimeoutContext;
103
110
  }
104
111
 
105
112
  /**
@@ -148,7 +155,11 @@ export class GridFSBucketReadStream extends Readable {
148
155
  end: 0,
149
156
  ...options
150
157
  },
151
- readPreference
158
+ readPreference,
159
+ timeoutContext:
160
+ options?.timeoutMS != null
161
+ ? new CSOTTimeoutContext({ timeoutMS: options.timeoutMS, serverSelectionTimeoutMS: 0 })
162
+ : undefined
152
163
  };
153
164
  }
154
165
 
@@ -196,7 +207,8 @@ export class GridFSBucketReadStream extends Readable {
196
207
  async abort(): Promise<void> {
197
208
  this.push(null);
198
209
  this.destroy();
199
- await this.s.cursor?.close();
210
+ const remainingTimeMS = this.s.timeoutContext?.getRemainingTimeMSOrThrow();
211
+ await this.s.cursor?.close({ timeoutMS: remainingTimeMS });
200
212
  }
201
213
  }
202
214
 
@@ -352,7 +364,22 @@ function init(stream: GridFSBucketReadStream): void {
352
364
  filter['n'] = { $gte: skip };
353
365
  }
354
366
  }
355
- stream.s.cursor = stream.s.chunks.find(filter).sort({ n: 1 });
367
+
368
+ let remainingTimeMS: number | undefined;
369
+ try {
370
+ remainingTimeMS = stream.s.timeoutContext?.getRemainingTimeMSOrThrow(
371
+ `Download timed out after ${stream.s.timeoutContext?.timeoutMS}ms`
372
+ );
373
+ } catch (error) {
374
+ return stream.destroy(error);
375
+ }
376
+
377
+ stream.s.cursor = stream.s.chunks
378
+ .find(filter, {
379
+ timeoutMode: stream.s.options.timeoutMS != null ? CursorTimeoutMode.LIFETIME : undefined,
380
+ timeoutMS: remainingTimeMS
381
+ })
382
+ .sort({ n: 1 });
356
383
 
357
384
  if (stream.s.readPreference) {
358
385
  stream.s.cursor.withReadPreference(stream.s.readPreference);
@@ -371,6 +398,18 @@ function init(stream: GridFSBucketReadStream): void {
371
398
  return;
372
399
  };
373
400
 
401
+ let remainingTimeMS: number | undefined;
402
+ try {
403
+ remainingTimeMS = stream.s.timeoutContext?.getRemainingTimeMSOrThrow(
404
+ `Download timed out after ${stream.s.timeoutContext?.timeoutMS}ms`
405
+ );
406
+ } catch (error) {
407
+ if (!stream.destroyed) stream.destroy(error);
408
+ return;
409
+ }
410
+
411
+ findOneOptions.timeoutMS = remainingTimeMS;
412
+
374
413
  stream.s.files.findOne(stream.s.filter, findOneOptions).then(handleReadResult, error => {
375
414
  if (stream.destroyed) return;
376
415
  stream.destroy(error);