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

edge-shake

v0.2.2

Published

Diagnostic CLI for Cloudflare Workers cold-start CPU issues (Error 1102) caused by heavy top-level module scope code.

Readme

edge-shake

Diagnose (and optionally fix) Cloudflare Workers Error 1102: "Script startup exceeded CPU time limit" — the cold-start CPU crash that shows up when frameworks like OpenNext (Next.js on Cloudflare) bundle heavy dependencies (ORM clients, crypto libs, large JSON/i18n data, Zod schemas) into the global execution scope, where Cloudflare's cold-start CPU budget is extremely tight.

npx edge-shake .open-next/worker.js

Why this happens

Cloudflare Workers give a new "isolate" only a tiny CPU budget to finish starting up before it can serve its first request. Code written to run at module scope — const db = drizzle(env.DB), a big translations object, a large Zod schema tree — all runs on every cold start, before your handler ever sees a request. If that setup work is heavy enough, Cloudflare kills the isolate and returns Error 1102.

The usual fix is deferring that work into a lazy getter so it only runs on first use, not on every cold start:

// Before — runs on every cold start
const db = drizzle(env.DB);

// After — runs once, on first actual use
let _db;
function getDB(env) {
  if (!_db) _db = drizzle(env.DB);
  return _db;
}

edge-shake finds code like this in your compiled bundle, explains why it's risky, and (with a license) can rewrite it for you automatically.

Free tier: diagnose

npx edge-shake [path-to-worker.js]   # defaults to .open-next/worker.js

Read-only. Never modifies your files. Run it as a post-build step, after opennextjs-cloudflare build and before wrangler deploy.

It parses your compiled bundle and flags top-level (module scope) code matching known-risky patterns:

  • ORM/DB client construction (Drizzle, Prisma, Kysely, and known createClient/createPool factories)
  • Large object/array literals (default threshold: 5 KB) — catches bloated i18n JSON, static config dumps
  • Large Zod/jose/jsonwebtoken/yup/ajv schema trees built at module scope
  • Repeated TextEncoder/TextDecoder construction inside a top-level loop

Exit code is non-zero when something is flagged, so it's CI-friendly.

Real example

Here's what edge-shake finds when pointed at a genuine .open-next/worker.js-style bundle — this isn't a hand-written test fixture, it's the actual output of esbuild bundling and minifying a Drizzle client construction the same way a real Cloudflare build would (fixtures/real-minified-drizzle.js):

$ npx edge-shake fixtures/real-minified-drizzle.js
edge-shake: found 1 risky top-level pattern(s) in fixtures/real-minified-drizzle.js

[MEDIUM] fixtures/real-minified-drizzle.js:14:24929 — orm-client-construction
  Matched: possible Drizzle ORM client (name mangled by bundler, matched via fingerprint)
  Top-level call taking an "env"-shaped argument, in a file that contains Drizzle ORM's
  runtime fingerprint — this MIGHT be a bundled/minified ORM client construction where the
  original constructor name was renamed by the bundler. This is a lower-confidence heuristic
  match (the fingerprint could belong to unrelated code elsewhere in the bundle) — verify
  manually before treating it as confirmed.
  Suggested fix (not applied):
    If this is client construction, wrap it in a lazy getter so it only runs on first use:
    let _client;
    function getClient(env) {
      if (!_client) _client = /* this call */;
      return _client;
    }

Summary: 1 finding(s) (0 high severity). This is a heuristic diagnostic — "risky", not
"will fail". Run with a paid license to auto-fix allowlisted patterns.

Notice this is flagged MEDIUM, not HIGH. Here's why.

A note on the fingerprint fallback (and why it's medium severity, not high)

Real bundlers rename identifiers. When we tested against unminified source, edge-shake matched the actual constructor name (drizzle, PrismaClient, etc.) directly, with high confidence. Once we tested against a real minified bundle, that name was gone — esbuild had renamed drizzle itself to a single letter. Name matching alone silently missed it.

The fix: minifiers can rename identifiers, but they can't rewrite the string literals a library's own runtime depends on. Drizzle's bundled code still contains literal strings like "drizzle:entityKind" no matter how aggressively it's minified. So when a top-level call takes an env-shaped argument in a file that contains one of these known fingerprint strings, we flag it — with a caveat baked into the severity.

We deliberately cap this fallback at medium severity, even for validated fingerprints, because it's a file-wide co-occurrence signal, not proof that this specific call is the client construction. We confirmed this can genuinely false-positive: an unrelated setupLogger(env.LOG_LEVEL) call in a file that happens to mention a fingerprint string elsewhere (e.g., in an unrelated comment or log message) will also get flagged. We chose to keep the fallback rather than drop it, because a false positive here just means "look at this line and decide for yourself" — the tool never auto-modifies anything based on this signal. A missed real ORM client construction, on the other hand, is a silent failure to catch the exact thing this tool exists for.

If this trade-off doesn't work for your use case, --threshold and reading the severity field let you filter to only high-confidence findings.

Paid tier: auto-fix

npx edge-shake fix [path-to-worker.js]           # prints a patch
npx edge-shake fix [path-to-worker.js] --write    # applies it in place

Requires a license key, set via EDGE_SHAKE_LICENSE_KEY or a .edge-shake.json / ~/.edge-shake/config.json file ({ "licenseKey": "..." }).

The auto-fixer is intentionally narrower and more conservative than the diagnostic: it only rewrites patterns from an explicit allowlist (currently: Drizzle, Prisma, Kysely client construction), and — unlike the diagnostic scanner — it never uses the fingerprint fallback described above. Auto-fixing requires being confident about what a call actually is, not just that it might be; when it can't be fully confident, it does nothing rather than guess:

  • If the tool can't trace every reference to a binding (shadowing, re-exports, destructuring it can't follow), it skips and reports why, rather than attempting a partial rewrite.
  • If it can't find an env parameter in any enclosing function scope for a call site, it skips that binding entirely — threading env incorrectly would be worse than not fixing it.
  • Every skip shows up in the report by name and line, so nothing silently falls through.

Example output (illustrative, not a captured run):

edge-shake fix: worker.js
  1 rewritten, 1 skipped

[APPLIED] line 3: "db" -> lazy getter "getDb" (2 call site(s) updated)
[SKIPPED] line 12: not auto-fixable, manual intervention needed: "cache" is exported and may be consumed elsewhere in the bundle

Development

npm install
npm run build
npm test

License

MIT