UNPKG

ts-data-forge

Version:

[![npm version](https://img.shields.io/npm/v/ts-data-forge.svg)](https://www.npmjs.com/package/ts-data-forge) [![npm downloads](https://img.shields.io/npm/dm/ts-data-forge.svg)](https://www.npmjs.com/package/ts-data-forge) [![License](https://img.shields.

488 lines (485 loc) 16.1 kB
import '../number/branded-types/finite-number.mjs'; import '../number/branded-types/int.mjs'; import '../number/branded-types/int16.mjs'; import '../number/branded-types/int32.mjs'; import '../number/branded-types/non-negative-finite-number.mjs'; import '../number/branded-types/non-negative-int16.mjs'; import '../number/branded-types/non-negative-int32.mjs'; import '../number/branded-types/non-zero-finite-number.mjs'; import '../number/branded-types/non-zero-int.mjs'; import '../number/branded-types/non-zero-int16.mjs'; import '../number/branded-types/non-zero-int32.mjs'; import '../number/branded-types/non-zero-safe-int.mjs'; import '../number/branded-types/non-zero-uint16.mjs'; import '../number/branded-types/non-zero-uint32.mjs'; import '../number/branded-types/positive-finite-number.mjs'; import '../number/branded-types/positive-int.mjs'; import '../number/branded-types/positive-int16.mjs'; import '../number/branded-types/positive-int32.mjs'; import '../number/branded-types/positive-safe-int.mjs'; import '../number/branded-types/positive-uint16.mjs'; import '../number/branded-types/positive-uint32.mjs'; import '../number/branded-types/safe-int.mjs'; import '../number/branded-types/safe-uint.mjs'; import '../number/branded-types/uint.mjs'; import '../number/branded-types/uint16.mjs'; import { asUint32 } from '../number/branded-types/uint32.mjs'; import '../number/enum/int8.mjs'; import '../number/enum/uint8.mjs'; import '../number/num.mjs'; import '../number/refined-number-utils.mjs'; /** Provides utility functions for ISetMapped. */ var ISetMapped; (function (ISetMapped) { /** * Creates a new ISetMapped instance with custom element transformation * functions. * * This factory function creates an immutable set that can use complex objects * as elements by providing bidirectional transformation functions. The * `toKey` function converts your custom element type to a primitive type that * can be efficiently stored, while `fromKey` reconstructs the original * element type for iteration and access. * * **Performance:** O(n) where n is the number of elements in the iterable. * * @example * * ```ts * type Point = Readonly<{ x: number; tag: string }>; * * const toKey = (point: Point) => JSON.stringify(point); * * // eslint-disable-next-line total-functions/no-unsafe-type-assertion * const fromKey = (key: string) => JSON.parse(key) as Point; * * const set = ISetMapped.create<Point, string>( * [ * { x: 1, tag: 'a' }, * { x: 1, tag: 'a' }, * { x: 2, tag: 'b' }, * ], * toKey, * fromKey, * ); * * assert.deepStrictEqual(Array.from(set), [ * { x: 1, tag: 'a' }, * { x: 2, tag: 'b' }, * ]); * ``` * * @template K The type of the custom elements. * @template KM The type of the mapped primitive keys. * @param iterable An iterable of elements using the custom element type. * @param toKey A function that converts a custom element `K` to a primitive * key `KM`. This function must be deterministic and produce unique values * for unique elements. * @param fromKey A function that converts a primitive key `KM` back to the * custom element `K`. This should be the inverse of `toKey`. * @returns A new ISetMapped instance containing all unique elements from the * iterable. */ ISetMapped.create = (iterable, toKey, fromKey) => new ISetMappedClass(iterable, toKey, fromKey); /** * Checks if two ISetMapped instances are structurally equal. * * Two ISetMapped instances are considered equal if they have the same size * and contain exactly the same elements. The comparison is performed on the * underlying mapped keys, so the transformation functions themselves don't * need to be identical. Elements are compared based on their mapped key * representations. * * **Performance:** O(n) where n is the size of the smaller set. * * @example * * ```ts * type Point = Readonly<{ x: number; tag: string }>; * * const toKey = (point: Point) => JSON.stringify(point); * * // eslint-disable-next-line total-functions/no-unsafe-type-assertion * const fromKey = (key: string) => JSON.parse(key) as Point; * * const first = ISetMapped.create<Point, string>( * [ * { x: 1, tag: 'a' }, * { x: 2, tag: 'b' }, * ], * toKey, * fromKey, * ); * * const second = ISetMapped.create<Point, string>( * [ * { x: 2, tag: 'b' }, * { x: 1, tag: 'a' }, * ], * toKey, * fromKey, * ); * * const third = ISetMapped.create<Point, string>( * [{ x: 3, tag: 'c' }], * toKey, * fromKey, * ); * * assert.isTrue(ISetMapped.equal(first, second)); * * assert.isFalse(ISetMapped.equal(first, third)); * ``` * * @template K The type of the custom elements. * @template KM The type of the mapped primitive keys. * @param a The first ISetMapped instance to compare. * @param b The second ISetMapped instance to compare. * @returns `true` if the sets contain exactly the same elements, `false` * otherwise. */ ISetMapped.equal = (a, b) => a.size === b.size && a.every((e) => b.has(e)); /** * Computes the difference between two ISetMapped instances. * * @example * * ```ts * type Point = Readonly<{ x: number; tag: string }>; * * const toKey = (point: Point) => JSON.stringify(point); * * // eslint-disable-next-line total-functions/no-unsafe-type-assertion * const fromKey = (key: string) => JSON.parse(key) as Point; * * const previous = ISetMapped.create<Point, string>( * [ * { x: 1, tag: 'a' }, * { x: 2, tag: 'b' }, * ], * toKey, * fromKey, * ); * * const current = ISetMapped.create<Point, string>( * [ * { x: 2, tag: 'b' }, * { x: 3, tag: 'c' }, * ], * toKey, * fromKey, * ); * * const { added, deleted } = ISetMapped.diff(previous, current); * * assert.deepStrictEqual(Array.from(added), [{ x: 3, tag: 'c' }]); * * assert.deepStrictEqual(Array.from(deleted), [{ x: 1, tag: 'a' }]); * ``` * * @template K The type of the elements. * @template KM The type of the mapped keys. * @param oldSet The original set. * @param newSet The new set. * @returns An object containing sets of added and deleted elements. */ ISetMapped.diff = (oldSet, newSet) => ({ deleted: oldSet.subtract(newSet), added: newSet.subtract(oldSet), }); /** * Computes the intersection of two ISetMapped instances. * * @example * * ```ts * type Point = Readonly<{ x: number; tag: string }>; * * const toKey = (point: Point) => JSON.stringify(point); * * // eslint-disable-next-line total-functions/no-unsafe-type-assertion * const fromKey = (key: string) => JSON.parse(key) as Point; * * const left = ISetMapped.create<Point, string>( * [ * { x: 1, tag: 'a' }, * { x: 2, tag: 'b' }, * ], * toKey, * fromKey, * ); * * const right = ISetMapped.create<Point, string>( * [{ x: 2, tag: 'b' }], * toKey, * fromKey, * ); * * const overlap = ISetMapped.intersection(left, right); * * assert.deepStrictEqual(Array.from(overlap), [{ x: 2, tag: 'b' }]); * ``` * * @template K The type of the elements. * @template KM The type of the mapped keys. * @param a The first set. * @param b The second set. * @returns A new ISetMapped instance representing the intersection. */ ISetMapped.intersection = (a, b) => a.intersect(b); /** * Computes the union of two ISetMapped instances. * * @example * * ```ts * type Point = Readonly<{ x: number; tag: string }>; * * const toKey = (point: Point) => JSON.stringify(point); * * // eslint-disable-next-line total-functions/no-unsafe-type-assertion * const fromKey = (key: string) => JSON.parse(key) as Point; * * const left = ISetMapped.create<Point, string>( * [{ x: 1, tag: 'a' }], * toKey, * fromKey, * ); * * const right = ISetMapped.create<Point, string>( * [{ x: 2, tag: 'b' }], * toKey, * fromKey, * ); * * const combined = ISetMapped.union(left, right); * * assert.deepStrictEqual(Array.from(combined), [ * { x: 1, tag: 'a' }, * { x: 2, tag: 'b' }, * ]); * ``` * * @template K The type of the elements. * @template KM The type of the mapped keys. * @param a The first set. * @param b The second set. * @returns A new ISetMapped instance representing the union. */ ISetMapped.union = (a, b) => a.union(b); })(ISetMapped || (ISetMapped = {})); /** * Internal class implementation for ISetMapped providing immutable set * operations with element transformation. * * This class implements the ISetMapped interface by maintaining a JavaScript * Set with primitive keys internally while exposing an API that works with * custom element types. The transformation between custom and primitive * elements is handled transparently through the provided `toKey` and `fromKey` * functions. * * **Implementation Details:** * * - Uses ReadonlySet<KM> internally where KM is the primitive key type * - Stores transformation functions for bidirectional element conversion * - Implements copy-on-write semantics for efficiency * - Provides optional debug messaging for development * * @template K The type of the custom elements. * @template KM The type of the mapped primitive keys. * @implements ISetMapped * @implements Iterable * @internal This class should not be used directly. Use ISetMapped.create() instead. */ class ISetMappedClass { #set; #toKey; #fromKey; #showNotFoundMessage; /** * Constructs an ISetMappedClass instance with custom element transformation. * * @param iterable An iterable of elements using the custom element type K. * @param toKey A function that converts a custom element K to a primitive key * KM. Must be deterministic and produce unique values for unique elements. * @param fromKey A function that converts a primitive key KM back to the * custom element K. Should be the inverse of the toKey function. * @param showNotFoundMessage Whether to log warning messages when operations * are performed on non-existent elements. Useful for debugging. Defaults to * false for production use. * @internal Use ISetMapped.create() instead of calling this constructor directly. */ constructor(iterable, toKey, fromKey, showNotFoundMessage = false) { this.#set = new Set(Array.from(iterable, toKey)); this.#toKey = toKey; this.#fromKey = fromKey; this.#showNotFoundMessage = showNotFoundMessage; } /** @inheritdoc */ get size() { return asUint32(this.#set.size); } /** @inheritdoc */ get isEmpty() { return this.size === 0; } /** @inheritdoc */ has(key) { return this.#set.has(this.#toKey(key)); } /** @inheritdoc */ every(predicate) { for (const key of this.values()) { if (!predicate(key)) return false; } return true; } /** @inheritdoc */ some(predicate) { for (const key of this.values()) { if (predicate(key)) return true; } return false; } /** @inheritdoc */ add(key) { if (this.has(key)) return this; return ISetMapped.create([...this.#set, this.#toKey(key)].map(this.#fromKey), this.#toKey, this.#fromKey); } /** @inheritdoc */ delete(key) { if (!this.has(key)) { if (this.#showNotFoundMessage) { console.warn(`ISetMapped.delete: key not found: ${String(this.#toKey(key))}`); } return this; } const keyMapped = this.#toKey(key); return ISetMapped.create(Array.from(this.#set) .filter((k) => !Object.is(k, keyMapped)) .map(this.#fromKey), this.#toKey, this.#fromKey); } /** @inheritdoc */ withMutations(actions) { const mut_result = new Set(this.#set); for (const action of actions) { const key = this.#toKey(action.key); switch (action.type) { case 'delete': mut_result.delete(key); break; case 'add': mut_result.add(key); break; } } return ISetMapped.create(Array.from(mut_result, this.#fromKey), this.#toKey, this.#fromKey); } /** @inheritdoc */ map(mapFn) { return ISetMapped.create(this.toArray().map(mapFn), this.#toKey, this.#fromKey); } /** @inheritdoc */ filter(predicate) { return ISetMapped.create(this.toArray().filter(predicate), this.#toKey, this.#fromKey); } /** @inheritdoc */ filterNot(predicate) { return ISetMapped.create(this.toArray().filter((k) => !predicate(k)), this.#toKey, this.#fromKey); } /** @inheritdoc */ forEach(callbackfn) { for (const km of this.#set) { callbackfn(this.#fromKey(km)); } } /** @inheritdoc */ isSubsetOf(set) { return this.every((k) => set.has(k)); } /** @inheritdoc */ isSupersetOf(set) { return set.every((k) => this.has(k)); } /** @inheritdoc */ subtract(set) { return ISetMapped.create(this.toArray().filter((k) => !set.has(k)), this.#toKey, this.#fromKey); } /** @inheritdoc */ intersect(set) { return ISetMapped.create(this.toArray().filter((k) => set.has(k)), this.#toKey, this.#fromKey); } /** @inheritdoc */ union(set) { return ISetMapped.create([...this.values(), ...set.values()], this.#toKey, this.#fromKey); } /** * @example * * ```ts * type Point = Readonly<{ x: number; tag: string }>; * * const toKey = (point: Point) => JSON.stringify(point); * * // eslint-disable-next-line total-functions/no-unsafe-type-assertion * const fromKey = (key: string) => JSON.parse(key) as Point; * * const set = ISetMapped.create<Point, string>( * [ * { x: 1, tag: 'a' }, * { x: 2, tag: 'b' }, * ], * toKey, * fromKey, * ); * * const collected = Array.from(set); * * assert.deepStrictEqual(collected, [ * { x: 1, tag: 'a' }, * { x: 2, tag: 'b' }, * ]); * ``` * * @inheritdoc */ *[Symbol.iterator]() { for (const k of this.keys()) { yield k; } } /** @inheritdoc */ *keys() { for (const km of this.#set.keys()) { yield this.#fromKey(km); } } /** @inheritdoc */ *values() { for (const km of this.#set.keys()) { // JavaScript Set's values() is an alias for keys() yield this.#fromKey(km); } } /** @inheritdoc */ *entries() { for (const km of this.#set.keys()) { // JavaScript Set's entries() yields [value, value] const a = this.#fromKey(km); yield [a, a]; } } /** @inheritdoc */ toArray() { return Array.from(this.values()); } /** @inheritdoc */ toRawSet() { return this.#set; } } export { ISetMapped }; //# sourceMappingURL=iset-mapped.mjs.map