Skip to content

Commit e5a717e

Browse files
committed
feat(devframe): add onServerError for post-bind server errors
startHttpAndWs's owned httpServer has no error listener once past the bind window, so a later runtime error (e.g. a transient EMFILE while accepting a connection) crashes the process. StartedServer exposes no handle a caller could attach their own listener to either, unlike the shared-server path where the caller already owns the object. Add onServerError instead of exposing the raw httpServer — handing out the raw object would let a caller call close() on it directly, bypassing the wrapper's own close() and leaking the WS transport. Attached only after the bind has already succeeded, so this never touches bind-time crash/hang semantics — only what happens afterward. No test: StartedServer deliberately doesn't expose the raw server, so there's no way to trigger a genuine post-bind error through the public API without a test-only seam. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
1 parent 5669caf commit e5a717e

1 file changed

Lines changed: 12 additions & 0 deletions

File tree

packages/devframe/src/node/server.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,14 @@ export interface StartHttpAndWsOptions {
110110
* own startup banner. Devframe does not print one itself.
111111
*/
112112
onReady?: (info: { origin: string, port: number, app: H3 }) => void | Promise<void>
113+
/**
114+
* Called for any error the owned HTTP server emits after it starts
115+
* listening — e.g. a transient `EMFILE` while accepting a connection.
116+
* Ignored when a `server` is supplied — the caller already owns that
117+
* object and can listen on it directly. Without this, a post-bind error
118+
* has no listener and crashes the process.
119+
*/
120+
onServerError?: (error: Error) => void
113121
}
114122

115123
export interface StartedServer {
@@ -258,6 +266,10 @@ export async function startHttpAndWs(options: StartHttpAndWsOptions): Promise<St
258266
await new Promise<void>((resolveListen) => {
259267
httpServer.listen(port, bindHost, () => resolveListen())
260268
})
269+
// Attached only now that the bind has already succeeded — this never
270+
// changes bind-time crash/hang semantics, only what happens afterward.
271+
if (options.onServerError)
272+
httpServer.on('error', options.onServerError)
261273
}
262274

263275
const address = httpServer.address()

0 commit comments

Comments
 (0)