mongodb 6.10.0-dev.20241106.sha.dc3fe957 → 6.10.0-dev.20241108.sha.fd7acde6

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 (215) hide show
  1. package/lib/admin.js +3 -2
  2. package/lib/admin.js.map +1 -1
  3. package/lib/beta.d.ts +558 -40
  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/auth/mongo_credentials.js +2 -1
  15. package/lib/cmap/auth/mongo_credentials.js.map +1 -1
  16. package/lib/cmap/auth/mongodb_oidc/k8s_machine_workflow.js +38 -0
  17. package/lib/cmap/auth/mongodb_oidc/k8s_machine_workflow.js.map +1 -0
  18. package/lib/cmap/auth/mongodb_oidc.js +2 -0
  19. package/lib/cmap/auth/mongodb_oidc.js.map +1 -1
  20. package/lib/cmap/connection.js +78 -6
  21. package/lib/cmap/connection.js.map +1 -1
  22. package/lib/cmap/connection_pool.js +14 -9
  23. package/lib/cmap/connection_pool.js.map +1 -1
  24. package/lib/cmap/wire_protocol/on_data.js +5 -1
  25. package/lib/cmap/wire_protocol/on_data.js.map +1 -1
  26. package/lib/cmap/wire_protocol/responses.js +30 -0
  27. package/lib/cmap/wire_protocol/responses.js.map +1 -1
  28. package/lib/collection.js +62 -3
  29. package/lib/collection.js.map +1 -1
  30. package/lib/connection_string.js +2 -0
  31. package/lib/connection_string.js.map +1 -1
  32. package/lib/cursor/abstract_cursor.js +218 -38
  33. package/lib/cursor/abstract_cursor.js.map +1 -1
  34. package/lib/cursor/aggregation_cursor.js +29 -7
  35. package/lib/cursor/aggregation_cursor.js.map +1 -1
  36. package/lib/cursor/change_stream_cursor.js +2 -2
  37. package/lib/cursor/change_stream_cursor.js.map +1 -1
  38. package/lib/cursor/client_bulk_write_cursor.js +1 -1
  39. package/lib/cursor/client_bulk_write_cursor.js.map +1 -1
  40. package/lib/cursor/find_cursor.js +18 -8
  41. package/lib/cursor/find_cursor.js.map +1 -1
  42. package/lib/cursor/list_collections_cursor.js +1 -1
  43. package/lib/cursor/list_collections_cursor.js.map +1 -1
  44. package/lib/cursor/list_indexes_cursor.js +1 -1
  45. package/lib/cursor/list_indexes_cursor.js.map +1 -1
  46. package/lib/cursor/run_command_cursor.js +6 -4
  47. package/lib/cursor/run_command_cursor.js.map +1 -1
  48. package/lib/db.js +63 -3
  49. package/lib/db.js.map +1 -1
  50. package/lib/error.js +27 -2
  51. package/lib/error.js.map +1 -1
  52. package/lib/explain.js +57 -1
  53. package/lib/explain.js.map +1 -1
  54. package/lib/gridfs/download.js +31 -3
  55. package/lib/gridfs/download.js.map +1 -1
  56. package/lib/gridfs/index.js +49 -14
  57. package/lib/gridfs/index.js.map +1 -1
  58. package/lib/gridfs/upload.js +80 -22
  59. package/lib/gridfs/upload.js.map +1 -1
  60. package/lib/index.js +9 -5
  61. package/lib/index.js.map +1 -1
  62. package/lib/mongo_client.js +70 -1
  63. package/lib/mongo_client.js.map +1 -1
  64. package/lib/operations/aggregate.js +2 -2
  65. package/lib/operations/aggregate.js.map +1 -1
  66. package/lib/operations/bulk_write.js +7 -2
  67. package/lib/operations/bulk_write.js.map +1 -1
  68. package/lib/operations/client_bulk_write/client_bulk_write.js +3 -3
  69. package/lib/operations/client_bulk_write/client_bulk_write.js.map +1 -1
  70. package/lib/operations/client_bulk_write/executor.js +14 -3
  71. package/lib/operations/client_bulk_write/executor.js.map +1 -1
  72. package/lib/operations/command.js +5 -2
  73. package/lib/operations/command.js.map +1 -1
  74. package/lib/operations/count.js +2 -2
  75. package/lib/operations/count.js.map +1 -1
  76. package/lib/operations/create_collection.js +8 -7
  77. package/lib/operations/create_collection.js.map +1 -1
  78. package/lib/operations/delete.js +6 -6
  79. package/lib/operations/delete.js.map +1 -1
  80. package/lib/operations/distinct.js +2 -2
  81. package/lib/operations/distinct.js.map +1 -1
  82. package/lib/operations/drop.js +8 -8
  83. package/lib/operations/drop.js.map +1 -1
  84. package/lib/operations/estimated_document_count.js +2 -2
  85. package/lib/operations/estimated_document_count.js.map +1 -1
  86. package/lib/operations/execute_operation.js +16 -10
  87. package/lib/operations/execute_operation.js.map +1 -1
  88. package/lib/operations/find.js +6 -3
  89. package/lib/operations/find.js.map +1 -1
  90. package/lib/operations/find_and_modify.js +2 -2
  91. package/lib/operations/find_and_modify.js.map +1 -1
  92. package/lib/operations/get_more.js +2 -1
  93. package/lib/operations/get_more.js.map +1 -1
  94. package/lib/operations/indexes.js +6 -6
  95. package/lib/operations/indexes.js.map +1 -1
  96. package/lib/operations/insert.js +6 -6
  97. package/lib/operations/insert.js.map +1 -1
  98. package/lib/operations/kill_cursors.js +5 -2
  99. package/lib/operations/kill_cursors.js.map +1 -1
  100. package/lib/operations/list_collections.js +2 -2
  101. package/lib/operations/list_collections.js.map +1 -1
  102. package/lib/operations/list_databases.js +2 -2
  103. package/lib/operations/list_databases.js.map +1 -1
  104. package/lib/operations/operation.js.map +1 -1
  105. package/lib/operations/profiling_level.js +2 -2
  106. package/lib/operations/profiling_level.js.map +1 -1
  107. package/lib/operations/remove_user.js +2 -2
  108. package/lib/operations/remove_user.js.map +1 -1
  109. package/lib/operations/rename.js +2 -2
  110. package/lib/operations/rename.js.map +1 -1
  111. package/lib/operations/run_command.js +6 -4
  112. package/lib/operations/run_command.js.map +1 -1
  113. package/lib/operations/search_indexes/create.js +5 -2
  114. package/lib/operations/search_indexes/create.js.map +1 -1
  115. package/lib/operations/search_indexes/drop.js +2 -2
  116. package/lib/operations/search_indexes/drop.js.map +1 -1
  117. package/lib/operations/search_indexes/update.js +2 -2
  118. package/lib/operations/search_indexes/update.js.map +1 -1
  119. package/lib/operations/set_profiling_level.js +2 -2
  120. package/lib/operations/set_profiling_level.js.map +1 -1
  121. package/lib/operations/stats.js +2 -2
  122. package/lib/operations/stats.js.map +1 -1
  123. package/lib/operations/update.js +8 -8
  124. package/lib/operations/update.js.map +1 -1
  125. package/lib/operations/validate_collection.js +2 -2
  126. package/lib/operations/validate_collection.js.map +1 -1
  127. package/lib/sdam/common.js +0 -7
  128. package/lib/sdam/common.js.map +1 -1
  129. package/lib/sdam/server.js +4 -1
  130. package/lib/sdam/server.js.map +1 -1
  131. package/lib/sdam/server_description.js +2 -0
  132. package/lib/sdam/server_description.js.map +1 -1
  133. package/lib/sdam/topology.js +38 -15
  134. package/lib/sdam/topology.js.map +1 -1
  135. package/lib/sessions.js +145 -74
  136. package/lib/sessions.js.map +1 -1
  137. package/lib/timeout.js +217 -16
  138. package/lib/timeout.js.map +1 -1
  139. package/lib/utils.js +31 -17
  140. package/lib/utils.js.map +1 -1
  141. package/lib/write_concern.js.map +1 -1
  142. package/mongodb.d.ts +558 -40
  143. package/package.json +3 -2
  144. package/src/admin.ts +6 -2
  145. package/src/bulk/common.ts +17 -5
  146. package/src/change_stream.ts +127 -52
  147. package/src/client-side-encryption/auto_encrypter.ts +12 -5
  148. package/src/client-side-encryption/client_encryption.ts +103 -20
  149. package/src/client-side-encryption/state_machine.ts +66 -32
  150. package/src/cmap/auth/mongo_credentials.ts +3 -2
  151. package/src/cmap/auth/mongodb_oidc/k8s_machine_workflow.ts +38 -0
  152. package/src/cmap/auth/mongodb_oidc.ts +3 -1
  153. package/src/cmap/connection.ts +105 -8
  154. package/src/cmap/connection_pool.ts +14 -14
  155. package/src/cmap/wire_protocol/on_data.ts +11 -1
  156. package/src/cmap/wire_protocol/responses.ts +35 -1
  157. package/src/collection.ts +81 -9
  158. package/src/connection_string.ts +2 -0
  159. package/src/cursor/abstract_cursor.ts +286 -39
  160. package/src/cursor/aggregation_cursor.ts +54 -8
  161. package/src/cursor/change_stream_cursor.ts +6 -2
  162. package/src/cursor/client_bulk_write_cursor.ts +6 -2
  163. package/src/cursor/find_cursor.ts +40 -9
  164. package/src/cursor/list_collections_cursor.ts +1 -1
  165. package/src/cursor/list_indexes_cursor.ts +1 -1
  166. package/src/cursor/run_command_cursor.ts +50 -5
  167. package/src/db.ts +75 -7
  168. package/src/error.ts +26 -1
  169. package/src/explain.ts +85 -0
  170. package/src/gridfs/download.ts +43 -4
  171. package/src/gridfs/index.ts +64 -16
  172. package/src/gridfs/upload.ts +152 -45
  173. package/src/index.ts +27 -5
  174. package/src/mongo_client.ts +75 -3
  175. package/src/operations/aggregate.ts +10 -2
  176. package/src/operations/bulk_write.ts +9 -2
  177. package/src/operations/client_bulk_write/client_bulk_write.ts +11 -3
  178. package/src/operations/client_bulk_write/executor.ts +15 -3
  179. package/src/operations/command.ts +18 -8
  180. package/src/operations/count.ts +10 -3
  181. package/src/operations/create_collection.ts +14 -7
  182. package/src/operations/delete.ts +15 -6
  183. package/src/operations/distinct.ts +7 -2
  184. package/src/operations/drop.ts +18 -8
  185. package/src/operations/estimated_document_count.ts +7 -2
  186. package/src/operations/execute_operation.ts +22 -13
  187. package/src/operations/find.ts +17 -5
  188. package/src/operations/find_and_modify.ts +7 -2
  189. package/src/operations/get_more.ts +4 -1
  190. package/src/operations/indexes.ts +20 -7
  191. package/src/operations/insert.ts +13 -6
  192. package/src/operations/kill_cursors.ts +10 -2
  193. package/src/operations/list_collections.ts +10 -1
  194. package/src/operations/list_databases.ts +9 -2
  195. package/src/operations/operation.ts +16 -2
  196. package/src/operations/profiling_level.ts +7 -2
  197. package/src/operations/remove_user.ts +7 -2
  198. package/src/operations/rename.ts +7 -2
  199. package/src/operations/run_command.ts +23 -4
  200. package/src/operations/search_indexes/create.ts +10 -2
  201. package/src/operations/search_indexes/drop.ts +7 -2
  202. package/src/operations/search_indexes/update.ts +7 -2
  203. package/src/operations/set_profiling_level.ts +4 -2
  204. package/src/operations/stats.ts +7 -2
  205. package/src/operations/update.ts +16 -8
  206. package/src/operations/validate_collection.ts +7 -2
  207. package/src/sdam/common.ts +0 -11
  208. package/src/sdam/server.ts +14 -4
  209. package/src/sdam/server_description.ts +4 -0
  210. package/src/sdam/topology.ts +43 -27
  211. package/src/sessions.ts +193 -89
  212. package/src/timeout.ts +310 -23
  213. package/src/transactions.ts +1 -1
  214. package/src/utils.ts +42 -28
  215. package/src/write_concern.ts +6 -3
