hooksentinel

Bundle Size

Why @hooksentinel/core is 3.23 KB min+gzip with zero runtime dependencies, and how the subpath exports keep it that way.

The numbers

ImportMin+gzip
@hooksentinel/core (core pipeline only, no providers)1.8 KB
@hooksentinel/core + one provider (e.g. stripe)3.23 KB
@hooksentinel/core/express+0.4 KB
@hooksentinel/core/fastify+0.4 KB
@hooksentinel/core/nestjs+0.9 KB
@hooksentinel/core/next+0.3 KB
@hooksentinel/core/hono+0.3 KB
@hooksentinel/core/lambda+0.4 KB
@hooksentinel/core/stores (memory)+0.2 KB
@hooksentinel/core/testingnot shipped to production

The commonly quoted "3.23 KB" figure is the realistic case most projects ship: the core pipeline plus a single provider adapter, tree-shaken. Adding a framework adapter or an idempotency store adds a few hundred bytes each, not kilobytes, because they're thin — the actual crypto and parsing logic lives once in the core and adapters just wire it up.

Zero runtime dependencies

@hooksentinel/core's package.json has no dependencies field. Everything it needs — HMAC signing, Ed25519 verification, JSON parsing — is available from the JavaScript standard library and Web Crypto (SubtleCrypto), which every target runtime (Node.js 18+, browsers, Cloudflare Workers, Deno, Bun) implements natively.

This matters for three concrete reasons:

  1. No supply-chain surface. A dependency-free package can't have one of its dependencies get compromised. npm ls @hooksentinel/core shows exactly one package, always.
  2. No version resolution conflicts. hooksentinel never pins a version of some crypto or HTTP library that collides with a version your app already uses.
  3. Runtime portability. Because nothing here reaches for Node's crypto module specifically, the same code runs on Node, the edge runtime, Workers, and Deno without a build-time swap — see the Hono / Cloudflare Workers guide for where this matters in practice.

Framework adapters (@hooksentinel/core/express, /nestjs, etc.) also declare no runtime dependencies — they use express, @nestjs/common, and so on only as peerDependencies, so your own installed version is what's used, and the package doesn't drag in a second copy.

Why subpath exports

Providers, framework adapters, idempotency stores, and the test signer all live behind separate entry points (@hooksentinel/core/express, @hooksentinel/core/stores, @hooksentinel/core/testing, ...) rather than being exported from the single main module. Import only @hooksentinel/core and stripe, and a bundler never even sees the code for GitHub's, Shopify's, or Discord's signature schemes, NestJS's DI wiring, or the test signer — there's nothing to tree-shake because it was never pulled in.

// only stripe's verification code and the core pipeline end up in your bundle
import { createWebhookHandler, stripe } from '@hooksentinel/core';
// this adds every provider's code to your bundle — avoid it
import * as hooksentinel from '@hooksentinel/core';

Measuring it yourself

npx esbuild --bundle --minify --format=esm src/webhooks/stripe.ts | gzip -c | wc -c

Numbers above were measured this way against a minimal handler file importing one provider, one framework adapter, and memoryStore.

Last updated on

On this page