mongodb 4.1.1 → 4.2.0

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 (174) hide show
  1. package/README.md +24 -17
  2. package/lib/admin.js +5 -5
  3. package/lib/admin.js.map +1 -1
  4. package/lib/bulk/common.js +60 -56
  5. package/lib/bulk/common.js.map +1 -1
  6. package/lib/change_stream.js +15 -13
  7. package/lib/change_stream.js.map +1 -1
  8. package/lib/cmap/auth/gssapi.js +1 -1
  9. package/lib/cmap/auth/gssapi.js.map +1 -1
  10. package/lib/cmap/auth/mongo_credentials.js +2 -1
  11. package/lib/cmap/auth/mongo_credentials.js.map +1 -1
  12. package/lib/cmap/auth/mongocr.js +2 -2
  13. package/lib/cmap/auth/mongocr.js.map +1 -1
  14. package/lib/cmap/auth/mongodb_aws.js +23 -28
  15. package/lib/cmap/auth/mongodb_aws.js.map +1 -1
  16. package/lib/cmap/auth/plain.js +1 -1
  17. package/lib/cmap/auth/plain.js.map +1 -1
  18. package/lib/cmap/auth/scram.js +5 -5
  19. package/lib/cmap/auth/scram.js.map +1 -1
  20. package/lib/cmap/auth/x509.js +1 -1
  21. package/lib/cmap/auth/x509.js.map +1 -1
  22. package/lib/cmap/command_monitoring_events.js +15 -15
  23. package/lib/cmap/command_monitoring_events.js.map +1 -1
  24. package/lib/cmap/commands.js +1 -1
  25. package/lib/cmap/commands.js.map +1 -1
  26. package/lib/cmap/connect.js +2 -2
  27. package/lib/cmap/connect.js.map +1 -1
  28. package/lib/cmap/connection.js +15 -15
  29. package/lib/cmap/connection.js.map +1 -1
  30. package/lib/cmap/connection_pool.js +3 -3
  31. package/lib/cmap/connection_pool.js.map +1 -1
  32. package/lib/cmap/message_stream.js +2 -2
  33. package/lib/cmap/message_stream.js.map +1 -1
  34. package/lib/cmap/stream_description.js +1 -1
  35. package/lib/cmap/stream_description.js.map +1 -1
  36. package/lib/cmap/wire_protocol/constants.js +4 -4
  37. package/lib/collection.js +42 -41
  38. package/lib/collection.js.map +1 -1
  39. package/lib/connection_string.js +94 -58
  40. package/lib/connection_string.js.map +1 -1
  41. package/lib/cursor/abstract_cursor.js +11 -11
  42. package/lib/cursor/abstract_cursor.js.map +1 -1
  43. package/lib/cursor/aggregation_cursor.js +15 -15
  44. package/lib/cursor/aggregation_cursor.js.map +1 -1
  45. package/lib/cursor/find_cursor.js +23 -23
  46. package/lib/cursor/find_cursor.js.map +1 -1
  47. package/lib/db.js +20 -20
  48. package/lib/db.js.map +1 -1
  49. package/lib/deps.js +1 -1
  50. package/lib/deps.js.map +1 -1
  51. package/lib/gridfs/index.js +3 -3
  52. package/lib/gridfs/index.js.map +1 -1
  53. package/lib/gridfs/upload.js +1 -1
  54. package/lib/gridfs/upload.js.map +1 -1
  55. package/lib/index.js +8 -2
  56. package/lib/index.js.map +1 -1
  57. package/lib/logger.js +5 -5
  58. package/lib/logger.js.map +1 -1
  59. package/lib/mongo_client.js +7 -7
  60. package/lib/mongo_client.js.map +1 -1
  61. package/lib/mongo_types.js.map +1 -1
  62. package/lib/operations/add_user.js +3 -3
  63. package/lib/operations/add_user.js.map +1 -1
  64. package/lib/operations/aggregate.js +3 -4
  65. package/lib/operations/aggregate.js.map +1 -1
  66. package/lib/operations/bulk_write.js +1 -1
  67. package/lib/operations/bulk_write.js.map +1 -1
  68. package/lib/operations/command.js +7 -3
  69. package/lib/operations/command.js.map +1 -1
  70. package/lib/operations/common_functions.js +1 -1
  71. package/lib/operations/common_functions.js.map +1 -1
  72. package/lib/operations/connect.js +1 -1
  73. package/lib/operations/connect.js.map +1 -1
  74. package/lib/operations/count.js +1 -1
  75. package/lib/operations/count.js.map +1 -1
  76. package/lib/operations/count_documents.js.map +1 -1
  77. package/lib/operations/create_collection.js +1 -1
  78. package/lib/operations/create_collection.js.map +1 -1
  79. package/lib/operations/delete.js +6 -6
  80. package/lib/operations/delete.js.map +1 -1
  81. package/lib/operations/distinct.js +4 -4
  82. package/lib/operations/distinct.js.map +1 -1
  83. package/lib/operations/drop.js +2 -2
  84. package/lib/operations/drop.js.map +1 -1
  85. package/lib/operations/estimated_document_count.js +2 -2
  86. package/lib/operations/estimated_document_count.js.map +1 -1
  87. package/lib/operations/eval.js.map +1 -1
  88. package/lib/operations/execute_operation.js +17 -7
  89. package/lib/operations/execute_operation.js.map +1 -1
  90. package/lib/operations/find.js +8 -8
  91. package/lib/operations/find.js.map +1 -1
  92. package/lib/operations/find_and_modify.js +7 -7
  93. package/lib/operations/find_and_modify.js.map +1 -1
  94. package/lib/operations/indexes.js +14 -14
  95. package/lib/operations/indexes.js.map +1 -1
  96. package/lib/operations/insert.js +5 -5
  97. package/lib/operations/insert.js.map +1 -1
  98. package/lib/operations/list_collections.js +12 -7
  99. package/lib/operations/list_collections.js.map +1 -1
  100. package/lib/operations/list_databases.js +1 -1
  101. package/lib/operations/list_databases.js.map +1 -1
  102. package/lib/operations/map_reduce.js +6 -6
  103. package/lib/operations/map_reduce.js.map +1 -1
  104. package/lib/operations/operation.js +2 -1
  105. package/lib/operations/operation.js.map +1 -1
  106. package/lib/operations/remove_user.js +1 -1
  107. package/lib/operations/remove_user.js.map +1 -1
  108. package/lib/operations/rename.js +2 -2
  109. package/lib/operations/rename.js.map +1 -1
  110. package/lib/operations/set_profiling_level.js +1 -1
  111. package/lib/operations/set_profiling_level.js.map +1 -1
  112. package/lib/operations/stats.js +2 -2
  113. package/lib/operations/stats.js.map +1 -1
  114. package/lib/operations/update.js +12 -12
  115. package/lib/operations/update.js.map +1 -1
  116. package/lib/sdam/monitor.js +14 -14
  117. package/lib/sdam/monitor.js.map +1 -1
  118. package/lib/sdam/server.js +19 -12
  119. package/lib/sdam/server.js.map +1 -1
  120. package/lib/sdam/server_description.js +3 -3
  121. package/lib/sdam/server_description.js.map +1 -1
  122. package/lib/sdam/server_selection.js +20 -1
  123. package/lib/sdam/server_selection.js.map +1 -1
  124. package/lib/sdam/srv_polling.js +9 -9
  125. package/lib/sdam/srv_polling.js.map +1 -1
  126. package/lib/sdam/topology.js +29 -21
  127. package/lib/sdam/topology.js.map +1 -1
  128. package/lib/sdam/topology_description.js +34 -12
  129. package/lib/sdam/topology_description.js.map +1 -1
  130. package/lib/sessions.js +19 -19
  131. package/lib/sessions.js.map +1 -1
  132. package/lib/transactions.js.map +1 -1
  133. package/lib/utils.js +53 -46
  134. package/lib/utils.js.map +1 -1
  135. package/mongodb.d.ts +97 -50
  136. package/mongodb.ts34.d.ts +96 -50
  137. package/package.json +44 -47
  138. package/src/bulk/common.ts +56 -35
  139. package/src/change_stream.ts +8 -6
  140. package/src/cmap/auth/mongo_credentials.ts +14 -3
  141. package/src/cmap/auth/mongodb_aws.ts +32 -31
  142. package/src/cmap/command_monitoring_events.ts +1 -1
  143. package/src/cmap/connection.ts +1 -0
  144. package/src/cmap/wire_protocol/constants.ts +4 -4
  145. package/src/collection.ts +32 -24
  146. package/src/connection_string.ts +103 -52
  147. package/src/cursor/abstract_cursor.ts +6 -4
  148. package/src/cursor/aggregation_cursor.ts +3 -3
  149. package/src/cursor/find_cursor.ts +1 -1
  150. package/src/db.ts +5 -6
  151. package/src/deps.ts +56 -19
  152. package/src/gridfs/download.ts +1 -1
  153. package/src/gridfs/upload.ts +11 -7
  154. package/src/index.ts +12 -1
  155. package/src/mongo_client.ts +18 -11
  156. package/src/mongo_types.ts +10 -13
  157. package/src/operations/aggregate.ts +1 -2
  158. package/src/operations/command.ts +5 -0
  159. package/src/operations/count.ts +1 -1
  160. package/src/operations/count_documents.ts +1 -1
  161. package/src/operations/create_collection.ts +1 -1
  162. package/src/operations/eval.ts +1 -1
  163. package/src/operations/execute_operation.ts +13 -2
  164. package/src/operations/indexes.ts +2 -2
  165. package/src/operations/list_collections.ts +12 -4
  166. package/src/operations/list_databases.ts +6 -1
  167. package/src/operations/operation.ts +3 -0
  168. package/src/sdam/server.ts +8 -0
  169. package/src/sdam/server_selection.ts +25 -0
  170. package/src/sdam/srv_polling.ts +12 -11
  171. package/src/sdam/topology.ts +27 -10
  172. package/src/sdam/topology_description.ts +35 -11
  173. package/src/transactions.ts +2 -2
  174. package/src/utils.ts +67 -56
