From e984073d533328ad884e05ef5876ca77a4b6f4e9 Mon Sep 17 00:00:00 2001 From: Nev Date: Tue, 26 May 2026 23:22:13 -0700 Subject: [PATCH] feat(iterator): add iterator collection helpers with docs and tests - add new iterator helper module with typed exports: - iterMap, iterFilter, iterTake, iterReduce, iterSome, iterEvery, iterToArray - arrToMap, iterUnion, iterIntersection, iterDifference - IterMapCallbackFn, IterPredicateCallbackFn, IterReduceCallbackFn, ArrToMapKeySelectorFn, ArrToMapValueSelectorFn - export new iterator helpers from index.ts - add comprehensive common tests for helper behavior and edge cases in helpers.test.ts - add TypeDoc examples and mark new APIs as @since 0.15.0 in helpers.ts - update Iterator utility matrix in README.md to include all new helper links --- .size-limit.json | 12 ++-- CHANGELOG.md | 5 ++ README.md | 2 +- docs/feature-backlog.md | 11 +-- lib/src/array/arrToMap.ts | 59 ++++++++++++++++ lib/src/funcs/fnBindArgs.ts | 3 + lib/src/funcs/funcs.ts | 4 ++ lib/src/index.ts | 12 ++++ lib/src/iterator/forOf.ts | 4 +- lib/src/iterator/helpers.ts | 48 +++++++++++++ lib/src/iterator/iterDifference.ts | 44 ++++++++++++ lib/src/iterator/iterEvery.ts | 37 ++++++++++ lib/src/iterator/iterFilter.ts | 52 ++++++++++++++ lib/src/iterator/iterIntersection.ts | 51 ++++++++++++++ lib/src/iterator/iterMap.ts | 47 +++++++++++++ lib/src/iterator/iterReduce.ts | 44 ++++++++++++ lib/src/iterator/iterSome.ts | 37 ++++++++++ lib/src/iterator/iterTake.ts | 67 +++++++++++++++++++ lib/src/iterator/iterToArray.ts | 41 ++++++++++++ lib/src/iterator/iterUnion.ts | 34 ++++++++++ lib/src/iterator/types.ts | 62 +++++++++++++++++ lib/test/bundle-size-check.js | 8 +-- lib/test/src/common/array/arrToMap.test.ts | 34 ++++++++++ .../common/iterator/iterDifference.test.ts | 23 +++++++ .../src/common/iterator/iterEvery.test.ts | 17 +++++ .../src/common/iterator/iterFilter.test.ts | 42 ++++++++++++ .../common/iterator/iterIntersection.test.ts | 30 +++++++++ lib/test/src/common/iterator/iterMap.test.ts | 42 ++++++++++++ .../src/common/iterator/iterReduce.test.ts | 25 +++++++ lib/test/src/common/iterator/iterSome.test.ts | 17 +++++ lib/test/src/common/iterator/iterTake.test.ts | 49 ++++++++++++++ .../src/common/iterator/iterToArray.test.ts | 34 ++++++++++ .../src/common/iterator/iterUnion.test.ts | 25 +++++++ 33 files changed, 999 insertions(+), 23 deletions(-) create mode 100644 lib/src/array/arrToMap.ts create mode 100644 lib/src/iterator/helpers.ts create mode 100644 lib/src/iterator/iterDifference.ts create mode 100644 lib/src/iterator/iterEvery.ts create mode 100644 lib/src/iterator/iterFilter.ts create mode 100644 lib/src/iterator/iterIntersection.ts create mode 100644 lib/src/iterator/iterMap.ts create mode 100644 lib/src/iterator/iterReduce.ts create mode 100644 lib/src/iterator/iterSome.ts create mode 100644 lib/src/iterator/iterTake.ts create mode 100644 lib/src/iterator/iterToArray.ts create mode 100644 lib/src/iterator/iterUnion.ts create mode 100644 lib/src/iterator/types.ts create mode 100644 lib/test/src/common/array/arrToMap.test.ts create mode 100644 lib/test/src/common/iterator/iterDifference.test.ts create mode 100644 lib/test/src/common/iterator/iterEvery.test.ts create mode 100644 lib/test/src/common/iterator/iterFilter.test.ts create mode 100644 lib/test/src/common/iterator/iterIntersection.test.ts create mode 100644 lib/test/src/common/iterator/iterMap.test.ts create mode 100644 lib/test/src/common/iterator/iterReduce.test.ts create mode 100644 lib/test/src/common/iterator/iterSome.test.ts create mode 100644 lib/test/src/common/iterator/iterTake.test.ts create mode 100644 lib/test/src/common/iterator/iterToArray.test.ts create mode 100644 lib/test/src/common/iterator/iterUnion.test.ts diff --git a/.size-limit.json b/.size-limit.json index 2b4cbd53..c322291b 100644 --- a/.size-limit.json +++ b/.size-limit.json @@ -2,42 +2,42 @@ { "name": "es5-full", "path": "lib/dist/es5/mod/ts-utils.js", - "limit": "35 kb", + "limit": "37 kb", "brotli": false, "running": false }, { "name": "es6-full", "path": "lib/dist/es6/mod/ts-utils.js", - "limit": "33.5 kb", + "limit": "35.5 kb", "brotli": false, "running": false }, { "name": "es5-full-brotli", "path": "lib/dist/es5/mod/ts-utils.js", - "limit": "12.5 kb", + "limit": "12.75 kb", "brotli": true, "running": false }, { "name": "es6-full-brotli", "path": "lib/dist/es6/mod/ts-utils.js", - "limit": "12 kb", + "limit": "12.5 kb", "brotli": true, "running": false }, { "name": "es5-zip", "path": "lib/dist/es5/mod/ts-utils.js", - "limit": "13.5 Kb", + "limit": "14 Kb", "gzip": true, "running": false }, { "name": "es6-zip", "path": "lib/dist/es6/mod/ts-utils.js", - "limit": "13.5 Kb", + "limit": "14 Kb", "gzip": true, "running": false }, diff --git a/CHANGELOG.md b/CHANGELOG.md index ce925039..f28afe71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,11 @@ - Adds shared queue logic and callback-argument support across microtask and nextTick scheduling - Adds `arrConcat` as a dedicated array helper and `fnBindArgs` as a dedicated function helper - Improves function binding typing with exported `BoundFunction` and updated signatures for `fnBind` / `fnBindArgs` +- [#574](https://github.com/nevware21/ts-utils/pull/574) feat(iterator,array): add iterator collection helpers, split helper modules, and align collection membership semantics + - Added new iterator helpers: `iterMap`, `iterFilter`, `iterTake`, `iterReduce`, `iterSome`, `iterEvery`, `iterToArray`, `iterUnion`, `iterIntersection`, `iterDifference` + - Added `arrToMap` helper in the array module and moved callback/type declarations into `iterator/types` + - Refactored iterator helper implementation/tests into per-function files and updated root exports + - Added NaN regression coverage and switched iterator set-operation membership checks to `arrIncludes` semantics for parity with array helpers ### Bug Fixes diff --git a/README.md b/README.md index 03f8b8d8..b2d22672 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,7 @@ Below is a categorized list of all available utilities with direct links to thei | Error | [createCustomError](https://nevware21.github.io/ts-utils/typedoc/functions/createCustomError.html)(); [isError](https://nevware21.github.io/ts-utils/typedoc/functions/isError.html)(); [throwError](https://nevware21.github.io/ts-utils/typedoc/functions/throwError.html)(); [throwRangeError](https://nevware21.github.io/ts-utils/typedoc/functions/throwRangeError.html)(); [throwTypeError](https://nevware21.github.io/ts-utils/typedoc/functions/throwTypeError.html)(); [throwUnsupported](https://nevware21.github.io/ts-utils/typedoc/functions/throwUnsupported.html)(); | Function | [fnApply](https://nevware21.github.io/ts-utils/typedoc/functions/fnApply.html)(); [fnBind](https://nevware21.github.io/ts-utils/typedoc/functions/fnBind.html)(); [fnBindArgs](https://nevware21.github.io/ts-utils/typedoc/functions/fnBindArgs.html)(); [fnCall](https://nevware21.github.io/ts-utils/typedoc/functions/fnCall.html)(); [createFnDeferredProxy](https://nevware21.github.io/ts-utils/typedoc/functions/createFnDeferredProxy.html)(); [createProxyFuncs](https://nevware21.github.io/ts-utils/typedoc/functions/createProxyFuncs.html)(); [readArgs](https://nevware21.github.io/ts-utils/typedoc/functions/readArgs.html)(); | Idle | [getCancelIdleCallback](https://nevware21.github.io/ts-utils/typedoc/functions/getCancelIdleCallback.html)(); [getIdleCallback](https://nevware21.github.io/ts-utils/typedoc/functions/getIdleCallback.html)(); [hasIdleCallback](https://nevware21.github.io/ts-utils/typedoc/functions/hasIdleCallback.html)(); [setDefaultIdleTimeout](https://nevware21.github.io/ts-utils/typedoc/functions/setDefaultIdleTimeout.html)(); [setDefaultMaxExecutionTime](https://nevware21.github.io/ts-utils/typedoc/functions/setDefaultMaxExecutionTime.html)(); -| Iterator | [createArrayIterator](https://nevware21.github.io/ts-utils/typedoc/functions/createArrayIterator.html)(); [createIterator](https://nevware21.github.io/ts-utils/typedoc/functions/createIterator.html)(); [createIterable](https://nevware21.github.io/ts-utils/typedoc/functions/createIterable.html)(); [createRangeIterator](https://nevware21.github.io/ts-utils/typedoc/functions/createRangeIterator.html)(); [iterForOf](https://nevware21.github.io/ts-utils/typedoc/functions/iterForOf.html)(); [isAsyncIterable](https://nevware21.github.io/ts-utils/typedoc/functions/isAsyncIterable.html)(); [isIterable](https://nevware21.github.io/ts-utils/typedoc/functions/isIterable.html)(); [isIterator](https://nevware21.github.io/ts-utils/typedoc/functions/isIterator.html)(); [makeIterable](https://nevware21.github.io/ts-utils/typedoc/functions/makeIterable.html)(); [arrAppend](https://nevware21.github.io/ts-utils/typedoc/functions/arrAppend.html)(); [arrFrom](https://nevware21.github.io/ts-utils/typedoc/functions/arrFrom.html)(); +| Iterator | [createArrayIterator](https://nevware21.github.io/ts-utils/typedoc/functions/createArrayIterator.html)(); [createIterator](https://nevware21.github.io/ts-utils/typedoc/functions/createIterator.html)(); [createIterable](https://nevware21.github.io/ts-utils/typedoc/functions/createIterable.html)(); [createIterableIterator](https://nevware21.github.io/ts-utils/typedoc/functions/createIterableIterator.html)(); [createRangeIterator](https://nevware21.github.io/ts-utils/typedoc/functions/createRangeIterator.html)(); [iterForOf](https://nevware21.github.io/ts-utils/typedoc/functions/iterForOf.html)(); [iterMap](https://nevware21.github.io/ts-utils/typedoc/functions/iterMap.html)(); [iterFilter](https://nevware21.github.io/ts-utils/typedoc/functions/iterFilter.html)(); [iterTake](https://nevware21.github.io/ts-utils/typedoc/functions/iterTake.html)(); [iterReduce](https://nevware21.github.io/ts-utils/typedoc/functions/iterReduce.html)(); [iterSome](https://nevware21.github.io/ts-utils/typedoc/functions/iterSome.html)(); [iterEvery](https://nevware21.github.io/ts-utils/typedoc/functions/iterEvery.html)(); [iterToArray](https://nevware21.github.io/ts-utils/typedoc/functions/iterToArray.html)(); [iterUnion](https://nevware21.github.io/ts-utils/typedoc/functions/iterUnion.html)(); [iterIntersection](https://nevware21.github.io/ts-utils/typedoc/functions/iterIntersection.html)(); [iterDifference](https://nevware21.github.io/ts-utils/typedoc/functions/iterDifference.html)(); [isAsyncIterable](https://nevware21.github.io/ts-utils/typedoc/functions/isAsyncIterable.html)(); [isIterable](https://nevware21.github.io/ts-utils/typedoc/functions/isIterable.html)(); [isIterator](https://nevware21.github.io/ts-utils/typedoc/functions/isIterator.html)(); [makeIterable](https://nevware21.github.io/ts-utils/typedoc/functions/makeIterable.html)(); [arrAppend](https://nevware21.github.io/ts-utils/typedoc/functions/arrAppend.html)(); [arrFrom](https://nevware21.github.io/ts-utils/typedoc/functions/arrFrom.html)(); [arrToMap](https://nevware21.github.io/ts-utils/typedoc/functions/arrToMap.html)(); | Number | [getIntValue](https://nevware21.github.io/ts-utils/typedoc/functions/getIntValue.html)(); [isInteger](https://nevware21.github.io/ts-utils/typedoc/functions/isInteger.html)(); [isIntegerInRange](https://nevware21.github.io/ts-utils/typedoc/functions/isIntegerInRange.html)(); [isFiniteNumber](https://nevware21.github.io/ts-utils/typedoc/functions/isFiniteNumber.html)(); [isNumber](https://nevware21.github.io/ts-utils/typedoc/functions/isNumber.html)(); | Math | [mathAbs](https://nevware21.github.io/ts-utils/typedoc/functions/mathAbs.html)(); [mathAcos](https://nevware21.github.io/ts-utils/typedoc/functions/mathAcos.html)(); [mathAsin](https://nevware21.github.io/ts-utils/typedoc/functions/mathAsin.html)(); [mathAtan](https://nevware21.github.io/ts-utils/typedoc/functions/mathAtan.html)(); [mathAtan2](https://nevware21.github.io/ts-utils/typedoc/functions/mathAtan2.html)(); [mathCeil](https://nevware21.github.io/ts-utils/typedoc/functions/mathCeil.html)(); [mathCos](https://nevware21.github.io/ts-utils/typedoc/functions/mathCos.html)(); [mathExp](https://nevware21.github.io/ts-utils/typedoc/functions/mathExp.html)(); [mathFloor](https://nevware21.github.io/ts-utils/typedoc/functions/mathFloor.html)(); [mathLog](https://nevware21.github.io/ts-utils/typedoc/functions/mathLog.html)(); [mathMax](https://nevware21.github.io/ts-utils/typedoc/functions/mathMax.html)(); [mathMin](https://nevware21.github.io/ts-utils/typedoc/functions/mathMin.html)(); [mathPow](https://nevware21.github.io/ts-utils/typedoc/functions/mathPow.html)(); [mathRandom](https://nevware21.github.io/ts-utils/typedoc/functions/mathRandom.html)(); [mathRound](https://nevware21.github.io/ts-utils/typedoc/functions/mathRound.html)(); [mathSin](https://nevware21.github.io/ts-utils/typedoc/functions/mathSin.html)(); [mathSqrt](https://nevware21.github.io/ts-utils/typedoc/functions/mathSqrt.html)(); [mathTan](https://nevware21.github.io/ts-utils/typedoc/functions/mathTan.html)(); [mathToInt](https://nevware21.github.io/ts-utils/typedoc/functions/mathToInt.html)(); [mathTrunc](https://nevware21.github.io/ts-utils/typedoc/functions/mathTrunc.html)(); | Object | [deepExtend](https://nevware21.github.io/ts-utils/typedoc/functions/deepExtend.html)(); [forEachOwnKey](https://nevware21.github.io/ts-utils/typedoc/functions/forEachOwnKey.html)(); [forEachOwnKeySafe](https://nevware21.github.io/ts-utils/typedoc/functions/forEachOwnKeySafe.html)(); [isObject](https://nevware21.github.io/ts-utils/typedoc/functions/isObject.html)(); [isUnsafePropKey](https://nevware21.github.io/ts-utils/typedoc/functions/isUnsafePropKey.html)(); [isUnsafeTarget](https://nevware21.github.io/ts-utils/typedoc/functions/isUnsafeTarget.html)(); [objAssign](https://nevware21.github.io/ts-utils/typedoc/functions/objAssign.html)(); [objCopyProps](https://nevware21.github.io/ts-utils/typedoc/functions/objCopyProps.html)(); [objCreate](https://nevware21.github.io/ts-utils/typedoc/functions/objCreate.html)(); [objDeepCopy](https://nevware21.github.io/ts-utils/typedoc/functions/objDeepCopy.html)(); [objDeepFreeze](https://nevware21.github.io/ts-utils/typedoc/functions/objDeepFreeze.html)(); [objDefaults](https://nevware21.github.io/ts-utils/typedoc/functions/objDefaults.html)(); [objDefine](https://nevware21.github.io/ts-utils/typedoc/functions/objDefine.html)(); [objDefineAccessors](https://nevware21.github.io/ts-utils/typedoc/functions/objDefineAccessors.html)(); [objDefineGet](https://nevware21.github.io/ts-utils/typedoc/functions/objDefineGet.html)(); [objDefineProp](https://nevware21.github.io/ts-utils/typedoc/functions/objDefineProp.html)(); [objDefineProps](https://nevware21.github.io/ts-utils/typedoc/functions/objDefineProps.html)(); [objDefineProperties](https://nevware21.github.io/ts-utils/typedoc/functions/objDefineProperties.html)(); [objDiff](https://nevware21.github.io/ts-utils/typedoc/functions/objDiff.html)(); [objEntries](https://nevware21.github.io/ts-utils/typedoc/functions/objEntries.html)(); [objExtend](https://nevware21.github.io/ts-utils/typedoc/functions/objExtend.html)(); [objForEachKey](https://nevware21.github.io/ts-utils/typedoc/functions/objForEachKey.html)(); [objForEachKeySafe](https://nevware21.github.io/ts-utils/typedoc/functions/objForEachKeySafe.html)(); [objFreeze](https://nevware21.github.io/ts-utils/typedoc/functions/objFreeze.html)(); [objFromEntries](https://nevware21.github.io/ts-utils/typedoc/functions/objFromEntries.html)(); [objGetOwnPropertyDescriptor](https://nevware21.github.io/ts-utils/typedoc/functions/objGetOwnPropertyDescriptor.html)(); [objGetOwnPropertyDescriptors](https://nevware21.github.io/ts-utils/typedoc/functions/objGetOwnPropertyDescriptors.html)(); [objGetOwnPropertyNames](https://nevware21.github.io/ts-utils/typedoc/functions/objGetOwnPropertyNames.html)(); [objGetOwnPropertySymbols](https://nevware21.github.io/ts-utils/typedoc/functions/objGetOwnPropertySymbols.html)(); [objHasOwn](https://nevware21.github.io/ts-utils/typedoc/functions/objHasOwn.html)(); [objHasOwnProperty](https://nevware21.github.io/ts-utils/typedoc/functions/objHasOwnProperty.html)(); [objIs](https://nevware21.github.io/ts-utils/typedoc/functions/objIs.html)(); [objIsExtensible](https://nevware21.github.io/ts-utils/typedoc/functions/objIsExtensible.html)(); [objIsFrozen](https://nevware21.github.io/ts-utils/typedoc/functions/objIsFrozen.html)(); [objIsSealed](https://nevware21.github.io/ts-utils/typedoc/functions/objIsSealed.html)(); [objKeys](https://nevware21.github.io/ts-utils/typedoc/functions/objKeys.html)(); [objMapValues](https://nevware21.github.io/ts-utils/typedoc/functions/objMapValues.html)(); [objMergeIf](https://nevware21.github.io/ts-utils/typedoc/functions/objMergeIf.html)(); [objOmit](https://nevware21.github.io/ts-utils/typedoc/functions/objOmit.html)(); [objOmitBy](https://nevware21.github.io/ts-utils/typedoc/functions/objOmitBy.html)(); [objPick](https://nevware21.github.io/ts-utils/typedoc/functions/objPick.html)(); [objPickBy](https://nevware21.github.io/ts-utils/typedoc/functions/objPickBy.html)(); [objPreventExtensions](https://nevware21.github.io/ts-utils/typedoc/functions/objPreventExtensions.html)(); [objPropertyIsEnumerable](https://nevware21.github.io/ts-utils/typedoc/functions/objPropertyIsEnumerable.html)(); [objSeal](https://nevware21.github.io/ts-utils/typedoc/functions/objSeal.html)(); [objGetPrototypeOf](https://nevware21.github.io/ts-utils/typedoc/functions/objGetPrototypeOf.html)(); [objSetPrototypeOf](https://nevware21.github.io/ts-utils/typedoc/functions/objSetPrototypeOf.html)(); [objToString](https://nevware21.github.io/ts-utils/typedoc/functions/objToString.html)(); [objValues](https://nevware21.github.io/ts-utils/typedoc/functions/objValues.html)();
[polyObjEntries](https://nevware21.github.io/ts-utils/typedoc/functions/polyObjEntries.html)(); [polyObjIs](https://nevware21.github.io/ts-utils/typedoc/functions/polyObjIs.html)(); [polyObjKeys](https://nevware21.github.io/ts-utils/typedoc/functions/polyObjKeys.html)();
diff --git a/docs/feature-backlog.md b/docs/feature-backlog.md index ec2bb516..64d65f57 100644 --- a/docs/feature-backlog.md +++ b/docs/feature-backlog.md @@ -33,18 +33,9 @@ Notes: - These are direct language-native wrappers with ES version markers for polyfill candidates - Other suggestions below are library-level utilities (not direct language features). -- Iterator helpers are intentionally listed as utility suggestions here rather than standard-language mappings. - Implementations should include ES5 polyfills where applicable for v0.x/v1.x compatibility -### A. Iterator and Collection Helpers (Medium Value) - -- `iterMap`, `iterFilter`, `iterTake` – Iterator transformation helpers -- `iterReduce`, `iterSome`, `iterEvery` – Iterator reduction/testing -- `iterToArray` for predictable materialization of iterables / iterators -- `arrToMap` helpers with stable key selection -- lightweight set operations for iterables - -### B. Reliability and Tooling (High Value) +### A. Reliability and Tooling (High Value) - keep bundle-size thresholds justified with measured report - require test parity for polyfill vs native behavior diff --git a/lib/src/array/arrToMap.ts b/lib/src/array/arrToMap.ts new file mode 100644 index 00000000..0282292a --- /dev/null +++ b/lib/src/array/arrToMap.ts @@ -0,0 +1,59 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { isArrayLike } from "../helpers/base"; +import { isUnsafePropKey } from "../object/isUnsafePropKey"; +import { iterForOf } from "../iterator/forOf"; +import { ArrToMapKeySelectorFn, ArrToMapValueSelectorFn } from "../iterator/types"; +import { arrForEach } from "./forEach"; + +/** + * Creates an object map from array-like, iterator or iterable values. + * + * Keys are generated in stable source order via `keySelector`; later duplicate keys overwrite earlier values. + * Unsafe keys (`__proto__`, `constructor`, `prototype`) are ignored. + * @since 0.15.0 + * @group Array + * @example + * ```ts + * const users = [ + * { id: "u1", name: "Ada" }, + * { id: "u2", name: "Lin" }, + * { id: "u1", name: "Ada Updated" } + * ]; + * + * arrToMap(users, (value) => value.id, (value) => value.name); + * // { u1: "Ada Updated", u2: "Lin" } + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function arrToMap(values: ArrayLike | Iterator | Iterable, keySelector: ArrToMapKeySelectorFn): { [key: string]: T }; +/*#__NO_SIDE_EFFECTS__*/ +export function arrToMap(values: ArrayLike | Iterator | Iterable, keySelector: ArrToMapKeySelectorFn, valueSelector: ArrToMapValueSelectorFn): { [key: string]: V }; +/*#__NO_SIDE_EFFECTS__*/ +export function arrToMap(values: ArrayLike | Iterator | Iterable, keySelector: ArrToMapKeySelectorFn, valueSelector?: ArrToMapValueSelectorFn): { [key: string]: V } { + let result: { [key: string]: V } = {}; + + function _processValue(value: T, index?: number) { + let valueIndex = index || 0; + let key = keySelector(value, valueIndex); + let keyValue = key + ""; + if (!isUnsafePropKey(keyValue)) { + result[keyValue] = valueSelector ? valueSelector(value, valueIndex) : (value as any as V); + } + } + + if (isArrayLike(values)) { + arrForEach(values, _processValue); + } else { + iterForOf(values as Iterator | Iterable, _processValue); + } + + + return result; +} diff --git a/lib/src/funcs/fnBindArgs.ts b/lib/src/funcs/fnBindArgs.ts index 7832c687..023e2b26 100644 --- a/lib/src/funcs/fnBindArgs.ts +++ b/lib/src/funcs/fnBindArgs.ts @@ -37,8 +37,11 @@ import { BoundFunction } from "./types"; * bound("!"); // "Hi friend!" * ``` */ +/*#__NO_SIDE_EFFECTS__*/ export function fnBindArgs any, T, TArgs extends any[]>(fn: F, thisArg: T, argArray: TArgs): BoundFunction; +/*#__NO_SIDE_EFFECTS__*/ export function fnBindArgs any, T>(fn: F, thisArg: T): F; +/*#__NO_SIDE_EFFECTS__*/ export function fnBindArgs any, T>(fn: F, thisArg: T, argArray?: any[]): any { return fn.bind.apply(fn, (argArray ? [ thisArg ].concat(argArray) : [ thisArg ]) as any); } diff --git a/lib/src/funcs/funcs.ts b/lib/src/funcs/funcs.ts index 2ea4a666..62c7b903 100644 --- a/lib/src/funcs/funcs.ts +++ b/lib/src/funcs/funcs.ts @@ -214,6 +214,7 @@ export function fnCall any, T>(fn: F, thisArg: T): R * module2.getX(); // 21 * ``` */ +/*#__NO_SIDE_EFFECTS__*/ export function fnBind any, T>(fn: F, thisArg: T): F; /** @@ -250,6 +251,7 @@ export function fnBind any, T>(fn: F, thisArg: T): * module2.getX(); // 21 * ``` */ +/*#__NO_SIDE_EFFECTS__*/ export function fnBind any, T, TArgs extends any[]>(fn: F, thisArg: T, ...argArray: TArgs): BoundFunction; /** @@ -276,8 +278,10 @@ export function fnBind any, T, TArgs extends any[] * bound("friend"); // "Hello friend" * ``` */ +/*#__NO_SIDE_EFFECTS__*/ export function fnBind(fn: F, thisArg: T, ...argArray: any[]): F; +/*#__NO_SIDE_EFFECTS__*/ export function fnBind any, T>(fn: F, thisArg: T): any { return fn.bind.apply(fn, ArrSlice[CALL](arguments, 1) as any); } diff --git a/lib/src/index.ts b/lib/src/index.ts index 50aa0c7b..49d888b5 100644 --- a/lib/src/index.ts +++ b/lib/src/index.ts @@ -12,6 +12,7 @@ export { arrAppend } from "./array/append"; export { arrConcat } from "./array/concat"; export { ArrPredicateCallbackFn, ArrPredicateCallbackFn2, ArrMapCallbackFn, ArrFlatMapCallbackFn, ArrFromMapFn } from "./array/callbacks"; export { arrAt } from "./array/at"; +export { arrToMap } from "./array/arrToMap"; export { arrChunk } from "./array/chunk"; export { arrCompact } from "./array/compact"; export { arrDifference } from "./array/difference"; @@ -91,6 +92,17 @@ export { hasValue } from "./helpers/value"; export { createArrayIterator } from "./iterator/array"; export { CreateIteratorContext, createIterator, createIterable, createIterableIterator, makeIterable } from "./iterator/create"; export { iterForOf } from "./iterator/forOf"; +export { IterMapCallbackFn, IterPredicateCallbackFn, IterReduceCallbackFn, ArrToMapKeySelectorFn, ArrToMapValueSelectorFn } from "./iterator/types"; +export { iterMap } from "./iterator/iterMap"; +export { iterFilter } from "./iterator/iterFilter"; +export { iterTake } from "./iterator/iterTake"; +export { iterReduce } from "./iterator/iterReduce"; +export { iterSome } from "./iterator/iterSome"; +export { iterEvery } from "./iterator/iterEvery"; +export { iterToArray } from "./iterator/iterToArray"; +export { iterUnion } from "./iterator/iterUnion"; +export { iterIntersection } from "./iterator/iterIntersection"; +export { iterDifference } from "./iterator/iterDifference"; export { isAsyncIterable, isIterable, isIterator } from "./iterator/iterator"; export { createRangeIterator } from "./iterator/range"; export { mathAbs } from "./math/abs"; diff --git a/lib/src/iterator/forOf.ts b/lib/src/iterator/forOf.ts index eeb34954..ba820c98 100644 --- a/lib/src/iterator/forOf.ts +++ b/lib/src/iterator/forOf.ts @@ -64,8 +64,8 @@ export function iterForOf(iter: Iterator | Iterable, callbackfn: (value } if (isIterator(iter)) { - let err: { e: any } = UNDEF_VALUE; - let iterResult: IteratorResult = UNDEF_VALUE; + let err: { e: any } | undefined = UNDEF_VALUE; + let iterResult: IteratorResult | null | undefined = UNDEF_VALUE; try { let count = 0; while(!(iterResult = iter.next()).done) { diff --git a/lib/src/iterator/helpers.ts b/lib/src/iterator/helpers.ts new file mode 100644 index 00000000..b150c6db --- /dev/null +++ b/lib/src/iterator/helpers.ts @@ -0,0 +1,48 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { fnCall } from "../funcs/funcs"; +import { isArrayLike, isStrictNullOrUndefined } from "../helpers/base"; +import { arrSlice } from "../array/slice"; +import { getKnownSymbol } from "../symbol/symbol"; +import { WellKnownSymbols } from "../symbol/well_known"; +import { createArrayIterator } from "./array"; +import { createIterableIterator } from "./create"; +import { isIterable, isIterator } from "./iterator"; + +export function _asIterator(iter: Iterator | Iterable | ArrayLike): Iterator { + if (isIterator(iter)) { + return iter; + } + + if (isArrayLike(iter)) { + return createArrayIterator(arrSlice(iter)); + } + + if (isIterable(iter)) { + let iteratorFn = (iter as any)[getKnownSymbol(WellKnownSymbols.iterator)]; + if (iteratorFn) { + return fnCall(iteratorFn, iter); + } + } + + return createIterableIterator({ + n: function() { + return true; + } + }); +} + +export function _getIteratorThisArg(source: Iterator, thisArg?: any): any { + return isStrictNullOrUndefined(thisArg) ? source : thisArg; +} + +export function _doIteratorReturn(source: Iterator, value?: R): R | undefined { + source.return && source.return(value as any); + return value; +} diff --git a/lib/src/iterator/iterDifference.ts b/lib/src/iterator/iterDifference.ts new file mode 100644 index 00000000..5ea327d4 --- /dev/null +++ b/lib/src/iterator/iterDifference.ts @@ -0,0 +1,44 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { arrForEach } from "../array/forEach"; +import { arrIncludes } from "../array/includes"; +import { iterForOf } from "./forOf"; +import { iterToArray } from "./iterToArray"; + +/** + * Returns values from `iter` that are absent from all `excludeIters`. + * @since 0.15.0 + * @group Iterator + * @example + * ```ts + * iterDifference([1, 2, 3, 4, 5], [2, 5], [9, 4]); // [1, 3] + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function iterDifference(iter: Iterator | Iterable, ...excludeIters: Array | Iterable>): T[] { + let sourceValues = iterToArray(iter); + let excludedValues: T[] = []; + + arrForEach(excludeIters, (excludeIter) => { + iterForOf(excludeIter, (value) => { + if (!arrIncludes(excludedValues, value)) { + excludedValues.push(value); + } + }); + }); + + let result: T[] = []; + arrForEach(sourceValues, (value) => { + if (!arrIncludes(excludedValues, value)) { + result.push(value); + } + }); + + return result; +} diff --git a/lib/src/iterator/iterEvery.ts b/lib/src/iterator/iterEvery.ts new file mode 100644 index 00000000..2e44aba3 --- /dev/null +++ b/lib/src/iterator/iterEvery.ts @@ -0,0 +1,37 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { fnCall } from "../funcs/funcs"; +import { iterForOf } from "./forOf"; +import { _asIterator, _getIteratorThisArg } from "./helpers"; +import { IterPredicateCallbackFn } from "./types"; + +/** + * Tests whether all values in the iterator / iterable match the predicate. + * @since 0.15.0 + * @group Iterator + * @example + * ```ts + * iterEvery([2, 4, 6], (value) => value % 2 === 0); // true + * iterEvery([2, 3, 6], (value) => value % 2 === 0); // false + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function iterEvery(iter: Iterator | Iterable, callbackfn: IterPredicateCallbackFn, thisArg?: any): boolean { + let source = _asIterator(iter); + let allMatched = true; + + iterForOf(source, (value, count) => { + if (!fnCall(callbackfn, _getIteratorThisArg(source, thisArg), value, count)) { + allMatched = false; + return -1; + } + }); + + return allMatched; +} diff --git a/lib/src/iterator/iterFilter.ts b/lib/src/iterator/iterFilter.ts new file mode 100644 index 00000000..ce07ad7e --- /dev/null +++ b/lib/src/iterator/iterFilter.ts @@ -0,0 +1,52 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { fnCall } from "../funcs/funcs"; +import { createIterableIterator } from "./create"; +import { _asIterator, _doIteratorReturn, _getIteratorThisArg } from "./helpers"; +import { IterPredicateCallbackFn } from "./types"; + +/** + * Creates a lazily filtered iterable iterator. + * @since 0.15.0 + * @group Iterator + * @example + * ```ts + * const filtered = iterFilter([1, 2, 3, 4, 5], (value) => value % 2 === 0); + * iterToArray(filtered); // [2, 4] + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function iterFilter(iter: Iterator | Iterable, callbackfn: IterPredicateCallbackFn, thisArg?: any): IterableIterator { + let source = _asIterator(iter); + let index = 0; + let isDone = false; + + return createIterableIterator({ + n: function() { + let hasValue = false; + while (!isDone && !hasValue) { + let next = source.next(); + isDone = !!next.done; + if (!isDone) { + hasValue = !!fnCall(callbackfn, _getIteratorThisArg(source, thisArg), next.value, index); + if (hasValue) { + this.v = next.value; + } + + index++; + } + } + + return !hasValue; + }, + r: function(value?: T) { + return _doIteratorReturn(source, value); + } + }); +} diff --git a/lib/src/iterator/iterIntersection.ts b/lib/src/iterator/iterIntersection.ts new file mode 100644 index 00000000..166b66a3 --- /dev/null +++ b/lib/src/iterator/iterIntersection.ts @@ -0,0 +1,51 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { arrForEach } from "../array/forEach"; +import { arrIncludes } from "../array/includes"; +import { iterToArray } from "./iterToArray"; + +/** + * Returns values that are present in every provided iterator / iterable. + * @since 0.15.0 + * @group Iterator + * @example + * ```ts + * iterIntersection([1, 2, 3, 4], [2, 4, 6], [0, 2, 4]); // [2, 4] + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function iterIntersection(...iters: Array | Iterable>): T[] { + if (iters.length === 0) { + return []; + } + + let result: T[] = []; + let firstValues = iterToArray(iters[0]); + let otherValues: T[][] = []; + + for (let lp = 1; lp < iters.length; lp++) { + otherValues.push(iterToArray(iters[lp])); + } + + arrForEach(firstValues, (value) => { + let inAll = true; + arrForEach(otherValues, (other) => { + if (!arrIncludes(other, value)) { + inAll = false; + return -1; + } + }); + + if (inAll && !arrIncludes(result, value)) { + result.push(value); + } + }); + + return result; +} diff --git a/lib/src/iterator/iterMap.ts b/lib/src/iterator/iterMap.ts new file mode 100644 index 00000000..ae9683b9 --- /dev/null +++ b/lib/src/iterator/iterMap.ts @@ -0,0 +1,47 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { fnCall } from "../funcs/funcs"; +import { createIterableIterator } from "./create"; +import { _asIterator, _doIteratorReturn, _getIteratorThisArg } from "./helpers"; +import { IterMapCallbackFn } from "./types"; + +/** + * Creates a lazily mapped iterable iterator. + * @since 0.15.0 + * @group Iterator + * @example + * ```ts + * const mapped = iterMap([1, 2, 3], (value) => value * 10); + * iterToArray(mapped); // [10, 20, 30] + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function iterMap(iter: Iterator | Iterable, callbackfn: IterMapCallbackFn, thisArg?: any): IterableIterator { + let source = _asIterator(iter); + let index = 0; + let isDone = false; + + return createIterableIterator({ + n: function() { + if (!isDone) { + let next = source.next(); + isDone = !!next.done; + if (!isDone) { + this.v = fnCall(callbackfn, _getIteratorThisArg(source, thisArg), next.value, index); + index++; + } + } + + return isDone; + }, + r: function(value?: R) { + return _doIteratorReturn(source, value); + } + }); +} diff --git a/lib/src/iterator/iterReduce.ts b/lib/src/iterator/iterReduce.ts new file mode 100644 index 00000000..5a7211a5 --- /dev/null +++ b/lib/src/iterator/iterReduce.ts @@ -0,0 +1,44 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { throwTypeError } from "../helpers/throw"; +import { iterForOf } from "./forOf"; +import { _asIterator } from "./helpers"; +import { IterReduceCallbackFn } from "./types"; + +/** + * Reduces an iterator / iterable into a single value. + * @since 0.15.0 + * @group Iterator + * @example + * ```ts + * iterReduce([1, 2, 3, 4], (prev, value) => (prev as number) + value, 0); // 10 + * iterReduce([4, 9, 2], (prev, value) => (prev as number) > value ? (prev as number) : value); // 9 + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function iterReduce(iter: Iterator | Iterable, callbackfn: IterReduceCallbackFn, initialValue?: T | R): R { + let source = _asIterator(iter); + let hasAccumulator = arguments.length > 2; + let accumulator = initialValue as T | R; + + iterForOf(source, (value, count) => { + if (!hasAccumulator) { + accumulator = value; + hasAccumulator = true; + } else { + accumulator = callbackfn(accumulator, value, count, source); + } + }); + + if (!hasAccumulator) { + throwTypeError("Reduce of empty iterator with no initial value"); + } + + return accumulator as R; +} diff --git a/lib/src/iterator/iterSome.ts b/lib/src/iterator/iterSome.ts new file mode 100644 index 00000000..613e4dc9 --- /dev/null +++ b/lib/src/iterator/iterSome.ts @@ -0,0 +1,37 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { fnCall } from "../funcs/funcs"; +import { iterForOf } from "./forOf"; +import { _asIterator, _getIteratorThisArg } from "./helpers"; +import { IterPredicateCallbackFn } from "./types"; + +/** + * Tests whether at least one value in the iterator / iterable matches the predicate. + * @since 0.15.0 + * @group Iterator + * @example + * ```ts + * iterSome([1, 3, 5, 8], (value) => value % 2 === 0); // true + * iterSome([1, 3, 5], (value) => value % 2 === 0); // false + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function iterSome(iter: Iterator | Iterable, callbackfn: IterPredicateCallbackFn, thisArg?: any): boolean { + let source = _asIterator(iter); + let matched = false; + + iterForOf(source, (value, count) => { + if (fnCall(callbackfn, _getIteratorThisArg(source, thisArg), value, count)) { + matched = true; + return -1; + } + }); + + return matched; +} diff --git a/lib/src/iterator/iterTake.ts b/lib/src/iterator/iterTake.ts new file mode 100644 index 00000000..8f4b5f03 --- /dev/null +++ b/lib/src/iterator/iterTake.ts @@ -0,0 +1,67 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { createIterableIterator } from "./create"; +import { _asIterator, _doIteratorReturn } from "./helpers"; +import { mathFloor } from "../math/floor"; + +/** + * Creates a lazy iterator that yields at most `count` values. + * @since 0.15.0 + * @group Iterator + * @example + * ```ts + * const firstThree = iterTake([10, 20, 30, 40, 50], 3); + * iterToArray(firstThree); // [10, 20, 30] + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function iterTake(iter: Iterator | Iterable, count: number): IterableIterator { + let source = _asIterator(iter); + let remaining = count > 0 ? mathFloor(count) : 0; + let complete = false; + + function _complete(value?: T): T | undefined { + if (!complete) { + complete = true; + return _doIteratorReturn(source, value); + } + + return value; + } + + return createIterableIterator({ + n: function() { + let isDone = true; + + if (!complete) { + if (remaining <= 0) { + _complete(); + } else { + let next = source.next(); + if (next.done) { + complete = true; + } else { + this.v = next.value; + remaining--; + if (remaining <= 0) { + _complete(); + } + + isDone = false; + } + } + } + + return isDone; + }, + r: function(value?: T) { + return _complete(value); + } + }); +} diff --git a/lib/src/iterator/iterToArray.ts b/lib/src/iterator/iterToArray.ts new file mode 100644 index 00000000..c7eea9a5 --- /dev/null +++ b/lib/src/iterator/iterToArray.ts @@ -0,0 +1,41 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { fnCall } from "../funcs/funcs"; +import { isFunction } from "../helpers/base"; +import { iterForOf } from "./forOf"; +import { _asIterator, _getIteratorThisArg } from "./helpers"; +import { IterMapCallbackFn } from "./types"; + +/** + * Materializes an iterator / iterable / array-like value as an array. + * @since 0.15.0 + * @group Iterator + * @example + * ```ts + * iterToArray(createRangeIterator(1, 3)); // [1, 2, 3] + * iterToArray([10, 20, 30], (value, idx) => value + idx); // [10, 21, 32] + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function iterToArray(iter: Iterator | Iterable | ArrayLike, mapFn?: IterMapCallbackFn, thisArg?: any): R[] { + let source = _asIterator(iter); + let values: R[] = []; + + if (isFunction(mapFn)) { + iterForOf(source, (value, count) => { + values.push(fnCall(mapFn, _getIteratorThisArg(source, thisArg), value, count || 0)); + }); + } else { + iterForOf(source, (value) => { + values.push(value as any as R); + }); + } + + return values; +} diff --git a/lib/src/iterator/iterUnion.ts b/lib/src/iterator/iterUnion.ts new file mode 100644 index 00000000..bdf45f87 --- /dev/null +++ b/lib/src/iterator/iterUnion.ts @@ -0,0 +1,34 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { arrIncludes } from "../array/includes"; +import { iterForOf } from "./forOf"; + +/** + * Returns unique values from all provided iterators / iterables. + * @since 0.15.0 + * @group Iterator + * @example + * ```ts + * iterUnion([1, 2, 3], [3, 4], createRangeIterator(4, 5)); // [1, 2, 3, 4, 5] + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function iterUnion(...iters: Array | Iterable>): T[] { + let result: T[] = []; + + for (let lp = 0; lp < iters.length; lp++) { + iterForOf(iters[lp], (value) => { + if (!arrIncludes(result, value)) { + result.push(value); + } + }); + } + + return result; +} diff --git a/lib/src/iterator/types.ts b/lib/src/iterator/types.ts new file mode 100644 index 00000000..4343c2fe --- /dev/null +++ b/lib/src/iterator/types.ts @@ -0,0 +1,62 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +/** + * Callback signature for {@link iterMap}. + * @since 0.15.0 + * @group Iterator + * @example + * ```ts + * const toLabel: IterMapCallbackFn = (value, index) => index + ":" + value; + * ``` + */ +export type IterMapCallbackFn = (value: T, index: number) => R; + +/** + * Callback signature for {@link iterFilter}, {@link iterSome} and {@link iterEvery}. + * @since 0.15.0 + * @group Iterator + * @example + * ```ts + * const isEven: IterPredicateCallbackFn = (value) => value % 2 === 0; + * ``` + */ +export type IterPredicateCallbackFn = (value: T, index: number) => unknown; + +/** + * Callback signature for {@link iterReduce}. + * @since 0.15.0 + * @group Iterator + * @example + * ```ts + * const sum: IterReduceCallbackFn = (previous, value) => (previous as number) + value; + * ``` + */ +export type IterReduceCallbackFn = (previousValue: T | R, currentValue: T, currentIndex: number, iter: Iterator) => R; + +/** + * Callback signature for key selection in {@link arrToMap}. + * @since 0.15.0 + * @group Array + * @example + * ```ts + * const byId: ArrToMapKeySelectorFn<{ id: string }> = (value) => value.id; + * ``` + */ +export type ArrToMapKeySelectorFn = (value: T, index: number) => string | number; + +/** + * Callback signature for value selection in {@link arrToMap}. + * @since 0.15.0 + * @group Array + * @example + * ```ts + * const pickName: ArrToMapValueSelectorFn<{ name: string }, string> = (value) => value.name; + * ``` + */ +export type ArrToMapValueSelectorFn = (value: T, index: number) => V; diff --git a/lib/test/bundle-size-check.js b/lib/test/bundle-size-check.js index 2fd2fc86..9289185a 100644 --- a/lib/test/bundle-size-check.js +++ b/lib/test/bundle-size-check.js @@ -7,25 +7,25 @@ const configs = [ { name: "es5-min-full", path: "../bundle/es5/umd/ts-utils.min.js", - limit: 38.5 * 1024, // 38.5 kb in bytes + limit: 40.5 * 1024, // 40.5 kb in bytes compress: false }, { name: "es6-min-full", path: "../bundle/es6/umd/ts-utils.min.js", - limit: 37.5 * 1024, // 37.5 kb in bytes + limit: 39.5 * 1024, // 39.5 kb in bytes compress: false }, { name: "es5-min-zip", path: "../bundle/es5/umd/ts-utils.min.js", - limit: 15 * 1024, // 15 kb in bytes + limit: 15.5 * 1024, // 15.5 kb in bytes compress: true }, { name: "es6-min-zip", path: "../bundle/es6/umd/ts-utils.min.js", - limit: 15 * 1024, // 15 kb in bytes + limit: 15.5 * 1024, // 15.5 kb in bytes compress: true }, { diff --git a/lib/test/src/common/array/arrToMap.test.ts b/lib/test/src/common/array/arrToMap.test.ts new file mode 100644 index 00000000..0e72c22e --- /dev/null +++ b/lib/test/src/common/array/arrToMap.test.ts @@ -0,0 +1,34 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { assert } from "@nevware21/tripwire-chai"; +import { arrToMap } from "../../../../src/array/arrToMap"; + +describe("arrToMap", () => { + it("creates stable key/value lookup", () => { + let entries = [ + { id: "a", value: 10 }, + { id: "b", value: 20 }, + { id: "a", value: 99 } + ]; + + let mapped = arrToMap(entries, (value) => value.id, (value) => value.value); + assert.equal(mapped.a, 99, "later duplicate keys overwrite earlier values"); + assert.equal(mapped.b, 20); + + let unsafeMapped = arrToMap([ + { key: "__proto__", value: 1 }, + { key: "constructor", value: 2 }, + { key: "ok", value: 3 } + ], (value) => value.key, (value) => value.value); + + assert.equal((unsafeMapped as any).ok, 3); + assert.equal((unsafeMapped as any).__proto__, Object.prototype); + assert.equal((unsafeMapped as any).constructor, Object); + }); +}); diff --git a/lib/test/src/common/iterator/iterDifference.test.ts b/lib/test/src/common/iterator/iterDifference.test.ts new file mode 100644 index 00000000..3bcacf02 --- /dev/null +++ b/lib/test/src/common/iterator/iterDifference.test.ts @@ -0,0 +1,23 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { assert } from "@nevware21/tripwire-chai"; +import { createArrayIterator } from "../../../../src/iterator/array"; +import { iterDifference } from "../../../../src/iterator/iterDifference"; + +describe("iterDifference", () => { + it("returns values absent from all exclude iterables", () => { + let difference = iterDifference([1, 2, 3, 4, 5], createArrayIterator([2, 5]), [9, 4]); + assert.deepEqual(difference, [1, 3]); + }); + + it("treats NaN as excluded when present", () => { + let difference = iterDifference([NaN], [NaN]); + assert.deepEqual(difference, []); + }); +}); diff --git a/lib/test/src/common/iterator/iterEvery.test.ts b/lib/test/src/common/iterator/iterEvery.test.ts new file mode 100644 index 00000000..cd0e65c5 --- /dev/null +++ b/lib/test/src/common/iterator/iterEvery.test.ts @@ -0,0 +1,17 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { assert } from "@nevware21/tripwire-chai"; +import { iterEvery } from "../../../../src/iterator/iterEvery"; + +describe("iterEvery", () => { + it("short-circuits when predicate fails", () => { + assert.equal(iterEvery([2, 4, 6], (value) => value % 2 === 0), true); + assert.equal(iterEvery([2, 3, 6], (value) => value % 2 === 0), false); + }); +}); diff --git a/lib/test/src/common/iterator/iterFilter.test.ts b/lib/test/src/common/iterator/iterFilter.test.ts new file mode 100644 index 00000000..58cb8366 --- /dev/null +++ b/lib/test/src/common/iterator/iterFilter.test.ts @@ -0,0 +1,42 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { assert } from "@nevware21/tripwire-chai"; +import { iterFilter } from "../../../../src/iterator/iterFilter"; +import { iterToArray } from "../../../../src/iterator/iterToArray"; + +describe("iterFilter", () => { + it("filters values lazily", () => { + let values = iterToArray(iterFilter([1, 2, 3, 4, 5], (value) => value % 2 === 0)); + assert.deepEqual(values, [2, 4]); + }); + + it("does not call source.next after completion", () => { + let callCount = 0; + let source: Iterator = { + next: () => { + callCount++; + if (callCount === 1) { + return { done: false, value: 1 }; + } + + if (callCount === 2) { + return { done: true, value: undefined as any }; + } + + throw new Error("next called after completion"); + } + }; + + let filtered = iterFilter(source, () => true); + assert.deepEqual(filtered.next(), { done: false, value: 1 }); + assert.equal(filtered.next().done, true); + assert.equal(filtered.next().done, true); + assert.equal(callCount, 2); + }); +}); diff --git a/lib/test/src/common/iterator/iterIntersection.test.ts b/lib/test/src/common/iterator/iterIntersection.test.ts new file mode 100644 index 00000000..31bbe1c1 --- /dev/null +++ b/lib/test/src/common/iterator/iterIntersection.test.ts @@ -0,0 +1,30 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { assert } from "@nevware21/tripwire-chai"; +import { createArrayIterator } from "../../../../src/iterator/array"; +import { iterIntersection } from "../../../../src/iterator/iterIntersection"; + +describe("iterIntersection", () => { + it("returns values present in all iterables", () => { + let intersection = iterIntersection([1, 2, 3, 4], createArrayIterator([2, 4, 6]), [0, 2, 4]); + assert.deepEqual(intersection, [2, 4]); + }); + + it("treats NaN as present across inputs", () => { + let intersection = iterIntersection([NaN], [NaN]); + assert.equal(intersection.length, 1); + assert.equal(intersection[0] !== intersection[0], true); + }); + + it("deduplicates repeated NaN values from the first iterable", () => { + let intersection = iterIntersection([NaN, NaN, NaN], [NaN]); + assert.equal(intersection.length, 1); + assert.equal(intersection[0] !== intersection[0], true); + }); +}); diff --git a/lib/test/src/common/iterator/iterMap.test.ts b/lib/test/src/common/iterator/iterMap.test.ts new file mode 100644 index 00000000..96740af1 --- /dev/null +++ b/lib/test/src/common/iterator/iterMap.test.ts @@ -0,0 +1,42 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { assert } from "@nevware21/tripwire-chai"; +import { iterMap } from "../../../../src/iterator/iterMap"; +import { iterToArray } from "../../../../src/iterator/iterToArray"; + +describe("iterMap", () => { + it("maps values lazily", () => { + let values = iterToArray(iterMap([1, 2, 3], (value) => value * 10)); + assert.deepEqual(values, [10, 20, 30]); + }); + + it("does not call source.next after completion", () => { + let callCount = 0; + let source: Iterator = { + next: () => { + callCount++; + if (callCount === 1) { + return { done: false, value: 1 }; + } + + if (callCount === 2) { + return { done: true, value: undefined as any }; + } + + throw new Error("next called after completion"); + } + }; + + let mapped = iterMap(source, (value) => value * 2); + assert.deepEqual(mapped.next(), { done: false, value: 2 }); + assert.equal(mapped.next().done, true); + assert.equal(mapped.next().done, true); + assert.equal(callCount, 2); + }); +}); diff --git a/lib/test/src/common/iterator/iterReduce.test.ts b/lib/test/src/common/iterator/iterReduce.test.ts new file mode 100644 index 00000000..7a3d2320 --- /dev/null +++ b/lib/test/src/common/iterator/iterReduce.test.ts @@ -0,0 +1,25 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { assert } from "@nevware21/tripwire-chai"; +import { createArrayIterator } from "../../../../src/iterator/array"; +import { iterReduce } from "../../../../src/iterator/iterReduce"; + +describe("iterReduce", () => { + it("supports with and without initial value", () => { + let total = iterReduce([1, 2, 3, 4], (previous, current) => (previous as number) + current, 0); + assert.equal(total, 10); + + let maxValue = iterReduce(createArrayIterator([4, 9, 2]), (previous, current) => { + return (previous as number) > current ? (previous as number) : current; + }); + assert.equal(maxValue, 9); + + assert.throws(() => iterReduce([], (previous, current) => (previous as number) + current), TypeError); + }); +}); diff --git a/lib/test/src/common/iterator/iterSome.test.ts b/lib/test/src/common/iterator/iterSome.test.ts new file mode 100644 index 00000000..8b6f1872 --- /dev/null +++ b/lib/test/src/common/iterator/iterSome.test.ts @@ -0,0 +1,17 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { assert } from "@nevware21/tripwire-chai"; +import { iterSome } from "../../../../src/iterator/iterSome"; + +describe("iterSome", () => { + it("short-circuits when predicate matches", () => { + assert.equal(iterSome([1, 3, 5, 8], (value) => value % 2 === 0), true); + assert.equal(iterSome([1, 3, 5], (value) => value % 2 === 0), false); + }); +}); diff --git a/lib/test/src/common/iterator/iterTake.test.ts b/lib/test/src/common/iterator/iterTake.test.ts new file mode 100644 index 00000000..f6ad8a6d --- /dev/null +++ b/lib/test/src/common/iterator/iterTake.test.ts @@ -0,0 +1,49 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { assert } from "@nevware21/tripwire-chai"; +import { iterTake } from "../../../../src/iterator/iterTake"; +import { iterToArray } from "../../../../src/iterator/iterToArray"; + +describe("iterTake", () => { + it("returns at most the requested number of values", () => { + let values = iterToArray(iterTake([10, 20, 30, 40, 50], 3)); + assert.deepEqual(values, [10, 20, 30]); + }); + + it("normalizes fractional counts to an integer limit", () => { + assert.deepEqual(iterToArray(iterTake([1, 2], 0.5)), []); + assert.deepEqual(iterToArray(iterTake([1, 2, 3, 4], 2.5)), [1, 2]); + }); + + it("closes the wrapped iterator when the count limit is reached", () => { + let nextCount = 0; + let returnCount = 0; + let source: Iterator = { + next: () => { + nextCount++; + return { + done: false, + value: nextCount + }; + }, + return: (value?: any) => { + returnCount++; + return { + done: true, + value: value as number + }; + } + }; + + let values = iterToArray(iterTake(source, 2)); + assert.deepEqual(values, [1, 2]); + assert.equal(nextCount, 2, "source.next() should only be called for yielded values"); + assert.equal(returnCount, 1, "source.return() should be called once when limit is reached"); + }); +}); diff --git a/lib/test/src/common/iterator/iterToArray.test.ts b/lib/test/src/common/iterator/iterToArray.test.ts new file mode 100644 index 00000000..8cb26d09 --- /dev/null +++ b/lib/test/src/common/iterator/iterToArray.test.ts @@ -0,0 +1,34 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { assert } from "@nevware21/tripwire-chai"; +import { createArrayIterator } from "../../../../src/iterator/array"; +import { createRangeIterator } from "../../../../src/iterator/range"; +import { iterToArray } from "../../../../src/iterator/iterToArray"; + +describe("iterToArray", () => { + it("supports iterables and iterators", () => { + let fromIterable = iterToArray(createRangeIterator(1, 3)); + assert.deepEqual(fromIterable, [1, 2, 3]); + + let fromIterator = iterToArray(createArrayIterator([10, 20, 30]), (value, idx) => value + idx); + assert.deepEqual(fromIterator, [10, 21, 32]); + }); + + it("supports array-like values", () => { + let arrayLike: ArrayLike = { + 0: 10, + 1: 20, + 2: 30, + length: 3 + }; + + let values = iterToArray(arrayLike, (value, idx) => value + idx); + assert.deepEqual(values, [10, 21, 32]); + }); +}); diff --git a/lib/test/src/common/iterator/iterUnion.test.ts b/lib/test/src/common/iterator/iterUnion.test.ts new file mode 100644 index 00000000..77e2d54b --- /dev/null +++ b/lib/test/src/common/iterator/iterUnion.test.ts @@ -0,0 +1,25 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { assert } from "@nevware21/tripwire-chai"; +import { createArrayIterator } from "../../../../src/iterator/array"; +import { createRangeIterator } from "../../../../src/iterator/range"; +import { iterUnion } from "../../../../src/iterator/iterUnion"; + +describe("iterUnion", () => { + it("returns unique values from all iterables", () => { + let union = iterUnion([1, 2, 3], createArrayIterator([3, 4, 5]), createRangeIterator(5, 6)); + assert.deepEqual(union, [1, 2, 3, 4, 5, 6]); + }); + + it("treats NaN as a duplicate value", () => { + let union = iterUnion([NaN], [NaN]); + assert.equal(union.length, 1); + assert.equal(union[0] !== union[0], true); + }); +});