@jarenjs/linq 0.34.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/src/async.js ADDED
@@ -0,0 +1,599 @@
1
+ //@ts-check
2
+ /**
3
+ * @file `fromAsync` — the SAME operator surface over async sources,
4
+ * emitting the SAME query documents (the D5 proof is a byte-identity
5
+ * test), with terminals returning promises. The rule: the pipeline is
6
+ * synchronous, the boundaries are async — a compiled Jaren query never
7
+ * awaits; what is asynchronous is where rows come from (`fromAsync`
8
+ * sources, providers) and where element-wise work happens (`mapAsync`,
9
+ * the ONE bounded-concurrency boundary).
10
+ *
11
+ * Execution walks the stage list over the item stream:
12
+ *
13
+ * - STREAMABLE stages (`where`, `select`/`selectMany`, `skip`,
14
+ * `take`, `distinct`, `ofType`, `cast`, `defaultIfEmpty`,
15
+ * constant `concat`) apply per item through per-stage compiled
16
+ * evaluators (`{$let: {it: '$'}}` documents — the engine itself, one
17
+ * item at a time), so memory stays flat.
18
+ * - BARRIER stages (`orderBy`, `groupBy`, `join`, `aggregate`,
19
+ * `reverse`) materialise: the stream so far is buffered and the
20
+ * maximal run of document stages executes through the sync engine
21
+ * over the buffer — inherent (the engine itself materialises for
22
+ * `$orderby`/`$groupby`), not incidental, and `explain()` names the
23
+ * forcing operator.
24
+ * - `mapAsync` applies the bounded-concurrency machinery between
25
+ * segments; it is NOT translatable to a document, so `toDocument()`
26
+ * refuses (`JL0005`) and `explain()` reports the split.
27
+ *
28
+ * Early termination CLOSES the source: every consumer is a
29
+ * `for await … break` chain, and async generators propagate `return()`
30
+ * inward — a generator left suspended would hold a file handle or a
31
+ * read transaction open.
32
+ */
33
+
34
+ import { compileDocument } from './provider.js';
35
+ import { emitDocument, wrapTerminal } from './document.js';
36
+ import { adaptAsyncSource } from './sources.js';
37
+ import { applyMapAsync, normalizeMapAsyncOptions } from './concurrency.js';
38
+ import { captureExpression, toExpression } from './expression.js';
39
+ import { LinqBuildError, LinqRuntimeError } from './errors.js';
40
+ import { semanticKey } from '@jarenjs/core/object';
41
+
42
+ /** Barrier stage kinds and the reason each materialises. */
43
+ const BARRIERS = {
44
+ orderBy: '$orderby materialises the tuple stream to sort it',
45
+ thenBy: '$orderby materialises the tuple stream to sort it',
46
+ groupBy: '$groupby materialises the tuple stream to group it',
47
+ join: 'a join needs the whole inner side',
48
+ aggregate: '$fold folds the whole stream into one value',
49
+ reverse: '$reverse needs the last item first',
50
+ };
51
+
52
+ /**
53
+ * The distinct/grouping key.
54
+ *
55
+ * ONE relation, shared with the synchronous query engine: the async
56
+ * operators must not decide two items are the same when a sync
57
+ * `distinct()` over the same data keeps them apart. The hand-rolled
58
+ * serializer this replaced had its own idea of sameness, and conflated
59
+ * `Infinity` with `null` (both stringify to `null`), `NaN` with the
60
+ * sentinel STRING it substituted for `NaN` (which a real string could
61
+ * therefore forge), and any two objects carrying an own `__proto__`.
62
+ * `semanticKey` is the suite's injective identity, so the async side uses
63
+ * that — and for a value it refuses to key, falls back to the ITEM'S OWN
64
+ * identity rather than a shared bucket.
65
+ * @param {any} value
66
+ * @returns {string | object}
67
+ */
68
+ function itemKey(value) {
69
+ try {
70
+ return semanticKey(value);
71
+ }
72
+ catch {
73
+ // a Date, a Map, a class instance, a cycle: not keyable as data, so
74
+ // it counts as distinct from everything, including another one like
75
+ // it. That is the safe direction — folding two together would drop a
76
+ // row `distinct()` was asked to keep.
77
+ return { unkeyable: value };
78
+ }
79
+ }
80
+
81
+ /** The deferred async sequence. Construct via `fromAsync` or
82
+ * `Sequence.prototype.mapAsync`. */
83
+ export class AsyncSequence {
84
+ #origin;
85
+ #stages;
86
+ #params;
87
+ #options;
88
+
89
+ /**
90
+ * @param {{ kind: 'source', iterate: () => AsyncIterator<any> }
91
+ * | { kind: 'sequence', runPrefix: () => any[], prefixDocument: () => any }} origin
92
+ * @param {readonly any[]} stages
93
+ * @param {ReadonlyMap<string, any>} params
94
+ * @param {{ compileTypeTest?: any }} options
95
+ */
96
+ constructor(origin, stages, params, options) {
97
+ this.#origin = origin;
98
+ this.#stages = stages;
99
+ this.#params = params;
100
+ this.#options = options;
101
+ }
102
+
103
+ /** @param {any} stage */
104
+ #with(stage) {
105
+ return new AsyncSequence(this.#origin, [...this.#stages, stage], this.#params, this.#options);
106
+ }
107
+
108
+ #externals() {
109
+ return {
110
+ names: [...this.#params.keys()],
111
+ values: Object.fromEntries(this.#params),
112
+ };
113
+ }
114
+
115
+ /** Compile one per-item evaluator: the stage expression under
116
+ * `{$let: {it: '$'}}` with the terminal-window discipline. */
117
+ #evaluator(returnExpr) {
118
+ const { names } = this.#externals();
119
+ return compileDocument({ $let: { it: '$' }, $return: returnExpr },
120
+ { ...this.#options, externals: names });
121
+ }
122
+
123
+ //#region the operator surface (same vocabulary as Sequence)
124
+
125
+ where(predicate) { return this.#chainCaptured('where', 'predicate', predicate); }
126
+ select(projection) { return this.#chainCaptured('select', 'projection', projection); }
127
+ selectMany(selector) { return this.#chainCaptured('select', 'projection', selector); }
128
+
129
+ /** @param {string} kind @param {string} slot @param {any} fn */
130
+ #chainCaptured(kind, slot, fn) {
131
+ // reuse the sync Sequence's capture through a local import-free
132
+ // seam: capture lives in expression.js and is stage-agnostic
133
+ return this.#with({ kind, [slot]: captureFor(this.#params, fn) });
134
+ }
135
+
136
+ orderBy(key, options) { return this.#orderStage('orderBy', key, false, options); }
137
+ orderByDescending(key, options) { return this.#orderStage('orderBy', key, true, options); }
138
+ thenBy(key, options) { return this.#orderStage('thenBy', key, false, options); }
139
+ thenByDescending(key, options) { return this.#orderStage('thenBy', key, true, options); }
140
+
141
+ #orderStage(kind, key, desc, options) {
142
+ const spec = { $key: captureFor(this.#params, key) };
143
+ if (desc) spec.$dir = 'desc';
144
+ if (options !== undefined) {
145
+ if (options.empty !== undefined) spec.$empty = options.empty;
146
+ if (options.collation !== undefined) spec.$collation = options.collation;
147
+ }
148
+ return this.#with({ kind, spec });
149
+ }
150
+
151
+ groupBy(key) { return this.#with({ kind: 'groupBy', key: captureFor(this.#params, key) }); }
152
+
153
+ aggregate(seed, step) {
154
+ return this.#with({
155
+ kind: 'aggregate',
156
+ seed: toExpression(seed),
157
+ step: captureFor(this.#params, step, ['acc', 'it']),
158
+ });
159
+ }
160
+
161
+ skip(count) { return this.#with({ kind: 'skip', count: requireIndex(count, 'skip') }); }
162
+ take(count) { return this.#with({ kind: 'take', count: requireIndex(count, 'take') }); }
163
+ distinct() { return this.#with({ kind: 'distinct' }); }
164
+ reverse() { return this.#with({ kind: 'reverse' }); }
165
+
166
+ /** Async sources are single-pass, so only a CONSTANT array can join
167
+ * the stream (`JL0005` otherwise; the format doc says why). */
168
+ concat(other) {
169
+ if (!Array.isArray(other)) {
170
+ throw new LinqBuildError('JL0005',
171
+ 'concat on an async sequence takes a constant array — an async source cannot be re-iterated for a second sequence');
172
+ }
173
+ return this.#with({
174
+ kind: 'concat',
175
+ other: { $for: { it: { $const: other } }, $return: '$it' },
176
+ items: other,
177
+ });
178
+ }
179
+
180
+ defaultIfEmpty(fallback = null) {
181
+ return this.#with({ kind: 'defaultIfEmpty', fallback: toExpression(fallback), value: fallback });
182
+ }
183
+
184
+ ofType(schema) { return this.#with({ kind: 'ofType', schema }); }
185
+ cast(schema) { return this.#with({ kind: 'cast', schema }); }
186
+
187
+ zip() {
188
+ throw new LinqBuildError('JL0006',
189
+ 'zip is unsupported: the query grammar has no positional co-iteration (see LINQ-FORMAT.md §4)');
190
+ }
191
+
192
+ /** The bounded-concurrency boundary (LINQ-FORMAT.md §11). */
193
+ mapAsync(fn, options) {
194
+ if (typeof fn !== 'function') {
195
+ throw new LinqBuildError('JL0005', 'mapAsync takes an async callback');
196
+ }
197
+ return this.#with({ kind: 'mapAsync', fn, options: normalizeMapAsyncOptions(options) });
198
+ }
199
+
200
+ params(bindings) {
201
+ validateParams(bindings);
202
+ const merged = new Map(this.#params);
203
+ for (const name of Object.keys(bindings)) merged.set(name, bindings[name]);
204
+ return new AsyncSequence(this.#origin, this.#stages, merged, this.#options);
205
+ }
206
+
207
+ //#endregion
208
+
209
+ //#region documents and reporting
210
+
211
+ /** The chain as one query document — refuses when a `mapAsync` sits
212
+ * in the chain, because a host callback has no document form. */
213
+ toDocument() {
214
+ if (this.#stages.some((s) => s.kind === 'mapAsync')) {
215
+ throw new LinqBuildError('JL0005',
216
+ 'toDocument() cannot represent mapAsync (a host callback); explain() reports the split');
217
+ }
218
+ const root = this.#origin.kind === 'sequence'
219
+ ? this.#origin.prefixDocument()
220
+ : '$[*]';
221
+ return emitDocument(root, this.#stages);
222
+ }
223
+
224
+ /** Barriers, the split, and — when representable — the document. */
225
+ explain() {
226
+ const barriers = [];
227
+ for (const stage of this.#stages) {
228
+ if (BARRIERS[stage.kind] !== undefined) {
229
+ barriers.push({ operator: stage.kind, reason: BARRIERS[stage.kind] });
230
+ }
231
+ }
232
+ const firstMap = this.#stages.findIndex((s) => s.kind === 'mapAsync');
233
+ const out = { barriers };
234
+ if (firstMap < 0) {
235
+ out.document = this.toDocument();
236
+ }
237
+ else {
238
+ const prefix = this.#stages.slice(0, firstMap);
239
+ out.split = {
240
+ pushed: this.#origin.kind === 'sequence'
241
+ ? this.#origin.prefixDocument()
242
+ : emitDocument('$[*]', prefix),
243
+ residual: this.#stages.slice(firstMap).map((s) => s.kind),
244
+ };
245
+ }
246
+ return out;
247
+ }
248
+
249
+ //#endregion
250
+
251
+ //#region execution
252
+
253
+ /** The item stream: segments of streamable stages around engine-run
254
+ * barrier chunks. @returns {AsyncGenerator<any>} */
255
+ async* [Symbol.asyncIterator]() {
256
+ const { names, values } = this.#externals();
257
+ let stream = this.#origin.kind === 'sequence'
258
+ ? arrayStream(this.#origin.runPrefix())
259
+ : this.#origin.iterate();
260
+
261
+ const stages = this.#stages;
262
+ let i = 0;
263
+ while (i < stages.length) {
264
+ const stage = stages[i];
265
+ if (BARRIERS[stage.kind] !== undefined) {
266
+ // the maximal run of document stages executes over the buffer
267
+ let j = i;
268
+ while (j < stages.length && stages[j].kind !== 'mapAsync') j++;
269
+ // the same RESULT-WINDOW discipline the synchronous terminals use:
270
+ // the engine maps a result to `undefined | item | items[]`, so one
271
+ // array-valued item is indistinguishable from several scalar ones
272
+ // and a barrier read `[[1,2]]` back as `[1,2]`. Wrapping the phrase
273
+ // makes the result exactly one item — the array of items — so a
274
+ // nested array round-trips through every barrier unchanged.
275
+ const doc = wrapTerminal(emitDocument('$[*]', stages.slice(i, j)), 'toArray');
276
+ const compiled = compileDocument(doc, { ...this.#options, externals: names });
277
+ const buffer = await collect(stream);
278
+ stream = arrayStream(compiled([...buffer], values) ?? []);
279
+ i = j;
280
+ continue;
281
+ }
282
+ if (stage.kind === 'mapAsync') {
283
+ stream = applyMapAsync(stream, stage.fn, stage.options);
284
+ i++;
285
+ continue;
286
+ }
287
+ stream = this.#applyStreamStage(stream, stage, values);
288
+ i++;
289
+ }
290
+ yield* iterateAndClose(stream);
291
+ }
292
+
293
+ /** @param {AsyncIterator<any>} stream @param {any} stage @param {any} values */
294
+ #applyStreamStage(stream, stage, values) {
295
+ const self = this;
296
+ switch (stage.kind) {
297
+ case 'where': {
298
+ const q = this.#evaluator(stage.predicate);
299
+ return (async function* () {
300
+ for await (const item of iterateAndClose(stream)) {
301
+ if (q.ebv(item, values)) yield item;
302
+ }
303
+ })();
304
+ }
305
+ case 'select': {
306
+ const q = this.#evaluator([stage.projection]);
307
+ return (async function* () {
308
+ for await (const item of iterateAndClose(stream)) {
309
+ yield* /** @type {any[]} */ (q(item, values));
310
+ }
311
+ })();
312
+ }
313
+ case 'ofType': {
314
+ const q = this.#evaluator({ $valid: ['$it', stage.schema] });
315
+ return (async function* () {
316
+ for await (const item of iterateAndClose(stream)) {
317
+ if (q(item, values) === true) yield item;
318
+ }
319
+ })();
320
+ }
321
+ case 'cast': {
322
+ const q = this.#evaluator([{ $assert: ['$it', stage.schema] }]);
323
+ return (async function* () {
324
+ for await (const item of iterateAndClose(stream)) {
325
+ yield* /** @type {any[]} */ (q(item, values));
326
+ }
327
+ })();
328
+ }
329
+ case 'skip': {
330
+ return (async function* () {
331
+ let remaining = stage.count;
332
+ for await (const item of iterateAndClose(stream)) {
333
+ if (remaining > 0) { remaining--; continue; }
334
+ yield item;
335
+ }
336
+ })();
337
+ }
338
+ case 'take': {
339
+ return (async function* () {
340
+ if (stage.count === 0) {
341
+ if (typeof stream.return === 'function') await stream.return(undefined);
342
+ return;
343
+ }
344
+ let remaining = stage.count;
345
+ for await (const item of iterateAndClose(stream)) {
346
+ yield item;
347
+ if (--remaining === 0) break; // for-await break closes the source
348
+ }
349
+ })();
350
+ }
351
+ case 'distinct': {
352
+ return (async function* () {
353
+ const seen = new Set();
354
+ for await (const item of iterateAndClose(stream)) {
355
+ const key = itemKey(item);
356
+ if (seen.has(key)) continue;
357
+ seen.add(key);
358
+ yield item;
359
+ }
360
+ })();
361
+ }
362
+ case 'concat': {
363
+ return (async function* () {
364
+ yield* iterateAndClose(stream);
365
+ yield* stage.items;
366
+ })();
367
+ }
368
+ default: { // 'defaultIfEmpty'
369
+ void self;
370
+ return (async function* () {
371
+ let any = false;
372
+ for await (const item of iterateAndClose(stream)) {
373
+ any = true;
374
+ yield item;
375
+ }
376
+ if (!any) yield stage.value ?? null;
377
+ })();
378
+ }
379
+ }
380
+ }
381
+
382
+ async toArray() {
383
+ return collect(this[Symbol.asyncIterator]());
384
+ }
385
+
386
+ /** @param {number} n */
387
+ async #window(n) {
388
+ const out = [];
389
+ for await (const item of this) {
390
+ out.push(item);
391
+ if (out.length === n) break;
392
+ }
393
+ return out;
394
+ }
395
+
396
+ async first() {
397
+ const w = await this.#window(1);
398
+ if (w.length === 0) throw new LinqRuntimeError('JL2001', 'first() found no element');
399
+ return w[0];
400
+ }
401
+
402
+ /** @param {any} [defaultValue] */
403
+ async firstOrDefault(defaultValue) {
404
+ const w = await this.#window(1);
405
+ return w.length === 0 ? defaultValue : w[0];
406
+ }
407
+
408
+ async single() {
409
+ const w = await this.#window(2);
410
+ if (w.length === 0) throw new LinqRuntimeError('JL2001', 'single() found no element');
411
+ if (w.length > 1) throw new LinqRuntimeError('JL2002', 'single() found more than one element');
412
+ return w[0];
413
+ }
414
+
415
+ /** @param {any} [defaultValue] */
416
+ async singleOrDefault(defaultValue) {
417
+ const w = await this.#window(2);
418
+ if (w.length > 1) throw new LinqRuntimeError('JL2002', 'singleOrDefault() found more than one element');
419
+ return w.length === 0 ? defaultValue : w[0];
420
+ }
421
+
422
+ async last() {
423
+ const all = await this.toArray();
424
+ if (all.length === 0) throw new LinqRuntimeError('JL2001', 'last() found no element');
425
+ return all[all.length - 1];
426
+ }
427
+
428
+ /** @param {any} [defaultValue] */
429
+ async lastOrDefault(defaultValue) {
430
+ const all = await this.toArray();
431
+ return all.length === 0 ? defaultValue : all[all.length - 1];
432
+ }
433
+
434
+ /** @param {number} index */
435
+ async elementAt(index) {
436
+ requireIndex(index, 'elementAt');
437
+ const w = await this.skip(index).#window(1);
438
+ if (w.length === 0) throw new LinqRuntimeError('JL2003', `elementAt(${index}) is out of range`);
439
+ return w[0];
440
+ }
441
+
442
+ /** @param {number} index @param {any} [defaultValue] */
443
+ async elementAtOrDefault(index, defaultValue) {
444
+ requireIndex(index, 'elementAtOrDefault');
445
+ const w = await this.skip(index).#window(1);
446
+ return w.length === 0 ? defaultValue : w[0];
447
+ }
448
+
449
+ async count() {
450
+ let n = 0;
451
+ // eslint-disable-next-line no-unused-vars
452
+ for await (const item of this) n++;
453
+ return n;
454
+ }
455
+
456
+ /** Aggregate terminals run the ENGINE over the collected items, so
457
+ * their semantics (type errors included) match the sync surface
458
+ * exactly. @param {string} terminal */
459
+ async #aggregateTerminal(terminal) {
460
+ const items = await this.toArray();
461
+ const { names, values } = this.#externals();
462
+ const compiled = compileDocument(wrapTerminal('$[*]', terminal),
463
+ { ...this.#options, externals: names });
464
+ return compiled(items, values);
465
+ }
466
+
467
+ async sum() { return this.#aggregateTerminal('sum'); }
468
+
469
+ async average() {
470
+ const v = await this.#aggregateTerminal('average');
471
+ if (v === undefined) throw new LinqRuntimeError('JL2001', 'average() of an empty sequence');
472
+ return v;
473
+ }
474
+
475
+ async min() {
476
+ const v = await this.#aggregateTerminal('min');
477
+ if (v === undefined) throw new LinqRuntimeError('JL2001', 'min() of an empty sequence');
478
+ return v;
479
+ }
480
+
481
+ async max() {
482
+ const v = await this.#aggregateTerminal('max');
483
+ if (v === undefined) throw new LinqRuntimeError('JL2001', 'max() of an empty sequence');
484
+ return v;
485
+ }
486
+
487
+ /** @param {any} [predicate] */
488
+ async any(predicate) {
489
+ const seq = predicate === undefined ? this : this.where(predicate);
490
+ for await (const item of seq) {
491
+ void item;
492
+ return true; // for-await return closes the source
493
+ }
494
+ return false;
495
+ }
496
+
497
+ /** @param {any} predicate */
498
+ async all(predicate) {
499
+ const q = this.#evaluator(captureFor(this.#params, predicate));
500
+ for await (const item of this) {
501
+ if (!q.ebv(item, this.#externals().values)) return false;
502
+ }
503
+ return true;
504
+ }
505
+
506
+ //#endregion
507
+ }
508
+
509
+ //#region helpers (module-private)
510
+
511
+ /** @param {any[]} items */
512
+ async function* arrayStream(items) {
513
+ yield* items;
514
+ }
515
+
516
+ /** Wrap a bare iterator so `for await` semantics (close-on-break,
517
+ * close-on-throw) apply uniformly. @param {AsyncIterator<any>} iterator */
518
+ function iterateAndClose(iterator) {
519
+ return {
520
+ [Symbol.asyncIterator]() {
521
+ return iterator;
522
+ },
523
+ };
524
+ }
525
+
526
+ /** @param {AsyncIterator<any> | AsyncIterable<any>} stream */
527
+ async function collect(stream) {
528
+ const out = [];
529
+ const iterable = typeof (/** @type {any} */ (stream))[Symbol.asyncIterator] === 'function'
530
+ ? /** @type {AsyncIterable<any>} */ (stream)
531
+ : iterateAndClose(/** @type {AsyncIterator<any>} */ (stream));
532
+ for await (const item of iterable) out.push(item);
533
+ return out;
534
+ }
535
+
536
+ /** @param {number} value @param {string} what */
537
+ function requireIndex(value, what) {
538
+ if (!Number.isInteger(value) || value < 0) {
539
+ throw new LinqBuildError('JL0005', `${what} takes a non-negative integer, got ${value}`);
540
+ }
541
+ return value;
542
+ }
543
+
544
+ /** @param {ReadonlyMap<string, any>} params @param {any} fn @param {readonly any[]} [roots] */
545
+ function captureFor(params, fn, roots = ['it']) {
546
+ if (typeof fn !== 'function') {
547
+ throw new LinqBuildError('JL0005', 'this operator takes a callback function');
548
+ }
549
+ return captureExpression(fn, roots, new Set(params.keys()));
550
+ }
551
+
552
+ /** @param {any} bindings */
553
+ function validateParams(bindings) {
554
+ if (bindings === null || typeof bindings !== 'object' || Array.isArray(bindings)) {
555
+ throw new LinqBuildError('JL0004', 'params takes an object of name → value bindings');
556
+ }
557
+ for (const name of Object.keys(bindings)) {
558
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
559
+ throw new LinqBuildError('JL0004', `'${name}' is not a valid parameter name`);
560
+ }
561
+ if (name === 'it' || name === 'it2' || name === 'acc' || name === 'g') {
562
+ throw new LinqBuildError('JL0004',
563
+ `'${name}' is reserved (the emitted document's own binding names: it, it2, acc, g)`);
564
+ }
565
+ }
566
+ }
567
+
568
+ /**
569
+ * Build an async sequence over an async source (LINQ-FORMAT.md §10).
570
+ * @param {any} source - async iterable, sync iterable, cursor or push
571
+ * queue
572
+ * @param {{ compileTypeTest?: any }} [options]
573
+ * @returns {AsyncSequence}
574
+ */
575
+ export function fromAsync(source, options = {}) {
576
+ return new AsyncSequence(
577
+ { kind: 'source', iterate: adaptAsyncSource(source) },
578
+ [], new Map(), options);
579
+ }
580
+
581
+ /**
582
+ * The `Sequence.prototype.mapAsync` seam: the sync chain becomes the
583
+ * PREFIX (compiled in memory or pushed WHOLE to its provider), and the
584
+ * async surface continues locally from its rows. `explain()` reports
585
+ * the split (D8's residual honesty, applied to the async boundary).
586
+ * @param {{ runPrefix: () => any[], prefixDocument: () => any,
587
+ * params: ReadonlyMap<string, any>, options: { compileTypeTest?: any } }} carrier
588
+ * @param {any} fn
589
+ * @param {any} options
590
+ * @returns {AsyncSequence}
591
+ */
592
+ export function asyncFromSequence(carrier, fn, options) {
593
+ const base = new AsyncSequence(
594
+ { kind: 'sequence', runPrefix: carrier.runPrefix, prefixDocument: carrier.prefixDocument },
595
+ [], carrier.params, carrier.options);
596
+ return base.mapAsync(fn, options);
597
+ }
598
+
599
+ //#endregion