Event System
Overview
Server extends EventDispatcher<ServerEventMap>, so event names and payload
types stay aligned:
import type { ServerEventMap } from "@hornjs/fest";
The current lifecycle events are:
servecloseerror
Lifecycle Event Types
ServerServeEvent
Dispatched after the runtime adapter reports that the server is listening.
ServerCloseEvent
Dispatched after shutdown completes and all waitUntil() tasks have settled.
ServerErrorEvent
Dispatched when asynchronous runtime adapter initialization fails.
The event exposes the original error through event.error.
Custom Events
ServerEventMap is built from the built-in lifecycle events plus
ServerEventMapCustom, so you can extend it with module augmentation.
import { Server } from "@hornjs/fest";
declare module "@hornjs/fest" {
interface ServerEventMapCustom {
reload: CustomEvent<{ full: boolean }>;
}
}
const server = new Server({
routes: {
"/": () => new Response("ok"),
},
fetch: () => new Response("Not Found", { status: 404 }),
});
server.addEventListener("reload", (event) => {
console.log(event.detail.full);
});
server.dispatchEvent(
new CustomEvent("reload", {
detail: { full: true },
}),
);
This works because ServerEventMap is defined as the built-in lifecycle map
intersected with the augmentable ServerEventMapCustom interface.
Listening for Events
Use standard DOM-style event listeners:
import { Server, ServerCloseEvent, ServerErrorEvent, ServerServeEvent } from "@hornjs/fest";
const server = new Server({
fetch() {
return new Response("hello");
},
});
server.addEventListener("serve", (event: ServerServeEvent) => {
console.log("ready", server.url);
});
server.addEventListener("error", (event: ServerErrorEvent) => {
console.error("failed", event.error);
});
server.addEventListener("close", (_event: ServerCloseEvent) => {
console.log("closed");
});
await server.ready();
Listening Model
Server integrates @hornjs/evt through the EventDispatcher base class, so
it uses the familiar DOM-style listener model.
Listening with addEventListener()
Register a listener for one lifecycle event.
typemust be a key fromServerEventMaplistenerreceives the correctly typed event objectonceandsignalbehave like the standard DOM API- listener identity follows the DOM model:
listener + capture
server.addEventListener(
"serve",
() => {
console.log("server is ready");
},
{ once: true },
);
Removing listeners
Remove a previously registered listener.
function onClose() {
console.log("closed");
}
server.addEventListener("close", onClose);
server.removeEventListener("close", onClose);
Dispatch semantics
Dispatch an event instance to all listeners registered for event.type.
In fest, lifecycle events are dispatched internally by Server, but the
underlying semantics come from @hornjs/evt:
- dispatch runs at-target only
- there is no bubbling tree
event.target,event.currentTarget, andevent.eventPhaseare normalized to match DOM-style dispatch behavior
server.dispatchEvent(new ServerCloseEvent());
In normal application code, you typically listen for lifecycle events rather than dispatching them yourself.
Event Timing
serve
Triggered from server.serve() after the adapter resolves the final listener
URL.
close
Triggered from server.close() after:
- the runtime adapter has closed
- pending
waitUntil()work has completed
error
Triggered only for non-abort failures during deferred adapter initialization.
If startup fails after serve() has already begun, you should still handle the
rejected promise from ready() or serve().
Programmatic Coordination
Events are useful when you want to react to lifecycle changes without tightly
coupling everything to await server.ready():
const server = new Server({
manual: true,
routes: {
"/": () => new Response("ok"),
},
fetch: () => new Response("Not Found", { status: 404 }),
});
server.addEventListener("serve", () => {
console.log("server is live");
});
await server.serve();
For one-off startup flows, await server.ready() is usually simpler. Events are
more useful when integrating logging, health reporting, or host-level process
coordination.
Behavior Notes
The event behavior inherited from @hornjs/evt is intentionally predictable:
- event names are typed string keys from
ServerEventMap - listener exceptions do not change the current dispatch loop by default
ServerErrorEventis part offestlifecycle semantics, notevt's optional cancelable dispatcher"error"event feature