or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

advanced-features.mdcore-framework.mderror-handling.mdevent-handling.mdhandlers-middleware.mdindex.mdrequest-processing.mdresponse-handling.mdruntime-adapters.mdweb-utilities.md
tile.json

advanced-features.mddocs/

Advanced Features

Advanced H3 features including proxy functionality, Server-Sent Events (SSE), WebSocket support, static file serving, and caching utilities.

Capabilities

Proxy Support

Forward requests to other servers with comprehensive proxy options.

/**
 * Proxy request to target server
 * @param event - H3 event
 * @param target - Target URL or server
 * @param opts - Proxy configuration options
 * @returns Promise resolving to response body
 */
function proxy(
  event: H3Event,
  target: string,
  opts?: ProxyOptions
): Promise<BodyInit | undefined | null>;

/**
 * Proxy request with full control (alias for proxy)
 * @param event - H3 event
 * @param target - Target URL or server
 * @param opts - Proxy configuration options
 * @returns Promise resolving to response body
 */
function proxyRequest(
  event: H3Event,
  target: string,
  opts?: ProxyOptions
): Promise<BodyInit | undefined | null>;

/**
 * Get headers for proxy request
 * @param event - H3 event
 * @param options - Header processing options
 * @returns Headers object for proxy request
 */
function getProxyRequestHeaders(
  event: H3Event,
  options?: {
    ignoredHeaders?: string[];
    allowedHeaders?: string[];
  }
): Headers;

/**
 * Fetch with H3 event context
 * @param event - H3 event
 * @param req - Request, URL, or string
 * @param init - Request initialization options
 * @returns Promise resolving to Response
 */
function fetchWithEvent(
  event: H3Event,
  req: ServerRequest | URL | string,
  init?: RequestInit
): Promise<Response>;

Usage Examples:

import { proxy, getProxyRequestHeaders, fetchWithEvent } from "h3";

// Basic proxy
const proxyHandler = defineHandler(async (event) => {
  return await proxy(event, "https://api.external-service.com");
});

// Proxy with options
const advancedProxyHandler = defineHandler(async (event) => {
  return await proxy(event, "https://backend.example.com", {
    headers: {
      "X-Forwarded-For": getRequestIP(event),
      "X-Original-Host": getRequestHost(event)
    },
    onProxyReq: (proxyReq, req) => {
      console.log(`Proxying ${req.method} ${req.url}`);
    },
    onProxyRes: (proxyRes, req, res) => {
      console.log(`Proxy response: ${proxyRes.status}`);
    }
  });
});

// Custom proxy headers
const customHeadersProxyHandler = defineHandler(async (event) => {
  const proxyHeaders = getProxyRequestHeaders(event, {
    ignoredHeaders: ["authorization", "cookie"],
    allowedHeaders: ["content-type", "accept", "user-agent"]
  });
  
  return await proxy(event, "https://public-api.com", {
    headers: {
      ...Object.fromEntries(proxyHeaders.entries()),
      "X-API-Key": process.env.EXTERNAL_API_KEY
    }
  });
});

// Fetch with event context
const fetchHandler = defineHandler(async (event) => {
  const response = await fetchWithEvent(
    event,
    "https://api.example.com/data",
    {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ query: getQuery(event) })
    }
  );
  
  const data = await response.json();
  return { data };
});

// Load balancing proxy
const loadBalancerHandler = defineHandler(async (event) => {
  const servers = [
    "https://server1.example.com",
    "https://server2.example.com",
    "https://server3.example.com"
  ];
  
  const serverIndex = Math.floor(Math.random() * servers.length);
  const targetServer = servers[serverIndex];
  
  return await proxy(event, targetServer, {
    headers: {
      "X-Load-Balancer": "h3-proxy",
      "X-Server-Index": serverIndex.toString()
    }
  });
});

Server-Sent Events (SSE)

Real-time server-to-client communication using Server-Sent Events.

/**
 * Create Server-Sent Events stream
 * @param event - H3 event
 * @param options - SSE configuration options
 * @returns Event stream helper object
 */
function createEventStream(
  event: H3Event,
  options?: EventStreamOptions
): EventStreamHelper;

interface EventStreamHelper {
  /**
   * Send a data event
   * @param data - Data to send (string or object)
   */
  push(data: string | any): void;
  
  /**
   * Send a named event with data
   * @param event - Event name
   * @param data - Event data
   */
  pushEvent(event: string, data: string | any): void;
  
  /**
   * Send an event with full control
   * @param message - Complete event message
   */
  pushMessage(message: EventStreamMessage): void;
  
