mongodb 7.7.0 → 7.8.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 (90) hide show
  1. package/README.md +1 -1
  2. package/lib/bulk/unordered.js +1 -1
  3. package/lib/bulk/unordered.js.map +1 -1
  4. package/lib/change_stream.js +3 -11
  5. package/lib/change_stream.js.map +1 -1
  6. package/lib/client-side-encryption/client_encryption.js.map +1 -1
  7. package/lib/cmap/wire_protocol/compression.js +2 -0
  8. package/lib/cmap/wire_protocol/compression.js.map +1 -1
  9. package/lib/collection.js +18 -119
  10. package/lib/collection.js.map +1 -1
  11. package/lib/connection_string.js +110 -15
  12. package/lib/connection_string.js.map +1 -1
  13. package/lib/cursor/abstract_cursor.js +9 -62
  14. package/lib/cursor/abstract_cursor.js.map +1 -1
  15. package/lib/cursor/aggregation_cursor.js +0 -9
  16. package/lib/cursor/aggregation_cursor.js.map +1 -1
  17. package/lib/cursor/find_cursor.js +0 -9
  18. package/lib/cursor/find_cursor.js.map +1 -1
  19. package/lib/cursor/list_collections_cursor.js +1 -1
  20. package/lib/cursor/list_collections_cursor.js.map +1 -1
  21. package/lib/cursor/list_indexes_cursor.js +1 -1
  22. package/lib/cursor/list_indexes_cursor.js.map +1 -1
  23. package/lib/cursor/run_command_cursor.js.map +1 -1
  24. package/lib/db.js +5 -58
  25. package/lib/db.js.map +1 -1
  26. package/lib/deps.js +35 -1
  27. package/lib/deps.js.map +1 -1
  28. package/lib/explain.js +0 -8
  29. package/lib/explain.js.map +1 -1
  30. package/lib/gridfs/download.js +3 -11
  31. package/lib/gridfs/download.js.map +1 -1
  32. package/lib/gridfs/index.js +4 -4
  33. package/lib/gridfs/index.js.map +1 -1
  34. package/lib/gridfs/upload.js +3 -18
  35. package/lib/gridfs/upload.js.map +1 -1
  36. package/lib/mongo_client.js +2 -60
  37. package/lib/mongo_client.js.map +1 -1
  38. package/lib/operations/command.js +0 -2
  39. package/lib/operations/command.js.map +1 -1
  40. package/lib/operations/create_collection.js +1 -1
  41. package/lib/operations/create_collection.js.map +1 -1
  42. package/lib/operations/indexes.js +45 -16
  43. package/lib/operations/indexes.js.map +1 -1
  44. package/lib/sdam/srv_polling.js +7 -2
  45. package/lib/sdam/srv_polling.js.map +1 -1
  46. package/lib/sdam/topology.js +3 -1
  47. package/lib/sdam/topology.js.map +1 -1
  48. package/lib/sessions.js +10 -47
  49. package/lib/sessions.js.map +1 -1
  50. package/lib/sort.js +1 -1
  51. package/lib/sort.js.map +1 -1
  52. package/lib/timeout.js +2 -11
  53. package/lib/timeout.js.map +1 -1
  54. package/lib/utils.js +84 -23
  55. package/lib/utils.js.map +1 -1
  56. package/mongodb.d.ts +249 -314
  57. package/package.json +1 -1
  58. package/src/bulk/unordered.ts +1 -1
  59. package/src/change_stream.ts +4 -12
  60. package/src/client-side-encryption/client_encryption.ts +5 -32
  61. package/src/client-side-encryption/kms_options.ts +1 -0
  62. package/src/cmap/wire_protocol/compression.ts +3 -1
  63. package/src/collection.ts +83 -71
  64. package/src/connection_string.ts +135 -19
  65. package/src/cursor/abstract_cursor.ts +20 -97
  66. package/src/cursor/aggregation_cursor.ts +1 -16
  67. package/src/cursor/find_cursor.ts +2 -17
  68. package/src/cursor/list_collections_cursor.ts +1 -1
  69. package/src/cursor/list_indexes_cursor.ts +1 -1
  70. package/src/cursor/run_command_cursor.ts +4 -31
  71. package/src/db.ts +13 -59
  72. package/src/deps.ts +38 -1
  73. package/src/explain.ts +0 -8
  74. package/src/gridfs/download.ts +7 -14
  75. package/src/gridfs/index.ts +27 -8
  76. package/src/gridfs/upload.ts +6 -21
  77. package/src/index.ts +2 -0
  78. package/src/mongo_client.ts +46 -61
  79. package/src/operations/command.ts +1 -7
  80. package/src/operations/create_collection.ts +4 -2
  81. package/src/operations/drop.ts +1 -1
  82. package/src/operations/indexes.ts +213 -17
  83. package/src/operations/operation.ts +1 -1
  84. package/src/operations/run_command.ts +1 -1
  85. package/src/sdam/srv_polling.ts +12 -3
  86. package/src/sdam/topology.ts +5 -1
  87. package/src/sessions.ts +21 -63
  88. package/src/sort.ts +1 -1
  89. package/src/timeout.ts +2 -13
  90. package/src/utils.ts +96 -30
