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

sas-box

v0.1.2

Published

Typed sync/async acquisition contracts with inspectable provider capabilities for TypeScript apps and LLM harnesses.

Readme

sas-box

Typed sync/async acquisition contracts for agentic development, LLM harnesses, and agent graphs.

One typed provider contract for a value, synchronous loader, or asynchronous loader. Swap how a feature acquires its dependencies, require synchronous access where needed, and test consumers with local fixtures.

Use it for mixed sync/async provider contracts, including dependencies inside an LLM agent harness. For an all-async consumer, a plain () => Promise<T> may be enough.

Install

npm install sas-box

Includes TypeScript declarations, CommonJS and ESM import support, and no runtime dependencies.

Replaceable acquisition boundaries

A shipping feature needs rates; its provider owns how those rates are obtained. The feature exposes a quote function and never reaches into provider internals.

import assert from 'node:assert/strict';
import { SasBox } from 'sas-box';

type Rates = { shippingCents: number; freeShippingFromCents: number };

function createQuote(rates: SasBox.Unknown<Rates>) {
  return async (subtotalCents: number): Promise<number> => {
    const current = await rates.async();
    return subtotalCents + (
      subtotalCents >= current.freeShippingFromCents ? 0 : current.shippingCents
    );
  };
}

async function main() {
  const fixture = SasBox.fromValue({
    shippingCents: 500, freeShippingFromCents: 5000,
  });
  const quote = createQuote(fixture);
  assert.equal(await quote(2500), 3000);
  assert.equal(await quote(6000), 6000);

  // In-memory implementation of an asynchronous provider.
  const alternate = SasBox.fromAsync(async () => ({
    shippingCents: 300, freeShippingFromCents: 4000,
  }));
  assert.equal(await createQuote(alternate)(2500), 2800);
  console.log('Shipping feature passed with both providers');
}

void main().catch(error => { console.error(error); process.exitCode = 1; });

In separate modules, export Rates, createQuote, and the selected provider. A quoting-rule change belongs with the feature and its tests; a rate-loading change belongs with the provider. Include both sides when a change affects their shared behavior. The provider contract is an entry point for humans and coding agents working on either module.

The example runs without a server, network connection, or dependency container. Each invocation acquires rates again; the box does not cache. A cached provider must implement its own sharing and freshness rules.

Typed provider checks and fixtures

A synchronous build step requires SasBox.Sync<Rates>. An asynchronous feature can accept SasBox.Unknown<Rates>. These requirements are checked before either provider runs.

import assert from 'node:assert/strict';
import { SasBox } from 'sas-box';

type Rates = { shippingCents: number };

function buildShippingLabel(source: SasBox.Sync<Rates>): string {
  return `Shipping: ${source.sync().shippingCents} cents`;
}

const fixture = SasBox.fromValue({ shippingCents: 500 });
const asynchronous = SasBox.fromAsync(async () => ({ shippingCents: 500 }));

function rejectedContracts() {
  // @ts-expect-error An async-only provider cannot satisfy a sync consumer.
  buildShippingLabel(asynchronous);
  // @ts-expect-error The provider payload must contain numeric shippingCents.
  const wrongPayload: SasBox.Unknown<Rates> = SasBox.fromValue({ shippingCents: '500' });
  return wrongPayload;
}

assert.equal(buildShippingLabel(fixture), 'Shipping: 500 cents');
console.log('Synchronous provider contract passed');

Save each example in its own example.ts file. To check it:

npm install --save-dev typescript @types/node
npx tsc --noEmit --strict --skipLibCheck --target ES2022 --module Node16 --moduleResolution Node16 example.ts

Run a checked example with bun example.ts, or compile it without --noEmit and run node example.js. Bun execution alone does not type-check.

The uncalled rejectedContracts function demonstrates rejected replacements. Each @ts-expect-error also requires the compiler to find an error on that line. Remove the directive to inspect the diagnostic. Type checking evaluates the declared contract; assertions evaluate selected behavior. Neither establishes that a real remote provider is correct or available.

Acquisition capabilities for host tooling

