@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/maybe-consumable.ts
CHANGED
|
@@ -1,101 +1,5 @@
|
|
|
1
|
-
import { Consumable } from "./consumable.js";
|
|
2
|
-
import type {
|
|
3
|
-
QueuingStrategy,
|
|
4
|
-
WritableStreamDefaultController,
|
|
5
|
-
} from "./stream.js";
|
|
6
|
-
import {
|
|
7
|
-
WritableStream as NativeWritableStream,
|
|
8
|
-
TransformStream,
|
|
9
|
-
} from "./stream.js";
|
|
1
|
+
import type { Consumable } from "./consumable.js";
|
|
10
2
|
|
|
11
3
|
export type MaybeConsumable<T> = T | Consumable<T>;
|
|
12
4
|
|
|
13
|
-
export
|
|
14
|
-
export function getValue<T>(value: MaybeConsumable<T>): T {
|
|
15
|
-
return value instanceof Consumable ? value.value : value;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function tryConsume<T, R>(
|
|
19
|
-
value: T,
|
|
20
|
-
callback: (value: T extends Consumable<infer U> ? U : T) => R,
|
|
21
|
-
): R {
|
|
22
|
-
if (value instanceof Consumable) {
|
|
23
|
-
return value.tryConsume(callback);
|
|
24
|
-
} else {
|
|
25
|
-
return callback(value as never);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export class UnwrapStream<T> extends TransformStream<
|
|
30
|
-
MaybeConsumable<T>,
|
|
31
|
-
T
|
|
32
|
-
> {
|
|
33
|
-
constructor() {
|
|
34
|
-
super({
|
|
35
|
-
transform(chunk, controller) {
|
|
36
|
-
MaybeConsumable.tryConsume(chunk, (chunk) => {
|
|
37
|
-
controller.enqueue(chunk as T);
|
|
38
|
-
});
|
|
39
|
-
},
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface WritableStreamSink<in T> {
|
|
45
|
-
start?(
|
|
46
|
-
controller: WritableStreamDefaultController,
|
|
47
|
-
): void | PromiseLike<void>;
|
|
48
|
-
write?(
|
|
49
|
-
chunk: T,
|
|
50
|
-
controller: WritableStreamDefaultController,
|
|
51
|
-
): void | PromiseLike<void>;
|
|
52
|
-
abort?(reason: unknown): void | PromiseLike<void>;
|
|
53
|
-
close?(): void | PromiseLike<void>;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export class WritableStream<in T> extends NativeWritableStream<
|
|
57
|
-
MaybeConsumable<T>
|
|
58
|
-
> {
|
|
59
|
-
constructor(
|
|
60
|
-
sink: WritableStreamSink<T>,
|
|
61
|
-
strategy?: QueuingStrategy<T>,
|
|
62
|
-
) {
|
|
63
|
-
let wrappedStrategy:
|
|
64
|
-
| QueuingStrategy<MaybeConsumable<T>>
|
|
65
|
-
| undefined;
|
|
66
|
-
if (strategy) {
|
|
67
|
-
wrappedStrategy = {};
|
|
68
|
-
if ("highWaterMark" in strategy) {
|
|
69
|
-
wrappedStrategy.highWaterMark = strategy.highWaterMark;
|
|
70
|
-
}
|
|
71
|
-
if ("size" in strategy) {
|
|
72
|
-
wrappedStrategy.size = (chunk) => {
|
|
73
|
-
return strategy.size!(
|
|
74
|
-
chunk instanceof Consumable ? chunk.value : chunk,
|
|
75
|
-
);
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
super(
|
|
81
|
-
{
|
|
82
|
-
start(controller) {
|
|
83
|
-
return sink.start?.(controller);
|
|
84
|
-
},
|
|
85
|
-
async write(chunk, controller) {
|
|
86
|
-
await MaybeConsumable.tryConsume(chunk, (chunk) =>
|
|
87
|
-
sink.write?.(chunk as T, controller),
|
|
88
|
-
);
|
|
89
|
-
},
|
|
90
|
-
abort(reason) {
|
|
91
|
-
return sink.abort?.(reason);
|
|
92
|
-
},
|
|
93
|
-
close() {
|
|
94
|
-
return sink.close?.();
|
|
95
|
-
},
|
|
96
|
-
},
|
|
97
|
-
wrappedStrategy,
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
5
|
+
export * as MaybeConsumable from "./maybe-consumable-ns.js";
|
package/src/push-readable.ts
CHANGED
|
@@ -17,9 +17,28 @@ export type PushReadableStreamSource<T> = (
|
|
|
17
17
|
controller: PushReadableStreamController<T>,
|
|
18
18
|
) => void | Promise<void>;
|
|
19
19
|
|
|
20
|
-
export
|
|
21
|
-
|
|
20
|
+
export type PushReadableLogger<T> = (
|
|
21
|
+
event:
|
|
22
|
+
| {
|
|
23
|
+
source: "producer";
|
|
24
|
+
operation: "enqueue";
|
|
25
|
+
value: T;
|
|
26
|
+
phase: "start" | "waiting" | "ignored" | "complete";
|
|
27
|
+
}
|
|
28
|
+
| {
|
|
29
|
+
source: "producer";
|
|
30
|
+
operation: "close" | "error";
|
|
31
|
+
explicit: boolean;
|
|
32
|
+
phase: "start" | "ignored" | "complete";
|
|
33
|
+
}
|
|
34
|
+
| {
|
|
35
|
+
source: "consumer";
|
|
36
|
+
operation: "pull" | "cancel";
|
|
37
|
+
phase: "start" | "complete";
|
|
38
|
+
},
|
|
39
|
+
) => void;
|
|
22
40
|
|
|
41
|
+
export class PushReadableStream<T> extends ReadableStream<T> {
|
|
23
42
|
/**
|
|
24
43
|
* Create a new `PushReadableStream` from a source.
|
|
25
44
|
*
|
|
@@ -30,81 +49,256 @@ export class PushReadableStream<T> extends ReadableStream<T> {
|
|
|
30
49
|
constructor(
|
|
31
50
|
source: PushReadableStreamSource<T>,
|
|
32
51
|
strategy?: QueuingStrategy<T>,
|
|
52
|
+
logger?: PushReadableLogger<T>,
|
|
33
53
|
) {
|
|
34
54
|
let waterMarkLow: PromiseResolver<void> | undefined;
|
|
55
|
+
let zeroHighWaterMarkAllowEnqueue = false;
|
|
35
56
|
const abortController = new AbortController();
|
|
36
57
|
|
|
37
58
|
super(
|
|
38
59
|
{
|
|
39
|
-
start:
|
|
40
|
-
await Promise.resolve();
|
|
41
|
-
|
|
60
|
+
start: (controller) => {
|
|
42
61
|
const result = source({
|
|
43
62
|
abortSignal: abortController.signal,
|
|
44
63
|
enqueue: async (chunk) => {
|
|
64
|
+
logger?.({
|
|
65
|
+
source: "producer",
|
|
66
|
+
operation: "enqueue",
|
|
67
|
+
value: chunk,
|
|
68
|
+
phase: "start",
|
|
69
|
+
});
|
|
70
|
+
|
|
45
71
|
if (abortController.signal.aborted) {
|
|
46
|
-
//
|
|
47
|
-
// throw
|
|
48
|
-
|
|
72
|
+
// In original `ReadableStream`, calling `enqueue` or `close`
|
|
73
|
+
// on an cancelled stream will throw an error,
|
|
74
|
+
//
|
|
75
|
+
// But in `PushReadableStream`, `enqueue` is an async function,
|
|
76
|
+
// the producer can't just check `abortSignal.aborted`
|
|
77
|
+
// before calling `enqueue`, as it might change when waiting
|
|
78
|
+
// for the backpressure to be reduced.
|
|
79
|
+
//
|
|
80
|
+
// So IMO it's better to handle this for the producer
|
|
81
|
+
// by simply ignoring the `enqueue` call.
|
|
82
|
+
//
|
|
83
|
+
// Note that we check `abortSignal.aborted` instead of `stopped`,
|
|
84
|
+
// as it's not allowed for the producer to call `enqueue` after
|
|
85
|
+
// they called `close` or `error`.
|
|
86
|
+
//
|
|
87
|
+
// Obviously, the producer should listen to the `abortSignal` and
|
|
88
|
+
// stop producing, but most pushing data sources don't support that.
|
|
89
|
+
logger?.({
|
|
90
|
+
source: "producer",
|
|
91
|
+
operation: "enqueue",
|
|
92
|
+
value: chunk,
|
|
93
|
+
phase: "ignored",
|
|
94
|
+
});
|
|
95
|
+
return;
|
|
49
96
|
}
|
|
50
97
|
|
|
51
98
|
if (controller.desiredSize === null) {
|
|
52
99
|
// `desiredSize` being `null` means the stream is in error state,
|
|
53
100
|
// `controller.enqueue` will throw an error for us.
|
|
54
101
|
controller.enqueue(chunk);
|
|
102
|
+
// istanbul ignore next
|
|
55
103
|
return;
|
|
56
104
|
}
|
|
57
105
|
|
|
58
|
-
if (
|
|
59
|
-
|
|
106
|
+
if (zeroHighWaterMarkAllowEnqueue) {
|
|
107
|
+
// When `highWaterMark` is set to `0`,
|
|
108
|
+
// `controller.desiredSize` will always be `0`,
|
|
109
|
+
// even if the consumer has called `reader.read()`.
|
|
110
|
+
// (in this case, each `reader.read()`/`pull`
|
|
111
|
+
// should allow one `enqueue` of any size)
|
|
112
|
+
//
|
|
113
|
+
// If the consumer has already called `reader.read()`,
|
|
114
|
+
// before the producer tries to `enqueue`,
|
|
115
|
+
// `controller.desiredSize` is `0` and normal `waterMarkLow` signal
|
|
116
|
+
// will never trigger,
|
|
117
|
+
// (because `ReadableStream` prevents reentrance of `pull`)
|
|
118
|
+
// The stream will stuck.
|
|
119
|
+
//
|
|
120
|
+
// So we need a special signal for this case.
|
|
121
|
+
zeroHighWaterMarkAllowEnqueue = false;
|
|
60
122
|
controller.enqueue(chunk);
|
|
123
|
+
logger?.({
|
|
124
|
+
source: "producer",
|
|
125
|
+
operation: "enqueue",
|
|
126
|
+
value: chunk,
|
|
127
|
+
phase: "complete",
|
|
128
|
+
});
|
|
61
129
|
return;
|
|
62
130
|
}
|
|
63
131
|
|
|
64
132
|
if (controller.desiredSize <= 0) {
|
|
133
|
+
logger?.({
|
|
134
|
+
source: "producer",
|
|
135
|
+
operation: "enqueue",
|
|
136
|
+
value: chunk,
|
|
137
|
+
phase: "waiting",
|
|
138
|
+
});
|
|
139
|
+
|
|
65
140
|
waterMarkLow = new PromiseResolver<void>();
|
|
66
141
|
await waterMarkLow.promise;
|
|
142
|
+
|
|
143
|
+
// Recheck consumer cancellation after async operations.
|
|
144
|
+
if (abortController.signal.aborted) {
|
|
145
|
+
logger?.({
|
|
146
|
+
source: "producer",
|
|
147
|
+
operation: "enqueue",
|
|
148
|
+
value: chunk,
|
|
149
|
+
phase: "ignored",
|
|
150
|
+
});
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
67
153
|
}
|
|
68
154
|
|
|
69
|
-
// `controller.enqueue` will throw error for us
|
|
70
|
-
// if the stream is already errored.
|
|
71
155
|
controller.enqueue(chunk);
|
|
156
|
+
logger?.({
|
|
157
|
+
source: "producer",
|
|
158
|
+
operation: "enqueue",
|
|
159
|
+
value: chunk,
|
|
160
|
+
phase: "complete",
|
|
161
|
+
});
|
|
72
162
|
},
|
|
73
163
|
close() {
|
|
164
|
+
logger?.({
|
|
165
|
+
source: "producer",
|
|
166
|
+
operation: "close",
|
|
167
|
+
explicit: true,
|
|
168
|
+
phase: "start",
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Since `enqueue` on an cancelled stream won't throw an error,
|
|
172
|
+
// so does `close`.
|
|
173
|
+
if (abortController.signal.aborted) {
|
|
174
|
+
logger?.({
|
|
175
|
+
source: "producer",
|
|
176
|
+
operation: "close",
|
|
177
|
+
explicit: true,
|
|
178
|
+
phase: "ignored",
|
|
179
|
+
});
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
74
183
|
controller.close();
|
|
184
|
+
logger?.({
|
|
185
|
+
source: "producer",
|
|
186
|
+
operation: "close",
|
|
187
|
+
explicit: true,
|
|
188
|
+
phase: "complete",
|
|
189
|
+
});
|
|
75
190
|
},
|
|
76
191
|
error(e) {
|
|
192
|
+
logger?.({
|
|
193
|
+
source: "producer",
|
|
194
|
+
operation: "error",
|
|
195
|
+
explicit: true,
|
|
196
|
+
phase: "start",
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Calling `error` on an already closed or errored stream is a no-op.
|
|
77
200
|
controller.error(e);
|
|
201
|
+
|
|
202
|
+
logger?.({
|
|
203
|
+
source: "producer",
|
|
204
|
+
operation: "error",
|
|
205
|
+
explicit: true,
|
|
206
|
+
phase: "complete",
|
|
207
|
+
});
|
|
78
208
|
},
|
|
79
209
|
});
|
|
80
210
|
|
|
81
211
|
if (result && "then" in result) {
|
|
212
|
+
// If `source` returns a `Promise`,
|
|
213
|
+
// close the stream when the `Promise` is resolved,
|
|
214
|
+
// and error the stream when the `Promise` is rejected.
|
|
215
|
+
// The producer can return a never-settling `Promise`
|
|
216
|
+
// to disable this behavior.
|
|
82
217
|
result.then(
|
|
83
218
|
() => {
|
|
219
|
+
logger?.({
|
|
220
|
+
source: "producer",
|
|
221
|
+
operation: "close",
|
|
222
|
+
explicit: false,
|
|
223
|
+
phase: "start",
|
|
224
|
+
});
|
|
225
|
+
|
|
84
226
|
try {
|
|
85
227
|
controller.close();
|
|
86
|
-
|
|
87
|
-
|
|
228
|
+
|
|
229
|
+
logger?.({
|
|
230
|
+
source: "producer",
|
|
231
|
+
operation: "close",
|
|
232
|
+
explicit: false,
|
|
233
|
+
phase: "complete",
|
|
234
|
+
});
|
|
235
|
+
} catch {
|
|
236
|
+
logger?.({
|
|
237
|
+
source: "producer",
|
|
238
|
+
operation: "close",
|
|
239
|
+
explicit: false,
|
|
240
|
+
phase: "ignored",
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// The stream is already closed by the producer,
|
|
244
|
+
// Or cancelled by the consumer.
|
|
88
245
|
}
|
|
89
246
|
},
|
|
90
247
|
(e) => {
|
|
248
|
+
logger?.({
|
|
249
|
+
source: "producer",
|
|
250
|
+
operation: "error",
|
|
251
|
+
explicit: false,
|
|
252
|
+
phase: "start",
|
|
253
|
+
});
|
|
254
|
+
|
|
91
255
|
controller.error(e);
|
|
256
|
+
|
|
257
|
+
logger?.({
|
|
258
|
+
source: "producer",
|
|
259
|
+
operation: "error",
|
|
260
|
+
explicit: false,
|
|
261
|
+
phase: "complete",
|
|
262
|
+
});
|
|
92
263
|
},
|
|
93
264
|
);
|
|
94
265
|
}
|
|
95
266
|
},
|
|
96
267
|
pull: () => {
|
|
268
|
+
logger?.({
|
|
269
|
+
source: "consumer",
|
|
270
|
+
operation: "pull",
|
|
271
|
+
phase: "start",
|
|
272
|
+
});
|
|
273
|
+
|
|
97
274
|
if (waterMarkLow) {
|
|
98
275
|
waterMarkLow.resolve();
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (strategy?.highWaterMark === 0) {
|
|
102
|
-
this.#zeroHighWaterMarkAllowEnqueue = true;
|
|
276
|
+
} else if (strategy?.highWaterMark === 0) {
|
|
277
|
+
zeroHighWaterMarkAllowEnqueue = true;
|
|
103
278
|
}
|
|
279
|
+
|
|
280
|
+
logger?.({
|
|
281
|
+
source: "consumer",
|
|
282
|
+
operation: "pull",
|
|
283
|
+
phase: "complete",
|
|
284
|
+
});
|
|
104
285
|
},
|
|
105
286
|
cancel: (reason) => {
|
|
287
|
+
logger?.({
|
|
288
|
+
source: "consumer",
|
|
289
|
+
operation: "cancel",
|
|
290
|
+
phase: "start",
|
|
291
|
+
});
|
|
292
|
+
|
|
106
293
|
abortController.abort(reason);
|
|
107
|
-
|
|
294
|
+
// Resolve it on cancellation. `pull` will check `abortSignal.aborted` again.
|
|
295
|
+
waterMarkLow?.resolve();
|
|
296
|
+
|
|
297
|
+
logger?.({
|
|
298
|
+
source: "consumer",
|
|
299
|
+
operation: "cancel",
|
|
300
|
+
phase: "complete",
|
|
301
|
+
});
|
|
108
302
|
},
|
|
109
303
|
},
|
|
110
304
|
strategy,
|
package/src/stream.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AbortSignal,
|
|
3
|
+
ReadableStreamIteratorOptions,
|
|
3
4
|
ReadableStream as ReadableStreamType,
|
|
4
5
|
TransformStream as TransformStreamType,
|
|
5
6
|
WritableStream as WritableStreamType,
|
|
6
7
|
} from "./types.js";
|
|
7
8
|
|
|
8
9
|
export * from "./types.js";
|
|
10
|
+
export { ReadableStream };
|
|
9
11
|
|
|
10
12
|
/** A controller object that allows you to abort one or more DOM requests as and when desired. */
|
|
11
13
|
export interface AbortController {
|
|
@@ -32,15 +34,73 @@ interface GlobalExtension {
|
|
|
32
34
|
TransformStream: typeof TransformStreamType;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
const
|
|
37
|
+
export const { AbortController } = globalThis as unknown as GlobalExtension;
|
|
36
38
|
|
|
37
|
-
export
|
|
39
|
+
export type ReadableStream<T> = ReadableStreamType<T>;
|
|
40
|
+
export type WritableStream<T> = WritableStreamType<T>;
|
|
41
|
+
export type TransformStream<I, O> = TransformStreamType<I, O>;
|
|
38
42
|
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
const ReadableStream = /* #__PURE__ */ (() => {
|
|
44
|
+
const { ReadableStream } = globalThis as unknown as GlobalExtension;
|
|
41
45
|
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
if (!ReadableStream.from) {
|
|
47
|
+
ReadableStream.from = function (iterable) {
|
|
48
|
+
const iterator =
|
|
49
|
+
Symbol.asyncIterator in iterable
|
|
50
|
+
? iterable[Symbol.asyncIterator]()
|
|
51
|
+
: iterable[Symbol.iterator]();
|
|
44
52
|
|
|
45
|
-
|
|
46
|
-
|
|
53
|
+
return new ReadableStream({
|
|
54
|
+
async pull(controller) {
|
|
55
|
+
const result = await iterator.next();
|
|
56
|
+
if (result.done) {
|
|
57
|
+
controller.close();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
controller.enqueue(result.value);
|
|
61
|
+
},
|
|
62
|
+
async cancel(reason) {
|
|
63
|
+
await iterator.return?.(reason);
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (
|
|
70
|
+
!ReadableStream.prototype[Symbol.asyncIterator] ||
|
|
71
|
+
!ReadableStream.prototype.values
|
|
72
|
+
) {
|
|
73
|
+
ReadableStream.prototype.values = async function* <R>(
|
|
74
|
+
this: ReadableStream<R>,
|
|
75
|
+
options?: ReadableStreamIteratorOptions,
|
|
76
|
+
) {
|
|
77
|
+
const reader = this.getReader();
|
|
78
|
+
try {
|
|
79
|
+
while (true) {
|
|
80
|
+
const { done, value } = await reader.read();
|
|
81
|
+
if (done) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
yield value;
|
|
85
|
+
}
|
|
86
|
+
} finally {
|
|
87
|
+
// Calling `iterator.return` will enter this `finally` block.
|
|
88
|
+
// We don't need to care about the parameter to `iterator.return`,
|
|
89
|
+
// it will be returned as the final `result.value` automatically.
|
|
90
|
+
if (!options?.preventCancel) {
|
|
91
|
+
await reader.cancel();
|
|
92
|
+
}
|
|
93
|
+
reader.releaseLock();
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
ReadableStream.prototype[Symbol.asyncIterator] =
|
|
98
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
99
|
+
ReadableStream.prototype.values;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return ReadableStream;
|
|
103
|
+
})();
|
|
104
|
+
|
|
105
|
+
export const { WritableStream, TransformStream } =
|
|
106
|
+
globalThis as unknown as GlobalExtension;
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import type { StructValueType } from "@yume-chan/struct";
|
|
1
|
+
import type { StructLike } from "@yume-chan/struct";
|
|
3
2
|
|
|
4
3
|
import { BufferedTransformStream } from "./buffered-transform.js";
|
|
5
4
|
|
|
6
|
-
export class StructDeserializeStream<
|
|
7
|
-
|
|
8
|
-
> extends BufferedTransformStream<StructValueType<T>> {
|
|
9
|
-
constructor(struct: T) {
|
|
5
|
+
export class StructDeserializeStream<T> extends BufferedTransformStream<T> {
|
|
6
|
+
constructor(struct: StructLike<T>) {
|
|
10
7
|
super((stream) => {
|
|
11
8
|
return struct.deserialize(stream) as never;
|
|
12
9
|
});
|
package/src/struct-serialize.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import type { StructInit, StructLike } from "@yume-chan/struct";
|
|
2
2
|
|
|
3
3
|
import { TransformStream } from "./stream.js";
|
|
4
4
|
|
|
5
5
|
export class StructSerializeStream<
|
|
6
|
-
T extends
|
|
7
|
-
> extends TransformStream<T
|
|
6
|
+
T extends StructLike<unknown>,
|
|
7
|
+
> extends TransformStream<StructInit<T>, Uint8Array> {
|
|
8
8
|
constructor(struct: T) {
|
|
9
9
|
super({
|
|
10
10
|
transform(chunk, controller) {
|
package/src/task.ts
CHANGED
|
@@ -12,10 +12,10 @@ interface GlobalExtension {
|
|
|
12
12
|
|
|
13
13
|
// `createTask` allows browser DevTools to track the call stack across async boundaries.
|
|
14
14
|
const { console } = globalThis as unknown as GlobalExtension;
|
|
15
|
-
export const createTask: (name: string) => Task =
|
|
15
|
+
export const createTask: (name: string) => Task = /* #__PURE__ */ (() =>
|
|
16
16
|
console?.createTask?.bind(console) ??
|
|
17
17
|
(() => ({
|
|
18
18
|
run(callback) {
|
|
19
19
|
return callback();
|
|
20
20
|
},
|
|
21
|
-
}));
|
|
21
|
+
})))();
|
package/src/try-close.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { BufferedReadableStream } from "./buffered.js";
|
|
2
|
+
import type { PushReadableStreamController } from "./push-readable.js";
|
|
3
|
+
import type {
|
|
4
|
+
ReadableStream,
|
|
5
|
+
ReadableStreamDefaultController,
|
|
6
|
+
ReadableStreamDefaultReader,
|
|
7
|
+
WritableStreamDefaultWriter,
|
|
8
|
+
} from "./stream.js";
|
|
9
|
+
|
|
10
|
+
export function tryClose(
|
|
11
|
+
controller: PushReadableStreamController<unknown>,
|
|
12
|
+
): boolean;
|
|
13
|
+
export function tryClose(
|
|
14
|
+
controller: ReadableStreamDefaultController<unknown>,
|
|
15
|
+
): boolean;
|
|
16
|
+
export function tryClose(writer: WritableStreamDefaultWriter<never>): boolean;
|
|
17
|
+
export function tryClose(controller: { close(): void }) {
|
|
18
|
+
try {
|
|
19
|
+
controller.close();
|
|
20
|
+
return true;
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function tryCancel(
|
|
27
|
+
stream: ReadableStream<unknown>,
|
|
28
|
+
): Promise<boolean>;
|
|
29
|
+
export async function tryCancel(
|
|
30
|
+
stream: BufferedReadableStream,
|
|
31
|
+
): Promise<boolean>;
|
|
32
|
+
export async function tryCancel(
|
|
33
|
+
reader: ReadableStreamDefaultReader<unknown>,
|
|
34
|
+
): Promise<boolean>;
|
|
35
|
+
export async function tryCancel(stream: {
|
|
36
|
+
cancel(): Promise<void>;
|
|
37
|
+
}): Promise<boolean> {
|
|
38
|
+
try {
|
|
39
|
+
await stream.cancel();
|
|
40
|
+
return true;
|
|
41
|
+
} catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -242,7 +242,7 @@ export declare class ReadableStream<out R> implements AsyncIterable<R> {
|
|
|
242
242
|
* such as an array, an async generator, or a Node.js readable stream.
|
|
243
243
|
*/
|
|
244
244
|
static from<R>(
|
|
245
|
-
asyncIterable: Iterable<R> | AsyncIterable<R
|
|
245
|
+
asyncIterable: Iterable<R> | AsyncIterable<R>,
|
|
246
246
|
): ReadableStream<R>;
|
|
247
247
|
}
|
|
248
248
|
|
|
@@ -253,7 +253,7 @@ export declare class ReadableStream<out R> implements AsyncIterable<R> {
|
|
|
253
253
|
*/
|
|
254
254
|
export declare interface ReadableStreamAsyncIterator<R>
|
|
255
255
|
extends AsyncIterableIterator<R> {
|
|
256
|
-
next(): Promise<IteratorResult<R,
|
|
256
|
+
next(): Promise<IteratorResult<R, void>>;
|
|
257
257
|
return(value?: R): Promise<IteratorResult<R>>;
|
|
258
258
|
}
|
|
259
259
|
|
package/src/wrap-readable.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { MaybePromiseLike } from "@yume-chan/async";
|
|
2
2
|
|
|
3
3
|
import type {
|
|
4
4
|
QueuingStrategy,
|
|
@@ -9,12 +9,13 @@ import { ReadableStream } from "./stream.js";
|
|
|
9
9
|
|
|
10
10
|
export type WrapReadableStreamStart<T> = (
|
|
11
11
|
controller: ReadableStreamDefaultController<T>,
|
|
12
|
-
) =>
|
|
12
|
+
) => MaybePromiseLike<ReadableStream<T>>;
|
|
13
13
|
|
|
14
14
|
export interface ReadableStreamWrapper<T> {
|
|
15
15
|
start: WrapReadableStreamStart<T>;
|
|
16
|
-
cancel
|
|
17
|
-
close
|
|
16
|
+
cancel?: (reason?: unknown) => MaybePromiseLike<void>;
|
|
17
|
+
close?: () => MaybePromiseLike<void>;
|
|
18
|
+
error?: (reason?: unknown) => MaybePromiseLike<void>;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
function getWrappedReadableStream<T>(
|
|
@@ -57,27 +58,32 @@ export class WrapReadableStream<T> extends ReadableStream<T> {
|
|
|
57
58
|
super(
|
|
58
59
|
{
|
|
59
60
|
start: async (controller) => {
|
|
60
|
-
|
|
61
|
-
// so using `this` synchronously causes
|
|
62
|
-
// "Must call super constructor in derived class before accessing 'this' or returning from derived constructor".
|
|
63
|
-
// Queue a microtask to avoid this.
|
|
64
|
-
await Promise.resolve();
|
|
65
|
-
|
|
66
|
-
this.readable = await getWrappedReadableStream(
|
|
61
|
+
const readable = await getWrappedReadableStream(
|
|
67
62
|
wrapper,
|
|
68
63
|
controller,
|
|
69
64
|
);
|
|
65
|
+
// `start` is called in `super()`, so can't use `this` synchronously.
|
|
66
|
+
// but it's fine after the first `await`
|
|
67
|
+
this.readable = readable;
|
|
70
68
|
this.#reader = this.readable.getReader();
|
|
71
69
|
},
|
|
72
70
|
pull: async (controller) => {
|
|
73
|
-
const
|
|
74
|
-
|
|
71
|
+
const { done, value } = await this.#reader
|
|
72
|
+
.read()
|
|
73
|
+
.catch((e) => {
|
|
74
|
+
if ("error" in wrapper) {
|
|
75
|
+
wrapper.error(e);
|
|
76
|
+
}
|
|
77
|
+
throw e;
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (done) {
|
|
75
81
|
controller.close();
|
|
76
82
|
if ("close" in wrapper) {
|
|
77
83
|
await wrapper.close?.();
|
|
78
84
|
}
|
|
79
85
|
} else {
|
|
80
|
-
controller.enqueue(
|
|
86
|
+
controller.enqueue(value);
|
|
81
87
|
}
|
|
82
88
|
},
|
|
83
89
|
cancel: async (reason) => {
|