utils.js 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.TimeoutController = exports.request = exports.matchesParentDomain = exports.parseUnsignedInteger = exports.parseInteger = exports.compareObjectId = exports.commandSupportsReadConcern = exports.shuffle = exports.supportsRetryableWrites = exports.enumToString = exports.emitWarningOnce = exports.emitWarning = exports.MONGODB_WARNING_CODE = exports.DEFAULT_PK_FACTORY = exports.HostAddress = exports.BufferPool = exports.List = exports.deepCopy = exports.isRecord = exports.setDifference = exports.isHello = exports.isSuperset = exports.resolveOptions = exports.hasAtomicOperators = exports.calculateDurationInMs = exports.now = exports.makeStateMachine = exports.errorStrictEqual = exports.arrayStrictEqual = exports.eachAsync = exports.maxWireVersion = exports.uuidV4 = exports.maybeCallback = exports.makeCounter = exports.MongoDBCollectionNamespace = exports.MongoDBNamespace = exports.ns = exports.getTopology = exports.decorateWithExplain = exports.decorateWithReadConcern = exports.decorateWithCollation = exports.isPromiseLike = exports.applyRetryableWrites = exports.filterOptions = exports.mergeOptions = exports.isObject = exports.normalizeHintField = exports.hostMatchesWildcards = exports.ByteUtils = void 0;
  4. const crypto = require("crypto");
  5. const http = require("http");
  6. const timers_1 = require("timers");
  7. const url = require("url");
  8. const url_1 = require("url");
  9. const bson_1 = require("./bson");
  10. const constants_1 = require("./cmap/wire_protocol/constants");
  11. const constants_2 = require("./constants");
  12. const error_1 = require("./error");
  13. const read_concern_1 = require("./read_concern");
  14. const read_preference_1 = require("./read_preference");
  15. const common_1 = require("./sdam/common");
  16. const write_concern_1 = require("./write_concern");
  17. exports.ByteUtils = {
  18. toLocalBufferType(buffer) {
  19. return Buffer.isBuffer(buffer)
  20. ? buffer
  21. : Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength);
  22. },
  23. equals(seqA, seqB) {
  24. return exports.ByteUtils.toLocalBufferType(seqA).equals(seqB);
  25. },
  26. compare(seqA, seqB) {
  27. return exports.ByteUtils.toLocalBufferType(seqA).compare(seqB);
  28. },
  29. toBase64(uint8array) {
  30. return exports.ByteUtils.toLocalBufferType(uint8array).toString('base64');
  31. }
  32. };
  33. /**
  34. * Determines if a connection's address matches a user provided list
  35. * of domain wildcards.
  36. */
  37. function hostMatchesWildcards(host, wildcards) {
  38. for (const wildcard of wildcards) {
  39. if (host === wildcard ||
  40. (wildcard.startsWith('*.') && host?.endsWith(wildcard.substring(2, wildcard.length))) ||
  41. (wildcard.startsWith('*/') && host?.endsWith(wildcard.substring(2, wildcard.length)))) {
  42. return true;
  43. }
  44. }
  45. return false;
  46. }
  47. exports.hostMatchesWildcards = hostMatchesWildcards;
  48. /**
  49. * Ensure Hint field is in a shape we expect:
  50. * - object of index names mapping to 1 or -1
  51. * - just an index name
  52. * @internal
  53. */
  54. function normalizeHintField(hint) {
  55. let finalHint = undefined;
  56. if (typeof hint === 'string') {
  57. finalHint = hint;
  58. }
  59. else if (Array.isArray(hint)) {
  60. finalHint = {};
  61. hint.forEach(param => {
  62. finalHint[param] = 1;
  63. });
  64. }
  65. else if (hint != null && typeof hint === 'object') {
  66. finalHint = {};
  67. for (const name in hint) {
  68. finalHint[name] = hint[name];
  69. }
  70. }
  71. return finalHint;
  72. }
  73. exports.normalizeHintField = normalizeHintField;
  74. const TO_STRING = (object) => Object.prototype.toString.call(object);
  75. /**
  76. * Checks if arg is an Object:
  77. * - **NOTE**: the check is based on the `[Symbol.toStringTag]() === 'Object'`
  78. * @internal
  79. */
  80. function isObject(arg) {
  81. return '[object Object]' === TO_STRING(arg);
  82. }
  83. exports.isObject = isObject;
  84. /** @internal */
  85. function mergeOptions(target, source) {
  86. return { ...target, ...source };
  87. }
  88. exports.mergeOptions = mergeOptions;
  89. /** @internal */
  90. function filterOptions(options, names) {
  91. const filterOptions = {};
  92. for (const name in options) {
  93. if (names.includes(name)) {
  94. filterOptions[name] = options[name];
  95. }
  96. }
  97. // Filtered options
  98. return filterOptions;
  99. }
  100. exports.filterOptions = filterOptions;
  101. /**
  102. * Applies retryWrites: true to a command if retryWrites is set on the command's database.
  103. * @internal
  104. *
  105. * @param target - The target command to which we will apply retryWrites.
  106. * @param db - The database from which we can inherit a retryWrites value.
  107. */
  108. function applyRetryableWrites(target, db) {
  109. if (db && db.s.options?.retryWrites) {
  110. target.retryWrites = true;
  111. }
  112. return target;
  113. }
  114. exports.applyRetryableWrites = applyRetryableWrites;
  115. /**
  116. * Applies a write concern to a command based on well defined inheritance rules, optionally
  117. * detecting support for the write concern in the first place.
  118. * @internal
  119. *
  120. * @param target - the target command we will be applying the write concern to
  121. * @param sources - sources where we can inherit default write concerns from
  122. * @param options - optional settings passed into a command for write concern overrides
  123. */
  124. /**
  125. * Checks if a given value is a Promise
  126. *
  127. * @typeParam T - The resolution type of the possible promise
  128. * @param value - An object that could be a promise
  129. * @returns true if the provided value is a Promise
  130. */
  131. function isPromiseLike(value) {
  132. return !!value && typeof value.then === 'function';
  133. }
  134. exports.isPromiseLike = isPromiseLike;
  135. /**
  136. * Applies collation to a given command.
  137. * @internal
  138. *
  139. * @param command - the command on which to apply collation
  140. * @param target - target of command
  141. * @param options - options containing collation settings
  142. */
  143. function decorateWithCollation(command, target, options) {
  144. const capabilities = getTopology(target).capabilities;
  145. if (options.collation && typeof options.collation === 'object') {
  146. if (capabilities && capabilities.commandsTakeCollation) {
  147. command.collation = options.collation;
  148. }
  149. else {
  150. throw new error_1.MongoCompatibilityError(`Current topology does not support collation`);
  151. }
  152. }
  153. }
  154. exports.decorateWithCollation = decorateWithCollation;
  155. /**
  156. * Applies a read concern to a given command.
  157. * @internal
  158. *
  159. * @param command - the command on which to apply the read concern
  160. * @param coll - the parent collection of the operation calling this method
  161. */
  162. function decorateWithReadConcern(command, coll, options) {
  163. if (options && options.session && options.session.inTransaction()) {
  164. return;
  165. }
  166. const readConcern = Object.assign({}, command.readConcern || {});
  167. if (coll.s.readConcern) {
  168. Object.assign(readConcern, coll.s.readConcern);
  169. }
  170. if (Object.keys(readConcern).length > 0) {
  171. Object.assign(command, { readConcern: readConcern });
  172. }
  173. }
  174. exports.decorateWithReadConcern = decorateWithReadConcern;
  175. /**
  176. * Applies an explain to a given command.
  177. * @internal
  178. *
  179. * @param command - the command on which to apply the explain
  180. * @param options - the options containing the explain verbosity
  181. */
  182. function decorateWithExplain(command, explain) {
  183. if (command.explain) {
  184. return command;
  185. }
  186. return { explain: command, verbosity: explain.verbosity };
  187. }
  188. exports.decorateWithExplain = decorateWithExplain;
  189. /**
  190. * A helper function to get the topology from a given provider. Throws
  191. * if the topology cannot be found.
  192. * @throws MongoNotConnectedError
  193. * @internal
  194. */
  195. function getTopology(provider) {
  196. // MongoClient or ClientSession or AbstractCursor
  197. if ('topology' in provider && provider.topology) {
  198. return provider.topology;
  199. }
  200. else if ('client' in provider && provider.client.topology) {
  201. return provider.client.topology;
  202. }
  203. throw new error_1.MongoNotConnectedError('MongoClient must be connected to perform this operation');
  204. }
  205. exports.getTopology = getTopology;
  206. /** @internal */
  207. function ns(ns) {
  208. return MongoDBNamespace.fromString(ns);
  209. }
  210. exports.ns = ns;
  211. /** @public */
  212. class MongoDBNamespace {
  213. /**
  214. * Create a namespace object
  215. *
  216. * @param db - database name
  217. * @param collection - collection name
  218. */
  219. constructor(db, collection) {
  220. this.db = db;
  221. this.collection = collection;
  222. this.collection = collection === '' ? undefined : collection;
  223. }
  224. toString() {
  225. return this.collection ? `${this.db}.${this.collection}` : this.db;
  226. }
  227. withCollection(collection) {
  228. return new MongoDBCollectionNamespace(this.db, collection);
  229. }
  230. static fromString(namespace) {
  231. if (typeof namespace !== 'string' || namespace === '') {
  232. // TODO(NODE-3483): Replace with MongoNamespaceError
  233. throw new error_1.MongoRuntimeError(`Cannot parse namespace from "${namespace}"`);
  234. }
  235. const [db, ...collectionParts] = namespace.split('.');
  236. const collection = collectionParts.join('.');
  237. return new MongoDBNamespace(db, collection === '' ? undefined : collection);
  238. }
  239. }
  240. exports.MongoDBNamespace = MongoDBNamespace;
  241. /**
  242. * @public
  243. *
  244. * A class representing a collection's namespace. This class enforces (through Typescript) that
  245. * the `collection` portion of the namespace is defined and should only be
  246. * used in scenarios where this can be guaranteed.
  247. */
  248. class MongoDBCollectionNamespace extends MongoDBNamespace {
  249. constructor(db, collection) {
  250. super(db, collection);
  251. this.collection = collection;
  252. }
  253. static fromString(namespace) {
  254. return super.fromString(namespace);
  255. }
  256. }
  257. exports.MongoDBCollectionNamespace = MongoDBCollectionNamespace;
  258. /** @internal */
  259. function* makeCounter(seed = 0) {
  260. let count = seed;
  261. while (true) {
  262. const newCount = count;
  263. count += 1;
  264. yield newCount;
  265. }
  266. }
  267. exports.makeCounter = makeCounter;
  268. function maybeCallback(promiseFn, callback) {
  269. const promise = promiseFn();
  270. if (callback == null) {
  271. return promise;
  272. }
  273. promise.then(result => callback(undefined, result), error => callback(error));
  274. return;
  275. }
  276. exports.maybeCallback = maybeCallback;
  277. /**
  278. * Synchronously Generate a UUIDv4
  279. * @internal
  280. */
  281. function uuidV4() {
  282. const result = crypto.randomBytes(16);
  283. result[6] = (result[6] & 0x0f) | 0x40;
  284. result[8] = (result[8] & 0x3f) | 0x80;
  285. return result;
  286. }
  287. exports.uuidV4 = uuidV4;
  288. /**
  289. * A helper function for determining `maxWireVersion` between legacy and new topology instances
  290. * @internal
  291. */
  292. function maxWireVersion(topologyOrServer) {
  293. if (topologyOrServer) {
  294. if (topologyOrServer.loadBalanced) {
  295. // Since we do not have a monitor, we assume the load balanced server is always
  296. // pointed at the latest mongodb version. There is a risk that for on-prem
  297. // deployments that don't upgrade immediately that this could alert to the
  298. // application that a feature is available that is actually not.
  299. return constants_1.MAX_SUPPORTED_WIRE_VERSION;
  300. }
  301. if (topologyOrServer.hello) {
  302. return topologyOrServer.hello.maxWireVersion;
  303. }
  304. if ('lastHello' in topologyOrServer && typeof topologyOrServer.lastHello === 'function') {
  305. const lastHello = topologyOrServer.lastHello();
  306. if (lastHello) {
  307. return lastHello.maxWireVersion;
  308. }
  309. }
  310. if (topologyOrServer.description &&
  311. 'maxWireVersion' in topologyOrServer.description &&
  312. topologyOrServer.description.maxWireVersion != null) {
  313. return topologyOrServer.description.maxWireVersion;
  314. }
  315. }
  316. return 0;
  317. }
  318. exports.maxWireVersion = maxWireVersion;
  319. /**
  320. * Applies the function `eachFn` to each item in `arr`, in parallel.
  321. * @internal
  322. *
  323. * @param arr - An array of items to asynchronously iterate over
  324. * @param eachFn - A function to call on each item of the array. The callback signature is `(item, callback)`, where the callback indicates iteration is complete.
  325. * @param callback - The callback called after every item has been iterated
  326. */
  327. function eachAsync(arr, eachFn, callback) {
  328. arr = arr || [];
  329. let idx = 0;
  330. let awaiting = 0;
  331. for (idx = 0; idx < arr.length; ++idx) {
  332. awaiting++;
  333. eachFn(arr[idx], eachCallback);
  334. }
  335. if (awaiting === 0) {
  336. callback();
  337. return;
  338. }
  339. function eachCallback(err) {
  340. awaiting--;
  341. if (err) {
  342. callback(err);
  343. return;
  344. }
  345. if (idx === arr.length && awaiting <= 0) {
  346. callback();
  347. }
  348. }
  349. }
  350. exports.eachAsync = eachAsync;
  351. /** @internal */
  352. function arrayStrictEqual(arr, arr2) {
  353. if (!Array.isArray(arr) || !Array.isArray(arr2)) {
  354. return false;
  355. }
  356. return arr.length === arr2.length && arr.every((elt, idx) => elt === arr2[idx]);
  357. }
  358. exports.arrayStrictEqual = arrayStrictEqual;
  359. /** @internal */
  360. function errorStrictEqual(lhs, rhs) {
  361. if (lhs === rhs) {
  362. return true;
  363. }
  364. if (!lhs || !rhs) {
  365. return lhs === rhs;
  366. }
  367. if ((lhs == null && rhs != null) || (lhs != null && rhs == null)) {
  368. return false;
  369. }
  370. if (lhs.constructor.name !== rhs.constructor.name) {
  371. return false;
  372. }
  373. if (lhs.message !== rhs.message) {
  374. return false;
  375. }
  376. return true;
  377. }
  378. exports.errorStrictEqual = errorStrictEqual;
  379. /** @internal */
  380. function makeStateMachine(stateTable) {
  381. return function stateTransition(target, newState) {
  382. const legalStates = stateTable[target.s.state];
  383. if (legalStates && legalStates.indexOf(newState) < 0) {
  384. throw new error_1.MongoRuntimeError(`illegal state transition from [${target.s.state}] => [${newState}], allowed: [${legalStates}]`);
  385. }
  386. target.emit('stateChanged', target.s.state, newState);
  387. target.s.state = newState;
  388. };
  389. }
  390. exports.makeStateMachine = makeStateMachine;
  391. /** @internal */
  392. function now() {
  393. const hrtime = process.hrtime();
  394. return Math.floor(hrtime[0] * 1000 + hrtime[1] / 1000000);
  395. }
  396. exports.now = now;
  397. /** @internal */
  398. function calculateDurationInMs(started) {
  399. if (typeof started !== 'number') {
  400. throw new error_1.MongoInvalidArgumentError('Numeric value required to calculate duration');
  401. }
  402. const elapsed = now() - started;
  403. return elapsed < 0 ? 0 : elapsed;
  404. }
  405. exports.calculateDurationInMs = calculateDurationInMs;
  406. /** @internal */
  407. function hasAtomicOperators(doc) {
  408. if (Array.isArray(doc)) {
  409. for (const document of doc) {
  410. if (hasAtomicOperators(document)) {
  411. return true;
  412. }
  413. }
  414. return false;
  415. }
  416. const keys = Object.keys(doc);
  417. return keys.length > 0 && keys[0][0] === '$';
  418. }
  419. exports.hasAtomicOperators = hasAtomicOperators;
  420. /**
  421. * Merge inherited properties from parent into options, prioritizing values from options,
  422. * then values from parent.
  423. * @internal
  424. */
  425. function resolveOptions(parent, options) {
  426. const result = Object.assign({}, options, (0, bson_1.resolveBSONOptions)(options, parent));
  427. // Users cannot pass a readConcern/writeConcern to operations in a transaction
  428. const session = options?.session;
  429. if (!session?.inTransaction()) {
  430. const readConcern = read_concern_1.ReadConcern.fromOptions(options) ?? parent?.readConcern;
  431. if (readConcern) {
  432. result.readConcern = readConcern;
  433. }
  434. const writeConcern = write_concern_1.WriteConcern.fromOptions(options) ?? parent?.writeConcern;
  435. if (writeConcern) {
  436. result.writeConcern = writeConcern;
  437. }
  438. }
  439. const readPreference = read_preference_1.ReadPreference.fromOptions(options) ?? parent?.readPreference;
  440. if (readPreference) {
  441. result.readPreference = readPreference;
  442. }
  443. return result;
  444. }
  445. exports.resolveOptions = resolveOptions;
  446. function isSuperset(set, subset) {
  447. set = Array.isArray(set) ? new Set(set) : set;
  448. subset = Array.isArray(subset) ? new Set(subset) : subset;
  449. for (const elem of subset) {
  450. if (!set.has(elem)) {
  451. return false;
  452. }
  453. }
  454. return true;
  455. }
  456. exports.isSuperset = isSuperset;
  457. /**
  458. * Checks if the document is a Hello request
  459. * @internal
  460. */
  461. function isHello(doc) {
  462. return doc[constants_2.LEGACY_HELLO_COMMAND] || doc.hello ? true : false;
  463. }
  464. exports.isHello = isHello;
  465. /** Returns the items that are uniquely in setA */
  466. function setDifference(setA, setB) {
  467. const difference = new Set(setA);
  468. for (const elem of setB) {
  469. difference.delete(elem);
  470. }
  471. return difference;
  472. }
  473. exports.setDifference = setDifference;
  474. const HAS_OWN = (object, prop) => Object.prototype.hasOwnProperty.call(object, prop);
  475. function isRecord(value, requiredKeys = undefined) {
  476. if (!isObject(value)) {
  477. return false;
  478. }
  479. const ctor = value.constructor;
  480. if (ctor && ctor.prototype) {
  481. if (!isObject(ctor.prototype)) {
  482. return false;
  483. }
  484. // Check to see if some method exists from the Object exists
  485. if (!HAS_OWN(ctor.prototype, 'isPrototypeOf')) {
  486. return false;
  487. }
  488. }
  489. if (requiredKeys) {
  490. const keys = Object.keys(value);
  491. return isSuperset(keys, requiredKeys);
  492. }
  493. return true;
  494. }
  495. exports.isRecord = isRecord;
  496. /**
  497. * Make a deep copy of an object
  498. *
  499. * NOTE: This is not meant to be the perfect implementation of a deep copy,
  500. * but instead something that is good enough for the purposes of
  501. * command monitoring.
  502. */
  503. function deepCopy(value) {
  504. if (value == null) {
  505. return value;
  506. }
  507. else if (Array.isArray(value)) {
  508. return value.map(item => deepCopy(item));
  509. }
  510. else if (isRecord(value)) {
  511. const res = {};
  512. for (const key in value) {
  513. res[key] = deepCopy(value[key]);
  514. }
  515. return res;
  516. }
  517. const ctor = value.constructor;
  518. if (ctor) {
  519. switch (ctor.name.toLowerCase()) {
  520. case 'date':
  521. return new ctor(Number(value));
  522. case 'map':
  523. return new Map(value);
  524. case 'set':
  525. return new Set(value);
  526. case 'buffer':
  527. return Buffer.from(value);
  528. }
  529. }
  530. return value;
  531. }
  532. exports.deepCopy = deepCopy;
  533. /**
  534. * A sequential list of items in a circularly linked list
  535. * @remarks
  536. * The head node is special, it is always defined and has a value of null.
  537. * It is never "included" in the list, in that, it is not returned by pop/shift or yielded by the iterator.
  538. * The circular linkage and always defined head node are to reduce checks for null next/prev references to zero.
  539. * New nodes are declared as object literals with keys always in the same order: next, prev, value.
  540. * @internal
  541. */
  542. class List {
  543. get length() {
  544. return this.count;
  545. }
  546. get [Symbol.toStringTag]() {
  547. return 'List';
  548. }
  549. constructor() {
  550. this.count = 0;
  551. // this is carefully crafted:
  552. // declaring a complete and consistently key ordered
  553. // object is beneficial to the runtime optimizations
  554. this.head = {
  555. next: null,
  556. prev: null,
  557. value: null
  558. };
  559. this.head.next = this.head;
  560. this.head.prev = this.head;
  561. }
  562. toArray() {
  563. return Array.from(this);
  564. }
  565. toString() {
  566. return `head <=> ${this.toArray().join(' <=> ')} <=> head`;
  567. }
  568. *[Symbol.iterator]() {
  569. for (const node of this.nodes()) {
  570. yield node.value;
  571. }
  572. }
  573. *nodes() {
  574. let ptr = this.head.next;
  575. while (ptr !== this.head) {
  576. // Save next before yielding so that we make removing within iteration safe
  577. const { next } = ptr;
  578. yield ptr;
  579. ptr = next;
  580. }
  581. }
  582. /** Insert at end of list */
  583. push(value) {
  584. this.count += 1;
  585. const newNode = {
  586. next: this.head,
  587. prev: this.head.prev,
  588. value
  589. };
  590. this.head.prev.next = newNode;
  591. this.head.prev = newNode;
  592. }
  593. /** Inserts every item inside an iterable instead of the iterable itself */
  594. pushMany(iterable) {
  595. for (const value of iterable) {
  596. this.push(value);
  597. }
  598. }
  599. /** Insert at front of list */
  600. unshift(value) {
  601. this.count += 1;
  602. const newNode = {
  603. next: this.head.next,
  604. prev: this.head,
  605. value
  606. };
  607. this.head.next.prev = newNode;
  608. this.head.next = newNode;
  609. }
  610. remove(node) {
  611. if (node === this.head || this.length === 0) {
  612. return null;
  613. }
  614. this.count -= 1;
  615. const prevNode = node.prev;
  616. const nextNode = node.next;
  617. prevNode.next = nextNode;
  618. nextNode.prev = prevNode;
  619. return node.value;
  620. }
  621. /** Removes the first node at the front of the list */
  622. shift() {
  623. return this.remove(this.head.next);
  624. }
  625. /** Removes the last node at the end of the list */
  626. pop() {
  627. return this.remove(this.head.prev);
  628. }
  629. /** Iterates through the list and removes nodes where filter returns true */
  630. prune(filter) {
  631. for (const node of this.nodes()) {
  632. if (filter(node.value)) {
  633. this.remove(node);
  634. }
  635. }
  636. }
  637. clear() {
  638. this.count = 0;
  639. this.head.next = this.head;
  640. this.head.prev = this.head;
  641. }
  642. /** Returns the first item in the list, does not remove */
  643. first() {
  644. // If the list is empty, value will be the head's null
  645. return this.head.next.value;
  646. }
  647. /** Returns the last item in the list, does not remove */
  648. last() {
  649. // If the list is empty, value will be the head's null
  650. return this.head.prev.value;
  651. }
  652. }
  653. exports.List = List;
  654. /**
  655. * A pool of Buffers which allow you to read them as if they were one
  656. * @internal
  657. */
  658. class BufferPool {
  659. constructor() {
  660. this.buffers = new List();
  661. this.totalByteLength = 0;
  662. }
  663. get length() {
  664. return this.totalByteLength;
  665. }
  666. /** Adds a buffer to the internal buffer pool list */
  667. append(buffer) {
  668. this.buffers.push(buffer);
  669. this.totalByteLength += buffer.length;
  670. }
  671. /**
  672. * If BufferPool contains 4 bytes or more construct an int32 from the leading bytes,
  673. * otherwise return null. Size can be negative, caller should error check.
  674. */
  675. getInt32() {
  676. if (this.totalByteLength < 4) {
  677. return null;
  678. }
  679. const firstBuffer = this.buffers.first();
  680. if (firstBuffer != null && firstBuffer.byteLength >= 4) {
  681. return firstBuffer.readInt32LE(0);
  682. }
  683. // Unlikely case: an int32 is split across buffers.
  684. // Use read and put the returned buffer back on top
  685. const top4Bytes = this.read(4);
  686. const value = top4Bytes.readInt32LE(0);
  687. // Put it back.
  688. this.totalByteLength += 4;
  689. this.buffers.unshift(top4Bytes);
  690. return value;
  691. }
  692. /** Reads the requested number of bytes, optionally consuming them */
  693. read(size) {
  694. if (typeof size !== 'number' || size < 0) {
  695. throw new error_1.MongoInvalidArgumentError('Argument "size" must be a non-negative number');
  696. }
  697. // oversized request returns empty buffer
  698. if (size > this.totalByteLength) {
  699. return Buffer.alloc(0);
  700. }
  701. // We know we have enough, we just don't know how it is spread across chunks
  702. // TODO(NODE-4732): alloc API should change based on raw option
  703. const result = Buffer.allocUnsafe(size);
  704. for (let bytesRead = 0; bytesRead < size;) {
  705. const buffer = this.buffers.shift();
  706. if (buffer == null) {
  707. break;
  708. }
  709. const bytesRemaining = size - bytesRead;
  710. const bytesReadable = Math.min(bytesRemaining, buffer.byteLength);
  711. const bytes = buffer.subarray(0, bytesReadable);
  712. result.set(bytes, bytesRead);
  713. bytesRead += bytesReadable;
  714. this.totalByteLength -= bytesReadable;
  715. if (bytesReadable < buffer.byteLength) {
  716. this.buffers.unshift(buffer.subarray(bytesReadable));
  717. }
  718. }
  719. return result;
  720. }
  721. }
  722. exports.BufferPool = BufferPool;
  723. /** @public */
  724. class HostAddress {
  725. constructor(hostString) {
  726. this.host = undefined;
  727. this.port = undefined;
  728. this.socketPath = undefined;
  729. this.isIPv6 = false;
  730. const escapedHost = hostString.split(' ').join('%20'); // escape spaces, for socket path hosts
  731. if (escapedHost.endsWith('.sock')) {
  732. // heuristically determine if we're working with a domain socket
  733. this.socketPath = decodeURIComponent(escapedHost);
  734. return;
  735. }
  736. const urlString = `iLoveJS://${escapedHost}`;
  737. let url;
  738. try {
  739. url = new url_1.URL(urlString);
  740. }
  741. catch (urlError) {
  742. const runtimeError = new error_1.MongoRuntimeError(`Unable to parse ${escapedHost} with URL`);
  743. runtimeError.cause = urlError;
  744. throw runtimeError;
  745. }
  746. const hostname = url.hostname;
  747. const port = url.port;
  748. let normalized = decodeURIComponent(hostname).toLowerCase();
  749. if (normalized.startsWith('[') && normalized.endsWith(']')) {
  750. this.isIPv6 = true;
  751. normalized = normalized.substring(1, hostname.length - 1);
  752. }
  753. this.host = normalized.toLowerCase();
  754. if (typeof port === 'number') {
  755. this.port = port;
  756. }
  757. else if (typeof port === 'string' && port !== '') {
  758. this.port = Number.parseInt(port, 10);
  759. }
  760. else {
  761. this.port = 27017;
  762. }
  763. if (this.port === 0) {
  764. throw new error_1.MongoParseError('Invalid port (zero) with hostname');
  765. }
  766. Object.freeze(this);
  767. }
  768. [Symbol.for('nodejs.util.inspect.custom')]() {
  769. return this.inspect();
  770. }
  771. inspect() {
  772. return `new HostAddress('${this.toString()}')`;
  773. }
  774. toString() {
  775. if (typeof this.host === 'string') {
  776. if (this.isIPv6) {
  777. return `[${this.host}]:${this.port}`;
  778. }
  779. return `${this.host}:${this.port}`;
  780. }
  781. return `${this.socketPath}`;
  782. }
  783. static fromString(s) {
  784. return new HostAddress(s);
  785. }
  786. static fromHostPort(host, port) {
  787. if (host.includes(':')) {
  788. host = `[${host}]`; // IPv6 address
  789. }
  790. return HostAddress.fromString(`${host}:${port}`);
  791. }
  792. static fromSrvRecord({ name, port }) {
  793. return HostAddress.fromHostPort(name, port);
  794. }
  795. toHostPort() {
  796. if (this.socketPath) {
  797. return { host: this.socketPath, port: 0 };
  798. }
  799. const host = this.host ?? '';
  800. const port = this.port ?? 0;
  801. return { host, port };
  802. }
  803. }
  804. exports.HostAddress = HostAddress;
  805. exports.DEFAULT_PK_FACTORY = {
  806. // We prefer not to rely on ObjectId having a createPk method
  807. createPk() {
  808. return new bson_1.ObjectId();
  809. }
  810. };
  811. /**
  812. * When the driver used emitWarning the code will be equal to this.
  813. * @public
  814. *
  815. * @example
  816. * ```ts
  817. * process.on('warning', (warning) => {
  818. * if (warning.code === MONGODB_WARNING_CODE) console.error('Ah an important warning! :)')
  819. * })
  820. * ```
  821. */
  822. exports.MONGODB_WARNING_CODE = 'MONGODB DRIVER';
  823. /** @internal */
  824. function emitWarning(message) {
  825. return process.emitWarning(message, { code: exports.MONGODB_WARNING_CODE });
  826. }
  827. exports.emitWarning = emitWarning;
  828. const emittedWarnings = new Set();
  829. /**
  830. * Will emit a warning once for the duration of the application.
  831. * Uses the message to identify if it has already been emitted
  832. * so using string interpolation can cause multiple emits
  833. * @internal
  834. */
  835. function emitWarningOnce(message) {
  836. if (!emittedWarnings.has(message)) {
  837. emittedWarnings.add(message);
  838. return emitWarning(message);
  839. }
  840. }
  841. exports.emitWarningOnce = emitWarningOnce;
  842. /**
  843. * Takes a JS object and joins the values into a string separated by ', '
  844. */
  845. function enumToString(en) {
  846. return Object.values(en).join(', ');
  847. }
  848. exports.enumToString = enumToString;
  849. /**
  850. * Determine if a server supports retryable writes.
  851. *
  852. * @internal
  853. */
  854. function supportsRetryableWrites(server) {
  855. if (!server) {
  856. return false;
  857. }
  858. if (server.loadBalanced) {
  859. // Loadbalanced topologies will always support retry writes
  860. return true;
  861. }
  862. if (server.description.logicalSessionTimeoutMinutes != null) {
  863. // that supports sessions
  864. if (server.description.type !== common_1.ServerType.Standalone) {
  865. // and that is not a standalone
  866. return true;
  867. }
  868. }
  869. return false;
  870. }
  871. exports.supportsRetryableWrites = supportsRetryableWrites;
  872. /**
  873. * Fisher–Yates Shuffle
  874. *
  875. * Reference: https://bost.ocks.org/mike/shuffle/
  876. * @param sequence - items to be shuffled
  877. * @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.
  878. */
  879. function shuffle(sequence, limit = 0) {
  880. const items = Array.from(sequence); // shallow copy in order to never shuffle the input
  881. if (limit > items.length) {
  882. throw new error_1.MongoRuntimeError('Limit must be less than the number of items');
  883. }
  884. let remainingItemsToShuffle = items.length;
  885. const lowerBound = limit % items.length === 0 ? 1 : items.length - limit;
  886. while (remainingItemsToShuffle > lowerBound) {
  887. // Pick a remaining element
  888. const randomIndex = Math.floor(Math.random() * remainingItemsToShuffle);
  889. remainingItemsToShuffle -= 1;
  890. // And swap it with the current element
  891. const swapHold = items[remainingItemsToShuffle];
  892. items[remainingItemsToShuffle] = items[randomIndex];
  893. items[randomIndex] = swapHold;
  894. }
  895. return limit % items.length === 0 ? items : items.slice(lowerBound);
  896. }
  897. exports.shuffle = shuffle;
  898. // TODO(NODE-4936): read concern eligibility for commands should be codified in command construction
  899. // @see https://github.com/mongodb/specifications/blob/master/source/read-write-concern/read-write-concern.rst#read-concern
  900. function commandSupportsReadConcern(command) {
  901. if (command.aggregate || command.count || command.distinct || command.find || command.geoNear) {
  902. return true;
  903. }
  904. return false;
  905. }
  906. exports.commandSupportsReadConcern = commandSupportsReadConcern;
  907. /**
  908. * Compare objectIds. `null` is always less
  909. * - `+1 = oid1 is greater than oid2`
  910. * - `-1 = oid1 is less than oid2`
  911. * - `+0 = oid1 is equal oid2`
  912. */
  913. function compareObjectId(oid1, oid2) {
  914. if (oid1 == null && oid2 == null) {
  915. return 0;
  916. }
  917. if (oid1 == null) {
  918. return -1;
  919. }
  920. if (oid2 == null) {
  921. return 1;
  922. }
  923. return exports.ByteUtils.compare(oid1.id, oid2.id);
  924. }
  925. exports.compareObjectId = compareObjectId;
  926. function parseInteger(value) {
  927. if (typeof value === 'number')
  928. return Math.trunc(value);
  929. const parsedValue = Number.parseInt(String(value), 10);
  930. return Number.isNaN(parsedValue) ? null : parsedValue;
  931. }
  932. exports.parseInteger = parseInteger;
  933. function parseUnsignedInteger(value) {
  934. const parsedInt = parseInteger(value);
  935. return parsedInt != null && parsedInt >= 0 ? parsedInt : null;
  936. }
  937. exports.parseUnsignedInteger = parseUnsignedInteger;
  938. /**
  939. * Determines whether a provided address matches the provided parent domain.
  940. *
  941. * If a DNS server were to become compromised SRV records would still need to
  942. * advertise addresses that are under the same domain as the srvHost.
  943. *
  944. * @param address - The address to check against a domain
  945. * @param srvHost - The domain to check the provided address against
  946. * @returns Whether the provided address matches the parent domain
  947. */
  948. function matchesParentDomain(address, srvHost) {
  949. // Remove trailing dot if exists on either the resolved address or the srv hostname
  950. const normalizedAddress = address.endsWith('.') ? address.slice(0, address.length - 1) : address;
  951. const normalizedSrvHost = srvHost.endsWith('.') ? srvHost.slice(0, srvHost.length - 1) : srvHost;
  952. const allCharacterBeforeFirstDot = /^.*?\./;
  953. // Remove all characters before first dot
  954. // Add leading dot back to string so
  955. // an srvHostDomain = '.trusted.site'
  956. // will not satisfy an addressDomain that endsWith '.fake-trusted.site'
  957. const addressDomain = `.${normalizedAddress.replace(allCharacterBeforeFirstDot, '')}`;
  958. const srvHostDomain = `.${normalizedSrvHost.replace(allCharacterBeforeFirstDot, '')}`;
  959. return addressDomain.endsWith(srvHostDomain);
  960. }
  961. exports.matchesParentDomain = matchesParentDomain;
  962. async function request(uri, options = {}) {
  963. return new Promise((resolve, reject) => {
  964. const requestOptions = {
  965. method: 'GET',
  966. timeout: 10000,
  967. json: true,
  968. ...url.parse(uri),
  969. ...options
  970. };
  971. const req = http.request(requestOptions, res => {
  972. res.setEncoding('utf8');
  973. let data = '';
  974. res.on('data', d => {
  975. data += d;
  976. });
  977. res.once('end', () => {
  978. if (options.json === false) {
  979. resolve(data);
  980. return;
  981. }
  982. try {
  983. const parsed = JSON.parse(data);
  984. resolve(parsed);
  985. }
  986. catch {
  987. // TODO(NODE-3483)
  988. reject(new error_1.MongoRuntimeError(`Invalid JSON response: "${data}"`));
  989. }
  990. });
  991. });
  992. req.once('timeout', () => req.destroy(new error_1.MongoNetworkTimeoutError(`Network request to ${uri} timed out after ${options.timeout} ms`)));
  993. req.once('error', error => reject(error));
  994. req.end();
  995. });
  996. }
  997. exports.request = request;
  998. /**
  999. * A custom AbortController that aborts after a specified timeout.
  1000. *
  1001. * If `timeout` is undefined or \<=0, the abort controller never aborts.
  1002. *
  1003. * This class provides two benefits over the built-in AbortSignal.timeout() method.
  1004. * - This class provides a mechanism for cancelling the timeout
  1005. * - This class supports infinite timeouts by interpreting a timeout of 0 as infinite. This is
  1006. * consistent with existing timeout options in the Node driver (serverSelectionTimeoutMS, for example).
  1007. * @internal
  1008. */
  1009. class TimeoutController extends AbortController {
  1010. constructor(timeout = 0, timeoutId = timeout > 0 ? (0, timers_1.setTimeout)(() => this.abort(), timeout) : null) {
  1011. super();
  1012. this.timeoutId = timeoutId;
  1013. }
  1014. clear() {
  1015. if (this.timeoutId != null) {
  1016. (0, timers_1.clearTimeout)(this.timeoutId);
  1017. }
  1018. this.timeoutId = null;
  1019. }
  1020. }
  1021. exports.TimeoutController = TimeoutController;
  1022. //# sourceMappingURL=utils.js.map