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

@cratestack/link-batch

v0.8.6

Published

A batshit-style automatic batch scheduler for CrateStack's generated TypeScript RPC client, shipped as an RpcLink.

Readme

@cratestack/link-batch

A batshit-style automatic batch scheduler for CrateStack's generated TypeScript RPC client (transport rpc schemas), shipped as an RpcLink (issue #182) rather than a fetch override — so it composes with @cratestack/link-logger, a retry link, or an auth-refresh link instead of clobbering them.

Multiple unary calls issued in the same tick collapse into a single POST /rpc/batch request instead of firing one POST /rpc/{op_id} each.

Usage

import { createBatchLink } from "@cratestack/link-batch";
import { CratestackRpcRuntime } from "./generated/runtime"; // your project's generated client

const runtime = new CratestackRpcRuntime("https://api.example.com", {
  links: [createBatchLink()],
});
const client = new MyGeneratedClient(runtime);

// These three calls, if issued in the same tick, become ONE /rpc/batch request:
const [a, b, c] = await Promise.all([
  client.widgets.get(1),
  client.widgets.get(2),
  client.widgets.get(3),
]);

Batching semantics

  • Window: defaults to queueMicrotask — calls fired synchronously in the same tick (e.g. inside Promise.all) collapse. Pass windowMs to widen the window across ticks.
  • Partitioning (fixed in #273; versions before this carried a bug here — see "Known limitations" below): each flush is split into partitions by transport envelope — headers (excluding Idempotency-Key, which is carried per-frame, not per-request), fetch reference, codec reference, and the resolved batch URL — and every partition is sent as its own POST /rpc/batch. Calls that share an envelope, the overwhelmingly common case, still collapse into exactly one request; calls that don't (e.g. two different Authorization headers) each keep their own, in a separate request, rather than one silently overwriting the other's.
  • Aggregate headers: pass headers to createBatchLink({ headers }) to declare headers that every synthesized /rpc/batch request carries, merged over each partition's own — a same-named per-call header is overridden by this value, not the other way around. Use this for service-level headers (e.g. an API key) rather than per-tenant auth, which should come from per-call headers that drive partitioning.
  • maxBatchSize is enforced per partition, not globally across the flush — a single oversized partition chunks into several concurrent requests; it never borrows headroom from a different partition.
  • Dedup: only calls sharing an explicit idempotencyKey are collapsed into one request frame by default — that's already the caller's own signal that the call is a safe repeat. Calls with no idempotency key are never auto-collapsed, since the server does no dedup of its own and silently merging two textually-identical but unmarked mutations would be unsafe. Pass a custom dedupe for full value-based collapsing, or () => null to disable dedup and only batch. Dedup runs within a partition — it never collapses calls across two different envelopes.
  • Correlation: results are fanned back out by matching each response frame's id, not array position — the server's /rpc/batch contract already guarantees order (see docs/design/rpc-transport.md §3.2), but id-based matching is strictly more robust and mirrors the repo's own Rust client-side batch debouncer (examples/rpc-batch-debounce).
  • Failure isolation: a partition that fails (network error, non-OK response) only rejects the callers queued in that partition — it never affects a concurrently in-flight partition.

Known limitations

  • Aborting an individual call's AbortSignal only cancels it if its batch hasn't been sent yet — it does not cancel an in-flight /rpc/batch request.
  • (Fixed in #273, kept here for anyone reading an older version's docs) Before #273, the synthesized /rpc/batch request reused the first queued call's headers/fetchFn/codec for the whole flush — per-call custom headers on later calls in the same window were silently dropped from the aggregate request instead of applied. This is why partitioning exists now.