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

@premaanshvyas/lockstep

v0.5.0

Published

Finds where your code assumes a third-party API can only return values it already knows about. Runs locally, executes nothing, uploads nothing.

Readme

lockstep

Finds where your code assumes a third-party API can only return values it already knows about.

npx lockstep

Runs locally. Reads your source and your installed SDKs. Executes nothing, uploads nothing, needs no account.


The problem

Stripe adds a new subscription status. Your code has this:

export function shouldRevokeAccess(sub: Stripe.Subscription): boolean {
  return sub.status === 'canceled' || sub.status === 'unpaid';
}

A paused customer now keeps full access without paying. Nothing crashes. No error is logged. Every test passes, because your tests never had a paused subscription in them — which is exactly why nobody caught it.

Vendors know this happens. Stripe's docs tell you to handle unfamiliar values gracefully. Plaid's docs say they add enum values at any time with no version bump. Nobody checks whether anyone actually does it.

What it does

  1. Inventory — which vendor SDKs are installed, at which version.
  2. Vendor truth — reads the type declarations inside node_modules and extracts every fixed list of values the SDK declares.
  3. Assumptions — runs the TypeScript compiler over your code and finds every place it treats one of those values as a closed set.
  4. Compare — subtracts one from the other, ranked by consequence.
● src/billing/access.ts:9
  handles 3 of 8 values of Subscription.Status · no fallback
  missing: canceled, incomplete, incomplete_expired, paused and unpaid
  → 5 unhandled values fall out of the switch and return undefined
  Stripe's own types declare no fallback for this field

Four layers, and an honest ledger

Type information runs out. Rather than quietly skipping what it can't resolve, lockstep reports it.

| Layer | Catches | Confidence | |---|---|---| | Type checker | typed TypeScript — exact call sites | certain | | AST shape | untyped JavaScript, comparisons, switches | probable | | HTTP scan | raw fetch() calls that skip the SDK | probable | | Ledger | dynamic access, unparseable files | reported, never dropped |

Every run ends with what was understood and what wasn't:

Coverage  5 of 7 decision points understood  ·  4 of 4 files read
          3 resolved by the compiler, 1 matched by shape, 1 raw HTTP call

2 could not be resolved:
     2 — indexed by a vendor value computed at runtime

A tool that tells you what it couldn't see is a tool you can believe when it says it found something.

Usage

npx lockstep [path]

  --inventory        where this code calls each vendor, and which response
                     fields it reads — facts only, no findings
  --by <mode>        inventory grouping: file | operation
  --file <path>      inventory: only files whose path contains this
  --sort <mode>      inventory operations: name (default) | calls
  --all              include low-severity findings
  --json             machine-readable output
  --fail-on <level>  exit non-zero at high | medium | low  (for CI)
                     findings only — the inventory never affects the exit code
  --help

Identical input bytes produce identical --json output bytes, so two commits can be diffed. If a limit stops the walk short, the report says so in its first lines and names every file it skipped. Exit codes: 0 normally, 1 no node_modules (or --fail-on), 2 bad path, 3 the project needs more heap than this process has — in which case it prints the exact command to run rather than producing a partial answer.

The inventory

--inventory prints the map: every place the code invokes a vendor, every field of a vendor response it reads, and — in the same list, at the same indent — every place something could not be resolved, with the reason.

Stripe  [email protected]
  app/api/stripe-webhook/route.ts
    :5     new Stripe()                              → Stripe
    :19    stripe.webhooks.constructEvent()          → Event
    :24    .data                           read      Event
    :26    .type                           read      Event
    :36    subscription.status             ?         receiver `subscription` was
                                                     widened to `any` at line 24
  4 calls · 3 operations · 4 fields read · 9 unresolved

No severity, no ranking, no advice. Calls are reported where they are written: a local function that forwards a vendor call is counted once, at its own line, and its callers are not counted — an identification tool says where the call is, not where control might eventually reach one.

--json carries the same records with no truncation, plus vendorRoots, the list of directories provenance was resolved against.

Vendors

Any third-party package your code calls, not a fixed list. A hand-written registry of 129 packages carries the things that cannot be derived — hostnames, ambient globals, a display label — for Stripe, OpenAI, Anthropic, Twilio, Plaid, Slack, Square, SendGrid, Resend, Shopify, and about 120 more across AI, payments, comms, auth, data, observability and developer tooling. Everything else is discovered: a package whose own shipped code both names an HTTP endpoint and sends HTTP requests is read in full, exactly like a curated one, and the evidence that promoted it is printed beside every row so you can check it.

Every other package your code imports is still listed by name with a file count. Nothing is omitted; --json carries every package in every tier under packages, and --all expands the summarised ones in the terminal.

The value lists are never hardcoded — they are read from whichever SDK version the project has installed, so this does not go stale when a vendor ships new values.

What it deliberately does not do yet

Run your code, write fixes, or open pull requests. Each of those needs something you have to grant it. This phase asks for nothing.

Development

npm install
npm run build
node dist/cli.js /path/to/project

MIT.