Hono Integration

WSX integrates seamlessly with Hono, a lightweight web framework designed for edge computing environments like Cloudflare Workers, Deno, and Bun.

Installation

npm install @wsx-sh/core @wsx-sh/hono

Basic Setup

Quick Start

import { createHonoWSXServer } from "@wsx-sh/hono";
import { Hono } from "hono";

// Create WSX server with Hono adapter
const wsx = createHonoWSXServer({
  path: "/ws",
});

const app = wsx.getApp();

// Add WSX handlers
wsx.on("hello", async (request, connection) => {
  return {
    id: request.id,
    target: request.target,
    html: `<div>Hello from Hono at ${new Date().toISOString()}!</div>`,
  };
});

// Regular Hono routes
app.get("/", (c) => {
  return c.html(`
    <!DOCTYPE html>
    <html>
    <head>
      <title>WSX with Hono</title>
      <script src="/wsx.js"></script>
    </head>
    <body>
      <div wx-config='{"url": "ws://localhost:8787/ws"}'>
        <div id="content">Click the button below</div>
        <button wx-send="hello" wx-target="#content">Say Hello</button>
      </div>
    </body>
    </html>
  `);
});

// Serve static files
app.get("/wsx.js", (c) => {
  return c.text(getWSXClientScript());
});

export default app;

Edge Runtime Deployment

Cloudflare Workers

// wrangler.toml
[env.production]
name = "wsx-hono-app"
main = "src/index.js"
compatibility_date = "2024-01-01"

[env.production.durable_objects]
bindings = [
  {name = "WSX_CONNECTIONS", class_name = "WSXConnections"}
]

// src/index.js
import { createHonoWSXServer } from "@wsx-sh/hono";

const wsx = createHonoWSXServer({
  path: '/ws',
  durableObjects: {
    namespace: 'WSX_CONNECTIONS'
  }
});

const app = wsx.getApp();

// Chat application
wsx.on("send-message", async (request, connection) => {
  const { message, room } = request.data;
  const user = connection.sessionData?.user || { name: 'Anonymous' };

  const messageHtml = `
    <div class="message">
      <strong>${user.name}:</strong> ${message}
      <span class="timestamp">${new Date().toLocaleTimeString()}</span>
    </div>
  `;

  // Broadcast to all connections in room
  const roomConnections = wsx.getConnections()
    .filter(conn => conn.sessionData?.room === room);

  roomConnections.forEach(conn => {
    wsx.sendToConnection(conn.id, "#messages", messageHtml, "beforeend");
  });

  return {
    id: request.id,
    target: "#message-input",
    html: `<input type="text" placeholder="Type a message..." />`,
    swap: "outerHTML"
  };
});

// Join room
wsx.on("join-room", async (request, connection) => {
  const { room, username } = request.data;

  connection.sessionData = {
    ...connection.sessionData,
    room,
    user: { name: username }
  };

  return {
    id: request.id,
    target: request.target,
    html: `<div>Joined room: ${room}</div>`
  };
});

export default app;

Deno Deploy

// deno.json
{
  "tasks": {
    "dev": "deno run --allow-net --allow-read --watch src/main.ts",
    "start": "deno run --allow-net --allow-read src/main.ts"
  },
  "imports": {
    "hono": "https://deno.land/x/hono@v4.0.0/mod.ts",
    "@wsx-sh/hono": "https://deno.land/x/wsx@v1.0.0/hono.ts"
  }
}

// src/main.ts
import { createHonoWSXServer } from "@wsx-sh/hono";
import { serve } from "https://deno.land/std@0.200.0/http/server.ts";

const wsx = createHonoWSXServer({
  path: '/ws'
});

const app = wsx.getApp();

// Real-time dashboard
wsx.on("get-stats", async (request, connection) => {
  const stats = await getSystemStats();

  const statsHtml = `
    <div class="stats-grid">
      <div class="stat">
        <h3>Memory Usage</h3>
        <div class="progress">
          <div class="bar" style="width: ${stats.memory.percent}%"></div>
        </div>
        <span>${stats.memory.used}MB / ${stats.memory.total}MB</span>
      </div>
      <div class="stat">
        <h3>CPU Usage</h3>
        <div class="progress">
          <div class="bar" style="width: ${stats.cpu.percent}%"></div>
        </div>
        <span>${stats.cpu.percent}%</span>
      </div>
    </div>
  `;

  return {
    id: request.id,
    target: request.target,
    html: statsHtml
  };
});

