utils.js 39 KB

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