Discord.js Collection

  • 264
  • 147
Короче я перенёс Discord.js Collection с Typescript на Javascript
Может кому понадобится

Могут быть ошибки в выполнении и документации

P.S Collection это улучшенная версия Map() с такими плюшками как filter(), first(), find() и прочее
Collection.js:
const Collection = class extends Map {
    /**
     * Obtains the value of the given key if it exists, otherwise sets and returns the value provided by the default value generator.
     *
     * @param key - The key to get if it exists, or set otherwise
     * @param defaultValueGenerator - A function that generates the default value
     *
     * @example
     * ```
     * collection.ensure(guildId, () => defaultGuildConfig);
     * ```
     */
    ensure = (key, defaultValueGenerator = (key, collection = this)) => {
        if (this.has(key)) return this.get(key);
        if (typeof defaultValueGenerator !== 'function') throw new TypeError(`${defaultValueGenerator} is not a function`);
        const defaultValue = defaultValueGenerator(key, this);
        this.set(key, defaultValue);
        return defaultValue;
    }

    /**
     * Checks if all of the elements exist in the collection.
     *
     * @param {Array} keys - The keys of the elements to check for
     *
     * @returns `true` if all of the elements exist, `false` if at least one does not exist.
     */
    hasAll = (...keys) => {
        return keys.every((k) => super.has(k));
    }

    /**
     * Checks if any of the elements exist in the collection.
     *
     * @param {Array} keys - The keys of the elements to check for
     *
     * @returns `true` if any of the elements exist, `false` if none exist.
     */
    hasAny = (...keys) => {
        return keys.some((k) => super.has(k));
    }

    /**
     * Obtains the first value(s) in this collection.
     *
     * @param {(Number|Array)=} amount Amount of values to obtain from the beginning
     *
     * @returns A single value if no amount is provided or an array of values, starting from the end if amount is negative
     */
    first = (amount) => {
        if (typeof amount === 'undefined') return this.values().next().value;
        if (amount < 0) return this.last(amount * -1);
        amount = Math.min(this.size, amount);
        const iter = this.values();
        return Array.from({ length: amount }, () => iter.next().value);
    }

    /**
     * Obtains the first key(s) in this collection.
     *
     * @param {(Number|Array)=} amount - Amount of keys to obtain from the beginning
     *
     * @returns A single key if no amount is provided or an array of keys, starting from the end if
     * amount is negative
     */
    firstKey = (amount) => {
        if (typeof amount === 'undefined') return this.keys().next().value;
        if (amount < 0) return this.lastKey(amount * -1);
        amount = Math.min(this.size, amount);
        const iter = this.keys();
        // eslint-disable-next-line @typescript-eslint/no-unsafe-return
        return Array.from({ length: amount }, () => iter.next().value);
    }

    /**
     * Obtains the last value(s) in this collection.
     *
     * @param {(Number|Array)=} amount - Amount of values to obtain from the end
     *
     * @returns A single value if no amount is provided or an array of values, starting from the start if
     * amount is negative
     */
    last = (amount) => {
        const arr = [...this.values()];
        if (typeof amount === 'undefined') return arr[arr.length - 1];
        if (amount < 0) return this.first(amount * -1);
        if (!amount) return [];
        return arr.slice(-amount);
    }

    /**
     * Obtains the last key(s) in this collection.
     *
     * @param {(Number|Array)=} amount - Amount of keys to obtain from the end
     *
     * @returns A single key if no amount is provided or an array of keys, starting from the start if
     * amount is negative
     */
    lastKey = (amount) => {
        const arr = [...this.keys()];
        if (typeof amount === 'undefined') return arr[arr.length - 1];
        if (amount < 0) return this.firstKey(amount * -1);
        if (!amount) return [];
        return arr.slice(-amount);
    }

    /**
     * Identical to {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at | Array.at()}.
     * Returns the item at a given index, allowing for positive and negative integers.
     * Negative integers count back from the last item in the collection.
     *
     * @param {Number} index - The index of the element to obtain
     */
    at = (index) => {
        index = Math.floor(index);
        const arr = [...this.values()];
        return arr.at(index);
    }

    /**
     * Identical to {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at | Array.at()}.
     * Returns the key at a given index, allowing for positive and negative integers.
     * Negative integers count back from the last item in the collection.
     *
     * @param {Number} index - The index of the key to obtain
     */
    keyAt = (index) => {
        index = Math.floor(index);
        const arr = [...this.keys()];
        return arr.at(index);
    }

    /**
     * Obtains unique random value(s) from this collection.
     *
     * @param {Number=} amount - Amount of values to obtain randomly
     *
     * @returns A single value if no amount is provided or an array of values
     */
    random = (amount) => {
        const arr = [...this.values()];
        if (typeof amount === 'undefined') return arr[Math.floor(Math.random() * arr.length)];
        if (!arr.length || !amount) return [];
        return Array.from(
            { length: Math.min(amount, arr.length) },
            () => arr.splice(Math.floor(Math.random() * arr.length), 1)[0],
        );
    }

    /**
     * Obtains unique random key(s) from this collection.
     *
     * @param {Number=} amount - Amount of keys to obtain randomly
     *
     * @returns A single key if no amount is provided or an array
     */
    randomKey = (amount) => {
        const arr = [...this.keys()];
        if (typeof amount === 'undefined') return arr[Math.floor(Math.random() * arr.length)];
        if (!arr.length || !amount) return [];
        return Array.from(
            { length: Math.min(amount, arr.length) },
            () => arr.splice(Math.floor(Math.random() * arr.length), 1)[0],
        );
    }

    /**
     * Identical to {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse | Array.reverse()}
     * but returns a Collection instead of an Array.
     */
    reverse = () => {
        const entries = [...this.entries()].reverse();
        this.clear();
        for (const [key, value] of entries) this.set(key, value);
        return this;
    }

    /**
     * Searches for a single item where the given function returns a truthy value. This behaves like
     * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find | Array.find()}.
     * <warn>All collections used in Discord.js are mapped using their `id` property, and if you want to find by id you
     * should use the `get` method. See
     * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get | MDN} for details.</warn>
     *
     * @param fn - The function to test with (should return boolean)
     * @param thisArg - Value to use as `this` when executing function
     *
     * @example
     * ```
     * collection.find(user => user.username === 'Bob');
     * ```
     */
    find = (fn = (value, key, collection = this) => Boolean, thisArg) => {
        if (typeof fn !== 'function') throw new TypeError(`${fn} is not a function`);
        if (typeof thisArg !== 'undefined') fn = fn.bind(thisArg);
        for (const [key, val] of this) {
            if (fn(val, key, this)) return val;
        }
        return undefined;
    }

    /**
     * Searches for the key of a single item where the given function returns a truthy value. This behaves like
     * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex | Array.findIndex()},
     * but returns the key rather than the positional index.
     *
     * @param fn - The function to test with (should return boolean)
     * @param thisArg - Value to use as `this` when executing function
     *
     * @example
     * ```
     * collection.findKey(user => user.username === 'Bob');
     * ```
     */
    findKey = (fn = (value, key, collection = this) => Boolean, thisArg) => {
        if (typeof fn !== 'function') throw new TypeError(`${fn} is not a function`);
        if (typeof thisArg !== 'undefined') fn = fn.bind(thisArg);
        for (const [key, val] of this) {
            if (fn(val, key, this)) return key;
        }
        return undefined;
    }

    /**
     * Removes items that satisfy the provided filter function.
     *
     * @param fn - Function used to test (should return a boolean)
     * @param thisArg - Value to use as `this` when executing function
     *
     * @returns The number of removed entries
     */
    sweep = (fn = (value, key, collection = this) => Boolean, thisArg) => {
        if (typeof fn !== 'function') throw new TypeError(`${fn} is not a function`);
        if (typeof thisArg !== 'undefined') fn = fn.bind(thisArg);
        const previousSize = this.size;
        for (const [key, val] of this) {
            if (fn(val, key, this)) this.delete(key);
        }
        return previousSize - this.size;
    }

    /**
     * Identical to
     * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter | Array.filter()},
     * but returns a Collection instead of an Array.
     *
     * @param fn - The function to test with (should return boolean)
     * @param thisArg - Value to use as `this` when executing function
     *
     * @example
     * ```
     * collection.filter(user => user.username === 'Bob');
     * ```
     */
    filter = (fn = (value, key, collection = this) => Boolean, thisArg) => {
        if (typeof fn !== 'function') throw new TypeError(`${fn} is not a function`);
        if (typeof thisArg !== 'undefined') fn = fn.bind(thisArg);
        const results = new this.constructor[Symbol.species]();
        for (const [key, val] of this) {
            if (fn(val, key, this)) results.set(key, val);
        }
        return results;
    }

    /**
     * Partitions the collection into two collections where the first collection
     * contains the items that passed and the second contains the items that failed.
     *
     * @param fn - Function used to test (should return a boolean)
     * @param thisArg - Value to use as `this` when executing function
     *
     * @example
     * ```
     * const [big, small] = collection.partition(guild => guild.memberCount > 250);
     * ```
     */
    partition = (fn = (value, key, collection = this) => Boolean, thisArg) => {
        if (typeof fn !== 'function') throw new TypeError(`${fn} is not a function`);
        if (typeof thisArg !== 'undefined') fn = fn.bind(thisArg);
        const results = [
            new this.constructor[Symbol.species](),
            new this.constructor[Symbol.species](),
        ];
        for (const [key, val] of this) {
            if (fn(val, key, this)) {
                results[0].set(key, val);
            } else {
                results[1].set(key, val);
            }
        }
        return results;
    }

    /**
     * Maps each item into a Collection, then joins the results into a single Collection. Identical in behavior to
     * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap | Array.flatMap()}.
     *
     * @param fn - Function that produces a new Collection
     * @param thisArg - Value to use as `this` when executing function
     *
     * @example
     * ```
     * collection.flatMap(guild => guild.members.cache);
     * ```
     */
    flatMap = (fn = (value, key, collection = this) => Boolean, thisArg) => {
        const collections = this.map(fn, thisArg);
        return new this.constructor[Symbol.species]().concat(...collections);
    }

    /**
     * Maps each item to another value into an array. Identical in behavior to
     * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map | Array.map()}.
     *
     * @param fn - Function that produces an element of the new array, taking three arguments
     * @param thisArg - Value to use as `this` when executing function
     *
     * @example
     * ```
     * collection.map(user => user.tag);
     * ```
     */
    map = (fn = (value, key, collection = this) => Boolean, thisArg) => {
        if (typeof fn !== 'function') throw new TypeError(`${fn} is not a function`);
        if (typeof thisArg !== 'undefined') fn = fn.bind(thisArg);
        const iter = this.entries();
        return Array.from({ length: this.size }, () => {
            // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
            const [key, value] = iter.next().value;
            // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
            return fn(value, key, this);
        });
    }

    /**
     * Maps each item to another value into a collection. Identical in behavior to
     * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map | Array.map()}.
     *
     * @param fn - Function that produces an element of the new collection, taking three arguments
     * @param thisArg - Value to use as `this` when executing function
     *
     * @example
     * ```
     * collection.mapValues(user => user.tag);
     * ```
     */
    mapValues = (fn = (value, key, collection = this) => Boolean, thisArg) => {
        if (typeof fn !== 'function') throw new TypeError(`${fn} is not a function`);
        if (typeof thisArg !== 'undefined') fn = fn.bind(thisArg);
        const coll = new this.constructor[Symbol.species]();
        for (const [key, val] of this) coll.set(key, fn(val, key, this));
        return coll;
    }

    /**
     * Checks if there exists an item that passes a test. Identical in behavior to
     * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some | Array.some()}.
     *
     * @param fn - Function used to test (should return a boolean)
     * @param thisArg - Value to use as `this` when executing function
     *
     * @example
     * ```
     * collection.some(user => user.discriminator === '0000');
     * ```
     */
    some = (fn = (value, key, collection = this) => Boolean, thisArg) => {
        if (typeof fn !== 'function') throw new TypeError(`${fn} is not a function`);
        if (typeof thisArg !== 'undefined') fn = fn.bind(thisArg);
        for (const [key, val] of this) {
            if (fn(val, key, this)) return true;
        }
        return false;
    }

    /**
     * Checks if all items passes a test. Identical in behavior to
     * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every | Array.every()}.
     *
     * @param fn - Function used to test (should return a boolean)
     * @param thisArg - Value to use as `this` when executing function
     *
     * @example
     * ```
     * collection.every(user => !user.bot);
     * ```
     */
    every = (fn = (value, key, collection = this) => Boolean, thisArg) => {
        if (typeof fn !== 'function') throw new TypeError(`${fn} is not a function`);
        if (typeof thisArg !== 'undefined') fn = fn.bind(thisArg);
        for (const [key, val] of this) {
            if (!fn(val, key, this)) return false;
        }
        return true;
    }

    /**
     * Applies a function to produce a single value. Identical in behavior to
     * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce | Array.reduce()}.
     *
     * @param fn - Function used to reduce, taking four arguments; `accumulator`, `currentValue`, `currentKey`,
     * and `collection`
     * @param initialValue - Starting value for the accumulator
     *
     * @example
     * ```
     * collection.reduce((acc, guild) => acc + guild.memberCount, 0);
     * ```
     */
    reduce = (fn = (accumulator, value, key, collection = this) => Boolean, thisArg) => {
        if (typeof fn !== 'function') throw new TypeError(`${fn} is not a function`);
        let accumulator;

        if (typeof initialValue !== 'undefined') {
            accumulator = initialValue;
            for (const [key, val] of this) accumulator = fn(accumulator, val, key, this);
            return accumulator;
        }
        let first = true;
        for (const [key, val] of this) {
            if (first) {
                accumulator = val;
                first = false;
                continue;
            }
            accumulator = fn(accumulator, val, key, this);
        }

        // No items iterated.
        if (first) {
            throw new TypeError('Reduce of empty collection with no initial value');
        }

        return accumulator;
    }

    /**
     * Identical to
     * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/forEach | Map.forEach()},
     * but returns the collection instead of undefined.
     *
     * @param fn - Function to execute for each element
     * @param thisArg - Value to use as `this` when executing function
     *
     * @example
     * ```
     * collection
     *  .each(user => console.log(user.username))
     *  .filter(user => user.bot)
     *  .each(user => console.log(user.username));
     * ```
     */
    each = (fn = (value, key, collection = this) => Boolean, thisArg) => {
        if (typeof fn !== 'function') throw new TypeError(`${fn} is not a function`);
        this.forEach(fn = (value, key, map = Map) => void thisArg);
        return this;
    }

    /**
     * Runs a function on the collection and returns the collection.
     *
     * @param fn - Function to execute
     * @param thisArg - Value to use as `this` when executing function
     *
     * @example
     * ```
     * collection
     *  .tap(coll => console.log(coll.size))
     *  .filter(user => user.bot)
     *  .tap(coll => console.log(coll.size))
     * ```
     */
    tap = (fn = (collection = this) => Boolean, thisArg) => {
        if (typeof fn !== 'function') throw new TypeError(`${fn} is not a function`);
        if (typeof thisArg !== 'undefined') fn = fn.bind(thisArg);
        fn(this);
        return this;
    }

    /**
     * Creates an identical shallow copy of this collection.
     *
     * @example
     * ```
     * const newColl = someColl.clone();
     * ```
     */
    clone = () => {
        return new this.constructor[Symbol.species](this);
    }

    /**
     * Combines this collection with others into a new collection. None of the source collections are modified.
     *
     * @param {Array} collections - Collections to merge
     *
     * @example
     * ```
     * const newColl = someColl.concat(someOtherColl, anotherColl, ohBoyAColl);
     * ```
     */
    concat = (...collections) => {
        const newColl = this.clone();
        for (const coll of collections) {
            for (const [key, val] of coll) newColl.set(key, val);
        }
        return newColl;
    }

    /**
     * Checks if this collection shares identical items with another.
     * This is different to checking for equality using equal-signs, because
     * the collections may be different objects, but contain the same data.
     *
     * @param collection - Collection to compare with
     *
     * @returns Whether the collections have identical contents
     */
    equals = (collection) => {
        if (!collection) return false; // runtime check
        if (this === collection) return true;
        if (this.size !== collection.size) return false;
        for (const [key, value] of this) {
            if (!collection.has(key) || value !== collection.get(key)) {
                return false;
            }
        }
        return true;
    }

    /**
     * The sort method sorts the items of a collection in place and returns it.
     * The sort is not necessarily stable in Node 10 or older.
     * The default sort order is according to string Unicode code points.
     *
     * @param compareFunction - Specifies a function that defines the sort order.
     * If omitted, the collection is sorted according to each character's Unicode code point value, according to the string conversion of each element.
     *
     * @example
     * ```
     * collection.sort((userA, userB) => userA.createdTimestamp - userB.createdTimestamp);
     * ```
     */
    sort = (compareFunction = defaultSort) => {
        const entries = [...this.entries()];
        entries.sort((a, b) => compareFunction(a[1], b[1], a[0], b[0]));

        // Perform clean-up
        super.clear();

        // Set the new entries
        for (const [k, v] of entries) {
            super.set(k, v);
        }
        return this;
    }

    /**
     * The intersect method returns a new structure containing items where the keys and values are present in both original structures.
     *
     * @param other - The other Collection to filter against
     */
    intersect = (other) => {
        const coll = new this.constructor[Symbol.species]();
        for (const [k, v] of other) {
            if (this.has(k) && Object.is(v, this.get(k))) {
                coll.set(k, v);
            }
        }
        return coll;
    }

    /**
     * The difference method returns a new structure containing items where the key is present in one of the original structures but not the other.
     *
     * @param other - The other Collection to filter against
     */
    difference = (other) => {
        const coll = new this.constructor[Symbol.species]();
        for (const [k, v] of other) {
            if (!this.has(k)) coll.set(k, v);
        }
        for (const [k, v] of this) {
            if (!other.has(k)) coll.set(k, v);
        }
        return coll;
    }

        /**
     * Merges two Collections together into a new Collection.
     * @param other - The other Collection to merge with
     * @param whenInSelf - Function getting the result if the entry only exists in this Collection
     * @param whenInOther - Function getting the result if the entry only exists in the other Collection
     * @param whenInBoth - Function getting the result if the entry exists in both Collections
     *
     * @example
     * ```
     * // Sums up the entries in two collections.
     * coll.merge(
     *  other,
     *  x => ({ keep: true, value: x }),
     *  y => ({ keep: true, value: y }),
     *  (x, y) => ({ keep: true, value: x + y }),
     * );
     * ```
     *
     * @example
     * ```
     * // Intersects two collections in a left-biased manner.
     * coll.merge(
     *  other,
     *  x => ({ keep: false }),
     *  y => ({ keep: false }),
     *  (x, _) => ({ keep: true, value: x }),
     * );
     * ```
     */
    merge = (
        other,
        whenInSelf = (value, key),
        whenInOther = (valueOther, key),
        whenInBoth = (value, valueOther, key)
    ) => {
        const coll = new this.constructor[Symbol.species]();
        const keys = new Set([...this.keys(), ...other.keys()]);
        for (const k of keys) {
            const hasInSelf = this.has(k);
            const hasInOther = other.has(k);

            if (hasInSelf && hasInOther) {
                const r = whenInBoth(this.get(k), other.get(k), k);
                if (r.keep) coll.set(k, r.value);
            } else if (hasInSelf) {
                const r = whenInSelf(this.get(k), k);
                if (r.keep) coll.set(k, r.value);
            } else if (hasInOther) {
                const r = whenInOther(other.get(k), k);
                if (r.keep) coll.set(k, r.value);
            }
        }
        return coll;
    }

    /**
     * The sorted method sorts the items of a collection and returns it.
     * The sort is not necessarily stable in Node 10 or older.
     * The default sort order is according to string Unicode code points.
     *
     * @param compareFunction - Specifies a function that defines the sort order.
     * If omitted, the collection is sorted according to each character's Unicode code point value,
     * according to the string conversion of each element.
     *
     * @example
     * ```
     * collection.sorted((userA, userB) => userA.createdTimestamp - userB.createdTimestamp);
     * ```
     */
    sorted = (compareFunction = defaultSort) => {
        return new this.constructor[Symbol.species](this).sort((av, bv, ak, bk) => compareFunction(av, bv, ak, bk));
    }

    toJSON = () => {
        // toJSON is called recursively by JSON.stringify.
        return [...this.values()];
    }

    /**
     * @param {Number} firstValue
     * @param {Number} secondValue
     */
    static defaultSort = (firstValue, secondValue) => {
        return Number(firstValue > secondValue) || Number(firstValue === secondValue) - 1;
    }

    /**
     * Creates a Collection from a list of entries.
     *
     * @param entries - The list of entries
     * @param combine - Function to combine an existing entry with a new one
     *
     * @example
     * ```
     * Collection.combineEntries([["a", 1], ["b", 2], ["a", 2]], (x, y) => x + y);
     * // returns Collection { "a" => 3, "b" => 2 }
     * ```
     */
    static combineEntries = (entries, combine = (firstValue, secondValue, key)) => {
        const coll = new Collection();
        for (const [k, v] of entries) {
            if (coll.has(k)) {
                coll.set(k, combine(coll.get(k), v, k));
            } else {
                coll.set(k, v);
            }
        }
        return coll;
    }
}

export default Collection
 
Top Bottom