@gjsify/web-streams 0.1.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/README.md +30 -0
- package/lib/esm/index.js +121 -0
- package/lib/esm/queuing-strategies.js +56 -0
- package/lib/esm/readable-stream.js +1064 -0
- package/lib/esm/text-decoder-stream.js +126 -0
- package/lib/esm/text-encoder-stream.js +46 -0
- package/lib/esm/transform-stream.js +336 -0
- package/lib/esm/util.js +161 -0
- package/lib/esm/writable-stream.js +676 -0
- package/lib/types/index.d.ts +77 -0
- package/lib/types/queuing-strategies.d.ts +18 -0
- package/lib/types/readable-stream.d.ts +61 -0
- package/lib/types/text-decoder-stream.d.ts +16 -0
- package/lib/types/text-encoder-stream.d.ts +15 -0
- package/lib/types/transform-stream.d.ts +21 -0
- package/lib/types/util.d.ts +40 -0
- package/lib/types/writable-stream.d.ts +49 -0
- package/package.json +44 -0
- package/src/index.spec.ts +2043 -0
- package/src/index.ts +131 -0
- package/src/queuing-strategies.ts +67 -0
- package/src/readable-stream.ts +1337 -0
- package/src/test.mts +6 -0
- package/src/text-decoder-stream.ts +183 -0
- package/src/text-encoder-stream.ts +62 -0
- package/src/transform-stream.ts +410 -0
- package/src/util.ts +170 -0
- package/src/writable-stream.ts +773 -0
- package/tsconfig.json +32 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,1337 @@
|
|
|
1
|
+
// WHATWG Streams — ReadableStream (default controller only)
|
|
2
|
+
// Adapted from refs/node/lib/internal/webstreams/readablestream.js
|
|
3
|
+
// Copyright (c) Node.js contributors. MIT license.
|
|
4
|
+
// Modifications: Removed primordials, BYOB/byte stream, transfer/inspect,
|
|
5
|
+
// Node.js error codes, assert. Rewritten in TypeScript for GJS.
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
kState, kType,
|
|
9
|
+
isBrandCheck,
|
|
10
|
+
createPromiseCallback,
|
|
11
|
+
dequeueValue,
|
|
12
|
+
enqueueValueWithSize,
|
|
13
|
+
extractHighWaterMark,
|
|
14
|
+
extractSizeAlgorithm,
|
|
15
|
+
resetQueue,
|
|
16
|
+
setPromiseHandled,
|
|
17
|
+
nonOpCancel,
|
|
18
|
+
nonOpPull,
|
|
19
|
+
nonOpStart,
|
|
20
|
+
getIterator,
|
|
21
|
+
iteratorNext,
|
|
22
|
+
AsyncIterator,
|
|
23
|
+
} from './util.js';
|
|
24
|
+
|
|
25
|
+
import {
|
|
26
|
+
WritableStreamDefaultWriter,
|
|
27
|
+
isWritableStream,
|
|
28
|
+
isWritableStreamLocked,
|
|
29
|
+
writableStreamAbort,
|
|
30
|
+
writableStreamCloseQueuedOrInFlight,
|
|
31
|
+
writableStreamDefaultWriterCloseWithErrorPropagation,
|
|
32
|
+
writableStreamDefaultWriterRelease,
|
|
33
|
+
writableStreamDefaultWriterWrite,
|
|
34
|
+
} from './writable-stream.js';
|
|
35
|
+
|
|
36
|
+
// ---- Internal symbols ----
|
|
37
|
+
|
|
38
|
+
const kCancel = Symbol('kCancel');
|
|
39
|
+
const kClose = Symbol('kClose');
|
|
40
|
+
const kChunk = Symbol('kChunk');
|
|
41
|
+
const kError = Symbol('kError');
|
|
42
|
+
const kPull = Symbol('kPull');
|
|
43
|
+
const kRelease = Symbol('kRelease');
|
|
44
|
+
const kSkipThrow = Symbol('kSkipThrow');
|
|
45
|
+
|
|
46
|
+
// ---- Lazy error singletons ----
|
|
47
|
+
|
|
48
|
+
let releasedError: TypeError | undefined;
|
|
49
|
+
let releasingError: TypeError | undefined;
|
|
50
|
+
|
|
51
|
+
function lazyReadableReleasedError(): TypeError {
|
|
52
|
+
if (releasedError) return releasedError;
|
|
53
|
+
releasedError = new TypeError('Reader released');
|
|
54
|
+
return releasedError;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function lazyReadableReleasingError(): TypeError {
|
|
58
|
+
if (releasingError) return releasingError;
|
|
59
|
+
releasingError = new TypeError('Releasing reader');
|
|
60
|
+
return releasingError;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ---- DOMException helper ----
|
|
64
|
+
|
|
65
|
+
function createAbortError(): Error {
|
|
66
|
+
if (typeof globalThis.DOMException === 'function') {
|
|
67
|
+
return new DOMException('The operation was aborted', 'AbortError');
|
|
68
|
+
}
|
|
69
|
+
const err = new Error('The operation was aborted');
|
|
70
|
+
err.name = 'AbortError';
|
|
71
|
+
return err;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
import { nextTick as _queueMicrotask } from '@gjsify/utils';
|
|
75
|
+
|
|
76
|
+
// ---- ReadableStream state factory ----
|
|
77
|
+
|
|
78
|
+
function createReadableStreamState() {
|
|
79
|
+
return {
|
|
80
|
+
disturbed: false,
|
|
81
|
+
reader: undefined as ReadableStreamDefaultReader | undefined,
|
|
82
|
+
state: 'readable' as string,
|
|
83
|
+
storedError: undefined as unknown,
|
|
84
|
+
controller: undefined as ReadableStreamDefaultController | undefined,
|
|
85
|
+
// closedPromise tracks stream-level close for watchers (pipeTo, etc.)
|
|
86
|
+
closedPromise: Promise.withResolvers<void>(),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ---- ReadableStream ----
|
|
91
|
+
|
|
92
|
+
class ReadableStream {
|
|
93
|
+
[kType] = 'ReadableStream';
|
|
94
|
+
[kState]: any;
|
|
95
|
+
|
|
96
|
+
constructor(source: any = {}, strategy: any = {}) {
|
|
97
|
+
if (source != null && typeof source !== 'object') {
|
|
98
|
+
throw new TypeError('source must be an object');
|
|
99
|
+
}
|
|
100
|
+
if (strategy != null && typeof strategy !== 'object') {
|
|
101
|
+
throw new TypeError('strategy must be an object');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
this[kState] = createReadableStreamState();
|
|
105
|
+
|
|
106
|
+
// The spec requires handling of the strategy first.
|
|
107
|
+
const size = strategy?.size;
|
|
108
|
+
const highWaterMark = strategy?.highWaterMark;
|
|
109
|
+
const type = source?.type;
|
|
110
|
+
|
|
111
|
+
if (type !== undefined) {
|
|
112
|
+
if (`${type}` === 'bytes') {
|
|
113
|
+
throw new RangeError('Byte streams not yet supported (use default controller)');
|
|
114
|
+
}
|
|
115
|
+
throw new RangeError(`Invalid type: ${type}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
setupReadableStreamDefaultControllerFromSource(
|
|
119
|
+
this,
|
|
120
|
+
source,
|
|
121
|
+
extractHighWaterMark(highWaterMark, 1),
|
|
122
|
+
extractSizeAlgorithm(size),
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
get locked(): boolean {
|
|
127
|
+
if (!isReadableStream(this)) throw new TypeError('Invalid this');
|
|
128
|
+
return isReadableStreamLocked(this);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
static from(iterable: unknown): ReadableStream {
|
|
132
|
+
return readableStreamFromIterable(iterable);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
cancel(reason: unknown = undefined): Promise<void> {
|
|
136
|
+
if (!isReadableStream(this))
|
|
137
|
+
return Promise.reject(new TypeError('Invalid this'));
|
|
138
|
+
if (isReadableStreamLocked(this))
|
|
139
|
+
return Promise.reject(new TypeError('ReadableStream is locked'));
|
|
140
|
+
return readableStreamCancel(this, reason);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
getReader(options: any = {}): ReadableStreamDefaultReader {
|
|
144
|
+
if (!isReadableStream(this)) throw new TypeError('Invalid this');
|
|
145
|
+
if (options != null && typeof options !== 'object') {
|
|
146
|
+
throw new TypeError('options must be an object');
|
|
147
|
+
}
|
|
148
|
+
const mode = options?.mode;
|
|
149
|
+
if (mode === undefined)
|
|
150
|
+
return new ReadableStreamDefaultReader(this);
|
|
151
|
+
if (`${mode}` === 'byob')
|
|
152
|
+
throw new RangeError('BYOB readers not yet supported');
|
|
153
|
+
throw new RangeError(`Invalid mode: ${mode}`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
pipeThrough(transform: any, options: any = {}): ReadableStream {
|
|
157
|
+
if (!isReadableStream(this)) throw new TypeError('Invalid this');
|
|
158
|
+
const readable = transform?.readable;
|
|
159
|
+
if (!isReadableStream(readable)) {
|
|
160
|
+
throw new TypeError('transform.readable must be a ReadableStream');
|
|
161
|
+
}
|
|
162
|
+
const writable = transform?.writable;
|
|
163
|
+
if (!isWritableStream(writable)) {
|
|
164
|
+
throw new TypeError('transform.writable must be a WritableStream');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (options != null && typeof options !== 'object') {
|
|
168
|
+
throw new TypeError('options must be an object');
|
|
169
|
+
}
|
|
170
|
+
const preventAbort = options?.preventAbort;
|
|
171
|
+
const preventCancel = options?.preventCancel;
|
|
172
|
+
const preventClose = options?.preventClose;
|
|
173
|
+
const signal = options?.signal;
|
|
174
|
+
|
|
175
|
+
if (signal !== undefined && !(signal instanceof Object && 'aborted' in signal)) {
|
|
176
|
+
throw new TypeError('options.signal must be an AbortSignal');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (isReadableStreamLocked(this))
|
|
180
|
+
throw new TypeError('The ReadableStream is locked');
|
|
181
|
+
if (isWritableStreamLocked(writable))
|
|
182
|
+
throw new TypeError('The WritableStream is locked');
|
|
183
|
+
|
|
184
|
+
const promise = readableStreamPipeTo(
|
|
185
|
+
this,
|
|
186
|
+
writable,
|
|
187
|
+
!!preventClose,
|
|
188
|
+
!!preventAbort,
|
|
189
|
+
!!preventCancel,
|
|
190
|
+
signal,
|
|
191
|
+
);
|
|
192
|
+
setPromiseHandled(promise);
|
|
193
|
+
|
|
194
|
+
return readable;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
pipeTo(destination: any, options: any = {}): Promise<void> {
|
|
198
|
+
try {
|
|
199
|
+
if (!isReadableStream(this)) throw new TypeError('Invalid this');
|
|
200
|
+
if (!isWritableStream(destination)) {
|
|
201
|
+
throw new TypeError('destination must be a WritableStream');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (options != null && typeof options !== 'object') {
|
|
205
|
+
throw new TypeError('options must be an object');
|
|
206
|
+
}
|
|
207
|
+
const preventAbort = options?.preventAbort;
|
|
208
|
+
const preventCancel = options?.preventCancel;
|
|
209
|
+
const preventClose = options?.preventClose;
|
|
210
|
+
const signal = options?.signal;
|
|
211
|
+
|
|
212
|
+
if (signal !== undefined && !(signal instanceof Object && 'aborted' in signal)) {
|
|
213
|
+
throw new TypeError('options.signal must be an AbortSignal');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (isReadableStreamLocked(this))
|
|
217
|
+
throw new TypeError('The ReadableStream is locked');
|
|
218
|
+
if (isWritableStreamLocked(destination))
|
|
219
|
+
throw new TypeError('The WritableStream is locked');
|
|
220
|
+
|
|
221
|
+
return readableStreamPipeTo(
|
|
222
|
+
this,
|
|
223
|
+
destination,
|
|
224
|
+
!!preventClose,
|
|
225
|
+
!!preventAbort,
|
|
226
|
+
!!preventCancel,
|
|
227
|
+
signal,
|
|
228
|
+
);
|
|
229
|
+
} catch (error) {
|
|
230
|
+
return Promise.reject(error);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
tee(): [ReadableStream, ReadableStream] {
|
|
235
|
+
if (!isReadableStream(this)) throw new TypeError('Invalid this');
|
|
236
|
+
return readableStreamDefaultTee(this, false);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
values(options: any = {}): AsyncIterableIterator<any> {
|
|
240
|
+
if (!isReadableStream(this)) throw new TypeError('Invalid this');
|
|
241
|
+
if (options != null && typeof options !== 'object') {
|
|
242
|
+
throw new TypeError('options must be an object');
|
|
243
|
+
}
|
|
244
|
+
const preventCancel = !!(options?.preventCancel);
|
|
245
|
+
|
|
246
|
+
const reader = new ReadableStreamDefaultReader(this);
|
|
247
|
+
|
|
248
|
+
const state = {
|
|
249
|
+
done: false,
|
|
250
|
+
current: undefined as Promise<unknown> | undefined,
|
|
251
|
+
};
|
|
252
|
+
let started = false;
|
|
253
|
+
|
|
254
|
+
function nextSteps(): Promise<IteratorResult<unknown>> {
|
|
255
|
+
if (state.done)
|
|
256
|
+
return Promise.resolve({ done: true, value: undefined });
|
|
257
|
+
|
|
258
|
+
if (reader[kState].stream === undefined) {
|
|
259
|
+
return Promise.reject(
|
|
260
|
+
new TypeError('The reader is not bound to a ReadableStream'));
|
|
261
|
+
}
|
|
262
|
+
const { promise, resolve, reject } = Promise.withResolvers<IteratorResult<unknown>>();
|
|
263
|
+
|
|
264
|
+
readableStreamDefaultReaderRead(reader, {
|
|
265
|
+
[kChunk](chunk: unknown) {
|
|
266
|
+
state.current = undefined;
|
|
267
|
+
resolve({ value: chunk, done: false });
|
|
268
|
+
},
|
|
269
|
+
[kClose]() {
|
|
270
|
+
state.current = undefined;
|
|
271
|
+
state.done = true;
|
|
272
|
+
readableStreamReaderGenericRelease(reader);
|
|
273
|
+
resolve({ done: true, value: undefined });
|
|
274
|
+
},
|
|
275
|
+
[kError](error: unknown) {
|
|
276
|
+
state.current = undefined;
|
|
277
|
+
state.done = true;
|
|
278
|
+
readableStreamReaderGenericRelease(reader);
|
|
279
|
+
reject(error);
|
|
280
|
+
},
|
|
281
|
+
});
|
|
282
|
+
return promise;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async function returnSteps(value: unknown): Promise<IteratorResult<unknown>> {
|
|
286
|
+
if (state.done)
|
|
287
|
+
return { done: true, value };
|
|
288
|
+
state.done = true;
|
|
289
|
+
|
|
290
|
+
if (reader[kState].stream === undefined) {
|
|
291
|
+
throw new TypeError('The reader is not bound to a ReadableStream');
|
|
292
|
+
}
|
|
293
|
+
if (!preventCancel) {
|
|
294
|
+
const result = readableStreamReaderGenericCancel(reader, value);
|
|
295
|
+
readableStreamReaderGenericRelease(reader);
|
|
296
|
+
await result;
|
|
297
|
+
return { done: true, value };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
readableStreamReaderGenericRelease(reader);
|
|
301
|
+
return { done: true, value };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return Object.setPrototypeOf({
|
|
305
|
+
next() {
|
|
306
|
+
if (!started) {
|
|
307
|
+
state.current = Promise.resolve();
|
|
308
|
+
started = true;
|
|
309
|
+
}
|
|
310
|
+
state.current = state.current !== undefined
|
|
311
|
+
? state.current.then(nextSteps, nextSteps)
|
|
312
|
+
: nextSteps();
|
|
313
|
+
return state.current;
|
|
314
|
+
},
|
|
315
|
+
|
|
316
|
+
return(error: unknown) {
|
|
317
|
+
started = true;
|
|
318
|
+
state.current = state.current !== undefined
|
|
319
|
+
? state.current.then(
|
|
320
|
+
() => returnSteps(error),
|
|
321
|
+
() => returnSteps(error))
|
|
322
|
+
: returnSteps(error);
|
|
323
|
+
return state.current;
|
|
324
|
+
},
|
|
325
|
+
|
|
326
|
+
[Symbol.asyncIterator]() { return this; },
|
|
327
|
+
}, AsyncIterator);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
[Symbol.asyncIterator](): AsyncIterableIterator<any> {
|
|
331
|
+
return this.values();
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
get [Symbol.toStringTag]() {
|
|
335
|
+
return 'ReadableStream';
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// ---- Async iterator read request (used by values()) ----
|
|
340
|
+
// Inlined into the values() method above as an object literal.
|
|
341
|
+
|
|
342
|
+
// ---- DefaultReadRequest ----
|
|
343
|
+
|
|
344
|
+
class DefaultReadRequest {
|
|
345
|
+
[kState]: { promise: Promise<ReadableStreamReadResult<unknown>>; resolve: ((v: ReadableStreamReadResult<unknown>) => void) | undefined; reject: ((e: unknown) => void) | undefined };
|
|
346
|
+
|
|
347
|
+
constructor() {
|
|
348
|
+
this[kState] = Promise.withResolvers<ReadableStreamReadResult<unknown>>();
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
[kChunk](value: unknown) {
|
|
352
|
+
this[kState].resolve?.({ value, done: false });
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
[kClose]() {
|
|
356
|
+
this[kState].resolve?.({ value: undefined, done: true });
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
[kError](error: unknown) {
|
|
360
|
+
this[kState].reject?.(error);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
get promise() { return this[kState].promise; }
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// ---- ReadableStreamDefaultReader ----
|
|
367
|
+
|
|
368
|
+
class ReadableStreamDefaultReader {
|
|
369
|
+
[kType] = 'ReadableStreamDefaultReader';
|
|
370
|
+
[kState]: any;
|
|
371
|
+
|
|
372
|
+
constructor(stream: ReadableStream) {
|
|
373
|
+
if (!isReadableStream(stream))
|
|
374
|
+
throw new TypeError('Expected a ReadableStream');
|
|
375
|
+
this[kState] = {
|
|
376
|
+
readRequests: [] as DefaultReadRequest[],
|
|
377
|
+
stream: undefined as ReadableStream | undefined,
|
|
378
|
+
close: {
|
|
379
|
+
promise: undefined as Promise<void> | undefined,
|
|
380
|
+
resolve: undefined as (() => void) | undefined,
|
|
381
|
+
reject: undefined as ((reason?: unknown) => void) | undefined,
|
|
382
|
+
},
|
|
383
|
+
};
|
|
384
|
+
setupReadableStreamDefaultReader(this, stream);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
read(): Promise<{ value: any; done: boolean }> {
|
|
388
|
+
if (!isReadableStreamDefaultReader(this))
|
|
389
|
+
return Promise.reject(new TypeError('Invalid this'));
|
|
390
|
+
if (this[kState].stream === undefined) {
|
|
391
|
+
return Promise.reject(
|
|
392
|
+
new TypeError('The reader is not attached to a stream'));
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const stream = this[kState].stream;
|
|
396
|
+
const controller = stream[kState].controller;
|
|
397
|
+
|
|
398
|
+
// Fast path: if data is already buffered in a default controller,
|
|
399
|
+
// return a resolved promise immediately without creating a read request.
|
|
400
|
+
if (stream[kState].state === 'readable' &&
|
|
401
|
+
isReadableStreamDefaultController(controller) &&
|
|
402
|
+
controller[kState].queue.length > 0) {
|
|
403
|
+
stream[kState].disturbed = true;
|
|
404
|
+
const chunk = dequeueValue(controller);
|
|
405
|
+
|
|
406
|
+
if (controller[kState].closeRequested && !controller[kState].queue.length) {
|
|
407
|
+
readableStreamDefaultControllerClearAlgorithms(controller);
|
|
408
|
+
readableStreamClose(stream);
|
|
409
|
+
} else {
|
|
410
|
+
readableStreamDefaultControllerCallPullIfNeeded(controller);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return Promise.resolve({ value: chunk, done: false });
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Slow path: create request and go through normal flow
|
|
417
|
+
const readRequest = new DefaultReadRequest();
|
|
418
|
+
readableStreamDefaultReaderRead(this, readRequest);
|
|
419
|
+
return readRequest.promise;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
releaseLock(): void {
|
|
423
|
+
if (!isReadableStreamDefaultReader(this))
|
|
424
|
+
throw new TypeError('Invalid this');
|
|
425
|
+
if (this[kState].stream === undefined)
|
|
426
|
+
return;
|
|
427
|
+
readableStreamDefaultReaderRelease(this);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
get closed(): Promise<void> {
|
|
431
|
+
if (!isReadableStreamDefaultReader(this))
|
|
432
|
+
return Promise.reject(new TypeError('Invalid this'));
|
|
433
|
+
return this[kState].close.promise;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
cancel(reason: unknown = undefined): Promise<void> {
|
|
437
|
+
if (!isReadableStreamDefaultReader(this))
|
|
438
|
+
return Promise.reject(new TypeError('Invalid this'));
|
|
439
|
+
if (this[kState].stream === undefined) {
|
|
440
|
+
return Promise.reject(new TypeError('The reader is not attached to a stream'));
|
|
441
|
+
}
|
|
442
|
+
return readableStreamReaderGenericCancel(this, reason);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
get [Symbol.toStringTag]() {
|
|
446
|
+
return 'ReadableStreamDefaultReader';
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// ---- ReadableStreamDefaultController ----
|
|
451
|
+
|
|
452
|
+
class ReadableStreamDefaultController {
|
|
453
|
+
[kType] = 'ReadableStreamDefaultController';
|
|
454
|
+
[kState]: any = {};
|
|
455
|
+
|
|
456
|
+
constructor(skipThrowSymbol?: symbol) {
|
|
457
|
+
if (skipThrowSymbol !== kSkipThrow) {
|
|
458
|
+
throw new TypeError('Illegal constructor');
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
get desiredSize(): number | null {
|
|
463
|
+
return readableStreamDefaultControllerGetDesiredSize(this);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
close(): void {
|
|
467
|
+
if (!readableStreamDefaultControllerCanCloseOrEnqueue(this))
|
|
468
|
+
throw new TypeError('Controller is already closed');
|
|
469
|
+
readableStreamDefaultControllerClose(this);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
enqueue(chunk: any = undefined): void {
|
|
473
|
+
if (!readableStreamDefaultControllerCanCloseOrEnqueue(this))
|
|
474
|
+
throw new TypeError('Controller is already closed');
|
|
475
|
+
readableStreamDefaultControllerEnqueue(this, chunk);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
error(error: unknown = undefined): void {
|
|
479
|
+
readableStreamDefaultControllerError(this, error);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
[kCancel](reason: unknown) {
|
|
483
|
+
return readableStreamDefaultControllerCancelSteps(this, reason);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
[kPull](readRequest: any) {
|
|
487
|
+
readableStreamDefaultControllerPullSteps(this, readRequest);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
[kRelease]() {}
|
|
491
|
+
|
|
492
|
+
get [Symbol.toStringTag]() {
|
|
493
|
+
return 'ReadableStreamDefaultController';
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// ---- Brand checks ----
|
|
498
|
+
|
|
499
|
+
const isReadableStream = isBrandCheck('ReadableStream');
|
|
500
|
+
const isReadableStreamDefaultController = isBrandCheck('ReadableStreamDefaultController');
|
|
501
|
+
const isReadableStreamDefaultReader = isBrandCheck('ReadableStreamDefaultReader');
|
|
502
|
+
|
|
503
|
+
// ---- Stream state helpers ----
|
|
504
|
+
|
|
505
|
+
function isReadableStreamLocked(stream: any): boolean {
|
|
506
|
+
return stream[kState].reader !== undefined;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function readableStreamCancel(stream: any, reason: unknown): Promise<void> {
|
|
510
|
+
stream[kState].disturbed = true;
|
|
511
|
+
switch (stream[kState].state) {
|
|
512
|
+
case 'closed':
|
|
513
|
+
return Promise.resolve();
|
|
514
|
+
case 'errored':
|
|
515
|
+
return Promise.reject(stream[kState].storedError);
|
|
516
|
+
}
|
|
517
|
+
readableStreamClose(stream);
|
|
518
|
+
|
|
519
|
+
return stream[kState].controller[kCancel](reason).then(() => {});
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function readableStreamClose(stream: any): void {
|
|
523
|
+
stream[kState].state = 'closed';
|
|
524
|
+
stream[kState].closedPromise.resolve();
|
|
525
|
+
const { reader } = stream[kState];
|
|
526
|
+
|
|
527
|
+
if (reader === undefined) return;
|
|
528
|
+
|
|
529
|
+
reader[kState].close.resolve?.();
|
|
530
|
+
|
|
531
|
+
if (readableStreamHasDefaultReader(stream)) {
|
|
532
|
+
for (let n = 0; n < reader[kState].readRequests.length; n++)
|
|
533
|
+
reader[kState].readRequests[n][kClose]();
|
|
534
|
+
reader[kState].readRequests = [];
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function readableStreamError(stream: any, error: unknown): void {
|
|
539
|
+
stream[kState].state = 'errored';
|
|
540
|
+
stream[kState].storedError = error;
|
|
541
|
+
setPromiseHandled(stream[kState].closedPromise.promise);
|
|
542
|
+
stream[kState].closedPromise.reject(error);
|
|
543
|
+
|
|
544
|
+
const { reader } = stream[kState];
|
|
545
|
+
|
|
546
|
+
if (reader === undefined) return;
|
|
547
|
+
|
|
548
|
+
setPromiseHandled(reader[kState].close.promise);
|
|
549
|
+
reader[kState].close.reject?.(error);
|
|
550
|
+
|
|
551
|
+
if (readableStreamHasDefaultReader(stream)) {
|
|
552
|
+
for (let n = 0; n < reader[kState].readRequests.length; n++)
|
|
553
|
+
reader[kState].readRequests[n][kError](error);
|
|
554
|
+
reader[kState].readRequests = [];
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function readableStreamHasDefaultReader(stream: any): boolean {
|
|
559
|
+
const { reader } = stream[kState];
|
|
560
|
+
if (reader === undefined) return false;
|
|
561
|
+
return reader[kState] !== undefined && reader[kType] === 'ReadableStreamDefaultReader';
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function readableStreamGetNumReadRequests(stream: any): number {
|
|
565
|
+
return stream[kState].reader[kState].readRequests.length;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function readableStreamFulfillReadRequest(stream: any, chunk: unknown, done: boolean): void {
|
|
569
|
+
const { reader } = stream[kState];
|
|
570
|
+
const readRequest = reader[kState].readRequests.shift();
|
|
571
|
+
|
|
572
|
+
if (done)
|
|
573
|
+
readRequest[kClose]();
|
|
574
|
+
else
|
|
575
|
+
readRequest[kChunk](chunk);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function readableStreamAddReadRequest(stream: any, readRequest: any): void {
|
|
579
|
+
stream[kState].reader[kState].readRequests.push(readRequest);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// ---- Reader generic operations ----
|
|
583
|
+
|
|
584
|
+
function readableStreamReaderGenericCancel(reader: any, reason: unknown): Promise<void> {
|
|
585
|
+
const { stream } = reader[kState];
|
|
586
|
+
return readableStreamCancel(stream, reason);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function readableStreamReaderGenericInitialize(reader: any, stream: any): void {
|
|
590
|
+
reader[kState].stream = stream;
|
|
591
|
+
stream[kState].reader = reader;
|
|
592
|
+
switch (stream[kState].state) {
|
|
593
|
+
case 'readable':
|
|
594
|
+
reader[kState].close = Promise.withResolvers<void>();
|
|
595
|
+
break;
|
|
596
|
+
case 'closed':
|
|
597
|
+
reader[kState].close = {
|
|
598
|
+
promise: Promise.resolve(),
|
|
599
|
+
resolve: undefined,
|
|
600
|
+
reject: undefined,
|
|
601
|
+
};
|
|
602
|
+
break;
|
|
603
|
+
case 'errored':
|
|
604
|
+
reader[kState].close = {
|
|
605
|
+
promise: Promise.reject(stream[kState].storedError),
|
|
606
|
+
resolve: undefined,
|
|
607
|
+
reject: undefined,
|
|
608
|
+
};
|
|
609
|
+
setPromiseHandled(reader[kState].close.promise);
|
|
610
|
+
break;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function readableStreamReaderGenericRelease(reader: any): void {
|
|
615
|
+
const { stream } = reader[kState];
|
|
616
|
+
|
|
617
|
+
const releasedStateError = lazyReadableReleasedError();
|
|
618
|
+
if (stream[kState].state === 'readable') {
|
|
619
|
+
reader[kState].close.reject?.(releasedStateError);
|
|
620
|
+
} else {
|
|
621
|
+
reader[kState].close = {
|
|
622
|
+
promise: Promise.reject(releasedStateError),
|
|
623
|
+
resolve: undefined,
|
|
624
|
+
reject: undefined,
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
setPromiseHandled(reader[kState].close.promise);
|
|
628
|
+
|
|
629
|
+
stream[kState].controller[kRelease]();
|
|
630
|
+
|
|
631
|
+
stream[kState].reader = undefined;
|
|
632
|
+
reader[kState].stream = undefined;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function readableStreamDefaultReaderRelease(reader: any): void {
|
|
636
|
+
readableStreamReaderGenericRelease(reader);
|
|
637
|
+
readableStreamDefaultReaderErrorReadRequests(reader, lazyReadableReleasingError());
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
function readableStreamDefaultReaderErrorReadRequests(reader: any, e: unknown): void {
|
|
641
|
+
for (let n = 0; n < reader[kState].readRequests.length; ++n) {
|
|
642
|
+
reader[kState].readRequests[n][kError](e);
|
|
643
|
+
}
|
|
644
|
+
reader[kState].readRequests = [];
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function readableStreamDefaultReaderRead(reader: any, readRequest: any): void {
|
|
648
|
+
const { stream } = reader[kState];
|
|
649
|
+
stream[kState].disturbed = true;
|
|
650
|
+
switch (stream[kState].state) {
|
|
651
|
+
case 'closed':
|
|
652
|
+
readRequest[kClose]();
|
|
653
|
+
break;
|
|
654
|
+
case 'errored':
|
|
655
|
+
readRequest[kError](stream[kState].storedError);
|
|
656
|
+
break;
|
|
657
|
+
case 'readable':
|
|
658
|
+
stream[kState].controller[kPull](readRequest);
|
|
659
|
+
break;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function setupReadableStreamDefaultReader(reader: any, stream: any): void {
|
|
664
|
+
if (isReadableStreamLocked(stream))
|
|
665
|
+
throw new TypeError('ReadableStream is locked');
|
|
666
|
+
readableStreamReaderGenericInitialize(reader, stream);
|
|
667
|
+
reader[kState].readRequests = [];
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// ---- Default controller internals ----
|
|
671
|
+
|
|
672
|
+
function readableStreamDefaultControllerClose(controller: any): void {
|
|
673
|
+
if (!readableStreamDefaultControllerCanCloseOrEnqueue(controller))
|
|
674
|
+
return;
|
|
675
|
+
controller[kState].closeRequested = true;
|
|
676
|
+
if (!controller[kState].queue.length) {
|
|
677
|
+
readableStreamDefaultControllerClearAlgorithms(controller);
|
|
678
|
+
readableStreamClose(controller[kState].stream);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
function readableStreamDefaultControllerEnqueue(controller: any, chunk: any): void {
|
|
683
|
+
if (!readableStreamDefaultControllerCanCloseOrEnqueue(controller))
|
|
684
|
+
return;
|
|
685
|
+
|
|
686
|
+
const { stream } = controller[kState];
|
|
687
|
+
|
|
688
|
+
if (isReadableStreamLocked(stream) &&
|
|
689
|
+
readableStreamGetNumReadRequests(stream)) {
|
|
690
|
+
readableStreamFulfillReadRequest(stream, chunk, false);
|
|
691
|
+
} else {
|
|
692
|
+
try {
|
|
693
|
+
const chunkSize = controller[kState].sizeAlgorithm(chunk);
|
|
694
|
+
enqueueValueWithSize(controller, chunk, chunkSize);
|
|
695
|
+
} catch (error) {
|
|
696
|
+
readableStreamDefaultControllerError(controller, error);
|
|
697
|
+
throw error;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
readableStreamDefaultControllerCallPullIfNeeded(controller);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function readableStreamDefaultControllerCanCloseOrEnqueue(controller: any): boolean {
|
|
704
|
+
const { stream } = controller[kState];
|
|
705
|
+
return !controller[kState].closeRequested &&
|
|
706
|
+
stream[kState].state === 'readable';
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
function readableStreamDefaultControllerGetDesiredSize(controller: any): number | null {
|
|
710
|
+
const { stream, highWaterMark, queueTotalSize } = controller[kState];
|
|
711
|
+
switch (stream[kState].state) {
|
|
712
|
+
case 'errored': return null;
|
|
713
|
+
case 'closed': return 0;
|
|
714
|
+
default: return highWaterMark - queueTotalSize;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
function readableStreamDefaultControllerHasBackpressure(controller: any): boolean {
|
|
719
|
+
return !readableStreamDefaultControllerShouldCallPull(controller);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function readableStreamDefaultControllerShouldCallPull(controller: any): boolean {
|
|
723
|
+
const { stream } = controller[kState];
|
|
724
|
+
if (!readableStreamDefaultControllerCanCloseOrEnqueue(controller) ||
|
|
725
|
+
!controller[kState].started)
|
|
726
|
+
return false;
|
|
727
|
+
|
|
728
|
+
if (isReadableStreamLocked(stream) &&
|
|
729
|
+
readableStreamGetNumReadRequests(stream)) {
|
|
730
|
+
return true;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
const desiredSize = readableStreamDefaultControllerGetDesiredSize(controller);
|
|
734
|
+
return desiredSize !== null && desiredSize > 0;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function readableStreamDefaultControllerCallPullIfNeeded(controller: any): void {
|
|
738
|
+
if (!readableStreamDefaultControllerShouldCallPull(controller))
|
|
739
|
+
return;
|
|
740
|
+
if (controller[kState].pulling) {
|
|
741
|
+
controller[kState].pullAgain = true;
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
controller[kState].pulling = true;
|
|
745
|
+
controller[kState].pullAlgorithm(controller).then(
|
|
746
|
+
() => {
|
|
747
|
+
controller[kState].pulling = false;
|
|
748
|
+
if (controller[kState].pullAgain) {
|
|
749
|
+
controller[kState].pullAgain = false;
|
|
750
|
+
readableStreamDefaultControllerCallPullIfNeeded(controller);
|
|
751
|
+
}
|
|
752
|
+
},
|
|
753
|
+
(error: unknown) => readableStreamDefaultControllerError(controller, error),
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
function readableStreamDefaultControllerClearAlgorithms(controller: any): void {
|
|
758
|
+
controller[kState].pullAlgorithm = undefined;
|
|
759
|
+
controller[kState].cancelAlgorithm = undefined;
|
|
760
|
+
controller[kState].sizeAlgorithm = undefined;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
function readableStreamDefaultControllerError(controller: any, error: unknown): void {
|
|
764
|
+
const { stream } = controller[kState];
|
|
765
|
+
if (stream[kState].state === 'readable') {
|
|
766
|
+
resetQueue(controller);
|
|
767
|
+
readableStreamDefaultControllerClearAlgorithms(controller);
|
|
768
|
+
readableStreamError(stream, error);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
function readableStreamDefaultControllerCancelSteps(controller: any, reason: unknown): Promise<void> {
|
|
773
|
+
resetQueue(controller);
|
|
774
|
+
const result = controller[kState].cancelAlgorithm(reason);
|
|
775
|
+
readableStreamDefaultControllerClearAlgorithms(controller);
|
|
776
|
+
return result;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
function readableStreamDefaultControllerPullSteps(controller: any, readRequest: any): void {
|
|
780
|
+
const { stream, queue } = controller[kState];
|
|
781
|
+
if (queue.length) {
|
|
782
|
+
const chunk = dequeueValue(controller);
|
|
783
|
+
if (controller[kState].closeRequested && !queue.length) {
|
|
784
|
+
readableStreamDefaultControllerClearAlgorithms(controller);
|
|
785
|
+
readableStreamClose(stream);
|
|
786
|
+
} else {
|
|
787
|
+
readableStreamDefaultControllerCallPullIfNeeded(controller);
|
|
788
|
+
}
|
|
789
|
+
readRequest[kChunk](chunk);
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
readableStreamAddReadRequest(stream, readRequest);
|
|
793
|
+
readableStreamDefaultControllerCallPullIfNeeded(controller);
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// ---- Setup functions ----
|
|
797
|
+
|
|
798
|
+
function setupReadableStreamDefaultController(
|
|
799
|
+
stream: any,
|
|
800
|
+
controller: any,
|
|
801
|
+
startAlgorithm: Function,
|
|
802
|
+
pullAlgorithm: Function,
|
|
803
|
+
cancelAlgorithm: Function,
|
|
804
|
+
highWaterMark: number,
|
|
805
|
+
sizeAlgorithm: (chunk: any) => number,
|
|
806
|
+
): void {
|
|
807
|
+
controller[kState] = {
|
|
808
|
+
cancelAlgorithm,
|
|
809
|
+
closeRequested: false,
|
|
810
|
+
highWaterMark,
|
|
811
|
+
pullAgain: false,
|
|
812
|
+
pullAlgorithm,
|
|
813
|
+
pulling: false,
|
|
814
|
+
queue: [] as { value: unknown; size: number }[],
|
|
815
|
+
queueTotalSize: 0,
|
|
816
|
+
started: false,
|
|
817
|
+
sizeAlgorithm,
|
|
818
|
+
stream,
|
|
819
|
+
};
|
|
820
|
+
stream[kState].controller = controller;
|
|
821
|
+
|
|
822
|
+
const startResult = startAlgorithm();
|
|
823
|
+
|
|
824
|
+
new Promise((r) => r(startResult)).then(
|
|
825
|
+
() => {
|
|
826
|
+
controller[kState].started = true;
|
|
827
|
+
readableStreamDefaultControllerCallPullIfNeeded(controller);
|
|
828
|
+
},
|
|
829
|
+
(error) => readableStreamDefaultControllerError(controller, error),
|
|
830
|
+
);
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
function setupReadableStreamDefaultControllerFromSource(
|
|
834
|
+
stream: any,
|
|
835
|
+
source: any,
|
|
836
|
+
highWaterMark: number,
|
|
837
|
+
sizeAlgorithm: (chunk: any) => number,
|
|
838
|
+
): void {
|
|
839
|
+
const controller = new ReadableStreamDefaultController(kSkipThrow);
|
|
840
|
+
const start = source?.start;
|
|
841
|
+
const pull = source?.pull;
|
|
842
|
+
const cancel = source?.cancel;
|
|
843
|
+
const startAlgorithm = start
|
|
844
|
+
? start.bind(source, controller)
|
|
845
|
+
: nonOpStart;
|
|
846
|
+
const pullAlgorithm = pull
|
|
847
|
+
? createPromiseCallback('source.pull', pull, source)
|
|
848
|
+
: nonOpPull;
|
|
849
|
+
const cancelAlgorithm = cancel
|
|
850
|
+
? createPromiseCallback('source.cancel', cancel, source)
|
|
851
|
+
: nonOpCancel;
|
|
852
|
+
|
|
853
|
+
setupReadableStreamDefaultController(
|
|
854
|
+
stream,
|
|
855
|
+
controller,
|
|
856
|
+
startAlgorithm,
|
|
857
|
+
pullAlgorithm,
|
|
858
|
+
cancelAlgorithm,
|
|
859
|
+
highWaterMark,
|
|
860
|
+
sizeAlgorithm,
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// ---- Internal factory (used by TransformStream and tee) ----
|
|
865
|
+
|
|
866
|
+
function createReadableStream(
|
|
867
|
+
start: Function,
|
|
868
|
+
pull: Function,
|
|
869
|
+
cancel: Function,
|
|
870
|
+
highWaterMark = 1,
|
|
871
|
+
size: (chunk: any) => number = () => 1,
|
|
872
|
+
): ReadableStream {
|
|
873
|
+
const stream = Object.create(ReadableStream.prototype);
|
|
874
|
+
stream[kType] = 'ReadableStream';
|
|
875
|
+
stream[kState] = createReadableStreamState();
|
|
876
|
+
|
|
877
|
+
const controller = new ReadableStreamDefaultController(kSkipThrow);
|
|
878
|
+
setupReadableStreamDefaultController(
|
|
879
|
+
stream, controller, start, pull, cancel, highWaterMark, size,
|
|
880
|
+
);
|
|
881
|
+
return stream;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// ---- readableStreamFromIterable (static from()) ----
|
|
885
|
+
|
|
886
|
+
function readableStreamFromIterable(iterable: unknown): ReadableStream {
|
|
887
|
+
let stream: any;
|
|
888
|
+
const iteratorRecord = getIterator(iterable as Record<string | symbol, unknown>, 'async');
|
|
889
|
+
|
|
890
|
+
const startAlgorithm = nonOpStart;
|
|
891
|
+
|
|
892
|
+
async function pullAlgorithm() {
|
|
893
|
+
const nextResult = iteratorNext(iteratorRecord);
|
|
894
|
+
const nextPromise = Promise.resolve(nextResult);
|
|
895
|
+
return nextPromise.then((iterResult: Record<string, unknown>) => {
|
|
896
|
+
if (typeof iterResult !== 'object' || iterResult === null) {
|
|
897
|
+
throw new TypeError(
|
|
898
|
+
'The promise returned by the iterator.next() method must fulfill with an object');
|
|
899
|
+
}
|
|
900
|
+
if (iterResult.done) {
|
|
901
|
+
readableStreamDefaultControllerClose(stream[kState].controller);
|
|
902
|
+
} else {
|
|
903
|
+
readableStreamDefaultControllerEnqueue(stream[kState].controller, iterResult.value);
|
|
904
|
+
}
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
async function cancelAlgorithm(reason: unknown) {
|
|
909
|
+
const iterator = iteratorRecord.iterator;
|
|
910
|
+
const returnMethod = iterator.return;
|
|
911
|
+
if (returnMethod === undefined) {
|
|
912
|
+
return Promise.resolve();
|
|
913
|
+
}
|
|
914
|
+
const returnResult = returnMethod.call(iterator, reason);
|
|
915
|
+
const returnPromise = Promise.resolve(returnResult);
|
|
916
|
+
return returnPromise.then((iterResult: Record<string, unknown>) => {
|
|
917
|
+
if (typeof iterResult !== 'object' || iterResult === null) {
|
|
918
|
+
throw new TypeError(
|
|
919
|
+
'The promise returned by the iterator.return() method must fulfill with an object');
|
|
920
|
+
}
|
|
921
|
+
return undefined;
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
stream = createReadableStream(
|
|
926
|
+
startAlgorithm,
|
|
927
|
+
pullAlgorithm,
|
|
928
|
+
cancelAlgorithm,
|
|
929
|
+
0,
|
|
930
|
+
);
|
|
931
|
+
|
|
932
|
+
return stream;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
// ---- readableStreamPipeTo ----
|
|
936
|
+
|
|
937
|
+
function readableStreamPipeTo(
|
|
938
|
+
source: any,
|
|
939
|
+
dest: any,
|
|
940
|
+
preventClose: boolean,
|
|
941
|
+
preventAbort: boolean,
|
|
942
|
+
preventCancel: boolean,
|
|
943
|
+
signal: any,
|
|
944
|
+
): Promise<void> {
|
|
945
|
+
let reader: any;
|
|
946
|
+
let writer: any;
|
|
947
|
+
|
|
948
|
+
// Both of these can throw synchronously. We want to capture
|
|
949
|
+
// the error and return a rejected promise instead.
|
|
950
|
+
try {
|
|
951
|
+
reader = new ReadableStreamDefaultReader(source);
|
|
952
|
+
writer = new WritableStreamDefaultWriter(dest);
|
|
953
|
+
} catch (error) {
|
|
954
|
+
return Promise.reject(error);
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
source[kState].disturbed = true;
|
|
958
|
+
|
|
959
|
+
let shuttingDown = false;
|
|
960
|
+
|
|
961
|
+
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
|
962
|
+
|
|
963
|
+
const state = {
|
|
964
|
+
currentWrite: Promise.resolve() as Promise<void>,
|
|
965
|
+
};
|
|
966
|
+
|
|
967
|
+
let abortListener: (() => void) | undefined;
|
|
968
|
+
|
|
969
|
+
// The error here can be undefined. The rejected arg
|
|
970
|
+
// tells us that the promise must be rejected even
|
|
971
|
+
// when error is undefined.
|
|
972
|
+
function finalize(rejected: boolean, error?: unknown) {
|
|
973
|
+
writableStreamDefaultWriterRelease(writer);
|
|
974
|
+
readableStreamReaderGenericRelease(reader);
|
|
975
|
+
if (signal !== undefined && abortListener) {
|
|
976
|
+
signal.removeEventListener('abort', abortListener);
|
|
977
|
+
abortListener = undefined;
|
|
978
|
+
}
|
|
979
|
+
if (rejected)
|
|
980
|
+
reject(error);
|
|
981
|
+
else
|
|
982
|
+
resolve();
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
async function waitForCurrentWrite() {
|
|
986
|
+
const write = state.currentWrite;
|
|
987
|
+
await write;
|
|
988
|
+
if (write !== state.currentWrite)
|
|
989
|
+
await waitForCurrentWrite();
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
function shutdownWithAnAction(
|
|
993
|
+
action: () => Promise<unknown>,
|
|
994
|
+
rejected?: boolean,
|
|
995
|
+
originalError?: unknown,
|
|
996
|
+
) {
|
|
997
|
+
if (shuttingDown) return;
|
|
998
|
+
shuttingDown = true;
|
|
999
|
+
if (dest[kState].state === 'writable' &&
|
|
1000
|
+
!writableStreamCloseQueuedOrInFlight(dest)) {
|
|
1001
|
+
waitForCurrentWrite().then(complete, (error) => finalize(true, error));
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
complete();
|
|
1005
|
+
|
|
1006
|
+
function complete() {
|
|
1007
|
+
action().then(
|
|
1008
|
+
() => finalize(!!rejected, originalError),
|
|
1009
|
+
(error) => finalize(true, error),
|
|
1010
|
+
);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
function shutdown(rejected?: boolean, error?: unknown) {
|
|
1015
|
+
if (shuttingDown) return;
|
|
1016
|
+
shuttingDown = true;
|
|
1017
|
+
if (dest[kState].state === 'writable' &&
|
|
1018
|
+
!writableStreamCloseQueuedOrInFlight(dest)) {
|
|
1019
|
+
waitForCurrentWrite().then(
|
|
1020
|
+
() => finalize(!!rejected, error),
|
|
1021
|
+
(err) => finalize(true, err),
|
|
1022
|
+
);
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
finalize(!!rejected, error);
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
function abortAlgorithm() {
|
|
1029
|
+
let error: unknown;
|
|
1030
|
+
if (signal.reason && (signal.reason as { name?: string }).name === 'AbortError') {
|
|
1031
|
+
// Cannot use the signal.reason directly if it's not a DOMException.
|
|
1032
|
+
error = createAbortError();
|
|
1033
|
+
} else {
|
|
1034
|
+
error = signal.reason;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
const actions: (() => Promise<void>)[] = [];
|
|
1038
|
+
if (!preventAbort) {
|
|
1039
|
+
actions.push(() => {
|
|
1040
|
+
if (dest[kState].state === 'writable')
|
|
1041
|
+
return writableStreamAbort(dest, error);
|
|
1042
|
+
return Promise.resolve();
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
if (!preventCancel) {
|
|
1046
|
+
actions.push(() => {
|
|
1047
|
+
if (source[kState].state === 'readable')
|
|
1048
|
+
return readableStreamCancel(source, error);
|
|
1049
|
+
return Promise.resolve();
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
shutdownWithAnAction(
|
|
1054
|
+
() => Promise.all(actions.map((action) => action())).then(() => undefined),
|
|
1055
|
+
true,
|
|
1056
|
+
error,
|
|
1057
|
+
);
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
function watchErrored(stream: any, watchPromise: Promise<unknown>, action: (error: unknown) => void) {
|
|
1061
|
+
if (stream[kState].state === 'errored')
|
|
1062
|
+
action(stream[kState].storedError);
|
|
1063
|
+
else
|
|
1064
|
+
watchPromise.then(undefined, action);
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
function watchClosed(stream: any, watchPromise: Promise<unknown>, action: () => void) {
|
|
1068
|
+
if (stream[kState].state === 'closed')
|
|
1069
|
+
action();
|
|
1070
|
+
else
|
|
1071
|
+
watchPromise.then(action, () => {});
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// PipeTo read request for the slow path
|
|
1075
|
+
class PipeToReadRequest {
|
|
1076
|
+
[kChunk](chunk: unknown) {
|
|
1077
|
+
// Per spec, pipeTo must queue a microtask for the write to avoid
|
|
1078
|
+
// synchronous write during enqueue().
|
|
1079
|
+
_queueMicrotask(() => {
|
|
1080
|
+
state.currentWrite = writableStreamDefaultWriterWrite(writer, chunk);
|
|
1081
|
+
setPromiseHandled(state.currentWrite);
|
|
1082
|
+
stepPromise.resolve!(false);
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
[kClose]() {
|
|
1086
|
+
stepPromise.resolve!(true);
|
|
1087
|
+
}
|
|
1088
|
+
[kError](error: unknown) {
|
|
1089
|
+
stepPromise.reject!(error);
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
let stepPromise: { resolve: ((v: boolean) => void) | null; reject: ((e: unknown) => void) | null } = {
|
|
1094
|
+
resolve: null,
|
|
1095
|
+
reject: null,
|
|
1096
|
+
};
|
|
1097
|
+
|
|
1098
|
+
async function step(): Promise<boolean> {
|
|
1099
|
+
if (shuttingDown) return true;
|
|
1100
|
+
|
|
1101
|
+
if (dest[kState].backpressure) {
|
|
1102
|
+
await writer[kState].ready.promise;
|
|
1103
|
+
if (shuttingDown) return true;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
const controller = source[kState].controller;
|
|
1107
|
+
|
|
1108
|
+
// Fast path: batch reads when data is buffered in a default controller.
|
|
1109
|
+
if (source[kState].state === 'readable' &&
|
|
1110
|
+
isReadableStreamDefaultController(controller) &&
|
|
1111
|
+
controller[kState].queue.length > 0) {
|
|
1112
|
+
|
|
1113
|
+
while (controller[kState].queue.length > 0) {
|
|
1114
|
+
if (shuttingDown) return true;
|
|
1115
|
+
|
|
1116
|
+
const chunk = dequeueValue(controller);
|
|
1117
|
+
|
|
1118
|
+
if (controller[kState].closeRequested && !controller[kState].queue.length) {
|
|
1119
|
+
readableStreamDefaultControllerClearAlgorithms(controller);
|
|
1120
|
+
readableStreamClose(source);
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
// Write the chunk
|
|
1124
|
+
state.currentWrite = writableStreamDefaultWriterWrite(writer, chunk);
|
|
1125
|
+
setPromiseHandled(state.currentWrite);
|
|
1126
|
+
|
|
1127
|
+
// Check backpressure after each write
|
|
1128
|
+
if (dest[kState].backpressure) {
|
|
1129
|
+
break;
|
|
1130
|
+
} else if (dest[kState].state !== 'writable' || writableStreamCloseQueuedOrInFlight(dest)) {
|
|
1131
|
+
break;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// Trigger pull if needed after batch
|
|
1136
|
+
readableStreamDefaultControllerCallPullIfNeeded(controller);
|
|
1137
|
+
|
|
1138
|
+
// Check if stream closed during batch
|
|
1139
|
+
if (source[kState].state === 'closed') {
|
|
1140
|
+
return true;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
// Yield to microtask queue between batches
|
|
1144
|
+
return false;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
// Slow path: use read request for async reads
|
|
1148
|
+
const { promise: readPromise, resolve: readResolve, reject: readReject } = Promise.withResolvers<boolean>();
|
|
1149
|
+
stepPromise = { resolve: readResolve, reject: readReject };
|
|
1150
|
+
readableStreamDefaultReaderRead(reader, new PipeToReadRequest());
|
|
1151
|
+
|
|
1152
|
+
return readPromise;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
async function run() {
|
|
1156
|
+
// Run until step resolves as true
|
|
1157
|
+
while (!await step());
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
if (signal !== undefined) {
|
|
1161
|
+
if (signal.aborted) {
|
|
1162
|
+
abortAlgorithm();
|
|
1163
|
+
return promise;
|
|
1164
|
+
}
|
|
1165
|
+
abortListener = abortAlgorithm;
|
|
1166
|
+
signal.addEventListener('abort', abortListener, { once: true });
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
setPromiseHandled(run());
|
|
1170
|
+
|
|
1171
|
+
watchErrored(source, reader[kState].close.promise, (error: unknown) => {
|
|
1172
|
+
if (!preventAbort) {
|
|
1173
|
+
return shutdownWithAnAction(
|
|
1174
|
+
() => writableStreamAbort(dest, error),
|
|
1175
|
+
true,
|
|
1176
|
+
error,
|
|
1177
|
+
);
|
|
1178
|
+
}
|
|
1179
|
+
shutdown(true, error);
|
|
1180
|
+
});
|
|
1181
|
+
|
|
1182
|
+
watchErrored(dest, writer[kState].close.promise, (error: unknown) => {
|
|
1183
|
+
if (!preventCancel) {
|
|
1184
|
+
return shutdownWithAnAction(
|
|
1185
|
+
() => readableStreamCancel(source, error),
|
|
1186
|
+
true,
|
|
1187
|
+
error,
|
|
1188
|
+
);
|
|
1189
|
+
}
|
|
1190
|
+
shutdown(true, error);
|
|
1191
|
+
});
|
|
1192
|
+
|
|
1193
|
+
watchClosed(source, reader[kState].close.promise, () => {
|
|
1194
|
+
if (!preventClose) {
|
|
1195
|
+
return shutdownWithAnAction(
|
|
1196
|
+
() => writableStreamDefaultWriterCloseWithErrorPropagation(writer));
|
|
1197
|
+
}
|
|
1198
|
+
shutdown();
|
|
1199
|
+
});
|
|
1200
|
+
|
|
1201
|
+
if (writableStreamCloseQueuedOrInFlight(dest) ||
|
|
1202
|
+
dest[kState].state === 'closed') {
|
|
1203
|
+
const error = new TypeError('Destination WritableStream is closed');
|
|
1204
|
+
if (!preventCancel) {
|
|
1205
|
+
shutdownWithAnAction(
|
|
1206
|
+
() => readableStreamCancel(source, error), true, error);
|
|
1207
|
+
} else {
|
|
1208
|
+
shutdown(true, error);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
return promise;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
// ---- readableStreamDefaultTee ----
|
|
1216
|
+
|
|
1217
|
+
function readableStreamDefaultTee(stream: any, cloneForBranch2: boolean): [ReadableStream, ReadableStream] {
|
|
1218
|
+
const reader = new ReadableStreamDefaultReader(stream);
|
|
1219
|
+
let reading = false;
|
|
1220
|
+
let canceled1 = false;
|
|
1221
|
+
let canceled2 = false;
|
|
1222
|
+
let reason1: unknown;
|
|
1223
|
+
let reason2: unknown;
|
|
1224
|
+
let branch1: any;
|
|
1225
|
+
let branch2: any;
|
|
1226
|
+
const cancelPromise = Promise.withResolvers<unknown>();
|
|
1227
|
+
|
|
1228
|
+
async function pullAlgorithm() {
|
|
1229
|
+
if (reading) return;
|
|
1230
|
+
reading = true;
|
|
1231
|
+
const readRequest = {
|
|
1232
|
+
[kChunk](value: unknown) {
|
|
1233
|
+
_queueMicrotask(() => {
|
|
1234
|
+
reading = false;
|
|
1235
|
+
const value1 = value;
|
|
1236
|
+
let value2 = value;
|
|
1237
|
+
if (!canceled2 && cloneForBranch2) {
|
|
1238
|
+
try {
|
|
1239
|
+
value2 = structuredClone(value2);
|
|
1240
|
+
} catch {
|
|
1241
|
+
// If structuredClone is not available, use the same reference
|
|
1242
|
+
// This is a best-effort approach for GJS environments
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
if (!canceled1) {
|
|
1246
|
+
readableStreamDefaultControllerEnqueue(
|
|
1247
|
+
branch1[kState].controller,
|
|
1248
|
+
value1);
|
|
1249
|
+
}
|
|
1250
|
+
if (!canceled2) {
|
|
1251
|
+
readableStreamDefaultControllerEnqueue(
|
|
1252
|
+
branch2[kState].controller,
|
|
1253
|
+
value2);
|
|
1254
|
+
}
|
|
1255
|
+
});
|
|
1256
|
+
},
|
|
1257
|
+
[kClose]() {
|
|
1258
|
+
// Use a microtask to avoid race conditions.
|
|
1259
|
+
// The Node.js reference uses process.nextTick() here; we use
|
|
1260
|
+
// queueMicrotask which is the closest equivalent in GJS.
|
|
1261
|
+
_queueMicrotask(() => {
|
|
1262
|
+
reading = false;
|
|
1263
|
+
if (!canceled1)
|
|
1264
|
+
readableStreamDefaultControllerClose(branch1[kState].controller);
|
|
1265
|
+
if (!canceled2)
|
|
1266
|
+
readableStreamDefaultControllerClose(branch2[kState].controller);
|
|
1267
|
+
if (!canceled1 || !canceled2)
|
|
1268
|
+
cancelPromise.resolve(undefined);
|
|
1269
|
+
});
|
|
1270
|
+
},
|
|
1271
|
+
[kError]() {
|
|
1272
|
+
reading = false;
|
|
1273
|
+
},
|
|
1274
|
+
};
|
|
1275
|
+
readableStreamDefaultReaderRead(reader, readRequest);
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
function cancel1Algorithm(reason: unknown) {
|
|
1279
|
+
canceled1 = true;
|
|
1280
|
+
reason1 = reason;
|
|
1281
|
+
if (canceled2) {
|
|
1282
|
+
const compositeReason = [reason1, reason2];
|
|
1283
|
+
cancelPromise.resolve(readableStreamCancel(stream, compositeReason));
|
|
1284
|
+
}
|
|
1285
|
+
return cancelPromise.promise;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
function cancel2Algorithm(reason: unknown) {
|
|
1289
|
+
canceled2 = true;
|
|
1290
|
+
reason2 = reason;
|
|
1291
|
+
if (canceled1) {
|
|
1292
|
+
const compositeReason = [reason1, reason2];
|
|
1293
|
+
cancelPromise.resolve(readableStreamCancel(stream, compositeReason));
|
|
1294
|
+
}
|
|
1295
|
+
return cancelPromise.promise;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
branch1 = createReadableStream(nonOpStart, pullAlgorithm, cancel1Algorithm);
|
|
1299
|
+
branch2 = createReadableStream(nonOpStart, pullAlgorithm, cancel2Algorithm);
|
|
1300
|
+
|
|
1301
|
+
reader[kState].close.promise.then(
|
|
1302
|
+
undefined,
|
|
1303
|
+
(error: unknown) => {
|
|
1304
|
+
readableStreamDefaultControllerError(branch1[kState].controller, error);
|
|
1305
|
+
readableStreamDefaultControllerError(branch2[kState].controller, error);
|
|
1306
|
+
if (!canceled1 || !canceled2)
|
|
1307
|
+
cancelPromise.resolve(undefined);
|
|
1308
|
+
},
|
|
1309
|
+
);
|
|
1310
|
+
|
|
1311
|
+
return [branch1, branch2];
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
// ---- Exports ----
|
|
1315
|
+
|
|
1316
|
+
export {
|
|
1317
|
+
ReadableStream,
|
|
1318
|
+
ReadableStreamDefaultReader,
|
|
1319
|
+
ReadableStreamDefaultController,
|
|
1320
|
+
|
|
1321
|
+
isReadableStream,
|
|
1322
|
+
isReadableStreamLocked,
|
|
1323
|
+
isReadableStreamDefaultReader,
|
|
1324
|
+
isReadableStreamDefaultController,
|
|
1325
|
+
|
|
1326
|
+
readableStreamCancel,
|
|
1327
|
+
readableStreamClose,
|
|
1328
|
+
readableStreamError,
|
|
1329
|
+
readableStreamDefaultControllerClose,
|
|
1330
|
+
readableStreamDefaultControllerEnqueue,
|
|
1331
|
+
readableStreamDefaultControllerError,
|
|
1332
|
+
readableStreamDefaultControllerGetDesiredSize,
|
|
1333
|
+
readableStreamDefaultControllerCanCloseOrEnqueue,
|
|
1334
|
+
readableStreamDefaultControllerHasBackpressure,
|
|
1335
|
+
setupReadableStreamDefaultController,
|
|
1336
|
+
createReadableStream,
|
|
1337
|
+
};
|