diff --git a/README.md b/README.md index d2bdd3e..f06668b 100644 --- a/README.md +++ b/README.md @@ -19,12 +19,15 @@ A lightweight **TypeScript** library for basic data management. - [Installation](#installation) - [Api](#api) - - [`Data`](#data) - - [`DataCore`](#datacore) - - [`Immutability`](#immutability) - - [`NamedWeakData`](#namedweakdata) - - [`Value`](#value) - - [`WeakData`](#weakdata) + - `abstract` + - [`Immutability`](#immutability) + - [`DataCore`](#datacore) + - **Base** + - [`Data`](#data) + - [`Value`](#value) + - **`WeakData`** + - [`IndexedWeakData`](#indexedweakdata) + - [`WeakData`](#weakdata) - [Immutability](#immutability) - [Sealed](#sealed) - [Frozen](#frozen) @@ -52,13 +55,23 @@ import { Immutability, // Class. + // Base. Data, + Value, + + // `WeakData`. NamedWeakData, + // Indexed. + IndexedWeakData, + // Basic. WeakData, - Value } from '@typescript-package/data'; ``` +### `DataCore` + +The base abstraction with immutability for handling data-related classes. + ### `Data` The `Data` class is a concrete class that wraps a value and provides methods for setting, retrieving, and destroying the value. @@ -87,46 +100,45 @@ data.destroy(); console.log(data.value); // Throws error or undefined (based on how it's handled) ``` -### `DataCore` +### `Value` -The base abstraction with immutability for handling data-related classes. +The class to manage the value of generic type variable `Type`. + +```typescript +import { Value } from '@typescript-package/data'; +``` ### `Immutability` Manages the immutability states of `this` current instance. -### `NamedWeakData` - ```typescript -import { NamedWeakData } from '@typescript-package/data'; - -// Define a class that extends NamedWeakData -export class ProfileData extends NamedWeakData {} - -// Create two instances with different names -const ageData = new ProfileData(25, 'age'); -const scoreData = new ProfileData(90, 'score'); +import { Immutability } from '@typescript-package/data'; +``` -// Access the values stored in each instance using their respective names -console.log(ageData.value); // Outputs: 25 -console.log(scoreData.value); // Outputs: 90 +### WeakData -// You can also retrieve the data from another instance using the static method `getFrom` -console.log(NamedWeakData.getFrom(ageData, 'age')); // Outputs: 25 -console.log(NamedWeakData.getFrom(scoreData, 'score')); // Outputs: 90 +### `IndexedWeakData` -// Setting new values -ageData.set(30); -console.log(ageData.value); // Outputs: 30 +```typescript +import { IndexedWeakData } from '@typescript-package/data'; -// Destroy an instance and clear its stored data -ageData.destroy(); -console.log(NamedWeakData.getFrom(ageData, 'age')); // Outputs: undefined +// Create an interface. +export interface Profile { + id: number, + age: number; + score: number; +} -// Clear all stored values from the map -scoreData.clear(); -console.log(NamedWeakData.getFrom(scoreData, 'score')); // Outputs: undefined +// Initialize multiple instances of `IndexedWeakData`. +export const profileData1 = new IndexedWeakData({ id: 1, age: 27, score: 1100 } as Profile, 'id'); +export const profileData2 = new IndexedWeakData({ id: 2, age: 127, score: 1200 } as Profile, 'id'); +export const profileData3 = new IndexedWeakData({ id: 3, age: 227, score: 1300 } as Profile, 'id'); +export const profileData4 = new IndexedWeakData({ id: 4, age: 327, score: 1400 } as Profile, 'id'); +// Get the value by using index. +console.log(`profileData1: `, profileData1.getByIndex(1)); // Output: {id: 1, age: 27, score: 1100} +console.log(`profileData3: `, profileData3.getByIndex(3)); // Output: {id: 3, age: 227, score: 1300} ``` ### `WeakData` @@ -137,30 +149,26 @@ The `WeakData` class is a concrete class that stores data in a static `WeakMap`. import { WeakData } from '@typescript-package/data'; // Example subclass of WeakData -class StringWeakData extends WeakData { +export class StringWeakData extends WeakData { constructor(value: string) { super(value); } } // Create a new instance of StringWeakData -const data1 = new StringWeakData("Hello, world!"); +export const data = new StringWeakData("Hello, world!"); // Access the current value -console.log(data1.value); // Output: Hello, world! +console.log(data.value); // Output: Hello, world! // Update the value -data1.set("New value"); -console.log(data1.value); // Output: New value +data.set("New value"); +console.log(data.value); // Output: New value // Destroy the value -data1.destroy(); +data.destroy(); ``` -### `Value` - -The class to manage the value of generic type variable `Type`. - ## Immutability ### Sealed diff --git a/package.json b/package.json index a60c7fd..d6ab7a7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-package/data", - "version": "1.0.0", + "version": "2.0.0", "author": "wwwdev.io ", "description": "A lightweight TypeScript library for basic data management.", "license": "MIT", @@ -27,7 +27,7 @@ "@typescript-package/data", "Data", "Immutability", - "NamedWeakData", + "IndexedWeakData", "Value", "WeakData" ], diff --git a/src/lib/data-core.abstract.ts b/src/lib/data-core.abstract.ts index 68b85ab..94d096d 100644 --- a/src/lib/data-core.abstract.ts +++ b/src/lib/data-core.abstract.ts @@ -28,11 +28,19 @@ export abstract class DataCore extends Immutability { */ public abstract get value(): Type; + /** + * @description Clears the value by setting to `undefined` or `null`. + * @public + * @abstract + * @returns {this} Returns `this` current instance. + */ + public abstract clear(): this; + /** * @description Abstract method to clear or remove the stored data value. * @public * @abstract - * @returns {this} Returns current instance. + * @returns {this} Returns `this` current instance. */ public abstract destroy(): this; @@ -43,7 +51,7 @@ export abstract class DataCore extends Immutability { */ public override lock(): this { Immutability.deepFreeze(this.value); - super.lock(); + super.lock(); return this; } @@ -52,7 +60,7 @@ export abstract class DataCore extends Immutability { * @public * @abstract * @param {Type} value The data of `Type` to set. - * @returns {this} Returns current instance. + * @returns {this} Returns `this` current instance. */ public abstract set(value: Type): this; } diff --git a/src/lib/data.class.ts b/src/lib/data.class.ts index d4316f3..54b5086 100644 --- a/src/lib/data.class.ts +++ b/src/lib/data.class.ts @@ -37,7 +37,7 @@ export class Data extends DataCore { #value; /** - * Creates an instance of `Data` child class. + * Creates an instance of `Data`. * @constructor * @param {Type} value Initial data value of generic type variable `Type`. */ @@ -46,10 +46,20 @@ export class Data extends DataCore { this.#value = new Value(value); } + /** + * @description Clears the value to `null`. + * @public + * @returns {this} Returns `this` current instance. + */ + public clear(): this { + this.#value.set(null as unknown as Type); + return this; + } + /** * @description Destroys the `Value` object by setting it to `null`. * @public - * @returns {this} Returns the current instance. + * @returns {this} Returns `this` current instance. */ public destroy(): this { this.#value = null as any; @@ -60,7 +70,7 @@ export class Data extends DataCore { * @description Sets the data value. * @public * @param {Type} value The data of `Type` to set. - * @returns {this} Returns the current instance. + * @returns {this} Returns `this` current instance. */ public set(value: Type) { super.validate(); diff --git a/src/lib/immutability.abstract.ts b/src/lib/immutability.abstract.ts index df7f3fd..3d625fc 100644 --- a/src/lib/immutability.abstract.ts +++ b/src/lib/immutability.abstract.ts @@ -19,6 +19,46 @@ export abstract class Immutability { return object; } + /** + * @description + * @public + * @readonly + * @type {boolean} + */ + public get frozen() { + return this.isFrozen(); + } + + /** + * @description + * @public + * @readonly + * @type {boolean} + */ + public get locked() { + return this.#locked; + } + + /** + * @description + * @public + * @readonly + * @type {boolean} + */ + public get mutable() { + return this.isMutable(); + } + + /** + * @description + * @public + * @readonly + * @type {boolean} + */ + public get sealed() { + return this.isSealed(); + } + /** * @description Privately stored locked state as `true` if locked, otherwise `false`. * @type {boolean} diff --git a/src/lib/index.ts b/src/lib/index.ts index fa83a1c..57526bb 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -3,6 +3,9 @@ export { DataCore } from './data-core.abstract'; export { Immutability } from './immutability.abstract'; // Class. export { Data } from './data.class'; +// `WeakData`. +export { IndexedWeakData } from './indexed-weak-data.class'; export { NamedWeakData } from './named-weak-data.class'; +export { WeakData } from './weak-data.class'; +// `Value`. export { Value } from './value.class'; -export { WeakData } from './weak-data.class'; \ No newline at end of file diff --git a/src/lib/indexed-weak-data.class.ts b/src/lib/indexed-weak-data.class.ts new file mode 100644 index 0000000..b72dcd9 --- /dev/null +++ b/src/lib/indexed-weak-data.class.ts @@ -0,0 +1,105 @@ +// Class. +import { WeakData } from "./weak-data.class"; +/** + * @description + * @export + * @class IndexedWeakData + * @template {object} [Obj=object] + * @template {keyof Obj} [Key=keyof Obj] + * @extends {WeakData} + */ +export class IndexedWeakData< + Obj extends object = object, + Key extends keyof Obj = keyof Obj, +> extends WeakData { + /** + * @description + * @public + * @static + * @template {object} [Obj=object] + * @param {number} index + * @returns {(Obj | undefined)} + */ + public static getByIndex(index: number): Obj | undefined { + return IndexedWeakData.#registry.get(index)?.deref()?.value; + } + + /** + * @description + * @static + * @type {Map} + */ + static #registry = new Map>>(); + + /** + * @description + * @static + * @type {FinalizationRegistry} + */ + static #finalizationRegistry = new FinalizationRegistry((id: number) => IndexedWeakData.#registry.delete(id)); + + /** + * @description + * @public + * @readonly + * @type {(number | undefined)} + */ + public get index(): number | undefined { + return Object.hasOwn(super.value, this.#key) ? super.value[this.#key] as number : undefined; + } + + /** + * @description + * @public + * @readonly + * @type {Key} + */ + public get key() { + return this.#key; + } + + /** + * @description + * @type {!Key} + */ + #key!: Key; + + /** + * Creates an instance of `IndexedWeakData`. + * @constructor + * @param {Obj} object + * @param {Key} key + */ + constructor(object: Obj, key: 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} + */ + public override destroy(): this { + typeof this.index === 'number' && IndexedWeakData.#registry.delete(this.index); + return super.destroy(); + } + + /** + * @description + * @public + * @param {number} index + * @returns {(Obj | undefined)} + */ + public getByIndex(index: number): Obj | undefined { + return IndexedWeakData.getByIndex(index); + } +} diff --git a/src/lib/named-weak-data.class.ts b/src/lib/named-weak-data.class.ts index 109ec2f..331762f 100644 --- a/src/lib/named-weak-data.class.ts +++ b/src/lib/named-weak-data.class.ts @@ -1,6 +1,7 @@ // Abstract. -import { DataCore } from './data-core.abstract'; +import { DataCore } from '.'; /** + * @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 @@ -42,6 +43,16 @@ export class NamedWeakData extends */ static readonly #value: Map> = new Map(); + /** + * @description + * @public + * @readonly + * @type {Name} + */ + public get name(): Name { + return this.#name; + } + /** * @description Returns the privately stored data value from the specified name of static `Map`. * @public @@ -52,25 +63,32 @@ export class NamedWeakData extends return NamedWeakData.#value.get(this.name)?.get(this); } + /** + * @description + * @type {Name} + */ + #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: Type, private name: Name = 'default' as Name) { + constructor(value: Type, name: Name = 'default' as Name) { super(); - NamedWeakData.#value.get(name) === undefined && NamedWeakData.#value.set(name, new WeakMap()); - NamedWeakData.#value.get(name)!.set(this, value); + this.#name = name; + NamedWeakData.#value.has(name) || NamedWeakData.#value.set(name, new WeakMap()); + NamedWeakData.#value.get(name)?.set(this, value); } /** - * @description + * @description Clears the value in the `WeakMap`. * @public * @returns {this} Returns `this` current instance. */ public clear(): this { - NamedWeakData.#value.clear(); + this.set(null as any); return this; } @@ -80,8 +98,7 @@ export class NamedWeakData extends * @returns {this} Returns `this` current instance. */ public destroy(): this { - NamedWeakData.#value.get(this.name)?.delete(this); - this.clear(); + NamedWeakData.#value.clear(); return this; } @@ -96,4 +113,4 @@ export class NamedWeakData extends NamedWeakData.#value.get(this.name)?.set(this, value); return this; } -} +} \ No newline at end of file diff --git a/src/lib/value.class.ts b/src/lib/value.class.ts index cd515dd..cd9c770 100644 --- a/src/lib/value.class.ts +++ b/src/lib/value.class.ts @@ -53,7 +53,7 @@ export class Value { /** * @description Sets the value of generic type variable `Type`. - * @protected + * @public * @returns {this} Returns `this` current instance. */ public set(value: Type) { diff --git a/src/lib/weak-data.class.ts b/src/lib/weak-data.class.ts index 3c6dcac..a1e1a12 100644 --- a/src/lib/weak-data.class.ts +++ b/src/lib/weak-data.class.ts @@ -8,6 +8,18 @@ import { DataCore } from './data-core.abstract'; * @extends {DataCore} */ export 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} Returns a new `WeakData` instance. + */ + public static create(value: Type): WeakData { + return new WeakData(value); + } + /** * @description Gets the data value from another instance. * @public @@ -20,6 +32,18 @@ export class WeakData extends DataCore { return WeakData.#value.get(instance); } + /** + * @description Checks whether the instance exists in the data. + * @public + * @static + * @template Type + * @param {WeakData} instance The instance to check. + * @returns {boolean} "a boolean indicating whether an element with the specified key exists or not." + */ + public static has(instance: WeakData): boolean { + return WeakData.#value.has(instance); + } + /** * @description Returns the `string` tag representation of the `WeakData` class when used in `Object.prototype.toString.call(instance)`. * @public @@ -38,17 +62,17 @@ export class WeakData extends DataCore { static readonly #value = new WeakMap(); /** - * @description + * @description Returns the value of `Type` from static `WeakMap`. * @public * @readonly - * @type {Type} + * @type {Type} */ public get value(): Type { return WeakData.#value.get(this) as Type; } /** - * Creates an instance of `WeakData` child class. + * Creates an instance of `WeakData`. * @constructor * @param {Type} value Initial data value of `Type`. */ @@ -57,25 +81,80 @@ export class WeakData extends DataCore { WeakData.#value.set(this, value); } + /** + * @description Clears the value to `null`. + * @public + * @returns {this} Returns `this` current instance. + */ + public clear(): this { + WeakData.#value.set(this, null as unknown as Type); + return this; + } + + /** + * @description Removes the data value in a static `WeakMap`. + * @public + * @returns {this} Returns `this` current instance. + */ + public delete(): this { + WeakData.#value.delete(this); + return this; + } + /** * @description Destroys the value in a static `WeakMap`. * @public - * @returns {this} Returns current instance. + * @returns {this} Returns `this` current instance. */ public destroy(): this { - WeakData.#value.delete(this); + this.clear().delete(); return this; } + /** + * @description Checks whether the static `WeakMap` has the instance. + * @public + * @param {Type} key The instance of `Type`. + * @returns {boolean} Returns the `boolean` indicating the. + */ + public has(key: Type = this.value): boolean { + return WeakData.#value.has(key); + } + /** * @description Sets the data value in a static `WeakMap`. * @public * @param {Type} value The data of `Type` to set. - * @returns {this} Returns current instance. + * @returns {this} Returns `this` current instance. */ public set(value: Type): this { 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} Returns `this` current instance. + */ + public update(callbackFn: (value: Type) => Type): this { + 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} Returns a `WeakData` instance with value of `Type`. + */ + public with(value: Type): WeakData { + return WeakData.create(value); + } } diff --git a/src/public-api.ts b/src/public-api.ts index 35c1b2a..bf6a9db 100644 --- a/src/public-api.ts +++ b/src/public-api.ts @@ -7,8 +7,14 @@ export { Immutability, // Class. + // Base. Data, + Value, + + // `WeakData`. NamedWeakData, + // Indexed. + IndexedWeakData, + // Basic. WeakData, - Value } from './lib'; diff --git a/src/test/data.spec.ts b/src/test/data.spec.ts index bb994ef..cf3d9db 100644 --- a/src/test/data.spec.ts +++ b/src/test/data.spec.ts @@ -1,13 +1,13 @@ import { Data } from "../lib"; // Example subclass of Data -class StringData extends Data { +export class StringData extends Data { constructor(value: string) { super(value); } } -const data = new StringData("Hello, world!"); +export const data = new StringData("Hello, world!"); // Access the current value console.log(data.value); // Output: Hello, world! @@ -23,4 +23,3 @@ try { } catch(e) { console.log(e); } - diff --git a/src/test/indexed-weak-data.spec.ts b/src/test/indexed-weak-data.spec.ts new file mode 100644 index 0000000..1479998 --- /dev/null +++ b/src/test/indexed-weak-data.spec.ts @@ -0,0 +1,18 @@ +import { IndexedWeakData } from "../lib"; + +// Create an interface. +export interface Profile { + id: number, + age: number; + score: number; +} + +// Initialize multiple instances of `IndexedWeakData`. +export const profileData1 = new IndexedWeakData({ id: 1, age: 27, score: 1100 } as Profile, 'id'); +export const profileData2 = new IndexedWeakData({ id: 2, age: 127, score: 1200 } as Profile, 'id'); +export const profileData3 = new IndexedWeakData({ id: 3, age: 227, score: 1300 } as Profile, 'id'); +export const profileData4 = new IndexedWeakData({ id: 4, age: 327, score: 1400 } as Profile, 'id'); + +// Get the value by using index. +console.log(`profileData1: `, profileData1.getByIndex(1)); // Output: {id: 1, age: 27, score: 1100} +console.log(`profileData3: `, profileData3.getByIndex(3)); // Output: {id: 3, age: 227, score: 1300} diff --git a/src/test/named-weak-data.spec.ts b/src/test/named-weak-data.spec.ts index 5291fda..f408e60 100644 --- a/src/test/named-weak-data.spec.ts +++ b/src/test/named-weak-data.spec.ts @@ -1,4 +1,4 @@ -import { NamedWeakData } from "../lib"; +import { Data, NamedWeakData } from "../lib"; // Define a class that extends NamedWeakData export class ProfileData extends NamedWeakData {} @@ -26,3 +26,35 @@ console.log(NamedWeakData.getFrom(ageData, 'age')); // Outputs: undefined // Clear all stored values from the map scoreData.clear(); console.log(NamedWeakData.getFrom(scoreData, 'score')); // Outputs: undefined + + + +// Profile +// export class UserData extends Data> {} + +export interface Profile { + id: number, + age: number; + score: number; +} + + +const someone = new NamedWeakData({} as any, 'profile'); + +someone.set({ + id: 27, + age: 25, + score: 1200 +}); + +console.log(someone.value); // { id: 27, age: 25, score: 1200 } + + +// +console.log(NamedWeakData.getFrom(someone, 'profile').age); + +// NamedWeakData.getFrom(profiles, 'profile')?.set(user1, { age: 25, score: 1200 }); +// NamedWeakData.getFrom(profiles, 'profile')?.set(user2, { age: 30, score: 1500 }); + + + diff --git a/src/test/weak-data.spec.ts b/src/test/weak-data.spec.ts index c99f4d3..7b80b3b 100644 --- a/src/test/weak-data.spec.ts +++ b/src/test/weak-data.spec.ts @@ -1,21 +1,35 @@ import { WeakData } from '../lib'; // Example subclass of WeakData -class StringWeakData extends WeakData { +export class StringWeakData extends WeakData { constructor(value: string) { super(value); } } // Create a new instance of StringWeakData -const data1 = new StringWeakData("Hello, world!"); +export const data = new StringWeakData("Hello, world!"); // Access the current value -console.log(data1.value); // Output: Hello, world! +console.log(data.value); // Output: Hello, world! // Update the value -data1.set("New value"); -console.log(data1.value); // Output: New value +data.set("New value"); +console.log(data.value); // Output: New value // Destroy the value -data1.destroy(); \ No newline at end of file +data.destroy(); + +export class ObjectWeakData extends WeakData { + constructor(value: Type) { + super(value); + } +} + +export const objectWeakData = new ObjectWeakData({ + name: 'Someone', + surname: 'Surname' +}); + + +objectWeakData.update((value => value));