@typescript-package/data
Version:
A lightweight TypeScript library for basic data management.
478 lines (469 loc) • 14.4 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.');
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