UNPKG

@typescript-package/map

Version:

A lightweight TypeScript library for enhanced `map` management.

491 lines (482 loc) 14.4 kB
import { Data, SymbolValue, WeakData } from '@typescript-package/data'; /** * @description * @export * @abstract * @class OnHook * @template Key * @template Value * @template {DataCore<any>} DataType */ class MapOnHook { /** * @description Hook called when the data is cleared. * @protected * @param {DataType} data The data holder. */ onClear(data) { } /** * @description Hook called when a value is deleted. * @protected * @param {DataType} data The data holder. */ onDelete(key, data) { } /** * @description Hook called before the `get` being invoked. * @protected * @param {Key} key The key to get the value. * @param {DataType} data The data holder. */ onGet(key, data) { } /** * @description Hook called when a value is added. * @protected * @param {key} key The key under which set the `value`. * @param {Type} value The value to set. * @param {Type} previousValue The previous value. * @param {DataType} data The data holder. */ onSet(key, value, previousValue, data) { } } /** * @description The abstract core class for building customizable `Map` and `DataCore` related classes. * @export * @abstract * @class CoreMap * @template Key * @template Value * @template {Map<Key, Value>} [MapType=Map<Key, Value>] The type of `Map`. * @template {DataCore<MapType>} [DataType=Data<MapType>] The `Data` storage type of `Map` type. * @extends {MapOnHook<Key, Value, DataType>} */ class CoreMap extends MapOnHook { /** * @description Returns the `string` tag representation of the `CoreMap` class when used in `Object.prototype.toString.call(instance)`. * @public * @readonly */ get [Symbol.toStringTag]() { return CoreMap.name; } /** * @description Returns the privately stored data class. * @public * @readonly * @type {DataType} */ get data() { return this.#data; } /** * @inheritdoc * @public * @readonly * @type {number} */ get size() { return this.#data.value.size; } /** * @description A privately stored data holder of generic type variable `DataType` for the `Map`. * @type {DataType} */ #data; /** * Creates an instance of `CoreMap` child class. * @constructor * @param {?[Key, Value][]} [entries] Initial value for `Map`. * @param {?MapTypeConstructor<Key, Value, MapType>} [map] The map of generic type variable `MapType` for `Map` value. * @param {?DataConstructorInput<MapType, DataType>} [data] The data store of generic type variable `DataType` for `Map` value. */ constructor(entries, map, data) { super(); this.#data = new (Array.isArray(data) ? data[0] : data ?? Data)(new (map ?? (Map))(entries), ...Array.isArray(data) ? data.slice(1) : []); } /** * @description Access to the readonly map by using a symbol. * @public * @returns {Readonly<MapType>} */ [SymbolValue]() { return this.#data.value; } /** * Clears all entries. * @inheritdoc * @public * @returns {this} */ clear() { this.onClear?.(this.#data); this.#data.value.clear(); return this; } /** * Deletes a value from the `key`. * @inheritdoc * @public * @param {Key} key The key to delete. * @returns {boolean} */ delete(key) { return this.#data.value.delete(key); } /** * @inheritdoc */ entries() { return this.#data.value.entries(); } /** * @inheritdoc * @public * @param {(value: Value, key: Key, map: Map<Key, Value>) => void} callbackfn * @param {?*} [thisArg] * @returns {this} */ forEach(callbackfn, thisArg) { this.#data.value.forEach(callbackfn, thisArg); return this; } /** * @inheritdoc * @public * @param {Key} key The key to get the value. */ get(key) { return this.onGet?.(key, this.#data), this.#data.value.get(key); } /** * @inheritdoc * @public * @param {Key} key The key to check. * @returns {boolean} */ has(key) { return this.onGet?.(key, this.#data), this.#data.value.has(key); } /** * @inheritdoc */ keys() { return this.#data.value.keys(); } /** * @inheritdoc * @public * @param {Key} key The key under which the `value` set. * @param {Value} value The value of `Value` type. * @returns {this} The `this` current instance for chaining. */ set(key, value) { this.onSet?.(key, value, this.#data.value.get(key), this.#data); this.#data.value.set(key, value); return this; } /** * @inheritdoc */ values() { return this.#data.value.values(); } } // Abstract. /** * @description The `DataMap` is a concrete class that extends `CoreMap` and encapsulates its data within a `DataCore` store, providing additional data management capabilities. * @export * @class DataMap * @template Key * @template Value * @template {DataCore<Map<Key, Value>>} [DataType=Data<Map<Key, Value>>] * @extends {CoreMap<Key, Value, Map<Key, Value>, DataType>} */ class DataMap extends CoreMap { /** * @description * @public * @static * @template {PropertyKey} Key * @template Value * @template {DataCore<Map<Key, Value>>} [DataType=Data<Map<Key, Value>>] * @param {Record<Key, Value>} obj * @param {?DataConstructorInput<Map<Key, Value>, DataType>} [data] * @returns {DataMap<Key, Value, DataType>} */ static fromObject(obj, data) { return new DataMap(Object.entries(obj), data); } /** * @description Returns the `string` tag representation of the `DataMap` class when used in `Object.prototype.toString.call(instance)`. * @public * @readonly */ get [Symbol.toStringTag]() { return DataMap.name; } /** * Creates an instance of `DataMap`. * @constructor * @param {?[Key, Value][]} [entries] Initial value for `Map`. * @param {?DataConstructorInput<Map<Key, Value>, DataType>} [data] The data store of `DataType` for `Map` value, also with params. */ constructor(entries, data) { super(entries, Map, data); } } // Abstract. /** * @description * @export * @class FactoryMap * @template Key * @template Value * @template {Map<Key, Value>} [MapType=Map<Key, Value>] * @template {DataCore<MapType>} [DataType=Data<MapType>] * @extends {CoreMap<Key, Value, MapType, DataType>} */ class FactoryMap extends CoreMap { /** * @description * @public * @static * @template {PropertyKey} Key * @template Value * @template {Map<Key, Value>} [MapType=Map<Key, Value>] * @template {DataCore<MapType>} [DataType=Data<MapType>] * @param {Record<Key, Value>} obj * @param {?MapTypeConstructor<Key, Value, MapType>} [map] * @param {?DataConstructorInput<MapType, DataType>} [data] * @param {{ * defaultValue?: () => Value; * cloner?: (value: Value) => Value; * }} [param0={}] * @param {() => Value} param0.defaultValue * @param {(value: Value) => Value} param0.cloner * @returns {FactoryMap<Key, Value, MapType, DataType>} */ static fromObject(obj, map, data, { defaultValue, cloner } = {}) { return new FactoryMap(Object.entries(obj), map, data, { cloner, defaultValue }); } /** * @description Returns the `string` tag representation of the `FactoryMap` class when used in `Object.prototype.toString.call(instance)`. * @public * @readonly */ get [Symbol.toStringTag]() { return FactoryMap.name; } /** * @description Returns the privately stored data class. * @public * @readonly * @type {DataType} */ get data() { return super.data; } /** * @description Returns the default value. * @public * @readonly * @type {Value | undefined} */ get defaultValue() { return this.#defaultValue?.(); } /** * @description Privately stored function to set the default value in missing keys. * @public * @readonly * @type {() => Value} */ #defaultValue; /** * @description Privately stored cloner function to clone the returned value. * @public * @readonly * @type {(value: Value) => Value} */ #cloner; /** * @description Privately stored comparator for sorting. * @param {[Key, Value]} a * @param {[Key, Value]} b * @returns {(a: [Key, Value], b: [Key, Value]) => number} */ #comparator = (a, b) => `${a}`.localeCompare(`${b}`); /** * @description Whether the map is ordered. * @type {boolean} */ #ordered; /** * Creates an instance of `FactoryMap`. * @constructor * @param {?[Key, Value][]} [entries] * @param {?MapTypeConstructor<Key, Value, MapType>} [map] * @param {?DataConstructorInput<MapType, DataType>} [data] * @param {{ * cloner?: (value: Value) => Value, * comparator?: ((a: [Key, Value], b: [Key, Value]) => number), * defaultValue?: () => Value, * ordered?: boolean; * }} [param0={}] * @param {(value: Value) => Value} param0.cloner * @param {(a: [Key, Value], b: [Key, Value]) => number} param0.comparator * @param {() => Value} param0.defaultValue * @param {boolean} param0.ordered */ constructor(entries, map, data, { cloner, comparator, defaultValue, ordered } = {}) { super(entries, map, data); this.#cloner = cloner; this.#defaultValue = defaultValue; this.#ordered = ordered; typeof comparator === 'function' && (this.#comparator = comparator); (Array.isArray(entries) && entries.length > 0 && ordered) && this.sort(); } /** * @inheritdoc * @public * @param {Key} key * @returns {(Value | undefined)} */ get(key) { (!super.has(key) && typeof this.#defaultValue === 'function') && super.data.value.set(key, this.#defaultValue()); const value = super.get(key); return typeof this.#cloner === 'function' && value ? this.#cloner(value) : value; } /** * @description Gets the cloner function, to use for e.g. `structuredClone`. * @public * @returns {((value: Value) => Value) | undefined} */ getCloner() { return this.#cloner; } /** * @description Returns the default value function. * @public * @returns {(() => Value) | undefined} */ getDefaultValue() { return this.#defaultValue; } /** * @inheritdoc * @public * @param {Key} key * @param {Value} value * @returns {this} */ set(key, value) { super.set(key, value); this.#ordered === true && this.sort(); return this; } /** * @description Sets the cloner function, to use for e.g. `structuredClone`. * @public * @param {(value: Value) => Value} clonerFn * @returns {this} The current instance of `FactoryMap`. * @example * ```ts * const map = new FactoryMap<string, number>(); * map.setCloner((value) => structuredClone(value)); * console.log(map.get('key')); // undefined * map.set('key', { a: 1 }); * console.log(map.get('key')); // { a: 1 } * map.set('key', { a: 2 }); * console.log(map.get('key')); // { a: 2 } * ``` */ setCloner(clonerFn) { typeof clonerFn === 'function' && (this.#cloner = clonerFn); return this; } /** * @description Sets the compare function used in sorting. * @public * @param {(a: [Key, Value], b: [Key, Value]) => number} compareFn * @returns {this} */ setComparator(compareFn) { typeof compareFn === 'function' && (this.#comparator = compareFn); return this; } /** * @description Sets the default value function. * @public * @param {() => Value} valueFn * @returns {this} The current instance of `FactoryMap`. * @example * ```ts * const map = new FactoryMap<string, number>(); * map.setDefaultValue(() => 0); * console.log(map.get('key')); // 0 * map.set('key', 1); * console.log(map.get('key')); // 1 * ``` */ setDefaultValue(valueFn) { this.#defaultValue = valueFn; return this; } /** * @description Sets whether the map should sort automatically after each `set`. * @public * @param {boolean} ordered * @returns {this} */ setOrdered(ordered) { this.#ordered = ordered; return this; } /** * @description Sorts the map with a stored or given comparator. * @public * @param {(a: [Key, Value], b: [Key, Value]) => number} [compareFn=this.#comparator] * @returns {this} */ sort(compareFn = this.#comparator) { const entries = [...super.entries()]; entries.length > 0 && (super.clear(), entries.sort((a, b) => compareFn(a, b)).forEach(([k, v]) => this.set(k, v))); return this; } } // Class. /** * @description The `WeakDataMap` class is a concrete class that stores data in a static `WeakMap`. * @export * @class WeakDataMap * @template Key * @template Value * @extends {DataMap<Key, Value, WeakData<Map<Key, Value>>>} */ class WeakDataMap extends DataMap { /** * @description Returns the `string` tag representation of the `WeakDataMap` class when used in `Object.prototype.toString.call(instance)`. * @public * @readonly * @type {string} */ get [Symbol.toStringTag]() { return WeakDataMap.name; } /** * Creates an instance of `WeakDataMap`. * @constructor * @param {?[Key, Value][]} [entries] */ constructor(entries) { super(entries, WeakData); } } /* * Public API Surface of map */ /** * Generated bundle index. Do not edit. */ export { CoreMap, DataMap, FactoryMap, MapOnHook, WeakDataMap }; //# sourceMappingURL=typescript-package-map.mjs.map