bun-ws-router
Version:
A simple and efficient WebSocket router for Bun with Zod/Valibot message validation.
82 lines • 2.44 kB
JavaScript
/* SPDX-FileCopyrightText: 2025-present Kriasoft */
/* SPDX-License-Identifier: MIT */
import * as v from "valibot";
/**
* Base schema for message metadata.
* Provides common fields that are available on all messages.
* Can be extended for specific message types.
*/
export const MessageMetadataSchema = v.object({
clientId: v.optional(v.string()),
timestamp: v.optional(v.pipe(v.number(), v.integer(), v.minValue(1))),
correlationId: v.optional(v.string()),
});
/**
* Base message schema that all specific message types extend.
* Defines the minimum structure required for routing.
*/
export const MessageSchema = v.object({
type: v.string(),
meta: MessageMetadataSchema,
});
/**
* Standard error codes for WebSocket communication.
* Used in ErrorMessage payloads for consistent error handling.
*/
export const ErrorCode = v.picklist([
"INVALID_MESSAGE_FORMAT",
"VALIDATION_FAILED",
"UNSUPPORTED_MESSAGE_TYPE",
"AUTHENTICATION_FAILED",
"AUTHORIZATION_FAILED",
"RESOURCE_NOT_FOUND",
"RATE_LIMIT_EXCEEDED",
"INTERNAL_SERVER_ERROR",
]);
export function messageSchema(messageType, payload, meta) {
const metaSchema = meta
? v.object({ ...MessageMetadataSchema.entries, ...meta.entries })
: MessageMetadataSchema;
const baseSchema = {
type: v.literal(messageType),
meta: metaSchema,
};
if (payload === undefined) {
return v.object(baseSchema);
}
return v.object({
...baseSchema,
payload,
});
}
/**
* Standard error message schema for consistent error responses.
*/
export const ErrorMessage = messageSchema("ERROR", v.object({
code: ErrorCode,
message: v.optional(v.string()),
context: v.optional(v.record(v.string(), v.any())),
}));
/**
* Creates a validated WebSocket message from a schema.
*
* @example
* ```typescript
* const EchoSchema = messageSchema("ECHO", v.object({ text: v.string() }));
* const message = createMessage(EchoSchema, { text: "Hello" });
*
* if (message.success) {
* ws.send(JSON.stringify(message.output));
* }
* ```
*/
export function createMessage(schema, payload, meta) {
const messageData = {
type: schema.entries.type.literal,
payload,
meta: meta || {},
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return v.safeParse(schema, messageData);
}
//# sourceMappingURL=schema.js.map