@reliverse/mapkit 2.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/mod.d.ts +86 -0
  2. package/dist/mod.js +251 -0
  3. package/package.json +21 -0
package/dist/mod.d.ts ADDED
@@ -0,0 +1,86 @@
1
+ type BaseOptions = {
2
+ /**
3
+ * Number of concurrently pending promises returned by `mapkit`.
4
+ *
5
+ * Must be an integer from 1 and up or `Infinity`.
6
+ *
7
+ * @default Infinity
8
+ */
9
+ readonly concurrency?: number;
10
+ };
11
+ export type Options = BaseOptions & {
12
+ /**
13
+ * When `true`, the first mapkit rejection will be rejected back to the consumer.
14
+ *
15
+ * When `false`, instead of stopping when a promise rejects, it will wait for all
16
+ * the promises to settle and then reject with an `AggregateError` containing all
17
+ * the errors from the rejected promises.
18
+ *
19
+ * Caveat: When `true`, any already-started async mapkits will continue to run
20
+ * until they resolve or reject. In the case of infinite concurrency with sync
21
+ * iterables, all mapkits are invoked on startup and will continue after the first
22
+ * rejection. AbortControl can be used to stop pulling new items.
23
+ *
24
+ * @default true
25
+ */
26
+ readonly stopOnError?: boolean;
27
+ /**
28
+ * You can abort the promises using `AbortController`.
29
+ *
30
+ * Rejects with the `signal.reason` immediately, stops pulling new items, and
31
+ * attempts to close the underlying iterator via `.return()` if available.
32
+ */
33
+ readonly signal?: AbortSignal;
34
+ };
35
+ export type IterableOptions = BaseOptions & {
36
+ /**
37
+ * Maximum number of promises returned by `mapkit` that have resolved but not yet
38
+ * collected by the consumer of the async iterable. Calls to `mapkit` will be
39
+ * limited so that there is never too much backpressure.
40
+ *
41
+ * Useful whenever you are consuming the iterable slower than what the mapkit
42
+ * function can produce concurrently.
43
+ *
44
+ * Default: `options.concurrency`
45
+ */
46
+ readonly backpressure?: number;
47
+ };
48
+ type MaybePromise<T> = T | Promise<T>;
49
+ /**
50
+ * Function which is called for every item in `input`. Expected to return a
51
+ * `Promise` or value.
52
+ *
53
+ * @param element - Iterated element.
54
+ * @param index - Index of the element in the source array.
55
+ */
56
+ export type mapkit<Element = unknown, NewElement = unknown> = (element: Element, index: number) => MaybePromise<NewElement | typeof pMapSkip>;
57
+ /**
58
+ * Return this value from a `mapkit` function to skip including the value in the
59
+ * returned array.
60
+ */
61
+ export declare const pMapSkip: unique symbol;
62
+ /**
63
+ * @param input - Synchronous or asynchronous iterable that is iterated over
64
+ * concurrently, calling the `mapkit` function for each element. Each iterated
65
+ * item is `await`'d before the `mapkit` is invoked so the iterable may return a
66
+ * `Promise` that resolves to an item.
67
+ * @param mapkit - Function which is called for every item in `input`. Expected
68
+ * to return a `Promise` or value.
69
+ * @returns A `Promise` that is fulfilled when all promises in `input` and ones
70
+ * returned from `mapkit` are fulfilled, or rejects if any of the promises
71
+ * reject. The fulfilled value is an `Array` of the fulfilled values returned
72
+ * from `mapkit` in `input` order, excluding `pMapSkip`.
73
+ */
74
+ export default function pMap<Element, NewElement>(input: AsyncIterable<Element | Promise<Element>> | Iterable<Element | Promise<Element>>, mapkit: mapkit<Element, NewElement>, options?: Options): Promise<Array<Exclude<NewElement, typeof pMapSkip>>>;
75
+ /**
76
+ * @param input - Synchronous or asynchronous iterable that is iterated over
77
+ * concurrently, calling the `mapkit` function for each element. Each iterated
78
+ * item is `await`'d before the `mapkit` is invoked so the iterable may return a
79
+ * `Promise` that resolves to an item.
80
+ * @param mapkit - Function which is called for every item in `input`. Expected
81
+ * to return a `Promise` or value.
82
+ * @returns An async iterable that streams each return value from `mapkit` in
83
+ * order, excluding `pMapSkip`.
84
+ */
85
+ export declare function pMapIterable<Element, NewElement>(input: AsyncIterable<Element | Promise<Element>> | Iterable<Element | Promise<Element>>, mapkit: mapkit<Element, NewElement>, options?: IterableOptions): AsyncIterable<Exclude<NewElement, typeof pMapSkip>>;
86
+ export {};
package/dist/mod.js ADDED
@@ -0,0 +1,251 @@
1
+ export const pMapSkip = /* @__PURE__ */ Symbol("skip");
2
+ export default async function pMap(input, mapkit, options = {}) {
3
+ const {
4
+ concurrency = Number.POSITIVE_INFINITY,
5
+ stopOnError = true,
6
+ signal
7
+ } = options;
8
+ if (!("Symbol" in globalThis) || input[Symbol.iterator] === void 0 && input[Symbol.asyncIterator] === void 0) {
9
+ throw new TypeError(
10
+ `Expected \`input\` to be either an \`Iterable\` or \`AsyncIterable\`, got (${typeof input})`
11
+ );
12
+ }
13
+ if (typeof mapkit !== "function") {
14
+ throw new TypeError("mapkit function is required");
15
+ }
16
+ if (!(Number.isSafeInteger(concurrency) && concurrency >= 1 || concurrency === Number.POSITIVE_INFINITY)) {
17
+ throw new TypeError(
18
+ `Expected \`concurrency\` to be an integer from 1 and up or \`Infinity\`, got \`${concurrency}\` (${typeof concurrency})`
19
+ );
20
+ }
21
+ return new Promise((resolve, reject) => {
22
+ const isAsyncIterable = input[Symbol.asyncIterator] !== void 0;
23
+ const iterator = isAsyncIterable ? input[Symbol.asyncIterator]() : input[Symbol.iterator]();
24
+ const results = [];
25
+ const errors = [];
26
+ let activeCount = 0;
27
+ let currentIndex = 0;
28
+ let isIterableDone = false;
29
+ let isSettled = false;
30
+ let isPumping = false;
31
+ let iteratorClosed = false;
32
+ const safeCloseIterator = async () => {
33
+ if (iteratorClosed) return;
34
+ const ret = iterator?.return;
35
+ if (typeof ret === "function") {
36
+ try {
37
+ iteratorClosed = true;
38
+ await ret.call(iterator);
39
+ } catch {
40
+ }
41
+ } else {
42
+ iteratorClosed = true;
43
+ }
44
+ };
45
+ const onAbort = () => {
46
+ if (isSettled) return;
47
+ isSettled = true;
48
+ void safeCloseIterator();
49
+ cleanup();
50
+ reject(signal?.reason);
51
+ };
52
+ signal?.addEventListener("abort", onAbort, { once: true });
53
+ function cleanup() {
54
+ signal?.removeEventListener("abort", onAbort);
55
+ }
56
+ function settleWithAggregateIfNeeded() {
57
+ if (errors.length > 0 && !stopOnError) {
58
+ cleanup();
59
+ reject(new AggregateError(errors, "One or more promises rejected"));
60
+ return true;
61
+ }
62
+ return false;
63
+ }
64
+ function resolveResults() {
65
+ cleanup();
66
+ const out = [];
67
+ for (let i = 0; i < results.length; i++) {
68
+ const v = results[i];
69
+ if (v !== void 0 && v !== pMapSkip) {
70
+ out.push(v);
71
+ }
72
+ }
73
+ resolve(out);
74
+ }
75
+ function checkCompletion() {
76
+ if (isSettled) return;
77
+ if (!isIterableDone) return;
78
+ if (activeCount > 0) return;
79
+ isSettled = true;
80
+ if (settleWithAggregateIfNeeded()) {
81
+ return;
82
+ }
83
+ resolveResults();
84
+ }
85
+ async function startmapkit(valueOrPromise, index) {
86
+ try {
87
+ const item = await valueOrPromise;
88
+ const result = await mapkit(item, index);
89
+ if (!isSettled) {
90
+ results[index] = result;
91
+ }
92
+ } catch (error) {
93
+ if (isSettled) return;
94
+ if (stopOnError) {
95
+ isSettled = true;
96
+ void safeCloseIterator();
97
+ cleanup();
98
+ reject(error);
99
+ return;
100
+ }
101
+ errors.push(error);
102
+ } finally {
103
+ if (!isSettled) {
104
+ activeCount--;
105
+ checkCompletion();
106
+ void pump();
107
+ }
108
+ }
109
+ }
110
+ async function pump() {
111
+ if (isSettled || isPumping) return;
112
+ isPumping = true;
113
+ try {
114
+ while (!isSettled && !isIterableDone && activeCount < concurrency) {
115
+ let next;
116
+ try {
117
+ next = iterator.next();
118
+ next = await next;
119
+ } catch (error) {
120
+ if (!isSettled) {
121
+ isSettled = true;
122
+ void safeCloseIterator();
123
+ cleanup();
124
+ reject(error);
125
+ }
126
+ return;
127
+ }
128
+ if (next.done) {
129
+ isIterableDone = true;
130
+ break;
131
+ }
132
+ const index = currentIndex++;
133
+ activeCount++;
134
+ void startmapkit(next.value, index);
135
+ }
136
+ checkCompletion();
137
+ } finally {
138
+ isPumping = false;
139
+ }
140
+ }
141
+ void pump();
142
+ });
143
+ }
144
+ export function pMapIterable(input, mapkit, options = {}) {
145
+ const { concurrency = Number.POSITIVE_INFINITY, backpressure = concurrency } = options;
146
+ if (!("Symbol" in globalThis) || input[Symbol.iterator] === void 0 && input[Symbol.asyncIterator] === void 0) {
147
+ throw new TypeError(
148
+ `Expected \`input\` to be either an \`Iterable\` or \`AsyncIterable\`, got (${typeof input})`
149
+ );
150
+ }
151
+ if (typeof mapkit !== "function") {
152
+ throw new TypeError("mapkit function is required");
153
+ }
154
+ if (!(Number.isSafeInteger(concurrency) && concurrency >= 1 || concurrency === Number.POSITIVE_INFINITY)) {
155
+ throw new TypeError(
156
+ `Expected \`concurrency\` to be an integer from 1 and up or \`Infinity\`, got \`${concurrency}\` (${typeof concurrency})`
157
+ );
158
+ }
159
+ if (!(Number.isSafeInteger(backpressure) && backpressure >= concurrency || backpressure === Number.POSITIVE_INFINITY)) {
160
+ throw new TypeError(
161
+ `Expected \`backpressure\` to be an integer from \`concurrency\` (${concurrency}) and up or \`Infinity\`, got \`${backpressure}\` (${typeof backpressure})`
162
+ );
163
+ }
164
+ return {
165
+ async *[Symbol.asyncIterator]() {
166
+ const iterator = input[Symbol.asyncIterator] !== void 0 ? input[Symbol.asyncIterator]() : input[Symbol.iterator]();
167
+ const resultQueue = [];
168
+ let runningCount = 0;
169
+ let currentIndex = 0;
170
+ let isDone = false;
171
+ let pulling = false;
172
+ let iteratorClosed = false;
173
+ const safeCloseIterator = async () => {
174
+ if (iteratorClosed) return;
175
+ const ret = iterator?.return;
176
+ if (typeof ret === "function") {
177
+ try {
178
+ iteratorClosed = true;
179
+ await ret.call(iterator);
180
+ } catch {
181
+ }
182
+ } else {
183
+ iteratorClosed = true;
184
+ }
185
+ };
186
+ const schedulePull = () => {
187
+ if (pulling || isDone) return;
188
+ pulling = true;
189
+ (async () => {
190
+ try {
191
+ while (!isDone && runningCount < concurrency && resultQueue.length < backpressure) {
192
+ let next;
193
+ try {
194
+ next = iterator.next();
195
+ next = await next;
196
+ } catch (error) {
197
+ isDone = true;
198
+ resultQueue.push(
199
+ Promise.resolve({
200
+ error,
201
+ done: false
202
+ })
203
+ );
204
+ break;
205
+ }
206
+ if (next.done) {
207
+ isDone = true;
208
+ resultQueue.push(Promise.resolve({ done: true }));
209
+ break;
210
+ }
211
+ const index = currentIndex++;
212
+ runningCount++;
213
+ const promise = (async () => {
214
+ try {
215
+ const element = await next.value;
216
+ const result = await mapkit(element, index);
217
+ return { value: result, done: false };
218
+ } catch (error) {
219
+ return { error, done: false };
220
+ } finally {
221
+ runningCount--;
222
+ schedulePull();
223
+ }
224
+ })();
225
+ resultQueue.push(promise);
226
+ }
227
+ } finally {
228
+ pulling = false;
229
+ }
230
+ })();
231
+ };
232
+ schedulePull();
233
+ while (resultQueue.length > 0) {
234
+ const result = await resultQueue.shift();
235
+ if (result.error) {
236
+ await safeCloseIterator();
237
+ throw result.error;
238
+ }
239
+ if (result.done) {
240
+ await safeCloseIterator();
241
+ return;
242
+ }
243
+ schedulePull();
244
+ if (result.value !== pMapSkip) {
245
+ yield result.value;
246
+ }
247
+ }
248
+ await safeCloseIterator();
249
+ }
250
+ };
251
+ }
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@reliverse/mapkit",
3
+ "version": "2.2.7",
4
+ "private": false,
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/mod.d.ts",
9
+ "default": "./dist/mod.js"
10
+ }
11
+ },
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
15
+ "dependencies": {},
16
+ "files": [
17
+ "dist",
18
+ "package.json"
19
+ ],
20
+ "license": "MIT"
21
+ }