@yume-chan/stream-extra 0.0.23 → 1.0.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 (100) hide show
  1. package/CHANGELOG.md +32 -2
  2. package/LICENSE +1 -1
  3. package/esm/buffered-transform.d.ts +3 -3
  4. package/esm/buffered-transform.d.ts.map +1 -1
  5. package/esm/buffered-transform.js.map +1 -1
  6. package/esm/buffered.d.ts +3 -6
  7. package/esm/buffered.d.ts.map +1 -1
  8. package/esm/buffered.js +87 -75
  9. package/esm/buffered.js.map +1 -1
  10. package/esm/concat.d.ts +4 -3
  11. package/esm/concat.d.ts.map +1 -1
  12. package/esm/concat.js +6 -4
  13. package/esm/concat.js.map +1 -1
  14. package/esm/consumable.d.ts +36 -43
  15. package/esm/consumable.d.ts.map +1 -1
  16. package/esm/consumable.js +74 -125
  17. package/esm/consumable.js.map +1 -1
  18. package/esm/distribution.d.ts +3 -2
  19. package/esm/distribution.d.ts.map +1 -1
  20. package/esm/distribution.js +22 -18
  21. package/esm/distribution.js.map +1 -1
  22. package/esm/duplex.d.ts +2 -2
  23. package/esm/duplex.d.ts.map +1 -1
  24. package/esm/duplex.js +2 -6
  25. package/esm/duplex.js.map +1 -1
  26. package/esm/encoding.d.ts +21 -0
  27. package/esm/encoding.d.ts.map +1 -0
  28. package/esm/encoding.js +4 -0
  29. package/esm/encoding.js.map +1 -0
  30. package/esm/index.d.ts +4 -1
  31. package/esm/index.d.ts.map +1 -1
  32. package/esm/index.js +4 -1
  33. package/esm/index.js.map +1 -1
  34. package/esm/maybe-consumable-ns.d.ts +16 -0
  35. package/esm/maybe-consumable-ns.d.ts.map +1 -0
  36. package/esm/maybe-consumable-ns.js +44 -0
  37. package/esm/maybe-consumable-ns.js.map +1 -0
  38. package/esm/maybe-consumable.d.ts +4 -0
  39. package/esm/maybe-consumable.d.ts.map +1 -0
  40. package/esm/maybe-consumable.js +2 -0
  41. package/esm/maybe-consumable.js.map +1 -0
  42. package/esm/push-readable.d.ts +16 -2
  43. package/esm/push-readable.d.ts.map +1 -1
  44. package/esm/push-readable.js +176 -17
  45. package/esm/push-readable.js.map +1 -1
  46. package/esm/stream.d.ts +7 -7
  47. package/esm/stream.d.ts.map +1 -1
  48. package/esm/stream.js +54 -5
  49. package/esm/stream.js.map +1 -1
  50. package/esm/struct-deserialize.d.ts +3 -4
  51. package/esm/struct-deserialize.d.ts.map +1 -1
  52. package/esm/struct-deserialize.js.map +1 -1
  53. package/esm/struct-serialize.d.ts +2 -2
  54. package/esm/struct-serialize.d.ts.map +1 -1
  55. package/esm/struct-serialize.js.map +1 -1
  56. package/esm/task.d.ts +5 -0
  57. package/esm/task.d.ts.map +1 -0
  58. package/esm/task.js +9 -0
  59. package/esm/task.js.map +1 -0
  60. package/esm/try-close.d.ts +10 -0
  61. package/esm/try-close.d.ts.map +1 -0
  62. package/esm/try-close.js +19 -0
  63. package/esm/try-close.js.map +1 -0
  64. package/esm/types.d.ts +34 -35
  65. package/esm/types.d.ts.map +1 -1
  66. package/esm/wrap-readable.d.ts +5 -4
  67. package/esm/wrap-readable.d.ts.map +1 -1
  68. package/esm/wrap-readable.js +14 -9
  69. package/esm/wrap-readable.js.map +1 -1
  70. package/esm/wrap-writable.d.ts +2 -2
  71. package/esm/wrap-writable.d.ts.map +1 -1
  72. package/esm/wrap-writable.js +4 -6
  73. package/esm/wrap-writable.js.map +1 -1
  74. package/package.json +11 -15
  75. package/src/buffered-transform.ts +2 -2
  76. package/src/buffered.ts +107 -83
  77. package/src/concat.ts +6 -4
  78. package/src/consumable.ts +132 -213
  79. package/src/distribution.ts +30 -20
  80. package/src/duplex.ts +4 -7
  81. package/src/encoding.ts +39 -0
  82. package/src/index.ts +4 -1
  83. package/src/maybe-consumable-ns.ts +75 -0
  84. package/src/maybe-consumable.ts +5 -0
  85. package/src/push-readable.ts +213 -19
  86. package/src/stream.ts +69 -9
  87. package/src/struct-deserialize.ts +4 -7
  88. package/src/struct-serialize.ts +3 -3
  89. package/src/task.ts +21 -0
  90. package/src/try-close.ts +44 -0
  91. package/src/types.ts +34 -34
  92. package/src/wrap-readable.ts +20 -14
  93. package/src/wrap-writable.ts +6 -9
  94. package/tsconfig.build.tsbuildinfo +1 -1
  95. package/CHANGELOG.json +0 -86
  96. package/esm/decode-utf8.d.ts +0 -5
  97. package/esm/decode-utf8.d.ts.map +0 -1
  98. package/esm/decode-utf8.js +0 -12
  99. package/esm/decode-utf8.js.map +0 -1
  100. package/src/decode-utf8.ts +0 -13
