pikku-websocket

$npx mdskill add pikkujs/pikku/pikku-websocket

Connect real-time features with structured WebSocket channels and auth.

  • Enables live updates, chat, and pub/sub in Pikku applications.
  • Integrates wireChannel, EventHub, and generated WebSocket clients.
  • Decides based on requests for real-time or WebSocket functionality.
  • Delivers type-safe routing and action-specific authentication.

SKILL.md

.github/skills/pikku-websocketView on GitHub ↗
---
name: pikku-websocket
description: 'Use when adding real-time features, WebSocket channels, live updates, chat, or pub/sub to a Pikku app. Covers wireChannel, action routing, auth, EventHub pub/sub, channel middleware, and generated WebSocket client.
TRIGGER when: code uses wireChannel, user asks about WebSocket, real-time, live updates, chat, pub/sub, or the generated WebSocket client.
DO NOT TRIGGER when: user asks about HTTP/REST (use pikku-http), SSE (use pikku-http with sse: true), or WebSocket deployment specifics (use pikku-deploy-uws).'
---

# Pikku WebSocket Wiring

Wire Pikku functions to WebSocket channels with structured message routing, auth per-action, pub/sub via EventHub, and auto-generated type-safe clients.

## Before You Start

```bash
pikku info functions --verbose   # See existing functions and their types
pikku info tags --verbose        # Understand project organization
```

Follow existing patterns. See `pikku-concepts` for the core mental model.

## API Reference

### `wireChannel(config)`

```typescript
import { wireChannel } from '@pikku/core/channel'

wireChannel({
  name: string,                      // Channel name (e.g. 'todos')
  onConnect: async () => {},         // Called when client connects
  onDisconnect: async () => {},      // Called when client disconnects
  onMessageWiring: {                 // Action → function mapping
    [actionName: string]: {
      func: PikkuFunc,
      auth?: boolean,                // Override channel-level auth
      permissions?: Record<string, PikkuPermission | PikkuPermission[]>,
    }
  },
  channelMiddleware?: PikkuChannelMiddleware[],
})
```

### `pikkuChannelMiddleware(fn)`

```typescript
import { pikkuChannelMiddleware } from '@pikku/core'

const middleware = pikkuChannelMiddleware(async (services, event, next) => {
  // Transform or filter events before/after
  await next(event) // Pass modified event, or next(null) to drop
})
```

### `addChannelMiddleware(domain, middlewares)`

```typescript
addChannelMiddleware('todos', [addTimestamp, filterSensitive])
```

## Usage Patterns

### Basic Channel

```typescript
wireChannel({
  name: 'todos',
  onConnect: async () => {},
  onDisconnect: async () => {},
  onMessageWiring: {
    create: { func: createTodo },
    list: { func: listTodos, auth: false },
  },
})
```

### Action Routing with Auth

Clients send `{ action: 'create', data: {...} }`. Pikku routes to the matching function.

```typescript
const authenticate = pikkuFunc({
  title: 'Authenticate',
  func: async ({ setSession }, { token }) => {
    const session = await verifyJWT(token)
    setSession(session)
    return { success: true }
  },
})

wireChannel({
  name: 'todos',
  onConnect: async () => {},
  onDisconnect: async () => {},
  onMessageWiring: {
    auth: { func: authenticate, auth: false }, // No session required
    subscribe: { func: subscribeTodos }, // Session required
    create: { func: createTodo },
  },
})
```

### Pub/Sub with EventHub

Use EventHub for real-time broadcasting across connections:

```typescript
wireChannel({
  name: 'todos',
  onConnect: async ({ eventHub, channel }) => {
    eventHub.subscribe('todos:updated', (data) => {
      channel.send(data)
    })
  },
  onDisconnect: async () => {},
  onMessageWiring: {
    create: {
      func: pikkuFunc({
        title: 'Create Todo',
        func: async ({ db, eventHub }, { text }) => {
          const todo = await db.createTodo({ text })
          eventHub.publish('todos:updated', {
            event: 'created',
            todo,
          })
          return { todo }
        },
      }),
    },
  },
})
```

### Channel Middleware

