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

@assistant-ui/inject

v0.1.0

Published

A minimal dependency injection library: generators that yield* the services they need

Readme

inject

A minimal dependency injection library. Functions declare what they need where they need it; you supply it once, at the top.

Services and injectables

A service is a typed key for a resource — a database, config, a logger.

type Database = { find(id: string): User };
const Database = createTag<Database, "Database">({ key: "Database" });

An injectable is a generator that requests services with yield*. yield* Database pauses the generator until the Database is supplied, then resumes with it.

function* getUser(id: string) {
  const db = yield* Database;
  return db.find(id);
}

Injectables compose. yield* one inside another and their requests merge, so getFullName inherits getUser's Database without naming it.

function* getFullName(id: string) {
  const user = yield* getUser(id); // inherits Database
  return `${user.firstName} ${user.lastName}`;
}

Running

inject(injectable, bindings) runs an injectable, supplies every service it requests, and returns a Promise of the result. Each service needs exactly one binding; a missing one is a compile error.

const user = await inject(getFullName("u1"), [provideValue(Database, db)]);

Bindings

A binding supplies one service.

provideValue(S, value) — a value.

provideValue(Database, db);

provide(S, builder) — a value built from other services. The builder is itself an injectable, so it requests with yield*. Built lazily, once per run — a singleton.

provide(Database, function* () {
  const config = yield* Config;
  return new Database(config.url);
});

contribute(S, builder) · contributeValue(S, value) — a fragment of a collection. A service bound this way resolves to all fragments concatenated, in binding order.

const Routes = createTag<readonly Route[], "Routes">({ key: "Routes" });

await inject(app(), [
  contributeValue(Routes, [{ path: "/api" }]),
  contributeValue(Routes, [{ path: "/admin" }]),
]); // Routes → [{ path: "/api" }, { path: "/admin" }]

Defaults

A service can carry its own value, used when no binding covers it — then it is no longer required. Pass a fixed defaultValue, or a default injectable that requests its own services.

const Logger = createTag({ key: "Logger", defaultValue: console });

const Database = createTag({
  key: "Database",
  default: function* () {
    const config = yield* Config;
    return new Database(config.url);
  },
});

ifExists(S, bindings)

Apply bindings only when something else provides S; otherwise drop them. Conditions resolve to a fixpoint, so one ifExists can enable another.

await inject(app(), [
  provideValue(Database, db),
  ifExists(Database, [provideValue(Cache, cache)]), // applied; dropped without Database
]);

Contexts

A context is a stack of binding layers. Resolving a service searches the stack innermost-first: inner layers shadow outer ones, and a service a layer doesn't bind falls through to the layers above. inject is the common case — one layer, run to completion. These tools build and reuse the stack.

injectLayer(injectable, bindings) — apply some bindings now, return an injectable for the rest. When a runner finally drives it, it stacks its bindings as a layer on top of that runner's context. Still-missing services stay in the type.

const partial = injectLayer(getFullName("u1"), [provideValue(Database, db)]);
const user = await inject(partial, [provideValue(Logger, logger)]);

Because it is a layer, its bindings shadow those below and its contributions are local to it. Singletons are shared across the whole stack.

runPromise(injectable, context?) — run against a context, defaulting to empty. inject(source, bindings) is runPromise(injectLayer(source, bindings)) plus a compile-time check that nothing is missing.

Grab the current context with yield* Context and run another injectable against it — reusing its bindings and the singletons already built in them, with nothing threaded through.

provide(Handler, function* () {
  const ctx = yield* Context; // the current stack
  return { run: (input) => runPromise(handle(input), ctx) };
});

runSync(injectable, context?) — like runPromise but returns the value directly. Throws if it reaches any async work (an async function* source, an async builder, an awaited value).