web-streams-shim 1.0.0 → 1.0.2

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.
@@ -0,0 +1,34 @@
1
+ name: Node.js Package
2
+ on:
3
+ release:
4
+ types: [created]
5
+
6
+ permissions:
7
+ contents: read
8
+ id-token: write
9
+
10
+ jobs:
11
+ build:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ - uses: actions/setup-node@v4
16
+ with:
17
+ node-version: 20
18
+ - run: npm install -g npm@latest # Ensure latest npm
19
+ - run: rm -f .npmrc
20
+ - run: npm ci
21
+ - run: npm test
22
+
23
+ publish-npm:
24
+ needs: build
25
+ runs-on: ubuntu-latest
26
+ steps:
27
+ - uses: actions/checkout@v4
28
+ - uses: actions/setup-node@v4
29
+ with:
30
+ node-version: 20
31
+ registry-url: https://registry.npmjs.org/
32
+ - run: npm install -g npm@latest
33
+ - run: npm ci
34
+ - run: npm publish
package/README.md ADDED
@@ -0,0 +1,50 @@
1
+ ## Web Streams Shim Library
2
+
3
+ This README provides a comprehensive overview of the features and architectural strategies used in this Web Streams Shim Library, which is designed to integrate modern streams functionality into legacy JavaScript environments.
4
+
5
+ This library provides essential polyfills and shims to ensure modern Web Streams functionality is available and compliant across environments where native support is missing or incomplete, particularly focusing on `ReadableStream`, `Request`, `Response`, and `Blob` objects.
6
+
7
+ ***
8
+
9
+ ## Key Features and Polyfills
10
+
11
+ The library focuses on extending core browser APIs to meet the latest Web Stream and Fetch specifications.
12
+
13
+ ### 1. ReadableStream Async Iteration and Disposal
14
+
15
+ The library adds **comprehensive support for modern JavaScript iteration and disposal patterns** to `ReadableStream` and its readers.
16
+
17
+ | Target | Method/Property | Description |
18
+ | :--- | :--- | :--- |
19
+ | `ReadableStream.prototype` | `[Symbol.asyncIterator]` | Allows the stream to be directly iterable in `for-await-of` loops. It reuses a reader associated with the stream, managed via a `WeakMap`. |
20
+ | `ReadableStream.prototype` | `values()` | An alias for `[Symbol.asyncIterator]` for explicit iteration. |
21
+ | `ReadableStreamDefaultReader.prototype` | `next()` | **Delegates directly to the reader’s native `read()` method**, fulfilling the async iterator requirement. |
22
+ | `ReadableStreamDefaultReader.prototype` | `return(reason)` | Handles early termination (e.g., `break` or `return` within iteration). It safely calls the internal **`terminate` function** to cancel the stream and release the lock. |
23
+ | `ReadableStreamDefaultReader.prototype` | `throw(reason)` | Handles error injection into the iteration. It calls `terminate` to cancel the stream and release the lock, and logs the error. |
24
+ | `ReadableStreamDefaultReader.prototype` | `[Symbol.asyncDispose]` | **Supports the async disposal pattern (`await using`)**. It safely cleans up resources by calling the internal `terminate` function. |
25
+
26
+ ### 2. Stream Construction Utility
27
+
28
+ The library adds the static method for creating streams from existing data sources.
29
+
30
+ | Target | Method | Description |
31
+ | :--- | :--- | :--- |
32
+ | `ReadableStream` | `from(obj)` | **Creates a new `ReadableStream` from any iterable or async iterable object**. It handles both synchronous and asynchronous iterators, including objects that yield `Promise`-like values. |
33
+
34
+ ### 3. Fetch and Body Integration Shims
35
+
36
+ These shims ensure `Request` and `Response` objects (Records) consistently expose their body as a stream and provide the `bytes()` utility.
37
+
38
+ | Target | Method/Property | Description |
39
+ | :--- | :--- | :--- |
40
+ | `Request.prototype` | `body` (Getter) | Polyfills the `body` property to return a **`ReadableStream` representation of the body content**. This is crucial for environments where `fetch` exists but streaming is absent. |
41
+ | `Response.prototype` | `body` (Getter) | Provides the body content as a `ReadableStream`. The implementation clones the original record, converts the body to a `Blob`, gets the blob's stream, and enqueues chunks via a controller. |
42
+ | `Request.prototype`, `Response.prototype`, `Blob.prototype` | `bytes()` | Adds the `bytes()` method, which **asynchronously returns the object's body/content as a `Uint8Array`**. It achieves this by calling the native `arrayBuffer()` and wrapping the result. |
43
+
44
+ ### 4. Duplex Compliance Shim
45
+
46
+ To satisfy modern `fetch` specifications when streaming request bodies, the library ensures compliance for **half-duplex operations**.
47
+
48
+ * **Property Injection:** The `duplex: 'half'` property is added to the prototypes of `Request`, `Response`, `ReadableStream`, and `Blob`.
49
+ * **Constructor Wrapping:** The global `Request` and `Response` constructors are subclassed and **wrapped** to automatically apply the `duplexHalf` utility function to all arguments passed during instantiation.
50
+ * **Fetch Wrapping:** The global `fetch` function is **wrapped** to automatically apply the `duplexHalf` utility function to its arguments before execution, guaranteeing compliance when streams are used in options.
@@ -0,0 +1,350 @@
1
+ /**
2
+
3
+ - Polyfill for ReadableStream async iterator protocol support
4
+ - Adds iterator methods to ReadableStream and ReadableStreamDefaultReader
5
+ - to make them compatible with async iteration (for-await-of loops) and disposal patterns
6
+ */
7
+ (() => {
8
+ // Early return if ReadableStream is not available
9
+ if (typeof ReadableStream === 'undefined') return;
10
+ const Q = fn => {
11
+ try {
12
+ return fn?.()
13
+ } catch {}
14
+ };
15
+ const constructPrototype = newClass => {
16
+ try {
17
+ if (newClass?.prototype) return newClass;
18
+ const constProto = newClass?.constructor?.prototype;
19
+ if (constProto) {
20
+ newClass.prototype = Q(() => constProto?.bind?.(constProto)) ?? Object.create(Object(constProto));
21
+ return newClass;
22
+ }
23
+ newClass.prototype = Q(() => newClass?.bind?.(newClass)) ?? Object.create(Object(newClass));
24
+ } catch (e) {
25
+ console.warn(e, newClass);
26
+ }
27
+ };
28
+ const extend = (thisClass, superClass) => {
29
+ try {
30
+ constructPrototype(thisClass);
31
+ constructPrototype(superClass);
32
+ Object.setPrototypeOf(
33
+ thisClass.prototype,
34
+ superClass?.prototype ??
35
+ superClass?.constructor?.prototype ??
36
+ superClass
37
+ );
38
+ Object.setPrototypeOf(thisClass, superClass);
39
+
40
+ } catch (e) {
41
+ console.warn(e, {
42
+ thisClass,
43
+ superClass
44
+ });
45
+ }
46
+ return thisClass;
47
+ };
48
+ /**
49
+
50
+ - Creates a function that returns a string for all string conversion methods
51
+ - Used to provide consistent string representations for polyfilled functions
52
+ - @param {string} str - The string to return
53
+ - @returns {Function} A function that returns the string for all conversion methods
54
+ - @private
55
+ */
56
+ const makeStringer = str => {
57
+ const stringer = () => str;
58
+ ['valueOf', 'toString', 'toLocaleString', Symbol.toPrimitive].forEach(x => {
59
+ stringer[x] = stringer;
60
+ });
61
+ stringer[Symbol.toStringTag] = str;
62
+ return stringer;
63
+ };
64
+
65
+ /**
66
+
67
+ - Sets string conversion methods on a function to indicate it’s polyfill code
68
+ - Provides consistent debugging experience by showing polyfill status
69
+ - @param {Function} obj - The function to modify
70
+ - @param {string} name - The function name (currently unused but kept for future use)
71
+ - @returns {Function} The modified function
72
+ - @private
73
+ */
74
+ const setStrings = (obj, name) => {
75
+ for (const str of ['toString', 'toLocaleString', Symbol.toStringTag]) {
76
+ Object.defineProperty(obj, str, {
77
+ value: makeStringer(`function ${obj.name}() { [polyfill code] }`),
78
+ configurable: true,
79
+ writable: true,
80
+ enumerable: false,
81
+ });
82
+ }
83
+ return obj;
84
+ };
85
+
86
+
87
+
88
+ /**
89
+
90
+ - Safely executes an async function and catches any errors
91
+ - @param {Function} fn - Async function to execute
92
+ - @returns {Promise<*>} The result of fn() or undefined if an error occurred
93
+ */
94
+ const asyncQ = async (fn) => {
95
+ try {
96
+ return await fn?.();
97
+ } catch {}
98
+ };
99
+
100
+ /**
101
+
102
+ - Terminates a stream/reader by calling all cleanup methods
103
+ - Attempts cancel, close, and releaseLock operations safely
104
+ - @param {ReadableStream|ReadableStreamDefaultReader} x - The stream or reader to terminate
105
+ - @param {*} reason - Optional reason for termination
106
+ - @returns {Promise} Promise that resolves to the closed state or undefined
107
+ */
108
+ const terminate = async (x, reason) => {
109
+ await asyncQ(async () => x.cancel(reason));
110
+ await asyncQ(async () => x.close(reason));
111
+ await asyncQ(async () => x.releaseLock(reason));
112
+ return await asyncQ(async () => x.closed);
113
+ };
114
+
115
+ /**
116
+
117
+ - Add next() method to ReadableStreamDefaultReader
118
+ - Makes readers compatible with async iterator protocol
119
+ */
120
+ (() => {
121
+ /**
122
+ - Iterator next() method for ReadableStreamDefaultReader
123
+ - Delegates to the reader’s read() method for async iterator compatibility
124
+ - @returns {Promise<{done: boolean, value: any}>} Iterator result object
125
+ - @note Sets the read method as prototype for better runtime type traceability
126
+ - @example
127
+ - const reader = stream.getReader();
128
+ - const { done, value } = await reader.next(); // Same as reader.read()
129
+ */
130
+ ReadableStreamDefaultReader.prototype.next ?? Object.defineProperty(ReadableStreamDefaultReader.prototype, 'next', {
131
+ value: extend(setStrings(function next() {
132
+ return this.read();
133
+ }), ReadableStreamDefaultReader.prototype.read),
134
+ configurable: true,
135
+ writable: true,
136
+ enumerable: false,
137
+ });
138
+ })();
139
+
140
+ /**
141
+
142
+ - Add Symbol.asyncIterator to ReadableStreamDefaultReader
143
+ - Makes readers directly iterable with for-await-of loops
144
+ */
145
+ (() => {
146
+ /**
147
+ - Async iterator method for ReadableStreamDefaultReader
148
+ - Returns the reader itself since it implements the async iterator protocol
149
+ - @returns {ReadableStreamDefaultReader} The reader itself
150
+ - @note Sets ReadableStreamDefaultReader as prototype for better runtime type traceability
151
+ - @example
152
+ - const reader = stream.getReader();
153
+ - for await (const chunk of reader) {
154
+ - console.log(chunk);
155
+ - }
156
+ */
157
+ ReadableStreamDefaultReader.prototype[Symbol.asyncIterator] ??= extend(setStrings(Object.defineProperty(function asyncIterator() {
158
+ return this;
159
+ }, 'name', {
160
+ value: 'Symbol.asyncIterator',
161
+ configurable: true,
162
+ writable: true,
163
+ enumerable: true,
164
+ })), ReadableStreamDefaultReader);
165
+ })();
166
+
167
+ /**
168
+
169
+ - Iterator completion and disposal methods for ReadableStreamDefaultReader
170
+ - Implements return() and throw() methods for proper async iterator protocol compliance
171
+ */
172
+ (() => {
173
+ /**
174
+ - Internal class representing the end of stream iteration
175
+ - Used to signal completion with optional return value
176
+ - @private
177
+ */
178
+ class StreamEnd {
179
+ /** @type {boolean} Always true to indicate iteration is complete */
180
+ done = true;
181
+ /** @type {*} The return/throw value */
182
+ value;
183
+
184
+ /**
185
+ - @param {*} value - The value to return when iteration ends
186
+ */
187
+ constructor(value) {
188
+ this.value = value;
189
+ }
190
+ }
191
+
192
+
193
+ /**
194
+ * Add return() method to ReadableStreamDefaultReader
195
+ * Handles early termination of async iteration
196
+ */
197
+ (() => {
198
+ /**
199
+ * Iterator return() method for ReadableStreamDefaultReader
200
+ * Called when async iteration is terminated early (break, return, etc.)
201
+ * Safely cancels the stream and releases the reader lock
202
+ * @param {*} reason - Optional reason for termination
203
+ * @returns {StreamEnd} Iterator result indicating completion
204
+ * @note Sets releaseLock as prototype for better runtime type traceability
205
+ * @example
206
+ * for await (const chunk of reader) {
207
+ * if (shouldStop) break; // Calls reader.return() automatically
208
+ * }
209
+ */
210
+ ReadableStreamDefaultReader.prototype['return'] ?? Object.defineProperty(ReadableStreamDefaultReader.prototype, 'return', {
211
+ value: extend(setStrings(Object.defineProperty(function $return(reason) {
212
+ terminate(this, reason);
213
+ return new StreamEnd(reason);
214
+ }, 'name', {
215
+ value: 'return',
216
+ configurable: true,
217
+ writable: true,
218
+ enumerable: true,
219
+ })), ReadableStreamDefaultReader.prototype.releaseLock),
220
+ configurable: true,
221
+ writable: true,
222
+ enumerable: false,
223
+ });
224
+ })();
225
+
226
+ /**
227
+ * Add throw() method to ReadableStreamDefaultReader
228
+ * Handles error injection into async iteration
229
+ */
230
+ (() => {
231
+ /**
232
+ * Iterator throw() method for ReadableStreamDefaultReader
233
+ * Called when an error is injected into async iteration
234
+ * Safely cancels the stream and releases the reader lock
235
+ * @param {*} reason - The error/reason being thrown
236
+ * @returns {StreamEnd} Iterator result indicating completion with error
237
+ * @note Sets controller error method as prototype for better runtime type traceability
238
+ * @example
239
+ * const iterator = reader[Symbol.asyncIterator]();
240
+ * iterator.throw(new Error('Stop processing'));
241
+ */
242
+ ReadableStreamDefaultReader.prototype['throw'] ?? Object.defineProperty(ReadableStreamDefaultReader.prototype, 'throw', {
243
+ value: extend(setStrings(Object.defineProperty(function $throw(reason) {
244
+ terminate(this, reason);
245
+ console.error(reason);
246
+ return new StreamEnd(reason);
247
+ }, 'name', {
248
+ value: 'throw',
249
+ configurable: true,
250
+ writable: true,
251
+ enumerable: true,
252
+ })), ReadableStreamDefaultController.prototype.error),
253
+ configurable: true,
254
+ writable: true,
255
+ enumerable: false,
256
+ });
257
+ })();
258
+
259
+ /**
260
+ * Add Symbol.asyncDispose method to ReadableStreamDefaultReader
261
+ * Supports the async disposal pattern (using/await using statements)
262
+ */
263
+ (() => {
264
+ // Use Symbol.asyncDispose if available, otherwise use string key as fallback
265
+ const $asyncDispose = Symbol.asyncDispose ?? 'Symbol.asyncDispose';
266
+
267
+ /**
268
+ * Async dispose method for ReadableStreamDefaultReader
269
+ * Called automatically when using 'await using' syntax
270
+ * Safely cancels the stream and releases the reader lock
271
+ * @param {*} reason - Optional disposal reason
272
+ * @returns {Promise} Promise that resolves when disposal is complete
273
+ * @note Sets closed property getter as prototype for better runtime type traceability
274
+ * @example
275
+ * await using reader = stream.getReader();
276
+ * // reader is automatically disposed when leaving scope
277
+ *
278
+ * // Or manually:
279
+ * await reader[Symbol.asyncDispose]();
280
+ */
281
+ ReadableStreamDefaultReader.prototype[$asyncDispose] ?? Object.defineProperty(ReadableStreamDefaultReader.prototype, $asyncDispose, {
282
+ value: extend(setStrings(Object.defineProperty(async function asyncDispose(reason) {
283
+ return await terminate(this, reason);
284
+ }, 'name', {
285
+ value: 'Symbol.asyncDispose',
286
+ configurable: true,
287
+ writable: true,
288
+ enumerable: true,
289
+ })), Object.getOwnPropertyDescriptor(ReadableStreamDefaultReader.prototype, 'closed').get),
290
+ configurable: true,
291
+ writable: true,
292
+ enumerable: false,
293
+ });
294
+ })();
295
+
296
+
297
+ })();
298
+
299
+ /**
300
+
301
+ - Add async iterator support to ReadableStream itself
302
+ - Makes streams directly iterable without needing to get a reader first
303
+ */
304
+ (() => {
305
+ // WeakMap to associate readers with streams for reuse
306
+ const $readers = new(globalThis.WeakMap ?? Map);
307
+
308
+
309
+ // Set prototype on getReader method for better type traceability
310
+ extend(ReadableStream.prototype.getReader, ReadableStreamDefaultReader);
311
+
312
+ /**
313
+ * Async iterator method for ReadableStream
314
+ * Returns a reader that can be used with for-await-of loops
315
+ * Reuses the same reader for multiple iteration attempts on the same stream
316
+ * @returns {ReadableStreamDefaultReader} A reader for async iteration
317
+ * @note Sets getReader as prototype for better runtime type traceability
318
+ * @example
319
+ * for await (const chunk of stream) {
320
+ * console.log(chunk);
321
+ * }
322
+ */
323
+ ReadableStream.prototype[Symbol.asyncIterator] ??= extend(setStrings(Object.defineProperty(function asyncIterator() {
324
+ const $reader = $readers.get(this) ?? Q(() => this?.getReader?.());
325
+ $readers.set(this, $reader);
326
+ return $reader;
327
+ }, 'name', {
328
+ value: 'Symbol.asyncIterator',
329
+ configurable: true,
330
+ writable: true,
331
+ enumerable: true,
332
+ })), ReadableStream.prototype.getReader);
333
+
334
+ /**
335
+ * Values method for ReadableStream
336
+ * Alias for Symbol.asyncIterator for explicit iteration
337
+ * @returns {ReadableStreamDefaultReader} A reader for async iteration
338
+ * @note Sets the asyncIterator method as prototype for better runtime type traceability
339
+ * @example
340
+ * for await (const chunk of stream.values()) {
341
+ * console.log(chunk);
342
+ * }
343
+ */
344
+ ReadableStream.prototype.values ??= extend(setStrings(function values() {
345
+ return this[Symbol.asyncIterator]();
346
+ }), ReadableStream.prototype[Symbol.asyncIterator]);
347
+
348
+
349
+ })();
350
+ })();
@@ -0,0 +1,166 @@
1
+ /**
2
+
3
+ - Polyfill for ReadableStream.from() method
4
+ - Creates a ReadableStream from an iterable object (sync or async)
5
+ - @see https://streams.spec.whatwg.org/#rs-from
6
+ */
7
+ (() => {
8
+ // Early return if ReadableStream is not available
9
+ if (typeof ReadableStream === 'undefined') return;
10
+ const Q = fn => {
11
+ try {
12
+ return fn?.()
13
+ } catch {}
14
+ };
15
+ const constructPrototype = newClass => {
16
+ try {
17
+ if (newClass?.prototype) return newClass;
18
+ const constProto = newClass?.constructor?.prototype;
19
+ if (constProto) {
20
+ newClass.prototype = Q(() => constProto?.bind?.(constProto)) ?? Object.create(Object(constProto));
21
+ return newClass;
22
+ }
23
+ newClass.prototype = Q(() => newClass?.bind?.(newClass)) ?? Object.create(Object(newClass));
24
+ } catch (e) {
25
+ console.warn(e, newClass);
26
+ }
27
+ };
28
+ const extend = (thisClass, superClass) => {
29
+ try {
30
+ constructPrototype(thisClass);
31
+ constructPrototype(superClass);
32
+ Object.setPrototypeOf(
33
+ thisClass.prototype,
34
+ superClass?.prototype ??
35
+ superClass?.constructor?.prototype ??
36
+ superClass
37
+ );
38
+ Object.setPrototypeOf(thisClass, superClass);
39
+
40
+ } catch (e) {
41
+ console.warn(e, {
42
+ thisClass,
43
+ superClass
44
+ });
45
+ }
46
+ return thisClass;
47
+ };
48
+ const makeStringer = str => {
49
+ const stringer = () => str;
50
+ ['valueOf', 'toString', 'toLocaleString', Symbol.toPrimitive].forEach(x => {
51
+ stringer[x] = stringer;
52
+ });
53
+ stringer[Symbol.toStringTag] = str;
54
+ return stringer;
55
+ };
56
+ const setStrings = (obj, name) => {
57
+ for (const str of ['toString', 'toLocaleString', Symbol.toStringTag]) {
58
+ Object.defineProperty(obj, str, {
59
+ value: makeStringer(`function ${obj.name}() { [polyfill code] }`),
60
+ configurable: true,
61
+ writable: true,
62
+ enumerable: false,
63
+ });
64
+ }
65
+ return obj;
66
+ };
67
+
68
+ const instanceOf = (x, y) => Q(() => x instanceof y);
69
+ /**
70
+
71
+ - Safely closes a ReadableStream controller
72
+ - @param {ReadableStreamDefaultController} ctrl - The controller to close
73
+ */
74
+ const close = ctrl => Q(() => ctrl.close());
75
+
76
+ /**
77
+
78
+ - Safely cancels a ReadableStream or ReadableStreamReader
79
+ - @param {ReadableStream|ReadableStreamDefaultReader} readable - The stream or reader to cancel
80
+ */
81
+ const cancel = readable => Q(() => readable.cancel());
82
+
83
+ /**
84
+
85
+ - Checks if a value is a Promise-like object
86
+ - @param {*} x - Value to check
87
+ - @returns {boolean} True if the value appears to be a Promise
88
+ */
89
+ const isPromise = x => instanceOf(x, Promise) ||
90
+ instanceOf(Promise.prototype, x?.constructor) ||
91
+ x?.constructor?.name === 'Promise' ||
92
+ typeof x?.then === 'function';
93
+ /**
94
+
95
+ - Creates a ReadableStream from an iterable object
96
+ - Polyfill implementation for ReadableStream.from()
97
+ - @param {Iterable|AsyncIterable} obj - An iterable or async iterable object
98
+ - @returns {ReadableStream} A new ReadableStream that yields values from the iterable
99
+ - @note Sets ReadableStream as the prototype of the from function for better runtime type traceability
100
+ - @example
101
+ - // From array
102
+ - const stream = ReadableStream.from([1, 2, 3]);
103
+ -
104
+ - // From generator
105
+ - function* gen() { yield 1; yield 2; yield 3; }
106
+ - const stream2 = ReadableStream.from(gen());
107
+ -
108
+ - // From async generator
109
+ - async function* asyncGen() { yield Promise.resolve(1); }
110
+ - const stream3 = ReadableStream.from(asyncGen());
111
+ */
112
+ ReadableStream.from ??= extend(setStrings(function from(obj) {
113
+ let $iter, $readableStream;
114
+
115
+
116
+ $readableStream = new ReadableStream({
117
+ /**
118
+ * Pull method for the ReadableStream
119
+ * Retrieves the next value from the iterator and enqueues it
120
+ * @param {ReadableStreamDefaultController} controller - Stream controller
121
+ */
122
+ pull: extend(setStrings(async function pull(controller) {
123
+ try {
124
+ if (isPromise(obj)) {
125
+ obj = await obj;
126
+ }
127
+ // Initialize iterator if not already done
128
+ // Try sync iterator first, then async iterator, then convert to array and get iterator as last resort
129
+ $iter ??= obj?.[Symbol.iterator]?.() ??
130
+ obj?.[Symbol.asyncIterator]?.() ?? [][Symbol.iterator].call(obj);
131
+
132
+ // Get next chunk from iterator
133
+ let chunk = $iter.next();
134
+
135
+ // Await if chunk is a promise
136
+ if (isPromise(chunk)) {
137
+ chunk = await chunk;
138
+ }
139
+
140
+ // If iterator is not done, enqueue the value
141
+ if (chunk?.done === false) {
142
+ let value = chunk?.value;
143
+
144
+ // Await value if it's a promise
145
+ if (isPromise(value)) {
146
+ value = await value;
147
+ }
148
+
149
+ controller.enqueue(value);
150
+ } else {
151
+ // Iterator is done, close the stream
152
+ close(controller);
153
+ }
154
+ } catch (e) {
155
+ // On error, close controller and cancel stream
156
+ close(controller);
157
+ cancel($readableStream);
158
+ throw e;
159
+ }
160
+ }), ReadableStreamDefaultController),
161
+ });
162
+
163
+ return $readableStream;
164
+
165
+ }), ReadableStream);
166
+ })();