@@ -152,6 +152,8 @@ export interface TopologyOptions extends BSONSerializeOptions, ServerOptions {
152
152
  /** The name of the replica set to connect to */
153
153
  replicaSet?: string;
154
154
  srvHost?: string;
155
+ srvAllowedHostsSuffix?: string;
156
+ srvHostValidator?: (host: string) => boolean;
155
157
  srvPoller?: SrvPoller;
156
158
  /** Indicates that a client should directly connect to a node without attempting to discover its topology type */
157
159
  directConnection: boolean;
@@ -334,7 +336,9 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
334
336
  heartbeatFrequencyMS: this.s.heartbeatFrequencyMS,
335
337
  srvHost: options.srvHost,
336
338
  srvMaxHosts: options.srvMaxHosts,
337
- srvServiceName: options.srvServiceName
339
+ srvServiceName: options.srvServiceName,
340
+ srvAllowedHostsSuffix: options.srvAllowedHostsSuffix,
341
+ srvHostValidator: options.srvHostValidator
338
342
  });
339
343
 
340
344
  this.on(Topology.TOPOLOGY_DESCRIPTION_CHANGED, this.s.detectShardedTopology);
package/src/sessions.ts CHANGED
@@ -30,7 +30,7 @@ import { RunCommandOperation } from './operations/run_command';
30
30
  import { ReadConcernLevel } from './read_concern';
31
31
  import { ReadPreference } from './read_preference';
32
32
  import { _advanceClusterTime, type ClusterTime, TopologyType } from './sdam/common';
