UNPKG

@roots/container

Version:

Collections utility

854 lines (853 loc) 21.7 kB
import { __decorate } from "tslib"; import { bind } from 'helpful-decorators'; import get from 'lodash-es/get.js'; import has from 'lodash-es/has.js'; import isArray from 'lodash-es/isArray.js'; import isEmpty from 'lodash-es/isEmpty.js'; import isEqual from 'lodash-es/isEqual.js'; import isFunction from 'lodash-es/isFunction.js'; import isNull from 'lodash-es/isNull.js'; import isNumber from 'lodash-es/isNumber.js'; import isString from 'lodash-es/isString.js'; import isUndefined from 'lodash-es/isUndefined.js'; import set from 'lodash-es/set.js'; import size from 'lodash-es/size.js'; import uniq from 'lodash-es/uniq.js'; import { mergeable } from './utilities.js'; /** * Provides a simple chainable interface for working with collections of data */ export default class Container { /** * Identifier */ ident = `container`; /** * The container store */ repository; /** * Class constructor * * @param repository - Key-value data store */ constructor(repository) { this.setStore(repository ?? {}); } /** * Returns the repository in its entirety as a plain JS object * * @example * ```js * container.all() * ``` */ all() { return this.repository; } /** * Return a number indicating the length of a matched key * in the repository. * * If no key is provided, returns the length of the repository. * * @param key - search key * @returns count of items */ count(key) { if (key) { if (!this.has(key)) { throw new Error(`${key} does not exist in the container`); } return size(this.get(key)); } return size(this.repository); } /** * Use each value as parameters in a supplied callback * * @example * ```js * container.withEntries('key', (key, value) => doSomething) * ``` */ each(key, callFn) { this.getEntries(key).forEach(([key, value]) => [ key, callFn(key, value), ]); return this; } /** * Calls a supplied function for every repository value, passing * the item's key and value as callback parameters. * * @example * ```js * container.withEntries('key', (key, value) => doSomething) * ``` */ every(fn) { this.getEntries().forEach(([key, value]) => { fn(key, value); }); return this; } /** * Merges object created from an array of tuples with the repository. * * @example * ```js * container.getEntries() * ``` * * @example * ```js * container.getEntries('key') * ``` */ fromEntries(entries) { this.mergeStore(Object.fromEntries(entries)); return this; } /** * Returns a value from the the repository. * * @remarks * If no key is passed the container store will be returned. * * @example * ```js * container.get('container.container-item') * ``` * * @example * ```js * container.get(['container', 'container-item']) * ``` */ get(key) { return get(this.repository, key); } /**w * Returns a repository key and value as a tuple * * @remarks * If no key is passed the container store will be used. * * @example * ```js * container.getEntries() * ``` * * @example * ```js * container.getEntries('key') * ``` */ getEntries(key) { if (!key) return Object.entries(this.repository); if (!this.has(key)) { throw new Error(`Key ${key} does not exist`); } return Object.entries(this.get(key)); } /** * Returns an array of values of the enumerable keys of a repository object * * @example * ```js * container.getKeys('item') * // => returns keys of container.repository[item] * ``` * * @example * ```js * container.getKeys() * // => returns keys of container.repository * ``` */ getKeys(key) { return Object.keys(key ? this.get(key) : this.all()); } /** * Get a repository item as a {@link MapConstructor}. * * @remarks * If no key is passed the container store is mapped. * * @example * Returns `repository.item` as a Map: * ```js * container.getMap('item') * ``` * * @example * Returns the entire repository as a Map: * * ```js * container.getMap() * ``` */ getMap(key) { const reducer = [ (map, [key, value]) => { map.set(key, value); return map; }, new Map(), ]; return this.getEntries(key).reduce(...reducer); } /** * Returns an array of values of the enumerable properties of a repository object * * @example * ```js * container.getValues('container.container-item') * ``` * * @example * ```js * container.getValues() * // => returns values from entire store * ``` */ getValues(key) { return Object.values(key ? this.get(key) : this.all()); } /** * Return a boolean indicating if a given key exists. * * @example * ```js * container.has('my-key') * // true if container.repository['my-key'] exists * ``` */ has(key) { return has(this.repository, key); } /** * Return a boolean indicating if the given key matches the given value. * * @example * ```js * container.is('my-key', {whatever: 'value'}) * // True if container.repository['my-key'] === {whatever: 'value'} * ``` */ is(key, value) { return isEqual(this.get(key), value); } /** * Return true if object is an array. * * @example * ```js * container.isArray('my-key') * // True if container.repository['my-key'] is an array * ``` * * @param key - The key to check * @returns True if the value is an array */ isArray(key) { return this.has(key) && isArray(this.get(key)); } /** * Return true if object is defined. * * @example * True if container has a 'my-key' entry with a definite value. * * ```js * container.isDefined('my-key') * ``` * * @param key - The key to check. * @returns True if the key is defined. */ isDefined(key) { return this.has(key) && !isUndefined(this.get(key)); } /** * Checks if value is empty. A value is considered empty unless * it’s an arguments object, array, string, or jQuery-like * collection with a length greater than 0 or an object with * own enumerable properties. * * @example * ```js * container.isEmpty('my-key') * // True if object associated with 'my-key' is empty. * ``` * * @param key - search key * @returns True if object is empty. */ isEmpty(key) { if (key) { return this.has(key) && isEmpty(this.get(key)); } return isEmpty(this.repository); } /** * Return a boolean indicating if the given key's value is false * * @example * ```js * container.isFalse('my-key') * // True if container.repository['my-key'] === false * ``` * * @param key - The key to check * @returns True if the key's value is false */ isFalse(key) { return this.is(key, false); } /** * Return true if object is a function * * @example * ```js * container.isFunction('my-key') * // True if object associated with 'my-key' is a fn. * ``` * * @param key - The key to check. * @returns True if object is a function. */ isFunction(key) { return this.has(key) && isFunction(this.get(key)); } /** * Return true if object is an instance of a class. * * @example * ```js * container.isInstanceOf('my-key', MyClass) * // True if object associated with 'my-key' is an instance of MyClass. * ``` * * @param key - The key to check. * @param instance - The class to check. * @returns True if object is an instance of the class. */ isInstanceOf(key, instance) { return this.has(key) && this.get(key) instanceof instance; } /** * Return true if object is an instance of any of the classes. * * @example * ```js * container.isInstanceOfAny('my-key', [MyClass, MyOtherClass]) * // True if object associated with 'my-key' is an instance of MyClass or MyOtherClass. * ``` * * @param key - the key to check * @param instances - The classes to check against * @returns */ isInstanceOfAny(key, instances) { return (this.has(key) && instances.some(instance => this.get(key) instanceof instance)); } /** * Return true if object is not an array. * * @example * ```js * container.isNotArray('my-key') * // True if container.repository['my-key'] is not an array * ``` * * @param key - The key to check * @returns True if object is not an array */ isNotArray(key) { return this.has(key) && !isArray(this.get(key)); } /** * Checks if value is not empty. A value is considered empty unless * it’s an arguments object, array, string, or jQuery-like * collection with a length greater than 0 or an object with * own enumerable properties. * * @example * ```js * container.isNotEmpty('my-key') * // True if object associated with 'my-key' is not empty. * ``` * * @param key - search key * @returns True if object is not empty. */ isNotEmpty(key) { return this.has(key) && !isEmpty(this.get(key)); } /** * Return true if object is not a function * * @example * ```js * container.isNotFunction('my-key') * // True if object associated with 'my-key' is not a fn. * ``` * * @param key - The key to check. * @returns True if object is not a function. */ isNotFunction(key) { return this.has(key) && !isFunction(this.get(key)); } /** * Return true if object is not an instance of a class. * * @example * ```js * container.isNotInstanceOf('my-key', MyClass) * // True if object associated with 'my-key' is not an instance of MyClass. * ``` * * @param key - The key to check. * @param instance - The class to check against * @returns */ isNotInstanceOf(key, instance) { return this.has(key) && !(this.get(key) instanceof instance); } /** * Return true if object is not an instance of any of the classes. * * @example * ```js * container.isNotInstanceOfAny('my-key', [MyClass, MyOtherClass]) * // True if object associated with 'my-key' is not an instance of MyClass or MyOtherClass. * ``` * * @param key - search key * @param instances - classes * @returns */ isNotInstanceOfAny(key, instances) { return (this.has(key) && !instances.some(instance => this.get(key) instanceof instance)); } /** * Return true if object is not null. * * @example * ```js * container.isNotNull('my-key') * // True if container.repository['my-key'] is not null * ``` * * @param key - The key to check * @returns True if object is not null */ isNotNull(key) { return this.has(key) && !isNull(this.get(key)); } /** * Return true if object is not a number. * * @example * ```js * container.isNumber('my-key') * // True if container.repository['my-key'] is not a number * ``` * * @param key - The key to check * @returns True if object is not a number */ isNotNumber(key) { return this.has(key) && !isNumber(this.get(key)); } /** * Return true if object is not a string. * * @example * ```js * container.isString('my-key') * // True if container.repository['my-key'] is not a string * ``` * * @param key - The key to check * @returns True if object is not a string */ isNotString(key) { return this.has(key) && !isString(this.get(key)); } /** * Return true if object is null. * * @example * ```js * container.isNull('my-key') * // True if container.repository['my-key'] is null * ``` * * @param key - The key to check * @returns True if object is null */ isNull(key) { return this.has(key) && isNull(this.get(key)); } /** * Return true if object is a number. * * @example * ```js * container.isNumber('my-key') * // True if container.repository['my-key'] is a number * ``` * * @param key - The key to check * @returns True if object is a number */ isNumber(key) { return this.has(key) && isNumber(this.get(key)); } /** * Return true if object is a string. * * @example * ```js * container.isString('my-key') * // True if container.repository['my-key'] is a string * ``` * * @param key - The key to check * @returns True if object is a string */ isString(key) { return this.has(key) && isString(this.get(key)); } /** * Return a boolean indicating if the given key's value is true * * @example * ```js * container.isTrue('my-key') * // True if container.repository['my-key'] === true * ``` * * @param key - The key to check * @returns True if the key's value is true */ isTrue(key) { return this.is(key, true); } /** * Return true if object is not defined. * * @example * ```js * container.isDefined('my-key') * // True if container has a 'my-key' entry with a definite value. * ``` * * @param key - The key to check. * @returns True if the key is not defined. */ isUndefined(key) { return !this.has(key) || isUndefined(this.get(key)); } /** * Merge a supplied value with an existing repository value * * @example * ```js * container.merge('key', {merge: values}) * ``` * * @param key - The key of the item to merge * @param value - The value to merge with the existing value */ merge(key, value) { const existent = this.get(key); if (typeof existent !== typeof value) { throw new Error(`Cannot merge ${typeof value} with ${typeof existent}`); } if (!mergeable(existent)) throw new Error(`${key} is a ${typeof existent} and cannot be merged with`); if (isArray(value)) { if (!isArray(existent)) { throw new Error(`${key} is not an array and cannot have an array merged onto it`); } this.set(key, [...existent, ...value]); } else { this.set(key, { ...existent, ...value }); } return this; } /** * Merge values onto the container store. * * @example * ```js * container.mergeStore({test: 'foo'}) * ``` * * @param values - Values to merge onto the container store * @returns The container instance */ mergeStore(values) { return this.setStore({ ...this.all(), ...values }); } /** * Mutate a repository item * * @example * ```js * container.mutate('key', currentValue => modifiedValue) * ``` * * @param key - The key of the item to mutate * @param mutationFn - The mutation function to run on the item */ mutate(key, mutationFn) { this.set(key, this.transform(key, mutationFn)); return this; } /** * Runs the entire repository through the supplied fn and returns * the transformed value. The transformed repository replaces the * original. * * @example * ```js * container.mutate('key', currentValue => modifiedValue) * ``` */ mutateStore(mutationFn) { const transform = this.transformStore(mutationFn); this.setStore(transform); return this; } /** * delete * * Delete an entry from the repository * * @example * ```js * container.remove('my-key') * // Remove container.repository['my-key'] * ``` */ remove(key) { delete this.repository[key]; return this; } /** * Set a repository value * * @example * ```js * container.set('key', value) * ``` */ set(key, value) { set(this.repository, key, value); return this; } /** * Replace the store with an all new set of values * * @example * ```js * container.setStore({ * new: ['store', 'contents'], * }) * ``` */ setStore(repository) { if (isUndefined(repository)) { throw new Error(`Repository cannot be empty`); } this.repository = repository; return this; } /** * Retrieve a container item, running it through the supplied fn. * * @remarks * Returns the transformed value. * * @example * ```js * container.transform('key', currentValue => modifiedValue) * ``` * * @param key - The key of the item to transform * @param fn - The function to transform the item with */ transform(key, mutationFn) { return mutationFn(this.get(key)); } /** * Runs the entire repository through the supplied fn and returns * the transformed value. * * @example * ```js * container.transform(store=> modifiedStore) * ``` * * @param fn - Function to run on the repository * @returns The transformed repository */ transformStore(transformFn) { return transformFn(this.all()); } /** * Returns unique elements of an array item * * @example * ```js * container.unique('item') * ``` */ unique(key) { if (!this.has(key)) { throw new Error(`${key} does not exist in the container`); } const value = this.get(key); if (!isArray(value)) { throw new Error(`${key} is not an array`); } return uniq(value); } } __decorate([ bind ], Container.prototype, "all", null); __decorate([ bind ], Container.prototype, "count", null); __decorate([ bind ], Container.prototype, "each", null); __decorate([ bind ], Container.prototype, "every", null); __decorate([ bind ], Container.prototype, "fromEntries", null); __decorate([ bind ], Container.prototype, "get", null); __decorate([ bind ], Container.prototype, "getEntries", null); __decorate([ bind ], Container.prototype, "getKeys", null); __decorate([ bind ], Container.prototype, "getMap", null); __decorate([ bind ], Container.prototype, "getValues", null); __decorate([ bind ], Container.prototype, "has", null); __decorate([ bind ], Container.prototype, "is", null); __decorate([ bind ], Container.prototype, "isArray", null); __decorate([ bind ], Container.prototype, "isDefined", null); __decorate([ bind ], Container.prototype, "isEmpty", null); __decorate([ bind ], Container.prototype, "isFalse", null); __decorate([ bind ], Container.prototype, "isFunction", null); __decorate([ bind ], Container.prototype, "isInstanceOf", null); __decorate([ bind ], Container.prototype, "isInstanceOfAny", null); __decorate([ bind ], Container.prototype, "isNotArray", null); __decorate([ bind ], Container.prototype, "isNotEmpty", null); __decorate([ bind ], Container.prototype, "isNotFunction", null); __decorate([ bind ], Container.prototype, "isNotInstanceOf", null); __decorate([ bind ], Container.prototype, "isNotInstanceOfAny", null); __decorate([ bind ], Container.prototype, "isNotNull", null); __decorate([ bind ], Container.prototype, "isNotNumber", null); __decorate([ bind ], Container.prototype, "isNotString", null); __decorate([ bind ], Container.prototype, "isNull", null); __decorate([ bind ], Container.prototype, "isNumber", null); __decorate([ bind ], Container.prototype, "isString", null); __decorate([ bind ], Container.prototype, "isTrue", null); __decorate([ bind ], Container.prototype, "isUndefined", null); __decorate([ bind ], Container.prototype, "merge", null); __decorate([ bind ], Container.prototype, "mergeStore", null); __decorate([ bind ], Container.prototype, "mutate", null); __decorate([ bind ], Container.prototype, "mutateStore", null); __decorate([ bind ], Container.prototype, "remove", null); __decorate([ bind ], Container.prototype, "set", null); __decorate([ bind ], Container.prototype, "setStore", null); __decorate([ bind ], Container.prototype, "transform", null); __decorate([ bind ], Container.prototype, "transformStore", null); __decorate([ bind ], Container.prototype, "unique", null);