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.

296 lines (293 loc) 11.6 kB
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 '../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 IMapMapped. */ var IMapMapped; (function (IMapMapped) { /** * Creates a new IMapMapped instance with custom key transformation functions. * * This factory function creates an immutable map that can use complex objects * as keys by providing bidirectional transformation functions. The `toKey` * function converts your custom key type to a primitive type that can be * efficiently stored, while `fromKey` reconstructs the original key type for * iteration and access. * * **Performance:** O(n) where n is the number of entries in the iterable. * * @template K The type of the custom keys. * @template V The type of the values. * @template KM The type of the mapped primitive keys. * @param iterable An iterable of key-value pairs using the custom key type. * @param toKey A function that converts a custom key `K` to a primitive key * `KM`. This function must be deterministic and produce unique values for * unique keys. * @param fromKey A function that converts a primitive key `KM` back to the * custom key `K`. This should be the inverse of `toKey`. * @returns A new IMapMapped instance containing all entries from the * iterable. */ IMapMapped.create = (iterable, toKey, fromKey) => new IMapMappedClass(iterable, toKey, fromKey); /** * Checks if two IMapMapped instances are structurally equal. * * Two IMapMapped instances are considered equal if they have the same size * and contain exactly the same key-value pairs. The comparison is performed * on the underlying mapped keys and values, so the transformation functions * themselves don't need to be identical. Values are compared using * JavaScript's `===` operator. * * **Performance:** O(n) where n is the size of the smaller map. * * @template K The type of the custom keys. * @template V The type of the values. * @template KM The type of the mapped primitive keys. * @param a The first IMapMapped instance to compare. * @param b The second IMapMapped instance to compare. * @returns `true` if the maps contain exactly the same key-value pairs, * `false` otherwise. */ IMapMapped.equal = (a, b) => a.size === b.size && a.every((v, k) => b.get(k) === v); })(IMapMapped || (IMapMapped = {})); /** * Internal class implementation for IMapMapped providing immutable map * operations with key transformation. * * This class implements the IMapMapped interface by maintaining a JavaScript * Map with primitive keys internally while exposing an API that works with * custom key types. The transformation between custom and primitive keys is * handled transparently through the provided `toKey` and `fromKey` functions. * * **Implementation Details:** * * - Uses ReadonlyMap<KM, V> internally where KM is the primitive key type * - Stores transformation functions for bidirectional key conversion * - Implements copy-on-write semantics for efficiency * - Provides optional debug messaging for development * * @template K The type of the custom keys. * @template V The type of the values. * @template KM The type of the mapped primitive keys. * @implements IMapMapped * @implements Iterable * @internal This class should not be used directly. Use IMapMapped.create() instead. */ class IMapMappedClass { #map; #toKey; #fromKey; #showNotFoundMessage; /** * Constructs an IMapMappedClass instance with custom key transformation. * * @param iterable An iterable of key-value pairs using the custom key type K. * @param toKey A function that converts a custom key K to a primitive key KM. * Must be deterministic and produce unique values for unique keys. * @param fromKey A function that converts a primitive key KM back to the * custom key K. Should be the inverse of the toKey function. * @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 IMapMapped.create() instead of calling this constructor directly. */ constructor(iterable, toKey, fromKey, showNotFoundMessage = false) { this.#map = new Map(Array.from(iterable, ([k, v]) => [toKey(k), v])); this.#toKey = toKey; this.#fromKey = fromKey; this.#showNotFoundMessage = showNotFoundMessage; } /** @inheritdoc */ get size() { return asUint32(this.#map.size); } /** @inheritdoc */ has(key) { return this.#map.has(this.#toKey(key)); } /** @inheritdoc */ get(key) { if (!this.has(key)) return none; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return some(this.#map.get(this.#toKey(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) { console.warn(`IMapMapped.delete: key not found: ${String(this.#toKey(key))}`); } return this; } const keyMapped = this.#toKey(key); return IMapMapped.create(Array.from(this.#map) .filter(([km]) => !Object.is(km, keyMapped)) .map(([km, v]) => tp(this.#fromKey(km), v)), this.#toKey, this.#fromKey); } /** @inheritdoc */ set(key, value) { if (value === this.get(key)) return this; // has no changes const keyMapped = this.#toKey(key); if (!this.has(key)) { return IMapMapped.create([...this.#map, tp(keyMapped, value)].map(([km, v]) => tp(this.#fromKey(km), v)), this.#toKey, this.#fromKey); } else { return IMapMapped.create(Array.from(this.#map, ([km, v]) => tp(this.#fromKey(km), Object.is(km, keyMapped) ? value : v)), this.#toKey, this.#fromKey); } } /** @inheritdoc */ update(key, updater) { const curr = this.get(key); if (isNone(curr)) { if (this.#showNotFoundMessage) { console.warn(`IMapMapped.update: key not found: ${String(this.#toKey(key))}`); } return this; } const keyMapped = this.#toKey(key); return IMapMapped.create(Array.from(this.#map.entries(), (keyValue) => pipe(keyValue) .map(([km, v]) => tp(km, Object.is(km, keyMapped) ? updater(curr.value) : v)) .map(([km, v]) => tp(this.#fromKey(km), v)).value), this.#toKey, this.#fromKey); } /** @inheritdoc */ withMutations(actions) { const mut_result = new Map(this.#map); for (const action of actions) { const key = this.#toKey(action.key); switch (action.type) { case 'delete': mut_result.delete(key); break; case 'set': mut_result.set(key, action.value); break; case 'update': { const curr = mut_result.get(key); if (!mut_result.has(key) || curr === undefined) { if (this.#showNotFoundMessage) { console.warn(`IMapMapped.withMutations::update: key not found: ${String(key)}`); } break; } mut_result.set(key, action.updater(curr)); break; } } } return IMapMapped.create(Array.from(mut_result, ([k, v]) => [this.#fromKey(k), v]), this.#toKey, this.#fromKey); } /** @inheritdoc */ map(mapFn) { return IMapMapped.create(this.toArray().map(([k, v]) => tp(k, mapFn(v, k))), this.#toKey, this.#fromKey); } /** @inheritdoc */ mapKeys(mapFn) { return IMapMapped.create(this.toArray().map(([k, v]) => tp(mapFn(k), v)), this.#toKey, this.#fromKey); } /** @inheritdoc */ mapEntries(mapFn) { return IMapMapped.create(this.toArray().map(mapFn), this.#toKey, this.#fromKey); } /** @inheritdoc */ forEach(callbackfn) { for (const [km, value] of this.#map.entries()) { callbackfn(value, this.#fromKey(km)); } } /** @inheritdoc */ *[Symbol.iterator]() { for (const e of this.entries()) { yield e; } } /** @inheritdoc */ *keys() { for (const km of this.#map.keys()) { yield this.#fromKey(km); } } /** @inheritdoc */ *values() { for (const v of this.#map.values()) { yield v; } } /** @inheritdoc */ *entries() { for (const [km, v] of this.#map.entries()) { yield [this.#fromKey(km), v]; } } /** @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 { IMapMapped }; //# sourceMappingURL=imap-mapped.mjs.map