  /**
   * Close the event stream
   */
  close(): void;
  
  /**
   * Send keep-alive ping
   */
  ping(): void;
}

Usage Examples:

import { createEventStream } from "h3";

// Basic SSE stream
const sseHandler = defineHandler((event) => {
  const eventStream = createEventStream(event);
  
  // Send initial data
  eventStream.push("Connected to stream");
  
  // Send periodic updates
  const interval = setInterval(() => {
    eventStream.push({
      timestamp: Date.now(),
      message: "Periodic update"
    });
  }, 1000);
  
  // Cleanup on client disconnect
  event.waitUntil(new Promise((resolve) => {
    const checkConnection = setInterval(() => {
      if (event.aborted) {
        clearInterval(interval);
        clearInterval(checkConnection);
        eventStream.close();
        resolve(undefined);
      }
    }, 5000);
  }));
  
  return eventStream;
});

// Named events SSE
const namedEventsHandler = defineHandler((event) => {
  const eventStream = createEventStream(event, {
    retry: 3000, // Client retry interval
    headers: {
      "Cache-Control": "no-cache",
      "Connection": "keep-alive"
    }
  });
  
  // Send different event types
  eventStream.pushEvent("user-connected", { userId: "123" });
  eventStream.pushEvent("notification", { 
    type: "info", 
    message: "Welcome to the stream" 
  });
  
  // Send with custom message format
  eventStream.pushMessage({
    event: "custom-event",
    data: JSON.stringify({ custom: "data" }),
    id: "msg-001",
    retry: 5000
  });
  
  return eventStream;
});

// Real-time data stream
const realtimeDataHandler = defineHandler(async (event) => {
  const eventStream = createEventStream(event);
  
  // Subscribe to real-time data source
  const unsubscribe = dataService.subscribe((data) => {
    eventStream.push(data);
  });
  
  // Handle client disconnect
  event.waitUntil(new Promise((resolve) => {
    const checkConnection = () => {
      if (event.aborted) {
        unsubscribe();
        eventStream.close();
        resolve(undefined);
      } else {
        setTimeout(checkConnection, 1000);
      }
    };
    checkConnection();
  }));
  
  return eventStream;
});

// Chat room SSE
const chatStreamHandler = defineHandler((event) => {
  const { roomId } = getRouterParams(event);
  const eventStream = createEventStream(event);
  
  // Join chat room
  chatService.joinRoom(roomId, (message) => {
    eventStream.pushEvent("message", {
      id: message.id,
      user: message.user,
      text: message.text,
      timestamp: message.timestamp
    });
  });
  
  // Send room history
  chatService.getHistory(roomId).then(messages => {
    eventStream.pushEvent("history", messages);
  });
  
  // Handle user leaving
  event.waitUntil(new Promise((resolve) => {
    const cleanup = () => {
      chatService.leaveRoom(roomId);
      eventStream.close();
      resolve(undefined);
    };
    
    if (event.aborted) {
      cleanup();
    } else {
      setTimeout(() => {
        if (event.aborted) cleanup();
      }, 1000);
    }
  }));
  
  return eventStream;
});

WebSocket Support

WebSocket handler definitions for real-time bidirectional communication.

/**
 * Define WebSocket event hooks
 * @param hooks - WebSocket lifecycle hooks
 * @returns WebSocket hooks object
 */
function defineWebSocket(hooks: Partial<WSHooks>): Partial<WSHooks>;

/**
 * Define WebSocket handler
 * @param hooks - WebSocket lifecycle hooks
 * @returns Event handler for WebSocket upgrade
 */
function defineWebSocketHandler(hooks: Partial<WSHooks>): EventHandler;

interface WSHooks {
  /**
   * Called when WebSocket connection is opened
   * @param peer - WebSocket peer connection
   */
  open(peer: WSPeer): void | Promise<void>;
  
  /**
   * Called when message is received
   * @param peer - WebSocket peer connection
   * @param message - Received message
   */
  message(peer: WSPeer, message: WSMessage): void | Promise<void>;
  
  /**
   * Called when WebSocket connection is closed
   * @param peer - WebSocket peer connection
   * @param details - Close details
   */
  close(peer: WSPeer, details: WSCloseDetails): void | Promise<void>;
  
  /**
   * Called when WebSocket error occurs
   * @param peer - WebSocket peer connection
   * @param error - Error object
   */
  error(peer: WSPeer, error: Error): void | Promise<void>;
  
  /**
   * Called during WebSocket upgrade
   * @param event - H3 event
   */
  upgrade(event: H3Event): void | Promise<void>;
}

Usage Examples:

