UNPKG

@typescript-package/data

Version:

A lightweight TypeScript library for basic data management.

753 lines (739 loc) 20.2 kB
/** * @description Manages the immutability states of `this` current instance. * @export * @abstract * @class Immutability */ class Immutability { /** * @description * @template Type * @param {Type} object * @returns {Readonly<Type>} */ static deepFreeze(object) { if (object && typeof object === "object" && !Object.isFrozen(object)) { Object.getOwnPropertyNames(object).forEach(prop => Immutability.deepFreeze(object[prop])); Object.freeze(object); } return object; } /** * @description * @public * @readonly * @type {boolean} */ get frozen() { return this.isFrozen(); } /** * @description * @public * @readonly * @type {boolean} */ get locked() { return this.#locked; } /** * @description * @public * @readonly * @type {boolean} */ get mutable() { return this.isMutable(); } /** * @description * @public * @readonly * @type {boolean} */ get sealed() { return this.isSealed(); } /** * @description Privately stored locked state as `true` if locked, otherwise `false`. * @type {boolean} */ #locked = false; /** * @description Deeply freezes current `this` instance. * @public * @returns {this} Returns current instance. */ deepFreeze() { if (this.isLocked()) { throw new Error('Cannot freeze a locked object.'); } Immutability.deepFreeze(this); return this; } /** * @description "Prevents the modification of existing property attributes and values, and prevents the addition of new properties." * @public * @returns {this} Returns current instance. */ freeze() { if (this.isLocked()) { throw new Error('Cannot freeze a locked object.'); } Object.freeze(this); return this; } /** * @description Checks whether `this` current instance is frozen. * @public * @returns {boolean} */ isFrozen() { return Object.isFrozen(this); } /** * @description Checks whether the current instance is locked. * @public * @returns {boolean} Returns a `boolean` indicating whether current instance is locked. */ isLocked() { return this.#locked === true; } /** * @description Checks whether the object is mutable. * @public * @returns {boolean} True if the object is mutable, otherwise `false`. */ isMutable() { return !this.isSealed() && !this.isFrozen() && !this.isLocked(); } /** * @description Checks whether `this` current instance is sealed. * @public * @returns {boolean} Returns a `boolean` indicating whether current instance is sealed. */ isSealed() { return Object.isSealed(this); } /** * @description Locks the object, means deeply freezes and blocks the `set()`, ensuring deep immutability. * It combines the features of `Object.freeze`, but extends immutability to nested structures (deep immutability). * @public * @returns {this} Returns current instance. */ lock() { Immutability.deepFreeze(this); this.#locked = true; return this; } /** * @description "Prevents the modification of attributes of existing properties, and prevents the addition of new properties." * @public * @returns {this} Returns current instance. */ seal() { if (this.isLocked()) { throw new Error('Cannot seal a locked object.'); } Object.seal(this); return this; } /** * @description Validates the ability to set the value. * @protected * @returns {this} Returns current instance. */ validate() { if (this.isLocked()) { throw new Error('Cannot set when data is locked.'); } return this; } } // Abstract. /** * @description The base abstraction with immutability for handling data-related classes. * @export * @abstract * @class DataCore * @template Type Represents the type of data value. * @extends {Immutability} */ class DataCore extends Immutability { /** * @description Returns the `string` tag representation of the `DataCore` class when used in `Object.prototype.toString.call(instance)`. * @public * @readonly * @type {string} */ get [Symbol.toStringTag]() { return DataCore.name; } /** * @description Returns the string tag of the current instance defined by the `Symbol.toStringTag`. * @public * @returns {string | undefined} The extracted class name, such as `'DataCore'`, or `undefined` if extraction fails. */ get tag() { const tag = Object.prototype.toString.call(this).slice(8, -1); return tag !== 'Object' ? tag : undefined; } /** * @inheritdoc * @public * @returns {this} */ lock() { Immutability.deepFreeze(this.value); super.lock(); return this; } } /** * @description The class to manage the value of generic type variable `Type`. * @export * @class Value * @template Type The type of the privately stored `#value`. */ class Value { /** * @description Returns the `string` tag representation of the `Value` class when used in `Object.prototype.toString.call(instance)`. * The `tag` getter returns the actual class name defined with the `Symbol.toStringTag()` of the child class. * @public * @readonly * @type {string} */ get [Symbol.toStringTag]() { return Value.name; } /** * @description Returns the string tag of the current instance defined by the `Symbol.toStringTag`. * @public * @returns {string | undefined} The extracted class name, such as `'Value'`, or `undefined` if extraction fails. */ get tag() { const tag = Object.prototype.toString.call(this).slice(8, -1); return tag !== 'Object' ? tag : undefined; } /** * @description Returns the privately stored value of generic type variable `Type`. * @public * @readonly * @type {Type} */ get value() { return this.#value; } /** * @description Privately stored value of generic type variable `Type`. * @type {Type} */ #value; /** * Creates an instance of child class. * @constructor * @param {Type} value The value of generic type variable `Type`. */ constructor(value) { this.#value = value; } /** * @description Sets the value of generic type variable `Type`. * @public * @param {Type} value The value of `Type` to set. * @returns {this} The `this` current instance. */ set(value) { this.#value = value; return this; } } // Abstract. /** * @description The `Data` class is a concrete class that wraps a value and provides methods for setting, retrieving, and destroying the value. * @export * @class Data * @template Type * @extends {DataCore<Type>} */ class Data extends DataCore { /** * @description Returns the `string` tag representation of the `Data` class when used in `Object.prototype.toString.call(instance)`. * @public * @readonly * @type {string} */ get [Symbol.toStringTag]() { return Data.name; } /** * @description Returns the privately stored value of generic type variable `Type`. * @public * @readonly * @type {Type} */ get value() { return this.#value.value; } /** * @description Privately stored value of class `Value<Type>`. * @type {Value<Type>} */ #value; /** * Creates an instance of `Data`. * @constructor * @param {Type} value Initial data value of generic type variable `Type`. */ constructor(value) { super(); this.#value = new Value(value); } /** * @description * @public * @returns {Type} */ [Symbol.for('value')]() { return this.value; } /** * @description Clears the value to `null`. * @public * @returns {this} The `this` current instance. */ clear() { this.#value.set(null); return this; } /** * @description Destroys the `Value` object by setting it to `null`. * @public * @returns {this} The `this` current instance. */ destroy() { this.#value = null; return this; } /** * @description Sets the data value. * @public * @param {Type} value The data value of `Type` to set. * @returns {this} The `this` current instance. */ set(value) { super.validate(), this.#value.set(value); return this; } } // Class. /** * @description * @export * @class ReadonlyData * @template Type * @extends {Data<Readonly<Type>>} */ class ReadonlyData extends Data { /** * @inheritdoc * @public * @readonly * @type {string} */ get [Symbol.toStringTag]() { return ReadonlyData.name; } } // Abstract. /** * @description A class that stores and returns an immutable version of the provided value. * The value is frozen at runtime and marked as `readonly` at type level. * @export * @class ImmutableData * @template Type The original input type. * @extends {ReadonlyData<Type>} */ class ImmutableData extends ReadonlyData { /** * @description Returns the `string` tag representation of the `ImmutableData` class when used in `Object.prototype.toString.call(instance)`. * @public * @readonly * @type {string} */ get [Symbol.toStringTag]() { return ImmutableData.name; } /** * Creates an instance of `ImmutableData`. * @constructor * @param {Type} value Initial value to store. */ constructor(value) { super(Immutability.deepFreeze(value)); } /** * @description Sets the data value. * @public * @param {Type} value The data value of `Type` to set. * @returns {this} The `this` current instance. */ set(value) { return super.set(Immutability.deepFreeze(value)); } } // Abstract. /** * @description The `WeakData` class is a concrete class that stores data in a static `WeakMap`. * @export * @class WeakData * @template Type * @extends {DataCore<Type>} */ class WeakData extends DataCore { /** * @description Returns a new `WeakData` instance with a given value. * @public * @static * @template Type * @param {Type} value The value of `Type`. * @returns {WeakData<Type>} Returns a new `WeakData` instance. */ static create(value) { return new WeakData(value); } /** * @description Gets the data value from another instance. * @public * @static * @template Type * @param {WeakData<Type>} instance Another instance from which to get the data. * @returns {Type} The value of the data stored in the given instance. */ static get(instance) { return WeakData.#value.get(instance); } /** * @description Checks whether the instance exists in the data. * @public * @static * @template Type * @param {WeakData<Type>} instance The instance to check. * @returns {boolean} "a boolean indicating whether an element with the specified key exists or not." */ static has(instance) { return WeakData.#value.has(instance); } /** * @description Returns the `string` tag representation of the `WeakData` class when used in `Object.prototype.toString.call(instance)`. * @public * @readonly * @type {string} */ get [Symbol.toStringTag]() { return WeakData.name; } /** * @description A static, privately stored `WeakMap` used for associating each instance with its value. * @readonly * @type {WeakMap} */ static #value = new WeakMap(); /** * @description Returns the readonly value of `Type` from static `WeakMap`. * @public * @readonly * @type {Type} */ get value() { return WeakData.#value.get(this); } /** * Creates an instance of `WeakData`. * @constructor * @param {Type} value Initial data value of `Type`. */ constructor(value) { super(); WeakData.#value.set(this, value); } /** * @description * @public * @returns {Type} */ [Symbol.for('value')]() { return this.value; } /** * @description Clears the value to `null`. * @public * @returns {this} The `this` current instance. */ clear() { WeakData.#value.set(this, null); return this; } /** * @description Removes the data value in a static `WeakMap`. * @public * @returns {this} The `this` current instance. */ delete() { WeakData.#value.delete(this); return this; } /** * @description Destroys the value in a static `WeakMap`. * @public * @returns {this} The `this` current instance. */ destroy() { this.clear().delete(); return this; } /** * @description Sets the data value in a static `WeakMap`. * @public * @param {Type} value The data of `Type` to set. * @returns {this} The `this` current instance. */ set(value) { super.validate(); WeakData.#value.set(this, value); return this; } /** * @description Applies the callback to the current value and updates it. * @public * @param {(value: Type) => Type} callbackFn The callback to apply to the value. * @returns {this} The `this` current instance. */ update(callbackFn) { if (typeof callbackFn === 'function') { this.set(callbackFn(this.value)); } else { throw new Error("`callbackFn` must a function type."); } return this; } /** * @description Creates a new instance with a new value of `Type`. * @public * @param {Type} value The value of `Type`. * @returns {WeakData<Type>} Returns a `WeakData` instance with value of `Type`. */ with(value) { return WeakData.create(value); } } // Class. /** * @description * @export * @class IndexedWeakData * @template {object} [Obj=object] * @template {keyof Obj} [Key=keyof Obj] * @extends {WeakData<Obj>} */ class IndexedWeakData extends WeakData { /** * @description * @public * @static * @template {object} [Obj=object] * @param {number} index * @returns {(Obj | undefined)} */ static getByIndex(index) { return IndexedWeakData.#registry.get(index)?.deref()?.value; } /** * @description * @static * @type {Map} */ static #registry = new Map(); /** * @description * @static * @type {FinalizationRegistry} */ static #finalizationRegistry = new FinalizationRegistry((id) => IndexedWeakData.#registry.delete(id)); /** * @description * @public * @readonly * @type {(number | undefined)} */ get index() { return Object.hasOwn(super.value, this.#key) ? super.value[this.#key] : undefined; } /** * @description * @public * @readonly * @type {Key} */ get key() { return this.#key; } /** * @description * @type {!Key} */ #key; /** * Creates an instance of `IndexedWeakData`. * @constructor * @param {Obj} object * @param {Key} key */ constructor(object, key) { super(object); if (typeof object[key] === 'number') { this.#key = key; if (this.index) { IndexedWeakData.#registry.set(this.index, new WeakRef(this)); IndexedWeakData.#finalizationRegistry.register(this, this.index); } } else { throw Error(`Key must be associated with \`number\` type value in \`object\`.`); } } /** * @inheritdoc * @public * @returns {this} */ destroy() { typeof this.index === 'number' && IndexedWeakData.#registry.delete(this.index); return super.destroy(); } /** * @description * @public * @param {number} index * @returns {(Obj | undefined)} */ getByIndex(index) { return IndexedWeakData.getByIndex(index); } } // Abstract. /** * @deprecated In favor of `WeakStorage` in `typescript-package/storage`. * @description The `NamedWeakData` class is a concrete class that manages data in a static `Map` where data is associated with a specified name. * @export * @class NamedWeakData * @template [Type=any] * @template {string} [Name='default'] * @extends {DataCore<Type>} */ class NamedWeakData extends DataCore { /** * @description Gets the data from another instance. * @public * @static * @template {string} Name * @template Type * @param {NamedWeakData<Name, Type>} instance Another instance from which to get the data. * @param {Name} name The name from which get the data. * @returns {Type} The value of the data stored in the given instance. */ static getFrom(instance, name) { return NamedWeakData.#value.get(name)?.get(instance); } /** * @description Returns the `string` tag representation of the `NamedWeakData` class when used in `Object.prototype.toString.call(instance)`. * @public * @readonly * @type {string} */ get [Symbol.toStringTag]() { return NamedWeakData.name; } /** * @description A private static `Map` stores under specified `string` type name the data value instance in `WeakMap`. * @static * @readonly * @type {Map<string, WeakMap<any, any>>} */ static #value = new Map(); /** * @description * @public * @readonly * @type {Name} */ get name() { return this.#name; } /** * @description Returns the privately stored data value from the specified name of static `Map`. * @public * @readonly * @type {Type} */ get value() { return NamedWeakData.#value.get(this.name)?.get(this); } /** * @description * @type {Name} */ #name; /** * Creates an instance of `NamedWeakData` child class. * @constructor * @param {Type} value Initial data value of `Type`. * @param {string} [name='default'] The name under which the value is stored, defaults to `default`. */ constructor(value, name = 'default') { super(); this.#name = name; NamedWeakData.#value.has(name) || NamedWeakData.#value.set(name, new WeakMap()); NamedWeakData.#value.get(name)?.set(this, value); } /** * @description Clears the value in the `WeakMap`. * @public * @returns {this} Returns `this` current instance. */ clear() { this.set(null); return this; } /** * @description * @public * @returns {this} Returns `this` current instance. */ destroy() { NamedWeakData.#value.clear(); return this; } /** * @description Sets the data value. * @public * @param {Type} value The data of `Type` to set. * @returns {this} Returns `this` current instance. */ set(value) { super.validate(); NamedWeakData.#value.get(this.name)?.set(this, value); return this; } } const SymbolValue = Symbol.for('value'); // Abstract class. /* * Public API Surface of data */ /** * Generated bundle index. Do not edit. */ export { Data, DataCore, Immutability, ImmutableData, IndexedWeakData, NamedWeakData, ReadonlyData, SymbolValue, Value, WeakData }; //# sourceMappingURL=typescript-package-data.mjs.map