@vielzeug/familiar 1.0.8 → 2.0.1
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 +24 -47
- package/dist/_pool.cjs +1 -1
- package/dist/_pool.cjs.map +1 -1
- package/dist/_pool.d.ts +4 -12
- package/dist/_pool.d.ts.map +1 -1
- package/dist/_pool.js +163 -222
- package/dist/_pool.js.map +1 -1
- package/dist/_queue.cjs +1 -1
- package/dist/_queue.cjs.map +1 -1
- package/dist/_queue.d.ts +3 -39
- package/dist/_queue.d.ts.map +1 -1
- package/dist/_queue.js +27 -28
- package/dist/_queue.js.map +1 -1
- package/dist/_stream-pool.cjs +2 -0
- package/dist/_stream-pool.cjs.map +1 -0
- package/dist/_stream-pool.d.ts +11 -0
- package/dist/_stream-pool.d.ts.map +1 -0
- package/dist/_stream-pool.js +152 -0
- package/dist/_stream-pool.js.map +1 -0
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/familiar.cjs +1 -27
- package/dist/familiar.cjs.map +1 -1
- package/dist/familiar.iife.js +1 -27
- package/dist/familiar.iife.js.map +1 -1
- package/dist/familiar.js +1 -27
- package/dist/familiar.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +3 -2
- package/dist/protocol.cjs +1 -1
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.d.ts +40 -44
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +42 -27
- package/dist/protocol.js.map +1 -1
- package/dist/testing/testing.cjs +1 -1
- package/dist/testing/testing.cjs.map +1 -1
- package/dist/testing/testing.d.ts +15 -22
- package/dist/testing/testing.d.ts.map +1 -1
- package/dist/testing/testing.js +53 -35
- package/dist/testing/testing.js.map +1 -1
- package/dist/types.d.ts +49 -119
- package/dist/types.d.ts.map +1 -1
- package/dist/worker.cjs +1 -27
- package/dist/worker.cjs.map +1 -1
- package/dist/worker.d.ts +11 -67
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +142 -190
- package/dist/worker.js.map +1 -1
- package/package.json +37 -32
- package/dist/_dev.cjs +0 -2
- package/dist/_dev.cjs.map +0 -1
- package/dist/_dev.js +0 -6
- package/dist/_dev.js.map +0 -1
package/dist/worker.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker.cjs","names":[],"sources":["../src/worker.ts"],"sourcesContent":["// Re-export all public types and classes so consumers only need one import.\nexport type {\n BatchOptions,\n GroupOptions,\n RunOptions,\n TaskFn,\n TaskGroup,\n WorkerHandle,\n WorkerOptions,\n WorkerStatus,\n} from './types';\nexport {\n FamiliarError,\n FamiliarInvalidOptionsError,\n FamiliarQueueFullError,\n FamiliarRuntimeError,\n FamiliarTaskError,\n FamiliarTerminatedError,\n FamiliarTimeoutError,\n} from './errors';\n\nimport type { SlotStrategy, TaskFn, WorkerHandle, WorkerOptions } from './types';\n\nimport { warn } from './_dev';\nimport { createPool } from './_pool';\nimport { unrefTimer } from './_timers';\nimport {\n FamiliarInvalidOptionsError,\n FamiliarRuntimeError,\n FamiliarTaskError,\n FamiliarTerminatedError,\n FamiliarTimeoutError,\n} from './errors';\n\n// ─── task() — optional validation helper ─────────────────────────────────────\n\n/**\n * Optional helper that validates a function is safe to serialize for use in `createWorker`.\n *\n * `createWorker` accepts any `TaskFn` directly — this helper exists only to catch the common\n * mistake of passing a bound or native function whose body cannot be serialized.\n *\n * IMPORTANT: The function is serialized via `.toString()` and runs in an isolated Worker scope.\n * It **cannot** close over variables from the surrounding module — any outer reference resolves\n * to `undefined` inside the worker.\n *\n * @throws FamiliarInvalidOptionsError if the function is bound or native.\n *\n * @example\n * // Without task() — works fine for plain arrow functions:\n * const worker = createWorker((n: number) => n * 2);\n *\n * // With task() — catches the mistake of passing Math.sqrt directly:\n * const worker = createWorker(task((n: number) => Math.sqrt(n)));\n */\nexport function task<TInput, TOutput>(fn: TaskFn<TInput, TOutput>): TaskFn<TInput, TOutput> {\n if (fn.toString().includes('[native code]')) {\n throw new FamiliarInvalidOptionsError('Task function cannot be a bound or native function');\n }\n\n return fn;\n}\n\n// ─── Options resolution ───────────────────────────────────────────────────────\n\n/** Upper bound on `concurrency`: generous headroom over realistic hardware/IO limits while still catching obvious misconfiguration (e.g. a typo like `50000`). */\nconst MAX_CONCURRENCY = 512;\n\nfunction resolveConcurrency(value: WorkerOptions['concurrency']): number {\n if (value === undefined) return 1;\n\n if (value === 'auto') {\n return Math.max(1, globalThis.navigator?.hardwareConcurrency ?? 1);\n }\n\n if (!Number.isInteger(value) || value < 1 || value > MAX_CONCURRENCY) {\n throw new FamiliarInvalidOptionsError(`\\`concurrency\\` must be a positive integer ≤ ${MAX_CONCURRENCY} or \"auto\"`);\n }\n\n return value;\n}\n\nfunction resolveOptions(options: WorkerOptions = {}): {\n concurrency: number;\n heartbeatWindow: number | undefined;\n maxQueue: number | undefined;\n onFull: 'reject' | 'wait';\n onSlotError: WorkerOptions['onSlotError'];\n timeout: number | undefined;\n} {\n const concurrency = resolveConcurrency(options.concurrency);\n const { heartbeatWindow, maxQueue, onFull = 'reject', onSlotError, timeout } = options;\n\n if (timeout !== undefined && (!Number.isFinite(timeout) || timeout <= 0)) {\n throw new FamiliarInvalidOptionsError('`timeout` must be a finite number greater than 0');\n }\n\n if (maxQueue !== undefined && (!Number.isInteger(maxQueue) || maxQueue < 1)) {\n throw new FamiliarInvalidOptionsError('`maxQueue` must be a positive integer');\n }\n\n if (heartbeatWindow !== undefined && (!Number.isFinite(heartbeatWindow) || heartbeatWindow <= 0)) {\n throw new FamiliarInvalidOptionsError('`heartbeatWindow` must be a finite number greater than 0');\n }\n\n return { concurrency, heartbeatWindow, maxQueue, onFull, onSlotError, timeout };\n}\n\n// ─── Worker script builder ────────────────────────────────────────────────────\n\n/**\n * @security `fn.toString()` is injected verbatim into the blob script. Any code embedded in the\n * serialized function executes only inside the isolated Worker scope — no shared memory, no access\n * to the host page's globals. Risk is therefore low, but callers should only pass plain functions\n * that do not close over module scope.\n */\nfunction buildWorkerScript(fn: TaskFn<unknown, unknown>, heartbeatInterval: number | undefined): string {\n return `\nconst __fn = (${fn.toString()});\n\nself.onmessage = async function (event) {\n const { id, input, stream } = event.data;\n\n // Automatically send heartbeats at half the heartbeatWindow interval.\n let heartbeatTimer = null;\n ${heartbeatInterval != null ? `heartbeatTimer = setInterval(() => self.postMessage({ id, heartbeat: true }), ${heartbeatInterval});` : ''}\n\n try {\n if (stream) {\n const iterable = await __fn(input);\n for await (const chunk of iterable) {\n self.postMessage({ id, chunk });\n }\n self.postMessage({ id, result: undefined });\n } else {\n const result = await __fn(input);\n self.postMessage({ id, result });\n }\n } catch (error) {\n self.postMessage({ id, error });\n } finally {\n if (heartbeatTimer) clearInterval(heartbeatTimer);\n }\n}`.trim();\n}\n\n// ─── SlotConfig discriminated union ──────────────────────────────────────────\n\ntype SlotConfig<TInput, TOutput> =\n | { fn: TaskFn<TInput, TOutput>; heartbeatInterval: number | undefined; kind: 'inline' }\n | { kind: 'module'; url: string };\n\n// ─── PendingTask ─────────────────────────────────────────────────────────────\n\ntype SlotMessage<TOutput> =\n | { error: unknown; id: number }\n | { chunk: TOutput; id: number }\n | { heartbeat: true; id: number }\n | { id: number; result: TOutput };\n\ntype PendingTask<TOutput> = {\n /** Emits intermediate stream chunks. Undefined for non-streaming tasks. */\n emit?: (chunk: TOutput) => void;\n /** Called with an error when the streaming dispatch is cancelled mid-flight, so the finish closure drains its waiters. */\n finishStream?: (err: unknown) => void;\n /**\n * Single-shot timer that fires if no heartbeat is received within watchdogMs.\n * Reset (cleared + recreated) on every incoming heartbeat message.\n * Uses setTimeout because the watchdog is conceptually one-shot with manual reset,\n * not a recurring interval.\n */\n heartbeatWatchdog?: ReturnType<typeof setTimeout>;\n id: number;\n reject: (reason: unknown) => void;\n resolve: (value: TOutput) => void;\n timer?: ReturnType<typeof setTimeout>;\n /** Watchdog window in ms (= heartbeatWindow). Stored on PendingTask to reset the timer on each heartbeat. */\n watchdogMs?: number;\n};\n\n// ─── Slot — implements SlotStrategy ──────────────────────────────────────────\n\nclass Slot<TInput, TOutput> implements SlotStrategy<TInput, TOutput> {\n private readonly config: SlotConfig<TInput, TOutput>;\n private readonly onSlotError: WorkerOptions['onSlotError'];\n private disposed = false;\n private pending: PendingTask<TOutput> | null = null;\n private taskId = 0;\n private worker: Worker | null = null;\n\n constructor(config: SlotConfig<TInput, TOutput>, onSlotError?: WorkerOptions['onSlotError']) {\n this.config = config;\n this.onSlotError = onSlotError;\n }\n\n prime(): Promise<void> {\n if (this.disposed) return Promise.resolve();\n\n try {\n this.ensureWorker();\n } catch {\n // Best-effort — errors surface on the first run() call.\n }\n\n return Promise.resolve();\n }\n\n run(input: TInput, transferables: Transferable[], timeout: number | undefined): Promise<TOutput> {\n return this.dispatch(input, transferables, timeout, false) as Promise<TOutput>;\n }\n\n runStream(input: TInput, transferables: Transferable[], timeout: number | undefined): AsyncIterable<TOutput> {\n const chunks: TOutput[] = [];\n let done = false;\n let error: unknown;\n const waiters: Array<() => void> = [];\n\n const emit = (chunk: TOutput) => {\n chunks.push(chunk);\n waiters.shift()?.();\n };\n\n const finish = (err?: unknown) => {\n done = true;\n error = err;\n\n for (const w of waiters.splice(0)) w();\n };\n\n // Dispatch the task in stream mode. The promise resolves when the worker signals done.\n // finishStream is stored on the PendingTask so cancel() can drain the waiters if the\n // consumer exits early (break/throw from for-await), preventing a permanently dangling Promise.\n // this.pending is set synchronously inside dispatch(), so this assignment is safe.\n this.dispatch(input, transferables, timeout, true, emit).then(() => finish(), finish);\n\n if (this.pending) this.pending.finishStream = finish;\n\n return {\n [Symbol.asyncIterator]() {\n let cursor = 0;\n\n return {\n async next(): Promise<IteratorResult<TOutput>> {\n while (cursor >= chunks.length && !done) {\n await new Promise<void>((resolve) => waiters.push(resolve));\n }\n\n if (cursor < chunks.length) {\n const value = chunks[cursor]!;\n\n // Null-out the consumed slot so GC can collect the value\n // without waiting for the entire stream to close.\n (chunks as (TOutput | null)[])[cursor] = null;\n cursor++;\n\n return { done: false, value };\n }\n\n if (error !== undefined) throw error;\n\n return { done: true, value: undefined as unknown as TOutput };\n },\n };\n },\n };\n }\n\n cancel(): void {\n const pending = this.pending;\n\n if (!pending) return;\n\n clearTimeout(pending.timer);\n clearTimeout(pending.heartbeatWatchdog);\n this.pending = null;\n // Terminate the worker: the streaming task may still be running and sending chunks.\n // A fresh worker is created on the next run() or runStream() call via ensureWorker().\n this.stopWorker();\n // Drain the stream finish closure so any pending .next() waiters resolve immediately\n // rather than leaking as permanently dangling Promises.\n pending.finishStream?.(new FamiliarTerminatedError('Stream was cancelled'));\n }\n\n terminate(): void {\n this.disposed = true;\n this.stopWorker();\n this.failPending(new FamiliarTerminatedError());\n }\n\n private dispatch(\n input: TInput,\n transferables: Transferable[],\n timeout: number | undefined,\n stream: boolean,\n emit?: (chunk: TOutput) => void,\n ): Promise<TOutput | void> {\n if (this.disposed) {\n return Promise.reject(new FamiliarTerminatedError());\n }\n\n let worker: Worker;\n\n try {\n worker = this.ensureWorker();\n } catch (error) {\n return Promise.reject(error);\n }\n\n // watchdogMs = heartbeatInterval * 2 (Nyquist margin: worker beats at interval, host allows 2× before firing).\n const watchdogMs =\n this.config.kind === 'inline' && this.config.heartbeatInterval != null\n ? this.config.heartbeatInterval * 2\n : undefined;\n\n return new Promise<TOutput | void>((resolve, reject) => {\n const id = this.taskId++;\n const pending: PendingTask<TOutput> = {\n emit,\n id,\n reject,\n resolve: resolve as (v: TOutput) => void,\n watchdogMs,\n };\n\n if (timeout !== undefined) {\n pending.timer = setTimeout(() => {\n this.restart(new FamiliarTimeoutError(timeout));\n }, timeout);\n unrefTimer(pending.timer);\n }\n\n if (watchdogMs !== undefined) {\n pending.heartbeatWatchdog = setTimeout(() => {\n this.restart(new FamiliarTimeoutError(watchdogMs));\n }, watchdogMs);\n unrefTimer(pending.heartbeatWatchdog);\n }\n\n this.pending = pending;\n\n try {\n worker.postMessage({ id, input, stream }, transferables);\n } catch (err) {\n this.failPending(new FamiliarRuntimeError(err instanceof Error ? err.message : String(err), { cause: err }));\n }\n });\n }\n\n private ensureWorker(): Worker {\n if (this.worker) return this.worker;\n\n if (typeof globalThis.Worker !== 'function') {\n throw new FamiliarRuntimeError('Worker API is unavailable in this runtime');\n }\n\n let worker: Worker;\n\n if (this.config.kind === 'module') {\n try {\n worker = new Worker(this.config.url, { type: 'module' });\n } catch (error) {\n throw new FamiliarRuntimeError('Failed to create Worker', { cause: error });\n }\n } else {\n try {\n const blob = new Blob(\n [buildWorkerScript(this.config.fn as TaskFn<unknown, unknown>, this.config.heartbeatInterval)],\n { type: 'application/javascript' },\n );\n const url = URL.createObjectURL(blob);\n\n try {\n worker = new Worker(url);\n } finally {\n URL.revokeObjectURL(url);\n }\n } catch (error) {\n throw new FamiliarRuntimeError('Failed to create Worker', { cause: error });\n }\n }\n\n worker.onmessage = (event: MessageEvent<SlotMessage<TOutput>>) => {\n const pending = this.pending;\n\n if (!pending || event.data.id !== pending.id) return;\n\n // Handle heartbeat message — reset the watchdog timer.\n if ('heartbeat' in event.data) {\n if (pending.watchdogMs !== undefined) {\n clearTimeout(pending.heartbeatWatchdog);\n pending.heartbeatWatchdog = setTimeout(() => {\n this.restart(new FamiliarTimeoutError(pending.watchdogMs!));\n }, pending.watchdogMs);\n unrefTimer(pending.heartbeatWatchdog);\n }\n\n return;\n }\n\n if ('chunk' in event.data) {\n pending.emit?.(event.data.chunk);\n\n return;\n }\n\n clearTimeout(pending.timer);\n clearTimeout(pending.heartbeatWatchdog);\n this.pending = null;\n\n if ('error' in event.data) {\n const cause = event.data.error instanceof Error ? event.data.error : new Error(String(event.data.error));\n\n pending.reject(new FamiliarTaskError(cause.message, { cause }));\n } else {\n pending.resolve(event.data.result);\n }\n };\n\n worker.onerror = (event: ErrorEvent) => {\n const error = new FamiliarRuntimeError(event.message);\n\n // Stop and fail before calling the external callback so it sees a clean state.\n this.stopWorker();\n this.failPending(error);\n\n this.onSlotError?.(error, () => void this.prime());\n };\n\n this.worker = worker;\n\n return worker;\n }\n\n private failPending(reason: unknown): void {\n const pending = this.pending;\n\n if (!pending) return;\n\n clearTimeout(pending.timer);\n clearTimeout(pending.heartbeatWatchdog);\n this.pending = null;\n pending.reject(reason);\n }\n\n private restart(reason: unknown): void {\n this.stopWorker();\n this.failPending(reason);\n }\n\n private stopWorker(): void {\n if (!this.worker) return;\n\n this.worker.terminate();\n this.worker = null;\n }\n}\n\n// ─── createWorker ─────────────────────────────────────────────────────────────\n\n/**\n * Creates a pool of Web Workers that run `fn` in parallel.\n *\n * The task function is serialized via `.toString()` and runs in a separate global scope.\n * It cannot close over variables from the surrounding module.\n *\n * Use the optional `task()` helper to validate that the function is not bound or native.\n * For workers that need imports, see `createModuleWorker`.\n *\n * @example\n * // Plain arrow function — most common case:\n * const worker = createWorker((n: number) => n * 2);\n *\n * // With task() for validation:\n * const worker = createWorker(task((n: number) => n * 2));\n */\nexport function createWorker<TInput, TOutput>(\n fn: TaskFn<TInput, TOutput>,\n options?: WorkerOptions,\n): WorkerHandle<TInput, TOutput> {\n const { concurrency, heartbeatWindow, maxQueue, onFull, onSlotError, timeout } = resolveOptions(options);\n const heartbeatInterval = heartbeatWindow != null ? Math.floor(heartbeatWindow / 2) : undefined;\n\n const slots = Array.from(\n { length: concurrency },\n () => new Slot<TInput, TOutput>({ fn, heartbeatInterval, kind: 'inline' }, onSlotError),\n );\n\n return createPool(slots, {\n concurrency,\n defaultTimeout: timeout,\n maxQueue,\n onFull,\n });\n}\n\n// ─── createModuleWorker ───────────────────────────────────────────────────────\n\n/**\n * Creates a pool of module-type Web Workers loaded from a real URL.\n *\n * Unlike `createWorker`, the worker file is a regular module — it can import utilities,\n * use top-level await, and reference module scope.\n *\n * Use `handleMessages` from `@vielzeug/familiar/protocol` in the worker file to implement\n * the message protocol without boilerplate.\n *\n * **Protocol**: The worker module must handle the `{ id, input }` message format and reply\n * with `{ id, result }` or `{ id, error: { name, message, stack } }`. For streaming, it must\n * send one or more `{ id, chunk }` messages followed by `{ id, result: undefined }`.\n * For heartbeat support, send `{ id, heartbeat: true }` at regular intervals.\n *\n * @example\n * ```ts\n * // my-worker.ts — use handleMessages for zero boilerplate:\n * import { handleMessages } from '@vielzeug/familiar/protocol';\n * handleMessages(async (input: number) => input * 2);\n *\n * // main.ts\n * const pool = createModuleWorker<number, number>(\n * new URL('./my-worker.ts', import.meta.url),\n * { concurrency: 4 },\n * );\n * ```\n */\nexport function createModuleWorker<TInput, TOutput>(\n url: URL | string,\n options?: WorkerOptions,\n): WorkerHandle<TInput, TOutput> {\n const { concurrency, heartbeatWindow, maxQueue, onFull, onSlotError, timeout } = resolveOptions(options);\n\n if (heartbeatWindow !== undefined) {\n warn(\n '`heartbeatWindow` has no effect on module workers — the worker script must implement the heartbeat protocol manually.',\n );\n }\n\n const href = typeof url === 'string' ? url : url.href;\n\n const slots = Array.from(\n { length: concurrency },\n () => new Slot<TInput, TOutput>({ kind: 'module', url: href }, onSlotError),\n );\n\n return createPool(slots, {\n concurrency,\n defaultTimeout: timeout,\n maxQueue,\n onFull,\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"worker.cjs","names":["#onSlotError","#url","#worker","#settlePending","#disposed","#ensureWorker","#dispatch","#pending","#taskId","#onMessage"],"sources":["../src/worker.ts"],"sourcesContent":["export { batch, createTaskGroup } from './_pool';\nexport {\n FamiliarError,\n FamiliarInvalidOptionsError,\n FamiliarQueueFullError,\n FamiliarRuntimeError,\n FamiliarTaskError,\n FamiliarTerminatedError,\n FamiliarTimeoutError,\n} from './errors';\nexport type {\n BatchOptions,\n DrainOptions,\n RunOptions,\n StreamWorkerPool,\n TaskGroup,\n TaskGroupOptions,\n WorkerOptions,\n WorkerPool,\n WorkerStats,\n WorkerStatus,\n} from './types';\n\nimport { createPool } from './_pool';\nimport { createStreamPool, type StreamSlot } from './_stream-pool';\nimport { unrefTimer } from './_timers';\nimport {\n FamiliarInvalidOptionsError,\n FamiliarRuntimeError,\n FamiliarTaskError,\n FamiliarTerminatedError,\n FamiliarTimeoutError,\n} from './errors';\nimport type { SerializedError, WorkerResponse } from './protocol';\nimport { PROTOCOL_VERSION } from './protocol';\nimport type { RunOptions, SlotStrategy, StreamWorkerPool, WorkerOptions, WorkerPool } from './types';\n\nconst MAX_CONCURRENCY = 512;\n\ntype ResolvedOptions = {\n concurrency: number;\n maxQueue: number | undefined;\n onFull: 'reject' | 'wait';\n onSlotError: WorkerOptions['onSlotError'];\n timeout: number | undefined;\n};\n\ntype Pending<TOutput> = {\n emit?: (value: TOutput) => void;\n id: number;\n reject: (reason: unknown) => void;\n resolve: (value: TOutput) => void;\n timer?: ReturnType<typeof setTimeout>;\n};\n\nexport type RunningStream<TChunk> = {\n done: Promise<void>;\n iterable: AsyncIterable<TChunk>;\n};\n\nfunction resolveOptions(options: WorkerOptions = {}): ResolvedOptions {\n const { concurrency = 1, maxQueue, onFull = 'reject', onSlotError, timeout } = options;\n const resolvedConcurrency =\n concurrency === 'auto' ? Math.max(1, globalThis.navigator?.hardwareConcurrency ?? 1) : concurrency;\n\n if (!Number.isInteger(resolvedConcurrency) || resolvedConcurrency < 1 || resolvedConcurrency > MAX_CONCURRENCY) {\n throw new FamiliarInvalidOptionsError(`\\`concurrency\\` must be a positive integer ≤ ${MAX_CONCURRENCY} or \"auto\"`);\n }\n\n if (maxQueue !== undefined && (!Number.isInteger(maxQueue) || maxQueue < 1)) {\n throw new FamiliarInvalidOptionsError('`maxQueue` must be a positive integer');\n }\n\n if (timeout !== undefined && (!Number.isFinite(timeout) || timeout <= 0)) {\n throw new FamiliarInvalidOptionsError('`timeout` must be a finite number greater than 0');\n }\n\n return { concurrency: resolvedConcurrency, maxQueue, onFull, onSlotError, timeout };\n}\n\nfunction isWorkerResponse<TOutput>(value: unknown): value is WorkerResponse<TOutput> {\n if (typeof value !== 'object' || value === null) return false;\n\n const response = value as Partial<WorkerResponse<TOutput>>;\n\n return (\n response.version === PROTOCOL_VERSION &&\n typeof response.id === 'number' &&\n (response.kind === 'chunk' || response.kind === 'error' || response.kind === 'result')\n );\n}\n\nfunction taskError(error: SerializedError): FamiliarTaskError {\n const cause = new Error(error.message);\n\n cause.name = error.name;\n cause.stack = error.stack;\n\n return new FamiliarTaskError(error.message, { cause });\n}\n\nclass Slot<TInput, TOutput> implements SlotStrategy<TInput, TOutput>, StreamSlot<TInput, TOutput> {\n readonly #onSlotError: WorkerOptions['onSlotError'];\n readonly #url: URL | string;\n #disposed = false;\n #pending: Pending<TOutput> | undefined;\n #taskId = 0;\n #worker: Worker | undefined;\n\n constructor(url: URL | string, onSlotError: WorkerOptions['onSlotError']) {\n this.#url = url;\n this.#onSlotError = onSlotError;\n }\n\n cancel(reason: unknown): void {\n this.#worker?.terminate();\n this.#worker = undefined;\n this.#settlePending('reject', reason);\n }\n\n prime(): Promise<void> {\n if (!this.#disposed) this.#ensureWorker();\n\n return Promise.resolve();\n }\n\n run(input: TInput, transferables: Transferable[], timeout: number | undefined): Promise<TOutput> {\n return this.#dispatch(input, transferables, timeout, 'run') as Promise<TOutput>;\n }\n\n stream(input: TInput, options: RunOptions): RunningStream<TOutput> {\n const stop = () => this.cancel(new FamiliarTerminatedError('Stream consumer stopped'));\n const chunks: TOutput[] = [];\n const waiters: Array<(result: IteratorResult<TOutput>) => void> = [];\n let done = false;\n let error: unknown;\n const emit = (value: TOutput) => {\n const waiter = waiters.shift();\n\n if (waiter) waiter({ done: false, value });\n else chunks.push(value);\n };\n const finish = (reason?: unknown) => {\n done = true;\n error = reason;\n\n while (waiters.length > 0) {\n const waiter = waiters.shift()!;\n\n if (reason !== undefined) waiter(Promise.reject(reason) as never);\n else waiter({ done: true, value: undefined as never });\n }\n };\n const donePromise = this.#dispatch(input, options.transferables ?? [], options.timeout, 'stream', emit).then(\n () => finish(),\n (reason: unknown) => finish(reason),\n );\n\n return {\n done: donePromise,\n iterable: {\n [Symbol.asyncIterator]: () => ({\n async next(): Promise<IteratorResult<TOutput>> {\n if (chunks.length > 0) return { done: false, value: chunks.shift()! };\n\n if (error !== undefined) throw error;\n\n if (done) return { done: true, value: undefined as never };\n\n return new Promise<IteratorResult<TOutput>>((resolve, reject) => {\n waiters.push((result) => {\n if (result instanceof Promise) void result.then(resolve, reject);\n else resolve(result);\n });\n });\n },\n async return(): Promise<IteratorResult<TOutput>> {\n stop();\n\n return { done: true, value: undefined as never };\n },\n }),\n },\n };\n }\n\n terminate(): void {\n this.#disposed = true;\n this.cancel(new FamiliarTerminatedError());\n }\n\n #dispatch(\n input: TInput,\n transferables: Transferable[],\n timeout: number | undefined,\n kind: 'run' | 'stream',\n emit?: (value: TOutput) => void,\n ): Promise<TOutput> {\n if (this.#disposed) return Promise.reject(new FamiliarTerminatedError());\n\n if (this.#pending) return Promise.reject(new FamiliarRuntimeError('Worker slot is already busy'));\n\n try {\n const worker = this.#ensureWorker();\n const id = this.#taskId++;\n\n return new Promise<TOutput>((resolve, reject) => {\n const pending: Pending<TOutput> = { emit, id, reject, resolve };\n\n this.#pending = pending;\n\n if (timeout !== undefined) {\n pending.timer = setTimeout(() => this.cancel(new FamiliarTimeoutError(timeout)), timeout);\n unrefTimer(pending.timer);\n }\n\n try {\n worker.postMessage({ id, input, kind, version: PROTOCOL_VERSION }, transferables);\n } catch (error) {\n this.cancel(new FamiliarRuntimeError('Failed to post message to worker', { cause: error }));\n }\n });\n } catch (error) {\n return Promise.reject(error);\n }\n }\n\n #ensureWorker(): Worker {\n if (this.#worker) return this.#worker;\n\n if (typeof Worker === 'undefined') throw new FamiliarRuntimeError('Worker API is unavailable in this runtime');\n\n try {\n const worker = new Worker(this.#url, { type: 'module' });\n\n worker.onmessage = (event: MessageEvent<unknown>) => this.#onMessage(event.data);\n worker.onerror = (event) => {\n const error = new FamiliarRuntimeError(event.message || 'Worker failed');\n\n this.#worker = undefined;\n this.#onSlotError?.(error);\n this.#settlePending('reject', error);\n };\n this.#worker = worker;\n\n return worker;\n } catch (error) {\n throw new FamiliarRuntimeError('Failed to create Worker', { cause: error });\n }\n }\n\n #onMessage(message: unknown): void {\n if (!this.#pending) return;\n\n if (!isWorkerResponse<TOutput>(message) || message.id !== this.#pending.id) {\n this.cancel(new FamiliarRuntimeError('Worker returned an incompatible protocol response'));\n\n return;\n }\n\n if (message.kind === 'chunk') {\n this.#pending.emit?.(message.value);\n\n return;\n }\n\n if (message.kind === 'error') {\n this.#settlePending('reject', taskError(message.error));\n\n return;\n }\n\n this.#settlePending('resolve', message.value);\n }\n\n #settlePending(kind: 'resolve' | 'reject', value: unknown): void {\n const pending = this.#pending;\n\n if (!pending) return;\n\n this.#pending = undefined;\n\n if (pending.timer) clearTimeout(pending.timer);\n\n if (kind === 'resolve') pending.resolve(value as TOutput);\n else pending.reject(value);\n }\n}\n\nfunction slots<TInput, TOutput>(url: URL | string, options: ResolvedOptions): Slot<TInput, TOutput>[] {\n return Array.from({ length: options.concurrency }, () => new Slot<TInput, TOutput>(url, options.onSlotError));\n}\n\n/** Create a pool backed by an ES module worker registered with exposeTask(). */\nexport function createWorker<TInput, TOutput>(\n url: URL | string,\n options: WorkerOptions = {},\n): WorkerPool<TInput, TOutput> {\n const resolved = resolveOptions(options);\n\n return createPool(slots<TInput, TOutput>(url, resolved), {\n concurrency: resolved.concurrency,\n defaultTimeout: resolved.timeout,\n maxQueue: resolved.maxQueue,\n onFull: resolved.onFull,\n });\n}\n\n/** Create a stream-only pool backed by an ES module worker registered with exposeStream(). */\nexport function createStreamWorker<TInput, TChunk>(\n url: URL | string,\n options: WorkerOptions = {},\n): StreamWorkerPool<TInput, TChunk> {\n const resolved = resolveOptions(options);\n\n return createStreamPool(slots<TInput, TChunk>(url, resolved), {\n concurrency: resolved.concurrency,\n defaultTimeout: resolved.timeout,\n maxQueue: resolved.maxQueue,\n onFull: resolved.onFull,\n });\n}\n"],"mappings":"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"}
|
package/dist/worker.d.ts
CHANGED
|
@@ -1,69 +1,13 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { batch, createTaskGroup } from './_pool';
|
|
2
2
|
export { FamiliarError, FamiliarInvalidOptionsError, FamiliarQueueFullError, FamiliarRuntimeError, FamiliarTaskError, FamiliarTerminatedError, FamiliarTimeoutError, } from './errors';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
*
|
|
14
|
-
* @throws FamiliarInvalidOptionsError if the function is bound or native.
|
|
15
|
-
*
|
|
16
|
-
* @example
|
|
17
|
-
* // Without task() — works fine for plain arrow functions:
|
|
18
|
-
* const worker = createWorker((n: number) => n * 2);
|
|
19
|
-
*
|
|
20
|
-
* // With task() — catches the mistake of passing Math.sqrt directly:
|
|
21
|
-
* const worker = createWorker(task((n: number) => Math.sqrt(n)));
|
|
22
|
-
*/
|
|
23
|
-
export declare function task<TInput, TOutput>(fn: TaskFn<TInput, TOutput>): TaskFn<TInput, TOutput>;
|
|
24
|
-
/**
|
|
25
|
-
* Creates a pool of Web Workers that run `fn` in parallel.
|
|
26
|
-
*
|
|
27
|
-
* The task function is serialized via `.toString()` and runs in a separate global scope.
|
|
28
|
-
* It cannot close over variables from the surrounding module.
|
|
29
|
-
*
|
|
30
|
-
* Use the optional `task()` helper to validate that the function is not bound or native.
|
|
31
|
-
* For workers that need imports, see `createModuleWorker`.
|
|
32
|
-
*
|
|
33
|
-
* @example
|
|
34
|
-
* // Plain arrow function — most common case:
|
|
35
|
-
* const worker = createWorker((n: number) => n * 2);
|
|
36
|
-
*
|
|
37
|
-
* // With task() for validation:
|
|
38
|
-
* const worker = createWorker(task((n: number) => n * 2));
|
|
39
|
-
*/
|
|
40
|
-
export declare function createWorker<TInput, TOutput>(fn: TaskFn<TInput, TOutput>, options?: WorkerOptions): WorkerHandle<TInput, TOutput>;
|
|
41
|
-
/**
|
|
42
|
-
* Creates a pool of module-type Web Workers loaded from a real URL.
|
|
43
|
-
*
|
|
44
|
-
* Unlike `createWorker`, the worker file is a regular module — it can import utilities,
|
|
45
|
-
* use top-level await, and reference module scope.
|
|
46
|
-
*
|
|
47
|
-
* Use `handleMessages` from `@vielzeug/familiar/protocol` in the worker file to implement
|
|
48
|
-
* the message protocol without boilerplate.
|
|
49
|
-
*
|
|
50
|
-
* **Protocol**: The worker module must handle the `{ id, input }` message format and reply
|
|
51
|
-
* with `{ id, result }` or `{ id, error: { name, message, stack } }`. For streaming, it must
|
|
52
|
-
* send one or more `{ id, chunk }` messages followed by `{ id, result: undefined }`.
|
|
53
|
-
* For heartbeat support, send `{ id, heartbeat: true }` at regular intervals.
|
|
54
|
-
*
|
|
55
|
-
* @example
|
|
56
|
-
* ```ts
|
|
57
|
-
* // my-worker.ts — use handleMessages for zero boilerplate:
|
|
58
|
-
* import { handleMessages } from '@vielzeug/familiar/protocol';
|
|
59
|
-
* handleMessages(async (input: number) => input * 2);
|
|
60
|
-
*
|
|
61
|
-
* // main.ts
|
|
62
|
-
* const pool = createModuleWorker<number, number>(
|
|
63
|
-
* new URL('./my-worker.ts', import.meta.url),
|
|
64
|
-
* { concurrency: 4 },
|
|
65
|
-
* );
|
|
66
|
-
* ```
|
|
67
|
-
*/
|
|
68
|
-
export declare function createModuleWorker<TInput, TOutput>(url: URL | string, options?: WorkerOptions): WorkerHandle<TInput, TOutput>;
|
|
3
|
+
export type { BatchOptions, DrainOptions, RunOptions, StreamWorkerPool, TaskGroup, TaskGroupOptions, WorkerOptions, WorkerPool, WorkerStats, WorkerStatus, } from './types';
|
|
4
|
+
import type { StreamWorkerPool, WorkerOptions, WorkerPool } from './types';
|
|
5
|
+
export type RunningStream<TChunk> = {
|
|
6
|
+
done: Promise<void>;
|
|
7
|
+
iterable: AsyncIterable<TChunk>;
|
|
8
|
+
};
|
|
9
|
+
/** Create a pool backed by an ES module worker registered with exposeTask(). */
|
|
10
|
+
export declare function createWorker<TInput, TOutput>(url: URL | string, options?: WorkerOptions): WorkerPool<TInput, TOutput>;
|
|
11
|
+
/** Create a stream-only pool backed by an ES module worker registered with exposeStream(). */
|
|
12
|
+
export declare function createStreamWorker<TInput, TChunk>(url: URL | string, options?: WorkerOptions): StreamWorkerPool<TInput, TChunk>;
|
|
69
13
|
//# sourceMappingURL=worker.d.ts.map
|
package/dist/worker.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,EACL,aAAa,EACb,2BAA2B,EAC3B,sBAAsB,EACtB,oBAAoB,EACpB,iBAAiB,EACjB,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,UAAU,CAAC;AAClB,YAAY,EACV,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,gBAAgB,EAChB,SAAS,EACT,gBAAgB,EAChB,aAAa,EACb,UAAU,EACV,WAAW,EACX,YAAY,GACb,MAAM,SAAS,CAAC;AAcjB,OAAO,KAAK,EAA4B,gBAAgB,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAoBrG,MAAM,MAAM,aAAa,CAAC,MAAM,IAAI;IAClC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACpB,QAAQ,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CACjC,CAAC;AA2OF,gFAAgF;AAChF,wBAAgB,YAAY,CAAC,MAAM,EAAE,OAAO,EAC1C,GAAG,EAAE,GAAG,GAAG,MAAM,EACjB,OAAO,GAAE,aAAkB,GAC1B,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,CAS7B;AAED,8FAA8F;AAC9F,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAC/C,GAAG,EAAE,GAAG,GAAG,MAAM,EACjB,OAAO,GAAE,aAAkB,GAC1B,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,CASlC"}
|
package/dist/worker.js
CHANGED
|
@@ -1,227 +1,179 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import "./
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { unrefTimer as e } from "./_timers.js";
|
|
2
|
+
import { FamiliarError as t, FamiliarInvalidOptionsError as n, FamiliarQueueFullError as r, FamiliarRuntimeError as i, FamiliarTaskError as a, FamiliarTerminatedError as o, FamiliarTimeoutError as s } from "./errors.js";
|
|
3
|
+
import { batch as c, createPool as l, createTaskGroup as u } from "./_pool.js";
|
|
4
|
+
import { createStreamPool as d } from "./_stream-pool.js";
|
|
5
|
+
import "./protocol.js";
|
|
5
6
|
//#region src/worker.ts
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if (e === void 0) return 1;
|
|
13
|
-
if (e === "auto") return Math.max(1, globalThis.navigator?.hardwareConcurrency ?? 1);
|
|
14
|
-
if (!Number.isInteger(e) || e < 1 || e > u) throw new t(`\`concurrency\` must be a positive integer ≤ ${u} or "auto"`);
|
|
15
|
-
return e;
|
|
16
|
-
}
|
|
17
|
-
function f(e = {}) {
|
|
18
|
-
let n = d(e.concurrency), { heartbeatWindow: r, maxQueue: i, onFull: a = "reject", onSlotError: o, timeout: s } = e;
|
|
19
|
-
if (s !== void 0 && (!Number.isFinite(s) || s <= 0)) throw new t("`timeout` must be a finite number greater than 0");
|
|
20
|
-
if (i !== void 0 && (!Number.isInteger(i) || i < 1)) throw new t("`maxQueue` must be a positive integer");
|
|
21
|
-
if (r !== void 0 && (!Number.isFinite(r) || r <= 0)) throw new t("`heartbeatWindow` must be a finite number greater than 0");
|
|
7
|
+
var f = 512;
|
|
8
|
+
function p(e = {}) {
|
|
9
|
+
let { concurrency: t = 1, maxQueue: r, onFull: i = "reject", onSlotError: a, timeout: o } = e, s = t === "auto" ? Math.max(1, globalThis.navigator?.hardwareConcurrency ?? 1) : t;
|
|
10
|
+
if (!Number.isInteger(s) || s < 1 || s > f) throw new n(`\`concurrency\` must be a positive integer ≤ ${f} or "auto"`);
|
|
11
|
+
if (r !== void 0 && (!Number.isInteger(r) || r < 1)) throw new n("`maxQueue` must be a positive integer");
|
|
12
|
+
if (o !== void 0 && (!Number.isFinite(o) || o <= 0)) throw new n("`timeout` must be a finite number greater than 0");
|
|
22
13
|
return {
|
|
23
|
-
concurrency:
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
timeout: s
|
|
14
|
+
concurrency: s,
|
|
15
|
+
maxQueue: r,
|
|
16
|
+
onFull: i,
|
|
17
|
+
onSlotError: a,
|
|
18
|
+
timeout: o
|
|
29
19
|
};
|
|
30
20
|
}
|
|
31
|
-
function
|
|
32
|
-
return
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
let heartbeatTimer = null;
|
|
40
|
-
${t == null ? "" : `heartbeatTimer = setInterval(() => self.postMessage({ id, heartbeat: true }), ${t});`}
|
|
41
|
-
|
|
42
|
-
try {
|
|
43
|
-
if (stream) {
|
|
44
|
-
const iterable = await __fn(input);
|
|
45
|
-
for await (const chunk of iterable) {
|
|
46
|
-
self.postMessage({ id, chunk });
|
|
47
|
-
}
|
|
48
|
-
self.postMessage({ id, result: undefined });
|
|
49
|
-
} else {
|
|
50
|
-
const result = await __fn(input);
|
|
51
|
-
self.postMessage({ id, result });
|
|
52
|
-
}
|
|
53
|
-
} catch (error) {
|
|
54
|
-
self.postMessage({ id, error });
|
|
55
|
-
} finally {
|
|
56
|
-
if (heartbeatTimer) clearInterval(heartbeatTimer);
|
|
57
|
-
}
|
|
58
|
-
}`.trim();
|
|
21
|
+
function m(e) {
|
|
22
|
+
if (typeof e != "object" || !e) return !1;
|
|
23
|
+
let t = e;
|
|
24
|
+
return t.version === 1 && typeof t.id == "number" && (t.kind === "chunk" || t.kind === "error" || t.kind === "result");
|
|
25
|
+
}
|
|
26
|
+
function h(e) {
|
|
27
|
+
let t = Error(e.message);
|
|
28
|
+
return t.name = e.name, t.stack = e.stack, new a(e.message, { cause: t });
|
|
59
29
|
}
|
|
60
|
-
var
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
30
|
+
var g = class {
|
|
31
|
+
#e;
|
|
32
|
+
#t;
|
|
33
|
+
#n = !1;
|
|
34
|
+
#r;
|
|
35
|
+
#i = 0;
|
|
36
|
+
#a;
|
|
67
37
|
constructor(e, t) {
|
|
68
|
-
this
|
|
38
|
+
this.#t = e, this.#e = t;
|
|
39
|
+
}
|
|
40
|
+
cancel(e) {
|
|
41
|
+
this.#a?.terminate(), this.#a = void 0, this.#l("reject", e);
|
|
69
42
|
}
|
|
70
43
|
prime() {
|
|
71
|
-
|
|
72
|
-
try {
|
|
73
|
-
this.ensureWorker();
|
|
74
|
-
} catch {}
|
|
75
|
-
return Promise.resolve();
|
|
44
|
+
return this.#n || this.#s(), Promise.resolve();
|
|
76
45
|
}
|
|
77
46
|
run(e, t, n) {
|
|
78
|
-
return this
|
|
47
|
+
return this.#o(e, t, n, "run");
|
|
79
48
|
}
|
|
80
|
-
|
|
81
|
-
let r = [], i =
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
49
|
+
stream(e, t) {
|
|
50
|
+
let n = () => this.cancel(new o("Stream consumer stopped")), r = [], i = [], a = !1, s, c = (e) => {
|
|
51
|
+
let t = i.shift();
|
|
52
|
+
t ? t({
|
|
53
|
+
done: !1,
|
|
54
|
+
value: e
|
|
55
|
+
}) : r.push(e);
|
|
56
|
+
}, l = (e) => {
|
|
57
|
+
for (a = !0, s = e; i.length > 0;) i.shift()(e === void 0 ? {
|
|
58
|
+
done: !0,
|
|
59
|
+
value: void 0
|
|
60
|
+
} : Promise.reject(e));
|
|
86
61
|
};
|
|
87
|
-
return
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
let t = r[e];
|
|
93
|
-
return r[e] = null, e++, {
|
|
62
|
+
return {
|
|
63
|
+
done: this.#o(e, t.transferables ?? [], t.timeout, "stream", c).then(() => l(), (e) => l(e)),
|
|
64
|
+
iterable: { [Symbol.asyncIterator]: () => ({
|
|
65
|
+
async next() {
|
|
66
|
+
if (r.length > 0) return {
|
|
94
67
|
done: !1,
|
|
95
|
-
value:
|
|
68
|
+
value: r.shift()
|
|
69
|
+
};
|
|
70
|
+
if (s !== void 0) throw s;
|
|
71
|
+
return a ? {
|
|
72
|
+
done: !0,
|
|
73
|
+
value: void 0
|
|
74
|
+
} : new Promise((e, t) => {
|
|
75
|
+
i.push((n) => {
|
|
76
|
+
n instanceof Promise ? n.then(e, t) : e(n);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
async return() {
|
|
81
|
+
return n(), {
|
|
82
|
+
done: !0,
|
|
83
|
+
value: void 0
|
|
96
84
|
};
|
|
97
85
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
done: !0,
|
|
101
|
-
value: void 0
|
|
102
|
-
};
|
|
103
|
-
} };
|
|
104
|
-
} };
|
|
105
|
-
}
|
|
106
|
-
cancel() {
|
|
107
|
-
let e = this.pending;
|
|
108
|
-
e && (clearTimeout(e.timer), clearTimeout(e.heartbeatWatchdog), this.pending = null, this.stopWorker(), e.finishStream?.(new a("Stream was cancelled")));
|
|
86
|
+
}) }
|
|
87
|
+
};
|
|
109
88
|
}
|
|
110
89
|
terminate() {
|
|
111
|
-
this
|
|
90
|
+
this.#n = !0, this.cancel(new o());
|
|
112
91
|
}
|
|
113
|
-
|
|
114
|
-
if (this
|
|
115
|
-
|
|
92
|
+
#o(t, n, r, a, c) {
|
|
93
|
+
if (this.#n) return Promise.reject(new o());
|
|
94
|
+
if (this.#r) return Promise.reject(new i("Worker slot is already busy"));
|
|
116
95
|
try {
|
|
117
|
-
|
|
96
|
+
let o = this.#s(), l = this.#i++;
|
|
97
|
+
return new Promise((u, d) => {
|
|
98
|
+
let f = {
|
|
99
|
+
emit: c,
|
|
100
|
+
id: l,
|
|
101
|
+
reject: d,
|
|
102
|
+
resolve: u
|
|
103
|
+
};
|
|
104
|
+
this.#r = f, r !== void 0 && (f.timer = setTimeout(() => this.cancel(new s(r)), r), e(f.timer));
|
|
105
|
+
try {
|
|
106
|
+
o.postMessage({
|
|
107
|
+
id: l,
|
|
108
|
+
input: t,
|
|
109
|
+
kind: a,
|
|
110
|
+
version: 1
|
|
111
|
+
}, n);
|
|
112
|
+
} catch (e) {
|
|
113
|
+
this.cancel(new i("Failed to post message to worker", { cause: e }));
|
|
114
|
+
}
|
|
115
|
+
});
|
|
118
116
|
} catch (e) {
|
|
119
117
|
return Promise.reject(e);
|
|
120
118
|
}
|
|
121
|
-
let u = this.config.kind === "inline" && this.config.heartbeatInterval != null ? this.config.heartbeatInterval * 2 : void 0;
|
|
122
|
-
return new Promise((a, d) => {
|
|
123
|
-
let f = this.taskId++, p = {
|
|
124
|
-
emit: c,
|
|
125
|
-
id: f,
|
|
126
|
-
reject: d,
|
|
127
|
-
resolve: a,
|
|
128
|
-
watchdogMs: u
|
|
129
|
-
};
|
|
130
|
-
n !== void 0 && (p.timer = setTimeout(() => {
|
|
131
|
-
this.restart(new o(n));
|
|
132
|
-
}, n), s(p.timer)), u !== void 0 && (p.heartbeatWatchdog = setTimeout(() => {
|
|
133
|
-
this.restart(new o(u));
|
|
134
|
-
}, u), s(p.heartbeatWatchdog)), this.pending = p;
|
|
135
|
-
try {
|
|
136
|
-
l.postMessage({
|
|
137
|
-
id: f,
|
|
138
|
-
input: e,
|
|
139
|
-
stream: i
|
|
140
|
-
}, t);
|
|
141
|
-
} catch (e) {
|
|
142
|
-
this.failPending(new r(e instanceof Error ? e.message : String(e), { cause: e }));
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
119
|
}
|
|
146
|
-
|
|
147
|
-
if (this
|
|
148
|
-
if (typeof
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
e =
|
|
120
|
+
#s() {
|
|
121
|
+
if (this.#a) return this.#a;
|
|
122
|
+
if (typeof Worker > "u") throw new i("Worker API is unavailable in this runtime");
|
|
123
|
+
try {
|
|
124
|
+
let e = new Worker(this.#t, { type: "module" });
|
|
125
|
+
return e.onmessage = (e) => this.#c(e.data), e.onerror = (e) => {
|
|
126
|
+
let t = new i(e.message || "Worker failed");
|
|
127
|
+
this.#a = void 0, this.#e?.(t), this.#l("reject", t);
|
|
128
|
+
}, this.#a = e, e;
|
|
152
129
|
} catch (e) {
|
|
153
|
-
throw new
|
|
130
|
+
throw new i("Failed to create Worker", { cause: e });
|
|
154
131
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
132
|
+
}
|
|
133
|
+
#c(e) {
|
|
134
|
+
if (this.#r) {
|
|
135
|
+
if (!m(e) || e.id !== this.#r.id) {
|
|
136
|
+
this.cancel(new i("Worker returned an incompatible protocol response"));
|
|
137
|
+
return;
|
|
161
138
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
return e.onmessage = (e) => {
|
|
166
|
-
let t = this.pending;
|
|
167
|
-
if (!(!t || e.data.id !== t.id)) {
|
|
168
|
-
if ("heartbeat" in e.data) {
|
|
169
|
-
t.watchdogMs !== void 0 && (clearTimeout(t.heartbeatWatchdog), t.heartbeatWatchdog = setTimeout(() => {
|
|
170
|
-
this.restart(new o(t.watchdogMs));
|
|
171
|
-
}, t.watchdogMs), s(t.heartbeatWatchdog));
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
if ("chunk" in e.data) {
|
|
175
|
-
t.emit?.(e.data.chunk);
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
if (clearTimeout(t.timer), clearTimeout(t.heartbeatWatchdog), this.pending = null, "error" in e.data) {
|
|
179
|
-
let n = e.data.error instanceof Error ? e.data.error : Error(String(e.data.error));
|
|
180
|
-
t.reject(new i(n.message, { cause: n }));
|
|
181
|
-
} else t.resolve(e.data.result);
|
|
139
|
+
if (e.kind === "chunk") {
|
|
140
|
+
this.#r.emit?.(e.value);
|
|
141
|
+
return;
|
|
182
142
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
let t = this.pending;
|
|
190
|
-
t && (clearTimeout(t.timer), clearTimeout(t.heartbeatWatchdog), this.pending = null, t.reject(e));
|
|
191
|
-
}
|
|
192
|
-
restart(e) {
|
|
193
|
-
this.stopWorker(), this.failPending(e);
|
|
143
|
+
if (e.kind === "error") {
|
|
144
|
+
this.#l("reject", h(e.error));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
this.#l("resolve", e.value);
|
|
148
|
+
}
|
|
194
149
|
}
|
|
195
|
-
|
|
196
|
-
|
|
150
|
+
#l(e, t) {
|
|
151
|
+
let n = this.#r;
|
|
152
|
+
n && (this.#r = void 0, n.timer && clearTimeout(n.timer), e === "resolve" ? n.resolve(t) : n.reject(t));
|
|
197
153
|
}
|
|
198
154
|
};
|
|
199
|
-
function
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
onFull: a
|
|
155
|
+
function _(e, t) {
|
|
156
|
+
return Array.from({ length: t.concurrency }, () => new g(e, t.onSlotError));
|
|
157
|
+
}
|
|
158
|
+
function v(e, t = {}) {
|
|
159
|
+
let n = p(t);
|
|
160
|
+
return l(_(e, n), {
|
|
161
|
+
concurrency: n.concurrency,
|
|
162
|
+
defaultTimeout: n.timeout,
|
|
163
|
+
maxQueue: n.maxQueue,
|
|
164
|
+
onFull: n.onFull
|
|
210
165
|
});
|
|
211
166
|
}
|
|
212
|
-
function
|
|
213
|
-
let
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
defaultTimeout: s,
|
|
220
|
-
maxQueue: i,
|
|
221
|
-
onFull: a
|
|
167
|
+
function y(e, t = {}) {
|
|
168
|
+
let n = p(t);
|
|
169
|
+
return d(_(e, n), {
|
|
170
|
+
concurrency: n.concurrency,
|
|
171
|
+
defaultTimeout: n.timeout,
|
|
172
|
+
maxQueue: n.maxQueue,
|
|
173
|
+
onFull: n.onFull
|
|
222
174
|
});
|
|
223
175
|
}
|
|
224
176
|
//#endregion
|
|
225
|
-
export {
|
|
177
|
+
export { t as FamiliarError, n as FamiliarInvalidOptionsError, r as FamiliarQueueFullError, i as FamiliarRuntimeError, a as FamiliarTaskError, o as FamiliarTerminatedError, s as FamiliarTimeoutError, c as batch, y as createStreamWorker, u as createTaskGroup, v as createWorker };
|
|
226
178
|
|
|
227
179
|
//# sourceMappingURL=worker.js.map
|