mongodb 4.7.0 → 4.9.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 (127) hide show
  1. package/lib/bson.js +4 -2
  2. package/lib/bson.js.map +1 -1
  3. package/lib/bulk/common.js +1 -0
  4. package/lib/bulk/common.js.map +1 -1
  5. package/lib/change_stream.js +136 -271
  6. package/lib/change_stream.js.map +1 -1
  7. package/lib/cmap/command_monitoring_events.js +2 -32
  8. package/lib/cmap/command_monitoring_events.js.map +1 -1
  9. package/lib/cmap/commands.js +1 -153
  10. package/lib/cmap/commands.js.map +1 -1
  11. package/lib/cmap/connect.js +3 -6
  12. package/lib/cmap/connect.js.map +1 -1
  13. package/lib/cmap/connection.js +21 -84
  14. package/lib/cmap/connection.js.map +1 -1
  15. package/lib/cmap/connection_pool.js +196 -170
  16. package/lib/cmap/connection_pool.js.map +1 -1
  17. package/lib/cmap/message_stream.js.map +1 -1
  18. package/lib/cmap/wire_protocol/compression.js +2 -6
  19. package/lib/cmap/wire_protocol/compression.js.map +1 -1
  20. package/lib/cmap/wire_protocol/constants.js +1 -3
  21. package/lib/cmap/wire_protocol/constants.js.map +1 -1
  22. package/lib/collection.js +24 -9
  23. package/lib/collection.js.map +1 -1
  24. package/lib/connection_string.js.map +1 -1
  25. package/lib/cursor/abstract_cursor.js +62 -75
  26. package/lib/cursor/abstract_cursor.js.map +1 -1
  27. package/lib/cursor/change_stream_cursor.js +115 -0
  28. package/lib/cursor/change_stream_cursor.js.map +1 -0
  29. package/lib/cursor/list_collections_cursor.js +37 -0
  30. package/lib/cursor/list_collections_cursor.js.map +1 -0
  31. package/lib/cursor/list_indexes_cursor.js +36 -0
  32. package/lib/cursor/list_indexes_cursor.js.map +1 -0
  33. package/lib/db.js +2 -2
  34. package/lib/db.js.map +1 -1
  35. package/lib/deps.js.map +1 -1
  36. package/lib/encrypter.js +3 -13
  37. package/lib/encrypter.js.map +1 -1
  38. package/lib/index.js +28 -21
  39. package/lib/index.js.map +1 -1
  40. package/lib/mongo_client.js +62 -21
  41. package/lib/mongo_client.js.map +1 -1
  42. package/lib/mongo_types.js.map +1 -1
  43. package/lib/operations/common_functions.js.map +1 -1
  44. package/lib/operations/create_collection.js.map +1 -1
  45. package/lib/operations/distinct.js +5 -5
  46. package/lib/operations/distinct.js.map +1 -1
  47. package/lib/operations/estimated_document_count.js +5 -0
  48. package/lib/operations/estimated_document_count.js.map +1 -1
  49. package/lib/operations/execute_operation.js +17 -8
  50. package/lib/operations/execute_operation.js.map +1 -1
  51. package/lib/operations/find.js +3 -0
  52. package/lib/operations/find.js.map +1 -1
  53. package/lib/operations/get_more.js +32 -7
  54. package/lib/operations/get_more.js.map +1 -1
  55. package/lib/operations/indexes.js +39 -65
  56. package/lib/operations/indexes.js.map +1 -1
  57. package/lib/operations/kill_cursors.js +32 -0
  58. package/lib/operations/kill_cursors.js.map +1 -0
  59. package/lib/operations/list_collections.js +1 -33
  60. package/lib/operations/list_collections.js.map +1 -1
  61. package/lib/operations/operation.js +4 -1
  62. package/lib/operations/operation.js.map +1 -1
  63. package/lib/read_preference.js.map +1 -1
  64. package/lib/sdam/common.js +2 -1
  65. package/lib/sdam/common.js.map +1 -1
  66. package/lib/sdam/monitor.js +2 -1
  67. package/lib/sdam/monitor.js.map +1 -1
  68. package/lib/sdam/server.js +1 -52
  69. package/lib/sdam/server.js.map +1 -1
  70. package/lib/sdam/server_description.js +51 -58
  71. package/lib/sdam/server_description.js.map +1 -1
  72. package/lib/sdam/srv_polling.js +2 -2
  73. package/lib/sdam/srv_polling.js.map +1 -1
  74. package/lib/sdam/topology.js +28 -67
  75. package/lib/sdam/topology.js.map +1 -1
  76. package/lib/sdam/topology_description.js +24 -42
  77. package/lib/sdam/topology_description.js.map +1 -1
  78. package/lib/sessions.js +29 -31
  79. package/lib/sessions.js.map +1 -1
  80. package/lib/utils.js +65 -70
  81. package/lib/utils.js.map +1 -1
  82. package/mongodb.d.ts +136 -73
  83. package/package.json +23 -22
  84. package/src/bson.ts +4 -0
  85. package/src/bulk/common.ts +1 -0
  86. package/src/change_stream.ts +147 -373
  87. package/src/cmap/command_monitoring_events.ts +3 -37
  88. package/src/cmap/commands.ts +2 -190
  89. package/src/cmap/connect.ts +20 -25
  90. package/src/cmap/connection.ts +27 -139
  91. package/src/cmap/connection_pool.ts +208 -169
  92. package/src/cmap/message_stream.ts +2 -3
  93. package/src/cmap/wire_protocol/compression.ts +8 -6
  94. package/src/cmap/wire_protocol/constants.ts +0 -2
  95. package/src/collection.ts +27 -13
  96. package/src/connection_string.ts +1 -1
  97. package/src/cursor/abstract_cursor.ts +98 -87
  98. package/src/cursor/change_stream_cursor.ts +194 -0
  99. package/src/cursor/list_collections_cursor.ts +52 -0
  100. package/src/cursor/list_indexes_cursor.ts +41 -0
  101. package/src/db.ts +2 -5
  102. package/src/deps.ts +13 -22
  103. package/src/encrypter.ts +4 -14
  104. package/src/index.ts +13 -9
  105. package/src/mongo_client.ts +102 -33
  106. package/src/mongo_types.ts +81 -57
  107. package/src/operations/common_functions.ts +1 -1
  108. package/src/operations/create_collection.ts +1 -2
  109. package/src/operations/distinct.ts +7 -9
  110. package/src/operations/estimated_document_count.ts +6 -0
  111. package/src/operations/execute_operation.ts +17 -8
  112. package/src/operations/find.ts +9 -0
  113. package/src/operations/get_more.ts +56 -13
  114. package/src/operations/indexes.ts +52 -89
  115. package/src/operations/kill_cursors.ts +53 -0
  116. package/src/operations/list_collections.ts +0 -43
  117. package/src/operations/operation.ts +5 -1
  118. package/src/read_preference.ts +5 -9
  119. package/src/sdam/common.ts +2 -0
  120. package/src/sdam/monitor.ts +2 -1
  121. package/src/sdam/server.ts +4 -89
  122. package/src/sdam/server_description.ts +70 -80
  123. package/src/sdam/srv_polling.ts +1 -1
  124. package/src/sdam/topology.ts +37 -103
  125. package/src/sdam/topology_description.ts +44 -68
  126. package/src/sessions.ts +32 -39
  127. package/src/utils.ts +78 -75
