utils.js 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. 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;
  4. exports.commandSupportsReadConcern = exports.shuffle = void 0;
  5. const crypto = require("crypto");
  6. const os = require("os");
  7. const url_1 = require("url");
  8. const bson_1 = require("./bson");
  9. const constants_1 = require("./cmap/wire_protocol/constants");
  10. const constants_2 = require("./constants");
  11. const error_1 = require("./error");
  12. const promise_provider_1 = require("./promise_provider");
  13. const read_concern_1 = require("./read_concern");
  14. const read_preference_1 = require("./read_preference");
  15. const common_1 = require("./sdam/common");
  16. const write_concern_1 = require("./write_concern");
  17. exports.MAX_JS_INT = Number.MAX_SAFE_INTEGER + 1;
  18. /**
  19. * Throws if collectionName is not a valid mongodb collection namespace.
  20. * @internal
  21. */
  22. function checkCollectionName(collectionName) {
  23. if ('string' !== typeof collectionName) {
  24. throw new error_1.MongoInvalidArgumentError('Collection name must be a String');
  25. }
  26. if (!collectionName || collectionName.indexOf('..') !== -1) {
  27. throw new error_1.MongoInvalidArgumentError('Collection names cannot be empty');
  28. }
  29. if (collectionName.indexOf('$') !== -1 &&
  30. collectionName.match(/((^\$cmd)|(oplog\.\$main))/) == null) {
  31. // TODO(NODE-3483): Use MongoNamespace static method
  32. throw new error_1.MongoInvalidArgumentError("Collection names must not contain '$'");
  33. }
  34. if (collectionName.match(/^\.|\.$/) != null) {
  35. // TODO(NODE-3483): Use MongoNamespace static method
  36. throw new error_1.MongoInvalidArgumentError("Collection names must not start or end with '.'");
  37. }
  38. // Validate that we are not passing 0x00 in the collection name
  39. if (collectionName.indexOf('\x00') !== -1) {
  40. // TODO(NODE-3483): Use MongoNamespace static method
  41. throw new error_1.MongoInvalidArgumentError('Collection names cannot contain a null character');
  42. }
  43. }
  44. exports.checkCollectionName = checkCollectionName;
  45. /**
  46. * Ensure Hint field is in a shape we expect:
  47. * - object of index names mapping to 1 or -1
  48. * - just an index name
  49. * @internal
  50. */
  51. function normalizeHintField(hint) {
  52. let finalHint = undefined;
  53. if (typeof hint === 'string') {
  54. finalHint = hint;
  55. }
  56. else if (Array.isArray(hint)) {
  57. finalHint = {};
  58. hint.forEach(param => {
  59. finalHint[param] = 1;
  60. });
  61. }
  62. else if (hint != null && typeof hint === 'object') {
  63. finalHint = {};
  64. for (const name in hint) {
  65. finalHint[name] = hint[name];
  66. }
  67. }
  68. return finalHint;
  69. }
  70. exports.normalizeHintField = normalizeHintField;
  71. /**
  72. * Create an index specifier based on
  73. * @internal
  74. */
  75. function parseIndexOptions(indexSpec) {
  76. const fieldHash = {};
  77. const indexes = [];
  78. let keys;
  79. // Get all the fields accordingly
  80. if ('string' === typeof indexSpec) {
  81. // 'type'
  82. indexes.push(indexSpec + '_' + 1);
  83. fieldHash[indexSpec] = 1;
  84. }
  85. else if (Array.isArray(indexSpec)) {
  86. indexSpec.forEach((f) => {
  87. if ('string' === typeof f) {
  88. // [{location:'2d'}, 'type']
  89. indexes.push(f + '_' + 1);
  90. fieldHash[f] = 1;
  91. }
  92. else if (Array.isArray(f)) {
  93. // [['location', '2d'],['type', 1]]
  94. indexes.push(f[0] + '_' + (f[1] || 1));
  95. fieldHash[f[0]] = f[1] || 1;
  96. }
  97. else if (isObject(f)) {
  98. // [{location:'2d'}, {type:1}]
  99. keys = Object.keys(f);
  100. keys.forEach(k => {
  101. indexes.push(k + '_' + f[k]);
  102. fieldHash[k] = f[k];
  103. });
  104. }
  105. else {
  106. // undefined (ignore)
  107. }
  108. });
  109. }
  110. else if (isObject(indexSpec)) {
  111. // {location:'2d', type:1}
  112. keys = Object.keys(indexSpec);
  113. Object.entries(indexSpec).forEach(([key, value]) => {
  114. indexes.push(key + '_' + value);
  115. fieldHash[key] = value;
  116. });
  117. }
  118. return {
  119. name: indexes.join('_'),
  120. keys: keys,
  121. fieldHash: fieldHash
  122. };
  123. }
  124. exports.parseIndexOptions = parseIndexOptions;
  125. /**
  126. * Checks if arg is an Object:
  127. * - **NOTE**: the check is based on the `[Symbol.toStringTag]() === 'Object'`
  128. * @internal
  129. */
  130. // eslint-disable-next-line @typescript-eslint/ban-types
  131. function isObject(arg) {
  132. return '[object Object]' === Object.prototype.toString.call(arg);
  133. }
  134. exports.isObject = isObject;
  135. /** @internal */
  136. function mergeOptions(target, source) {
  137. return { ...target, ...source };
  138. }
  139. exports.mergeOptions = mergeOptions;
  140. /** @internal */
  141. function filterOptions(options, names) {
  142. const filterOptions = {};
  143. for (const name in options) {
  144. if (names.includes(name)) {
  145. filterOptions[name] = options[name];
  146. }
  147. }
  148. // Filtered options
  149. return filterOptions;
  150. }
  151. exports.filterOptions = filterOptions;
  152. /**
  153. * Executes the given operation with provided arguments.
  154. *
  155. * @remarks
  156. * This method reduces large amounts of duplication in the entire codebase by providing
  157. * a single point for determining whether callbacks or promises should be used. Additionally
  158. * it allows for a single point of entry to provide features such as implicit sessions, which
  159. * are required by the Driver Sessions specification in the event that a ClientSession is
  160. * not provided
  161. *
  162. * @internal
  163. *
  164. * @param topology - The topology to execute this operation on
  165. * @param operation - The operation to execute
  166. * @param args - Arguments to apply the provided operation
  167. * @param options - Options that modify the behavior of the method
  168. */
  169. function executeLegacyOperation(topology, operation, args, options) {
  170. const Promise = promise_provider_1.PromiseProvider.get();
  171. if (!Array.isArray(args)) {
  172. // TODO(NODE-3483)
  173. throw new error_1.MongoRuntimeError('This method requires an array of arguments to apply');
  174. }
  175. options = options !== null && options !== void 0 ? options : {};
  176. let callback = args[args.length - 1];
  177. // The driver sessions spec mandates that we implicitly create sessions for operations
  178. // that are not explicitly provided with a session.
  179. let session;
  180. let opOptions;
  181. let owner;
  182. if (!options.skipSessions && topology.hasSessionSupport()) {
  183. opOptions = args[args.length - 2];
  184. if (opOptions == null || opOptions.session == null) {
  185. owner = Symbol();
  186. session = topology.startSession({ owner });
  187. const optionsIndex = args.length - 2;
  188. args[optionsIndex] = Object.assign({}, args[optionsIndex], { session: session });
  189. }
  190. else if (opOptions.session && opOptions.session.hasEnded) {
  191. throw new error_1.MongoExpiredSessionError();
  192. }
  193. }
  194. function makeExecuteCallback(resolve, reject) {
  195. return function (err, result) {
  196. if (session && session.owner === owner && !(options === null || options === void 0 ? void 0 : options.returnsCursor)) {
  197. session.endSession(() => {
  198. delete opOptions.session;
  199. if (err)
  200. return reject(err);
  201. resolve(result);
  202. });
  203. }
  204. else {
  205. if (err)
  206. return reject(err);
  207. resolve(result);
  208. }
  209. };
  210. }
  211. // Execute using callback
  212. if (typeof callback === 'function') {
  213. callback = args.pop();
  214. const handler = makeExecuteCallback(result => callback(undefined, result), err => callback(err, null));
  215. args.push(handler);
  216. try {
  217. return operation(...args);
  218. }
  219. catch (e) {
  220. handler(e);
  221. throw e;
  222. }
  223. }
  224. // Return a Promise
  225. if (args[args.length - 1] != null) {
  226. // TODO(NODE-3483)
  227. throw new error_1.MongoRuntimeError('Final argument to `executeLegacyOperation` must be a callback');
  228. }
  229. return new Promise((resolve, reject) => {
  230. const handler = makeExecuteCallback(resolve, reject);
  231. args[args.length - 1] = handler;
  232. try {
  233. return operation(...args);
  234. }
  235. catch (e) {
  236. handler(e);
  237. }
  238. });
  239. }
  240. exports.executeLegacyOperation = executeLegacyOperation;
  241. /**
  242. * Applies retryWrites: true to a command if retryWrites is set on the command's database.
  243. * @internal
  244. *
  245. * @param target - The target command to which we will apply retryWrites.
  246. * @param db - The database from which we can inherit a retryWrites value.
  247. */
  248. function applyRetryableWrites(target, db) {
  249. var _a;
  250. if (db && ((_a = db.s.options) === null || _a === void 0 ? void 0 : _a.retryWrites)) {
  251. target.retryWrites = true;
  252. }
  253. return target;
  254. }
  255. exports.applyRetryableWrites = applyRetryableWrites;
  256. /**
  257. * Applies a write concern to a command based on well defined inheritance rules, optionally
  258. * detecting support for the write concern in the first place.
  259. * @internal
  260. *
  261. * @param target - the target command we will be applying the write concern to
  262. * @param sources - sources where we can inherit default write concerns from
  263. * @param options - optional settings passed into a command for write concern overrides
  264. */
  265. function applyWriteConcern(target, sources, options) {
  266. options = options !== null && options !== void 0 ? options : {};
  267. const db = sources.db;
  268. const coll = sources.collection;
  269. if (options.session && options.session.inTransaction()) {
  270. // writeConcern is not allowed within a multi-statement transaction
  271. if (target.writeConcern) {
  272. delete target.writeConcern;
  273. }
  274. return target;
  275. }
  276. const writeConcern = write_concern_1.WriteConcern.fromOptions(options);
  277. if (writeConcern) {
  278. return Object.assign(target, { writeConcern });
  279. }
  280. if (coll && coll.writeConcern) {
  281. return Object.assign(target, { writeConcern: Object.assign({}, coll.writeConcern) });
  282. }
  283. if (db && db.writeConcern) {
  284. return Object.assign(target, { writeConcern: Object.assign({}, db.writeConcern) });
  285. }
  286. return target;
  287. }
  288. exports.applyWriteConcern = applyWriteConcern;
  289. /**
  290. * Checks if a given value is a Promise
  291. *
  292. * @typeParam T - The result type of maybePromise
  293. * @param maybePromise - An object that could be a promise
  294. * @returns true if the provided value is a Promise
  295. */
  296. function isPromiseLike(maybePromise) {
  297. return !!maybePromise && typeof maybePromise.then === 'function';
  298. }
  299. exports.isPromiseLike = isPromiseLike;
  300. /**
  301. * Applies collation to a given command.
  302. * @internal
  303. *
  304. * @param command - the command on which to apply collation
  305. * @param target - target of command
  306. * @param options - options containing collation settings
  307. */
  308. function decorateWithCollation(command, target, options) {
  309. const capabilities = getTopology(target).capabilities;
  310. if (options.collation && typeof options.collation === 'object') {
  311. if (capabilities && capabilities.commandsTakeCollation) {
  312. command.collation = options.collation;
  313. }
  314. else {
  315. throw new error_1.MongoCompatibilityError(`Current topology does not support collation`);
  316. }
  317. }
  318. }
  319. exports.decorateWithCollation = decorateWithCollation;
  320. /**
  321. * Applies a read concern to a given command.
  322. * @internal
  323. *
  324. * @param command - the command on which to apply the read concern
  325. * @param coll - the parent collection of the operation calling this method
  326. */
  327. function decorateWithReadConcern(command, coll, options) {
  328. if (options && options.session && options.session.inTransaction()) {
  329. return;
  330. }
  331. const readConcern = Object.assign({}, command.readConcern || {});
  332. if (coll.s.readConcern) {
  333. Object.assign(readConcern, coll.s.readConcern);
  334. }
  335. if (Object.keys(readConcern).length > 0) {
  336. Object.assign(command, { readConcern: readConcern });
  337. }
  338. }
  339. exports.decorateWithReadConcern = decorateWithReadConcern;
  340. /**
  341. * Applies an explain to a given command.
  342. * @internal
  343. *
  344. * @param command - the command on which to apply the explain
  345. * @param options - the options containing the explain verbosity
  346. */
  347. function decorateWithExplain(command, explain) {
  348. if (command.explain) {
  349. return command;
  350. }
  351. return { explain: command, verbosity: explain.verbosity };
  352. }
  353. exports.decorateWithExplain = decorateWithExplain;
  354. /**
  355. * A helper function to get the topology from a given provider. Throws
  356. * if the topology cannot be found.
  357. * @internal
  358. */
  359. function getTopology(provider) {
  360. if (`topology` in provider && provider.topology) {
  361. return provider.topology;
  362. }
  363. else if ('client' in provider.s && provider.s.client.topology) {
  364. return provider.s.client.topology;
  365. }
  366. else if ('db' in provider.s && provider.s.db.s.client.topology) {
  367. return provider.s.db.s.client.topology;
  368. }
  369. throw new error_1.MongoNotConnectedError('MongoClient must be connected to perform this operation');
  370. }
  371. exports.getTopology = getTopology;
  372. /**
  373. * Default message handler for generating deprecation warnings.
  374. * @internal
  375. *
  376. * @param name - function name
  377. * @param option - option name
  378. * @returns warning message
  379. */
  380. function defaultMsgHandler(name, option) {
  381. return `${name} option [${option}] is deprecated and will be removed in a later version.`;
  382. }
  383. exports.defaultMsgHandler = defaultMsgHandler;
  384. /**
  385. * Deprecates a given function's options.
  386. * @internal
  387. *
  388. * @param this - the bound class if this is a method
  389. * @param config - configuration for deprecation
  390. * @param fn - the target function of deprecation
  391. * @returns modified function that warns once per deprecated option, and executes original function
  392. */
  393. function deprecateOptions(config, fn) {
  394. if (process.noDeprecation === true) {
  395. return fn;
  396. }
  397. const msgHandler = config.msgHandler ? config.msgHandler : defaultMsgHandler;
  398. const optionsWarned = new Set();
  399. function deprecated(...args) {
  400. const options = args[config.optionsIndex];
  401. // ensure options is a valid, non-empty object, otherwise short-circuit
  402. if (!isObject(options) || Object.keys(options).length === 0) {
  403. return fn.bind(this)(...args); // call the function, no change
  404. }
  405. // interrupt the function call with a warning
  406. for (const deprecatedOption of config.deprecatedOptions) {
  407. if (deprecatedOption in options && !optionsWarned.has(deprecatedOption)) {
  408. optionsWarned.add(deprecatedOption);
  409. const msg = msgHandler(config.name, deprecatedOption);
  410. emitWarning(msg);
  411. if (this && 'getLogger' in this) {
  412. const logger = this.getLogger();
  413. if (logger) {
  414. logger.warn(msg);
  415. }
  416. }
  417. }
  418. }
  419. return fn.bind(this)(...args);
  420. }
  421. // These lines copied from https://github.com/nodejs/node/blob/25e5ae41688676a5fd29b2e2e7602168eee4ceb5/lib/internal/util.js#L73-L80
  422. // The wrapper will keep the same prototype as fn to maintain prototype chain
  423. Object.setPrototypeOf(deprecated, fn);
  424. if (fn.prototype) {
  425. // Setting this (rather than using Object.setPrototype, as above) ensures
  426. // that calling the unwrapped constructor gives an instanceof the wrapped
  427. // constructor.
  428. deprecated.prototype = fn.prototype;
  429. }
  430. return deprecated;
  431. }
  432. exports.deprecateOptions = deprecateOptions;
  433. /** @internal */
  434. function ns(ns) {
  435. return MongoDBNamespace.fromString(ns);
  436. }
  437. exports.ns = ns;
  438. /** @public */
  439. class MongoDBNamespace {
  440. /**
  441. * Create a namespace object
  442. *
  443. * @param db - database name
  444. * @param collection - collection name
  445. */
  446. constructor(db, collection) {
  447. this.db = db;
  448. this.collection = collection;
  449. }
  450. toString() {
  451. return this.collection ? `${this.db}.${this.collection}` : this.db;
  452. }
  453. withCollection(collection) {
  454. return new MongoDBNamespace(this.db, collection);
  455. }
  456. static fromString(namespace) {
  457. if (!namespace) {
  458. // TODO(NODE-3483): Replace with MongoNamespaceError
  459. throw new error_1.MongoRuntimeError(`Cannot parse namespace from "${namespace}"`);
  460. }
  461. const [db, ...collection] = namespace.split('.');
  462. return new MongoDBNamespace(db, collection.join('.'));
  463. }
  464. }
  465. exports.MongoDBNamespace = MongoDBNamespace;
  466. /** @internal */
  467. function* makeCounter(seed = 0) {
  468. let count = seed;
  469. while (true) {
  470. const newCount = count;
  471. count += 1;
  472. yield newCount;
  473. }
  474. }
  475. exports.makeCounter = makeCounter;
  476. /**
  477. * Helper function for either accepting a callback, or returning a promise
  478. * @internal
  479. *
  480. * @param callback - The last function argument in exposed method, controls if a Promise is returned
  481. * @param wrapper - A function that wraps the callback
  482. * @returns Returns void if a callback is supplied, else returns a Promise.
  483. */
  484. function maybePromise(callback, wrapper) {
  485. const Promise = promise_provider_1.PromiseProvider.get();
  486. let result;
  487. if (typeof callback !== 'function') {
  488. result = new Promise((resolve, reject) => {
  489. callback = (err, res) => {
  490. if (err)
  491. return reject(err);
  492. resolve(res);
  493. };
  494. });
  495. }
  496. wrapper((err, res) => {
  497. if (err != null) {
  498. try {
  499. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  500. callback(err);
  501. }
  502. catch (error) {
  503. process.nextTick(() => {
  504. throw error;
  505. });
  506. }
  507. return;
  508. }
  509. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  510. callback(err, res);
  511. });
  512. return result;
  513. }
  514. exports.maybePromise = maybePromise;
  515. /** @internal */
  516. function databaseNamespace(ns) {
  517. return ns.split('.')[0];
  518. }
  519. exports.databaseNamespace = databaseNamespace;
  520. /**
  521. * Synchronously Generate a UUIDv4
  522. * @internal
  523. */
  524. function uuidV4() {
  525. const result = crypto.randomBytes(16);
  526. result[6] = (result[6] & 0x0f) | 0x40;
  527. result[8] = (result[8] & 0x3f) | 0x80;
  528. return result;
  529. }
  530. exports.uuidV4 = uuidV4;
  531. /**
  532. * A helper function for determining `maxWireVersion` between legacy and new topology instances
  533. * @internal
  534. */
  535. function maxWireVersion(topologyOrServer) {
  536. if (topologyOrServer) {
  537. if (topologyOrServer.loadBalanced) {
  538. // Since we do not have a monitor, we assume the load balanced server is always
  539. // pointed at the latest mongodb version. There is a risk that for on-prem
  540. // deployments that don't upgrade immediately that this could alert to the
  541. // application that a feature is avaiable that is actually not.
  542. return constants_1.MAX_SUPPORTED_WIRE_VERSION;
  543. }
  544. if (topologyOrServer.hello) {
  545. return topologyOrServer.hello.maxWireVersion;
  546. }
  547. if ('lastHello' in topologyOrServer && typeof topologyOrServer.lastHello === 'function') {
  548. const lastHello = topologyOrServer.lastHello();
  549. if (lastHello) {
  550. return lastHello.maxWireVersion;
  551. }
  552. }
  553. if (topologyOrServer.description &&
  554. 'maxWireVersion' in topologyOrServer.description &&
  555. topologyOrServer.description.maxWireVersion != null) {
  556. return topologyOrServer.description.maxWireVersion;
  557. }
  558. }
  559. return 0;
  560. }
  561. exports.maxWireVersion = maxWireVersion;
  562. /**
  563. * Checks that collation is supported by server.
  564. * @internal
  565. *
  566. * @param server - to check against
  567. * @param cmd - object where collation may be specified
  568. */
  569. function collationNotSupported(server, cmd) {
  570. return cmd && cmd.collation && maxWireVersion(server) < 5;
  571. }
  572. exports.collationNotSupported = collationNotSupported;
  573. /**
  574. * Applies the function `eachFn` to each item in `arr`, in parallel.
  575. * @internal
  576. *
  577. * @param arr - An array of items to asynchronously iterate over
  578. * @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.
  579. * @param callback - The callback called after every item has been iterated
  580. */
  581. function eachAsync(arr, eachFn, callback) {
  582. arr = arr || [];
  583. let idx = 0;
  584. let awaiting = 0;
  585. for (idx = 0; idx < arr.length; ++idx) {
  586. awaiting++;
  587. eachFn(arr[idx], eachCallback);
  588. }
  589. if (awaiting === 0) {
  590. callback();
  591. return;
  592. }
  593. function eachCallback(err) {
  594. awaiting--;
  595. if (err) {
  596. callback(err);
  597. return;
  598. }
  599. if (idx === arr.length && awaiting <= 0) {
  600. callback();
  601. }
  602. }
  603. }
  604. exports.eachAsync = eachAsync;
  605. /** @internal */
  606. function eachAsyncSeries(arr, eachFn, callback) {
  607. arr = arr || [];
  608. let idx = 0;
  609. let awaiting = arr.length;
  610. if (awaiting === 0) {
  611. callback();
  612. return;
  613. }
  614. function eachCallback(err) {
  615. idx++;
  616. awaiting--;
  617. if (err) {
  618. callback(err);
  619. return;
  620. }
  621. if (idx === arr.length && awaiting <= 0) {
  622. callback();
  623. return;
  624. }
  625. eachFn(arr[idx], eachCallback);
  626. }
  627. eachFn(arr[idx], eachCallback);
  628. }
  629. exports.eachAsyncSeries = eachAsyncSeries;
  630. /** @internal */
  631. function arrayStrictEqual(arr, arr2) {
  632. if (!Array.isArray(arr) || !Array.isArray(arr2)) {
  633. return false;
  634. }
  635. return arr.length === arr2.length && arr.every((elt, idx) => elt === arr2[idx]);
  636. }
  637. exports.arrayStrictEqual = arrayStrictEqual;
  638. /** @internal */
  639. function errorStrictEqual(lhs, rhs) {
  640. if (lhs === rhs) {
  641. return true;
  642. }
  643. if (!lhs || !rhs) {
  644. return lhs === rhs;
  645. }
  646. if ((lhs == null && rhs != null) || (lhs != null && rhs == null)) {
  647. return false;
  648. }
  649. if (lhs.constructor.name !== rhs.constructor.name) {
  650. return false;
  651. }
  652. if (lhs.message !== rhs.message) {
  653. return false;
  654. }
  655. return true;
  656. }
  657. exports.errorStrictEqual = errorStrictEqual;
  658. /** @internal */
  659. function makeStateMachine(stateTable) {
  660. return function stateTransition(target, newState) {
  661. const legalStates = stateTable[target.s.state];
  662. if (legalStates && legalStates.indexOf(newState) < 0) {
  663. throw new error_1.MongoRuntimeError(`illegal state transition from [${target.s.state}] => [${newState}], allowed: [${legalStates}]`);
  664. }
  665. target.emit('stateChanged', target.s.state, newState);
  666. target.s.state = newState;
  667. };
  668. }
  669. exports.makeStateMachine = makeStateMachine;
  670. // eslint-disable-next-line @typescript-eslint/no-var-requires
  671. const NODE_DRIVER_VERSION = require('../package.json').version;
  672. function makeClientMetadata(options) {
  673. options = options !== null && options !== void 0 ? options : {};
  674. const metadata = {
  675. driver: {
  676. name: 'nodejs',
  677. version: NODE_DRIVER_VERSION
  678. },
  679. os: {
  680. type: os.type(),
  681. name: process.platform,
  682. architecture: process.arch,
  683. version: os.release()
  684. },
  685. platform: `Node.js ${process.version}, ${os.endianness()} (unified)`
  686. };
  687. // support optionally provided wrapping driver info
  688. if (options.driverInfo) {
  689. if (options.driverInfo.name) {
  690. metadata.driver.name = `${metadata.driver.name}|${options.driverInfo.name}`;
  691. }
  692. if (options.driverInfo.version) {
  693. metadata.version = `${metadata.driver.version}|${options.driverInfo.version}`;
  694. }
  695. if (options.driverInfo.platform) {
  696. metadata.platform = `${metadata.platform}|${options.driverInfo.platform}`;
  697. }
  698. }
  699. if (options.appName) {
  700. // MongoDB requires the appName not exceed a byte length of 128
  701. const buffer = Buffer.from(options.appName);
  702. metadata.application = {
  703. name: buffer.byteLength > 128 ? buffer.slice(0, 128).toString('utf8') : options.appName
  704. };
  705. }
  706. return metadata;
  707. }
  708. exports.makeClientMetadata = makeClientMetadata;
  709. /** @internal */
  710. function now() {
  711. const hrtime = process.hrtime();
  712. return Math.floor(hrtime[0] * 1000 + hrtime[1] / 1000000);
  713. }
  714. exports.now = now;
  715. /** @internal */
  716. function calculateDurationInMs(started) {
  717. if (typeof started !== 'number') {
  718. throw new error_1.MongoInvalidArgumentError('Numeric value required to calculate duration');
  719. }
  720. const elapsed = now() - started;
  721. return elapsed < 0 ? 0 : elapsed;
  722. }
  723. exports.calculateDurationInMs = calculateDurationInMs;
  724. /**
  725. * Creates an interval timer which is able to be woken up sooner than
  726. * the interval. The timer will also debounce multiple calls to wake
  727. * ensuring that the function is only ever called once within a minimum
  728. * interval window.
  729. * @internal
  730. *
  731. * @param fn - An async function to run on an interval, must accept a `callback` as its only parameter
  732. */
  733. function makeInterruptibleAsyncInterval(fn, options) {
  734. let timerId;
  735. let lastCallTime;
  736. let cannotBeExpedited = false;
  737. let stopped = false;
  738. options = options !== null && options !== void 0 ? options : {};
  739. const interval = options.interval || 1000;
  740. const minInterval = options.minInterval || 500;
  741. const immediate = typeof options.immediate === 'boolean' ? options.immediate : false;
  742. const clock = typeof options.clock === 'function' ? options.clock : now;
  743. function wake() {
  744. const currentTime = clock();
  745. const nextScheduledCallTime = lastCallTime + interval;
  746. const timeUntilNextCall = nextScheduledCallTime - currentTime;
  747. // For the streaming protocol: there is nothing obviously stopping this
  748. // interval from being woken up again while we are waiting "infinitely"
  749. // for `fn` to be called again`. Since the function effectively
  750. // never completes, the `timeUntilNextCall` will continue to grow
  751. // negatively unbounded, so it will never trigger a reschedule here.
  752. // This is possible in virtualized environments like AWS Lambda where our
  753. // clock is unreliable. In these cases the timer is "running" but never
  754. // actually completes, so we want to execute immediately and then attempt
  755. // to reschedule.
  756. if (timeUntilNextCall < 0) {
  757. executeAndReschedule();
  758. return;
  759. }
  760. // debounce multiple calls to wake within the `minInterval`
  761. if (cannotBeExpedited) {
  762. return;
  763. }
  764. // reschedule a call as soon as possible, ensuring the call never happens
  765. // faster than the `minInterval`
  766. if (timeUntilNextCall > minInterval) {
  767. reschedule(minInterval);
  768. cannotBeExpedited = true;
  769. }
  770. }
  771. function stop() {
  772. stopped = true;
  773. if (timerId) {
  774. clearTimeout(timerId);
  775. timerId = undefined;
  776. }
  777. lastCallTime = 0;
  778. cannotBeExpedited = false;
  779. }
  780. function reschedule(ms) {
  781. if (stopped)
  782. return;
  783. if (timerId) {
  784. clearTimeout(timerId);
  785. }
  786. timerId = setTimeout(executeAndReschedule, ms || interval);
  787. }
  788. function executeAndReschedule() {
  789. cannotBeExpedited = false;
  790. lastCallTime = clock();
  791. fn(err => {
  792. if (err)
  793. throw err;
  794. reschedule(interval);
  795. });
  796. }
  797. if (immediate) {
  798. executeAndReschedule();
  799. }
  800. else {
  801. lastCallTime = clock();
  802. reschedule(undefined);
  803. }
  804. return { wake, stop };
  805. }
  806. exports.makeInterruptibleAsyncInterval = makeInterruptibleAsyncInterval;
  807. /** @internal */
  808. function hasAtomicOperators(doc) {
  809. if (Array.isArray(doc)) {
  810. for (const document of doc) {
  811. if (hasAtomicOperators(document)) {
  812. return true;
  813. }
  814. }
  815. return false;
  816. }
  817. const keys = Object.keys(doc);
  818. return keys.length > 0 && keys[0][0] === '$';
  819. }
  820. exports.hasAtomicOperators = hasAtomicOperators;
  821. /**
  822. * Merge inherited properties from parent into options, prioritizing values from options,
  823. * then values from parent.
  824. * @internal
  825. */
  826. function resolveOptions(parent, options) {
  827. var _a, _b, _c;
  828. const result = Object.assign({}, options, (0, bson_1.resolveBSONOptions)(options, parent));
  829. // Users cannot pass a readConcern/writeConcern to operations in a transaction
  830. const session = options === null || options === void 0 ? void 0 : options.session;
  831. if (!(session === null || session === void 0 ? void 0 : session.inTransaction())) {
  832. const readConcern = (_a = read_concern_1.ReadConcern.fromOptions(options)) !== null && _a !== void 0 ? _a : parent === null || parent === void 0 ? void 0 : parent.readConcern;
  833. if (readConcern) {
  834. result.readConcern = readConcern;
  835. }
  836. const writeConcern = (_b = write_concern_1.WriteConcern.fromOptions(options)) !== null && _b !== void 0 ? _b : parent === null || parent === void 0 ? void 0 : parent.writeConcern;
  837. if (writeConcern) {
  838. result.writeConcern = writeConcern;
  839. }
  840. }
  841. const readPreference = (_c = read_preference_1.ReadPreference.fromOptions(options)) !== null && _c !== void 0 ? _c : parent === null || parent === void 0 ? void 0 : parent.readPreference;
  842. if (readPreference) {
  843. result.readPreference = readPreference;
  844. }
  845. return result;
  846. }
  847. exports.resolveOptions = resolveOptions;
  848. function isSuperset(set, subset) {
  849. set = Array.isArray(set) ? new Set(set) : set;
  850. subset = Array.isArray(subset) ? new Set(subset) : subset;
  851. for (const elem of subset) {
  852. if (!set.has(elem)) {
  853. return false;
  854. }
  855. }
  856. return true;
  857. }
  858. exports.isSuperset = isSuperset;
  859. /**
  860. * Checks if the document is a Hello request
  861. * @internal
  862. */
  863. function isHello(doc) {
  864. return doc[constants_2.LEGACY_HELLO_COMMAND] || doc.hello ? true : false;
  865. }
  866. exports.isHello = isHello;
  867. /** Returns the items that are uniquely in setA */
  868. function setDifference(setA, setB) {
  869. const difference = new Set(setA);
  870. for (const elem of setB) {
  871. difference.delete(elem);
  872. }
  873. return difference;
  874. }
  875. exports.setDifference = setDifference;
  876. function isRecord(value, requiredKeys = undefined) {
  877. const toString = Object.prototype.toString;
  878. const hasOwnProperty = Object.prototype.hasOwnProperty;
  879. const isObject = (v) => toString.call(v) === '[object Object]';
  880. if (!isObject(value)) {
  881. return false;
  882. }
  883. const ctor = value.constructor;
  884. if (ctor && ctor.prototype) {
  885. if (!isObject(ctor.prototype)) {
  886. return false;
  887. }
  888. // Check to see if some method exists from the Object exists
  889. if (!hasOwnProperty.call(ctor.prototype, 'isPrototypeOf')) {
  890. return false;
  891. }
  892. }
  893. if (requiredKeys) {
  894. const keys = Object.keys(value);
  895. return isSuperset(keys, requiredKeys);
  896. }
  897. return true;
  898. }
  899. exports.isRecord = isRecord;
  900. /**
  901. * Make a deep copy of an object
  902. *
  903. * NOTE: This is not meant to be the perfect implementation of a deep copy,
  904. * but instead something that is good enough for the purposes of
  905. * command monitoring.
  906. */
  907. function deepCopy(value) {
  908. if (value == null) {
  909. return value;
  910. }
  911. else if (Array.isArray(value)) {
  912. return value.map(item => deepCopy(item));
  913. }
  914. else if (isRecord(value)) {
  915. const res = {};
  916. for (const key in value) {
  917. res[key] = deepCopy(value[key]);
  918. }
  919. return res;
  920. }
  921. const ctor = value.constructor;
  922. if (ctor) {
  923. switch (ctor.name.toLowerCase()) {
  924. case 'date':
  925. return new ctor(Number(value));
  926. case 'map':
  927. return new Map(value);
  928. case 'set':
  929. return new Set(value);
  930. case 'buffer':
  931. return Buffer.from(value);
  932. }
  933. }
  934. return value;
  935. }
  936. exports.deepCopy = deepCopy;
  937. /** @internal */
  938. const kBuffers = Symbol('buffers');
  939. /** @internal */
  940. const kLength = Symbol('length');
  941. /**
  942. * A pool of Buffers which allow you to read them as if they were one
  943. * @internal
  944. */
  945. class BufferPool {
  946. constructor() {
  947. this[kBuffers] = [];
  948. this[kLength] = 0;
  949. }
  950. get length() {
  951. return this[kLength];
  952. }
  953. /** Adds a buffer to the internal buffer pool list */
  954. append(buffer) {
  955. this[kBuffers].push(buffer);
  956. this[kLength] += buffer.length;
  957. }
  958. /** Returns the requested number of bytes without consuming them */
  959. peek(size) {
  960. return this.read(size, false);
  961. }
  962. /** Reads the requested number of bytes, optionally consuming them */
  963. read(size, consume = true) {
  964. if (typeof size !== 'number' || size < 0) {
  965. throw new error_1.MongoInvalidArgumentError('Argument "size" must be a non-negative number');
  966. }
  967. if (size > this[kLength]) {
  968. return Buffer.alloc(0);
  969. }
  970. let result;
  971. // read the whole buffer
  972. if (size === this.length) {
  973. result = Buffer.concat(this[kBuffers]);
  974. if (consume) {
  975. this[kBuffers] = [];
  976. this[kLength] = 0;
  977. }
  978. }
  979. // size is within first buffer, no need to concat
  980. else if (size <= this[kBuffers][0].length) {
  981. result = this[kBuffers][0].slice(0, size);
  982. if (consume) {
  983. this[kBuffers][0] = this[kBuffers][0].slice(size);
  984. this[kLength] -= size;
  985. }
  986. }
  987. // size is beyond first buffer, need to track and copy
  988. else {
  989. result = Buffer.allocUnsafe(size);
  990. let idx;
  991. let offset = 0;
  992. let bytesToCopy = size;
  993. for (idx = 0; idx < this[kBuffers].length; ++idx) {
  994. let bytesCopied;
  995. if (bytesToCopy > this[kBuffers][idx].length) {
  996. bytesCopied = this[kBuffers][idx].copy(result, offset, 0);
  997. offset += bytesCopied;
  998. }
  999. else {
  1000. bytesCopied = this[kBuffers][idx].copy(result, offset, 0, bytesToCopy);
  1001. if (consume) {
  1002. this[kBuffers][idx] = this[kBuffers][idx].slice(bytesCopied);
  1003. }
  1004. offset += bytesCopied;
  1005. break;
  1006. }
  1007. bytesToCopy -= bytesCopied;
  1008. }
  1009. // compact the internal buffer array
  1010. if (consume) {
  1011. this[kBuffers] = this[kBuffers].slice(idx);
  1012. this[kLength] -= size;
  1013. }
  1014. }
  1015. return result;
  1016. }
  1017. }
  1018. exports.BufferPool = BufferPool;
  1019. /** @public */
  1020. class HostAddress {
  1021. constructor(hostString) {
  1022. const escapedHost = hostString.split(' ').join('%20'); // escape spaces, for socket path hosts
  1023. const { hostname, port } = new url_1.URL(`mongodb://${escapedHost}`);
  1024. if (hostname.endsWith('.sock')) {
  1025. // heuristically determine if we're working with a domain socket
  1026. this.socketPath = decodeURIComponent(hostname);
  1027. }
  1028. else if (typeof hostname === 'string') {
  1029. this.isIPv6 = false;
  1030. let normalized = decodeURIComponent(hostname).toLowerCase();
  1031. if (normalized.startsWith('[') && normalized.endsWith(']')) {
  1032. this.isIPv6 = true;
  1033. normalized = normalized.substring(1, hostname.length - 1);
  1034. }
  1035. this.host = normalized.toLowerCase();
  1036. if (typeof port === 'number') {
  1037. this.port = port;
  1038. }
  1039. else if (typeof port === 'string' && port !== '') {
  1040. this.port = Number.parseInt(port, 10);
  1041. }
  1042. else {
  1043. this.port = 27017;
  1044. }
  1045. if (this.port === 0) {
  1046. throw new error_1.MongoParseError('Invalid port (zero) with hostname');
  1047. }
  1048. }
  1049. else {
  1050. throw new error_1.MongoInvalidArgumentError('Either socketPath or host must be defined.');
  1051. }
  1052. Object.freeze(this);
  1053. }
  1054. [Symbol.for('nodejs.util.inspect.custom')]() {
  1055. return this.inspect();
  1056. }
  1057. inspect() {
  1058. return `new HostAddress('${this.toString(true)}')`;
  1059. }
  1060. /**
  1061. * @param ipv6Brackets - optionally request ipv6 bracket notation required for connection strings
  1062. */
  1063. toString(ipv6Brackets = false) {
  1064. if (typeof this.host === 'string') {
  1065. if (this.isIPv6 && ipv6Brackets) {
  1066. return `[${this.host}]:${this.port}`;
  1067. }
  1068. return `${this.host}:${this.port}`;
  1069. }
  1070. return `${this.socketPath}`;
  1071. }
  1072. static fromString(s) {
  1073. return new HostAddress(s);
  1074. }
  1075. static fromHostPort(host, port) {
  1076. if (host.includes(':')) {
  1077. host = `[${host}]`; // IPv6 address
  1078. }
  1079. return HostAddress.fromString(`${host}:${port}`);
  1080. }
  1081. static fromSrvRecord({ name, port }) {
  1082. return HostAddress.fromHostPort(name, port);
  1083. }
  1084. }
  1085. exports.HostAddress = HostAddress;
  1086. exports.DEFAULT_PK_FACTORY = {
  1087. // We prefer not to rely on ObjectId having a createPk method
  1088. createPk() {
  1089. return new bson_1.ObjectId();
  1090. }
  1091. };
  1092. /**
  1093. * When the driver used emitWarning the code will be equal to this.
  1094. * @public
  1095. *
  1096. * @example
  1097. * ```js
  1098. * process.on('warning', (warning) => {
  1099. * if (warning.code === MONGODB_WARNING_CODE) console.error('Ah an important warning! :)')
  1100. * })
  1101. * ```
  1102. */
  1103. exports.MONGODB_WARNING_CODE = 'MONGODB DRIVER';
  1104. /** @internal */
  1105. function emitWarning(message) {
  1106. return process.emitWarning(message, { code: exports.MONGODB_WARNING_CODE });
  1107. }
  1108. exports.emitWarning = emitWarning;
  1109. const emittedWarnings = new Set();
  1110. /**
  1111. * Will emit a warning once for the duration of the application.
  1112. * Uses the message to identify if it has already been emitted
  1113. * so using string interpolation can cause multiple emits
  1114. * @internal
  1115. */
  1116. function emitWarningOnce(message) {
  1117. if (!emittedWarnings.has(message)) {
  1118. emittedWarnings.add(message);
  1119. return emitWarning(message);
  1120. }
  1121. }
  1122. exports.emitWarningOnce = emitWarningOnce;
  1123. /**
  1124. * Takes a JS object and joins the values into a string separated by ', '
  1125. */
  1126. function enumToString(en) {
  1127. return Object.values(en).join(', ');
  1128. }
  1129. exports.enumToString = enumToString;
  1130. /**
  1131. * Determine if a server supports retryable writes.
  1132. *
  1133. * @internal
  1134. */
  1135. function supportsRetryableWrites(server) {
  1136. return (!!server.loadBalanced ||
  1137. (server.description.maxWireVersion >= 6 &&
  1138. !!server.description.logicalSessionTimeoutMinutes &&
  1139. server.description.type !== common_1.ServerType.Standalone));
  1140. }
  1141. exports.supportsRetryableWrites = supportsRetryableWrites;
  1142. function parsePackageVersion({ version }) {
  1143. const [major, minor, patch] = version.split('.').map((n) => Number.parseInt(n, 10));
  1144. return { major, minor, patch };
  1145. }
  1146. exports.parsePackageVersion = parsePackageVersion;
  1147. /**
  1148. * Fisher–Yates Shuffle
  1149. *
  1150. * Reference: https://bost.ocks.org/mike/shuffle/
  1151. * @param sequence - items to be shuffled
  1152. * @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.
  1153. */
  1154. function shuffle(sequence, limit = 0) {
  1155. const items = Array.from(sequence); // shallow copy in order to never shuffle the input
  1156. if (limit > items.length) {
  1157. throw new error_1.MongoRuntimeError('Limit must be less than the number of items');
  1158. }
  1159. let remainingItemsToShuffle = items.length;
  1160. const lowerBound = limit % items.length === 0 ? 1 : items.length - limit;
  1161. while (remainingItemsToShuffle > lowerBound) {
  1162. // Pick a remaining element
  1163. const randomIndex = Math.floor(Math.random() * remainingItemsToShuffle);
  1164. remainingItemsToShuffle -= 1;
  1165. // And swap it with the current element
  1166. const swapHold = items[remainingItemsToShuffle];
  1167. items[remainingItemsToShuffle] = items[randomIndex];
  1168. items[randomIndex] = swapHold;
  1169. }
  1170. return limit % items.length === 0 ? items : items.slice(lowerBound);
  1171. }
  1172. exports.shuffle = shuffle;
  1173. // TODO: this should be codified in command construction
  1174. // @see https://github.com/mongodb/specifications/blob/master/source/read-write-concern/read-write-concern.rst#read-concern
  1175. function commandSupportsReadConcern(command, options) {
  1176. if (command.aggregate || command.count || command.distinct || command.find || command.geoNear) {
  1177. return true;
  1178. }
  1179. if (command.mapReduce &&
  1180. options &&
  1181. options.out &&
  1182. (options.out.inline === 1 || options.out === 'inline')) {
  1183. return true;
  1184. }
  1185. return false;
  1186. }
  1187. exports.commandSupportsReadConcern = commandSupportsReadConcern;
  1188. //# sourceMappingURL=utils.js.map