33
- import { TimeoutContext } from './timeout';
33
+ import { type TimeoutContext } from './timeout';
34
34
  import {
35
35
  isTransactionCommand,
36
36
  Transaction,
@@ -60,9 +60,8 @@ export interface ClientSessionOptions {
60
60
  defaultTransactionOptions?: TransactionOptions;
61
61
  /**
62
62
  * @public
63
- * @experimental
64
63
  * An overriding timeoutMS value to use for a client-side timeout.
65
- * If not provided the session uses the timeoutMS specified on the MongoClient.
64
+ * @deprecated `timeoutMS` has no effect and will be removed in a future release.
66
65
  */
67
66
  defaultTimeoutMS?: number;
68
67
 
@@ -92,7 +91,10 @@ export interface EndSessionOptions {
92
91
  force?: boolean;
93
92
  forceClear?: boolean;
94
93
 
95
- /** Specifies the time an operation will run until it throws a timeout error */
94
+ /**
95
+ * Specifies the time an operation will run until it throws a timeout error
96
+ * @deprecated `timeoutMS` has no effect and will be removed in a future release.
97
+ */
96
98
  timeoutMS?: number;
97
99
  }
98
100
 
@@ -138,8 +140,8 @@ export class ClientSession
138
140
  /** @internal */
139
141
  public txnNumberIncrement: number;
140
142
  /**
141
- * @experimental
142
143
  * Specifies the time an operation in a given `ClientSession` will run until it throws a timeout error
144
+ * @deprecated `timeoutMS` has no effect and will be removed in a future release.
143
145
  */
144
146
  timeoutMS?: number;
145
147
 
@@ -186,7 +188,6 @@ export class ClientSession
186
188
  this.sessionPool = sessionPool;
187
189
  this.hasEnded = false;
188
190
  this.clientOptions = clientOptions;
189
- this.timeoutMS = options.defaultTimeoutMS ?? client.s.options?.timeoutMS;
190
191
 
191
192
  this.explicit = !!options.explicit;
192
193
  this._serverSession = this.explicit ? this.sessionPool.acquire() : null;
@@ -424,9 +425,12 @@ export class ClientSession
424
425
  /**
425
426
  * Commits the currently active transaction in this session.
426
427
  *
427
- * @param options - Optional options, can be used to override `defaultTimeoutMS`.
428
+ * @param _options - `timeoutMS` is deprecated and has no effect.
428
429
  */
429
- async commitTransaction(options?: { timeoutMS?: number }): Promise<void> {
430
+ async commitTransaction(_options?: {
431
+ /** @deprecated `timeoutMS` has no effect and will be removed in a future release. */
432
+ timeoutMS?: number;
433
+ }): Promise<void> {
430
434
  if (this.transaction.state === TxnState.NO_TRANSACTION) {
431
435
  throw new MongoTransactionError('No transaction started');
432
436
  }
@@ -453,16 +457,9 @@ export class ClientSession
453
457
  maxTimeMS?: number;
454
458
  } = { commitTransaction: 1 };
455
459
 
456
- const timeoutMS =
457
- typeof options?.timeoutMS === 'number'
458
- ? options.timeoutMS
459
- : typeof this.timeoutMS === 'number'
460
- ? this.timeoutMS
461
- : null;
462
-
463
460
  const wc = this.transaction.options.writeConcern ?? this.clientOptions?.writeConcern;
464
461
  if (wc != null) {
465
- if (timeoutMS == null && this.timeoutContext == null) {
462
+ if (this.timeoutContext == null) {
466
463
  WriteConcern.apply(command, { wtimeoutMS: 10000, w: 'majority', ...wc });
467
464
  } else {
468
465
  const wcKeys = Object.keys(wc);
@@ -477,7 +474,7 @@ export class ClientSession
477
474
  }
478
475
 
479
476
  if (this.transaction.state === TxnState.TRANSACTION_COMMITTED || this.commitAttempted) {
480
- if (timeoutMS == null && this.timeoutContext == null) {
477
+ if (this.timeoutContext == null) {
481
478
  WriteConcern.apply(command, { wtimeoutMS: 10000, ...wc, w: 'majority' });
482
479
  } else {
483
480
  WriteConcern.apply(command, { w: 'majority', ...wc, wtimeoutMS: undefined });
@@ -499,18 +496,8 @@ export class ClientSession
499
496
  });
500
497
  operation.maxAttempts = this.clientOptions.maxAdaptiveRetries + 1;
501
498
 
502
- const timeoutContext =
503
- this.timeoutContext ??
504
- (typeof timeoutMS === 'number'
505
- ? TimeoutContext.create({
506
- serverSelectionTimeoutMS: this.clientOptions.serverSelectionTimeoutMS,
507
- socketTimeoutMS: this.clientOptions.socketTimeoutMS,
508
- timeoutMS
509
- })
510
- : null);
511
-
512
499
  try {
513
- await executeOperation(this.client, operation, timeoutContext);
500
+ await executeOperation(this.client, operation);
514
501
  this.commitAttempted = undefined;
515
502
  return;
516
503
  } catch (firstCommitError) {
@@ -534,7 +521,7 @@ export class ClientSession
534
521
  bypassPinningCheck: true
535
522
  });
536
523
  op.maxAttempts = remainingAttempts;
537
- await executeOperation(this.client, op, timeoutContext);
524
+ await executeOperation(this.client, op);
538
525
  return;
539
526
  } catch (retryCommitError) {
540
527
  // If the retry failed, we process that error instead of the original
@@ -602,26 +589,8 @@ export class ClientSession
602
589
  recoveryToken?: Document;
603
590
  } = { abortTransaction: 1 };
604
591
 
605
- const timeoutMS =
606
- typeof options?.timeoutMS === 'number'
607
- ? options.timeoutMS
608
- : this.timeoutContext?.csotEnabled()
609
- ? this.timeoutContext.timeoutMS // refresh timeoutMS for abort operation
610
- : typeof this.timeoutMS === 'number'
611
- ? this.timeoutMS
612
- : null;
613
-
614
- const timeoutContext =
615
- timeoutMS != null
616
- ? TimeoutContext.create({
617
- timeoutMS,
618
- serverSelectionTimeoutMS: this.clientOptions.serverSelectionTimeoutMS,
619
- socketTimeoutMS: this.clientOptions.socketTimeoutMS
620
- })
621
- : null;
622
-
623
592
  const wc = this.transaction.options.writeConcern ?? this.clientOptions?.writeConcern;
624
- if (wc != null && timeoutMS == null) {
593
+ if (wc != null) {
625
594
  WriteConcern.apply(command, { wtimeoutMS: 10000, w: 'majority', ...wc });
626
595
  }
627
596
 
@@ -636,7 +605,7 @@ export class ClientSession
636
605
  });
637
606
 
638
607
  try {
639
- await executeOperation(this.client, operation, timeoutContext);
608
+ await executeOperation(this.client, operation);
640
609
  this.unpin();
641
610
  return;
642
611
  } catch (firstAbortError) {
@@ -649,7 +618,7 @@ export class ClientSession
649
618
 
650
619
  if (firstAbortError instanceof MongoError && isRetryableWriteError(firstAbortError)) {
651
620
  try {
652
- await executeOperation(this.client, operation, timeoutContext);
621
+ await executeOperation(this.client, operation);
653
622
  return;
654
623
  } catch (secondAbortError) {
655
624
  if (secondAbortError.name === 'MongoRuntimeError') throw secondAbortError;
@@ -716,25 +685,14 @@ export class ClientSession
716
685
  options?: TransactionOptions & {
717
686
  /**
718
687
  * Configures a timeoutMS expiry for the entire withTransactionCallback.
719
- *
720
- * @remarks
721
- * - The remaining timeout will not be applied to callback operations that do not use the ClientSession.
722
- * - Overriding timeoutMS for operations executed using the explicit session inside the provided callback will result in a client-side error.
688
+ * @deprecated `timeoutMS` has no effect and will be removed in a future release.
723
689
  */
724
690
  timeoutMS?: number;
725
691
  }
726
692
  ): Promise<T> {
727
693
  const MAX_TIMEOUT = 120_000;
728
694
 
729
- const timeoutMS = options?.timeoutMS ?? this.timeoutMS ?? null;
730
- this.timeoutContext =
731
- timeoutMS != null
732
- ? TimeoutContext.create({
733
- timeoutMS,
734
- serverSelectionTimeoutMS: this.clientOptions.serverSelectionTimeoutMS,
735
- socketTimeoutMS: this.clientOptions.socketTimeoutMS
736
- })
737
- : null;
695
+ // timeoutMS is a no-op, so a CSOT timeout context is never created
738
696
 
739
697
  // 1. Define the following:
740
698
  // 1.1 Record the current monotonic time, which will be used to enforce the 120-second / CSOT timeout before later retry attempts.
package/src/sort.ts CHANGED
@@ -81,7 +81,7 @@ function isReadonlyArray<T>(value: any): value is readonly T[] {
81
81
 
82
82
  /** @internal */
83
83
  function pairToMap(v: readonly [string, SortDirection]): SortForCmd {
84
- return new Map([[`${v[0]}`, prepareDirection([v[1]])]]);
84
+ return new Map([[`${v[0]}`, prepareDirection(v[1])]]);
85
85
  }
86
86
 
87
87
  /** @internal */
package/src/timeout.ts CHANGED
@@ -150,23 +150,12 @@ function isLegacyTimeoutContextOptions(v: unknown): v is LegacyTimeoutContextOpt
150
150
  );
151
151
  }
152
152
 
153
- function isCSOTTimeoutContextOptions(v: unknown): v is CSOTTimeoutContextOptions {
154
- return (
155
- v != null &&
156
- typeof v === 'object' &&
157
- 'serverSelectionTimeoutMS' in v &&
158
- typeof v.serverSelectionTimeoutMS === 'number' &&
159
- 'timeoutMS' in v &&
160
- typeof v.timeoutMS === 'number'
161
- );
162
- }
163
-
164
153
  /** @internal */
165
154
  export abstract class TimeoutContext {
166
155
  static create(options: TimeoutContextOptions): TimeoutContext {
167
156
  if (options.session?.timeoutContext != null) return options.session?.timeoutContext;
168
- if (isCSOTTimeoutContextOptions(options)) return new CSOTTimeoutContext(options);
169
- else if (isLegacyTimeoutContextOptions(options)) return new LegacyTimeoutContext(options);
157
+ // timeoutMS is a no-op, so a CSOT context is never created
158
+ if (isLegacyTimeoutContextOptions(options)) return new LegacyTimeoutContext(options);
170
159
  else throw new MongoRuntimeError('Unrecognized options');
171
160
  }
172
161
 
package/src/utils.ts CHANGED
@@ -4,6 +4,7 @@ import { promises as fs } from 'fs';
4
4
  import * as http from 'http';
5
5
  import * as process from 'process';
6
6
  import { clearTimeout, setTimeout } from 'timers';
7
+ import { domainToASCII } from 'url';
7
8
 
8
9
  import {
9
10
  ByteUtils,
@@ -29,7 +30,7 @@ import {
29
30
  MongoParseError,
30
31
  MongoRuntimeError
31
32
  } from './error';
32
- import type { MongoClient } from './mongo_client';
33
+ import type { MongoClient, MongoOptions } from './mongo_client';
33
34
  import { type Abortable } from './mongo_types';
34
35
  import type { CommandOperationOptions, OperationParent } from './operations/command';
35
36
  import type { Hint, OperationOptions } from './operations/operation';
@@ -493,12 +494,19 @@ export function resolveTimeoutOptions<T extends Partial<TimeoutContextOptions>>(
493
494
  ): T &
494
495
  Pick<
495
496
  MongoClient['s']['options'],
496
- 'timeoutMS' | 'serverSelectionTimeoutMS' | 'waitQueueTimeoutMS' | 'socketTimeoutMS'
497
- > {
498
- const { socketTimeoutMS, serverSelectionTimeoutMS, waitQueueTimeoutMS, timeoutMS } =
499
- client.s.options;
500
- return { socketTimeoutMS, serverSelectionTimeoutMS, waitQueueTimeoutMS, timeoutMS, ...options };
497
+ 'serverSelectionTimeoutMS' | 'waitQueueTimeoutMS' | 'socketTimeoutMS'
498
+ > & { timeoutMS?: undefined } {
499
+ const { socketTimeoutMS, serverSelectionTimeoutMS, waitQueueTimeoutMS } = client.s.options;
500
+ // timeoutMS is a no-op, so it is never forwarded
501
+ return {
502
+ socketTimeoutMS,
503
+ serverSelectionTimeoutMS,
504
+ waitQueueTimeoutMS,
505
+ ...options,
506
+ timeoutMS: undefined
507
+ };
501
508
  }
509
+
502
510
  /**
503
511
  * Merge inherited properties from parent into options, prioritizing values from options,
504
512
  * then values from parent.
@@ -514,7 +522,6 @@ export function resolveOptions<T extends CommandOperationOptions>(
514
522
  ): T {
515
523
  const result: T = Object.assign({}, options, resolveBSONOptions(options, parent));
516
524
 
517
- const timeoutMS = options?.timeoutMS ?? parent?.timeoutMS;
518
525
  // Users cannot pass a readConcern/writeConcern to operations in a transaction
519
526
  const session = options?.session;
520
527
 
@@ -524,35 +531,20 @@ export function resolveOptions<T extends CommandOperationOptions>(
524
531
  result.readConcern = readConcern;
525
532
  }
526
533
 
527
- let writeConcern = WriteConcern.fromOptions(options) ?? parent?.writeConcern;
534
+ const writeConcern = WriteConcern.fromOptions(options) ?? parent?.writeConcern;
528
535
  if (writeConcern) {
529
- if (timeoutMS != null) {
530
- writeConcern = WriteConcern.fromOptions({
531
- writeConcern: {
532
- ...writeConcern,
533
- wtimeout: undefined,
534
- wtimeoutMS: undefined
535
- }
536
- });
537
- }
538
536
  result.writeConcern = writeConcern;
539
537
  }
540
538
  }
541
539
 
542
- result.timeoutMS = timeoutMS;
540
+ // timeoutMS is a no-op, so it is never propagated to the operation
541
+ result.timeoutMS = undefined;
543
542
 
544
543
  const readPreference = ReadPreference.fromOptions(options) ?? parent?.readPreference;
545
544
  if (readPreference) {
546
545
  result.readPreference = readPreference;
547
546
  }
548
547
 
549
- const isConvenientTransaction = session?.explicit && session?.timeoutContext != null;
550
- if (isConvenientTransaction && options?.timeoutMS != null) {
551
- throw new MongoInvalidArgumentError(
552
- 'An operation cannot be given a timeoutMS setting when inside a withTransaction call that has a timeoutMS setting'
553
- );
554
- }
555
-
556
548
  return result;
557
549
  }
558
550
 
@@ -1151,14 +1143,12 @@ export function parseUnsignedInteger(value: unknown): number | null {
1151
1143
  * If a DNS server were to become compromised SRV records would still need to
1152
1144
  * advertise addresses that are under the same domain as the srvHost.
1153
1145
  *
1154
- * @param address - The address to check against a domain
1146
+ * @param address - The address to check against a domain, already normalized with {@link normalizeDnsName}
1155
1147
  * @param srvHost - The domain to check the provided address against
1156
1148
  * @returns void
1157
1149
  */
1158
- export function checkParentDomainMatch(address: string, srvHost: string): void {
1159
- // Remove trailing dot if exists on either the resolved address or the srv hostname
1160
- const normalizedAddress = address.endsWith('.') ? address.slice(0, address.length - 1) : address;
1161
- const normalizedSrvHost = srvHost.endsWith('.') ? srvHost.slice(0, srvHost.length - 1) : srvHost;
1150
+ export function checkParentDomainMatch(normalizedAddress: string, srvHost: string): void {
1151
+ const normalizedSrvHost = normalizeDnsName(srvHost);
1162
1152
 
1163
1153
  const allCharacterBeforeFirstDot = /^.*?\./;
1164
1154
  const srvIsLessThanThreeParts = normalizedSrvHost.split('.').length < 3;
@@ -1187,6 +1177,82 @@ export function checkParentDomainMatch(address: string, srvHost: string): void {
1187
1177
  }
1188
1178
  }
1189
1179
 
1180
+ /**
1181
+ * Forbidden domain code points, per the WHATWG URL Standard. `domainToASCII` parses its input as
1182
+ * URL text, so names containing these characters are rejected before conversion.
1183
+ */
1184
+ // eslint-disable-next-line no-control-regex
1185
+ const FORBIDDEN_DOMAIN_CODE_POINTS = /[\u0000-\u0020\u007F#%/:<>?@[\\\]^|]/;
1186
+
1187
+ /**
1188
+ * Converts a DNS name to its lowercase A-label (Punycode) form, using the WHATWG URL Standard's
1189
+ * domain parser (with beStrict false).
1190
+ *
1191
+ * @returns the converted name, or `null` if the name cannot be converted
1192
+ */
1193
+ export function dnsNameToASCII(name: string): string | null {
1194
+ if (FORBIDDEN_DOMAIN_CODE_POINTS.test(name)) {
1195
+ return null;
1196
+ }
1197
+ // The standard returns an ASCII name lowercased, even if its `xn--` labels are not valid A-labels
1198
+ // (https://github.com/whatwg/url/commit/a8d5ca3716c3). Node.js only does so from 24.20.0, which
1199
+ // updated Ada to 4.0.0, so it is done here to behave the same on every Node.js version.
1200
+ const ascii = /^\p{ASCII}*$/u.test(name) ? name.toLowerCase() : domainToASCII(name);
1201
+ return ascii === '' ? null : ascii;
1202
+ }
1203
+
1204
+ /**
1205
+ * Normalizes a DNS host name so it can be compared against another normalized host name, as
1206
+ * required by the Initial DNS Seedlist Discovery specification: a trailing `.` is removed, and the
1207
+ * name is converted to its lowercase A-label (Punycode) form.
1208
+ *
1209
+ * @throws MongoAPIError if the name is not a valid DNS host name
1210
+ */
1211
+ export function normalizeDnsName(name: string): string {
1212
+ const withoutTrailingDot = name.endsWith('.') ? name.slice(0, -1) : name;
1213
+ const normalized = dnsNameToASCII(withoutTrailingDot);
1214
+ if (normalized == null) {
1215
+ throw new MongoAPIError('Invalid DNS host name');
1216
+ }
1217
+ return normalized;
1218
+ }
1219
+
1220
+ /**
1221
+ * Verifies a host name returned by an SRV lookup. Exactly one verification applies:
1222
+ * - with `srvHostValidator`, the validator's return value is the complete verdict
1223
+ * - with `srvAllowedHostsSuffix`, the host must end with the (already normalized) suffix
1224
+ * - otherwise, the host must share the parent domain of `srvHost`, see {@link checkParentDomainMatch}
1225
+ *
1226
+ * @param host - A host name returned by an SRV lookup, already normalized with {@link normalizeDnsName}
1227
+ * @param srvHost - The host from the `mongodb+srv` connection string
1228
+ * @throws MongoAPIError if the host fails verification, or the validator throws or returns a
1229
+ * non-boolean value (the `srvHostValidator` option transform wraps the validator to throw in those
1230
+ * cases)
1231
+ */
1232
+ export function verifySrvHost(
1233
+ host: string,
1234
+ srvHost: string,
1235
+ options: Pick<MongoOptions, 'srvAllowedHostsSuffix' | 'srvHostValidator'>
1236
+ ): void {
1237
+ const { srvAllowedHostsSuffix, srvHostValidator } = options;
1238
+
1239
+ if (srvHostValidator != null) {
1240
+ if (!srvHostValidator(host)) {
1241
+ throw new MongoAPIError('Server record was rejected by srvHostValidator');
1242
+ }
1243
+ return;
1244
+ }
1245
+
1246
+ if (srvAllowedHostsSuffix != null) {
1247
+ if (!host.endsWith(srvAllowedHostsSuffix)) {
1248
+ throw new MongoAPIError('Server record does not end with srvAllowedHostsSuffix');
1249
+ }
1250
+ return;
1251
+ }
1252
+
1253
+ checkParentDomainMatch(host, srvHost);
1254
+ }
1255
+
1190
1256
  /**
1191
1257
  * Perform a get request that returns status and body.
1192
1258
  * @internal