// Auto-refresh stats every 5 seconds
setInterval(async () => {
  const connections = wsx.getConnections();
  const stats = await getSystemStats();

  connections.forEach(conn => {
    wsx.sendToConnection(conn.id, "#live-stats", generateStatsHTML(stats));
  });
}, 5000);

async function getSystemStats() {
  const memInfo = Deno.memoryUsage();
  return {
    memory: {
      used: Math.round(memInfo.heapUsed / 1024 / 1024),
      total: Math.round(memInfo.heapTotal / 1024 / 1024),
      percent: Math.round((memInfo.heapUsed / memInfo.heapTotal) * 100)
    },
    cpu: {
      percent: Math.round(Math.random() * 100) // Mock CPU usage
    }
  };
}

serve(app.fetch, { port: 8000 });

Bun Runtime

// package.json
{
  "name": "wsx-hono-bun",
  "type": "module",
  "scripts": {
    "dev": "bun run --watch src/index.js",
    "start": "bun run src/index.js"
  },
  "dependencies": {
    "hono": "^4.0.0",
    "@wsx-sh/hono": "^1.0.0"
  }
}

// src/index.js
import { createHonoWSXServer } from "@wsx-sh/hono";

const wsx = createHonoWSXServer({
  path: '/ws'
});

const app = wsx.getApp();

// File upload with real-time progress
wsx.on("upload-file", async (request, connection) => {
  const { file, filename } = request.data;

  try {
    // Write file using Bun's optimized file operations
    const path = `./uploads/${filename}`;
    await Bun.write(path, file);

    // Broadcast upload completion
    wsx.broadcast("#upload-status", `
      <div class="upload-success">
        File "${filename}" uploaded successfully!
      </div>
    `);

    return {
      id: request.id,
      target: request.target,
      html: `<div>File uploaded: ${filename}</div>`
    };
  } catch (error) {
    return {
      id: request.id,
      target: request.target,
      html: `<div class="error">Upload failed: ${error.message}</div>`
    };
  }
});

// Real-time file processing
wsx.on("process-file", async (request, connection) => {
  const { filename } = request.data;

  // Send processing updates
  wsx.sendToConnection(connection.id, "#status",
    `<div>Processing ${filename}...</div>`);

  // Process file (example: image optimization)
  const result = await processFile(filename);

  return {
    id: request.id,
    target: request.target,
    html: `<div>File processed: ${result.outputPath}</div>`
  };
});

export default {
  port: 3000,
  fetch: app.fetch
};

Middleware Integration

Authentication Middleware

import { jwt } from "hono/jwt";

const app = wsx.getApp();

// JWT authentication middleware
app.use(
  "/api/*",
  jwt({
    secret: process.env.JWT_SECRET,
  })
);

// Protected API routes
app.get("/api/profile", async (c) => {
  const user = c.get("jwtPayload");
  return c.json({ user });
});

// WSX authentication
wsx.onConnection = async (connection) => {
  const token = extractTokenFromConnection(connection);

  if (token) {
    try {
      const payload = await verifyJWT(token);
      connection.sessionData = { user: payload };
    } catch (error) {
      connection.close(1008, "Invalid token");
    }
  }
};

function extractTokenFromConnection(connection) {
  // Extract from query parameter or header
  const url = new URL(connection.request.url);
  return url.searchParams.get("token");
}

CORS Middleware

import { cors } from "hono/cors";

app.use(
  "/api/*",
  cors({
    origin: ["https://your-domain.com", "https://www.your-domain.com"],
    allowMethods: ["GET", "POST", "PUT", "DELETE"],
    allowHeaders: ["Content-Type", "Authorization"],
  })
);

Rate Limiting

import { rateLimiter } from "hono/rate-limiter";

// Rate limiting for API routes
app.use(
  "/api/*",
  rateLimiter({
    windowMs: 15 * 60 * 1000, // 15 minutes
    max: 100, // limit each IP to 100 requests per windowMs
    message: "Too many requests from this IP",
  })
);

// WSX-specific rate limiting
const wsxRateLimiter = new Map();

wsx.on("rate-limited-action", async (request, connection) => {
  const ip = connection.ip || "unknown";
  const key = `${ip}:${request.handler}`;
  const now = Date.now();

  const limit = wsxRateLimiter.get(key);

  if (limit && now - limit.lastReset < 60000) {
    // 1 minute window
    if (limit.count >= 10) {
      return {
        id: request.id,
        target: request.target,
        html: `<div class="error">Rate limit exceeded</div>`,
      };
    }
    limit.count++;
  } else {
    wsxRateLimiter.set(key, { count: 1, lastReset: now });
  }

  return {
    id: request.id,
    target: request.target,
    html: `<div>Action completed</div>`,
  };
});

