import * as i0 from '@angular/core'; import { Provider, InjectionToken, EnvironmentProviders, OnDestroy } from '@angular/core'; import { HttpContextToken, HttpBackend, HttpRequest, HttpEvent, HttpContext, HttpResponse } from '@angular/common/http'; import { WorkerSerializer } from '@angular-helpers/worker-http/serializer'; import { WorkerInterceptorSpec } from '@angular-helpers/worker-http/interceptors'; import { Observable } from 'rxjs'; /** * Discriminated union for worker HTTP feature kinds. * Mirrors Angular's HttpFeatureKind pattern. */ type WorkerHttpFeatureKind = 'WorkerConfigs' | 'WorkerRoutes' | 'WorkerFallback' | 'WorkerSerialization' | 'WorkerInterceptors' | 'Telemetry' | 'StreamsPolyfill'; /** * Feature object — mirrors Angular's HttpFeature shape. */ interface WorkerHttpFeature { readonly kind: K; readonly providers: Provider[]; } /** * Single worker definition. */ interface WorkerConfig { /** Unique identifier used for routing / explicit selection */ id: string; /** URL of the worker script (passed to `new Worker(url)`) */ workerUrl: URL; /** Maximum worker instances in the pool (default: 1) */ maxInstances?: number; /** * Execution mode for the worker. * * - `'worker'` (default) — Dedicated Web Worker. Each tab has its own worker instance. * - `'shared'` — Shared Web Worker. Multiple tabs share the same worker instances. */ mode?: 'worker' | 'shared'; /** * Name for the SharedWorker. Required when `mode: 'shared'` to ensure multiple * tabs connect to the same worker instance. If `maxInstances > 1`, names are * suffixed with the instance index (e.g. `api-1`, `api-2`). */ name?: string; } /** * URL-pattern to worker auto-routing rule. */ interface WorkerRoute { /** URL pattern to match (RegExp or string prefix) */ pattern: RegExp | string; /** Must match a WorkerConfig.id */ worker: string; /** Higher priority = evaluated first (default: 0) */ priority?: number; } /** * Fallback strategy when workers are unavailable (SSR, old browsers). */ type WorkerFallbackStrategy = 'main-thread' | 'error'; /** * Serializable HTTP request — POJO version of Angular's HttpRequest. * Structured-clone safe: no classes, no functions, no prototype chains. */ interface SerializableRequest { method: string; url: string; headers: Record; params: Record; body: unknown; responseType: 'json' | 'text' | 'blob' | 'arraybuffer'; withCredentials: boolean; context: Record; } /** * Serializable HTTP response — POJO version of Angular's HttpResponse. */ interface SerializableResponse { status: number; statusText: string; headers: Record; body: unknown; url: string; } /** * Telemetry hooks for `WorkerHttpBackend`. * * Extension point for APM integrations (Sentry, Datadog, OpenTelemetry, * ad-hoc metrics). Fires on the main thread, synchronously, at three * lifecycle points of every HTTP request that reaches * `WorkerHttpBackend`: * * - `onRequest` — after worker resolution / fallback decision, BEFORE * dispatch. * - `onResponse` — when a successful response is returned from the worker * (or the fallback `FetchBackend`). * - `onError` — when the request fails in transport or surfaces as a * non-2xx `HttpErrorResponse`. * * Subscribers are invoked inside a try/catch — a throwing or misbehaving * subscriber is isolated from the HTTP request and from every other * subscriber. Telemetry errors are logged via `console.error` and * swallowed. * * Multiple subscribers are supported via a multi-provider DI token. * Register with `withTelemetry(...)`; every call appends one subscriber. */ /** * How a request was dispatched. * * - `'worker'` — dispatched to a worker from the pool. * - `'fallback-fetch'` — dispatched to the main-thread `FetchBackend` * (SSR context, no matching route, unknown worker with `'main-thread'` * fallback strategy). */ type WorkerHttpTransportKind = 'worker' | 'fallback-fetch'; /** * Base shape shared by every telemetry event. */ interface WorkerHttpTelemetryEventBase { /** Stable id unique to this request within the process. Correlates all three events. */ readonly requestId: string; /** HTTP method (`'GET'`, `'POST'`, ...). */ readonly method: string; /** URL without query params, as Angular sees it. */ readonly url: string; /** URL with query params baked in. */ readonly urlWithParams: string; /** Worker id that served the request. `null` when routed to fallback fetch. */ readonly workerId: string | null; /** How the request was actually dispatched. */ readonly transport: WorkerHttpTransportKind; /** `performance.now()` value at emission time. */ readonly timestamp: number; } /** Fires before the request is dispatched. */ interface WorkerHttpRequestEvent extends WorkerHttpTelemetryEventBase { readonly kind: 'request'; } /** Fires when a successful response is returned. */ interface WorkerHttpResponseEvent extends WorkerHttpTelemetryEventBase { readonly kind: 'response'; readonly status: number; readonly durationMs: number; } /** Fires when the request fails. */ interface WorkerHttpErrorEvent extends WorkerHttpTelemetryEventBase { readonly kind: 'error'; readonly error: unknown; readonly durationMs: number; } /** * Telemetry subscriber — all callbacks are optional. */ interface WorkerHttpTelemetry { onRequest?(event: WorkerHttpRequestEvent): void; onResponse?(event: WorkerHttpResponseEvent): void; onError?(event: WorkerHttpErrorEvent): void; } /** * Per-worker interceptor specs map. Key is the worker id from * `WorkerConfig.id`, plus the special `'*'` wildcard that applies to every * worker. Specs from `'*'` are prepended to the worker-specific specs at * resolve time. */ type WorkerInterceptorSpecsMap = Readonly>; /** * Per-request HttpContextToken that carries the target worker ID. * * `null` → use URL-pattern auto-routing (or main-thread fallback if no route matches). * * @example * ```typescript * // With WorkerHttpClient (recommended) * this.http.get('/api/data', { worker: 'secure' }); * * // With standard HttpClient (power user) * this.http.get('/api/data', { * context: new HttpContext().set(WORKER_TARGET, 'secure'), * }); * ``` */ declare const WORKER_TARGET: HttpContextToken; /** * Per-request HttpContextToken carrying an external `AbortSignal`. * * When the signal fires, the backend posts a `cancel` message to the worker * and the request errors with `WorkerHttpAbortError` (wrapped by Angular into * `HttpErrorResponse`). * * @example * ```typescript * const ac = new AbortController(); * this.http.get('/api/users', { signal: ac.signal }); * // later * ac.abort(); * ``` */ declare const WORKER_HTTP_SIGNAL: HttpContextToken; /** * Per-request HttpContextToken carrying a timeout in milliseconds. * * When set, overrides the transport-level `requestTimeout` for this single * call. `0` or non-finite disables the timeout for this request only. * * On expiry the request errors with `WorkerHttpTimeoutError`. */ declare const WORKER_HTTP_TIMEOUT: HttpContextToken; /** * Optional serializer for crossing the worker boundary. * Provided via `withWorkerSerialization()`. Defaults to `null` (structured clone). * * When set, `WorkerHttpBackend` serializes the request body before `postMessage` * using this serializer. The worker-side `createWorkerPipeline()` receives the * serialized form — add a worker interceptor to deserialize it if needed. */ declare const WORKER_HTTP_SERIALIZER_TOKEN: InjectionToken; /** * Per-worker interceptor specs provided via `withWorkerInterceptors()`. * Defaults to an empty map (no interceptors). */ declare const WORKER_HTTP_INTERCEPTORS_TOKEN: InjectionToken>>; /** * Enable Safari streams polyfill for transferable ReadableStream/TransformStream * support in workers. Provided via `withWorkerStreamsPolyfill()`. * Defaults to `false` (native streams only). * * When enabled, the transport dynamically imports `@angular-helpers/worker-http/streams-polyfill` * on Safari 16-17 to enable stream transfer via postMessage. */ declare const WORKER_HTTP_STREAMS_POLYFILL_TOKEN: InjectionToken; /** * Sets up the worker HTTP infrastructure and replaces Angular's `HttpBackend` * with `WorkerHttpBackend`. * * Drop-in companion to `provideHttpClient()`. Can be used INSTEAD of it — * `HttpClient` and the full interceptor chain are included automatically. * * @example * ```typescript * // app.config.ts * export const appConfig: ApplicationConfig = { * providers: [ * provideWorkerHttpClient( * withWorkerConfigs([ * { id: 'public', workerUrl: new URL('./workers/public.worker', import.meta.url) }, * ]), * withWorkerRoutes([ * { pattern: /\/api\//, worker: 'public', priority: 1 }, * ]), * withWorkerFallback('main-thread'), * ), * ], * }; * ``` */ declare function provideWorkerHttpClient(...features: WorkerHttpFeature[]): EnvironmentProviders; /** * Registers worker definitions (id + workerUrl + optional pool size). * * At least one config is required for any request to reach a worker. * * @example * ```typescript * withWorkerConfigs([ * { id: 'public', workerUrl: new URL('./workers/public.worker', import.meta.url) }, * { id: 'secure', workerUrl: new URL('./workers/secure.worker', import.meta.url), maxInstances: 2 }, * ]) * ``` */ declare function withWorkerConfigs(configs: WorkerConfig[]): WorkerHttpFeature<'WorkerConfigs'>; /** * Declares URL-pattern → worker routing rules evaluated in priority order. * * When a request URL matches a pattern, the associated worker handles it. * Explicit `WORKER_TARGET` context always takes precedence over routes. * * @example * ```typescript * withWorkerRoutes([ * { pattern: /\/api\/secure\//, worker: 'secure', priority: 10 }, * { pattern: /\/api\//, worker: 'public', priority: 1 }, * ]) * ``` */ declare function withWorkerRoutes(routes: WorkerRoute[]): WorkerHttpFeature<'WorkerRoutes'>; /** * Sets the fallback strategy when workers are unavailable (SSR, old browsers, * or when no route matches). * * - `'main-thread'` (default) — silently delegates to `FetchBackend` * - `'error'` — throws, forcing explicit handling in the application * * @example * ```typescript * withWorkerFallback('main-thread') // SSR-safe * ``` */ declare function withWorkerFallback(strategy: WorkerFallbackStrategy): WorkerHttpFeature<'WorkerFallback'>; /** * Configures a custom serializer for crossing the worker boundary. * * By default `WorkerHttpBackend` relies on the browser's structured clone algorithm * (safe for plain objects, arrays, primitives, `Date`, `ArrayBuffer`). * Use `withWorkerSerialization` when your request bodies contain types that * structured clone cannot handle (e.g. class instances, circular references, `Map`, `Set`). * * **Worker-side note:** The serialized form is what the worker receives as `req.body`. * If you use `createSerovalSerializer` or similar, add a worker-side interceptor * to deserialize the body before calling `fetch()`. * * @example * ```typescript * import { createSerovalSerializer } from '@angular-helpers/worker-http/serializer'; * * provideWorkerHttpClient( * withWorkerConfigs([...]), * withWorkerSerialization(createSerovalSerializer()), * ) * ``` */ declare function withWorkerSerialization(serializer: WorkerSerializer): WorkerHttpFeature<'WorkerSerialization'>; /** * Configures the worker-side interceptor pipeline from Angular DI. * * Specs are forwarded to each worker via an `init-interceptors` handshake * message posted before any HTTP request. Workers must call * `createConfigurableWorkerPipeline()` to receive and act on the handshake. * * Two shapes are accepted: * - `WorkerInterceptorSpec[]` — applied to every registered worker * - `Record` — per-worker, with the * special `'*'` key applied to all workers in addition to the * worker-specific specs * * @example * ```typescript * provideWorkerHttpClient( * withWorkerConfigs([{ id: 'api', workerUrl: ... }]), * withWorkerInterceptors([ * workerLogging(), * workerRetry({ maxRetries: 3 }), * workerCache({ ttl: 30_000 }), * ]), * ); * ``` * * @example Per-worker specs * ```typescript * withWorkerInterceptors({ * '*': [workerLogging()], * 'secure': [workerHmacSigning({ keyMaterial })], * }); * ``` */ declare function withWorkerInterceptors(specs: readonly WorkerInterceptorSpec[] | WorkerInterceptorSpecsMap): WorkerHttpFeature<'WorkerInterceptors'>; /** * Registers a telemetry subscriber for `WorkerHttpBackend`. * * Telemetry hooks are a main-thread extension point for APM integrations * (Sentry, Datadog, OpenTelemetry, ad-hoc logging). They fire synchronously * at three lifecycle points of every HTTP request handled by the backend: * * - `onRequest` — after worker resolution, before dispatch * - `onResponse` — when a successful response is emitted * - `onError` — when the request fails * * The feature is repeatable — call `withTelemetry(...)` multiple times to * attach independent subscribers. All of them receive every event, in * registration order. A throwing subscriber is isolated: the backend * catches and logs the error so telemetry bugs never break the request. * * @example Basic counter * ```typescript * const counters = { requests: 0, errors: 0 }; * provideWorkerHttpClient( * withWorkerConfigs([...]), * withTelemetry({ * onRequest: () => counters.requests++, * onError: () => counters.errors++, * }), * ); * ``` * * @example Latency histogram * ```typescript * withTelemetry({ * onResponse: (e) => histogram.record(e.durationMs, { workerId: e.workerId }), * onError: (e) => histogram.record(e.durationMs, { workerId: e.workerId, error: true }), * }); * ``` */ declare function withTelemetry(telemetry: WorkerHttpTelemetry): WorkerHttpFeature<'Telemetry'>; /** * Enables the Safari streams polyfill for transferable ReadableStream/TransformStream * support in Web Workers. * * Safari 16-17 lacks native transferable streams. When this feature is enabled, * the transport layer dynamically loads a ponyfill that enables stream transfer * via postMessage on affected browsers. * * **When to use:** * - Your application uses `responseType: 'stream'` and targets Safari 16-17 * - You see "DataCloneError" when transferring streams to/from workers * * **Bundle impact:** The polyfill is lazy-loaded only on Safari 16-17 when * streams are actually used. Non-Safari browsers and modern Safari pay 0 bytes. * * @example * ```typescript * provideWorkerHttpClient( * withWorkerConfigs([...]), * withWorkerStreamsPolyfill(), // Enable for Safari compatibility * ) * ``` */ declare function withWorkerStreamsPolyfill(): WorkerHttpFeature<'StreamsPolyfill'>; /** * Angular `HttpBackend` replacement that routes HTTP requests to web workers. * * Registered via `provideWorkerHttpClient()`. Not meant to be used directly. * * Flow per request: * 1. Check SSR: if `Worker` is undefined → fallback strategy * 2. Resolve target worker ID from `WORKER_TARGET` context or URL-pattern routing * 3. Serialize `HttpRequest` → `SerializableRequest` (structured-clone safe) * 4. Dispatch to the worker's `WorkerTransport` * 5. Deserialize `SerializableResponse` → `HttpResponse` */ declare class WorkerHttpBackend extends HttpBackend implements OnDestroy { private readonly configs; private readonly routes; private readonly fallback; private readonly serializer; private readonly interceptorSpecs; private readonly fetchBackend; private readonly telemetry; private readonly streamsPolyfill; private readonly transports; handle(req: HttpRequest): Observable>; ngOnDestroy(): void; private getOrCreateTransport; private resolveSpecsFor; private handleFallback; private buildEventBase; private emitRequest; private emitResponse; private emitError; private dispatch; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵprov: i0.ɵɵInjectableDeclaration; } /** * Options accepted by `WorkerHttpClient` methods. * Identical to `HttpClient` options with an optional `worker` field added. */ interface WorkerRequestOptions { /** Target worker ID. Overrides URL-pattern routing for this specific request. */ worker?: string | null; /** * External `AbortSignal`. When it fires, the backend posts a `cancel` to * the worker and the request errors with `WorkerHttpAbortError` (wrapped in * `HttpErrorResponse`). Useful with `AbortController` or * `takeUntilDestroyed()`. */ signal?: AbortSignal; /** * Per-request timeout in milliseconds. Overrides the transport-level * `requestTimeout` for this single call. On expiry the request errors with * `WorkerHttpTimeoutError`. `0` or non-finite disables the timeout for this * request only. */ timeout?: number; context?: HttpContext; headers?: Record; params?: Record>; responseType?: 'json'; withCredentials?: boolean; observe?: 'body'; reportProgress?: boolean; transferCache?: { includeHeaders?: string[]; } | boolean; } /** * Convenience wrapper over `HttpClient` that adds an optional `{ worker }` field * to every method. Under the hood it sets `WORKER_TARGET` on the `HttpContext` — * the caller never has to touch the context manually. * * Usage is identical to `HttpClient` — just inject `WorkerHttpClient` instead. * * @example * ```typescript * @Injectable({ providedIn: 'root' }) * export class DataService { * private readonly http = inject(WorkerHttpClient); * * getUsers(): Observable { * return this.http.get('/api/users'); // auto-routed by URL pattern * } * * getSensitiveReport(): Observable { * return this.http.get('/api/secure/reports', { worker: 'secure' }); * } * } * ``` */ declare class WorkerHttpClient { private readonly http; get(url: string, options?: WorkerRequestOptions): Observable; post(url: string, body: unknown, options?: WorkerRequestOptions): Observable; put(url: string, body: unknown, options?: WorkerRequestOptions): Observable; patch(url: string, body: unknown, options?: WorkerRequestOptions): Observable; delete(url: string, options?: WorkerRequestOptions): Observable; head(url: string, options?: WorkerRequestOptions): Observable; private withWorker; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵprov: i0.ɵɵInjectableDeclaration; } /** * Converts an Angular `HttpRequest` into a structured-clone-safe POJO * that can be sent to a web worker via `postMessage`. * * Notes: * - `urlWithParams` is used so query params embedded via `HttpParams` are included. * - The `context` field is intentionally left empty: `HttpContext` uses class references * as keys which cannot cross the worker boundary. */ declare function toSerializableRequest(req: HttpRequest): SerializableRequest; /** * Converts a worker `SerializableResponse` back into an Angular `HttpResponse`. */ declare function toHttpResponse(res: SerializableResponse, req: HttpRequest): HttpResponse; /** * Matches a URL against a sorted list of `WorkerRoute` rules. * Rules with higher `priority` are evaluated first. * Returns the matched worker ID or `null` if no rule matches. */ declare function matchWorkerRoute(url: string, routes: Array<{ pattern: RegExp | string; worker: string; priority?: number; }>): string | null; export { WORKER_HTTP_INTERCEPTORS_TOKEN, WORKER_HTTP_SERIALIZER_TOKEN, WORKER_HTTP_SIGNAL, WORKER_HTTP_STREAMS_POLYFILL_TOKEN, WORKER_HTTP_TIMEOUT, WORKER_TARGET, WorkerHttpBackend, WorkerHttpClient, matchWorkerRoute, provideWorkerHttpClient, toHttpResponse, toSerializableRequest, withTelemetry, withWorkerConfigs, withWorkerFallback, withWorkerInterceptors, withWorkerRoutes, withWorkerSerialization, withWorkerStreamsPolyfill }; export type { SerializableRequest, SerializableResponse, WorkerConfig, WorkerFallbackStrategy, WorkerHttpErrorEvent, WorkerHttpFeature, WorkerHttpFeatureKind, WorkerHttpRequestEvent, WorkerHttpResponseEvent, WorkerHttpTelemetry, WorkerHttpTelemetryEventBase, WorkerHttpTransportKind, WorkerInterceptorSpecsMap, WorkerRequestOptions, WorkerRoute };