@yume-chan/stream-extra 0.0.18 → 0.0.20

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 (91) hide show
  1. package/CHANGELOG.json +27 -0
  2. package/CHANGELOG.md +16 -1
  3. package/README.md +1 -1
  4. package/esm/buffered-transform.d.ts +10 -10
  5. package/esm/buffered-transform.d.ts.map +1 -1
  6. package/esm/buffered-transform.js +56 -55
  7. package/esm/buffered-transform.js.map +1 -1
  8. package/esm/buffered.d.ts +24 -27
  9. package/esm/buffered.d.ts.map +1 -1
  10. package/esm/buffered.js +132 -123
  11. package/esm/buffered.js.map +1 -1
  12. package/esm/consumable.d.ts +52 -0
  13. package/esm/consumable.d.ts.map +1 -0
  14. package/esm/consumable.js +178 -0
  15. package/esm/consumable.js.map +1 -0
  16. package/esm/decode-utf8.d.ts +4 -4
  17. package/esm/decode-utf8.js +11 -11
  18. package/esm/distribution.d.ts +21 -0
  19. package/esm/distribution.d.ts.map +1 -0
  20. package/esm/distribution.js +99 -0
  21. package/esm/distribution.js.map +1 -0
  22. package/esm/duplex.d.ts +44 -47
  23. package/esm/duplex.d.ts.map +1 -1
  24. package/esm/duplex.js +92 -92
  25. package/esm/duplex.js.map +1 -1
  26. package/esm/gather-string.d.ts +6 -6
  27. package/esm/gather-string.d.ts.map +1 -1
  28. package/esm/gather-string.js +15 -13
  29. package/esm/gather-string.js.map +1 -1
  30. package/esm/index.d.ts +16 -15
  31. package/esm/index.d.ts.map +1 -1
  32. package/esm/index.js +16 -15
  33. package/esm/index.js.map +1 -1
  34. package/esm/inspect.d.ts +4 -4
  35. package/esm/inspect.js +11 -11
  36. package/esm/pipe-from.d.ts +12 -11
  37. package/esm/pipe-from.d.ts.map +1 -1
  38. package/esm/pipe-from.js +23 -25
  39. package/esm/pipe-from.js.map +1 -1
  40. package/esm/push-readable.d.ts +19 -18
  41. package/esm/push-readable.d.ts.map +1 -1
  42. package/esm/push-readable.js +66 -61
  43. package/esm/push-readable.js.map +1 -1
  44. package/esm/split-string.d.ts +4 -4
  45. package/esm/split-string.d.ts.map +1 -1
  46. package/esm/split-string.js +24 -24
  47. package/esm/split-string.js.map +1 -1
  48. package/esm/stream.d.ts +25 -24
  49. package/esm/stream.d.ts.map +1 -1
  50. package/esm/stream.js +26 -26
  51. package/esm/stream.js.map +1 -1
  52. package/esm/struct-deserialize.d.ts +6 -6
  53. package/esm/struct-deserialize.d.ts.map +1 -1
  54. package/esm/struct-deserialize.js +8 -8
  55. package/esm/struct-serialize.d.ts +5 -5
  56. package/esm/struct-serialize.js +10 -10
  57. package/esm/wrap-readable.d.ts +21 -20
  58. package/esm/wrap-readable.d.ts.map +1 -1
  59. package/esm/wrap-readable.js +56 -56
  60. package/esm/wrap-readable.js.map +1 -1
  61. package/esm/wrap-writable.d.ts +14 -12
  62. package/esm/wrap-writable.d.ts.map +1 -1
  63. package/esm/wrap-writable.js +62 -52
  64. package/esm/wrap-writable.js.map +1 -1
  65. package/package.json +9 -8
  66. package/src/buffered-transform.ts +15 -22
  67. package/src/buffered.ts +41 -33
  68. package/src/consumable.ts +281 -0
  69. package/src/distribution.ts +113 -0
  70. package/src/duplex.ts +35 -34
  71. package/src/gather-string.ts +6 -4
  72. package/src/index.ts +16 -15
  73. package/src/inspect.ts +1 -1
  74. package/src/pipe-from.ts +8 -6
  75. package/src/push-readable.ts +12 -12
  76. package/src/split-string.ts +5 -2
  77. package/src/stream.ts +1 -1
  78. package/src/struct-deserialize.ts +1 -1
  79. package/src/wrap-readable.ts +9 -9
  80. package/src/wrap-writable.ts +21 -9
  81. package/tsconfig.build.json +6 -1
  82. package/tsconfig.build.tsbuildinfo +1 -1
  83. package/esm/chunk.d.ts +0 -5
  84. package/esm/chunk.d.ts.map +0 -1
  85. package/esm/chunk.js +0 -15
  86. package/esm/chunk.js.map +0 -1
  87. package/esm/trace.d.ts +0 -16
  88. package/esm/trace.d.ts.map +0 -1
  89. package/esm/trace.js +0 -131
  90. package/esm/trace.js.map +0 -1
  91. package/src/chunk.ts +0 -15