@@ -21,6 +21,7 @@ import { ReadPreference, type ReadPreferenceLike } from '../read_preference';
21
21
  import { type AsyncDisposable, configureResourceManagement } from '../resource_management';
22
22
  import type { Server } from '../sdam/server';
23
23
  import { ClientSession, maybeClearPinnedConnection } from '../sessions';
24
+ import { type CSOTTimeoutContext, type Timeout, TimeoutContext } from '../timeout';
24
25
  import { type MongoDBNamespace, squashError } from '../utils';
25
26
 
26
27
  /**
@@ -60,6 +61,46 @@ export interface CursorStreamOptions {
60
61
  /** @public */
61
62
  export type CursorFlag = (typeof CURSOR_FLAGS)[number];
62
63
 
64
+ /**
65
+ * @public
66
+ * @experimental
67
+ * Specifies how `timeoutMS` is applied to the cursor. Can be either `'cursorLifeTime'` or `'iteration'`
68
+ * When set to `'iteration'`, the deadline specified by `timeoutMS` applies to each call of
69
+ * `cursor.next()`.
70
+ * When set to `'cursorLifetime'`, the deadline applies to the life of the entire cursor.
71
+ *
72
+ * Depending on the type of cursor being used, this option has different default values.
73
+ * For non-tailable cursors, this value defaults to `'cursorLifetime'`
74
+ * For tailable cursors, this value defaults to `'iteration'` since tailable cursors, by
75
+ * definition can have an arbitrarily long lifetime.
76
+ *
77
+ * @example
78
+ * ```ts
79
+ * const cursor = collection.find({}, {timeoutMS: 100, timeoutMode: 'iteration'});
80
+ * for await (const doc of cursor) {
81
+ * // process doc
82
+ * // This will throw a timeout error if any of the iterator's `next()` calls takes more than 100ms, but
83
+ * // will continue to iterate successfully otherwise, regardless of the number of batches.
84
+ * }
85
+ * ```
86
+ *
87
+ * @example
88
+ * ```ts
89
+ * const cursor = collection.find({}, { timeoutMS: 1000, timeoutMode: 'cursorLifetime' });
90
+ * const docs = await cursor.toArray(); // This entire line will throw a timeout error if all batches are not fetched and returned within 1000ms.
91
+ * ```
92
+ */
93
+ export const CursorTimeoutMode = Object.freeze({
94
+ ITERATION: 'iteration',
95
+ LIFETIME: 'cursorLifetime'
96
+ } as const);
97
+
98
+ /**
99
+ * @public
100
+ * @experimental
101
+ */
102
+ export type CursorTimeoutMode = (typeof CursorTimeoutMode)[keyof typeof CursorTimeoutMode];
103
+
63
104
  /** @public */
