CoolFace
Apppublic

opusdev/vector-similarity-api

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes
utils.ts1510 linesDownload Raw Back to src
1import * as crypto from 'crypto';2import type { SrvRecord } from 'dns';3import { type EventEmitter } from 'events';4import { promises as fs } from 'fs';5import * as http from 'http';6import { clearTimeout, setTimeout } from 'timers';7import * as url from 'url';8import { URL } from 'url';9import { promisify } from 'util';10 11import { deserialize, type Document, ObjectId, resolveBSONOptions } from './bson';12import type { Connection } from './cmap/connection';13import { MAX_SUPPORTED_WIRE_VERSION } from './cmap/wire_protocol/constants';14import type { Collection } from './collection';15import { kDecoratedKeys, LEGACY_HELLO_COMMAND } from './constants';16import type { AbstractCursor } from './cursor/abstract_cursor';17import type { FindCursor } from './cursor/find_cursor';18import type { Db } from './db';19import {20  type AnyError,21  MongoAPIError,22  MongoInvalidArgumentError,23  MongoNetworkTimeoutError,24  MongoNotConnectedError,25  MongoParseError,26  MongoRuntimeError27} from './error';28import type { MongoClient } from './mongo_client';29import { type Abortable } from './mongo_types';30import type { CommandOperationOptions, OperationParent } from './operations/command';31import type { Hint, OperationOptions } from './operations/operation';32import { ReadConcern } from './read_concern';33import { ReadPreference } from './read_preference';34import { ServerType } from './sdam/common';35import type { Server } from './sdam/server';36import type { Topology } from './sdam/topology';37import type { ClientSession } from './sessions';38import { type TimeoutContextOptions } from './timeout';39import { WriteConcern } from './write_concern';40 41/**42 * MongoDB Driver style callback43 * @public44 */45export type Callback<T = any> = (error?: AnyError, result?: T) => void;46 47export type AnyOptions = Document;48 49export const ByteUtils = {50  toLocalBufferType(this: void, buffer: Buffer | Uint8Array): Buffer {51    return Buffer.isBuffer(buffer)52      ? buffer53      : Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength);54  },55 56  equals(this: void, seqA: Uint8Array, seqB: Uint8Array) {57    return ByteUtils.toLocalBufferType(seqA).equals(seqB);58  },59 60  compare(this: void, seqA: Uint8Array, seqB: Uint8Array) {61    return ByteUtils.toLocalBufferType(seqA).compare(seqB);62  },63 64  toBase64(this: void, uint8array: Uint8Array) {65    return ByteUtils.toLocalBufferType(uint8array).toString('base64');66  }67};68 69/**70 * Returns true if value is a Uint8Array or a Buffer71 * @param value - any value that may be a Uint8Array72 */73export function isUint8Array(value: unknown): value is Uint8Array {74  return (75    value != null &&76    typeof value === 'object' &&77    Symbol.toStringTag in value &&78    value[Symbol.toStringTag] === 'Uint8Array'79  );80}81 82/**83 * Determines if a connection's address matches a user provided list84 * of domain wildcards.85 */86export function hostMatchesWildcards(host: string, wildcards: string[]): boolean {87  for (const wildcard of wildcards) {88    if (89      host === wildcard ||90      (wildcard.startsWith('*.') && host?.endsWith(wildcard.substring(2, wildcard.length))) ||91      (wildcard.startsWith('*/') && host?.endsWith(wildcard.substring(2, wildcard.length)))92    ) {93      return true;94    }95  }96  return false;97}98 99/**100 * Ensure Hint field is in a shape we expect:101 * - object of index names mapping to 1 or -1102 * - just an index name103 * @internal104 */105export function normalizeHintField(hint?: Hint): Hint | undefined {106  let finalHint = undefined;107 108  if (typeof hint === 'string') {109    finalHint = hint;110  } else if (Array.isArray(hint)) {111    finalHint = {};112 113    hint.forEach(param => {114      finalHint[param] = 1;115    });116  } else if (hint != null && typeof hint === 'object') {117    finalHint = {} as Document;118    for (const name in hint) {119      finalHint[name] = hint[name];120    }121  }122 123  return finalHint;124}125 126const TO_STRING = (object: unknown) => Object.prototype.toString.call(object);127/**128 * Checks if arg is an Object:129 * - **NOTE**: the check is based on the `[Symbol.toStringTag]() === 'Object'`130 * @internal131 */132 133export function isObject(arg: unknown): arg is object {134  return '[object Object]' === TO_STRING(arg);135}136 137/** @internal */138export function mergeOptions<T, S>(target: T, source: S): T & S {139  return { ...target, ...source };140}141 142/** @internal */143export function filterOptions(options: AnyOptions, names: ReadonlyArray<string>): AnyOptions {144  const filterOptions: AnyOptions = {};145 146  for (const name in options) {147    if (names.includes(name)) {148      filterOptions[name] = options[name];149    }150  }151 152  // Filtered options153  return filterOptions;154}155 156interface HasRetryableWrites {157  retryWrites?: boolean;158}159/**160 * Applies retryWrites: true to a command if retryWrites is set on the command's database.161 * @internal162 *163 * @param target - The target command to which we will apply retryWrites.164 * @param db - The database from which we can inherit a retryWrites value.165 */166export function applyRetryableWrites<T extends HasRetryableWrites>(target: T, db?: Db): T {167  if (db && db.s.options?.retryWrites) {168    target.retryWrites = true;169  }170 171  return target;172}173 174/**175 * Applies a write concern to a command based on well defined inheritance rules, optionally176 * detecting support for the write concern in the first place.177 * @internal178 *179 * @param target - the target command we will be applying the write concern to180 * @param sources - sources where we can inherit default write concerns from181 * @param options - optional settings passed into a command for write concern overrides182 */183 184/**185 * Checks if a given value is a Promise186 *187 * @typeParam T - The resolution type of the possible promise188 * @param value - An object that could be a promise189 * @returns true if the provided value is a Promise190 */191export function isPromiseLike<T = unknown>(value?: unknown): value is PromiseLike<T> {192  return (193    value != null &&194    typeof value === 'object' &&195    'then' in value &&196    typeof value.then === 'function'197  );198}199 200/**201 * Applies collation to a given command.202 * @internal203 *204 * @param command - the command on which to apply collation205 * @param target - target of command206 * @param options - options containing collation settings207 */208export function decorateWithCollation(command: Document, options: AnyOptions): void {209  if (options.collation && typeof options.collation === 'object') {210    command.collation = options.collation;211  }212}213 214/**215 * Applies a read concern to a given command.216 * @internal217 *218 * @param command - the command on which to apply the read concern219 * @param coll - the parent collection of the operation calling this method220 */221export function decorateWithReadConcern(222  command: Document,223  coll: { s: { readConcern?: ReadConcern } },224  options?: OperationOptions225): void {226  if (options && options.session && options.session.inTransaction()) {227    return;228  }229  const readConcern = Object.assign({}, command.readConcern || {});230  if (coll.s.readConcern) {231    Object.assign(readConcern, coll.s.readConcern);232  }233 234  if (Object.keys(readConcern).length > 0) {235    Object.assign(command, { readConcern: readConcern });236  }237}238 239/**240 * @internal241 */242export type TopologyProvider =243  | MongoClient244  | ClientSession245  | FindCursor246  | AbstractCursor247  | Collection<any>248  | Db;249 250/**251 * A helper function to get the topology from a given provider. Throws252 * if the topology cannot be found.253 * @throws MongoNotConnectedError254 * @internal255 */256export function getTopology(provider: TopologyProvider): Topology {257  // MongoClient or ClientSession or AbstractCursor258  if ('topology' in provider && provider.topology) {259    return provider.topology;260  } else if ('client' in provider && provider.client.topology) {261    return provider.client.topology;262  }263 264  throw new MongoNotConnectedError('MongoClient must be connected to perform this operation');265}266 267/** @internal */268export function ns(ns: string): MongoDBNamespace {269  return MongoDBNamespace.fromString(ns);270}271 272/** @public */273export class MongoDBNamespace {274  db: string;275  collection?: string;276  /**277   * Create a namespace object278   *279   * @param db - database name280   * @param collection - collection name281   */282  constructor(db: string, collection?: string) {283    this.db = db;284    this.collection = collection === '' ? undefined : collection;285  }286 287  toString(): string {288    return this.collection ? `${this.db}.${this.collection}` : this.db;289  }290 291  withCollection(collection: string): MongoDBCollectionNamespace {292    return new MongoDBCollectionNamespace(this.db, collection);293  }294 295  static fromString(namespace?: string): MongoDBNamespace {296    if (typeof namespace !== 'string' || namespace === '') {297      // TODO(NODE-3483): Replace with MongoNamespaceError298      throw new MongoRuntimeError(`Cannot parse namespace from "${namespace}"`);299    }300 301    const [db, ...collectionParts] = namespace.split('.');302    const collection = collectionParts.join('.');303    return new MongoDBNamespace(db, collection === '' ? undefined : collection);304  }305}306 307/**308 * @public309 *310 * A class representing a collection's namespace.  This class enforces (through Typescript) that311 * the `collection` portion of the namespace is defined and should only be312 * used in scenarios where this can be guaranteed.313 */314export class MongoDBCollectionNamespace extends MongoDBNamespace {315  override collection: string;316 317  constructor(db: string, collection: string) {318    super(db, collection);319    this.collection = collection;320  }321 322  static override fromString(namespace?: string): MongoDBCollectionNamespace {323    return super.fromString(namespace) as MongoDBCollectionNamespace;324  }325}326 327/** @internal */328export function* makeCounter(seed = 0): Generator<number> {329  let count = seed;330  while (true) {331    const newCount = count;332    count += 1;333    yield newCount;334  }335}336 337/**338 * Synchronously Generate a UUIDv4339 * @internal340 */341export function uuidV4(): Buffer {342  const result = crypto.randomBytes(16);343  result[6] = (result[6] & 0x0f) | 0x40;344  result[8] = (result[8] & 0x3f) | 0x80;345  return result;346}347 348/**349 * A helper function for determining `maxWireVersion` between legacy and new topology instances350 * @internal351 */352export function maxWireVersion(handshakeAware?: Connection | Topology | Server): number {353  if (handshakeAware) {354    if (handshakeAware.hello) {355      return handshakeAware.hello.maxWireVersion;356    }357 358    if (handshakeAware.serverApi?.version) {359      // We return the max supported wire version for serverAPI.360      return MAX_SUPPORTED_WIRE_VERSION;361    }362    // This is the fallback case for load balanced mode. If we are building commands the363    // object being checked will be a connection, and we will have a hello response on364    // it. For other cases, such as retryable writes, the object will be a server or365    // topology, and there will be no hello response on those objects, so we return366    // the max wire version so we support retryability. Once we have a min supported367    // wire version of 9, then the needsRetryableWriteLabel() check can remove the368    // usage of passing the wire version into it.369    if (handshakeAware.loadBalanced) {370      return MAX_SUPPORTED_WIRE_VERSION;371    }372 373    if ('lastHello' in handshakeAware && typeof handshakeAware.lastHello === 'function') {374      const lastHello = handshakeAware.lastHello();375      if (lastHello) {376        return lastHello.maxWireVersion;377      }378    }379 380    if (381      handshakeAware.description &&382      'maxWireVersion' in handshakeAware.description &&383      handshakeAware.description.maxWireVersion != null384    ) {385      return handshakeAware.description.maxWireVersion;386    }387  }388 389  return 0;390}391 392/** @internal */393export function arrayStrictEqual(arr: unknown[], arr2: unknown[]): boolean {394  if (!Array.isArray(arr) || !Array.isArray(arr2)) {395    return false;396  }397 398  return arr.length === arr2.length && arr.every((elt, idx) => elt === arr2[idx]);399}400 401/** @internal */402export function errorStrictEqual(lhs?: AnyError | null, rhs?: AnyError | null): boolean {403  if (lhs === rhs) {404    return true;405  }406 407  if (!lhs || !rhs) {408    return lhs === rhs;409  }410 411  if ((lhs == null && rhs != null) || (lhs != null && rhs == null)) {412    return false;413  }414 415  if (lhs.constructor.name !== rhs.constructor.name) {416    return false;417  }418 419  if (lhs.message !== rhs.message) {420    return false;421  }422 423  return true;424}425 426interface StateTable {427  [key: string]: string[];428}429interface ObjectWithState {430  s: { state: string };431  emit(event: 'stateChanged', state: string, newState: string): void;432}433interface StateTransitionFunction {434  (target: ObjectWithState, newState: string): void;435}436 437/** @public */438export type EventEmitterWithState = {439  /** @internal */440  stateChanged(previous: string, current: string): void;441};442 443/** @internal */444export function makeStateMachine(stateTable: StateTable): StateTransitionFunction {445  return function stateTransition(target, newState) {446    const legalStates = stateTable[target.s.state];447    if (legalStates && legalStates.indexOf(newState) < 0) {448      throw new MongoRuntimeError(449        `illegal state transition from [${target.s.state}] => [${newState}], allowed: [${legalStates}]`450      );451    }452 453    target.emit('stateChanged', target.s.state, newState);454    target.s.state = newState;455  };456}457 458/** @internal */459export function now(): number {460  const hrtime = process.hrtime();461  return Math.floor(hrtime[0] * 1000 + hrtime[1] / 1000000);462}463 464/** @internal */465export function calculateDurationInMs(started: number | undefined): number {466  if (typeof started !== 'number') {467    return -1;468  }469 470  const elapsed = now() - started;471  return elapsed < 0 ? 0 : elapsed;472}473 474/** @internal */475export function hasAtomicOperators(476  doc: Document | Document[],477  options?: CommandOperationOptions478): boolean {479  if (Array.isArray(doc)) {480    for (const document of doc) {481      if (hasAtomicOperators(document)) {482        return true;483      }484    }485    return false;486  }487 488  const keys = Object.keys(doc);489  // In this case we need to throw if all the atomic operators are undefined.490  if (options?.ignoreUndefined) {491    let allUndefined = true;492    for (const key of keys) {493      // eslint-disable-next-line no-restricted-syntax494      if (doc[key] !== undefined) {495        allUndefined = false;496        break;497      }498    }499    if (allUndefined) {500      throw new MongoInvalidArgumentError(501        'Update operations require that all atomic operators have defined values, but none were provided.'502      );503    }504  }505 506  return keys.length > 0 && keys[0][0] === '$';507}508 509export function resolveTimeoutOptions<T extends Partial<TimeoutContextOptions>>(510  client: MongoClient,511  options: T512): T &513  Pick<514    MongoClient['s']['options'],515    'timeoutMS' | 'serverSelectionTimeoutMS' | 'waitQueueTimeoutMS' | 'socketTimeoutMS'516  > {517  const { socketTimeoutMS, serverSelectionTimeoutMS, waitQueueTimeoutMS, timeoutMS } =518    client.s.options;519  return { socketTimeoutMS, serverSelectionTimeoutMS, waitQueueTimeoutMS, timeoutMS, ...options };520}521/**522 * Merge inherited properties from parent into options, prioritizing values from options,523 * then values from parent.524 *525 * @param parent - An optional owning class of the operation being run. ex. Db/Collection/MongoClient.526 * @param options - The options passed to the operation method.527 *528 * @internal529 */530export function resolveOptions<T extends CommandOperationOptions>(531  parent: OperationParent | undefined,532  options?: T533): T {534  const result: T = Object.assign({}, options, resolveBSONOptions(options, parent));535 536  const timeoutMS = options?.timeoutMS ?? parent?.timeoutMS;537  // Users cannot pass a readConcern/writeConcern to operations in a transaction538  const session = options?.session;539 540  if (!session?.inTransaction()) {541    const readConcern = ReadConcern.fromOptions(options) ?? parent?.readConcern;542    if (readConcern) {543      result.readConcern = readConcern;544    }545 546    let writeConcern = WriteConcern.fromOptions(options) ?? parent?.writeConcern;547    if (writeConcern) {548      if (timeoutMS != null) {549        writeConcern = WriteConcern.fromOptions({550          writeConcern: {551            ...writeConcern,552            wtimeout: undefined,553            wtimeoutMS: undefined554          }555        });556      }557      result.writeConcern = writeConcern;558    }559  }560 561  result.timeoutMS = timeoutMS;562 563  const readPreference = ReadPreference.fromOptions(options) ?? parent?.readPreference;564  if (readPreference) {565    result.readPreference = readPreference;566  }567 568  const isConvenientTransaction = session?.explicit && session?.timeoutContext != null;569  if (isConvenientTransaction && options?.timeoutMS != null) {570    throw new MongoInvalidArgumentError(571      'An operation cannot be given a timeoutMS setting when inside a withTransaction call that has a timeoutMS setting'572    );573  }574 575  return result;576}577 578export function isSuperset(set: Set<any> | any[], subset: Set<any> | any[]): boolean {579  set = Array.isArray(set) ? new Set(set) : set;580  subset = Array.isArray(subset) ? new Set(subset) : subset;581  for (const elem of subset) {582    if (!set.has(elem)) {583      return false;584    }585  }586  return true;587}588 589/**590 * Checks if the document is a Hello request591 * @internal592 */593export function isHello(doc: Document): boolean {594  return doc[LEGACY_HELLO_COMMAND] || doc.hello ? true : false;595}596 597/** Returns the items that are uniquely in setA */598export function setDifference<T>(setA: Iterable<T>, setB: Iterable<T>): Set<T> {599  const difference = new Set<T>(setA);600  for (const elem of setB) {601    difference.delete(elem);602  }603  return difference;604}605 606const HAS_OWN = (object: unknown, prop: string) =>607  Object.prototype.hasOwnProperty.call(object, prop);608 609export function isRecord<T extends readonly string[]>(610  value: unknown,611  requiredKeys: T612): value is Record<T[number], any>;613export function isRecord(value: unknown): value is Record<string, any>;614export function isRecord(615  value: unknown,616  requiredKeys: string[] | undefined = undefined617): value is Record<string, any> {618  if (!isObject(value)) {619    return false;620  }621 622  const ctor = (value as any).constructor;623  if (ctor && ctor.prototype) {624    if (!isObject(ctor.prototype)) {625      return false;626    }627 628    // Check to see if some method exists from the Object exists629    if (!HAS_OWN(ctor.prototype, 'isPrototypeOf')) {630      return false;631    }632  }633 634  if (requiredKeys) {635    const keys = Object.keys(value as Record<string, any>);636    return isSuperset(keys, requiredKeys);637  }638 639  return true;640}641 642type ListNode<T> = {643  value: T;644  next: ListNode<T> | HeadNode<T>;645  prev: ListNode<T> | HeadNode<T>;646};647 648type HeadNode<T> = {649  value: null;650  next: ListNode<T>;651  prev: ListNode<T>;652};653 654/**655 * When a list is empty the head is a reference with pointers to itself656 * So this type represents that self referential state657 */658type EmptyNode = {659  value: null;660  next: EmptyNode;661  prev: EmptyNode;662};663 664/**665 * A sequential list of items in a circularly linked list666 * @remarks667 * The head node is special, it is always defined and has a value of null.668 * It is never "included" in the list, in that, it is not returned by pop/shift or yielded by the iterator.669 * The circular linkage and always defined head node are to reduce checks for null next/prev references to zero.670 * New nodes are declared as object literals with keys always in the same order: next, prev, value.671 * @internal672 */673export class List<T = unknown> {674  private readonly head: HeadNode<T> | EmptyNode;675  private count: number;676 677  get length() {678    return this.count;679  }680 681  get [Symbol.toStringTag]() {682    return 'List' as const;683  }684 685  constructor() {686    this.count = 0;687 688    // this is carefully crafted:689    // declaring a complete and consistently key ordered690    // object is beneficial to the runtime optimizations691    this.head = {692      next: null,693      prev: null,694      value: null695    } as unknown as EmptyNode;696    this.head.next = this.head;697    this.head.prev = this.head;698  }699 700  toArray() {701    return Array.from(this);702  }703 704  toString() {705    return `head <=> ${this.toArray().join(' <=> ')} <=> head`;706  }707 708  *[Symbol.iterator](): Generator<T, void, void> {709    for (const node of this.nodes()) {710      yield node.value;711    }712  }713 714  private *nodes(): Generator<ListNode<T>, void, void> {715    let ptr: HeadNode<T> | ListNode<T> | EmptyNode = this.head.next;716    while (ptr !== this.head) {717      // Save next before yielding so that we make removing within iteration safe718      const { next } = ptr as ListNode<T>;719      yield ptr as ListNode<T>;720      ptr = next;721    }722  }723 724  /** Insert at end of list */725  push(value: T) {726    this.count += 1;727    const newNode: ListNode<T> = {728      next: this.head as HeadNode<T>,729      prev: this.head.prev as ListNode<T>,730      value731    };732    this.head.prev.next = newNode;733    this.head.prev = newNode;734  }735 736  /** Inserts every item inside an iterable instead of the iterable itself */737  pushMany(iterable: Iterable<T>) {738    for (const value of iterable) {739      this.push(value);740    }741  }742 743  /** Insert at front of list */744  unshift(value: T) {745    this.count += 1;746    const newNode: ListNode<T> = {747      next: this.head.next as ListNode<T>,748      prev: this.head as HeadNode<T>,749      value750    };751    this.head.next.prev = newNode;752    this.head.next = newNode;753  }754 755  private remove(node: ListNode<T> | EmptyNode): T | null {756    if (node === this.head || this.length === 0) {757      return null;758    }759 760    this.count -= 1;761 762    const prevNode = node.prev;763    const nextNode = node.next;764    prevNode.next = nextNode;765    nextNode.prev = prevNode;766 767    return node.value;768  }769 770  /** Removes the first node at the front of the list */771  shift(): T | null {772    return this.remove(this.head.next);773  }774 775  /** Removes the last node at the end of the list */776  pop(): T | null {777    return this.remove(this.head.prev);778  }779 780  /** Iterates through the list and removes nodes where filter returns true */781  prune(filter: (value: T) => boolean) {782    for (const node of this.nodes()) {783      if (filter(node.value)) {784        this.remove(node);785      }786    }787  }788 789  clear() {790    this.count = 0;791    this.head.next = this.head as EmptyNode;792    this.head.prev = this.head as EmptyNode;793  }794 795  /** Returns the first item in the list, does not remove */796  first(): T | null {797    // If the list is empty, value will be the head's null798    return this.head.next.value;799  }800 801  /** Returns the last item in the list, does not remove */802  last(): T | null {803    // If the list is empty, value will be the head's null804    return this.head.prev.value;805  }806}807 808/**809 * A pool of Buffers which allow you to read them as if they were one810 * @internal811 */812export class BufferPool {813  private buffers: List<Buffer>;814  private totalByteLength: number;815 816  constructor() {817    this.buffers = new List();818    this.totalByteLength = 0;819  }820 821  get length(): number {822    return this.totalByteLength;823  }824 825  /** Adds a buffer to the internal buffer pool list */826  append(buffer: Buffer): void {827    this.buffers.push(buffer);828    this.totalByteLength += buffer.length;829  }830 831  /**832   * If BufferPool contains 4 bytes or more construct an int32 from the leading bytes,833   * otherwise return null. Size can be negative, caller should error check.834   */835  getInt32(): number | null {836    if (this.totalByteLength < 4) {837      return null;838    }839    const firstBuffer = this.buffers.first();840    if (firstBuffer != null && firstBuffer.byteLength >= 4) {841      return firstBuffer.readInt32LE(0);842    }843 844    // Unlikely case: an int32 is split across buffers.845    // Use read and put the returned buffer back on top846    const top4Bytes = this.read(4);847    const value = top4Bytes.readInt32LE(0);848 849    // Put it back.850    this.totalByteLength += 4;851    this.buffers.unshift(top4Bytes);852 853    return value;854  }855 856  /** Reads the requested number of bytes, optionally consuming them */857  read(size: number): Buffer {858    if (typeof size !== 'number' || size < 0) {859      throw new MongoInvalidArgumentError('Argument "size" must be a non-negative number');860    }861 862    // oversized request returns empty buffer863    if (size > this.totalByteLength) {864      return Buffer.alloc(0);865    }866 867    // We know we have enough, we just don't know how it is spread across chunks868    // TODO(NODE-4732): alloc API should change based on raw option869    const result = Buffer.allocUnsafe(size);870 871    for (let bytesRead = 0; bytesRead < size; ) {872      const buffer = this.buffers.shift();873      if (buffer == null) {874        break;875      }876      const bytesRemaining = size - bytesRead;877      const bytesReadable = Math.min(bytesRemaining, buffer.byteLength);878      const bytes = buffer.subarray(0, bytesReadable);879 880      result.set(bytes, bytesRead);881 882      bytesRead += bytesReadable;883      this.totalByteLength -= bytesReadable;884      if (bytesReadable < buffer.byteLength) {885        this.buffers.unshift(buffer.subarray(bytesReadable));886      }887    }888 889    return result;890  }891}892 893/** @public */894export class HostAddress {895  host: string | undefined = undefined;896  port: number | undefined = undefined;897  socketPath: string | undefined = undefined;898  isIPv6 = false;899 900  constructor(hostString: string) {901    const escapedHost = hostString.split(' ').join('%20'); // escape spaces, for socket path hosts902 903    if (escapedHost.endsWith('.sock')) {904      // heuristically determine if we're working with a domain socket905      this.socketPath = decodeURIComponent(escapedHost);906      return;907    }908 909    const urlString = `iLoveJS://${escapedHost}`;910    let url;911    try {912      url = new URL(urlString);913    } catch (urlError) {914      const runtimeError = new MongoRuntimeError(`Unable to parse ${escapedHost} with URL`);915      runtimeError.cause = urlError;916      throw runtimeError;917    }918 919    const hostname = url.hostname;920    const port = url.port;921 922    let normalized = decodeURIComponent(hostname).toLowerCase();923    if (normalized.startsWith('[') && normalized.endsWith(']')) {924      this.isIPv6 = true;925      normalized = normalized.substring(1, hostname.length - 1);926    }927 928    this.host = normalized.toLowerCase();929 930    if (typeof port === 'number') {931      this.port = port;932    } else if (typeof port === 'string' && port !== '') {933      this.port = Number.parseInt(port, 10);934    } else {935      this.port = 27017;936    }937 938    if (this.port === 0) {939      throw new MongoParseError('Invalid port (zero) with hostname');940    }941    Object.freeze(this);942  }943 944  [Symbol.for('nodejs.util.inspect.custom')](): string {945    return this.inspect();946  }947 948  inspect(): string {949    return `new HostAddress('${this.toString()}')`;950  }951 952  toString(): string {953    if (typeof this.host === 'string') {954      if (this.isIPv6) {955        return `[${this.host}]:${this.port}`;956      }957      return `${this.host}:${this.port}`;958    }959    return `${this.socketPath}`;960  }961 962  static fromString(this: void, s: string): HostAddress {963    return new HostAddress(s);964  }965 966  static fromHostPort(host: string, port: number): HostAddress {967    if (host.includes(':')) {968      host = `[${host}]`; // IPv6 address969    }970    return HostAddress.fromString(`${host}:${port}`);971  }972 973  static fromSrvRecord({ name, port }: SrvRecord): HostAddress {974    return HostAddress.fromHostPort(name, port);975  }976 977  toHostPort(): { host: string; port: number } {978    if (this.socketPath) {979      return { host: this.socketPath, port: 0 };980    }981 982    const host = this.host ?? '';983    const port = this.port ?? 0;984    return { host, port };985  }986}987 988export const DEFAULT_PK_FACTORY = {989  // We prefer not to rely on ObjectId having a createPk method990  createPk(): ObjectId {991    return new ObjectId();992  }993};994 995/**996 * When the driver used emitWarning the code will be equal to this.997 * @public998 *999 * @example1000 * ```ts1001 * process.on('warning', (warning) => {1002 *  if (warning.code === MONGODB_WARNING_CODE) console.error('Ah an important warning! :)')1003 * })1004 * ```1005 */1006export const MONGODB_WARNING_CODE = 'MONGODB DRIVER';1007 1008/** @internal */1009export function emitWarning(message: string): void {1010  return process.emitWarning(message, { code: MONGODB_WARNING_CODE } as any);1011}1012 1013const emittedWarnings = new Set();1014/**1015 * Will emit a warning once for the duration of the application.1016 * Uses the message to identify if it has already been emitted1017 * so using string interpolation can cause multiple emits1018 * @internal1019 */1020export function emitWarningOnce(message: string): void {1021  if (!emittedWarnings.has(message)) {1022    emittedWarnings.add(message);1023    return emitWarning(message);1024  }1025}1026 1027/**1028 * Takes a JS object and joins the values into a string separated by ', '1029 */1030export function enumToString(en: Record<string, unknown>): string {1031  return Object.values(en).join(', ');1032}1033 1034/**1035 * Determine if a server supports retryable writes.1036 *1037 * @internal1038 */1039export function supportsRetryableWrites(server?: Server): boolean {1040  if (!server) {1041    return false;1042  }1043 1044  if (server.loadBalanced) {1045    // Loadbalanced topologies will always support retry writes1046    return true;1047  }1048 1049  if (server.description.logicalSessionTimeoutMinutes != null) {1050    // that supports sessions1051    if (server.description.type !== ServerType.Standalone) {1052      // and that is not a standalone1053      return true;1054    }1055  }1056 1057  return false;1058}1059 1060/**1061 * Fisher–Yates Shuffle1062 *1063 * Reference: https://bost.ocks.org/mike/shuffle/1064 * @param sequence - items to be shuffled1065 * @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.1066 */1067export function shuffle<T>(sequence: Iterable<T>, limit = 0): Array<T> {1068  const items = Array.from(sequence); // shallow copy in order to never shuffle the input1069 1070  if (limit > items.length) {1071    throw new MongoRuntimeError('Limit must be less than the number of items');1072  }1073 1074  let remainingItemsToShuffle = items.length;1075  const lowerBound = limit % items.length === 0 ? 1 : items.length - limit;1076  while (remainingItemsToShuffle > lowerBound) {1077    // Pick a remaining element1078    const randomIndex = Math.floor(Math.random() * remainingItemsToShuffle);1079    remainingItemsToShuffle -= 1;1080 1081    // And swap it with the current element1082    const swapHold = items[remainingItemsToShuffle];1083    items[remainingItemsToShuffle] = items[randomIndex];1084    items[randomIndex] = swapHold;1085  }1086 1087  return limit % items.length === 0 ? items : items.slice(lowerBound);1088}1089 1090/**1091 * TODO(NODE-4936): read concern eligibility for commands should be codified in command construction1092 * @internal1093 * @see https://github.com/mongodb/specifications/blob/master/source/read-write-concern/read-write-concern.md#read-concern1094 */1095export function commandSupportsReadConcern(command: Document): boolean {1096  if (command.aggregate || command.count || command.distinct || command.find || command.geoNear) {1097    return true;1098  }1099 1100  return false;1101}1102 1103/**1104 * Compare objectIds. `null` is always less1105 * - `+1 = oid1 is greater than oid2`1106 * - `-1 = oid1 is less than oid2`1107 * - `+0 = oid1 is equal oid2`1108 */1109export function compareObjectId(oid1?: ObjectId | null, oid2?: ObjectId | null): 0 | 1 | -1 {1110  if (oid1 == null && oid2 == null) {1111    return 0;1112  }1113 1114  if (oid1 == null) {1115    return -1;1116  }1117 1118  if (oid2 == null) {1119    return 1;1120  }1121 1122  return ByteUtils.compare(oid1.id, oid2.id);1123}1124 1125export function parseInteger(value: unknown): number | null {1126  if (typeof value === 'number') return Math.trunc(value);1127  const parsedValue = Number.parseInt(String(value), 10);1128 1129  return Number.isNaN(parsedValue) ? null : parsedValue;1130}1131 1132export function parseUnsignedInteger(value: unknown): number | null {1133  const parsedInt = parseInteger(value);1134 1135  return parsedInt != null && parsedInt >= 0 ? parsedInt : null;1136}1137 1138/**1139 * This function throws a MongoAPIError in the event that either of the following is true:1140 * * If the provided address domain does not match the provided parent domain1141 * * 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 parent1142 *1143 * If a DNS server were to become compromised SRV records would still need to1144 * advertise addresses that are under the same domain as the srvHost.1145 *1146 * @param address - The address to check against a domain1147 * @param srvHost - The domain to check the provided address against1148 * @returns void1149 */1150export function checkParentDomainMatch(address: string, srvHost: string): void {1151  // Remove trailing dot if exists on either the resolved address or the srv hostname1152  const normalizedAddress = address.endsWith('.') ? address.slice(0, address.length - 1) : address;1153  const normalizedSrvHost = srvHost.endsWith('.') ? srvHost.slice(0, srvHost.length - 1) : srvHost;1154 1155  const allCharacterBeforeFirstDot = /^.*?\./;1156  const srvIsLessThanThreeParts = normalizedSrvHost.split('.').length < 3;1157  // Remove all characters before first dot1158  // Add leading dot back to string so1159  //   an srvHostDomain = '.trusted.site'1160  //   will not satisfy an addressDomain that endsWith '.fake-trusted.site'1161  const addressDomain = `.${normalizedAddress.replace(allCharacterBeforeFirstDot, '')}`;1162  let srvHostDomain = srvIsLessThanThreeParts1163    ? normalizedSrvHost1164    : `.${normalizedSrvHost.replace(allCharacterBeforeFirstDot, '')}`;1165 1166  if (!srvHostDomain.startsWith('.')) {1167    srvHostDomain = '.' + srvHostDomain;1168  }1169  if (1170    srvIsLessThanThreeParts &&1171    normalizedAddress.split('.').length <= normalizedSrvHost.split('.').length1172  ) {1173    throw new MongoAPIError(1174      'Server record does not have at least one more domain level than parent URI'1175    );1176  }1177  if (!addressDomain.endsWith(srvHostDomain)) {1178    throw new MongoAPIError('Server record does not share hostname with parent URI');1179  }1180}1181 1182interface RequestOptions {1183  json?: boolean;1184  method?: string;1185  timeout?: number;1186  headers?: http.OutgoingHttpHeaders;1187}1188 1189/**1190 * Perform a get request that returns status and body.1191 * @internal1192 */1193export function get(1194  url: URL | string,1195  options: http.RequestOptions = {}1196): Promise<{ body: string; status: number | undefined }> {1197  return new Promise((resolve, reject) => {1198    /* eslint-disable prefer-const */1199    let timeoutId: NodeJS.Timeout;1200    const request = http

Showing the first 1,200 of 1510 lines. Download the file for the rest.