package/src/buffered.ts CHANGED
@@ -1,15 +1,15 @@
1
+ import type { MaybePromiseLike } from "@yume-chan/async";
1
2
  import type { AsyncExactReadable } from "@yume-chan/struct";
2
- import { ExactReadableEndedError } from "@yume-chan/struct";
3
+ import { bipedal, ExactReadableEndedError } from "@yume-chan/struct";
3
4
 
4
5
  import { PushReadableStream } from "./push-readable.js";
5
6
  import type { ReadableStream, ReadableStreamDefaultReader } from "./stream.js";
6
-
7
- const NOOP = () => {
8
- // no-op
9
- };
7
+ import { tryCancel } from "./try-close.js";
10
8
 
11
9
  export class BufferedReadableStream implements AsyncExactReadable {
12
10
  #buffered: Uint8Array | undefined;
11
+ // PERF: `subarray` is slow
12
+ // don't use it until absolutely necessary
13
13
  #bufferedOffset = 0;
14
14
  #bufferedLength = 0;
15
15
 
@@ -26,99 +26,124 @@ export class BufferedReadableStream implements AsyncExactReadable {
26
26
  this.reader = stream.getReader();
27
27
  }
28
28
 
29
- async #readSource() {
29
+ #readBuffered(length: number) {
30
+ if (!this.#buffered) {
31
+ return undefined;
32
+ }
33
+
34
+ const value = this.#buffered.subarray(
35
+ this.#bufferedOffset,
36
+ this.#bufferedOffset + length,
37
+ );
38
+
39
+ // PERF: Synchronous path for reading from internal buffer
40
+ if (this.#bufferedLength > length) {
41
+ this.#position += length;
42
+ this.#bufferedOffset += length;
43
+ this.#bufferedLength -= length;
44
+ return value;
45
+ }
46
+
47
+ this.#position += this.#bufferedLength;
48
+ this.#buffered = undefined;
49
+ this.#bufferedOffset = 0;
50
+ this.#bufferedLength = 0;
51
+ return value;
52
+ }
53
+
54
+ async #readSource(length: number): Promise<Uint8Array> {
30
55
  const { done, value } = await this.reader.read();
31
56
  if (done) {
32
57
  throw new ExactReadableEndedError();
33
58
  }
59
+
60
+ if (value.length > length) {
61
+ this.#buffered = value;
62
+ this.#bufferedOffset = length;
63
+ this.#bufferedLength = value.length - length;
64
+ this.#position += length;
65
+ return value.subarray(0, length);
66
+ }
67
+
68
+ this.#position += value.length;
34
69
  return value;