import { defineWebSocketHandler, defineWebSocket } from "h3";

// Basic WebSocket handler
const wsHandler = defineWebSocketHandler({
  open(peer) {
    console.log("WebSocket connection opened");
    peer.send("Welcome to WebSocket server");
  },
  
  message(peer, message) {
    console.log("Received:", message.text());
    
    // Echo message back
    peer.send(`Echo: ${message.text()}`);
  },
  
  close(peer, details) {
    console.log("WebSocket connection closed:", details.code);
  },
  
  error(peer, error) {
    console.error("WebSocket error:", error);
  }
});

// Chat WebSocket
const chatWsHandler = defineWebSocketHandler({
  async open(peer) {
    // Store connection in chat room
    const roomId = peer.url.searchParams.get("room") || "general";
    await chatService.addPeer(roomId, peer);
    
    // Send room info
    peer.send(JSON.stringify({
      type: "room-info",
      roomId,
      userCount: await chatService.getUserCount(roomId)
    }));
  },
  
  async message(peer, message) {
    const data = JSON.parse(message.text());
    const roomId = peer.url.searchParams.get("room") || "general";
    
    switch (data.type) {
      case "chat-message":
        // Broadcast to all peers in room
        await chatService.broadcast(roomId, {
          type: "message",
          user: data.user,
          text: data.text,
          timestamp: Date.now()
        });
        break;
        
      case "typing":
        // Broadcast typing indicator
        await chatService.broadcastToOthers(roomId, peer, {
          type: "user-typing",
          user: data.user
        });
        break;
    }
  },
  
  async close(peer) {
    const roomId = peer.url.searchParams.get("room") || "general";
    await chatService.removePeer(roomId, peer);
  }
});

// Game WebSocket
const gameWsHandler = defineWebSocketHandler({
  async upgrade(event) {
    // Validate game token
    const token = getQuery(event).token;
    const gameSession = await validateGameToken(token);
    
    if (!gameSession) {
      throw HTTPError.status(401, "Invalid game token");
    }
    
    // Store game info in context
    event.context.gameId = gameSession.gameId;
    event.context.playerId = gameSession.playerId;
  },
  
  async open(peer) {
    const { gameId, playerId } = peer.context;
    
    // Add player to game
    await gameService.addPlayer(gameId, playerId, peer);
    
    // Send game state
    const gameState = await gameService.getState(gameId);
    peer.send(JSON.stringify({
      type: "game-state",
      state: gameState
    }));
  },
  
  async message(peer, message) {
    const data = JSON.parse(message.text());
    const { gameId, playerId } = peer.context;
    
    // Process game action
    const result = await gameService.processAction(gameId, playerId, data);
    
    if (result.valid) {
      // Broadcast update to all players
      await gameService.broadcastUpdate(gameId, result.update);
    } else {
      // Send error to player
      peer.send(JSON.stringify({
        type: "error",
        message: result.error
      }));
    }
  }
});

// WebSocket with authentication
const authWsHandler = defineWebSocketHandler({
  async upgrade(event) {
    const token = getHeader(event, "authorization")?.replace("Bearer ", "");
    
    if (!token) {
      throw HTTPError.status(401, "Authentication required");
    }
    
    const user = await verifyJWTToken(token);
    if (!user) {
      throw HTTPError.status(401, "Invalid token");
    }
    
    event.context.user = user;
  },
  
  open(peer) {
    const user = peer.context.user;
    console.log(`User ${user.id} connected via WebSocket`);
    
    peer.send(JSON.stringify({
      type: "authenticated",
      user: { id: user.id, name: user.name }
    }));
  },
  
  async message(peer, message) {
    const user = peer.context.user;
    const data = JSON.parse(message.text());
    
    // Process authenticated user action
    await processUserAction(user, data);
  }
});

Static File Serving

Serve static files with caching, compression, and security features.

/**
 * Serve static files from filesystem
 * @param event - H3 event
 * @param options - Static serving options
 * @returns Promise resolving to Response or undefined
 */
function serveStatic(
  event: H3Event,
  options: ServeStaticOptions
): Promise<Response | undefined>;

Usage Examples:

import { serveStatic } from "h3";

// Basic static serving
const staticHandler = defineHandler(async (event) => {
  return await serveStatic(event, {
    root: "./public",
    index: ["index.html"],
    dotFiles: "deny"
  });
});

// Advanced static serving
const advancedStaticHandler = defineHandler(async (event) => {
  return await serveStatic(event, {
    root: "./assets",
    index: ["index.html", "index.htm"],
    dotFiles: "deny",
    etag: true,
    lastModified: true,
    maxAge: 86400, // 24 hours
    immutable: event.url.pathname.includes("/static/"),
    fallthrough: false,
    redirect: true,
    extensions: ["html", "htm"]
  });
});