```typescript
const addTimestamp = pikkuChannelMiddleware(
  async ({ logger }, event, next) => {
    logger.info({ phase: 'before-send', event })
    await next({ ...event, sentAt: Date.now() })
  }
)

const filterSensitive = pikkuChannelMiddleware(
  async (_services, event, next) => {
    if (event.internal) return await next(null)  // Drop event
    await next(event)
  }
)

// Apply globally to a domain
addChannelMiddleware('todos', [addTimestamp, filterSensitive])

// Or inline on wiring
wireChannel({
  name: 'todos',
  channelMiddleware: [addTimestamp],
  onConnect: async () => {},
  onDisconnect: async () => {},
  onMessageWiring: { ... },
})
```

### Generated WebSocket Client

After `npx pikku prebuild`:

```typescript
import { PikkuWebSocket } from '.pikku/pikku-websocket.gen.js'

const pikku = new PikkuWebSocket(ws)
const todosRoute = pikku.getRoute('todos')

// Send action (type-safe)
const result = await todosRoute.send('create', { text: 'Buy milk' })

// Subscribe to events
todosRoute.subscribe('todos:updated', (data) => {
  console.log(data.event, data.todo)
})
```

## Complete Example

```typescript
// functions/chat.functions.ts
export const authenticate = pikkuFunc({
  title: 'Authenticate',
  func: async ({ jwt }, { token }, { setSession }) => {
    const payload = await jwt.verify(token)
    setSession({ userId: payload.userId })
    return { success: true }
  },
})

export const sendMessage = pikkuFunc({
  title: 'Send Message',
  func: async ({ db, eventHub }, { text }, { session }) => {
    const message = await db.createMessage({
      text,
      userId: session.userId,
    })
    eventHub.publish('chat:message', { message })
    return { message }
  },
})

export const listMessages = pikkuSessionlessFunc({
  title: 'List Messages',
  func: async ({ db }, { limit }) => {
    return { messages: await db.listMessages(limit) }
  },
})

// wirings/chat.channel.ts
wireChannel({
  name: 'chat',
  onConnect: async ({ eventHub, channel }) => {
    eventHub.subscribe('chat:message', (data) => {
      channel.send(data)
    })
  },
  onDisconnect: async () => {},
  onMessageWiring: {
    auth: { func: authenticate, auth: false },
    send: { func: sendMessage },
    history: { func: listMessages, auth: false },
  },
})
```

More from pikkujs/pikku

SkillDescription
pikku-addon'Use when creating or consuming reusable function packages (addons) in Pikku. Covers wireAddon, addon(), pikkuAddonServices, pikkuAddonWireServices, addon package structure, and cross-project function sharing.
pikku-ai-agent'Use when building AI agents, chatbots, or LLM-powered assistants with Pikku. Covers pikkuAIAgent, tool registration, memory, streaming, and agent invocation.
pikku-ai-vercel'Use when setting up AI agent execution with the Vercel AI SDK in a Pikku app. Covers VercelAIAgentRunner for streaming and non-streaming AI agent steps.
pikku-ai-voice'Use when adding voice input (speech-to-text) or voice output (text-to-speech) to AI agents in a Pikku app. Covers voiceInput/voiceOutput middleware hooks and STT/TTS service interfaces.
pikku-auth-js'Use when integrating Auth.js (NextAuth) with a Pikku app. Covers createAuthHandler, createAuthRoutes, and Auth.js configuration.
pikku-aws'Use when setting up AWS services (S3, SQS, Secrets Manager) in a Pikku app. Covers S3Content for file storage, SQSQueueService for queues, and AWSSecrets for secret management.
pikku-backblaze'Use when setting up Backblaze B2 file storage in a Pikku app. Covers B2Content for file uploads, downloads, and signed URLs.
pikku-cli'Use when building CLI commands with Pikku. Covers wireCLI, pikkuCLICommand, subcommands, options, parameters, custom renderers, and nested command groups.
pikku-concepts'Foundational guide to Pikku framework concepts. Use this skill when working with any Pikku codebase, starting a new Pikku project, or migrating a backend to Pikku. Covers the core mental model, function types, project structure, code generation, testing, and how Pikku maps to traditional backend patterns.
pikku-config'Use when managing secrets, environment variables, config, or OAuth2 credentials in a Pikku app. Covers wireSecret, wireVariable, wireOAuth2Credential, and typed config access.