web-streams-shim 1.0.7 → 1.0.8
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 +1 -1
- package/CONTRIBUTING.md +247 -0
- package/Record-body.js +0 -12
- package/extensions/README.md +318 -0
- package/extensions/Record-stream.js +100 -0
- package/extensions/file.js +31 -3
- package/extensions/location.js +21 -0
- package/extensions/type-extensions.js +121 -16
- package/extensions/web-streams-extensions.d.ts +57 -0
- package/extensions/web-streams-extensions.js +298 -9
- package/package.json +20 -4
- package/test/test.html +2 -416
- package/test/test.js +897 -0
- package/web-streams-core.d.ts +228 -0
- package/web-streams-core.js +155 -69
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# Contributing to web-streams-shim
|
|
2
|
+
|
|
3
|
+
Thank you for your interest in contributing to web-streams-shim! This document provides guidelines and information for contributors.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
web-streams-shim provides polyfills and shims to ensure modern Web Streams functionality is available across environments where native support is missing or incomplete. The library focuses on:
|
|
8
|
+
|
|
9
|
+
- ReadableStream async iteration support
|
|
10
|
+
- ReadableStream.from() static method
|
|
11
|
+
- Request/Response/Blob body streaming extensions
|
|
12
|
+
- bytes() method for binary data handling
|
|
13
|
+
- BYOB (Bring Your Own Buffer) reader support
|
|
14
|
+
|
|
15
|
+
## Design Philosophy
|
|
16
|
+
|
|
17
|
+
### Self-Contained Files
|
|
18
|
+
Each polyfill file is intentionally **self-contained** with duplicated utility functions. This design allows:
|
|
19
|
+
- Individual files to work independently when needed
|
|
20
|
+
- Easy cherry-picking of specific polyfills
|
|
21
|
+
- Minimal dependencies and side effects
|
|
22
|
+
- Better tree-shaking in bundlers
|
|
23
|
+
|
|
24
|
+
While this creates some duplication, it's a deliberate trade-off for modularity.
|
|
25
|
+
|
|
26
|
+
### Feature Detection First
|
|
27
|
+
All polyfills use feature detection before applying. Native implementations are never overwritten unless `FORCE_POLYFILLS` is enabled (test mode only).
|
|
28
|
+
|
|
29
|
+
### Prototype Extension Pattern
|
|
30
|
+
The library extends native prototypes safely using:
|
|
31
|
+
- Conditional property definition
|
|
32
|
+
- Non-enumerable properties to avoid iteration issues
|
|
33
|
+
- Proper prototype chain management
|
|
34
|
+
|
|
35
|
+
## Development Setup
|
|
36
|
+
|
|
37
|
+
### Prerequisites
|
|
38
|
+
- Node.js 18+ (for development dependencies)
|
|
39
|
+
- A modern web browser for testing
|
|
40
|
+
|
|
41
|
+
### Getting Started
|
|
42
|
+
```bash
|
|
43
|
+
# Clone the repository
|
|
44
|
+
git clone https://github.com/Patrick-ring-motive/web-streams-shim.git
|
|
45
|
+
cd web-streams-shim
|
|
46
|
+
|
|
47
|
+
# Install dependencies
|
|
48
|
+
npm install
|
|
49
|
+
|
|
50
|
+
# Run tests (opens test.html in browser)
|
|
51
|
+
npm test
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Code Structure
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
web-streams-shim/
|
|
58
|
+
├── web-streams-core.js # Main polyfill bundle
|
|
59
|
+
├── ReadableStream-asyncIterator.js
|
|
60
|
+
├── ReadableStream-from.js
|
|
61
|
+
├── ReadableStreamBYOBReader.js
|
|
62
|
+
├── ReadableStreamDefaultReader-constructor.js
|
|
63
|
+
├── Record-body.js # Request/Response body streams
|
|
64
|
+
├── Record-bytes.js # bytes() method polyfill
|
|
65
|
+
├── Record-duplex.js # Duplex stream support
|
|
66
|
+
├── extensions/ # Non-standard extensions
|
|
67
|
+
│ ├── web-streams-extensions.js # Additional convenience methods
|
|
68
|
+
│ ├── Record-stream.js # stream() alias method
|
|
69
|
+
│ ├── file.js # File object support
|
|
70
|
+
│ ├── location.js # Location-specific patches
|
|
71
|
+
│ └── type-extensions.js # Type system extensions
|
|
72
|
+
└── test/
|
|
73
|
+
├── test.html # Test runner page
|
|
74
|
+
└── test.js # Test suite
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Making Contributions
|
|
78
|
+
|
|
79
|
+
### Reporting Issues
|
|
80
|
+
When reporting bugs, please include:
|
|
81
|
+
- Browser name and version
|
|
82
|
+
- Operating system
|
|
83
|
+
- Minimal code example reproducing the issue
|
|
84
|
+
- Expected vs actual behavior
|
|
85
|
+
- Console errors (if any)
|
|
86
|
+
|
|
87
|
+
### Code Contributions
|
|
88
|
+
|
|
89
|
+
#### Adding New Polyfills
|
|
90
|
+
1. Create a new self-contained file with the pattern:
|
|
91
|
+
```javascript
|
|
92
|
+
(() => {
|
|
93
|
+
// Feature detection
|
|
94
|
+
if (typeof TargetAPI === 'undefined') return;
|
|
95
|
+
|
|
96
|
+
// Utility functions (Q, extend, setStrings, etc.)
|
|
97
|
+
const Q = fn => { /* ... */ };
|
|
98
|
+
|
|
99
|
+
// Polyfill implementation with conditional application
|
|
100
|
+
if (!TargetAPI.prototype.method) {
|
|
101
|
+
Object.defineProperty(TargetAPI.prototype, 'method', {
|
|
102
|
+
value: /* implementation */,
|
|
103
|
+
configurable: true,
|
|
104
|
+
writable: true,
|
|
105
|
+
enumerable: false
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
})();
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
2. Add comprehensive JSDoc comments
|
|
112
|
+
3. Include the polyfill in web-streams-core.js if it's a core feature
|
|
113
|
+
4. Add tests in test/test.js
|
|
114
|
+
5. Update README.md with compatibility information
|
|
115
|
+
|
|
116
|
+
#### Testing Requirements
|
|
117
|
+
- All new features must include tests in test/test.js
|
|
118
|
+
- Tests should verify both polyfilled and native behavior
|
|
119
|
+
- Test error cases and edge conditions
|
|
120
|
+
- Verify async iteration patterns work correctly
|
|
121
|
+
|
|
122
|
+
#### Code Style
|
|
123
|
+
- Use 4-space indentation
|
|
124
|
+
- Use descriptive variable names
|
|
125
|
+
- Prefer `const` over `let` where possible
|
|
126
|
+
- Use optional chaining (`?.`) for safe property access
|
|
127
|
+
- Wrap all polyfills in IIFEs to avoid global scope pollution
|
|
128
|
+
- Use arrow functions for utility functions
|
|
129
|
+
- Use `function` keyword for named methods/constructors
|
|
130
|
+
|
|
131
|
+
#### Documentation
|
|
132
|
+
- Add JSDoc comments for all public APIs
|
|
133
|
+
- Include `@param`, `@returns`, and `@example` tags
|
|
134
|
+
- Document browser compatibility in README.md
|
|
135
|
+
- Update TypeScript definitions in .d.ts files
|
|
136
|
+
|
|
137
|
+
### Pull Request Process
|
|
138
|
+
|
|
139
|
+
1. **Fork and Branch**
|
|
140
|
+
- Fork the repository
|
|
141
|
+
- Create a feature branch: `git checkout -b feature/my-feature`
|
|
142
|
+
- Make your changes with clear, atomic commits
|
|
143
|
+
|
|
144
|
+
2. **Test Your Changes**
|
|
145
|
+
- Run the test suite in multiple browsers
|
|
146
|
+
- Add new tests for new features
|
|
147
|
+
- Ensure no regressions in existing functionality
|
|
148
|
+
|
|
149
|
+
3. **Update Documentation**
|
|
150
|
+
- Update README.md if adding new features
|
|
151
|
+
- Add/update TypeScript definitions
|
|
152
|
+
- Include code examples in JSDoc comments
|
|
153
|
+
|
|
154
|
+
4. **Submit Pull Request**
|
|
155
|
+
- Provide clear description of changes
|
|
156
|
+
- Reference any related issues
|
|
157
|
+
- Ensure all tests pass
|
|
158
|
+
- Be responsive to code review feedback
|
|
159
|
+
|
|
160
|
+
## Testing Guidelines
|
|
161
|
+
|
|
162
|
+
### Browser Testing
|
|
163
|
+
Test in at least:
|
|
164
|
+
- Chrome/Edge (latest)
|
|
165
|
+
- Firefox (latest)
|
|
166
|
+
- Safari (latest)
|
|
167
|
+
- One older browser version where polyfills are needed
|
|
168
|
+
|
|
169
|
+
### Test Patterns
|
|
170
|
+
```javascript
|
|
171
|
+
runner.test('Feature: Description', async () => {
|
|
172
|
+
const stream = new ReadableStream({/* ... */});
|
|
173
|
+
// Test implementation
|
|
174
|
+
assert(condition, 'Assertion message');
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Running Tests
|
|
179
|
+
Open `test/test.html` in a browser. Tests run automatically and display results with:
|
|
180
|
+
- ✓ Green for passing tests
|
|
181
|
+
- ✗ Red for failing tests
|
|
182
|
+
- Summary statistics at the bottom
|
|
183
|
+
|
|
184
|
+
## Browser Compatibility
|
|
185
|
+
|
|
186
|
+
### Target Browsers
|
|
187
|
+
The library targets browsers with:
|
|
188
|
+
- Partial Web Streams API support
|
|
189
|
+
- Missing async iteration on streams
|
|
190
|
+
- Missing ReadableStream.from()
|
|
191
|
+
- Incomplete body streaming support
|
|
192
|
+
|
|
193
|
+
### Known Issues
|
|
194
|
+
- IE11: Not supported (requires full polyfill like web-streams-polyfill)
|
|
195
|
+
- Safari < 14.1: Limited BYOB reader support
|
|
196
|
+
- Firefox < 100: Some iteration edge cases
|
|
197
|
+
|
|
198
|
+
See README.md for detailed compatibility matrices.
|
|
199
|
+
|
|
200
|
+
## Performance Considerations
|
|
201
|
+
|
|
202
|
+
### Best Practices
|
|
203
|
+
- Polyfills only apply when features are missing
|
|
204
|
+
- Use feature detection, not browser detection
|
|
205
|
+
- Avoid unnecessary object creation in hot paths
|
|
206
|
+
- Leverage native implementations when available
|
|
207
|
+
|
|
208
|
+
### Benchmarking
|
|
209
|
+
When adding performance-sensitive code:
|
|
210
|
+
- Compare against native implementation
|
|
211
|
+
- Test with large datasets
|
|
212
|
+
- Measure memory usage for streaming operations
|
|
213
|
+
- Profile in multiple browsers
|
|
214
|
+
|
|
215
|
+
## Release Process
|
|
216
|
+
|
|
217
|
+
1. Update version in package.json
|
|
218
|
+
2. Update CHANGELOG (if exists)
|
|
219
|
+
3. Create git tag: `git tag v1.0.x`
|
|
220
|
+
4. Push tag: `git push origin v1.0.x`
|
|
221
|
+
5. Create GitHub release
|
|
222
|
+
6. npm publish happens automatically via GitHub Actions
|
|
223
|
+
|
|
224
|
+
## Community
|
|
225
|
+
|
|
226
|
+
### Getting Help
|
|
227
|
+
- GitHub Issues: Bug reports and feature requests
|
|
228
|
+
- GitHub Discussions: General questions and ideas
|
|
229
|
+
- Email: patrick.ring.motive@gmail.com
|
|
230
|
+
|
|
231
|
+
### Code of Conduct
|
|
232
|
+
- Be respectful and inclusive
|
|
233
|
+
- Provide constructive feedback
|
|
234
|
+
- Help others learn and grow
|
|
235
|
+
- Focus on what's best for the community
|
|
236
|
+
|
|
237
|
+
## License
|
|
238
|
+
|
|
239
|
+
This project is licensed under the ISC License - see the LICENSE file for details.
|
|
240
|
+
|
|
241
|
+
## Additional Resources
|
|
242
|
+
|
|
243
|
+
- [Web Streams API Specification](https://streams.spec.whatwg.org/)
|
|
244
|
+
- [MDN Web Streams Documentation](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API)
|
|
245
|
+
- [Can I Use - Web Streams](https://caniuse.com/streams)
|
|
246
|
+
|
|
247
|
+
Thank you for contributing to web-streams-shim!
|
package/Record-body.js
CHANGED
|
@@ -84,18 +84,6 @@
|
|
|
84
84
|
|
|
85
85
|
/**
|
|
86
86
|
|
|
87
|
-
- Safely executes a function and catches any errors
|
|
88
|
-
- @param {Function} fn - Function to execute
|
|
89
|
-
- @returns {*} The result of fn() or undefined if an error occurred
|
|
90
|
-
*/
|
|
91
|
-
const Q = fn => {
|
|
92
|
-
try {
|
|
93
|
-
return fn?.();
|
|
94
|
-
} catch {}
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
|
|
99
87
|
- Safely checks instanceof relationship to avoid errors with cross-realm objects
|
|
100
88
|
- @param {*} x - The object to check
|
|
101
89
|
- @param {Function} y - The constructor to check against
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
# Web Streams Extensions
|
|
2
|
+
|
|
3
|
+
**Non-standard convenience methods and polyfills for modern serverless environments**
|
|
4
|
+
|
|
5
|
+
This directory contains extensions to the Web Streams API that are **intentionally non-standard**. They provide helpful functionality and API consistency that isn't part of official specifications but is useful in practice.
|
|
6
|
+
|
|
7
|
+
## Purpose
|
|
8
|
+
|
|
9
|
+
These extensions are designed for:
|
|
10
|
+
- **Cloudflare Workers** - Missing File, Location constructors
|
|
11
|
+
- **Vercel Edge Runtime** - Limited Web API surface
|
|
12
|
+
- **Deno Deploy** - Some API gaps in standard library
|
|
13
|
+
- **Google Apps Script** - Non-browser environment needing browser APIs
|
|
14
|
+
- **Other serverless platforms** - Various API compatibility needs
|
|
15
|
+
|
|
16
|
+
## Files
|
|
17
|
+
|
|
18
|
+
### Core Extensions
|
|
19
|
+
|
|
20
|
+
#### [`web-streams-extensions.js`](web-streams-extensions.js)
|
|
21
|
+
**Complete extension bundle with all non-standard methods**
|
|
22
|
+
|
|
23
|
+
Adds:
|
|
24
|
+
- `Request/Response.stream()` - Method alias for `.body` property
|
|
25
|
+
- `Request/Response/Blob` async iteration - Direct iteration over content
|
|
26
|
+
- `Blob.body` and `Blob.bodyUsed` - Match Request/Response API surface
|
|
27
|
+
- `Blob` helper methods - `blob()`, `clone()`, `formData()`, `json()`
|
|
28
|
+
- `ArrayBuffer` iteration - `bytes()`, `Symbol.iterator`, `values()`
|
|
29
|
+
- `SharedArrayBuffer` iteration - `bytes()`, `Symbol.iterator`, `values()`
|
|
30
|
+
- `ReadableStream` helper methods - `text()`, `json()`, `arrayBuffer()`, `blob()`, `bytes()`, `formData()`, `clone()`, `stream()`, `body`
|
|
31
|
+
- `ReadableStream.from()` fallback - Creates streams in limited environments
|
|
32
|
+
- Advanced parsing - `sharedArrayBuffer()`, `dataView()`, `searchParams()`, `file()` on Request/Response/Blob/ReadableStream
|
|
33
|
+
- `DataView` iteration - `bytes()`, `Symbol.iterator`, `values()`
|
|
34
|
+
|
|
35
|
+
```javascript
|
|
36
|
+
// Usage
|
|
37
|
+
import 'web-streams-shim/extensions';
|
|
38
|
+
|
|
39
|
+
// Method-based API
|
|
40
|
+
const stream = response.stream();
|
|
41
|
+
|
|
42
|
+
// Direct iteration
|
|
43
|
+
for await (const chunk of response) {
|
|
44
|
+
console.log(chunk);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Blob API consistency
|
|
48
|
+
const blobStream = myBlob.body;
|
|
49
|
+
const isReading = myBlob.bodyUsed;
|
|
50
|
+
|
|
51
|
+
// ArrayBuffer iteration
|
|
52
|
+
const buffer = await response.arrayBuffer();
|
|
53
|
+
for (const byte of buffer) {
|
|
54
|
+
// Process each byte
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// SharedArrayBuffer iteration
|
|
58
|
+
const shared = await response.sharedArrayBuffer();
|
|
59
|
+
for (const byte of shared) {
|
|
60
|
+
// Process each byte (thread-safe)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ReadableStream parsing helpers (consume stream)
|
|
64
|
+
const stream = response.body;
|
|
65
|
+
const text = await stream.text(); // Parse as text
|
|
66
|
+
const json = await stream.json(); // Parse as JSON
|
|
67
|
+
const blob = await stream.blob(); // Create Blob
|
|
68
|
+
const bytes = await stream.bytes(); // Get Uint8Array
|
|
69
|
+
const buffer = await stream.arrayBuffer(); // Get ArrayBuffer
|
|
70
|
+
const form = await stream.formData(); // Parse FormData
|
|
71
|
+
const cloned = stream.clone(); // Tee and return copy
|
|
72
|
+
|
|
73
|
+
// ReadableStream self-reference (API consistency)
|
|
74
|
+
const same = stream.stream(); // Returns self
|
|
75
|
+
const body = stream.body; // Returns self
|
|
76
|
+
|
|
77
|
+
// Advanced parsing methods (work on Request/Response/Blob/ReadableStream)
|
|
78
|
+
const shared = await response.sharedArrayBuffer(); // SharedArrayBuffer for Workers
|
|
79
|
+
const view = await response.dataView(); // DataView for binary manipulation
|
|
80
|
+
const params = await response.searchParams(); // URLSearchParams from text
|
|
81
|
+
const file = await response.file('data.json', { // File object with metadata
|
|
82
|
+
type: 'application/json',
|
|
83
|
+
lastModified: Date.now()
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// DataView iteration
|
|
87
|
+
const view = await response.dataView();
|
|
88
|
+
for (const byte of view) {
|
|
89
|
+
// Iterate over DataView bytes
|
|
90
|
+
}
|
|
91
|
+
const viewBytes = view.bytes(); // Convert to Uint8Array
|
|
92
|
+
|
|
93
|
+
// ReadableStream.from() - works even without native support
|
|
94
|
+
const stream = ReadableStream.from(['hello', 'world']);
|
|
95
|
+
for await (const chunk of stream) {
|
|
96
|
+
console.log(chunk); // 'hello', 'world'
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
#### [`Record-stream.js`](Record-stream.js)
|
|
101
|
+
**Minimal stream() method only**
|
|
102
|
+
|
|
103
|
+
Lightweight alternative that only adds `stream()` method and `Blob.body` property. Use when you don't need the other extensions.
|
|
104
|
+
|
|
105
|
+
```javascript
|
|
106
|
+
// Usage
|
|
107
|
+
import 'web-streams-shim/extensions/Record-stream.js';
|
|
108
|
+
|
|
109
|
+
const stream = response.stream(); // Alias for response.body
|
|
110
|
+
const blobStream = myBlob.body;
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
#### [`type-extensions.js`](type-extensions.js)
|
|
114
|
+
**Prototype chain setup for runtime type traceability**
|
|
115
|
+
|
|
116
|
+
Extends Web API methods to inherit from the class of the type they return. This provides better debugging and type introspection.
|
|
117
|
+
|
|
118
|
+
```javascript
|
|
119
|
+
// After loading type-extensions.js
|
|
120
|
+
Response.prototype.blob instanceof Function // true
|
|
121
|
+
Response.prototype.blob.__proto__ === Blob // true (extended with Blob)
|
|
122
|
+
|
|
123
|
+
// Better runtime type checking
|
|
124
|
+
console.log(Response.prototype.json); // Shows connection to JSON
|
|
125
|
+
console.log(Response.prototype.arrayBuffer); // Shows connection to ArrayBuffer
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Extends:**
|
|
129
|
+
- `Request/Response/Blob/ReadableStream` methods: blob(), text(), json(), arrayBuffer(), stream(), formData(), bytes(), slice(), sharedArrayBuffer(), dataView(), searchParams(), file()
|
|
130
|
+
- `Request/Response/Blob` getters: url, headers, body
|
|
131
|
+
- `ArrayBuffer` methods: bytes(), slice()
|
|
132
|
+
- `SharedArrayBuffer` methods: bytes(), slice()
|
|
133
|
+
- `DataView` methods: bytes()
|
|
134
|
+
- Iteration: ArrayBuffer, SharedArrayBuffer, DataView (Symbol.iterator, values())
|
|
135
|
+
|
|
136
|
+
### Polyfills for Missing Constructors
|
|
137
|
+
|
|
138
|
+
#### [`file.js`](file.js)
|
|
139
|
+
**File constructor polyfill for serverless environments**
|
|
140
|
+
|
|
141
|
+
Provides a complete `File` class implementation when the native constructor is missing. Extends `Blob` with file-specific properties.
|
|
142
|
+
|
|
143
|
+
```javascript
|
|
144
|
+
// Usage in Cloudflare Workers
|
|
145
|
+
import 'web-streams-shim/extensions/file.js';
|
|
146
|
+
|
|
147
|
+
const file = new File(['content'], 'document.txt', {
|
|
148
|
+
type: 'text/plain',
|
|
149
|
+
lastModified: Date.now()
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
console.log(file.name); // 'document.txt'
|
|
153
|
+
console.log(file.lastModified); // timestamp
|
|
154
|
+
console.log(file.size); // 7 (from Blob)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Properties:**
|
|
158
|
+
- `name` - File name string
|
|
159
|
+
- `lastModified` - Timestamp in milliseconds
|
|
160
|
+
- `lastModifiedDate` - Date object (deprecated but included)
|
|
161
|
+
- `webkitRelativePath` - Always empty string
|
|
162
|
+
- All `Blob` properties (size, type, etc.)
|
|
163
|
+
|
|
164
|
+
#### [`location.js`](location.js)
|
|
165
|
+
**Location constructor polyfill for serverless environments**
|
|
166
|
+
|
|
167
|
+
Provides a `Location` class when it doesn't exist. Extends `URL` with Location-specific methods and properties.
|
|
168
|
+
|
|
169
|
+
```javascript
|
|
170
|
+
// Usage in Deno Deploy
|
|
171
|
+
import 'web-streams-shim/extensions/location.js';
|
|
172
|
+
|
|
173
|
+
const loc = new Location('https://example.com/path?query=value');
|
|
174
|
+
|
|
175
|
+
console.log(loc.href); // 'https://example.com/path?query=value'
|
|
176
|
+
console.log(loc.pathname); // '/path'
|
|
177
|
+
console.log(loc.search); // '?query=value'
|
|
178
|
+
|
|
179
|
+
// Location-specific methods (no-ops in server context)
|
|
180
|
+
loc.assign('https://other.com'); // Updates href, doesn't navigate
|
|
181
|
+
loc.reload(); // Console log, doesn't actually reload
|
|
182
|
+
loc.replace('https://other.com'); // Updates href, doesn't navigate
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**Why these exist:** Browser code often checks `location.href` or creates `new Location()`. These polyfills allow that code to run in serverless environments without modification, even though navigation methods are no-ops.
|
|
186
|
+
|
|
187
|
+
## Usage
|
|
188
|
+
|
|
189
|
+
### Load Everything
|
|
190
|
+
```html
|
|
191
|
+
<script src="https://cdn.jsdelivr.net/npm/web-streams-shim/extensions/web-streams-extensions.js"></script>
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Selective Loading
|
|
195
|
+
```javascript
|
|
196
|
+
// Just stream() method
|
|
197
|
+
await import('web-streams-shim/extensions/Record-stream.js');
|
|
198
|
+
|
|
199
|
+
// Just File constructor
|
|
200
|
+
await import('web-streams-shim/extensions/file.js');
|
|
201
|
+
|
|
202
|
+
// Type system extensions
|
|
203
|
+
await import('web-streams-shim/extensions/type-extensions.js');
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### With Module Bundler
|
|
207
|
+
```javascript
|
|
208
|
+
// All extensions
|
|
209
|
+
import 'web-streams-shim/extensions';
|
|
210
|
+
|
|
211
|
+
// Individual extensions
|
|
212
|
+
import 'web-streams-shim/extensions/file.js';
|
|
213
|
+
import 'web-streams-shim/extensions/location.js';
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Important Notes
|
|
217
|
+
|
|
218
|
+
### Non-Standard Warning
|
|
219
|
+
**These extensions are NOT part of any web standard.** They are convenience methods that:
|
|
220
|
+
- Fill gaps in serverless platforms
|
|
221
|
+
- Provide API consistency
|
|
222
|
+
- Enable browser code to run server-side
|
|
223
|
+
- Add useful iteration patterns
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
### ArrayBuffer/SharedArrayBuffer/DataView Iteration Caution
|
|
227
|
+
|
|
228
|
+
Making `ArrayBuffer`, `SharedArrayBuffer`, and `DataView` iterable is particularly non-standard. This could:
|
|
229
|
+
- Surprise developers expecting normal buffer behavior
|
|
230
|
+
- Conflict with future standards
|
|
231
|
+
- Break code that relies on buffers not being iterable
|
|
232
|
+
|
|
233
|
+
Use with awareness of these implications.
|
|
234
|
+
|
|
235
|
+
### ReadableStream.from() Fallback
|
|
236
|
+
|
|
237
|
+
The `ReadableStream.from()` implementation provides fallbacks for environments where:
|
|
238
|
+
- The static `from()` method doesn't exist
|
|
239
|
+
- The constructor doesn't support async iterable sources
|
|
240
|
+
- Streams need to be created from iterables in any environment
|
|
241
|
+
|
|
242
|
+
The fallback chain tries: native `from()` → constructor with async source → collect to Blob then Response.body
|
|
243
|
+
|
|
244
|
+
## Testing
|
|
245
|
+
|
|
246
|
+
Extensions should be tested in target environments:
|
|
247
|
+
|
|
248
|
+
```javascript
|
|
249
|
+
// Test in your serverless platform
|
|
250
|
+
import 'web-streams-shim/extensions';
|
|
251
|
+
|
|
252
|
+
// Verify File works
|
|
253
|
+
const file = new File(['test'], 'test.txt');
|
|
254
|
+
console.assert(file.name === 'test.txt', 'File name works');
|
|
255
|
+
|
|
256
|
+
// Verify Location works
|
|
257
|
+
const loc = new Location('https://example.com/path');
|
|
258
|
+
console.assert(loc.pathname === '/path', 'Location parsing works');
|
|
259
|
+
|
|
260
|
+
// Verify stream() alias
|
|
261
|
+
const res = new Response('test');
|
|
262
|
+
console.assert(res.stream() === res.body, 'stream() is alias for body');
|
|
263
|
+
|
|
264
|
+
// Verify async iteration
|
|
265
|
+
for await (const chunk of res) {
|
|
266
|
+
console.log('Chunk received:', chunk);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Verify SharedArrayBuffer
|
|
270
|
+
const shared = await res.sharedArrayBuffer();
|
|
271
|
+
console.assert(shared instanceof SharedArrayBuffer, 'SharedArrayBuffer works');
|
|
272
|
+
|
|
273
|
+
// Verify DataView iteration
|
|
274
|
+
const view = await new Response('test').dataView();
|
|
275
|
+
let byteCount = 0;
|
|
276
|
+
for (const byte of view) {
|
|
277
|
+
byteCount++;
|
|
278
|
+
}
|
|
279
|
+
console.assert(byteCount === 4, 'DataView iteration works');
|
|
280
|
+
|
|
281
|
+
// Verify searchParams
|
|
282
|
+
const params = await new Response('key=value&foo=bar').searchParams();
|
|
283
|
+
console.assert(params.get('key') === 'value', 'searchParams works');
|
|
284
|
+
|
|
285
|
+
// Verify file() method
|
|
286
|
+
const fileObj = await res.file('data.txt', { type: 'text/plain' });
|
|
287
|
+
console.assert(fileObj.name === 'data.txt', 'file() method works');
|
|
288
|
+
|
|
289
|
+
// Verify ReadableStream.from fallback
|
|
290
|
+
const stream = ReadableStream.from(['a', 'b', 'c']);
|
|
291
|
+
const chunks = [];
|
|
292
|
+
for await (const chunk of stream) {
|
|
293
|
+
chunks.push(chunk);
|
|
294
|
+
}
|
|
295
|
+
console.assert(chunks.length === 3, 'ReadableStream.from works');
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## Related
|
|
299
|
+
|
|
300
|
+
- [../README.md](../README.md) - Main library documentation
|
|
301
|
+
- [../CONTRIBUTING.md](../CONTRIBUTING.md) - Contributing guidelines
|
|
302
|
+
- [web-streams-core.js](../web-streams-core.js) - Standard polyfills
|
|
303
|
+
|
|
304
|
+
## Philosophy
|
|
305
|
+
|
|
306
|
+
Extensions exist to be **pragmatic** rather than **pure**:
|
|
307
|
+
- Standards are ideal, but environments vary
|
|
308
|
+
- Browser APIs are useful server-side too
|
|
309
|
+
- Consistency helps developer experience
|
|
310
|
+
- Non-standard doesn't mean wrong
|
|
311
|
+
|
|
312
|
+
If a method helps you ship code faster and works reliably, use it. Just document that it's non-standard so future maintainers understand.
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
**Version:** 1.0.7
|
|
317
|
+
**Last Updated:** December 31, 2025
|
|
318
|
+
**Maintained by:** patrick.ring.motive@gmail.com
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Record Stream Extensions - Minimal stream() method polyfills
|
|
3
|
+
*
|
|
4
|
+
* Adds stream() method to Request, Response, and Blob objects as a convenient
|
|
5
|
+
* alias for the .body property. This is a lightweight alternative to the full
|
|
6
|
+
* web-streams-extensions.js that only adds the stream() method.
|
|
7
|
+
*
|
|
8
|
+
* Also adds Blob.body property for API consistency with Request/Response.
|
|
9
|
+
*
|
|
10
|
+
* Use this instead of web-streams-extensions.js when you only need the
|
|
11
|
+
* stream() alias and don't want the other non-standard extensions.
|
|
12
|
+
*
|
|
13
|
+
* @note Non-standard convenience method
|
|
14
|
+
*/
|
|
15
|
+
(() => {
|
|
16
|
+
const Q = fn => {
|
|
17
|
+
try {
|
|
18
|
+
return fn?.()
|
|
19
|
+
} catch {}
|
|
20
|
+
};
|
|
21
|
+
const constructPrototype = newClass => {
|
|
22
|
+
try {
|
|
23
|
+
if (newClass?.prototype) return newClass;
|
|
24
|
+
const constProto = newClass?.constructor?.prototype;
|
|
25
|
+
if (constProto) {
|
|
26
|
+
newClass.prototype = Q(() => constProto?.bind?.(constProto)) ?? Object.create(Object(constProto));
|
|
27
|
+
return newClass;
|
|
28
|
+
}
|
|
29
|
+
newClass.prototype = Q(() => newClass?.bind?.(newClass)) ?? Object.create(Object(newClass));
|
|
30
|
+
} catch (e) {
|
|
31
|
+
console.warn(e, newClass);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
const extend = (thisClass, superClass) => {
|
|
35
|
+
try {
|
|
36
|
+
constructPrototype(thisClass);
|
|
37
|
+
constructPrototype(superClass);
|
|
38
|
+
Object.setPrototypeOf(
|
|
39
|
+
thisClass.prototype,
|
|
40
|
+
superClass?.prototype ??
|
|
41
|
+
superClass?.constructor?.prototype ??
|
|
42
|
+
superClass
|
|
43
|
+
);
|
|
44
|
+
Object.setPrototypeOf(thisClass, superClass);
|
|
45
|
+
|
|
46
|
+
} catch (e) {
|
|
47
|
+
console.warn(e, {
|
|
48
|
+
thisClass,
|
|
49
|
+
superClass
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return thisClass;
|
|
53
|
+
};
|
|
54
|
+
const makeStringer = str => {
|
|
55
|
+
const stringer = () => str;
|
|
56
|
+
['valueOf', 'toString', 'toLocaleString', Symbol.toPrimitive].forEach(x => {
|
|
57
|
+
stringer[x] = stringer;
|
|
58
|
+
});
|
|
59
|
+
stringer[Symbol.toStringTag] = str;
|
|
60
|
+
return stringer;
|
|
61
|
+
};
|
|
62
|
+
const setStrings = (obj) => {
|
|
63
|
+
let type = 'function';
|
|
64
|
+
if(String(obj).trim().startsWith('class')||/^[A-Z]|^.[A-Z]/.test(obj?.name)){
|
|
65
|
+
type = 'class';
|
|
66
|
+
}
|
|
67
|
+
if(String(obj).trim().startsWith('async')||/async/i.test(obj?.name)){
|
|
68
|
+
type = 'async function';
|
|
69
|
+
}
|
|
70
|
+
for (const str of ['toString', 'toLocaleString', Symbol.toStringTag]) {
|
|
71
|
+
Object.defineProperty(obj, str, {
|
|
72
|
+
value: makeStringer(`${type} ${obj.name} { [polyfill code] }`),
|
|
73
|
+
configurable: true,
|
|
74
|
+
writable: true,
|
|
75
|
+
enumerable: false,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return obj;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
for (const record of [Q(() => Request), Q(() => Response)]) {
|
|
82
|
+
(() => {
|
|
83
|
+
let currentRecord = record;
|
|
84
|
+
while(currentRecord.__proto__.name === currentRecord.name) {
|
|
85
|
+
currentRecord = currentRecord.__proto__;
|
|
86
|
+
}
|
|
87
|
+
(currentRecord?.prototype ?? {}).stream ??= extend(setStrings(function stream() {
|
|
88
|
+
return this.body;
|
|
89
|
+
}), Q(() => ReadableStream) ?? {});
|
|
90
|
+
})();
|
|
91
|
+
}
|
|
92
|
+
if(!('body' in Blob.prototype)){
|
|
93
|
+
Object.defineProperty(Blob.prototype,'body',{
|
|
94
|
+
get:extend(setStrings(function body(){return this.stream();})),
|
|
95
|
+
set:()=>{},
|
|
96
|
+
configurable:true,
|
|
97
|
+
enumerable:false
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
})();
|