You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					1086 lines
				
				41 KiB
			
		
		
			
		
	
	
					1086 lines
				
				41 KiB
			| 
											3 years ago
										 | "use strict"; | ||
|  | Object.defineProperty(exports, "__esModule", { value: true }); | ||
|  | exports.FEATURE_FLAGS = exports.DEFAULT_OPTIONS = exports.OPTIONS = exports.parseOptions = exports.resolveSRVRecord = void 0; | ||
|  | const dns = require("dns"); | ||
|  | const fs = require("fs"); | ||
|  | const mongodb_connection_string_url_1 = require("mongodb-connection-string-url"); | ||
|  | const url_1 = require("url"); | ||
|  | const mongo_credentials_1 = require("./cmap/auth/mongo_credentials"); | ||
|  | const providers_1 = require("./cmap/auth/providers"); | ||
|  | const compression_1 = require("./cmap/wire_protocol/compression"); | ||
|  | const encrypter_1 = require("./encrypter"); | ||
|  | const error_1 = require("./error"); | ||
|  | const mongo_client_1 = require("./mongo_client"); | ||
|  | const mongo_logger_1 = require("./mongo_logger"); | ||
|  | const read_concern_1 = require("./read_concern"); | ||
|  | const read_preference_1 = require("./read_preference"); | ||
|  | const utils_1 = require("./utils"); | ||
|  | const write_concern_1 = require("./write_concern"); | ||
|  | const VALID_TXT_RECORDS = ['authSource', 'replicaSet', 'loadBalanced']; | ||
|  | const LB_SINGLE_HOST_ERROR = 'loadBalanced option only supported with a single host in the URI'; | ||
|  | const LB_REPLICA_SET_ERROR = 'loadBalanced option not supported with a replicaSet option'; | ||
|  | const LB_DIRECT_CONNECTION_ERROR = 'loadBalanced option not supported when directConnection is provided'; | ||
|  | /** | ||
|  |  * Determines whether a provided address matches the provided parent domain in order | ||
|  |  * to avoid certain attack vectors. | ||
|  |  * | ||
|  |  * @param srvAddress - The address to check against a domain | ||
|  |  * @param parentDomain - The domain to check the provided address against | ||
|  |  * @returns Whether the provided address matches the parent domain | ||
|  |  */ | ||
|  | function matchesParentDomain(srvAddress, parentDomain) { | ||
|  |     const regex = /^.*?\./; | ||
|  |     const srv = `.${srvAddress.replace(regex, '')}`; | ||
|  |     const parent = `.${parentDomain.replace(regex, '')}`; | ||
|  |     return srv.endsWith(parent); | ||
|  | } | ||
|  | /** | ||
|  |  * Lookup a `mongodb+srv` connection string, combine the parts and reparse it as a normal | ||
|  |  * connection string. | ||
|  |  * | ||
|  |  * @param uri - The connection string to parse | ||
|  |  * @param options - Optional user provided connection string options | ||
|  |  */ | ||
|  | async function resolveSRVRecord(options) { | ||
|  |     if (typeof options.srvHost !== 'string') { | ||
|  |         throw new error_1.MongoAPIError('Option "srvHost" must not be empty'); | ||
|  |     } | ||
|  |     if (options.srvHost.split('.').length < 3) { | ||
|  |         // TODO(NODE-3484): Replace with MongoConnectionStringError
 | ||
|  |         throw new error_1.MongoAPIError('URI must include hostname, domain name, and tld'); | ||
|  |     } | ||
|  |     // Resolve the SRV record and use the result as the list of hosts to connect to.
 | ||
|  |     const lookupAddress = options.srvHost; | ||
|  |     const addresses = await dns.promises.resolveSrv(`_${options.srvServiceName}._tcp.${lookupAddress}`); | ||
|  |     if (addresses.length === 0) { | ||
|  |         throw new error_1.MongoAPIError('No addresses found at host'); | ||
|  |     } | ||
|  |     for (const { name } of addresses) { | ||
|  |         if (!matchesParentDomain(name, lookupAddress)) { | ||
|  |             throw new error_1.MongoAPIError('Server record does not share hostname with parent URI'); | ||
|  |         } | ||
|  |     } | ||
|  |     const hostAddresses = addresses.map(r => utils_1.HostAddress.fromString(`${r.name}:${r.port ?? 27017}`)); | ||
|  |     validateLoadBalancedOptions(hostAddresses, options, true); | ||
|  |     // Resolve TXT record and add options from there if they exist.
 | ||
|  |     let record; | ||
|  |     try { | ||
|  |         record = await dns.promises.resolveTxt(lookupAddress); | ||
|  |     } | ||
|  |     catch (error) { | ||
|  |         if (error.code !== 'ENODATA' && error.code !== 'ENOTFOUND') { | ||
|  |             throw error; | ||
|  |         } | ||
|  |         return hostAddresses; | ||
|  |     } | ||
|  |     if (record.length > 1) { | ||
|  |         throw new error_1.MongoParseError('Multiple text records not allowed'); | ||
|  |     } | ||
|  |     const txtRecordOptions = new url_1.URLSearchParams(record[0].join('')); | ||
|  |     const txtRecordOptionKeys = [...txtRecordOptions.keys()]; | ||
|  |     if (txtRecordOptionKeys.some(key => !VALID_TXT_RECORDS.includes(key))) { | ||
|  |         throw new error_1.MongoParseError(`Text record may only set any of: ${VALID_TXT_RECORDS.join(', ')}`); | ||
|  |     } | ||
|  |     if (VALID_TXT_RECORDS.some(option => txtRecordOptions.get(option) === '')) { | ||
|  |         throw new error_1.MongoParseError('Cannot have empty URI params in DNS TXT Record'); | ||
|  |     } | ||
|  |     const source = txtRecordOptions.get('authSource') ?? undefined; | ||
|  |     const replicaSet = txtRecordOptions.get('replicaSet') ?? undefined; | ||
|  |     const loadBalanced = txtRecordOptions.get('loadBalanced') ?? undefined; | ||
|  |     if (!options.userSpecifiedAuthSource && | ||
|  |         source && | ||
|  |         options.credentials && | ||
|  |         !providers_1.AUTH_MECHS_AUTH_SRC_EXTERNAL.has(options.credentials.mechanism)) { | ||
|  |         options.credentials = mongo_credentials_1.MongoCredentials.merge(options.credentials, { source }); | ||
|  |     } | ||
|  |     if (!options.userSpecifiedReplicaSet && replicaSet) { | ||
|  |         options.replicaSet = replicaSet; | ||
|  |     } | ||
|  |     if (loadBalanced === 'true') { | ||
|  |         options.loadBalanced = true; | ||
|  |     } | ||
|  |     if (options.replicaSet && options.srvMaxHosts > 0) { | ||
|  |         throw new error_1.MongoParseError('Cannot combine replicaSet option with srvMaxHosts'); | ||
|  |     } | ||
|  |     validateLoadBalancedOptions(hostAddresses, options, true); | ||
|  |     return hostAddresses; | ||
|  | } | ||
|  | exports.resolveSRVRecord = resolveSRVRecord; | ||
|  | /** | ||
|  |  * Checks if TLS options are valid | ||
|  |  * | ||
|  |  * @param allOptions - All options provided by user or included in default options map | ||
|  |  * @throws MongoAPIError if TLS options are invalid | ||
|  |  */ | ||
|  | function checkTLSOptions(allOptions) { | ||
|  |     if (!allOptions) | ||
|  |         return; | ||
|  |     const check = (a, b) => { | ||
|  |         if (allOptions.has(a) && allOptions.has(b)) { | ||
|  |             throw new error_1.MongoAPIError(`The '${a}' option cannot be used with the '${b}' option`); | ||
|  |         } | ||
|  |     }; | ||
|  |     check('tlsInsecure', 'tlsAllowInvalidCertificates'); | ||
|  |     check('tlsInsecure', 'tlsAllowInvalidHostnames'); | ||
|  |     check('tlsInsecure', 'tlsDisableCertificateRevocationCheck'); | ||
|  |     check('tlsInsecure', 'tlsDisableOCSPEndpointCheck'); | ||
|  |     check('tlsAllowInvalidCertificates', 'tlsDisableCertificateRevocationCheck'); | ||
|  |     check('tlsAllowInvalidCertificates', 'tlsDisableOCSPEndpointCheck'); | ||
|  |     check('tlsDisableCertificateRevocationCheck', 'tlsDisableOCSPEndpointCheck'); | ||
|  | } | ||
|  | const TRUTHS = new Set(['true', 't', '1', 'y', 'yes']); | ||
|  | const FALSEHOODS = new Set(['false', 'f', '0', 'n', 'no', '-1']); | ||
|  | function getBoolean(name, value) { | ||
|  |     if (typeof value === 'boolean') | ||
|  |         return value; | ||
|  |     const valueString = String(value).toLowerCase(); | ||
|  |     if (TRUTHS.has(valueString)) { | ||
|  |         if (valueString !== 'true') { | ||
|  |             (0, utils_1.emitWarningOnce)(`deprecated value for ${name} : ${valueString} - please update to ${name} : true instead`); | ||
|  |         } | ||
|  |         return true; | ||
|  |     } | ||
|  |     if (FALSEHOODS.has(valueString)) { | ||
|  |         if (valueString !== 'false') { | ||
|  |             (0, utils_1.emitWarningOnce)(`deprecated value for ${name} : ${valueString} - please update to ${name} : false instead`); | ||
|  |         } | ||
|  |         return false; | ||
|  |     } | ||
|  |     throw new error_1.MongoParseError(`Expected ${name} to be stringified boolean value, got: ${value}`); | ||
|  | } | ||
|  | function getIntFromOptions(name, value) { | ||
|  |     const parsedInt = (0, utils_1.parseInteger)(value); | ||
|  |     if (parsedInt != null) { | ||
|  |         return parsedInt; | ||
|  |     } | ||
|  |     throw new error_1.MongoParseError(`Expected ${name} to be stringified int value, got: ${value}`); | ||
|  | } | ||
|  | function getUIntFromOptions(name, value) { | ||
|  |     const parsedValue = getIntFromOptions(name, value); | ||
|  |     if (parsedValue < 0) { | ||
|  |         throw new error_1.MongoParseError(`${name} can only be a positive int value, got: ${value}`); | ||
|  |     } | ||
|  |     return parsedValue; | ||
|  | } | ||
|  | function* entriesFromString(value) { | ||
|  |     const keyValuePairs = value.split(','); | ||
|  |     for (const keyValue of keyValuePairs) { | ||
|  |         const [key, value] = keyValue.split(':'); | ||
|  |         if (value == null) { | ||
|  |             throw new error_1.MongoParseError('Cannot have undefined values in key value pairs'); | ||
|  |         } | ||
|  |         yield [key, value]; | ||
|  |     } | ||
|  | } | ||
|  | class CaseInsensitiveMap extends Map { | ||
|  |     constructor(entries = []) { | ||
|  |         super(entries.map(([k, v]) => [k.toLowerCase(), v])); | ||
|  |     } | ||
|  |     has(k) { | ||
|  |         return super.has(k.toLowerCase()); | ||
|  |     } | ||
|  |     get(k) { | ||
|  |         return super.get(k.toLowerCase()); | ||
|  |     } | ||
|  |     set(k, v) { | ||
|  |         return super.set(k.toLowerCase(), v); | ||
|  |     } | ||
|  |     delete(k) { | ||
|  |         return super.delete(k.toLowerCase()); | ||
|  |     } | ||
|  | } | ||
|  | function parseOptions(uri, mongoClient = undefined, options = {}) { | ||
|  |     if (mongoClient != null && !(mongoClient instanceof mongo_client_1.MongoClient)) { | ||
|  |         options = mongoClient; | ||
|  |         mongoClient = undefined; | ||
|  |     } | ||
|  |     const url = new mongodb_connection_string_url_1.default(uri); | ||
|  |     const { hosts, isSRV } = url; | ||
|  |     const mongoOptions = Object.create(null); | ||
|  |     // Feature flags
 | ||
|  |     for (const flag of Object.getOwnPropertySymbols(options)) { | ||
|  |         if (exports.FEATURE_FLAGS.has(flag)) { | ||
|  |             mongoOptions[flag] = options[flag]; | ||
|  |         } | ||
|  |     } | ||
|  |     mongoOptions.hosts = isSRV ? [] : hosts.map(utils_1.HostAddress.fromString); | ||
|  |     const urlOptions = new CaseInsensitiveMap(); | ||
|  |     if (url.pathname !== '/' && url.pathname !== '') { | ||
|  |         const dbName = decodeURIComponent(url.pathname[0] === '/' ? url.pathname.slice(1) : url.pathname); | ||
|  |         if (dbName) { | ||
|  |             urlOptions.set('dbName', [dbName]); | ||
|  |         } | ||
|  |     } | ||
|  |     if (url.username !== '') { | ||
|  |         const auth = { | ||
|  |             username: decodeURIComponent(url.username) | ||
|  |         }; | ||
|  |         if (typeof url.password === 'string') { | ||
|  |             auth.password = decodeURIComponent(url.password); | ||
|  |         } | ||
|  |         urlOptions.set('auth', [auth]); | ||
|  |     } | ||
|  |     for (const key of url.searchParams.keys()) { | ||
|  |         const values = [...url.searchParams.getAll(key)]; | ||
|  |         if (values.includes('')) { | ||
|  |             throw new error_1.MongoAPIError('URI cannot contain options with no value'); | ||
|  |         } | ||
|  |         if (!urlOptions.has(key)) { | ||
|  |             urlOptions.set(key, values); | ||
|  |         } | ||
|  |     } | ||
|  |     const objectOptions = new CaseInsensitiveMap(Object.entries(options).filter(([, v]) => v != null)); | ||
|  |     // Validate options that can only be provided by one of uri or object
 | ||
|  |     if (urlOptions.has('serverApi')) { | ||
|  |         throw new error_1.MongoParseError('URI cannot contain `serverApi`, it can only be passed to the client'); | ||
|  |     } | ||
|  |     if (objectOptions.has('loadBalanced')) { | ||
|  |         throw new error_1.MongoParseError('loadBalanced is only a valid option in the URI'); | ||
|  |     } | ||
|  |     // All option collection
 | ||
|  |     const allOptions = new CaseInsensitiveMap(); | ||
|  |     const allKeys = new Set([ | ||
|  |         ...urlOptions.keys(), | ||
|  |         ...objectOptions.keys(), | ||
|  |         ...exports.DEFAULT_OPTIONS.keys() | ||
|  |     ]); | ||
|  |     for (const key of allKeys) { | ||
|  |         const values = []; | ||
|  |         const objectOptionValue = objectOptions.get(key); | ||
|  |         if (objectOptionValue != null) { | ||
|  |             values.push(objectOptionValue); | ||
|  |         } | ||
|  |         const urlValue = urlOptions.get(key); | ||
|  |         if (urlValue != null) { | ||
|  |             values.push(...urlValue); | ||
|  |         } | ||
|  |         const defaultOptionsValue = exports.DEFAULT_OPTIONS.get(key); | ||
|  |         if (defaultOptionsValue != null) { | ||
|  |             values.push(defaultOptionsValue); | ||
|  |         } | ||
|  |         allOptions.set(key, values); | ||
|  |     } | ||
|  |     if (allOptions.has('tlsCertificateKeyFile') && !allOptions.has('tlsCertificateFile')) { | ||
|  |         allOptions.set('tlsCertificateFile', allOptions.get('tlsCertificateKeyFile')); | ||
|  |     } | ||
|  |     if (allOptions.has('tls') || allOptions.has('ssl')) { | ||
|  |         const tlsAndSslOpts = (allOptions.get('tls') || []) | ||
|  |             .concat(allOptions.get('ssl') || []) | ||
|  |             .map(getBoolean.bind(null, 'tls/ssl')); | ||
|  |         if (new Set(tlsAndSslOpts).size !== 1) { | ||
|  |             throw new error_1.MongoParseError('All values of tls/ssl must be the same.'); | ||
|  |         } | ||
|  |     } | ||
|  |     checkTLSOptions(allOptions); | ||
|  |     const unsupportedOptions = (0, utils_1.setDifference)(allKeys, Array.from(Object.keys(exports.OPTIONS)).map(s => s.toLowerCase())); | ||
|  |     if (unsupportedOptions.size !== 0) { | ||
|  |         const optionWord = unsupportedOptions.size > 1 ? 'options' : 'option'; | ||
|  |         const isOrAre = unsupportedOptions.size > 1 ? 'are' : 'is'; | ||
|  |         throw new error_1.MongoParseError(`${optionWord} ${Array.from(unsupportedOptions).join(', ')} ${isOrAre} not supported`); | ||
|  |     } | ||
|  |     // Option parsing and setting
 | ||
|  |     for (const [key, descriptor] of Object.entries(exports.OPTIONS)) { | ||
|  |         const values = allOptions.get(key); | ||
|  |         if (!values || values.length === 0) | ||
|  |             continue; | ||
|  |         setOption(mongoOptions, key, descriptor, values); | ||
|  |     } | ||
|  |     if (mongoOptions.credentials) { | ||
|  |         const isGssapi = mongoOptions.credentials.mechanism === providers_1.AuthMechanism.MONGODB_GSSAPI; | ||
|  |         const isX509 = mongoOptions.credentials.mechanism === providers_1.AuthMechanism.MONGODB_X509; | ||
|  |         const isAws = mongoOptions.credentials.mechanism === providers_1.AuthMechanism.MONGODB_AWS; | ||
|  |         if ((isGssapi || isX509) && | ||
|  |             allOptions.has('authSource') && | ||
|  |             mongoOptions.credentials.source !== '$external') { | ||
|  |             // If authSource was explicitly given and its incorrect, we error
 | ||
|  |             throw new error_1.MongoParseError(`${mongoOptions.credentials} can only have authSource set to '$external'`); | ||
|  |         } | ||
|  |         if (!(isGssapi || isX509 || isAws) && mongoOptions.dbName && !allOptions.has('authSource')) { | ||
|  |             // inherit the dbName unless GSSAPI or X509, then silently ignore dbName
 | ||
|  |             // and there was no specific authSource given
 | ||
|  |             mongoOptions.credentials = mongo_credentials_1.MongoCredentials.merge(mongoOptions.credentials, { | ||
|  |                 source: mongoOptions.dbName | ||
|  |             }); | ||
|  |         } | ||
|  |         if (isAws && mongoOptions.credentials.username && !mongoOptions.credentials.password) { | ||
|  |             throw new error_1.MongoMissingCredentialsError(`When using ${mongoOptions.credentials.mechanism} password must be set when a username is specified`); | ||
|  |         } | ||
|  |         mongoOptions.credentials.validate(); | ||
|  |         // Check if the only auth related option provided was authSource, if so we can remove credentials
 | ||
|  |         if (mongoOptions.credentials.password === '' && | ||
|  |             mongoOptions.credentials.username === '' && | ||
|  |             mongoOptions.credentials.mechanism === providers_1.AuthMechanism.MONGODB_DEFAULT && | ||
|  |             Object.keys(mongoOptions.credentials.mechanismProperties).length === 0) { | ||
|  |             delete mongoOptions.credentials; | ||
|  |         } | ||
|  |     } | ||
|  |     if (!mongoOptions.dbName) { | ||
|  |         // dbName default is applied here because of the credential validation above
 | ||
|  |         mongoOptions.dbName = 'test'; | ||
|  |     } | ||
|  |     validateLoadBalancedOptions(hosts, mongoOptions, isSRV); | ||
|  |     if (mongoClient && mongoOptions.autoEncryption) { | ||
|  |         encrypter_1.Encrypter.checkForMongoCrypt(); | ||
|  |         mongoOptions.encrypter = new encrypter_1.Encrypter(mongoClient, uri, options); | ||
|  |         mongoOptions.autoEncrypter = mongoOptions.encrypter.autoEncrypter; | ||
|  |     } | ||
|  |     // Potential SRV Overrides and SRV connection string validations
 | ||
|  |     mongoOptions.userSpecifiedAuthSource = | ||
|  |         objectOptions.has('authSource') || urlOptions.has('authSource'); | ||
|  |     mongoOptions.userSpecifiedReplicaSet = | ||
|  |         objectOptions.has('replicaSet') || urlOptions.has('replicaSet'); | ||
|  |     if (isSRV) { | ||
|  |         // SRV Record is resolved upon connecting
 | ||
|  |         mongoOptions.srvHost = hosts[0]; | ||
|  |         if (mongoOptions.directConnection) { | ||
|  |             throw new error_1.MongoAPIError('SRV URI does not support directConnection'); | ||
|  |         } | ||
|  |         if (mongoOptions.srvMaxHosts > 0 && typeof mongoOptions.replicaSet === 'string') { | ||
|  |             throw new error_1.MongoParseError('Cannot use srvMaxHosts option with replicaSet'); | ||
|  |         } | ||
|  |         // SRV turns on TLS by default, but users can override and turn it off
 | ||
|  |         const noUserSpecifiedTLS = !objectOptions.has('tls') && !urlOptions.has('tls'); | ||
|  |         const noUserSpecifiedSSL = !objectOptions.has('ssl') && !urlOptions.has('ssl'); | ||
|  |         if (noUserSpecifiedTLS && noUserSpecifiedSSL) { | ||
|  |             mongoOptions.tls = true; | ||
|  |         } | ||
|  |     } | ||
|  |     else { | ||
|  |         const userSpecifiedSrvOptions = urlOptions.has('srvMaxHosts') || | ||
|  |             objectOptions.has('srvMaxHosts') || | ||
|  |             urlOptions.has('srvServiceName') || | ||
|  |             objectOptions.has('srvServiceName'); | ||
|  |         if (userSpecifiedSrvOptions) { | ||
|  |             throw new error_1.MongoParseError('Cannot use srvMaxHosts or srvServiceName with a non-srv connection string'); | ||
|  |         } | ||
|  |     } | ||
|  |     if (mongoOptions.directConnection && mongoOptions.hosts.length !== 1) { | ||
|  |         throw new error_1.MongoParseError('directConnection option requires exactly one host'); | ||
|  |     } | ||
|  |     if (!mongoOptions.proxyHost && | ||
|  |         (mongoOptions.proxyPort || mongoOptions.proxyUsername || mongoOptions.proxyPassword)) { | ||
|  |         throw new error_1.MongoParseError('Must specify proxyHost if other proxy options are passed'); | ||
|  |     } | ||
|  |     if ((mongoOptions.proxyUsername && !mongoOptions.proxyPassword) || | ||
|  |         (!mongoOptions.proxyUsername && mongoOptions.proxyPassword)) { | ||
|  |         throw new error_1.MongoParseError('Can only specify both of proxy username/password or neither'); | ||
|  |     } | ||
|  |     const proxyOptions = ['proxyHost', 'proxyPort', 'proxyUsername', 'proxyPassword'].map(key => urlOptions.get(key) ?? []); | ||
|  |     if (proxyOptions.some(options => options.length > 1)) { | ||
|  |         throw new error_1.MongoParseError('Proxy options cannot be specified multiple times in the connection string'); | ||
|  |     } | ||
|  |     const loggerFeatureFlag = Symbol.for('@@mdb.enableMongoLogger'); | ||
|  |     mongoOptions[loggerFeatureFlag] = mongoOptions[loggerFeatureFlag] ?? false; | ||
|  |     let loggerEnvOptions = {}; | ||
|  |     let loggerClientOptions = {}; | ||
|  |     if (mongoOptions[loggerFeatureFlag]) { | ||
|  |         loggerEnvOptions = { | ||
|  |             MONGODB_LOG_COMMAND: process.env.MONGODB_LOG_COMMAND, | ||
|  |             MONGODB_LOG_TOPOLOGY: process.env.MONGODB_LOG_TOPOLOGY, | ||
|  |             MONGODB_LOG_SERVER_SELECTION: process.env.MONGODB_LOG_SERVER_SELECTION, | ||
|  |             MONGODB_LOG_CONNECTION: process.env.MONGODB_LOG_CONNECTION, | ||
|  |             MONGODB_LOG_ALL: process.env.MONGODB_LOG_ALL, | ||
|  |             MONGODB_LOG_MAX_DOCUMENT_LENGTH: process.env.MONGODB_LOG_MAX_DOCUMENT_LENGTH, | ||
|  |             MONGODB_LOG_PATH: process.env.MONGODB_LOG_PATH | ||
|  |         }; | ||
|  |         loggerClientOptions = { | ||
|  |             mongodbLogPath: mongoOptions.mongodbLogPath | ||
|  |         }; | ||
|  |     } | ||
|  |     mongoOptions.mongoLoggerOptions = mongo_logger_1.MongoLogger.resolveOptions(loggerEnvOptions, loggerClientOptions); | ||
|  |     return mongoOptions; | ||
|  | } | ||
|  | exports.parseOptions = parseOptions; | ||
|  | /** | ||
|  |  * #### Throws if LB mode is true: | ||
|  |  * - hosts contains more than one host | ||
|  |  * - there is a replicaSet name set | ||
|  |  * - directConnection is set | ||
|  |  * - if srvMaxHosts is used when an srv connection string is passed in | ||
|  |  * | ||
|  |  * @throws MongoParseError | ||
|  |  */ | ||
|  | function validateLoadBalancedOptions(hosts, mongoOptions, isSrv) { | ||
|  |     if (mongoOptions.loadBalanced) { | ||
|  |         if (hosts.length > 1) { | ||
|  |             throw new error_1.MongoParseError(LB_SINGLE_HOST_ERROR); | ||
|  |         } | ||
|  |         if (mongoOptions.replicaSet) { | ||
|  |             throw new error_1.MongoParseError(LB_REPLICA_SET_ERROR); | ||
|  |         } | ||
|  |         if (mongoOptions.directConnection) { | ||
|  |             throw new error_1.MongoParseError(LB_DIRECT_CONNECTION_ERROR); | ||
|  |         } | ||
|  |         if (isSrv && mongoOptions.srvMaxHosts > 0) { | ||
|  |             throw new error_1.MongoParseError('Cannot limit srv hosts with loadBalanced enabled'); | ||
|  |         } | ||
|  |     } | ||
|  |     return; | ||
|  | } | ||
|  | function setOption(mongoOptions, key, descriptor, values) { | ||
|  |     const { target, type, transform, deprecated } = descriptor; | ||
|  |     const name = target ?? key; | ||
|  |     if (deprecated) { | ||
|  |         const deprecatedMsg = typeof deprecated === 'string' ? `: ${deprecated}` : ''; | ||
|  |         (0, utils_1.emitWarning)(`${key} is a deprecated option${deprecatedMsg}`); | ||
|  |     } | ||
|  |     switch (type) { | ||
|  |         case 'boolean': | ||
|  |             mongoOptions[name] = getBoolean(name, values[0]); | ||
|  |             break; | ||
|  |         case 'int': | ||
|  |             mongoOptions[name] = getIntFromOptions(name, values[0]); | ||
|  |             break; | ||
|  |         case 'uint': | ||
|  |             mongoOptions[name] = getUIntFromOptions(name, values[0]); | ||
|  |             break; | ||
|  |         case 'string': | ||
|  |             if (values[0] == null) { | ||
|  |                 break; | ||
|  |             } | ||
|  |             mongoOptions[name] = String(values[0]); | ||
|  |             break; | ||
|  |         case 'record': | ||
|  |             if (!(0, utils_1.isRecord)(values[0])) { | ||
|  |                 throw new error_1.MongoParseError(`${name} must be an object`); | ||
|  |             } | ||
|  |             mongoOptions[name] = values[0]; | ||
|  |             break; | ||
|  |         case 'any': | ||
|  |             mongoOptions[name] = values[0]; | ||
|  |             break; | ||
|  |         default: { | ||
|  |             if (!transform) { | ||
|  |                 throw new error_1.MongoParseError('Descriptors missing a type must define a transform'); | ||
|  |             } | ||
|  |             const transformValue = transform({ name, options: mongoOptions, values }); | ||
|  |             mongoOptions[name] = transformValue; | ||
|  |             break; | ||
|  |         } | ||
|  |     } | ||
|  | } | ||
|  | exports.OPTIONS = { | ||
|  |     appName: { | ||
|  |         target: 'metadata', | ||
|  |         transform({ options, values: [value] }) { | ||
|  |             return (0, utils_1.makeClientMetadata)({ ...options.driverInfo, appName: String(value) }); | ||
|  |         } | ||
|  |     }, | ||
|  |     auth: { | ||
|  |         target: 'credentials', | ||
|  |         transform({ name, options, values: [value] }) { | ||
|  |             if (!(0, utils_1.isRecord)(value, ['username', 'password'])) { | ||
|  |                 throw new error_1.MongoParseError(`${name} must be an object with 'username' and 'password' properties`); | ||
|  |             } | ||
|  |             return mongo_credentials_1.MongoCredentials.merge(options.credentials, { | ||
|  |                 username: value.username, | ||
|  |                 password: value.password | ||
|  |             }); | ||
|  |         } | ||
|  |     }, | ||
|  |     authMechanism: { | ||
|  |         target: 'credentials', | ||
|  |         transform({ options, values: [value] }) { | ||
|  |             const mechanisms = Object.values(providers_1.AuthMechanism); | ||
|  |             const [mechanism] = mechanisms.filter(m => m.match(RegExp(String.raw `\b${value}\b`, 'i'))); | ||
|  |             if (!mechanism) { | ||
|  |                 throw new error_1.MongoParseError(`authMechanism one of ${mechanisms}, got ${value}`); | ||
|  |             } | ||
|  |             let source = options.credentials?.source; | ||
|  |             if (mechanism === providers_1.AuthMechanism.MONGODB_PLAIN || | ||
|  |                 providers_1.AUTH_MECHS_AUTH_SRC_EXTERNAL.has(mechanism)) { | ||
|  |                 // some mechanisms have '$external' as the Auth Source
 | ||
|  |                 source = '$external'; | ||
|  |             } | ||
|  |             let password = options.credentials?.password; | ||
|  |             if (mechanism === providers_1.AuthMechanism.MONGODB_X509 && password === '') { | ||
|  |                 password = undefined; | ||
|  |             } | ||
|  |             return mongo_credentials_1.MongoCredentials.merge(options.credentials, { | ||
|  |                 mechanism, | ||
|  |                 source, | ||
|  |                 password | ||
|  |             }); | ||
|  |         } | ||
|  |     }, | ||
|  |     authMechanismProperties: { | ||
|  |         target: 'credentials', | ||
|  |         transform({ options, values: [optionValue] }) { | ||
|  |             if (typeof optionValue === 'string') { | ||
|  |                 const mechanismProperties = Object.create(null); | ||
|  |                 for (const [key, value] of entriesFromString(optionValue)) { | ||
|  |                     try { | ||
|  |                         mechanismProperties[key] = getBoolean(key, value); | ||
|  |                     } | ||
|  |                     catch { | ||
|  |                         mechanismProperties[key] = value; | ||
|  |                     } | ||
|  |                 } | ||
|  |                 return mongo_credentials_1.MongoCredentials.merge(options.credentials, { | ||
|  |                     mechanismProperties | ||
|  |                 }); | ||
|  |             } | ||
|  |             if (!(0, utils_1.isRecord)(optionValue)) { | ||
|  |                 throw new error_1.MongoParseError('AuthMechanismProperties must be an object'); | ||
|  |             } | ||
|  |             return mongo_credentials_1.MongoCredentials.merge(options.credentials, { mechanismProperties: optionValue }); | ||
|  |         } | ||
|  |     }, | ||
|  |     authSource: { | ||
|  |         target: 'credentials', | ||
|  |         transform({ options, values: [value] }) { | ||
|  |             const source = String(value); | ||
|  |             return mongo_credentials_1.MongoCredentials.merge(options.credentials, { source }); | ||
|  |         } | ||
|  |     }, | ||
|  |     autoEncryption: { | ||
|  |         type: 'record' | ||
|  |     }, | ||
|  |     bsonRegExp: { | ||
|  |         type: 'boolean' | ||
|  |     }, | ||
|  |     serverApi: { | ||
|  |         target: 'serverApi', | ||
|  |         transform({ values: [version] }) { | ||
|  |             const serverApiToValidate = typeof version === 'string' ? { version } : version; | ||
|  |             const versionToValidate = serverApiToValidate && serverApiToValidate.version; | ||
|  |             if (!versionToValidate) { | ||
|  |                 throw new error_1.MongoParseError(`Invalid \`serverApi\` property; must specify a version from the following enum: ["${Object.values(mongo_client_1.ServerApiVersion).join('", "')}"]`); | ||
|  |             } | ||
|  |             if (!Object.values(mongo_client_1.ServerApiVersion).some(v => v === versionToValidate)) { | ||
|  |                 throw new error_1.MongoParseError(`Invalid server API version=${versionToValidate}; must be in the following enum: ["${Object.values(mongo_client_1.ServerApiVersion).join('", "')}"]`); | ||
|  |             } | ||
|  |             return serverApiToValidate; | ||
|  |         } | ||
|  |     }, | ||
|  |     checkKeys: { | ||
|  |         type: 'boolean' | ||
|  |     }, | ||
|  |     compressors: { | ||
|  |         default: 'none', | ||
|  |         target: 'compressors', | ||
|  |         transform({ values }) { | ||
|  |             const compressionList = new Set(); | ||
|  |             for (const compVal of values) { | ||
|  |                 const compValArray = typeof compVal === 'string' ? compVal.split(',') : compVal; | ||
|  |                 if (!Array.isArray(compValArray)) { | ||
|  |                     throw new error_1.MongoInvalidArgumentError('compressors must be an array or a comma-delimited list of strings'); | ||
|  |                 } | ||
|  |                 for (const c of compValArray) { | ||
|  |                     if (Object.keys(compression_1.Compressor).includes(String(c))) { | ||
|  |                         compressionList.add(String(c)); | ||
|  |                     } | ||
|  |                     else { | ||
|  |                         throw new error_1.MongoInvalidArgumentError(`${c} is not a valid compression mechanism. Must be one of: ${Object.keys(compression_1.Compressor)}.`); | ||
|  |                     } | ||
|  |                 } | ||
|  |             } | ||
|  |             return [...compressionList]; | ||
|  |         } | ||
|  |     }, | ||
|  |     connectTimeoutMS: { | ||
|  |         default: 30000, | ||
|  |         type: 'uint' | ||
|  |     }, | ||
|  |     dbName: { | ||
|  |         type: 'string' | ||
|  |     }, | ||
|  |     directConnection: { | ||
|  |         default: false, | ||
|  |         type: 'boolean' | ||
|  |     }, | ||
|  |     driverInfo: { | ||
|  |         target: 'metadata', | ||
|  |         default: (0, utils_1.makeClientMetadata)(), | ||
|  |         transform({ options, values: [value] }) { | ||
|  |             if (!(0, utils_1.isRecord)(value)) | ||
|  |                 throw new error_1.MongoParseError('DriverInfo must be an object'); | ||
|  |             return (0, utils_1.makeClientMetadata)({ | ||
|  |                 driverInfo: value, | ||
|  |                 appName: options.metadata?.application?.name | ||
|  |             }); | ||
|  |         } | ||
|  |     }, | ||
|  |     enableUtf8Validation: { type: 'boolean', default: true }, | ||
|  |     family: { | ||
|  |         transform({ name, values: [value] }) { | ||
|  |             const transformValue = getIntFromOptions(name, value); | ||
|  |             if (transformValue === 4 || transformValue === 6) { | ||
|  |                 return transformValue; | ||
|  |             } | ||
|  |             throw new error_1.MongoParseError(`Option 'family' must be 4 or 6 got ${transformValue}.`); | ||
|  |         } | ||
|  |     }, | ||
|  |     fieldsAsRaw: { | ||
|  |         type: 'record' | ||
|  |     }, | ||
|  |     forceServerObjectId: { | ||
|  |         default: false, | ||
|  |         type: 'boolean' | ||
|  |     }, | ||
|  |     fsync: { | ||
|  |         deprecated: 'Please use journal instead', | ||
|  |         target: 'writeConcern', | ||
|  |         transform({ name, options, values: [value] }) { | ||
|  |             const wc = write_concern_1.WriteConcern.fromOptions({ | ||
|  |                 writeConcern: { | ||
|  |                     ...options.writeConcern, | ||
|  |                     fsync: getBoolean(name, value) | ||
|  |                 } | ||
|  |             }); | ||
|  |             if (!wc) | ||
|  |                 throw new error_1.MongoParseError(`Unable to make a writeConcern from fsync=${value}`); | ||
|  |             return wc; | ||
|  |         } | ||
|  |     }, | ||
|  |     heartbeatFrequencyMS: { | ||
|  |         default: 10000, | ||
|  |         type: 'uint' | ||
|  |     }, | ||
|  |     ignoreUndefined: { | ||
|  |         type: 'boolean' | ||
|  |     }, | ||
|  |     j: { | ||
|  |         deprecated: 'Please use journal instead', | ||
|  |         target: 'writeConcern', | ||
|  |         transform({ name, options, values: [value] }) { | ||
|  |             const wc = write_concern_1.WriteConcern.fromOptions({ | ||
|  |                 writeConcern: { | ||
|  |                     ...options.writeConcern, | ||
|  |                     journal: getBoolean(name, value) | ||
|  |                 } | ||
|  |             }); | ||
|  |             if (!wc) | ||
|  |                 throw new error_1.MongoParseError(`Unable to make a writeConcern from journal=${value}`); | ||
|  |             return wc; | ||
|  |         } | ||
|  |     }, | ||
|  |     journal: { | ||
|  |         target: 'writeConcern', | ||
|  |         transform({ name, options, values: [value] }) { | ||
|  |             const wc = write_concern_1.WriteConcern.fromOptions({ | ||
|  |                 writeConcern: { | ||
|  |                     ...options.writeConcern, | ||
|  |                     journal: getBoolean(name, value) | ||
|  |                 } | ||
|  |             }); | ||
|  |             if (!wc) | ||
|  |                 throw new error_1.MongoParseError(`Unable to make a writeConcern from journal=${value}`); | ||
|  |             return wc; | ||
|  |         } | ||
|  |     }, | ||
|  |     keepAlive: { | ||
|  |         default: true, | ||
|  |         type: 'boolean' | ||
|  |     }, | ||
|  |     keepAliveInitialDelay: { | ||
|  |         default: 120000, | ||
|  |         type: 'uint' | ||
|  |     }, | ||
|  |     loadBalanced: { | ||
|  |         default: false, | ||
|  |         type: 'boolean' | ||
|  |     }, | ||
|  |     localThresholdMS: { | ||
|  |         default: 15, | ||
|  |         type: 'uint' | ||
|  |     }, | ||
|  |     maxConnecting: { | ||
|  |         default: 2, | ||
|  |         transform({ name, values: [value] }) { | ||
|  |             const maxConnecting = getUIntFromOptions(name, value); | ||
|  |             if (maxConnecting === 0) { | ||
|  |                 throw new error_1.MongoInvalidArgumentError('maxConnecting must be > 0 if specified'); | ||
|  |             } | ||
|  |             return maxConnecting; | ||
|  |         } | ||
|  |     }, | ||
|  |     maxIdleTimeMS: { | ||
|  |         default: 0, | ||
|  |         type: 'uint' | ||
|  |     }, | ||
|  |     maxPoolSize: { | ||
|  |         default: 100, | ||
|  |         type: 'uint' | ||
|  |     }, | ||
|  |     maxStalenessSeconds: { | ||
|  |         target: 'readPreference', | ||
|  |         transform({ name, options, values: [value] }) { | ||
|  |             const maxStalenessSeconds = getUIntFromOptions(name, value); | ||
|  |             if (options.readPreference) { | ||
|  |                 return read_preference_1.ReadPreference.fromOptions({ | ||
|  |                     readPreference: { ...options.readPreference, maxStalenessSeconds } | ||
|  |                 }); | ||
|  |             } | ||
|  |             else { | ||
|  |                 return new read_preference_1.ReadPreference('secondary', undefined, { maxStalenessSeconds }); | ||
|  |             } | ||
|  |         } | ||
|  |     }, | ||
|  |     minInternalBufferSize: { | ||
|  |         type: 'uint' | ||
|  |     }, | ||
|  |     minPoolSize: { | ||
|  |         default: 0, | ||
|  |         type: 'uint' | ||
|  |     }, | ||
|  |     minHeartbeatFrequencyMS: { | ||
|  |         default: 500, | ||
|  |         type: 'uint' | ||
|  |     }, | ||
|  |     monitorCommands: { | ||
|  |         default: false, | ||
|  |         type: 'boolean' | ||
|  |     }, | ||
|  |     name: { | ||
|  |         target: 'driverInfo', | ||
|  |         transform({ values: [value], options }) { | ||
|  |             return { ...options.driverInfo, name: String(value) }; | ||
|  |         } | ||
|  |     }, | ||
|  |     noDelay: { | ||
|  |         default: true, | ||
|  |         type: 'boolean' | ||
|  |     }, | ||
|  |     pkFactory: { | ||
|  |         default: utils_1.DEFAULT_PK_FACTORY, | ||
|  |         transform({ values: [value] }) { | ||
|  |             if ((0, utils_1.isRecord)(value, ['createPk']) && typeof value.createPk === 'function') { | ||
|  |                 return value; | ||
|  |             } | ||
|  |             throw new error_1.MongoParseError(`Option pkFactory must be an object with a createPk function, got ${value}`); | ||
|  |         } | ||
|  |     }, | ||
|  |     promoteBuffers: { | ||
|  |         type: 'boolean' | ||
|  |     }, | ||
|  |     promoteLongs: { | ||
|  |         type: 'boolean' | ||
|  |     }, | ||
|  |     promoteValues: { | ||
|  |         type: 'boolean' | ||
|  |     }, | ||
|  |     proxyHost: { | ||
|  |         type: 'string' | ||
|  |     }, | ||
|  |     proxyPassword: { | ||
|  |         type: 'string' | ||
|  |     }, | ||
|  |     proxyPort: { | ||
|  |         type: 'uint' | ||
|  |     }, | ||
|  |     proxyUsername: { | ||
|  |         type: 'string' | ||
|  |     }, | ||
|  |     raw: { | ||
|  |         default: false, | ||
|  |         type: 'boolean' | ||
|  |     }, | ||
|  |     readConcern: { | ||
|  |         transform({ values: [value], options }) { | ||
|  |             if (value instanceof read_concern_1.ReadConcern || (0, utils_1.isRecord)(value, ['level'])) { | ||
|  |                 return read_concern_1.ReadConcern.fromOptions({ ...options.readConcern, ...value }); | ||
|  |             } | ||
|  |             throw new error_1.MongoParseError(`ReadConcern must be an object, got ${JSON.stringify(value)}`); | ||
|  |         } | ||
|  |     }, | ||
|  |     readConcernLevel: { | ||
|  |         target: 'readConcern', | ||
|  |         transform({ values: [level], options }) { | ||
|  |             return read_concern_1.ReadConcern.fromOptions({ | ||
|  |                 ...options.readConcern, | ||
|  |                 level: level | ||
|  |             }); | ||
|  |         } | ||
|  |     }, | ||
|  |     readPreference: { | ||
|  |         default: read_preference_1.ReadPreference.primary, | ||
|  |         transform({ values: [value], options }) { | ||
|  |             if (value instanceof read_preference_1.ReadPreference) { | ||
|  |                 return read_preference_1.ReadPreference.fromOptions({ | ||
|  |                     readPreference: { ...options.readPreference, ...value }, | ||
|  |                     ...value | ||
|  |                 }); | ||
|  |             } | ||
|  |             if ((0, utils_1.isRecord)(value, ['mode'])) { | ||
|  |                 const rp = read_preference_1.ReadPreference.fromOptions({ | ||
|  |                     readPreference: { ...options.readPreference, ...value }, | ||
|  |                     ...value | ||
|  |                 }); | ||
|  |                 if (rp) | ||
|  |                     return rp; | ||
|  |                 else | ||
|  |                     throw new error_1.MongoParseError(`Cannot make read preference from ${JSON.stringify(value)}`); | ||
|  |             } | ||
|  |             if (typeof value === 'string') { | ||
|  |                 const rpOpts = { | ||
|  |                     hedge: options.readPreference?.hedge, | ||
|  |                     maxStalenessSeconds: options.readPreference?.maxStalenessSeconds | ||
|  |                 }; | ||
|  |                 return new read_preference_1.ReadPreference(value, options.readPreference?.tags, rpOpts); | ||
|  |             } | ||
|  |             throw new error_1.MongoParseError(`Unknown ReadPreference value: ${value}`); | ||
|  |         } | ||
|  |     }, | ||
|  |     readPreferenceTags: { | ||
|  |         target: 'readPreference', | ||
|  |         transform({ values, options }) { | ||
|  |             const tags = Array.isArray(values[0]) | ||
|  |                 ? values[0] | ||
|  |                 : values; | ||
|  |             const readPreferenceTags = []; | ||
|  |             for (const tag of tags) { | ||
|  |                 const readPreferenceTag = Object.create(null); | ||
|  |                 if (typeof tag === 'string') { | ||
|  |                     for (const [k, v] of entriesFromString(tag)) { | ||
|  |                         readPreferenceTag[k] = v; | ||
|  |                     } | ||
|  |                 } | ||
|  |                 if ((0, utils_1.isRecord)(tag)) { | ||
|  |                     for (const [k, v] of Object.entries(tag)) { | ||
|  |                         readPreferenceTag[k] = v; | ||
|  |                     } | ||
|  |                 } | ||
|  |                 readPreferenceTags.push(readPreferenceTag); | ||
|  |             } | ||
|  |             return read_preference_1.ReadPreference.fromOptions({ | ||
|  |                 readPreference: options.readPreference, | ||
|  |                 readPreferenceTags | ||
|  |             }); | ||
|  |         } | ||
|  |     }, | ||
|  |     replicaSet: { | ||
|  |         type: 'string' | ||
|  |     }, | ||
|  |     retryReads: { | ||
|  |         default: true, | ||
|  |         type: 'boolean' | ||
|  |     }, | ||
|  |     retryWrites: { | ||
|  |         default: true, | ||
|  |         type: 'boolean' | ||
|  |     }, | ||
|  |     serializeFunctions: { | ||
|  |         type: 'boolean' | ||
|  |     }, | ||
|  |     serverSelectionTimeoutMS: { | ||
|  |         default: 30000, | ||
|  |         type: 'uint' | ||
|  |     }, | ||
|  |     servername: { | ||
|  |         type: 'string' | ||
|  |     }, | ||
|  |     socketTimeoutMS: { | ||
|  |         default: 0, | ||
|  |         type: 'uint' | ||
|  |     }, | ||
|  |     srvMaxHosts: { | ||
|  |         type: 'uint', | ||
|  |         default: 0 | ||
|  |     }, | ||
|  |     srvServiceName: { | ||
|  |         type: 'string', | ||
|  |         default: 'mongodb' | ||
|  |     }, | ||
|  |     ssl: { | ||
|  |         target: 'tls', | ||
|  |         type: 'boolean' | ||
|  |     }, | ||
|  |     sslCA: { | ||
|  |         target: 'ca', | ||
|  |         transform({ values: [value] }) { | ||
|  |             return fs.readFileSync(String(value), { encoding: 'ascii' }); | ||
|  |         } | ||
|  |     }, | ||
|  |     sslCRL: { | ||
|  |         target: 'crl', | ||
|  |         transform({ values: [value] }) { | ||
|  |             return fs.readFileSync(String(value), { encoding: 'ascii' }); | ||
|  |         } | ||
|  |     }, | ||
|  |     sslCert: { | ||
|  |         target: 'cert', | ||
|  |         transform({ values: [value] }) { | ||
|  |             return fs.readFileSync(String(value), { encoding: 'ascii' }); | ||
|  |         } | ||
|  |     }, | ||
|  |     sslKey: { | ||
|  |         target: 'key', | ||
|  |         transform({ values: [value] }) { | ||
|  |             return fs.readFileSync(String(value), { encoding: 'ascii' }); | ||
|  |         } | ||
|  |     }, | ||
|  |     sslPass: { | ||
|  |         deprecated: true, | ||
|  |         target: 'passphrase', | ||
|  |         type: 'string' | ||
|  |     }, | ||
|  |     sslValidate: { | ||
|  |         target: 'rejectUnauthorized', | ||
|  |         type: 'boolean' | ||
|  |     }, | ||
|  |     tls: { | ||
|  |         type: 'boolean' | ||
|  |     }, | ||
|  |     tlsAllowInvalidCertificates: { | ||
|  |         target: 'rejectUnauthorized', | ||
|  |         transform({ name, values: [value] }) { | ||
|  |             // allowInvalidCertificates is the inverse of rejectUnauthorized
 | ||
|  |             return !getBoolean(name, value); | ||
|  |         } | ||
|  |     }, | ||
|  |     tlsAllowInvalidHostnames: { | ||
|  |         target: 'checkServerIdentity', | ||
|  |         transform({ name, values: [value] }) { | ||
|  |             // tlsAllowInvalidHostnames means setting the checkServerIdentity function to a noop
 | ||
|  |             return getBoolean(name, value) ? () => undefined : undefined; | ||
|  |         } | ||
|  |     }, | ||
|  |     tlsCAFile: { | ||
|  |         target: 'ca', | ||
|  |         transform({ values: [value] }) { | ||
|  |             return fs.readFileSync(String(value), { encoding: 'ascii' }); | ||
|  |         } | ||
|  |     }, | ||
|  |     tlsCertificateFile: { | ||
|  |         target: 'cert', | ||
|  |         transform({ values: [value] }) { | ||
|  |             return fs.readFileSync(String(value), { encoding: 'ascii' }); | ||
|  |         } | ||
|  |     }, | ||
|  |     tlsCertificateKeyFile: { | ||
|  |         target: 'key', | ||
|  |         transform({ values: [value] }) { | ||
|  |             return fs.readFileSync(String(value), { encoding: 'ascii' }); | ||
|  |         } | ||
|  |     }, | ||
|  |     tlsCertificateKeyFilePassword: { | ||
|  |         target: 'passphrase', | ||
|  |         type: 'any' | ||
|  |     }, | ||
|  |     tlsInsecure: { | ||
|  |         transform({ name, options, values: [value] }) { | ||
|  |             const tlsInsecure = getBoolean(name, value); | ||
|  |             if (tlsInsecure) { | ||
|  |                 options.checkServerIdentity = () => undefined; | ||
|  |                 options.rejectUnauthorized = false; | ||
|  |             } | ||
|  |             else { | ||
|  |                 options.checkServerIdentity = options.tlsAllowInvalidHostnames | ||
|  |                     ? () => undefined | ||
|  |                     : undefined; | ||
|  |                 options.rejectUnauthorized = options.tlsAllowInvalidCertificates ? false : true; | ||
|  |             } | ||
|  |             return tlsInsecure; | ||
|  |         } | ||
|  |     }, | ||
|  |     w: { | ||
|  |         target: 'writeConcern', | ||
|  |         transform({ values: [value], options }) { | ||
|  |             return write_concern_1.WriteConcern.fromOptions({ writeConcern: { ...options.writeConcern, w: value } }); | ||
|  |         } | ||
|  |     }, | ||
|  |     waitQueueTimeoutMS: { | ||
|  |         default: 0, | ||
|  |         type: 'uint' | ||
|  |     }, | ||
|  |     writeConcern: { | ||
|  |         target: 'writeConcern', | ||
|  |         transform({ values: [value], options }) { | ||
|  |             if ((0, utils_1.isRecord)(value) || value instanceof write_concern_1.WriteConcern) { | ||
|  |                 return write_concern_1.WriteConcern.fromOptions({ | ||
|  |                     writeConcern: { | ||
|  |                         ...options.writeConcern, | ||
|  |                         ...value | ||
|  |                     } | ||
|  |                 }); | ||
|  |             } | ||
|  |             else if (value === 'majority' || typeof value === 'number') { | ||
|  |                 return write_concern_1.WriteConcern.fromOptions({ | ||
|  |                     writeConcern: { | ||
|  |                         ...options.writeConcern, | ||
|  |                         w: value | ||
|  |                     } | ||
|  |                 }); | ||
|  |             } | ||
|  |             throw new error_1.MongoParseError(`Invalid WriteConcern cannot parse: ${JSON.stringify(value)}`); | ||
|  |         } | ||
|  |     }, | ||
|  |     wtimeout: { | ||
|  |         deprecated: 'Please use wtimeoutMS instead', | ||
|  |         target: 'writeConcern', | ||
|  |         transform({ values: [value], options }) { | ||
|  |             const wc = write_concern_1.WriteConcern.fromOptions({ | ||
|  |                 writeConcern: { | ||
|  |                     ...options.writeConcern, | ||
|  |                     wtimeout: getUIntFromOptions('wtimeout', value) | ||
|  |                 } | ||
|  |             }); | ||
|  |             if (wc) | ||
|  |                 return wc; | ||
|  |             throw new error_1.MongoParseError(`Cannot make WriteConcern from wtimeout`); | ||
|  |         } | ||
|  |     }, | ||
|  |     wtimeoutMS: { | ||
|  |         target: 'writeConcern', | ||
|  |         transform({ values: [value], options }) { | ||
|  |             const wc = write_concern_1.WriteConcern.fromOptions({ | ||
|  |                 writeConcern: { | ||
|  |                     ...options.writeConcern, | ||
|  |                     wtimeoutMS: getUIntFromOptions('wtimeoutMS', value) | ||
|  |                 } | ||
|  |             }); | ||
|  |             if (wc) | ||
|  |                 return wc; | ||
|  |             throw new error_1.MongoParseError(`Cannot make WriteConcern from wtimeout`); | ||
|  |         } | ||
|  |     }, | ||
|  |     zlibCompressionLevel: { | ||
|  |         default: 0, | ||
|  |         type: 'int' | ||
|  |     }, | ||
|  |     // Custom types for modifying core behavior
 | ||
|  |     connectionType: { type: 'any' }, | ||
|  |     srvPoller: { type: 'any' }, | ||
|  |     // Accepted NodeJS Options
 | ||
|  |     minDHSize: { type: 'any' }, | ||
|  |     pskCallback: { type: 'any' }, | ||
|  |     secureContext: { type: 'any' }, | ||
|  |     enableTrace: { type: 'any' }, | ||
|  |     requestCert: { type: 'any' }, | ||
|  |     rejectUnauthorized: { type: 'any' }, | ||
|  |     checkServerIdentity: { type: 'any' }, | ||
|  |     ALPNProtocols: { type: 'any' }, | ||
|  |     SNICallback: { type: 'any' }, | ||
|  |     session: { type: 'any' }, | ||
|  |     requestOCSP: { type: 'any' }, | ||
|  |     localAddress: { type: 'any' }, | ||
|  |     localPort: { type: 'any' }, | ||
|  |     hints: { type: 'any' }, | ||
|  |     lookup: { type: 'any' }, | ||
|  |     ca: { type: 'any' }, | ||
|  |     cert: { type: 'any' }, | ||
|  |     ciphers: { type: 'any' }, | ||
|  |     crl: { type: 'any' }, | ||
|  |     ecdhCurve: { type: 'any' }, | ||
|  |     key: { type: 'any' }, | ||
|  |     passphrase: { type: 'any' }, | ||
|  |     pfx: { type: 'any' }, | ||
|  |     secureProtocol: { type: 'any' }, | ||
|  |     index: { type: 'any' }, | ||
|  |     // Legacy Options, these are unused but left here to avoid errors with CSFLE lib
 | ||
|  |     useNewUrlParser: { type: 'boolean' }, | ||
|  |     useUnifiedTopology: { type: 'boolean' } | ||
|  | }; | ||
|  | exports.DEFAULT_OPTIONS = new CaseInsensitiveMap(Object.entries(exports.OPTIONS) | ||
|  |     .filter(([, descriptor]) => descriptor.default != null) | ||
|  |     .map(([k, d]) => [k, d.default])); | ||
|  | /** | ||
|  |  * Set of permitted feature flags | ||
|  |  * @internal | ||
|  |  */ | ||
|  | exports.FEATURE_FLAGS = new Set([ | ||
|  |     Symbol.for('@@mdb.skipPingOnConnect'), | ||
|  |     Symbol.for('@@mdb.enableMongoLogger') | ||
|  | ]); | ||
|  | //# sourceMappingURL=connection_string.js.map
 |