@yume-chan/stream-extra 0.0.24 → 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.
- package/CHANGELOG.md +22 -1
- package/esm/buffered-transform.d.ts +3 -3
- package/esm/buffered-transform.d.ts.map +1 -1
- package/esm/buffered-transform.js.map +1 -1
- package/esm/buffered.d.ts +3 -6
- package/esm/buffered.d.ts.map +1 -1
- package/esm/buffered.js +87 -75
- package/esm/buffered.js.map +1 -1
- package/esm/concat.js +2 -2
- package/esm/concat.js.map +1 -1
- package/esm/consumable.d.ts +15 -10
- package/esm/consumable.d.ts.map +1 -1
- package/esm/consumable.js +82 -86
- package/esm/consumable.js.map +1 -1
- package/esm/duplex.d.ts +2 -2
- package/esm/duplex.d.ts.map +1 -1
- package/esm/duplex.js +2 -6
- package/esm/duplex.js.map +1 -1
- package/esm/index.d.ts +1 -0
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +1 -0
- package/esm/index.js.map +1 -1
- package/esm/maybe-consumable-ns.d.ts +16 -0
- package/esm/maybe-consumable-ns.d.ts.map +1 -0
- package/esm/maybe-consumable-ns.js +44 -0
- package/esm/maybe-consumable-ns.js.map +1 -0
- package/esm/maybe-consumable.d.ts +2 -19
- package/esm/maybe-consumable.d.ts.map +1 -1
- package/esm/maybe-consumable.js +1 -61
- package/esm/maybe-consumable.js.map +1 -1
- package/esm/push-readable.d.ts +16 -2
- package/esm/push-readable.d.ts.map +1 -1
- package/esm/push-readable.js +176 -17
- package/esm/push-readable.js.map +1 -1
- package/esm/stream.d.ts +5 -5
- package/esm/stream.d.ts.map +1 -1
- package/esm/stream.js +54 -5
- package/esm/stream.js.map +1 -1
- package/esm/struct-deserialize.d.ts +3 -4
- package/esm/struct-deserialize.d.ts.map +1 -1
- package/esm/struct-deserialize.js.map +1 -1
- package/esm/struct-serialize.d.ts +2 -2
- package/esm/struct-serialize.d.ts.map +1 -1
- package/esm/struct-serialize.js.map +1 -1
- package/esm/task.d.ts.map +1 -1
- package/esm/task.js +2 -2
- package/esm/task.js.map +1 -1
- package/esm/try-close.d.ts +10 -0
- package/esm/try-close.d.ts.map +1 -0
- package/esm/try-close.js +19 -0
- package/esm/try-close.js.map +1 -0
- package/esm/types.d.ts +2 -3
- package/esm/types.d.ts.map +1 -1
- package/esm/wrap-readable.d.ts +5 -4
- package/esm/wrap-readable.d.ts.map +1 -1
- package/esm/wrap-readable.js +14 -9
- package/esm/wrap-readable.js.map +1 -1
- package/esm/wrap-writable.d.ts +2 -2
- package/esm/wrap-writable.d.ts.map +1 -1
- package/esm/wrap-writable.js +4 -6
- package/esm/wrap-writable.js.map +1 -1
- package/package.json +11 -13
- package/src/buffered-transform.ts +2 -2
- package/src/buffered.ts +107 -83
- package/src/concat.ts +2 -2
- package/src/consumable.ts +117 -112
- package/src/duplex.ts +4 -7
- package/src/index.ts +1 -0
- package/src/maybe-consumable-ns.ts +75 -0
- package/src/maybe-consumable.ts +2 -98
- package/src/push-readable.ts +213 -19
- package/src/stream.ts +68 -8
- package/src/struct-deserialize.ts +3 -6
- package/src/struct-serialize.ts +3 -3
- package/src/task.ts +2 -2
- package/src/try-close.ts +44 -0
- package/src/types.ts +2 -2
- package/src/wrap-readable.ts +20 -14
- package/src/wrap-writable.ts +6 -9
- package/tsconfig.build.tsbuildinfo +1 -1
- package/CHANGELOG.json +0 -104
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
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
|
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
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
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,5 +1,5 @@
|
|
|
1
1
|
import { PromiseResolver } from "@yume-chan/async";
|
|
2
|
-
import {
|
|
2
|
+
import { EmptyUint8Array } from "@yume-chan/struct";
|
|
3
3
|
|
|
4
4
|
import type { ReadableStreamDefaultController } from "./stream.js";
|
|
5
5
|
import { ReadableStream, WritableStream } from "./stream.js";
|
|
@@ -101,7 +101,7 @@ export class ConcatBufferStream {
|
|
|
101
101
|
let offset = 0;
|
|
102
102
|
switch (this.#segments.length) {
|
|
103
103
|
case 0:
|
|
104
|
-
result =
|
|
104
|
+
result = EmptyUint8Array;
|
|
105
105
|
break;
|
|
106
106
|
case 1:
|
|
107
107
|
result = this.#segments[0]!;
|
package/src/consumable.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PromiseResolver } from "@yume-chan/async";
|
|
1
|
+
import { PromiseResolver, isPromiseLike } from "@yume-chan/async";
|
|
2
2
|
|
|
3
3
|
import type {
|
|
4
4
|
QueuingStrategy,
|
|
@@ -12,11 +12,123 @@ import {
|
|
|
12
12
|
import type { Task } from "./task.js";
|
|
13
13
|
import { createTask } from "./task.js";
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
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;
|
|
24
|
+
}
|
|
25
|
+
|
|
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
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
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
|
+
},
|
|
61
|
+
},
|
|
62
|
+
wrappedStrategy,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
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;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
constructor(
|
|
78
|
+
source: Consumable.ReadableStreamSource<T>,
|
|
79
|
+
strategy?: QueuingStrategy<T>,
|
|
80
|
+
) {
|
|
81
|
+
let wrappedController:
|
|
82
|
+
| Consumable.ReadableStreamController<T>
|
|
83
|
+
| undefined;
|
|
84
|
+
|
|
85
|
+
let wrappedStrategy: QueuingStrategy<Consumable<T>> | undefined;
|
|
86
|
+
if (strategy) {
|
|
87
|
+
wrappedStrategy = {};
|
|
88
|
+
if ("highWaterMark" in strategy) {
|
|
89
|
+
wrappedStrategy.highWaterMark = strategy.highWaterMark;
|
|
90
|
+
}
|
|
91
|
+
if ("size" in strategy) {
|
|
92
|
+
wrappedStrategy.size = (chunk) => {
|
|
93
|
+
return strategy.size!(chunk.value);
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
super(
|
|
99
|
+
{
|
|
100
|
+
async start(controller) {
|
|
101
|
+
wrappedController = {
|
|
102
|
+
async enqueue(chunk) {
|
|
103
|
+
await ReadableStream.enqueue(controller, chunk);
|
|
104
|
+
},
|
|
105
|
+
close() {
|
|
106
|
+
controller.close();
|
|
107
|
+
},
|
|
108
|
+
error(reason) {
|
|
109
|
+
controller.error(reason);
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
await source.start?.(wrappedController);
|
|
114
|
+
},
|
|
115
|
+
async pull() {
|
|
116
|
+
await source.pull?.(wrappedController!);
|
|
117
|
+
},
|
|
118
|
+
async cancel(reason) {
|
|
119
|
+
await source.cancel?.(reason);
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
wrappedStrategy,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
17
125
|
}
|
|
18
126
|
|
|
19
127
|
export class Consumable<T> {
|
|
128
|
+
static readonly WritableStream = WritableStream;
|
|
129
|
+
|
|
130
|
+
static readonly ReadableStream = ReadableStream;
|
|
131
|
+
|
|
20
132
|
readonly #task: Task;
|
|
21
133
|
readonly #resolver: PromiseResolver<void>;
|
|
22
134
|
|
|
@@ -76,58 +188,7 @@ export namespace Consumable {
|
|
|
76
188
|
close?(): void | PromiseLike<void>;
|
|
77
189
|
}
|
|
78
190
|
|
|
79
|
-
export
|
|
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;
|
|
89
|
-
}
|
|
90
|
-
|
|
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
|
-
);
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
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
|
-
},
|
|
126
|
-
},
|
|
127
|
-
wrappedStrategy,
|
|
128
|
-
);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
191
|
+
export type WritableStream<in T> = typeof Consumable.WritableStream<T>;
|
|
131
192
|
|
|
132
193
|
export interface ReadableStreamController<T> {
|
|
133
194
|
enqueue(chunk: T): Promise<void>;
|
|
@@ -145,61 +206,5 @@ export namespace Consumable {
|
|
|
145
206
|
cancel?(reason: unknown): void | PromiseLike<void>;
|
|
146
207
|
}
|
|
147
208
|
|
|
148
|
-
export
|
|
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
|
-
}
|
|
157
|
-
|
|
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
|
-
}
|
|
176
|
-
|
|
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);
|
|
193
|
-
},
|
|
194
|
-
async pull() {
|
|
195
|
-
await source.pull?.(wrappedController!);
|
|
196
|
-
},
|
|
197
|
-
async cancel(reason) {
|
|
198
|
-
await source.cancel?.(reason);
|
|
199
|
-
},
|
|
200
|
-
},
|
|
201
|
-
wrappedStrategy,
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
209
|
+
export type ReadableStream<T> = typeof Consumable.ReadableStream<T>;
|
|
205
210
|
}
|
package/src/duplex.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import type { MaybePromiseLike } from "@yume-chan/async";
|
|
1
2
|
import { PromiseResolver } from "@yume-chan/async";
|
|
2
|
-
import type { ValueOrPromise } from "@yume-chan/struct";
|
|
3
3
|
|
|
4
4
|
import type {
|
|
5
5
|
QueuingStrategy,
|
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
WritableStreamDefaultWriter,
|
|
9
9
|
} from "./stream.js";
|
|
10
10
|
import { WritableStream } from "./stream.js";
|
|
11
|
+
import { tryClose } from "./try-close.js";
|
|
11
12
|
import { WrapReadableStream } from "./wrap-readable.js";
|
|
12
13
|
|
|
13
14
|
const NOOP = () => {
|
|
@@ -27,7 +28,7 @@ export interface DuplexStreamFactoryOptions {
|
|
|
27
28
|
* `DuplexStreamFactory#dispose` yourself, you can return `false`
|
|
28
29
|
* (or a `Promise` that resolves to `false`) to disable the automatic call.
|
|
29
30
|
*/
|
|
30
|
-
close?: (() =>
|
|
31
|
+
close?: (() => MaybePromiseLike<boolean | void>) | undefined;
|
|
31
32
|
|
|
32
33
|
/**
|
|
33
34
|
* Callback when any `ReadableStream` is closed (the other peer doesn't produce any more data),
|
|
@@ -134,11 +135,7 @@ export class DuplexStreamFactory<R, W> {
|
|
|
134
135
|
this.#closed.resolve();
|
|
135
136
|
|
|
136
137
|
for (const controller of this.#readableControllers) {
|
|
137
|
-
|
|
138
|
-
controller.close();
|
|
139
|
-
} catch {
|
|
140
|
-
// ignore
|
|
141
|
-
}
|
|
138
|
+
tryClose(controller);
|
|
142
139
|
}
|
|
143
140
|
|
|
144
141
|
await this.#options.dispose?.();
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Consumable } from "./consumable.js";
|
|
2
|
+
import type { MaybeConsumable } from "./maybe-consumable.js";
|
|
3
|
+
import type {
|
|
4
|
+
QueuingStrategy,
|
|
5
|
+
WritableStreamDefaultController,
|
|
6
|
+
} from "./stream.js";
|
|
7
|
+
import { WritableStream as NativeWritableStream } from "./stream.js";
|
|
8
|
+
|
|
9
|
+
export function getValue<T>(value: MaybeConsumable<T>): T {
|
|
10
|
+
return value instanceof Consumable ? value.value : value;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function tryConsume<T, R>(
|
|
14
|
+
value: T,
|
|
15
|
+
callback: (value: T extends Consumable<infer U> ? U : T) => R,
|
|
16
|
+
): R {
|
|
17
|
+
if (value instanceof Consumable) {
|
|
18
|
+
return value.tryConsume(callback);
|
|
19
|
+
} else {
|
|
20
|
+
return callback(value as never);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface WritableStreamSink<in T> {
|
|
25
|
+
start?(
|
|
26
|
+
controller: WritableStreamDefaultController,
|
|
27
|
+
): void | PromiseLike<void>;
|
|
28
|
+
write?(
|
|
29
|
+
chunk: T,
|
|
30
|
+
controller: WritableStreamDefaultController,
|
|
31
|
+
): void | PromiseLike<void>;
|
|
32
|
+
abort?(reason: unknown): void | PromiseLike<void>;
|
|
33
|
+
close?(): void | PromiseLike<void>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class WritableStream<in T> extends NativeWritableStream<
|
|
37
|
+
MaybeConsumable<T>
|
|
38
|
+
> {
|
|
39
|
+
constructor(sink: WritableStreamSink<T>, strategy?: QueuingStrategy<T>) {
|
|
40
|
+
let wrappedStrategy: QueuingStrategy<MaybeConsumable<T>> | undefined;
|
|
41
|
+
if (strategy) {
|
|
42
|
+
wrappedStrategy = {};
|
|
43
|
+
if ("highWaterMark" in strategy) {
|
|
44
|
+
wrappedStrategy.highWaterMark = strategy.highWaterMark;
|
|
45
|
+
}
|
|
46
|
+
if ("size" in strategy) {
|
|
47
|
+
wrappedStrategy.size = (chunk) => {
|
|
48
|
+
return strategy.size!(
|
|
49
|
+
chunk instanceof Consumable ? chunk.value : chunk,
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
super(
|
|
56
|
+
{
|
|
57
|
+
start(controller) {
|
|
58
|
+
return sink.start?.(controller);
|
|
59
|
+
},
|
|
60
|
+
async write(chunk, controller) {
|
|
61
|
+
await tryConsume(chunk, (chunk) =>
|
|
62
|
+
sink.write?.(chunk as T, controller),
|
|
63
|
+
);
|
|
64
|
+
},
|
|
65
|
+
abort(reason) {
|
|
66
|
+
return sink.abort?.(reason);
|
|
67
|
+
},
|
|
68
|
+
close() {
|
|
69
|
+
return sink.close?.();
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
wrappedStrategy,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|