@angular-helpers/worker-http 0.7.0 → 21.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +101 -7
- package/fesm2022/angular-helpers-worker-http-backend.mjs +41 -1
- package/fesm2022/angular-helpers-worker-http-esbuild-plugin.mjs +170 -0
- package/fesm2022/angular-helpers-worker-http-streams-polyfill.mjs +125 -0
- package/fesm2022/angular-helpers-worker-http-transport.mjs +28 -0
- package/package.json +53 -30
- package/types/angular-helpers-worker-http-backend.d.ts +36 -2
- package/types/angular-helpers-worker-http-esbuild-plugin.d.ts +46 -0
- package/types/angular-helpers-worker-http-streams-polyfill.d.ts +34 -0
- package/types/angular-helpers-worker-http-transport.d.ts +8 -0
package/README.md
CHANGED
|
@@ -16,13 +16,15 @@ On top of that, workers provide a natural isolation boundary for security-sensit
|
|
|
16
16
|
|
|
17
17
|
## Package map
|
|
18
18
|
|
|
19
|
-
| Entry point
|
|
20
|
-
|
|
|
21
|
-
| `@angular-helpers/worker-http/transport`
|
|
22
|
-
| `@angular-helpers/worker-http/serializer`
|
|
23
|
-
| `@angular-helpers/worker-http/interceptors`
|
|
24
|
-
| `@angular-helpers/worker-http/crypto`
|
|
25
|
-
| `@angular-helpers/worker-http/backend`
|
|
19
|
+
| Entry point | Description | Status |
|
|
20
|
+
| ----------------------------------------------- | ---------------------------------------------------------------- | ------------ |
|
|
21
|
+
| `@angular-helpers/worker-http/transport` | Typed RPC bridge, round-robin pool, cancellation | ✅ Available |
|
|
22
|
+
| `@angular-helpers/worker-http/serializer` | Pluggable serialization (structured clone, seroval, auto-detect) | ✅ Available |
|
|
23
|
+
| `@angular-helpers/worker-http/interceptors` | Pure-function interceptor pipeline for workers | ✅ Available |
|
|
24
|
+
| `@angular-helpers/worker-http/crypto` | WebCrypto primitives (HMAC, AES-GCM, SHA hashing) | ✅ Available |
|
|
25
|
+
| `@angular-helpers/worker-http/backend` | Angular `HttpBackend` replacement — `provideWorkerHttpClient()` | ✅ Available |
|
|
26
|
+
| `@angular-helpers/worker-http/esbuild-plugin` | esbuild plugin for auto-bundling interceptors into workers | ✅ Available |
|
|
27
|
+
| `@angular-helpers/worker-http/streams-polyfill` | Safari streams ponyfill for transferable stream support | ✅ Available |
|
|
26
28
|
|
|
27
29
|
---
|
|
28
30
|
|
|
@@ -43,6 +45,43 @@ Angular HttpClient createWorkerPipeline([
|
|
|
43
45
|
|
|
44
46
|
---
|
|
45
47
|
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
### Quick setup with ng-add
|
|
51
|
+
|
|
52
|
+
The easiest way to get started is using the Angular CLI schematic:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
ng add @angular-helpers/worker-http
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
This will:
|
|
59
|
+
|
|
60
|
+
1. Install the package
|
|
61
|
+
2. Create a worker file at `src/app/workers/http-api.worker.ts`
|
|
62
|
+
3. Update `tsconfig.json` with webworker lib
|
|
63
|
+
4. Add `provideWorkerHttpClient()` to your `app.config.ts`
|
|
64
|
+
|
|
65
|
+
**Options:**
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# Custom worker path
|
|
69
|
+
ng add @angular-helpers/worker-http --workerPath=src/workers/api.worker.ts
|
|
70
|
+
|
|
71
|
+
# Configure esbuild plugin (for custom build setups)
|
|
72
|
+
ng add @angular-helpers/worker-http --installEsbuildPlugin=true
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Manual installation
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npm install @angular-helpers/worker-http
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Then follow the setup in the `/backend` section below.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
46
85
|
## Entry points
|
|
47
86
|
|
|
48
87
|
### `/transport` — Typed RPC bridge
|
|
@@ -510,6 +549,61 @@ interface WorkerHttpTelemetryEventBase {
|
|
|
510
549
|
|
|
511
550
|
---
|
|
512
551
|
|
|
552
|
+
### `/esbuild-plugin` — Interceptor auto-bundling
|
|
553
|
+
|
|
554
|
+
An esbuild plugin that automatically discovers and bundles interceptor files into your worker builds. When using Angular with a custom webpack/esbuild configuration, this ensures your interceptors are included in the worker bundle without manual imports.
|
|
555
|
+
|
|
556
|
+
```typescript
|
|
557
|
+
// esbuild.config.ts
|
|
558
|
+
import { workerHttpPlugin } from '@angular-helpers/worker-http/esbuild-plugin';
|
|
559
|
+
|
|
560
|
+
export default {
|
|
561
|
+
plugins: [
|
|
562
|
+
workerHttpPlugin({
|
|
563
|
+
// Explicit interceptors (relative to project root)
|
|
564
|
+
interceptors: ['./src/interceptors/auth.ts', './src/interceptors/logging.ts'],
|
|
565
|
+
|
|
566
|
+
// Or auto-discover all files matching interceptor naming pattern
|
|
567
|
+
autoDiscover: true,
|
|
568
|
+
}),
|
|
569
|
+
],
|
|
570
|
+
};
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
**Options:**
|
|
574
|
+
|
|
575
|
+
| Option | Type | Default | Description |
|
|
576
|
+
| -------------- | ---------- | ------- | ------------------------------------------------------ |
|
|
577
|
+
| `interceptors` | `string[]` | `[]` | Explicit list of interceptor paths to bundle |
|
|
578
|
+
| `autoDiscover` | `boolean` | `false` | Scan `src/` for files matching `*interceptor*` pattern |
|
|
579
|
+
|
|
580
|
+
Discovered interceptors are merged with explicit ones. Test files (`.spec.ts`, `.test.ts`) are automatically excluded.
|
|
581
|
+
|
|
582
|
+
---
|
|
583
|
+
|
|
584
|
+
### `/streams-polyfill` — Safari transferable streams
|
|
585
|
+
|
|
586
|
+
Safari 16-17 lack native transferable `ReadableStream`/`TransformStream` support. This ponyfill enables stream transfer in workers for those browsers, loaded lazily only when needed.
|
|
587
|
+
|
|
588
|
+
```typescript
|
|
589
|
+
// Enable in your app config (main thread)
|
|
590
|
+
import { withWorkerStreamsPolyfill } from '@angular-helpers/worker-http/backend';
|
|
591
|
+
|
|
592
|
+
provideWorkerHttpClient(
|
|
593
|
+
withWorkerConfigs([...]),
|
|
594
|
+
withWorkerStreamsPolyfill(), // Enable Safari 16-17 compatibility
|
|
595
|
+
);
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
**When to use:**
|
|
599
|
+
|
|
600
|
+
- Your app uses `responseType: 'stream'` and targets Safari 16-17
|
|
601
|
+
- You see `DataCloneError` when transferring streams to/from workers
|
|
602
|
+
|
|
603
|
+
**Bundle impact:** Zero for modern browsers. The polyfill is lazy-loaded only on affected Safari versions when streams are actually used.
|
|
604
|
+
|
|
605
|
+
---
|
|
606
|
+
|
|
513
607
|
## SSR + hydration
|
|
514
608
|
|
|
515
609
|
Worker HTTP integrates transparently with Angular SSR. The two problems SSR
|
|
@@ -62,6 +62,15 @@ const WORKER_HTTP_INTERCEPTORS_TOKEN = new InjectionToken('WorkerHttpInterceptor
|
|
|
62
62
|
* affects the HTTP request).
|
|
63
63
|
*/
|
|
64
64
|
const WORKER_HTTP_TELEMETRY_TOKEN = new InjectionToken('WorkerHttpTelemetry', { factory: () => [] });
|
|
65
|
+
/**
|
|
66
|
+
* Enable Safari streams polyfill for transferable ReadableStream/TransformStream
|
|
67
|
+
* support in workers. Provided via `withWorkerStreamsPolyfill()`.
|
|
68
|
+
* Defaults to `false` (native streams only).
|
|
69
|
+
*
|
|
70
|
+
* When enabled, the transport dynamically imports `@angular-helpers/worker-http/streams-polyfill`
|
|
71
|
+
* on Safari 16-17 to enable stream transfer via postMessage.
|
|
72
|
+
*/
|
|
73
|
+
const WORKER_HTTP_STREAMS_POLYFILL_TOKEN = new InjectionToken('WorkerHttpStreamsPolyfill', { factory: () => false });
|
|
65
74
|
|
|
66
75
|
/**
|
|
67
76
|
* Converts an Angular `HttpRequest` into a structured-clone-safe POJO
|
|
@@ -156,6 +165,7 @@ class WorkerHttpBackend extends HttpBackend {
|
|
|
156
165
|
interceptorSpecs = inject(WORKER_HTTP_INTERCEPTORS_TOKEN);
|
|
157
166
|
fetchBackend = inject(FetchBackend, { optional: true });
|
|
158
167
|
telemetry = inject(WORKER_HTTP_TELEMETRY_TOKEN);
|
|
168
|
+
streamsPolyfill = inject(WORKER_HTTP_STREAMS_POLYFILL_TOKEN);
|
|
159
169
|
transports = new Map();
|
|
160
170
|
handle(req) {
|
|
161
171
|
if (typeof Worker === 'undefined') {
|
|
@@ -208,6 +218,7 @@ class WorkerHttpBackend extends HttpBackend {
|
|
|
208
218
|
workerFactory: () => new Worker(config.workerUrl, { type: 'module' }),
|
|
209
219
|
maxInstances: config.maxInstances ?? 1,
|
|
210
220
|
initMessage: specs.length > 0 ? { type: 'init-interceptors', specs } : undefined,
|
|
221
|
+
streamsPolyfill: this.streamsPolyfill,
|
|
211
222
|
});
|
|
212
223
|
this.transports.set(config.id, transport);
|
|
213
224
|
return transport;
|
|
@@ -558,9 +569,38 @@ function withTelemetry(telemetry) {
|
|
|
558
569
|
providers: [{ provide: WORKER_HTTP_TELEMETRY_TOKEN, useValue: telemetry, multi: true }],
|
|
559
570
|
};
|
|
560
571
|
}
|
|
572
|
+
/**
|
|
573
|
+
* Enables the Safari streams polyfill for transferable ReadableStream/TransformStream
|
|
574
|
+
* support in Web Workers.
|
|
575
|
+
*
|
|
576
|
+
* Safari 16-17 lacks native transferable streams. When this feature is enabled,
|
|
577
|
+
* the transport layer dynamically loads a ponyfill that enables stream transfer
|
|
578
|
+
* via postMessage on affected browsers.
|
|
579
|
+
*
|
|
580
|
+
* **When to use:**
|
|
581
|
+
* - Your application uses `responseType: 'stream'` and targets Safari 16-17
|
|
582
|
+
* - You see "DataCloneError" when transferring streams to/from workers
|
|
583
|
+
*
|
|
584
|
+
* **Bundle impact:** The polyfill is lazy-loaded only on Safari 16-17 when
|
|
585
|
+
* streams are actually used. Non-Safari browsers and modern Safari pay 0 bytes.
|
|
586
|
+
*
|
|
587
|
+
* @example
|
|
588
|
+
* ```typescript
|
|
589
|
+
* provideWorkerHttpClient(
|
|
590
|
+
* withWorkerConfigs([...]),
|
|
591
|
+
* withWorkerStreamsPolyfill(), // Enable for Safari compatibility
|
|
592
|
+
* )
|
|
593
|
+
* ```
|
|
594
|
+
*/
|
|
595
|
+
function withWorkerStreamsPolyfill() {
|
|
596
|
+
return {
|
|
597
|
+
kind: 'StreamsPolyfill',
|
|
598
|
+
providers: [{ provide: WORKER_HTTP_STREAMS_POLYFILL_TOKEN, useValue: true }],
|
|
599
|
+
};
|
|
600
|
+
}
|
|
561
601
|
|
|
562
602
|
/**
|
|
563
603
|
* Generated bundle index. Do not edit.
|
|
564
604
|
*/
|
|
565
605
|
|
|
566
|
-
export { WORKER_HTTP_INTERCEPTORS_TOKEN, WORKER_HTTP_SERIALIZER_TOKEN, WORKER_TARGET, WorkerHttpBackend, WorkerHttpClient, matchWorkerRoute, provideWorkerHttpClient, toHttpResponse, toSerializableRequest, withTelemetry, withWorkerConfigs, withWorkerFallback, withWorkerInterceptors, withWorkerRoutes, withWorkerSerialization };
|
|
606
|
+
export { WORKER_HTTP_INTERCEPTORS_TOKEN, WORKER_HTTP_SERIALIZER_TOKEN, WORKER_HTTP_STREAMS_POLYFILL_TOKEN, WORKER_TARGET, WorkerHttpBackend, WorkerHttpClient, matchWorkerRoute, provideWorkerHttpClient, toHttpResponse, toSerializableRequest, withTelemetry, withWorkerConfigs, withWorkerFallback, withWorkerInterceptors, withWorkerRoutes, withWorkerSerialization, withWorkerStreamsPolyfill };
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* esbuild plugin for worker-http that bundles interceptors into worker files.
|
|
6
|
+
*
|
|
7
|
+
* This plugin:
|
|
8
|
+
* 1. Intercepts worker file builds
|
|
9
|
+
* 2. Discovers interceptor files if autoDiscover is true
|
|
10
|
+
* 3. Injects interceptor imports into the worker bootstrap
|
|
11
|
+
* 4. Ensures interceptors are available in the worker's interceptor pipeline
|
|
12
|
+
*
|
|
13
|
+
* @param options - Plugin configuration
|
|
14
|
+
* @returns esbuild Plugin
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* import { workerHttpPlugin } from '@angular-helpers/worker-http/esbuild-plugin';
|
|
19
|
+
*
|
|
20
|
+
* const config = {
|
|
21
|
+
* plugins: [
|
|
22
|
+
* workerHttpPlugin({
|
|
23
|
+
* autoDiscover: true,
|
|
24
|
+
* })
|
|
25
|
+
* ]
|
|
26
|
+
* };
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
function workerHttpPlugin(options = {}) {
|
|
30
|
+
const { interceptors = [], autoDiscover = false } = options;
|
|
31
|
+
return {
|
|
32
|
+
name: 'worker-http',
|
|
33
|
+
setup(build) {
|
|
34
|
+
const filter = /\.worker\.(ts|js|mjs)$/;
|
|
35
|
+
const discoveredInterceptors = [];
|
|
36
|
+
// Auto-discover interceptors from src directory
|
|
37
|
+
if (autoDiscover) {
|
|
38
|
+
const rootDir = build.initialOptions.absWorkingDir ?? process.cwd();
|
|
39
|
+
discoveredInterceptors.push(...discoverInterceptors(rootDir));
|
|
40
|
+
}
|
|
41
|
+
const allInterceptors = [...new Set([...interceptors, ...discoveredInterceptors])];
|
|
42
|
+
// Intercept worker file loads to inject interceptor imports
|
|
43
|
+
build.onLoad({ filter }, async (args) => {
|
|
44
|
+
let contents = await fs.promises.readFile(args.path, 'utf-8');
|
|
45
|
+
if (allInterceptors.length > 0) {
|
|
46
|
+
const importStatements = generateInterceptorImports(allInterceptors, args.path);
|
|
47
|
+
contents = injectImports(contents, importStatements);
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
contents,
|
|
51
|
+
loader: 'ts',
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Discovers interceptor files by scanning src directories.
|
|
59
|
+
* Looks for files matching interceptor naming patterns
|
|
60
|
+
* in TypeScript or JavaScript files.
|
|
61
|
+
*/
|
|
62
|
+
function discoverInterceptors(rootDir) {
|
|
63
|
+
const interceptors = [];
|
|
64
|
+
const srcDir = path.join(rootDir, 'src');
|
|
65
|
+
if (!fs.existsSync(srcDir)) {
|
|
66
|
+
return interceptors;
|
|
67
|
+
}
|
|
68
|
+
scanDirectory(srcDir, interceptors, rootDir);
|
|
69
|
+
return interceptors;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Recursively scans a directory for interceptor files.
|
|
73
|
+
*/
|
|
74
|
+
function scanDirectory(dir, interceptors, rootDir) {
|
|
75
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
76
|
+
for (const entry of entries) {
|
|
77
|
+
const fullPath = path.join(dir, entry.name);
|
|
78
|
+
if (entry.isDirectory()) {
|
|
79
|
+
// Skip node_modules and dist
|
|
80
|
+
if (entry.name === 'node_modules' || entry.name === 'dist') {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
scanDirectory(fullPath, interceptors, rootDir);
|
|
84
|
+
}
|
|
85
|
+
else if (entry.isFile()) {
|
|
86
|
+
const isInterceptorFile = /interceptor/i.test(entry.name) &&
|
|
87
|
+
(entry.name.endsWith('.ts') || entry.name.endsWith('.js') || entry.name.endsWith('.mjs')) &&
|
|
88
|
+
!entry.name.endsWith('.spec.ts') &&
|
|
89
|
+
!entry.name.endsWith('.test.ts') &&
|
|
90
|
+
!entry.name.endsWith('.d.ts');
|
|
91
|
+
if (isInterceptorFile) {
|
|
92
|
+
// Get relative path from project root
|
|
93
|
+
const relativePath = path.relative(rootDir, fullPath);
|
|
94
|
+
interceptors.push('./' + relativePath.replace(/\\/g, '/'));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Generates import statements for discovered interceptors.
|
|
101
|
+
* Converts relative paths to valid import specifiers.
|
|
102
|
+
*/
|
|
103
|
+
function generateInterceptorImports(interceptorPaths, workerFilePath) {
|
|
104
|
+
return interceptorPaths.map((interceptorPath, index) => {
|
|
105
|
+
// Create a valid identifier from the path
|
|
106
|
+
const identifier = `interceptor_${index}_${interceptorPath.replace(/[^a-zA-Z0-9]/g, '_')}`;
|
|
107
|
+
return `import ${identifier} from '${interceptorPath}';`;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Injects import statements at the top of the worker file.
|
|
112
|
+
* Preserves any existing imports.
|
|
113
|
+
*/
|
|
114
|
+
function injectImports(contents, importStatements) {
|
|
115
|
+
if (importStatements.length === 0) {
|
|
116
|
+
return contents;
|
|
117
|
+
}
|
|
118
|
+
const imports = importStatements.join('\n');
|
|
119
|
+
const marker = '// Auto-injected by worker-http esbuild plugin';
|
|
120
|
+
// Check if already injected
|
|
121
|
+
if (contents.includes(marker)) {
|
|
122
|
+
return contents;
|
|
123
|
+
}
|
|
124
|
+
const injection = `${marker}\n${imports}\n`;
|
|
125
|
+
// Find the first non-comment, non-whitespace line to insert before
|
|
126
|
+
const lines = contents.split('\n');
|
|
127
|
+
let insertIndex = 0;
|
|
128
|
+
for (let i = 0; i < lines.length; i++) {
|
|
129
|
+
const line = lines[i].trim();
|
|
130
|
+
// Skip shebang, comments, and empty lines
|
|
131
|
+
if (line.startsWith('#!') ||
|
|
132
|
+
line.startsWith('//') ||
|
|
133
|
+
line.startsWith('/*') ||
|
|
134
|
+
line.startsWith('*') ||
|
|
135
|
+
line === '') {
|
|
136
|
+
insertIndex = i + 1;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
lines.splice(insertIndex, 0, injection);
|
|
142
|
+
return lines.join('\n');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* esbuild plugin for @angular-helpers/worker-http
|
|
147
|
+
*
|
|
148
|
+
* Automatically bundles worker interceptors and injects them into the worker bootstrap.
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* ```typescript
|
|
152
|
+
* // angular.json custom webpack config
|
|
153
|
+
* import { workerHttpPlugin } from '@angular-helpers/worker-http/esbuild-plugin';
|
|
154
|
+
*
|
|
155
|
+
* export default {
|
|
156
|
+
* plugins: [
|
|
157
|
+
* workerHttpPlugin({
|
|
158
|
+
* interceptors: ['./src/interceptors/auth.ts'],
|
|
159
|
+
* autoDiscover: true
|
|
160
|
+
* })
|
|
161
|
+
* ]
|
|
162
|
+
* };
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Generated bundle index. Do not edit.
|
|
168
|
+
*/
|
|
169
|
+
|
|
170
|
+
export { workerHttpPlugin };
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detects if the current browser needs the streams ponyfill.
|
|
3
|
+
*
|
|
4
|
+
* Safari 16-17 fails to transfer `ReadableStream`/`TransformStream`
|
|
5
|
+
* to/from Web Workers via `structuredClone` because they lack
|
|
6
|
+
* the transferable streams implementation.
|
|
7
|
+
*
|
|
8
|
+
* @param userAgent - Optional user agent string for testing (defaults to feature detection)
|
|
9
|
+
* @returns `true` if ponyfill is needed, `false` if native works
|
|
10
|
+
*/
|
|
11
|
+
function needsPolyfill(userAgent) {
|
|
12
|
+
// If userAgent provided (testing mode), use UA-based detection
|
|
13
|
+
if (userAgent !== undefined) {
|
|
14
|
+
return isLegacySafari(userAgent);
|
|
15
|
+
}
|
|
16
|
+
// Quick check: if we're not in a browser context, assume no polyfill needed
|
|
17
|
+
if (typeof ReadableStream === 'undefined') {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const rs = new ReadableStream({
|
|
22
|
+
start(controller) {
|
|
23
|
+
controller.close();
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
// Safari < 18 lacks the transferable streams spec extension
|
|
27
|
+
// The presence of 'getReader' alone isn't enough — we need to verify
|
|
28
|
+
// that the stream can be transferred via postMessage
|
|
29
|
+
const channel = new MessageChannel();
|
|
30
|
+
let transferable = true;
|
|
31
|
+
// Try to transfer — this throws on Safari 16-17
|
|
32
|
+
try {
|
|
33
|
+
channel.port1.postMessage(rs, [rs]);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
transferable = false;
|
|
37
|
+
}
|
|
38
|
+
channel.port1.close();
|
|
39
|
+
channel.port2.close();
|
|
40
|
+
return !transferable;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Any error means we should try polyfill
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* User-agent based detection for Safari.
|
|
49
|
+
* Used as a fast-path before the more expensive transfer test.
|
|
50
|
+
*
|
|
51
|
+
* @param userAgent - Optional user agent string (defaults to navigator.userAgent)
|
|
52
|
+
*/
|
|
53
|
+
function isSafari(userAgent) {
|
|
54
|
+
const ua = userAgent ?? (typeof navigator !== 'undefined' ? navigator.userAgent : '');
|
|
55
|
+
return /Safari/.test(ua) && !/Chrome/.test(ua) && !/Chromium/.test(ua);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Check if Safari version is known to need polyfill (< 18).
|
|
59
|
+
*
|
|
60
|
+
* @param userAgent - Optional user agent string (defaults to navigator.userAgent)
|
|
61
|
+
*/
|
|
62
|
+
function isLegacySafari(userAgent) {
|
|
63
|
+
if (!isSafari(userAgent)) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
const ua = userAgent ?? (typeof navigator !== 'undefined' ? navigator.userAgent : '');
|
|
67
|
+
const match = /Version\/(\d+)/.exec(ua);
|
|
68
|
+
if (!match) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
const version = Number.parseInt(match[1], 10);
|
|
72
|
+
return version < 18;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Ponyfill for Web Streams API that supports transfer to/from workers.
|
|
77
|
+
*
|
|
78
|
+
* Uses `web-streams-polyfill` internally but only loads it when needed.
|
|
79
|
+
* This keeps bundle size small for non-Safari browsers.
|
|
80
|
+
*
|
|
81
|
+
* @see {@link needsPolyfill} for detection
|
|
82
|
+
*/
|
|
83
|
+
let cachedPonyfill = null;
|
|
84
|
+
/**
|
|
85
|
+
* Lazily loads the web-streams-polyfill ponyfill.
|
|
86
|
+
*
|
|
87
|
+
* @returns Ponyfilled streams or native if not needed
|
|
88
|
+
*/
|
|
89
|
+
async function ponyfillStreams() {
|
|
90
|
+
if (cachedPonyfill) {
|
|
91
|
+
return cachedPonyfill;
|
|
92
|
+
}
|
|
93
|
+
// Dynamic import to avoid bundling polyfill for non-Safari users
|
|
94
|
+
const { ReadableStream, TransformStream, WritableStream } = await import('web-streams-polyfill');
|
|
95
|
+
cachedPonyfill = {
|
|
96
|
+
ReadableStream,
|
|
97
|
+
TransformStream,
|
|
98
|
+
WritableStream,
|
|
99
|
+
};
|
|
100
|
+
return cachedPonyfill;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Safari Transferable Streams Ponyfill
|
|
105
|
+
*
|
|
106
|
+
* Provides `ReadableStream` and `TransformStream` implementations that support
|
|
107
|
+
* `structuredClone` transfer to/from Web Workers on Safari 16-17.
|
|
108
|
+
*
|
|
109
|
+
* This is a **ponyfill** (not a polyfill) — it does not modify global scope.
|
|
110
|
+
* Native implementations are re-exported when available.
|
|
111
|
+
*
|
|
112
|
+
* Usage:
|
|
113
|
+
* ```typescript
|
|
114
|
+
* import '@angular-helpers/worker-http/streams-polyfill';
|
|
115
|
+
* ```
|
|
116
|
+
* Or let the transport auto-inject when `safariPolyfill: true` is configured.
|
|
117
|
+
*
|
|
118
|
+
* @see {@link detect} for feature detection
|
|
119
|
+
*/
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Generated bundle index. Do not edit.
|
|
123
|
+
*/
|
|
124
|
+
|
|
125
|
+
export { needsPolyfill, ponyfillStreams };
|
|
@@ -134,6 +134,8 @@ function createWorkerTransport(config) {
|
|
|
134
134
|
const maxInstances = Math.min(config.maxInstances ?? 1, typeof navigator !== 'undefined' ? (navigator.hardwareConcurrency ?? 4) : 1);
|
|
135
135
|
const requestTimeout = config.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
136
136
|
const transferDetection = config.transferDetection ?? 'none';
|
|
137
|
+
const streamsPolyfill = config.streamsPolyfill ?? false;
|
|
138
|
+
let polyfillLoaded = false;
|
|
137
139
|
function createWorker() {
|
|
138
140
|
if (config.workerFactory) {
|
|
139
141
|
return config.workerFactory();
|
|
@@ -167,6 +169,12 @@ function createWorkerTransport(config) {
|
|
|
167
169
|
}
|
|
168
170
|
const requestId = crypto.randomUUID();
|
|
169
171
|
return new Observable((subscriber) => {
|
|
172
|
+
// Lazy-load polyfill on first request if enabled
|
|
173
|
+
if (streamsPolyfill && !polyfillLoaded) {
|
|
174
|
+
loadStreamsPolyfill().catch((err) => {
|
|
175
|
+
console.warn('[worker-http] Streams polyfill failed to load:', err);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
170
178
|
const worker = getOrCreateWorker();
|
|
171
179
|
let settled = false;
|
|
172
180
|
let timeoutHandle;
|
|
@@ -233,6 +241,26 @@ function createWorkerTransport(config) {
|
|
|
233
241
|
};
|
|
234
242
|
});
|
|
235
243
|
}
|
|
244
|
+
/**
|
|
245
|
+
* Lazy-loads the streams polyfill if enabled and not already loaded.
|
|
246
|
+
* Called before operations that might involve stream transfer.
|
|
247
|
+
*/
|
|
248
|
+
async function loadStreamsPolyfill() {
|
|
249
|
+
if (!streamsPolyfill || polyfillLoaded) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
try {
|
|
253
|
+
const { needsPolyfill, ponyfillStreams } = await import('@angular-helpers/worker-http/streams-polyfill');
|
|
254
|
+
if (needsPolyfill()) {
|
|
255
|
+
await ponyfillStreams();
|
|
256
|
+
}
|
|
257
|
+
polyfillLoaded = true;
|
|
258
|
+
}
|
|
259
|
+
catch (err) {
|
|
260
|
+
// Log warning but don't block request — streams will fail naturally
|
|
261
|
+
console.warn('[worker-http] Failed to load streams polyfill:', err);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
236
264
|
function terminate() {
|
|
237
265
|
terminated = true;
|
|
238
266
|
for (const worker of workers) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,53 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@angular-helpers/worker-http",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "21.0.0",
|
|
4
4
|
"description": "Angular HTTP over Web Workers — off-main-thread HTTP pipelines with configurable interceptors, WebCrypto security, and pluggable serialization",
|
|
5
|
+
"schematics": "./schematics/collection.json",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./types/angular-helpers-worker-http.d.ts",
|
|
9
|
+
"esm": "./esm/src/index.js",
|
|
10
|
+
"default": "./fesm2022/angular-helpers-worker-http.mjs"
|
|
11
|
+
},
|
|
12
|
+
"./backend": {
|
|
13
|
+
"types": "./types/angular-helpers-worker-http-backend.d.ts",
|
|
14
|
+
"esm": "./esm/backend/src/index.js",
|
|
15
|
+
"default": "./fesm2022/angular-helpers-worker-http-backend.mjs"
|
|
16
|
+
},
|
|
17
|
+
"./transport": {
|
|
18
|
+
"types": "./types/angular-helpers-worker-http-transport.d.ts",
|
|
19
|
+
"esm": "./esm/transport/src/index.js",
|
|
20
|
+
"default": "./fesm2022/angular-helpers-worker-http-transport.mjs"
|
|
21
|
+
},
|
|
22
|
+
"./interceptors": {
|
|
23
|
+
"types": "./types/angular-helpers-worker-http-interceptors.d.ts",
|
|
24
|
+
"esm": "./esm/interceptors/src/index.js",
|
|
25
|
+
"default": "./fesm2022/angular-helpers-worker-http-interceptors.mjs"
|
|
26
|
+
},
|
|
27
|
+
"./crypto": {
|
|
28
|
+
"types": "./types/angular-helpers-worker-http-crypto.d.ts",
|
|
29
|
+
"esm": "./esm/crypto/src/index.js",
|
|
30
|
+
"default": "./fesm2022/angular-helpers-worker-http-crypto.mjs"
|
|
31
|
+
},
|
|
32
|
+
"./serializer": {
|
|
33
|
+
"types": "./types/angular-helpers-worker-http-serializer.d.ts",
|
|
34
|
+
"esm": "./esm/serializer/src/index.js",
|
|
35
|
+
"default": "./fesm2022/angular-helpers-worker-http-serializer.mjs"
|
|
36
|
+
},
|
|
37
|
+
"./esbuild-plugin": {
|
|
38
|
+
"types": "./types/angular-helpers-worker-http-esbuild-plugin.d.ts",
|
|
39
|
+
"esm": "./esm/esbuild-plugin/src/index.js",
|
|
40
|
+
"default": "./fesm2022/angular-helpers-worker-http-esbuild-plugin.mjs"
|
|
41
|
+
},
|
|
42
|
+
"./streams-polyfill": {
|
|
43
|
+
"types": "./types/angular-helpers-worker-http-streams-polyfill.d.ts",
|
|
44
|
+
"esm": "./esm/streams-polyfill/src/index.js",
|
|
45
|
+
"default": "./fesm2022/angular-helpers-worker-http-streams-polyfill.mjs"
|
|
46
|
+
},
|
|
47
|
+
"./package.json": {
|
|
48
|
+
"default": "./package.json"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
5
51
|
"keywords": [
|
|
6
52
|
"angular",
|
|
7
53
|
"web-worker",
|
|
@@ -41,39 +87,16 @@
|
|
|
41
87
|
},
|
|
42
88
|
"seroval": {
|
|
43
89
|
"optional": true
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
|
-
"module": "fesm2022/angular-helpers-worker-http.mjs",
|
|
47
|
-
"typings": "types/angular-helpers-worker-http.d.ts",
|
|
48
|
-
"exports": {
|
|
49
|
-
"./package.json": {
|
|
50
|
-
"default": "./package.json"
|
|
51
|
-
},
|
|
52
|
-
".": {
|
|
53
|
-
"types": "./types/angular-helpers-worker-http.d.ts",
|
|
54
|
-
"default": "./fesm2022/angular-helpers-worker-http.mjs"
|
|
55
|
-
},
|
|
56
|
-
"./backend": {
|
|
57
|
-
"types": "./types/angular-helpers-worker-http-backend.d.ts",
|
|
58
|
-
"default": "./fesm2022/angular-helpers-worker-http-backend.mjs"
|
|
59
|
-
},
|
|
60
|
-
"./crypto": {
|
|
61
|
-
"types": "./types/angular-helpers-worker-http-crypto.d.ts",
|
|
62
|
-
"default": "./fesm2022/angular-helpers-worker-http-crypto.mjs"
|
|
63
|
-
},
|
|
64
|
-
"./interceptors": {
|
|
65
|
-
"types": "./types/angular-helpers-worker-http-interceptors.d.ts",
|
|
66
|
-
"default": "./fesm2022/angular-helpers-worker-http-interceptors.mjs"
|
|
67
90
|
},
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"default": "./fesm2022/angular-helpers-worker-http-serializer.mjs"
|
|
91
|
+
"esbuild": {
|
|
92
|
+
"optional": true
|
|
71
93
|
},
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
"default": "./fesm2022/angular-helpers-worker-http-transport.mjs"
|
|
94
|
+
"web-streams-polyfill": {
|
|
95
|
+
"optional": true
|
|
75
96
|
}
|
|
76
97
|
},
|
|
98
|
+
"module": "fesm2022/angular-helpers-worker-http.mjs",
|
|
99
|
+
"typings": "types/angular-helpers-worker-http.d.ts",
|
|
77
100
|
"sideEffects": false,
|
|
78
101
|
"dependencies": {
|
|
79
102
|
"tslib": "^2.3.0"
|
|
@@ -9,7 +9,7 @@ import { Observable } from 'rxjs';
|
|
|
9
9
|
* Discriminated union for worker HTTP feature kinds.
|
|
10
10
|
* Mirrors Angular's HttpFeatureKind pattern.
|
|
11
11
|
*/
|
|
12
|
-
type WorkerHttpFeatureKind = 'WorkerConfigs' | 'WorkerRoutes' | 'WorkerFallback' | 'WorkerSerialization' | 'WorkerInterceptors' | 'Telemetry';
|
|
12
|
+
type WorkerHttpFeatureKind = 'WorkerConfigs' | 'WorkerRoutes' | 'WorkerFallback' | 'WorkerSerialization' | 'WorkerInterceptors' | 'Telemetry' | 'StreamsPolyfill';
|
|
13
13
|
/**
|
|
14
14
|
* Feature object — mirrors Angular's HttpFeature<K> shape.
|
|
15
15
|
*/
|
|
@@ -182,6 +182,15 @@ declare const WORKER_HTTP_SERIALIZER_TOKEN: InjectionToken<WorkerSerializer>;
|
|
|
182
182
|
* Defaults to an empty map (no interceptors).
|
|
183
183
|
*/
|
|
184
184
|
declare const WORKER_HTTP_INTERCEPTORS_TOKEN: InjectionToken<Readonly<Record<string, readonly WorkerInterceptorSpec[]>>>;
|
|
185
|
+
/**
|
|
186
|
+
* Enable Safari streams polyfill for transferable ReadableStream/TransformStream
|
|
187
|
+
* support in workers. Provided via `withWorkerStreamsPolyfill()`.
|
|
188
|
+
* Defaults to `false` (native streams only).
|
|
189
|
+
*
|
|
190
|
+
* When enabled, the transport dynamically imports `@angular-helpers/worker-http/streams-polyfill`
|
|
191
|
+
* on Safari 16-17 to enable stream transfer via postMessage.
|
|
192
|
+
*/
|
|
193
|
+
declare const WORKER_HTTP_STREAMS_POLYFILL_TOKEN: InjectionToken<boolean>;
|
|
185
194
|
|
|
186
195
|
/**
|
|
187
196
|
* Sets up the worker HTTP infrastructure and replaces Angular's `HttpBackend`
|
|
@@ -345,6 +354,30 @@ declare function withWorkerInterceptors(specs: readonly WorkerInterceptorSpec[]
|
|
|
345
354
|
* ```
|
|
346
355
|
*/
|
|
347
356
|
declare function withTelemetry(telemetry: WorkerHttpTelemetry): WorkerHttpFeature<'Telemetry'>;
|
|
357
|
+
/**
|
|
358
|
+
* Enables the Safari streams polyfill for transferable ReadableStream/TransformStream
|
|
359
|
+
* support in Web Workers.
|
|
360
|
+
*
|
|
361
|
+
* Safari 16-17 lacks native transferable streams. When this feature is enabled,
|
|
362
|
+
* the transport layer dynamically loads a ponyfill that enables stream transfer
|
|
363
|
+
* via postMessage on affected browsers.
|
|
364
|
+
*
|
|
365
|
+
* **When to use:**
|
|
366
|
+
* - Your application uses `responseType: 'stream'` and targets Safari 16-17
|
|
367
|
+
* - You see "DataCloneError" when transferring streams to/from workers
|
|
368
|
+
*
|
|
369
|
+
* **Bundle impact:** The polyfill is lazy-loaded only on Safari 16-17 when
|
|
370
|
+
* streams are actually used. Non-Safari browsers and modern Safari pay 0 bytes.
|
|
371
|
+
*
|
|
372
|
+
* @example
|
|
373
|
+
* ```typescript
|
|
374
|
+
* provideWorkerHttpClient(
|
|
375
|
+
* withWorkerConfigs([...]),
|
|
376
|
+
* withWorkerStreamsPolyfill(), // Enable for Safari compatibility
|
|
377
|
+
* )
|
|
378
|
+
* ```
|
|
379
|
+
*/
|
|
380
|
+
declare function withWorkerStreamsPolyfill(): WorkerHttpFeature<'StreamsPolyfill'>;
|
|
348
381
|
|
|
349
382
|
/**
|
|
350
383
|
* Angular `HttpBackend` replacement that routes HTTP requests to web workers.
|
|
@@ -366,6 +399,7 @@ declare class WorkerHttpBackend extends HttpBackend implements OnDestroy {
|
|
|
366
399
|
private readonly interceptorSpecs;
|
|
367
400
|
private readonly fetchBackend;
|
|
368
401
|
private readonly telemetry;
|
|
402
|
+
private readonly streamsPolyfill;
|
|
369
403
|
private readonly transports;
|
|
370
404
|
handle(req: HttpRequest<unknown>): Observable<HttpEvent<unknown>>;
|
|
371
405
|
ngOnDestroy(): void;
|
|
@@ -460,5 +494,5 @@ declare function matchWorkerRoute(url: string, routes: Array<{
|
|
|
460
494
|
priority?: number;
|
|
461
495
|
}>): string | null;
|
|
462
496
|
|
|
463
|
-
export { WORKER_HTTP_INTERCEPTORS_TOKEN, WORKER_HTTP_SERIALIZER_TOKEN, WORKER_TARGET, WorkerHttpBackend, WorkerHttpClient, matchWorkerRoute, provideWorkerHttpClient, toHttpResponse, toSerializableRequest, withTelemetry, withWorkerConfigs, withWorkerFallback, withWorkerInterceptors, withWorkerRoutes, withWorkerSerialization };
|
|
497
|
+
export { WORKER_HTTP_INTERCEPTORS_TOKEN, WORKER_HTTP_SERIALIZER_TOKEN, WORKER_HTTP_STREAMS_POLYFILL_TOKEN, WORKER_TARGET, WorkerHttpBackend, WorkerHttpClient, matchWorkerRoute, provideWorkerHttpClient, toHttpResponse, toSerializableRequest, withTelemetry, withWorkerConfigs, withWorkerFallback, withWorkerInterceptors, withWorkerRoutes, withWorkerSerialization, withWorkerStreamsPolyfill };
|
|
464
498
|
export type { SerializableRequest, SerializableResponse, WorkerConfig, WorkerFallbackStrategy, WorkerHttpErrorEvent, WorkerHttpFeature, WorkerHttpFeatureKind, WorkerHttpRequestEvent, WorkerHttpResponseEvent, WorkerHttpTelemetry, WorkerHttpTelemetryEventBase, WorkerHttpTransportKind, WorkerInterceptorSpecsMap, WorkerRequestOptions, WorkerRoute };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Plugin } from 'esbuild';
|
|
2
|
+
|
|
3
|
+
interface WorkerHttpPluginOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Explicit list of interceptor file paths to bundle into the worker.
|
|
6
|
+
* Paths are relative to project root.
|
|
7
|
+
* @example ['./src/interceptors/auth.ts', './src/interceptors/logging.ts']
|
|
8
|
+
*/
|
|
9
|
+
interceptors?: string[];
|
|
10
|
+
/**
|
|
11
|
+
* Auto-discover interceptors by scanning src folders for files
|
|
12
|
+
* matching the interceptor naming pattern.
|
|
13
|
+
* Discovered interceptors are merged with explicit interceptors list.
|
|
14
|
+
* @default false
|
|
15
|
+
*/
|
|
16
|
+
autoDiscover?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* esbuild plugin for worker-http that bundles interceptors into worker files.
|
|
20
|
+
*
|
|
21
|
+
* This plugin:
|
|
22
|
+
* 1. Intercepts worker file builds
|
|
23
|
+
* 2. Discovers interceptor files if autoDiscover is true
|
|
24
|
+
* 3. Injects interceptor imports into the worker bootstrap
|
|
25
|
+
* 4. Ensures interceptors are available in the worker's interceptor pipeline
|
|
26
|
+
*
|
|
27
|
+
* @param options - Plugin configuration
|
|
28
|
+
* @returns esbuild Plugin
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* import { workerHttpPlugin } from '@angular-helpers/worker-http/esbuild-plugin';
|
|
33
|
+
*
|
|
34
|
+
* const config = {
|
|
35
|
+
* plugins: [
|
|
36
|
+
* workerHttpPlugin({
|
|
37
|
+
* autoDiscover: true,
|
|
38
|
+
* })
|
|
39
|
+
* ]
|
|
40
|
+
* };
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
declare function workerHttpPlugin(options?: WorkerHttpPluginOptions): Plugin;
|
|
44
|
+
|
|
45
|
+
export { workerHttpPlugin };
|
|
46
|
+
export type { WorkerHttpPluginOptions };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detects if the current browser needs the streams ponyfill.
|
|
3
|
+
*
|
|
4
|
+
* Safari 16-17 fails to transfer `ReadableStream`/`TransformStream`
|
|
5
|
+
* to/from Web Workers via `structuredClone` because they lack
|
|
6
|
+
* the transferable streams implementation.
|
|
7
|
+
*
|
|
8
|
+
* @param userAgent - Optional user agent string for testing (defaults to feature detection)
|
|
9
|
+
* @returns `true` if ponyfill is needed, `false` if native works
|
|
10
|
+
*/
|
|
11
|
+
declare function needsPolyfill(userAgent?: string): boolean;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Ponyfill for Web Streams API that supports transfer to/from workers.
|
|
15
|
+
*
|
|
16
|
+
* Uses `web-streams-polyfill` internally but only loads it when needed.
|
|
17
|
+
* This keeps bundle size small for non-Safari browsers.
|
|
18
|
+
*
|
|
19
|
+
* @see {@link needsPolyfill} for detection
|
|
20
|
+
*/
|
|
21
|
+
interface StreamPonyfillExports {
|
|
22
|
+
ReadableStream: typeof ReadableStream;
|
|
23
|
+
TransformStream: typeof TransformStream;
|
|
24
|
+
WritableStream: typeof WritableStream;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Lazily loads the web-streams-polyfill ponyfill.
|
|
28
|
+
*
|
|
29
|
+
* @returns Ponyfilled streams or native if not needed
|
|
30
|
+
*/
|
|
31
|
+
declare function ponyfillStreams(): Promise<StreamPonyfillExports>;
|
|
32
|
+
|
|
33
|
+
export { needsPolyfill, ponyfillStreams };
|
|
34
|
+
export type { StreamPonyfillExports };
|
|
@@ -68,6 +68,14 @@ interface WorkerTransportConfig {
|
|
|
68
68
|
type: string;
|
|
69
69
|
[key: string]: unknown;
|
|
70
70
|
};
|
|
71
|
+
/**
|
|
72
|
+
* Enable Safari streams polyfill for transferable ReadableStream/TransformStream
|
|
73
|
+
* support. When `true`, the transport lazy-loads the ponyfill on Safari 16-17
|
|
74
|
+
* to enable stream transfer via postMessage.
|
|
75
|
+
*
|
|
76
|
+
* @default false
|
|
77
|
+
*/
|
|
78
|
+
streamsPolyfill?: boolean;
|
|
71
79
|
}
|
|
72
80
|
/**
|
|
73
81
|
* Message sent from main thread to worker.
|