@angular-helpers/worker-http 0.6.0 → 0.7.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 +33 -2
- package/fesm2022/angular-helpers-worker-http-backend.mjs +1 -0
- package/fesm2022/angular-helpers-worker-http-crypto.mjs +5 -1
- package/fesm2022/angular-helpers-worker-http-interceptors.mjs +13 -6
- package/fesm2022/angular-helpers-worker-http-transport.mjs +150 -10
- package/fesm2022/angular-helpers-worker-http.mjs +0 -17
- package/package.json +1 -1
- package/types/angular-helpers-worker-http-transport.d.ts +67 -5
- package/types/angular-helpers-worker-http.d.ts +1 -15
package/README.md
CHANGED
|
@@ -67,9 +67,40 @@ transport.terminate();
|
|
|
67
67
|
**Features:**
|
|
68
68
|
|
|
69
69
|
- Round-robin pool (`maxInstances`) for parallel request handling
|
|
70
|
-
- Request cancellation via `AbortController` in the worker
|
|
71
|
-
- Automatic `Transferable` detection for zero-copy `ArrayBuffer` transfer
|
|
72
70
|
- Lazy worker instantiation — no worker created until first request
|
|
71
|
+
- **Cancellation that actually aborts `fetch()`** — unsubscribing posts a
|
|
72
|
+
cancel message; the worker-side message loop threads an `AbortSignal` all
|
|
73
|
+
the way into `fetch()` so the in-flight HTTP request is truly aborted
|
|
74
|
+
- **Per-request timeout** (default `30_000` ms) via `requestTimeout`; errors
|
|
75
|
+
with `WorkerHttpTimeoutError` and sends a cancel message to the worker.
|
|
76
|
+
Set to `0` to disable.
|
|
77
|
+
- **Opt-in transferable detection** via `transferDetection: 'auto'` — passes
|
|
78
|
+
detected `ArrayBuffer` / `MessagePort` / `ImageBitmap` /
|
|
79
|
+
`OffscreenCanvas` / streams as the transfer list of `postMessage`, enabling
|
|
80
|
+
zero-copy transfer of large buffers. Default is `'none'` to preserve the
|
|
81
|
+
caller's access to the original data after post.
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import {
|
|
85
|
+
createWorkerTransport,
|
|
86
|
+
WorkerHttpTimeoutError,
|
|
87
|
+
} from '@angular-helpers/worker-http/transport';
|
|
88
|
+
|
|
89
|
+
const transport = createWorkerTransport({
|
|
90
|
+
workerUrl: new URL('./workers/api.worker', import.meta.url),
|
|
91
|
+
maxInstances: 2,
|
|
92
|
+
requestTimeout: 10_000, // override default 30 s
|
|
93
|
+
transferDetection: 'auto', // zero-copy ArrayBuffer at postMessage
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
transport.execute(request).subscribe({
|
|
97
|
+
error: (err) => {
|
|
98
|
+
if (err instanceof WorkerHttpTimeoutError) {
|
|
99
|
+
// dedicated timeout handling
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
```
|
|
73
104
|
|
|
74
105
|
---
|
|
75
106
|
|
|
@@ -285,6 +285,7 @@ class WorkerHttpBackend extends HttpBackend {
|
|
|
285
285
|
}
|
|
286
286
|
catch (telemetryError) {
|
|
287
287
|
// A throwing telemetry subscriber must never affect the HTTP request.
|
|
288
|
+
// oxlint-disable-next-line no-console -- defensive log when user-provided telemetry throws
|
|
288
289
|
console.error('[WorkerHttpBackend] telemetry subscriber threw:', telemetryError);
|
|
289
290
|
}
|
|
290
291
|
}
|
|
@@ -80,9 +80,13 @@ async function createAesEncryptor(config) {
|
|
|
80
80
|
? new Uint8Array(config.keyMaterial.buffer.slice(0))
|
|
81
81
|
: new Uint8Array(config.keyMaterial);
|
|
82
82
|
const cryptoKey = await crypto.subtle.importKey('raw', keyMaterial, { name: algorithm, length: keyLength }, false, ['encrypt', 'decrypt']);
|
|
83
|
+
// AES-GCM uses a 96-bit (12-byte) IV per NIST SP 800-38D recommendation.
|
|
84
|
+
// AES-CBC and AES-CTR require exactly one block (16 bytes) for their IV /
|
|
85
|
+
// counter respectively; passing 12 bytes causes `OperationError`.
|
|
86
|
+
const ivLength = algorithm === 'AES-GCM' ? 12 : 16;
|
|
83
87
|
return {
|
|
84
88
|
async encrypt(data) {
|
|
85
|
-
const iv = crypto.getRandomValues(new Uint8Array(
|
|
89
|
+
const iv = crypto.getRandomValues(new Uint8Array(ivLength));
|
|
86
90
|
const params = algorithm === 'AES-GCM'
|
|
87
91
|
? { name: algorithm, iv }
|
|
88
92
|
: algorithm === 'AES-CBC'
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Composes interceptor functions around a final handler, producing a single
|
|
3
|
-
* `(req) => Promise<resp>` chain. Pure — no side effects.
|
|
3
|
+
* `(req, signal?) => Promise<resp>` chain. Pure — no side effects.
|
|
4
|
+
*
|
|
5
|
+
* The optional `signal` is threaded through interceptor boundaries automatically
|
|
6
|
+
* via a wrapper around `next`, so legacy `WorkerInterceptorFn` implementations
|
|
7
|
+
* (which take only `req, next`) keep working and still propagate cancellation
|
|
8
|
+
* when they invoke `next(req)`.
|
|
4
9
|
*/
|
|
5
10
|
function buildChain(fns, finalHandler) {
|
|
6
|
-
return fns.reduceRight((next, interceptor) => (req) => interceptor(req, next), finalHandler);
|
|
11
|
+
return fns.reduceRight((next, interceptor) => (req, signal) => interceptor(req, (r) => next(r, signal)), finalHandler);
|
|
7
12
|
}
|
|
8
13
|
/**
|
|
9
14
|
* Performs the actual `fetch()` call inside the worker, translating the
|
|
@@ -98,7 +103,7 @@ function attachRequestLoop(chain) {
|
|
|
98
103
|
const controller = new AbortController();
|
|
99
104
|
controllers.set(requestId, controller);
|
|
100
105
|
try {
|
|
101
|
-
const response = await chain(payload);
|
|
106
|
+
const response = await chain(payload, controller.signal);
|
|
102
107
|
self.postMessage({ type: 'response', requestId, result: response });
|
|
103
108
|
}
|
|
104
109
|
catch (error) {
|
|
@@ -145,7 +150,7 @@ function attachRequestLoop(chain) {
|
|
|
145
150
|
* ```
|
|
146
151
|
*/
|
|
147
152
|
function createWorkerPipeline(interceptors) {
|
|
148
|
-
const chain = buildChain(interceptors, (req) => executeFetch(req));
|
|
153
|
+
const chain = buildChain(interceptors, (req, signal) => executeFetch(req, signal));
|
|
149
154
|
attachRequestLoop(chain);
|
|
150
155
|
}
|
|
151
156
|
|
|
@@ -325,7 +330,9 @@ function hmacSigningInterceptor(config) {
|
|
|
325
330
|
* ```
|
|
326
331
|
*/
|
|
327
332
|
function loggingInterceptor(config) {
|
|
328
|
-
const logger = config?.logger ??
|
|
333
|
+
const logger = config?.logger ??
|
|
334
|
+
// oxlint-disable-next-line no-console -- default logger of a logging interceptor; consumers inject their own via config.logger
|
|
335
|
+
((msg, data) => console.log(msg, data));
|
|
329
336
|
const includeHeaders = config?.includeHeaders ?? false;
|
|
330
337
|
function safeLog(message, data) {
|
|
331
338
|
try {
|
|
@@ -538,7 +545,7 @@ function createConfigurableWorkerPipeline() {
|
|
|
538
545
|
if (data.type === INIT_MESSAGE_TYPE) {
|
|
539
546
|
const specs = data.specs ?? [];
|
|
540
547
|
const fns = specs.map((spec) => resolveSpec(spec));
|
|
541
|
-
const chain = buildChain(fns, (req) => executeFetch(req));
|
|
548
|
+
const chain = buildChain(fns, (req, signal) => executeFetch(req, signal));
|
|
542
549
|
// Swap to the regular request loop and replay any buffered messages.
|
|
543
550
|
attachRequestLoop(chain);
|
|
544
551
|
const handler = self.onmessage;
|
|
@@ -1,14 +1,118 @@
|
|
|
1
1
|
import { Observable } from 'rxjs';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Scans a payload one level deep and collects every `Transferable` instance
|
|
5
|
+
* (ArrayBuffer, MessagePort, ImageBitmap, OffscreenCanvas, ReadableStream,
|
|
6
|
+
* WritableStream, TransformStream) found in its own enumerable properties.
|
|
7
|
+
*
|
|
8
|
+
* Used by `createWorkerTransport` when `transferDetection === 'auto'` to build
|
|
9
|
+
* the second argument of `worker.postMessage(data, transfer)` so large buffers
|
|
10
|
+
* move zero-copy instead of being structured-cloned.
|
|
11
|
+
*
|
|
12
|
+
* Design notes:
|
|
13
|
+
* - Only one level deep by design: deep traversal has quadratic cost on heavy
|
|
14
|
+
* graphs and makes the transfer list surprising. Real payloads that care
|
|
15
|
+
* about zero-copy put the buffer at the top level.
|
|
16
|
+
* - Duplicates are filtered — the same buffer referenced twice is transferred
|
|
17
|
+
* only once (required by the structured-clone algorithm).
|
|
18
|
+
* - Returns an empty array for primitives, plain serializable values, or when
|
|
19
|
+
* no transferable is found; `postMessage` accepts `[]` safely.
|
|
20
|
+
*/
|
|
21
|
+
function detectTransferables(payload) {
|
|
22
|
+
if (payload === null || payload === undefined)
|
|
23
|
+
return [];
|
|
24
|
+
if (typeof payload !== 'object')
|
|
25
|
+
return [];
|
|
26
|
+
const found = [];
|
|
27
|
+
const seen = new Set();
|
|
28
|
+
const collect = (value) => {
|
|
29
|
+
if (value === null || value === undefined)
|
|
30
|
+
return;
|
|
31
|
+
if (isTransferable(value)) {
|
|
32
|
+
if (!seen.has(value)) {
|
|
33
|
+
seen.add(value);
|
|
34
|
+
found.push(value);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
if (isTransferable(payload)) {
|
|
39
|
+
collect(payload);
|
|
40
|
+
return found;
|
|
41
|
+
}
|
|
42
|
+
if (Array.isArray(payload)) {
|
|
43
|
+
for (const item of payload)
|
|
44
|
+
collect(item);
|
|
45
|
+
return found;
|
|
46
|
+
}
|
|
47
|
+
for (const key of Object.keys(payload)) {
|
|
48
|
+
collect(payload[key]);
|
|
49
|
+
}
|
|
50
|
+
return found;
|
|
51
|
+
}
|
|
52
|
+
function isTransferable(value) {
|
|
53
|
+
if (value === null || value === undefined)
|
|
54
|
+
return false;
|
|
55
|
+
if (typeof value !== 'object')
|
|
56
|
+
return false;
|
|
57
|
+
// ArrayBuffer is the common case; check first.
|
|
58
|
+
if (typeof ArrayBuffer !== 'undefined' && value instanceof ArrayBuffer)
|
|
59
|
+
return true;
|
|
60
|
+
// Typed-array views carry an underlying buffer but are NOT Transferable —
|
|
61
|
+
// only the buffer is. Caller must pass `.buffer` explicitly if they want it.
|
|
62
|
+
if (typeof MessagePort !== 'undefined' && value instanceof MessagePort)
|
|
63
|
+
return true;
|
|
64
|
+
if (typeof ImageBitmap !== 'undefined' && value instanceof ImageBitmap)
|
|
65
|
+
return true;
|
|
66
|
+
if (typeof OffscreenCanvas !== 'undefined' && value instanceof OffscreenCanvas)
|
|
67
|
+
return true;
|
|
68
|
+
if (typeof ReadableStream !== 'undefined' && value instanceof ReadableStream)
|
|
69
|
+
return true;
|
|
70
|
+
if (typeof WritableStream !== 'undefined' && value instanceof WritableStream)
|
|
71
|
+
return true;
|
|
72
|
+
if (typeof TransformStream !== 'undefined' && value instanceof TransformStream)
|
|
73
|
+
return true;
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Thrown by `createWorkerTransport` when a request exceeds its configured
|
|
79
|
+
* `requestTimeout`. Consumers can `instanceof`-check this error to distinguish
|
|
80
|
+
* timeout rejections from transport/worker errors.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```typescript
|
|
84
|
+
* transport.execute(req).subscribe({
|
|
85
|
+
* error: (err) => {
|
|
86
|
+
* if (err instanceof WorkerHttpTimeoutError) {
|
|
87
|
+
* // dedicated timeout handling
|
|
88
|
+
* }
|
|
89
|
+
* },
|
|
90
|
+
* });
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
class WorkerHttpTimeoutError extends Error {
|
|
94
|
+
name = 'WorkerHttpTimeoutError';
|
|
95
|
+
timeoutMs;
|
|
96
|
+
constructor(timeoutMs) {
|
|
97
|
+
super(`Worker request timed out after ${timeoutMs} ms`);
|
|
98
|
+
this.timeoutMs = timeoutMs;
|
|
99
|
+
// Maintain a proper prototype chain across TS transpile targets.
|
|
100
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
|
|
3
105
|
/**
|
|
4
106
|
* Creates a typed, Observable-based transport for communicating with a web worker.
|
|
5
107
|
*
|
|
6
108
|
* Features:
|
|
7
109
|
* - Request/response correlation via `requestId`
|
|
8
|
-
* -
|
|
110
|
+
* - Cancellation on Observable unsubscribe (also aborts `fetch()` in the worker)
|
|
111
|
+
* - Per-request timeout (default 30 s) rejecting with `WorkerHttpTimeoutError`
|
|
9
112
|
* - Optional worker pool with round-robin dispatch
|
|
10
113
|
* - Lazy worker creation (default)
|
|
11
|
-
* -
|
|
114
|
+
* - Opt-in transferable detection (`transferDetection: 'auto'`) for zero-copy
|
|
115
|
+
* `ArrayBuffer` / stream payloads
|
|
12
116
|
*
|
|
13
117
|
* @example
|
|
14
118
|
* ```typescript
|
|
@@ -28,6 +132,8 @@ function createWorkerTransport(config) {
|
|
|
28
132
|
let roundRobinIndex = 0;
|
|
29
133
|
let terminated = false;
|
|
30
134
|
const maxInstances = Math.min(config.maxInstances ?? 1, typeof navigator !== 'undefined' ? (navigator.hardwareConcurrency ?? 4) : 1);
|
|
135
|
+
const requestTimeout = config.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
136
|
+
const transferDetection = config.transferDetection ?? 'none';
|
|
31
137
|
function createWorker() {
|
|
32
138
|
if (config.workerFactory) {
|
|
33
139
|
return config.workerFactory();
|
|
@@ -62,12 +168,24 @@ function createWorkerTransport(config) {
|
|
|
62
168
|
const requestId = crypto.randomUUID();
|
|
63
169
|
return new Observable((subscriber) => {
|
|
64
170
|
const worker = getOrCreateWorker();
|
|
171
|
+
let settled = false;
|
|
172
|
+
let timeoutHandle;
|
|
173
|
+
const cleanup = () => {
|
|
174
|
+
worker.removeEventListener('message', messageHandler);
|
|
175
|
+
worker.removeEventListener('error', errorHandler);
|
|
176
|
+
if (timeoutHandle !== undefined) {
|
|
177
|
+
clearTimeout(timeoutHandle);
|
|
178
|
+
timeoutHandle = undefined;
|
|
179
|
+
}
|
|
180
|
+
};
|
|
65
181
|
const messageHandler = (event) => {
|
|
66
182
|
const data = event.data;
|
|
67
183
|
if (data.requestId !== requestId)
|
|
68
184
|
return;
|
|
69
|
-
|
|
70
|
-
|
|
185
|
+
if (settled)
|
|
186
|
+
return;
|
|
187
|
+
settled = true;
|
|
188
|
+
cleanup();
|
|
71
189
|
if (data.type === 'error') {
|
|
72
190
|
const err = data.error;
|
|
73
191
|
subscriber.error(new Error(err.message));
|
|
@@ -78,17 +196,39 @@ function createWorkerTransport(config) {
|
|
|
78
196
|
}
|
|
79
197
|
};
|
|
80
198
|
const errorHandler = (event) => {
|
|
81
|
-
|
|
82
|
-
|
|
199
|
+
if (settled)
|
|
200
|
+
return;
|
|
201
|
+
settled = true;
|
|
202
|
+
cleanup();
|
|
83
203
|
subscriber.error(new Error(event.message ?? 'Worker error'));
|
|
84
204
|
};
|
|
85
205
|
worker.addEventListener('message', messageHandler);
|
|
86
206
|
worker.addEventListener('error', errorHandler);
|
|
87
|
-
|
|
207
|
+
if (transferDetection === 'auto') {
|
|
208
|
+
const transferables = detectTransferables(request);
|
|
209
|
+
worker.postMessage({ type: 'request', requestId, payload: request }, transferables);
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
worker.postMessage({ type: 'request', requestId, payload: request });
|
|
213
|
+
}
|
|
214
|
+
if (requestTimeout > 0 && Number.isFinite(requestTimeout)) {
|
|
215
|
+
timeoutHandle = setTimeout(() => {
|
|
216
|
+
if (settled)
|
|
217
|
+
return;
|
|
218
|
+
settled = true;
|
|
219
|
+
cleanup();
|
|
220
|
+
// Ask the worker to abort any in-flight work for this id. The
|
|
221
|
+
// cancellation fix wires this through to `fetch()`.
|
|
222
|
+
worker.postMessage({ type: 'cancel', requestId });
|
|
223
|
+
subscriber.error(new WorkerHttpTimeoutError(requestTimeout));
|
|
224
|
+
}, requestTimeout);
|
|
225
|
+
}
|
|
88
226
|
// Teardown: send cancel message on unsubscribe
|
|
89
227
|
return () => {
|
|
90
|
-
|
|
91
|
-
|
|
228
|
+
if (settled)
|
|
229
|
+
return;
|
|
230
|
+
settled = true;
|
|
231
|
+
cleanup();
|
|
92
232
|
worker.postMessage({ type: 'cancel', requestId });
|
|
93
233
|
};
|
|
94
234
|
});
|
|
@@ -116,4 +256,4 @@ function createWorkerTransport(config) {
|
|
|
116
256
|
* Generated bundle index. Do not edit.
|
|
117
257
|
*/
|
|
118
258
|
|
|
119
|
-
export { createWorkerTransport };
|
|
259
|
+
export { WorkerHttpTimeoutError, createWorkerTransport, detectTransferables };
|
|
@@ -1,20 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @angular-helpers/worker-http
|
|
3
|
-
*
|
|
4
|
-
* Angular HTTP over Web Workers — off-main-thread HTTP pipelines
|
|
5
|
-
* with configurable interceptors, WebCrypto security, and pluggable serialization.
|
|
6
|
-
*
|
|
7
|
-
* Sub-entry points:
|
|
8
|
-
* - @angular-helpers/worker-http/transport (P1: typed RPC bridge)
|
|
9
|
-
* - @angular-helpers/worker-http/serializer (P2: TOON, seroval, auto-detect)
|
|
10
|
-
* - @angular-helpers/worker-http/backend (P3: Angular HttpBackend replacement)
|
|
11
|
-
* - @angular-helpers/worker-http/interceptors (P4: pure-fn interceptors for workers)
|
|
12
|
-
* - @angular-helpers/worker-http/crypto (P5: WebCrypto primitives)
|
|
13
|
-
*/
|
|
14
|
-
const WORKER_HTTP_VERSION = '0.0.1';
|
|
15
|
-
|
|
16
1
|
/**
|
|
17
2
|
* Generated bundle index. Do not edit.
|
|
18
3
|
*/
|
|
19
|
-
|
|
20
|
-
export { WORKER_HTTP_VERSION };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@angular-helpers/worker-http",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Angular HTTP over Web Workers — off-main-thread HTTP pipelines with configurable interceptors, WebCrypto security, and pluggable serialization",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"angular",
|
|
@@ -34,9 +34,27 @@ interface WorkerTransportConfig {
|
|
|
34
34
|
workerUrl?: string | URL;
|
|
35
35
|
/** Maximum number of worker instances in the pool (default: 1) */
|
|
36
36
|
maxInstances?: number;
|
|
37
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* Transfer strategy for `postMessage` payloads.
|
|
39
|
+
*
|
|
40
|
+
* - `'none'` (default) — payloads are always structured-cloned, preserving
|
|
41
|
+
* the caller's access to the original data after post.
|
|
42
|
+
* - `'auto'` — shallowly walks the payload and passes every detected
|
|
43
|
+
* `Transferable` (ArrayBuffer, MessagePort, ImageBitmap, OffscreenCanvas,
|
|
44
|
+
* ReadableStream, WritableStream, TransformStream) in the transfer list
|
|
45
|
+
* of `postMessage`. Large buffers move zero-copy; their `byteLength`
|
|
46
|
+
* becomes `0` in the main thread after post.
|
|
47
|
+
*
|
|
48
|
+
* The `'manual'` value is reserved for a future API where callers supply
|
|
49
|
+
* their own transfer list per request. It currently behaves like `'none'`.
|
|
50
|
+
*/
|
|
38
51
|
transferDetection?: 'auto' | 'manual' | 'none';
|
|
39
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* Per-request timeout in milliseconds. If the worker does not respond
|
|
54
|
+
* within this window, `execute()` errors with `WorkerHttpTimeoutError`
|
|
55
|
+
* and a cancel message is posted to the worker. Set to `0` or
|
|
56
|
+
* non-finite to disable the timeout entirely. Default: `30000` (30 s).
|
|
57
|
+
*/
|
|
40
58
|
requestTimeout?: number;
|
|
41
59
|
/**
|
|
42
60
|
* Optional handshake message posted to every worker as soon as it is
|
|
@@ -103,10 +121,12 @@ interface WorkerTransport<TRequest = unknown, TResponse = unknown> {
|
|
|
103
121
|
*
|
|
104
122
|
* Features:
|
|
105
123
|
* - Request/response correlation via `requestId`
|
|
106
|
-
* -
|
|
124
|
+
* - Cancellation on Observable unsubscribe (also aborts `fetch()` in the worker)
|
|
125
|
+
* - Per-request timeout (default 30 s) rejecting with `WorkerHttpTimeoutError`
|
|
107
126
|
* - Optional worker pool with round-robin dispatch
|
|
108
127
|
* - Lazy worker creation (default)
|
|
109
|
-
* -
|
|
128
|
+
* - Opt-in transferable detection (`transferDetection: 'auto'`) for zero-copy
|
|
129
|
+
* `ArrayBuffer` / stream payloads
|
|
110
130
|
*
|
|
111
131
|
* @example
|
|
112
132
|
* ```typescript
|
|
@@ -123,5 +143,47 @@ interface WorkerTransport<TRequest = unknown, TResponse = unknown> {
|
|
|
123
143
|
*/
|
|
124
144
|
declare function createWorkerTransport<TRequest = unknown, TResponse = unknown>(config: WorkerTransportConfig): WorkerTransport<TRequest, TResponse>;
|
|
125
145
|
|
|
126
|
-
|
|
146
|
+
/**
|
|
147
|
+
* Thrown by `createWorkerTransport` when a request exceeds its configured
|
|
148
|
+
* `requestTimeout`. Consumers can `instanceof`-check this error to distinguish
|
|
149
|
+
* timeout rejections from transport/worker errors.
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```typescript
|
|
153
|
+
* transport.execute(req).subscribe({
|
|
154
|
+
* error: (err) => {
|
|
155
|
+
* if (err instanceof WorkerHttpTimeoutError) {
|
|
156
|
+
* // dedicated timeout handling
|
|
157
|
+
* }
|
|
158
|
+
* },
|
|
159
|
+
* });
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
declare class WorkerHttpTimeoutError extends Error {
|
|
163
|
+
readonly name = "WorkerHttpTimeoutError";
|
|
164
|
+
readonly timeoutMs: number;
|
|
165
|
+
constructor(timeoutMs: number);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Scans a payload one level deep and collects every `Transferable` instance
|
|
170
|
+
* (ArrayBuffer, MessagePort, ImageBitmap, OffscreenCanvas, ReadableStream,
|
|
171
|
+
* WritableStream, TransformStream) found in its own enumerable properties.
|
|
172
|
+
*
|
|
173
|
+
* Used by `createWorkerTransport` when `transferDetection === 'auto'` to build
|
|
174
|
+
* the second argument of `worker.postMessage(data, transfer)` so large buffers
|
|
175
|
+
* move zero-copy instead of being structured-cloned.
|
|
176
|
+
*
|
|
177
|
+
* Design notes:
|
|
178
|
+
* - Only one level deep by design: deep traversal has quadratic cost on heavy
|
|
179
|
+
* graphs and makes the transfer list surprising. Real payloads that care
|
|
180
|
+
* about zero-copy put the buffer at the top level.
|
|
181
|
+
* - Duplicates are filtered — the same buffer referenced twice is transferred
|
|
182
|
+
* only once (required by the structured-clone algorithm).
|
|
183
|
+
* - Returns an empty array for primitives, plain serializable values, or when
|
|
184
|
+
* no transferable is found; `postMessage` accepts `[]` safely.
|
|
185
|
+
*/
|
|
186
|
+
declare function detectTransferables(payload: unknown): Transferable[];
|
|
187
|
+
|
|
188
|
+
export { WorkerHttpTimeoutError, createWorkerTransport, detectTransferables };
|
|
127
189
|
export type { WorkerErrorResponse, WorkerMessage, WorkerResponse, WorkerTransport, WorkerTransportConfig };
|
|
@@ -1,16 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @angular-helpers/worker-http
|
|
3
|
-
*
|
|
4
|
-
* Angular HTTP over Web Workers — off-main-thread HTTP pipelines
|
|
5
|
-
* with configurable interceptors, WebCrypto security, and pluggable serialization.
|
|
6
|
-
*
|
|
7
|
-
* Sub-entry points:
|
|
8
|
-
* - @angular-helpers/worker-http/transport (P1: typed RPC bridge)
|
|
9
|
-
* - @angular-helpers/worker-http/serializer (P2: TOON, seroval, auto-detect)
|
|
10
|
-
* - @angular-helpers/worker-http/backend (P3: Angular HttpBackend replacement)
|
|
11
|
-
* - @angular-helpers/worker-http/interceptors (P4: pure-fn interceptors for workers)
|
|
12
|
-
* - @angular-helpers/worker-http/crypto (P5: WebCrypto primitives)
|
|
13
|
-
*/
|
|
14
|
-
declare const WORKER_HTTP_VERSION = "0.0.1";
|
|
15
1
|
|
|
16
|
-
export {
|
|
2
|
+
export { };
|