utils.js 41 KB

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