64
105
  export interface AbstractCursorOptions extends BSONSerializeOptions {
65
106
  session?: ClientSession;
@@ -103,8 +144,46 @@ export interface AbstractCursorOptions extends BSONSerializeOptions {
103
144
  */
104
145
  awaitData?: boolean;
105
146
  noCursorTimeout?: boolean;
106
- /** @internal TODO(NODE-5688): make this public */
147
+ /** 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. */
107
148
  timeoutMS?: number;
149
+ /**
150
+ * @public
151
+ * @experimental
152
+ * Specifies how `timeoutMS` is applied to the cursor. Can be either `'cursorLifeTime'` or `'iteration'`
153
+ * When set to `'iteration'`, the deadline specified by `timeoutMS` applies to each call of
154
+ * `cursor.next()`.
155
+ * When set to `'cursorLifetime'`, the deadline applies to the life of the entire cursor.
156
+ *
157
+ * Depending on the type of cursor being used, this option has different default values.
158
+ * For non-tailable cursors, this value defaults to `'cursorLifetime'`
159
+ * For tailable cursors, this value defaults to `'iteration'` since tailable cursors, by
160
+ * definition can have an arbitrarily long lifetime.
161
+ *
162
+ * @example
163
+ * ```ts
164
+ * const cursor = collection.find({}, {timeoutMS: 100, timeoutMode: 'iteration'});
165
+ * for await (const doc of cursor) {
166
+ * // process doc
167
+ * // This will throw a timeout error if any of the iterator's `next()` calls takes more than 100ms, but
168
+ * // will continue to iterate successfully otherwise, regardless of the number of batches.
169
+ * }
170
+ * ```
171
+ *
172
+ * @example
173
+ * ```ts
174
+ * const cursor = collection.find({}, { timeoutMS: 1000, timeoutMode: 'cursorLifetime' });
175
+ * const docs = await cursor.toArray(); // This entire line will throw a timeout error if all batches are not fetched and returned within 1000ms.
176
+ * ```
177
+ */
178
+ timeoutMode?: CursorTimeoutMode;
179
+
180
+ /**
181
+ * @internal
182
+ *
183
+ * A timeout context to govern the total time the cursor can live. If provided, the cursor
184
+ * cannot be used in ITERATION mode.
185
+ */
186
+ timeoutContext?: CursorTimeoutContext;
108
187
  }
109
188
 
110
189
  /** @internal */
@@ -117,6 +196,8 @@ export type InternalAbstractCursorOptions = Omit<AbstractCursorOptions, 'readPre
117
196
  oplogReplay?: boolean;
118
197
  exhaust?: boolean;
119
198
  partial?: boolean;
199
+
200
+ omitMaxTimeMS?: boolean;
120
201
  };
