@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.
@@ -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
- if (typeof value.next === 'function') {
11
- return {
12
- [Symbol.iterator]() {
13
- return value
14
- }
15
- }
16
- }
17
-
18
- throw new TypeError('Expected an iterable or iterator')
19
- }
20
-
21
- /*
22
- * Like `iterable`, but accepts (and prefers) an async iterable/iterator so
23
- * `for await` correctly awaits values produced by async generators such as
24
- * `mapAsync`/`filterAsync`, in addition to ordinary sync iterables.
25
- */
26
- function asyncIterable(value) {
27
- if (value == null) {
28
- throw new TypeError('Expected an iterable, got null or undefined')
29
- }
30
-
31
- if (
32
- typeof value[Symbol.asyncIterator] === 'function' ||
33
- typeof value[Symbol.iterator] === 'function'
34
- ) {
35
- return value
36
- }
37
-
38
- if (typeof value.next === 'function') {
39
- return {
40
- [Symbol.asyncIterator]() {
41
- return value
42
- }
43
- }
44
- }
45
-
46
- throw new TypeError('Expected an iterable or iterator')
47
- }
48
-
49
- export function map(items, fn) {
50
- return (function* () {
51
- let index = 0
52
- for (const item of iterable(items)) {
53
- yield fn(item, index++)
54
- }
55
- })()
56
- }
57
-
58
- export function filter(items, fn) {
59
- return (function* () {
60
- let index = 0
61
- for (const item of iterable(items)) {
62
- if (fn(item, index++)) yield item
63
- }
64
- })()
65
- }
66
-
67
- export function take(items, count) {
68
- return (function* () {
69
- if (count <= 0) return
70
-
71
- let emitted = 0
72
- for (const item of iterable(items)) {
73
- yield item
74
- emitted++
75
- if (emitted >= count) return
76
- }
77
- })()
78
- }
79
-
80
- export function skip(items, count) {
81
- return (function* () {
82
- let index = 0
83
- for (const item of iterable(items)) {
84
- if (index++ < count) continue
85
- yield item
86
- }
87
- })()
88
- }
89
-
90
- export function unique(items) {
91
- return (function* () {
92
- const seen = new Set()
93
- for (const item of iterable(items)) {
94
- if (seen.has(item)) continue
95
- seen.add(item)
96
- yield item
97
- }
98
- })()
99
- }
100
-
101
- export function flatMap(items, fn) {
102
- return (function* () {
103
- let index = 0
104
- for (const item of iterable(items)) {
105
- for (const value of iterable(fn(item, index++))) {
106
- yield value
107
- }
108
- }
109
- })()
110
- }
111
-
112
- export function reduce(items, fn, initial) {
113
- const iterator = iterable(items)[Symbol.iterator]()
114
- let accumulator
115
-
116
- if (arguments.length >= 3) {
117
- accumulator = initial
118
- } else {
119
- const first = iterator.next()
120
- if (first.done) {
121
- throw new TypeError('Reduce of empty iterable with no initial value')
122
- }
123
- accumulator = first.value
124
- }
125
-
126
- let index = arguments.length >= 3 ? 0 : 1
127
- for (let step = iterator.next(); !step.done; step = iterator.next()) {
128
- accumulator = fn(accumulator, step.value, index++)
129
- }
130
-
131
- return accumulator
132
- }
133
-
134
- export function toArray(items) {
135
- return Array.from(iterable(items))
136
- }
137
-
138
- export function count(items) {
139
- let result = 0
140
- for (const _ of iterable(items)) result++
141
- return result
142
- }
143
-
144
- export function first(items) {
145
- for (const item of iterable(items)) return item
146
- return undefined
147
- }
148
-
149
- export function find(items, predicate) {
150
- let index = 0
151
- for (const item of iterable(items)) {
152
- if (predicate(item, index++)) return item
153
- }
154
- return undefined
155
- }
156
-
157
- export function some(items, predicate) {
158
- let index = 0
159
- for (const item of iterable(items)) {
160
- if (predicate(item, index++)) return true
161
- }
162
- return false
163
- }
164
-
165
- export function every(items, predicate) {
166
- let index = 0
167
- for (const item of iterable(items)) {
168
- if (!predicate(item, index++)) return false
169
- }
170
- return true
171
- }
172
-
173
- export function sort(items, fn) {
174
- return Array.from(iterable(items)).sort(fn)
175
- }
176
-
177
- export function sortBy(items, keyFn) {
178
- return Array.from(iterable(items)).sort((a, b) => {
179
- const keyA = keyFn(a)
180
- const keyB = keyFn(b)
181
- if (keyA < keyB) return -1
182
- if (keyA > keyB) return 1
183
- return 0
184
- })
185
- }
186
-
187
- export function distinctBy(items, keyFn) {
188
- return (function* () {
189
- const seen = new Set()
190
- for (const item of iterable(items)) {
191
- const key = keyFn(item)
192
- if (seen.has(key)) continue
193
- seen.add(key)
194
- yield item
195
- }
196
- })()
197
- }
198
-
199
- export function tap(items, fn) {
200
- return (function* () {
201
- let index = 0
202
- for (const item of iterable(items)) {
203
- fn(item, index++)
204
- yield item
205
- }
206
- })()
207
- }
208
-
209
- export function chunk(items, size) {
210
- if (!Number.isInteger(size) || size <= 0) {
211
- throw new RangeError('chunk size must be a positive integer')
212
- }
213
-
214
- return (function* () {
215
- let buffer = []
216
- for (const item of iterable(items)) {
217
- buffer.push(item)
218
- if (buffer.length === size) {
219
- yield buffer
220
- buffer = []
221
- }
222
- }
223
- if (buffer.length) yield buffer
224
- })()
225
- }
226
-
227
- export function window(items, size) {
228
- if (!Number.isInteger(size) || size <= 0) {
229
- throw new RangeError('window size must be a positive integer')
230
- }
231
-
232
- return (function* () {
233
- let buffer = []
234
- for (const item of iterable(items)) {
235
- buffer.push(item)
236
- if (buffer.length > size) buffer.shift()
237
- if (buffer.length === size) yield buffer.slice()
238
- }
239
- })()
240
- }
241
-
242
- export function zip(items, other) {
243
- return (function* () {
244
- const left = iterable(items)[Symbol.iterator]()
245
- const right = iterable(other)[Symbol.iterator]()
246
-
247
- while (true) {
248
- const a = left.next()
249
- const b = right.next()
250
- if (a.done || b.done) return
251
- yield [a.value, b.value]
252
- }
253
- })()
254
- }
255
-
256
- export function zipWith(items, other, fn) {
257
- return (function* () {
258
- const left = iterable(items)[Symbol.iterator]()
259
- const right = iterable(other)[Symbol.iterator]()
260
-
261
- while (true) {
262
- const a = left.next()
263
- const b = right.next()
264
- if (a.done || b.done) return
265
- yield fn(a.value, b.value)
266
- }
267
- })()
268
- }
269
-
270
- export function scan(items, fn, initial) {
271
- return (function* () {
272
- let accumulator = initial
273
- let index = 0
274
- for (const item of iterable(items)) {
275
- accumulator = fn(accumulator, item, index++)
276
- yield accumulator
277
- }
278
- })()
279
- }
280
-
281
- export function partition(items, predicate) {
282
- const truthy = []
283
- const falsy = []
284
- let index = 0
285
-
286
- for (const item of iterable(items)) {
287
- if (predicate(item, index++)) {
288
- truthy.push(item)
289
- } else {
290
- falsy.push(item)
291
- }
292
- }
293
-
294
- return [truthy, falsy]
295
- }
296
-
297
- export function groupBy(items, keyFn) {
298
- const groups = new Map()
299
-
300
- for (const item of iterable(items)) {
301
- const key = keyFn(item)
302
- if (!groups.has(key)) groups.set(key, [])
303
- groups.get(key).push(item)
304
- }
305
-
306
- return groups
307
- }
308
-
309
- export async function* mapAsync(items, fn) {
310
- let index = 0
311
- for await (const item of asyncIterable(items)) {
312
- yield await fn(item, index++)
313
- }
314
- }
315
-
316
- export async function* filterAsync(items, fn) {
317
- let index = 0
318
- for await (const item of asyncIterable(items)) {
319
- if (await fn(item, index++)) yield item
320
- }
321
- }
322
-
323
- export async function toArrayAsync(items) {
324
- const result = []
325
- for await (const item of asyncIterable(items)) result.push(item)
326
- return result
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
+ }