@typescript-package/map
Version:
A lightweight TypeScript library for enhanced `map` management.
491 lines (482 loc) • 14.4 kB
JavaScript
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