Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ If you find this library useful, please consider [sponsoring @nevware21](https:/
- Zero dependencies
- Cross-environment compatibility (Browser, Node.js, Web Workers)
- Extensive type checking utilities
- ESNext-aware inspection helpers for async iterables and integer range validation (for example isAsyncIterable() and isIntegerInRange())
- Modern ECMAScript features with backward compatibility
- Polyfill support for older environments

Expand Down
15 changes: 1 addition & 14 deletions docs/feature-backlog.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,27 +21,14 @@ Identify practical, minification-friendly, cross-environment additions that fit

## Suggested Additions (Proposed Only)

### Language-Native Suggestions (with ECMAScript Version)

#### String Methods (ES6+)
(All major String methods currently implemented)

#### Array Methods (ES6+)
(All major Array methods currently implemented)

#### Object Utilities (ES6+)
(All major Object utilities currently implemented)
### Additional Language-Native Suggestions (with ECMAScript Version)

#### Set/Map Utilities (ES6+ Data Structures)
- `setFrom` – Safe Set construction from iterables
- `mapFrom` – Safe Map construction from key-value pairs
- `setIntersection` / `setUnion` / `setDifference` – Set algebra helpers
- `mapMerge` – Map concatenation helper

#### Type/Value Inspection (ES6+)
- `isAsyncIterable` – ES6+ type checks
- `isIntegerInRange` – Safe integer range validation

Notes:

- These are direct language-native wrappers with ES version markers for polyfill candidates
Expand Down
23 changes: 23 additions & 0 deletions lib/src/helpers/number.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,29 @@ export function getIntValue(value?: string | number, defValue?: number): number
*/
export const isInteger: (value: unknown) => value is number = (/* #__PURE__*/_pureAssign((/* #__PURE__*/_pureRef<(value: unknown) => value is number>(NumberCls as any, "isInteger")), _polyNumberIsInteger));

/**
* Checks if a value is an integer and within the provided inclusive range.
* @function
* @since 0.14.0
* @group Type Identity
* @group Number
* @param value - The value to check
* @param min - The minimum inclusive integer value
* @param max - The maximum inclusive integer value
* @returns True if the value is an integer and min <= value <= max, false otherwise
* @example
* ```ts
* isIntegerInRange(5, 0, 10); // true
* isIntegerInRange(10, 0, 10); // true
* isIntegerInRange(11, 0, 10); // false
* isIntegerInRange(3.14, 0, 10); // false
* ```
*/
/*#__NO_SIDE_EFFECTS__*/
export function isIntegerInRange(value: unknown, min: number, max: number): value is number {
return isInteger(value) && isInteger(min) && isInteger(max) && min <= max && value >= min && value <= max;
}

/**
* A polyfill implementation of Number.isInteger that checks if a value is an integer.
* @internal
Expand Down
6 changes: 3 additions & 3 deletions lib/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export { deepExtend, objExtend } from "./helpers/extend";
export { getValueByKey, setValueByKey, getValueByIter, setValueByIter } from "./helpers/get_set_value";
export { ILazyValue, getLazy, setBypassLazyCache, getWritableLazy } from "./helpers/lazy";
export { IGetLength as GetLengthImpl, getLength } from "./helpers/length";
export { getIntValue, isInteger, isFiniteNumber } from "./helpers/number";
export { getIntValue, isInteger, isIntegerInRange, isFiniteNumber } from "./helpers/number";
export { getPerformance, hasPerformance, elapsedTime, perfNow } from "./helpers/perf";
export { createFilenameRegex, createLiteralRegex, createWildcardRegex, makeGlobRegex } from "./helpers/regexp";
export { safe, ISafeReturn, SafeReturnType } from "./helpers/safe";
Expand All @@ -85,7 +85,7 @@ 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 { isIterable, isIterator } from "./iterator/iterator";
export { isAsyncIterable, isIterable, isIterator } from "./iterator/iterator";
export { createRangeIterator } from "./iterator/range";
export { mathAbs } from "./math/abs";
export { mathCeil, mathFloor } from "./math/floor";
Expand Down Expand Up @@ -126,7 +126,7 @@ export { objSetPrototypeOf } from "./object/set_proto";
export { objIsFrozen, objIsSealed } from "./object/object_state";
export { strCamelCase, strCapitalizeWords, strKebabCase, strLetterCase, strSnakeCase } from "./string/conversion";
export { strCount } from "./string/count";
export { strAt, polyStrAt } from "./string/at";
export { strAt } from "./string/at";
export { strEndsWith } from "./string/ends_with";
export { strContains, strIncludes, polyStrIncludes } from "./string/includes";
export { strIndexOf, strLastIndexOf } from "./string/index_of";
Expand Down
22 changes: 22 additions & 0 deletions lib/src/iterator/iterator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,26 @@ export function isIterator<T = any>(value: any): value is Iterator<T> {
/*#__NO_SIDE_EFFECTS__*/
export function isIterable<T = any>(value: any): value is Iterable<T> {
return !isStrictNullOrUndefined(value) && isFunction(value[getKnownSymbol(WellKnownSymbols.iterator)]);
}

/**
* Checks if the value looks like it is async iterable, contains a [symbol.asyncIterator].
*
* @since 0.14.0
* @group Type Identity
* @group Iterator
* @typeParam T - Identifies the return type of the async iterator
* @param value - The value to be checked
* @returns True if the value is an AsyncIterable, otherwise false
* @example
* ```ts
* isAsyncIterable(null); // false
* isAsyncIterable(undefined); // false
* isAsyncIterable("null"); // false
* isAsyncIterable([]); // false
* ```
*/
/*#__NO_SIDE_EFFECTS__*/
export function isAsyncIterable<T = any>(value: any): value is AsyncIterable<T> {
return !isStrictNullOrUndefined(value) && isFunction(value[getKnownSymbol(WellKnownSymbols.asyncIterator)]);
}
29 changes: 28 additions & 1 deletion lib/test/src/common/helpers/number.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import { assert } from "@nevware21/tripwire-chai";
import { isFiniteNumber, isInteger, _polyNumberIsInteger, getIntValue } from "../../../../src/helpers/number";
import { isFiniteNumber, isInteger, isIntegerInRange, _polyNumberIsInteger, getIntValue } from "../../../../src/helpers/number";

describe("number helpers", () => {
describe("getIntValue", () => {
Expand Down Expand Up @@ -79,6 +79,33 @@ describe("number helpers", () => {
});
});

describe("isIntegerInRange", () => {
it("should return true for integers within inclusive bounds", () => {
assert.equal(isIntegerInRange(0, 0, 10), true, "Should include min bound");
assert.equal(isIntegerInRange(10, 0, 10), true, "Should include max bound");
assert.equal(isIntegerInRange(5, 0, 10), true, "Should include middle values");
assert.equal(isIntegerInRange(-5, -10, -1), true, "Should support negative ranges");
});

it("should return false for values outside the range", () => {
assert.equal(isIntegerInRange(-1, 0, 10), false, "Should fail below min");
assert.equal(isIntegerInRange(11, 0, 10), false, "Should fail above max");
});

it("should return false for non-integer values", () => {
assert.equal(isIntegerInRange(3.14, 0, 10), false, "Should fail non-integer value");
assert.equal(isIntegerInRange("5" as any, 0, 10), false, "Should fail string value");
assert.equal(isIntegerInRange(NaN, 0, 10), false, "Should fail NaN");
assert.equal(isIntegerInRange(Infinity, 0, 10), false, "Should fail Infinity");
});

it("should return false for invalid range bounds", () => {
assert.equal(isIntegerInRange(5, 10, 0), false, "Should fail when min > max");
assert.equal(isIntegerInRange(5, 0.1, 10), false, "Should fail non-integer min");
assert.equal(isIntegerInRange(5, 0, 10.1), false, "Should fail non-integer max");
});
});

describe("polyNumberIsInteger", () => {
it("should identify integer values", () => {
assert.equal(_polyNumberIsInteger(0), true, "Should return true for 0");
Expand Down
50 changes: 50 additions & 0 deletions lib/test/src/esnext/iterator/iterator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* @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 { isAsyncIterable, isIterable, isIterator } from "../../../../src/iterator/iterator";

describe("ESNext iterator tests", () => {
async function* _asyncGenerator() {
yield 1;
}

function* _generator() {
yield 1;
}

describe("isAsyncIterable", () => {
it("should identify async iterable values", () => {
let asyncGenerator = _asyncGenerator();
let customAsyncIterable = {
[Symbol.asyncIterator]: function() {
return asyncGenerator;
}
};

assert.equal(isAsyncIterable(asyncGenerator), true, "Should identify async generator instances");
assert.equal(isAsyncIterable(customAsyncIterable), true, "Should identify objects with Symbol.asyncIterator");
});

it("should reject non async iterable values", () => {
assert.equal(isAsyncIterable(null), false, "Should handle null");
assert.equal(isAsyncIterable(undefined), false, "Should handle undefined");
assert.equal(isAsyncIterable("value"), false, "Should reject strings");
assert.equal(isAsyncIterable([]), false, "Should reject arrays");
assert.equal(isAsyncIterable(_generator()), false, "Should reject sync generators");
assert.equal(isAsyncIterable({ [Symbol.asyncIterator]: 1 } as any), false, "Should require function async iterator");
});

it("should remain distinct from sync iterator checks", () => {
let asyncGenerator = _asyncGenerator();

assert.equal(isIterable(asyncGenerator), false, "Async generators are not sync iterable");
assert.equal(isIterator(asyncGenerator), true, "Async generators are iterators");
});
});
});
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@
},
"devDependencies": {
"@istanbuljs/nyc-config-typescript": "^1.0.2",
"@microsoft/api-extractor": "^7.34.4",
"@microsoft/api-extractor": "^7.57.7",
"@nevware21/coverage-tools": ">= 0.1.4 < 2.x",
"@nevware21/grunt-eslint-ts": "^0.5.1",
"@nevware21/grunt-ts-plugin": "^0.5.1",
Expand Down Expand Up @@ -257,7 +257,7 @@
"size-limit": "^12.0.0",
"ts-mocha": "^11.1.0",
"tslib": "^2.6.0",
"typedoc": "^0.28.2",
"typedoc": "^0.28.18",
"typedoc-github-theme": "^0.4.0",
"typescript": "~5.2.2",
"uglify-js": "^3.15.5"
Expand Down
Loading