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

@luwiostack/bootstrap

v0.1.0

Published

Bootstrap (startup) config for apps — fetch the one config document before mount, with ETag caching + stale-while-revalidate, then build your app from it. Runtime/reactive state belongs in your data layer (e.g. TanStack Query), not here. React-free core +

Readme

@luwiostack/bootstrap

Fetch your app's startup config — the one document a backend owns that decides how the app boots (which locales to mount, feature flags, a default) — once, before mount, with ETag caching, validation, and stale-while-revalidate, then build your app from it. A React-free loader (@luwiostack/bootstrap) plus React bindings — a provider and hooks (@luwiostack/bootstrap/react). It knows nothing about locales or the rest of Luwio, so it works in any app.

Bootstrap config, not runtime state. This is for the single document you load once at startup. Data that changes during a session and the UI should react to — settings, live flags, per-language content, anything refetched — belongs in your data layer (e.g. @luwiostack/http + TanStack Query), not here.

Many apps can't render until they know something the backend decides at runtime — which locales to mount, feature flags, a theme, an API base. @luwiostack/bootstrap packages the fetch → cache → revalidate loop for that one document so you don't hand-roll it: load it before mount, build the app (and router) from it, then read it anywhere in React.

npm i @luwiostack/bootstrap

The bootstrap

import { createBootstrap, httpSource } from '@luwiostack/bootstrap'
import { sessionStore } from '@luwiostack/storage'

export const bootstrap = createBootstrap<AppConfig>({
  fetch: httpSource('/api/config'),             // conditional GET (sends If-None-Match, reads ETag)
  storage: sessionStore('my-app:bootstrap'), // optional — survives a refresh, like the browser's HTTP cache
})

const config = await bootstrap.load() // in your entry, before mounting

createBootstrap returns { load, get, revalidate, watch }:

  • load() — fetch (or revalidate against the cache) and return the config; marks its version as applied. A browser refresh re-runs it; the conditional request keeps it cheap (304) when nothing changed. Concurrent load() calls share a single in-flight request, so it's safe to call from several places at once (or under React StrictMode).
  • get() — the last loaded value, read synchronously. Throws before the first load() — in practice you always await load() before mount, so it's ready by the time React reads it.
  • revalidate() — re-check without touching the running app; resolves { changed, version }.
  • watch(onChange, { intervalMs? }) — stale-while-revalidate: re-checks on focus / visibility / interval and fires onChange once when the published version diverges. Returns an unsubscribe. A failed background re-check (a network blip, or a newly-published config that validate rejects) is swallowed — you keep the config you have — so pass debug: true to log those outcomes (and every fetch) under [bootstrap].

storage is optional. It only remembers the last-seen version across reloads, so load() can send If-None-Match on the next boot and take a 304. If your endpoint already ships Cache-Control + ETag, the browser's own HTTP cache does that revalidation for you — so you can drop storage entirely and httpSource still gets a cheap 304 when nothing changed. Omit it to always go to the network (well, to the browser cache); add it when you want the last config kept in sessionStorage/localStorage independently of HTTP caching. Within a single page session, revalidate()/watch() work either way.

map shapes the raw JSON into what your app consumes (only the raw body is cached):

createBootstrap<AppConfigJson, AppConfig>({ fetch, storage, map: (json) => hydrate(json) })

validate runs on a freshly fetched body before it's cached or mapped. Throw to reject a bad config — the error propagates out of load() (to your entry's catch, or an error boundary), so you surface it on screen. A rejected config never poisons the cache.

createBootstrap({ fetch, storage, validate: (c) => ConfigSchema.parse(c) }) // zod / valibot / …

combineBootstrap merges several endpoints into one bootstrap (fetched in parallel, keyed by name, change detected across all). Bind it to a const so map's parameter type infers:

const fetch = combineBootstrap({ theme: httpSource('/api/theme'), features: httpSource('/api/features') })
export const bootstrap = createBootstrap({ fetch, map: ({ theme, features }) => ({ ...theme, ...features }) })

