Guide · @staticflux/worker · @staticflux/document · @staticflux/wasm · @staticflux/surface
Workers, documents, Wasm & surfaces
Heavy work runs off the main thread by default — supervised, replicated, traced end to end, and with a fallback for every browser gap.
#Supervised workers and typed protocols
export const DocumentWorkerProtocol = defineProtocol({
id: 'studio/document-worker',
version: 1,
requests: { open: request<OpenArgs, { readonly entities: number }>({ idempotent: true }) },
events: { archived: event<{ readonly docId: string; readonly archive: DocumentArchive }>() },
});
const worker = createSupervisedWorker({
id: 'studio/document-worker',
protocol: DocumentWorkerProtocol,
spawn: dedicatedWorkerSpawner(() => new Worker(new URL('./document.worker.ts', import.meta.url), { type: 'module' })),
contexts, // session, flags, decisions, platform → every worker
replicas: [replica, presence],
rehydrate: () => lastArchive, // state restored after a respawn
heartbeat: { intervalMs: 5_000, timeoutMs: 4_000, maxMissed: 3 },
restart: { intensity: 3, periodMs: 60_000, baseDelayMs: 50 },
fallback: { description: 'Read-only view', activate: () => readOnly.set(true), deactivate: () => readOnly.set(false) },
});
const opened = await worker.request('open', { docId: '42', blocks }, { signal });
- Worker lifecycle:
spawning → handshaking → ready ⇄ busy, heartbeat-basedunresponsive,crashed → respawningwith backoff,failed(fallback activated, app degraded) when the intensity is exceeded. Throttled background tabs recover instead of respawning. - One owner per store; replicas never mutate (I-9):
replica.intent(action, args)sends writes to the owner, patches come back batched per frame; a gap or epoch mismatch resnapshots. - Every envelope carries
traceparent— worker telemetry joins the main timeline. Raw tokens are not replicated; a token broker executes authorized calls on the owning side. - Multi-tab:
createTabCoordinator(SharedWorker or leader tab via Web Locks + BroadcastChannel, with numbered terms) from@staticflux/worker/coordinator.
#The document engine
@staticflux/document runs inside the document worker. v1 (used by Studio): an ECS world (entities, structure-of-arrays component stores, queries, systems) materialized from a DocumentCRDT (Yjs behind an interface), a DocumentEngine that is itself a replicable store, presence, a collaboration transport contract with an in-memory mock, and snapshot / restore for archives and respawn. v2 (@staticflux/document/v2): one CRDT document per page behind the engine-neutral DocumentCRDTv2 port, a DocumentRepo with one commit per gesture, versioned components, a Loro engine, a binary render projection and a spatial index. The main thread holds only a projection — no CRDT library in the main bundle.
Document v2, the stage and the editor ↔ render pipeline
#Wasm as worker-scoped units
export const ImageCodec = defineWasmModule<ImageCodecExports>({
id: 'media/image-codec',
source: { chunk: 'wasm:image-codec' }, // a logical chunk: gated, warmed, budgeted
features: { required: ['simd'] },
variants: [{ id: 'threads', source: { chunk: 'wasm:image-codec-mt' }, requires: ['threads'] }],
budget: { bytes: 400_000, compileMs: 80 },
});
export const imageCodecUnit = wasmModuleUnit({ id: 'media/image-codec-unit', provides: ImageCodecToken, module: ImageCodec });
Feature facts (SIMD, threads, memory64, exceptions, GC) come from WebAssembly.validate probes plus cross-origin isolation; threaded builds are picked only when shared memory is actually usable, otherwise the next variant. Instantiation is the unit’s init (compileStreaming, with an ArrayBuffer fallback). Main-thread Wasm needs an explicit allowMainThread: { reason, budgetMs }.
#Rich surfaces
@staticflux/surface gives DOM, Canvas2D, WebGL, WebGPU and HTML-in-Canvas one Surface contract. Engines (RenderEngine: init, frame, resize, input, dispose) never touch the DOM, so the same engine runs in a render worker via a transferred OffscreenCanvas (createWorkerSurface / hostRenderSurfaces) or on the main thread. Input is forwarded as compact timestamped events and becomes intents where the state lives; pure-canvas content gets a parallel accessible DOM layer; per-surface frame metrics carry budgets. Tiled GPU raster (/raster), canvas text input (/text-input) and image scaling (/image) are separate entries, covered in the editors guide.
AGENTS.md on GitHub: @staticflux/worker · @staticflux/document · @staticflux/wasm · @staticflux/surface · @staticflux/platform