HermesHandler is a lightweight, framework-agnostic message router for browser extensions and event-driven systems. It provides structured request dispatching with a strict response envelope { ok:true, result?, info? } | { ok:false, error, info? }, built-in timeout handling, cooperative cancellation, and safe normalization.
Designed for reliability and clarity, HermesHandler is especially well-suited for LLM-driven agents, modular browser architectures, automation layers, and distributed runtime systems.
- 🔁 Deterministic message routing via
type - 📦 Strict response envelope: { ok:true, result?, info? } | { ok:false, error, info? }
- ⏱ Built-in timeout handling
- 🛑 Cooperative cancellation via
AbortSignal - 🧊 Immutable (shallow-frozen) responses
- 🧠 LLM-friendly deterministic contract
- 🧩 Framework-agnostic (no runtime dependencies)
- 📘 Type-safe via generated
.d.ts
npm install hermes-handlerimport { HermesHandler } from "hermes-handler";
const handlers = {
ping: () => ({ ok: true, result: "pong" }),
greet: (msg) => {
return { ok: true, result: `Hello ${msg.payload.name}` };
}
};
const hermes = new HermesHandler(handlers);
const res = await hermes.dispatch({ type: "ping" });
if (res.ok) {
console.log(res.result); // "pong"
}Attach HermesHandler to a runtime listener:
browser.runtime.onMessage.addListener(
hermes.getListener()
);HermesHandler supports both:
- Promise-returning listeners (MV3 / Firefox / polyfill)
- Callback-style
sendResponse + return true
All responses follow a strict envelope.
Hermes never mutates result; any unexpected or conflicting fields are preserved under info.
If a handler returns inconsistent envelopes (e.g. { ok:false, error, result }), Hermes warns and preserves extras under info.
If a handler returns an envelope that already includes info, Hermes preserves it under info.handlerInfo when additional fields must also be recorded.
{ ok: true, result: any, info?: any }{ ok: false, error: string, info?: any }Primitive return values are automatically normalized:
return "hello";Becomes:
{ ok: true, result: "hello" }Malformed envelopes are coerced into valid error responses.
Handlers can be time-limited:
const hermes = new HermesHandler(handlers, {
timeoutMs: 7000
});If exceeded, HermesHandler returns:
{ ok: false, error: "Handler <type> timed out (7000 ms)" }Each handler receives an AbortSignal:
async function longTask(msg, ctx) {
if (ctx.signal?.aborted) {
return { ok: false, error: "Cancelled" };
}
ctx.signal?.addEventListener("abort", () => {
console.log("Cancelled externally");
});
}HermesHandler aborts the signal once a request lifecycle completes.
initialHandlers
Record<string, HermesHandlerFn>
options
timeoutMs?: numberonUnknown?: (msg, ctx) => HermesResponseonError?: (err, msg, ctx) => HermesResponselogger?: HermesLogger | null
Register or overwrite a handler.
Register multiple handlers at once.
Remove a handler.
Check if a handler exists.
Returns a runtime-compatible message listener.
Dispatch a message manually (useful for testing or non-extension environments).
List registered message types (registration order).
HermesHandler emits warnings and errors through a configurable logger.
By default, it uses the global console. You can disable logging entirely or provide a custom logger implementation.
const hermes = new HermesHandler(handlers, {
logger: null
});const hermes = new HermesHandler(handlers, {
logger: {
warn: (...args) => myLogger.warn(...args),
error: (...args) => myLogger.error(...args)
}
});HermesLogger shape
interface HermesLogger {
debug?(message?: any, ...optionalParams: any[]): void;
info?(message?: any, ...optionalParams: any[]): void;
warn?(message?: any, ...optionalParams: any[]): void;
error?(message?: any, ...optionalParams: any[]): void;
}If logger is null, HermesHandler will not emit any console output.
HermesHandler enforces a predictable and deterministic runtime contract. By standardizing request/response handling and isolating message dispatch logic, it simplifies reasoning about complex systems—particularly those involving automation, background scripts, or LLM-driven tool execution.
The core remains intentionally minimal, dependency-free, and portable.
MIT