// SPA serving with fallback
const spaHandler = defineHandler(async (event) => {
  const response = await serveStatic(event, {
    root: "./dist",
    index: ["index.html"],
    fallthrough: true
  });
  
  // Fallback to index.html for SPA routes
  if (!response && !event.url.pathname.startsWith("/api/")) {
    return await serveStatic(event, {
      root: "./dist",
      index: ["index.html"]
    });
  }
  
  return response;
});

// Multiple static directories
const multiStaticHandler = defineHandler(async (event) => {
  // Try serving from uploads first
  if (event.url.pathname.startsWith("/uploads/")) {
    const response = await serveStatic(event, {
      root: "./uploads",
      maxAge: 3600
    });
    if (response) return response;
  }
  
  // Fall back to public directory
  return await serveStatic(event, {
    root: "./public",
    maxAge: 86400
  });
});

Type Definitions

Proxy Types

/**
 * Proxy configuration options
 */
interface ProxyOptions {
  /**
   * Additional headers to send
   */
  headers?: Record<string, string>;
  
  /**
   * Transform request before sending
   */
  onProxyReq?: (proxyReq: Request, req: Request) => void;
  
  /**
   * Transform response before returning
   */
  onProxyRes?: (proxyRes: Response, req: Request, res: Response) => void;
  
  /**
   * Handle proxy errors
   */
  onError?: (error: Error, req: Request, res: Response) => void;
  
  /**
   * Custom agent for requests
   */
  agent?: any;
  
  /**
   * Timeout in milliseconds
   */
  timeout?: number;
  
  /**
   * Follow redirects
   */
  followRedirects?: boolean;
}

SSE Types

/**
 * Server-Sent Events options
 */
interface EventStreamOptions {
  /**
   * Client retry interval in milliseconds
   */
  retry?: number;
  
  /**
   * Additional response headers
   */
  headers?: Record<string, string>;
  
  /**
   * Auto-close timeout
   */
  autoClose?: number;
}

/**
 * SSE message format
 */
interface EventStreamMessage {
  /**
   * Event name
   */
  event?: string;
  
  /**
   * Event data
   */
  data: string;
  
  /**
   * Event ID
   */
  id?: string;
  
  /**
   * Retry interval
   */
  retry?: number;
}

WebSocket Types

/**
 * WebSocket peer connection
 */
interface WSPeer {
  /**
   * Send message to peer
   */
  send(message: string | ArrayBuffer): void;
  
  /**
   * Close connection
   */
  close(code?: number, reason?: string): void;
  
  /**
   * Connection URL
   */
  url: URL;
  
  /**
   * Connection context
   */
  context: any;
  
  /**
   * Ready state
   */
  readyState: number;
}

/**
 * WebSocket message
 */
interface WSMessage {
  /**
   * Get message as text
   */
  text(): string;
  
  /**
   * Get message as ArrayBuffer
   */
  arrayBuffer(): ArrayBuffer;
  
  /**
   * Message type
   */
  type: "text" | "binary";
}

/**
 * WebSocket close details
 */
interface WSCloseDetails {
  /**
   * Close code
   */
  code: number;
  
  /**
   * Close reason
   */
  reason: string;
}

Static Serving Types

/**
 * Static serving options
 */
interface ServeStaticOptions {
  /**
   * Root directory to serve from
   */
  root: string;
  
  /**
   * Index file names
   */
  index?: string[];
  
  /**
   * How to handle dotfiles
   */
  dotFiles?: "allow" | "deny" | "ignore";
  
  /**
   * Enable ETag generation
   */
  etag?: boolean;
  
  /**
   * Enable Last-Modified header
   */
  lastModified?: boolean;
  
  /**
   * Cache max age in seconds
   */
  maxAge?: number;
  
  /**
   * Mark as immutable
   */
  immutable?: boolean;
  
  /**
   * Allow fallthrough for missing files
   */
  fallthrough?: boolean;
  
  /**
   * Redirect directories without trailing slash
   */
  redirect?: boolean;
  
  /**
   * File extensions to try
   */
  extensions?: string[];
}

/**
 * Static asset metadata
 */
interface StaticAssetMeta {
  /**
   * File path
   */
  path: string;
  
  /**
   * File size
   */
  size: number;
  
  /**
   * MIME type
   */
  type: string;
  
  /**
   * Last modified date
   */
  mtime: Date;
  
  /**
   * ETag value
   */
  etag?: string;
}