@typescript-package/data
Version:
A lightweight TypeScript library for basic data management.
753 lines (739 loc) • 20.2 kB
JavaScript
/**
* @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