diff --git a/.size-limit.json b/.size-limit.json
index 4c18c11c..bcc1ea5a 100644
--- a/.size-limit.json
+++ b/.size-limit.json
@@ -2,21 +2,21 @@
{
"name": "es5-full",
"path": "lib/dist/es5/mod/ts-utils.js",
- "limit": "37.5 kb",
+ "limit": "37.75 kb",
"brotli": false,
"running": false
},
{
"name": "es6-full",
"path": "lib/dist/es6/mod/ts-utils.js",
- "limit": "36 kb",
+ "limit": "36.5 kb",
"brotli": false,
"running": false
},
{
"name": "es5-full-brotli",
"path": "lib/dist/es5/mod/ts-utils.js",
- "limit": "12.75 kb",
+ "limit": "13 kb",
"brotli": true,
"running": false
},
@@ -30,7 +30,7 @@
{
"name": "es5-zip",
"path": "lib/dist/es5/mod/ts-utils.js",
- "limit": "14 Kb",
+ "limit": "14.5 Kb",
"gzip": true,
"running": false
},
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a6e722e0..15ceac9e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,9 +1,11 @@
-# Unreleased
+# Unreleased (Proposed 0.17.0)
## Changelog
### Features
+- feat(array): add `arrIndexOfSubset()` and `arrLastIndexOfSubset()` to locate the first/last index at which an array of values matches contiguously within another, re-searching later/earlier occurrences of the leading value when it repeats (eg. matching one stack trace against another) instead of stopping at the first, wrong occurrence
+
### Bug Fixes
# v0.16.0 July 18th, 2026
diff --git a/README.md b/README.md
index 9fa567b3..4a424057 100644
--- a/README.md
+++ b/README.md
@@ -120,8 +120,8 @@ Below is a categorized list of all available utilities with direct links to thei
| Value Check | [hasValue](https://nevware21.github.io/ts-utils/typedoc/functions/hasValue.html)(); [isDefined](https://nevware21.github.io/ts-utils/typedoc/functions/isDefined.html)(); [isEmpty](https://nevware21.github.io/ts-utils/typedoc/functions/isEmpty.html)(); [isNotTruthy](https://nevware21.github.io/ts-utils/typedoc/functions/isNotTruthy.html)(); [isNullOrUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isNullOrUndefined.html)(); [isStrictNullOrUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isStrictNullOrUndefined.html)(); [isStrictUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isStrictUndefined.html)(); [isTruthy](https://nevware21.github.io/ts-utils/typedoc/functions/isTruthy.html)(); [isUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isUndefined.html)();
| Value | [getValueByKey](https://nevware21.github.io/ts-utils/typedoc/functions/getValueByKey.html)(); [setValueByKey](https://nevware21.github.io/ts-utils/typedoc/functions/setValueByKey.html)(); [getValueByIter](https://nevware21.github.io/ts-utils/typedoc/functions/getValueByIter.html)(); [setValueByIter](https://nevware21.github.io/ts-utils/typedoc/functions/setValueByIter.html)(); [encodeAsJson](https://nevware21.github.io/ts-utils/typedoc/functions/encodeAsJson.html)(); [encodeAsHtml](https://nevware21.github.io/ts-utils/typedoc/functions/encodeAsHtml.html)(); [asString](https://nevware21.github.io/ts-utils/typedoc/functions/asString.html)(); [getIntValue](https://nevware21.github.io/ts-utils/typedoc/functions/getIntValue.html)(); [normalizeJsName](https://nevware21.github.io/ts-utils/typedoc/functions/normalizeJsName.html)();
| |
-| Array | [arrAppend](https://nevware21.github.io/ts-utils/typedoc/functions/arrAppend.html)(); [arrConcat](https://nevware21.github.io/ts-utils/typedoc/functions/arrConcat.html)(); [arrAt](https://nevware21.github.io/ts-utils/typedoc/functions/arrAt.html)(); [arrChunk](https://nevware21.github.io/ts-utils/typedoc/functions/arrChunk.html)(); [arrCompact](https://nevware21.github.io/ts-utils/typedoc/functions/arrCompact.html)(); [arrContains](https://nevware21.github.io/ts-utils/typedoc/functions/arrContains.html)(); [arrDifference](https://nevware21.github.io/ts-utils/typedoc/functions/arrDifference.html)(); [arrDrop](https://nevware21.github.io/ts-utils/typedoc/functions/arrDrop.html)(); [arrDropWhile](https://nevware21.github.io/ts-utils/typedoc/functions/arrDropWhile.html)(); [arrEvery](https://nevware21.github.io/ts-utils/typedoc/functions/arrEvery.html)(); [arrFilter](https://nevware21.github.io/ts-utils/typedoc/functions/arrFilter.html)(); [arrFill](https://nevware21.github.io/ts-utils/typedoc/functions/arrFill.html)(); [arrFind](https://nevware21.github.io/ts-utils/typedoc/functions/arrFind.html)(); [arrFindIndex](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindIndex.html)(); [arrFindLast](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindLast.html)(); [arrFindLastIndex](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindLastIndex.html)(); [arrFlatten](https://nevware21.github.io/ts-utils/typedoc/functions/arrFlatten.html)(); [arrForEach](https://nevware21.github.io/ts-utils/typedoc/functions/arrForEach.html)(); [arrFrom](https://nevware21.github.io/ts-utils/typedoc/functions/arrFrom.html)(); [arrGroupBy](https://nevware21.github.io/ts-utils/typedoc/functions/arrGroupBy.html)(); [arrIncludes](https://nevware21.github.io/ts-utils/typedoc/functions/arrIncludes.html)(); [arrIndexKeys](https://nevware21.github.io/ts-utils/typedoc/functions/arrIndexKeys.html)(); [arrIndexOf](https://nevware21.github.io/ts-utils/typedoc/functions/arrIndexOf.html)(); [arrIntersection](https://nevware21.github.io/ts-utils/typedoc/functions/arrIntersection.html)(); [arrKeys](https://nevware21.github.io/ts-utils/typedoc/functions/arrKeys.html)(); [arrLastIndexOf](https://nevware21.github.io/ts-utils/typedoc/functions/arrLastIndexOf.html)(); [arrMap](https://nevware21.github.io/ts-utils/typedoc/functions/arrMap.html)(); [arrPartition](https://nevware21.github.io/ts-utils/typedoc/functions/arrPartition.html)(); [arrReduce](https://nevware21.github.io/ts-utils/typedoc/functions/arrReduce.html)(); [arrReverse](https://nevware21.github.io/ts-utils/typedoc/functions/arrReverse.html)(); [arrRotate](https://nevware21.github.io/ts-utils/typedoc/functions/arrRotate.html)(); [arrSample](https://nevware21.github.io/ts-utils/typedoc/functions/arrSample.html)(); [arrShuffle](https://nevware21.github.io/ts-utils/typedoc/functions/arrShuffle.html)(); [arrSlice](https://nevware21.github.io/ts-utils/typedoc/functions/arrSlice.html)(); [arrSome](https://nevware21.github.io/ts-utils/typedoc/functions/arrSome.html)(); [arrTake](https://nevware21.github.io/ts-utils/typedoc/functions/arrTake.html)(); [arrTakeWhile](https://nevware21.github.io/ts-utils/typedoc/functions/arrTakeWhile.html)(); [arrUnion](https://nevware21.github.io/ts-utils/typedoc/functions/arrUnion.html)(); [arrUnique](https://nevware21.github.io/ts-utils/typedoc/functions/arrUnique.html)(); [arrUnzip](https://nevware21.github.io/ts-utils/typedoc/functions/arrUnzip.html)(); [arrWith](https://nevware21.github.io/ts-utils/typedoc/functions/arrWith.html)(); [arrZip](https://nevware21.github.io/ts-utils/typedoc/functions/arrZip.html)(); [getLength](https://nevware21.github.io/ts-utils/typedoc/functions/getLength.html)(); [isArray](https://nevware21.github.io/ts-utils/typedoc/functions/isArray.html)();
[polyIsArray](https://nevware21.github.io/ts-utils/typedoc/functions/polyIsArray.html)(); [polyArrIncludes](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrIncludes.html)(); [polyArrFind](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFind.html)(); [polyArrFindIndex](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFindIndex.html)(); [polyArrFindLast](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFindLast.html)(); [polyArrFindLastIndex](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFindLastIndex.html)(); [polyArrFrom](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFrom.html)();
-| ArrayLike | [arrAt](https://nevware21.github.io/ts-utils/typedoc/functions/arrAt.html)(); [arrChunk](https://nevware21.github.io/ts-utils/typedoc/functions/arrChunk.html)(); [arrCompact](https://nevware21.github.io/ts-utils/typedoc/functions/arrCompact.html)(); [arrContains](https://nevware21.github.io/ts-utils/typedoc/functions/arrContains.html)(); [arrDifference](https://nevware21.github.io/ts-utils/typedoc/functions/arrDifference.html)(); [arrDrop](https://nevware21.github.io/ts-utils/typedoc/functions/arrDrop.html)(); [arrDropWhile](https://nevware21.github.io/ts-utils/typedoc/functions/arrDropWhile.html)(); [arrEvery](https://nevware21.github.io/ts-utils/typedoc/functions/arrEvery.html)(); [arrFilter](https://nevware21.github.io/ts-utils/typedoc/functions/arrFilter.html)(); [arrFill](https://nevware21.github.io/ts-utils/typedoc/functions/arrFill.html)(); [arrFind](https://nevware21.github.io/ts-utils/typedoc/functions/arrFind.html)(); [arrFindIndex](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindIndex.html)(); [arrFindLast](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindLast.html)(); [arrFindLastIndex](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindLastIndex.html)(); [arrFlatten](https://nevware21.github.io/ts-utils/typedoc/functions/arrFlatten.html)(); [arrForEach](https://nevware21.github.io/ts-utils/typedoc/functions/arrForEach.html)(); [arrFrom](https://nevware21.github.io/ts-utils/typedoc/functions/arrFrom.html)(); [arrGroupBy](https://nevware21.github.io/ts-utils/typedoc/functions/arrGroupBy.html)(); [arrIncludes](https://nevware21.github.io/ts-utils/typedoc/functions/arrIncludes.html)(); [arrIndexKeys](https://nevware21.github.io/ts-utils/typedoc/functions/arrIndexKeys.html)(); [arrIndexOf](https://nevware21.github.io/ts-utils/typedoc/functions/arrIndexOf.html)(); [arrIntersection](https://nevware21.github.io/ts-utils/typedoc/functions/arrIntersection.html)(); [arrKeys](https://nevware21.github.io/ts-utils/typedoc/functions/arrKeys.html)(); [arrLastIndexOf](https://nevware21.github.io/ts-utils/typedoc/functions/arrLastIndexOf.html)(); [arrMap](https://nevware21.github.io/ts-utils/typedoc/functions/arrMap.html)(); [arrPartition](https://nevware21.github.io/ts-utils/typedoc/functions/arrPartition.html)(); [arrReduce](https://nevware21.github.io/ts-utils/typedoc/functions/arrReduce.html)(); [arrReverse](https://nevware21.github.io/ts-utils/typedoc/functions/arrReverse.html)(); [arrRotate](https://nevware21.github.io/ts-utils/typedoc/functions/arrRotate.html)(); [arrSample](https://nevware21.github.io/ts-utils/typedoc/functions/arrSample.html)(); [arrShuffle](https://nevware21.github.io/ts-utils/typedoc/functions/arrShuffle.html)(); [arrSlice](https://nevware21.github.io/ts-utils/typedoc/functions/arrSlice.html)(); [arrSome](https://nevware21.github.io/ts-utils/typedoc/functions/arrSome.html)(); [arrTake](https://nevware21.github.io/ts-utils/typedoc/functions/arrTake.html)(); [arrTakeWhile](https://nevware21.github.io/ts-utils/typedoc/functions/arrTakeWhile.html)(); [arrUnion](https://nevware21.github.io/ts-utils/typedoc/functions/arrUnion.html)(); [arrUnique](https://nevware21.github.io/ts-utils/typedoc/functions/arrUnique.html)(); [arrUnzip](https://nevware21.github.io/ts-utils/typedoc/functions/arrUnzip.html)(); [arrWith](https://nevware21.github.io/ts-utils/typedoc/functions/arrWith.html)(); [arrZip](https://nevware21.github.io/ts-utils/typedoc/functions/arrZip.html)(); [getLength](https://nevware21.github.io/ts-utils/typedoc/functions/getLength.html)(); [objEntries](https://nevware21.github.io/ts-utils/typedoc/functions/objEntries.html)(); [objValues](https://nevware21.github.io/ts-utils/typedoc/functions/objValues.html)();
+| Array | [arrAppend](https://nevware21.github.io/ts-utils/typedoc/functions/arrAppend.html)(); [arrConcat](https://nevware21.github.io/ts-utils/typedoc/functions/arrConcat.html)(); [arrAt](https://nevware21.github.io/ts-utils/typedoc/functions/arrAt.html)(); [arrChunk](https://nevware21.github.io/ts-utils/typedoc/functions/arrChunk.html)(); [arrCompact](https://nevware21.github.io/ts-utils/typedoc/functions/arrCompact.html)(); [arrContains](https://nevware21.github.io/ts-utils/typedoc/functions/arrContains.html)(); [arrDifference](https://nevware21.github.io/ts-utils/typedoc/functions/arrDifference.html)(); [arrDrop](https://nevware21.github.io/ts-utils/typedoc/functions/arrDrop.html)(); [arrDropWhile](https://nevware21.github.io/ts-utils/typedoc/functions/arrDropWhile.html)(); [arrEvery](https://nevware21.github.io/ts-utils/typedoc/functions/arrEvery.html)(); [arrFilter](https://nevware21.github.io/ts-utils/typedoc/functions/arrFilter.html)(); [arrFill](https://nevware21.github.io/ts-utils/typedoc/functions/arrFill.html)(); [arrFind](https://nevware21.github.io/ts-utils/typedoc/functions/arrFind.html)(); [arrFindIndex](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindIndex.html)(); [arrFindLast](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindLast.html)(); [arrFindLastIndex](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindLastIndex.html)(); [arrFlatten](https://nevware21.github.io/ts-utils/typedoc/functions/arrFlatten.html)(); [arrForEach](https://nevware21.github.io/ts-utils/typedoc/functions/arrForEach.html)(); [arrFrom](https://nevware21.github.io/ts-utils/typedoc/functions/arrFrom.html)(); [arrGroupBy](https://nevware21.github.io/ts-utils/typedoc/functions/arrGroupBy.html)(); [arrIncludes](https://nevware21.github.io/ts-utils/typedoc/functions/arrIncludes.html)(); [arrIndexKeys](https://nevware21.github.io/ts-utils/typedoc/functions/arrIndexKeys.html)(); [arrIndexOf](https://nevware21.github.io/ts-utils/typedoc/functions/arrIndexOf.html)(); [arrIndexOfSubset](https://nevware21.github.io/ts-utils/typedoc/functions/arrIndexOfSubset.html)(); [arrIntersection](https://nevware21.github.io/ts-utils/typedoc/functions/arrIntersection.html)(); [arrKeys](https://nevware21.github.io/ts-utils/typedoc/functions/arrKeys.html)(); [arrLastIndexOf](https://nevware21.github.io/ts-utils/typedoc/functions/arrLastIndexOf.html)(); [arrLastIndexOfSubset](https://nevware21.github.io/ts-utils/typedoc/functions/arrLastIndexOfSubset.html)(); [arrMap](https://nevware21.github.io/ts-utils/typedoc/functions/arrMap.html)(); [arrPartition](https://nevware21.github.io/ts-utils/typedoc/functions/arrPartition.html)(); [arrReduce](https://nevware21.github.io/ts-utils/typedoc/functions/arrReduce.html)(); [arrReverse](https://nevware21.github.io/ts-utils/typedoc/functions/arrReverse.html)(); [arrRotate](https://nevware21.github.io/ts-utils/typedoc/functions/arrRotate.html)(); [arrSample](https://nevware21.github.io/ts-utils/typedoc/functions/arrSample.html)(); [arrShuffle](https://nevware21.github.io/ts-utils/typedoc/functions/arrShuffle.html)(); [arrSlice](https://nevware21.github.io/ts-utils/typedoc/functions/arrSlice.html)(); [arrSome](https://nevware21.github.io/ts-utils/typedoc/functions/arrSome.html)(); [arrTake](https://nevware21.github.io/ts-utils/typedoc/functions/arrTake.html)(); [arrTakeWhile](https://nevware21.github.io/ts-utils/typedoc/functions/arrTakeWhile.html)(); [arrUnion](https://nevware21.github.io/ts-utils/typedoc/functions/arrUnion.html)(); [arrUnique](https://nevware21.github.io/ts-utils/typedoc/functions/arrUnique.html)(); [arrUnzip](https://nevware21.github.io/ts-utils/typedoc/functions/arrUnzip.html)(); [arrWith](https://nevware21.github.io/ts-utils/typedoc/functions/arrWith.html)(); [arrZip](https://nevware21.github.io/ts-utils/typedoc/functions/arrZip.html)(); [getLength](https://nevware21.github.io/ts-utils/typedoc/functions/getLength.html)(); [isArray](https://nevware21.github.io/ts-utils/typedoc/functions/isArray.html)();
[polyIsArray](https://nevware21.github.io/ts-utils/typedoc/functions/polyIsArray.html)(); [polyArrIncludes](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrIncludes.html)(); [polyArrFind](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFind.html)(); [polyArrFindIndex](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFindIndex.html)(); [polyArrFindLast](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFindLast.html)(); [polyArrFindLastIndex](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFindLastIndex.html)(); [polyArrFrom](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFrom.html)();
+| ArrayLike | [arrAt](https://nevware21.github.io/ts-utils/typedoc/functions/arrAt.html)(); [arrChunk](https://nevware21.github.io/ts-utils/typedoc/functions/arrChunk.html)(); [arrCompact](https://nevware21.github.io/ts-utils/typedoc/functions/arrCompact.html)(); [arrContains](https://nevware21.github.io/ts-utils/typedoc/functions/arrContains.html)(); [arrDifference](https://nevware21.github.io/ts-utils/typedoc/functions/arrDifference.html)(); [arrDrop](https://nevware21.github.io/ts-utils/typedoc/functions/arrDrop.html)(); [arrDropWhile](https://nevware21.github.io/ts-utils/typedoc/functions/arrDropWhile.html)(); [arrEvery](https://nevware21.github.io/ts-utils/typedoc/functions/arrEvery.html)(); [arrFilter](https://nevware21.github.io/ts-utils/typedoc/functions/arrFilter.html)(); [arrFill](https://nevware21.github.io/ts-utils/typedoc/functions/arrFill.html)(); [arrFind](https://nevware21.github.io/ts-utils/typedoc/functions/arrFind.html)(); [arrFindIndex](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindIndex.html)(); [arrFindLast](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindLast.html)(); [arrFindLastIndex](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindLastIndex.html)(); [arrFlatten](https://nevware21.github.io/ts-utils/typedoc/functions/arrFlatten.html)(); [arrForEach](https://nevware21.github.io/ts-utils/typedoc/functions/arrForEach.html)(); [arrFrom](https://nevware21.github.io/ts-utils/typedoc/functions/arrFrom.html)(); [arrGroupBy](https://nevware21.github.io/ts-utils/typedoc/functions/arrGroupBy.html)(); [arrIncludes](https://nevware21.github.io/ts-utils/typedoc/functions/arrIncludes.html)(); [arrIndexKeys](https://nevware21.github.io/ts-utils/typedoc/functions/arrIndexKeys.html)(); [arrIndexOf](https://nevware21.github.io/ts-utils/typedoc/functions/arrIndexOf.html)(); [arrIndexOfSubset](https://nevware21.github.io/ts-utils/typedoc/functions/arrIndexOfSubset.html)(); [arrIntersection](https://nevware21.github.io/ts-utils/typedoc/functions/arrIntersection.html)(); [arrKeys](https://nevware21.github.io/ts-utils/typedoc/functions/arrKeys.html)(); [arrLastIndexOf](https://nevware21.github.io/ts-utils/typedoc/functions/arrLastIndexOf.html)(); [arrLastIndexOfSubset](https://nevware21.github.io/ts-utils/typedoc/functions/arrLastIndexOfSubset.html)(); [arrMap](https://nevware21.github.io/ts-utils/typedoc/functions/arrMap.html)(); [arrPartition](https://nevware21.github.io/ts-utils/typedoc/functions/arrPartition.html)(); [arrReduce](https://nevware21.github.io/ts-utils/typedoc/functions/arrReduce.html)(); [arrReverse](https://nevware21.github.io/ts-utils/typedoc/functions/arrReverse.html)(); [arrRotate](https://nevware21.github.io/ts-utils/typedoc/functions/arrRotate.html)(); [arrSample](https://nevware21.github.io/ts-utils/typedoc/functions/arrSample.html)(); [arrShuffle](https://nevware21.github.io/ts-utils/typedoc/functions/arrShuffle.html)(); [arrSlice](https://nevware21.github.io/ts-utils/typedoc/functions/arrSlice.html)(); [arrSome](https://nevware21.github.io/ts-utils/typedoc/functions/arrSome.html)(); [arrTake](https://nevware21.github.io/ts-utils/typedoc/functions/arrTake.html)(); [arrTakeWhile](https://nevware21.github.io/ts-utils/typedoc/functions/arrTakeWhile.html)(); [arrUnion](https://nevware21.github.io/ts-utils/typedoc/functions/arrUnion.html)(); [arrUnique](https://nevware21.github.io/ts-utils/typedoc/functions/arrUnique.html)(); [arrUnzip](https://nevware21.github.io/ts-utils/typedoc/functions/arrUnzip.html)(); [arrWith](https://nevware21.github.io/ts-utils/typedoc/functions/arrWith.html)(); [arrZip](https://nevware21.github.io/ts-utils/typedoc/functions/arrZip.html)(); [getLength](https://nevware21.github.io/ts-utils/typedoc/functions/getLength.html)(); [objEntries](https://nevware21.github.io/ts-utils/typedoc/functions/objEntries.html)(); [objValues](https://nevware21.github.io/ts-utils/typedoc/functions/objValues.html)();
| DOM | [isElement](https://nevware21.github.io/ts-utils/typedoc/functions/isElement.html)(); [isElementLike](https://nevware21.github.io/ts-utils/typedoc/functions/isElementLike.html)();
| Enum | [createEnum](https://nevware21.github.io/ts-utils/typedoc/functions/createEnum.html)(); [createEnumKeyMap](https://nevware21.github.io/ts-utils/typedoc/functions/createEnumKeyMap.html)(); [createEnumValueMap](https://nevware21.github.io/ts-utils/typedoc/functions/createEnumValueMap.html)(); [createSimpleMap](https://nevware21.github.io/ts-utils/typedoc/functions/createSimpleMap.html)(); [createTypeMap](https://nevware21.github.io/ts-utils/typedoc/functions/createTypeMap.html)();
| 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)();
diff --git a/lib/src/array/arrIndexOfSubset.ts b/lib/src/array/arrIndexOfSubset.ts
new file mode 100644
index 00000000..c0982c23
--- /dev/null
+++ b/lib/src/array/arrIndexOfSubset.ts
@@ -0,0 +1,197 @@
+/*
+ * @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 { mathTrunc } from "../math/trunc";
+import { arrIndexOf, arrLastIndexOf } from "./indexOf";
+
+/**
+ * Normalizes a fromIndex argument the same way the native `indexOf()` / `lastIndexOf()` do -- an
+ * omitted (`undefined`) value falls back to defValue, while `null` and `NaN` both coerce to `0`
+ * (matching `ToInteger(null) === 0`), and any other finite value is truncated towards zero.
+ */
+function _normalizeFromIndex(value: number | null | undefined, defValue: number): number {
+ if (value === undefined) {
+ return defValue;
+ }
+
+ if (value === null || isNaN(value)) {
+ return 0;
+ }
+
+ return mathTrunc(value);
+}
+
+/**
+ * The arrIndexOfSubset() method returns the index within {@link theArray} at which {@link subset}
+ * first matches contiguously, comparing elements using strict equality (the same method used by the
+ * === or triple-equals operator).
+ *
+ * Unlike a simple `arrIndexOf()` of the first element of {@link subset}, this continues searching
+ * later occurrences of that first element until it finds one where the whole sequence lines up (or
+ * exhausts {@link theArray}), which avoids landing on the wrong occurrence when values repeat -- for
+ * example matching one stack trace against another when recursive calls repeat identical lines.
+ *
+ * If {@link theArray} has fewer remaining elements than {@link subset} from a candidate position, the
+ * comparison is limited to just those available elements -- so a {@link subset} that "runs off the end"
+ * of {@link theArray} is still considered a match against its available prefix. This makes it possible
+ * to match a shorter (eg. truncated) array against a longer one, or vice-versa.
+ * @function
+ * @since 0.17.0
+ * @group Array
+ * @group ArrayLike
+ * @typeParam T - Identifies the type of array elements
+ * @param theArray - The array or array-like object of elements to be searched.
+ * @param subset - The (ordered) array or array-like object of elements to locate within theArray.
+ * @param fromIndex - The index to start the search at. If the provided index value is a negative
+ * number, it is taken as the offset from the end of theArray. Default: 0 (search from the start).
+ * @returns The index within theArray at which subset fully matches (or matches theArray's available
+ * elements from that position), or -1 if no such position exists. Returns fromIndex (normalized to a
+ * valid index, or the length of theArray) if subset is empty.
+ * @example
+ * ```ts
+ * arrIndexOfSubset([1, 2, 3, 4, 5], [3, 4]); // 2
+ * arrIndexOfSubset([1, 2, 3, 4, 5], [4, 3]); // -1
+ * arrIndexOfSubset(["a", "b", "a", "b", "c"], ["b", "c"]); // 3
+ * arrIndexOfSubset([1, 2, 3], [3, 4, 5]); // 2 (matches the available "3")
+ * arrIndexOfSubset([1, 2, 3], []); // 0
+ *
+ * // Repeated values -- a plain indexOf() of the first element would land on index 0
+ * let haystack = ["at foo", "at bar", "at foo", "at bar", "at baz"];
+ * arrIndexOfSubset(haystack, ["at foo", "at bar", "at baz"]); // 2
+ * ```
+ */
+/*#__NO_SIDE_EFFECTS__*/
+export function arrIndexOfSubset(theArray: ArrayLike, subset: ArrayLike, fromIndex?: number): number {
+ if (!isArrayLike(theArray) || !isArrayLike(subset)) {
+ return -1;
+ }
+
+ let haystackLen = theArray.length;
+ let subsetLen = subset.length;
+
+ let searchFrom = _normalizeFromIndex(fromIndex, 0);
+ if (searchFrom < 0) {
+ searchFrom = haystackLen + searchFrom;
+ }
+
+ if (searchFrom < 0) {
+ searchFrom = 0;
+ }
+
+ if (subsetLen === 0) {
+ return searchFrom < haystackLen ? searchFrom : haystackLen;
+ }
+
+ while (searchFrom < haystackLen) {
+ let pos = arrIndexOf(theArray, subset[0], searchFrom);
+ if (pos === -1) {
+ return -1;
+ }
+
+ // Only compare as many elements as are actually available in theArray from pos
+ let matchLen = subsetLen < (haystackLen - pos) ? subsetLen : (haystackLen - pos);
+ let isMatch = true;
+ for (let lp = 1; isMatch && lp < matchLen; lp++) {
+ isMatch = theArray[pos + lp] === subset[lp];
+ }
+
+ if (isMatch) {
+ return pos;
+ }
+
+ searchFrom = pos + 1;
+ }
+
+ return -1;
+}
+
+/**
+ * The arrLastIndexOfSubset() method returns the last index within {@link theArray} at which
+ * {@link subset} matches contiguously, comparing elements using strict equality (the same method
+ * used by the === or triple-equals operator). It is the mirror of {@link arrIndexOfSubset}, searching
+ * backwards from {@link fromIndex} (or the end of theArray) towards the start.
+ *
+ * As with {@link arrIndexOfSubset}, if {@link theArray} has fewer remaining elements than
+ * {@link subset} from a candidate position, the comparison is limited to just those available
+ * elements -- so a {@link subset} that "runs off the end" of {@link theArray} is still considered a
+ * match against its available prefix.
+ * @function
+ * @since 0.17.0
+ * @group Array
+ * @group ArrayLike
+ * @typeParam T - Identifies the type of array elements
+ * @param theArray - The array or array-like object of elements to be searched.
+ * @param subset - The (ordered) array or array-like object of elements to locate within theArray.
+ * @param fromIndex - The index to start searching backwards from (the position at which subset's
+ * first element may occur). If the provided index value is a negative number, it is taken as the
+ * offset from the end of theArray. Default: theArray's length - 1 (search from the end).
+ * @returns The last index within theArray at which subset fully matches (or matches theArray's
+ * available elements from that position), or -1 if no such position exists. Returns fromIndex
+ * (normalized to a valid index, or theArray's length) if subset is empty.
+ * @example
+ * ```ts
+ * arrLastIndexOfSubset([1, 2, 3, 4, 5], [3, 4]); // 2
+ * arrLastIndexOfSubset([1, 2, 3, 4, 5], [4, 3]); // -1
+ * arrLastIndexOfSubset(["a", "b", "a", "b", "c"], ["a", "b"]); // 2 (the later occurrence)
+ * arrLastIndexOfSubset([1, 2, 3], [3, 4, 5]); // 2 (matches the available "3")
+ * arrLastIndexOfSubset([1, 2, 3], []); // 3
+ *
+ * // Repeated values -- returns the later occurrence, unlike arrIndexOfSubset()
+ * let haystack = ["at foo", "at bar", "at foo", "at bar", "at baz"];
+ * arrLastIndexOfSubset(haystack, ["at foo", "at bar"]); // 2
+ * ```
+ */
+/*#__NO_SIDE_EFFECTS__*/
+export function arrLastIndexOfSubset(theArray: ArrayLike, subset: ArrayLike, fromIndex?: number): number {
+ if (!isArrayLike(theArray) || !isArrayLike(subset)) {
+ return -1;
+ }
+
+ let haystackLen = theArray.length;
+ let subsetLen = subset.length;
+
+ let searchFrom = _normalizeFromIndex(fromIndex, haystackLen);
+ if (searchFrom < 0) {
+ searchFrom = haystackLen + searchFrom;
+ }
+
+ if (searchFrom > haystackLen) {
+ searchFrom = haystackLen;
+ }
+
+ if (subsetLen === 0) {
+ return searchFrom < 0 ? 0 : searchFrom;
+ }
+
+ if (searchFrom > haystackLen - 1) {
+ searchFrom = haystackLen - 1;
+ }
+
+ while (searchFrom >= 0) {
+ let pos = arrLastIndexOf(theArray, subset[0], searchFrom);
+ if (pos === -1) {
+ return -1;
+ }
+
+ // Only compare as many elements as are actually available in theArray from pos
+ let matchLen = subsetLen < (haystackLen - pos) ? subsetLen : (haystackLen - pos);
+ let isMatch = true;
+ for (let lp = 1; isMatch && lp < matchLen; lp++) {
+ isMatch = theArray[pos + lp] === subset[lp];
+ }
+
+ if (isMatch) {
+ return pos;
+ }
+
+ searchFrom = pos - 1;
+ }
+
+ return -1;
+}
diff --git a/lib/src/index.ts b/lib/src/index.ts
index d7d3a00d..f9917d0a 100644
--- a/lib/src/index.ts
+++ b/lib/src/index.ts
@@ -28,6 +28,7 @@ export { arrFrom } from "./array/from";
export { ArrGroupByCallbackFn, arrGroupBy } from "./array/groupBy";
export { arrContains, arrIncludes } from "./array/includes";
export { arrIndexOf, arrLastIndexOf } from "./array/indexOf";
+export { arrIndexOfSubset, arrLastIndexOfSubset } from "./array/arrIndexOfSubset";
export { arrIntersection } from "./array/intersection";
export { arrIndexKeys } from "./array/arrIndexKeys";
export { arrMap } from "./array/map";
diff --git a/lib/test/bundle-size-check.js b/lib/test/bundle-size-check.js
index 9289185a..f479393e 100644
--- a/lib/test/bundle-size-check.js
+++ b/lib/test/bundle-size-check.js
@@ -7,13 +7,13 @@ const configs = [
{
name: "es5-min-full",
path: "../bundle/es5/umd/ts-utils.min.js",
- limit: 40.5 * 1024, // 40.5 kb in bytes
+ limit: 41.5 * 1024, // 41.5 kb in bytes
compress: false
},
{
name: "es6-min-full",
path: "../bundle/es6/umd/ts-utils.min.js",
- limit: 39.5 * 1024, // 39.5 kb in bytes
+ limit: 40.5 * 1024, // 40.5 kb in bytes
compress: false
},
{
diff --git a/lib/test/src/common/array/arrIndexOfSubset.test.ts b/lib/test/src/common/array/arrIndexOfSubset.test.ts
new file mode 100644
index 00000000..3b9ea7ac
--- /dev/null
+++ b/lib/test/src/common/array/arrIndexOfSubset.test.ts
@@ -0,0 +1,175 @@
+/*
+ * @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 { arrIndexOfSubset, arrLastIndexOfSubset } from "../../../../src/array/arrIndexOfSubset";
+
+describe("arrIndexOfSubset", () => {
+
+ it("null / undefined", () => {
+ assert.equal(arrIndexOfSubset(null as any, [1]), -1);
+ assert.equal(arrIndexOfSubset(undefined as any, [1]), -1);
+ assert.equal(arrIndexOfSubset([1, 2], null as any), -1);
+ assert.equal(arrIndexOfSubset([1, 2], undefined as any), -1);
+ });
+
+ it("empty subset", () => {
+ assert.equal(arrIndexOfSubset([1, 2, 3], []), 0);
+ assert.equal(arrIndexOfSubset([1, 2, 3], [], 2), 2);
+ assert.equal(arrIndexOfSubset([1, 2, 3], [], 10), 3);
+ assert.equal(arrIndexOfSubset([], []), 0);
+ });
+
+ it("simple example", () => {
+ assert.equal(arrIndexOfSubset([1, 2, 3, 4, 5], [3, 4]), 2);
+ assert.equal(arrIndexOfSubset([1, 2, 3, 4, 5], [4, 3]), -1);
+ assert.equal(arrIndexOfSubset([1, 2, 3, 4, 5], [1, 2, 3, 4, 5]), 0);
+ assert.equal(arrIndexOfSubset([1, 2, 3, 4, 5], [5]), 4);
+ assert.equal(arrIndexOfSubset([1, 2, 3, 4, 5], [6]), -1);
+ // subset overruns theArray's end, so it matches against the available prefix
+ assert.equal(arrIndexOfSubset([1, 2, 3, 4, 5], [1, 2, 3, 4, 5, 6]), 0);
+ });
+
+ it("truncated subset matches available prefix", () => {
+ assert.equal(arrIndexOfSubset([1, 2, 3], [3, 4, 5]), 2);
+ assert.equal(arrIndexOfSubset([1, 2, 3], [2, 3, 99, 100]), 1);
+ });
+
+ it("fromIndex", () => {
+ const array = ["a", "b", "a", "b", "c"];
+ assert.equal(arrIndexOfSubset(array, ["a", "b"]), 0);
+ assert.equal(arrIndexOfSubset(array, ["a", "b"], 1), 2);
+ assert.equal(arrIndexOfSubset(array, ["a", "b"], -2), -1);
+ assert.equal(arrIndexOfSubset(array, ["b", "c"], -2), 3);
+ });
+
+ it("repeated leading value requires re-searching later occurrences", () => {
+ const haystack = ["at foo", "at bar", "at foo", "at bar", "at baz"];
+ assert.equal(arrIndexOfSubset(haystack, ["at foo", "at bar", "at baz"]), 2);
+ assert.equal(arrIndexOfSubset(haystack, ["at foo", "at qux"]), -1);
+ });
+
+ it("NaN / non-integer fromIndex", () => {
+ const array = [1, 2, 3, 4, 5];
+ assert.equal(arrIndexOfSubset(array, [3, 4], NaN), 2);
+ assert.equal(arrIndexOfSubset(array, [1, 2], NaN), 0);
+ assert.equal(arrIndexOfSubset(array, [3, 4], 1.9), 2);
+ assert.equal(arrIndexOfSubset(array, [3, 4], -1.9), -1);
+ assert.equal(arrIndexOfSubset(array, [], NaN), 0);
+ });
+
+ it("null fromIndex coerces to 0, unlike omitted (undefined) fromIndex", () => {
+ const array = [1, 2, 3, 4, 5];
+ assert.equal(arrIndexOfSubset(array, [1, 2], null as any), 0);
+ assert.equal(arrIndexOfSubset(array, [3, 4], null as any), 2);
+ assert.equal(arrIndexOfSubset(array, [], null as any), 0);
+ });
+
+ it("array like", () => {
+ let arrayLike = {
+ length: 4,
+ 0: "potato",
+ 1: "tomato",
+ 2: "chillies",
+ 3: "green-pepper"
+ };
+
+ assert.equal(arrIndexOfSubset(arrayLike, ["tomato", "chillies"]), 1);
+ assert.equal(arrIndexOfSubset(arrayLike, ["chillies", "tomato"]), -1);
+ });
+});
+
+describe("arrLastIndexOfSubset", () => {
+
+ it("null / undefined", () => {
+ assert.equal(arrLastIndexOfSubset(null as any, [1]), -1);
+ assert.equal(arrLastIndexOfSubset(undefined as any, [1]), -1);
+ assert.equal(arrLastIndexOfSubset([1, 2], null as any), -1);
+ assert.equal(arrLastIndexOfSubset([1, 2], undefined as any), -1);
+ });
+
+ it("empty subset", () => {
+ assert.equal(arrLastIndexOfSubset([1, 2, 3], []), 3);
+ assert.equal(arrLastIndexOfSubset([1, 2, 3], [], 2), 2);
+ assert.equal(arrLastIndexOfSubset([1, 2, 3], [], 10), 3);
+ assert.equal(arrLastIndexOfSubset([], []), 0);
+ });
+
+ it("simple example", () => {
+ assert.equal(arrLastIndexOfSubset([1, 2, 3, 4, 5], [3, 4]), 2);
+ assert.equal(arrLastIndexOfSubset([1, 2, 3, 4, 5], [4, 3]), -1);
+ assert.equal(arrLastIndexOfSubset([1, 2, 3, 4, 5], [1, 2, 3, 4, 5]), 0);
+ assert.equal(arrLastIndexOfSubset([1, 2, 3, 4, 5], [5]), 4);
+ assert.equal(arrLastIndexOfSubset([1, 2, 3, 4, 5], [6]), -1);
+ // subset overruns theArray's end, so it matches against the available prefix
+ assert.equal(arrLastIndexOfSubset([1, 2, 3, 4, 5], [1, 2, 3, 4, 5, 6]), 0);
+ });
+
+ it("truncated subset matches available prefix", () => {
+ assert.equal(arrLastIndexOfSubset([1, 2, 3], [3, 4, 5]), 2);
+ assert.equal(arrLastIndexOfSubset([1, 2, 3], [2, 3, 99, 100]), 1);
+ });
+
+ it("returns the later occurrence, unlike arrIndexOfSubset", () => {
+ const array = ["a", "b", "a", "b", "c"];
+ assert.equal(arrLastIndexOfSubset(array, ["a", "b"]), 2);
+ assert.equal(arrLastIndexOfSubset(array, ["b", "c"]), 3);
+
+ const haystack = ["at foo", "at bar", "at foo", "at bar", "at baz"];
+ assert.equal(arrLastIndexOfSubset(haystack, ["at foo", "at bar"]), 2);
+ assert.equal(arrLastIndexOfSubset(haystack, ["at foo", "at qux"]), -1);
+ });
+
+ it("fromIndex", () => {
+ const array = ["a", "b", "a", "b", "c"];
+ assert.equal(arrLastIndexOfSubset(array, ["a", "b"], null as any), 0);
+ assert.equal(arrLastIndexOfSubset(array, ["a", "b"], 1), 0);
+ assert.equal(arrLastIndexOfSubset(array, ["a", "b"], 4), 2);
+ assert.equal(arrLastIndexOfSubset(array, ["a", "b"], -2), 2);
+ assert.equal(arrLastIndexOfSubset(array, ["a", "b"], -4), 0);
+ });
+
+ it("array like", () => {
+ let arrayLike = {
+ length: 4,
+ 0: "potato",
+ 1: "tomato",
+ 2: "chillies",
+ 3: "green-pepper"
+ };
+
+ assert.equal(arrLastIndexOfSubset(arrayLike, ["tomato", "chillies"]), 1);
+ assert.equal(arrLastIndexOfSubset(arrayLike, ["chillies", "tomato"]), -1);
+ });
+
+ it("NaN / non-integer fromIndex", () => {
+ const array = [1, 2, 3, 4, 5];
+ // NaN fromIndex normalizes to 0 (matching native lastIndexOf), not the default (array length)
+ assert.equal(arrLastIndexOfSubset(array, [1, 2], NaN), 0);
+ assert.equal(arrLastIndexOfSubset(array, [3, 4], NaN), -1);
+ assert.equal(arrLastIndexOfSubset(array, [3, 4], 2.9), 2);
+ assert.equal(arrLastIndexOfSubset(array, [3, 4], -1.9), 2);
+ assert.equal(arrLastIndexOfSubset(array, [], NaN), 0);
+ });
+
+ it("null fromIndex coerces to 0, unlike omitted (undefined) fromIndex", () => {
+ const array = [1, 2, 3, 4, 5];
+ assert.equal(arrLastIndexOfSubset(array, [1, 2], null as any), 0);
+ assert.equal(arrLastIndexOfSubset(array, [3, 4], null as any), -1);
+ assert.equal(arrLastIndexOfSubset(array, [], null as any), 0);
+ });
+
+ it("null fromIndex searches from index 0, not from the end (regression)", () => {
+ // [3, 4] genuinely exists (at index 2), so this only returns -1 if null caused the
+ // search to start from index 0 rather than defaulting to theArray's length like an
+ // omitted fromIndex would.
+ const array = [1, 2, 3, 4, 5];
+ assert.equal(arrLastIndexOfSubset(array, [3, 4]), 2, "omitted fromIndex finds it from the end");
+ assert.equal(arrLastIndexOfSubset(array, [3, 4], null as any), -1, "null fromIndex only searches index 0");
+ });
+});