web-streams-shim 1.0.4 → 1.0.5

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 CHANGED
@@ -1,28 +1,38 @@
1
- ## Web Streams Shim Library
1
+ ## Web Streams Shim
2
2
 
3
- The Web Streams Shim Library is designed to create parity between the streaming interfaces within modern runtimes. This does not polyfill the interfaces if they are missing entirely. For that you will want [web-streams-polyfill](https://www.npmjs.com/package/web-streams-polyfill) which is not affiliated with the project.
3
+ 🛶 Web Streams Shim is designed to create parity between the streaming interfaces within modern runtimes. This does not polyfill the interfaces if they are missing entirely. For that you will want [web-streams-polyfill](https://www.npmjs.com/package/web-streams-polyfill) which is not affiliated with the project.
4
4
 
5
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
6
 
7
+ ## Install
8
+
9
+ ```html
10
+ <script src="https://cdn.jsdelivr.net/npm/web-streams-shim@1.0.4/web-streams-core.js"></script>
11
+ ```
12
+
7
13
  ***
8
14
 
9
15
  ## Key Features and Polyfills
10
16
 
11
17
  The library focuses on extending core browser APIs to meet the latest Web Stream and Fetch specifications.
12
18
 
13
- ### 1. ReadableStream Async Iteration
19
+ ### Conditional Filling
20
+
21
+ Each polyfill performs feature detection before initializing. If a feature is detected as already present then it is skipped so as not to overwrite native behavior where possible.
22
+
23
+ ### ReadableStream Async Iteration
14
24
 
15
25
  The library adds **comprehensive support for modern JavaScript iteration patterns** to `ReadableStream` and its readers.
16
26
 
17
27
  | Target | Method/Property | Description |
18
28
  | :--- | :--- | :--- |
19
- | [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) | `[Symbol.asyncIterator]` | Allows the stream to be directly iterable in `for-await-of` loops. |
29
+ | [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) | [`[Symbol.asyncIterator]`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncIterator) | Allows the stream to be directly iterable in `for-await-of` loops. |
20
30
  | [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) | `values()` | An alias for `[Symbol.asyncIterator]` for explicit iteration. |
21
31
 
22
32
  ![ReadableStream.asyncIterator](https://caniuse.smokestack.workers.dev/?feature=api.ReadableStream.@@asyncIterator)
23
33
  ![ReadableStream.values](https://caniuse.smokestack.workers.dev/?feature=api.ReadableStream.values)
24
34
 
25
- ### 2. Stream Construction Utility
35
+ ### Stream Construction Utility
26
36
 
27
37
  The library adds the static method for creating streams from existing data sources.
28
38
 
@@ -32,15 +42,15 @@ The library adds the static method for creating streams from existing data sourc
32
42
 
33
43
  ![ReadableStream.from](https://caniuse.smokestack.workers.dev/?feature=api.ReadableStream.from)
34
44
 
35
- ### 3. Fetch and Body Integration Shims
45
+ ### Body and Bytes Shims
36
46
 
37
47
  These shims ensure `Request` and `Response` objects (Records) consistently expose their body as a stream and provide the `bytes()` utility.
38
48
 
39
49
  | Target | Method/Property | Description |
40
50
  | :--- | :--- | :--- |
41
- | `Request` | [`body`](https://developer.mozilla.org/en-US/docs/Web/API/Request/body) | 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` | [`body`](https://developer.mozilla.org/en-US/docs/Web/API/Response/body) | 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`](https://developer.mozilla.org/en-US/docs/Web/API/Request/bytes), [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response/bytes), [`Blob`](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. |
51
+ | [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request/body) | [`body`](https://developer.mozilla.org/en-US/docs/Web/API/Request/body) | 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. |
52
+ | [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response/body) | [`body`](https://developer.mozilla.org/en-US/docs/Web/API/Response/body) | Provides the body content as a `ReadableStream`. |
53
+ | [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request/bytes), [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response/bytes), [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob/bytes) | [`bytes()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) | Adds the `bytes()` method, which **asynchronously returns the object's body/content as a `Uint8Array`**. |
44
54
 
45
55
  ![Request.body](https://caniuse.smokestack.workers.dev/?feature=api.Request.body)
46
56
  ![Response.body](https://caniuse.smokestack.workers.dev/?feature=api.Response.body)
@@ -48,9 +58,9 @@ These shims ensure `Request` and `Response` objects (Records) consistently expos
48
58
  ![Response.bytes](https://caniuse.smokestack.workers.dev/?feature=api.Response.bytes)
49
59
  ![Blob.bytes](https://caniuse.smokestack.workers.dev/?feature=api.Blob.bytes)
50
60
 
51
- ### 4. Duplex Compliance Shim
61
+ ### Duplex Compliance Shim
52
62
 
53
- To satisfy modern `fetch` specifications when streaming request bodies, the library ensures compliance for **half-duplex operations**.
63
+ To satisfy modern `fetch` specifications when streaming request bodies, the library ensures compliance for **half-duplex operations**. This is in many ways a reverse shim as it allows legacy code to continue to work in the absence of a duplex parameter that did not exist when the code was implemented.
54
64
 
55
65
  * **Property Injection:** The `duplex: 'half'` property is added to the prototypes of `Request`, `Response`, `ReadableStream`, and `Blob`.
56
66
  * **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.
@@ -58,12 +68,12 @@ To satisfy modern `fetch` specifications when streaming request bodies, the libr
58
68
 
59
69
  ![Request.duplex](https://caniuse.smokestack.workers.dev/?feature=api.Request.duplex)
60
70
 
61
- ### 5. ReadableStreamDefaultReader Constructor Support
71
+ ### ReadableStreamDefaultReader Constructor Support
62
72
 
63
73
  The library adds support for the `ReadableStreamDefaultReader` constructor.
64
74
 
65
75
  | Target | Method/Property | Description |
66
76
  | :--- | :--- | :--- |
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. |
77
+ | [`ReadableStreamDefaultReader`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader) | [`constructor(stream)`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader/ReadableStreamDefaultReader) | **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
78
 
69
79
  ![ReadableStreamDefaultReader.constructor](https://caniuse.smokestack.workers.dev/?feature=api.ReadableStreamDefaultReader.constructor)
@@ -0,0 +1,175 @@
1
+ (() => {
2
+ const Q = fn => {
3
+ try {
4
+ return fn?.()
5
+ } catch {}
6
+ };
7
+ const $global = Q(() => globalThis) ?? Q(() => global) ?? Q(() => self) ?? Q(() => window) ?? this;
8
+ const constructPrototype = newClass => {
9
+ try {
10
+ if (newClass?.prototype) return newClass;
11
+ const constProto = newClass?.constructor?.prototype;
12
+ if (constProto) {
13
+ newClass.prototype = Q(() => constProto?.bind?.(constProto)) ?? Object.create(Object(constProto));
14
+ return newClass;
15
+ }
16
+ newClass.prototype = Q(() => newClass?.bind?.(newClass)) ?? Object.create(Object(newClass));
17
+ } catch (e) {
18
+ console.warn(e, newClass);
19
+ }
20
+ };
21
+ const extend = (thisClass, superClass) => {
22
+ try {
23
+ constructPrototype(thisClass);
24
+ constructPrototype(superClass);
25
+ Object.setPrototypeOf(thisClass.prototype, superClass?.prototype ?? superClass?.constructor?.prototype ?? superClass);
26
+ Object.setPrototypeOf(thisClass, superClass);
27
+ } catch (e) {
28
+ console.warn(e, {
29
+ thisClass,
30
+ superClass
31
+ });
32
+ }
33
+ return thisClass;
34
+ };
35
+ /**
36
+
37
+ - Creates a function that returns a string for all string conversion methods
38
+ - Used to provide consistent string representations for polyfilled functions
39
+ - @param {string} str - The string to return
40
+ - @returns {Function} A function that returns the string for all conversion methods
41
+ - @private
42
+ */
43
+ const makeStringer = str => {
44
+ const stringer = () => str;
45
+ ['valueOf', 'toString', 'toLocaleString', Symbol.toPrimitive].forEach(x => {
46
+ stringer[x] = stringer;
47
+ });
48
+ stringer[Symbol.toStringTag] = str;
49
+ return stringer;
50
+ };
51
+ /**
52
+
53
+ - Sets string conversion methods on a function to indicate it’s polyfill code
54
+ - Provides consistent debugging experience by showing polyfill status
55
+ - @param {Function} obj - The function to modify
56
+ - @returns {Function} The modified function
57
+ - @private
58
+ */
59
+ const setStrings = (obj) => {
60
+ for (const str of ['toString', 'toLocaleString', Symbol.toStringTag]) {
61
+ Object.defineProperty(obj, str, {
62
+ value: makeStringer(`function ${obj.name}() { [polyfill code] }`),
63
+ configurable: true,
64
+ writable: true,
65
+ enumerable: false,
66
+ });
67
+ }
68
+ return obj;
69
+ };
70
+ const assign = (target, source) => {
71
+ const props = Object.getOwnPropertyDescriptors(source);
72
+ for (const key in props) {
73
+ try {
74
+ Object.defineProperty(target, key, props[key]);
75
+ } catch (e) {
76
+ console.warn(e, key, props[key]);
77
+ }
78
+ }
79
+ for (const key in source) {
80
+ try {
81
+ target[key] ??= source[key];
82
+ } catch (e) {
83
+ console.warn(e, key, props[key]);
84
+ }
85
+ }
86
+ return target;
87
+ };
88
+ const cloneClass = $class => {
89
+ const clonePrototype = assign({}, $class.prototype);
90
+ const clone = $class.bind(clonePrototype);
91
+ assign(clone, $class);
92
+ clone.prototype = Object.setPrototypeOf(clonePrototype, Object.getPrototypeOf($class.prototype));
93
+ Object.setPrototypeOf(clone, Object.getPrototypeOf($class));
94
+ return clone;
95
+ };
96
+ if (!$global.ReadableStreamBYOBReader) {
97
+ $global.ReadableStreamBYOBReader ??= cloneClass(ReadableStreamDefaultReader);
98
+ Object.defineProperty(ReadableStreamBYOBReader, 'name', {
99
+ value: 'ReadableStreamBYOBReader',
100
+ enumerable: true,
101
+ configurable: true,
102
+ writable: true
103
+ });
104
+ setStrings(ReadableStreamBYOBReader);
105
+ const _getReader = ReadableStream.prototype.getReader;
106
+ ReadableStream.prototype.getReader = Object.setPrototypeOf(function getReader(options) {
107
+ const reader = _getReader.call(this);
108
+ if (options?.mode == 'byob') {
109
+ Object.setPrototypeOf(reader, ReadableStreamBYOBReader);
110
+ }
111
+ return reader;
112
+ }, _getReader);
113
+ extend(ReadableStreamBYOBReader, ReadableStreamDefaultReader);
114
+ const _read = ReadableStreamBYOBReader.prototype.read;
115
+ ReadableStreamBYOBReader.prototype.read = extend(setStrings(async function read(view) {
116
+ // If no view is provided, fall back to default behavior
117
+ if (!view) {
118
+ return _read.call(this,view);
119
+ }
120
+ // Read from the underlying stream (default reader behavior)
121
+ const result = await _read.call(this,view);
122
+ // If done, return with the view and done flag
123
+ if (result.done != false) {
124
+ return {
125
+ value: view,
126
+ done: true
127
+ };
128
+ }
129
+ // Convert the chunk to Uint8Array if needed
130
+ const chunk = result.value instanceof Uint8Array ? result.value : new Uint8Array(result.value);
131
+ // Determine how much data we can copy
132
+ const bytesToCopy = Math.min(chunk.byteLength, view.byteLength);
133
+ // Create a temporary view to copy into the provided view
134
+ const targetView = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
135
+ // Copy the data into the provided buffer
136
+ targetView.set(chunk.subarray(0, bytesToCopy), 0);
137
+ // Create a view of the filled portion
138
+ const filledView = new view.constructor(view.buffer, view.byteOffset, bytesToCopy);
139
+ return {
140
+ value: filledView,
141
+ done: false
142
+ };
143
+ }), _read);
144
+ }
145
+ const supportsReadableStreamBYOBReaderConstructor = () => {
146
+ try {
147
+ const stream = new ReadableStream({
148
+ start(controller) {
149
+ controller.enqueue(new Uint8Array([0]));
150
+ controller.close();
151
+ },
152
+ type: 'bytes'
153
+ });
154
+ const reader = new ReadableStreamBYOBReader(stream);
155
+ reader.read(new Uint8Array([0]));
156
+ return true;
157
+ } catch {
158
+ return false;
159
+ }
160
+ };
161
+ if (!supportsReadableStreamBYOBReaderConstructor()) {
162
+ const _ReadableStreamBYOBReader = $global.ReadableStreamBYOBReader;
163
+ const $ReadableStreamBYOBReader = function ReadableStreamBYOBReader(stream) {
164
+ return Object.setPrototypeOf(stream.getReader(), $global.ReadableStreamBYOBReader.prototype);
165
+ };
166
+ setStrings($ReadableStreamBYOBReader);
167
+ extend($ReadableStreamBYOBReader, _ReadableStreamBYOBReader);
168
+ $global.ReadableStreamBYOBReader = new Proxy($ReadableStreamBYOBReader, Object.setPrototypeOf({
169
+ construct: Object.setPrototypeOf(function construct(_, [stream]) {
170
+ return $ReadableStreamBYOBReader(stream);
171
+ }, $ReadableStreamBYOBReader.prototype)
172
+ }, $ReadableStreamBYOBReader));
173
+ $global.ReadableStreamBYOBReader.prototype.constructor = $global.ReadableStreamBYOBReader;
174
+ }
175
+ })();
@@ -4,6 +4,7 @@
4
4
  return fn?.()
5
5
  } catch {}
6
6
  };
7
+ const $global = Q(()=>globalThis) ?? Q(()=>global) ?? Q(()=>self) ?? Q(()=>window) ?? this;
7
8
  const constructPrototype = newClass => {
8
9
  try {
9
10
  if (newClass?.prototype) return newClass;
@@ -92,18 +93,18 @@
92
93
 
93
94
 
94
95
  if (!supportsReadableStreamDefaultReaderConstructor()) {
95
- const _ReadableStreamDefaultReader = globalThis.ReadableStreamDefaultReader;
96
+ const _ReadableStreamDefaultReader = $global.ReadableStreamDefaultReader;
96
97
  const $ReadableStreamDefaultReader = function ReadableStreamDefaultReader(stream) {
97
- return Object.setPrototypeOf(stream.getReader(), globalThis.ReadableStreamDefaultReader.prototype);
98
+ return Object.setPrototypeOf(stream.getReader(), $global.ReadableStreamDefaultReader.prototype);
98
99
  };
99
100
  setStrings($ReadableStreamDefaultReader);
100
101
  extend($ReadableStreamDefaultReader, _ReadableStreamDefaultReader);
101
- globalThis.ReadableStreamDefaultReader = new Proxy($ReadableStreamDefaultReader, Object.setPrototypeOf({
102
+ $global.ReadableStreamDefaultReader = new Proxy($ReadableStreamDefaultReader, Object.setPrototypeOf({
102
103
  construct:Object.setPrototypeOf(function construct(_, [stream]) {
103
104
  return $ReadableStreamDefaultReader(stream)
104
105
  },$ReadableStreamDefaultReader.prototype)
105
106
  },$ReadableStreamDefaultReader));
106
- globalThis.ReadableStreamDefaultReader.prototype.constructor = globalThis.ReadableStreamDefaultReader
107
+ $global.ReadableStreamDefaultReader.prototype.constructor = $global.ReadableStreamDefaultReader
107
108
  }
108
109
 
109
110
  })();
package/Record-duplex.js CHANGED
@@ -89,7 +89,7 @@
89
89
  - Creates a new Request class that extends the original but processes arguments
90
90
  */
91
91
  (() => {
92
- let $Request = Request;
92
+ const $Request = Request;
93
93
 
94
94
 
95
95
  /**
@@ -102,13 +102,13 @@
102
102
  * body: new ReadableStream()
103
103
  * }); // ReadableStream automatically gets duplex: 'half'
104
104
  */
105
- $Request = class Request extends $Request {
105
+ const _Request = class Request extends $Request {
106
106
  constructor(...args) {
107
107
  super(...args.map(duplexHalf));
108
108
  }
109
109
  };
110
110
 
111
- $global.Request = $Request;
111
+ $global.Request = _Request;
112
112
 
113
113
 
114
114
  })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "web-streams-shim",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "",
5
5
  "main": "web-streams-core.js",
6
6
  "scripts": {
@@ -739,7 +739,7 @@
739
739
  - Creates a new Request class that extends the original but processes arguments
740
740
  */
741
741
  (() => {
742
- let $Request = Request;
742
+ const $Request = Request;
743
743
 
744
744
 
745
745
  /**
@@ -752,13 +752,13 @@
752
752
  * body: new ReadableStream()
753
753
  * }); // ReadableStream automatically gets duplex: 'half'
754
754
  */
755
- $Request = class Request extends $Request {
755
+ const _Request = class Request extends $Request {
756
756
  constructor(...args) {
757
757
  super(...args.map(duplexHalf));
758
758
  }
759
759
  };
760
760
 
761
- $global.Request = $Request;
761
+ $global.Request = _Request;
762
762
 
763
763
 
764
764
  })();