121
202
 
122
203
  /** @public */
@@ -146,7 +227,11 @@ export abstract class AbstractCursor<
146
227
  private cursorClient: MongoClient;
147
228
  /** @internal */
148
229
  private transform?: (doc: TSchema) => any;
149
- /** @internal */
230
+ /**
231
+ * @internal
232
+ * This is true whether or not the first command fails. It only indicates whether or not the first
233
+ * command has been run.
234
+ */
150
235
  private initialized: boolean;
151
236
  /** @internal */
152
237
  private isClosed: boolean;
@@ -154,6 +239,8 @@ export abstract class AbstractCursor<
154
239
  private isKilled: boolean;
155
240
  /** @internal */
156
241
  protected readonly cursorOptions: InternalAbstractCursorOptions;
242
+ /** @internal */
243
+ protected timeoutContext?: CursorTimeoutContext;
157
244
 
158
245
  /** @event */
159
246
  static readonly CLOSE = 'close' as const;
@@ -183,9 +270,50 @@ export abstract class AbstractCursor<
183
270
  options.readPreference && options.readPreference instanceof ReadPreference
184
271
  ? options.readPreference
185
272
  : ReadPreference.primary,
186
- ...pluckBSONSerializeOptions(options)
273
+ ...pluckBSONSerializeOptions(options),
274
+ timeoutMS: options?.timeoutContext?.csotEnabled()
275
+ ? options.timeoutContext.timeoutMS
276
+ : options.timeoutMS,
277
+ tailable: options.tailable,
278
+ awaitData: options.awaitData
187
279
  };
