This is the Idle addon for Time-Provider.
It adds a scheduler.idle facade exposing the idle callback API (request, cancelled via dispose() on the returned handle), beside the scheduler.timers and scheduler.microtasks that core already provides.
Just like the plugin packages, this addon is tree-shakable.
It is split into a default (system/real-time) entry point and a deterministic one, so each import pulls in only the code it needs:
@time-provider/addon-idle - for a system (real time) Time-Provider
created via @time-provider/core. .scheduler.idle.request passes through to the real
requestIdleCallback, or throws a clear error when the host has no native equivalent (e.g.
Safari) - cancelIdleCallback itself stays an internal detail; cancel by calling dispose() on
the handle .scheduler.idle.request returns.
@time-provider/addon-idle/deterministic - for a deterministic
Time-Provider (fixed/manual/sequential) created via
@time-provider/core/deterministic. Requests made through .scheduler.idle.request stay
pending until a test declares the runtime idle via .scheduler.idle.drain().
Usage
import { createTimeProvider } from"@time-provider/core";
import { createTimeProvider as createDeterministicTimeProvider } from"@time-provider/core/deterministic";
import { plugin } from"@time-provider/plugin-native";
import { plugin as deterministicPlugin } from"@time-provider/plugin-native/deterministic";
import { addon } from"@time-provider/addon-idle";
import { addon as deterministicAddon } from"@time-provider/addon-idle/deterministic";
// System: real requestIdleCallback under the hood (or a clear error if not available)const timeProvider = createTimeProvider.for(plugin).use(addon).create();
timeProvider.scheduler.idle.request(() =>console.log("Idle!"));
// Deterministic: requests stay pending until you declare the runtime idleconst manual = createDeterministicTimeProvider
.for(deterministicPlugin)
.use(deterministicAddon)
.asManual()
.withInitialTime(0)
.create();
manual.scheduler.idle.request(() =>console.log("Idle!"));
manual.scheduler.idle.drain(); // the idle callback runs here// Like the native API, a callback receives a deadline and a request takes a timeout
manual.scheduler.idle.request((deadline) =>console.log(deadline.didTimeout), { timeout: 1000 });
manual.clock.moveBy({ seconds: 1 }); // true: still pending at its timeout, so it ran anyway
Simulated idle periods
There is no such thing as a real idle period on a deterministic runtime - unlike a timeout,
nothing about elapsed simulated time says the runtime has spare capacity - so request() just
registers the callback under this addon's own tag in the runtime's shared due-heap. Clock moves and
reads never fire it on their own; only drain() does, by retrieving up to maxCount
pending requests (oldest first) directly through that tag - without scanning any other pending
timer/interval/recurring entry sharing the heap:
manual.scheduler.timers.once({ milliseconds: 50 }, () =>console.log("Busy!"));
manual.scheduler.idle.request(() =>console.log("Idle!"));
manual.clock.moveBy({ milliseconds: 50 }); // "Busy!" - the idle request is still pending
manual.scheduler.idle.drain(); // "Idle!"
Omit maxCount to run everything currently pending, or pass it to cap how much idle work a
single idle period allows through:
manual.scheduler.idle.request(() =>console.log("first"));
manual.scheduler.idle.request(() =>console.log("second"));
manual.scheduler.idle.drain(1); // "first" - "second" stays pending for the next drain
With the compat addon
Compose @time-provider/addon-compatbefore this addon and its .compat facade also gets requestIdleCallback/cancelIdleCallback, delegating to request and to the handle's dispose(). They are declared as an optional compat? on WithIdleApi, since they are only there when both addons are composed.
Idle callback addon for Time-Provider. Inject time. Control it in tests.
We found that @time-provider/addon-idle demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.It has 1 open source maintainer collaborating on the project.