@@ -0,0 +1,53 @@
1
+ import type { Long } from '../bson';
2
+ import { MongoRuntimeError } from '../error';
3
+ import type { Server } from '../sdam/server';
4
+ import type { ClientSession } from '../sessions';
5
+ import type { Callback, MongoDBNamespace } from '../utils';
6
+ import { AbstractOperation, Aspect, defineAspects, OperationOptions } from './operation';
7
+
8
+ /**
9
+ * https://www.mongodb.com/docs/manual/reference/command/killCursors/
10
+ * @internal
11
+ */
12
+ interface KillCursorsCommand {
13
+ killCursors: string;
14
+ cursors: Long[];
15
+ comment?: unknown;
16
+ }
17
+
18
+ export class KillCursorsOperation extends AbstractOperation {
19
+ cursorId: Long;
20
+
21
+ constructor(cursorId: Long, ns: MongoDBNamespace, server: Server, options: OperationOptions) {
22
+ super(options);
23
+ this.ns = ns;
24
+ this.cursorId = cursorId;
25
+ this.server = server;
26
+ }
27
+
28
+ execute(server: Server, session: ClientSession | undefined, callback: Callback<void>): void {
29
+ if (server !== this.server) {
30
+ return callback(
31
+ new MongoRuntimeError('Killcursor must run on the same server operation began on')
32
+ );
33
+ }
34
+
35
+ const killCursors = this.ns.collection;
36
+ if (killCursors == null) {
37
+ // Cursors should have adopted the namespace returned by MongoDB
38
+ // which should always defined a collection name (even a pseudo one, ex. db.aggregate())
39
+ return callback(
40
+ new MongoRuntimeError('A collection name must be determined before killCursors')
41
+ );
42
+ }
43
+
44
+ const killCursorsCommand: KillCursorsCommand = {
45
+ killCursors,
46
+ cursors: [this.cursorId]
47
+ };
48
+
49
+ server.command(this.ns, killCursorsCommand, { session }, () => callback());
50
+ }
51
+ }
52
+
53
+ defineAspects(KillCursorsOperation, [Aspect.MUST_SELECT_SAME_SERVER]);
@@ -1,11 +1,9 @@
1
1
  import type { Binary, Document } from '../bson';
