@angular-helpers/worker-http 0.2.0 → 0.4.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 CHANGED
@@ -16,13 +16,13 @@ On top of that, workers provide a natural isolation boundary for security-sensit
16
16
 
17
17
  ## Package map
18
18
 
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()` | 🔧 In progress |
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
26
 
27
27
  ---
28
28
 
@@ -313,13 +313,9 @@ const hash = await hasher.hash('SHA-256', data); // → hex string
313
313
 
314
314
  ---
315
315
 
316
- ### `/backend` — Angular `HttpBackend` replacement (in progress)
316
+ ### `/backend` — Angular `HttpBackend` replacement
317
317
 
318
- > 🔧 **This entry point is currently in development.**
319
-
320
- The goal is a drop-in replacement for Angular's `HttpBackend` that transparently routes requests to the appropriate worker.
321
-
322
- **Planned API:**
318
+ Drop-in replacement for Angular's `HttpBackend` that transparently routes `HttpClient` requests to Web Workers. Use `WorkerHttpClient` exactly like `HttpClient` — the routing is invisible to application code.
323
319
 
324
320
  ```typescript
325
321
  // app.config.ts
@@ -328,34 +324,86 @@ import {
328
324
  withWorkerConfigs,
329
325
  withWorkerRoutes,
330
326
  withWorkerFallback,
327
+ withWorkerSerialization,
328
+ withWorkerInterceptors,
331
329
  } from '@angular-helpers/worker-http/backend';
330
+ import { createSerovalSerializer } from '@angular-helpers/worker-http/serializer';
331
+ import { workerLogging, workerRetry, workerCache } from '@angular-helpers/worker-http/interceptors';
332
332
 