188
- this.cursorOptions.timeoutMS = options.timeoutMS;
280
+
281
+ if (this.cursorOptions.timeoutMS != null) {
282
+ if (options.timeoutMode == null) {
283
+ if (options.tailable) {
284
+ if (options.awaitData) {
285
+ if (
286
+ options.maxAwaitTimeMS != null &&
287
+ options.maxAwaitTimeMS >= this.cursorOptions.timeoutMS
288
+ )
289
+ throw new MongoInvalidArgumentError(
290
+ 'Cannot specify maxAwaitTimeMS >= timeoutMS for a tailable awaitData cursor'
291
+ );
292
+ }
293
+
294
+ this.cursorOptions.timeoutMode = CursorTimeoutMode.ITERATION;
295
+ } else {
296
+ this.cursorOptions.timeoutMode = CursorTimeoutMode.LIFETIME;
297
+ }
298
+ } else {
299
+ if (options.tailable && options.timeoutMode === CursorTimeoutMode.LIFETIME) {
300
+ throw new MongoInvalidArgumentError(
301
+ "Cannot set tailable cursor's timeoutMode to LIFETIME"
302
+ );
303
+ }
304
+ this.cursorOptions.timeoutMode = options.timeoutMode;
305
+ }
306
+ } else {
307
+ if (options.timeoutMode != null)
308
+ throw new MongoInvalidArgumentError('Cannot set timeoutMode without setting timeoutMS');
309
+ }
310
+
311
+ // Set for initial command
312
+ this.cursorOptions.omitMaxTimeMS =
313
+ this.cursorOptions.timeoutMS != null &&
314
+ ((this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION &&
315
+ !this.cursorOptions.tailable) ||
316
+ (this.cursorOptions.tailable && !this.cursorOptions.awaitData));
189
317
 
190
318
  const readConcern = ReadConcern.fromOptions(options);
191
319
  if (readConcern) {
@@ -222,6 +350,8 @@ export abstract class AbstractCursor<
222
350
  utf8: options?.enableUtf8Validation === false ? false : true
223
351
  }
224
352
  };
353
+
354
+ this.timeoutContext = options.timeoutContext;
225
355
  }
226
356
 
227
357
  /**
@@ -400,12 +530,21 @@ export abstract class AbstractCursor<
400
530
  return false;
401
531
  }
402
532
 
403
- do {
404
- if ((this.documents?.length ?? 0) !== 0) {
405
- return true;
533
+ if (this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION && this.cursorId != null) {
534
+ this.timeoutContext?.refresh();
535
+ }
536
+ try {
537
+ do {
538
+ if ((this.documents?.length ?? 0) !== 0) {
539
+ return true;
540
+ }
541
+ await this.fetchBatch();
542
+ } while (!this.isDead || (this.documents?.length ?? 0) !== 0);
543
+ } finally {
544
+ if (this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION) {
545
+ this.timeoutContext?.clear();
406
546
  }
407
- await this.fetchBatch();
408
- } while (!this.isDead || (this.documents?.length ?? 0) !== 0);
547
+ }
409
548
 
410
549
  return false;
411
550
  }
@@ -416,14 +555,24 @@ export abstract class AbstractCursor<
416
555
  throw new MongoCursorExhaustedError();
417
556
  }
418
557
 
419
- do {
420
- const doc = this.documents?.shift(this.deserializationOptions);
421
- if (doc != null) {
422
- if (this.transform != null) return await this.transformDocument(doc);
423
- return doc;
558
+ if (this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION && this.cursorId != null) {
559
+ this.timeoutContext?.refresh();
560
+ }
561
+
562
+ try {
563
+ do {
564
+ const doc = this.documents?.shift(this.deserializationOptions);
565
+ if (doc != null) {
566
+ if (this.transform != null) return await this.transformDocument(doc);
567
+ return doc;
568
+ }
569
+ await this.fetchBatch();
570
+ } while (!this.isDead || (this.documents?.length ?? 0) !== 0);
571
+ } finally {
572
+ if (this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION) {
573
+ this.timeoutContext?.clear();
424
574
  }
425
- await this.fetchBatch();
426
- } while (!this.isDead || (this.documents?.length ?? 0) !== 0);
575
+ }
427
576
 
428
577
  return null;
429
578
  }
@@ -436,18 +585,27 @@ export abstract class AbstractCursor<
436
585
  throw new MongoCursorExhaustedError();
437
586
  }
438
587
 
439
- let doc = this.documents?.shift(this.deserializationOptions);
440
- if (doc != null) {
441
- if (this.transform != null) return await this.transformDocument(doc);
442
- return doc;
588
+ if (this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION && this.cursorId != null) {
589
+ this.timeoutContext?.refresh();
443
590
  }
591
+ try {
592
+ let doc = this.documents?.shift(this.deserializationOptions);
593
+ if (doc != null) {
594
+ if (this.transform != null) return await this.transformDocument(doc);
595
+ return doc;
596
+ }
444
597
 
445
- await this.fetchBatch();
598
+ await this.fetchBatch();
446
599
 
447
- doc = this.documents?.shift(this.deserializationOptions);
448
- if (doc != null) {
449
- if (this.transform != null) return await this.transformDocument(doc);
450
- return doc;
600
+ doc = this.documents?.shift(this.deserializationOptions);
601
+ if (doc != null) {
602
+ if (this.transform != null) return await this.transformDocument(doc);
603
+ return doc;
604
+ }
605
+ } finally {
606
+ if (this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION) {
607
+ this.timeoutContext?.clear();
608
+ }
451
609
  }
452
610
 
453
611
  return null;
@@ -476,8 +634,8 @@ export abstract class AbstractCursor<
476
634
  /**
477
635
  * Frees any client-side resources used by the cursor.
478
636
  */
