UNPKG

@typescript-package/data

Version:

A lightweight TypeScript library for basic data management.

478 lines (469 loc) 14.4 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.'); return Immutability.deepFreeze(this), 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.'); } return Object.freeze(this), 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() { return Immutability.deepFreeze(this), this.#locked = true, 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.'); return Object.seal(this), 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 core abstraction with immutability for handling data-related classes. * @export * @abstract * @class DataCore * @template T Represents the type of data value. * @template Async Indicates whether the operations are asynchronous. * @extends {Immutability} * @implements {DataShape<T>} */ class DataCore // For immutability features. extends Immutability { /** * @description Symbol key for accessing the value of the data instance. * @public * @static * @type {*} */ static valueSymbol = Symbol.for('value'); /** * @description The `string` tag representation of the `DataCore` class when used in `Object.prototype.toString.call(instance)`. * @public * @static * @type {string} */ static toStringTag = 'DataCore'; /** * @description Checks whether the provided value implements the iterable interface. * @param {unknown} value The value to inspect. * @returns {value is Iterable<unknown>} True when value exposes an iterator function. */ static isIterable(value) { return value != null && typeof value[Symbol.iterator] === 'function'; } /** * @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.toStringTag; } /** * @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() { return Immutability.deepFreeze(this.value), super.lock(), this; } /** * @description Returns an iterator for the data value. * @public * @returns {IterableIterator<T>} */ *[Symbol.iterator]() { const value = this.value; DataCore.isIterable(value) ? yield* value : yield value; } } // Abstract. /** * @description The abstract `AdapterData` class extends `DataCore` adding functionality for managing data value by adapter with arguments. * Designed to create data containers of `T` type managed by adapters that require constructor arguments. * @export * @abstract * @class AdapterData * @template T * @template {unknown[]} [G=unknown[]] Arguments type for the adapter constructor. * @template {boolean} [R=false] Indicates if the adapter operations are asynchronous. * @template {DataAdapter<T, R> | undefined} [A=DataAdapter<T, R>] The adapter type. * @extends {DataCore<T, R>} */ class AdapterData extends DataCore { /** * @description Returns the `string` tag representation of the `BaseData` class when used in `Object.prototype.toString.call(instance)`. * @public * @readonly * @type {string} */ get [Symbol.toStringTag]() { return AdapterData.toStringTag; } /** * @description The underlying adapter to handle the data value. * @public * @readonly * @type {(A | undefined)} */ get adapter() { return this.#adapter; } /** * @description Indicates if the adapter operations are asynchronous. * @public * @readonly * @type {R} */ get async() { return this.#async; } /** * @description Returns the privately stored value of generic type variable `T`. * @public * @readonly * @type {T} */ get value() { return this.#adapter ? this.#adapter.value : undefined; } /** * @description Optional privately stored adapter of type `A`. * @type {?A} */ #adapter; /** * @description Indicates if the adapter operations are asynchronous. * @type {R} */ #async; /** * Creates an instance of `AdapterData`. * @constructor * @param {R} async Async switch for the adapter operations. * @param {?{new (...args: G): A}} [adapter] The adapter constructor. * @param {...G} args The arguments passed to the adapter constructor. */ constructor(async, adapter, ...args) { super(); adapter && (this.#adapter = new adapter(...args)); this.#async = async ?? false; } /** * @description Clears the value to `undefined`. * @public * @returns {AsyncReturn<R, this>} The `this` current instance. */ clear() { return this.returnThis(this.#adapter ? this.#adapter.clear() : this); } /** * @description Destroys the value by setting it to `null`. * @public * @returns {AsyncReturn<R, this>} The `this` current instance. */ destroy() { return this.returnThis(this.#adapter ? this.#adapter.destroy() : this); } /** * @description Gets the value either asynchronously or synchronously based on the `R` generic type variable. * @public * @returns {AsyncReturn<R, T>} */ getValue() { return this.#adapter ? this.#adapter.getValue() : (this.#async ? Promise.resolve(this.value) : this.value); } /** * @description Sets the data value. * @public * @param {T} value The data value of `T` to set. * @returns {AsyncReturn<R, this>} The `this` current instance. */ setValue(value) { return super.validate(), this.returnThis(this.#adapter ? this.#adapter.setValue(value) : this); } /** * @description The helper method to return conditional `this` based on async type `R`, and returned `result` of adapter. * @param {AsyncReturn<R, A> | this} result The result of the adapter operation if provided, or `this`. * @returns {AsyncReturn<R, this>} The `this` current instance. */ returnThis(result) { return (result instanceof Promise ? result.then(() => this) : this.#async ? Promise.resolve(this) : this); } } /** * @description The `BaseData` is an abstract class that extends core features, adding functionality for managing the data value directly or through the adapter. * @export * @abstract * @class BaseData * @template T Type of the data value. * @template {unknown[]} [G=unknown[]] The arguments parameter type. * @template {boolean} [R=false] Indicates if the data operations are asynchronous. * @template {DataAdapter<T, R> | undefined} [A=undefined] Adapter type extending `DataAdapter` for handling the data value with it. * @extends {AdapterData<T, G, R, A>} */ class BaseData extends AdapterData { /** * @description Returns the `string` tag representation of the `BaseData` class when used in `Object.prototype.toString.call(instance)`. * @public * @readonly * @type {string} */ get [Symbol.toStringTag]() { return BaseData.toStringTag; } /** * @description Returns the privately stored value of generic type variable `T`. * @public * @readonly * @type {T} */ get value() { return super.adapter ? super.adapter.value : this.#value; } /** * @description Privately stored value of type `T`. * @type {T} */ #value; /** * Creates an instance of `BaseData`. * @constructor * @param {R} async Indicates if the data operations are asynchronous. * @param {T} value Initial data value of generic type variable `T`. * @param {?{new (value: T, ...args: G): A}} [adapter] Optional adapter class constructor for handling the data value. * @param {...G} args Arguments passed to the adapter class constructor, after the `value` parameter. */ constructor(async, value, adapter, ...args) { super(async, adapter, ...[value, ...args]); !adapter && (this.#value = value); } /** * @description Clears the value to `undefined`. * @public * @returns {AsyncReturn<R, this>} The `this` current instance. */ clear() { return super.adapter ? super.clear() : ((this.#value = undefined), super.returnThis(this)); } /** * @description Destroys the value by setting it to `null`. * @public * @returns {AsyncReturn<R, this>} The `this` current instance. */ destroy() { return super.adapter ? super.destroy() : ((this.#value = null), super.returnThis(this)); } /** * @description Sets the data value. * @public * @param {T} value The data value of `T` to set. * @returns {AsyncReturn<R, this>} The `this` current instance. */ setValue(value) { return super.adapter ? super.setValue(value) : (super.validate(), (this.#value = value), super.returnThis(this)); } } // Abstract. /** * @description The `Data` class is a concrete class that extends the `BaseData` abstract class for instantiate base functionality. * @public * @export * @class Data * @template T Type of the data value. * @template {unknown[]} [G=unknown[]] Arguments passed to the adapter class constructor, after the `value` parameter. * @template {boolean} [R=false] Indicates whether the data operations are asynchronous. * @template {DataAdapter<T, R> | undefined} [A=DataAdapter<T, R>] Adapter type extending `DataAdapter` for handling the data value. * @extends {BaseData<T, G, R, A>} */ class Data extends BaseData { /** * @inheritdoc * @public * @readonly * @type {string} */ static toStringTag = 'Data'; /** * @inheritdoc * @public * @readonly * @type {string} */ get [Symbol.toStringTag]() { return Data.toStringTag; } } // Abstract class /* * Public API Surface of data */ /** * Generated bundle index. Do not edit. */ export { AdapterData, BaseData, Data, DataCore, Immutability }; //# sourceMappingURL=typescript-package-data.mjs.map