web-streams-shim 1.0.0 → 1.0.3
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/.github/workflows/npm_publish.yml +34 -0
- package/README.md +69 -0
- package/ReadableStream-asyncIterator.js +350 -0
- package/ReadableStream-from.js +166 -0
- package/ReadableStreamDefaultReader-constructor.js +109 -0
- package/Record-body.js +274 -0
- package/Record-bytes.js +92 -0
- package/Record-duplex.js +171 -0
- package/extensions/type-extensions.js +17 -0
- package/package.json +17 -12
- package/web-streams-core.js +843 -0
|
@@ -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,69 @@
|
|
|
1
|
+
## Web Streams Shim Library
|
|
2
|
+
|
|
3
|
+
This README provides an overview of the features in this Web Streams Shim Library, which is designed to create parity between the streaming interfaces within modern runtimes.
|
|
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
|
|
14
|
+
|
|
15
|
+
The library adds **comprehensive support for modern JavaScript iteration patterns** to `ReadableStream` and its readers.
|
|
16
|
+
|
|
17
|
+
| Target | Method/Property | Description |
|
|
18
|
+
| :--- | :--- | :--- |
|
|
19
|
+
| [`ReadableStream.prototype`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) | `[Symbol.asyncIterator]` | Allows the stream to be directly iterable in `for-await-of` loops. |
|
|
20
|
+
| [`ReadableStream.prototype`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) | `values()` | An alias for `[Symbol.asyncIterator]` for explicit iteration. |
|
|
21
|
+
|
|
22
|
+

|
|
23
|
+

|
|
24
|
+
|
|
25
|
+
### 2. Stream Construction Utility
|
|
26
|
+
|
|
27
|
+
The library adds the static method for creating streams from existing data sources.
|
|
28
|
+
|
|
29
|
+
| Target | Method/Property | Description |
|
|
30
|
+
| :--- | :--- | :--- |
|
|
31
|
+
| [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) | [`from(obj)`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/from_static) | **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. |
|
|
32
|
+
|
|
33
|
+

|
|
34
|
+
|
|
35
|
+
### 3. Fetch and Body Integration Shims
|
|
36
|
+
|
|
37
|
+
These shims ensure `Request` and `Response` objects (Records) consistently expose their body as a stream and provide the `bytes()` utility.
|
|
38
|
+
|
|
39
|
+
| Target | Method/Property | Description |
|
|
40
|
+
| :--- | :--- | :--- |
|
|
41
|
+
| `Request.prototype` | [`body`](https://developer.mozilla.org/en-US/docs/Web/API/Request/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. |
|
|
42
|
+
| `Response.prototype` | [`body`](https://developer.mozilla.org/en-US/docs/Web/API/Response/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. |
|
|
43
|
+
| [`Request.prototype`](https://developer.mozilla.org/en-US/docs/Web/API/Request/bytes), [`Response.prototype`](https://developer.mozilla.org/en-US/docs/Web/API/Response/bytes), [`Blob.prototype`](https://developer.mozilla.org/en-US/docs/Web/API/Blob/bytes) | `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. |
|
|
44
|
+
|
|
45
|
+

|
|
46
|
+

|
|
47
|
+

|
|
48
|
+

|
|
49
|
+

|
|
50
|
+
|
|
51
|
+
### 4. Duplex Compliance Shim
|
|
52
|
+
|
|
53
|
+
To satisfy modern `fetch` specifications when streaming request bodies, the library ensures compliance for **half-duplex operations**.
|
|
54
|
+
|
|
55
|
+
* **Property Injection:** The `duplex: 'half'` property is added to the prototypes of `Request`, `Response`, `ReadableStream`, and `Blob`.
|
|
56
|
+
* **Constructor Wrapping:** The global `Request` and `Response` constructors are subclassed and **wrapped** to automatically apply `duplex: 'half'` utility function to all arguments passed during instantiation.
|
|
57
|
+
* **Fetch Wrapping:** The global `fetch` function is **wrapped** to automatically apply `duplex: 'half'` to its arguments before execution, guaranteeing compliance when streams are used in options.
|
|
58
|
+
|
|
59
|
+

|
|
60
|
+
|
|
61
|
+
### 5. ReadableStreamDefaultReader Constructor Support
|
|
62
|
+
|
|
63
|
+
The library adds support for the `ReadableStreamDefaultReader` constructor.
|
|
64
|
+
|
|
65
|
+
| Target | Method/Property | Description |
|
|
66
|
+
| :--- | :--- | :--- |
|
|
67
|
+
| [`ReadableStreamDefaultReader`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader) | [`constructor(stream)`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/ReadableStream) | **Polyfills the `ReadableStreamDefaultReader` constructor** to accept a stream directly. In environments where the native constructor doesn't support this (like Bun), it delegates to `stream.getReader()` and properly sets up the prototype chain. This allows `new ReadableStreamDefaultReader(stream)` to work consistently across all runtimes. |
|
|
68
|
+
|
|
69
|
+

|
|
@@ -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
|
+
})();
|