123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997 |
- 'use strict';
- const MongoError = require('./core/error').MongoError;
- const WriteConcern = require('./write_concern');
- var shallowClone = function(obj) {
- var copy = {};
- for (var name in obj) copy[name] = obj[name];
- return copy;
- };
- // Set simple property
- var getSingleProperty = function(obj, name, value) {
- Object.defineProperty(obj, name, {
- enumerable: true,
- get: function() {
- return value;
- }
- });
- };
- var formatSortValue = (exports.formatSortValue = function(sortDirection) {
- var value = ('' + sortDirection).toLowerCase();
- switch (value) {
- case 'ascending':
- case 'asc':
- case '1':
- return 1;
- case 'descending':
- case 'desc':
- case '-1':
- return -1;
- default:
- throw new Error(
- 'Illegal sort clause, must be of the form ' +
- "[['field1', '(ascending|descending)'], " +
- "['field2', '(ascending|descending)']]"
- );
- }
- });
- var formattedOrderClause = (exports.formattedOrderClause = function(sortValue) {
- var orderBy = new Map();
- if (sortValue == null) return null;
- if (Array.isArray(sortValue)) {
- if (sortValue.length === 0) {
- return null;
- }
- for (var i = 0; i < sortValue.length; i++) {
- if (sortValue[i].constructor === String) {
- orderBy.set(`${sortValue[i]}`, 1);
- } else {
- orderBy.set(`${sortValue[i][0]}`, formatSortValue(sortValue[i][1]));
- }
- }
- } else if (sortValue != null && typeof sortValue === 'object') {
- if (sortValue instanceof Map) {
- orderBy = sortValue;
- } else {
- var sortKeys = Object.keys(sortValue);
- for (var k of sortKeys) {
- orderBy.set(k, sortValue[k]);
- }
- }
- } else if (typeof sortValue === 'string') {
- orderBy.set(`${sortValue}`, 1);
- } else {
- throw new Error(
- 'Illegal sort clause, must be of the form ' +
- "[['field1', '(ascending|descending)'], ['field2', '(ascending|descending)']]"
- );
- }
- return orderBy;
- });
- var checkCollectionName = function checkCollectionName(collectionName) {
- if ('string' !== typeof collectionName) {
- throw new MongoError('collection name must be a String');
- }
- if (!collectionName || collectionName.indexOf('..') !== -1) {
- throw new MongoError('collection names cannot be empty');
- }
- if (
- collectionName.indexOf('$') !== -1 &&
- collectionName.match(/((^\$cmd)|(oplog\.\$main))/) == null
- ) {
- throw new MongoError("collection names must not contain '$'");
- }
- if (collectionName.match(/^\.|\.$/) != null) {
- throw new MongoError("collection names must not start or end with '.'");
- }
- // Validate that we are not passing 0x00 in the collection name
- if (collectionName.indexOf('\x00') !== -1) {
- throw new MongoError('collection names cannot contain a null character');
- }
- };
- var handleCallback = function(callback, err, value1, value2) {
- try {
- if (callback == null) return;
- if (callback) {
- return value2 ? callback(err, value1, value2) : callback(err, value1);
- }
- } catch (err) {
- process.nextTick(function() {
- throw err;
- });
- return false;
- }
- return true;
- };
- /**
- * Wrap a Mongo error document in an Error instance
- * @ignore
- * @api private
- */
- var toError = function(error) {
- if (error instanceof Error) return error;
- var msg = error.err || error.errmsg || error.errMessage || error;
- var e = MongoError.create({ message: msg, driver: true });
- // Get all object keys
- var keys = typeof error === 'object' ? Object.keys(error) : [];
- for (var i = 0; i < keys.length; i++) {
- try {
- e[keys[i]] = error[keys[i]];
- } catch (err) {
- // continue
- }
- }
- return e;
- };
- /**
- * @ignore
- */
- var normalizeHintField = function normalizeHintField(hint) {
- var finalHint = null;
- if (typeof hint === 'string') {
- finalHint = hint;
- } else if (Array.isArray(hint)) {
- finalHint = {};
- hint.forEach(function(param) {
- finalHint[param] = 1;
- });
- } else if (hint != null && typeof hint === 'object') {
- finalHint = {};
- for (var name in hint) {
- finalHint[name] = hint[name];
- }
- }
- return finalHint;
- };
- /**
- * Create index name based on field spec
- *
- * @ignore
- * @api private
- */
- var parseIndexOptions = function(fieldOrSpec) {
- var fieldHash = {};
- var indexes = [];
- var keys;
- // Get all the fields accordingly
- if ('string' === typeof fieldOrSpec) {
- // 'type'
- indexes.push(fieldOrSpec + '_' + 1);
- fieldHash[fieldOrSpec] = 1;
- } else if (Array.isArray(fieldOrSpec)) {
- fieldOrSpec.forEach(function(f) {
- if ('string' === typeof f) {
- // [{location:'2d'}, 'type']
- indexes.push(f + '_' + 1);
- fieldHash[f] = 1;
- } else if (Array.isArray(f)) {
- // [['location', '2d'],['type', 1]]
- indexes.push(f[0] + '_' + (f[1] || 1));
- fieldHash[f[0]] = f[1] || 1;
- } else if (isObject(f)) {
- // [{location:'2d'}, {type:1}]
- keys = Object.keys(f);
- keys.forEach(function(k) {
- indexes.push(k + '_' + f[k]);
- fieldHash[k] = f[k];
- });
- } else {
- // undefined (ignore)
- }
- });
- } else if (isObject(fieldOrSpec)) {
- // {location:'2d', type:1}
- keys = Object.keys(fieldOrSpec);
- keys.forEach(function(key) {
- indexes.push(key + '_' + fieldOrSpec[key]);
- fieldHash[key] = fieldOrSpec[key];
- });
- }
- return {
- name: indexes.join('_'),
- keys: keys,
- fieldHash: fieldHash
- };
- };
- var isObject = (exports.isObject = function(arg) {
- return '[object Object]' === Object.prototype.toString.call(arg);
- });
- var debugOptions = function(debugFields, options) {
- var finaloptions = {};
- debugFields.forEach(function(n) {
- finaloptions[n] = options[n];
- });
- return finaloptions;
- };
- var decorateCommand = function(command, options, exclude) {
- for (var name in options) {
- if (exclude.indexOf(name) === -1) command[name] = options[name];
- }
- return command;
- };
- var mergeOptions = function(target, source) {
- for (var name in source) {
- target[name] = source[name];
- }
- return target;
- };
- // Merge options with translation
- var translateOptions = function(target, source) {
- var translations = {
- // SSL translation options
- sslCA: 'ca',
- sslCRL: 'crl',
- sslValidate: 'rejectUnauthorized',
- sslKey: 'key',
- sslCert: 'cert',
- sslPass: 'passphrase',
- // SocketTimeout translation options
- socketTimeoutMS: 'socketTimeout',
- connectTimeoutMS: 'connectionTimeout',
- // Replicaset options
- replicaSet: 'setName',
- rs_name: 'setName',
- secondaryAcceptableLatencyMS: 'acceptableLatency',
- connectWithNoPrimary: 'secondaryOnlyConnectionAllowed',
- // Mongos options
- acceptableLatencyMS: 'localThresholdMS'
- };
- for (var name in source) {
- if (translations[name]) {
- target[translations[name]] = source[name];
- } else {
- target[name] = source[name];
- }
- }
- return target;
- };
- var filterOptions = function(options, names) {
- var filterOptions = {};
- for (var name in options) {
- if (names.indexOf(name) !== -1) filterOptions[name] = options[name];
- }
- // Filtered options
- return filterOptions;
- };
- // Write concern keys
- const WRITE_CONCERN_KEYS = ['w', 'j', 'wtimeout', 'fsync', 'writeConcern'];
- /**
- * If there is no WriteConcern related options defined on target then inherit from source.
- * Otherwise, do not inherit **any** options from source.
- * @internal
- * @param {object} target - options object conditionally receiving the writeConcern options
- * @param {object} source - options object containing the potentially inherited writeConcern options
- */
- function conditionallyMergeWriteConcern(target, source) {
- let found = false;
- for (const wcKey of WRITE_CONCERN_KEYS) {
- if (wcKey in target) {
- // Found a writeConcern option
- found = true;
- break;
- }
- }
- if (!found) {
- for (const wcKey of WRITE_CONCERN_KEYS) {
- if (source[wcKey]) {
- if (!('writeConcern' in target)) {
- target.writeConcern = {};
- }
- target.writeConcern[wcKey] = source[wcKey];
- }
- }
- }
- return target;
- }
- /**
- * Executes the given operation with provided arguments.
- *
- * This method reduces large amounts of duplication in the entire codebase by providing
- * a single point for determining whether callbacks or promises should be used. Additionally
- * it allows for a single point of entry to provide features such as implicit sessions, which
- * are required by the Driver Sessions specification in the event that a ClientSession is
- * not provided
- *
- * @param {object} topology The topology to execute this operation on
- * @param {function} operation The operation to execute
- * @param {array} args Arguments to apply the provided operation
- * @param {object} [options] Options that modify the behavior of the method
- */
- const executeLegacyOperation = (topology, operation, args, options) => {
- if (topology == null) {
- throw new TypeError('This method requires a valid topology instance');
- }
- if (!Array.isArray(args)) {
- throw new TypeError('This method requires an array of arguments to apply');
- }
- options = options || {};
- const Promise = topology.s.promiseLibrary;
- let callback = args[args.length - 1];
- // The driver sessions spec mandates that we implicitly create sessions for operations
- // that are not explicitly provided with a session.
- let session, opOptions, owner;
- if (!options.skipSessions && topology.hasSessionSupport()) {
- opOptions = args[args.length - 2];
- if (opOptions == null || opOptions.session == null) {
- owner = Symbol();
- session = topology.startSession({ owner });
- const optionsIndex = args.length - 2;
- args[optionsIndex] = Object.assign({}, args[optionsIndex], { session: session });
- } else if (opOptions.session && opOptions.session.hasEnded) {
- throw new MongoError('Use of expired sessions is not permitted');
- }
- }
- const makeExecuteCallback = (resolve, reject) =>
- function executeCallback(err, result) {
- if (session && session.owner === owner && !options.returnsCursor) {
- session.endSession(() => {
- delete opOptions.session;
- if (err) return reject(err);
- resolve(result);
- });
- } else {
- if (err) return reject(err);
- resolve(result);
- }
- };
- // Execute using callback
- if (typeof callback === 'function') {
- callback = args.pop();
- const handler = makeExecuteCallback(
- result => callback(null, result),
- err => callback(err, null)
- );
- args.push(handler);
- try {
- return operation.apply(null, args);
- } catch (e) {
- handler(e);
- throw e;
- }
- }
- // Return a Promise
- if (args[args.length - 1] != null) {
- throw new TypeError('final argument to `executeLegacyOperation` must be a callback');
- }
- return new Promise(function(resolve, reject) {
- const handler = makeExecuteCallback(resolve, reject);
- args[args.length - 1] = handler;
- try {
- return operation.apply(null, args);
- } catch (e) {
- handler(e);
- }
- });
- };
- /**
- * Applies retryWrites: true to a command if retryWrites is set on the command's database.
- *
- * @param {object} target The target command to which we will apply retryWrites.
- * @param {object} db The database from which we can inherit a retryWrites value.
- */
- function applyRetryableWrites(target, db) {
- if (db && db.s.options.retryWrites) {
- target.retryWrites = true;
- }
- return target;
- }
- /**
- * Applies a write concern to a command based on well defined inheritance rules, optionally
- * detecting support for the write concern in the first place.
- *
- * @param {Object} target the target command we will be applying the write concern to
- * @param {Object} sources sources where we can inherit default write concerns from
- * @param {Object} [options] optional settings passed into a command for write concern overrides
- * @returns {Object} the (now) decorated target
- */
- function applyWriteConcern(target, sources, options) {
- options = options || {};
- const db = sources.db;
- const coll = sources.collection;
- if (options.session && options.session.inTransaction()) {
- // writeConcern is not allowed within a multi-statement transaction
- if (target.writeConcern) {
- delete target.writeConcern;
- }
- return target;
- }
- const writeConcern = WriteConcern.fromOptions(options);
- if (writeConcern) {
- return Object.assign(target, { writeConcern });
- }
- if (coll && coll.writeConcern) {
- return Object.assign(target, { writeConcern: Object.assign({}, coll.writeConcern) });
- }
- if (db && db.writeConcern) {
- return Object.assign(target, { writeConcern: Object.assign({}, db.writeConcern) });
- }
- return target;
- }
- /**
- * Checks if a given value is a Promise
- *
- * @param {*} maybePromise
- * @return true if the provided value is a Promise
- */
- function isPromiseLike(maybePromise) {
- return maybePromise && typeof maybePromise.then === 'function';
- }
- /**
- * Applies collation to a given command.
- *
- * @param {object} [command] the command on which to apply collation
- * @param {(Cursor|Collection)} [target] target of command
- * @param {object} [options] options containing collation settings
- */
- function decorateWithCollation(command, target, options) {
- const topology = (target.s && target.s.topology) || target.topology;
- if (!topology) {
- throw new TypeError('parameter "target" is missing a topology');
- }
- const capabilities = topology.capabilities();
- if (options.collation && typeof options.collation === 'object') {
- if (capabilities && capabilities.commandsTakeCollation) {
- command.collation = options.collation;
- } else {
- throw new MongoError(`Current topology does not support collation`);
- }
- }
- }
- /**
- * Applies a read concern to a given command.
- *
- * @param {object} command the command on which to apply the read concern
- * @param {Collection} coll the parent collection of the operation calling this method
- */
- function decorateWithReadConcern(command, coll, options) {
- if (options && options.session && options.session.inTransaction()) {
- return;
- }
- let readConcern = Object.assign({}, command.readConcern || {});
- if (coll.s.readConcern) {
- Object.assign(readConcern, coll.s.readConcern);
- }
- if (Object.keys(readConcern).length > 0) {
- Object.assign(command, { readConcern: readConcern });
- }
- }
- /**
- * Applies an explain to a given command.
- * @internal
- *
- * @param {object} command - the command on which to apply the explain
- * @param {Explain} explain - the options containing the explain verbosity
- * @return the new command
- */
- function decorateWithExplain(command, explain) {
- if (command.explain) {
- return command;
- }
- return { explain: command, verbosity: explain.verbosity };
- }
- const nodejsMajorVersion = +process.version.split('.')[0].substring(1);
- const emitProcessWarning = msg =>
- nodejsMajorVersion <= 6
- ? process.emitWarning(msg, 'DeprecationWarning', MONGODB_WARNING_CODE)
- : process.emitWarning(msg, { type: 'DeprecationWarning', code: MONGODB_WARNING_CODE });
- // eslint-disable-next-line no-console
- const emitConsoleWarning = msg => console.error(msg);
- const emitDeprecationWarning = process.emitWarning ? emitProcessWarning : emitConsoleWarning;
- /**
- * Default message handler for generating deprecation warnings.
- *
- * @param {string} name function name
- * @param {string} option option name
- * @return {string} warning message
- * @ignore
- * @api private
- */
- function defaultMsgHandler(name, option) {
- return `${name} option [${option}] is deprecated and will be removed in a later version.`;
- }
- /**
- * Deprecates a given function's options.
- *
- * @param {object} config configuration for deprecation
- * @param {string} config.name function name
- * @param {Array} config.deprecatedOptions options to deprecate
- * @param {number} config.optionsIndex index of options object in function arguments array
- * @param {function} [config.msgHandler] optional custom message handler to generate warnings
- * @param {function} fn the target function of deprecation
- * @return {function} modified function that warns once per deprecated option, and executes original function
- * @ignore
- * @api private
- */
- function deprecateOptions(config, fn) {
- if (process.noDeprecation === true) {
- return fn;
- }
- const msgHandler = config.msgHandler ? config.msgHandler : defaultMsgHandler;
- const optionsWarned = new Set();
- function deprecated() {
- const options = arguments[config.optionsIndex];
- // ensure options is a valid, non-empty object, otherwise short-circuit
- if (!isObject(options) || Object.keys(options).length === 0) {
- return fn.apply(this, arguments);
- }
- config.deprecatedOptions.forEach(deprecatedOption => {
- if (
- Object.prototype.hasOwnProperty.call(options, deprecatedOption) &&
- !optionsWarned.has(deprecatedOption)
- ) {
- optionsWarned.add(deprecatedOption);
- const msg = msgHandler(config.name, deprecatedOption);
- emitDeprecationWarning(msg);
- if (this && this.getLogger) {
- const logger = this.getLogger();
- if (logger) {
- logger.warn(msg);
- }
- }
- }
- });
- return fn.apply(this, arguments);
- }
- // These lines copied from https://github.com/nodejs/node/blob/25e5ae41688676a5fd29b2e2e7602168eee4ceb5/lib/internal/util.js#L73-L80
- // The wrapper will keep the same prototype as fn to maintain prototype chain
- Object.setPrototypeOf(deprecated, fn);
- if (fn.prototype) {
- // Setting this (rather than using Object.setPrototype, as above) ensures
- // that calling the unwrapped constructor gives an instanceof the wrapped
- // constructor.
- deprecated.prototype = fn.prototype;
- }
- return deprecated;
- }
- const SUPPORTS = {};
- // Test asyncIterator support
- try {
- require('./async/async_iterator');
- SUPPORTS.ASYNC_ITERATOR = true;
- } catch (e) {
- SUPPORTS.ASYNC_ITERATOR = false;
- }
- class MongoDBNamespace {
- constructor(db, collection) {
- this.db = db;
- this.collection = collection;
- }
- toString() {
- return this.collection ? `${this.db}.${this.collection}` : this.db;
- }
- withCollection(collection) {
- return new MongoDBNamespace(this.db, collection);
- }
- static fromString(namespace) {
- if (!namespace) {
- throw new Error(`Cannot parse namespace from "${namespace}"`);
- }
- const index = namespace.indexOf('.');
- return new MongoDBNamespace(namespace.substring(0, index), namespace.substring(index + 1));
- }
- }
- function* makeCounter(seed) {
- let count = seed || 0;
- while (true) {
- const newCount = count;
- count += 1;
- yield newCount;
- }
- }
- /**
- * Helper function for either accepting a callback, or returning a promise
- *
- * @param {Object} parent an instance of parent with promiseLibrary.
- * @param {object} parent.s an object containing promiseLibrary.
- * @param {function} parent.s.promiseLibrary an object containing promiseLibrary.
- * @param {[Function]} callback an optional callback.
- * @param {Function} fn A function that takes a callback
- * @returns {Promise|void} Returns nothing if a callback is supplied, else returns a Promise.
- */
- function maybePromise(parent, callback, fn) {
- const PromiseLibrary = (parent && parent.s && parent.s.promiseLibrary) || Promise;
- let result;
- if (typeof callback !== 'function') {
- result = new PromiseLibrary((resolve, reject) => {
- callback = (err, res) => {
- if (err) return reject(err);
- resolve(res);
- };
- });
- }
- fn(function(err, res) {
- if (err != null) {
- try {
- callback(err);
- } catch (error) {
- return process.nextTick(() => {
- throw error;
- });
- }
- return;
- }
- callback(err, res);
- });
- return result;
- }
- function now() {
- const hrtime = process.hrtime();
- return Math.floor(hrtime[0] * 1000 + hrtime[1] / 1000000);
- }
- function calculateDurationInMs(started) {
- if (typeof started !== 'number') {
- throw TypeError('numeric value required to calculate duration');
- }
- const elapsed = now() - started;
- return elapsed < 0 ? 0 : elapsed;
- }
- /**
- * Creates an interval timer which is able to be woken up sooner than
- * the interval. The timer will also debounce multiple calls to wake
- * ensuring that the function is only ever called once within a minimum
- * interval window.
- *
- * @param {function} fn An async function to run on an interval, must accept a `callback` as its only parameter
- * @param {object} [options] Optional settings
- * @param {number} [options.interval] The interval at which to run the provided function
- * @param {number} [options.minInterval] The minimum time which must pass between invocations of the provided function
- * @param {boolean} [options.immediate] Execute the function immediately when the interval is started
- */
- function makeInterruptableAsyncInterval(fn, options) {
- let timerId;
- let lastCallTime;
- let lastWakeTime;
- let stopped = false;
- options = options || {};
- const interval = options.interval || 1000;
- const minInterval = options.minInterval || 500;
- const immediate = typeof options.immediate === 'boolean' ? options.immediate : false;
- const clock = typeof options.clock === 'function' ? options.clock : now;
- function wake() {
- const currentTime = clock();
- const timeSinceLastWake = currentTime - lastWakeTime;
- const timeSinceLastCall = currentTime - lastCallTime;
- const timeUntilNextCall = interval - timeSinceLastCall;
- lastWakeTime = currentTime;
- // For the streaming protocol: there is nothing obviously stopping this
- // interval from being woken up again while we are waiting "infinitely"
- // for `fn` to be called again`. Since the function effectively
- // never completes, the `timeUntilNextCall` will continue to grow
- // negatively unbounded, so it will never trigger a reschedule here.
- // debounce multiple calls to wake within the `minInterval`
- if (timeSinceLastWake < minInterval) {
- return;
- }
- // reschedule a call as soon as possible, ensuring the call never happens
- // faster than the `minInterval`
- if (timeUntilNextCall > minInterval) {
- reschedule(minInterval);
- }
- // This is possible in virtualized environments like AWS Lambda where our
- // clock is unreliable. In these cases the timer is "running" but never
- // actually completes, so we want to execute immediately and then attempt
- // to reschedule.
- if (timeUntilNextCall < 0) {
- executeAndReschedule();
- }
- }
- function stop() {
- stopped = true;
- if (timerId) {
- clearTimeout(timerId);
- timerId = null;
- }
- lastCallTime = 0;
- lastWakeTime = 0;
- }
- function reschedule(ms) {
- if (stopped) return;
- clearTimeout(timerId);
- timerId = setTimeout(executeAndReschedule, ms || interval);
- }
- function executeAndReschedule() {
- lastWakeTime = 0;
- lastCallTime = clock();
- fn(err => {
- if (err) throw err;
- reschedule(interval);
- });
- }
- if (immediate) {
- executeAndReschedule();
- } else {
- lastCallTime = clock();
- reschedule();
- }
- return { wake, stop };
- }
- function hasAtomicOperators(doc) {
- if (Array.isArray(doc)) {
- return doc.reduce((err, u) => err || hasAtomicOperators(u), null);
- }
- return (
- Object.keys(typeof doc.toBSON !== 'function' ? doc : doc.toBSON())
- .map(k => k[0])
- .indexOf('$') >= 0
- );
- }
- /**
- * When the driver used emitWarning the code will be equal to this.
- * @public
- *
- * @example
- * ```js
- * process.on('warning', (warning) => {
- * if (warning.code === MONGODB_WARNING_CODE) console.error('Ah an important warning! :)')
- * })
- * ```
- */
- const MONGODB_WARNING_CODE = 'MONGODB DRIVER';
- /**
- * @internal
- * @param {string} message - message to warn about
- */
- function emitWarning(message) {
- if (process.emitWarning) {
- return nodejsMajorVersion <= 6
- ? process.emitWarning(message, undefined, MONGODB_WARNING_CODE)
- : process.emitWarning(message, { code: MONGODB_WARNING_CODE });
- } else {
- // Approximate the style of print out on node versions pre 8.x
- // eslint-disable-next-line no-console
- return console.error(`[${MONGODB_WARNING_CODE}] Warning:`, message);
- }
- }
- const emittedWarnings = new Set();
- /**
- * Will emit a warning once for the duration of the application.
- * Uses the message to identify if it has already been emitted
- * so using string interpolation can cause multiple emits
- * @internal
- * @param {string} message - message to warn about
- */
- function emitWarningOnce(message) {
- if (!emittedWarnings.has(message)) {
- emittedWarnings.add(message);
- return emitWarning(message);
- }
- }
- function isSuperset(set, subset) {
- set = Array.isArray(set) ? new Set(set) : set;
- subset = Array.isArray(subset) ? new Set(subset) : subset;
- for (const elem of subset) {
- if (!set.has(elem)) {
- return false;
- }
- }
- return true;
- }
- function isRecord(value, requiredKeys) {
- const toString = Object.prototype.toString;
- const hasOwnProperty = Object.prototype.hasOwnProperty;
- const isObject = v => toString.call(v) === '[object Object]';
- if (!isObject(value)) {
- return false;
- }
- const ctor = value.constructor;
- if (ctor && ctor.prototype) {
- if (!isObject(ctor.prototype)) {
- return false;
- }
- // Check to see if some method exists from the Object exists
- if (!hasOwnProperty.call(ctor.prototype, 'isPrototypeOf')) {
- return false;
- }
- }
- if (requiredKeys) {
- const keys = Object.keys(value);
- return isSuperset(keys, requiredKeys);
- }
- return true;
- }
- /**
- * Make a deep copy of an object
- *
- * NOTE: This is not meant to be the perfect implementation of a deep copy,
- * but instead something that is good enough for the purposes of
- * command monitoring.
- */
- function deepCopy(value) {
- if (value == null) {
- return value;
- } else if (Array.isArray(value)) {
- return value.map(item => deepCopy(item));
- } else if (isRecord(value)) {
- const res = {};
- for (const key in value) {
- res[key] = deepCopy(value[key]);
- }
- return res;
- }
- const ctor = value.constructor;
- if (ctor) {
- switch (ctor.name.toLowerCase()) {
- case 'date':
- return new ctor(Number(value));
- case 'map':
- return new Map(value);
- case 'set':
- return new Set(value);
- case 'buffer':
- return Buffer.from(value);
- }
- }
- return value;
- }
- /**
- * @param {{version: string}} pkg
- * @returns {{ major: number; minor: number; patch: number }}
- */
- function parsePackageVersion(pkg) {
- const versionParts = pkg.version.split('.').map(n => Number.parseInt(n, 10));
- return { major: versionParts[0], minor: versionParts[1], patch: versionParts[2] };
- }
- module.exports = {
- filterOptions,
- mergeOptions,
- translateOptions,
- shallowClone,
- getSingleProperty,
- checkCollectionName,
- toError,
- formattedOrderClause,
- parseIndexOptions,
- normalizeHintField,
- handleCallback,
- decorateCommand,
- isObject,
- debugOptions,
- MAX_JS_INT: Number.MAX_SAFE_INTEGER + 1,
- conditionallyMergeWriteConcern,
- executeLegacyOperation,
- applyRetryableWrites,
- applyWriteConcern,
- isPromiseLike,
- decorateWithCollation,
- decorateWithReadConcern,
- decorateWithExplain,
- deprecateOptions,
- SUPPORTS,
- MongoDBNamespace,
- emitDeprecationWarning,
- makeCounter,
- maybePromise,
- now,
- calculateDurationInMs,
- makeInterruptableAsyncInterval,
- hasAtomicOperators,
- MONGODB_WARNING_CODE,
- emitWarning,
- emitWarningOnce,
- deepCopy,
- parsePackageVersion
- };
|