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

@stnd/store

v0.5.0

Published

--- title: "@stnd/store" aliases: [] created: 2026-07-05 07:46 modified: 2026-07-05 19:23 last_audited: 2026-07-14 audit_interval_days: 90 next_audit: 2026-10-12 audit_priority: 3 maturity: sprout mode: read publish: false status: active tags: - package

Readme


title: "@stnd/store" aliases: [] created: 2026-07-05 07:46 modified: 2026-07-05 19:23 last_audited: 2026-07-14 audit_interval_days: 90 next_audit: 2026-10-12 audit_priority: 3 maturity: sprout mode: read publish: false status: active tags:

  • package
  • stnd theme: kernel type: package visibility: private

@stnd/store

The Standard runtime store. A lightweight provide/inject service registry that acts as the nervous system of the framework — shared reactive state without props drilling, no coupling between modules.

Any module can provide a value (a Svelte store, a function, a primitive). Any component or module can inject it back by key. The store lives for the lifetime of the page.

ELI5

Two modules that don’t know about each other both need “the current user” or “the dialog controller” — instead of threading props through everything, whoever sets it up once calls provide("user", userStore), and anyone else calls inject("user") to get it back. Like a tiny, page-scoped global registry, without the coupling of a real global.

Use it:

import { provide, inject } from "@stnd/store";
provide("myThing", someValue); // once, at boot
const thing = inject("myThing"); // anywhere, anytime

Why this exists

Astro’s architecture separates server rendering from client-side islands. In a multi-module system, you have many independent components that need access to the same runtime state (who’s logged in, the search function, the dialog controller) without knowing about each other.

You cannot:

  • Pass props across Astro’s server/client boundary to every island
  • Import a singleton store directly (it would work, but bypasses the module system and makes testing and overriding impossible)
  • Use Svelte context (only works within a component tree)

@stnd/store solves this with a simple contract:

  • Something that knows the value registers it with provide(key, value) — once, at boot
  • Everything that needs it reads it with inject(key) — anywhere, anytime

Install

pnpm add @stnd/store

API

provide(key, value)

Register any value under a well-known key. Call this in boot scripts or module init code, before components mount.

import { provide } from "@stnd/store";
import { userStore } from "@stnd/account/models/User.js";

provide("user", userStore);

Calling provide twice with the same key overwrites the previous value and logs a warning.


inject(key, fallback?)

Retrieve a registered value. Returns the value, or fallback (default: null) if nothing was provided.

import { inject } from "@stnd/store";

const userStore = inject("user");
const search = inject("search", async () => []);

Injecting a missing key with no fallback logs a warning. This is intentional — it helps you catch timing issues where a consumer mounts before the provider has run.


has(key)

Check whether a key has been registered without triggering a warning.

import { has } from "@stnd/store";

if (has("search")) {
  // safe to inject
}

revoke(key)

Remove a key from the store. Returns true if the key existed.

import { revoke } from "@stnd/store";

revoke("search");

entries()

List all registered entries. Useful for debugging what’s available at runtime.

import { entries } from "@stnd/store";

console.log(entries());
// [["user", atom(...)], ["search", fn], ["dialog", {...}]]

clear()

Wipe the entire store. Use in tests only — not in application code.

import { clear } from "@stnd/store";

clear();

The module manifest shortcut

The preferred way to register stores is through a module manifest store: field. The framework auto-generates the provide() calls at build time via virtual:stnd/store — you never write the boot code manually.

// packages/account/index.module.js
export default {
  id: "stnd-account",
  store: {
    user: "./models/User.js#userStore",
  },
};
// apps/stnd.gd/modules/launcher/index.module.js
export default {
  id: "launcher",
  store: {
    search: "./search.js#searchNotes",
  },
};

The #exportName suffix selects a named export. Without it, the default export is used.

This is wired automatically — StndInit.astro imports virtual:stnd/store in a client <script>, which runs before any island mounts.


Usage in Astro

Server side (.astro files)

The store is client-only. Do not import @stnd/store in the frontmatter of Astro files — that runs on the server where the store doesn’t exist yet.

---
// ✗ Wrong — this runs on the server
import { inject } from "@stnd/store";
const user = inject("user"); // always null on the server
---

For server-side data, use Astro’s Astro.locals (populated by middleware) or read directly from your models.

---
// ✓ Correct — read server state from locals
const user = Astro.locals.user;
---

Client islands (.astro <script> blocks)

