@johnhenry/iteration 0.0.0

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 (88) hide show
  1. package/MIT-LICENSE.txt +20 -0
  2. package/dist/abort.d.ts +26 -0
  3. package/dist/abort.d.ts.map +1 -0
  4. package/dist/abort.js +69 -0
  5. package/dist/abort.js.map +1 -0
  6. package/dist/assertions/eventualequal.d.ts +9 -0
  7. package/dist/assertions/eventualequal.d.ts.map +1 -0
  8. package/dist/assertions/eventualequal.js +30 -0
  9. package/dist/assertions/eventualequal.js.map +1 -0
  10. package/dist/async-channel.d.ts +101 -0
  11. package/dist/async-channel.d.ts.map +1 -0
  12. package/dist/async-channel.js +208 -0
  13. package/dist/async-channel.js.map +1 -0
  14. package/dist/channel-decorators.d.ts +23 -0
  15. package/dist/channel-decorators.d.ts.map +1 -0
  16. package/dist/channel-decorators.js +31 -0
  17. package/dist/channel-decorators.js.map +1 -0
  18. package/dist/concurrency.d.ts +43 -0
  19. package/dist/concurrency.d.ts.map +1 -0
  20. package/dist/concurrency.js +145 -0
  21. package/dist/concurrency.js.map +1 -0
  22. package/dist/consumers.d.ts +199 -0
  23. package/dist/consumers.d.ts.map +1 -0
  24. package/dist/consumers.js +349 -0
  25. package/dist/consumers.js.map +1 -0
  26. package/dist/count.d.ts +37 -0
  27. package/dist/count.d.ts.map +1 -0
  28. package/dist/count.js +70 -0
  29. package/dist/count.js.map +1 -0
  30. package/dist/empty-iterator.d.ts +15 -0
  31. package/dist/empty-iterator.d.ts.map +1 -0
  32. package/dist/empty-iterator.js +16 -0
  33. package/dist/empty-iterator.js.map +1 -0
  34. package/dist/eventualequal.d.ts +2 -0
  35. package/dist/eventualequal.d.ts.map +1 -0
  36. package/dist/eventualequal.js +54 -0
  37. package/dist/eventualequal.js.map +1 -0
  38. package/dist/exhaust.d.ts +7 -0
  39. package/dist/exhaust.d.ts.map +1 -0
  40. package/dist/exhaust.js +22 -0
  41. package/dist/exhaust.js.map +1 -0
  42. package/dist/index.d.ts +16 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +16 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/is-iterator.d.ts +5 -0
  47. package/dist/is-iterator.d.ts.map +1 -0
  48. package/dist/is-iterator.js +5 -0
  49. package/dist/is-iterator.js.map +1 -0
  50. package/dist/iterator-tools.d.ts +164 -0
  51. package/dist/iterator-tools.d.ts.map +1 -0
  52. package/dist/iterator-tools.js +231 -0
  53. package/dist/iterator-tools.js.map +1 -0
  54. package/dist/itertools.d.ts +237 -0
  55. package/dist/itertools.d.ts.map +1 -0
  56. package/dist/itertools.js +471 -0
  57. package/dist/itertools.js.map +1 -0
  58. package/dist/tee.d.ts +18 -0
  59. package/dist/tee.d.ts.map +1 -0
  60. package/dist/tee.js +118 -0
  61. package/dist/tee.js.map +1 -0
  62. package/dist/transduce.d.ts +79 -0
  63. package/dist/transduce.d.ts.map +1 -0
  64. package/dist/transduce.js +37 -0
  65. package/dist/transduce.js.map +1 -0
  66. package/dist/transducers.d.ts +123 -0
  67. package/dist/transducers.d.ts.map +1 -0
  68. package/dist/transducers.js +212 -0
  69. package/dist/transducers.js.map +1 -0
  70. package/package.json +122 -0
  71. package/readme.md +761 -0
  72. package/src/abort.ts +80 -0
  73. package/src/assertions/eventualequal.ts +36 -0
  74. package/src/async-channel.ts +228 -0
  75. package/src/channel-decorators.ts +49 -0
  76. package/src/concurrency.ts +168 -0
  77. package/src/consumers.ts +425 -0
  78. package/src/count.ts +72 -0
  79. package/src/empty-iterator.ts +17 -0
  80. package/src/eventualequal.ts +54 -0
  81. package/src/exhaust.ts +26 -0
  82. package/src/index.ts +17 -0
  83. package/src/is-iterator.ts +9 -0
  84. package/src/iterator-tools.ts +290 -0
  85. package/src/itertools.ts +518 -0
  86. package/src/tee.ts +136 -0
  87. package/src/transduce.ts +161 -0
  88. package/src/transducers.ts +256 -0
