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
| Import | Min+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/testing | not 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:
- No supply-chain surface. A dependency-free package can't have one of its dependencies get compromised.
npm ls @hooksentinel/coreshows exactly one package, always. - No version resolution conflicts. hooksentinel never pins a version of some crypto or HTTP library that collides with a version your app already uses.
- Runtime portability. Because nothing here reaches for Node's
cryptomodule 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 -cNumbers above were measured this way against a minimal handler file importing one provider, one framework adapter, and memoryStore.
Last updated on