Database Integration

Cloudflare D1

// wrangler.toml
[env.production.d1_databases];
binding = "DB";
database_name = "wsx-app";
database_id = "your-database-id";

// Handler with D1 database
wsx.on("create-post", async (request, connection, env) => {
  const { title, content } = request.data;
  const userId = connection.sessionData?.user?.id;

  if (!userId) {
    return {
      id: request.id,
      target: request.target,
      html: `<div class="error">Authentication required</div>`,
    };
  }

  try {
    const result = await env.DB.prepare(
      "INSERT INTO posts (title, content, user_id, created_at) VALUES (?, ?, ?, ?)"
    )
      .bind(title, content, userId, new Date().toISOString())
      .run();

    return {
      id: request.id,
      target: request.target,
      html: `<div>Post created with ID: ${result.meta.last_row_id}</div>`,
    };
  } catch (error) {
    return {
      id: request.id,
      target: request.target,
      html: `<div class="error">Failed to create post</div>`,
    };
  }
});

Deno KV

// Using Deno KV for persistence
const kv = await Deno.openKv();

wsx.on("save-data", async (request, connection) => {
  const { key, value } = request.data;
  const userId = connection.sessionData?.user?.id;

  if (!userId) {
    return {
      id: request.id,
      target: request.target,
      html: `<div class="error">Authentication required</div>`,
    };
  }

  try {
    await kv.set([`user:${userId}`, key], value);

    return {
      id: request.id,
      target: request.target,
      html: `<div>Data saved successfully</div>`,
    };
  } catch (error) {
    return {
      id: request.id,
      target: request.target,
      html: `<div class="error">Failed to save data</div>`,
    };
  }
});

wsx.on("load-data", async (request, connection) => {
  const { key } = request.data;
  const userId = connection.sessionData?.user?.id;

  if (!userId) {
    return {
      id: request.id,
      target: request.target,
      html: `<div class="error">Authentication required</div>`,
    };
  }

  try {
    const result = await kv.get([`user:${userId}`, key]);

    return {
      id: request.id,
      target: request.target,
      html: `<div>Data: ${JSON.stringify(result.value)}</div>`,
    };
  } catch (error) {
    return {
      id: request.id,
      target: request.target,
      html: `<div class="error">Failed to load data</div>`,
    };
  }
});

Real-Time Applications

Live Collaboration

// Collaborative document editing
const documents = new Map();

wsx.on("join-document", async (request, connection) => {
  const { documentId } = request.data;
  const user = connection.sessionData?.user;

  if (!user) {
    return {
      id: request.id,
      target: request.target,
      html: `<div class="error">Authentication required</div>`,
    };
  }

  // Track document participants
  if (!documents.has(documentId)) {
    documents.set(documentId, new Set());
  }

  documents.get(documentId).add(connection.id);
  connection.sessionData.documentId = documentId;

  // Notify other participants
  const participants = Array.from(documents.get(documentId))
    .map((id) => wsx.getConnection(id))
    .filter((conn) => conn && conn.id !== connection.id);

  participants.forEach((conn) => {
    wsx.sendToConnection(
      conn.id,
      "#participants",
      `<div>${user.name} joined the document</div>`,
      "beforeend"
    );
  });

  return {
    id: request.id,
    target: request.target,
    html: `<div>Joined document: ${documentId}</div>`,
  };
});

wsx.on("document-change", async (request, connection) => {
  const { change, position } = request.data;
  const documentId = connection.sessionData?.documentId;
  const user = connection.sessionData?.user;

  if (!documentId || !user) {
    return {
      id: request.id,
      target: request.target,
      html: `<div class="error">Invalid session</div>`,
    };
  }

  // Broadcast change to other participants
  const participants = Array.from(documents.get(documentId) || [])
    .map((id) => wsx.getConnection(id))
    .filter((conn) => conn && conn.id !== connection.id);

  const changeHtml = `
    <div class="document-change" data-position="${position}">
      <span class="user">${user.name}</span> made a change
    </div>
  `;

  participants.forEach((conn) => {
    wsx.sendToConnection(conn.id, "#document-changes", changeHtml, "beforeend");
  });

  return {
    id: request.id,
    target: request.target,
    html: `<div>Change synchronized</div>`,
  };
});

Live Dashboard

// Real-time analytics dashboard
let dashboardInterval;

