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.

324 lines (321 loc) 10.9 kB
import { isSome } from '../functional/optional/impl/optional-is-some.mjs'; import { none } from '../functional/optional/impl/optional-none.mjs'; import { some } from '../functional/optional/impl/optional-some.mjs'; import { isNone } from '../functional/optional/impl/optional-is-none.mjs'; import { pipe } from '../functional/pipe.mjs'; import '@sindresorhus/is'; import { tp } from '../others/tuple.mjs'; import { unknownToString } from '../others/unknown-to-string.mjs'; 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 IMap. */ var IMap; (function (IMap) { /** * Creates a new IMap instance from an iterable of key-value pairs. * * This factory function accepts any iterable of [key, value] tuples, * including arrays, JavaScript Maps, other IMaps, or custom iterables. The * resulting IMap will contain all the entries from the input iterable. * * **Performance:** O(n) where n is the number of entries in the iterable. * * @example * * ```ts * const map = IMap.create<string, number | string>([ * ['id', 1], * ['status', 'active'], * ]); * * assert.isTrue(map.size === 2); * * assert.deepStrictEqual(map.get('status'), Optional.some('active')); * ``` * * @template K The type of the keys. Must extend MapSetKeyType. * @template V The type of the values. * @param iterable An iterable of key-value pairs (e.g., Array, Map, IMap, * etc.) * @returns A new IMap instance containing all entries from the iterable. */ IMap.create = (iterable) => new IMapClass(iterable); /** * Checks if two IMap instances are structurally equal. * * Two IMaps are considered equal if they have the same size and contain * exactly the same key-value pairs. The order of entries does not matter for * equality comparison. Values are compared using JavaScript's `===` * operator. * * **Performance:** O(n) where n is the size of the smaller map. * * @example * * ```ts * const first = IMap.create<'a' | 'b', number>([ * ['a', 1], * ['b', 2], * ]); * * const second = IMap.create<'a' | 'b', number>([ * ['b', 2], * ['a', 1], * ]); * * const third = IMap.create<'a' | 'b', number>([ * ['a', 1], * ['b', 3], * ]); * * assert.isTrue(IMap.equal(first, second)); * * assert.isFalse(IMap.equal(first, third)); * ``` * * @template K The type of the keys. * @template V The type of the values. * @param a The first IMap instance to compare. * @param b The second IMap instance to compare. * @returns `true` if the maps contain exactly the same key-value pairs, * `false` otherwise. */ IMap.equal = (a, b) => a.size === b.size && a.every((v, k) => pipe(b.get(k)).map((v2) => isSome(v2) && v2.value === v).value); })(IMap || (IMap = {})); /** * Internal class implementation for IMap providing immutable map operations. * * This class implements the IMap interface using JavaScript's native Map as the * underlying storage mechanism for optimal performance. All mutation operations * create new instances rather than modifying the existing map, ensuring * immutability. * * **Implementation Details:** * * - Uses Map<K, V> internally for type safety and performance * - Implements copy-on-write semantics for efficiency * - Provides optional debug messaging for development * * @template K The type of the keys. Must extend MapSetKeyType. * @template V The type of the values. * @implements IMap * @implements Iterable * @internal This class should not be used directly. Use IMap.create() instead. */ class IMapClass { #map; #showNotFoundMessage; /** * Constructs an IMapClass instance with the given entries. * * @param iterable An iterable of key-value pairs to populate the map. * @param showNotFoundMessage Whether to log warning messages when operations * are performed on non-existent keys. Useful for debugging. Defaults to * false for production use. * @internal Use IMap.create() instead of calling this constructor directly. */ constructor(iterable, showNotFoundMessage = false) { this.#map = new Map(iterable); this.#showNotFoundMessage = showNotFoundMessage; } /** @inheritdoc */ get size() { return asUint32(this.#map.size); } /** @inheritdoc */ has(key) { // eslint-disable-next-line total-functions/no-unsafe-type-assertion return this.#map.has(key); } /** @inheritdoc */ get(key) { if (!this.has(key)) return none; // eslint-disable-next-line total-functions/no-unsafe-type-assertion, @typescript-eslint/no-non-null-assertion return some(this.#map.get(key)); } /** @inheritdoc */ every(predicate) { for (const [k, v] of this.entries()) { if (!predicate(v, k)) return false; } return true; } /** @inheritdoc */ some(predicate) { for (const [k, v] of this.entries()) { if (predicate(v, k)) return true; } return false; } /** @inheritdoc */ delete(key) { if (!this.has(key)) { if (this.#showNotFoundMessage) { const keyStr = unknownToString(key); console.warn(`IMap.delete: key not found: ${keyStr}`); } return this; } return IMap.create(Array.from(this.#map).filter(([k]) => !Object.is(k, key))); } /** @inheritdoc */ set(key, value) { const curr = this.get(key); if (isSome(curr) && value === curr.value) return this; // has no changes if (!this.has(key)) { return IMap.create([...this.#map, tp(key, value)]); } else { return IMap.create(Array.from(this.#map, ([k, v]) => tp(k, Object.is(k, key) ? value : v))); } } /** @inheritdoc */ update(key, updater) { const curr = this.get(key); if (isNone(curr)) { if (this.#showNotFoundMessage) { const keyStr = unknownToString(key); console.warn(`IMap.update: key not found: ${keyStr}`); } return this; } return IMap.create(Array.from(this.#map, ([k, v]) => tp(k, Object.is(k, key) ? updater(curr.value) : v))); } /** @inheritdoc */ withMutations(actions) { const mut_result = new Map(this.#map); for (const action of actions) { switch (action.type) { case 'delete': mut_result.delete(action.key); break; case 'set': mut_result.set(action.key, action.value); break; case 'update': { const { key } = action; const curr = mut_result.get(key); if (!mut_result.has(key) || curr === undefined) { if (this.#showNotFoundMessage) { const keyStr = unknownToString(key); console.warn(`IMap.withMutations: key not found: ${keyStr}`); } break; } mut_result.set(key, action.updater(curr)); break; } } } return IMap.create(mut_result); } /** @inheritdoc */ map(mapFn) { return IMap.create(this.toArray().map(([k, v]) => tp(k, mapFn(v, k)))); } /** @inheritdoc */ mapKeys(mapFn) { return IMap.create(this.toArray().map(([k, v]) => tp(mapFn(k), v))); } /** @inheritdoc */ mapEntries(mapFn) { return IMap.create(this.toArray().map(mapFn)); } /** @inheritdoc */ forEach(callbackfn) { for (const [key, value] of this.#map.entries()) { callbackfn(value, key); } } /** * @example * * ```ts * const entries = [ * ['first', 1], * ['second', 2], * ] satisfies readonly (readonly [string, number])[]; * * const map = IMap.create(entries); * * const collected = Array.from(map); * * assert.deepStrictEqual(collected, [ * ['first', 1], * ['second', 2], * ]); * ``` * * @inheritdoc */ [Symbol.iterator]() { return this.#map[Symbol.iterator](); } /** @inheritdoc */ keys() { return this.#map.keys(); } /** @inheritdoc */ values() { return this.#map.values(); } /** @inheritdoc */ entries() { return this.#map.entries(); } /** @inheritdoc */ toKeysArray() { return Array.from(this.keys()); } /** @inheritdoc */ toValuesArray() { return Array.from(this.values()); } /** @inheritdoc */ toEntriesArray() { return Array.from(this.entries()); } /** @inheritdoc */ toArray() { return Array.from(this.entries()); } /** @inheritdoc */ toRawMap() { return this.#map; } } export { IMap }; //# sourceMappingURL=imap.mjs.map