Supersedes #317, which is the same bump without the runtime checking below.
The API we actually use is unchanged
Three entry points, all intact across the major:
Import
v2
serve from @hono/node-server
same (options, listeningListener) signature; fetch, hostname, port all still valid
RESPONSE_ALREADY_SENT from @hono/node-server/utils/response
still exported
getConnInfo from @hono/node-server/conninfo
still exported
The peer is hono: ^4 (we are on 4.13.7) and the engine >=20 (CI and the image are on 22).
What v2 actually changes
Two new defaults rather than removals:
overrideGlobalObjects (default true) — swaps the global Request/Response for a lighter implementation.
autoCleanupIncoming (default true) — destroys an incoming request the application never finished consuming. The README is explicit that this is the behaviour you want when accepting connections from arbitrary clients; leaving the default alone is the safe choice for us.
Which is why this was run, not reasoned about
Neither default is something 168 unit tests or a docker build would notice, and the risky surface is the SSE proxy at app.ts:742 — it writes to the raw ServerResponse and returns RESPONSE_ALREADY_SENT, exactly the mechanism a lightweight Response could break.
So I drove a real server against the mock backend on both versions with the same script:
Check
1.19.17
2.1.1
/api/health
200
200
/api/auth/login
200 + session
200 + session
/api/events (SSE)
200, text/event-stream, chunked, 0 bytes in 12s
identical
POST /api/jmap with a body
—
200, body reached upstream and answered
The SSE stream carries no bytes on either version because the mock emits no events on that endpoint — worth stating plainly, since a lone "0 bytes" reads like a failure. It is the same 0 bytes before and after, with the same headers and the same chunked encoding, which is the comparison that matters.
The POST confirms request bodies still proxy through with autoCleanupIncoming on.
Verification
npm run typecheck, npm test (1153 web, 168 server) and npm run build all pass. The diff is the version range and 10 lines of lockfile.
Supersedes #317, which is the same bump without the runtime checking below.
## The API we actually use is unchanged
Three entry points, all intact across the major:
| Import | v2 |
|---|---|
| `serve` from `@hono/node-server` | same `(options, listeningListener)` signature; `fetch`, `hostname`, `port` all still valid |
| `RESPONSE_ALREADY_SENT` from `@hono/node-server/utils/response` | still exported |
| `getConnInfo` from `@hono/node-server/conninfo` | still exported |
The peer is `hono: ^4` (we are on 4.13.7) and the engine `>=20` (CI and the image are on 22).
## What v2 actually changes
Two new defaults rather than removals:
- **`overrideGlobalObjects`** (default `true`) — swaps the global Request/Response for a lighter implementation.
- **`autoCleanupIncoming`** (default `true`) — destroys an incoming request the application never finished consuming. The README is explicit that this is the behaviour you want when accepting connections from arbitrary clients; leaving the default alone is the safe choice for us.
## Which is why this was run, not reasoned about
Neither default is something 168 unit tests or a `docker build` would notice, and the risky surface is the SSE proxy at `app.ts:742` — it writes to the raw `ServerResponse` and returns `RESPONSE_ALREADY_SENT`, exactly the mechanism a lightweight Response could break.
So I drove a real server against the mock backend on both versions with the same script:
| Check | 1.19.17 | 2.1.1 |
|---|---|---|
| `/api/health` | 200 | 200 |
| `/api/auth/login` | 200 + session | 200 + session |
| `/api/events` (SSE) | `200`, `text/event-stream`, `chunked`, 0 bytes in 12s | identical |
| `POST /api/jmap` with a body | — | 200, body reached upstream and answered |
The SSE stream carries no bytes on either version because the mock emits no events on that endpoint — worth stating plainly, since a lone "0 bytes" reads like a failure. It is the same 0 bytes before and after, with the same headers and the same chunked encoding, which is the comparison that matters.
The POST confirms request bodies still proxy through with `autoCleanupIncoming` on.
## Verification
`npm run typecheck`, `npm test` (1153 web, 168 server) and `npm run build` all pass. The diff is the version range and 10 lines of lockfile.
**Merged** 2026-09-10 as coffey-labs/ihasmail@7d9d5b005c86
<sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Supersedes #317, which is the same bump without the runtime checking below.
The API we actually use is unchanged
Three entry points, all intact across the major:
servefrom@hono/node-server(options, listeningListener)signature;fetch,hostname,portall still validRESPONSE_ALREADY_SENTfrom@hono/node-server/utils/responsegetConnInfofrom@hono/node-server/conninfoThe peer is
hono: ^4(we are on 4.13.7) and the engine>=20(CI and the image are on 22).What v2 actually changes
Two new defaults rather than removals:
overrideGlobalObjects(defaulttrue) — swaps the global Request/Response for a lighter implementation.autoCleanupIncoming(defaulttrue) — destroys an incoming request the application never finished consuming. The README is explicit that this is the behaviour you want when accepting connections from arbitrary clients; leaving the default alone is the safe choice for us.Which is why this was run, not reasoned about
Neither default is something 168 unit tests or a
docker buildwould notice, and the risky surface is the SSE proxy atapp.ts:742— it writes to the rawServerResponseand returnsRESPONSE_ALREADY_SENT, exactly the mechanism a lightweight Response could break.So I drove a real server against the mock backend on both versions with the same script:
/api/health/api/auth/login/api/events(SSE)200,text/event-stream,chunked, 0 bytes in 12sPOST /api/jmapwith a bodyThe SSE stream carries no bytes on either version because the mock emits no events on that endpoint — worth stating plainly, since a lone "0 bytes" reads like a failure. It is the same 0 bytes before and after, with the same headers and the same chunked encoding, which is the comparison that matters.
The POST confirms request bodies still proxy through with
autoCleanupIncomingon.Verification
npm run typecheck,npm test(1153 web, 168 server) andnpm run buildall pass. The diff is the version range and 10 lines of lockfile.Merged 2026-09-10 as coffey-labs/ihasmail@7d9d5b005c
Rebuilt from: git history, session transcript.