"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FEATURE_FLAGS = exports.DEFAULT_OPTIONS = exports.OPTIONS = exports.parseOptions = exports.checkTLSOptions = 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 logger_1 = require("./logger");
const mongo_client_1 = require("./mongo_client");
const promise_provider_1 = require("./promise_provider");
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
*/
function resolveSRVRecord(options, callback) {
if (typeof options.srvHost !== 'string') {
return callback(new error_1.MongoAPIError('Option "srvHost" must not be empty'));
}
if (options.srvHost.split('.').length < 3) {
// TODO(NODE-3484): Replace with MongoConnectionStringError
return callback(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;
dns.resolveSrv(`_${options.srvServiceName}._tcp.${lookupAddress}`, (err, addresses) => {
if (err)
return callback(err);
if (addresses.length === 0) {
return callback(new error_1.MongoAPIError('No addresses found at host'));
}
for (const { name } of addresses) {
if (!matchesParentDomain(name, lookupAddress)) {
return callback(new error_1.MongoAPIError('Server record does not share hostname with parent URI'));
}
}
const hostAddresses = addresses.map(r => { var _a; return utils_1.HostAddress.fromString(`${r.name}:${(_a = r.port) !== null && _a !== void 0 ? _a : 27017}`); });
const lbError = validateLoadBalancedOptions(hostAddresses, options, true);
if (lbError) {
return callback(lbError);
}
// Resolve TXT record and add options from there if they exist.
dns.resolveTxt(lookupAddress, (err, record) => {
var _a, _b, _c;
if (err) {
if (err.code !== 'ENODATA' && err.code !== 'ENOTFOUND') {
return callback(err);
}
}
else {
if (record.length > 1) {
return callback(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))) {
return callback(new error_1.MongoParseError(`Text record may only set any of: ${VALID_TXT_RECORDS.join(', ')}`));
}
if (VALID_TXT_RECORDS.some(option => txtRecordOptions.get(option) === '')) {
return callback(new error_1.MongoParseError('Cannot have empty URI params in DNS TXT Record'));
}
const source = (_a = txtRecordOptions.get('authSource')) !== null && _a !== void 0 ? _a : undefined;
const replicaSet = (_b = txtRecordOptions.get('replicaSet')) !== null && _b !== void 0 ? _b : undefined;
const loadBalanced = (_c = txtRecordOptions.get('loadBalanced')) !== null && _c !== void 0 ? _c : 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) {
return callback(new error_1.MongoParseError('Cannot combine replicaSet option with srvMaxHosts'));
}
const lbError = validateLoadBalancedOptions(hostAddresses, options, true);
if (lbError) {
return callback(lbError);
}
}
callback(undefined, hostAddresses);
});
});
}
exports.resolveSRVRecord = resolveSRVRecord;
/**
* Checks if TLS options are valid
*
* @param options - The options used for options parsing
* @throws MongoParseError if TLS options are invalid
*/
function checkTLSOptions(options) {
if (!options)
return;
const check = (a, b) => {
if (Reflect.has(options, a) && Reflect.has(options, b)) {
throw new error_1.MongoParseError(`The '${a}' option cannot be used with '${b}'`);
}
};
check('tlsInsecure', 'tlsAllowInvalidCertificates');
check('tlsInsecure', 'tlsAllowInvalidHostnames');
check('tlsInsecure', 'tlsDisableCertificateRevocationCheck');
check('tlsInsecure', 'tlsDisableOCSPEndpointCheck');
check('tlsAllowInvalidCertificates', 'tlsDisableCertificateRevocationCheck');
check('tlsAllowInvalidCertificates', 'tlsDisableOCSPEndpointCheck');
check('tlsDisableCertificateRevocationCheck', 'tlsDisableOCSPEndpointCheck');
}
exports.checkTLSOptions = checkTLSOptions;
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 getInt(name, value) {
if (typeof value === 'number')
return Math.trunc(value);
const parsedValue = Number.parseInt(String(value), 10);
if (!Number.isNaN(parsedValue))
return parsedValue;
throw new error_1.MongoParseError(`Expected ${name} to be stringified int value, got: ${value}`);
}
function getUint(name, value) {
const parsedValue = getInt(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.');
}
}
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';
}
checkTLSOptions(mongoOptions);
if (options.promiseLibrary) {
promise_provider_1.PromiseProvider.set(options.promiseLibrary);
}
const lbError = validateLoadBalancedOptions(hosts, mongoOptions, isSRV);
if (lbError) {
throw lbError;
}
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 => { var _a; return (_a = urlOptions.get(key)) !== null && _a !== void 0 ? _a : []; });
if (proxyOptions.some(options => options.length > 1)) {
throw new error_1.MongoParseError('Proxy options cannot be specified multiple times in the connection string');
}
return mongoOptions;
}
exports.parseOptions = parseOptions;
function validateLoadBalancedOptions(hosts, mongoOptions, isSrv) {
if (mongoOptions.loadBalanced) {
if (hosts.length > 1) {
return new error_1.MongoParseError(LB_SINGLE_HOST_ERROR);
}
if (mongoOptions.replicaSet) {
return new error_1.MongoParseError(LB_REPLICA_SET_ERROR);
}
if (mongoOptions.directConnection) {
return new error_1.MongoParseError(LB_DIRECT_CONNECTION_ERROR);
}
if (isSrv && mongoOptions.srvMaxHosts > 0) {
return 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 !== null && target !== void 0 ? 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] = getInt(name, values[0]);
break;
case 'uint':
mongoOptions[name] = getUint(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] }) {
var _a, _b;
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 = (_a = options.credentials) === null || _a === void 0 ? void 0 : _a.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 = (_b = options.credentials) === null || _b === void 0 ? void 0 : _b.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] }) {
var _a, _b;
if (!(0, utils_1.isRecord)(value))
throw new error_1.MongoParseError('DriverInfo must be an object');
return (0, utils_1.makeClientMetadata)({
driverInfo: value,
appName: (_b = (_a = options.metadata) === null || _a === void 0 ? void 0 : _a.application) === null || _b === void 0 ? void 0 : _b.name
});
}
},
enableUtf8Validation: { type: 'boolean', default: true },
family: {
transform({ name, values: [value] }) {
const transformValue = getInt(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'
},
logger: {
default: new logger_1.Logger('MongoClient'),
transform({ values: [value] }) {
if (value instanceof logger_1.Logger) {
return value;
}
(0, utils_1.emitWarning)('Alternative loggers might not be supported');
// TODO: make Logger an interface that others can implement, make usage consistent in driver
// DRIVERS-1204
return;
}
},
loggerLevel: {
target: 'logger',
transform({ values: [value] }) {
return new logger_1.Logger('MongoClient', { loggerLevel: value });
}
},
maxConnecting: {
default: 2,
transform({ name, values: [value] }) {
const maxConnecting = getUint(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 = getUint(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}`);
}
},
promiseLibrary: {
deprecated: true,
type: 'any'
},
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 }) {
var _a, _b, _c;
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: (_a = options.readPreference) === null || _a === void 0 ? void 0 : _a.hedge,
maxStalenessSeconds: (_b = options.readPreference) === null || _b === void 0 ? void 0 : _b.maxStalenessSeconds
};
return new read_preference_1.ReadPreference(value, (_c = options.readPreference) === null || _c === void 0 ? void 0 : _c.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: getUint('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: getUint('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')]);
//# sourceMappingURL=connection_string.js.map