A host can build tooling from a provider's alias and hasSync() result without loading its value. Application-defined metadata can describe ownership or purpose alongside the box.

import assert from 'node:assert/strict';
import { SasBox } from 'sas-box';

type DescribedProvider<T> = {
  metadata: { owner: string; purpose: string };
  provider: SasBox.Unknown<T>;
};

let acquisitions = 0;
const rates: DescribedProvider<{ shippingCents: number }> = {
  metadata: { owner: 'checkout', purpose: 'shipping rates' },
  provider: SasBox.fromSync(() => {
    acquisitions++;
    return { shippingCents: 500 };
  }, 'shipping-rates'),
};

function describe<T>(entry: DescribedProvider<T>) {
  return {
    ...entry.metadata,
    alias: entry.provider.alias,
    syncAvailable: entry.provider.hasSync(),
  };
}

assert.deepEqual(describe(rates), {
  owner: 'checkout',
  purpose: 'shipping rates',
  alias: 'shipping-rates',
  syncAvailable: true,
});
assert.equal(acquisitions, 0);
console.log(describe(rates));

The metadata record belongs to the application; sas-box supplies the inspectable acquisition contract. A catalog can group providers by owner, and a sync-only host can reject an incompatible provider before calling it. hasSync() reports a capability, not readiness, cost, health, or permission. Use the return value of assertHasSync() when the caller needs a statically narrowed sync provider.

Inside an LLM agent harness

An agent harness coordinates a large language model (LLM), tools, and their execution environment. An agent workflow graph organizes that work into nodes and routing edges. sas-box supplies typed dependencies to those nodes: configuration, context, or clients acquired through a local fixture or an asynchronous provider.

This retrieval node receives its query from graph state and obtains a search client through a provider. Its input and output stay the same when the provider changes.

import assert from 'node:assert/strict';
import { SasBox } from 'sas-box';

type Retriever = { search(query: string): Promise<readonly string[]> };
type RetrievalState = { query: string };

function createRetrieveNode(provider: SasBox.Unknown<Retriever>) {
  return async (state: RetrievalState): Promise<{ documents: readonly string[] }> => {
    const retriever = await provider.async();
    return { documents: await retriever.search(state.query) };
  };
}

async function main() {
  const fixture = SasBox.fromValue<Retriever>({
    async search(query) {
      return query === 'refunds' ? ['Refunds are available within 30 days.'] : [];
    },
  }, 'fixture-retriever');

  // In-memory async initialization; an application can create its remote client here.
  let initializations = 0;
  const initialized = SasBox.fromAsync<Retriever>(async () => {
    initializations++;
    const documents = new Map([
      ['refunds', ['Refunds are available within 30 days.']],
    ]);
    return { async search(query) { return documents.get(query) ?? []; } };
  }, 'initialized-retriever');

  const expected = { documents: ['Refunds are available within 30 days.'] };
  assert.equal(initialized.hasSync(), false);
  assert.equal(initializations, 0);
  assert.deepEqual(await createRetrieveNode(fixture)({ query: 'refunds' }), expected);
  assert.deepEqual(await createRetrieveNode(initialized)({ query: 'refunds' }), expected);
  assert.equal(initializations, 1);
  assert.deepEqual(await createRetrieveNode(fixture)({ query: 'unknown' }), { documents: [] });
  console.log('Retrieval node passed with local and asynchronous providers');
}

void main().catch(error => { console.error(error); process.exitCode = 1; });

The provider acquires a callable client; the node passes the query to that client. This keeps state-dependent work explicit because box callbacks take no arguments. When the contract and fixtures cover the proposed change, a coding agent can use them as a focused working context, then run the type check and assertions before integrating the node into the harness.

The harness owns node routing, retries, cancellation, and persistence. Acquisition runs on every node invocation; share a client in the provider when its lifetime should span multiple calls. The example needs no graph framework, model API, or network connection.

Supply separate implementations

Use new SasBox.Sync(sync, async, alias?) when the same value can be acquired through two implementations. This Node.js example reads a settings file; create the file and run the example:

node -e "require('node:fs').writeFileSync('settings.json', JSON.stringify({port: 3000}))"
import { readFileSync } from 'node:fs';
import { readFile } from 'node:fs/promises';
import { SasBox } from 'sas-box';

const source = new SasBox.Sync(
  () => readFileSync('settings.json', 'utf8'),
  () => readFile('settings.json', 'utf8'),
  'settings-file',
);

console.log(source.sync()); // {"port":3000}
source.async().then(console.log); // {"port":3000}

The caller chooses the route. Both implementations should satisfy the same application contract. fromSync(fn).async() invokes fn immediately; it does not move blocking work off the event loop.

API

All exports come from sas-box.

| Factory or constructor | Result | | --- | --- | | SasBox.fromValue(value, alias?) | SasBox.Sync<T> returning the supplied value | | SasBox.fromSync(fn, alias?) | SasBox.Sync<T> with a Promise adapter | | SasBox.fromAsync(fn, alias?) | SasBox.Async<Awaited<T>>; accepts Promise-like results | | new SasBox.Sync(sync, async, alias?) | Both acquisition routes | | new SasBox.Async(async, alias?) | Async-only acquisition | | new SasBox.Unknown(syncOrUndefined, async, alias?) | A capability determined at runtime |

| Member | Behavior | | --- | --- | | sync | A callback returning exactly T, or undefined | | async() | Returns Promise<Awaited<T>> | | hasSync() | Checks for a sync callback without invoking it; returns a boolean | | assertHasSync() | Returns a sync-capable view or throws SasBoxAssertionError | | getAsSasBoxSync() | Checks the capability and returns a plain { sync, async } view | | getAsSasBoxAsync() | Returns a plain { async } view | | resolveSyncFirst(thisArg?) | Invokes sync if present, otherwise async; always returns a Promise | | getSyncFirstResolver(thisArg?) | Returns a callback with the same sync-first behavior | | alias | Readonly label used in assertion messages |

ISasBoxSync<T>, ISasBoxAsync<T>, and their union ISasBox<T> describe the structural views without requiring a class instance. hasSync() does not narrow an unknown box's static type; use the return value of assertHasSync() when a callable sync route is required.

Promise and error behavior

.sync() preserves the callback's exact return type, even if that type is a Promise. Promise-returning entry points assimilate Promises and structural thenables:

import { SasBox } from 'sas-box';

const box = SasBox.fromSync(() => Promise.resolve(42));
const raw: Promise<number> = box.sync();
const flattened: Promise<number> = box.async();
const selected: Promise<number> = box.resolveSyncFirst();

Promise.all([raw, flattened, selected]).then(console.log); // [42, 42, 42]

A direct sync callback throw is synchronous. Throws through .async() or sync-first resolution become rejected Promises. A SasBox.Sync<Promise<T>> provides a Promise synchronously; it does not make the eventual T available.

Sync-first resolution passes thisArg to the selected callback, defaulting to null. Prefer callbacks that close over their dependencies, or bind methods before passing them to a factory.

Scope and composition

Agent integration contract

| Decision | Owner | | --- | --- | | Which acquisition routes exist | Provider and its box type | | Which route this execution phase accepts | Agent host | | Whether a tool may run and which arguments are valid | Host authorization and input validation | | Timeouts, cancellation, retries, and sharing | Application execution policy |

Acquire a tool definition or context with the box, validate it in the host, then invoke the tool through your executor. A successful acquisition does not certify that the tool is authorized, safe, fresh, or free of side effects.

Boxes do not cache, deduplicate, retry, cancel, dispose of, or own acquired resources. An async-only box does not gain a sync route after resolution. The provider owns freshness and resource policy; create a new box if the available capabilities change.

Use a plain value or factory when callers already agree on one acquisition mode. Use a box when multiple producers and hosts need to inspect or require different modes through one contract.

With a dependency container such as DI Bag, register a factory calling the chosen route: () => provider.sync() or () => provider.async(). The container controls its own sharing and disposal; the provider contract works independently of the container.

Validate a change

With Node.js, npm, and Bun installed:

npm ci
npm run check
npm pack --dry-run

Release notes · npm package