package/src/buffered.ts CHANGED
@@ -1,25 +1,24 @@
1
+ import type { AsyncExactReadable } from "@yume-chan/struct";
2
+ import { ExactReadableEndedError } from "@yume-chan/struct";
3
+
1
4
  import { PushReadableStream } from "./push-readable.js";
2
- import {
3
- type ReadableStream,
4
- type ReadableStreamDefaultReader,
5
- } from "./stream.js";
5
+ import type { ReadableStream, ReadableStreamDefaultReader } from "./stream.js";
6
6
 
7
- export class BufferedReadableStreamEndedError extends Error {
8
- public constructor() {
9
- super("Stream ended");
7
+ const NOOP = () => {
8
+ // no-op
9
+ };
10
10
 
11
- // Fix Error's prototype chain when compiling to ES5
12
- Object.setPrototypeOf(this, new.target.prototype);
13
- }
14
- }
11
+ export class BufferedReadableStream implements AsyncExactReadable {
12
+ #buffered: Uint8Array | undefined;
13
+ #bufferedOffset = 0;
14
+ #bufferedLength = 0;
15
15
 
16
- export class BufferedReadableStream {
17
- private buffered: Uint8Array | undefined;
18
- private bufferedOffset = 0;
19
- private bufferedLength = 0;
16
+ #position = 0;
17
+ public get position() {
18
+ return this.#position;
19
+ }
20
20
 
21
21
  protected readonly stream: ReadableStream<Uint8Array>;
22
-
23
22
  protected readonly reader: ReadableStreamDefaultReader<Uint8Array>;
24
23
 
25
24
  public constructor(stream: ReadableStream<Uint8Array>) {
@@ -30,8 +29,9 @@ export class BufferedReadableStream {
30
29
  private async readSource() {
31
30
  const { done, value } = await this.reader.read();
32
31
  if (done) {
33
- throw new BufferedReadableStreamEndedError();
32
+ throw new ExactReadableEndedError();
34
33
  }
34
+ this.#position += value.byteLength;
35
35
  return value;
36
36
  }
37
37
 
@@ -51,9 +51,9 @@ export class BufferedReadableStream {
51
51
  }
52
52
 
53
53
  if (array.byteLength > length) {
54
- this.buffered = array;
55
- this.bufferedOffset = length;
56
- this.bufferedLength = array.byteLength - length;
54
+ this.#buffered = array;
55
+ this.#bufferedOffset = length;
56
+ this.#bufferedLength = array.byteLength - length;
57
57
  return array.subarray(0, length);
58
58
  }
59
59
 
@@ -71,9 +71,9 @@ export class BufferedReadableStream {
71
71
  }
72
72
 
73
73
  if (array.byteLength > length) {
74
- this.buffered = array;
75
- this.bufferedOffset = length;
76
- this.bufferedLength = array.byteLength - length;
74
+ this.#buffered = array;
75
+ this.#bufferedOffset = length;
76
+ this.#bufferedLength = array.byteLength - length;
77
77
  result.set(array.subarray(0, length), index);
78
78
  return result;
79
79
  }
@@ -91,20 +91,22 @@ export class BufferedReadableStream {
91
91
  * @param length
92
92
  * @returns
93
93
  */
94
- public read(length: number): Uint8Array | Promise<Uint8Array> {
94
+ public readExactly(length: number): Uint8Array | Promise<Uint8Array> {
95
95
  // PERF: Add a synchronous path for reading from internal buffer
96
- if (this.buffered) {
97
- const array = this.buffered;
98
- const offset = this.bufferedOffset;
99
- if (this.bufferedLength > length) {
96
+ if (this.#buffered) {
97
+ const array = this.#buffered;
98
+ const offset = this.#bufferedOffset;
99
+ if (this.#bufferedLength > length) {
100
100
  // PERF: `subarray` is slow
101
101
  // don't use it until absolutely necessary
102
- this.bufferedOffset += length;
103
- this.bufferedLength -= length;
102
+ this.#bufferedOffset += length;
103
+ this.#bufferedLength -= length;
104
104
  return array.subarray(offset, offset + length);
105
105
  }
106
106
 
107
- this.buffered = undefined;
107
+ this.#buffered = undefined;
108
+ this.#bufferedLength = 0;
109
+ this.#bufferedOffset = 0;
108
110
  return this.readAsync(length, array.subarray(offset));
109
111
  }
110
112
 
@@ -117,10 +119,16 @@ export class BufferedReadableStream {
117
119
  * @returns A `ReadableStream`
118
120
  */
119
121
  public release(): ReadableStream<Uint8Array> {
120
- if (this.buffered) {
122
+ if (this.#bufferedLength > 0) {
121
123
  return new PushReadableStream<Uint8Array>(async (controller) => {
122
124
  // Put the remaining data back to the stream
123
- await controller.enqueue(this.buffered!);
125
+ const buffered = this.#buffered!.subarray(this.#bufferedOffset);
126
+ await controller.enqueue(buffered);
127
+
128
+ controller.abortSignal.addEventListener("abort", () => {
129
+ // NOOP: the reader might already be released
130
+ this.reader.cancel().catch(NOOP);
131
+ });
124
132
 
125
133
  // Manually pipe the stream
126
134
  while (true) {
@@ -0,0 +1,281 @@
1
+ import { PromiseResolver } from "@yume-chan/async";
2
+
3
+ import type { QueuingStrategy, WritableStreamDefaultWriter } from "./stream.js";
4
+ import { ReadableStream, TransformStream, WritableStream } from "./stream.js";
5
+
6
+ interface Task {
7
+ run<T>(callback: () => T): T;
8
+ }
9
+
10
+ interface Console {
11
+ createTask(name: string): Task;
12
+ }
13
+
14
+ interface GlobalExtension {
15
+ console: Console;
16
+ }
17
+
18
+ // `createTask` allows browser DevTools to track the call stack across async boundaries.
19
+ const { console } = globalThis as unknown as GlobalExtension;
20
+ const createTask: Console["createTask"] =
21
+ console.createTask?.bind(console) ??
22
+ (() => ({
23
+ run(callback) {
24
+ return callback();
25
+ },
26
+ }));
27
+
28
+ export class Consumable<T> {
29
+ readonly #task: Task;
30
+ readonly #resolver: PromiseResolver<void>;
31
+
32
+ public readonly value: T;
33
+ public readonly consumed: Promise<void>;
34
+
35
+ public constructor(value: T) {
36
+ this.#task = createTask("Consumable");
37
+ this.value = value;
38
+ this.#resolver = new PromiseResolver<void>();
39
+ this.consumed = this.#resolver.promise;
40
+ }
41
+
42
+ public consume() {
43
+ this.#resolver.resolve();
44
+ }
45
+
46
+ public error(error: any) {
47
+ this.#resolver.reject(error);
48
+ }
49
+
50
+ public async tryConsume<U>(callback: (value: T) => U) {
51
+ try {
52
+ // eslint-disable-next-line @typescript-eslint/await-thenable
53
+ const result = await this.#task.run(() => callback(this.value));
54
+ this.consume();
55
+ return result;
56
+ } catch (e) {
57
+ this.#resolver.reject(e);
58
+ throw e;
59
+ }
60
+ }
61
+ }
62
+
63
+ async function enqueue<T>(
64
+ controller: { enqueue: (chunk: Consumable<T>) => void },
65
+ chunk: T
66
+ ) {
67
+ const output = new Consumable(chunk);
68
+ controller.enqueue(output);
69
+ await output.consumed;
70
+ }
71
+
72
+ export class WrapConsumableStream<T> extends TransformStream<T, Consumable<T>> {
73
+ public constructor() {
74
+ super({
75
+ async transform(chunk, controller) {
76
+ await enqueue(controller, chunk);
77
+ },
78
+ });
79
+ }
80
+ }
81
+
82
+ export class UnwrapConsumableStream<T> extends TransformStream<
83
+ Consumable<T>,
84
+ T
85
+ > {
86
+ public constructor() {
87
+ super({
88
+ transform(chunk, controller) {
89
+ controller.enqueue(chunk.value);
90
+ chunk.consume();
91
+ },
92
+ });
93
+ }
94
+ }
95
+
96
+ export interface ConsumableReadableStreamController<T> {
97
+ enqueue(chunk: T): Promise<void>;
98
+ close(): void;
99
+ error(reason: any): void;
100
+ }
101
+
102
+ export interface ConsumableReadableStreamSource<T> {
103
+ start?(
104
+ controller: ConsumableReadableStreamController<T>
105
+ ): void | PromiseLike<void>;
106
+ pull?(
107
+ controller: ConsumableReadableStreamController<T>
108
+ ): void | PromiseLike<void>;
109
+ cancel?(reason: any): void | PromiseLike<void>;
110
+ }
111
+
112
+ export class ConsumableReadableStream<T> extends ReadableStream<Consumable<T>> {
113
+ public constructor(
114
+ source: ConsumableReadableStreamSource<T>,
115
+ strategy?: QueuingStrategy<T>
116
+ ) {
117
+ let wrappedController:
118
+ | ConsumableReadableStreamController<T>
119
+ | undefined;
120
+
121
+ let wrappedStrategy: QueuingStrategy<Consumable<T>> | undefined;
122
+ if (strategy) {
123
+ wrappedStrategy = {};
124
+ if ("highWaterMark" in strategy) {
125
+ wrappedStrategy.highWaterMark = strategy.highWaterMark;
126
+ }
127
+ if ("size" in strategy) {
128
+ wrappedStrategy.size = (chunk) => {
129
+ return strategy.size!(chunk.value);
130
+ };
131
+ }
132
+ }
133
+
134
+ super(
135
+ {
136
+ async start(controller) {
137
+ wrappedController = {
138
+ async enqueue(chunk) {
139
+ await enqueue(controller, chunk);
140
+ },
141
+ close() {
142
+ controller.close();
143
+ },
144
+ error(reason) {
145
+ controller.error(reason);
146
+ },
147
+ };
148
+
149
+ await source.start?.(wrappedController);
150
+ },
151
+ async pull() {
152
+ await source.pull?.(wrappedController!);
153
+ },
154
+ async cancel(reason) {
155
+ await source.cancel?.(reason);
156
+ },
157
+ },
158
+ wrappedStrategy
159
+ );
160
+ }
161
+ }
162
+
163
+ export interface ConsumableWritableStreamSink<T> {
164
+ start?(): void | PromiseLike<void>;
165
+ write?(chunk: T): void | PromiseLike<void>;
166
+ abort?(reason: any): void | PromiseLike<void>;
167
+ close?(): void | PromiseLike<void>;
168
+ }
169
+
170
+ export class ConsumableWritableStream<T> extends WritableStream<Consumable<T>> {
171
+ public static async write<T>(
172
+ writer: WritableStreamDefaultWriter<Consumable<T>>,
173
+ value: T
174
+ ) {
175
+ const consumable = new Consumable(value);
176
+ await writer.write(consumable);
177
+ await consumable.consumed;
178
+ }
179
+
180
+ public constructor(
181
+ sink: ConsumableWritableStreamSink<T>,
182
+ strategy?: QueuingStrategy<T>
183
+ ) {
184
+ let wrappedStrategy: QueuingStrategy<Consumable<T>> | undefined;
185
+ if (strategy) {
186
+ wrappedStrategy = {};
187
+ if ("highWaterMark" in strategy) {
188
+ wrappedStrategy.highWaterMark = strategy.highWaterMark;
189
+ }
190
+ if ("size" in strategy) {
191
+ wrappedStrategy.size = (chunk) => {
192
+ return strategy.size!(chunk.value);
193
+ };
194
+ }
195
+ }
196
+
197
+ super(
198
+ {
199
+ start() {
200
+ return sink.start?.();
201
+ },
202
+ async write(chunk) {
203
+ await chunk.tryConsume((value) => sink.write?.(value));
204
+ chunk.consume();
205
+ },
206
+ abort(reason) {
207
+ return sink.abort?.(reason);
208
+ },
209
+ close() {
210
+ return sink.close?.();
211
+ },
212
+ },
213
+ wrappedStrategy
214
+ );
215
+ }
216
+ }
217
+
218
+ export interface ConsumableTransformer<I, O> {
219
+ start?(
220
+ controller: ConsumableReadableStreamController<O>
221
+ ): void | PromiseLike<void>;
222
+ transform?(
223
+ chunk: I,
224
+ controller: ConsumableReadableStreamController<O>
225
+ ): void | PromiseLike<void>;
226
+ flush?(
227
+ controller: ConsumableReadableStreamController<O>
228
+ ): void | PromiseLike<void>;
229
+ }
230
+
231
+ export class ConsumableTransformStream<I, O> extends TransformStream<
232
+ Consumable<I>,
233
+ Consumable<O>
234
+ > {
235
+ public constructor(transformer: ConsumableTransformer<I, O>) {
236
+ let wrappedController:
237
+ | ConsumableReadableStreamController<O>
238
+ | undefined;
239
+
240
+ super({
241
+ async start(controller) {
242
+ wrappedController = {
243
+ async enqueue(chunk) {
244
+ await enqueue(controller, chunk);
245
+ },
246
+ close() {
247
+ controller.terminate();
248
+ },
249
+ error(reason) {
250
+ controller.error(reason);
251
+ },
252
+ };
253
+
254
+ await transformer.start?.(wrappedController);
255
+ },
256
+ async transform(chunk) {
257
+ await chunk.tryConsume((value) =>
258
+ transformer.transform?.(value, wrappedController!)
259
+ );
260
+ chunk.consume();
261
+ },
262
+ async flush() {
263
+ await transformer.flush?.(wrappedController!);
264
+ },
265
+ });
266
+ }
267
+ }
268
+
269
+ export class ConsumableInspectStream<T> extends TransformStream<
270
+ Consumable<T>,
271
+ Consumable<T>
272
+ > {
273
+ public constructor(callback: (value: T) => void) {
274
+ super({
275
+ transform(chunk, controller) {
276
+ callback(chunk.value);
277
+ controller.enqueue(chunk);
278
+ },
279
+ });
280
+ }
281
+ }
@@ -0,0 +1,113 @@
1
+ import { ConsumableTransformStream } from "./consumable.js";
2
+
3
+ /**
4
+ * Splits or combines buffers to specified size.
5
+ */
6
+ export class BufferCombiner {
7
+ #capacity: number;
8
+ readonly #buffer: Uint8Array;
9
+ #offset: number;
10
+ #available: number;
11
+
12
+ public constructor(size: number) {
13
+ this.#capacity = size;
14
+ this.#buffer = new Uint8Array(size);
15
+ this.#offset = 0;
16
+ this.#available = size;
17
+ }
18
+
19
+ /**
20
+ * Pushes data to the combiner.
21
+ * @param data The input data to be split or combined.
22
+ * @returns
23
+ * A generator that yields buffers of specified size.
24
+ * It may yield the same buffer multiple times, consume the data before calling `next`.
25
+ */
26
+ public *push(data: Uint8Array): Generator<Uint8Array, void, void> {
27
+ let offset = 0;
28
+ let available = data.byteLength;
29
+
30
+ if (this.#offset !== 0) {
31
+ if (available >= this.#available) {
32
+ this.#buffer.set(
33
+ data.subarray(0, this.#available),
34
+ this.#offset
35
+ );
36
+ offset += this.#available;
37
+ available -= this.#available;
38
+
39
+ yield this.#buffer;
40
+ this.#offset = 0;
41
+ this.#available = this.#capacity;
42
+
43
+ if (available === 0) {
44
+ return;
45
+ }
46
+ } else {
47
+ this.#buffer.set(data, this.#offset);
48
+ this.#offset += available;
49
+ this.#available -= available;
50
+ return;
51
+ }
52
+ }
53
+
54
+ while (available >= this.#capacity) {
55
+ const end = offset + this.#capacity;
56
+ yield data.subarray(offset, end);
57
+ offset = end;
58
+ available -= this.#capacity;
59
+ }
60
+
61
+ if (available > 0) {
62
+ this.#buffer.set(data.subarray(offset), this.#offset);
63
+ this.#offset += available;
64
+ this.#available -= available;
65
+ }
66
+ }
67
+
68
+ public flush(): Uint8Array | undefined {
69
+ if (this.#offset === 0) {
70
+ return undefined;
71
+ }
72
+
73
+ const output = this.#buffer.subarray(0, this.#offset);
74
+ this.#offset = 0;
75
+ this.#available = this.#capacity;
76
+ return output;
77
+ }
78
+ }
79
+
80
+ export class DistributionStream extends ConsumableTransformStream<
81
+ Uint8Array,
82
+ Uint8Array
83
+ > {
84
+ public constructor(size: number, combine = false) {
85
+ const combiner = combine ? new BufferCombiner(size) : undefined;
86
+ super({
87
+ async transform(chunk, controller) {
88
+ if (combiner) {
89
+ for (const buffer of combiner.push(chunk)) {
90
+ await controller.enqueue(buffer);
91
+ }
92
+ } else {
93
+ let offset = 0;
94
+ let available = chunk.byteLength;
95
+ while (available > 0) {
96
+ const end = offset + size;
97
+ await controller.enqueue(chunk.subarray(offset, end));
98
+ offset = end;
99
+ available -= size;
100
+ }
101
+ }
102
+ },
103
+ async flush(controller) {
104
+ if (combiner) {
105
+ const data = combiner.flush();
106
+ if (data) {
107
+ await controller.enqueue(data);
108
+ }
109
+ }
110
+ },
111
+ });
112
+ }
113
+ }
package/src/duplex.ts CHANGED
@@ -1,14 +1,18 @@
1
1
  import { PromiseResolver } from "@yume-chan/async";
2
- import { type ValueOrPromise } from "@yume-chan/struct";
2
+ import type { ValueOrPromise } from "@yume-chan/struct";
3
3
 
4
- import {
5
- WritableStream,
6
- type ReadableStream,
7
- type ReadableStreamDefaultController,
8
- type WritableStreamDefaultWriter,
4
+ import type {
5
+ ReadableStream,
6
+ ReadableStreamDefaultController,
7
+ WritableStreamDefaultWriter,
9
8
  } from "./stream.js";
9
+ import { WritableStream } from "./stream.js";
10
10
  import { WrapReadableStream } from "./wrap-readable.js";
11
11
 
12
+ const NOOP = () => {
13
+ // no-op
14
+ };
15
+
12
16
  export interface DuplexStreamFactoryOptions {
13
17
  /**
14
18
  * Callback when any `ReadableStream` is cancelled (the user doesn't need any more data),
@@ -42,33 +46,33 @@ export interface DuplexStreamFactoryOptions {
42
46
  * when any of them is closed, all other streams will be closed as well.
43
47
  */
44
48
  export class DuplexStreamFactory<R, W> {
45
- private readableControllers: ReadableStreamDefaultController<R>[] = [];
46
- private writers: WritableStreamDefaultWriter<W>[] = [];
49
+ #readableControllers: ReadableStreamDefaultController<R>[] = [];
50
+ #writers: WritableStreamDefaultWriter<W>[] = [];
47
51
 
48
- private _writableClosed = false;
52
+ #writableClosed = false;
49
53
  public get writableClosed() {
50
- return this._writableClosed;
54
+ return this.#writableClosed;
51
55
  }
52
56
 
53
- private _closed = new PromiseResolver<void>();
57
+ #closed = new PromiseResolver<void>();
54
58
  public get closed() {
55
- return this._closed.promise;
59
+ return this.#closed.promise;
56
60
  }
57
61
 
58
- private options: DuplexStreamFactoryOptions;
62
+ readonly #options: DuplexStreamFactoryOptions;
59
63
 
60
64
  public constructor(options?: DuplexStreamFactoryOptions) {
61
- this.options = options ?? {};
65
+ this.#options = options ?? {};
62
66
  }
63
67
 
64
68
  public wrapReadable(readable: ReadableStream<R>): WrapReadableStream<R> {
65
69
  return new WrapReadableStream<R>({
66
70
  start: (controller) => {
67
- this.readableControllers.push(controller);
71
+ this.#readableControllers.push(controller);
68
72
  return readable;
69
73
  },
70
74
  cancel: async () => {
71
- // cancel means the local peer closes the connection first.
75
+ // cancel means the local peer wants to close the connection.
72
76
  await this.close();
73
77
  },
74
78
  close: async () => {
@@ -80,13 +84,12 @@ export class DuplexStreamFactory<R, W> {
80
84
 
81
85
  public createWritable(stream: WritableStream<W>): WritableStream<W> {
82
86
  const writer = stream.getWriter();
83
- this.writers.push(writer);
87
+ this.#writers.push(writer);
84
88
 
85
89
  // `WritableStream` has no way to tell if the remote peer has closed the connection.
86
90
  // So it only triggers `close`.
87
91
  return new WritableStream<W>({
88
92
  write: async (chunk) => {
89
- await writer.ready;
90
93
  await writer.write(chunk);
91
94
  },
92
95
  abort: async (reason) => {
@@ -94,45 +97,43 @@ export class DuplexStreamFactory<R, W> {
94
97
  await this.close();
95
98
  },
96
99
  close: async () => {
97
- await writer.close().catch((e) => {
98
- void e;
99
- });
100
+ // NOOP: the writer is already closed
101
+ await writer.close().catch(NOOP);
100
102
  await this.close();
101
103
  },
102
104
  });
103
105
  }
104
106
 
105
107
  public async close() {
106
- if (this._writableClosed) {
108
+ if (this.#writableClosed) {
107
109
  return;
108
110
  }
109
- this._writableClosed = true;
111
+ this.#writableClosed = true;
110
112
 
111
113
  // Call `close` first, so it can still write data to `WritableStream`s.
112
- if ((await this.options.close?.()) !== false) {
114
+ if ((await this.#options.close?.()) !== false) {
113
115
  // `close` can return `false` to disable automatic `dispose`.
114
116
  await this.dispose();
115
117
  }
116
118
 
117
- for (const writer of this.writers) {
118
- await writer.close().catch((e) => {
119
- void e;
120
- });
119
+ for (const writer of this.#writers) {
120
+ // NOOP: the writer is already closed
121
+ writer.close().catch(NOOP);
121
122
  }
122
123
  }
123
124
 
124
125
  public async dispose() {
125
- this._writableClosed = true;
126
- this._closed.resolve();
126
+ this.#writableClosed = true;
127
+ this.#closed.resolve();
127
128
 
128
- for (const controller of this.readableControllers) {
129
+ for (const controller of this.#readableControllers) {
129
130
  try {
130
131
  controller.close();
131
- } catch (e) {
132
- void e;
132
+ } catch {
133
+ // ignore
133
134
  }
134
135
  }
135
136
 
136
- await this.options.dispose?.();
137
+ await this.#options.dispose?.();
137
138
  }
138
139
  }
@@ -1,14 +1,16 @@
1
1
  import { WritableStream } from "./stream.js";
2
2
 
3
- export class GatherStringStream extends WritableStream<string>{
3
+ export class GatherStringStream extends WritableStream<string> {
4
4
  // PERF: rope (concat strings) is faster than `[].join('')`
5
- private _result = '';
6
- public get result() { return this._result; }
5
+ #result = "";
6
+ public get result() {
7
+ return this.#result;
8
+ }
7
9
 
8
10
  public constructor() {
9
11
  super({
10
12
  write: (chunk) => {
11
- this._result += chunk;
13
+ this.#result += chunk;
12
14
  },
13
15
  });
14
16
  }