kinetex 1.3.0 → 1.4.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 (120) hide show
  1. package/README.md +246 -9
  2. package/dist/browser/kinetex.esm.js +38 -22
  3. package/dist/browser/kinetex.js +2545 -550
  4. package/dist/browser/kinetex.min.js +38 -22
  5. package/dist/cjs/aws-sigv4.js +133 -19
  6. package/dist/cjs/cache.js +49 -7
  7. package/dist/cjs/circuit-breaker.js +45 -3
  8. package/dist/cjs/client.js +387 -104
  9. package/dist/cjs/cookie-parser.js +103 -5
  10. package/dist/cjs/cookie-store.js +125 -28
  11. package/dist/cjs/core.js +465 -66
  12. package/dist/cjs/dedup.js +49 -11
  13. package/dist/cjs/digest.js +160 -24
  14. package/dist/cjs/graphql.js +164 -24
  15. package/dist/cjs/headers.js +303 -45
  16. package/dist/cjs/interceptors.js +221 -7
  17. package/dist/cjs/lifecycle.js +89 -40
  18. package/dist/cjs/logging.js +168 -15
  19. package/dist/cjs/mod.js +3 -2
  20. package/dist/cjs/pagination.js +247 -22
  21. package/dist/cjs/progress.js +177 -27
  22. package/dist/cjs/proxy.js +412 -0
  23. package/dist/cjs/response.js +316 -47
  24. package/dist/cjs/socks5.js +131 -15
  25. package/dist/cjs/sse.js +173 -43
  26. package/dist/cjs/url.js +191 -45
  27. package/dist/cjs/utils.js +222 -48
  28. package/dist/cjs/ws.js +19 -10
  29. package/dist/esm/aws-sigv4.js +133 -19
  30. package/dist/esm/aws-sigv4.js.map +1 -1
  31. package/dist/esm/cache.js +49 -7
  32. package/dist/esm/cache.js.map +1 -1
  33. package/dist/esm/circuit-breaker.js +45 -3
  34. package/dist/esm/circuit-breaker.js.map +1 -1
  35. package/dist/esm/client.js +387 -104
  36. package/dist/esm/client.js.map +1 -1
  37. package/dist/esm/cookie-parser.js +103 -5
  38. package/dist/esm/cookie-parser.js.map +1 -1
  39. package/dist/esm/cookie-store.js +125 -28
  40. package/dist/esm/cookie-store.js.map +1 -1
  41. package/dist/esm/core.js +465 -66
  42. package/dist/esm/core.js.map +1 -1
  43. package/dist/esm/dedup.js +49 -11
  44. package/dist/esm/dedup.js.map +1 -1
  45. package/dist/esm/digest.js +160 -24
  46. package/dist/esm/digest.js.map +1 -1
  47. package/dist/esm/graphql.js +164 -24
  48. package/dist/esm/graphql.js.map +1 -1
  49. package/dist/esm/headers.js +303 -45
  50. package/dist/esm/headers.js.map +1 -1
  51. package/dist/esm/interceptors.js +221 -7
  52. package/dist/esm/interceptors.js.map +1 -1
  53. package/dist/esm/lifecycle.js +89 -40
  54. package/dist/esm/lifecycle.js.map +1 -1
  55. package/dist/esm/logging.js +168 -15
  56. package/dist/esm/logging.js.map +1 -1
  57. package/dist/esm/mod.js +3 -2
  58. package/dist/esm/mod.js.map +1 -1
  59. package/dist/esm/pagination.js +247 -22
  60. package/dist/esm/pagination.js.map +1 -1
  61. package/dist/esm/progress.js +177 -27
  62. package/dist/esm/progress.js.map +1 -1
  63. package/dist/esm/proxy.js +413 -0
  64. package/dist/esm/proxy.js.map +1 -0
  65. package/dist/esm/response.js +316 -47
  66. package/dist/esm/response.js.map +1 -1
  67. package/dist/esm/socks5.js +131 -15
  68. package/dist/esm/socks5.js.map +1 -1
  69. package/dist/esm/sse.js +173 -43
  70. package/dist/esm/sse.js.map +1 -1
  71. package/dist/esm/types.js.map +1 -1
  72. package/dist/esm/url.js +191 -45
  73. package/dist/esm/url.js.map +1 -1
  74. package/dist/esm/utils.js +222 -48
  75. package/dist/esm/utils.js.map +1 -1
  76. package/dist/esm/ws.js +19 -10
  77. package/dist/esm/ws.js.map +1 -1
  78. package/dist/types/aws-sigv4.d.ts.map +1 -1
  79. package/dist/types/cache.d.ts +19 -1
  80. package/dist/types/cache.d.ts.map +1 -1
  81. package/dist/types/circuit-breaker.d.ts +14 -1
  82. package/dist/types/circuit-breaker.d.ts.map +1 -1
  83. package/dist/types/client.d.ts +69 -11
  84. package/dist/types/client.d.ts.map +1 -1
  85. package/dist/types/cookie-parser.d.ts +0 -17
  86. package/dist/types/cookie-parser.d.ts.map +1 -1
  87. package/dist/types/cookie-store.d.ts.map +1 -1
  88. package/dist/types/core.d.ts +103 -25
  89. package/dist/types/core.d.ts.map +1 -1
  90. package/dist/types/dedup.d.ts.map +1 -1
  91. package/dist/types/digest.d.ts +17 -37
  92. package/dist/types/digest.d.ts.map +1 -1
  93. package/dist/types/graphql.d.ts.map +1 -1
  94. package/dist/types/headers.d.ts +45 -27
  95. package/dist/types/headers.d.ts.map +1 -1
  96. package/dist/types/interceptors.d.ts +102 -0
  97. package/dist/types/interceptors.d.ts.map +1 -1
  98. package/dist/types/lifecycle.d.ts +19 -2
  99. package/dist/types/lifecycle.d.ts.map +1 -1
  100. package/dist/types/logging.d.ts +22 -3
  101. package/dist/types/logging.d.ts.map +1 -1
  102. package/dist/types/mod.d.ts +5 -3
  103. package/dist/types/mod.d.ts.map +1 -1
  104. package/dist/types/pagination.d.ts +0 -25
  105. package/dist/types/pagination.d.ts.map +1 -1
  106. package/dist/types/progress.d.ts +1 -1
  107. package/dist/types/progress.d.ts.map +1 -1
  108. package/dist/types/proxy.d.ts +50 -0
  109. package/dist/types/proxy.d.ts.map +1 -0
  110. package/dist/types/response.d.ts +7 -1
  111. package/dist/types/response.d.ts.map +1 -1
  112. package/dist/types/socks5.d.ts.map +1 -1
  113. package/dist/types/sse.d.ts.map +1 -1
  114. package/dist/types/types.d.ts +114 -3
  115. package/dist/types/types.d.ts.map +1 -1
  116. package/dist/types/url.d.ts +0 -14
  117. package/dist/types/url.d.ts.map +1 -1
  118. package/dist/types/utils.d.ts.map +1 -1
  119. package/dist/types/ws.d.ts.map +1 -1
  120. package/package.json +1 -1