@@ -38,9 +38,11 @@ export type WithId<TSchema> = EnhancedOmit<TSchema, '_id'> & { _id: InferIdType<
38
38
  * `TSchema['_id'] extends ObjectId` which translated to "Is the _id property ObjectId?"
39
39
  * we instead ask "Does ObjectId look like (have the same shape) as the _id?"
40
40
  */
41
- export type OptionalId<TSchema extends { _id?: any }> = ObjectId extends TSchema['_id'] // a Schema with ObjectId _id type or "any" or "indexed type" provided
42
- ? EnhancedOmit<TSchema, '_id'> & { _id?: InferIdType<TSchema> } // a Schema provided but _id type is not ObjectId
43
- : WithId<TSchema>; // TODO(NODE-3285): Improve type readability
41
+ export type OptionalId<TSchema> = TSchema extends { _id?: any }
42
+ ? ObjectId extends TSchema['_id'] // a Schema with ObjectId _id type or "any" or "indexed type" provided
43
+ ? EnhancedOmit<TSchema, '_id'> & { _id?: InferIdType<TSchema> } // a Schema provided but _id type is not ObjectId
44
+ : WithId<TSchema>
45
+ : EnhancedOmit<TSchema, '_id'> & { _id?: InferIdType<TSchema> }; // TODO(NODE-3285): Improve type readability
44
46
 
45
47
  /** TypeScript Omit (Exclude to be specific) does not work for objects with an "any" indexed type, and breaks discriminated unions @public */
46
48
  export type EnhancedOmit<TRecordOrUnion, KeyUnion> = string extends keyof TRecordOrUnion
@@ -55,8 +57,7 @@ export type WithoutId<TSchema> = Omit<TSchema, '_id'>;
55
57
  /** A MongoDB filter can be some portion of the schema or a set of operators @public */
56
58
  export type Filter<TSchema> = {
57
59
  [P in keyof TSchema]?: Condition<TSchema[P]>;
58
- } &
59
- RootFilterOperators<TSchema>;
60
+ } & RootFilterOperators<TSchema>;
60
61
 
