@aventara/client 1.0.0-rc.10 → 1.0.0-rc.11

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.
@@ -0,0 +1,604 @@
1
+ import { moduleSpecifierWriter, } from "./module-style.interface.js";
2
+ export function emitOperationSetModule(style, cached = false) {
3
+ return {
4
+ path: "runtime/operation-set.ts",
5
+ source: operationSetModuleSource(moduleSpecifierWriter(style), cached),
6
+ };
7
+ }
8
+ function operationSetModuleSource(from, cached) {
9
+ return `${cached ? `import { isCachedRead, sendCachedRead } from ${from("./cached-read")};\n` : ""}import { OPERATION_SET_LIMITS } from ${from("./descriptor")};
10
+ import {
11
+ type AvCallOptions,
12
+ type EntryOutcome,
13
+ operationSetBody,
14
+ operationSetEntry,
15
+ operationSetKey,
16
+ type PlatformSignal,
17
+ type PreparedOperation,
18
+ prepareOperation,
19
+ sendOperation,
20
+ sendOperationSet,
21
+ type TransportConnection,
22
+ } from ${from("./transport")};
23
+
24
+ /**
25
+ * The client's calls: sent when made, like fetch, and grouped.
26
+ *
27
+ * A call checks and encodes its arguments and is queued when it is made; a call
28
+ * never awaited still runs. The calls made in one tick go out together as one
29
+ * Operation Set (POST /_all), each answered on its own as its answer arrives; a
30
+ * call made alone goes to its own route. avLazy(() => call) makes a call only
31
+ * when it is first awaited. A call with headers or
32
+ * a request id, one made with grouping: false, every call of a client made with
33
+ * grouping: false, and a transaction are always sent alone${cached ? ", and a read the\n * ClientContract marks cache is sent alone as a GET" : ""}.
34
+ *
35
+ * A call's signal aborts that call only. Aborted before it is sent, it is never
36
+ * sent. Nothing is aborted for you: there is no default timeout.
37
+ */
38
+
39
+ /** What the queue reads of an AbortSignal, where the consumer's lib declares none. */
40
+ interface AbortSource {
41
+ readonly aborted: boolean;
42
+ readonly reason?: unknown;
43
+ addEventListener?(type: "abort", listener: () => void): void;
44
+ removeEventListener?(type: "abort", listener: () => void): void;
45
+ }
46
+
47
+ /** What the queue reads of an AbortController. */
48
+ interface AbortHandle {
49
+ readonly signal: AbortSource;
50
+ abort(reason?: unknown): void;
51
+ }
52
+
53
+ /** How a call is sent. */
54
+ type Dispatch =
55
+ | { readonly kind: "operation"; readonly operation: PreparedOperation; readonly groupable: boolean }
56
+ | { readonly kind: "alone"; readonly send: (signal: PlatformSignal | undefined) => Promise<unknown> }
57
+ | { readonly kind: "refused"; readonly error: unknown };
58
+
59
+ /** One call: how it is sent, what aborts it, and how it settles. */
60
+ interface CallRecord {
61
+ readonly dispatch: Dispatch;
62
+ /** Its own signal, and the signal of each \`all\` it is in. */
63
+ readonly sources: AbortSource[];
64
+ /** What an abort of one of its sources does while it waits in the queue or in a set. */
65
+ onAbort: ((reason: unknown) => void) | undefined;
66
+ settled: boolean;
67
+ resolve(value: unknown): void;
68
+ reject(error: unknown): void;
69
+ }
70
+
71
+ /** Every call of this tree, by the promise its caller holds. */
72
+ const CALLS = new WeakMap<object, CallRecord>();
73
+
74
+ /** The platform's AbortController, or undefined where there is none. */
75
+ function abortHandle(): AbortHandle | undefined {
76
+ const Platform = (globalThis as { readonly AbortController?: new () => AbortHandle }).AbortController;
77
+ return typeof Platform === "function" ? new Platform() : undefined;
78
+ }
79
+
80
+ /** What an aborted source rejects with: its reason, else an AbortError. */
81
+ function abortReason(source: AbortSource): unknown {
82
+ if (source.reason !== undefined) {
83
+ return source.reason;
84
+ }
85
+ const Platform = (globalThis as { readonly DOMException?: new (message: string, name: string) => Error }).DOMException;
86
+ if (typeof Platform === "function") {
87
+ return new Platform("This operation was aborted", "AbortError");
88
+ }
89
+ const error = new Error("This operation was aborted");
90
+ error.name = "AbortError";
91
+ return error;
92
+ }
93
+
94
+ /** The first of the sources that has aborted. */
95
+ function abortedSource(sources: readonly AbortSource[]): AbortSource | undefined {
96
+ return sources.find((source) => source.aborted);
97
+ }
98
+
99
+ /** Each source's listener per record, so it can be removed. */
100
+ const LISTENERS = new WeakMap<AbortSource, Map<CallRecord, () => void>>();
101
+
102
+ /** Runs \`onAbort\` on the first abort of any of the record's sources, from now on. */
103
+ function watch(record: CallRecord, onAbort: (reason: unknown) => void): void {
104
+ unwatch(record);
105
+ record.onAbort = onAbort;
106
+ for (const source of record.sources) {
107
+ source.addEventListener?.("abort", aborted(record, source));
108
+ }
109
+ }
110
+
111
+ /** Stops watching the record's sources. */
112
+ function unwatch(record: CallRecord): void {
113
+ if (record.onAbort === undefined) {
114
+ return;
115
+ }
116
+ record.onAbort = undefined;
117
+ for (const source of record.sources) {
118
+ const listener = LISTENERS.get(source)?.get(record);
119
+ if (listener !== undefined) {
120
+ source.removeEventListener?.("abort", listener);
121
+ LISTENERS.get(source)?.delete(record);
122
+ }
123
+ }
124
+ }
125
+
126
+ /** The listener a record keeps on one source. */
127
+ function aborted(record: CallRecord, source: AbortSource): () => void {
128
+ const listeners = LISTENERS.get(source) ?? new Map<CallRecord, () => void>();
129
+ LISTENERS.set(source, listeners);
130
+ const listener = (): void => {
131
+ const onAbort = record.onAbort;
132
+ unwatch(record);
133
+ onAbort?.(abortReason(source));
134
+ };
135
+ listeners.set(record, listener);
136
+ return listener;
137
+ }
138
+
139
+ /** Adds an abort source to a call: one of the \`all\` calls it is in. */
140
+ function addSource(record: CallRecord, source: AbortSource): void {
141
+ record.sources.push(source);
142
+ if (record.onAbort !== undefined) {
143
+ source.addEventListener?.("abort", aborted(record, source));
144
+ }
145
+ }
146
+
147
+ /** Settles a call once. */
148
+ function settle(record: CallRecord, outcome: EntryOutcome): void {
149
+ if (record.settled) {
150
+ return;
151
+ }
152
+ record.settled = true;
153
+ unwatch(record);
154
+ if (outcome.kind === "value") {
155
+ record.resolve(outcome.value);
156
+ } else {
157
+ record.reject(outcome.error);
158
+ }
159
+ }
160
+
161
+ /**
162
+ * One signal for the sources: none, the one, or one that aborts with the first
163
+ * of them (AbortSignal.any, or a controller where the runtime has no any).
164
+ */
165
+ function signalOf(sources: readonly AbortSource[]): PlatformSignal | undefined {
166
+ if (sources.length <= 1) {
167
+ return sources[0] as PlatformSignal | undefined;
168
+ }
169
+ const any = (globalThis as unknown as { readonly AbortSignal?: { readonly any?: (signals: readonly AbortSource[]) => AbortSource } })
170
+ .AbortSignal?.any;
171
+ if (typeof any === "function") {
172
+ return any(sources) as unknown as PlatformSignal;
173
+ }
174
+ const handle = abortHandle();
175
+ if (handle === undefined) {
176
+ return sources[0] as PlatformSignal | undefined;
177
+ }
178
+ for (const source of sources) {
179
+ source.addEventListener?.("abort", () => handle.abort(abortReason(source)));
180
+ }
181
+ return handle.signal as unknown as PlatformSignal;
182
+ }
183
+
184
+ /** The UTF-8 length of a JSON text: what a request body costs against maxRequestBytes. */
185
+ function utf8Length(text: string): number {
186
+ let bytes = 0;
187
+ for (let index = 0; index < text.length; index += 1) {
188
+ const unit = text.charCodeAt(index);
189
+ if (unit < 0x80) {
190
+ bytes += 1;
191
+ } else if (unit < 0x800) {
192
+ bytes += 2;
193
+ } else if (unit >= 0xd800 && unit <= 0xdbff && index + 1 < text.length) {
194
+ bytes += 4;
195
+ index += 1;
196
+ } else {
197
+ bytes += 3;
198
+ }
199
+ }
200
+ return bytes;
201
+ }
202
+
203
+ /**
204
+ * A group cut into Operation Sets in submission order: each at most maxAll
205
+ * entries and at most maxRequestBytes of body. A call too large to share a
206
+ * request is a set of one, sent to its own route.
207
+ */
208
+ function split(records: readonly CallRecord[]): CallRecord[][] {
209
+ const sets: CallRecord[][] = [];
210
+ let current: CallRecord[] = [];
211
+ let bytes = utf8Length(operationSetBody([]));
212
+ for (const record of records) {
213
+ const operation = (record.dispatch as { readonly operation: PreparedOperation }).operation;
214
+ const cost = (current.length === 0 ? 0 : 1) + utf8Length(operationSetEntry(operationSetKey(current.length), operation));
215
+ if (current.length > 0 && (current.length >= OPERATION_SET_LIMITS.maxAll || bytes + cost > OPERATION_SET_LIMITS.maxRequestBytes)) {
216
+ sets.push(current);
217
+ current = [];
218
+ bytes = utf8Length(operationSetBody([]));
219
+ }
220
+ bytes += (current.length === 0 ? 0 : 1) + utf8Length(operationSetEntry(operationSetKey(current.length), operation));
221
+ current.push(record);
222
+ }
223
+ if (current.length > 0) {
224
+ sets.push(current);
225
+ }
226
+ return sets;
227
+ }
228
+
229
+ /**
230
+ * Whether a call may be sent in an Operation Set: not when the caller opted out,
231
+ * or set headers or a request id, which belong to one request.
232
+ */
233
+ function groupable(options: AvCallOptions): boolean {
234
+ return (
235
+ options.grouping !== false &&
236
+ (options.requestId === undefined || options.requestId === "") &&
237
+ Object.keys(options.headers ?? {}).length === 0
238
+ );
239
+ }
240
+
241
+ /**
242
+ * One client's calls: each queued when it is made, and the ones made in one
243
+ * tick sent together.
244
+ */
245
+ export class CallQueue {
246
+ readonly #connect: () => TransportConnection;
247
+ #grouping: boolean;
248
+ #waiting: CallRecord[] = [];
249
+ #scheduled = false;
250
+ /** Calls submitted so far: a flush waits until a microtask passes with none. */
251
+ #submitted = 0;
252
+
253
+ constructor(connect: () => TransportConnection, grouping: boolean) {
254
+ this.#connect = connect;
255
+ this.#grouping = grouping;
256
+ }
257
+
258
+ /** One operation call: checked, encoded and queued now. */
259
+ operation(
260
+ resource: string,
261
+ family: string,
262
+ variant: string,
263
+ args: Readonly<Record<string, unknown>>,
264
+ options: AvCallOptions,
265
+ check?: (resource: string, family: string, args: unknown) => void,
266
+ ): Promise<unknown> {
267
+ let dispatch: Dispatch;
268
+ try {
269
+ const operation = prepareOperation(resource, family, variant, args, options, check);
270
+ dispatch = {
271
+ kind: "operation",
272
+ operation,
273
+ groupable: ${cached ? "!isCachedRead(operation) && " : ""}groupable(options),
274
+ };
275
+ } catch (error) {
276
+ dispatch = { kind: "refused", error };
277
+ }
278
+ return this.#call(dispatch, options.signal);
279
+ }
280
+
281
+ /** A call that is always sent alone (a transaction): queued now, \`send\` runs at the flush. */
282
+ alone(send: (signal: PlatformSignal | undefined) => Promise<unknown>, signal: PlatformSignal | undefined): Promise<unknown> {
283
+ return this.#call({ kind: "alone", send }, signal);
284
+ }
285
+
286
+ #call(dispatch: Dispatch, signal: PlatformSignal | undefined): Promise<unknown> {
287
+ let resolve: (value: unknown) => void = () => undefined;
288
+ let reject: (error: unknown) => void = () => undefined;
289
+ const settled = new Promise<unknown>((onResolve, onReject) => {
290
+ resolve = onResolve;
291
+ reject = onReject;
292
+ });
293
+ const record: CallRecord = {
294
+ dispatch,
295
+ sources: signal === undefined ? [] : [signal as unknown as AbortSource],
296
+ onAbort: undefined,
297
+ settled: false,
298
+ resolve,
299
+ reject,
300
+ };
301
+ CALLS.set(settled, record);
302
+ this.#submit(record);
303
+ return settled;
304
+ }
305
+
306
+ #submit(record: CallRecord): void {
307
+ const source = abortedSource(record.sources);
308
+ if (source !== undefined) {
309
+ settle(record, { kind: "error", error: abortReason(source) });
310
+ return;
311
+ }
312
+ const dispatch = record.dispatch;
313
+ if (dispatch.kind === "refused") {
314
+ settle(record, { kind: "error", error: dispatch.error });
315
+ return;
316
+ }
317
+ // Every call waits for the flush, so an all made in the same tick reaches
318
+ // each of its calls before it is sent, the ones sent alone too.
319
+ this.#waiting.push(record);
320
+ this.#submitted += 1;
321
+ watch(record, (reason) => {
322
+ this.#waiting = this.#waiting.filter((waiting) => waiting !== record);
323
+ settle(record, { kind: "error", error: reason });
324
+ });
325
+ if (!this.#scheduled) {
326
+ this.#scheduled = true;
327
+ this.#settle(-1);
328
+ }
329
+ }
330
+
331
+ /**
332
+ * Flushes once the calls stop coming: a microtask later, and again for as long
333
+ * as each microtask brought another call. Awaiting a call adopts it a
334
+ * microtask after it is written (Promise.all, an await in an async function), so
335
+ * the calls of one Promise.all, and of the async functions it awaits together,
336
+ * land in one flush; no timer is involved.
337
+ */
338
+ #settle(seen: number): void {
339
+ void Promise.resolve().then(() => {
340
+ if (this.#submitted === seen) {
341
+ this.#flush();
342
+ } else {
343
+ this.#settle(this.#submitted);
344
+ }
345
+ });
346
+ }
347
+
348
+ /**
349
+ * Sends what waits, in the order it was made: each call that is sent alone to
350
+ * its own route; the groupable ones as Operation Sets, a set of one to its own
351
+ * route.
352
+ */
353
+ #flush(): void {
354
+ this.#scheduled = false;
355
+ const waiting = this.#waiting;
356
+ this.#waiting = [];
357
+ const grouped: CallRecord[] = [];
358
+ for (const record of waiting) {
359
+ const dispatch = record.dispatch;
360
+ if (dispatch.kind === "operation" && dispatch.groupable && this.#grouping) {
361
+ grouped.push(record);
362
+ } else {
363
+ this.#sendAlone(record);
364
+ }
365
+ }
366
+ for (const set of split(grouped)) {
367
+ if (set.length === 1 || !this.#grouping) {
368
+ for (const record of set) {
369
+ this.#sendAlone(record);
370
+ }
371
+ } else {
372
+ this.#sendSet(set);
373
+ }
374
+ }
375
+ }
376
+
377
+ #sendAlone(record: CallRecord): void {
378
+ unwatch(record);
379
+ const source = abortedSource(record.sources);
380
+ if (source !== undefined) {
381
+ settle(record, { kind: "error", error: abortReason(source) });
382
+ return;
383
+ }
384
+ const dispatch = record.dispatch;
385
+ const signal = signalOf(record.sources);
386
+ let sent: Promise<unknown>;
387
+ try {
388
+ sent =
389
+ dispatch.kind === "operation"
390
+ ? ${cached ? "(isCachedRead(dispatch.operation) ? sendCachedRead : sendOperation)" : "sendOperation"}(this.#connect(), dispatch.operation, signal)
391
+ : dispatch.kind === "alone"
392
+ ? dispatch.send(signal)
393
+ : Promise.reject(dispatch.error);
394
+ } catch (error) {
395
+ sent = Promise.reject(error);
396
+ }
397
+ sent.then(
398
+ (value) => settle(record, { kind: "value", value }),
399
+ (error: unknown) => settle(record, { kind: "error", error }),
400
+ );
401
+ }
402
+
403
+ #sendSet(records: readonly CallRecord[]): void {
404
+ const waiting = new Set(records);
405
+ // The request is aborted only when no entry in it still waits for its answer.
406
+ const request = records.some((record) => record.sources.length > 0) ? abortHandle() : undefined;
407
+ const answer = (record: CallRecord, outcome: EntryOutcome): void => {
408
+ if (waiting.delete(record)) {
409
+ settle(record, outcome);
410
+ }
411
+ };
412
+ for (const record of records) {
413
+ watch(record, (reason) => {
414
+ answer(record, { kind: "error", error: reason });
415
+ if (waiting.size === 0) {
416
+ request?.abort();
417
+ }
418
+ });
419
+ }
420
+ let connection: TransportConnection;
421
+ try {
422
+ connection = this.#connect();
423
+ } catch (error) {
424
+ for (const record of records) {
425
+ answer(record, { kind: "error", error });
426
+ }
427
+ return;
428
+ }
429
+ const operations = records.map((record) => (record.dispatch as { readonly operation: PreparedOperation }).operation);
430
+ void sendOperationSet(connection, operations, request?.signal as PlatformSignal | undefined, (index, outcome) => {
431
+ const record = records[index];
432
+ if (record !== undefined) {
433
+ answer(record, outcome);
434
+ }
435
+ }).then((answered) => {
436
+ if (answered === "absent") {
437
+ // The server has no /_all: each call again on its own route, and no more sets.
438
+ this.#grouping = false;
439
+ for (const record of records) {
440
+ if (waiting.delete(record)) {
441
+ this.#sendAlone(record);
442
+ }
443
+ }
444
+ }
445
+ });
446
+ }
447
+ }
448
+
449
+ /** The call's options with \`signal\` in place of its own: what a call sent alone sends with. */
450
+ export function withSignal(options: AvCallOptions, signal: PlatformSignal | undefined): AvCallOptions {
451
+ const copy: { -readonly [K in keyof AvCallOptions]: AvCallOptions[K] } = { ...options };
452
+ delete copy.signal;
453
+ if (signal !== undefined) {
454
+ copy.signal = signal;
455
+ }
456
+ return copy;
457
+ }
458
+
459
+ /** Each avLazy thenable, by the function that makes its call. */
460
+ const LAZY = new WeakMap<object, () => PromiseLike<unknown>>();
461
+
462
+ /** Whether this runs in a page (or a worker): it has a location; a server has none. */
463
+ function inPage(): boolean {
464
+ return (globalThis as { readonly location?: unknown }).location !== undefined;
465
+ }
466
+
467
+ /**
468
+ * Describe a call now, make it later: returns a promise-like that calls \`run\`
469
+ * on its first then, catch or finally, and settles as the call it made. In a
470
+ * page it makes the call once and keeps its answer. On a server, where one
471
+ * module serves every request, it keeps nothing: each then, catch or finally
472
+ * makes the call again, in the context awaiting it. Calls are otherwise made,
473
+ * queued and sent when written; avClient.all accepts one as it accepts a call,
474
+ * and its signal aborts that call too.
475
+ *
476
+ * @example const posts = avLazy(() => avClient.Post.find.many({}));
477
+ */
478
+ export function avLazy<T>(run: () => PromiseLike<T>): Promise<T> {
479
+ let made: Promise<T> | undefined;
480
+ const make = (): Promise<T> => {
481
+ if (made !== undefined) {
482
+ return made;
483
+ }
484
+ let call: Promise<T>;
485
+ try {
486
+ call = Promise.resolve(run());
487
+ } catch (error) {
488
+ call = Promise.reject(error);
489
+ }
490
+ if (inPage()) {
491
+ made = call;
492
+ }
493
+ return call;
494
+ };
495
+ const lazy = Object.freeze({
496
+ then: <A = T, B = never>(
497
+ onFulfilled?: ((value: T) => A | PromiseLike<A>) | null,
498
+ onRejected?: ((reason: unknown) => B | PromiseLike<B>) | null,
499
+ ): Promise<A | B> => make().then(onFulfilled, onRejected),
500
+ catch: <B = never>(onRejected?: ((reason: unknown) => B | PromiseLike<B>) | null): Promise<T | B> =>
501
+ make().catch(onRejected),
502
+ finally: (onFinally?: (() => void) | null): Promise<T> => make().finally(onFinally),
503
+ [Symbol.toStringTag]: "Promise",
504
+ });
505
+ LAZY.set(lazy, make);
506
+ return lazy as unknown as Promise<T>;
507
+ }
508
+
509
+ /** How one call of \`all(…, { settle: true })\` settled. */
510
+ type SettledEntry =
511
+ | { readonly status: "fulfilled"; readonly value: unknown }
512
+ | { readonly status: "rejected"; readonly reason: unknown };
513
+
514
+ function isThenable(value: unknown): value is PromiseLike<unknown> {
515
+ return (
516
+ (typeof value === "object" || typeof value === "function") &&
517
+ value !== null &&
518
+ typeof (value as { readonly then?: unknown }).then === "function"
519
+ );
520
+ }
521
+
522
+ /**
523
+ * \`avClient.all(entries, options?)\`: awaits an object of calls together, so they
524
+ * are grouped, and resolves one value per key. Strict by default: the first
525
+ * failure rejects it, and its other calls are aborted (fail-fast, not atomic:
526
+ * what already ran stays done). With settle: true, every key settles on its own,
527
+ * as { status: "fulfilled", value } or { status: "rejected", reason }, and it never
528
+ * rejects for one key. Its signal aborts each of its calls; inside it a call
529
+ * waits on its own signal and that one. Calls outside it are never touched. A
530
+ * promise that is not one of this client's calls is awaited as Promise.all would.
531
+ *
532
+ * @throws TypeError, before anything is sent, when entries is not an object
533
+ * whose every value is a call.
534
+ */
535
+ export function all(entries: unknown, options: { readonly signal?: PlatformSignal; readonly settle?: boolean } = {}): Promise<unknown> {
536
+ if (typeof entries !== "object" || entries === null || Array.isArray(entries)) {
537
+ return Promise.reject(new TypeError("all takes an object of calls: { key: avClient.Resource.find.many(…), … }."));
538
+ }
539
+ const keyed: (readonly [string, PromiseLike<unknown>])[] = [];
540
+ for (const key of Object.keys(entries)) {
541
+ const value: unknown = (entries as Readonly<Record<string, unknown>>)[key];
542
+ if (!isThenable(value)) {
543
+ return Promise.reject(new TypeError("all's entry " + JSON.stringify(key) + " is not a call."));
544
+ }
545
+ keyed.push([key, value]);
546
+ }
547
+ // Aborts this all's calls: its signal, or its first failure when strict.
548
+ const group = abortHandle();
549
+ const outer = options.signal as AbortSource | undefined;
550
+ const forward = (): void => {
551
+ if (outer !== undefined) {
552
+ group?.abort(abortReason(outer));
553
+ }
554
+ };
555
+ if (outer?.aborted === true) {
556
+ forward();
557
+ } else {
558
+ outer?.addEventListener?.("abort", forward);
559
+ }
560
+ // What each of its calls also waits on; without a controller, its signal alone.
561
+ const source: AbortSource | undefined = group?.signal ?? outer;
562
+ const outcomes = keyed.map(
563
+ ([, value]) =>
564
+ new Promise<unknown>((resolve, reject) => {
565
+ const call = LAZY.get(value)?.() ?? value;
566
+ const record = CALLS.get(call);
567
+ if (record !== undefined && source !== undefined) {
568
+ addSource(record, source);
569
+ }
570
+ const signal = group?.signal;
571
+ if (signal?.aborted === true) {
572
+ reject(abortReason(signal));
573
+ } else {
574
+ signal?.addEventListener?.("abort", () => reject(abortReason(signal)));
575
+ }
576
+ call.then(resolve, reject);
577
+ }),
578
+ );
579
+ const done = <T>(result: T): T => {
580
+ outer?.removeEventListener?.("abort", forward);
581
+ return result;
582
+ };
583
+ const byKey = (values: readonly unknown[]): Readonly<Record<string, unknown>> =>
584
+ Object.fromEntries(keyed.map(([key], index) => [key, values[index]]));
585
+ if (options.settle === true) {
586
+ return Promise.all(
587
+ outcomes.map((outcome) =>
588
+ outcome.then(
589
+ (value): SettledEntry => ({ status: "fulfilled", value }),
590
+ (reason: unknown): SettledEntry => ({ status: "rejected", reason }),
591
+ ),
592
+ ),
593
+ ).then((settled) => done(byKey(settled)));
594
+ }
595
+ return Promise.all(outcomes).then(
596
+ (values) => done(byKey(values)),
597
+ (error: unknown) => {
598
+ group?.abort();
599
+ throw done(error);
600
+ },
601
+ );
602
+ }
603
+ `;
604
+ }