@yume-chan/stream-extra 0.0.23 → 0.0.24

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 (58) hide show
  1. package/CHANGELOG.json +18 -0
  2. package/CHANGELOG.md +10 -1
  3. package/LICENSE +1 -1
  4. package/esm/concat.d.ts +4 -3
  5. package/esm/concat.d.ts.map +1 -1
  6. package/esm/concat.js +6 -4
  7. package/esm/concat.js.map +1 -1
  8. package/esm/consumable.d.ts +30 -42
  9. package/esm/consumable.d.ts.map +1 -1
  10. package/esm/consumable.js +96 -143
  11. package/esm/consumable.js.map +1 -1
  12. package/esm/distribution.d.ts +3 -2
  13. package/esm/distribution.d.ts.map +1 -1
  14. package/esm/distribution.js +22 -18
  15. package/esm/distribution.js.map +1 -1
  16. package/esm/encoding.d.ts +21 -0
  17. package/esm/encoding.d.ts.map +1 -0
  18. package/esm/encoding.js +4 -0
  19. package/esm/encoding.js.map +1 -0
  20. package/esm/index.d.ts +3 -1
  21. package/esm/index.d.ts.map +1 -1
  22. package/esm/index.js +3 -1
  23. package/esm/index.js.map +1 -1
  24. package/esm/maybe-consumable.d.ts +21 -0
  25. package/esm/maybe-consumable.d.ts.map +1 -0
  26. package/esm/maybe-consumable.js +62 -0
  27. package/esm/maybe-consumable.js.map +1 -0
  28. package/esm/stream.d.ts +4 -4
  29. package/esm/stream.d.ts.map +1 -1
  30. package/esm/struct-deserialize.d.ts +1 -1
  31. package/esm/struct-deserialize.d.ts.map +1 -1
  32. package/esm/struct-deserialize.js.map +1 -1
  33. package/esm/struct-serialize.d.ts +1 -1
  34. package/esm/struct-serialize.d.ts.map +1 -1
  35. package/esm/task.d.ts +5 -0
  36. package/esm/task.d.ts.map +1 -0
  37. package/esm/task.js +9 -0
  38. package/esm/task.js.map +1 -0
  39. package/esm/types.d.ts +32 -32
  40. package/esm/types.d.ts.map +1 -1
  41. package/package.json +8 -10
  42. package/src/concat.ts +6 -4
  43. package/src/consumable.ts +144 -230
  44. package/src/distribution.ts +30 -20
  45. package/src/encoding.ts +39 -0
  46. package/src/index.ts +3 -1
  47. package/src/maybe-consumable.ts +101 -0
  48. package/src/stream.ts +4 -4
  49. package/src/struct-deserialize.ts +2 -2
  50. package/src/struct-serialize.ts +1 -1
  51. package/src/task.ts +21 -0
  52. package/src/types.ts +32 -32
  53. package/tsconfig.build.tsbuildinfo +1 -1
  54. package/esm/decode-utf8.d.ts +0 -5
  55. package/esm/decode-utf8.d.ts.map +0 -1
  56. package/esm/decode-utf8.js +0 -12
  57. package/esm/decode-utf8.js.map +0 -1
  58. package/src/decode-utf8.ts +0 -13
package/src/concat.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { PromiseResolver } from "@yume-chan/async";
2
+ import { EMPTY_UINT8_ARRAY } 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 = EMPTY_UINT8_ARRAY;
103
105
  break;
104
106
  case 1:
105
107
  result = this.#segments[0]!;
package/src/consumable.ts CHANGED
@@ -5,30 +5,17 @@ import type {
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
- }
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";
17
14
 
18
- interface GlobalExtension {
19
- console: Console;
15
+ function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
16
+ return typeof value === "object" && value !== null && "then" in value;
20
17
  }
