/** * Web Worker {@link IsolationTransport} + `spawnIsolated` convenience for browser isolation. * * @remarks * Implements the `IsolationTransport`/`PortLike` ducks declared (and treated as a read-only contract) in * `types.ts` against a real browser `Worker`, and nothing else: this module never touches `protocol.ts`, * `host.ts`, `serve.ts`, or `codec.ts` directly — it only produces the transport `createIsolatedService` * (from `host.ts`) drives. * * The project's tsconfig limits `lib` to `ESNext`, so the DOM `Worker`/`WorkerOptions` types referenced * below are not in scope by default (`ErrorEvent`/`MessageEvent` ARE ambiently available via * `@types/node`'s `web-globals/*.d.ts` global augmentation, but the DOM `Worker` class is not — only * node's unrelated `worker_threads.Worker` is). Re-declare here the **minimum** surface this module * touches, mirroring the OPFS storage battery's established convention (`src/batteries/storage/opfs/ * index.ts`) — structurally compatible with the real DOM types, so callers pass real `Worker` instances * straight through. */ import type { IsolatedService, IsolatedServiceOptions } from "./host"; import type { IsolatedServiceSpec, IsolationTransport } from "./types"; /** Minimal subset of the DOM `MessageEvent` interface this module touches. */ export interface BrowserMessageEvent { /** The message payload delivered via `postMessage`. */ readonly data: unknown; } /** Minimal subset of the DOM `ErrorEvent` interface this module touches — fired on a `Worker` instance * when an uncaught error escapes the worker's top-level scope. */ export interface BrowserErrorEvent { /** Human-readable error message. */ readonly message: string; } /** Minimal subset of the DOM `WorkerOptions` dictionary this module forwards verbatim to `new * Worker(url, options)` — used ONLY for the `string | URL` spawn form (a caller-supplied * {@link WorkerResolver} constructs its own `Worker` however it likes, this dictionary never applies * there). Classic scripts are the default (`type` omitted) — matching this repo's own LiteRT-LM Worker * prototype (`docs/.vitepress/theme/components/agent/litert_lm_worker_proxy.ts`), which deliberately * avoids `{ type: 'module' }` because Emscripten-style glue calls `importScripts()`, illegal in a module * worker. Pass `{ type: 'module' }` explicitly when the guest script is an ES module. */ export interface BrowserWorkerOptions { /** `'classic'` (default when omitted) or `'module'`. */ type?: 'classic' | 'module'; /** Worker credentials mode, forwarded verbatim. */ credentials?: 'omit' | 'same-origin' | 'include'; /** A developer-facing name for the worker (surfaced in devtools). */ name?: string; } /** Minimal subset of the DOM `Worker` interface this module touches. Structurally compatible with the * real DOM `Worker` — callers (and {@link WorkerResolver} implementations) pass/construct real `Worker` * instances directly. */ export interface BrowserWorker { /** Post a message to the worker, optionally transferring ownership of listed transferables. */ postMessage(message: unknown, transfer?: unknown[]): void; /** Subscribe to the worker's `'message'` event (fired on every `postMessage` received from the guest). */ addEventListener(type: 'message', listener: (ev: BrowserMessageEvent) => void): void; /** Subscribe to the worker's `'error'` event (fired when an uncaught error escapes the guest's * top-level scope). */ addEventListener(type: 'error', listener: (ev: BrowserErrorEvent) => void): void; /** Subscribe to the worker's `'messageerror'` event (fired when a received message could not be * deserialized). */ addEventListener(type: 'messageerror', listener: (ev: BrowserMessageEvent) => void): void; /** Unsubscribe a previously-added listener. */ removeEventListener(type: 'message' | 'error' | 'messageerror', listener: (ev: never) => void): void; /** Terminate the worker immediately — no further events, no graceful shutdown at this layer. */ terminate(): void; } /** * Bring-your-own Worker spawner — the first-class seam for handing {@link spawnIsolated}/{@link * createWorkerTransport} a `Worker` constructed however the caller's bundler/pooling strategy demands * (a `new Worker(new URL(...), import.meta.url)` Vite/webpack pattern, a worker pool that recycles * threads, a test harness's Blob-URL worker, etc.). Called once per `connect()` (including every * `recycle()`) — see {@link createWorkerTransport}'s remarks for why this makes a resolver the SINGLE * source of Worker creation for a given transport. */ export type WorkerResolver = (ctx: { spec: IsolatedServiceSpec; }) => BrowserWorker | Promise; /** Options accepted by {@link spawnIsolated}/{@link createWorkerTransport}, layered on top of {@link * IsolatedServiceOptions}. */ export interface SpawnIsolatedOptions extends IsolatedServiceOptions { /** * How to obtain the guest `Worker`: * * - A `string | URL` — the guest script's URL; `createWorkerTransport` constructs `new Worker(url, * workerOptions)` itself on every `connect()`. * - A {@link WorkerResolver} — full control: bring a `Worker` from any bundler pattern or pool. Invoked * with `{ spec }` and may return a `Worker` synchronously or via a `Promise`. */ worker: string | URL | WorkerResolver; /** Forwarded verbatim to `new Worker(url, workerOptions)` — used ONLY for the `string | URL` spawn * form (ignored when `worker` is a {@link WorkerResolver}, which constructs its own `Worker`). * Default: classic script (no `type`) — see {@link BrowserWorkerOptions}'s doc for why. */ workerOptions?: BrowserWorkerOptions; } /** * Build an {@link IsolationTransport} that spawns/re-spawns a real browser `Worker` per {@link * SpawnIsolatedOptions.worker}. * * @remarks * `connect()` resolves the `Worker` (constructing it for a `string | URL` spec, or awaiting a {@link * WorkerResolver}), wraps it in a {@link PortLike} (`post` → `postMessage` with any {@link * @nhtio/adk/batteries/isolation!transfer}-marked values unwrapped into the transfer list; `onMessage` → * `addEventListener('message', ...)`), and wires the worker's `'error'`/`'messageerror'` events to the * transport's `onCrash` handlers. `terminate()` calls `worker.terminate()`. `createIsolatedService`'s * `recycle()` re-enters this SAME `connect()` — since a resolver is invoked fresh on every call, it is * the single source of Worker creation for the service's whole lifetime (every respawn goes through it, * never a cached instance). */ export declare const createWorkerTransport: (spec: IsolatedServiceSpec, options: SpawnIsolatedOptions) => IsolationTransport; /** * Sugar for `createIsolatedService(spec, createWorkerTransport(spec, options), options)` — spawn a * real Worker-backed {@link IsolatedService} in one call. * * @remarks * The transport-only keys (`worker`/`workerOptions`) are stripped before the remaining options reach * `createIsolatedService` — its validator is deliberately strict (unknown keys rejected), accepting * only the base {@link IsolatedServiceOptions} shape; the transport-only keys were already validated * (and consumed) by {@link createWorkerTransport}. * * @throws {@link @nhtio/adk/batteries/isolation!E_ISOLATION_UNSUPPORTED_ENV} when no browser `Worker` * global is present. * @throws {@link @nhtio/adk/batteries/isolation!E_INVALID_ISOLATION_OPTIONS} when `options` fails * validation. */ export declare const spawnIsolated: (spec: S, options: SpawnIsolatedOptions) => IsolatedService;