@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,161 @@
1
+ import { abortable, type SignalOptions } from "./abort.ts";
2
+ import { type ReducerStep, reduceAsync, reduceSync, type Transducer } from "./iterator-tools.ts";
3
+
4
+ /**
5
+ * The innermost reducer step ("emit"): pushes an emitted item onto the
6
+ * pending-emission buffer (the accumulator) and returns the buffer.
7
+ * Replaces the v2.0 innermost step (conjoinSync/conjoinAsync), which
8
+ * wrapped the previous accumulator in a fresh generator per item --
9
+ * forming an ever-growing generator chain and leaking ~176 bytes per item
10
+ * on long streams. See reduceSync (iterator-tools.ts) for the full
11
+ * protocol description.
12
+ */
13
+ const emit: ReducerStep<unknown> = (buffer, item) => {
14
+ buffer.push(item);
15
+ return buffer;
16
+ };
17
+
18
+ /**
19
+ * Transduce -- low-level plumbing kept for protocol experimentation:
20
+ * `reduce(itemCollection, reducer(lastreducer), init)`.
21
+ * @kind function
22
+ * @name transduce
23
+ * @ignore
24
+ */
25
+ export const transduce = <Source, Result>(
26
+ itemCollection: Source,
27
+ reducer: (last: ReducerStep<never>) => ReducerStep<never>,
28
+ lastreducer: ReducerStep<never>,
29
+ init: unknown[],
30
+ reduce: (itemCollection: Source, step: ReducerStep<never>, init: unknown[]) => Result,
31
+ ): Result => reduce(itemCollection, reducer(lastreducer), init);
32
+
33
+ /**
34
+ * Compose Functions
35
+ * @kind function
36
+ * @name composeFunctions
37
+ * @ignore
38
+ */
39
+ const composeFunctions =
40
+ (...functions: Array<Transducer<unknown, unknown>>) =>
41
+ (input: ReducerStep<unknown>): ReducerStep<unknown> =>
42
+ functions.reduceRight((input, func) => func(input), input);
43
+
44
+ /**
45
+ * Create a function that transduces an asynchronous iterator from a list
46
+ * of transducer functions. Transducers compose left to right as written:
47
+ * the first listed transducer sees source items first.
48
+ * @kind function
49
+ * @name transduceAsync
50
+ * @param transducers list of transducers
51
+ * @see transducers
52
+ * @see transduceSync
53
+ * @example <caption>Asynchronously log transduced numbers </caption>
54
+ * ```javascript
55
+ * import { transduceAsync, transducers, countAsync } from '@johnhenry/iteration';
56
+ * const {
57
+ * map,
58
+ * filter,
59
+ * take,
60
+ * } = transducers;
61
+ * const LIMIT = 2 ** 2;
62
+ * const transduce = transduceAsync(
63
+ * filter(x => x % 2),
64
+ * map(x => x + 1),
65
+ * take(LIMIT),
66
+ * );
67
+ * for await (const result of transduce(countAsync(Infinity))) {
68
+ * console.log(result);
69
+ * }
70
+ * ```
71
+ */
72
+ export function transduceAsync<A, B>(
73
+ t1: Transducer<A, B>,
74
+ ): (itemCollection: AsyncIterable<A> | Iterable<A>, options?: SignalOptions) => AsyncGenerator<B>;
75
+ export function transduceAsync<A, B, C>(
76
+ t1: Transducer<A, B>,
77
+ t2: Transducer<B, C>,
78
+ ): (itemCollection: AsyncIterable<A> | Iterable<A>, options?: SignalOptions) => AsyncGenerator<C>;
79
+ export function transduceAsync<A, B, C, D>(
80
+ t1: Transducer<A, B>,
81
+ t2: Transducer<B, C>,
82
+ t3: Transducer<C, D>,
83
+ ): (itemCollection: AsyncIterable<A> | Iterable<A>, options?: SignalOptions) => AsyncGenerator<D>;
84
+ export function transduceAsync<A, B, C, D, E>(
85
+ t1: Transducer<A, B>,
86
+ t2: Transducer<B, C>,
87
+ t3: Transducer<C, D>,
88
+ t4: Transducer<D, E>,
89
+ ): (itemCollection: AsyncIterable<A> | Iterable<A>, options?: SignalOptions) => AsyncGenerator<E>;
90
+ export function transduceAsync<A, B, C, D, E, F>(
91
+ t1: Transducer<A, B>,
92
+ t2: Transducer<B, C>,
93
+ t3: Transducer<C, D>,
94
+ t4: Transducer<D, E>,
95
+ t5: Transducer<E, F>,
96
+ ): (itemCollection: AsyncIterable<A> | Iterable<A>, options?: SignalOptions) => AsyncGenerator<F>;
97
+ export function transduceAsync(
98
+ ...functions: Array<Transducer<any, any>>
99
+ ): (itemCollection: AsyncIterable<unknown> | Iterable<unknown>, options?: SignalOptions) => AsyncGenerator<unknown>;
100
+ export function transduceAsync(...functions: Array<Transducer<any, any>>) {
101
+ return (itemCollection: AsyncIterable<unknown> | Iterable<unknown>, { signal }: SignalOptions = {}) =>
102
+ reduceAsync(signal ? abortable(itemCollection, signal) : itemCollection, composeFunctions(...functions)(emit), []);
103
+ }
104
+
105
+ /**
106
+ * Create a function that transduces a synchronous iterator from a list
107
+ * of transducer functions. Transducers compose left to right as written:
108
+ * the first listed transducer sees source items first.
109
+ * @kind function
110
+ * @name transduceSync
111
+ * @param transducers list of transducers
112
+ * @see transducers
113
+ * @see transduceAsync
114
+ * @example <caption>Synchronously log transduced numbers </caption>
115
+ * ```javascript
116
+ * import { transduceSync, transducers, countSync } from '@johnhenry/iteration';
117
+ * const {
118
+ * map,
119
+ * filter,
120
+ * take,
121
+ * } = transducers;
122
+ * const LIMIT = 2 ** 2;
123
+ * const transduce = transduceSync(
124
+ * filter(x => x % 2),
125
+ * map(x => x + 1),
126
+ * take(LIMIT),
127
+ * );
128
+ * for (const result of transduce(countSync(Infinity))) {
129
+ * console.log(result);
130
+ * }
131
+ * ```
132
+ */
133
+ export function transduceSync<A, B>(t1: Transducer<A, B>): (itemCollection: Iterable<A>) => Generator<B>;
134
+ export function transduceSync<A, B, C>(
135
+ t1: Transducer<A, B>,
136
+ t2: Transducer<B, C>,
137
+ ): (itemCollection: Iterable<A>) => Generator<C>;
138
+ export function transduceSync<A, B, C, D>(
139
+ t1: Transducer<A, B>,
140
+ t2: Transducer<B, C>,
141
+ t3: Transducer<C, D>,
142
+ ): (itemCollection: Iterable<A>) => Generator<D>;
143
+ export function transduceSync<A, B, C, D, E>(
144
+ t1: Transducer<A, B>,
145
+ t2: Transducer<B, C>,
146
+ t3: Transducer<C, D>,
147
+ t4: Transducer<D, E>,
148
+ ): (itemCollection: Iterable<A>) => Generator<E>;
149
+ export function transduceSync<A, B, C, D, E, F>(
150
+ t1: Transducer<A, B>,
151
+ t2: Transducer<B, C>,
152
+ t3: Transducer<C, D>,
153
+ t4: Transducer<D, E>,
154
+ t5: Transducer<E, F>,
155
+ ): (itemCollection: Iterable<A>) => Generator<F>;
156
+ export function transduceSync(
157
+ ...functions: Array<Transducer<any, any>>
158
+ ): (itemCollection: Iterable<unknown>) => Generator<unknown>;
159
+ export function transduceSync(...functions: Array<Transducer<any, any>>) {
160
+ return (itemCollection: Iterable<unknown>) => reduceSync(itemCollection, composeFunctions(...functions)(emit), []);
161
+ }
@@ -0,0 +1,256 @@
1
+ /**
2
+ * Functions that return transducers
3
+ * @kind namespace
4
+ * @name transducerReturners
5
+ * @see transduceSync
6
+ * @see transduceAsync
7
+ */
8
+
9
+ import { HALT, type ReducerStep, type Transducer } from "./iterator-tools.ts";
10
+
11
+ /**
12
+ * Create a transducer that maps values
13
+ * @kind function
14
+ * @name map
15
+ * @param transform transformation function applied to each item
16
+ * @returns transducer
17
+ */
18
+ export const map =
19
+ <In, Out>(transform: (item: In) => Out): Transducer<In, Out> =>
20
+ (conjoin) =>
21
+ (init, item) =>
22
+ conjoin(init, transform(item));
23
+
24
+ /**
25
+ * Create a transducer that filters values
26
+ * @kind function
27
+ * @name filter
28
+ * @param predicate boolean function to determine if an item is emitted
29
+ * @returns transducer
30
+ */
31
+ export const filter =
32
+ <In>(predicate: (item: In) => boolean): Transducer<In, In> =>
33
+ (conjoin) =>
34
+ (init, item) =>
35
+ predicate(item) ? conjoin(init, item) : init;
36
+
37
+ /**
38
+ * Create a transducer that halts after a given number of values
39
+ * @kind function
40
+ * @name take
41
+ * @param limit maximum total items to emit
42
+ * @returns transducer
43
+ */
44
+ export const take =
45
+ <In>(limit: number): Transducer<In, In> =>
46
+ (conjoin) => {
47
+ let amount = 0;
48
+ // biome-ignore lint/complexity/noCommaOperator: deliberate single-expression step function
49
+ return (init, item) => (amount < limit ? (amount++, conjoin(init, item)) : HALT);
50
+ };
51
+
52
+ /**
53
+ * Create a transducer that drops the first N values
54
+ * before begining to yield
55
+ * @kind function
56
+ * @name drop
57
+ * @param limit number of leading items to drop
58
+ * @returns transducer
59
+ * @see reject
60
+ */
61
+ export const drop =
62
+ <In>(limit: number): Transducer<In, In> =>
63
+ (conjoin) => {
64
+ let amount = 0;
65
+ // biome-ignore lint/complexity/noCommaOperator: deliberate single-expression step function
66
+ return (init, item) => (amount >= limit ? (amount++, conjoin(init, item)) : (amount++, init));
67
+ };
68
+
69
+ /**
70
+ * Create a transducer that rejects values matching a predicate.
71
+ * The complement of `filter`.
72
+ * @kind function
73
+ * @name reject
74
+ * @param predicate boolean function; matching items are dropped
75
+ * @returns transducer
76
+ * @see filter
77
+ * @see drop
78
+ */
79
+ export const reject =
80
+ <In>(predicate: (item: In) => boolean): Transducer<In, In> =>
81
+ (conjoin) =>
82
+ (init, item) =>
83
+ predicate(item) ? init : conjoin(init, item);
84
+
85
+ /**
86
+ * Create a transducer that groups items by quantity before emitting.
87
+ * Note: this currently returns arrays -- would sets make more sense?
88
+ * Any trailing items that don't fill a complete group are flushed as a
89
+ * final, shorter group when the source iterator completes (see the
90
+ * `.complete` transducer protocol in transduce.ts/iterator-tools.ts).
91
+ * @kind function
92
+ * @name group
93
+ * @param limit size of group
94
+ * @returns transducer
95
+ */
96
+ export const group =
97
+ <In>(limit: number): Transducer<In, In[]> =>
98
+ (conjoin) => {
99
+ const partition: In[] = [];
100
+ const step: ReducerStep<In> = (init, item) => {
101
+ partition.push(item);
102
+ if (partition.length === limit) {
103
+ return conjoin(init, partition.splice(0, limit));
104
+ }
105
+ return init;
106
+ };
107
+ step.complete = (init) => {
108
+ const out = partition.length > 0 ? conjoin(init, partition.splice(0)) : init;
109
+ const buffer = out === HALT ? init : out;
110
+ return conjoin.complete ? conjoin.complete(buffer) : buffer;
111
+ };
112
+ return step;
113
+ };
114
+
115
+ /**
116
+ * Create a transducer that accumulates items into a result and emits them
117
+ * Similar to #Array.reduce
118
+ * @kind function
119
+ * @name accumulate
120
+ * @param func accumulation function
121
+ * @param initial initial accumulation value
122
+ * @returns transducer
123
+ */
124
+ export const accumulate =
125
+ <In, Acc = In>(
126
+ func: (accumulated: Acc, item: In) => Acc = (a, b) =>
127
+ ((a as unknown as number) + (b as unknown as number)) as unknown as Acc,
128
+ initial: Acc = 0 as unknown as Acc,
129
+ ): Transducer<In, Acc> =>
130
+ (conjoin) => {
131
+ // Fresh per (conjoin) invocation -- i.e. per transduce run, matching
132
+ // every other stateful transducer in this file (take's `amount`,
133
+ // group's `partition`, etc). Mutating the outer `initial` parameter
134
+ // directly here would carry accumulated state over into the *next*
135
+ // transduce run that reuses this same accumulate(...) instance.
136
+ let accumulated = initial;
137
+ return (init, item) => {
138
+ accumulated = func(accumulated, item);
139
+ return conjoin(init, accumulated);
140
+ };
141
+ };
142
+
143
+ /**
144
+ * Create a transducer that skips consecutive duplicate items (by key).
145
+ * Mirrors the `unique_justseen` recipe documented in Python's itertools
146
+ * docs -- the transducer-pipeline, adjacent-only counterpart to
147
+ * itertools.ts's global, unbounded-memory uniqueSync/uniqueAsync.
148
+ * @kind function
149
+ * @name dedupe
150
+ * @param keyFn maps an item to the key duplicates are compared by
151
+ * @returns transducer
152
+ * @see uniqueSync
153
+ */
154
+ export const dedupe =
155
+ <In>(keyFn: (item: In) => unknown = (x) => x): Transducer<In, In> =>
156
+ (conjoin) => {
157
+ let hasLast = false;
158
+ let lastKey: unknown;
159
+ return (init, item) => {
160
+ const key = keyFn(item);
161
+ if (hasLast && Object.is(key, lastKey)) {
162
+ return init;
163
+ }
164
+ hasLast = true;
165
+ lastKey = key;
166
+ return conjoin(init, item);
167
+ };
168
+ };
169
+
170
+ /**
171
+ * Create a transducer that inserts `separator` between consecutive
172
+ * emitted items -- not before the first item, and not after the last.
173
+ * @kind function
174
+ * @name interpose
175
+ * @param separator value inserted between items
176
+ * @returns transducer
177
+ */
178
+ export const interpose =
179
+ <In, Sep = In>(separator: Sep): Transducer<In, In | Sep> =>
180
+ (conjoin) => {
181
+ let started = false;
182
+ return (init, item) => {
183
+ if (started) {
184
+ const out = conjoin(init, separator);
185
+ if (out === HALT) {
186
+ return HALT;
187
+ }
188
+ init = out;
189
+ }
190
+ started = true;
191
+ return conjoin(init, item);
192
+ };
193
+ };
194
+
195
+ /**
196
+ * Create a transducer that groups consecutive items sharing a key into
197
+ * arrays, emitting each completed group as soon as the key changes.
198
+ * The transducer-pipeline counterpart to groupBySync/groupByAsync
199
+ * (itertools.ts); distinct from `group`, above, which chunks by a fixed
200
+ * size rather than by a shared key. Any trailing group is flushed when the
201
+ * source iterator completes, following the same `.complete` protocol as
202
+ * `group`.
203
+ * @kind function
204
+ * @name partitionBy
205
+ * @param keyFn maps an item to the key runs are grouped by
206
+ * @returns transducer
207
+ * @see groupBySync
208
+ * @see group
209
+ */
210
+ export const partitionBy =
211
+ <In>(keyFn: (item: In) => unknown = (x) => x): Transducer<In, In[]> =>
212
+ (conjoin) => {
213
+ let hasCurrent = false;
214
+ let currentKey: unknown;
215
+ let bucket: In[] = [];
216
+ const step: ReducerStep<In> = (init, item) => {
217
+ const key = keyFn(item);
218
+ if (!hasCurrent) {
219
+ hasCurrent = true;
220
+ currentKey = key;
221
+ bucket = [item];
222
+ return init;
223
+ }
224
+ if (Object.is(key, currentKey)) {
225
+ bucket.push(item);
226
+ return init;
227
+ }
228
+ const out = conjoin(init, bucket);
229
+ currentKey = key;
230
+ bucket = [item];
231
+ return out;
232
+ };
233
+ step.complete = (init) => {
234
+ const out = bucket.length > 0 ? conjoin(init, bucket) : init;
235
+ const buffer = out === HALT ? init : out;
236
+ return conjoin.complete ? conjoin.complete(buffer) : buffer;
237
+ };
238
+ return step;
239
+ };
240
+
241
+ /**
242
+ * Create a transducer that calls `fn(item)` for its side effect and passes
243
+ * the item through unchanged. RxJS-style, for debugging/instrumenting a
244
+ * pipeline without altering its values.
245
+ * @kind function
246
+ * @name tap
247
+ * @param fn called with each item, for side effects
248
+ * @returns transducer
249
+ */
250
+ export const tap =
251
+ <In>(fn: (item: In) => unknown): Transducer<In, In> =>
252
+ (conjoin) =>
253
+ (init, item) => {
254
+ fn(item);
255
+ return conjoin(init, item);
256
+ };