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
14 changes: 7 additions & 7 deletions .size-limit.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,42 @@
{
"name": "es5-full",
"path": "lib/dist/es5/mod/ts-utils.js",
"limit": "28 kb",
"limit": "28.5 kb",
"brotli": false,
"running": false
},
{
"name": "es6-full",
"path": "lib/dist/es6/mod/ts-utils.js",
"limit": "27 kb",
"limit": "27.5 kb",
"brotli": false,
"running": false
},
{
"name": "es5-full-brotli",
"path": "lib/dist/es5/mod/ts-utils.js",
"limit": "10 kb",
"limit": "10.5 kb",
"brotli": true,
"running": false
},
{
"name": "es6-full-brotli",
"path": "lib/dist/es6/mod/ts-utils.js",
"limit": "9.75 kb",
"limit": "10.5 kb",
"brotli": true,
"running": false
},
{
"name": "es5-zip",
"path": "lib/dist/es5/mod/ts-utils.js",
"limit": "11 Kb",
"limit": "11.5 Kb",
"gzip": true,
"running": false
},
{
"name": "es6-zip",
"path": "lib/dist/es6/mod/ts-utils.js",
"limit": "10.75 Kb",
"limit": "11.5 Kb",
"gzip": true,
"running": false
},
Expand Down Expand Up @@ -68,7 +68,7 @@
{
"name": "es5-poly",
"path": "lib/bundle/es5/ts-polyfills-utils.js",
"limit": "10 kb",
"limit": "11.5 kb",
"brotli": false,
"running": false
},
Expand Down
6 changes: 2 additions & 4 deletions docs/feature-backlog.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,9 @@ Identify practical, minification-friendly, cross-environment additions that fit
### Language-Native Suggestions (with ECMAScript Version)

#### String Methods (ES6+)
- `strMatchAll` – ES2020 (String.prototype.matchAll for iterator over all regex matches)
- `strAt` – ES2022 (String.prototype.at, supports negative indexing)
(All major String methods currently implemented)

#### Array Methods (ES6+)
- `arrWith` – ES2023 (Array.prototype.with for immutable element replacement)
- `arrFlatMap` – ES2019 (Array.prototype.flatMap)

#### Object Utilities (ES6+)
Expand All @@ -40,7 +38,7 @@ Identify practical, minification-friendly, cross-environment additions that fit
- `mapMerge` – Map concatenation helper

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

Notes:
Expand Down
32 changes: 32 additions & 0 deletions lib/src/helpers/regexp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,3 +213,35 @@ export function makeGlobRegex(value: string, ignoreCase?: boolean, fullMatch?: b
});
}, !!ignoreCase, fullMatch);
}


