123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188 |
- "use strict";
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.parsePackageVersion = exports.supportsRetryableWrites = exports.enumToString = exports.emitWarningOnce = exports.emitWarning = exports.MONGODB_WARNING_CODE = exports.DEFAULT_PK_FACTORY = exports.HostAddress = exports.BufferPool = exports.deepCopy = exports.isRecord = exports.setDifference = exports.isHello = exports.isSuperset = exports.resolveOptions = exports.hasAtomicOperators = exports.makeInterruptibleAsyncInterval = exports.calculateDurationInMs = exports.now = exports.makeClientMetadata = exports.makeStateMachine = exports.errorStrictEqual = exports.arrayStrictEqual = exports.eachAsyncSeries = exports.eachAsync = exports.collationNotSupported = exports.maxWireVersion = exports.uuidV4 = exports.databaseNamespace = exports.maybePromise = exports.makeCounter = exports.MongoDBNamespace = exports.ns = exports.deprecateOptions = exports.defaultMsgHandler = exports.getTopology = exports.decorateWithExplain = exports.decorateWithReadConcern = exports.decorateWithCollation = exports.isPromiseLike = exports.applyWriteConcern = exports.applyRetryableWrites = exports.executeLegacyOperation = exports.filterOptions = exports.mergeOptions = exports.isObject = exports.parseIndexOptions = exports.normalizeHintField = exports.checkCollectionName = exports.MAX_JS_INT = void 0;
- exports.commandSupportsReadConcern = exports.shuffle = void 0;
- const crypto = require("crypto");
- const os = require("os");
- const url_1 = require("url");
- const bson_1 = require("./bson");
- const constants_1 = require("./cmap/wire_protocol/constants");
- const constants_2 = require("./constants");
- const error_1 = require("./error");
- const promise_provider_1 = require("./promise_provider");
- const read_concern_1 = require("./read_concern");
- const read_preference_1 = require("./read_preference");
- const common_1 = require("./sdam/common");
- const write_concern_1 = require("./write_concern");
- exports.MAX_JS_INT = Number.MAX_SAFE_INTEGER + 1;
- /**
- * Throws if collectionName is not a valid mongodb collection namespace.
- * @internal
- */
- function checkCollectionName(collectionName) {
- if ('string' !== typeof collectionName) {
- throw new error_1.MongoInvalidArgumentError('Collection name must be a String');
- }
- if (!collectionName || collectionName.indexOf('..') !== -1) {
- throw new error_1.MongoInvalidArgumentError('Collection names cannot be empty');
- }
- if (collectionName.indexOf('$') !== -1 &&
- collectionName.match(/((^\$cmd)|(oplog\.\$main))/) == null) {
- // TODO(NODE-3483): Use MongoNamespace static method
- throw new error_1.MongoInvalidArgumentError("Collection names must not contain '$'");
- }
- if (collectionName.match(/^\.|\.$/) != null) {
- // TODO(NODE-3483): Use MongoNamespace static method
- throw new error_1.MongoInvalidArgumentError("Collection names must not start or end with '.'");
- }
- // Validate that we are not passing 0x00 in the collection name
- if (collectionName.indexOf('\x00') !== -1) {
- // TODO(NODE-3483): Use MongoNamespace static method
- throw new error_1.MongoInvalidArgumentError('Collection names cannot contain a null character');
- }
- }
- exports.checkCollectionName = checkCollectionName;
- /**
- * Ensure Hint field is in a shape we expect:
- * - object of index names mapping to 1 or -1
- * - just an index name
- * @internal
- */
- function normalizeHintField(hint) {
- let finalHint = undefined;
- if (typeof hint === 'string') {
- finalHint = hint;
- }
- else if (Array.isArray(hint)) {
- finalHint = {};
- hint.forEach(param => {
- finalHint[param] = 1;
- });
- }
- else if (hint != null && typeof hint === 'object') {
- finalHint = {};
- for (const name in hint) {
- finalHint[name] = hint[name];
- }
- }
- return finalHint;
- }
- exports.normalizeHintField = normalizeHintField;
- /**
- * Create an index specifier based on
- * @internal
- */
- function parseIndexOptions(indexSpec) {
- const fieldHash = {};
- const indexes = [];
- let keys;
- // Get all the fields accordingly
- if ('string' === typeof indexSpec) {
- // 'type'
- indexes.push(indexSpec + '_' + 1);
- fieldHash[indexSpec] = 1;
- }
- else if (Array.isArray(indexSpec)) {
- indexSpec.forEach((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(k => {
- indexes.push(k + '_' + f[k]);
- fieldHash[k] = f[k];
- });
- }
- else {
- // undefined (ignore)
- }
- });
- }
- else if (isObject(indexSpec)) {
- // {location:'2d', type:1}
- keys = Object.keys(indexSpec);
- Object.entries(indexSpec).forEach(([key, value]) => {
- indexes.push(key + '_' + value);
- fieldHash[key] = value;
- });
- }
- return {
- name: indexes.join('_'),
- keys: keys,
- fieldHash: fieldHash
- };
- }
- exports.parseIndexOptions = parseIndexOptions;
- /**
- * Checks if arg is an Object:
- * - **NOTE**: the check is based on the `[Symbol.toStringTag]() === 'Object'`
- * @internal
- */
- // eslint-disable-next-line @typescript-eslint/ban-types
- function isObject(arg) {
- return '[object Object]' === Object.prototype.toString.call(arg);
- }
- exports.isObject = isObject;
- /** @internal */
- function mergeOptions(target, source) {
- return { ...target, ...source };
- }
- exports.mergeOptions = mergeOptions;
- /** @internal */
- function filterOptions(options, names) {
- const filterOptions = {};
- for (const name in options) {
- if (names.includes(name)) {
- filterOptions[name] = options[name];
- }
- }
- // Filtered options
- return filterOptions;
- }
- exports.filterOptions = filterOptions;
- /**
- * Executes the given operation with provided arguments.
- *
- * @remarks
- * 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
- *
- * @internal
- *
- * @param topology - The topology to execute this operation on
- * @param operation - The operation to execute
- * @param args - Arguments to apply the provided operation
- * @param options - Options that modify the behavior of the method
- */
- function executeLegacyOperation(topology, operation, args, options) {
- const Promise = promise_provider_1.PromiseProvider.get();
- if (!Array.isArray(args)) {
- // TODO(NODE-3483)
- throw new error_1.MongoRuntimeError('This method requires an array of arguments to apply');
- }
- options = options !== null && options !== void 0 ? options : {};
- 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;
- let opOptions;
- let 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 error_1.MongoExpiredSessionError();
- }
- }
- function makeExecuteCallback(resolve, reject) {
- return function (err, result) {
- if (session && session.owner === owner && !(options === null || options === void 0 ? void 0 : 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(undefined, result), err => callback(err, null));
- args.push(handler);
- try {
- return operation(...args);
- }
- catch (e) {
- handler(e);
- throw e;
- }
- }
- // Return a Promise
- if (args[args.length - 1] != null) {
- // TODO(NODE-3483)
- throw new error_1.MongoRuntimeError('Final argument to `executeLegacyOperation` must be a callback');
- }
- return new Promise((resolve, reject) => {
- const handler = makeExecuteCallback(resolve, reject);
- args[args.length - 1] = handler;
- try {
- return operation(...args);
- }
- catch (e) {
- handler(e);
- }
- });
- }
- exports.executeLegacyOperation = executeLegacyOperation;
- /**
- * Applies retryWrites: true to a command if retryWrites is set on the command's database.
- * @internal
- *
- * @param target - The target command to which we will apply retryWrites.
- * @param db - The database from which we can inherit a retryWrites value.
- */
- function applyRetryableWrites(target, db) {
- var _a;
- if (db && ((_a = db.s.options) === null || _a === void 0 ? void 0 : _a.retryWrites)) {
- target.retryWrites = true;
- }
- return target;
- }
- exports.applyRetryableWrites = applyRetryableWrites;
- /**
- * Applies a write concern to a command based on well defined inheritance rules, optionally
- * detecting support for the write concern in the first place.
- * @internal
- *
- * @param target - the target command we will be applying the write concern to
- * @param sources - sources where we can inherit default write concerns from
- * @param options - optional settings passed into a command for write concern overrides
- */
- function applyWriteConcern(target, sources, options) {
- options = options !== null && options !== void 0 ? 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 = write_concern_1.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;
- }
- exports.applyWriteConcern = applyWriteConcern;
- /**
- * Checks if a given value is a Promise
- *
- * @typeParam T - The result type of maybePromise
- * @param maybePromise - An object that could be a promise
- * @returns true if the provided value is a Promise
- */
- function isPromiseLike(maybePromise) {
- return !!maybePromise && typeof maybePromise.then === 'function';
- }
- exports.isPromiseLike = isPromiseLike;
- /**
- * Applies collation to a given command.
- * @internal
- *
- * @param command - the command on which to apply collation
- * @param target - target of command
- * @param options - options containing collation settings
- */
- function decorateWithCollation(command, target, options) {
- const capabilities = getTopology(target).capabilities;
- if (options.collation && typeof options.collation === 'object') {
- if (capabilities && capabilities.commandsTakeCollation) {
- command.collation = options.collation;
- }
- else {
- throw new error_1.MongoCompatibilityError(`Current topology does not support collation`);
- }
- }
- }
- exports.decorateWithCollation = decorateWithCollation;
- /**
- * Applies a read concern to a given command.
- * @internal
- *
- * @param command - the command on which to apply the read concern
- * @param coll - the parent collection of the operation calling this method
- */
- function decorateWithReadConcern(command, coll, options) {
- if (options && options.session && options.session.inTransaction()) {
- return;
- }
- const 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 });
- }
- }
- exports.decorateWithReadConcern = decorateWithReadConcern;
- /**
- * Applies an explain to a given command.
- * @internal
- *
- * @param command - the command on which to apply the explain
- * @param options - the options containing the explain verbosity
- */
- function decorateWithExplain(command, explain) {
- if (command.explain) {
- return command;
- }
- return { explain: command, verbosity: explain.verbosity };
- }
- exports.decorateWithExplain = decorateWithExplain;
- /**
- * A helper function to get the topology from a given provider. Throws
- * if the topology cannot be found.
- * @internal
- */
- function getTopology(provider) {
- if (`topology` in provider && provider.topology) {
- return provider.topology;
- }
- else if ('client' in provider.s && provider.s.client.topology) {
- return provider.s.client.topology;
- }
- else if ('db' in provider.s && provider.s.db.s.client.topology) {
- return provider.s.db.s.client.topology;
- }
- throw new error_1.MongoNotConnectedError('MongoClient must be connected to perform this operation');
- }
- exports.getTopology = getTopology;
- /**
- * Default message handler for generating deprecation warnings.
- * @internal
- *
- * @param name - function name
- * @param option - option name
- * @returns warning message
- */
- function defaultMsgHandler(name, option) {
- return `${name} option [${option}] is deprecated and will be removed in a later version.`;
- }
- exports.defaultMsgHandler = defaultMsgHandler;
- /**
- * Deprecates a given function's options.
- * @internal
- *
- * @param this - the bound class if this is a method
- * @param config - configuration for deprecation
- * @param fn - the target function of deprecation
- * @returns modified function that warns once per deprecated option, and executes original function
- */
- function deprecateOptions(config, fn) {
- if (process.noDeprecation === true) {
- return fn;
- }
- const msgHandler = config.msgHandler ? config.msgHandler : defaultMsgHandler;
- const optionsWarned = new Set();
- function deprecated(...args) {
- const options = args[config.optionsIndex];
- // ensure options is a valid, non-empty object, otherwise short-circuit
- if (!isObject(options) || Object.keys(options).length === 0) {
- return fn.bind(this)(...args); // call the function, no change
- }
- // interrupt the function call with a warning
- for (const deprecatedOption of config.deprecatedOptions) {
- if (deprecatedOption in options && !optionsWarned.has(deprecatedOption)) {
- optionsWarned.add(deprecatedOption);
- const msg = msgHandler(config.name, deprecatedOption);
- emitWarning(msg);
- if (this && 'getLogger' in this) {
- const logger = this.getLogger();
- if (logger) {
- logger.warn(msg);
- }
- }
- }
- }
- return fn.bind(this)(...args);
- }
- // 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;
- }
- exports.deprecateOptions = deprecateOptions;
- /** @internal */
- function ns(ns) {
- return MongoDBNamespace.fromString(ns);
- }
- exports.ns = ns;
- /** @public */
- class MongoDBNamespace {
- /**
- * Create a namespace object
- *
- * @param db - database name
- * @param collection - collection name
- */
- 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) {
- // TODO(NODE-3483): Replace with MongoNamespaceError
- throw new error_1.MongoRuntimeError(`Cannot parse namespace from "${namespace}"`);
- }
- const [db, ...collection] = namespace.split('.');
- return new MongoDBNamespace(db, collection.join('.'));
- }
- }
- exports.MongoDBNamespace = MongoDBNamespace;
- /** @internal */
- function* makeCounter(seed = 0) {
- let count = seed;
- while (true) {
- const newCount = count;
- count += 1;
- yield newCount;
- }
- }
- exports.makeCounter = makeCounter;
- /**
- * Helper function for either accepting a callback, or returning a promise
- * @internal
- *
- * @param callback - The last function argument in exposed method, controls if a Promise is returned
- * @param wrapper - A function that wraps the callback
- * @returns Returns void if a callback is supplied, else returns a Promise.
- */
- function maybePromise(callback, wrapper) {
- const Promise = promise_provider_1.PromiseProvider.get();
- let result;
- if (typeof callback !== 'function') {
- result = new Promise((resolve, reject) => {
- callback = (err, res) => {
- if (err)
- return reject(err);
- resolve(res);
- };
- });
- }
- wrapper((err, res) => {
- if (err != null) {
- try {
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- callback(err);
- }
- catch (error) {
- process.nextTick(() => {
- throw error;
- });
- }
- return;
- }
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- callback(err, res);
- });
- return result;
- }
- exports.maybePromise = maybePromise;
- /** @internal */
- function databaseNamespace(ns) {
- return ns.split('.')[0];
- }
- exports.databaseNamespace = databaseNamespace;
- /**
- * Synchronously Generate a UUIDv4
- * @internal
- */
- function uuidV4() {
- const result = crypto.randomBytes(16);
- result[6] = (result[6] & 0x0f) | 0x40;
- result[8] = (result[8] & 0x3f) | 0x80;
- return result;
- }
- exports.uuidV4 = uuidV4;
- /**
- * A helper function for determining `maxWireVersion` between legacy and new topology instances
- * @internal
- */
- function maxWireVersion(topologyOrServer) {
- if (topologyOrServer) {
- if (topologyOrServer.loadBalanced) {
- // Since we do not have a monitor, we assume the load balanced server is always
- // pointed at the latest mongodb version. There is a risk that for on-prem
- // deployments that don't upgrade immediately that this could alert to the
- // application that a feature is avaiable that is actually not.
- return constants_1.MAX_SUPPORTED_WIRE_VERSION;
- }
- if (topologyOrServer.hello) {
- return topologyOrServer.hello.maxWireVersion;
- }
- if ('lastHello' in topologyOrServer && typeof topologyOrServer.lastHello === 'function') {
- const lastHello = topologyOrServer.lastHello();
- if (lastHello) {
- return lastHello.maxWireVersion;
- }
- }
- if (topologyOrServer.description &&
- 'maxWireVersion' in topologyOrServer.description &&
- topologyOrServer.description.maxWireVersion != null) {
- return topologyOrServer.description.maxWireVersion;
- }
- }
- return 0;
- }
- exports.maxWireVersion = maxWireVersion;
- /**
- * Checks that collation is supported by server.
- * @internal
- *
- * @param server - to check against
- * @param cmd - object where collation may be specified
- */
- function collationNotSupported(server, cmd) {
- return cmd && cmd.collation && maxWireVersion(server) < 5;
- }
- exports.collationNotSupported = collationNotSupported;
- /**
- * Applies the function `eachFn` to each item in `arr`, in parallel.
- * @internal
- *
- * @param arr - An array of items to asynchronously iterate over
- * @param eachFn - A function to call on each item of the array. The callback signature is `(item, callback)`, where the callback indicates iteration is complete.
- * @param callback - The callback called after every item has been iterated
- */
- function eachAsync(arr, eachFn, callback) {
- arr = arr || [];
- let idx = 0;
- let awaiting = 0;
- for (idx = 0; idx < arr.length; ++idx) {
- awaiting++;
- eachFn(arr[idx], eachCallback);
- }
- if (awaiting === 0) {
- callback();
- return;
- }
- function eachCallback(err) {
- awaiting--;
- if (err) {
- callback(err);
- return;
- }
- if (idx === arr.length && awaiting <= 0) {
- callback();
- }
- }
- }
- exports.eachAsync = eachAsync;
- /** @internal */
- function eachAsyncSeries(arr, eachFn, callback) {
- arr = arr || [];
- let idx = 0;
- let awaiting = arr.length;
- if (awaiting === 0) {
- callback();
- return;
- }
- function eachCallback(err) {
- idx++;
- awaiting--;
- if (err) {
- callback(err);
- return;
- }
- if (idx === arr.length && awaiting <= 0) {
- callback();
- return;
- }
- eachFn(arr[idx], eachCallback);
- }
- eachFn(arr[idx], eachCallback);
- }
- exports.eachAsyncSeries = eachAsyncSeries;
- /** @internal */
- function arrayStrictEqual(arr, arr2) {
- if (!Array.isArray(arr) || !Array.isArray(arr2)) {
- return false;
- }
- return arr.length === arr2.length && arr.every((elt, idx) => elt === arr2[idx]);
- }
- exports.arrayStrictEqual = arrayStrictEqual;
- /** @internal */
- function errorStrictEqual(lhs, rhs) {
- if (lhs === rhs) {
- return true;
- }
- if (!lhs || !rhs) {
- return lhs === rhs;
- }
- if ((lhs == null && rhs != null) || (lhs != null && rhs == null)) {
- return false;
- }
- if (lhs.constructor.name !== rhs.constructor.name) {
- return false;
- }
- if (lhs.message !== rhs.message) {
- return false;
- }
- return true;
- }
- exports.errorStrictEqual = errorStrictEqual;
- /** @internal */
- function makeStateMachine(stateTable) {
- return function stateTransition(target, newState) {
- const legalStates = stateTable[target.s.state];
- if (legalStates && legalStates.indexOf(newState) < 0) {
- throw new error_1.MongoRuntimeError(`illegal state transition from [${target.s.state}] => [${newState}], allowed: [${legalStates}]`);
- }
- target.emit('stateChanged', target.s.state, newState);
- target.s.state = newState;
- };
- }
- exports.makeStateMachine = makeStateMachine;
- // eslint-disable-next-line @typescript-eslint/no-var-requires
- const NODE_DRIVER_VERSION = require('../package.json').version;
- function makeClientMetadata(options) {
- options = options !== null && options !== void 0 ? options : {};
- const metadata = {
- driver: {
- name: 'nodejs',
- version: NODE_DRIVER_VERSION
- },
- os: {
- type: os.type(),
- name: process.platform,
- architecture: process.arch,
- version: os.release()
- },
- platform: `Node.js ${process.version}, ${os.endianness()} (unified)`
- };
- // support optionally provided wrapping driver info
- if (options.driverInfo) {
- if (options.driverInfo.name) {
- metadata.driver.name = `${metadata.driver.name}|${options.driverInfo.name}`;
- }
- if (options.driverInfo.version) {
- metadata.version = `${metadata.driver.version}|${options.driverInfo.version}`;
- }
- if (options.driverInfo.platform) {
- metadata.platform = `${metadata.platform}|${options.driverInfo.platform}`;
- }
- }
- if (options.appName) {
- // MongoDB requires the appName not exceed a byte length of 128
- const buffer = Buffer.from(options.appName);
- metadata.application = {
- name: buffer.byteLength > 128 ? buffer.slice(0, 128).toString('utf8') : options.appName
- };
- }
- return metadata;
- }
- exports.makeClientMetadata = makeClientMetadata;
- /** @internal */
- function now() {
- const hrtime = process.hrtime();
- return Math.floor(hrtime[0] * 1000 + hrtime[1] / 1000000);
- }
- exports.now = now;
- /** @internal */
- function calculateDurationInMs(started) {
- if (typeof started !== 'number') {
- throw new error_1.MongoInvalidArgumentError('Numeric value required to calculate duration');
- }
- const elapsed = now() - started;
- return elapsed < 0 ? 0 : elapsed;
- }
- exports.calculateDurationInMs = calculateDurationInMs;
- /**
- * 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.
- * @internal
- *
- * @param fn - An async function to run on an interval, must accept a `callback` as its only parameter
- */
- function makeInterruptibleAsyncInterval(fn, options) {
- let timerId;
- let lastCallTime;
- let cannotBeExpedited = false;
- let stopped = false;
- options = options !== null && options !== void 0 ? 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 nextScheduledCallTime = lastCallTime + interval;
- const timeUntilNextCall = nextScheduledCallTime - 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.
- // 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();
- return;
- }
- // debounce multiple calls to wake within the `minInterval`
- if (cannotBeExpedited) {
- return;
- }
- // reschedule a call as soon as possible, ensuring the call never happens
- // faster than the `minInterval`
- if (timeUntilNextCall > minInterval) {
- reschedule(minInterval);
- cannotBeExpedited = true;
- }
- }
- function stop() {
- stopped = true;
- if (timerId) {
- clearTimeout(timerId);
- timerId = undefined;
- }
- lastCallTime = 0;
- cannotBeExpedited = false;
- }
- function reschedule(ms) {
- if (stopped)
- return;
- if (timerId) {
- clearTimeout(timerId);
- }
- timerId = setTimeout(executeAndReschedule, ms || interval);
- }
- function executeAndReschedule() {
- cannotBeExpedited = false;
- lastCallTime = clock();
- fn(err => {
- if (err)
- throw err;
- reschedule(interval);
- });
- }
- if (immediate) {
- executeAndReschedule();
- }
- else {
- lastCallTime = clock();
- reschedule(undefined);
- }
- return { wake, stop };
- }
- exports.makeInterruptibleAsyncInterval = makeInterruptibleAsyncInterval;
- /** @internal */
- function hasAtomicOperators(doc) {
- if (Array.isArray(doc)) {
- for (const document of doc) {
- if (hasAtomicOperators(document)) {
- return true;
- }
- }
- return false;
- }
- const keys = Object.keys(doc);
- return keys.length > 0 && keys[0][0] === '$';
- }
- exports.hasAtomicOperators = hasAtomicOperators;
- /**
- * Merge inherited properties from parent into options, prioritizing values from options,
- * then values from parent.
- * @internal
- */
- function resolveOptions(parent, options) {
- var _a, _b, _c;
- const result = Object.assign({}, options, (0, bson_1.resolveBSONOptions)(options, parent));
- // Users cannot pass a readConcern/writeConcern to operations in a transaction
- const session = options === null || options === void 0 ? void 0 : options.session;
- if (!(session === null || session === void 0 ? void 0 : session.inTransaction())) {
- const readConcern = (_a = read_concern_1.ReadConcern.fromOptions(options)) !== null && _a !== void 0 ? _a : parent === null || parent === void 0 ? void 0 : parent.readConcern;
- if (readConcern) {
- result.readConcern = readConcern;
- }
- const writeConcern = (_b = write_concern_1.WriteConcern.fromOptions(options)) !== null && _b !== void 0 ? _b : parent === null || parent === void 0 ? void 0 : parent.writeConcern;
- if (writeConcern) {
- result.writeConcern = writeConcern;
- }
- }
- const readPreference = (_c = read_preference_1.ReadPreference.fromOptions(options)) !== null && _c !== void 0 ? _c : parent === null || parent === void 0 ? void 0 : parent.readPreference;
- if (readPreference) {
- result.readPreference = readPreference;
- }
- return result;
- }
- exports.resolveOptions = resolveOptions;
- 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;
- }
- exports.isSuperset = isSuperset;
- /**
- * Checks if the document is a Hello request
- * @internal
- */
- function isHello(doc) {
- return doc[constants_2.LEGACY_HELLO_COMMAND] || doc.hello ? true : false;
- }
- exports.isHello = isHello;
- /** Returns the items that are uniquely in setA */
- function setDifference(setA, setB) {
- const difference = new Set(setA);
- for (const elem of setB) {
- difference.delete(elem);
- }
- return difference;
- }
- exports.setDifference = setDifference;
- function isRecord(value, requiredKeys = undefined) {
- 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;
- }
- exports.isRecord = isRecord;
- /**
- * 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;
- }
- exports.deepCopy = deepCopy;
- /** @internal */
- const kBuffers = Symbol('buffers');
- /** @internal */
- const kLength = Symbol('length');
- /**
- * A pool of Buffers which allow you to read them as if they were one
- * @internal
- */
- class BufferPool {
- constructor() {
- this[kBuffers] = [];
- this[kLength] = 0;
- }
- get length() {
- return this[kLength];
- }
- /** Adds a buffer to the internal buffer pool list */
- append(buffer) {
- this[kBuffers].push(buffer);
- this[kLength] += buffer.length;
- }
- /** Returns the requested number of bytes without consuming them */
- peek(size) {
- return this.read(size, false);
- }
- /** Reads the requested number of bytes, optionally consuming them */
- read(size, consume = true) {
- if (typeof size !== 'number' || size < 0) {
- throw new error_1.MongoInvalidArgumentError('Argument "size" must be a non-negative number');
- }
- if (size > this[kLength]) {
- return Buffer.alloc(0);
- }
- let result;
- // read the whole buffer
- if (size === this.length) {
- result = Buffer.concat(this[kBuffers]);
- if (consume) {
- this[kBuffers] = [];
- this[kLength] = 0;
- }
- }
- // size is within first buffer, no need to concat
- else if (size <= this[kBuffers][0].length) {
- result = this[kBuffers][0].slice(0, size);
- if (consume) {
- this[kBuffers][0] = this[kBuffers][0].slice(size);
- this[kLength] -= size;
- }
- }
- // size is beyond first buffer, need to track and copy
- else {
- result = Buffer.allocUnsafe(size);
- let idx;
- let offset = 0;
- let bytesToCopy = size;
- for (idx = 0; idx < this[kBuffers].length; ++idx) {
- let bytesCopied;
- if (bytesToCopy > this[kBuffers][idx].length) {
- bytesCopied = this[kBuffers][idx].copy(result, offset, 0);
- offset += bytesCopied;
- }
- else {
- bytesCopied = this[kBuffers][idx].copy(result, offset, 0, bytesToCopy);
- if (consume) {
- this[kBuffers][idx] = this[kBuffers][idx].slice(bytesCopied);
- }
- offset += bytesCopied;
- break;
- }
- bytesToCopy -= bytesCopied;
- }
- // compact the internal buffer array
- if (consume) {
- this[kBuffers] = this[kBuffers].slice(idx);
- this[kLength] -= size;
- }
- }
- return result;
- }
- }
- exports.BufferPool = BufferPool;
- /** @public */
- class HostAddress {
- constructor(hostString) {
- const escapedHost = hostString.split(' ').join('%20'); // escape spaces, for socket path hosts
- const { hostname, port } = new url_1.URL(`mongodb://${escapedHost}`);
- if (hostname.endsWith('.sock')) {
- // heuristically determine if we're working with a domain socket
- this.socketPath = decodeURIComponent(hostname);
- }
- else if (typeof hostname === 'string') {
- this.isIPv6 = false;
- let normalized = decodeURIComponent(hostname).toLowerCase();
- if (normalized.startsWith('[') && normalized.endsWith(']')) {
- this.isIPv6 = true;
- normalized = normalized.substring(1, hostname.length - 1);
- }
- this.host = normalized.toLowerCase();
- if (typeof port === 'number') {
- this.port = port;
- }
- else if (typeof port === 'string' && port !== '') {
- this.port = Number.parseInt(port, 10);
- }
- else {
- this.port = 27017;
- }
- if (this.port === 0) {
- throw new error_1.MongoParseError('Invalid port (zero) with hostname');
- }
- }
- else {
- throw new error_1.MongoInvalidArgumentError('Either socketPath or host must be defined.');
- }
- Object.freeze(this);
- }
- [Symbol.for('nodejs.util.inspect.custom')]() {
- return this.inspect();
- }
- inspect() {
- return `new HostAddress('${this.toString(true)}')`;
- }
- /**
- * @param ipv6Brackets - optionally request ipv6 bracket notation required for connection strings
- */
- toString(ipv6Brackets = false) {
- if (typeof this.host === 'string') {
- if (this.isIPv6 && ipv6Brackets) {
- return `[${this.host}]:${this.port}`;
- }
- return `${this.host}:${this.port}`;
- }
- return `${this.socketPath}`;
- }
- static fromString(s) {
- return new HostAddress(s);
- }
- static fromHostPort(host, port) {
- if (host.includes(':')) {
- host = `[${host}]`; // IPv6 address
- }
- return HostAddress.fromString(`${host}:${port}`);
- }
- static fromSrvRecord({ name, port }) {
- return HostAddress.fromHostPort(name, port);
- }
- }
- exports.HostAddress = HostAddress;
- exports.DEFAULT_PK_FACTORY = {
- // We prefer not to rely on ObjectId having a createPk method
- createPk() {
- return new bson_1.ObjectId();
- }
- };
- /**
- * 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! :)')
- * })
- * ```
- */
- exports.MONGODB_WARNING_CODE = 'MONGODB DRIVER';
- /** @internal */
- function emitWarning(message) {
- return process.emitWarning(message, { code: exports.MONGODB_WARNING_CODE });
- }
- exports.emitWarning = emitWarning;
- 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
- */
- function emitWarningOnce(message) {
- if (!emittedWarnings.has(message)) {
- emittedWarnings.add(message);
- return emitWarning(message);
- }
- }
- exports.emitWarningOnce = emitWarningOnce;
- /**
- * Takes a JS object and joins the values into a string separated by ', '
- */
- function enumToString(en) {
- return Object.values(en).join(', ');
- }
- exports.enumToString = enumToString;
- /**
- * Determine if a server supports retryable writes.
- *
- * @internal
- */
- function supportsRetryableWrites(server) {
- return (!!server.loadBalanced ||
- (server.description.maxWireVersion >= 6 &&
- !!server.description.logicalSessionTimeoutMinutes &&
- server.description.type !== common_1.ServerType.Standalone));
- }
- exports.supportsRetryableWrites = supportsRetryableWrites;
- function parsePackageVersion({ version }) {
- const [major, minor, patch] = version.split('.').map((n) => Number.parseInt(n, 10));
- return { major, minor, patch };
- }
- exports.parsePackageVersion = parsePackageVersion;
- /**
- * Fisher–Yates Shuffle
- *
- * Reference: https://bost.ocks.org/mike/shuffle/
- * @param sequence - items to be shuffled
- * @param limit - Defaults to `0`. If nonzero shuffle will slice the randomized array e.g, `.slice(0, limit)` otherwise will return the entire randomized array.
- */
- function shuffle(sequence, limit = 0) {
- const items = Array.from(sequence); // shallow copy in order to never shuffle the input
- if (limit > items.length) {
- throw new error_1.MongoRuntimeError('Limit must be less than the number of items');
- }
- let remainingItemsToShuffle = items.length;
- const lowerBound = limit % items.length === 0 ? 1 : items.length - limit;
- while (remainingItemsToShuffle > lowerBound) {
- // Pick a remaining element
- const randomIndex = Math.floor(Math.random() * remainingItemsToShuffle);
- remainingItemsToShuffle -= 1;
- // And swap it with the current element
- const swapHold = items[remainingItemsToShuffle];
- items[remainingItemsToShuffle] = items[randomIndex];
- items[randomIndex] = swapHold;
- }
- return limit % items.length === 0 ? items : items.slice(lowerBound);
- }
- exports.shuffle = shuffle;
- // TODO: this should be codified in command construction
- // @see https://github.com/mongodb/specifications/blob/master/source/read-write-concern/read-write-concern.rst#read-concern
- function commandSupportsReadConcern(command, options) {
- if (command.aggregate || command.count || command.distinct || command.find || command.geoNear) {
- return true;
- }
- if (command.mapReduce &&
- options &&
- options.out &&
- (options.out.inline === 1 || options.out === 'inline')) {
- return true;
- }
- return false;
- }
- exports.commandSupportsReadConcern = commandSupportsReadConcern;
- //# sourceMappingURL=utils.js.map
|