madapes

@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
npm
2025-12-27 10:44:43 +00:00
3
latest
5.6 KiB
Assets (1)
Versions (3) View all
0.3.3 2025-12-27
0.3.2 2025-12-27
0.3.1 2025-12-27