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

@steve.kite/stdlib

v0.0.12

Published

A verified, tree-shakeable TypeScript standard library

Readme

Steve's stdlib

This is a my personal stdlib - a set of verified code that agents can use to write typescript. It is mostly copy of existing code or wrappers. It is mostly a single library and i expect compilers to strip down when building for web.

Context is passed explicitly as the first argument to every function that needs it. That argument is always named ctx, including in callback APIs.

Context

Create one root context before starting the rest of the application. It begins with the reserved name <root>. Only a root context has named, and it can be used to create any number of named contexts, including contexts with the same name. Every context used after that point is derived from the root:

import { createContextNamespace, createRootContext } from "@steve.kite/stdlib";

const root = createRootContext();
root.name; // "<root>"

const ctx = root.named("api");
ctx.name; // "api"

const requestId = createContextNamespace("request-id", "unknown");
const requestCtx = requestId.set(ctx, "request-42");

requestId.get(requestCtx); // "request-42"

The returned Context cannot itself be renamed and does not expose named; namespaces, lifetimes, and other operations derive new contexts from it while retaining its name.

Context objects can be extended globally with typed getters and setters. Declare the properties once in an ambient TypeScript file, then register their runtime behavior during application startup:

import { registerContextExtension } from "@steve.kite/stdlib";

declare global {
    namespace stdlib {
        interface ContextExtensions {
            requestId: string;
            priority: number;
        }
    }
}

registerContextExtension("requestId", (ctx) => requestId.get(ctx));
registerContextExtension("priority", {
    get: () => 0,
    set: (ctx, value) => {
        console.log("New priority", value, ctx);
    },
});

After registration, every Context has typed requestId and priority properties. Setters perform the registered action; they do not replace the immutable context. Use ContextNamespace.set when you need a derived context.

Use detach(ctx) to copy a context's namespace values into a new root context that can be named again. Namespace values are retained by default. Set detachable: false when declaring a namespace whose value belongs only to the current context chain:

const transaction = createContextNamespace("transaction", undefined, {
    detachable: false,
});

const detachedRoot = detach(ctx);
const workerCtx = detachedRoot.named("worker");

The built-in context name, lifetime, and after-commit transaction queue are non-detachable. A detached root is named <root>, has no lifetime, and its derived contexts use the default next-tick after-commit behavior unless withAfterCommit is applied again.

Lifetime

A context can carry an optional abort signal in the ContextLifetime namespace. The built-in ctx.lifetime getter reads that namespace directly. withLifetime returns a derived context whose lifetime ends when either the parent lifetime or the supplied signal aborts:

import { createRootContext, scoped, timeout, withLifetime } from "@steve.kite/stdlib";

const ctx = createRootContext().named("api");
const controller = new AbortController();
const operationCtx = withLifetime(ctx, controller.signal);

await timeout(operationCtx, { ms: 10_000 }, async (ctx) => {
    await fetch("https://example.com", { signal: ctx.lifetime });
});

await scoped(operationCtx, async (ctx) => {
    // ctx.lifetime remains active only until this callback settles.
});

The timeout signal also aborts when the callback settles, preventing work started inside that scope from accidentally outliving it. Cancellation is cooperative: operations inside the callback must observe ctx.lifetime. scoped provides the same callback-bound lifetime without adding a deadline and inherits cancellation from its parent context.

After commit

Register work that should run after the current operation commits with ctx.afterCommit. Without further configuration, the callbacks begin on the next microtask. They run one at a time in registration order, and each callback receives the context used to register it:

ctx.afterCommit(async (ctx) => {
    await updateRedis(ctx);
});

Use withAfterCommit when a transaction controls when post-commit work may start. It returns a derived context and an async drain function. Invoke the function only after the commit succeeds:

const [transactionCtx, runAfterCommit] = withAfterCommit(ctx);

transactionCtx.afterCommit(async (ctx) => {
    await updateRedis(ctx);
});

await commitTransaction();
await runAfterCommit();

The function form, afterCommit(ctx, callback), has the same behavior as ctx.afterCommit(callback).

Logging

Install a Pino-compatible logger on the root before deriving application contexts. The logger reference is stored and invoked as-is:

import pino from "pino";
import {
    createRootContext,
    logger as contextLogger,
    withLogContext,
    withLogger,
} from "@steve.kite/stdlib";

let root = createRootContext();
const logger = pino();
root = withLogger(root, logger);
contextLogger.get(root); // logger

const ctx = root.named("api");
const requestCtx = withLogContext(ctx, {
    requestId: "request-42",
    operation: "load-user",
});

requestCtx.log.info("user:load userId=user-1");