/**
* Create a regular expression that escapes all special regex characters in the input string,
* treating it as a literal string to match.
* @since 0.14.0
* @group RegExp
* @param matcher - The string value to be escaped and converted into a RegExp for literal matching.
* @returns A new Regular Expression that matches the literal string value.
* @example
* ```ts
* let regex = createLiteralRegex("Hello.World");
*
* let matches = regex.exec("Hello.World");
* matches[0]; // "Hello.World"
*
* let matches = regex.exec("HelloXWorld");
* matches; // null - dot does not match as wildcard
*
* let regex = createLiteralRegex("(test)");
* let matches = regex.exec("(test)");
* matches[0]; // "(test)"
*
* let matches = regex.exec("test");
* matches; // null
* ```
*/
/*#__NO_SIDE_EFFECTS__*/
export function createLiteralRegex(matcher: string) {
// eslint-disable-next-line security/detect-non-literal-regexp
return new RegExp(strReplace(asString(matcher), /[.*+?^${}()|[\]\\]/g, "\\$&") || "(?:)", "g");
}
7 changes: 5 additions & 2 deletions lib/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,14 @@ export { ILazyValue, getLazy, setBypassLazyCache, getWritableLazy } from "./help
export { IGetLength as GetLengthImpl, getLength } from "./helpers/length";
export { getIntValue, isInteger, isFiniteNumber } from "./helpers/number";
export { getPerformance, hasPerformance, elapsedTime, perfNow } from "./helpers/perf";
export { createFilenameRegex, createWildcardRegex, makeGlobRegex } from "./helpers/regexp";
export { createFilenameRegex, createLiteralRegex, createWildcardRegex, makeGlobRegex } from "./helpers/regexp";
export { safe, ISafeReturn, SafeReturnType } from "./helpers/safe";
export { safeGet } from "./helpers/safe_get";
export { safeGetLazy, safeGetWritableLazy, safeGetDeferred, safeGetWritableDeferred } from "./helpers/safe_lazy";
export { throwError, throwTypeError, throwRangeError } from "./helpers/throw";
export { hasValue } from "./helpers/value";
export { createArrayIterator } from "./iterator/array";
export { CreateIteratorContext, createIterator, createIterable, makeIterable } from "./iterator/create";
export { CreateIteratorContext, createIterator, createIterable, createIterableIterator, makeIterable } from "./iterator/create";
export { iterForOf } from "./iterator/forOf";
export { isIterable, isIterator } from "./iterator/iterator";
export { createRangeIterator } from "./iterator/range";
Expand Down Expand Up @@ -124,9 +124,11 @@ 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 { strEndsWith } from "./string/ends_with";
export { strContains, strIncludes, polyStrIncludes } from "./string/includes";
export { strIndexOf, strLastIndexOf } from "./string/index_of";
export { strMatchAll, polyStrMatchAll } from "./string/match_all";
export { strIsNullOrWhiteSpace, strIsNullOrEmpty } from "./string/is_null_or";
export { strPadEnd, strPadStart } from "./string/pad";
export { strReplace } from "./string/replace";
Expand Down Expand Up @@ -163,4 +165,5 @@ export { polyObjIs } from "./polyfills/object/objIs";
export { polyStrSymSplit } from "./polyfills/split";
export { polyGetKnownSymbol, polyNewSymbol, polySymbolFor, polySymbolKeyFor } from "./polyfills/symbol";
export { polyStrTrim, polyStrTrimEnd, polyStrTrimStart } from "./polyfills/trim";
export { polyStrReplaceAll } from "./string/replace_all";
export { polyObjHasOwn } from "./object/has_own";
57 changes: 57 additions & 0 deletions lib/src/iterator/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,63 @@ export function makeIterable<T, I>(target: T, ctx: CreateIteratorContext<I>): T
return target as T & Iterable<I>;
}

/**
* Create an iterable iterator which conforms to both the `Iterator` and `Iterable` protocols,
* it uses the provided `ctx` to manage moving to the `next` value and can be used directly in
* `for...of` loops or consumed via `.next()` calls.
*
* The returned object satisfies both protocols: its `[Symbol.iterator]()` method returns itself,
* making it usable anywhere an `Iterable` is expected, while also exposing the `next()` method
* of the `Iterator` protocol.
*
* @see [Iterator protocol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterator_protocol)
* @see [Iterable protocol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterable_protocol)
* @since 0.14.0
* @group Iterator
* @typeParam T - Identifies the type that will be returned by the iterator
* @param ctx - The context used to manage the iteration over the items.
* @returns A new IterableIterator instance
* @example
* ```ts
* let idx = -1;
* let theValues = [ 5, 10, 15, 20, 25, 30 ];
*
* let theIterator = createIterableIterator<number>({
* n: function() {
* idx++;
* let isDone = idx >= theValues.length;
* if (!isDone) {
* this.v = theValues[idx];
* }
* return isDone;
* }
* });
*
* // Can be consumed as an iterator
* theIterator.next(); // { value: 5, done: false }
*
* // Or used in a for...of loop (Iterable protocol)
* let values: number[] = [];
* for (const value of theIterator) {
* values.push(value);
* }
* // Values: [10, 15, 20, 25, 30]
* ```
*/
/*#__NO_SIDE_EFFECTS__*/
export function createIterableIterator<T>(ctx: CreateIteratorContext<T>): IterableIterator<T> {
let iterator = createIterator(ctx);
let itSymbol = getKnownSymbol(WellKnownSymbols.iterator);

function _createIterator() {
return iterator;
}

(iterator as any)[itSymbol] = _createIterator;

return iterator as IterableIterator<T>;
}