61
62
  /** @public */
62
63
  export type Condition<T> = AlternativeType<T> | FilterOperators<AlternativeType<T>>;
@@ -255,8 +256,7 @@ export type SetFields<TSchema> = ({
255
256
  readonly [key in KeysOfAType<TSchema, ReadonlyArray<any> | undefined>]?:
256
257
  | OptionalId<Flatten<TSchema[key]>>
257
258
  | AddToSetOperators<Array<OptionalId<Flatten<TSchema[key]>>>>;
258
- } &
259
- NotAcceptedFields<TSchema, ReadonlyArray<any> | undefined>) & {
259
+ } & NotAcceptedFields<TSchema, ReadonlyArray<any> | undefined>) & {
260
260
  readonly [key: string]: AddToSetOperators<any> | any;
261
261
  };
262
262
 
@@ -265,8 +265,7 @@ export type PushOperator<TSchema> = ({
265
265
  readonly [key in KeysOfAType<TSchema, ReadonlyArray<any>>]?:
266
266
  | Flatten<TSchema[key]>
267
267
  | ArrayOperator<Array<Flatten<TSchema[key]>>>;
268
- } &
269
- NotAcceptedFields<TSchema, ReadonlyArray<any>>) & {
268
+ } & NotAcceptedFields<TSchema, ReadonlyArray<any>>) & {
270
269
  readonly [key: string]: ArrayOperator<any> | any;
271
270
  };
272
271
 
@@ -275,16 +274,14 @@ export type PullOperator<TSchema> = ({
275
274
  readonly [key in KeysOfAType<TSchema, ReadonlyArray<any>>]?:
276
275
  | Partial<Flatten<TSchema[key]>>
277
276
  | FilterOperations<Flatten<TSchema[key]>>;
278
- } &
279
- NotAcceptedFields<TSchema, ReadonlyArray<any>>) & {
277
+ } & NotAcceptedFields<TSchema, ReadonlyArray<any>>) & {
280
278
  readonly [key: string]: FilterOperators<any> | any;
281
279
  };
282
280
 
283
281
  /** @public */
284
282
  export type PullAllOperator<TSchema> = ({
285
283
  readonly [key in KeysOfAType<TSchema, ReadonlyArray<any>>]?: TSchema[key];
286
- } &
287
- NotAcceptedFields<TSchema, ReadonlyArray<any>>) & {
284
+ } & NotAcceptedFields<TSchema, ReadonlyArray<any>>) & {
288
285
  readonly [key: string]: ReadonlyArray<any>;
289
286
  };
290
287
 
@@ -1,5 +1,4 @@
1
1
  import { CommandOperation, CommandOperationOptions, CollationOptions } from './command';
2
- import { ReadPreference } from '../read_preference';
3
2
  import { MongoInvalidArgumentError } from '../error';
4
3
  import { maxWireVersion, MongoDBNamespace } from '../utils';