@@ -0,0 +1,425 @@
1
+ /**
2
+ * Terminal consumer operations: resolve an iterable to a single value (or a
3
+ * Promise of one), rather than returning another iterable. Two families:
4
+ *
5
+ * - Iterator-Helper parity (some/every/find/forEach/fold): native ES2025
6
+ * Iterator Helpers give sync code `.some()`/`.every()`/`.find()`/
7
+ * `.forEach()`/`.reduce()` for free via Iterator.from(iterable); there is
8
+ * no shipped async equivalent (AsyncIterator helpers are TC39 Stage 2),
9
+ * so the async half here is hand-rolled.
10
+ * - Summary/aggregate consumers (first/last/nth/quantify/min/max): not part
11
+ * of core Python itertools, but a near-universal expectation for any
12
+ * itertools-adjacent library (see itertools-ts's "summary" namespace);
13
+ * `quantify` specifically is a named recipe from Python's own itertools
14
+ * docs, not an invented name.
15
+ *
16
+ * House conventions:
17
+ * - Async variants `await` their callback's return value (predicate/fn/keyFn
18
+ * may be async); sync variants do not, matching native Iterator Helpers'
19
+ * own non-awaiting behavior and every other sync callback in this library
20
+ * (groupBySync's keyFn, uniqueSync's keyFn, etc.).
21
+ * - Required-callback functions put the callback first (matching
22
+ * itertools.ts: takeWhileSync(predicate, iterable)). Optional-callback
23
+ * functions put `iterable` first (matching Python's own builtin order:
24
+ * min(iterable, key=..., default=...)).
25
+ * - find/first/last/nth/min/max return `undefined` (or a caller-supplied
26
+ * `defaultValue`) on an empty iterable/no match, rather than throwing --
27
+ * consistent with Array.prototype.find, deliberately diverging from
28
+ * Python's own min()/max() (which throw without a default).
29
+ *
30
+ * `fold` is named deliberately -- NOT `reduce` -- to avoid colliding with
31
+ * reduceSync/reduceAsync in iterator-tools.ts, which are an internal
32
+ * streaming primitive (yield each emitted item) rather than a terminal
33
+ * reduce-to-one-value.
34
+ */
35
+
36
+ import { abortable, type SignalOptions } from "./abort.ts";
37
+ import { isliceAsync, isliceSync } from "./itertools.ts";
38
+
39
+ type MaybePromise<T> = T | Promise<T>;
40
+ export type AnyAsyncIterable<T> = AsyncIterable<T> | Iterable<T>;
41
+
42
+ const maybeAbortable = <T>(iterable: AnyAsyncIterable<T>, signal?: AbortSignal): AnyAsyncIterable<T> =>
43
+ signal ? abortable(iterable, signal) : iterable;
44
+
45
+ /**
46
+ * True for a numeric NaN key. `NaN < x` and `NaN > x` are always false, so
47
+ * without an explicit check a NaN key silently falls out of every
48
+ * comparison instead of poisoning the result -- see minSync/maxSync.
49
+ * @ignore
50
+ */
51
+ const isNaNKey = (key: unknown): boolean => typeof key === "number" && Number.isNaN(key);
52
+
53
+ /**
54
+ * True if any item satisfies predicate. Short-circuits on the first match.
55
+ * @kind function
56
+ * @name someSync
57
+ */
58
+ export const someSync = <T>(predicate: (item: T) => boolean, iterable: Iterable<T>): boolean =>
59
+ Iterator.from(iterable).some(predicate);
60
+
61
+ /**
62
+ * Asynchronous dual of someSync. Short-circuits (a `return` inside
63
+ * `for await...of` closes the underlying async iterator).
64
+ * @kind function
65
+ * @name someAsync
66
+ */
67
+ export const someAsync = async <T>(
68
+ predicate: (item: T) => MaybePromise<boolean>,
69
+ iterable: AnyAsyncIterable<T>,
70
+ { signal }: SignalOptions = {},
71
+ ): Promise<boolean> => {
72
+ for await (const item of maybeAbortable(iterable, signal)) {
73
+ if (await predicate(item)) return true;
74
+ }
75
+ return false;
76
+ };
77
+
78
+ /**
79
+ * True only if every item satisfies predicate. Short-circuits on the first
80
+ * failure.
81
+ * @kind function
82
+ * @name everySync
83
+ */
84
+ export const everySync = <T>(predicate: (item: T) => boolean, iterable: Iterable<T>): boolean =>
85
+ Iterator.from(iterable).every(predicate);
86
+
87
+ /**
88
+ * Asynchronous dual of everySync.
89
+ * @kind function
90
+ * @name everyAsync
91
+ */
92
+ export const everyAsync = async <T>(
93
+ predicate: (item: T) => MaybePromise<boolean>,
94
+ iterable: AnyAsyncIterable<T>,
95
+ { signal }: SignalOptions = {},
96
+ ): Promise<boolean> => {
97
+ for await (const item of maybeAbortable(iterable, signal)) {
98
+ if (!(await predicate(item))) return false;
99
+ }
100
+ return true;
101
+ };
102
+
103
+ /**
104
+ * The first item satisfying predicate, or undefined. Short-circuits.
105
+ * @kind function
106
+ * @name findSync
107
+ */
108
+ export const findSync = <T>(predicate: (item: T) => boolean, iterable: Iterable<T>): T | undefined =>
109
+ Iterator.from(iterable).find(predicate);
110
+
111
+ /**
112
+ * Asynchronous dual of findSync.
113
+ * @kind function
114
+ * @name findAsync
115
+ */
116
+ export const findAsync = async <T>(
117
+ predicate: (item: T) => MaybePromise<boolean>,
118
+ iterable: AnyAsyncIterable<T>,
119
+ { signal }: SignalOptions = {},
120
+ ): Promise<T | undefined> => {
121
+ for await (const item of maybeAbortable(iterable, signal)) {
122
+ if (await predicate(item)) return item;
123
+ }
124
+ return undefined;
125
+ };
126
+
127
+ /**
128
+ * Call fn once per item, for side effects. Returns undefined.
129
+ * @kind function
130
+ * @name forEachSync
131
+ */
132
+ export const forEachSync = <T>(fn: (item: T) => unknown, iterable: Iterable<T>): void =>
133
+ Iterator.from(iterable).forEach(fn);
134
+
135
+ /**
136
+ * Asynchronous dual of forEachSync. Distinct from `run` (iterator-tools.ts)
137
+ * -- `run` is framed around "rendering a program" (reversed argument order,
138
+ * `render` defaults to console.log); this is the itertools-parity-family
139
+ * addition with the family's usual (fn, iterable) argument order.
140
+ * @kind function
141
+ * @name forEachAsync
142
+ * @see run
143
+ */
144
+ export const forEachAsync = async <T>(
145
+ fn: (item: T) => unknown,
146
+ iterable: AnyAsyncIterable<T>,
147
+ { signal }: SignalOptions = {},
148
+ ): Promise<void> => {
149
+ for await (const item of maybeAbortable(iterable, signal)) {
150
+ await fn(item);
151
+ }
152
+ };
153
+
154
+ /**
155
+ * Reduce iterable to a single value, given an explicit initial value
156
+ * (unlike Array.prototype.reduce, `init` is required -- avoids replicating
157
+ * reduce's "throws on empty array with no initial value" edge case).
158
+ * Named `fold`, not `reduce`, to avoid colliding with reduceSync/reduceAsync
159
+ * (iterator-tools.ts), which are an internal streaming primitive, not a
160
+ * terminal reduce-to-one-value.
161
+ * @kind function
162
+ * @name foldSync
163
+ */
164
+ export const foldSync = <T, Acc>(fn: (acc: Acc, item: T) => Acc, init: Acc, iterable: Iterable<T>): Acc =>
165
+ Iterator.from(iterable).reduce(fn, init);
166
+
167
+ /**
168
+ * Asynchronous dual of foldSync.
169
+ * @kind function
170
+ * @name foldAsync
171
+ */
172
+ export const foldAsync = async <T, Acc>(
173
+ fn: (acc: Acc, item: T) => MaybePromise<Acc>,
174
+ init: Acc,
175
+ iterable: AnyAsyncIterable<T>,
176
+ { signal }: SignalOptions = {},
177
+ ): Promise<Acc> => {
178
+ let acc = init;
179
+ for await (const item of maybeAbortable(iterable, signal)) {
180
+ acc = await fn(acc, item);
181
+ }
182
+ return acc;
183
+ };
184
+
185
+ /**
186
+ * The first item, or defaultValue if the iterable is empty.
187
+ * @kind function
188
+ * @name firstSync
189
+ */
190
+ export const firstSync = <T, D = undefined>(iterable: Iterable<T>, defaultValue?: D): T | D => {
191
+ const { value, done } = iterable[Symbol.iterator]().next();
192
+ return done ? (defaultValue as D) : value;
193
+ };
194
+
195
+ /**
196
+ * Asynchronous dual of firstSync.
197
+ * @kind function
198
+ * @name firstAsync
199
+ */
200
+ export const firstAsync = async <T, D = undefined>(
201
+ iterable: AsyncIterable<T>,
202
+ defaultValue?: D,
203
+ { signal }: SignalOptions = {},
204
+ ): Promise<T | D> => {
205
+ if (signal) {
206
+ for await (const item of abortable(iterable, signal)) {
207
+ return item;
208
+ }
209
+ return defaultValue as D;
210
+ }
211
+ const { value, done } = await iterable[Symbol.asyncIterator]().next();
212
+ return done ? (defaultValue as D) : value;
213
+ };
214
+
215
+ /**
216
+ * The last item, or defaultValue if the iterable is empty. Necessarily
217
+ * consumes the entire iterable -- no way around it for a generic source.
218
+ * @kind function
219
+ * @name lastSync
220
+ */
221
+ export const lastSync = <T, D = undefined>(iterable: Iterable<T>, defaultValue?: D): T | D => {
222
+ let result: T | D = defaultValue as D;
223
+ for (const item of iterable) {
224
+ result = item;
225
+ }
226
+ return result;
227
+ };
228
+
229
+ /**
230
+ * Asynchronous dual of lastSync.
231
+ * @kind function
232
+ * @name lastAsync
233
+ */
234
+ export const lastAsync = async <T, D = undefined>(
235
+ iterable: AnyAsyncIterable<T>,
236
+ defaultValue?: D,
237
+ { signal }: SignalOptions = {},
238
+ ): Promise<T | D> => {
239
+ let result: T | D = defaultValue as D;
240
+ for await (const item of maybeAbortable(iterable, signal)) {
241
+ result = item;
242
+ }
243
+ return result;
244
+ };
245
+
246
+ /**
247
+ * The item at position n (0-indexed), or defaultValue if out of range.
248
+ * Reuses isliceSync rather than duplicating positional-skip logic.
249
+ * @kind function
250
+ * @name nthSync
251
+ */
252
+ export const nthSync = <T, D = undefined>(iterable: Iterable<T>, n: number, defaultValue?: D): T | D => {
253
+ for (const item of isliceSync(iterable, n, n + 1)) {
254
+ return item;
255
+ }
256
+ return defaultValue as D;
257
+ };
258
+
259
+ /**
260
+ * Asynchronous dual of nthSync.
261
+ * @kind function
262
+ * @name nthAsync
263
+ */
264
+ export const nthAsync = async <T, D = undefined>(
265
+ iterable: AsyncIterable<T>,
266
+ n: number,
267
+ defaultValue?: D,
268
+ { signal }: SignalOptions = {},
269
+ ): Promise<T | D> => {
270
+ for await (const item of isliceAsync(maybeAbortable(iterable, signal) as AsyncIterable<T>, n, n + 1)) {
271
+ return item;
272
+ }
273
+ return defaultValue as D;
274
+ };
275
+
276
+ /**
277
+ * Count items satisfying predicate (default: count all items). Named after
278
+ * the `quantify(iterable, pred=bool)` recipe documented in Python's own
279
+ * itertools docs -- not part of core itertools, but not an invented name
280
+ * either. Deliberately not named `count`/`countSync`, which already mirror
281
+ * Python's itertools.count (an integer *sequence generator*, unrelated).
282
+ * @kind function
283
+ * @name quantifySync
284
+ */
285
+ export const quantifySync = <T>(iterable: Iterable<T>, predicate: (item: T) => boolean = () => true): number => {
286
+ let count = 0;
287
+ for (const item of iterable) {
288
+ if (predicate(item)) count++;
289
+ }
290
+ return count;
291
+ };
292
+
293
+ /**
294
+ * Asynchronous dual of quantifySync.
295
+ * @kind function
296
+ * @name quantifyAsync
297
+ */
298
+ export const quantifyAsync = async <T>(
299
+ iterable: AnyAsyncIterable<T>,
300
+ predicate: (item: T) => MaybePromise<boolean> = () => true,
301
+ { signal }: SignalOptions = {},
302
+ ): Promise<number> => {
303
+ let count = 0;
304
+ for await (const item of maybeAbortable(iterable, signal)) {
305
+ if (await predicate(item)) count++;
306
+ }
307
+ return count;
308
+ };
309
+
310
+ /**
311
+ * The item with the minimum key (default: the item itself), or defaultValue
312
+ * if the iterable is empty. Necessarily consumes the entire iterable.
313
+ * Mirrors Python's builtin min(iterable, key=..., default=...), but
314
+ * returns undefined rather than throwing when empty and no default is given.
315
+ * A NaN key anywhere in the iterable poisons the result to NaN, matching
316
+ * `Math.min`'s own NaN-poisoning convention (rather than silently dropping
317
+ * out of every comparison and returning an unrelated wrong item).
318
+ * @kind function
319
+ * @name minSync
320
+ */
321
+ export const minSync = <T, D = undefined>(
322
+ iterable: Iterable<T>,
323
+ keyFn: (item: T) => unknown = (x) => x,
324
+ defaultValue?: D,
325
+ ): T | D => {
326
+ let best: T | undefined, bestKey: unknown;
327
+ let found = false;
328
+ let sawNaN = false;
329
+ for (const item of iterable) {
330
+ const key = keyFn(item);
331
+ if (isNaNKey(key)) sawNaN = true;
332
+ if (!found || (key as number) < (bestKey as number)) {
333
+ best = item;
334
+ bestKey = key;
335
+ found = true;
336
+ }
337
+ }
338
+ if (sawNaN) return NaN as unknown as T | D;
339
+ return found ? (best as T) : (defaultValue as D);
340
+ };
341
+
342
+ /**
343
+ * Asynchronous dual of minSync; keyFn may be async. Same NaN-poisoning
344
+ * convention as minSync.
345
+ * @kind function
346
+ * @name minAsync
347
+ */
348
+ export const minAsync = async <T, D = undefined>(
349
+ iterable: AnyAsyncIterable<T>,
350
+ keyFn: (item: T) => unknown = (x) => x,
351
+ defaultValue?: D,
352
+ { signal }: SignalOptions = {},
353
+ ): Promise<T | D> => {
354
+ let best: T | undefined, bestKey: unknown;
355
+ let found = false;
356
+ let sawNaN = false;
357
+ for await (const item of maybeAbortable(iterable, signal)) {
358
+ const key = await keyFn(item);
359
+ if (isNaNKey(key)) sawNaN = true;
360
+ if (!found || (key as number) < (bestKey as number)) {
361
+ best = item;
362
+ bestKey = key;
363
+ found = true;
364
+ }
365
+ }
366
+ if (sawNaN) return NaN as unknown as T | D;
367
+ return found ? (best as T) : (defaultValue as D);
368
+ };
369
+
370
+ /**
371
+ * The item with the maximum key (default: the item itself), or defaultValue
372
+ * if the iterable is empty. Mirrors Python's builtin
373
+ * max(iterable, key=..., default=...); see minSync for the
374
+ * undefined-vs-throw divergence and the NaN-poisoning convention.
375
+ * @kind function
376
+ * @name maxSync
377
+ */
378
+ export const maxSync = <T, D = undefined>(
379
+ iterable: Iterable<T>,
380
+ keyFn: (item: T) => unknown = (x) => x,
381
+ defaultValue?: D,
382
+ ): T | D => {
383
+ let best: T | undefined, bestKey: unknown;
384
+ let found = false;
385
+ let sawNaN = false;
386
+ for (const item of iterable) {
387
+ const key = keyFn(item);
388
+ if (isNaNKey(key)) sawNaN = true;
389
+ if (!found || (key as number) > (bestKey as number)) {
390
+ best = item;
391
+ bestKey = key;
392
+ found = true;
393
+ }
394
+ }
395
+ if (sawNaN) return NaN as unknown as T | D;
396
+ return found ? (best as T) : (defaultValue as D);
397
+ };
398
+
399
+ /**
400
+ * Asynchronous dual of maxSync; keyFn may be async. Same NaN-poisoning
401
+ * convention as minSync.
402
+ * @kind function
403
+ * @name maxAsync
404
+ */
405
+ export const maxAsync = async <T, D = undefined>(
406
+ iterable: AnyAsyncIterable<T>,
407
+ keyFn: (item: T) => unknown = (x) => x,
408
+ defaultValue?: D,
409
+ { signal }: SignalOptions = {},
410
+ ): Promise<T | D> => {
411
+ let best: T | undefined, bestKey: unknown;
412
+ let found = false;
413
+ let sawNaN = false;
414
+ for await (const item of maybeAbortable(iterable, signal)) {
415
+ const key = await keyFn(item);
416
+ if (isNaNKey(key)) sawNaN = true;
417
+ if (!found || (key as number) > (bestKey as number)) {
418
+ best = item;
419
+ bestKey = key;
420
+ found = true;
421
+ }
422
+ }
423
+ if (sawNaN) return NaN as unknown as T | D;
424
+ return found ? (best as T) : (defaultValue as D);
425
+ };
package/src/count.ts ADDED
@@ -0,0 +1,72 @@
1
+ type Numeric = number | bigint;
2
+
3
+ const count = <N extends Numeric>(zero: N, one: N) =>
4
+ function* (min: N = zero, max?: N, inc: N = one): Generator<N> {
5
+ if (max === undefined) {
6
+ if (min < zero) {
7
+ max = zero;
8
+ } else {
9
+ max = min;
10
+ min = zero;
11
+ }
12
+ }
13
+
14
+ if (min < max) {
15
+ for (let i = min; i <= max; i = ((i as number) + (inc as number)) as N) {
16
+ yield i;
17
+ }
18
+ } else if (min > max) {
19
+ for (let i = min; i >= max; i = ((i as number) - (inc as number)) as N) {
20
+ yield i;
21
+ }
22
+ } else {
23
+ yield min;
24
+ }
25
+
26
+ return;
27
+ };
28
+
29
+ /**
30
+ * Create a sequence of numbers
31
+ * @kind function
32
+ * @name countSync
33
+ * @param min number at which to start iteration
34
+ * @param max number before which to stop iteration
35
+ * @param inc increment
36
+ * @see countAsync
37
+ * @example <caption>Log an infinite sequence of numbers starting with 5 </caption>
38
+ * ```javascript
39
+ * import { countSync } from '@johnhenry/iteration';
40
+ * for(const num of countSync(5, Infinity)){
41
+ * console.log(num);
42
+ * }
43
+ * ```
44
+ */
45
+ export const countSync = count<number>(0, 1);
46
+ export const countBigSync = count<bigint>(0n, 1n);
47
+ /**
48
+ * Create an asynchronous sequence of numbers
49
+ * @kind function
50
+ * @name countAsync
51
+ * @param min number at which to start iteration
52
+ * @param max number before which to stop iteration
53
+ * @param inc increment
54
+ * @see countSync
55
+ * @example <caption>Log an infinite sequence of numbers starting with 5 </caption>
56
+ * ```javascript
57
+ * import { countAsync } from '@johnhenry/iteration';
58
+ * for await(const num of countAsync(5, Infinity)){
59
+ * console.log(num);
60
+ * }
61
+ * ```
62
+ */
63
+ export const countAsync = async function* (min = 0, max?: number, inc = 1): AsyncGenerator<number> {
64
+ yield* countSync(min, max, inc);
65
+ };
66
+ // min/inc default to undefined here (not 0/1) so countBigSync's own
67
+ // BigInt defaults (bound via count(0n, 1n)) actually apply -- a Number
68
+ // default of 0/1 would flow straight through and crash the first time
69
+ // `min += inc` mixed a BigInt with a Number.
70
+ export const countBigAsync = async function* (min?: bigint, max?: bigint, inc?: bigint): AsyncGenerator<bigint> {
71
+ yield* countBigSync(min, max, inc);
72
+ };
@@ -0,0 +1,17 @@
1
+ import { conjoinAsync, conjoinSync } from "./iterator-tools.ts";
2
+ /**
3
+ * "The" Empty Iterator
4
+ * Immediately finishes and yields nothing.
5
+ * @kind function
6
+ * @name emptySync
7
+ */
8
+
9
+ export const emptySync: Generator<never> = conjoinSync<never>();
10
+
11
+ /**
12
+ * "The" Empty Asynchronous Iterator
13
+ * Immediately finishes and yields nothing.
14
+ * @kind function
15
+ * @name emptyAsync
16
+ */
17
+ export const emptyAsync: AsyncGenerator<never> = conjoinAsync<never>();
@@ -0,0 +1,54 @@
1
+ import { exhaust } from "./exhaust.ts";
2
+ import { isAsyncIterator, isIterator } from "./is-iterator.ts";
3
+
4
+ export const eventualequal = async (a: unknown, b: unknown): Promise<boolean> => {
5
+ if (a === b) return true;
6
+ if (a && b && typeof a === "object" && typeof b === "object") {
7
+ let length: number, i: number, keys: string[];
8
+ if (Array.isArray(a) && Array.isArray(b)) {
9
+ length = a.length;
10
+ if (length !== b.length) return false;
11
+ for (i = length; i-- !== 0; ) {
12
+ if (!(await eventualequal(a[i], b[i]))) return false;
13
+ }
14
+ return true;
15
+ }
16
+ if (isIterator(a) || isAsyncIterator(a) || isIterator(b) || isAsyncIterator(b)) {
17
+ try {
18
+ return eventualequal(await exhaust(a), await exhaust(b));
19
+ } catch (_e) {
20
+ return false;
21
+ }
22
+ }
23
+
24
+ if (a.constructor === RegExp) {
25
+ return (a as RegExp).source === (b as RegExp).source && (a as RegExp).flags === (b as RegExp).flags;
26
+ }
27
+ if (a.valueOf !== Object.prototype.valueOf) {
28
+ return a.valueOf() === b.valueOf();
29
+ }
30
+ if (a.toString !== Object.prototype.toString) {
31
+ return a.toString() === b.toString();
32
+ }
33
+
34
+ keys = Object.keys(a);
35
+ length = keys.length;
36
+ if (length !== Object.keys(b).length) return false;
37
+
38
+ for (i = length; i-- !== 0; ) {
39
+ if (!Object.hasOwn(b, keys[i] as string)) return false;
40
+ }
41
+
42
+ for (i = length; i-- !== 0; ) {
43
+ const key = keys[i] as string;
44
+ if (!(await eventualequal((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key])))
45
+ return false;
46
+ }
47
+
48
+ return true;
49
+ }
50
+
51
+ // true if both NaN, false otherwise
52
+ // biome-ignore lint/suspicious/noSelfCompare: deliberate NaN check (x !== x is true only for NaN)
53
+ return a !== a && b !== b;
54
+ };
package/src/exhaust.ts ADDED
@@ -0,0 +1,26 @@
1
+ import { isAsyncIterator, isIterator } from "./is-iterator.ts";
2
+
3
+ export const exhaustSync = <T>(iterator: Iterable<T>): T[] => {
4
+ return [...iterator];
5
+ };
6
+ export const exhaustAsync = async <T>(asyncIterator: AsyncIterable<T>): Promise<T[]> => {
7
+ const r: T[] = [];
8
+ for await (const o of asyncIterator) {
9
+ r.push(o);
10
+ }
11
+ return r;
12
+ };
13
+
14
+ export function exhaust<T>(obj: Iterable<T>): T[];
15
+ export function exhaust<T>(obj: AsyncIterable<T>): Promise<T[]>;
16
+ export function exhaust(obj: unknown): unknown[] | Promise<unknown[]>;
17
+ export function exhaust(obj: unknown): unknown[] | Promise<unknown[]> {
18
+ if (isIterator(obj)) {
19
+ return exhaustSync(obj);
20
+ } else if (isAsyncIterator(obj)) {
21
+ return exhaustAsync(obj);
22
+ }
23
+ throw new TypeError(`${obj} is not iterable`);
24
+ }
25
+
26
+ export default exhaust;
package/src/index.ts ADDED
@@ -0,0 +1,17 @@
1
+ export * from "./abort.ts";
2
+ export * from "./async-channel.ts";
3
+ export * from "./concurrency.ts";
4
+ export * from "./consumers.ts";
5
+ export * from "./count.ts";
6
+ export * from "./empty-iterator.ts";
7
+ export * from "./exhaust.ts";
8
+ export * from "./is-iterator.ts";
9
+ export * from "./iterator-tools.ts";
10
+ export * from "./itertools.ts";
11
+ export * from "./tee.ts";
12
+ export * from "./transduce.ts";
13
+
14
+ import * as channelDecorators from "./channel-decorators.ts";
15
+ import * as transducers from "./transducers.ts";
16
+
17
+ export { channelDecorators, transducers };
@@ -0,0 +1,9 @@
1
+ export const isIterator = (obj: unknown): obj is Iterable<unknown> =>
2
+ typeof (obj as Iterable<unknown>)?.[Symbol.iterator] === "function";
3
+
4
+ export const isAsyncIterator = (obj: unknown): obj is AsyncIterable<unknown> =>
5
+ typeof (obj as AsyncIterable<unknown>)?.[Symbol.asyncIterator] === "function";
6
+
7
+ export const exhaustable = (obj: unknown): boolean => isIterator(obj) || isAsyncIterator(obj);
8
+
9
+ export default exhaustable;