2
- import { AbstractCursor } from '../cursor/abstract_cursor';
3
2
  import type { Db } from '../db';
4
3
  import type { Server } from '../sdam/server';
5
4
  import type { ClientSession } from '../sessions';
6
5
  import { Callback, maxWireVersion } from '../utils';
7
6
  import { CommandOperation, CommandOperationOptions } from './command';
8
- import { executeOperation, ExecutionResult } from './execute_operation';
9
7
  import { Aspect, defineAspects } from './operation';
10
8
 
11
9
  /** @public */
@@ -86,47 +84,6 @@ export interface CollectionInfo extends Document {
86
84
  idIndex?: Document;
87
85
  }
88
86
 
89
- /** @public */
90
- export class ListCollectionsCursor<
91
- T extends Pick<CollectionInfo, 'name' | 'type'> | CollectionInfo =
92
- | Pick<CollectionInfo, 'name' | 'type'>
93
- | CollectionInfo
94
- > extends AbstractCursor<T> {
95
- parent: Db;
96
- filter: Document;
97
- options?: ListCollectionsOptions;
98
-
99
- constructor(db: Db, filter: Document, options?: ListCollectionsOptions) {
100
- super(db.s.client, db.s.namespace, options);
101
- this.parent = db;
102
- this.filter = filter;
103
- this.options = options;
104
- }
105
-
106
- clone(): ListCollectionsCursor<T> {
107
- return new ListCollectionsCursor(this.parent, this.filter, {
108
- ...this.options,
109
- ...this.cursorOptions
110
- });
111
- }
112
-
113
- /** @internal */
114
- _initialize(session: ClientSession | undefined, callback: Callback<ExecutionResult>): void {
115
- const operation = new ListCollectionsOperation(this.parent, this.filter, {
116
- ...this.cursorOptions,
117
- ...this.options,
118
- session
119
- });
120
-
121
- executeOperation(this.parent.s.client, operation, (err, response) => {
122
- if (err || response == null) return callback(err);
123
-
124
- // TODO: NODE-2882
125
- callback(undefined, { server: operation.server, session, response });
126
- });
127
- }
128
- }
129
-
130
87
  defineAspects(ListCollectionsOperation, [
131
88
  Aspect.READ_OPERATION,
132
89
  Aspect.RETRYABLE,
@@ -11,7 +11,7 @@ export const Aspect = {
11
11
  EXPLAINABLE: Symbol('EXPLAINABLE'),
12
12
  SKIP_COLLATION: Symbol('SKIP_COLLATION'),
13
13
  CURSOR_CREATING: Symbol('CURSOR_CREATING'),
14
- CURSOR_ITERATING: Symbol('CURSOR_ITERATING')
14
+ MUST_SELECT_SAME_SERVER: Symbol('MUST_SELECT_SAME_SERVER')
15
15
  } as const;
16
16
 
17
17
  /** @public */
@@ -94,6 +94,10 @@ export abstract class AbstractOperation<TResult = any> {
94
94
  return this[kSession];
95
95
  }
96
96
 
97
+ clearSession() {
98
+ this[kSession] = undefined;
99
+ }
100
+
97
101
  get canRetryRead(): boolean {
98
102
  return true;
99
103
  }
@@ -163,14 +163,10 @@ export class ReadPreference {
163
163
  } else if (!(readPreference instanceof ReadPreference) && typeof readPreference === 'object') {
164
164
  const mode = readPreference.mode || readPreference.preference;
165
165
  if (mode && typeof mode === 'string') {
166
- return new ReadPreference(
167
- mode as ReadPreferenceMode,
168
- readPreference.tags ?? readPreferenceTags,
169
- {
170
- maxStalenessSeconds: readPreference.maxStalenessSeconds,
171
- hedge: options.hedge
172
- }
173
- );
166
+ return new ReadPreference(mode, readPreference.tags ?? readPreferenceTags, {
167
+ maxStalenessSeconds: readPreference.maxStalenessSeconds,
168
+ hedge: options.hedge
169
+ });
174
170
  }
175
171
  }
176
172
 
@@ -193,7 +189,7 @@ export class ReadPreference {
193
189
  } else if (r && !(r instanceof ReadPreference) && typeof r === 'object') {
194
190
  const mode = r.mode || r.preference;
195
191
  if (mode && typeof mode === 'string') {
196
- options.readPreference = new ReadPreference(mode as ReadPreferenceMode, r.tags, {
192
+ options.readPreference = new ReadPreference(mode, r.tags, {
197
193
  maxStalenessSeconds: r.maxStalenessSeconds
198
194
  });
199
195
  }
@@ -1,3 +1,5 @@
1
+ import { clearTimeout } from 'timers';
2
+
1
3
  import type { Binary, Long, Timestamp } from '../bson';
2
4
  import type { ClientSession } from '../sessions';
3
5
  import type { Topology } from './topology';
@@ -1,4 +1,4 @@
1
- import { setTimeout } from 'timers';
1
+ import { clearTimeout, setTimeout } from 'timers';
2
2
 
3
3
  import { Document, Long } from '../bson';
4
4
  import { connect } from '../cmap/connect';
@@ -374,6 +374,7 @@ function makeTopologyVersion(tv: TopologyVersion) {
374
374
  return {
375
375
  processId: tv.processId,
376
376
  // tests mock counter as just number, but in a real situation counter should always be a Long
377
+ // TODO(NODE-2674): Preserve int64 sent from MongoDB
377
378
  counter: Long.isLong(tv.counter) ? tv.counter : Long.fromNumber(tv.counter)
378
379
  };
379
380
  }
@@ -1,4 +1,4 @@
1
- import type { Document, Long } from '../bson';
1
+ import type { Document } from '../bson';
2
2
  import { CommandOptions, Connection, DestroyOptions, GetMoreOptions } from '../cmap/connection';
3
3
  import {
4
4
  ConnectionPool,
@@ -29,6 +29,7 @@ import {
29
29
  MongoNetworkError,
30
30
  MongoNetworkTimeoutError,
31
31
  MongoServerClosedError,
32
+ MongoServerError,
32
33
  MongoUnexpectedServerResponseError,
33
34
  needsRetryableWriteLabel
34
35
  } from '../error';
@@ -374,88 +375,6 @@ export class Server extends TypedEventEmitter<ServerEvents> {
374
375
  callback
375
376
  );
376
377
  }
377
-
378
- /**
379
- * Execute a `getMore` against the server
380
- * @internal
381
- */
382
- getMore(
383
- ns: MongoDBNamespace,
384
- cursorId: Long,
385
- options: GetMoreOptions,
386
- callback: Callback<Document>
387
- ): void {
388
- if (this.s.state === STATE_CLOSING || this.s.state === STATE_CLOSED) {
389
- callback(new MongoServerClosedError());
390
- return;
391
- }
392
-
393
- this.s.operationCount += 1;
394
-
395
- this.s.pool.withConnection(
396
- options.session?.pinnedConnection,
397
- (err, conn, cb) => {
398
- if (err || !conn) {
399
- this.s.operationCount -= 1;
400
- markServerUnknown(this, err);
401
- return cb(err);
402
- }
403
-
404
- conn.getMore(
405
- ns,
406
- cursorId,
407
- options,
408
- makeOperationHandler(this, conn, {}, options, (error, response) => {
409
- this.s.operationCount -= 1;
410
- cb(error, response);
411
- })
412
- );
413
- },
414
- callback
415
- );
416
- }
417
-
418
- /**
419
- * Execute a `killCursors` command against the server
420
- * @internal
421
- */
422
- killCursors(
423
- ns: MongoDBNamespace,
424
- cursorIds: Long[],
425
- options: CommandOptions,
426
- callback?: Callback
427
- ): void {
428
- if (this.s.state === STATE_CLOSING || this.s.state === STATE_CLOSED) {
429
- if (typeof callback === 'function') {
430
- callback(new MongoServerClosedError());
431
- }
432
-
433
- return;
434
- }
435
-
436
- this.s.operationCount += 1;
437
- this.s.pool.withConnection(
438
- options.session?.pinnedConnection,
439
- (err, conn, cb) => {
440
- if (err || !conn) {
441
- this.s.operationCount -= 1;
442
- markServerUnknown(this, err);
443
- return cb(err);
444
- }
445
-
446
- conn.killCursors(
447
- ns,
448
- cursorIds,
449
- options,
450
- makeOperationHandler(this, conn, {}, undefined, (error, response) => {
451
- this.s.operationCount -= 1;
452
- cb(error, response);
453
- })
454
- );
455
- },
456
- callback
457
- );
458
- }
459
378
  }
460
379
 
461
380
  function calculateRoundTripTime(oldRtt: number, duration: number): number {
@@ -467,7 +386,7 @@ function calculateRoundTripTime(oldRtt: number, duration: number): number {
467
386
  return alpha * duration + (1 - alpha) * oldRtt;
468
387
  }
469
388
 
470
- function markServerUnknown(server: Server, error?: MongoError) {
389
+ function markServerUnknown(server: Server, error?: MongoServerError) {
471
390
  // Load balancer servers can never be marked unknown.
472
391
  if (server.loadBalanced) {
473
392
  return;
@@ -479,11 +398,7 @@ function markServerUnknown(server: Server, error?: MongoError) {
479
398
 
480
399
  server.emit(
481
400
  Server.DESCRIPTION_RECEIVED,
482
- new ServerDescription(server.description.hostAddress, undefined, {
483
- error,
484
- topologyVersion:
485
- error && error.topologyVersion ? error.topologyVersion : server.description.topologyVersion
486
- })
401
+ new ServerDescription(server.description.hostAddress, undefined, { error })
487
402
  );
488
403
  }
489
404
 
@@ -1,6 +1,6 @@
1
1
  import { Document, Long, ObjectId } from '../bson';
2
- import type { MongoError } from '../error';
3
- import { arrayStrictEqual, errorStrictEqual, HostAddress, now } from '../utils';
2
+ import { MongoRuntimeError, MongoServerError } from '../error';
3
+ import { arrayStrictEqual, compareObjectId, errorStrictEqual, HostAddress, now } from '../utils';
4
4
  import type { ClusterTime } from './common';
5
5
  import { ServerType } from './common';
6
6
 
@@ -31,14 +31,11 @@ export type TagSet = { [key: string]: string };
31
31
  /** @internal */
32
32
  export interface ServerDescriptionOptions {
33
33
  /** An Error used for better reporting debugging */
34
- error?: MongoError;
34
+ error?: MongoServerError;
35
35
 
36
36
  /** The round trip time to ping this server (in ms) */
37
37
  roundTripTime?: number;
38
38
 
39
- /** The topologyVersion */
40
- topologyVersion?: TopologyVersion;
41
-
42
39
  /** If the client is in load balancing mode. */
43
40
  loadBalanced?: boolean;
44
41
  }
@@ -50,28 +47,25 @@ export interface ServerDescriptionOptions {
50
47
  * @public
51
48
  */
52
49
  export class ServerDescription {
53
- private _hostAddress: HostAddress;
54
50
  address: string;
55
51
  type: ServerType;
56
52
  hosts: string[];
57
53
  passives: string[];
58
54
  arbiters: string[];
59
55
  tags: TagSet;
60
-
61
- error?: MongoError;
62
- topologyVersion?: TopologyVersion;
56
+ error: MongoServerError | null;
57
+ topologyVersion: TopologyVersion | null;
63
58
  minWireVersion: number;
64
59
  maxWireVersion: number;
65
60
  roundTripTime: number;
66
61
  lastUpdateTime: number;
67
62
  lastWriteDate: number;
68
-
69
- me?: string;
70
- primary?: string;
71
- setName?: string;
72
- setVersion?: number;
73
- electionId?: ObjectId;
74
- logicalSessionTimeoutMinutes?: number;
63
+ me: string | null;
64
+ primary: string | null;
65
+ setName: string | null;
66
+ setVersion: number | null;
67
+ electionId: ObjectId | null;
68
+ logicalSessionTimeoutMinutes: number | null;
75
69
 
76
70
  // NOTE: does this belong here? It seems we should gossip the cluster time at the CMAP level
77
71
  $clusterTime?: ClusterTime;
@@ -83,14 +77,19 @@ export class ServerDescription {
83
77
  * @param address - The address of the server
84
78
  * @param hello - An optional hello response for this server
85
79
  */
86
- constructor(address: HostAddress | string, hello?: Document, options?: ServerDescriptionOptions) {
87
- if (typeof address === 'string') {
88
- this._hostAddress = new HostAddress(address);
89
- this.address = this._hostAddress.toString();
90
- } else {
91
- this._hostAddress = address;
92
- this.address = this._hostAddress.toString();
80
+ constructor(
81
+ address: HostAddress | string,
82
+ hello?: Document,
83
+ options: ServerDescriptionOptions = {}
84
+ ) {
85
+ if (address == null || address === '') {
86
+ throw new MongoRuntimeError('ServerDescription must be provided with a non-empty address');
93
87
  }
88
+
89
+ this.address =
90
+ typeof address === 'string'
91
+ ? HostAddress.fromString(address).toString(false) // Use HostAddress to normalize
92
+ : address.toString(false);
94
93
  this.type = parseServerType(hello, options);
95
94
  this.hosts = hello?.hosts?.map((host: string) => host.toLowerCase()) ?? [];
96
95
  this.passives = hello?.passives?.map((host: string) => host.toLowerCase()) ?? [];
@@ -101,49 +100,20 @@ export class ServerDescription {
101
100
  this.roundTripTime = options?.roundTripTime ?? -1;
102
101
  this.lastUpdateTime = now();
103
102
  this.lastWriteDate = hello?.lastWrite?.lastWriteDate ?? 0;
104
-
105
- if (options?.topologyVersion) {
106
- this.topologyVersion = options.topologyVersion;
107
- } else if (hello?.topologyVersion) {
108
- this.topologyVersion = hello.topologyVersion;
109
- }
110
-
111
- if (options?.error) {
112
- this.error = options.error;
113
- }
114
-
115
- if (hello?.primary) {
116
- this.primary = hello.primary;
117
- }
118
-
119
- if (hello?.me) {
120
- this.me = hello.me.toLowerCase();
121
- }
122
-
123
- if (hello?.setName) {
124
- this.setName = hello.setName;
125
- }
126
-
127
- if (hello?.setVersion) {
128
- this.setVersion = hello.setVersion;
129
- }
130
-
131
- if (hello?.electionId) {
132
- this.electionId = hello.electionId;
133
- }
134
-
135
- if (hello?.logicalSessionTimeoutMinutes) {
136
- this.logicalSessionTimeoutMinutes = hello.logicalSessionTimeoutMinutes;
137
- }
138
-
139
- if (hello?.$clusterTime) {
140
- this.$clusterTime = hello.$clusterTime;
141
- }
103
+ this.error = options.error ?? null;
104
+ // TODO(NODE-2674): Preserve int64 sent from MongoDB
105
+ this.topologyVersion = this.error?.topologyVersion ?? hello?.topologyVersion ?? null;
106
+ this.setName = hello?.setName ?? null;
107
+ this.setVersion = hello?.setVersion ?? null;
108
+ this.electionId = hello?.electionId ?? null;
109
+ this.logicalSessionTimeoutMinutes = hello?.logicalSessionTimeoutMinutes ?? null;
110
+ this.primary = hello?.primary ?? null;
111
+ this.me = hello?.me?.toLowerCase() ?? null;
112
+ this.$clusterTime = hello?.$clusterTime ?? null;
142
113
  }
143
114
 
144
115
  get hostAddress(): HostAddress {
145
- if (this._hostAddress) return this._hostAddress;
146
- else return new HostAddress(this.address);
116
+ return HostAddress.fromString(this.address);
147
117
  }
148
118
 
149
119
  get allHosts(): string[] {
@@ -179,15 +149,17 @@ export class ServerDescription {
179
149
  * Determines if another `ServerDescription` is equal to this one per the rules defined
180
150
  * in the {@link https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst#serverdescription|SDAM spec}
181
151
  */
182
- equals(other: ServerDescription): boolean {
152
+ equals(other?: ServerDescription | null): boolean {
153
+ // Despite using the comparator that would determine a nullish topologyVersion as greater than
154
+ // for equality we should only always perform direct equality comparison
183
155
  const topologyVersionsEqual =
184
- this.topologyVersion === other.topologyVersion ||
185
- compareTopologyVersion(this.topologyVersion, other.topologyVersion) === 0;
156
+ this.topologyVersion === other?.topologyVersion ||
157
+ compareTopologyVersion(this.topologyVersion, other?.topologyVersion) === 0;
186
158
 
187
- const electionIdsEqual: boolean =
188
- this.electionId && other.electionId
189
- ? other.electionId && this.electionId.equals(other.electionId)
190
- : this.electionId === other.electionId;
159
+ const electionIdsEqual =
160
+ this.electionId != null && other?.electionId != null
161
+ ? compareObjectId(this.electionId, other.electionId) === 0
162
+ : this.electionId === other?.electionId;
191
163
 
192
164
  return (
193
165
  other != null &&
@@ -254,19 +226,37 @@ function tagsStrictEqual(tags: TagSet, tags2: TagSet): boolean {
254
226
  /**
255
227
  * Compares two topology versions.
256
228
  *
257
- * @returns A negative number if `lhs` is older than `rhs`; positive if `lhs` is newer than `rhs`; 0 if they are equivalent.
229
+ * 1. If the response topologyVersion is unset or the ServerDescription's
230
+ * topologyVersion is null, the client MUST assume the response is more recent.
231
+ * 1. If the response's topologyVersion.processId is not equal to the
232
+ * ServerDescription's, the client MUST assume the response is more recent.
233
+ * 1. If the response's topologyVersion.processId is equal to the
234
+ * ServerDescription's, the client MUST use the counter field to determine
235
+ * which topologyVersion is more recent.
236
+ *
237
+ * ```ts
238
+ * currentTv < newTv === -1
239
+ * currentTv === newTv === 0
240
+ * currentTv > newTv === 1
241
+ * ```
258
242
  */
259
- export function compareTopologyVersion(lhs?: TopologyVersion, rhs?: TopologyVersion): number {
260
- if (lhs == null || rhs == null) {
243
+ export function compareTopologyVersion(
244
+ currentTv?: TopologyVersion | null,
245
+ newTv?: TopologyVersion | null
246
+ ): 0 | -1 | 1 {
247
+ if (currentTv == null || newTv == null) {
261
248
  return -1;
262
249
  }
263
250
 
264
- if (lhs.processId.equals(rhs.processId)) {
265
- // tests mock counter as just number, but in a real situation counter should always be a Long
266
- const lhsCounter = Long.isLong(lhs.counter) ? lhs.counter : Long.fromNumber(lhs.counter);
267
- const rhsCounter = Long.isLong(rhs.counter) ? lhs.counter : Long.fromNumber(rhs.counter);
268
- return lhsCounter.compare(rhsCounter);
251
+ if (!currentTv.processId.equals(newTv.processId)) {
252
+ return -1;
269
253
  }
270
254
 
271
- return -1;
255
+ // TODO(NODE-2674): Preserve int64 sent from MongoDB
256
+ const currentCounter = Long.isLong(currentTv.counter)
257
+ ? currentTv.counter
258
+ : Long.fromNumber(currentTv.counter);
259
+ const newCounter = Long.isLong(newTv.counter) ? newTv.counter : Long.fromNumber(newTv.counter);
260
+
261
+ return currentCounter.compare(newCounter);
272
262
  }
@@ -1,5 +1,5 @@
1
1
  import * as dns from 'dns';
2
- import { setTimeout } from 'timers';
2
+ import { clearTimeout, setTimeout } from 'timers';
3
3
 
4
4
  import { MongoRuntimeError } from '../error';
5
5
  import { Logger, LoggerOptions } from '../logger';