wsx.on("start-dashboard", async (request, connection) => {
  const user = connection.sessionData?.user;

  if (!user?.isAdmin) {
    return {
      id: request.id,
      target: request.target,
      html: `<div class="error">Admin access required</div>`,
    };
  }

  // Start sending real-time updates
  dashboardInterval = setInterval(async () => {
    const stats = await getAnalytics();

    const dashboardHtml = `
      <div class="dashboard-stats">
        <div class="metric">
          <h3>Active Users</h3>
          <span class="value">${stats.activeUsers}</span>
        </div>
        <div class="metric">
          <h3>Requests/sec</h3>
          <span class="value">${stats.requestsPerSecond}</span>
        </div>
        <div class="metric">
          <h3>Error Rate</h3>
          <span class="value">${stats.errorRate}%</span>
        </div>
      </div>
    `;

    wsx.sendToConnection(connection.id, "#dashboard", dashboardHtml);
  }, 1000);

  return {
    id: request.id,
    target: request.target,
    html: `<div>Dashboard started</div>`,
  };
});

wsx.onDisconnection = (connection) => {
  if (dashboardInterval) {
    clearInterval(dashboardInterval);
  }
};

Error Handling

Global Error Handler

app.onError = (err, c) => {
  console.error("Hono error:", err);

  return c.json(
    {
      error: "Internal server error",
      message:
        process.env.NODE_ENV === "development"
          ? err.message
          : "Something went wrong",
    },
    500
  );
};

// WSX error handler
wsx.onError = (error, connection) => {
  console.error("WSX error:", error);

  if (connection) {
    connection.send(
      JSON.stringify({
        error: "An unexpected error occurred",
        id: "error",
      })
    );
  }
};

Graceful Shutdown

// Handle shutdown gracefully
process.on("SIGTERM", () => {
  console.log("Received SIGTERM, shutting down gracefully");

  // Close all WSX connections
  const connections = wsx.getConnections();
  connections.forEach((conn) => {
    conn.send(
      JSON.stringify({
        type: "shutdown",
        message: "Server is shutting down",
      })
    );
    conn.close();
  });

  process.exit(0);
});

Performance Optimization

Edge Caching

// Cache responses at the edge
const cache = new Map();

wsx.on("cached-data", async (request, connection) => {
  const cacheKey = `${request.handler}:${JSON.stringify(request.data)}`;

  // Check cache first
  if (cache.has(cacheKey)) {
    const cached = cache.get(cacheKey);
    return {
      ...cached,
      id: request.id,
    };
  }

  // Generate response
  const data = await getExpensiveData(request.data);
  const response = {
    target: request.target,
    html: `<div>Data: ${JSON.stringify(data)}</div>`,
  };

  // Cache for 5 minutes
  cache.set(cacheKey, response);
  setTimeout(() => cache.delete(cacheKey), 5 * 60 * 1000);

  return {
    id: request.id,
    ...response,
  };
});

Connection Pooling

// Efficient connection management
class ConnectionPool {
  constructor(maxConnections = 1000) {
    this.connections = new Map();
    this.maxConnections = maxConnections;
  }

  addConnection(connection) {
    if (this.connections.size >= this.maxConnections) {
      // Close oldest connection
      const oldest = this.connections.keys().next().value;
      const oldConnection = this.connections.get(oldest);
      oldConnection.close();
      this.connections.delete(oldest);
    }

    this.connections.set(connection.id, {
      connection,
      lastActivity: Date.now(),
    });
  }

  updateActivity(connectionId) {
    const conn = this.connections.get(connectionId);
    if (conn) {
      conn.lastActivity = Date.now();
    }
  }

  cleanupInactive() {
    const now = Date.now();
    const timeout = 30 * 60 * 1000; // 30 minutes

    for (const [id, conn] of this.connections) {
      if (now - conn.lastActivity > timeout) {
        conn.connection.close();
        this.connections.delete(id);
      }
    }
  }
}

const connectionPool = new ConnectionPool();

wsx.onConnection = (connection) => {
  connectionPool.addConnection(connection);
};

// Cleanup inactive connections every 5 minutes
setInterval(() => {
  connectionPool.cleanupInactive();
}, 5 * 60 * 1000);

Best Practices

  1. Use Edge Computing: Leverage Hono’s edge runtime capabilities
  2. Optimize for Cold Starts: Minimize initialization time
  3. Handle Errors Gracefully: Implement proper error handling
  4. Cache Strategically: Use appropriate caching strategies
  5. Monitor Performance: Track metrics and optimize bottlenecks
  6. Secure Connections: Implement authentication and rate limiting
  7. Scale Horizontally: Design for distributed deployment

Next Steps