utils.js 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.kDispose = exports.randomBytes = exports.COSMOS_DB_MSG = exports.DOCUMENT_DB_MSG = exports.COSMOS_DB_CHECK = exports.DOCUMENT_DB_CHECK = exports.MONGODB_WARNING_CODE = exports.DEFAULT_PK_FACTORY = exports.HostAddress = exports.BufferPool = exports.List = exports.MongoDBCollectionNamespace = exports.MongoDBNamespace = exports.ByteUtils = void 0;
  4. exports.isUint8Array = isUint8Array;
  5. exports.hostMatchesWildcards = hostMatchesWildcards;
  6. exports.normalizeHintField = normalizeHintField;
  7. exports.isObject = isObject;
  8. exports.mergeOptions = mergeOptions;
  9. exports.filterOptions = filterOptions;
  10. exports.applyRetryableWrites = applyRetryableWrites;
  11. exports.isPromiseLike = isPromiseLike;
  12. exports.decorateWithCollation = decorateWithCollation;
  13. exports.decorateWithReadConcern = decorateWithReadConcern;
  14. exports.getTopology = getTopology;
  15. exports.ns = ns;
  16. exports.makeCounter = makeCounter;
  17. exports.uuidV4 = uuidV4;
  18. exports.maxWireVersion = maxWireVersion;
  19. exports.arrayStrictEqual = arrayStrictEqual;
  20. exports.errorStrictEqual = errorStrictEqual;
  21. exports.makeStateMachine = makeStateMachine;
  22. exports.now = now;
  23. exports.calculateDurationInMs = calculateDurationInMs;
  24. exports.hasAtomicOperators = hasAtomicOperators;
  25. exports.resolveTimeoutOptions = resolveTimeoutOptions;
  26. exports.resolveOptions = resolveOptions;
  27. exports.isSuperset = isSuperset;
  28. exports.isHello = isHello;
  29. exports.setDifference = setDifference;
  30. exports.isRecord = isRecord;
  31. exports.emitWarning = emitWarning;
  32. exports.emitWarningOnce = emitWarningOnce;
  33. exports.enumToString = enumToString;
  34. exports.supportsRetryableWrites = supportsRetryableWrites;
  35. exports.shuffle = shuffle;
  36. exports.commandSupportsReadConcern = commandSupportsReadConcern;
  37. exports.compareObjectId = compareObjectId;
  38. exports.parseInteger = parseInteger;
  39. exports.parseUnsignedInteger = parseUnsignedInteger;
  40. exports.checkParentDomainMatch = checkParentDomainMatch;
  41. exports.get = get;
  42. exports.request = request;
  43. exports.isHostMatch = isHostMatch;
  44. exports.promiseWithResolvers = promiseWithResolvers;
  45. exports.squashError = squashError;
  46. exports.once = once;
  47. exports.maybeAddIdToDocuments = maybeAddIdToDocuments;
  48. exports.fileIsAccessible = fileIsAccessible;
  49. exports.csotMin = csotMin;
  50. exports.noop = noop;
  51. exports.decorateDecryptionResult = decorateDecryptionResult;
  52. exports.addAbortListener = addAbortListener;
  53. exports.abortable = abortable;
  54. const crypto = require("crypto");
  55. const fs_1 = require("fs");
  56. const http = require("http");
  57. const timers_1 = require("timers");
  58. const url = require("url");
  59. const url_1 = require("url");
  60. const util_1 = require("util");
  61. const bson_1 = require("./bson");
  62. const constants_1 = require("./cmap/wire_protocol/constants");
  63. const constants_2 = require("./constants");
  64. const error_1 = require("./error");
  65. const read_concern_1 = require("./read_concern");
  66. const read_preference_1 = require("./read_preference");
  67. const common_1 = require("./sdam/common");
  68. const write_concern_1 = require("./write_concern");
  69. exports.ByteUtils = {
  70. toLocalBufferType(buffer) {
  71. return Buffer.isBuffer(buffer)
  72. ? buffer
  73. : Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength);
  74. },
  75. equals(seqA, seqB) {
  76. return exports.ByteUtils.toLocalBufferType(seqA).equals(seqB);
  77. },
  78. compare(seqA, seqB) {
  79. return exports.ByteUtils.toLocalBufferType(seqA).compare(seqB);
  80. },
  81. toBase64(uint8array) {
  82. return exports.ByteUtils.toLocalBufferType(uint8array).toString('base64');
  83. }
  84. };
  85. /**
  86. * Returns true if value is a Uint8Array or a Buffer
  87. * @param value - any value that may be a Uint8Array
  88. */
  89. function isUint8Array(value) {
  90. return (value != null &&
  91. typeof value === 'object' &&
  92. Symbol.toStringTag in value &&
  93. value[Symbol.toStringTag] === 'Uint8Array');
  94. }
  95. /**
  96. * Determines if a connection's address matches a user provided list
  97. * of domain wildcards.
  98. */
  99. function hostMatchesWildcards(host, wildcards) {
  100. for (const wildcard of wildcards) {
  101. if (host === wildcard ||
  102. (wildcard.startsWith('*.') && host?.endsWith(wildcard.substring(2, wildcard.length))) ||
  103. (wildcard.startsWith('*/') && host?.endsWith(wildcard.substring(2, wildcard.length)))) {
  104. return true;
  105. }
  106. }
  107. return false;
  108. }
  109. /**
  110. * Ensure Hint field is in a shape we expect:
  111. * - object of index names mapping to 1 or -1
  112. * - just an index name
  113. * @internal
  114. */
  115. function normalizeHintField(hint) {
  116. let finalHint = undefined;
  117. if (typeof hint === 'string') {
  118. finalHint = hint;
  119. }
  120. else if (Array.isArray(hint)) {
  121. finalHint = {};
  122. hint.forEach(param => {
  123. finalHint[param] = 1;
  124. });
  125. }
  126. else if (hint != null && typeof hint === 'object') {
  127. finalHint = {};
  128. for (const name in hint) {
  129. finalHint[name] = hint[name];
  130. }
  131. }
  132. return finalHint;
  133. }
  134. const TO_STRING = (object) => Object.prototype.toString.call(object);
  135. /**
  136. * Checks if arg is an Object:
  137. * - **NOTE**: the check is based on the `[Symbol.toStringTag]() === 'Object'`
  138. * @internal
  139. */
  140. function isObject(arg) {
  141. return '[object Object]' === TO_STRING(arg);
  142. }
  143. /** @internal */
  144. function mergeOptions(target, source) {
  145. return { ...target, ...source };
  146. }
  147. /** @internal */
  148. function filterOptions(options, names) {
  149. const filterOptions = {};
  150. for (const name in options) {
  151. if (names.includes(name)) {
  152. filterOptions[name] = options[name];
  153. }
  154. }
  155. // Filtered options
  156. return filterOptions;
  157. }
  158. /**
  159. * Applies retryWrites: true to a command if retryWrites is set on the command's database.
  160. * @internal
  161. *
  162. * @param target - The target command to which we will apply retryWrites.
  163. * @param db - The database from which we can inherit a retryWrites value.
  164. */
  165. function applyRetryableWrites(target, db) {
  166. if (db && db.s.options?.retryWrites) {
  167. target.retryWrites = true;
  168. }
  169. return target;
  170. }
  171. /**
  172. * Applies a write concern to a command based on well defined inheritance rules, optionally
  173. * detecting support for the write concern in the first place.
  174. * @internal
  175. *
  176. * @param target - the target command we will be applying the write concern to
  177. * @param sources - sources where we can inherit default write concerns from
  178. * @param options - optional settings passed into a command for write concern overrides
  179. */
  180. /**
  181. * Checks if a given value is a Promise
  182. *
  183. * @typeParam T - The resolution type of the possible promise
  184. * @param value - An object that could be a promise
  185. * @returns true if the provided value is a Promise
  186. */
  187. function isPromiseLike(value) {
  188. return (value != null &&
  189. typeof value === 'object' &&
  190. 'then' in value &&
  191. typeof value.then === 'function');
  192. }
  193. /**
  194. * Applies collation to a given command.
  195. * @internal
  196. *
  197. * @param command - the command on which to apply collation
  198. * @param target - target of command
  199. * @param options - options containing collation settings
  200. */
  201. function decorateWithCollation(command, options) {
  202. if (options.collation && typeof options.collation === 'object') {
  203. command.collation = options.collation;
  204. }
  205. }
  206. /**
  207. * Applies a read concern to a given command.
  208. * @internal
  209. *
  210. * @param command - the command on which to apply the read concern
  211. * @param coll - the parent collection of the operation calling this method
  212. */
  213. function decorateWithReadConcern(command, coll, options) {
  214. if (options && options.session && options.session.inTransaction()) {
  215. return;
  216. }
  217. const readConcern = Object.assign({}, command.readConcern || {});
  218. if (coll.s.readConcern) {
  219. Object.assign(readConcern, coll.s.readConcern);
  220. }
  221. if (Object.keys(readConcern).length > 0) {
  222. Object.assign(command, { readConcern: readConcern });
  223. }
  224. }
  225. /**
  226. * A helper function to get the topology from a given provider. Throws
  227. * if the topology cannot be found.
  228. * @throws MongoNotConnectedError
  229. * @internal
  230. */
  231. function getTopology(provider) {
  232. // MongoClient or ClientSession or AbstractCursor
  233. if ('topology' in provider && provider.topology) {
  234. return provider.topology;
  235. }
  236. else if ('client' in provider && provider.client.topology) {
  237. return provider.client.topology;
  238. }
  239. throw new error_1.MongoNotConnectedError('MongoClient must be connected to perform this operation');
  240. }
  241. /** @internal */
  242. function ns(ns) {
  243. return MongoDBNamespace.fromString(ns);
  244. }
  245. /** @public */
  246. class MongoDBNamespace {
  247. /**
  248. * Create a namespace object
  249. *
  250. * @param db - database name
  251. * @param collection - collection name
  252. */
  253. constructor(db, collection) {
  254. this.db = db;
  255. this.collection = collection === '' ? undefined : collection;
  256. }
  257. toString() {
  258. return this.collection ? `${this.db}.${this.collection}` : this.db;
  259. }
  260. withCollection(collection) {
  261. return new MongoDBCollectionNamespace(this.db, collection);
  262. }
  263. static fromString(namespace) {
  264. if (typeof namespace !== 'string' || namespace === '') {
  265. // TODO(NODE-3483): Replace with MongoNamespaceError
  266. throw new error_1.MongoRuntimeError(`Cannot parse namespace from "${namespace}"`);
  267. }
  268. const [db, ...collectionParts] = namespace.split('.');
  269. const collection = collectionParts.join('.');
  270. return new MongoDBNamespace(db, collection === '' ? undefined : collection);
  271. }
  272. }
  273. exports.MongoDBNamespace = MongoDBNamespace;
  274. /**
  275. * @public
  276. *
  277. * A class representing a collection's namespace. This class enforces (through Typescript) that
  278. * the `collection` portion of the namespace is defined and should only be
  279. * used in scenarios where this can be guaranteed.
  280. */
  281. class MongoDBCollectionNamespace extends MongoDBNamespace {
  282. constructor(db, collection) {
  283. super(db, collection);
  284. this.collection = collection;
  285. }
  286. static fromString(namespace) {
  287. return super.fromString(namespace);
  288. }
  289. }
  290. exports.MongoDBCollectionNamespace = MongoDBCollectionNamespace;
  291. /** @internal */
  292. function* makeCounter(seed = 0) {
  293. let count = seed;
  294. while (true) {
  295. const newCount = count;
  296. count += 1;
  297. yield newCount;
  298. }
  299. }
  300. /**
  301. * Synchronously Generate a UUIDv4
  302. * @internal
  303. */
  304. function uuidV4() {
  305. const result = crypto.randomBytes(16);
  306. result[6] = (result[6] & 0x0f) | 0x40;
  307. result[8] = (result[8] & 0x3f) | 0x80;
  308. return result;
  309. }
  310. /**
  311. * A helper function for determining `maxWireVersion` between legacy and new topology instances
  312. * @internal
  313. */
  314. function maxWireVersion(handshakeAware) {
  315. if (handshakeAware) {
  316. if (handshakeAware.hello) {
  317. return handshakeAware.hello.maxWireVersion;
  318. }
  319. if (handshakeAware.serverApi?.version) {
  320. // We return the max supported wire version for serverAPI.
  321. return constants_1.MAX_SUPPORTED_WIRE_VERSION;
  322. }
  323. // This is the fallback case for load balanced mode. If we are building commands the
  324. // object being checked will be a connection, and we will have a hello response on
  325. // it. For other cases, such as retryable writes, the object will be a server or
  326. // topology, and there will be no hello response on those objects, so we return
  327. // the max wire version so we support retryability. Once we have a min supported
  328. // wire version of 9, then the needsRetryableWriteLabel() check can remove the
  329. // usage of passing the wire version into it.
  330. if (handshakeAware.loadBalanced) {
  331. return constants_1.MAX_SUPPORTED_WIRE_VERSION;
  332. }
  333. if ('lastHello' in handshakeAware && typeof handshakeAware.lastHello === 'function') {
  334. const lastHello = handshakeAware.lastHello();
  335. if (lastHello) {
  336. return lastHello.maxWireVersion;
  337. }
  338. }
  339. if (handshakeAware.description &&
  340. 'maxWireVersion' in handshakeAware.description &&
  341. handshakeAware.description.maxWireVersion != null) {
  342. return handshakeAware.description.maxWireVersion;
  343. }
  344. }
  345. return 0;
  346. }
  347. /** @internal */
  348. function arrayStrictEqual(arr, arr2) {
  349. if (!Array.isArray(arr) || !Array.isArray(arr2)) {
  350. return false;
  351. }
  352. return arr.length === arr2.length && arr.every((elt, idx) => elt === arr2[idx]);
  353. }
  354. /** @internal */
  355. function errorStrictEqual(lhs, rhs) {
  356. if (lhs === rhs) {
  357. return true;
  358. }
  359. if (!lhs || !rhs) {
  360. return lhs === rhs;
  361. }
  362. if ((lhs == null && rhs != null) || (lhs != null && rhs == null)) {
  363. return false;
  364. }
  365. if (lhs.constructor.name !== rhs.constructor.name) {
  366. return false;
  367. }
  368. if (lhs.message !== rhs.message) {
  369. return false;
  370. }
  371. return true;
  372. }
  373. /** @internal */
  374. function makeStateMachine(stateTable) {
  375. return function stateTransition(target, newState) {
  376. const legalStates = stateTable[target.s.state];
  377. if (legalStates && legalStates.indexOf(newState) < 0) {
  378. throw new error_1.MongoRuntimeError(`illegal state transition from [${target.s.state}] => [${newState}], allowed: [${legalStates}]`);
  379. }
  380. target.emit('stateChanged', target.s.state, newState);
  381. target.s.state = newState;
  382. };
  383. }
  384. /** @internal */
  385. function now() {
  386. const hrtime = process.hrtime();
  387. return Math.floor(hrtime[0] * 1000 + hrtime[1] / 1000000);
  388. }
  389. /** @internal */
  390. function calculateDurationInMs(started) {
  391. if (typeof started !== 'number') {
  392. return -1;
  393. }
  394. const elapsed = now() - started;
  395. return elapsed < 0 ? 0 : elapsed;
  396. }
  397. /** @internal */
  398. function hasAtomicOperators(doc, options) {
  399. if (Array.isArray(doc)) {
  400. for (const document of doc) {
  401. if (hasAtomicOperators(document)) {
  402. return true;
  403. }
  404. }
  405. return false;
  406. }
  407. const keys = Object.keys(doc);
  408. // In this case we need to throw if all the atomic operators are undefined.
  409. if (options?.ignoreUndefined) {
  410. let allUndefined = true;
  411. for (const key of keys) {
  412. // eslint-disable-next-line no-restricted-syntax
  413. if (doc[key] !== undefined) {
  414. allUndefined = false;
  415. break;
  416. }
  417. }
  418. if (allUndefined) {
  419. throw new error_1.MongoInvalidArgumentError('Update operations require that all atomic operators have defined values, but none were provided.');
  420. }
  421. }
  422. return keys.length > 0 && keys[0][0] === '$';
  423. }
  424. function resolveTimeoutOptions(client, options) {
  425. const { socketTimeoutMS, serverSelectionTimeoutMS, waitQueueTimeoutMS, timeoutMS } = client.s.options;
  426. return { socketTimeoutMS, serverSelectionTimeoutMS, waitQueueTimeoutMS, timeoutMS, ...options };
  427. }
  428. /**
  429. * Merge inherited properties from parent into options, prioritizing values from options,
  430. * then values from parent.
  431. *
  432. * @param parent - An optional owning class of the operation being run. ex. Db/Collection/MongoClient.
  433. * @param options - The options passed to the operation method.
  434. *
  435. * @internal
  436. */
  437. function resolveOptions(parent, options) {
  438. const result = Object.assign({}, options, (0, bson_1.resolveBSONOptions)(options, parent));
  439. const timeoutMS = options?.timeoutMS ?? parent?.timeoutMS;
  440. // Users cannot pass a readConcern/writeConcern to operations in a transaction
  441. const session = options?.session;
  442. if (!session?.inTransaction()) {
  443. const readConcern = read_concern_1.ReadConcern.fromOptions(options) ?? parent?.readConcern;
  444. if (readConcern) {
  445. result.readConcern = readConcern;
  446. }
  447. let writeConcern = write_concern_1.WriteConcern.fromOptions(options) ?? parent?.writeConcern;
  448. if (writeConcern) {
  449. if (timeoutMS != null) {
  450. writeConcern = write_concern_1.WriteConcern.fromOptions({
  451. writeConcern: {
  452. ...writeConcern,
  453. wtimeout: undefined,
  454. wtimeoutMS: undefined
  455. }
  456. });
  457. }
  458. result.writeConcern = writeConcern;
  459. }
  460. }
  461. result.timeoutMS = timeoutMS;
  462. const readPreference = read_preference_1.ReadPreference.fromOptions(options) ?? parent?.readPreference;
  463. if (readPreference) {
  464. result.readPreference = readPreference;
  465. }
  466. const isConvenientTransaction = session?.explicit && session?.timeoutContext != null;
  467. if (isConvenientTransaction && options?.timeoutMS != null) {
  468. throw new error_1.MongoInvalidArgumentError('An operation cannot be given a timeoutMS setting when inside a withTransaction call that has a timeoutMS setting');
  469. }
  470. return result;
  471. }
  472. function isSuperset(set, subset) {
  473. set = Array.isArray(set) ? new Set(set) : set;
  474. subset = Array.isArray(subset) ? new Set(subset) : subset;
  475. for (const elem of subset) {
  476. if (!set.has(elem)) {
  477. return false;
  478. }
  479. }
  480. return true;
  481. }
  482. /**
  483. * Checks if the document is a Hello request
  484. * @internal
  485. */
  486. function isHello(doc) {
  487. return doc[constants_2.LEGACY_HELLO_COMMAND] || doc.hello ? true : false;
  488. }
  489. /** Returns the items that are uniquely in setA */
  490. function setDifference(setA, setB) {
  491. const difference = new Set(setA);
  492. for (const elem of setB) {
  493. difference.delete(elem);
  494. }
  495. return difference;
  496. }
  497. const HAS_OWN = (object, prop) => Object.prototype.hasOwnProperty.call(object, prop);
  498. function isRecord(value, requiredKeys = undefined) {
  499. if (!isObject(value)) {
  500. return false;
  501. }
  502. const ctor = value.constructor;
  503. if (ctor && ctor.prototype) {
  504. if (!isObject(ctor.prototype)) {
  505. return false;
  506. }
  507. // Check to see if some method exists from the Object exists
  508. if (!HAS_OWN(ctor.prototype, 'isPrototypeOf')) {
  509. return false;
  510. }
  511. }
  512. if (requiredKeys) {
  513. const keys = Object.keys(value);
  514. return isSuperset(keys, requiredKeys);
  515. }
  516. return true;
  517. }
  518. /**
  519. * A sequential list of items in a circularly linked list
  520. * @remarks
  521. * The head node is special, it is always defined and has a value of null.
  522. * It is never "included" in the list, in that, it is not returned by pop/shift or yielded by the iterator.
  523. * The circular linkage and always defined head node are to reduce checks for null next/prev references to zero.
  524. * New nodes are declared as object literals with keys always in the same order: next, prev, value.
  525. * @internal
  526. */
  527. class List {
  528. get length() {
  529. return this.count;
  530. }
  531. get [Symbol.toStringTag]() {
  532. return 'List';
  533. }
  534. constructor() {
  535. this.count = 0;
  536. // this is carefully crafted:
  537. // declaring a complete and consistently key ordered
  538. // object is beneficial to the runtime optimizations
  539. this.head = {
  540. next: null,
  541. prev: null,
  542. value: null
  543. };
  544. this.head.next = this.head;
  545. this.head.prev = this.head;
  546. }
  547. toArray() {
  548. return Array.from(this);
  549. }
  550. toString() {
  551. return `head <=> ${this.toArray().join(' <=> ')} <=> head`;
  552. }
  553. *[Symbol.iterator]() {
  554. for (const node of this.nodes()) {
  555. yield node.value;
  556. }
  557. }
  558. *nodes() {
  559. let ptr = this.head.next;
  560. while (ptr !== this.head) {
  561. // Save next before yielding so that we make removing within iteration safe
  562. const { next } = ptr;
  563. yield ptr;
  564. ptr = next;
  565. }
  566. }
  567. /** Insert at end of list */
  568. push(value) {
  569. this.count += 1;
  570. const newNode = {
  571. next: this.head,
  572. prev: this.head.prev,
  573. value
  574. };
  575. this.head.prev.next = newNode;
  576. this.head.prev = newNode;
  577. }
  578. /** Inserts every item inside an iterable instead of the iterable itself */
  579. pushMany(iterable) {
  580. for (const value of iterable) {
  581. this.push(value);
  582. }
  583. }
  584. /** Insert at front of list */
  585. unshift(value) {
  586. this.count += 1;
  587. const newNode = {
  588. next: this.head.next,
  589. prev: this.head,
  590. value
  591. };
  592. this.head.next.prev = newNode;
  593. this.head.next = newNode;
  594. }
  595. remove(node) {
  596. if (node === this.head || this.length === 0) {
  597. return null;
  598. }
  599. this.count -= 1;
  600. const prevNode = node.prev;
  601. const nextNode = node.next;
  602. prevNode.next = nextNode;
  603. nextNode.prev = prevNode;
  604. return node.value;
  605. }
  606. /** Removes the first node at the front of the list */
  607. shift() {
  608. return this.remove(this.head.next);
  609. }
  610. /** Removes the last node at the end of the list */
  611. pop() {
  612. return this.remove(this.head.prev);
  613. }
  614. /** Iterates through the list and removes nodes where filter returns true */
  615. prune(filter) {
  616. for (const node of this.nodes()) {
  617. if (filter(node.value)) {
  618. this.remove(node);
  619. }
  620. }
  621. }
  622. clear() {
  623. this.count = 0;
  624. this.head.next = this.head;
  625. this.head.prev = this.head;
  626. }
  627. /** Returns the first item in the list, does not remove */
  628. first() {
  629. // If the list is empty, value will be the head's null
  630. return this.head.next.value;
  631. }
  632. /** Returns the last item in the list, does not remove */
  633. last() {
  634. // If the list is empty, value will be the head's null
  635. return this.head.prev.value;
  636. }
  637. }
  638. exports.List = List;
  639. /**
  640. * A pool of Buffers which allow you to read them as if they were one
  641. * @internal
  642. */
  643. class BufferPool {
  644. constructor() {
  645. this.buffers = new List();
  646. this.totalByteLength = 0;
  647. }
  648. get length() {
  649. return this.totalByteLength;
  650. }
  651. /** Adds a buffer to the internal buffer pool list */
  652. append(buffer) {
  653. this.buffers.push(buffer);
  654. this.totalByteLength += buffer.length;
  655. }
  656. /**
  657. * If BufferPool contains 4 bytes or more construct an int32 from the leading bytes,
  658. * otherwise return null. Size can be negative, caller should error check.
  659. */
  660. getInt32() {
  661. if (this.totalByteLength < 4) {
  662. return null;
  663. }
  664. const firstBuffer = this.buffers.first();
  665. if (firstBuffer != null && firstBuffer.byteLength >= 4) {
  666. return firstBuffer.readInt32LE(0);
  667. }
  668. // Unlikely case: an int32 is split across buffers.
  669. // Use read and put the returned buffer back on top
  670. const top4Bytes = this.read(4);
  671. const value = top4Bytes.readInt32LE(0);
  672. // Put it back.
  673. this.totalByteLength += 4;
  674. this.buffers.unshift(top4Bytes);
  675. return value;
  676. }
  677. /** Reads the requested number of bytes, optionally consuming them */
  678. read(size) {
  679. if (typeof size !== 'number' || size < 0) {
  680. throw new error_1.MongoInvalidArgumentError('Argument "size" must be a non-negative number');
  681. }
  682. // oversized request returns empty buffer
  683. if (size > this.totalByteLength) {
  684. return Buffer.alloc(0);
  685. }
  686. // We know we have enough, we just don't know how it is spread across chunks
  687. // TODO(NODE-4732): alloc API should change based on raw option
  688. const result = Buffer.allocUnsafe(size);
  689. for (let bytesRead = 0; bytesRead < size;) {
  690. const buffer = this.buffers.shift();
  691. if (buffer == null) {
  692. break;
  693. }
  694. const bytesRemaining = size - bytesRead;
  695. const bytesReadable = Math.min(bytesRemaining, buffer.byteLength);
  696. const bytes = buffer.subarray(0, bytesReadable);
  697. result.set(bytes, bytesRead);
  698. bytesRead += bytesReadable;
  699. this.totalByteLength -= bytesReadable;
  700. if (bytesReadable < buffer.byteLength) {
  701. this.buffers.unshift(buffer.subarray(bytesReadable));
  702. }
  703. }
  704. return result;
  705. }
  706. }
  707. exports.BufferPool = BufferPool;
  708. /** @public */
  709. class HostAddress {
  710. constructor(hostString) {
  711. this.host = undefined;
  712. this.port = undefined;
  713. this.socketPath = undefined;
  714. this.isIPv6 = false;
  715. const escapedHost = hostString.split(' ').join('%20'); // escape spaces, for socket path hosts
  716. if (escapedHost.endsWith('.sock')) {
  717. // heuristically determine if we're working with a domain socket
  718. this.socketPath = decodeURIComponent(escapedHost);
  719. return;
  720. }
  721. const urlString = `iLoveJS://${escapedHost}`;
  722. let url;
  723. try {
  724. url = new url_1.URL(urlString);
  725. }
  726. catch (urlError) {
  727. const runtimeError = new error_1.MongoRuntimeError(`Unable to parse ${escapedHost} with URL`);
  728. runtimeError.cause = urlError;
  729. throw runtimeError;
  730. }
  731. const hostname = url.hostname;
  732. const port = url.port;
  733. let normalized = decodeURIComponent(hostname).toLowerCase();
  734. if (normalized.startsWith('[') && normalized.endsWith(']')) {
  735. this.isIPv6 = true;
  736. normalized = normalized.substring(1, hostname.length - 1);
  737. }
  738. this.host = normalized.toLowerCase();
  739. if (typeof port === 'number') {
  740. this.port = port;
  741. }
  742. else if (typeof port === 'string' && port !== '') {
  743. this.port = Number.parseInt(port, 10);
  744. }
  745. else {
  746. this.port = 27017;
  747. }
  748. if (this.port === 0) {
  749. throw new error_1.MongoParseError('Invalid port (zero) with hostname');
  750. }
  751. Object.freeze(this);
  752. }
  753. [Symbol.for('nodejs.util.inspect.custom')]() {
  754. return this.inspect();
  755. }
  756. inspect() {
  757. return `new HostAddress('${this.toString()}')`;
  758. }
  759. toString() {
  760. if (typeof this.host === 'string') {
  761. if (this.isIPv6) {
  762. return `[${this.host}]:${this.port}`;
  763. }
  764. return `${this.host}:${this.port}`;
  765. }
  766. return `${this.socketPath}`;
  767. }
  768. static fromString(s) {
  769. return new HostAddress(s);
  770. }
  771. static fromHostPort(host, port) {
  772. if (host.includes(':')) {
  773. host = `[${host}]`; // IPv6 address
  774. }
  775. return HostAddress.fromString(`${host}:${port}`);
  776. }
  777. static fromSrvRecord({ name, port }) {
  778. return HostAddress.fromHostPort(name, port);
  779. }
  780. toHostPort() {
  781. if (this.socketPath) {
  782. return { host: this.socketPath, port: 0 };
  783. }
  784. const host = this.host ?? '';
  785. const port = this.port ?? 0;
  786. return { host, port };
  787. }
  788. }
  789. exports.HostAddress = HostAddress;
  790. exports.DEFAULT_PK_FACTORY = {
  791. // We prefer not to rely on ObjectId having a createPk method
  792. createPk() {
  793. return new bson_1.ObjectId();
  794. }
  795. };
  796. /**
  797. * When the driver used emitWarning the code will be equal to this.
  798. * @public
  799. *
  800. * @example
  801. * ```ts
  802. * process.on('warning', (warning) => {
  803. * if (warning.code === MONGODB_WARNING_CODE) console.error('Ah an important warning! :)')
  804. * })
  805. * ```
  806. */
  807. exports.MONGODB_WARNING_CODE = 'MONGODB DRIVER';
  808. /** @internal */
  809. function emitWarning(message) {
  810. return process.emitWarning(message, { code: exports.MONGODB_WARNING_CODE });
  811. }
  812. const emittedWarnings = new Set();
  813. /**
  814. * Will emit a warning once for the duration of the application.
  815. * Uses the message to identify if it has already been emitted
  816. * so using string interpolation can cause multiple emits
  817. * @internal
  818. */
  819. function emitWarningOnce(message) {
  820. if (!emittedWarnings.has(message)) {
  821. emittedWarnings.add(message);
  822. return emitWarning(message);
  823. }
  824. }
  825. /**
  826. * Takes a JS object and joins the values into a string separated by ', '
  827. */
  828. function enumToString(en) {
  829. return Object.values(en).join(', ');
  830. }
  831. /**
  832. * Determine if a server supports retryable writes.
  833. *
  834. * @internal
  835. */
  836. function supportsRetryableWrites(server) {
  837. if (!server) {
  838. return false;
  839. }
  840. if (server.loadBalanced) {
  841. // Loadbalanced topologies will always support retry writes
  842. return true;
  843. }
  844. if (server.description.logicalSessionTimeoutMinutes != null) {
  845. // that supports sessions
  846. if (server.description.type !== common_1.ServerType.Standalone) {
  847. // and that is not a standalone
  848. return true;
  849. }
  850. }
  851. return false;
  852. }
  853. /**
  854. * Fisher–Yates Shuffle
  855. *
  856. * Reference: https://bost.ocks.org/mike/shuffle/
  857. * @param sequence - items to be shuffled
  858. * @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.
  859. */
  860. function shuffle(sequence, limit = 0) {
  861. const items = Array.from(sequence); // shallow copy in order to never shuffle the input
  862. if (limit > items.length) {
  863. throw new error_1.MongoRuntimeError('Limit must be less than the number of items');
  864. }
  865. let remainingItemsToShuffle = items.length;
  866. const lowerBound = limit % items.length === 0 ? 1 : items.length - limit;
  867. while (remainingItemsToShuffle > lowerBound) {
  868. // Pick a remaining element
  869. const randomIndex = Math.floor(Math.random() * remainingItemsToShuffle);
  870. remainingItemsToShuffle -= 1;
  871. // And swap it with the current element
  872. const swapHold = items[remainingItemsToShuffle];
  873. items[remainingItemsToShuffle] = items[randomIndex];
  874. items[randomIndex] = swapHold;
  875. }
  876. return limit % items.length === 0 ? items : items.slice(lowerBound);
  877. }
  878. /**
  879. * TODO(NODE-4936): read concern eligibility for commands should be codified in command construction
  880. * @internal
  881. * @see https://github.com/mongodb/specifications/blob/master/source/read-write-concern/read-write-concern.md#read-concern
  882. */
  883. function commandSupportsReadConcern(command) {
  884. if (command.aggregate || command.count || command.distinct || command.find || command.geoNear) {
  885. return true;
  886. }
  887. return false;
  888. }
  889. /**
  890. * Compare objectIds. `null` is always less
  891. * - `+1 = oid1 is greater than oid2`
  892. * - `-1 = oid1 is less than oid2`
  893. * - `+0 = oid1 is equal oid2`
  894. */
  895. function compareObjectId(oid1, oid2) {
  896. if (oid1 == null && oid2 == null) {
  897. return 0;
  898. }
  899. if (oid1 == null) {
  900. return -1;
  901. }
  902. if (oid2 == null) {
  903. return 1;
  904. }
  905. return exports.ByteUtils.compare(oid1.id, oid2.id);
  906. }
  907. function parseInteger(value) {
  908. if (typeof value === 'number')
  909. return Math.trunc(value);
  910. const parsedValue = Number.parseInt(String(value), 10);
  911. return Number.isNaN(parsedValue) ? null : parsedValue;
  912. }
  913. function parseUnsignedInteger(value) {
  914. const parsedInt = parseInteger(value);
  915. return parsedInt != null && parsedInt >= 0 ? parsedInt : null;
  916. }
  917. /**
  918. * This function throws a MongoAPIError in the event that either of the following is true:
  919. * * If the provided address domain does not match the provided parent domain
  920. * * If the parent domain contains less than three `.` separated parts and the provided address does not contain at least one more domain level than its parent
  921. *
  922. * If a DNS server were to become compromised SRV records would still need to
  923. * advertise addresses that are under the same domain as the srvHost.
  924. *
  925. * @param address - The address to check against a domain
  926. * @param srvHost - The domain to check the provided address against
  927. * @returns void
  928. */
  929. function checkParentDomainMatch(address, srvHost) {
  930. // Remove trailing dot if exists on either the resolved address or the srv hostname
  931. const normalizedAddress = address.endsWith('.') ? address.slice(0, address.length - 1) : address;
  932. const normalizedSrvHost = srvHost.endsWith('.') ? srvHost.slice(0, srvHost.length - 1) : srvHost;
  933. const allCharacterBeforeFirstDot = /^.*?\./;
  934. const srvIsLessThanThreeParts = normalizedSrvHost.split('.').length < 3;
  935. // Remove all characters before first dot
  936. // Add leading dot back to string so
  937. // an srvHostDomain = '.trusted.site'
  938. // will not satisfy an addressDomain that endsWith '.fake-trusted.site'
  939. const addressDomain = `.${normalizedAddress.replace(allCharacterBeforeFirstDot, '')}`;
  940. let srvHostDomain = srvIsLessThanThreeParts
  941. ? normalizedSrvHost
  942. : `.${normalizedSrvHost.replace(allCharacterBeforeFirstDot, '')}`;
  943. if (!srvHostDomain.startsWith('.')) {
  944. srvHostDomain = '.' + srvHostDomain;
  945. }
  946. if (srvIsLessThanThreeParts &&
  947. normalizedAddress.split('.').length <= normalizedSrvHost.split('.').length) {
  948. throw new error_1.MongoAPIError('Server record does not have at least one more domain level than parent URI');
  949. }
  950. if (!addressDomain.endsWith(srvHostDomain)) {
  951. throw new error_1.MongoAPIError('Server record does not share hostname with parent URI');
  952. }
  953. }
  954. /**
  955. * Perform a get request that returns status and body.
  956. * @internal
  957. */
  958. function get(url, options = {}) {
  959. return new Promise((resolve, reject) => {
  960. /* eslint-disable prefer-const */
  961. let timeoutId;
  962. const request = http
  963. .get(url, options, response => {
  964. response.setEncoding('utf8');
  965. let body = '';
  966. response.on('data', chunk => (body += chunk));
  967. response.on('end', () => {
  968. (0, timers_1.clearTimeout)(timeoutId);
  969. resolve({ status: response.statusCode, body });
  970. });
  971. })
  972. .on('error', error => {
  973. (0, timers_1.clearTimeout)(timeoutId);
  974. reject(error);
  975. })
  976. .end();
  977. timeoutId = (0, timers_1.setTimeout)(() => {
  978. request.destroy(new error_1.MongoNetworkTimeoutError(`request timed out after 10 seconds`));
  979. }, 10000);
  980. });
  981. }
  982. async function request(uri, options = {}) {
  983. return await new Promise((resolve, reject) => {
  984. const requestOptions = {
  985. method: 'GET',
  986. timeout: 10000,
  987. json: true,
  988. ...url.parse(uri),
  989. ...options
  990. };
  991. const req = http.request(requestOptions, res => {
  992. res.setEncoding('utf8');
  993. let data = '';
  994. res.on('data', d => {
  995. data += d;
  996. });
  997. res.once('end', () => {
  998. if (options.json === false) {
  999. resolve(data);
  1000. return;
  1001. }
  1002. try {
  1003. const parsed = JSON.parse(data);
  1004. resolve(parsed);
  1005. }
  1006. catch {
  1007. // TODO(NODE-3483)
  1008. reject(new error_1.MongoRuntimeError(`Invalid JSON response: "${data}"`));
  1009. }
  1010. });
  1011. });
  1012. req.once('timeout', () => req.destroy(new error_1.MongoNetworkTimeoutError(`Network request to ${uri} timed out after ${options.timeout} ms`)));
  1013. req.once('error', error => reject(error));
  1014. req.end();
  1015. });
  1016. }
  1017. /** @internal */
  1018. exports.DOCUMENT_DB_CHECK = /(\.docdb\.amazonaws\.com$)|(\.docdb-elastic\.amazonaws\.com$)/;
  1019. /** @internal */
  1020. exports.COSMOS_DB_CHECK = /\.cosmos\.azure\.com$/;
  1021. /** @internal */
  1022. exports.DOCUMENT_DB_MSG = 'You appear to be connected to a DocumentDB cluster. For more information regarding feature compatibility and support please visit https://www.mongodb.com/supportability/documentdb';
  1023. /** @internal */
  1024. exports.COSMOS_DB_MSG = 'You appear to be connected to a CosmosDB cluster. For more information regarding feature compatibility and support please visit https://www.mongodb.com/supportability/cosmosdb';
  1025. /** @internal */
  1026. function isHostMatch(match, host) {
  1027. return host && match.test(host.toLowerCase()) ? true : false;
  1028. }
  1029. function promiseWithResolvers() {
  1030. let resolve;
  1031. let reject;
  1032. const promise = new Promise(function withResolversExecutor(promiseResolve, promiseReject) {
  1033. resolve = promiseResolve;
  1034. reject = promiseReject;
  1035. });
  1036. return { promise, resolve, reject };
  1037. }
  1038. /**
  1039. * A noop function intended for use in preventing unhandled rejections.
  1040. *
  1041. * @example
  1042. * ```js
  1043. * const promise = myAsyncTask();
  1044. * // eslint-disable-next-line github/no-then
  1045. * promise.then(undefined, squashError);
  1046. * ```
  1047. */
  1048. function squashError(_error) {
  1049. return;
  1050. }
  1051. exports.randomBytes = (0, util_1.promisify)(crypto.randomBytes);
  1052. /**
  1053. * Replicates the events.once helper.
  1054. *
  1055. * Removes unused signal logic and It **only** supports 0 or 1 argument events.
  1056. *
  1057. * @param ee - An event emitter that may emit `ev`
  1058. * @param name - An event name to wait for
  1059. */
  1060. async function once(ee, name, options) {
  1061. options?.signal?.throwIfAborted();
  1062. const { promise, resolve, reject } = promiseWithResolvers();
  1063. const onEvent = (data) => resolve(data);
  1064. const onError = (error) => reject(error);
  1065. const abortListener = addAbortListener(options?.signal, function () {
  1066. reject(this.reason);
  1067. });
  1068. ee.once(name, onEvent).once('error', onError);
  1069. try {
  1070. return await promise;
  1071. }
  1072. finally {
  1073. ee.off(name, onEvent);
  1074. ee.off('error', onError);
  1075. abortListener?.[exports.kDispose]();
  1076. }
  1077. }
  1078. function maybeAddIdToDocuments(collection, document, options) {
  1079. const forceServerObjectId = options.forceServerObjectId ?? collection.db.options?.forceServerObjectId ?? false;
  1080. // no need to modify the docs if server sets the ObjectId
  1081. if (forceServerObjectId) {
  1082. return document;
  1083. }
  1084. if (document._id == null) {
  1085. document._id = collection.s.pkFactory.createPk();
  1086. }
  1087. return document;
  1088. }
  1089. async function fileIsAccessible(fileName, mode) {
  1090. try {
  1091. await fs_1.promises.access(fileName, mode);
  1092. return true;
  1093. }
  1094. catch {
  1095. return false;
  1096. }
  1097. }
  1098. function csotMin(duration1, duration2) {
  1099. if (duration1 === 0)
  1100. return duration2;
  1101. if (duration2 === 0)
  1102. return duration1;
  1103. return Math.min(duration1, duration2);
  1104. }
  1105. function noop() {
  1106. return;
  1107. }
  1108. /**
  1109. * Recurse through the (identically-shaped) `decrypted` and `original`
  1110. * objects and attach a `decryptedKeys` property on each sub-object that
  1111. * contained encrypted fields. Because we only call this on BSON responses,
  1112. * we do not need to worry about circular references.
  1113. *
  1114. * @internal
  1115. */
  1116. function decorateDecryptionResult(decrypted, original, isTopLevelDecorateCall = true) {
  1117. if (isTopLevelDecorateCall) {
  1118. // The original value could have been either a JS object or a BSON buffer
  1119. if (Buffer.isBuffer(original)) {
  1120. original = (0, bson_1.deserialize)(original);
  1121. }
  1122. if (Buffer.isBuffer(decrypted)) {
  1123. throw new error_1.MongoRuntimeError('Expected result of decryption to be deserialized BSON object');
  1124. }
  1125. }
  1126. if (!decrypted || typeof decrypted !== 'object')
  1127. return;
  1128. for (const k of Object.keys(decrypted)) {
  1129. const originalValue = original[k];
  1130. // An object was decrypted by libmongocrypt if and only if it was
  1131. // a BSON Binary object with subtype 6.
  1132. if (originalValue && originalValue._bsontype === 'Binary' && originalValue.sub_type === 6) {
  1133. if (!decrypted[constants_2.kDecoratedKeys]) {
  1134. Object.defineProperty(decrypted, constants_2.kDecoratedKeys, {
  1135. value: [],
  1136. configurable: true,
  1137. enumerable: false,
  1138. writable: false
  1139. });
  1140. }
  1141. // this is defined in the preceding if-statement
  1142. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  1143. decrypted[constants_2.kDecoratedKeys].push(k);
  1144. // Do not recurse into this decrypted value. It could be a sub-document/array,
  1145. // in which case there is no original value associated with its subfields.
  1146. continue;
  1147. }
  1148. decorateDecryptionResult(decrypted[k], originalValue, false);
  1149. }
  1150. }
  1151. /** @internal */
  1152. exports.kDispose = Symbol.dispose ?? Symbol('dispose');
  1153. /**
  1154. * A utility that helps with writing listener code idiomatically
  1155. *
  1156. * @example
  1157. * ```js
  1158. * using listener = addAbortListener(signal, function () {
  1159. * console.log('aborted', this.reason);
  1160. * });
  1161. * ```
  1162. *
  1163. * @param signal - if exists adds an abort listener
  1164. * @param listener - the listener to be added to signal
  1165. * @returns A disposable that will remove the abort listener
  1166. */
  1167. function addAbortListener(signal, listener) {
  1168. if (signal == null)
  1169. return;
  1170. signal.addEventListener('abort', listener, { once: true });
  1171. return { [exports.kDispose]: () => signal.removeEventListener('abort', listener) };
  1172. }
  1173. /**
  1174. * Takes a promise and races it with a promise wrapping the abort event of the optionally provided signal.
  1175. * The given promise is _always_ ordered before the signal's abort promise.
  1176. * When given an already rejected promise and an already aborted signal, the promise's rejection takes precedence.
  1177. *
  1178. * Any asynchronous processing in `promise` will continue even after the abort signal has fired,
  1179. * but control will be returned to the caller
  1180. *
  1181. * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race
  1182. *
  1183. * @param promise - A promise to discard if the signal aborts
  1184. * @param options - An options object carrying an optional signal
  1185. */
  1186. async function abortable(promise, { signal }) {
  1187. if (signal == null) {
  1188. return await promise;
  1189. }
  1190. const { promise: aborted, reject } = promiseWithResolvers();
  1191. const abortListener = signal.aborted
  1192. ? reject(signal.reason)
  1193. : addAbortListener(signal, function () {
  1194. reject(this.reason);
  1195. });
  1196. try {
  1197. return await Promise.race([promise, aborted]);
  1198. }
  1199. finally {
  1200. abortListener?.[exports.kDispose]();
  1201. }
  1202. }
  1203. //# sourceMappingURL=utils.js.map