@aurora/runtime-ws (0.3.3)
Published 2025-12-27 10:44:43 +00:00 by vlad
Installation
@aurora:registry=npm install @aurora/runtime-ws@0.3.3"@aurora/runtime-ws": "0.3.3"About this package
@aurora/runtime-ws
WebSocket support for real-time subscriptions in Aurora applications.
Overview
Aurora's WebSocket package provides:
- WebSocket server with Bun native support
- Subscription management with multi-tenant isolation
- Event fan-out from event log to subscribers
- Message protocol with subscribe/unsubscribe/update/heartbeat
- Integration with Aurora projections
Installation
bun add @aurora/runtime-ws
Quick Start
import { WebSocketServer } from "@aurora/runtime-ws";
const ws = new WebSocketServer({
port: 3001,
path: "/ws",
});
ws.onConnection((socket, req) => {
console.log("Client connected");
socket.onMessage((message) => {
console.log("Received:", message);
});
socket.onClose(() => {
console.log("Client disconnected");
});
});
await ws.start();
WebSocket Server
Creating Server
import { WebSocketServer } from "@aurora/runtime-ws";
const server = new WebSocketServer({
port: 3001,
path: "/ws",
heartbeatInterval: 30000, // 30 seconds
});
Connection Handling
server.onConnection((socket, req) => {
const tenantId = req.headers.get("X-Tenant-ID");
if (!tenantId) {
socket.close(4001, "Missing tenant ID");
return;
}
// Attach tenant to socket
(socket as any).tenantId = tenantId;
console.log(`Client connected for tenant ${tenantId}`);
});
Message Handling
socket.onMessage((message) => {
switch (message.type) {
case "subscribe":
handleSubscription(socket, message);
break;
case "unsubscribe":
handleUnsubscription(socket, message);
break;
default:
socket.send({ type: "error", code: "UNKNOWN_MESSAGE" });
}
});
Subscription Manager
Creating Manager
import { SubscriptionManager } from "@aurora/runtime-ws";
const manager = new SubscriptionManager();
// Add subscription
manager.addSubscription(socket, {
tenantId: "tenant-1",
subscriptionId: "sub-123",
topic: "orders",
filter: { status: "pending" },
});
// Remove subscription
manager.removeSubscription(socket, "sub-123");
// Get subscriptions
const subscriptions = manager.getSubscriptions(socket);
Broadcasting
// Broadcast to all subscribers
manager.broadcast("orders", {
type: "update",
data: { orderId: "123", status: "pending" },
});
// Broadcast to specific tenant
manager.broadcastToTenant("tenant-1", "orders", {
type: "update",
data: { /* ... */ },
});
// Broadcast with filter
manager.broadcastWithFilter("orders", {
type: "update",
data: { /* ... */ },
}, (sub) => sub.filter.status === "pending");
Event Fan-out
Creating Fan-out
import { EventFanout } from "@aurora/runtime-ws";
import { EventLog } from "@aurora/runtime-eventlog";
const eventLog = new EventLog({ natsUrl: "nats://localhost:4222" });
const manager = new SubscriptionManager();
const fanout = new EventFanout(eventLog, manager);
// Subscribe to events
fanout.subscribe({
subjects: ["orders.*.OrderCreated", "orders.*.OrderUpdated"],
handler: (event) => {
// Parse event and broadcast to subscribers
const subscription = {
tenantId: event.tenantId,
subscriptionId: event.orderId,
topic: "orders",
};
manager.broadcastToTenant(event.tenantId, "orders", {
type: "update",
data: event.payload,
});
},
});
await fanout.start();
Message Protocol
Subscribe
{
"type": "subscribe",
"subscriptionId": "sub-123",
"topic": "orders",
"filter": {
"status": "pending"
}
}
Unsubscribe
{
"type": "unsubscribe",
"subscriptionId": "sub-123"
}
Update
{
"type": "update",
"subscriptionId": "sub-123",
"topic": "orders",
"data": {
"orderId": "123",
"status": "pending",
"amount": 100
}
}
Heartbeat
{
"type": "heartbeat",
"timestamp": "2024-12-26T10:30:00Z"
}
Client Example
Browser WebSocket
const ws = new WebSocket("ws://localhost:3001/ws", {
headers: {
"X-Tenant-ID": "tenant-1",
"Authorization": "Bearer token",
},
});
// Subscribe
ws.send(JSON.stringify({
type: "subscribe",
subscriptionId: "orders-sub",
topic: "orders",
}));
// Receive updates
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.type === "update" && message.topic === "orders") {
console.log("Order updated:", message.data);
}
};
// Unsubscribe
ws.send(JSON.stringify({
type: "unsubscribe",
subscriptionId: "orders-sub",
}));
React Hook
import { useState, useEffect } from "react";
function useSubscription<T>(topic: string, filter?: any) {
const [data, setData] = useState<T | null>(null);
useEffect(() => {
const ws = new WebSocket("ws://localhost:3001/ws");
const subscriptionId = Math.random().toString(36);
ws.onopen = () => {
ws.send(JSON.stringify({
type: "subscribe",
subscriptionId,
topic,
filter,
}));
};
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.type === "update" && message.topic === topic) {
setData(message.data);
}
};
return () => {
ws.send(JSON.stringify({
type: "unsubscribe",
subscriptionId,
}));
ws.close();
};
}, [topic]);
return data;
}
function OrderUpdates() {
const order = useSubscription("orders", { status: "pending" });
if (!order) return <div>Waiting for updates...</div>;
return (
<div>
<p>Order: {order.orderId}</p>
<p>Status: {order.status}</p>
</div>
);
}
Integration with HTTP Server
import { Server } from "@aurora/runtime-http";
import { WebSocketServer } from "@aurora/runtime-ws";
const httpServer = new Server({ port: 3000 });
const wsServer = new WebSocketServer({ port: 3001 });
// Share auth between HTTP and WS
httpServer.use(authMiddleware);
wsServer.onConnection(async (socket, req) => {
const token = req.headers.get("Authorization");
if (!token) {
socket.close(4001, "Unauthorized");
return;
}
const user = await verifyToken(token);
if (!user) {
socket.close(4001, "Invalid token");
return;
}
(socket as any).user = user;
});
await httpServer.start();
await wsServer.start();
License
MIT
Dependencies
Dependencies
| ID | Version |
|---|---|
| @aurora/runtime-core | 0.3.3 |
| @aurora/runtime-effect | 0.3.3 |
| @aurora/runtime-eventlog | 0.3.3 |
Details
2025-12-27 10:44:43 +00:00
Assets (1)
Versions (3)
View all
npm
3
latest
5.6 KiB
runtime-ws-0.3.3.tgz
5.6 KiB