333
- bootstrapApplication(AppComponent, {
333
+ export const appConfig: ApplicationConfig = {
334
334
  providers: [
335
335
  provideWorkerHttpClient(
336
336
  withWorkerConfigs([
337
+ {
338
+ id: 'api',
339
+ workerUrl: new URL('./workers/api.worker', import.meta.url),
340
+ maxInstances: 2, // round-robin pool
341
+ },
337
342
  {
338
343
  id: 'secure',
339
344
  workerUrl: new URL('./workers/secure.worker', import.meta.url),
340
- maxInstances: 2,
341
345
  },
342
346
  ]),
343
- withWorkerRoutes([{ pattern: /\/api\/secure\//, worker: 'secure', priority: 10 }]),
344
- withWorkerFallback('main-thread'), // SSR-safe fallback
347
+ withWorkerRoutes([
348
+ { pattern: /\/api\/secure\//, worker: 'secure', priority: 10 },
349
+ { pattern: /\/api\//, worker: 'api', priority: 5 },
350
+ ]),
351
+ withWorkerFallback('main-thread'), // SSR-safe
352
+ withWorkerSerialization(createSerovalSerializer()), // optional: complex bodies
353
+ withWorkerInterceptors([
354
+ workerLogging(),
355
+ workerRetry({ maxRetries: 3 }),
356
+ workerCache({ ttl: 60000 }),
357
+ ]),
345
358
  ),
346
359
  ],
347
- });
360
+ };
348
361
 
349
- // data.service.ts — identical to normal HttpClient usage
362
+ // data.service.ts — WorkerHttpClient is a drop-in for HttpClient
350
363
  export class DataService {
351
- private http = inject(HttpClient);
364
+ private http = inject(WorkerHttpClient);
365
+
366
+ getUsers() {
367
+ return this.http.get<User[]>('/api/users'); // auto-routed to 'api' worker
368
+ }
352
369
 
353
- getReports() {
354
- return this.http.get<Report[]>('/api/secure/reports');
370
+ getSecureData() {
371
+ // per-request override via { worker } option or WORKER_TARGET context token
372
+ return this.http.get('/api/secure/payments', { worker: 'secure' });
355
373
  }
356
374
  }
375
+
376
+ // workers/api.worker.ts — runs on a separate OS thread
377
+ import { createConfigurableWorkerPipeline } from '@angular-helpers/worker-http/interceptors';
378
+
379
+ // The pipeline is built at runtime from the specs configured via
380
+ // `withWorkerInterceptors([...])` in `app.config.ts`. Custom interceptors
381
+ // not covered by the built-in catalogue can be wired in here:
382
+ //
383
+ // import { registerInterceptor } from '@angular-helpers/worker-http/interceptors';
384
+ // registerInterceptor('auth-token', (config) => async (req, next) => { ... });
385
+ //
386
+ // then referenced from the main thread with `workerCustom('auth-token', config)`.
387
+ createConfigurableWorkerPipeline();
357
388
  ```
358
389
 
390
+ If you prefer to keep the pipeline composition inside the worker file, use
391
+ `createWorkerPipeline([interceptors])` instead — it stays available for full
392
+ manual control.
393
+
394
+ **Features:**
395
+
396
+ - `provideWorkerHttpClient(...features)` — replaces `provideHttpClient()`; do not use both
397
+ - `withWorkerConfigs(configs)` — register named workers with optional pool size
398
+ - `withWorkerRoutes(routes)` — URL-pattern routing with priority ordering
399
+ - `withWorkerFallback(strategy)` — `'main-thread'` (SSR-safe) or `'error'`
400
+ - `withWorkerSerialization(serializer)` — plug in `createSerovalSerializer()` for complex request bodies (`Date`, `Map`, `Set`)
401
+ - `withWorkerInterceptors(specs | specsByWorker)` — configure the worker-side pipeline from Angular DI; pairs with `createConfigurableWorkerPipeline()` in the worker file
402
+ - `WORKER_TARGET` — `HttpContextToken<string | null>` for per-request worker routing via `HttpContext`
403
+ - `WorkerHttpClient` — `HttpClient` wrapper with optional `{ worker: string }` routing field
404
+ - `WorkerHttpBackend` — the `HttpBackend` implementation (injectable for advanced use)
405
+ - `matchWorkerRoute(url, routes)` — pure utility to test routing rules
406
+
359
407
  ---
360
408
 
361
409
  ## Design principles
@@ -392,6 +440,48 @@ Server-Side Rendering (SSR) is supported via automatic fallback to the main thre
392
440
 
393
441
  ---
394
442
 
443
+ ## Benchmarks
444
+
445
+ A reproducible benchmark suite ships with the demo app at
446
+ [`/demo/worker-http-benchmark`](../../src/app/demo/worker-http-benchmark) and compares three
447
+ transport modes across four workloads:
448
+
449
+ | Mode | What it measures |
450
+ | --------------- | ---------------------------------------------------------- |
451
+ | `main-thread` | Baseline — the same simulated work runs on the main thread |
452
+ | `worker-pool-1` | Single worker — measures pure transport overhead |
453
+ | `worker-pool-4` | Four workers — measures the benefit of parallel dispatch |
454
+
455
+ Each scenario simulates identical "server" work (synchronous CPU burn + async delay + payload
456
+ generation), so the only variable being compared is **where** the work runs.
457
+
458
+ **Workloads**:
459
+
460
+ - 100 small sequential requests — pure transport overhead
461
+ - 1 large response (10MB) — serialization / structured clone cost
462
+ - 50 parallel requests — pool benefit
463
+ - 20 parallel requests + 500ms main-thread CPU burn — real-world jank case
464
+
465
+ **Metrics collected**:
466
+
467
+ - **Long Tasks** (`PerformanceObserver` / `longtask`) — count and total duration (Chromium-only)
468
+ - **Dropped frames** (`requestAnimationFrame` deltas > 25 ms) — visible jank proxy, works in every browser
469
+ - **Wall-clock total** for the scenario
470
+ - **Success / failure** counts
471
+
472
+ To run locally:
473
+
474
+ ```bash
475
+ npm run build:workers
476
+ npm start
477
+ # open https://localhost:4200/demo/worker-http-benchmark
478
+ ```
479
+
480
+ Numbers vary by hardware, browser, and current system load — always run a scenario several times
481
+ and watch the trend, not a single value.
482
+
483
+ ---
484
+
395
485
  ## Related documentation
396
486
 
397
487
  - [Architecture & Feasibility Study](../../docs/sdd-angular-http-web-workers.md)
@@ -1,3 +1,431 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, inject, Injectable, makeEnvironmentProviders } from '@angular/core';
3
+ import { HttpContextToken, HttpHeaders, HttpResponse, HttpBackend, FetchBackend, HttpErrorResponse, HttpClient, HttpContext, provideHttpClient, withFetch } from '@angular/common/http';
4
+ import { throwError } from 'rxjs';
5
+ import { map, catchError } from 'rxjs/operators';
6
+ import { createWorkerTransport } from '@angular-helpers/worker-http/transport';
7
+
8
+ /**
9
+ * Per-request HttpContextToken that carries the target worker ID.
10
+ *
11
+ * `null` → use URL-pattern auto-routing (or main-thread fallback if no route matches).
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * // With WorkerHttpClient (recommended)
16
+ * this.http.get('/api/data', { worker: 'secure' });
17
+ *
18
+ * // With standard HttpClient (power user)
19
+ * this.http.get('/api/data', {
20
+ * context: new HttpContext().set(WORKER_TARGET, 'secure'),
21
+ * });
22
+ * ```
23
+ */
24
+ const WORKER_TARGET = new HttpContextToken(() => null);
25
+ /**
26
+ * Registered worker definitions provided via `withWorkerConfigs()`.
27
+ */
28
+ const WORKER_HTTP_CONFIGS_TOKEN = new InjectionToken('WorkerHttpConfigs', {
29
+ factory: () => [],
30
+ });
31
+ /**
32
+ * URL-pattern routing rules provided via `withWorkerRoutes()`.
33
+ */
34
+ const WORKER_HTTP_ROUTES_TOKEN = new InjectionToken('WorkerHttpRoutes', {
35
+ factory: () => [],
36
+ });
37
+ /**
38
+ * Fallback strategy provided via `withWorkerFallback()`.
39
+ * Defaults to `'main-thread'` (safe for SSR / unsupported environments).
40
+ */
41
+ const WORKER_HTTP_FALLBACK_TOKEN = new InjectionToken('WorkerHttpFallback', { factory: () => 'main-thread' });
42
+ /**
43
+ * Optional serializer for crossing the worker boundary.
44
+ * Provided via `withWorkerSerialization()`. Defaults to `null` (structured clone).
45
+ *
46
+ * When set, `WorkerHttpBackend` serializes the request body before `postMessage`
47
+ * using this serializer. The worker-side `createWorkerPipeline()` receives the
48
+ * serialized form — add a worker interceptor to deserialize it if needed.
49
+ */
50
+ const WORKER_HTTP_SERIALIZER_TOKEN = new InjectionToken('WorkerHttpSerializer', { factory: () => null });
51
+ /**
52
+ * Per-worker interceptor specs provided via `withWorkerInterceptors()`.
53
+ * Defaults to an empty map (no interceptors).
54
+ */
55
+ const WORKER_HTTP_INTERCEPTORS_TOKEN = new InjectionToken('WorkerHttpInterceptors', { factory: () => ({}) });
56
+
57
+ /**
58
+ * Converts an Angular `HttpRequest` into a structured-clone-safe POJO
59
+ * that can be sent to a web worker via `postMessage`.
60
+ *
61
+ * Notes:
62
+ * - `urlWithParams` is used so query params embedded via `HttpParams` are included.
63
+ * - The `context` field is intentionally left empty: `HttpContext` uses class references
64
+ * as keys which cannot cross the worker boundary.
65
+ */
66
+ function toSerializableRequest(req) {
67
+ const headers = {};
68
+ for (const name of req.headers.keys()) {
69
+ headers[name.toLowerCase()] = req.headers.getAll(name) ?? [];
70
+ }
71
+ const params = {};
72
+ for (const name of req.params.keys()) {
73
+ params[name] = req.params.getAll(name) ?? [];
74
+ }
75
+ return {
76
+ method: req.method,
77
+ url: req.urlWithParams,
78
+ headers,
79
+ params,
80
+ body: req.body,
81
+ responseType: req.responseType,
82
+ withCredentials: req.withCredentials,
83
+ context: {},
84
+ };
85
+ }
86
+ /**
87
+ * Converts a worker `SerializableResponse` back into an Angular `HttpResponse`.
88
+ */
89
+ function toHttpResponse(res, req) {
90
+ let headers = new HttpHeaders();
91
+ for (const [key, values] of Object.entries(res.headers)) {
92
+ for (const value of values) {
93
+ headers = headers.append(key, value);
94
+ }
95
+ }
96
+ return new HttpResponse({
97
+ body: res.body,
98
+ headers,
99
+ status: res.status,
100
+ statusText: res.statusText,
101
+ url: res.url || req.urlWithParams,
102
+ });
103
+ }
104
+ /**
105
+ * Matches a URL against a sorted list of `WorkerRoute` rules.
106
+ * Rules with higher `priority` are evaluated first.
107
+ * Returns the matched worker ID or `null` if no rule matches.
108
+ */
109
+ function matchWorkerRoute(url, routes) {
110
+ const sorted = [...routes].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
111
+ for (const route of sorted) {
112
+ const pattern = typeof route.pattern === 'string' ? new RegExp(route.pattern) : route.pattern;
113
+ if (pattern.test(url)) {
114
+ return route.worker;
115
+ }
116
+ }
117
+ return null;
118
+ }
119
+
120
+ /**
121
+ * Angular `HttpBackend` replacement that routes HTTP requests to web workers.
122
+ *
123
+ * Registered via `provideWorkerHttpClient()`. Not meant to be used directly.
124
+ *
125
+ * Flow per request:
126
+ * 1. Check SSR: if `Worker` is undefined → fallback strategy
127
+ * 2. Resolve target worker ID from `WORKER_TARGET` context or URL-pattern routing
128
+ * 3. Serialize `HttpRequest` → `SerializableRequest` (structured-clone safe)
129
+ * 4. Dispatch to the worker's `WorkerTransport`
130
+ * 5. Deserialize `SerializableResponse` → `HttpResponse`
131
+ */
132
+ class WorkerHttpBackend extends HttpBackend {
133
+ configs = inject(WORKER_HTTP_CONFIGS_TOKEN);
134
+ routes = inject(WORKER_HTTP_ROUTES_TOKEN);
135
+ fallback = inject(WORKER_HTTP_FALLBACK_TOKEN);
136
+ serializer = inject(WORKER_HTTP_SERIALIZER_TOKEN);
137
+ interceptorSpecs = inject(WORKER_HTTP_INTERCEPTORS_TOKEN);
138
+ fetchBackend = inject(FetchBackend, { optional: true });
139
+ transports = new Map();
140
+ handle(req) {
141
+ if (typeof Worker === 'undefined') {
142
+ return this.handleFallback(req, 'Web Workers are not available in this environment (SSR)');
143
+ }
144
+ const workerId = req.context.get(WORKER_TARGET) ?? matchWorkerRoute(req.url, this.routes);
145
+ if (!workerId) {
146
+ return this.handleFallback(req, `No worker route matched for URL: ${req.url}`);
147
+ }
148
+ const config = this.configs.find((c) => c.id === workerId);
149
+ if (!config) {
150
+ return throwError(() => new Error(`[WorkerHttpBackend] Unknown worker id: "${workerId}". ` +
151
+ `Register it via withWorkerConfigs([{ id: "${workerId}", workerUrl: ... }]).`));
152
+ }
153
+ const transport = this.getOrCreateTransport(config);
154
+ const serializable = toSerializableRequest(req);
155
+ const body = this.serializer !== null && serializable.body !== null && serializable.body !== undefined
156
+ ? this.serializer.serialize(serializable.body).data
157
+ : serializable.body;
158
+ const payload = body !== serializable.body ? { ...serializable, body } : serializable;
159
+ return transport.execute(payload).pipe(map((res) => toHttpResponse(res, req)), catchError((err) => throwError(() => new HttpErrorResponse({
160
+ error: err,
161
+ status: 0,
162
+ statusText: 'Worker Error',
163
+ url: req.urlWithParams,
164
+ }))));
165
+ }
166
+ ngOnDestroy() {
167
+ for (const transport of this.transports.values()) {
168
+ transport.terminate();
169
+ }
170
+ this.transports.clear();
171
+ }
172
+ getOrCreateTransport(config) {
173
+ const existing = this.transports.get(config.id);
174
+ if (existing)
175
+ return existing;
176
+ const specs = this.resolveSpecsFor(config.id);
177
+ const transport = createWorkerTransport({
178
+ workerFactory: () => new Worker(config.workerUrl, { type: 'module' }),
179
+ maxInstances: config.maxInstances ?? 1,
180
+ initMessage: specs.length > 0 ? { type: 'init-interceptors', specs } : undefined,
181
+ });
182
+ this.transports.set(config.id, transport);
183
+ return transport;
184
+ }
185
+ resolveSpecsFor(workerId) {
186
+ const wildcard = this.interceptorSpecs['*'] ?? [];
187
+ const specific = this.interceptorSpecs[workerId] ?? [];
188
+ if (wildcard.length === 0)
189
+ return specific;
190
+ if (specific.length === 0)
191
+ return wildcard;
192
+ return [...wildcard, ...specific];
193
+ }
194
+ handleFallback(req, reason) {
195
+ if (this.fallback === 'error' || !this.fetchBackend) {
196
+ return throwError(() => new Error(`[WorkerHttpBackend] ${reason}`));
197
+ }
198
+ return this.fetchBackend.handle(req);
199
+ }
200
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WorkerHttpBackend, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
201
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WorkerHttpBackend });
202
+ }
203
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WorkerHttpBackend, decorators: [{
204
+ type: Injectable
205
+ }] });
206
+
207
+ /**
208
+ * Convenience wrapper over `HttpClient` that adds an optional `{ worker }` field
209
+ * to every method. Under the hood it sets `WORKER_TARGET` on the `HttpContext` —
210
+ * the caller never has to touch the context manually.
211
+ *
212
+ * Usage is identical to `HttpClient` — just inject `WorkerHttpClient` instead.
213
+ *
214
+ * @example
215
+ * ```typescript
216
+ * @Injectable({ providedIn: 'root' })
217
+ * export class DataService {
218
+ * private readonly http = inject(WorkerHttpClient);
219
+ *
220
+ * getUsers(): Observable<User[]> {
221
+ * return this.http.get<User[]>('/api/users'); // auto-routed by URL pattern
222
+ * }
223
+ *
224
+ * getSensitiveReport(): Observable<Report> {
225
+ * return this.http.get<Report>('/api/secure/reports', { worker: 'secure' });
226
+ * }
227
+ * }
228
+ * ```
229
+ */
230
+ class WorkerHttpClient {
231
+ http = inject(HttpClient);
232
+ get(url, options) {
233
+ return this.http.get(url, this.withWorker(options));
234
+ }
235
+ post(url, body, options) {
236
+ return this.http.post(url, body, this.withWorker(options));
237
+ }
238
+ put(url, body, options) {
239
+ return this.http.put(url, body, this.withWorker(options));
240
+ }
241
+ patch(url, body, options) {
242
+ return this.http.patch(url, body, this.withWorker(options));
243
+ }
244
+ delete(url, options) {
245
+ return this.http.delete(url, this.withWorker(options));
246
+ }
247
+ head(url, options) {
248
+ return this.http.head(url, this.withWorker(options));
249
+ }
250
+ withWorker(options) {
251
+ const { worker = null, context, ...rest } = options ?? {};
252
+ return {
253
+ ...rest,
254
+ context: (context ?? new HttpContext()).set(WORKER_TARGET, worker),
255
+ };
256
+ }
257
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WorkerHttpClient, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
258
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WorkerHttpClient });
259
+ }
260
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WorkerHttpClient, decorators: [{
261
+ type: Injectable
262
+ }] });
263
+
264
+ /**
265
+ * Sets up the worker HTTP infrastructure and replaces Angular's `HttpBackend`
266
+ * with `WorkerHttpBackend`.
267
+ *
268
+ * Drop-in companion to `provideHttpClient()`. Can be used INSTEAD of it —
269
+ * `HttpClient` and the full interceptor chain are included automatically.
270
+ *
271
+ * @example
272
+ * ```typescript
273
+ * // app.config.ts
274
+ * export const appConfig: ApplicationConfig = {
275
+ * providers: [
276
+ * provideWorkerHttpClient(
277
+ * withWorkerConfigs([
278
+ * { id: 'public', workerUrl: new URL('./workers/public.worker', import.meta.url) },
279
+ * ]),
280
+ * withWorkerRoutes([
281
+ * { pattern: /\/api\//, worker: 'public', priority: 1 },
282
+ * ]),
283
+ * withWorkerFallback('main-thread'),
284
+ * ),
285
+ * ],
286
+ * };
287
+ * ```
288
+ */
289
+ function provideWorkerHttpClient(...features) {
290
+ const featureProviders = features.flatMap((f) => f.providers);
291
+ return makeEnvironmentProviders([
292
+ provideHttpClient(withFetch()),
293
+ FetchBackend,
294
+ { provide: HttpBackend, useClass: WorkerHttpBackend },
295
+ WorkerHttpClient,
296
+ ...featureProviders,
297
+ ]);
298
+ }
299
+ /**
300
+ * Registers worker definitions (id + workerUrl + optional pool size).
301
+ *
302
+ * At least one config is required for any request to reach a worker.
303
+ *
304
+ * @example
305
+ * ```typescript
306
+ * withWorkerConfigs([
307
+ * { id: 'public', workerUrl: new URL('./workers/public.worker', import.meta.url) },
308
+ * { id: 'secure', workerUrl: new URL('./workers/secure.worker', import.meta.url), maxInstances: 2 },
309
+ * ])
310
+ * ```
311
+ */
312
+ function withWorkerConfigs(configs) {
313
+ return {
314
+ kind: 'WorkerConfigs',
315
+ providers: [{ provide: WORKER_HTTP_CONFIGS_TOKEN, useValue: configs }],
316
+ };
317
+ }
318
+ /**
319
+ * Declares URL-pattern → worker routing rules evaluated in priority order.
320
+ *
321
+ * When a request URL matches a pattern, the associated worker handles it.
322
+ * Explicit `WORKER_TARGET` context always takes precedence over routes.
323
+ *
324
+ * @example
325
+ * ```typescript
326
+ * withWorkerRoutes([
327
+ * { pattern: /\/api\/secure\//, worker: 'secure', priority: 10 },
328
+ * { pattern: /\/api\//, worker: 'public', priority: 1 },
329
+ * ])
330
+ * ```
331
+ */
332
+ function withWorkerRoutes(routes) {
333
+ return {
334
+ kind: 'WorkerRoutes',
335
+ providers: [{ provide: WORKER_HTTP_ROUTES_TOKEN, useValue: routes }],
336
+ };
337
+ }
338
+ /**
339
+ * Sets the fallback strategy when workers are unavailable (SSR, old browsers,
340
+ * or when no route matches).
341
+ *
342
+ * - `'main-thread'` (default) — silently delegates to `FetchBackend`
343
+ * - `'error'` — throws, forcing explicit handling in the application
344
+ *
345
+ * @example
346
+ * ```typescript
347
+ * withWorkerFallback('main-thread') // SSR-safe
348
+ * ```
349
+ */
350
+ function withWorkerFallback(strategy) {
351
+ return {
352
+ kind: 'WorkerFallback',
353
+ providers: [{ provide: WORKER_HTTP_FALLBACK_TOKEN, useValue: strategy }],
354
+ };
355
+ }
356
+ /**
357
+ * Configures a custom serializer for crossing the worker boundary.
358
+ *
359
+ * By default `WorkerHttpBackend` relies on the browser's structured clone algorithm
360
+ * (safe for plain objects, arrays, primitives, `Date`, `ArrayBuffer`).
361
+ * Use `withWorkerSerialization` when your request bodies contain types that
362
+ * structured clone cannot handle (e.g. class instances, circular references, `Map`, `Set`).
363
+ *
364
+ * **Worker-side note:** The serialized form is what the worker receives as `req.body`.
365
+ * If you use `createSerovalSerializer` or similar, add a worker-side interceptor
366
+ * to deserialize the body before calling `fetch()`.
367
+ *
368
+ * @example
369
+ * ```typescript
370
+ * import { createSerovalSerializer } from '@angular-helpers/worker-http/serializer';
371
+ *
372
+ * provideWorkerHttpClient(
373
+ * withWorkerConfigs([...]),
374
+ * withWorkerSerialization(createSerovalSerializer()),
375
+ * )
376
+ * ```
377
+ */
378
+ function withWorkerSerialization(serializer) {
379
+ return {
380
+ kind: 'WorkerSerialization',
381
+ providers: [{ provide: WORKER_HTTP_SERIALIZER_TOKEN, useValue: serializer }],
382
+ };
383
+ }
384
+ /**
385
+ * Configures the worker-side interceptor pipeline from Angular DI.
386
+ *
387
+ * Specs are forwarded to each worker via an `init-interceptors` handshake
388
+ * message posted before any HTTP request. Workers must call
389
+ * `createConfigurableWorkerPipeline()` to receive and act on the handshake.
390
+ *
391
+ * Two shapes are accepted:
392
+ * - `WorkerInterceptorSpec[]` — applied to every registered worker
393
+ * - `Record<workerId, WorkerInterceptorSpec[]>` — per-worker, with the
394
+ * special `'*'` key applied to all workers in addition to the
395
+ * worker-specific specs
396
+ *
397
+ * @example
398
+ * ```typescript
399
+ * provideWorkerHttpClient(
400
+ * withWorkerConfigs([{ id: 'api', workerUrl: ... }]),
401
+ * withWorkerInterceptors([
402
+ * workerLogging(),
403
+ * workerRetry({ maxRetries: 3 }),
404
+ * workerCache({ ttl: 30_000 }),
405
+ * ]),
406
+ * );
407
+ * ```
408
+ *
409
+ * @example Per-worker specs
410
+ * ```typescript
411
+ * withWorkerInterceptors({
412
+ * '*': [workerLogging()],
413
+ * 'secure': [workerHmacSigning({ keyMaterial })],
414
+ * });
415
+ * ```
416
+ */
417
+ function withWorkerInterceptors(specs) {
418
+ const map = Array.isArray(specs)
419
+ ? { '*': [...specs] }
420
+ : specs;
421
+ return {
422
+ kind: 'WorkerInterceptors',
423
+ providers: [{ provide: WORKER_HTTP_INTERCEPTORS_TOKEN, useValue: map }],
424
+ };
425
+ }
426
+
1
427
  /**
2
428
  * Generated bundle index. Do not edit.
3
429
  */
430
+
431
+ export { WORKER_HTTP_INTERCEPTORS_TOKEN, WORKER_HTTP_SERIALIZER_TOKEN, WORKER_TARGET, WorkerHttpBackend, WorkerHttpClient, matchWorkerRoute, provideWorkerHttpClient, toHttpResponse, toSerializableRequest, withWorkerConfigs, withWorkerFallback, withWorkerInterceptors, withWorkerRoutes, withWorkerSerialization };