479
- async close(): Promise<void> {
480
- await this.cleanup();
637
+ async close(options?: { timeoutMS?: number }): Promise<void> {
638
+ await this.cleanup(options?.timeoutMS);
481
639
  }
482
640
 
483
641
  /**
@@ -652,12 +810,17 @@ export abstract class AbstractCursor<
652
810
  * if the resultant data has already been retrieved by this cursor.
653
811
  */
654
812
  rewind(): void {
813
+ if (this.timeoutContext && this.timeoutContext.owner !== this) {
814
+ throw new MongoAPIError(`Cannot rewind cursor that does not own its timeout context.`);
815
+ }
655
816
  if (!this.initialized) {
656
817
  return;
657
818
  }
658
819
 
659
820
  this.cursorId = null;
660
821
  this.documents?.clear();
822
+ this.timeoutContext?.clear();
823
+ this.timeoutContext = undefined;
661
824
  this.isClosed = false;
662
825
  this.isKilled = false;
663
826
  this.initialized = false;
@@ -696,18 +859,20 @@ export abstract class AbstractCursor<
696
859
  'Unexpected null selectedServer. A cursor creating command should have set this'
697
860
  );
698
861
  }
862
+ const getMoreOptions = {
863
+ ...this.cursorOptions,
864
+ session: this.cursorSession,
865
+ batchSize
866
+ };
867
+
699
868
  const getMoreOperation = new GetMoreOperation(
700
869
  this.cursorNamespace,
701
870
  this.cursorId,
702
871
  this.selectedServer,
703
- {
704
- ...this.cursorOptions,
705
- session: this.cursorSession,
706
- batchSize
707
- }
872
+ getMoreOptions
708
873
  );
709
874
 
710
- return await executeOperation(this.cursorClient, getMoreOperation);
875
+ return await executeOperation(this.cursorClient, getMoreOperation, this.timeoutContext);
711
876
  }
712
877
 
713
878
  /**
@@ -718,8 +883,19 @@ export abstract class AbstractCursor<
718
883
  * a significant refactor.
719
884
  */
720
885
  private async cursorInit(): Promise<void> {
886
+ if (this.cursorOptions.timeoutMS != null) {
887
+ this.timeoutContext ??= new CursorTimeoutContext(
888
+ TimeoutContext.create({
889
+ serverSelectionTimeoutMS: this.client.s.options.serverSelectionTimeoutMS,
890
+ timeoutMS: this.cursorOptions.timeoutMS
891
+ }),
892
+ this
893
+ );
894
+ }
721
895
  try {
722
896
  const state = await this._initialize(this.cursorSession);
897
+ // Set omitMaxTimeMS to the value needed for subsequent getMore calls
898
+ this.cursorOptions.omitMaxTimeMS = this.cursorOptions.timeoutMS != null;
723
899
  const response = state.response;
724
900
  this.selectedServer = state.server;
725
901
  this.cursorId = response.id;
@@ -729,7 +905,7 @@ export abstract class AbstractCursor<
729
905
  } catch (error) {
730
906
  // the cursor is now initialized, even if an error occurred
731
907
  this.initialized = true;
732
- await this.cleanup(error);
908
+ await this.cleanup(undefined, error);
733
909
  throw error;
734
910
  }
735
911
 
@@ -770,10 +946,10 @@ export abstract class AbstractCursor<
770
946
  this.documents = response;
771
947
  } catch (error) {
772
948
  try {
773
- await this.cleanup(error);
774
- } catch (error) {
949
+ await this.cleanup(undefined, error);
950
+ } catch (cleanupError) {
775
951
  // `cleanupCursor` should never throw, squash and throw the original error
776
- squashError(error);
952
+ squashError(cleanupError);
777
953
  }
778
954
  throw error;
779
955
  }
@@ -791,9 +967,23 @@ export abstract class AbstractCursor<
791
967
  }
792
968
 
793
969
  /** @internal */
