@roots/container
Version:
Collections utility
854 lines (853 loc) • 21.7 kB
JavaScript
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);