Something went wrong. Try again.
Monorepo for the nibling.run platform
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258// Guest-facing timers in Node's shape: Timeout and Immediate objects wrap the loop's// handles, so ref()/unref() decide liveness and clearTimeout accepts the object or// its numeric id. node:timers and node:timers/promises are built from the same// functions as the globals.import type { Immediate as LoopImmediate, Loop, Timer } from "./loop";
const TIMEOUT_MAX = 2 ** 31 - 1;
type Callback = (...args: any[]) => void;type Warn = (message: string, type: string) => void;
export interface Timers { setTimeout(fn: Callback, ms?: unknown, ...args: unknown[]): Timeout; clearTimeout(t: unknown): void; setInterval(fn: Callback, ms?: unknown, ...args: unknown[]): Timeout; clearInterval(t: unknown): void; setImmediate(fn: Callback, ...args: unknown[]): Immediate; clearImmediate(im: unknown): void;}
export interface Timeout { ref(): this; unref(): this; hasRef(): boolean; refresh(): this; close(): this; [Symbol.toPrimitive](): number;}
export interface Immediate { ref(): this; unref(): this; hasRef(): boolean;}
function checkCallback(fn: unknown): asserts fn is Callback { if (typeof fn !== "function") { throw Object.assign(new TypeError(`The "callback" argument must be of type function. Received ${describe(fn)}`), { code: "ERR_INVALID_ARG_TYPE", }); }}
function describe(v: unknown): string { if (v === null || v === undefined) return String(v); return typeof v === "string" ? `type string ('${v}')` : `type ${typeof v} (${String(v)})`;}
/** Node's delay coercion: anything that is not a number in [1, 2^31-1] becomes 1. */function coerceDelay(ms: unknown, warn: Warn): number { const after = Number(ms); if (after >= 1 && after <= TIMEOUT_MAX) return after; if (after > TIMEOUT_MAX) { warn(`${after} does not fit into a 32-bit signed integer.\nTimeout duration was set to 1.`, "TimeoutOverflowWarning"); } else if (after < 0) { warn(`${after} is a negative number.\nTimeout duration was set to 1.`, "TimeoutNegativeWarning"); } else if (Number.isNaN(after) && ms !== undefined) { warn(`${String(ms)} is not a number.\nTimeout duration was set to 1.`, "TimeoutNaNWarning"); } return 1;}
export function createTimers(loop: Loop, warn: Warn): Timers { const immediateHandles = new WeakMap<object, LoopImmediate>();
class Timeout { #timer: Timer; #destroyed = false;
constructor(fn: Callback, ms: unknown, args: unknown[], repeat: boolean) { this.#timer = loop.addTimer(() => fn.apply(this, args), coerceDelay(ms, warn), repeat); }
ref(): this { loop.setTimerRef(this.#timer, true); return this; }
unref(): this { loop.setTimerRef(this.#timer, false); return this; }
hasRef(): boolean { return this.#timer.ref; }
refresh(): this { if (!this.#destroyed) loop.refreshTimer(this.#timer); return this; }
close(): this { this.#destroyed = true; loop.clearTimer(this.#timer); return this; }
[Symbol.toPrimitive](): number { return this.#timer.id; } }
class Immediate { #immediate: LoopImmediate;
constructor(fn: Callback, args: unknown[]) { this.#immediate = loop.addImmediate(() => fn.apply(this, args)); immediateHandles.set(this, this.#immediate); }
ref(): this { loop.setImmediateRef(this.#immediate, true); return this; }
unref(): this { loop.setImmediateRef(this.#immediate, false); return this; }
hasRef(): boolean { return this.#immediate.ref; } }
function clear(t: unknown): void { if (t instanceof Timeout) { t.close(); return; } // Node also clears by the primitive id (from +timeout or a string of it). if (typeof t === "number" || typeof t === "string") { const timer = loop.timerById(Number(t)); if (timer) loop.clearTimer(timer); } }
return { setTimeout(fn, ms, ...args) { checkCallback(fn); return new Timeout(fn, ms, args, false); }, clearTimeout: clear, setInterval(fn, ms, ...args) { checkCallback(fn); return new Timeout(fn, ms, args, true); }, clearInterval: clear, setImmediate(fn, ...args) { checkCallback(fn); return new Immediate(fn, args); }, clearImmediate(im) { const handle = typeof im === "object" && im !== null ? immediateHandles.get(im) : undefined; if (handle) loop.clearImmediate(handle); }, };}
type TimerOptions = { ref?: boolean; signal?: AbortSignal };
/** node:timers/promises: the promise forms frameworks import. */export function timersPromises(timers: Timers) { function timeout<T>(ms?: number, value?: T, options?: TimerOptions): Promise<T | undefined> { return settle(options, (done) => timers.setTimeout(() => done(value), ms), (t) => timers.clearTimeout(t)); } function immediate<T>(value?: T, options?: TimerOptions): Promise<T | undefined> { return settle(options, (done) => timers.setImmediate(() => done(value)), (im) => timers.clearImmediate(im)); }
/** setInterval(after, value): an async iterator that yields `value` once per tick, * catching up (one yield per missed tick) if the consumer falls behind, as Node's * own timers/promises.setInterval does. */ async function* interval<T>(after?: number, value?: T, options?: TimerOptions): AsyncGenerator<T | undefined> { const signal = options?.signal; if (signal?.aborted) throw abortError(signal);
let notYielded = 0; let wake: (() => void) | undefined; const timer = timers.setInterval(() => { notYielded++; wake?.(); wake = undefined; }, after); if (options?.ref === false) timer.unref();
let onAbort: (() => void) | undefined; if (signal) { onAbort = (): void => { timers.clearInterval(timer); wake?.(); wake = undefined; }; signal.addEventListener("abort", onAbort, { once: true }); }
try { while (!signal?.aborted) { if (notYielded === 0) await new Promise<void>((resolve) => (wake = resolve)); for (; notYielded > 0; notYielded--) yield value; } throw abortError(signal!); } finally { timers.clearInterval(timer); if (signal && onAbort) signal.removeEventListener("abort", onAbort); } }
return { setTimeout: timeout, setImmediate: immediate, setInterval: interval, scheduler: makeScheduler(timeout, immediate) };}
/** timers/promises.scheduler: the awaitable forms of the WICG Scheduling proposal. * `yield()` defers to the browser's native `scheduler.yield()` when present (it * cooperates with the page's own task scheduling better than a bare setImmediate); * `wait()` has no native counterpart (`scheduler.postTask` is not the same shape), * so it is always our own setTimeout. */function makeScheduler( timeout: <T>(ms?: number, value?: T, options?: TimerOptions) => Promise<T | undefined>, immediate: <T>(value?: T, options?: TimerOptions) => Promise<T | undefined>,): { wait(delay?: number, options?: TimerOptions): Promise<void>; yield(): Promise<void> } { const native = (globalThis as { scheduler?: { yield?: () => Promise<void> } }).scheduler; class Scheduler { yield(): Promise<void> { return native?.yield ? native.yield() : (immediate() as Promise<void>); } wait(delay?: number, options?: TimerOptions): Promise<void> { return timeout(delay, undefined, options) as Promise<void>; } } return new Scheduler();}
function settle<T, H extends { unref(): unknown }>( options: { ref?: boolean; signal?: AbortSignal } | undefined, start: (done: (v: T) => void) => H, cancel: (h: H) => void,): Promise<T> { const signal = options?.signal; if (signal?.aborted) return Promise.reject(abortError(signal)); return new Promise((resolve, reject) => { const onAbort = (): void => { cancel(handle); reject(abortError(signal!)); }; const handle = start((v) => { signal?.removeEventListener("abort", onAbort); resolve(v); }); if (options?.ref === false) handle.unref(); signal?.addEventListener("abort", onAbort, { once: true }); });}
function abortError(signal: AbortSignal): Error { return Object.assign(new Error("The operation was aborted"), { name: "AbortError", code: "ABORT_ERR", cause: signal.reason });}