35
70
  }
36
71
 
37
- async #readAsync(length: number, initial?: Uint8Array) {
38
- let result: Uint8Array;
39
- let index: number;
72
+ iterateExactly(
73
+ length: number,
74
+ ): Iterator<MaybePromiseLike<Uint8Array>, void, void> {
75
+ let state = this.#buffered ? 0 : 1;
76
+ return {
77
+ next: () => {
78
+ switch (state) {
79
+ case 0: {
80
+ const value = this.#readBuffered(length)!;
81
+ if (value.length === length) {
82
+ state = 2;
83
+ } else {
84
+ length -= value.length;
85
+ state = 1;
86
+ }
87
+ return { done: false, value };
88
+ }
89
+ case 1:
90
+ state = 3;
91
+ return {
92
+ done: false,
93
+ value: this.#readSource(length).then((value) => {
94
+ if (value.length === length) {
95
+ state = 2;
96
+ } else {
97
+ length -= value.length;
98
+ state = 1;
99
+ }
100
+ return value;
101
+ }),
102
+ };
103
+ case 2:
104
+ return { done: true, value: undefined };
105
+ case 3:
106
+ throw new Error(
107
+ "Can't call `next` before previous Promise resolves",
108
+ );
109
+ default:
110
+ throw new Error("unreachable");
111
+ }
112
+ },
113
+ };
114
+ }
40
115
 
116
+ readExactly = bipedal(function* (
117
+ this: BufferedReadableStream,
118
+ then,
119
+ length: number,
120
+ ) {
121
+ let result: Uint8Array | undefined;
122
+ let index = 0;
123
+
124
+ const initial = this.#readBuffered(length);
41
125
  if (initial) {
126
+ if (initial.length === length) {
127
+ return initial;
128
+ }
129
+
42
130
  result = new Uint8Array(length);
43
- result.set(initial);
44
- index = initial.length;
131
+ result.set(initial, index);
132
+ index += initial.length;
45
133
  length -= initial.length;
46
134
  } else {
47
- const array = await this.#readSource();
48
- if (array.length === length) {
49
- this.#position += length;
50
- return array;
51
- }
52
-
53
- if (array.length > length) {
54
- this.#buffered = array;
55
- this.#bufferedOffset = length;
56
- this.#bufferedLength = array.length - length;
57
- this.#position += length;
58
- return array.subarray(0, length);
59
- }
60
-
61
135
  result = new Uint8Array(length);
62
- result.set(array);
63
- index = array.length;
64
- length -= array.length;
65
- this.#position += array.length;
66
136
  }
67
137
 
68
138
  while (length > 0) {
69
- const array = await this.#readSource();
70
- if (array.length === length) {
71
- result.set(array, index);
72
- this.#position += length;
73
- return result;
74
- }
75
-
76
- if (array.length > length) {
77
- this.#buffered = array;
78
- this.#bufferedOffset = length;
79
- this.#bufferedLength = array.length - length;
80
- result.set(array.subarray(0, length), index);
81
- this.#position += length;
82
- return result;
83
- }
84
-
85
- result.set(array, index);
86
- index += array.length;
87
- length -= array.length;
88
- this.#position += array.length;
139
+ const value = yield* then(this.#readSource(length));
140
+ result.set(value, index);
141
+ index += value.length;
142
+ length -= value.length;
89
143
  }
90
144
 
91
145
  return result;
92
- }
93
-
94
- /**
95
- *
96
- * @param length
97
- * @returns
98
- */
99
- readExactly(length: number): Uint8Array | Promise<Uint8Array> {
100
- // PERF: Add a synchronous path for reading from internal buffer
101
- if (this.#buffered) {
102
- const array = this.#buffered;
103
- const offset = this.#bufferedOffset;
104
- if (this.#bufferedLength > length) {
105
- // PERF: `subarray` is slow
106
- // don't use it until absolutely necessary
107
- this.#bufferedOffset += length;
108
- this.#bufferedLength -= length;
109
- this.#position += length;
110
- return array.subarray(offset, offset + length);
111
- }
112
-
113
- this.#buffered = undefined;
114
- this.#bufferedLength = 0;
115
- this.#bufferedOffset = 0;
116
- this.#position += array.length - offset;
117
- return this.#readAsync(length, array.subarray(offset));
118
- }
119
-
120
- return this.#readAsync(length);
121
- }
146
+ });
122
147
 
