/** * detach/drivers/microtaskBatch.ts — Batch detached work into ONE microtask. * * Pattern: Producer-consumer with batched flush. Same shape as * agentfootprint's `EventDispatcher` flush queue and the React * reconciler's microtask scheduling — accumulate during the * current sync slice, drain at the next microtask boundary. * Role: Default driver for in-process detach. Cheapest scheduling * primitive on V8/JSC: one `queueMicrotask` per batch * regardless of how many work items, so the perf budget * amortizes. Suitable for browser AND node AND edge runtimes * (queueMicrotask is universal since 2018). * * Lifecycle: * * schedule(child, input, refId) ← driver entry * └─ create handle (queued) * └─ register in detachRegistry * └─ push work item onto local queue * └─ if no microtask scheduled yet → queueMicrotask(flush) * └─ return handle (sync — passive recorder rule) * * flush() (microtask) ← deferred * └─ swap out queue (drain races safely) * └─ for each item: _markRunning, await runChild, _markDone/_markFailed * └─ unregister handle from detachRegistry * * Why microtask (and not setImmediate / setTimeout): * - Microtasks run BEFORE returning to the event loop — guarantees * the work finishes within the current "tick" if the runtime allows * - Lowest possible deferral cost (~50ns on modern V8) * - Works in EVERY JS runtime (browser, node, deno, bun, edge) * - Doesn't require any timer infrastructure → no GC pressure * * Re-entrancy: * - If `runChild` calls `schedule()` for nested detach, the new item * lands on the SAME queue. Because `scheduled` flips back to false * at the start of `flush`, the new item triggers a fresh microtask. * - Worst-case: O(n) microtasks for n nested levels. Acceptable — * real-world detach trees are shallow. */ import { type ChildRunner } from '../runChild.js'; import type { DetachDriver } from '../types.js'; /** * Build a microtask-batch driver wired to a custom child runner. Most * consumers want the default singleton `microtaskBatchDriver` instead; * this factory exists for tests and for advanced consumers who want to * inject their own runner (e.g., a runner that wraps the child in a * tracing context). */ export declare function createMicrotaskBatchDriver(runChild?: ChildRunner): DetachDriver; /** * Default singleton. Most consumers import this and pass it to * `executor.detachAndJoinLater(child, input, { driver: microtaskBatchDriver })` * (or rely on it being the executor's default driver, set in T5b). */ export declare const microtaskBatchDriver: DetachDriver;