logger.get(ctx) returns the original logger reference, or undefined when no logger is installed. withLogContext(ctx, fields) returns a derived context and shallow-merges its fields with existing log context. Each ctx.log.<level>(...) call passes those fields as the first argument to the original logger method, followed by the provided arguments. The exported levels are trace, debug, info, warn, error, and fatal. Without an installed logger, every ctx.log method is a no-op.

Telemetry

Install an OpenTelemetry-style tracer adapter on the root before creating named contexts. Naming a context carries the tracer but does not create a span:

import { createRootContext, traceSpan, withTracer, type Tracer } from "@steve.kite/stdlib";

declare const tracer: Tracer;

let root = createRootContext();
root = withTracer(root, tracer);

const ctx = root.named("worker");
traceSpan.get(ctx); // undefined

await ctx.span("job:run", async (ctx) => {
    traceSpan.get(ctx); // The child "job:run" span.
    // Perform traced work with ctx.
});

ctx.span(name, work) is the convenience form of span(ctx, name, work). It records thrown or rejected failures, ends its child span when the work settles, and returns the work's result. Without an installed tracer it calls work(ctx) directly and performs no tracing. tracer.get(ctx) returns the installed tracer and traceSpan.get(ctx) returns the current span.

The tracer adapter creates spans with startSpan(name, parentSpan); spans provide end() and may provide recordException(error).

Concurrency

Concurrency primitives carry Context instead of accepting separate abort signals. The context is always first and every work callback receives it first:

import {
    GracefulShutdown,
    asyncLock,
    backoff,
    createRootContext,
    forever,
    shutdown,
    withShutdown,
} from "@steve.kite/stdlib";

let root = createRootContext();
const appShutdown = new GracefulShutdown();
root = withShutdown(root, appShutdown);

const ctx = root.named("worker");
shutdown.get(ctx); // appShutdown

const lock = asyncLock();
await backoff(ctx, async (ctx, attempt) => {
    await lock.runInLock(ctx, async (ctx) => {
        // Perform one attempt while holding the lock. Throw to retry.
    });
});

const loop = forever(ctx, { name: "poller", delay: 15_000 }, async (ctx) => {
    // Poll using ctx; its lifetime ends when shutdown begins.
});
await appShutdown.shutdown();
await loop;

asyncLock() ignores reentry detection by default, preserving ordinary lock contention (and deadlocking if an outer callback awaits its own nested acquisition). Use asyncLock({ reentry: "allow" }) to let nested calls run immediately while the context owns that lock, or reentry: "block" to reject a detected nested call with an error. Ownership expires when the outer callback settles, even if its context escapes.

Retries should almost always wrap lock acquisition as shown above. This releases the lock before each backoff delay, allowing unrelated callers to make progress. Only put a retry inside runInLock when excluding every other caller for the entire retry window is an intentional requirement.

Locks and queues are not bound to a context when created. runInLock(ctx, work) uses its caller's context to create asyncLock.wait and asyncQueue.wait spans when acquisition is contended. Each wait span ends when that caller acquires the lock, before its protected work starts; uncontended acquisition creates no wait span.

Additional coordination primitives follow the same context-first callback shape:

import { mapAsyncLock, mapReadWriteLock, readWriteLock, semaphore } from "@steve.kite/stdlib";

const permits = semaphore(4);
await permits.run(ctx, async (ctx) => {
    // At most four permit holders run concurrently.
});

const users = mapAsyncLock<string>();
await users.runInLock(ctx, userId, async (ctx) => {
    // Serialized only with callers using the same userId.
});

const state = readWriteLock();
await state.runInReadLock(ctx, async (ctx) => {
    // Readers may run together.
});
await state.runInWriteLock(ctx, async (ctx) => {
    // Writers are exclusive.
});

const records = mapReadWriteLock<string>();
await records.runInWriteLock(ctx, recordId, async (ctx) => {
    // Read/write exclusion is isolated to recordId.
});

Read/write locks give queued writers priority over new readers. Keyed locks discard an entry once it has no active or waiting callers. Contended waits emit semaphore.wait, mapAsyncLock.wait, readWriteLock.{read,write}.wait, or mapReadWriteLock.{read,write}.wait spans from the caller's context.

Create the application's GracefulShutdown before deriving named contexts and install it on the root with withShutdown(root, appShutdown). Read it later with shutdown.get(ctx); shutdown is not a field on Context. A forever started with a derived context automatically registers under its required name, so the coordinator can report and await the loop without a separate registration call.

The concurrency module also exports asyncQueue, delay, retry, AbortedError, and abort helpers. GracefulShutdown reports named handlers that fail or do not finish before its timeout.

License

Apache 2.0