You can safely call inject inside a <script> tag — this runs on the client after virtual:stnd/store has already called provide.

<button id="logout">Log out</button>

<script>
  import { inject } from "@stnd/store";

  const userStore = inject("user");

  document.getElementById("logout").addEventListener("click", () => {
    // userStore is a nanostores atom
    console.log("Logging out", userStore.get());
  });
</script>

Make sure StndInit.astro (or equivalent) is included in your base layout so virtual:stnd/store runs first.


Usage in Svelte

In a Svelte 5 component (recommended)

<script>
  import { inject } from "@stnd/store";
  import { useStore } from "nanostores";

  // inject returns the nanostores atom itself
  const userStore = inject("user");
  const user = useStore(userStore);
</script>

{#if $user}
  <p>Hello, {$user.name}</p>
{:else}
  <p>Not logged in</p>
{/if}

With a fallback for optional services

<script>
  import { inject } from "@stnd/store";

  // The search function may not exist on all apps
  const search = inject("search", async () => []);

  let query = $state("");
  let results = $state([]);

  async function handleInput() {
    results = await search(query);
  }
</script>

Accessing the dialog service

@stnd/ui/dialog.js registers itself under the "dialog" key on load. Any Svelte component can trigger a confirmation without importing from @stnd/ui directly:

<script>
  import { inject } from "@stnd/store";

  const dialog = inject("dialog");

  async function handleDelete() {
    const confirmed = await dialog.confirm({
      title: "Delete this item?",
      description: "This cannot be undone.",
      intent: "danger",
      confirmLabel: "Delete",
    });

    if (confirmed) {
      // proceed
    }
  }
</script>

<button onclick={handleDelete}>Delete</button>

Providing your own service

If a module needs to expose a runtime service to other modules, call provide in a boot script.

// modules/search/client/boot.js
import { provide } from "@stnd/store";

let index = null;

async function loadIndex() {
  const res = await fetch("/search-index.json");
  index = await res.json();
}

async function searchContent(query) {
  if (!index) await loadIndex();
  return index.filter((item) =>
    item.title.toLowerCase().includes(query.toLowerCase()),
  );
}

provide("search", searchContent);

Or, if the service is a reactive Svelte store:

// modules/theme/client/boot.js
import { provide } from "@stnd/store";
import { atom } from "nanostores";

export const themeStore = atom("light");

provide("theme", themeStore);

Then register it via the module manifest (preferred over manual boot scripts):

// modules/theme/index.module.js
export default {
  id: "theme",
  store: {
    theme: "./client/boot.js#themeStore",
  },
};

Timing: the boot order

The store is populated in this order on every page load:

1. StndInit.astro runs <script> on the client
     └── imports virtual:stnd/store
           └── calls provide() for every manifest store: entry

2. Module boot scripts run (declared via scripts: in manifests)
     └── may call provide() for dynamic services

3. Svelte islands mount
     └── call inject() to consume what was provided

If you call inject("user") in a Svelte component’s <script> and get null, it almost always means virtual:stnd/store hasn’t run yet — check that StndInit.astro is included in your base layout before any island.


Logging

All store operations emit @stnd/log debug messages tagged [store]. To see them, set the log level to DEBUG:

STND_LOG_LEVEL=DEBUG astro dev

Example output:

stnd:: [store] [provide] "user"
stnd:: [store] [provide] "search"
stnd:: [store] [inject] "user"
stnd:: [store] [inject] "search"

Overwriting a key or injecting a missing key emits a warning at the WARN level, visible in all environments:

stnd:: ⚠ [store] [provide] overwriting existing key: "user"
stnd:: ⚠ [store] [inject] "theme" not found — returning null

This is your first clue when something is provided twice (a module manifest and a boot script both registering the same key) or when a component tries to consume a service that was never registered.


Known keys

These keys are registered by @stnd packages and should not be reused:

| Key | Type | Provided by | | ---------- | ----------------------------------- | ----------------------------------- | | "user" | nanostores atom | @stnd/account via module manifest | | "search" | async (query: string) => Result[] | App-specific launcher module | | "dialog" | { confirm, state } | @stnd/ui/dialog.js on import |


Notes / Observations

(jot down anything noticed here — quirks, gotchas, ideas)

Todo

  • [ ] Nothing tracked yet. [priority:: 3] [token_scale:: 3] [created:: 2026-07-14] [area:: framework]

License

MIT