npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@zakkster/lite-defer

v1.0.0

Published

Deferred reactive values for @zakkster/lite-signal: defer() returns a value that lags its source and catches up on a scheduler, coalescing a synchronous burst into one downstream update -- a concurrency frontier with a pending() staleness signal. microtas

Readme

@zakkster/lite-defer

npm version Zero-GC sponsor npm bundle size npm downloads npm total downloads lite-signal peer TypeScript Dependencies License: MIT

Deferred reactive values for @zakkster/lite-signal.

defer(source) returns a value that lags its source and catches up on a scheduler, coalescing a synchronous burst into a single downstream update. It draws a concurrency frontier across the reactive graph: urgent consumers read the source and see every change immediately; expensive consumers read the deferred value and update on the scheduler's cadence -- a frame, an idle slot, a microtask -- instead of on every set.

This is useDeferredValue / Solid's createDeferred for lite-signal, plus a pending() signal that tells you, reactively, when the deferred value is behind.

npm install @zakkster/lite-defer

Peer dependency: @zakkster/lite-signal ^1.5.0 (uses createRoot). ESM only. DOM-free core. MIT.


The frontier

flowchart LR
    S["source<br/>(eager, urgent)"] -->|every change, sync| U["urgent consumers"]
    S -->|coalesced, on schedule| D["deferred value"]
    D --> X["expensive consumers<br/>(big list, layout, paint)"]
    S -.->|moves ahead| P["pending() = true"]
    D -.->|catches up| P2["pending() = false"]

The source is computed eagerly. What the frontier defers is the work downstream of the deferred value -- the re-render, the re-layout, the expensive recompute that reads it. Place the frontier where you want that work throttled.


defer(source, opts?)

import { defer } from "@zakkster/lite-defer";
import { signal, computed, effect } from "@zakkster/lite-signal";

const query = signal("");
const deferredQuery = defer(query);              // lags `query`, catches up next microtask

// Expensive: filters 10k rows. Runs on the deferred cadence, not on every keystroke.
const results = computed(() => filterRows(deferredQuery()));

effect(() => { input.value = query(); });        // urgent: the field stays responsive
effect(() => { render(results()); });            // heavy: updates after the burst settles
effect(() => { spinner.hidden = !deferredQuery.pending(); });   // stale indicator

deferredQuery() seeds synchronously to query's current value, then lags: a burst of keystrokes coalesces into one catch-up to the final value.

To defer an expensive derivation, defer its cheap input (as above): search runs on the deferred value's cadence because it reads deferredQuery(), not query().

pending()

deferredQuery.pending() is a reactive boolean: true the instant the source moves ahead, false again when the deferred value catches up. The hook for dimming stale content or showing a spinner while the heavy update is in flight -- the value lags, but the "I'm stale" signal is immediate.

Options

| Option | Default | Meaning | | --- | --- | --- | | schedule | microtask | how to coalesce the catch-up: (flush) => void | | equals | Object.is | equality for the deferred value; gates the catch-up |


Schedulers

A scheduler is (flush: () => void) => void: it receives a flush thunk and calls it when the deferred value should catch up. Coalescing is built in -- defer schedules at most one flush per burst.

import { defer, raf, idle, timeout } from "@zakkster/lite-defer";

defer(src);                          // microtask (default) -- end of current task
defer(src, { schedule: raf });       // frame cadence -- at most once per animation frame
defer(src, { schedule: idle });      // lowest priority -- an idle slot
defer(src, { schedule: timeout(150) }); // debounce cadence -- 150ms after the last change

raf and idle fall back to a timeout where the host lacks requestAnimationFrame / requestIdleCallback (Node, SSR). Any (flush) => void works -- including lite-raf's frame scheduler if you already ship it.


deferEffect(fn, opts?)

An effect whose runs are coalesced onto a scheduler -- it runs at most once per burst on the scheduler's cadence. A render effect at frame cadence collapses all of a frame's state changes into one run:

import { deferEffect, raf } from "@zakkster/lite-defer";

const stop = deferEffect(() => { paint(world()); }, { schedule: raf });   // paint <= once/frame

The initial run is also scheduled (the engine's scheduled-effect semantics). This is a thin, named wrapper over lite-signal's effect(fn, { scheduler }).


Ownership

defer's internals are detached (createRoot), so an enclosing scope re-running never tears the deferred value down. The caller owns teardown:

deferredQuery.dispose();   // stop tracking, dispose the deferred + staleness signals

Call defer once at setup. There is no auto-cleanup.


Zero-GC

The deferred signal, the staleness signal, the tracking effect, and the flush thunk are all fixed per defer. A churn of source-set + catch-up allocates nothing on the engine pool (20,000 cycles flat, verified): the tracking effect re-tracks on a stable read order (cursor reuse), and both signal sets are allocation-free.

Honest non-claim: the scheduler's own queue entry -- a microtask, a rAF callback, a timer -- is the host's, not the engine pool's. And defer does not defer the source's computation (model A): reading the source inside the tracking effect forces it synchronously. Defer a cheap input upstream to defer the work.


API

defer<T>(source: () => T, opts?: { schedule?: Scheduler; equals?: (a: T, b: T) => boolean }): Deferred<T>
deferEffect(fn: () => void, opts?: { schedule?: Scheduler }): () => void
createDeferrer(registry): { defer, deferEffect }      // bind to a non-default registry

microtask: Scheduler   raf: Scheduler   idle: Scheduler   timeout(ms): Scheduler
type Scheduler = (flush: () => void) => void
type Deferred<T> = (() => T) & { pending(): boolean; dispose(): void }

License

MIT (c) 2026 Zahary Shinikchiev <[email protected]>