@varunkanwar/atelier
v0.1.1
Published
A task runtime for browser workloads with parallelism, backpressure, and cancellation
Maintainers
Readme
Atelier
Atelier is a browser-only task runtime for Web Worker workloads that need predictable concurrency, backpressure, and cancellation without adopting a pipeline DSL. It is intentionally small: a runtime, task proxies, and two executors backed by a shared queue.
Use it when you have CPU-heavy or bursty work in the browser and you need to control how much work is in flight and what happens under load. It does not try to schedule across tasks or define a pipeline language; those decisions stay with your app.
Installation
bun add @varunkanwar/atelier
# or
npm install @varunkanwar/atelierQuick start
Main thread:
import { createTaskRuntime } from '@varunkanwar/atelier'
type ResizeAPI = {
process: (image: ImageData) => Promise<ImageData>
}
const runtime = createTaskRuntime()
const resize = runtime.defineTask<ResizeAPI>({
type: 'parallel',
worker: () => new Worker(new URL('./resize.worker.ts', import.meta.url), { type: 'module' }),
keyOf: image => image.docId,
timeoutMs: 10_000,
})
const result = await resize.process(image)Worker:
import { expose } from 'comlink'
import { createTaskWorker, type TaskContext, type StripTaskContext } from '@varunkanwar/atelier'
const handlers = {
async process(image: ImageData, ctx: TaskContext) {
ctx.throwIfAborted()
return resized
},
}
export type ResizeAPI = StripTaskContext<typeof handlers>
expose(createTaskWorker(handlers))How it works
Each task call flows through a DispatchQueue that enforces maxInFlight and
maxQueueDepth. A call moves through three phases: waiting (call-site blocked
before admission), pending (accepted but not dispatched), and in-flight (running
on a worker). When the queue is full, the policy determines whether callers
wait, are rejected, or are dropped.
For pipeline-level flow control, parallelLimit caps concurrency across a set
of items without introducing a DSL. It pairs well with queue backpressure to
avoid large intermediate allocations.
Cancellation and timeouts
If you provide a keyOf function, AbortTaskController can cancel all queued
and in-flight work for a given key. timeoutMs creates an AbortSignal per call
and is treated like cancellation. Cancellation can happen while waiting,
queued, or in-flight; the worker harness exposes __cancel so handlers can
cooperate.
const runtime = createTaskRuntime()
const resize = runtime.defineTask<ResizeAPI>({ /* ... */, keyOf: image => image.docId })
const promise = resize.process(image) // image.docId === 'doc-123'
runtime.abortTaskController.abort('doc-123')
await promiseZero-copy transfers
Atelier automatically transfers common large data types (ArrayBuffer, ImageData, ImageBitmap, streams, etc.) to avoid structured cloning. You can override that per call:
// Disable transfer for debugging or to keep ownership
await resize.with({ transfer: [] }).process(imageData)
// Selective transfer
await colorCorrect.with({ transfer: [image.data.buffer] }).process(image, lut)
// Keep the result in the worker
await encoder.with({ transferResult: false }).addFrame(frame)Transfers move ownership: the sender’s buffers become detached. Clone first if you need to keep the original.
Observability
The runtime exposes a state snapshot API plus an event stream for metrics, spans and traces. Spans are opt-in and sampled; events are emitted only when listeners are registered.
const runtime = createTaskRuntime({
observability: { spans: { mode: 'auto', sampleRate: 1 } },
})
const unsubscribe = runtime.subscribeEvents(event => {
// MetricEvent | SpanEvent | TraceEvent
})Docs
- Design notes: https://github.com/VarunKanwar/atelier/blob/main/docs/design/README.md
- Observability model: https://github.com/VarunKanwar/atelier/blob/main/docs/design/observability.md
- API reference (curated and published at
/docs/on the site) - Testing: https://github.com/VarunKanwar/atelier/blob/main/docs/testing.md
- Demo site (scenarios and UI): https://github.com/VarunKanwar/atelier/tree/main/apps/site
Development
bun install
bun run test
bun run check:fix