Skip to content
This repository was archived by the owner on Oct 22, 2025. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions examples/kitchen-sink/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Kitchen Sink Example for RivetKit

Example project demonstrating all RivetKit features with [RivetKit](https://rivetkit.org).

[Learn More →](https://github.com/rivet-dev/rivetkit)

[Discord](https://rivet.dev/discord) — [Documentation](https://rivetkit.org) — [Issues](https://github.com/rivet-dev/rivetkit/issues)

## Getting Started

### Prerequisites

- Node.js 18+ or Bun
- RivetKit development environment

### Installation

```sh
git clone https://github.com/rivet-dev/rivetkit
cd rivetkit/examples/kitchen-sink
npm install
```

### Development

```sh
npm run dev
```

This will start both the backend server (port 8080) and frontend development server (port 5173).

Open [http://localhost:5173](http://localhost:5173) in your browser.

## License

Apache 2.0
126 changes: 126 additions & 0 deletions examples/kitchen-sink/SPEC.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# Kitchen Sink Example Specification

## Overview
Comprehensive example demonstrating all RivetKit features with a simple React frontend.

## UI Structure

### Header (Always visible)

**Configuration Row 1:**
- **Transport**: WebSocket ↔ SSE toggle
- **Encoding**: JSON ↔ CBOR ↔ Bare dropdown
- **Connection Mode**: Handle ↔ Connection toggle

**Actor Management Row 2:**
- **Actor Name**: dropdown
- **Key**: input
- **Region**: input
- **Input JSON**: textarea
- **Buttons**: `create()` | `get()` | `getOrCreate()` | `getForId()` (when no actor connected)
- **OR Button**: `dispose()` (when actor connected)

**Status Row 3:**
- Connection status indicator with actor info (Name | Key | Actor ID | Status)

### Main Content (8 Tabs - disabled until actor connected)

#### 1. Actions
- Action name dropdown/input
- Arguments JSON textarea
- Call button
- Response display

#### 2. Events
- Event listener controls
- Live event feed with timestamps
- Event type filter

#### 3. Schedule
- `schedule.at()` with timestamp input
- `schedule.after()` with delay input
- Scheduled task history
- Custom alarm data input

#### 4. Sleep
- `sleep()` button
- Sleep timeout configuration
- Lifecycle event display (`onStart`, `onStop`)

#### 5. Connections (Only visible in Connection Mode)
- Connection state display
- Connection info (ID, transport, encoding)
- Connection-specific event history

#### 6. Raw HTTP
- HTTP method dropdown
- Path input
- Headers textarea
- Body textarea
- Send button & response display

#### 7. Raw WebSocket
- Message input (text/binary toggle)
- Send button
- Message history (sent/received with timestamps)

#### 8. Metadata
- Actor name, tags, region display

## User Flow
1. Configure transport/encoding/connection mode
2. Fill actor details (name, key, region, input)
3. Click create/get/getOrCreate/getForId
4. Status shows connection info, tabs become enabled
5. Use tabs to test features
6. Click dispose() to disconnect and return to step 1

## Actors

### 1. `demo` - Main comprehensive actor
- All action types (sync/async/promise)
- Events and state management
- Scheduling capabilities (`schedule.at()`, `schedule.after()`)
- Sleep functionality with configurable timeout
- Connection state support
- Lifecycle hooks (`onStart`, `onStop`, `onConnect`, `onDisconnect`)
- Metadata access

### 2. `http` - Raw HTTP handling
- `onFetch()` handler
- Multiple endpoint examples
- Various HTTP methods support

### 3. `websocket` - Raw WebSocket handling
- `onWebSocket()` handler
- Text and binary message support
- Connection lifecycle management

## Features Demonstrated

### Core Features
- **Actions**: sync, async, promise-based with various input types
- **Events**: broadcasting to connected clients
- **State Management**: actor state + per-connection state
- **Scheduling**: `schedule.at()`, `schedule.after()` with alarm handlers
- **Force Sleep**: `sleep()` method with configurable sleep timeout
- **Lifecycle Hooks**: `onStart`, `onStop`, `onConnect`, `onDisconnect`

### Configuration Options
- **Transport**: WebSocket vs Server-Sent Events
- **Encoding**: JSON, CBOR, Bare

### Raw Protocols
- **Raw HTTP**: Direct HTTP request handling
- **Raw WebSocket**: Direct WebSocket connection handling

### Connection Patterns
- **Handle Mode**: Fire-and-forget action calls
- **Connection Mode**: Persistent connection with real-time events

### Actor Management
- **Create**: `client.actor.create(key, opts)`
- **Get**: `client.actor.get(key, opts)`
- **Get or Create**: `client.actor.getOrCreate(key, opts)`
- **Get by ID**: `client.actor.getForId(actorId, opts)`
- **Dispose**: `client.dispose()` - disconnect all connections
13 changes: 13 additions & 0 deletions examples/kitchen-sink/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>RivetKit Kitchen Sink</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/frontend/main.tsx"></script>
</body>
</html>
29 changes: 29 additions & 0 deletions examples/kitchen-sink/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "example-kitchen-sink",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "concurrently \"npm run dev:backend\" \"npm run dev:frontend\"",
"dev:backend": "tsx --watch src/backend/server.ts",
"dev:frontend": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"check-types": "tsc --noEmit"
},
"dependencies": {
"@rivetkit/react": "workspace:*",
"rivetkit": "workspace:*",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.2.66",
"@types/react-dom": "^18.2.22",
"@vitejs/plugin-react": "^4.2.1",
"concurrently": "^8.2.2",
"tsx": "^4.7.1",
"typescript": "^5.2.2",
"vite": "^5.2.0"
}
}
155 changes: 155 additions & 0 deletions examples/kitchen-sink/src/backend/actors/demo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import { actor } from "rivetkit";
import { handleHttpRequest, httpActions } from "./http";
import { handleWebSocket, websocketActions } from "./websocket";

export const demo = actor({
createState: (_c, input) => ({
input,
count: 0,
lastMessage: "",
alarmHistory: [] as { id: string; time: number; data?: any }[],
startCount: 0,
stopCount: 0,
}),
connState: {
connectionTime: 0,
},
onStart: (c) => {
c.state.startCount += 1;
c.log.info({ msg: "demo actor started", startCount: c.state.startCount });
},
onStop: (c) => {
c.state.stopCount += 1;
c.log.info({ msg: "demo actor stopped", stopCount: c.state.stopCount });
},
onConnect: (c, conn) => {
conn.state.connectionTime = Date.now();
c.log.info({
msg: "client connected",
connectionTime: conn.state.connectionTime,
});
},
onDisconnect: (c) => {
c.log.info("client disconnected");
},
onFetch: handleHttpRequest,
onWebSocket: handleWebSocket,
actions: {
// Sync actions
increment: (c, amount: number = 1) => {
c.state.count += amount;
c.broadcast("countChanged", { count: c.state.count, amount });
return c.state.count;
},
getCount: (c) => {
return c.state.count;
},
setMessage: (c, message: string) => {
c.state.lastMessage = message;
c.broadcast("messageChanged", { message });
return message;
},

// Async actions
delayedIncrement: async (c, amount: number = 1, delayMs: number = 1000) => {
await new Promise((resolve) => setTimeout(resolve, delayMs));
c.state.count += amount;
c.broadcast("countChanged", { count: c.state.count, amount });
return c.state.count;
},

// Promise action
promiseAction: () => {
return Promise.resolve({
timestamp: Date.now(),
message: "promise resolved",
});
},

// State management
getState: (c) => {
return {
actorState: c.state,
connectionState: c.conn.state,
};
},

// Scheduling
scheduleAlarmAt: (c, timestamp: number, data?: any) => {
const id = `alarm-${Date.now()}`;
c.schedule.at(timestamp, "onAlarm", { id, data });
return { id, scheduledFor: timestamp };
},
scheduleAlarmAfter: (c, delayMs: number, data?: any) => {
const id = `alarm-${Date.now()}`;
c.schedule.after(delayMs, "onAlarm", { id, data });
return { id, scheduledFor: Date.now() + delayMs };
},
onAlarm: (c, payload: { id: string; data?: any }) => {
const alarmEntry = { ...payload, time: Date.now() };
c.state.alarmHistory.push(alarmEntry);
c.broadcast("alarmTriggered", alarmEntry);
c.log.info({ msg: "alarm triggered", ...alarmEntry });
},
getAlarmHistory: (c) => {
return c.state.alarmHistory;
},
clearAlarmHistory: (c) => {
c.state.alarmHistory = [];
return true;
},

// Sleep
triggerSleep: (c) => {
c.sleep();
return "sleep triggered";
},

// Lifecycle info
getLifecycleInfo: (c) => {
return {
startCount: c.state.startCount,
stopCount: c.state.stopCount,
};
},

// Metadata
getMetadata: (c) => {
return {
name: c.name,
};
},
getInput: (c) => {
return c.state.input;
},
getActorState: (c) => {
return c.state;
},
getConnState: (c) => {
return c.conn.state;
},

// Events
broadcastCustomEvent: (c, eventName: string, data: any) => {
c.broadcast(eventName, data);
return { eventName, data, timestamp: Date.now() };
},

// Connections
listConnections: (c) => {
return Array.from(c.conns.values()).map((conn) => ({
id: conn.id,
connectedAt: conn.state.connectionTime,
}));
},

// HTTP actions
...httpActions,

// WebSocket actions
...websocketActions,
},
options: {
sleepTimeout: 2000,
},
});
Loading
Loading