httpSource(url, { init? }) passes any RequestInit (headers, mode, credentials, signal) straight through and adds If-None-Match itself; it falls back to hashing the body if the server sends no ETag. A non-2xx response, or a body that isn't JSON (a proxy error page, an auth redirect), throws a clear error out of load() rather than a raw parse failure. Storage (from @luwiostack/storage): sessionStore (default), localStore, memoryStore.

Not just HTTP. fetch is any (version) => Promise<FetchResult>, so config can come from anywhere. For a bundled/imported value use fileSource — the non-network counterpart, versioned by a hash of the data (so a rebuilt file counts as a change):

import { createBootstrap, fileSource } from '@luwiostack/bootstrap'
import config from './config.json'

createBootstrap({ fetch: fileSource(config) })
// code-split instead: fileSource(() => import('./config.json').then((m) => m.default))

Or write your own fetch: return { status: 'fresh', version, data } for a body, or { status: 'unchanged', version } to reuse the stored copy (from an env var, localStorage, a <script> — anything with a version you control).

React — @luwiostack/bootstrap/react

The bootstrap loads outside React, before mount — await bootstrap.load() in your entry, build the router from it, then mount under <BootstrapProvider>. Because it resolved before mount, reading it in a component is a plain synchronous call: no Suspense, no loading or error branch.

import { createRoot } from 'react-dom/client'
import { BootstrapProvider } from '@luwiostack/bootstrap/react'
import { RouterProvider } from '@luwiostack/router'
import { bootstrap } from './bootstrap'

const root = createRoot(document.getElementById('root')!)
root.render(<Splash />)

bootstrap
  .load()
  .then((config) =>
    root.render(
      <BootstrapProvider bootstrap={bootstrap}>
        <RouterProvider router={createAppRouter(config)} />
      </BootstrapProvider>,
    ),
  )
  .catch((err) => root.render(<BootError error={err} />)) // a rejected load (incl. validate) lands here

useBootstrap — read the config

useBootstrap<T>() returns the loaded config anywhere below the provider. Wrap it once next to where you create the bootstrap for a typed, app-specific hook.

import { useBootstrap } from '@luwiostack/bootstrap/react'

export const useAppConfig = () => useBootstrap<AppConfig>()

function ThemeToggle() {
  const { theme } = useAppConfig()
  return <button>Theme: {theme}</button>
}

useBootstrapUpdate — the reload nudge

An open tab won't notice a publish on its own. useBootstrapUpdate() reads the bootstrap from the provider, runs its watch(), and flips available when the backend publishes a newer config. Config is only re-applied on reload — so nudge, don't swap it under the user.

const { available, reload } = useBootstrapUpdate()
if (available) return <button onClick={reload}>New configuration available — Reload</button>

Bootstrap vs runtime state

@luwiostack/bootstrap is for the one language-independent document you load once at startup (which locales exist, the default, feature flags, a theme). Anything that changes during a session — the classic case is the per-language content once a language is active — is ongoing state, and belongs in your data layer: @luwiostack/http + TanStack Query, keyed so it refetches on change.

import { createRequest, type Endpoint } from '@luwiostack/http'
import { useHttpClient } from '@luwiostack/http/react'
import { useQuery } from '@tanstack/react-query'

const getLanguageConfig: Endpoint<[lang: string, signal?: AbortSignal], LanguageConfig> = (
  client, lang, signal,
) => createRequest(client, { path: `/config/${lang}`, signal })

function LanguagePanel({ lang }: { lang: string }) {
  const client = useHttpClient()
  const { data } = useQuery({
    queryKey: ['language-config', lang],
    queryFn: ({ signal }) => getLanguageConfig(client, lang, signal),
  })
  return <Panel config={data} />
}

Rule of thumb: if it's needed to build the app (router, providers) and doesn't change without a reload, it's bootstrap. If the UI should re-render when it changes, it's runtime state — reach for TanStack Query, not this package.