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

@pumped-fn/lite

v2.1.5

Published

Lightweight dependency injection with minimal reactivity

Readme

@pumped-fn/lite

Coverage: 97%+ statements Coverage: 100% functions Tests: 300 passed

Scoped Ambient State for TypeScript — a scope-local atom graph with explicit dependencies and opt-in reactivity.

State lives in the scope, not in the component tree. Handlers and components observe — they don't own or construct dependencies. The same graph works across React, server handlers, background jobs, and tests.

Frontend — atoms form a reactive dependency graph (homeData <- auth). UI subscribes via controllers; auth changes cascade to dependents automatically. Components are projections of state, not owners.

Backend — atoms are infrastructure singletons (db pool, cache). Runtime config enters the scope as tags; atoms consume it via tags.required(). Contexts bound per request carry tags (tenantId, traceId) without parameter drilling. Extensions wrap every resolve/exec for logging, tracing, auth. Cleanup is guaranteed.

Both — presets swap any atom/flow for testing or multi-tenant isolation. Tags carry runtime config; presets replace implementations. No mocks, no test-only code paths.

npx @pumped-fn/lite              # CLI reference
npx @pumped-fn/lite primitives   # API
npx @pumped-fn/lite diagrams     # mermaid source

How It Works

sequenceDiagram
    participant App
    participant Scope
    participant Ext as Extension
    participant Atom
    participant Ctx as ExecutionContext
    participant Child as ChildContext
    participant Flow
    participant Ctrl as Controller

    Note over App,Ctrl: (*) stable ref — same identity until released

    %% ── Scope Creation & Extension Init ──
    App->>Scope: createScope({ extensions, presets, tags, gc })
    Scope-->>App: scope (sync return)

    loop each extension (sequential)
        Scope->>Ext: await ext.init(scope)
    end
    Note right of Scope: all init() done → scope.ready resolves
    Note right of Scope: any init() throws → scope.ready rejects
    Note right of Scope: resolve() auto‑awaits scope.ready

    %% ── Observers (register before or after resolve) ──
    App->>Scope: scope.on('resolving' | 'resolved' | 'failed', atom, listener)
    Scope-->>App: unsubscribe fn
    Note right of Scope: scope.on listens to AtomState transitions

    %% ── Atom Resolution ──
    Note right of Scope: singletons — created once, reused across contexts. deps can include tags.required()
    App->>Scope: resolve(atom)
    Scope->>Scope: cache hit? → return cached
    alt preset hit
        Scope->>Scope: value → store directly, skip factory
        Scope->>Scope: atom → resolve that atom instead
        Scope->>Scope: ⚡ emit 'resolved' (no 'resolving')
    end
    Scope->>Scope: state → resolving
    Scope->>Scope: ⚡ emit 'resolving' → scope.on listeners
    Scope->>Ext: wrapResolve(next, { kind: "atom", target, scope })
    Ext->>Atom: next() → factory(ctx, deps)
    Note right of Atom: ctx.cleanup(fn) → stored per atom
    Note right of Atom: cleanups run LIFO on release/invalidate
    Atom-->>Ext: value
    Note right of Ext: ext returns value — may transform or replace
    alt factory succeeds
        Ext-->>Scope: value (*) cached in entry
        Scope->>Scope: state → resolved
        Scope->>Scope: ⚡ emit 'resolved' → scope.on + ctrl.on listeners
    else factory throws
        Atom-->>Scope: error
        Scope->>Scope: state → failed
        Scope->>Scope: ⚡ emit 'failed' → scope.on listeners (ctrl.on '*' only)
    end

    %% ── Context Creation ──
    Note right of Scope: HTTP request, job, transaction — groups exec calls with shared tags + guaranteed cleanup
    App->>Scope: scope.createContext({ tags })
    Scope-->>App: ctx

    %% ── Execution ──
    alt ctx.exec({ flow, input, tags })
        Ctx->>Ctx: preset? → flow: re‑exec with replacement / fn: run as factory
        Ctx->>Ctx: flow.parse(input) if defined
        Ctx->>Child: create child (parent = ctx, merged tags)
        Child->>Ext: wrapExec(next, flow, childCtx)
        Ext->>Flow: next() → factory(childCtx, deps)
        Note right of Flow: childCtx.onClose(result: CloseResult) → { ok: true } | { ok: false, error }
        Flow-->>Ext: output
        Note right of Ext: ext returns output — may transform or replace
        Ext-->>Child: output
    else ctx.exec({ name?, fn, params, tags })
        Ctx->>Child: create child (parent = ctx)
        Child->>Ext: wrapExec(next, fn, childCtx)
        Ext->>Child: next() → fn(childCtx, ...params)
        Child-->>Ext: result
    end
    Ctx->>Child: [A] close(result) → run onClose(CloseResult) LIFO
    Child-->>Ctx: output
    Ctx-->>App: output

    %% ── Resource (execution‑scoped) ──
    rect rgb(245, 240, 255)
        Note over App,Ctrl: Resource (per‑execution middleware)
        Note right of Scope: reusable factory resolved fresh per execution chain — logger, transaction, trace span

        App->>App: resource({ deps, factory })
        App-->>App: Resource definition (inert)

        Note right of Child: during dep resolution in ctx.exec():
        Note right of Child: seek hierarchy for existing instance
        alt cache hit (seek‑up)
            Child->>Child: reuse instance from parent ✓
        else cache miss
            Child->>Ext: wrapResolve(next, { kind: "resource", target, ctx })
            Ext->>Child: next() → factory(parentCtx, deps)
            Note right of Child: parentCtx.onClose(result) → cleanup registered
            Child-->>Ext: instance stored on parent context
        end
    end

    %% ── Reactivity (opt‑in) ──
    rect rgb(240, 248, 255)
        Note over App,Ctrl: Reactivity (opt‑in — atoms are static by default)
        Note right of Scope: live config, UI state, cache invalidation — when values change after initial resolve
        App->>Scope: controller(atom)
        Scope-->>Ctrl: ctrl (*)

        App->>Ctrl: ctrl.on('resolving' | 'resolved' | '*', listener)
        Ctrl-->>App: unsubscribe
        Note right of Ctrl: ctrl.on listens to per‑atom entry events

        App->>Ctrl: ctrl.set(v) / ctrl.update(fn)
        Ctrl->>Scope: scheduleInvalidation
        Scope->>Scope: run atom cleanups (LIFO)
        Scope->>Scope: ⚡ emit 'resolving' → scope.on + ctrl.on
        Scope->>Atom: apply new value (skip factory)
        Scope->>Scope: state → resolved
        Scope->>Scope: ⚡ emit 'resolved' → scope.on + ctrl.on

        App->>Ctrl: ctrl.invalidate()
        Ctrl->>Scope: scheduleInvalidation
        Scope->>Scope: run atom cleanups (LIFO)
        Scope->>Scope: ⚡ emit 'resolving' → scope.on + ctrl.on
        Scope->>Atom: re‑run factory
        Scope->>Scope: state → resolved
        Scope->>Scope: ⚡ emit 'resolved' → scope.on + ctrl.on

        App->>Scope: select(atom, selector, { eq })
        Scope-->>App: handle { get, subscribe }

        Note over App,Ctrl: Dependency reactivity — atom deps only
        Note right of Scope: watch:true replaces manual ctx.cleanup(ctx.scope.on('resolved', dep, () => ctx.invalidate()))
        App->>Scope: resolve(derivedAtom)
        Note right of Scope: deps: { src: controller(srcAtom, { resolve: true, watch: true, eq? }) }
        Scope->>Scope: on dep 'resolved': eq(prev, next) → scheduleInvalidation if changed
        Note right of Scope: watch listener auto-cleaned on re-resolve / release / dispose
    end

    %% ── Cleanup & Teardown ──
    rect rgb(255, 245, 238)
        Note over App,Scope: Teardown
        App->>Ctx: ctx.close(result?) — same as [A]
        Ctx->>Ctx: run onClose(CloseResult) cleanups (LIFO, idempotent)

        App->>Scope: release(atom)
        Scope->>Scope: run atom cleanups (LIFO)
        Scope->>Scope: remove from cache + controllers

        App->>Scope: flush()
        Note right of Scope: await pending invalidation chain

        App->>Scope: dispose()
        loop each extension
            Scope->>Ext: ext.dispose(scope)
        end
        Scope->>Scope: release all atoms, run all cleanups
    end

API reference: dist/index.d.mts | Patterns: PATTERNS.md | CLI: npx @pumped-fn/lite

License

MIT