/**
* Create an iterator which conforms to the `Iterator` protocol, it uses the provided `ctx` to
* managed moving to the `next`.
Expand Down
6 changes: 5 additions & 1 deletion lib/src/polyfills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import { polyObjHasOwn } from "./object/has_own";
import { polyArrAt } from "./array/at";
import { polyArrFill } from "./array/fill";
import { polyArrWith } from "./array/with";
import { polyStrAt } from "./string/at";
import { polyStrMatchAll } from "./string/match_all";

(function () {

Expand Down Expand Up @@ -60,7 +62,9 @@ import { polyArrWith } from "./array/with";
"trimRight": polyStrTrimEnd,
"substr": polyStrSubstr,
"includes": polyStrIncludes,
"replaceAll": polyStrReplaceAll
"replaceAll": polyStrReplaceAll,
"at": polyStrAt,
"matchAll": polyStrMatchAll
};

const arrayClsPolyfills = {
Expand Down
75 changes: 75 additions & 0 deletions lib/src/string/at.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* @nevware21/ts-utils
* https://github.com/nevware21/ts-utils
*
* Copyright (c) 2026 NevWare21 Solutions LLC
* Licensed under the MIT license.
*/

import { polyArrAt } from "../array/at";
import { StrProto } from "../internal/constants";
import { _throwIfNullOrUndefined } from "../internal/throwIf";
import { _unwrapFunctionWithPoly } from "../internal/unwrapFunction";
import { mathToInt } from "../math/to_int";
import { asString } from "./as_string";

/**
* The `strAt()` method takes an integer value and returns a new string consisting of the single
* UTF-16 code unit located at the specified offset into the string. It accepts both positive and
* negative integers: negative integers count back from the last string character.
*
* This is the equivalent of `String.prototype.at()` and falls back to {@link polyStrAt} in
* environments where the native method is unavailable.
*
* @function
* @since 0.14.0
* @group String
* @param value - The string value to retrieve a character from.
* @param index - The zero-based index of the character to return. A negative index counts from
* the end of the string: `-1` returns the last character, `-2` the second-to-last, and so on.
* @returns A single-character string at the given index, or `undefined` if the index is out of
* range.
* @throws `TypeError` if `value` is `null` or `undefined`.
* @example
* ```ts
* strAt("hello", 0); // "h"
* strAt("hello", 1); // "e"
* strAt("hello", -1); // "o" — last character
* strAt("hello", -2); // "l" — second-to-last
* strAt("hello", 99); // undefined — out of range
* strAt("hello", -99); // undefined — out of range
* ```
*/
export const strAt: (value: string, index: number) => string | undefined = (/*#__PURE__*/_unwrapFunctionWithPoly("at", StrProto as any, polyStrAt));

/**
* Polyfill implementation of `String.prototype.at()` that returns the character at the given
* integer index, supporting negative indices which count back from the end of the string.
*
* Delegates index normalisation and bounds checking to {@link polyArrAt} by treating the string
* as an array-like object, matching native `String.prototype.at()` behaviour exactly.
*
* @since 0.14.0
* @group String
* @group Polyfill
* @param value - The string value to retrieve a character from.
* @param index - The zero-based index of the character to return. Negative values count from the
* end: `-1` is the last character, `-2` is the second-to-last, and so on.
* @returns A single-character string at the normalised index, or `undefined` if out of range.
* @throws `TypeError` if `value` is `null` or `undefined`.
* @example
* ```ts
* polyStrAt("hello", 0); // "h"
* polyStrAt("hello", -1); // "o"
* polyStrAt("hello", -2); // "l"
* polyStrAt("hello", 99); // undefined
* polyStrAt("hello", -99); // undefined
* ```
*/
/*#__NO_SIDE_EFFECTS__*/
export function polyStrAt(value: string, index: number): string | undefined {
_throwIfNullOrUndefined(value);

// Reuse the Array.at polyfill for index normalization and bounds behavior.
return polyArrAt(asString(value), mathToInt(index));
}
Loading
Loading