794
- private async cleanup(error?: Error) {
970
+ private async cleanup(timeoutMS?: number, error?: Error) {
795
971
  this.isClosed = true;
796
972
  const session = this.cursorSession;
973
+ const timeoutContextForKillCursors = (): CursorTimeoutContext | undefined => {
974
+ if (timeoutMS != null) {
975
+ this.timeoutContext?.clear();
976
+ return new CursorTimeoutContext(
977
+ TimeoutContext.create({
978
+ serverSelectionTimeoutMS: this.client.s.options.serverSelectionTimeoutMS,
979
+ timeoutMS
980
+ }),
981
+ this
982
+ );
983
+ } else {
984
+ return this.timeoutContext?.refreshed();
985
+ }
986
+ };
797
987
  try {
798
988
  if (
799
989
  !this.isKilled &&
@@ -806,11 +996,13 @@ export abstract class AbstractCursor<
806
996
  this.isKilled = true;
807
997
  const cursorId = this.cursorId;
808
998
  this.cursorId = Long.ZERO;
999
+
809
1000
  await executeOperation(
810
1001
  this.cursorClient,
811
1002
  new KillCursorsOperation(cursorId, this.cursorNamespace, this.selectedServer, {
812
1003
  session
813
- })
1004
+ }),
1005
+ timeoutContextForKillCursors()
814
1006
  );
815
1007
  }
816
1008
  } catch (error) {
@@ -952,3 +1144,58 @@ class ReadableCursorStream extends Readable {
952
1144
  }
953
1145
 
954
1146
  configureResourceManagement(AbstractCursor.prototype);
1147
+
1148
+ /**
1149
+ * @internal
1150
+ * The cursor timeout context is a wrapper around a timeout context
1151
+ * that keeps track of the "owner" of the cursor. For timeout contexts
1152
+ * instantiated inside a cursor, the owner will be the cursor.
1153
+ *
1154
+ * All timeout behavior is exactly the same as the wrapped timeout context's.
1155
+ */
1156
+ export class CursorTimeoutContext extends TimeoutContext {
1157
+ constructor(
1158
+ public timeoutContext: TimeoutContext,
1159
+ public owner: symbol | AbstractCursor
1160
+ ) {
1161
+ super();
1162
+ }
1163
+ override get serverSelectionTimeout(): Timeout | null {
1164
+ return this.timeoutContext.serverSelectionTimeout;
1165
+ }
1166
+ override get connectionCheckoutTimeout(): Timeout | null {
1167
+ return this.timeoutContext.connectionCheckoutTimeout;
1168
+ }
1169
+ override get clearServerSelectionTimeout(): boolean {
1170
+ return this.timeoutContext.clearServerSelectionTimeout;
1171
+ }
1172
+ override get clearConnectionCheckoutTimeout(): boolean {
1173
+ return this.timeoutContext.clearConnectionCheckoutTimeout;
1174
+ }
1175
+ override get timeoutForSocketWrite(): Timeout | null {
1176
+ return this.timeoutContext.timeoutForSocketWrite;
1177
+ }
1178
+ override get timeoutForSocketRead(): Timeout | null {
1179
+ return this.timeoutContext.timeoutForSocketRead;
1180
+ }
1181
+ override csotEnabled(): this is CSOTTimeoutContext {
1182
+ return this.timeoutContext.csotEnabled();
1183
+ }
1184
+ override refresh(): void {
1185
+ if (typeof this.owner !== 'symbol') return this.timeoutContext.refresh();
1186
+ }
1187
+ override clear(): void {
1188
+ if (typeof this.owner !== 'symbol') return this.timeoutContext.clear();
1189
+ }
1190
+ override get maxTimeMS(): number | null {
1191
+ return this.timeoutContext.maxTimeMS;
1192
+ }
1193
+
1194
+ get timeoutMS(): number | null {
1195
+ return this.timeoutContext.csotEnabled() ? this.timeoutContext.timeoutMS : null;
1196
+ }
1197
+
1198
+ override refreshed(): CursorTimeoutContext {
1199
+ return new CursorTimeoutContext(this.timeoutContext.refreshed(), this.owner);
1200
+ }
1201
+ }
@@ -1,5 +1,12 @@
1
1
  import type { Document } from '../bson';
2
- import type { ExplainCommandOptions, ExplainVerbosityLike } from '../explain';
2
+ import { MongoAPIError } from '../error';
3
+ import {
4
+ Explain,
5
+ ExplainableCursor,
6
+ type ExplainCommandOptions,
7
+ type ExplainVerbosityLike,
8
+ validateExplainTimeoutOptions
9
+ } from '../explain';
3
10
  import type { MongoClient } from '../mongo_client';
4
11
  import { AggregateOperation, type AggregateOptions } from '../operations/aggregate';
5
12
  import { executeOperation } from '../operations/execute_operation';
@@ -7,8 +14,8 @@ import type { ClientSession } from '../sessions';
7
14
  import type { Sort } from '../sort';
8
15
  import { mergeOptions, type MongoDBNamespace } from '../utils';
9
16
  import {
10
- AbstractCursor,
11
17
  type AbstractCursorOptions,
18
+ CursorTimeoutMode,
12
19
  type InitialCursorResponse
13
20
  } from './abstract_cursor';
14
21
 
@@ -22,7 +29,7 @@ export interface AggregationCursorOptions extends AbstractCursorOptions, Aggrega
22
29
  * or higher stream
23
30
  * @public
24
31
  */
25
- export class AggregationCursor<TSchema = any> extends AbstractCursor<TSchema> {
32
+ export class AggregationCursor<TSchema = any> extends ExplainableCursor<TSchema> {
26
33
  public readonly pipeline: Document[];
27
34
  /** @internal */
28
35
  private aggregateOptions: AggregateOptions;
@@ -38,6 +45,15 @@ export class AggregationCursor<TSchema = any> extends AbstractCursor<TSchema> {
38
45
 
39
46
  this.pipeline = pipeline;
40
47
  this.aggregateOptions = options;
48
+
49
+ const lastStage: Document | undefined = this.pipeline[this.pipeline.length - 1];
50
+
51
+ if (
52
+ this.cursorOptions.timeoutMS != null &&
53
+ this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION &&
54
+ (lastStage?.$merge != null || lastStage?.$out != null)
55
+ )
56
+ throw new MongoAPIError('Cannot use $out or $merge stage with ITERATION timeoutMode');
41
57
  }
42
58
 
43
59
  clone(): AggregationCursor<TSchema> {
@@ -54,26 +70,49 @@ export class AggregationCursor<TSchema = any> extends AbstractCursor<TSchema> {
54
70
 
55
71
  /** @internal */
56
72
  async _initialize(session: ClientSession): Promise<InitialCursorResponse> {
57
- const aggregateOperation = new AggregateOperation(this.namespace, this.pipeline, {
73
+ const options = {
58
74
  ...this.aggregateOptions,
59
75
  ...this.cursorOptions,
60
76
  session
61
- });
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 aggregateOptions'
84
+ );
85
+ }
86
+ }
87
+
88
+ const aggregateOperation = new AggregateOperation(this.namespace, this.pipeline, options);
62
89
 
63
- const response = await executeOperation(this.client, aggregateOperation);
90
+ const response = await executeOperation(this.client, aggregateOperation, this.timeoutContext);
64
91
 
65
92
  return { server: aggregateOperation.server, session, response };
66
93
  }
67
94
 
68
95
  /** Execute the explain for the cursor */
69
- async explain(verbosity?: ExplainVerbosityLike | ExplainCommandOptions): Promise<Document> {
96
+ async explain(): Promise<Document>;
97
+ async explain(verbosity: ExplainVerbosityLike | ExplainCommandOptions): Promise<Document>;
98
+ async explain(options: { timeoutMS?: number }): Promise<Document>;
99
+ async explain(
100
+ verbosity: ExplainVerbosityLike | ExplainCommandOptions,
101
+ options: { timeoutMS?: number }
102
+ ): Promise<Document>;
103
+ async explain(
104
+ verbosity?: ExplainVerbosityLike | ExplainCommandOptions | { timeoutMS?: number },
105
+ options?: { timeoutMS?: number }
106
+ ): Promise<Document> {
107
+ const { explain, timeout } = this.resolveExplainTimeoutOptions(verbosity, options);
70
108
  return (
71
109
  await executeOperation(
72
110
  this.client,
73
111
  new AggregateOperation(this.namespace, this.pipeline, {
74
112
  ...this.aggregateOptions, // NOTE: order matters here, we may need to refine this
75
113
  ...this.cursorOptions,
76
- explain: verbosity ?? true
114
+ ...timeout,
115
+ explain: explain ?? true
77
116
  })
78
117
  )
79
118
  ).shift(this.deserializationOptions);
@@ -95,6 +134,13 @@ export class AggregationCursor<TSchema = any> extends AbstractCursor<TSchema> {
95
134
  addStage<T = Document>(stage: Document): AggregationCursor<T>;
96
135
  addStage<T = Document>(stage: Document): AggregationCursor<T> {
97
136
  this.throwIfInitialized();
137
+ if (
138
+ this.cursorOptions.timeoutMS != null &&
139
+ this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION &&
140
+ (stage.$out != null || stage.$merge != null)
141
+ ) {
142
+ throw new MongoAPIError('Cannot use $out or $merge stage with ITERATION timeoutMode');
143
+ }
98
144
  this.pipeline.push(stage);
99
145
  return this as unknown as AggregationCursor<T>;
100
146
  }
@@ -55,7 +55,7 @@ export class ChangeStreamCursor<
55
55
  pipeline: Document[] = [],
56
56
  options: ChangeStreamCursorOptions = {}
57
57
  ) {
58
- super(client, namespace, options);
58
+ super(client, namespace, { ...options, tailable: true, awaitData: true });
59
59
 
60
60
  this.pipeline = pipeline;
61
61
  this.changeStreamCursorOptions = options;
@@ -133,7 +133,11 @@ export class ChangeStreamCursor<
133
133
  session
134
134
  });
135
135
 
136
- const response = await executeOperation(session.client, aggregateOperation);
136
+ const response = await executeOperation(
137
+ session.client,
138
+ aggregateOperation,
139
+ this.timeoutContext
140
+ );
137
141
 
138
142
  const server = aggregateOperation.server;
139
143
  this.maxWireVersion = maxWireVersion(server);
@@ -34,7 +34,7 @@ export class ClientBulkWriteCursor extends AbstractCursor {
34
34
  constructor(
35
35
  client: MongoClient,
36
36
  commandBuilder: ClientBulkWriteCommandBuilder,
37
- options: ClientBulkWriteOptions = {}
37
+ options: ClientBulkWriteCursorOptions = {}
38
38
  ) {
39
39
  super(client, new MongoDBNamespace('admin', '$cmd'), options);
40
40
 
@@ -71,7 +71,11 @@ export class ClientBulkWriteCursor extends AbstractCursor {
71
71
  session
72
72
  });
73
73
 
74
- const response = await executeOperation(this.client, clientBulkWriteOperation);
74
+ const response = await executeOperation(
75
+ this.client,
76
+ clientBulkWriteOperation,
77
+ this.timeoutContext
78
+ );
75
79
  this.cursorResponse = response;
76
80
 
77
81
  return { server: clientBulkWriteOperation.server, session, response };