double-meh-bundler
v1.0.0
Published
Server-side bundler for the double-meh bundle protocol: one fetch-handler core, framework adapters as thin subpaths.
Maintainers
Readme
double-meh-bundler :// 
The server side of the double-meh bundle protocol: accepts one bundled request, fans out to your services on the backend network, and returns all responses in a single compressed envelope. Many small JSON responses sharing one compression window is the payoff — measured ~40–48% fewer bytes on bursts of small responses — plus aggregation across origins that HTTP/2 multiplexing cannot do.
The core is a web-standard fetch handler — usable directly with Bun.serve, Deno.serve, service workers, and web-handler frameworks; adapters for callback servers are thin subpaths with zero framework dependencies.
Docs: browse the wiki · search it.
Install
npm i double-meh-bundlerUsage
import {createBundler} from 'double-meh-bundler';
const bundler = createBundler({
// the allow-list is the security boundary — required, no default
isUrlAcceptable: url => url.startsWith('/api/'),
resolveUrl: url => new URL(url, 'http://api.internal:8080').href
});
Bun.serve({fetch: bundler}); // or Deno.serve(bundler), or any Request → Response hostNode and Express are one adapter away:
import {createServer} from 'node:http';
import {createBundler} from 'double-meh-bundler';
import {toNodeHandler} from 'double-meh-bundler/node.js';
createServer(toNodeHandler(createBundler({isUrlAcceptable}))).listen(3000);
// Express: app.put('/bundle', toNodeHandler(createBundler({isUrlAcceptable})));
// Koa: import {toKoaMiddleware} from 'double-meh-bundler/koa.js';
// app.use(route.put('/bundle', toKoaMiddleware(createBundler({isUrlAcceptable}))));Observability and per-part transforms are opt-in hooks:
createBundler({
isUrlAcceptable,
onBundleStart: ({parts}) => metrics.count('bundle', parts.length),
onItemFinish: (part, {durationMs}) => metrics.timing(part.url, durationMs),
onBundleFinish: (bundle, {durationMs}) => metrics.timing('bundle', durationMs),
processResult: part => redact(part) // and processBundle for the whole envelope
});Observers are never awaited and their failures are swallowed — instrumentation cannot fail a bundle. Transforms are awaited; a nullish return keeps the original.
A client that asks for application/vnd.double-meh.bundle+jsonl gets the same parts streamed as their upstreams complete — a {"v":1} header line, then one part per line — so a slow upstream stops holding up the fast ones. It costs compression (per-part gzip flushing runs +25% bytes at 10 parts, +57% at 50), so it is client-negotiated rather than automatic: see the wire format.
The client side is double-meh's io.bundle — transparent batching with per-URL caching, ETag/304 revalidation, and error granularity intact. The wire format (v1) is deliberately library-independent: application/vnd.double-meh.bundle{-request}+json envelopes, id-correlated parts echoing their URLs, per-part conditional headers, outer-request auth/cookie propagation, synthetic parts for bundler-side failures, and binary parts riding base64 sorted last to preserve compression locality.
Zero runtime dependencies. ESM. Node ≥ 18 (the code floor: web-standard fetch/Request/Response globals), Bun, Deno; the core also runs wherever a fetch handler does.
Release history
- 1.0.0 The initial release: a fetch-handler core with
node:http/Express and Koa adapters, instrumentation and transform hooks, and streamed…bundle+jsonlbundles.
See the release notes for the long-form history.
License
BSD-3-Clause © Eugene Lazutkin
