diff --git a/.husky/pre-commit b/.husky/pre-commit
index 009b3f8..859bd52 100644
--- a/.husky/pre-commit
+++ b/.husky/pre-commit
@@ -1 +1 @@
-pnpm lint
+pnpm lint:MA
diff --git a/.xo-config.json b/.xo-config.json
index 1a87094..bfe3926 100644
--- a/.xo-config.json
+++ b/.xo-config.json
@@ -1,6 +1,7 @@
{
"env": ["jest", "node"],
"rules": {
+ "no-await-in-loop": "off",
"import/extensions": "off",
"unicorn/prefer-module": "off",
"unicorn/no-array-for-each": "off",
diff --git a/README.md b/README.md
index f385ae2..926aeb9 100644
--- a/README.md
+++ b/README.md
@@ -1,57 +1,60 @@
# n-tuple-array
-Get a specified amount of items when iterating over a JavaScript array, instead of the single item that arrays provide per iteration, by default.
+Get a **`configurable`** amount of items when iterating over a JavaScript array, instead of a single item that arrays provide per iteration, by default.
## Motivation
-Imagine that you received a collection of coordinates (latitude and longitude), but they were sent
-as a flat array of values to speed up the data transfers.
+Imagine that you received a large collection of coordinates (latitude and longitude), but they were sent
+as a flat array of values to speed up the data transfer.
-`n-tuple-array` can help you get out the coordinates in pairs (their logical representation), such that you'd go **from**
+`n-tuple-array` can help you get out the coordinates in pairs (i.e their logical representation), such that you'd go
+
+**from**
```json
-// flatCoords
+// flat coordinates
["5.7225", "-9.6273", "2.68452", "-30.9501", ...]
```
**to**
```javascript
-// generate pairs by default
-const coordIterable = tuplesFromArray({ list: flatCoords });
+// the iterable will generate pairs by default
+const coordsIterable = tuplesFromArray({ list: flatCoords });
// using for..of, get pairs as ["5.7225", "-9.6273"] ...
-for (const pair of coordIterable) {
+for (const pair of coordsIterable) {
console.log(pair);
}
-// OR manipulate pairs with regular array functions
-const coordPairs = Array.from(coordIterable);
-console.log(Array.isArray(coordPairs)); // true
-// prints ["5.7225", "-9.6273"] ...
-coordPairs
+// OR manipulate pairs with regular array
+// functions like map, filter, forEach ...
+const coordsInPairs = Array.from(coordsIterable);
+console.log(Array.isArray(coordsInPairs)); // true
+coordsInPairs
.map(pair => {
+ // pair is ["5.7225", "-9.6273"] ...
return myTransform(pair);
})
.forEach((pair) => {
+ // pair is ["5.7225", "-9.6273"] ...
placeOnMap(pair);
});
```
### Some Real World Examples
-#### Wole Joko
+#### 1. Wole Joko
-I first tried my hands on this concept when [building wole-joko](https://github.com/chalu/wole-joko/blob/dev/src/js/utils.js#L57-L92), a _live coding task_ I was asked to do in an engineering manager interview :man_shrugging. It was a simulation of people entering an event hall to get seated, but only two could get in at a time. I later took some time to [give the project more life](https://wole-joko.netlify.app/)
+I first tried my hands on this concept when [fleshing out wole-joko](https://github.com/chalu/wole-joko/blob/dev/src/js/utils.js#L57-L92), which strated as a _live coding task_ I was asked to do in an engineering manager interview :man_shrugging
+It was a simulation of people entering an event hall to get seated, but **only two** could get in at a time - https://wole-joko.netlify.app/
-#### Execute max of X tasks in parallel
+#### 2. Execute max of `N` async tasks
-JS challenge by [@thdxr on X.com](https://twitter.com/thdxr)

-
> The below was adapted for more concise terminal output
-`n-tuple-array` solution. View [code here](https://github.com/chalu/n-tuple-array/blob/main/src/demo/demo-classic.ts#L6-L40)
+`n-tuple-array` solution. View [code here](https://github.com/chalu/n-tuple-array/blob/main/src/examples/classic.ts#L6-L40)

@@ -92,5 +95,5 @@ for (const quintet of quintetIterator) {
}
```
-See more examples in [src/demo](./src/demo/)
+See more examples in [src/examples](./src/examples/)
diff --git a/dist/cjs/index.js b/dist/cjs/index.js
index 927cbf0..8d4c23e 100644
--- a/dist/cjs/index.js
+++ b/dist/cjs/index.js
@@ -1,6 +1,10 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.tuplesFromArray = exports.InvalidInvocationParameterError = void 0;
+/**
+ * An exception than can be thrown if there is no input array, `maxItems` is <= 0 or is not
+ * a number, or `match` is not a function
+ */
class InvalidInvocationParameterError extends Error {
}
exports.InvalidInvocationParameterError = InvalidInvocationParameterError;
@@ -18,6 +22,33 @@ const validateParametersOrThrow = (list, maxItems, match) => {
throw new InvalidInvocationParameterError(message);
}
};
+/**
+ * Returns an iterable iterator that ouputs a configured
+ * list of items when iterating over a given array
+ *
+ * @typeParam T - Type of items the input list contains
+ *
+ * @param config - An object to indicate the input array `config.list`, and set the
+ * max size of items per interation `config.maxItems`. You can also optionally specify `config.match`
+ * as a function that should return true to filter in items from the input array
+ * (or false to filter them out) when deciding what items is to be included per iteration
+ *
+ * @function
+ * @throws InvalidInvocationParameterError
+ * This exception is thrown if there is no input array, `maxItems` is <= 0 or is not
+ * a number, or `match` is not a function
+ *
+ * @returns an IterableIterator
+ *
+ * @example
+ * Here is an example that will get max of 3 items from
+ * each iteration on the returned iterable
+ * ```javascript
+ * const iterable = tuplesFromArray({
+ * list:[], maxSize: 3, match: (itm) => !!itm
+ * });
+ * ```
+ */
const tuplesFromArray = (config) => {
const { list, match, maxItems = 2 } = config;
validateParametersOrThrow(list, maxItems, match);
@@ -50,13 +81,13 @@ const tuplesFromArray = (config) => {
return { value: items, done: items.length === 0 };
};
const iterable = {
+ next: proceedNext,
[Symbol.iterator]() {
- return {
- next: proceedNext,
- };
+ return this;
},
};
return iterable;
};
exports.tuplesFromArray = tuplesFromArray;
exports.default = exports.tuplesFromArray;
+//# sourceMappingURL=index.js.map
\ No newline at end of file
diff --git a/dist/cjs/index.js.map b/dist/cjs/index.js.map
new file mode 100644
index 0000000..402b09c
--- /dev/null
+++ b/dist/cjs/index.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;AAuBA;;;GAGG;AACH,MAAa,+BAAgC,SAAQ,KAAK;CAAG;AAA7D,0EAA6D;AAE7D,MAAM,yBAAyB,GAAG,CAAI,IAAS,EAAE,QAAgB,EAAE,KAA6B,EAAE,EAAE;IACnG,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,+BAA+B,CAAC,8BAA8B,CAAC,CAAC;IAC3E,CAAC;IAED,IACC,OAAO,QAAQ,KAAK,QAAQ;WACnB,CAAC,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,IAAI,CAAC,CAAC,EACvD,CAAC;QACF,MAAM,OAAO,GAAG,0EAA0E,CAAC;QAC3F,MAAM,IAAI,+BAA+B,CAAC,OAAO,CAAC,CAAC;IACpD,CAAC;IAED,IAAI,KAAK,KAAK,SAAS,IAAI,OAAO,KAAK,KAAK,UAAU,EAAE,CAAC;QACxD,MAAM,OAAO,GAAG,iDAAiD,CAAC;QAClE,MAAM,IAAI,+BAA+B,CAAC,OAAO,CAAC,CAAC;IACpD,CAAC;AACF,CAAC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACI,MAAM,eAAe,GAAG,CAAI,MAAsB,EAAE,EAAE;IAC5D,MAAM,EAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,GAAG,CAAC,EAAC,GAAG,MAAM,CAAC;IAC3C,yBAAyB,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;IAEjD,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,MAAM,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC;IAEvD,MAAM,WAAW,GAAG,GAAc,EAAE;QACnC,MAAM,KAAK,GAAyB,EAAE,CAAC;QAEvC,IAAI,MAAM,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAC3B,OAAO,EAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAC,CAAC;QAChC,CAAC;QAED,MAAM,QAAQ,GAAG,KAAK,KAAK,SAAS;YACnC,yDAAyD;YACzD,8CAA8C;YAC9C,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC;YAE7C,4DAA4D;YAC5D,4DAA4D;YAC5D,uBAAuB;YACvB,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC;QAEf,OAAO,MAAM,GAAG,QAAQ,EAAE,CAAC;YAC1B,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;YAC1B,MAAM,IAAI,CAAC,CAAC;YAEZ,IAAI,KAAK,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC3B,SAAS;YACV,CAAC;YAED,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAEjB,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;gBAC3C,MAAM;YACP,CAAC;QACF,CAAC;QAED,OAAO,EAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,MAAM,KAAK,CAAC,EAAC,CAAC;IACjD,CAAC,CAAC;IAEF,MAAM,QAAQ,GAA2C;QACxD,IAAI,EAAE,WAAW;QAEjB,CAAC,MAAM,CAAC,QAAQ,CAAC;YAChB,OAAO,IAAI,CAAC;QACb,CAAC;KACD,CAAC;IAEF,OAAO,QAAQ,CAAC;AACjB,CAAC,CAAC;AAnDW,QAAA,eAAe,mBAmD1B;AAEF,kBAAe,uBAAe,CAAC"}
\ No newline at end of file
diff --git a/dist/cjs/package.json b/dist/cjs/package.json
new file mode 100644
index 0000000..1cd945a
--- /dev/null
+++ b/dist/cjs/package.json
@@ -0,0 +1,3 @@
+{
+ "type": "commonjs"
+}
diff --git a/dist/cjs/types/index.d.ts b/dist/cjs/types/index.d.ts
index cb5aa72..cbe5290 100644
--- a/dist/cjs/types/index.d.ts
+++ b/dist/cjs/types/index.d.ts
@@ -1,13 +1,53 @@
-type Item = T | undefined;
-type Value = Array- >;
export type Matcher = (item: T | unknown) => boolean;
export type TupleConfig = {
+ /**
+ * The input array to use
+ */
list: T[];
+ /**
+ * The max number of items to return from the input array, per iteration
+ * @defaultValue 2
+ */
maxItems?: number;
+ /**
+ * When provided, a function used to determine which items in the input array
+ * are eligible to be included, per iteration
+ */
match?: Matcher;
};
+/**
+ * An exception than can be thrown if there is no input array, `maxItems` is <= 0 or is not
+ * a number, or `match` is not a function
+ */
export declare class InvalidInvocationParameterError extends Error {
}
-export declare const tuplesFromArray: (config: TupleConfig) => Iterable>;
+/**
+ * Returns an iterable iterator that ouputs a configured
+ * list of items when iterating over a given array
+ *
+ * @typeParam T - Type of items the input list contains
+ *
+ * @param config - An object to indicate the input array `config.list`, and set the
+ * max size of items per interation `config.maxItems`. You can also optionally specify `config.match`
+ * as a function that should return true to filter in items from the input array
+ * (or false to filter them out) when deciding what items is to be included per iteration
+ *
+ * @function
+ * @throws InvalidInvocationParameterError
+ * This exception is thrown if there is no input array, `maxItems` is <= 0 or is not
+ * a number, or `match` is not a function
+ *
+ * @returns an IterableIterator
+ *
+ * @example
+ * Here is an example that will get max of 3 items from
+ * each iteration on the returned iterable
+ * ```javascript
+ * const iterable = tuplesFromArray({
+ * list:[], maxSize: 3, match: (itm) => !!itm
+ * });
+ * ```
+ */
+export declare const tuplesFromArray: (config: TupleConfig) => IterableIterator<(T | undefined)[]>;
export default tuplesFromArray;
//# sourceMappingURL=index.d.ts.map
\ No newline at end of file
diff --git a/dist/cjs/types/index.d.ts.map b/dist/cjs/types/index.d.ts.map
index 8d87987..cb8e02a 100644
--- a/dist/cjs/types/index.d.ts.map
+++ b/dist/cjs/types/index.d.ts.map
@@ -1 +1 @@
-{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,KAAK,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC;AAC7B,KAAK,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAG/B,MAAM,MAAM,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,OAAO,KAAK,OAAO,CAAC;AACxD,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI;IAC5B,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;CACnB,CAAC;AAEF,qBAAa,+BAAgC,SAAQ,KAAK;CAAG;AAqB7D,eAAO,MAAM,eAAe,mDAmD3B,CAAC;AAEF,eAAe,eAAe,CAAC"}
\ No newline at end of file
+{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,OAAO,KAAK,OAAO,CAAC;AAExD,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI;IAC5B;;OAEG;IACH,IAAI,EAAE,CAAC,EAAE,CAAC;IAEV;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;CACnB,CAAC;AAEF;;;GAGG;AACH,qBAAa,+BAAgC,SAAQ,KAAK;CAAG;AAqB7D;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,eAAe,oEAmD3B,CAAC;AAEF,eAAe,eAAe,CAAC"}
\ No newline at end of file
diff --git a/dist/esm/index.js.map b/dist/esm/index.js.map
new file mode 100644
index 0000000..c284d3b
--- /dev/null
+++ b/dist/esm/index.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAuBA;;;GAGG;AACH,MAAM,OAAO,+BAAgC,SAAQ,KAAK;CAAG;AAE7D,MAAM,yBAAyB,GAAG,CAAI,IAAS,EAAE,QAAgB,EAAE,KAA6B,EAAE,EAAE;IACnG,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,+BAA+B,CAAC,8BAA8B,CAAC,CAAC;IAC3E,CAAC;IAED,IACC,OAAO,QAAQ,KAAK,QAAQ;WACnB,CAAC,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,IAAI,CAAC,CAAC,EACvD,CAAC;QACF,MAAM,OAAO,GAAG,0EAA0E,CAAC;QAC3F,MAAM,IAAI,+BAA+B,CAAC,OAAO,CAAC,CAAC;IACpD,CAAC;IAED,IAAI,KAAK,KAAK,SAAS,IAAI,OAAO,KAAK,KAAK,UAAU,EAAE,CAAC;QACxD,MAAM,OAAO,GAAG,iDAAiD,CAAC;QAClE,MAAM,IAAI,+BAA+B,CAAC,OAAO,CAAC,CAAC;IACpD,CAAC;AACF,CAAC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAI,MAAsB,EAAE,EAAE;IAC5D,MAAM,EAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,GAAG,CAAC,EAAC,GAAG,MAAM,CAAC;IAC3C,yBAAyB,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;IAEjD,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,MAAM,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC;IAEvD,MAAM,WAAW,GAAG,GAAc,EAAE;QACnC,MAAM,KAAK,GAAyB,EAAE,CAAC;QAEvC,IAAI,MAAM,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAC3B,OAAO,EAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAC,CAAC;QAChC,CAAC;QAED,MAAM,QAAQ,GAAG,KAAK,KAAK,SAAS;YACnC,yDAAyD;YACzD,8CAA8C;YAC9C,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC;YAE7C,4DAA4D;YAC5D,4DAA4D;YAC5D,uBAAuB;YACvB,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC;QAEf,OAAO,MAAM,GAAG,QAAQ,EAAE,CAAC;YAC1B,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;YAC1B,MAAM,IAAI,CAAC,CAAC;YAEZ,IAAI,KAAK,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC3B,SAAS;YACV,CAAC;YAED,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAEjB,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;gBAC3C,MAAM;YACP,CAAC;QACF,CAAC;QAED,OAAO,EAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,MAAM,KAAK,CAAC,EAAC,CAAC;IACjD,CAAC,CAAC;IAEF,MAAM,QAAQ,GAA2C;QACxD,IAAI,EAAE,WAAW;QAEjB,CAAC,MAAM,CAAC,QAAQ,CAAC;YAChB,OAAO,IAAI,CAAC;QACb,CAAC;KACD,CAAC;IAEF,OAAO,QAAQ,CAAC;AACjB,CAAC,CAAC;AAEF,eAAe,eAAe,CAAC"}
\ No newline at end of file
diff --git a/dist/esm/index.mjs b/dist/esm/index.mjs
index 927cbf0..5bd2ab4 100644
--- a/dist/esm/index.mjs
+++ b/dist/esm/index.mjs
@@ -1,9 +1,9 @@
-"use strict";
-Object.defineProperty(exports, "__esModule", { value: true });
-exports.tuplesFromArray = exports.InvalidInvocationParameterError = void 0;
-class InvalidInvocationParameterError extends Error {
+/**
+ * An exception than can be thrown if there is no input array, `maxItems` is <= 0 or is not
+ * a number, or `match` is not a function
+ */
+export class InvalidInvocationParameterError extends Error {
}
-exports.InvalidInvocationParameterError = InvalidInvocationParameterError;
const validateParametersOrThrow = (list, maxItems, match) => {
if (!list || !Array.isArray(list)) {
throw new InvalidInvocationParameterError('expected list to be an array');
@@ -18,7 +18,34 @@ const validateParametersOrThrow = (list, maxItems, match) => {
throw new InvalidInvocationParameterError(message);
}
};
-const tuplesFromArray = (config) => {
+/**
+ * Returns an iterable iterator that ouputs a configured
+ * list of items when iterating over a given array
+ *
+ * @typeParam T - Type of items the input list contains
+ *
+ * @param config - An object to indicate the input array `config.list`, and set the
+ * max size of items per interation `config.maxItems`. You can also optionally specify `config.match`
+ * as a function that should return true to filter in items from the input array
+ * (or false to filter them out) when deciding what items is to be included per iteration
+ *
+ * @function
+ * @throws InvalidInvocationParameterError
+ * This exception is thrown if there is no input array, `maxItems` is <= 0 or is not
+ * a number, or `match` is not a function
+ *
+ * @returns an IterableIterator
+ *
+ * @example
+ * Here is an example that will get max of 3 items from
+ * each iteration on the returned iterable
+ * ```javascript
+ * const iterable = tuplesFromArray({
+ * list:[], maxSize: 3, match: (itm) => !!itm
+ * });
+ * ```
+ */
+export const tuplesFromArray = (config) => {
const { list, match, maxItems = 2 } = config;
validateParametersOrThrow(list, maxItems, match);
let cursor = 0;
@@ -50,13 +77,12 @@ const tuplesFromArray = (config) => {
return { value: items, done: items.length === 0 };
};
const iterable = {
+ next: proceedNext,
[Symbol.iterator]() {
- return {
- next: proceedNext,
- };
+ return this;
},
};
return iterable;
};
-exports.tuplesFromArray = tuplesFromArray;
-exports.default = exports.tuplesFromArray;
+export default tuplesFromArray;
+//# sourceMappingURL=index.js.map
\ No newline at end of file
diff --git a/dist/esm/package.json b/dist/esm/package.json
new file mode 100644
index 0000000..4720025
--- /dev/null
+++ b/dist/esm/package.json
@@ -0,0 +1,3 @@
+{
+ "type": "module"
+}
diff --git a/dist/esm/types/index.d.ts b/dist/esm/types/index.d.ts
index cb5aa72..cbe5290 100644
--- a/dist/esm/types/index.d.ts
+++ b/dist/esm/types/index.d.ts
@@ -1,13 +1,53 @@
-type Item = T | undefined;
-type Value = Array
- >;
export type Matcher = (item: T | unknown) => boolean;
export type TupleConfig = {
+ /**
+ * The input array to use
+ */
list: T[];
+ /**
+ * The max number of items to return from the input array, per iteration
+ * @defaultValue 2
+ */
maxItems?: number;
+ /**
+ * When provided, a function used to determine which items in the input array
+ * are eligible to be included, per iteration
+ */
match?: Matcher;
};
+/**
+ * An exception than can be thrown if there is no input array, `maxItems` is <= 0 or is not
+ * a number, or `match` is not a function
+ */
export declare class InvalidInvocationParameterError extends Error {
}
-export declare const tuplesFromArray: (config: TupleConfig) => Iterable>;
+/**
+ * Returns an iterable iterator that ouputs a configured
+ * list of items when iterating over a given array
+ *
+ * @typeParam T - Type of items the input list contains
+ *
+ * @param config - An object to indicate the input array `config.list`, and set the
+ * max size of items per interation `config.maxItems`. You can also optionally specify `config.match`
+ * as a function that should return true to filter in items from the input array
+ * (or false to filter them out) when deciding what items is to be included per iteration
+ *
+ * @function
+ * @throws InvalidInvocationParameterError
+ * This exception is thrown if there is no input array, `maxItems` is <= 0 or is not
+ * a number, or `match` is not a function
+ *
+ * @returns an IterableIterator
+ *
+ * @example
+ * Here is an example that will get max of 3 items from
+ * each iteration on the returned iterable
+ * ```javascript
+ * const iterable = tuplesFromArray({
+ * list:[], maxSize: 3, match: (itm) => !!itm
+ * });
+ * ```
+ */
+export declare const tuplesFromArray: (config: TupleConfig) => IterableIterator<(T | undefined)[]>;
export default tuplesFromArray;
//# sourceMappingURL=index.d.ts.map
\ No newline at end of file
diff --git a/dist/esm/types/index.d.ts.map b/dist/esm/types/index.d.ts.map
index 8d87987..cb8e02a 100644
--- a/dist/esm/types/index.d.ts.map
+++ b/dist/esm/types/index.d.ts.map
@@ -1 +1 @@
-{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,KAAK,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC;AAC7B,KAAK,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAG/B,MAAM,MAAM,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,OAAO,KAAK,OAAO,CAAC;AACxD,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI;IAC5B,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;CACnB,CAAC;AAEF,qBAAa,+BAAgC,SAAQ,KAAK;CAAG;AAqB7D,eAAO,MAAM,eAAe,mDAmD3B,CAAC;AAEF,eAAe,eAAe,CAAC"}
\ No newline at end of file
+{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,OAAO,KAAK,OAAO,CAAC;AAExD,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI;IAC5B;;OAEG;IACH,IAAI,EAAE,CAAC,EAAE,CAAC;IAEV;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;CACnB,CAAC;AAEF;;;GAGG;AACH,qBAAa,+BAAgC,SAAQ,KAAK;CAAG;AAqB7D;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,eAAe,oEAmD3B,CAAC;AAEF,eAAe,eAAe,CAAC"}
\ No newline at end of file
diff --git a/fix-pkgs b/fix-pkgs
new file mode 100755
index 0000000..e1f4413
--- /dev/null
+++ b/fix-pkgs
@@ -0,0 +1,11 @@
+cat >dist/cjs/package.json <dist/esm/package.json < i + 1);
+const isEven = item => {
+ if (
+ !item
+ || typeof item !== 'number'
+ || item % 2 !== 0
+ ) {
+ return false;
+ }
+
+ return true;
+};
+
+// Use the lib
+const quintetIterator = tuplesFromArray({
+ list: numbers, maxItems: 5, match: isEven,
+});
+
+for (const quintet of quintetIterator) {
+ // Prints [ 2, 4, 6, 8, 10 ] ... [ 92, 94, 96, 98, 100 ]
+ console.log(quintet);
+}
diff --git a/src/examples/js/basic.mjs b/src/examples/js/basic.mjs
new file mode 100644
index 0000000..72d27df
--- /dev/null
+++ b/src/examples/js/basic.mjs
@@ -0,0 +1,25 @@
+import tuplesFromArray from '../../../dist/esm/index.mjs';
+
+// Some setup
+const numbers = Array.from({length: 100}, (_, i) => i + 1);
+const isEven = item => {
+ if (
+ !item
+ || typeof item !== 'number'
+ || item % 2 !== 0
+ ) {
+ return false;
+ }
+
+ return true;
+};
+
+// Use the lib
+const quintetIterator = tuplesFromArray({
+ list: numbers, maxItems: 5, match: isEven,
+});
+
+for (const quintet of quintetIterator) {
+ // Prints [ 2, 4, 6, 8, 10 ] ... [ 92, 94, 96, 98, 100 ]
+ console.log(quintet);
+}
diff --git a/src/examples/js/concurrent.js b/src/examples/js/concurrent.js
new file mode 100644
index 0000000..0ad348e
--- /dev/null
+++ b/src/examples/js/concurrent.js
@@ -0,0 +1,39 @@
+const indexToDate = (index = 0) => batchId => new Promise(resolve => {
+ console.log(`[Batch ${batchId}] task ${index + 1} starting`);
+ const delay = Math.random() * 3000;
+ const dt = new Date(`2024-07-${(index + 1) % 30}`);
+ setTimeout(() => {
+ console.log(`[Batch ${batchId}] task ${index + 1} completed in ${delay.toFixed(2)} ms`);
+ resolve(dt);
+ }, delay);
+});
+
+const processTask = async (todo, batchId) => {
+ const out = await todo(batchId);
+ return out;
+};
+
+const executeConcurrent = async (todos, maxBatchSize) => {
+ const done = [];
+ const todosCopy = todos.slice();
+ const worker = async (_, batchIndex) => {
+ let todo = todosCopy.shift();
+ while (todo) {
+ const [result] = await Promise.allSettled([processTask(todo, batchIndex + 1)]);
+ done.push(result);
+ todo = todosCopy.shift();
+ }
+ };
+
+ const batchStarters = Array.from({length: maxBatchSize}, worker);
+ await Promise.all(batchStarters);
+ return done;
+};
+
+(async () => {
+ const tasks = Array.from({length: 5}, (_, i) => indexToDate(i));
+ const maxTasksPerTime = 2;
+
+ const allDone = await executeConcurrent(tasks, maxTasksPerTime);
+ console.log(allDone);
+})();
diff --git a/src/examples/js/with-lib.mjs b/src/examples/js/with-lib.mjs
new file mode 100644
index 0000000..02a5c6f
--- /dev/null
+++ b/src/examples/js/with-lib.mjs
@@ -0,0 +1,43 @@
+import tuplesFromArray from '../../../dist/esm/index.mjs';
+
+const indexToDate = (index = 0) => batchId => new Promise(resolve => {
+ console.log(`[Batch ${batchId}] task ${index + 1} starting`);
+ const delay = Math.random() * 3000;
+ const dt = new Date(`2024-07-${(index + 1) % 30}`);
+ setTimeout(() => {
+ console.log(`[Batch ${batchId}] task ${index + 1} completed in ${delay.toFixed(2)} ms`);
+ resolve(dt);
+ }, delay);
+});
+
+const processTask = async (todo, batchId) => {
+ const out = await todo(batchId);
+ return out;
+};
+
+const executeWitLibrary = async (todos, maxBatchSize) => {
+ console.log(`do <= ${maxBatchSize} of ${todos.length} tasks, at any given time`);
+ const done = [];
+
+ let batchIndex = 1;
+ const todosBatchIterable = tuplesFromArray({list: todos, maxItems: maxBatchSize});
+ for (const batch of todosBatchIterable) {
+ console.log(`----- starting batch [${batchIndex}], ${batch.length} todos`);
+ const results = await Promise.allSettled(
+ batch.map(todo => processTask(todo, batchIndex)),
+ );
+ done.push(...results);
+
+ batchIndex += 1;
+ }
+
+ return done;
+};
+
+(async () => {
+ const tasks = Array.from({length: 5}, (_, i) => indexToDate(i));
+ const maxTasksPerTime = 2;
+
+ const allDone = await executeWitLibrary(tasks, maxTasksPerTime);
+ console.log(allDone);
+})();
diff --git a/src/examples/js/without-lib.js b/src/examples/js/without-lib.js
new file mode 100644
index 0000000..e9803b7
--- /dev/null
+++ b/src/examples/js/without-lib.js
@@ -0,0 +1,52 @@
+const indexToDate = (index = 0) => batchId => new Promise(resolve => {
+ console.log(`[Batch ${batchId}] task ${index + 1} starting`);
+ const delay = Math.random() * 3000;
+ const dt = new Date(`2024-07-${(index + 1) % 30}`);
+ setTimeout(() => {
+ console.log(`[Batch ${batchId}] task ${index + 1} completed in ${delay.toFixed(2)} ms`);
+ resolve(dt);
+ }, delay);
+});
+
+const processTask = async (todo, batchId) => {
+ const out = await todo(batchId);
+ return out;
+};
+
+// Everywhere start/end is used is repititive and error prone
+const executeWithoutLibrary = async (todos, maxBatchSize) => {
+ console.log(`do <= ${maxBatchSize} of ${todos.length} tasks, at any given time`);
+ const done = [];
+
+ let start = 0;
+ let end = start + maxBatchSize;
+ let batchIndex = 1;
+ let batch = todos.slice(start, end);
+ while (batch.length > 0) {
+ console.log(`----- starting batch [${batchIndex}], ${batch.length} todos`);
+ const results = await Promise.allSettled(
+ batch.map(todo => processTask(todo, batchIndex)),
+ );
+ done.push(...results);
+
+ start = end;
+ if (start >= todos.length) {
+ break;
+ }
+
+ end = Math.min(start + maxBatchSize, todos.length);
+ batch = todos.slice(start, end);
+
+ batchIndex += 1;
+ }
+
+ return done;
+};
+
+(async () => {
+ const tasks = Array.from({length: 5}, (_, i) => indexToDate(i));
+ const maxTasksPerTime = 2;
+
+ const allDone = await executeWithoutLibrary(tasks, maxTasksPerTime);
+ console.log(allDone);
+})();
diff --git a/src/demo/demo-basics.ts b/src/examples/ts/basic.ts
similarity index 95%
rename from src/demo/demo-basics.ts
rename to src/examples/ts/basic.ts
index e0c13df..68fb5e9 100644
--- a/src/demo/demo-basics.ts
+++ b/src/examples/ts/basic.ts
@@ -1,7 +1,7 @@
-import {tuplesFromArray} from '../index';
+import {tuplesFromArray} from '../../index';
import {
hexadecimals, isDate, dates, uuids,
-} from './demo-utils';
+} from './utils';
console.log('----- Example 1 [10 Hex in twos. Used default param values] ------');
for (const hexPair of tuplesFromArray({list: hexadecimals(10)})) {
diff --git a/src/demo/demo-classic.ts b/src/examples/ts/classic.ts
similarity index 94%
rename from src/demo/demo-classic.ts
rename to src/examples/ts/classic.ts
index e191584..922f8f2 100644
--- a/src/demo/demo-classic.ts
+++ b/src/examples/ts/classic.ts
@@ -1,7 +1,7 @@
-import {tuplesFromArray} from '../index';
+import {tuplesFromArray} from '../../index';
import {
isDate, dates, canadaDateFormat, delay, formatCount,
-} from './demo-utils';
+} from './utils';
const processItem = async (item: T | undefined) => {
await delay(Math.random() * 500); // Simulate some async workload
diff --git a/src/demo/demo-utils.ts b/src/examples/ts/utils.ts
similarity index 100%
rename from src/demo/demo-utils.ts
rename to src/examples/ts/utils.ts
diff --git a/src/index.ts b/src/index.ts
index 6adea2f..ea90e85 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,14 +1,30 @@
-type Item = T | undefined;
-type Value = Array
- >;
-type Result = IteratorResult, Value>;
+type Result = IteratorResult, Array>;
export type Matcher = (item: T | unknown) => boolean;
+
export type TupleConfig = {
+ /**
+ * The input array to use
+ */
list: T[];
+
+ /**
+ * The max number of items to return from the input array, per iteration
+ * @defaultValue 2
+ */
maxItems?: number;
+
+ /**
+ * When provided, a function used to determine which items in the input array
+ * are eligible to be included, per iteration
+ */
match?: Matcher;
};
+/**
+ * An exception than can be thrown if there is no input array, `maxItems` is <= 0 or is not
+ * a number, or `match` is not a function
+ */
export class InvalidInvocationParameterError extends Error {}
const validateParametersOrThrow = (list: T[], maxItems: number, match: Matcher | undefined) => {
@@ -30,6 +46,33 @@ const validateParametersOrThrow = (list: T[], maxItems: number, match: Matche
}
};
+/**
+ * Returns an iterable iterator that ouputs a configured
+ * list of items when iterating over a given array
+ *
+ * @typeParam T - Type of items the input list contains
+ *
+ * @param config - An object to indicate the input array `config.list`, and set the
+ * max size of items per interation `config.maxItems`. You can also optionally specify `config.match`
+ * as a function that should return true to filter in items from the input array
+ * (or false to filter them out) when deciding what items is to be included per iteration
+ *
+ * @function
+ * @throws InvalidInvocationParameterError
+ * This exception is thrown if there is no input array, `maxItems` is <= 0 or is not
+ * a number, or `match` is not a function
+ *
+ * @returns an IterableIterator
+ *
+ * @example
+ * Here is an example that will get max of 3 items from
+ * each iteration on the returned iterable
+ * ```javascript
+ * const iterable = tuplesFromArray({
+ * list:[], maxSize: 3, match: (itm) => !!itm
+ * });
+ * ```
+ */
export const tuplesFromArray = (config: TupleConfig) => {
const {list, match, maxItems = 2} = config;
validateParametersOrThrow(list, maxItems, match);
@@ -38,7 +81,7 @@ export const tuplesFromArray = (config: TupleConfig) => {
const maxItemSize = Number.parseInt(`${maxItems}`, 10);
const proceedNext = (): Result => {
- const items: Value = [];
+ const items: Array = [];
if (cursor >= list.length) {
return {done: true, value: []};
@@ -72,11 +115,11 @@ export const tuplesFromArray = (config: TupleConfig) => {
return {value: items, done: items.length === 0};
};
- const iterable: Iterable> = {
- [Symbol.iterator](): Iterator> {
- return {
- next: proceedNext,
- };
+ const iterable: IterableIterator> = {
+ next: proceedNext,
+
+ [Symbol.iterator](): IterableIterator> {
+ return this;
},
};
diff --git a/tsconfig.base.json b/tsconfig.base.json
index 211db10..18ddf57 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -1,21 +1,35 @@
{
"compilerOptions": {
"strict": true,
- "rootDir": "./src",
- "esModuleInterop": true,
- "forceConsistentCasingInFileNames": true,
- "skipLibCheck": true,
+ "pretty": true,
"checkJs": true,
"allowJs": true,
+ "rootDir": "./src",
+ "skipLibCheck": true,
"declaration": true,
+ "esModuleInterop": true,
"declarationMap": true,
+ "sourceMap": true,
+ "inlineSourceMap": false,
+ "resolveJsonModule": true,
+ "moduleResolution": "Node",
+ "moduleDetection": "force",
"noUncheckedIndexedAccess": true,
- "allowSyntheticDefaultImports": true
+ "allowSyntheticDefaultImports": true,
+ "forceConsistentCasingInFileNames": true,
+ "noFallthroughCasesInSwitch": true,
+ "lib": [
+ "ES2022",
+ "DOM",
+ "DOM.Iterable"
+ ]
},
"include": [
"src/**/*"
],
"exclude": [
- "src/demo/**"
+ "src/examples/**",
+ "node_modules",
+ "dist"
]
}
\ No newline at end of file
diff --git a/tsconfig.cjs.json b/tsconfig.cjs.json
index 5d35037..5801811 100644
--- a/tsconfig.cjs.json
+++ b/tsconfig.cjs.json
@@ -1,13 +1,8 @@
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
- "lib": [
- "ES2022",
- "DOM"
- ],
"target": "ES2022",
"module": "CommonJS",
- "moduleResolution": "Node",
"outDir": "dist/cjs",
"declarationDir": "dist/cjs/types"
}
diff --git a/tsconfig.esm.json b/tsconfig.esm.json
index 8beeb6a..bed57b6 100644
--- a/tsconfig.esm.json
+++ b/tsconfig.esm.json
@@ -1,13 +1,8 @@
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
- "lib": [
- "ES2022",
- "DOM"
- ],
- "target": "ES2022",
- "module": "NodeNext",
- "moduleResolution": "NodeNext",
+ "target": "ESNext",
+ "module": "ESNext",
"outDir": "dist/esm",
"declarationDir": "dist/esm/types"
}