UNPKG

@on-the-ground/daemonizer

Version:

A minimal async control flow framework for browser and Node.js daemons.

211 lines (204 loc) 10.2 kB
/** * A standard `DOMException` used to signal that an operation was aborted due to a timeout. * * This error uses the `"AbortError"` name so it integrates cleanly with `AbortSignal`-based cancellation. * It is returned by `withTimeout()` when the specified timeout duration elapses. * * @example * try { * await withTimeout(doSomething, 1000); * } catch (err) { * if (err === timeoutError) { * console.warn("Operation timed out"); * } * } */ declare const timeoutError: DOMException; /** * Unique symbol used to identify embedded AbortSignal in an object. * Prevents key collisions when mixing custom objects with abort handling logic. */ declare const SIGNAL_KEY: unique symbol; /** * A union type representing either a raw `AbortSignal` or an object containing an embedded signal. * * This abstraction allows flexible signal passing — either directly or wrapped within another structure. * * @example * const direct: SignalSource = new AbortController().signal; * const wrapped: SignalSource = { [SIGNAL_KEY]: new AbortController().signal }; */ type SignalSource = AbortSignal | { [SIGNAL_KEY]: AbortSignal; }; declare function isSignalSource(sigSrc: unknown): sigSrc is SignalSource; /** * Merges an external `AbortSignal` with an existing `SignalSource`. * * If the `sigSrc` is a raw signal, returns a new merged `AbortSignal`. * If the `sigSrc` is a wrapped signal object, returns a shallow clone with the embedded signal replaced * by the merged one. * * This is useful for combining timeout signals with externally provided cancellation sources. * * @param signal - An `AbortSignal` to merge in (e.g. from timeout or internal controller). * @param sigSrc - An existing signal or signal-carrying object to merge with. * @returns A new `SignalSource` of the same type as `sigSrc` with merged cancellation behavior. */ declare function mergeAbortSignal<S extends SignalSource>(signal: AbortSignal, sigSrc: S): S; /** * Extracts the `AbortSignal` from a `SignalSource`, whether it's direct or embedded. * * @param sigSrc - A `SignalSource`, either a raw signal or an object containing one. * @returns The corresponding `AbortSignal`. */ declare function signalFrom(sigSrc: SignalSource): AbortSignal; /** * Wraps a promise with an AbortSignal to allow it to be aborted. * If the signal is already aborted, it rejects immediately. * If the signal aborts while the promise is pending, it rejects with an ErrAborted error. * * @param signal - An AbortSignal to monitor for abortion. * @returns A function that takes a promise and returns a new promise that can be aborted. */ declare const withAbort: <T>(promise: Promise<T>, signal?: AbortSignal) => Promise<T>; /** * Runs an async operation with a timeout and optional external AbortSignal. * * If the operation does not complete within the given timeout, * it is aborted with a `timeoutError`. If an external `AbortSignal` is provided * and it fires first, the operation is also aborted with that signal's reason. * * Internally, it merges the external signal (if provided) with an internal timeout-based signal. * Then it wraps the callback execution with `withAbort()` to ensure proper cancellation handling. * * @template T - The type of the resolved value. * @param callback - An async function that accepts an `AbortSignal` and returns a `Promise<T>`. * This function is expected to respect the signal and abort early if triggered. * @param timeout - Timeout in milliseconds after which the operation will be aborted. * @param extSigSrc - Optional `AbortSignal` to combine with the timeout signal. * Abortion from either will cancel the operation. * @returns A `Promise<T>` that resolves if the callback completes within time, * or rejects with `timeoutError` or the external signal's reason. */ declare const withTimeout: <T, S extends SignalSource>(callback: (signal: S) => Promise<T>, timeout: number, extSigSrc?: S) => Promise<T>; declare class ErrZeroCapacity extends Error { constructor(); } declare const errZeroCapacity: ErrZeroCapacity; /** * A bounded queue that allows asynchronous producers and consumers to interact. * It supports pushing items, closing the queue, and iterating over the items. * If the queue is full, producers will wait until space is available or fail fast. * If the queue is empty, consumers will wait until items are available. * If the queue is closed, consumers will receive a done signal. */ declare class BoundedQueue<T> implements AsyncIterable<T> { private readonly capacity; private readonly buffer; private readonly waitingConsumers; private readonly waitingProducers; private closed; constructor(capacity: number); private tryPushInternal; /** * Attempts to push a value into the queue without waiting. * Returns true if successful, false if the queue is full or closed. */ tryPush: (value: T) => boolean; /** * Pushes a value into the queue, waiting if necessary until space is available. * Returns true if successful, false if the queue is closed. */ push: (value: T) => Promise<boolean>; /** * Closes the queue, signaling that no more items will be added. * All waiting consumers will be resolved with a done signal. * Waiting producers will be resolved with an Opening signal. */ close: () => void; private nextInternal; /** * Returns the next item from the queue, waiting if necessary until an item is available. * If the queue is closed, it resolves with a done signal. */ next: () => Promise<IteratorResult<T>>; /** * Returns an async iterator for the queue, allowing iteration over its items. * The iterator will yield items until the queue is closed. */ [Symbol.asyncIterator]: () => AsyncIterator<T>; get isClosed(): boolean; get size(): number; get isEmpty(): boolean; } /** * TaskGroup is a utility class that allows managing a group of tasks * and waiting for all of them to complete. It is useful for coordinating multiple asynchronous * operations and ensuring that all tasks are done before proceeding. */ declare class TaskGroup { private count; private waiters; add(n: number): void; done(): void; wait(): Promise<void>; } /** * Daemon is a utility class that manages an event loop processing events * from a bounded queue. It allows pushing events to the queue and handles them * asynchronously using a provided handler function. * It supports graceful shutdown and ensures that all events are processed before closing. * * @template E - The type of events to handle. * @param signal - An AbortSignal to allow cancellation of the event loop. * @param handleEvent - A function that processes each event. It should return a * Promise that resolves when the event is handled. * @param bufferSize - The size of the bounded queue buffer. Defaults to 10. * @param loopIntervalMs - Interval in milliseconds used for two purposes: * (1) to periodically yield control to the JS event loop for fairness, * (2) if `strictInterval` is enabled, to enforce a per-event processing timeout. * Defaults to 8ms (roughly 60fps frame budget). * @param strictInterval - If true, each event handler must complete within * `loopIntervalMs`, otherwise it is aborted. If false, handlers may take longer * but yielding still happens at the interval. Defaults to false. */ declare class Daemon<E, S extends SignalSource> { readonly id: string; readonly tg: TaskGroup; readonly eventStream: BoundedQueue<E>; constructor(signalSource: S, handleEvent: (signalSource: S, event: E) => Promise<void>, bufferSize?: number, loopIntervalMs?: number, strictInterval?: boolean); /** * Closes the event handler, stopping it from accepting new events. * It waits for the event loop to finish processing all events before resolving. */ close: () => Promise<void>; /** * Pushes an event to the event stream. * Returns true if the event was successfully pushed, false if the event handler is closed. * * @param event - The event to push to the event stream. */ pushEvent: (event: E) => Promise<boolean>; } /** * Launches an event loop that processes events from an async iterable. * It handles each event using the provided handler function and yields control * to allow other macro tasks to run periodically. * * @param signal - An AbortSignal to allow cancellation of the loop. * @param taskGroup - A TaskGroup to manage the lifecycle of the loop. * @param eventStream - An async iterable of events to process. * @param handleEvent - A function that processes each event. * @param loopIntervalMs - Interval in milliseconds used for two purposes: * (1) to periodically yield control to the JS event loop for fairness, * (2) if `strictInterval` is enabled, to enforce a per-event processing timeout. * Defaults to 8ms (roughly 60fps frame budget). * @param strictInterval - If true, each event handler must complete within * `loopIntervalMs`, otherwise it is aborted. If false, handlers may take longer * but yielding still happens at the interval. Defaults to false. */ declare function launchEventLoop<T, S extends SignalSource>(sigSrc: S, taskGroup: TaskGroup, eventStream: AsyncIterable<T>, handleEvent: (signalSource: S, event: T) => Promise<void>, loopIntervalMs?: number, strictInterval?: boolean): Promise<void>; /** * MacroTaskYielder is a utility class that allows yielding control back to the event loop * at specified intervals. This is useful in long-running tasks to prevent blocking the event loop * and allow other tasks to run. */ declare class MacroTaskYielder { private readonly interval; private lastYield; constructor(interval?: number); yieldByInterval: () => Promise<void>; } export { BoundedQueue, Daemon, ErrZeroCapacity, MacroTaskYielder, SIGNAL_KEY, TaskGroup, errZeroCapacity, isSignalSource, launchEventLoop, mergeAbortSignal, signalFrom, timeoutError, withAbort, withTimeout }; export type { SignalSource };