bun-ws-router
Version:
Lightweight client/server WebSocket router for Bun with type-safe Zod/Valibot validation.
93 lines • 3.42 kB
JavaScript
// SPDX-FileCopyrightText: 2025-present Kriasoft
// SPDX-License-Identifier: MIT
export class HandlerRegistry {
// Multi-handler storage: type → array of handlers
handlers = new Map();
// Schema storage for validation: type → schema
schemas = new Map();
/**
* Registers a handler for a message type.
* Returns unsubscribe function that removes only this handler.
*/
register(schema, handler, extractType) {
const type = extractType(schema);
// Store schema for validation (first registration wins)
if (!this.schemas.has(type)) {
this.schemas.set(type, schema);
}
// Add handler to array
const handlers = this.handlers.get(type) ?? [];
handlers.push(handler);
this.handlers.set(type, handlers);
// Return unsubscribe function
return () => {
const current = this.handlers.get(type);
if (current) {
const index = current.indexOf(handler);
if (index !== -1) {
current.splice(index, 1);
}
}
};
}
/**
* Dispatches message to registered handlers.
* Executes in registration order with error isolation.
* Returns true if handlers were found, false otherwise.
*/
dispatch(msg) {
const handlers = this.handlers.get(msg.type);
if (!handlers || handlers.length === 0) {
return false; // No handlers found
}
// Stable iteration: snapshot handlers array before dispatch
// (allows unsubscribe during dispatch without affecting current iteration)
const snapshot = [...handlers];
for (const handler of snapshot) {
try {
handler(msg);
}
catch (error) {
console.error(`[Client] Handler error for ${msg.type}:`, error);
// Continue dispatching remaining handlers (error isolation)
}
}
return true; // Handlers executed
}
/**
* Gets schema for validation by message type.
*/
getSchema(type) {
return this.schemas.get(type);
}
/**
* Validates message against registered schema.
* Returns { success: true, data } if validation succeeds.
* Returns { success: false, reason: "no-schema" } if no schema registered.
* Returns { success: false, reason: "validation-failed" } if validation fails.
*/
validate(msg, safeParse) {
if (!msg || typeof msg !== "object" || !("type" in msg)) {
return { success: false, reason: "validation-failed", error: null };
}
const type = msg.type;
if (typeof type !== "string") {
return { success: false, reason: "validation-failed", error: null };
}
const schema = this.getSchema(type);
if (!schema) {
return { success: false, reason: "no-schema" }; // No schema registered
}
const result = safeParse(schema, msg);
if (!result.success) {
console.warn(`[Client] Validation failed for ${type}:`, result.error);
return {
success: false,
reason: "validation-failed",
error: result.error,
};
}
return { success: true, data: result.data };
}
}
//# sourceMappingURL=handlers.js.map