@@ -71,9 +71,29 @@ function buildPage(items, response, state, config, next, hasNextOverride) {
71
71
  /**
72
72
  * Core paginator — yields one Page<T> per fetch.
73
73
  */
74
+ /**
75
+ * Strategies whose next request is selected by a cursor, token or next-URL
76
+ * carried in `PaginationState`, rather than by the page and offset numbers.
77
+ * A repeated or absent value for those means the paginator has nothing new to
78
+ * ask for, however many pages `hasNext` is willing to agree to.
79
+ */
80
+ const CURSOR_DRIVEN_STRATEGIES = new Set([
81
+ "cursor",
82
+ "relay",
83
+ "link-header",
84
+ "token",
85
+ "keyset",
86
+ ]);
74
87
  export async function* paginate(config, strategy = "page") {
75
88
  let state = buildInitialState(config, strategy);
76
89
  let pageNum = 0;
90
+ // Cursors/tokens already asked for. A server that keeps handing back the
91
+ // same one would otherwise be followed forever.
92
+ const seenCursors = new Set();
93
+ if (state.cursor !== null)
94
+ seenCursors.add(state.cursor);
95
+ if (state.token !== null)
96
+ seenCursors.add(state.token);
77
97
  while (!state.done) {
78
98
  if (config.signal?.aborted)
79
99
  break;
@@ -94,25 +114,86 @@ export async function* paginate(config, strategy = "page") {
94
114
  throw err;
95
115
  }
96
116
  // Extract + transform + filter items
97
- let items = config.getItems(response, state);
117
+ const extracted = config.getItems(response, state);
118
+ // The count the *server* returned, before transform/filter. The offset
119
+ // advances by this, not by the surviving item count: a filter makes those
120
+ // differ, and advancing by the filtered count walks back over rows the
121
+ // client has already seen and steps over rows it has not. Measured with a
122
+ // parity filter over three-item pages, the paginator requested offsets
123
+ // 0, 2, 4, 6, 8, 10 instead of 0, 3, 6, 9 — never yielding the odd
124
+ // items and yielding every even one twice.
125
+ const rawCount = extracted.length;
126
+ let items = extracted;
98
127
  if (config.transform)
99
128
  items = items.map(config.transform);
100
129
  if (config.filter)
101
130
  items = items.filter(config.filter);
102
131
  // Get next state
103
132
  const nextPartial = config.getNext ? config.getNext(response, state) : null;
104
- const hasMore = config.hasNext(response, state);
133
+ let hasMore = config.hasNext(response, state);
134
+ // Progress guard.
135
+ //
136
+ // `hasNext` and `getNext` are two independent callbacks, and nothing
137
+ // checked that agreeing to another page actually gave the fetcher
138
+ // something new to ask for. `maxPages` defaults to 0 — "unlimited" — so
139
+ // when they disagreed the loop had no bound at all: a Relay connection
140
+ // reporting `hasNextPage: true` with a null `endCursor`, or a cursor API
141
+ // that keeps returning the cursor it was given, turned the paginator into
142
+ // an unbounded request generator against a real API, re-fetching the same
143
+ // page forever. Stop when the next request would be identical to this one,
144
+ // and when a cursor or token repeats a value already used.
145
+ if (hasMore) {
146
+ const nextCursor = nextPartial?.cursor ?? state.cursor;
147
+ const nextToken = nextPartial?.token ?? state.token;
148
+ const nextPage = nextPartial?.page ?? state.page + 1;
149
+ const nextOffset = nextPartial?.offset ?? state.offset + rawCount;
150
+ // `getNext` answered "there is nothing new to ask for", on a strategy
151
+ // whose fetch is driven by a cursor or a token. There the page/offset
152
+ // arithmetic is arithmetic on fields the fetcher never sends, so it looks
153
+ // like progress and is not: a Relay connection reporting
154
+ // `hasNextPage: true` beside a null `endCursor` was followed forever,
155
+ // re-fetching the same page on every tick. Scoped to the cursor-driven
156
+ // strategies because a `page`/`offset` config is entitled to progress by
157
+ // those numbers, and there a null `getNext` is just a no-op. Every
158
+ // built-in cursor-driven strategy returns a cursor when it has one, so
159
+ // this cannot fire against a working server.
160
+ const cursorDriven = CURSOR_DRIVEN_STRATEGIES.has(state.strategy);
161
+ const noNext = cursorDriven &&
162
+ config.getNext !== undefined &&
163
+ (nextPartial === null || nextPartial === undefined);
164
+ const repeats = (nextCursor !== null && seenCursors.has(nextCursor)) ||
165
+ (nextToken !== null && seenCursors.has(nextToken));
166
+ const nothingMoved = nextPage === state.page &&
167
+ nextOffset === state.offset &&
168
+ nextCursor === state.cursor &&
169
+ nextToken === state.token;
170
+ if (noNext || repeats || nothingMoved) {
171
+ hasMore = false;
172
+ }
173
+ else {
174
+ if (nextCursor !== null)
175
+ seenCursors.add(nextCursor);
176
+ if (nextToken !== null)
177
+ seenCursors.add(nextToken);
178
+ }
179
+ }
105
180
  const page = buildPage(items, response, state, config, nextPartial, hasMore);
106
181
  // Track previous cursor for bidirectional pagination
107
182
  const newPrevCursor = state.cursor;
108
183
  state = {
109
184
  ...state,
110
185
  page: nextPartial?.page ?? state.page + 1,
111
- offset: nextPartial?.offset ?? state.offset + items.length,
186
+ offset: nextPartial?.offset ?? state.offset + rawCount,
112
187
  cursor: nextPartial?.cursor ?? state.cursor,
113
188
  token: nextPartial?.token ?? state.token,
114
189
  done: !hasMore,
115
- totalFetched: state.totalFetched + items.length,
190
+ // The count the server returned, for the same reason the offset below
191
+ // advances by `rawCount` rather than by `items.length`: a `filter` makes
192
+ // the two differ. "Fetched so far" is a statement about the requests
193
+ // made, not about what survived them, and it was reported as the
194
+ // surviving count — so a filter dropping half of every page also halved
195
+ // the total, understating the work done by half.
196
+ totalFetched: state.totalFetched + rawCount,
116
197
  prevCursor: newPrevCursor,
117
198
  };
118
199
  pageNum++;
@@ -159,10 +240,22 @@ export async function collectPages(config, strategy = "page") {
159
240
  * Take exactly N items across pages. Stops fetching once N items are collected.
160
241
  */
161
242
  export async function takeItems(n, config, strategy = "page") {
243
+ // The loop pushed an item and only *then* tested `length >= n`, so
244
+ // `takeItems(0)` returned one item instead of none, and every negative N did
245
+ // too (1 >= -5). N <= 0 is a request for nothing and is answered without
246
+ // touching the network at all.
247
+ //
248
+ // NaN is excluded the same way, since every comparison against it is false
249
+ // and it would otherwise have walked every page to no end. `Infinity` is
250
+ // deliberately left to run: it is the documented "more than exists" case,
251
+ // and the `>=` below is simply never satisfied by it.
252
+ if (typeof n !== "number" || Number.isNaN(n) || n <= 0)
253
+ return [];
254
+ const limit = Math.floor(n);
162
255
  const items = [];
163
256
  for await (const item of paginateItems(config, strategy)) {
164
257
  items.push(item);
165
- if (items.length >= n)
258
+ if (items.length >= limit)
166
259
  break;
167
260
  }
168
261
  return items;
@@ -325,13 +418,32 @@ export function createLinkHeaderPaginator(options) {
325
418
  * // Returns: "https://api.example.com/items?page=2"
326
419
  * ```
327
420
  */
421
+ /**
422
+ * One RFC 5988 link-value: `<uri>` followed by parameters, `rel` quoted either
423
+ * way.
424
+ *
425
+ * Case-insensitive, because RFC 5988 §3.1 states that relation types are
426
+ * compared in a case-insensitive manner, and RFC 8288 §4 makes parameter
427
+ * *names* case-insensitive as well. Matching `rel` case-sensitively meant a
428
+ * server sending `rel="Next"` — legal, and used in the wild — produced no next
429
+ * page: pagination silently stopped one page early with no error to explain
430
+ * it.
431
+ */
432
+ const RE_LINK_ENTRY = /<([^>]+)>.*?rel=["']([^"']+)["']/i;
328
433
  export function parseLinkHeaderNext(linkHeader) {
329
434
  if (!linkHeader)
330
435
  return null;
331
- for (const part of linkHeader.split(",")) {
436
+ // Split *before* every link's opening bracket rather than on commas. A URI
437
+ // may contain a comma — `?ids=1,2,3` is routine — and splitting on it cut
438
+ // the entry in half, so the regex no longer found the `rel` and the function
439
+ // reported "no next page": the pagination stopping early against a server
440
+ // that had more to give. A lookahead split keys off the one character that
441
+ // genuinely cannot appear inside a link-value's parameters.
442
+ const parts = linkHeader.split(/(?=<)/).filter((p) => p.trimStart().startsWith("<"));
443
+ for (const part of parts) {
332
444
  // Handle both double and single quoted rel values per RFC 5988
333
- const match = part.match(/<([^>]+)>.*?rel=["']([^"']+)["']/);
334
- if (match && match[2] && match[2].split(/\s+/).includes("next"))
445
+ const match = part.match(RE_LINK_ENTRY);
446
+ if (match && match[2] && match[2].toLowerCase().split(/\s+/).includes("next"))
335
447
  return match[1];
336
448
  }
337
449
  return null;
@@ -413,24 +525,53 @@ export function createKeysetPaginator(options) {
413
525
  * Useful when pages are independent (offset/page strategy).
414
526
  */
415
527
  export async function* prefetchPaginate(config, strategy = "page", prefetchAhead = 2) {
528
+ const ahead = Math.max(0, Math.floor(Number.isFinite(prefetchAhead) ? prefetchAhead : 0));
416
529
  const queue = [];
417
530
  const gen = paginate(config, strategy);
418
531
  let done = false;
419
- async function enqueue() {
532
+ // The *pending call* is queued, not its value. Awaiting `gen.next()` here
533
+ // made this strictly sequential and worse than a plain `paginate`: the
534
+ // consumer was blocked on the next page's fetch before it could process the
535
+ // page it already held, so the queue cost bookkeeping and prefetched
536
+ // nothing. Queuing the call lets the next request be in flight while the
537
+ // consumer works, which is the entire point of the `prefetchAhead` argument.
538
+ //
539
+ // Note this is overlap, not request-level parallelism: `paginate` is a
540
+ // single async generator and runs one body at a time, so only one request is
541
+ // ever outstanding. `ahead` bounds how far the consumer may run ahead of the
542
+ // fetches, not how many sockets are open.
543
+ //
544
+ // Order is preserved because the generator resolves its own `next()` calls in
545
+ // the order they were made, so the queue stays in page order.
546
+ function enqueue() {
420
547
  if (done)
421
548
  return;
422
- const { value, done: d } = await gen.next();
423
- done = d ?? false;
424
- queue.push(Promise.resolve(value ?? null));
549
+ const pending = gen.next().then((r) => {
550
+ if (r.done) {
551
+ done = true;
552
+ return null;
553
+ }
554
+ return r.value ?? null;
555
+ });
556
+ // A consumer that stops early (a `break` in the `for await`) leaves queued
557
+ // requests in flight with nobody awaiting them. If one of those rejects —
558
+ // an aborted request, a server error on a page the consumer never wanted —
559
+ // the rejection is unhandled, which in Node terminates the process and in
560
+ // a browser surfaces as a global `unhandledrejection` the caller cannot
561
+ // catch or attribute. Marking it handled here is safe: awaiting the same
562
+ // promise below still observes the rejection and rethrows it, so a
563
+ // *consumed* failure is reported exactly as before.
564
+ pending.catch(() => { });
565
+ queue.push(pending);
425
566
  }
426
567
  // Fill prefetch queue
427
- for (let i = 0; i < prefetchAhead + 1; i++)
428
- await enqueue();
568
+ for (let i = 0; i < ahead + 1; i++)
569
+ enqueue();
429
570
  while (queue.length > 0) {
430
571
  const page = await queue.shift();
431
572
  if (page === null || page === undefined)
432
573
  break;
433
- await enqueue();
574
+ enqueue();
434
575
  yield page;
435
576
  }
436
577
  }
@@ -449,7 +590,23 @@ export async function* prefetchPaginate(config, strategy = "page", prefetchAhead
449
590
  * @returns Base64-encoded JSON string
450
591
  */
451
592
  export function serializePaginationState(state) {
452
- return btoa(JSON.stringify(state));
593
+ // `btoa` takes a *binary* (Latin-1) string and throws InvalidCharacterError
594
+ // on anything above U+00FF. A cursor is opaque and can hold any character,
595
+ // so a perfectly ordinary state — `cursor: "curseur-e-acute-☃"` — made this
596
+ // throw and the paginator un-resumable. The transport has to be UTF-8.
597
+ const json = JSON.stringify(state);
598
+ if (typeof TextEncoder !== "undefined" && typeof btoa === "function") {
599
+ const bytes = new TextEncoder().encode(json);
600
+ let binary = "";
601
+ // Chunked: String.fromCharCode(...bytes) overflows the argument limit on
602
+ // a large state and throws a RangeError of its own.
603
+ const CHUNK = 0x8000;
604
+ for (let i = 0; i < bytes.length; i += CHUNK) {
605
+ binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK));
606
+ }
607
+ return btoa(binary);
608
+ }
609
+ return btoa(json);
453
610
  }
454
611
  /**
455
612
  * Deserialize a serialized pagination state string back to PaginationState.
@@ -458,15 +615,70 @@ export function serializePaginationState(state) {
458
615
  * @returns The reconstructed pagination state
459
616
  * @throws Error if the string is not valid base64/JSON
460
617
  */
618
+ /** The strategies a serialized state may name. Anything else is not a state. */
619
+ const PAGINATION_STRATEGIES = new Set([
620
+ "offset",
621
+ "page",
622
+ "cursor",
623
+ "relay",
624
+ "link-header",
625
+ "token",
626
+ "keyset",
627
+ ]);
628
+ /** A count or position: a finite number, so `NaN` and `Infinity` are refused. */
629
+ function isFiniteNumber(v) {
630
+ return typeof v === "number" && Number.isFinite(v);
631
+ }
632
+ /**
633
+ * A cursor field must be a string, `null`, or absent.
634
+ *
635
+ * `undefined` is allowed on purpose: these three fields are only meaningful for
636
+ * the strategies that use them, so states written by an older version — which
637
+ * the format carries no version tag for — legitimately omit them. A *number*
638
+ * or an object is never a cursor, and that is what the check is for.
639
+ */
640
+ function isNullableString(v) {
641
+ return v === undefined || v === null || typeof v === "string";
642
+ }
461
643
  export function deserializePaginationState(serialized) {
644
+ let parsed;
462
645
  try {
463
- // FIX (H6): serialized state can originate from untrusted URLs/clients —
464
- // strip prototype-pollution keys before the state is spread into requests.
465
- return sanitizeParsedJSON(JSON.parse(atob(serialized)));
646
+ const binary = atob(serialized);
647
+ const bytes = new Uint8Array(binary.length);
648
+ for (let i = 0; i < binary.length; i++)
649
+ bytes[i] = binary.charCodeAt(i);
650
+ const json = typeof TextDecoder !== "undefined"
651
+ ? new TextDecoder("utf-8", { fatal: true }).decode(bytes)
652
+ : binary;
653
+ parsed = JSON.parse(json);
466
654
  }
467
655
  catch {
468
656
  throw new Error("Invalid pagination state string");
469
657
  }
658
+ // FIX (H6): serialized state can originate from untrusted URLs/clients —
659
+ // strip prototype-pollution keys before the state is spread into requests.
660
+ const state = sanitizeParsedJSON(parsed);
661
+ // `atob` and `JSON.parse` succeeding says nothing about the *shape*. A
662
+ // string that decodes to `{"hello":"world"}`, or to `[1,2,3]`, or to
663
+ // `null`, was returned as a `PaginationState` and typed as one — a caller
664
+ // resuming from it read `page`/`offset`/`done` off an object that has none,
665
+ // and the paginator restarted from `undefined` rather than saying so.
666
+ if (typeof state !== "object" || state === null || Array.isArray(state)) {
667
+ throw new Error("Invalid pagination state string");
668
+ }
669
+ const s = state;
670
+ if (!isFiniteNumber(s.page) ||
671
+ !isFiniteNumber(s.offset) ||
672
+ !isFiniteNumber(s.totalFetched) ||
673
+ typeof s.strategy !== "string" ||
674
+ !PAGINATION_STRATEGIES.has(s.strategy) ||
675
+ typeof s.done !== "boolean" ||
676
+ !isNullableString(s.cursor) ||
677
+ !isNullableString(s.prevCursor) ||
678
+ !isNullableString(s.token)) {
679
+ throw new Error("Invalid pagination state string");
680
+ }
681
+ return state;
470
682
  }
471
683
  // ============================================================================
472
684
  // §9 UTILITIES
@@ -537,12 +749,25 @@ export async function* mergePaginators(...paginators) {
537
749
  const active = new Set(iters.map((_, i) => i));
538
750
  while (active.size > 0) {
539
751
  for (const i of [...active]) {
540
- const { value, done } = await iters[i].next();
541
- if (done) {
752
+ let result;
753
+ try {
754
+ result = await iters[i].next();
755
+ }
756
+ catch {
757
+ // A source that fails is dropped from the rotation, not allowed to
758
+ // abort the merge. One endpoint returning an error ended the whole
759
+ // iteration and discarded everything the other sources had already
760
+ // yielded — the opposite of what merging sources is for, and it did so
761
+ // silently. Isolating the failure matches what the transports and the
762
+ // batch flushes already do elsewhere in the client.
763
+ active.delete(i);
764
+ continue;
765
+ }
766
+ if (result.done) {
542
767
  active.delete(i);
543
768
  continue;
544
769
  }
545
- yield value;
770
+ yield result.value;
546
771
  }
547
772
  }
548
773
  }