123
148
  /**
124
149
  * Return a readable stream with unconsumed data (if any) and
@@ -133,8 +158,7 @@ export class BufferedReadableStream implements AsyncExactReadable {
133
158
  await controller.enqueue(buffered);
134
159
 
135
160
  controller.abortSignal.addEventListener("abort", () => {
136
- // NOOP: the reader might already be released
137
- this.reader.cancel().catch(NOOP);
161
+ void tryCancel(this.reader);
138
162
  });
139
163
 
140
164
  // Manually pipe the stream
@@ -142,9 +166,9 @@ export class BufferedReadableStream implements AsyncExactReadable {
142
166
  const { done, value } = await this.reader.read();
143
167
  if (done) {
144
168
  return;
145
- } else {
146
- await controller.enqueue(value);
147
169
  }
170
+
171
+ await controller.enqueue(value);
148
172
  }
149
173
  });
150
174
  } else {
package/src/concat.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { PromiseResolver } from "@yume-chan/async";
2
+ import { EmptyUint8Array } from "@yume-chan/struct";
2
3
 
3
4
  import type { ReadableStreamDefaultController } from "./stream.js";
4
5
  import { ReadableStream, WritableStream } from "./stream.js";
@@ -81,9 +82,10 @@ export interface ConcatBufferReadableStream
81
82
  * A `TransformStream` that concatenates `Uint8Array`s.
82
83
  *
83
84
  * If you want to decode the result as string,
84
- * prefer `.pipeThrough(new DecodeUtf8Stream()).pipeThrough(new ConcatStringStream())`,
85
- * than `.pipeThough(new ConcatBufferStream()).pipeThrough(new DecodeUtf8Stream())`,
86
- * because concatenating strings is faster than concatenating `Uint8Array`s.
85
+ * prefer `.pipeThrough(new TextDecoderStream()).pipeThrough(new ConcatStringStream())`,
86
+ * than `.pipeThough(new ConcatBufferStream()).pipeThrough(new TextDecoderStream())`,
87
+ * because of JavaScript engine optimizations,
88
+ * concatenating strings is faster than concatenating `Uint8Array`s.
87
89
  */
