Worker execution
Use laneops/worker for large pipelines that should run off the main thread. Node uses a fixed worker_threads pool with native/Wasm selection; browsers use module Workers with Wasm. All eight numeric pipeline dtypes are supported.
Submit a pipeline
import { createVectorExecutor } from 'laneops/worker'
const executor = await createVectorExecutor({ concurrency: 2, maxQueue: 8 })
try {
const controller = new AbortController()
const input = new Float32Array([1, 2, 3])
const task = executor.submit(
{ dtype: 'f32', nodes: [{ op: 'mul', operand: 2 }] },
input,
[],
{ signal: controller.signal },
)
input.fill(100) // submit already copied the original values
console.log(await task.result) // Float32Array [2, 4, 6]
// task.cancel() or controller.abort() cancels unfinished work.
} finally {
await executor.dispose()
}submit(plan, input, operands?, { signal }?) accepts a definition or pipeline.plan. It validates and copies input views synchronously, then returns { id, state, result, cancel }. Inputs are never detached by the executor. Results are independent arrays transferred back from the worker. There is no async out, arena-handle input or public transfer mode. Snapshot cost still scales with input bytes; small tasks may be faster with synchronous operations.
Cancellation and errors
Tasks transition from queued to running, then completed, cancelled, failed or disposed. Invalid inputs, pre-aborted signals, exhausted budgets, full queues and unavailable executors throw during submission. Once accepted, failures reject task.result; always observe that Promise. Running cancellation rejects immediately and terminates the worker. Its byte reservation remains until termination finishes, which may wait for a synchronous native kernel to return. Workers are replaced automatically; failed replacement initialization rejects remaining tasks and fails the executor. dispose() cancels unfinished work, waits for termination and returns the same Promise on repeated calls.
Queue and memory limits
Defaults: concurrency: 1 (maximum 16), maxQueue: 32, maxTaskBytes: 64 MiB, maxBytes: 256 MiB, startupTimeoutMs: 10000, taskTimeoutMs: 30000. The per-task reservation is 2 × (inputCount + 1) × inputByteLength + 4096, covering snapshots/output and worst-case Wasm scratch payload. This bounds admitted task data, not process RSS, caller-owned outputs, GC or retained Wasm heap capacity. getStats() exposes queue/running/retiring counts and current/peak reservations. info reports the latest successful backend for each pool slot.
Browser and Vite setup
Vite consumers should configure both Worker and Wasm URLs:
import { createVectorExecutor } from 'laneops/worker'
import workerUrl from 'laneops/worker/runtime?worker&url'
import scalarUrl from 'laneops/wasm/scalar?url'
import simdUrl from 'laneops/wasm/simd?url'
const executor = await createVectorExecutor({
workerUrl: new URL(workerUrl, location.href),
wasm: {
scalar: new URL(scalarUrl, location.href),
simd: new URL(simdUrl, location.href),
},
})The ?worker&url transform bundles the runtime's dependencies. Static ESM consumers can use laneops/worker/browser and the default adjacent assets. No shared memory or cross-origin isolation is required. Try pnpm example:vite with ?worker&simd=off or ?worker&simd=required. See the Worker contract for task states, error names and budget accounting, and performance guidance for deciding when to use an executor.