When I started building edge services on Cloudflare Workers, I kept hitting the same problem: bundle size.
Every "lightweight" utility pulled in dependencies. zod (1.1 MB), p-retry (7 KB + deps), axios-retry (requires axios). On Workers where cold starts matter, every KB counts.
So I built three zero-dependency replacements:
1. fetch-retry-jitter (1.6 KB gzip)
Replaces: p-retry, axios-retry, custom retry loops
import { fetchRetry } from 'fetch-retry-jitter';
const response = await fetchRetry('https://api.example.com/data', {
retries: 3,
baseDelayMs: 300,
maxDelayMs: 5000,
circuitBreaker: { failureThreshold: 5, resetTimeoutMs: 30000 },
timeoutMs: 5000,
deduplicate: true,
});
Features:
- Exponential backoff with full jitter (AWS recommended)
- Circuit breaker (closed/open/half-open states)
- Request deduplication — coalesces identical in-flight requests
- Retry-After header support (seconds + HTTP-date)
- Per-attempt
timeoutMs+retryIfpredicate -
AbortSignalsupport + stream body protection - 1.6 KB gzip, zero dependencies
2. env-assert-typed (528 B gzip)
Replaces: Zod (1.1 MB), Valibot (188 KB), Joi (50 KB+)
import { envAssert } from 'env-assert-typed';
const env = envAssert({
PORT: { type: 'number', required: true },
HOST: { type: 'string', default: '0.0.0.0' },
DEBUG: { type: 'boolean' },
REGION: { type: 'string', required: true, choices: ['us', 'eu'] },
API_KEY: { type: 'string', required: true, secret: true },
});
Features:
-
Automatic TypeScript inference — required →
T, default →T, optional →T | undefined -
Discriminated union schema — compile errors on mismatched
default/choicestypes -
coerce— custom string→type parsing -
refine— cross-field validation with access to all env -
refineAsync— async refinement hook -
transform— post-coercion transformation -
secret— marks sensitive values for error masking - Factory functions for defaults
- 528 B gzip, zero dependencies
3. ci-diff-changed (Action, ~80 lines bash)
Replaces tj-actions/changed-files (Node container + API calls)
- uses: Yearnless/ci-diff-changed@v1
id: diff
with:
base-ref: origin/main
pattern: 'packages/(api|web)/'
Outputs JSON array of changed files — perfect for conditional CI in monorepos.
Results
| Before | After | Savings |
|---|---|---|
| zod (1.1 MB) | env-assert-typed | 99.9% smaller |
| p-retry (7 KB + deps) | fetch-retry-jitter | ~75% smaller |
| tj-actions/changed-files (container) | ci-diff-changed (bash) | No container |
Total bundle reduction: ~40 KB → ~2 KB (95% reduction)
Why Zero-Dep Matters on the Edge
- Cold starts — Every KB adds milliseconds to cold start
- Bundle limits — Workers free tier: 1 MB, paid: 10 MB
- Supply chain — Zero deps = zero supply chain risk
- Auditability — Read the entire source in 5 minutes
Try Them
npm i fetch-retry-jitter env-assert-typed
# GitHub Action:
# uses: Yearnless/ci-diff-changed@v1
All three include:
- ✅ Provenance-signed npm packages
- ✅ Full TypeScript inference
- ✅ 100% test coverage
- ✅ Zero dependencies
- ✅ Works on Node, Bun, Deno, Cloudflare Workers, browsers
Repos:
Starter templates using them:
Built while learning in public. Feedback welcome!
Top comments (0)