@skweb/realtime
v0.2.0
Published
Core declarative realtime layer: types, message router, local + Redis broadcasters. Shared by @skweb/ws, @skweb/socketio, and @skweb/framework.
Maintainers
Readme
@skweb/realtime
Core declarative realtime layer: types, message router, and local + Redis broadcasters.
Shared by @skweb/ws, @skweb/socketio, and @skweb/framework. Provides the same declarative message-handling ergonomics as @skweb/express / @skweb/fastify (controllers + middleware + ajv), but for realtime sockets.
What it provides
- Types:
InboundMessage,RealtimeSocket,RealtimeApp,MessageContext,Broadcaster,MessageHandler,MessageMiddleware,ConnectionHook— the runtime-agnostic contract. loadMessageHandlers(dir): declarative handler loading from a directory, mirroringloadControllers()from@skweb/express.dispatchMessage(opts): ajv schema validation + middleware chain + ack callback, mirroringuseController().LocalBroadcaster: single-node broadcaster with in-memory user-socket mapping. No Redis required.
Usage with @skweb/ws (standalone, no framework)
import { createWsApp } from '@skweb/ws'
import { loadMessageHandlers, dispatchMessage, LocalBroadcaster } from '@skweb/realtime'
import Ajv from 'ajv'
const app = createWsApp()
const ajv = new Ajv()
const broadcaster = new LocalBroadcaster({ app })
const logger = console
// Load handlers from a directory (like loadControllers)
const handlers = await loadMessageHandlers('./src/handlers')
app.onConnection((socket) => {
// Authenticate, set userId, register with broadcaster
broadcaster.registerSocket(socket.id, socket.data.get('userId'))
})
app.onDisconnection((socket) => {
broadcaster.unregisterSocket(socket.id, socket.data.get('userId'))
})
app.onMessage((socket, msg) => {
// Dispatch with ajv + middleware chain + ack
await dispatchMessage({ socket, msg, handlers, ajv, broadcaster, logger })
})
app.startServe(3001)MessageHandler contract
// src/handlers/chat.ts
import type { MessageHandler } from '@skweb/realtime'
export default {
type: 'chat.message',
schema: {
type: 'object',
properties: { text: { type: 'string' } },
required: ['text']
},
middlewares: [
async (ctx, next) => {
// auth check, rate limit, etc.
await next()
}
],
async handle(ctx) {
// ctx.payload is validated by ajv
// ctx.socket.send() to reply
// ctx.broadcast.broadcast() to fan out
// ctx.ack() for ack callback
ctx.broadcast.broadcast('chat.message', ctx.payload)
}
} satisfies MessageHandlerMulti-node broadcasting
For multi-node deployments (multiple processes/servers), use RedisBroadcaster (also from @skweb/realtime, re-exported by @skweb/ws, @skweb/socketio, and @skweb/framework) instead of LocalBroadcaster. RedisBroadcaster publishes to a Redis pub/sub channel for cross-node delivery:
import { RedisBroadcaster } from '@skweb/realtime'
// @skweb/redis instances satisfy the RedisLike contract
// ({ publish(channel, msg), subCh(channel, cb) }).
const broadcaster = new RedisBroadcaster({
app,
redis: redisInstance, // optional; without it, falls back to local delivery
channel: 'skweb:realtime',
logger
})
broadcaster.start() // subscribe to the redis channelLicense
MIT
