@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
package/src/test.mts
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// WHATWG TextDecoderStream for GJS
|
|
2
|
+
// Reference: refs/deno/ext/web/08_text_encoding.js (lines 299–412)
|
|
3
|
+
// Copyright (c) 2018-2026 the Deno authors. MIT license.
|
|
4
|
+
// Reimplemented for GJS — pure TypeScript, no native bindings.
|
|
5
|
+
//
|
|
6
|
+
// GJS's TextDecoder does not support the `stream` option, so we manually
|
|
7
|
+
// buffer incomplete multi-byte UTF-8 sequences across chunks.
|
|
8
|
+
|
|
9
|
+
import { TransformStream } from './transform-stream.js';
|
|
10
|
+
|
|
11
|
+
/** Check if TextDecoder supports the `stream` option */
|
|
12
|
+
function hasStreamingSupport(): boolean {
|
|
13
|
+
try {
|
|
14
|
+
const td = new TextDecoder();
|
|
15
|
+
td.decode(new Uint8Array([0xC3]), { stream: true });
|
|
16
|
+
return true;
|
|
17
|
+
} catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const supportsStreaming = hasStreamingSupport();
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* For UTF-8 without streaming TextDecoder support:
|
|
26
|
+
* Determine how many bytes at the end of `buf` form an incomplete
|
|
27
|
+
* multi-byte sequence. Returns 0 if the buffer ends on a complete character.
|
|
28
|
+
*/
|
|
29
|
+
function incompleteUtf8Tail(buf: Uint8Array): number {
|
|
30
|
+
const len = buf.length;
|
|
31
|
+
if (len === 0) return 0;
|
|
32
|
+
|
|
33
|
+
// Check last byte: is it a continuation byte or a start byte?
|
|
34
|
+
// Start bytes:
|
|
35
|
+
// 0xxxxxxx (0x00-0x7F) — single byte, complete
|
|
36
|
+
// 110xxxxx (0xC0-0xDF) — starts 2-byte sequence
|
|
37
|
+
// 1110xxxx (0xE0-0xEF) — starts 3-byte sequence
|
|
38
|
+
// 11110xxx (0xF0-0xF7) — starts 4-byte sequence
|
|
39
|
+
// Continuation: 10xxxxxx (0x80-0xBF)
|
|
40
|
+
|
|
41
|
+
// Scan back up to 3 bytes to find the start of the last multi-byte sequence
|
|
42
|
+
for (let i = 1; i <= Math.min(3, len); i++) {
|
|
43
|
+
const b = buf[len - i];
|
|
44
|
+
if ((b & 0x80) === 0) {
|
|
45
|
+
// ASCII byte — everything before is complete, this byte is complete
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
if ((b & 0xC0) !== 0x80) {
|
|
49
|
+
// This is a start byte
|
|
50
|
+
let expectedLen: number;
|
|
51
|
+
if ((b & 0xE0) === 0xC0) expectedLen = 2;
|
|
52
|
+
else if ((b & 0xF0) === 0xE0) expectedLen = 3;
|
|
53
|
+
else if ((b & 0xF8) === 0xF0) expectedLen = 4;
|
|
54
|
+
else return 0; // Invalid start byte, treat as complete
|
|
55
|
+
|
|
56
|
+
if (i < expectedLen) {
|
|
57
|
+
// We have fewer bytes than the sequence needs — incomplete
|
|
58
|
+
return i;
|
|
59
|
+
}
|
|
60
|
+
// Sequence is complete
|
|
61
|
+
return 0;
|
|
62
|
+
}
|
|
63
|
+
// Continuation byte — keep scanning back
|
|
64
|
+
}
|
|
65
|
+
// If we scanned back 3 continuation bytes without finding a start byte,
|
|
66
|
+
// something is wrong; treat as complete to avoid infinite buffering
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* TextDecoderStream decodes a stream of bytes into strings.
|
|
72
|
+
*
|
|
73
|
+
* Uses TextDecoder with `stream: true` when available (Node.js, browsers).
|
|
74
|
+
* On GJS where `stream` option is not supported, manually buffers incomplete
|
|
75
|
+
* multi-byte UTF-8 sequences across chunks.
|
|
76
|
+
*/
|
|
77
|
+
export class TextDecoderStream {
|
|
78
|
+
#decoder: TextDecoder;
|
|
79
|
+
#transform: TransformStream;
|
|
80
|
+
|
|
81
|
+
constructor(label?: string, options?: TextDecoderOptions) {
|
|
82
|
+
this.#decoder = new TextDecoder(label, options);
|
|
83
|
+
|
|
84
|
+
if (supportsStreaming) {
|
|
85
|
+
// Native streaming support (Node.js, modern browsers)
|
|
86
|
+
this.#transform = new TransformStream({
|
|
87
|
+
transform: (chunk, controller) => {
|
|
88
|
+
const bytes = toUint8Array(chunk);
|
|
89
|
+
const decoded = this.#decoder.decode(bytes, { stream: true });
|
|
90
|
+
if (decoded) {
|
|
91
|
+
controller.enqueue(decoded);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
flush: (controller) => {
|
|
95
|
+
const final = this.#decoder.decode();
|
|
96
|
+
if (final) {
|
|
97
|
+
controller.enqueue(final);
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
cancel: () => {
|
|
101
|
+
this.#decoder.decode();
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
} else {
|
|
105
|
+
// GJS fallback: manually buffer incomplete UTF-8 sequences
|
|
106
|
+
let pendingBytes = new Uint8Array(0);
|
|
107
|
+
|
|
108
|
+
this.#transform = new TransformStream({
|
|
109
|
+
transform: (chunk, controller) => {
|
|
110
|
+
const incoming = toUint8Array(chunk);
|
|
111
|
+
|
|
112
|
+
// Merge pending bytes with new data
|
|
113
|
+
let combined: Uint8Array;
|
|
114
|
+
if (pendingBytes.length > 0) {
|
|
115
|
+
combined = new Uint8Array(pendingBytes.length + incoming.length);
|
|
116
|
+
combined.set(pendingBytes, 0);
|
|
117
|
+
combined.set(incoming, pendingBytes.length);
|
|
118
|
+
} else {
|
|
119
|
+
combined = incoming;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Check for incomplete multi-byte sequence at the end
|
|
123
|
+
const tail = incompleteUtf8Tail(combined);
|
|
124
|
+
let decodable: Uint8Array;
|
|
125
|
+
if (tail > 0) {
|
|
126
|
+
decodable = combined.slice(0, combined.length - tail);
|
|
127
|
+
pendingBytes = combined.slice(combined.length - tail);
|
|
128
|
+
} else {
|
|
129
|
+
decodable = combined;
|
|
130
|
+
pendingBytes = new Uint8Array(0);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (decodable.length > 0) {
|
|
134
|
+
const decoded = this.#decoder.decode(decodable);
|
|
135
|
+
if (decoded) {
|
|
136
|
+
controller.enqueue(decoded);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
flush: (controller) => {
|
|
141
|
+
if (pendingBytes.length > 0) {
|
|
142
|
+
// Decode remaining bytes (may produce replacement characters)
|
|
143
|
+
const decoded = this.#decoder.decode(pendingBytes);
|
|
144
|
+
if (decoded) {
|
|
145
|
+
controller.enqueue(decoded);
|
|
146
|
+
}
|
|
147
|
+
pendingBytes = new Uint8Array(0);
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
cancel: () => {
|
|
151
|
+
pendingBytes = new Uint8Array(0);
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
get encoding(): string {
|
|
158
|
+
return this.#decoder.encoding;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
get fatal(): boolean {
|
|
162
|
+
return this.#decoder.fatal;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
get ignoreBOM(): boolean {
|
|
166
|
+
return this.#decoder.ignoreBOM;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
get readable(): ReadableStream<string> {
|
|
170
|
+
return this.#transform.readable;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
get writable(): WritableStream<BufferSource> {
|
|
174
|
+
return this.#transform.writable;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function toUint8Array(chunk: BufferSource): Uint8Array {
|
|
179
|
+
if (chunk instanceof Uint8Array) return chunk;
|
|
180
|
+
if (chunk instanceof ArrayBuffer) return new Uint8Array(chunk);
|
|
181
|
+
if (ArrayBuffer.isView(chunk)) return new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
|
|
182
|
+
throw new TypeError('chunk must be a BufferSource');
|
|
183
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// WHATWG TextEncoderStream for GJS
|
|
2
|
+
// Reference: refs/deno/ext/web/08_text_encoding.js (lines 414–490)
|
|
3
|
+
// Copyright (c) 2018-2026 the Deno authors. MIT license.
|
|
4
|
+
// Reimplemented for GJS — pure TypeScript, no native bindings.
|
|
5
|
+
|
|
6
|
+
import { TransformStream } from './transform-stream.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* TextEncoderStream encodes a stream of strings into UTF-8 encoded bytes.
|
|
10
|
+
*
|
|
11
|
+
* Handles surrogate pairs split across chunks: if the last code unit of a
|
|
12
|
+
* chunk is a high surrogate (0xD800–0xDBFF), it is held until the next chunk.
|
|
13
|
+
* If the stream ends with a pending high surrogate, U+FFFD replacement bytes
|
|
14
|
+
* are emitted.
|
|
15
|
+
*/
|
|
16
|
+
export class TextEncoderStream {
|
|
17
|
+
#pendingHighSurrogate: string | null = null;
|
|
18
|
+
#encoder = new TextEncoder();
|
|
19
|
+
#transform: TransformStream;
|
|
20
|
+
|
|
21
|
+
constructor() {
|
|
22
|
+
this.#transform = new TransformStream({
|
|
23
|
+
transform: (chunk: string, controller) => {
|
|
24
|
+
chunk = String(chunk);
|
|
25
|
+
if (chunk === '') {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (this.#pendingHighSurrogate !== null) {
|
|
29
|
+
chunk = this.#pendingHighSurrogate + chunk;
|
|
30
|
+
}
|
|
31
|
+
const lastCodeUnit = chunk.charCodeAt(chunk.length - 1);
|
|
32
|
+
if (0xD800 <= lastCodeUnit && lastCodeUnit <= 0xDBFF) {
|
|
33
|
+
this.#pendingHighSurrogate = chunk.slice(-1);
|
|
34
|
+
chunk = chunk.slice(0, -1);
|
|
35
|
+
} else {
|
|
36
|
+
this.#pendingHighSurrogate = null;
|
|
37
|
+
}
|
|
38
|
+
if (chunk) {
|
|
39
|
+
controller.enqueue(this.#encoder.encode(chunk));
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
flush: (controller) => {
|
|
43
|
+
if (this.#pendingHighSurrogate !== null) {
|
|
44
|
+
// U+FFFD replacement character in UTF-8
|
|
45
|
+
controller.enqueue(new Uint8Array([0xEF, 0xBF, 0xBD]));
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get encoding(): string {
|
|
52
|
+
return 'utf-8';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get readable(): ReadableStream<Uint8Array> {
|
|
56
|
+
return this.#transform.readable;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
get writable(): WritableStream<string> {
|
|
60
|
+
return this.#transform.writable;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
// WHATWG Streams — TransformStream
|
|
2
|
+
// Adapted from refs/node/lib/internal/webstreams/transformstream.js
|
|
3
|
+
// Copyright (c) Node.js contributors. MIT license.
|
|
4
|
+
// Modifications: Removed primordials, transfer, inspect, Node.js error codes
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
kState, kType,
|
|
8
|
+
isBrandCheck,
|
|
9
|
+
createPromiseCallback,
|
|
10
|
+
extractHighWaterMark,
|
|
11
|
+
extractSizeAlgorithm,
|
|
12
|
+
nonOpFlush,
|
|
13
|
+
nonOpCancel,
|
|
14
|
+
} from './util.js';
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
createReadableStream,
|
|
18
|
+
readableStreamDefaultControllerCanCloseOrEnqueue,
|
|
19
|
+
readableStreamDefaultControllerClose,
|
|
20
|
+
readableStreamDefaultControllerEnqueue,
|
|
21
|
+
readableStreamDefaultControllerError,
|
|
22
|
+
readableStreamDefaultControllerGetDesiredSize,
|
|
23
|
+
readableStreamDefaultControllerHasBackpressure,
|
|
24
|
+
} from './readable-stream.js';
|
|
25
|
+
|
|
26
|
+
import {
|
|
27
|
+
createWritableStream,
|
|
28
|
+
writableStreamDefaultControllerErrorIfNeeded,
|
|
29
|
+
} from './writable-stream.js';
|
|
30
|
+
|
|
31
|
+
const kSkipThrow = Symbol('kSkipThrow');
|
|
32
|
+
|
|
33
|
+
// ---- TransformStream ----
|
|
34
|
+
|
|
35
|
+
export class TransformStream {
|
|
36
|
+
[kType] = 'TransformStream';
|
|
37
|
+
[kState]: any;
|
|
38
|
+
|
|
39
|
+
constructor(
|
|
40
|
+
transformer: any = {},
|
|
41
|
+
writableStrategy: any = {},
|
|
42
|
+
readableStrategy: any = {},
|
|
43
|
+
) {
|
|
44
|
+
if (transformer != null && typeof transformer !== 'object') {
|
|
45
|
+
throw new TypeError('transformer must be an object');
|
|
46
|
+
}
|
|
47
|
+
if (writableStrategy != null && typeof writableStrategy !== 'object') {
|
|
48
|
+
throw new TypeError('writableStrategy must be an object');
|
|
49
|
+
}
|
|
50
|
+
if (readableStrategy != null && typeof readableStrategy !== 'object') {
|
|
51
|
+
throw new TypeError('readableStrategy must be an object');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const readableType = transformer?.readableType;
|
|
55
|
+
const writableType = transformer?.writableType;
|
|
56
|
+
const start = transformer?.start;
|
|
57
|
+
|
|
58
|
+
if (readableType !== undefined) {
|
|
59
|
+
throw new RangeError(`Invalid readableType: ${readableType}`);
|
|
60
|
+
}
|
|
61
|
+
if (writableType !== undefined) {
|
|
62
|
+
throw new RangeError(`Invalid writableType: ${writableType}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const readableHighWaterMark = extractHighWaterMark(readableStrategy?.highWaterMark, 0);
|
|
66
|
+
const readableSize = extractSizeAlgorithm(readableStrategy?.size);
|
|
67
|
+
const writableHighWaterMark = extractHighWaterMark(writableStrategy?.highWaterMark, 1);
|
|
68
|
+
const writableSize = extractSizeAlgorithm(writableStrategy?.size);
|
|
69
|
+
|
|
70
|
+
const startPromise = Promise.withResolvers<void>();
|
|
71
|
+
|
|
72
|
+
initializeTransformStream(
|
|
73
|
+
this,
|
|
74
|
+
startPromise,
|
|
75
|
+
writableHighWaterMark,
|
|
76
|
+
writableSize,
|
|
77
|
+
readableHighWaterMark,
|
|
78
|
+
readableSize,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
setupTransformStreamDefaultControllerFromTransformer(this, transformer);
|
|
82
|
+
|
|
83
|
+
if (start !== undefined) {
|
|
84
|
+
startPromise.resolve(start.call(transformer, this[kState].controller) as unknown as void);
|
|
85
|
+
} else {
|
|
86
|
+
startPromise.resolve();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
get readable(): any {
|
|
91
|
+
if (!isTransformStream(this)) throw new TypeError('Invalid this');
|
|
92
|
+
return this[kState].readable;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
get writable(): any {
|
|
96
|
+
if (!isTransformStream(this)) throw new TypeError('Invalid this');
|
|
97
|
+
return this[kState].writable;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
get [Symbol.toStringTag]() {
|
|
101
|
+
return 'TransformStream';
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ---- TransformStreamDefaultController ----
|
|
106
|
+
|
|
107
|
+
export class TransformStreamDefaultController {
|
|
108
|
+
[kType] = 'TransformStreamDefaultController';
|
|
109
|
+
[kState]: any;
|
|
110
|
+
|
|
111
|
+
constructor(skipThrowSymbol?: symbol) {
|
|
112
|
+
if (skipThrowSymbol !== kSkipThrow) {
|
|
113
|
+
throw new TypeError('Illegal constructor');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
get desiredSize(): number | null {
|
|
118
|
+
if (!isTransformStreamDefaultController(this)) throw new TypeError('Invalid this');
|
|
119
|
+
const { stream } = this[kState];
|
|
120
|
+
const { readable } = stream[kState];
|
|
121
|
+
const readableController = readable[kState].controller;
|
|
122
|
+
return readableStreamDefaultControllerGetDesiredSize(readableController);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
enqueue(chunk?: any): void {
|
|
126
|
+
if (!isTransformStreamDefaultController(this)) throw new TypeError('Invalid this');
|
|
127
|
+
transformStreamDefaultControllerEnqueue(this, chunk);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
error(reason?: unknown): void {
|
|
131
|
+
if (!isTransformStreamDefaultController(this)) throw new TypeError('Invalid this');
|
|
132
|
+
transformStreamDefaultControllerError(this, reason);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
terminate(): void {
|
|
136
|
+
if (!isTransformStreamDefaultController(this)) throw new TypeError('Invalid this');
|
|
137
|
+
transformStreamDefaultControllerTerminate(this);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
get [Symbol.toStringTag]() {
|
|
141
|
+
return 'TransformStreamDefaultController';
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ---- Brand checks ----
|
|
146
|
+
|
|
147
|
+
export const isTransformStream = isBrandCheck('TransformStream');
|
|
148
|
+
export const isTransformStreamDefaultController = isBrandCheck('TransformStreamDefaultController');
|
|
149
|
+
|
|
150
|
+
// ---- Internal functions ----
|
|
151
|
+
|
|
152
|
+
async function defaultTransformAlgorithm(chunk: unknown, controller: any) {
|
|
153
|
+
transformStreamDefaultControllerEnqueue(controller, chunk);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function initializeTransformStream(
|
|
157
|
+
stream: any,
|
|
158
|
+
startPromise: any,
|
|
159
|
+
writableHighWaterMark: number,
|
|
160
|
+
writableSizeAlgorithm: (chunk: any) => number,
|
|
161
|
+
readableHighWaterMark: number,
|
|
162
|
+
readableSizeAlgorithm: (chunk: any) => number,
|
|
163
|
+
): void {
|
|
164
|
+
const startAlgorithm = () => startPromise.promise;
|
|
165
|
+
|
|
166
|
+
const writable = createWritableStream(
|
|
167
|
+
startAlgorithm,
|
|
168
|
+
(chunk: unknown) => transformStreamDefaultSinkWriteAlgorithm(stream, chunk),
|
|
169
|
+
() => transformStreamDefaultSinkCloseAlgorithm(stream),
|
|
170
|
+
(reason: unknown) => transformStreamDefaultSinkAbortAlgorithm(stream, reason),
|
|
171
|
+
writableHighWaterMark,
|
|
172
|
+
writableSizeAlgorithm,
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
const readable = createReadableStream(
|
|
176
|
+
startAlgorithm,
|
|
177
|
+
() => transformStreamDefaultSourcePullAlgorithm(stream),
|
|
178
|
+
(reason: unknown) => transformStreamDefaultSourceCancelAlgorithm(stream, reason),
|
|
179
|
+
readableHighWaterMark,
|
|
180
|
+
readableSizeAlgorithm,
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
stream[kState] = {
|
|
184
|
+
readable,
|
|
185
|
+
writable,
|
|
186
|
+
controller: undefined,
|
|
187
|
+
backpressure: undefined as boolean | undefined,
|
|
188
|
+
backpressureChange: {
|
|
189
|
+
promise: undefined as Promise<void> | undefined,
|
|
190
|
+
resolve: undefined as ((value: void | PromiseLike<void>) => void) | undefined,
|
|
191
|
+
reject: undefined as ((reason?: unknown) => void) | undefined,
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
transformStreamSetBackpressure(stream, true);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function transformStreamError(stream: any, error: unknown): void {
|
|
199
|
+
const { readable } = stream[kState];
|
|
200
|
+
readableStreamDefaultControllerError(readable[kState].controller, error);
|
|
201
|
+
transformStreamErrorWritableAndUnblockWrite(stream, error);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function transformStreamErrorWritableAndUnblockWrite(stream: any, error: unknown): void {
|
|
205
|
+
const { controller, writable } = stream[kState];
|
|
206
|
+
transformStreamDefaultControllerClearAlgorithms(controller);
|
|
207
|
+
writableStreamDefaultControllerErrorIfNeeded(writable[kState].controller, error);
|
|
208
|
+
transformStreamUnblockWrite(stream);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function transformStreamUnblockWrite(stream: any): void {
|
|
212
|
+
if (stream[kState].backpressure) {
|
|
213
|
+
transformStreamSetBackpressure(stream, false);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function transformStreamSetBackpressure(stream: any, backpressure: boolean): void {
|
|
218
|
+
if (stream[kState].backpressureChange.promise !== undefined) {
|
|
219
|
+
stream[kState].backpressureChange.resolve?.();
|
|
220
|
+
}
|
|
221
|
+
stream[kState].backpressureChange = Promise.withResolvers<void>();
|
|
222
|
+
stream[kState].backpressure = backpressure;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function setupTransformStreamDefaultController(
|
|
226
|
+
stream: any,
|
|
227
|
+
controller: any,
|
|
228
|
+
transformAlgorithm: Function,
|
|
229
|
+
flushAlgorithm: Function,
|
|
230
|
+
cancelAlgorithm: Function,
|
|
231
|
+
): void {
|
|
232
|
+
controller[kState] = {
|
|
233
|
+
stream,
|
|
234
|
+
transformAlgorithm,
|
|
235
|
+
flushAlgorithm,
|
|
236
|
+
cancelAlgorithm,
|
|
237
|
+
finishPromise: undefined as Promise<void> | undefined,
|
|
238
|
+
};
|
|
239
|
+
stream[kState].controller = controller;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function setupTransformStreamDefaultControllerFromTransformer(
|
|
243
|
+
stream: any,
|
|
244
|
+
transformer: any,
|
|
245
|
+
): void {
|
|
246
|
+
const controller = new TransformStreamDefaultController(kSkipThrow);
|
|
247
|
+
const transform = transformer?.transform;
|
|
248
|
+
const flush = transformer?.flush;
|
|
249
|
+
const cancel = transformer?.cancel;
|
|
250
|
+
const transformAlgorithm = transform
|
|
251
|
+
? createPromiseCallback('transformer.transform', transform, transformer)
|
|
252
|
+
: defaultTransformAlgorithm;
|
|
253
|
+
const flushAlgorithm = flush
|
|
254
|
+
? createPromiseCallback('transformer.flush', flush, transformer)
|
|
255
|
+
: nonOpFlush;
|
|
256
|
+
const cancelAlgorithm = cancel
|
|
257
|
+
? createPromiseCallback('transformer.cancel', cancel, transformer)
|
|
258
|
+
: nonOpCancel;
|
|
259
|
+
|
|
260
|
+
setupTransformStreamDefaultController(
|
|
261
|
+
stream, controller, transformAlgorithm, flushAlgorithm, cancelAlgorithm,
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function transformStreamDefaultControllerClearAlgorithms(controller: any): void {
|
|
266
|
+
controller[kState].transformAlgorithm = undefined;
|
|
267
|
+
controller[kState].flushAlgorithm = undefined;
|
|
268
|
+
controller[kState].cancelAlgorithm = undefined;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function transformStreamDefaultControllerEnqueue(controller: any, chunk: any): void {
|
|
272
|
+
const { stream } = controller[kState];
|
|
273
|
+
const { readable } = stream[kState];
|
|
274
|
+
const readableController = readable[kState].controller;
|
|
275
|
+
if (!readableStreamDefaultControllerCanCloseOrEnqueue(readableController)) {
|
|
276
|
+
throw new TypeError('Unable to enqueue');
|
|
277
|
+
}
|
|
278
|
+
try {
|
|
279
|
+
readableStreamDefaultControllerEnqueue(readableController, chunk);
|
|
280
|
+
} catch (error: unknown) {
|
|
281
|
+
transformStreamErrorWritableAndUnblockWrite(stream, error);
|
|
282
|
+
throw readable[kState].storedError;
|
|
283
|
+
}
|
|
284
|
+
const backpressure = readableStreamDefaultControllerHasBackpressure(readableController);
|
|
285
|
+
if (backpressure !== stream[kState].backpressure) {
|
|
286
|
+
transformStreamSetBackpressure(stream, true);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function transformStreamDefaultControllerError(controller: any, error: unknown): void {
|
|
291
|
+
transformStreamError(controller[kState].stream, error);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async function transformStreamDefaultControllerPerformTransform(controller: any, chunk: any): Promise<void> {
|
|
295
|
+
try {
|
|
296
|
+
const transformAlgorithm = controller[kState].transformAlgorithm;
|
|
297
|
+
if (transformAlgorithm === undefined) return;
|
|
298
|
+
return await transformAlgorithm(chunk, controller);
|
|
299
|
+
} catch (error) {
|
|
300
|
+
transformStreamError(controller[kState].stream, error);
|
|
301
|
+
throw error;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function transformStreamDefaultControllerTerminate(controller: any): void {
|
|
306
|
+
const { stream } = controller[kState];
|
|
307
|
+
const { readable } = stream[kState];
|
|
308
|
+
readableStreamDefaultControllerClose(readable[kState].controller);
|
|
309
|
+
transformStreamErrorWritableAndUnblockWrite(stream, new TypeError('TransformStream has been terminated'));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function transformStreamDefaultSinkWriteAlgorithm(stream: any, chunk: unknown): Promise<void> {
|
|
313
|
+
const { controller } = stream[kState];
|
|
314
|
+
if (stream[kState].backpressure) {
|
|
315
|
+
const backpressureChange = stream[kState].backpressureChange.promise;
|
|
316
|
+
return backpressureChange.then(() => {
|
|
317
|
+
const { writable } = stream[kState];
|
|
318
|
+
if (writable[kState].state === 'erroring') {
|
|
319
|
+
throw writable[kState].storedError;
|
|
320
|
+
}
|
|
321
|
+
return transformStreamDefaultControllerPerformTransform(controller, chunk);
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
return transformStreamDefaultControllerPerformTransform(controller, chunk);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
async function transformStreamDefaultSinkAbortAlgorithm(stream: any, reason: unknown): Promise<void> {
|
|
328
|
+
const { controller, readable } = stream[kState];
|
|
329
|
+
if (controller[kState].finishPromise !== undefined) {
|
|
330
|
+
return controller[kState].finishPromise;
|
|
331
|
+
}
|
|
332
|
+
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
|
333
|
+
controller[kState].finishPromise = promise;
|
|
334
|
+
const cancelPromise = controller[kState].cancelAlgorithm(reason);
|
|
335
|
+
transformStreamDefaultControllerClearAlgorithms(controller);
|
|
336
|
+
cancelPromise.then(
|
|
337
|
+
() => {
|
|
338
|
+
if (readable[kState].state === 'errored') {
|
|
339
|
+
reject(readable[kState].storedError);
|
|
340
|
+
} else {
|
|
341
|
+
readableStreamDefaultControllerError(readable[kState].controller, reason);
|
|
342
|
+
resolve();
|
|
343
|
+
}
|
|
344
|
+
},
|
|
345
|
+
(error: unknown) => {
|
|
346
|
+
readableStreamDefaultControllerError(readable[kState].controller, error);
|
|
347
|
+
reject(error);
|
|
348
|
+
},
|
|
349
|
+
);
|
|
350
|
+
return controller[kState].finishPromise;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function transformStreamDefaultSinkCloseAlgorithm(stream: any): Promise<void> {
|
|
354
|
+
const { readable, controller } = stream[kState];
|
|
355
|
+
if (controller[kState].finishPromise !== undefined) {
|
|
356
|
+
return controller[kState].finishPromise;
|
|
357
|
+
}
|
|
358
|
+
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
|
359
|
+
controller[kState].finishPromise = promise;
|
|
360
|
+
const flushPromise = controller[kState].flushAlgorithm(controller);
|
|
361
|
+
transformStreamDefaultControllerClearAlgorithms(controller);
|
|
362
|
+
flushPromise.then(
|
|
363
|
+
() => {
|
|
364
|
+
if (readable[kState].state === 'errored') {
|
|
365
|
+
reject(readable[kState].storedError);
|
|
366
|
+
} else {
|
|
367
|
+
readableStreamDefaultControllerClose(readable[kState].controller);
|
|
368
|
+
resolve();
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
(error: unknown) => {
|
|
372
|
+
readableStreamDefaultControllerError(readable[kState].controller, error);
|
|
373
|
+
reject(error);
|
|
374
|
+
},
|
|
375
|
+
);
|
|
376
|
+
return controller[kState].finishPromise;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function transformStreamDefaultSourcePullAlgorithm(stream: any): Promise<void> {
|
|
380
|
+
transformStreamSetBackpressure(stream, false);
|
|
381
|
+
return stream[kState].backpressureChange.promise;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function transformStreamDefaultSourceCancelAlgorithm(stream: any, reason: unknown): Promise<void> {
|
|
385
|
+
const { controller, writable } = stream[kState];
|
|
386
|
+
if (controller[kState].finishPromise !== undefined) {
|
|
387
|
+
return controller[kState].finishPromise;
|
|
388
|
+
}
|
|
389
|
+
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
|
390
|
+
controller[kState].finishPromise = promise;
|
|
391
|
+
const cancelPromise = controller[kState].cancelAlgorithm(reason);
|
|
392
|
+
transformStreamDefaultControllerClearAlgorithms(controller);
|
|
393
|
+
cancelPromise.then(
|
|
394
|
+
() => {
|
|
395
|
+
if (writable[kState].state === 'errored') {
|
|
396
|
+
reject(writable[kState].storedError);
|
|
397
|
+
} else {
|
|
398
|
+
writableStreamDefaultControllerErrorIfNeeded(writable[kState].controller, reason);
|
|
399
|
+
transformStreamUnblockWrite(stream);
|
|
400
|
+
resolve();
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
(error: unknown) => {
|
|
404
|
+
writableStreamDefaultControllerErrorIfNeeded(writable[kState].controller, error);
|
|
405
|
+
transformStreamUnblockWrite(stream);
|
|
406
|
+
reject(error);
|
|
407
|
+
},
|
|
408
|
+
);
|
|
409
|
+
return controller[kState].finishPromise;
|
|
410
|
+
}
|