@panagos/jojoscript 0.7.0 → 0.8.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.
- package/README.md +429 -418
- package/bin/jojo.js +430 -170
- package/package.json +50 -59
- package/src/compiler.js +197 -169
- package/src/pipeline-hash.js +27 -0
- package/src/pipeline-ir.js +100 -0
- package/src/pipeline-parser.js +583 -418
- package/src/runtime/checkpoint-store.js +122 -0
- package/src/runtime/checkpoint.js +69 -0
- package/src/runtime/collections.js +438 -327
- package/src/runtime/errors.js +29 -0
- package/src/runtime/execution-context.js +86 -0
- package/src/runtime/index.js +51 -29
- package/src/runtime/inspect.js +62 -0
- package/src/runtime/metrics.js +41 -0
- package/src/runtime/retry.js +67 -0
- package/src/runtime/trace.js +66 -0
|
@@ -1,327 +1,438 @@
|
|
|
1
|
-
function iterable(value) {
|
|
2
|
-
if (value == null) {
|
|
3
|
-
throw new TypeError('Expected an iterable, got null or undefined')
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
if (typeof value[Symbol.iterator] === 'function') {
|
|
7
|
-
return value
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const iterator =
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export function
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
export function
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
if (seen.has(
|
|
193
|
-
seen.add(
|
|
194
|
-
yield item
|
|
195
|
-
}
|
|
196
|
-
})()
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
export function
|
|
200
|
-
return (function* () {
|
|
201
|
-
let index = 0
|
|
202
|
-
for (const item of iterable(items)) {
|
|
203
|
-
fn(item, index++)
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
export function
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
export function
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
1
|
+
function iterable(value) {
|
|
2
|
+
if (value == null) {
|
|
3
|
+
throw new TypeError('Expected an iterable, got null or undefined')
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
if (typeof value[Symbol.iterator] === 'function') {
|
|
7
|
+
return value
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
* An async generator/iterable (e.g. from `mapAsync`, `checkpoint`,
|
|
12
|
+
* `inspect`, or `traceNode` wrapping one of those) has `.next` too, but
|
|
13
|
+
* calling it synchronously here would silently loop forever comparing a
|
|
14
|
+
* Promise's `.done` (always `undefined`) instead of a real boolean. Fail
|
|
15
|
+
* fast with a clear message instead of hanging.
|
|
16
|
+
*/
|
|
17
|
+
if (typeof value[Symbol.asyncIterator] === 'function') {
|
|
18
|
+
throw new TypeError(
|
|
19
|
+
'Expected a sync iterable but got an async iterable/generator ' +
|
|
20
|
+
'(e.g. produced by mapAsync, filterAsync, checkpoint, inspect, or ' +
|
|
21
|
+
'retry on an async function). Use the async equivalent instead ' +
|
|
22
|
+
'(e.g. toArrayAsync, or `for await`).'
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (typeof value.next === 'function') {
|
|
27
|
+
return {
|
|
28
|
+
[Symbol.iterator]() {
|
|
29
|
+
return value
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
throw new TypeError('Expected an iterable or iterator')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/*
|
|
38
|
+
* Like `iterable`, but accepts (and prefers) an async iterable/iterator so
|
|
39
|
+
* `for await` correctly awaits values produced by async generators such as
|
|
40
|
+
* `mapAsync`/`filterAsync`, in addition to ordinary sync iterables.
|
|
41
|
+
*/
|
|
42
|
+
function asyncIterable(value) {
|
|
43
|
+
if (value == null) {
|
|
44
|
+
throw new TypeError('Expected an iterable, got null or undefined')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (
|
|
48
|
+
typeof value[Symbol.asyncIterator] === 'function' ||
|
|
49
|
+
typeof value[Symbol.iterator] === 'function'
|
|
50
|
+
) {
|
|
51
|
+
return value
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (typeof value.next === 'function') {
|
|
55
|
+
return {
|
|
56
|
+
[Symbol.asyncIterator]() {
|
|
57
|
+
return value
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
throw new TypeError('Expected an iterable or iterator')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const PARALLEL_TAG = Symbol('jojo.parallel')
|
|
66
|
+
|
|
67
|
+
/*
|
|
68
|
+
* Marks an iterable as wanting bounded-concurrency processing by the very
|
|
69
|
+
* next `map()` stage: `users |> parallel(8) |> map(fetchProfile)`. This
|
|
70
|
+
* does not itself run anything concurrently — it just tags the upstream
|
|
71
|
+
* iterable so `map()` knows to switch from sequential to a bounded worker
|
|
72
|
+
* pool. Any stage other than `map` receiving this tag is a usage error
|
|
73
|
+
* (the tag object has no iterator of its own).
|
|
74
|
+
*/
|
|
75
|
+
export function parallel(items, concurrency, meta) {
|
|
76
|
+
if (!Number.isInteger(concurrency) || concurrency <= 0) {
|
|
77
|
+
throw new RangeError('parallel concurrency must be a positive integer')
|
|
78
|
+
}
|
|
79
|
+
return { [PARALLEL_TAG]: concurrency, source: items, meta }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function isParallelTagged(value) {
|
|
83
|
+
return value != null && typeof value === 'object' && PARALLEL_TAG in value
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function getIterator(items) {
|
|
87
|
+
if (items == null) {
|
|
88
|
+
throw new TypeError('Expected an iterable, got null or undefined')
|
|
89
|
+
}
|
|
90
|
+
if (typeof items[Symbol.asyncIterator] === 'function') return items[Symbol.asyncIterator]()
|
|
91
|
+
if (typeof items[Symbol.iterator] === 'function') return items[Symbol.iterator]()
|
|
92
|
+
if (typeof items.next === 'function') return items
|
|
93
|
+
throw new TypeError('Expected an iterable or iterator')
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/*
|
|
97
|
+
* Bounded-concurrency mapping: at most `concurrency` calls to `fn` are ever
|
|
98
|
+
* in flight at once (no unbounded `Promise.all`), memory usage stays
|
|
99
|
+
* bounded by `concurrency`, and results are yielded in the original order
|
|
100
|
+
* as soon as the item at the head of the window resolves — later items in
|
|
101
|
+
* the window keep running concurrently in the background while the head
|
|
102
|
+
* is awaited.
|
|
103
|
+
*
|
|
104
|
+
* `pullNext()` returns the in-flight promise wrapped in a plain object
|
|
105
|
+
* (`{ promise }`) rather than the promise itself: since `pullNext` is an
|
|
106
|
+
* `async function`, directly returning/awaiting the bare `fn(...)` promise
|
|
107
|
+
* would make JS "flatten" it — `await pullNext()` would then already be
|
|
108
|
+
* the *resolved value* of `fn`, silently serializing every call (no two
|
|
109
|
+
* calls ever truly overlap) and, since a resolved value of `0`/`''`/etc.
|
|
110
|
+
* is falsy, incorrectly being treated as "no more items".
|
|
111
|
+
*/
|
|
112
|
+
async function* mapConcurrent(items, fn, concurrency) {
|
|
113
|
+
const iterator = getIterator(items)
|
|
114
|
+
const window = []
|
|
115
|
+
let index = 0
|
|
116
|
+
let exhausted = false
|
|
117
|
+
|
|
118
|
+
async function pullNext() {
|
|
119
|
+
if (exhausted) return undefined
|
|
120
|
+
const step = await iterator.next()
|
|
121
|
+
if (step.done) {
|
|
122
|
+
exhausted = true
|
|
123
|
+
return undefined
|
|
124
|
+
}
|
|
125
|
+
const i = index++
|
|
126
|
+
return { promise: Promise.resolve(fn(step.value, i)) }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
for (let i = 0; i < concurrency && !exhausted; i++) {
|
|
130
|
+
const started = await pullNext()
|
|
131
|
+
if (started) window.push(started)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
while (window.length) {
|
|
135
|
+
const head = window.shift()
|
|
136
|
+
yield await head.promise
|
|
137
|
+
if (!exhausted) {
|
|
138
|
+
const started = await pullNext()
|
|
139
|
+
if (started) window.push(started)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function map(items, fn) {
|
|
145
|
+
if (isParallelTagged(items)) {
|
|
146
|
+
return mapConcurrent(items.source, fn, items[PARALLEL_TAG])
|
|
147
|
+
}
|
|
148
|
+
return (function* () {
|
|
149
|
+
let index = 0
|
|
150
|
+
for (const item of iterable(items)) {
|
|
151
|
+
yield fn(item, index++)
|
|
152
|
+
}
|
|
153
|
+
})()
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function filter(items, fn) {
|
|
157
|
+
return (function* () {
|
|
158
|
+
let index = 0
|
|
159
|
+
for (const item of iterable(items)) {
|
|
160
|
+
if (fn(item, index++)) yield item
|
|
161
|
+
}
|
|
162
|
+
})()
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function take(items, count) {
|
|
166
|
+
return (function* () {
|
|
167
|
+
if (count <= 0) return
|
|
168
|
+
|
|
169
|
+
let emitted = 0
|
|
170
|
+
for (const item of iterable(items)) {
|
|
171
|
+
yield item
|
|
172
|
+
emitted++
|
|
173
|
+
if (emitted >= count) return
|
|
174
|
+
}
|
|
175
|
+
})()
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function skip(items, count) {
|
|
179
|
+
return (function* () {
|
|
180
|
+
let index = 0
|
|
181
|
+
for (const item of iterable(items)) {
|
|
182
|
+
if (index++ < count) continue
|
|
183
|
+
yield item
|
|
184
|
+
}
|
|
185
|
+
})()
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function unique(items) {
|
|
189
|
+
return (function* () {
|
|
190
|
+
const seen = new Set()
|
|
191
|
+
for (const item of iterable(items)) {
|
|
192
|
+
if (seen.has(item)) continue
|
|
193
|
+
seen.add(item)
|
|
194
|
+
yield item
|
|
195
|
+
}
|
|
196
|
+
})()
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function flatMap(items, fn) {
|
|
200
|
+
return (function* () {
|
|
201
|
+
let index = 0
|
|
202
|
+
for (const item of iterable(items)) {
|
|
203
|
+
for (const value of iterable(fn(item, index++))) {
|
|
204
|
+
yield value
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
})()
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function reduce(items, fn, initial) {
|
|
211
|
+
const iterator = iterable(items)[Symbol.iterator]()
|
|
212
|
+
let accumulator
|
|
213
|
+
|
|
214
|
+
if (arguments.length >= 3) {
|
|
215
|
+
accumulator = initial
|
|
216
|
+
} else {
|
|
217
|
+
const first = iterator.next()
|
|
218
|
+
if (first.done) {
|
|
219
|
+
throw new TypeError('Reduce of empty iterable with no initial value')
|
|
220
|
+
}
|
|
221
|
+
accumulator = first.value
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
let index = arguments.length >= 3 ? 0 : 1
|
|
225
|
+
for (let step = iterator.next(); !step.done; step = iterator.next()) {
|
|
226
|
+
accumulator = fn(accumulator, step.value, index++)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return accumulator
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function toArray(items) {
|
|
233
|
+
return Array.from(iterable(items))
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function count(items) {
|
|
237
|
+
let result = 0
|
|
238
|
+
for (const _ of iterable(items)) result++
|
|
239
|
+
return result
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function first(items) {
|
|
243
|
+
for (const item of iterable(items)) return item
|
|
244
|
+
return undefined
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function find(items, predicate) {
|
|
248
|
+
let index = 0
|
|
249
|
+
for (const item of iterable(items)) {
|
|
250
|
+
if (predicate(item, index++)) return item
|
|
251
|
+
}
|
|
252
|
+
return undefined
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export function some(items, predicate) {
|
|
256
|
+
let index = 0
|
|
257
|
+
for (const item of iterable(items)) {
|
|
258
|
+
if (predicate(item, index++)) return true
|
|
259
|
+
}
|
|
260
|
+
return false
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function every(items, predicate) {
|
|
264
|
+
let index = 0
|
|
265
|
+
for (const item of iterable(items)) {
|
|
266
|
+
if (!predicate(item, index++)) return false
|
|
267
|
+
}
|
|
268
|
+
return true
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function sort(items, fn) {
|
|
272
|
+
return Array.from(iterable(items)).sort(fn)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function sortBy(items, keyFn) {
|
|
276
|
+
return Array.from(iterable(items)).sort((a, b) => {
|
|
277
|
+
const keyA = keyFn(a)
|
|
278
|
+
const keyB = keyFn(b)
|
|
279
|
+
if (keyA < keyB) return -1
|
|
280
|
+
if (keyA > keyB) return 1
|
|
281
|
+
return 0
|
|
282
|
+
})
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export function distinctBy(items, keyFn) {
|
|
286
|
+
return (function* () {
|
|
287
|
+
const seen = new Set()
|
|
288
|
+
for (const item of iterable(items)) {
|
|
289
|
+
const key = keyFn(item)
|
|
290
|
+
if (seen.has(key)) continue
|
|
291
|
+
seen.add(key)
|
|
292
|
+
yield item
|
|
293
|
+
}
|
|
294
|
+
})()
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export function tap(items, fn) {
|
|
298
|
+
return (function* () {
|
|
299
|
+
let index = 0
|
|
300
|
+
for (const item of iterable(items)) {
|
|
301
|
+
fn(item, index++)
|
|
302
|
+
yield item
|
|
303
|
+
}
|
|
304
|
+
})()
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export function chunk(items, size) {
|
|
308
|
+
if (!Number.isInteger(size) || size <= 0) {
|
|
309
|
+
throw new RangeError('chunk size must be a positive integer')
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return (function* () {
|
|
313
|
+
let buffer = []
|
|
314
|
+
for (const item of iterable(items)) {
|
|
315
|
+
buffer.push(item)
|
|
316
|
+
if (buffer.length === size) {
|
|
317
|
+
yield buffer
|
|
318
|
+
buffer = []
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (buffer.length) yield buffer
|
|
322
|
+
})()
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/*
|
|
326
|
+
* Groups a lazy/streaming source into fixed-size arrays, remaining lazy —
|
|
327
|
+
* items are never buffered beyond the current, not-yet-full batch, which
|
|
328
|
+
* is exactly the bounded buffering §13 (backpressure) requires. Equivalent
|
|
329
|
+
* to `chunk`, exposed under the pipeline's own vocabulary.
|
|
330
|
+
*/
|
|
331
|
+
export function batch(items, size, meta) {
|
|
332
|
+
if (!Number.isInteger(size) || size <= 0) {
|
|
333
|
+
throw new RangeError('batch size must be a positive integer')
|
|
334
|
+
}
|
|
335
|
+
return chunk(items, size)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export function window(items, size) {
|
|
339
|
+
if (!Number.isInteger(size) || size <= 0) {
|
|
340
|
+
throw new RangeError('window size must be a positive integer')
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return (function* () {
|
|
344
|
+
let buffer = []
|
|
345
|
+
for (const item of iterable(items)) {
|
|
346
|
+
buffer.push(item)
|
|
347
|
+
if (buffer.length > size) buffer.shift()
|
|
348
|
+
if (buffer.length === size) yield buffer.slice()
|
|
349
|
+
}
|
|
350
|
+
})()
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export function zip(items, other) {
|
|
354
|
+
return (function* () {
|
|
355
|
+
const left = iterable(items)[Symbol.iterator]()
|
|
356
|
+
const right = iterable(other)[Symbol.iterator]()
|
|
357
|
+
|
|
358
|
+
while (true) {
|
|
359
|
+
const a = left.next()
|
|
360
|
+
const b = right.next()
|
|
361
|
+
if (a.done || b.done) return
|
|
362
|
+
yield [a.value, b.value]
|
|
363
|
+
}
|
|
364
|
+
})()
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export function zipWith(items, other, fn) {
|
|
368
|
+
return (function* () {
|
|
369
|
+
const left = iterable(items)[Symbol.iterator]()
|
|
370
|
+
const right = iterable(other)[Symbol.iterator]()
|
|
371
|
+
|
|
372
|
+
while (true) {
|
|
373
|
+
const a = left.next()
|
|
374
|
+
const b = right.next()
|
|
375
|
+
if (a.done || b.done) return
|
|
376
|
+
yield fn(a.value, b.value)
|
|
377
|
+
}
|
|
378
|
+
})()
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export function scan(items, fn, initial) {
|
|
382
|
+
return (function* () {
|
|
383
|
+
let accumulator = initial
|
|
384
|
+
let index = 0
|
|
385
|
+
for (const item of iterable(items)) {
|
|
386
|
+
accumulator = fn(accumulator, item, index++)
|
|
387
|
+
yield accumulator
|
|
388
|
+
}
|
|
389
|
+
})()
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
export function partition(items, predicate) {
|
|
393
|
+
const truthy = []
|
|
394
|
+
const falsy = []
|
|
395
|
+
let index = 0
|
|
396
|
+
|
|
397
|
+
for (const item of iterable(items)) {
|
|
398
|
+
if (predicate(item, index++)) {
|
|
399
|
+
truthy.push(item)
|
|
400
|
+
} else {
|
|
401
|
+
falsy.push(item)
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return [truthy, falsy]
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export function groupBy(items, keyFn) {
|
|
409
|
+
const groups = new Map()
|
|
410
|
+
|
|
411
|
+
for (const item of iterable(items)) {
|
|
412
|
+
const key = keyFn(item)
|
|
413
|
+
if (!groups.has(key)) groups.set(key, [])
|
|
414
|
+
groups.get(key).push(item)
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return groups
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
export async function* mapAsync(items, fn) {
|
|
421
|
+
let index = 0
|
|
422
|
+
for await (const item of asyncIterable(items)) {
|
|
423
|
+
yield await fn(item, index++)
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
export async function* filterAsync(items, fn) {
|
|
428
|
+
let index = 0
|
|
429
|
+
for await (const item of asyncIterable(items)) {
|
|
430
|
+
if (await fn(item, index++)) yield item
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
export async function toArrayAsync(items) {
|
|
435
|
+
const result = []
|
|
436
|
+
for await (const item of asyncIterable(items)) result.push(item)
|
|
437
|
+
return result
|
|
438
|
+
}
|