21
18
 
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
19
  export class Consumable<T> {
33
20
  readonly #task: Task;
34
21
  readonly #resolver: PromiseResolver<void>;
@@ -51,11 +38,23 @@ export class Consumable<T> {
51
38
  this.#resolver.reject(error);
52
39
  }
53
40
 
54
- async tryConsume<U>(callback: (value: T) => U) {
41
+ tryConsume<U>(callback: (value: T) => U) {
55
42
  try {
56
- // eslint-disable-next-line @typescript-eslint/await-thenable
57
- const result = await this.#task.run(() => callback(this.value));
58
- this.#resolver.resolve();
43
+ let result = this.#task.run(() => callback(this.value));
44
+ if (isPromiseLike(result)) {
45
+ result = result.then(
46
+ (value) => {
47
+ this.#resolver.resolve();
48
+ return value;
49
+ },
50
+ (e) => {
51
+ this.#resolver.reject(e);
52
+ throw e;
53
+ },
54
+ ) as U;
55
+ } else {
56
+ this.#resolver.resolve();
57
+ }
59
58
  return result;
60
59
  } catch (e) {
61
60
  this.#resolver.reject(e);
@@ -64,228 +63,143 @@ export class Consumable<T> {
64
63
  }
65
64
  }
66
65
 
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
-
76
- export class WrapConsumableStream<T> extends TransformStream<T, Consumable<T>> {
77
- constructor() {
78
- super({
79
- async transform(chunk, controller) {
80
- await enqueue(controller, chunk);
81
- },
82
- });
66
+ export namespace Consumable {
67
+ export interface WritableStreamSink<in T> {
68
+ start?(
69
+ controller: WritableStreamDefaultController,
70
+ ): void | PromiseLike<void>;
71
+ write?(
72
+ chunk: T,
73
+ controller: WritableStreamDefaultController,
74
+ ): void | PromiseLike<void>;
75
+ abort?(reason: unknown): void | PromiseLike<void>;
76
+ close?(): void | PromiseLike<void>;
83
77
  }
84
- }
85
78
 
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
- });
97
- }
98
- }
99
-
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
- constructor(
118
- source: ConsumableReadableStreamSource<T>,
119
- strategy?: QueuingStrategy<T>,
120
- ) {
121
- let wrappedController:
122
- | ConsumableReadableStreamController<T>
123
- | undefined;
124
-
125
- let wrappedStrategy: QueuingStrategy<Consumable<T>> | undefined;
126
- if (strategy) {
127
- wrappedStrategy = {};
128
- if ("highWaterMark" in strategy) {
129
- wrappedStrategy.highWaterMark = strategy.highWaterMark;
130
- }
131
- if ("size" in strategy) {
132
- wrappedStrategy.size = (chunk) => {
133
- return strategy.size!(chunk.value);
134
- };
135
- }
79
+ export class WritableStream<in T> extends NativeWritableStream<
80
+ Consumable<T>
81
+ > {
82
+ static async write<T>(
83
+ writer: WritableStreamDefaultWriter<Consumable<T>>,
84
+ value: T,
85
+ ) {
86
+ const consumable = new Consumable(value);
87
+ await writer.write(consumable);
88
+ await consumable.consumed;
136
89
  }
137
90
 
138
- super(
139
- {
140
- async start(controller) {
141
- wrappedController = {
142
- async enqueue(chunk) {
143
- await enqueue(controller, chunk);
144
- },
145
- close() {
146
- controller.close();
147
- },
148
- error(reason) {
149
- controller.error(reason);
150
- },
91
+ constructor(
92
+ sink: WritableStreamSink<T>,
93
+ strategy?: QueuingStrategy<T>,
94
+ ) {
95
+ let wrappedStrategy: QueuingStrategy<Consumable<T>> | undefined;
96
+ if (strategy) {
97
+ wrappedStrategy = {};
98
+ if ("highWaterMark" in strategy) {
99
+ wrappedStrategy.highWaterMark = strategy.highWaterMark;
100
+ }
101
+ if ("size" in strategy) {
102
+ wrappedStrategy.size = (chunk) => {
103
+ return strategy.size!(
104
+ chunk instanceof Consumable ? chunk.value : chunk,
105
+ );
151
106
  };
107
+ }
108
+ }
152
109
 
153
- await source.start?.(wrappedController);
154
- },
155
- async pull() {
156
- await source.pull?.(wrappedController!);
157
- },
158
- async cancel(reason) {
159
- await source.cancel?.(reason);
110
+ super(
111
+ {
112
+ start(controller) {
113
+ return sink.start?.(controller);
114
+ },
115
+ async write(chunk, controller) {
116
+ await chunk.tryConsume((chunk) =>
117
+ sink.write?.(chunk, controller),
118
+ );
119
+ },
120
+ abort(reason) {
121
+ return sink.abort?.(reason);
122
+ },
123
+ close() {
124
+ return sink.close?.();
125
+ },
160
126
  },
161
- },
162
- wrappedStrategy,
163
- );
127
+ wrappedStrategy,
128
+ );
129
+ }
164
130
  }
165
- }
166
-
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
- }
178
131
 
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;
132
+ export interface ReadableStreamController<T> {
133
+ enqueue(chunk: T): Promise<void>;
134
+ close(): void;
135
+ error(reason: unknown): void;
187
136
  }
188
137
 
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
- }
205
-
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
- );
138
+ export interface ReadableStreamSource<T> {
139
+ start?(
140
+ controller: ReadableStreamController<T>,
141
+ ): void | PromiseLike<void>;
142
+ pull?(
143
+ controller: ReadableStreamController<T>,
144
+ ): void | PromiseLike<void>;
145
+ cancel?(reason: unknown): void | PromiseLike<void>;
225
146
  }
226
- }
227
147
 
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
- }
148
+ export class ReadableStream<T> extends NativeReadableStream<Consumable<T>> {
149
+ static async enqueue<T>(
150
+ controller: { enqueue: (chunk: Consumable<T>) => void },
151
+ chunk: T,
152
+ ) {
153
+ const output = new Consumable(chunk);
154
+ controller.enqueue(output);
155
+ await output.consumed;
156
+ }
240
157
 
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;
158
+ constructor(
159
+ source: ReadableStreamSource<T>,
160
+ strategy?: QueuingStrategy<T>,
161
+ ) {
162
+ let wrappedController: ReadableStreamController<T> | undefined;
163
+
164
+ let wrappedStrategy: QueuingStrategy<Consumable<T>> | undefined;
165
+ if (strategy) {
166
+ wrappedStrategy = {};
167
+ if ("highWaterMark" in strategy) {
168
+ wrappedStrategy.highWaterMark = strategy.highWaterMark;
169
+ }
170
+ if ("size" in strategy) {
171
+ wrappedStrategy.size = (chunk) => {
172
+ return strategy.size!(chunk.value);
173
+ };
174
+ }
175
+ }
249
176
 
250
- super({
251
- async start(controller) {
252
- wrappedController = {
253
- async enqueue(chunk) {
254
- await enqueue(controller, chunk);
177
+ super(
178
+ {
179
+ async start(controller) {
180
+ wrappedController = {
181
+ async enqueue(chunk) {
182
+ await ReadableStream.enqueue(controller, chunk);
183
+ },
184
+ close() {
185
+ controller.close();
186
+ },
187
+ error(reason) {
188
+ controller.error(reason);
189
+ },
190
+ };
191
+
192
+ await source.start?.(wrappedController);
255
193
  },
256
- close() {
257
- controller.terminate();
194
+ async pull() {
195
+ await source.pull?.(wrappedController!);
258
196
  },
259
- error(reason) {
260
- controller.error(reason);
197
+ async cancel(reason) {
198
+ await source.cancel?.(reason);
261
199
  },
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
- });
276
- }
277
- }
278
-
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
- });
200
+ },
201
+ wrappedStrategy,
202
+ );
203
+ }
290
204
  }
291
205
  }