5
4
  import { Aspect, defineAspects, Hint } from './operation';
@@ -65,7 +64,7 @@ export class AggregateOperation<T = Document> extends CommandOperation<T> {
65
64
  }
66
65
 
67
66
  if (this.hasWriteStage) {
68
- this.readPreference = ReadPreference.primary;
67
+ this.trySecondaryWrite = true;
69
68
  }
70
69
 
71
70
  if (this.explain && this.writeConcern) {
@@ -10,6 +10,7 @@ import type { Server } from '../sdam/server';
10
10
  import type { BSONSerializeOptions, Document } from '../bson';
11
11
  import type { ReadConcernLike } from './../read_concern';
12
12
  import { Explain, ExplainOptions } from '../explain';
13
+ import { MIN_SECONDARY_WRITE_WIRE_VERSION } from '../sdam/server_selection';
13
14
 
14
15
  const SUPPORTS_WRITE_CONCERN_AND_COLLATION = 5;
15
16
 
@@ -126,6 +127,10 @@ export abstract class CommandOperation<T> extends AbstractOperation<T> {
126
127
  Object.assign(cmd, { readConcern: this.readConcern });
127
128
  }
128
129
 
130
+ if (this.trySecondaryWrite && serverWireVersion < MIN_SECONDARY_WRITE_WIRE_VERSION) {
131
+ options.omitReadPreference = true;
132
+ }
133
+
129
134
  if (options.collation && serverWireVersion < SUPPORTS_WRITE_CONCERN_AND_COLLATION) {
130
135
  callback(
131
136
  new MongoCompatibilityError(
@@ -25,7 +25,7 @@ export class CountOperation extends CommandOperation<number> {
25
25
  query: Document;
26
26
 
27
27
  constructor(namespace: MongoDBNamespace, filter: Document, options: CountOptions) {
28
- super(({ s: { namespace: namespace } } as unknown) as Collection, options);
28
+ super({ s: { namespace: namespace } } as unknown as Collection, options);
29
29
 
30
30
  this.options = options;
31
31
  this.collectionName = namespace.collection;
@@ -40,7 +40,7 @@ export class CountDocumentsOperation extends AggregateOperation<number> {
40
40
  }
41
41
 
42
42
  // NOTE: We're avoiding creating a cursor here to reduce the callstack.
43
- const response = (result as unknown) as Document;
43
+ const response = result as unknown as Document;
44
44
  if (response.cursor == null || response.cursor.firstBatch == null) {
45
45
  callback(undefined, 0);
46
46
  return;
@@ -37,7 +37,7 @@ const ILLEGAL_COMMAND_FIELDS = new Set([
37
37
  export interface TimeSeriesCollectionOptions extends Document {
38
38
  timeField: string;
39
39
  metaField?: string;
40
- granularity?: string;
40
+ granularity?: 'seconds' | 'minutes' | 'hours' | string;
41
41
  }
42
42
 
43
43
  /** @public */
@@ -43,7 +43,7 @@ export class EvalOperation extends CommandOperation<Document> {
43
43
  let finalParameters: Document[] = [];
44
44
 
45
45
  // If not a code object translate to one
46
- if (!(finalCode && ((finalCode as unknown) as { _bsontype: string })._bsontype === 'Code')) {
46
+ if (!(finalCode && (finalCode as unknown as { _bsontype: string })._bsontype === 'Code')) {
47
47
  finalCode = new Code(finalCode as never);
48
48
  }
49
49
 
@@ -17,6 +17,7 @@ import type { Topology } from '../sdam/topology';
17
17
  import type { ClientSession } from '../sessions';
18
18
  import type { Document } from '../bson';
19
19
  import { supportsRetryableWrites } from '../utils';
20
+ import { secondaryWritableServerSelector, ServerSelector } from '../sdam/server_selection';
20
21
 
21
22
  const MMAPv1_RETRY_WRITES_ERROR_CODE = MONGODB_ERROR_CODES.IllegalOperation;
22
23
  const MMAPv1_RETRY_WRITES_ERROR_MESSAGE =
@@ -150,6 +151,16 @@ function executeWithServerSelection(
150
151
  session.unpin();
151
152
  }
152
153
 
154
+ let selector: ReadPreference | ServerSelector;
155
+
156
+ // If operation should try to write to secondary use the custom server selector
157
+ // otherwise provide the read preference.
158
+ if (operation.trySecondaryWrite) {
159
+ selector = secondaryWritableServerSelector(topology.commonWireVersion, readPreference);
160
+ } else {
161
+ selector = readPreference;
162
+ }
163
+
153
164
  const serverSelectionOptions = { session };
154
165
  function callbackWithRetry(err?: any, result?: any) {
155
166
  if (err == null) {
@@ -182,7 +193,7 @@ function executeWithServerSelection(
182
193
  }
183
194
 
184
195
  // select a new server, and attempt to retry the operation
185
- topology.selectServer(readPreference, serverSelectionOptions, (e?: any, server?: any) => {
196
+ topology.selectServer(selector, serverSelectionOptions, (e?: any, server?: any) => {
186
197
  if (
187
198
  e ||
188
199
  (operation.hasAspect(Aspect.READ_OPERATION) && !supportsRetryableReads(server)) ||
@@ -227,7 +238,7 @@ function executeWithServerSelection(
227
238
  }
228
239
 
229
240
  // select a server, and execute the operation against it
230
- topology.selectServer(readPreference, serverSelectionOptions, (err?: any, server?: any) => {
241
+ topology.selectServer(selector, serverSelectionOptions, (err?: any, server?: any) => {
231
242
  if (err) {
232
243
  callback(err);
233
244
  return;
@@ -159,7 +159,7 @@ function makeIndexSpec(indexSpec: IndexSpecification, options: any): IndexDescri
159
159
  }
160
160
 
161
161
  /** @internal */
162
- export class IndexesOperation extends AbstractOperation<Document> {
162
+ export class IndexesOperation extends AbstractOperation<Document[]> {
163
163
  options: IndexInformationOptions;
164
164
  collection: Collection;
165
165
 
@@ -169,7 +169,7 @@ export class IndexesOperation extends AbstractOperation<Document> {
169
169
  this.collection = collection;
170
170
  }
171
171
 
172
- execute(server: Server, session: ClientSession, callback: Callback<Document>): void {
172
+ execute(server: Server, session: ClientSession, callback: Callback<Document[]>): void {
173
173
  const coll = this.collection;
174
174
  const options = this.options;
175
175
 
@@ -15,6 +15,8 @@ const LIST_COLLECTIONS_WIRE_VERSION = 3;
15
15
  export interface ListCollectionsOptions extends CommandOperationOptions {
16
16
  /** Since 4.0: If true, will only return the collection name in the response, and will omit additional info */
17
17
  nameOnly?: boolean;
18
+ /** Since 4.0: If true and nameOnly is true, allows a user without the required privilege (i.e. listCollections action on the database) to run the command when access control is enforced. */
19
+ authorizedCollections?: boolean;
18
20
  /** The batchSize for the returned command cursor or if pre 2.8 the systems batch collection */
19
21
  batchSize?: number;
20
22
  }
@@ -25,6 +27,7 @@ export class ListCollectionsOperation extends CommandOperation<string[]> {
25
27
  db: Db;
26
28
  filter: Document;
27
29
  nameOnly: boolean;
30
+ authorizedCollections: boolean;
28
31
  batchSize?: number;
29
32
 
30
33
  constructor(db: Db, filter: Document, options?: ListCollectionsOptions) {
@@ -34,6 +37,7 @@ export class ListCollectionsOperation extends CommandOperation<string[]> {
34
37
  this.db = db;
35
38
  this.filter = filter;
36
39
  this.nameOnly = !!this.options.nameOnly;
40
+ this.authorizedCollections = !!this.options.authorizedCollections;
37
41
 
38
42
  if (typeof this.options.batchSize === 'number') {
39
43
  this.batchSize = this.options.batchSize;
@@ -90,14 +94,18 @@ export class ListCollectionsOperation extends CommandOperation<string[]> {
90
94
  return;
91
95
  }
92
96
 
93
- const command = {
97
+ return super.executeCommand(server, session, this.generateCommand(), callback);
98
+ }
99
+
100
+ /* This is here for the purpose of unit testing the final command that gets sent. */
101
+ generateCommand(): Document {
102
+ return {
94
103
  listCollections: 1,
95
104
  filter: this.filter,
96
105
  cursor: this.batchSize ? { batchSize: this.batchSize } : {},
97
- nameOnly: this.nameOnly
106
+ nameOnly: this.nameOnly,
107
+ authorizedCollections: this.authorizedCollections
98
108
  };
99
-
100
- return super.executeCommand(server, session, command, callback);
101
109
  }
102
110
  }
103
111
 
@@ -7,7 +7,12 @@ import type { Db } from '../db';
7
7
  import type { ClientSession } from '../sessions';
8
8
 
9
9
  /** @public */
10
- export type ListDatabasesResult = string[] | Document[];
10
+ export interface ListDatabasesResult {
11
+ databases: ({ name: string; sizeOnDisk?: number; empty?: boolean } & Document)[];
12
+ totalSize?: number;
13
+ totalSizeMb?: number;
14
+ ok: 1 | 0;
15
+ }
11
16
 
12
17
  /** @public */
13
18
  export interface ListDatabasesOptions extends CommandOperationOptions {
@@ -31,6 +31,7 @@ export interface OperationOptions extends BSONSerializeOptions {
31
31
 
32
32
  /** @internal Hints to `executeOperation` that this operation should not unpin on an ended transaction */
33
33
  bypassPinningCheck?: boolean;
34
+ omitReadPreference?: boolean;
34
35
  }
35
36
 
36
37
  /** @internal */
@@ -49,6 +50,7 @@ export abstract class AbstractOperation<TResult = any> {
49
50
  readPreference: ReadPreference;
50
51
  server!: Server;
51
52
  bypassPinningCheck: boolean;
53
+ trySecondaryWrite: boolean;
52
54
 
53
55
  // BSON serialization options
54
56
  bsonOptions?: BSONSerializeOptions;
@@ -72,6 +74,7 @@ export abstract class AbstractOperation<TResult = any> {
72
74
 
73
75
  this.options = options;
74
76
  this.bypassPinningCheck = !!options.bypassPinningCheck;
77
+ this.trySecondaryWrite = false;
75
78
  }
76
79
 
77
80
  abstract execute(server: Server, session: ClientSession, callback: Callback<TResult>): void;
@@ -299,6 +299,14 @@ export class Server extends TypedEventEmitter<ServerEvents> {
299
299
  // Clone the options
300
300
  const finalOptions = Object.assign({}, options, { wireProtocolCommand: false });
301
301
 
302
+ // There are cases where we need to flag the read preference not to get sent in
303
+ // the command, such as pre-5.0 servers attempting to perform an aggregate write
304
+ // with a non-primary read preference. In this case the effective read preference
305
+ // (primary) is not the same as the provided and must be removed completely.
306
+ if (finalOptions.omitReadPreference) {
307
+ delete finalOptions.readPreference;
308
+ }
309
+
302
310
  // error if collation not supported
303
311
  if (collationNotSupported(this, cmd)) {
304
312
  callback(new MongoCompatibilityError(`Server ${this.name} does not support collation`));
@@ -8,6 +8,9 @@ import type { ServerDescription, TagSet } from './server_description';
8
8
  const IDLE_WRITE_PERIOD = 10000;
9
9
  const SMALLEST_MAX_STALENESS_SECONDS = 90;
10
10
 
11
+ // Minimum version to try writes on secondaries.
12
+ export const MIN_SECONDARY_WRITE_WIRE_VERSION = 13;
13
+
11
14
  /** @public */
12
15
  export type ServerSelector = (
13
16
  topologyDescription: TopologyDescription,
@@ -28,6 +31,28 @@ export function writableServerSelector(): ServerSelector {
28
31
  );
29
32
  }
30
33
 
34
+ /**
35
+ * Returns a server selector that uses a read preference to select a
36
+ * server potentially for a write on a secondary.
37
+ */
38
+ export function secondaryWritableServerSelector(
39
+ wireVersion?: number,
40
+ readPreference?: ReadPreference
41
+ ): ServerSelector {
42
+ // If server version < 5.0, read preference always primary.
43
+ // If server version >= 5.0...
44
+ // - If read preference is supplied, use that.
45
+ // - If no read preference is supplied, use primary.
46
+ if (
47
+ !readPreference ||
48
+ !wireVersion ||
49
+ (wireVersion && wireVersion < MIN_SECONDARY_WRITE_WIRE_VERSION)
50
+ ) {
51
+ return readPreferenceServerSelector(ReadPreference.primary);
52
+ }
53
+ return readPreferenceServerSelector(readPreference);
54
+ }
55
+
31
56
  /**
32
57
  * Reduces the passed in array of servers by the rules of the "Max Staleness" specification
33
58
  * found here: https://github.com/mongodb/specifications/blob/master/source/max-staleness/max-staleness.rst
@@ -29,18 +29,15 @@ export class SrvPollingEvent {
29
29
  this.srvRecords = srvRecords;
30
30
  }
31
31
 
32
- addresses(): Map<string, HostAddress> {
33
- return new Map(
34
- this.srvRecords.map(record => {
35
- const host = new HostAddress(`${record.name}:${record.port}`);
36
- return [host.toString(), host];
37
- })
38
- );
32
+ hostnames(): Set<string> {
33
+ return new Set(this.srvRecords.map(r => HostAddress.fromSrvRecord(r).toString()));
39
34
  }
40
35
  }
41
36
 
42
37
  /** @internal */
43
38
  export interface SrvPollerOptions extends LoggerOptions {
39
+ srvServiceName: string;
40
+ srvMaxHosts: number;
44
41
  srvHost: string;
45
42
  heartbeatFrequencyMS: number;
46
43
  }
@@ -58,6 +55,8 @@ export class SrvPoller extends TypedEventEmitter<SrvPollerEvents> {
58
55
  logger: Logger;
59
56
  haMode: boolean;
60
57
  generation: number;
58
+ srvMaxHosts: number;
59
+ srvServiceName: string;
61
60
  _timeout?: NodeJS.Timeout;
62
61
 
63
62
  /** @event */
@@ -71,8 +70,10 @@ export class SrvPoller extends TypedEventEmitter<SrvPollerEvents> {
71
70
  }
72
71
 
73
72
  this.srvHost = options.srvHost;
73
+ this.srvMaxHosts = options.srvMaxHosts ?? 0;
74
+ this.srvServiceName = options.srvServiceName ?? 'mongodb';
74
75
  this.rescanSrvIntervalMS = 60000;
75
- this.heartbeatFrequencyMS = options.heartbeatFrequencyMS || 10000;
76
+ this.heartbeatFrequencyMS = options.heartbeatFrequencyMS ?? 10000;
76
77
  this.logger = new Logger('srvPoller', options);
77
78
 
78
79
  this.haMode = false;
@@ -82,7 +83,7 @@ export class SrvPoller extends TypedEventEmitter<SrvPollerEvents> {
82
83
  }
83
84
 
84
85
  get srvAddress(): string {
85
- return `_mongodb._tcp.${this.srvHost}`;
86
+ return `_${this.srvServiceName}._tcp.${this.srvHost}`;
86
87
  }
87
88
 
88
89
  get intervalMS(): number {
@@ -143,13 +144,13 @@ export class SrvPoller extends TypedEventEmitter<SrvPollerEvents> {
143
144
  }
144
145
 
145
146
  const finalAddresses: dns.SrvRecord[] = [];
146
- srvRecords.forEach((record: dns.SrvRecord) => {
147
+ for (const record of srvRecords) {
147
148
  if (matchesParentDomain(record.name, this.srvHost)) {
148
149
  finalAddresses.push(record);
149
150
  } else {
150
151
  this.parentDomainMismatch(record);
151
152
  }
152
- });
153
+ }
153
154
 
154
155
  if (!finalAddresses.length) {
155
156
  this.failure('No valid addresses found at host');
@@ -27,7 +27,8 @@ import {
27
27
  HostAddress,
28
28
  ns,
29
29
  emitWarning,
30
- EventEmitterWithState
30
+ EventEmitterWithState,
31
+ shuffle
31
32
  } from '../utils';
32
33
  import {
33
34
  TopologyType,
@@ -140,6 +141,8 @@ export interface TopologyPrivate {
140
141
 
141
142
  /** @public */
142
143
  export interface TopologyOptions extends BSONSerializeOptions, ServerOptions {
144
+ srvMaxHosts: number;
145
+ srvServiceName: string;
143
146
  hosts: HostAddress[];
144
147
  retryWrites: boolean;
145
148
  retryReads: boolean;
@@ -182,8 +185,8 @@ export type TopologyEvents = {
182
185
  topologyOpening(event: TopologyOpeningEvent): void;
183
186
  topologyDescriptionChanged(event: TopologyDescriptionChangedEvent): void;
184
187
  error(error: Error): void;
185
- /** TODO(NODE-3273) - remove error @internal */
186
- open(error: undefined, topology: Topology): void;
188
+ /** @internal */
189
+ open(topology: Topology): void;
187
190
  close(): void;
188
191
  timeout(): void;
189
192
  } & Omit<ServerEvents, 'connect'> &
@@ -290,8 +293,15 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
290
293
  const topologyType = topologyTypeFromOptions(options);
291
294
  const topologyId = globalTopologyCounter++;
292
295
 
296
+ const selectedHosts =
297
+ options.srvMaxHosts == null ||
298
+ options.srvMaxHosts === 0 ||
299
+ options.srvMaxHosts >= seedlist.length
300
+ ? seedlist
301
+ : shuffle(seedlist, options.srvMaxHosts);
302
+
293
303
  const serverDescriptions = new Map();
294
- for (const hostAddress of seedlist) {
304
+ for (const hostAddress of selectedHosts) {
295
305
  serverDescriptions.set(hostAddress.toString(), new ServerDescription(hostAddress));
296
306
  }
297
307
 
@@ -339,7 +349,9 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
339
349
  options.srvPoller ??
340
350
  new SrvPoller({
341
351
  heartbeatFrequencyMS: this.s.heartbeatFrequencyMS,
342
- srvHost: options.srvHost
352
+ srvHost: options.srvHost,
353
+ srvMaxHosts: options.srvMaxHosts,
354
+ srvServiceName: options.srvServiceName
343
355
  });
344
356
 
345
357
  this.on(Topology.TOPOLOGY_DESCRIPTION_CHANGED, this.s.detectShardedTopology);
@@ -363,7 +375,10 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
363
375
 
364
376
  private detectSrvRecords(ev: SrvPollingEvent) {
365
377
  const previousTopologyDescription = this.s.description;
366
- this.s.description = this.s.description.updateFromSrvPollingEvent(ev);
378
+ this.s.description = this.s.description.updateFromSrvPollingEvent(
379
+ ev,
380
+ this.s.options.srvMaxHosts
381
+ );
367
382
  if (this.s.description === previousTopologyDescription) {
368
383
  // Nothing changed, so return
369
384
  return;
@@ -456,8 +471,7 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
456
471
  }
457
472
 
458
473
  stateTransition(this, STATE_CONNECTED);
459
- // TODO(NODE-3273) - remove err
460
- this.emit(Topology.OPEN, err, this);
474
+ this.emit(Topology.OPEN, this);
461
475
  this.emit(Topology.CONNECT, this);
462
476
 
463
477
  if (typeof callback === 'function') callback(undefined, this);
@@ -467,8 +481,7 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
467
481
  }
468
482
 
469
483
  stateTransition(this, STATE_CONNECTED);
470
- // TODO(NODE-3273) - remove err
471
- this.emit(Topology.OPEN, err, this);
484
+ this.emit(Topology.OPEN, this);
472
485
  this.emit(Topology.CONNECT, this);
473
486
 
474
487
  if (typeof callback === 'function') callback(undefined, this);
@@ -797,6 +810,10 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
797
810
  return result;
798
811
  }
799
812
 
813
+ get commonWireVersion(): number | undefined {
814
+ return this.description.commonWireVersion;
815
+ }
816
+
800
817
  get logicalSessionTimeoutMinutes(): number | undefined {
801
818
  return this.description.logicalSessionTimeoutMinutes;
802
819
  }
@@ -4,6 +4,7 @@ import { TopologyType, ServerType } from './common';
4
4
  import type { ObjectId, Document } from '../bson';
5
5
  import type { SrvPollingEvent } from './srv_polling';
6
6
  import { MongoError, MongoRuntimeError } from '../error';
7
+ import { shuffle } from '../utils';
7
8
 
8
9
  // constants related to compatibility checks
9
10
  const MIN_SUPPORTED_SERVER_VERSION = WIRE_CONSTANTS.MIN_SUPPORTED_SERVER_VERSION;
@@ -139,23 +140,46 @@ export class TopologyDescription {
139
140
  * Returns a new TopologyDescription based on the SrvPollingEvent
140
141
  * @internal
141
142
  */
142
- updateFromSrvPollingEvent(ev: SrvPollingEvent): TopologyDescription {
143
- const newAddresses = ev.addresses();
144
- const serverDescriptions = new Map(this.servers);
145
- for (const address of this.servers.keys()) {
146
- if (newAddresses.has(address)) {
147
- newAddresses.delete(address);
148
- } else {
149
- serverDescriptions.delete(address);
143
+ updateFromSrvPollingEvent(ev: SrvPollingEvent, srvMaxHosts = 0): TopologyDescription {
144
+ /** The SRV addresses defines the set of addresses we should be using */
145
+ const incomingHostnames = ev.hostnames();
146
+ const currentHostnames = new Set(this.servers.keys());
147
+
148
+ const hostnamesToAdd = new Set<string>(incomingHostnames);
149
+ const hostnamesToRemove = new Set<string>();
150
+ for (const hostname of currentHostnames) {
151
+ // filter hostnamesToAdd (made from incomingHostnames) down to what is *not* present in currentHostnames
152
+ hostnamesToAdd.delete(hostname);
153
+ if (!incomingHostnames.has(hostname)) {
154
+ // If the SRV Records no longer include this hostname
155
+ // we have to stop using it
156
+ hostnamesToRemove.add(hostname);
150
157
  }
151
158
  }
152
159
 
153
- if (serverDescriptions.size === this.servers.size && newAddresses.size === 0) {
160
+ if (hostnamesToAdd.size === 0 && hostnamesToRemove.size === 0) {
161
+ // No new hosts to add and none to remove
154
162
  return this;
155
163
  }
156
164
 
157
- for (const [address, host] of newAddresses) {
158
- serverDescriptions.set(address, new ServerDescription(host));
165
+ const serverDescriptions = new Map(this.servers);
166
+ for (const removedHost of hostnamesToRemove) {
167
+ serverDescriptions.delete(removedHost);
168
+ }
169
+
170
+ if (hostnamesToAdd.size > 0) {
171
+ if (srvMaxHosts === 0) {
172
+ // Add all!
173
+ for (const hostToAdd of hostnamesToAdd) {
174
+ serverDescriptions.set(hostToAdd, new ServerDescription(hostToAdd));
175
+ }
176
+ } else if (serverDescriptions.size < srvMaxHosts) {
177
+ // Add only the amount needed to get us back to srvMaxHosts
178
+ const selectedHosts = shuffle(hostnamesToAdd, srvMaxHosts - serverDescriptions.size);
179
+ for (const selectedHostToAdd of selectedHosts) {
180
+ serverDescriptions.set(selectedHostToAdd, new ServerDescription(selectedHostToAdd));
181
+ }
182
+ }
159
183
  }
160
184
 
161
185
  return new TopologyDescription(
@@ -1,6 +1,6 @@
1
1
  import { ReadPreference } from './read_preference';
2
2
  import { MongoRuntimeError, MongoTransactionError } from './error';
3
- import { ReadConcern } from './read_concern';
3
+ import { ReadConcern, ReadConcernLike } from './read_concern';
4
4
  import { WriteConcern } from './write_concern';
5
5
  import type { Server } from './sdam/server';
6
6
  import type { CommandOperationOptions } from './operations/command';
@@ -63,7 +63,7 @@ const COMMITTED_STATES: Set<TxnState> = new Set([
63
63
  export interface TransactionOptions extends CommandOperationOptions {
64
64
  // TODO(NODE-3344): These options use the proper class forms of these settings, it should accept the basic enum values too
65
65
  /** A default read concern for commands in this transaction */
66
- readConcern?: ReadConcern;
66
+ readConcern?: ReadConcernLike;
67
67
  /** A default writeConcern for commands in this transaction */
68
68
  writeConcern?: WriteConcern;
69
69
  /** A default read preference for commands in this transaction */