88
90
  export class ConcatBufferStream {
89
91
  #segments: Uint8Array[] = [];
@@ -99,7 +101,7 @@ export class ConcatBufferStream {
99
101
  let offset = 0;
100
102
  switch (this.#segments.length) {
101
103
  case 0:
102
- result = new Uint8Array(0);
104
+ result = EmptyUint8Array;
103
105
  break;
104
106
  case 1:
105
107
  result = this.#segments[0]!;
package/src/consumable.ts CHANGED
@@ -1,125 +1,85 @@
1
- import { PromiseResolver } from "@yume-chan/async";
1
+ import { PromiseResolver, isPromiseLike } from "@yume-chan/async";
2
2
 
3
3
  import type {
4
4
  QueuingStrategy,
5
5
  WritableStreamDefaultController,
6
6
  WritableStreamDefaultWriter,
7
7
  } from "./stream.js";
8
- import { ReadableStream, TransformStream, WritableStream } from "./stream.js";
9
-
10
- interface Task {
11
- run<T>(callback: () => T): T;
12
- }
13
-
14
- interface Console {
15
- createTask(name: string): Task;
16
- }
17
-
18
- interface GlobalExtension {
19
- console: Console;
20
- }
21
-
22
- // `createTask` allows browser DevTools to track the call stack across async boundaries.
23
- const { console } = globalThis as unknown as GlobalExtension;
24
- const createTask: Console["createTask"] =
25
- console.createTask?.bind(console) ??
26
- (() => ({
27
- run(callback) {
28
- return callback();
29
- },
30
- }));
31
-
32
- export class Consumable<T> {
33
- readonly #task: Task;
34
- readonly #resolver: PromiseResolver<void>;
35
-
36
- readonly value: T;
37
- readonly consumed: Promise<void>;
38
-
39
- constructor(value: T) {
40
- this.#task = createTask("Consumable");
41
- this.value = value;
42
- this.#resolver = new PromiseResolver<void>();
43
- this.consumed = this.#resolver.promise;
44
- }
45
-
46
- consume() {
47
- this.#resolver.resolve();
48
- }
8
+ import {
9
+ ReadableStream as NativeReadableStream,
10
+ WritableStream as NativeWritableStream,
11
+ } from "./stream.js";
12
+ import type { Task } from "./task.js";
13
+ import { createTask } from "./task.js";
49
14
 
50
- error(error: unknown) {
51
- this.#resolver.reject(error);
15
+ // Workaround https://github.com/evanw/esbuild/issues/3923
16
+ class WritableStream<in T> extends NativeWritableStream<Consumable<T>> {
17
+ static async write<T>(
18
+ writer: WritableStreamDefaultWriter<Consumable<T>>,
19
+ value: T,
20
+ ) {
21
+ const consumable = new Consumable(value);
22
+ await writer.write(consumable);
23
+ await consumable.consumed;
52
24
  }
53
25
 
54
- async tryConsume<U>(callback: (value: T) => U) {
55
- try {
56
- // eslint-disable-next-line @typescript-eslint/await-thenable
57
- const result = await this.#task.run(() => callback(this.value));
58
- this.#resolver.resolve();
59
- return result;
60
- } catch (e) {
61
- this.#resolver.reject(e);
62
- throw e;
26
+ constructor(
27
+ sink: Consumable.WritableStreamSink<T>,
28
+ strategy?: QueuingStrategy<T>,
29
+ ) {
30
+ let wrappedStrategy: QueuingStrategy<Consumable<T>> | undefined;
31
+ if (strategy) {
32
+ wrappedStrategy = {};
33
+ if ("highWaterMark" in strategy) {
34
+ wrappedStrategy.highWaterMark = strategy.highWaterMark;
35
+ }
36
+ if ("size" in strategy) {
37
+ wrappedStrategy.size = (chunk) => {
38
+ return strategy.size!(
39
+ chunk instanceof Consumable ? chunk.value : chunk,
40
+ );
41
+ };
42
+ }
63
43
  }
64
- }
65
- }
66
-
67
- async function enqueue<T>(
68
- controller: { enqueue: (chunk: Consumable<T>) => void },
69
- chunk: T,
70
- ) {
71
- const output = new Consumable(chunk);
72
- controller.enqueue(output);
73
- await output.consumed;
74
- }
75
44
 
76
- export class WrapConsumableStream<T> extends TransformStream<T, Consumable<T>> {
77
- constructor() {
78
- super({
79
- async transform(chunk, controller) {
80
- await enqueue(controller, chunk);
45
+ super(
46
+ {
47
+ start(controller) {
48
+ return sink.start?.(controller);
49
+ },
50
+ async write(chunk, controller) {
51
+ await chunk.tryConsume((chunk) =>
52
+ sink.write?.(chunk, controller),
53
+ );
54
+ },
55
+ abort(reason) {
56
+ return sink.abort?.(reason);
57
+ },
58
+ close() {
59
+ return sink.close?.();
60
+ },
81
61
  },
82
- });
62
+ wrappedStrategy,
63
+ );
83
64
  }
84
65
  }
85
66
 
86
- export class UnwrapConsumableStream<T> extends TransformStream<
87
- Consumable<T>,
88
- T
89
- > {
90
- constructor() {
91
- super({
92
- transform(chunk, controller) {
93
- controller.enqueue(chunk.value);
94
- chunk.consume();
95
- },
96
- });
67
+ class ReadableStream<T> extends NativeReadableStream<Consumable<T>> {
68
+ static async enqueue<T>(
69
+ controller: { enqueue: (chunk: Consumable<T>) => void },
70
+ chunk: T,
71
+ ) {
72
+ const output = new Consumable(chunk);
73
+ controller.enqueue(output);
74
+ await output.consumed;
97
75
  }
98
- }
99
76
 
100
- export interface ConsumableReadableStreamController<T> {
101
- enqueue(chunk: T): Promise<void>;
102
- close(): void;
103
- error(reason: any): void;
104
- }
105
-
106
- export interface ConsumableReadableStreamSource<T> {
107
- start?(
108
- controller: ConsumableReadableStreamController<T>,
109
- ): void | PromiseLike<void>;
110
- pull?(
111
- controller: ConsumableReadableStreamController<T>,
112
- ): void | PromiseLike<void>;
113
- cancel?(reason: any): void | PromiseLike<void>;
114
- }
115
-
116
- export class ConsumableReadableStream<T> extends ReadableStream<Consumable<T>> {
117
77
  constructor(
118
- source: ConsumableReadableStreamSource<T>,
78
+ source: Consumable.ReadableStreamSource<T>,
119
79
  strategy?: QueuingStrategy<T>,
120
80
  ) {
121
81
  let wrappedController:
122
- | ConsumableReadableStreamController<T>
82
+ | Consumable.ReadableStreamController<T>
123
83
  | undefined;
124
84
 
125
85
  let wrappedStrategy: QueuingStrategy<Consumable<T>> | undefined;
@@ -140,7 +100,7 @@ export class ConsumableReadableStream<T> extends ReadableStream<Consumable<T>> {
140
100
  async start(controller) {
141
101
  wrappedController = {
142
102
  async enqueue(chunk) {
143
- await enqueue(controller, chunk);
103
+ await ReadableStream.enqueue(controller, chunk);
144
104
  },
145
105
  close() {
146
106
  controller.close();
@@ -164,128 +124,87 @@ export class ConsumableReadableStream<T> extends ReadableStream<Consumable<T>> {
164
124
  }
165
125
  }
166
126
 
167
- export interface ConsumableWritableStreamSink<T> {
168
- start?(
169
- controller: WritableStreamDefaultController,
170
- ): void | PromiseLike<void>;
171
- write?(
172
- chunk: T,
173
- controller: WritableStreamDefaultController,
174
- ): void | PromiseLike<void>;
175
- abort?(reason: any): void | PromiseLike<void>;
176
- close?(): void | PromiseLike<void>;
177
- }
127
+ export class Consumable<T> {
128
+ static readonly WritableStream = WritableStream;
178
129
 
179
- export class ConsumableWritableStream<T> extends WritableStream<Consumable<T>> {
180
- static async write<T>(
181
- writer: WritableStreamDefaultWriter<Consumable<T>>,
182
- value: T,
183
- ) {
184
- const consumable = new Consumable(value);
185
- await writer.write(consumable);
186
- await consumable.consumed;
187
- }
130
+ static readonly ReadableStream = ReadableStream;
188
131
 
189
- constructor(
190
- sink: ConsumableWritableStreamSink<T>,
191
- strategy?: QueuingStrategy<T>,
192
- ) {
193
- let wrappedStrategy: QueuingStrategy<Consumable<T>> | undefined;
194
- if (strategy) {
195
- wrappedStrategy = {};
196
- if ("highWaterMark" in strategy) {
197
- wrappedStrategy.highWaterMark = strategy.highWaterMark;
198
- }
199
- if ("size" in strategy) {
200
- wrappedStrategy.size = (chunk) => {
201
- return strategy.size!(chunk.value);
202
- };
203
- }
204
- }
132
+ readonly #task: Task;
133
+ readonly #resolver: PromiseResolver<void>;
205
134
 
206
- super(
207
- {
208
- start(controller) {
209
- return sink.start?.(controller);
210
- },
211
- async write(chunk, controller) {
212
- await chunk.tryConsume((value) =>
213
- sink.write?.(value, controller),
214
- );
215
- },
216
- abort(reason) {
217
- return sink.abort?.(reason);
218
- },
219
- close() {
220
- return sink.close?.();
221
- },
222
- },
223
- wrappedStrategy,
224
- );
135
+ readonly value: T;
136
+ readonly consumed: Promise<void>;
137
+
138
+ constructor(value: T) {
139
+ this.#task = createTask("Consumable");
140
+ this.value = value;
141
+ this.#resolver = new PromiseResolver<void>();
142
+ this.consumed = this.#resolver.promise;
225
143
  }
226
- }
227
144
 
228
- export interface ConsumableTransformer<I, O> {
229
- start?(
230
- controller: ConsumableReadableStreamController<O>,
231
- ): void | PromiseLike<void>;
232
- transform?(
233
- chunk: I,
234
- controller: ConsumableReadableStreamController<O>,
235
- ): void | PromiseLike<void>;
236
- flush?(
237
- controller: ConsumableReadableStreamController<O>,
238
- ): void | PromiseLike<void>;
239
- }
145
+ consume() {
146
+ this.#resolver.resolve();
147
+ }
240
148
 
241
- export class ConsumableTransformStream<I, O> extends TransformStream<
242
- Consumable<I>,
243
- Consumable<O>
244
- > {
245
- constructor(transformer: ConsumableTransformer<I, O>) {
246
- let wrappedController:
247
- | ConsumableReadableStreamController<O>
248
- | undefined;
149
+ error(error: unknown) {
150
+ this.#resolver.reject(error);
151
+ }
249
152
 
250
- super({
251
- async start(controller) {
252
- wrappedController = {
253
- async enqueue(chunk) {
254
- await enqueue(controller, chunk);
255
- },
256
- close() {
257
- controller.terminate();
153
+ tryConsume<U>(callback: (value: T) => U) {
154
+ try {
155
+ let result = this.#task.run(() => callback(this.value));
156
+ if (isPromiseLike(result)) {
157
+ result = result.then(
158
+ (value) => {
159
+ this.#resolver.resolve();
160
+ return value;
258
161
  },
259
- error(reason) {
260
- controller.error(reason);
162
+ (e) => {
163
+ this.#resolver.reject(e);
164
+ throw e;
261
165
  },
262
- };
263
-
264
- await transformer.start?.(wrappedController);
265
- },
266
- async transform(chunk) {
267
- await chunk.tryConsume((value) =>
268
- transformer.transform?.(value, wrappedController!),
269
- );
270
- chunk.consume();
271
- },
272
- async flush() {
273
- await transformer.flush?.(wrappedController!);
274
- },
275
- });
166
+ ) as U;
167
+ } else {
168
+ this.#resolver.resolve();
169
+ }
170
+ return result;
171
+ } catch (e) {
172
+ this.#resolver.reject(e);
173
+ throw e;
174
+ }
276
175
  }
277
176
  }
278
177
 
279
- export class ConsumableInspectStream<T> extends TransformStream<
280
- Consumable<T>,
281
- Consumable<T>
282
- > {
283
- constructor(callback: (value: T) => void) {
284
- super({
285
- transform(chunk, controller) {
286
- callback(chunk.value);
287
- controller.enqueue(chunk);
288
- },
289
- });
178
+ export namespace Consumable {
179
+ export interface WritableStreamSink<in T> {
180
+ start?(
181
+ controller: WritableStreamDefaultController,
182
+ ): void | PromiseLike<void>;
183
+ write?(
184
+ chunk: T,
185
+ controller: WritableStreamDefaultController,
186
+ ): void | PromiseLike<void>;
187
+ abort?(reason: unknown): void | PromiseLike<void>;
188
+ close?(): void | PromiseLike<void>;
189
+ }
190
+
191
+ export type WritableStream<in T> = typeof Consumable.WritableStream<T>;
192
+
193
+ export interface ReadableStreamController<T> {
194
+ enqueue(chunk: T): Promise<void>;
195
+ close(): void;
196
+ error(reason: unknown): void;
290
197
  }
198
+
199
+ export interface ReadableStreamSource<T> {
200
+ start?(
201
+ controller: ReadableStreamController<T>,
202
+ ): void | PromiseLike<void>;
203
+ pull?(
204
+ controller: ReadableStreamController<T>,
205
+ ): void | PromiseLike<void>;
206
+ cancel?(reason: unknown): void | PromiseLike<void>;
207
+ }
208
+
209
+ export type ReadableStream<T> = typeof Consumable.ReadableStream<T>;
291
210
  }