@@ -1,4 +1,6 @@
1
- import { ConsumableTransformStream } from "./consumable.js";
1
+ import { Consumable } from "./consumable.js";
2
+ import { MaybeConsumable } from "./maybe-consumable.js";
3
+ import { TransformStream } from "./stream.js";
2
4
 
3
5
  /**
4
6
  * Splits or combines buffers to specified size.
@@ -25,7 +27,7 @@ export class BufferCombiner {
25
27
  */
26
28
  *push(data: Uint8Array): Generator<Uint8Array, void, void> {
27
29
  let offset = 0;
28
- let available = data.byteLength;
30
+ let available = data.length;
29
31
 
30
32
  if (this.#offset !== 0) {
31
33
  if (available >= this.#available) {
@@ -77,34 +79,42 @@ export class BufferCombiner {
77
79
  }
78
80
  }
79
81
 
80
- export class DistributionStream extends ConsumableTransformStream<
81
- Uint8Array,
82
- Uint8Array
82
+ export class DistributionStream extends TransformStream<
83
+ MaybeConsumable<Uint8Array>,
84
+ MaybeConsumable<Uint8Array>
83
85
  > {
84
86
  constructor(size: number, combine = false) {
85
87
  const combiner = combine ? new BufferCombiner(size) : undefined;
86
88
  super({
87
89
  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;
90
+ await MaybeConsumable.tryConsume(chunk, async (chunk) => {
91
+ if (combiner) {
92
+ for (const buffer of combiner.push(chunk)) {
93
+ await Consumable.ReadableStream.enqueue(
94
+ controller,
95
+ buffer,
96
+ );
97
+ }
98
+ } else {
99
+ let offset = 0;
100
+ let available = chunk.length;
101
+ while (available > 0) {
102
+ const end = offset + size;
103
+ await Consumable.ReadableStream.enqueue(
104
+ controller,
105
+ chunk.subarray(offset, end),
106
+ );
107
+ offset = end;
108
+ available -= size;
109
+ }
100
110
  }
101
- }
111
+ });
102
112
  },
103
- async flush(controller) {
113
+ flush(controller) {
104
114
  if (combiner) {
105
115
  const data = combiner.flush();
106
116
  if (data) {
107
- await controller.enqueue(data);
117
+ controller.enqueue(data);
108
118
  }
109
119
  }
110
120
  },
@@ -0,0 +1,39 @@
1
+ import type { TransformStream } from "./stream.js";
2
+
3
+ export interface TextDecoderOptions {
4
+ fatal?: boolean;
5
+ ignoreBOM?: boolean;
6
+ }
7
+
8
+ declare class TextDecoderStreamType extends TransformStream<
9
+ ArrayBufferView | ArrayBuffer,
10
+ string
11
+ > {
12
+ constructor(label?: string, options?: TextDecoderOptions);
13
+
14
+ readonly encoding: string;
15
+ readonly fatal: boolean;
16
+ readonly ignoreBOM: boolean;
17
+ }
18
+
19
+ declare class TextEncoderStreamType extends TransformStream<
20
+ string,
21
+ Uint8Array
22
+ > {
23
+ constructor();
24
+
25
+ readonly encoding: string;
26
+ }
27
+
28
+ interface GlobalExtension {
29
+ TextDecoderStream: typeof TextDecoderStreamType;
30
+ TextEncoderStream: typeof TextEncoderStreamType;
31
+ }
32
+
33
+ const Global = globalThis as unknown as GlobalExtension;
34
+
35
+ export const TextDecoderStream = Global.TextDecoderStream;
36
+ export type TextDecoderStream = TextDecoderStreamType;
37
+
38
+ export const TextEncoderStream = Global.TextEncoderStream;
39
+ export type TextEncoderStream = TextEncoderStreamType;
package/src/index.ts CHANGED
@@ -2,15 +2,17 @@ export * from "./buffered-transform.js";
2
2
  export * from "./buffered.js";
3
3
  export * from "./concat.js";
4
4
  export * from "./consumable.js";
5
- export * from "./decode-utf8.js";
6
5
  export * from "./distribution.js";
7
6
  export * from "./duplex.js";
7
+ export * from "./encoding.js";
8
8
  export * from "./inspect.js";
9
+ export * from "./maybe-consumable.js";
9
10
  export * from "./pipe-from.js";
10
11
  export * from "./push-readable.js";
11
12
  export * from "./split-string.js";
12
13
  export * from "./stream.js";
13
14
  export * from "./struct-deserialize.js";
14
15
  export * from "./struct-serialize.js";
16
+ export * from "./task.js";
15
17
  export * from "./wrap-readable.js";
16
18
  export * from "./wrap-writable.js";