Skip to content

risingwavelabs/wavelet

Repository files navigation

Wavelet

Slack CI npm License SKILL.md

The reactive backend for agents and apps.

Your app shouldn't poll for answers. Answers should flow to your app.

Wavelet lets you define a computation in SQL and subscribe to its result. When the underlying data changes, every connected app and AI agent receives the updated result automatically. No API to build, no cache to manage, no WebSocket to wire up.

// Define what to compute
queries: {
  revenue: {
    query: sql`SELECT tenant_id, SUM(amount) AS total FROM orders GROUP BY tenant_id`,
    filterBy: 'tenant_id',
  }
}

// Subscribe from your app
const { data } = useWavelet('revenue')
// data updates automatically. Each tenant sees only their own numbers.

Built on RisingWave. By the RisingWave team.

Agent Onboarding

npx skills add risingwavelabs/skills --skill wavelet

Or read SKILL.md directly.

Use Cases

Real-time SaaS Dashboards

Your customers log in and see their own metrics updating live -- revenue, active users, API usage. One query definition, thousands of tenants, each isolated by JWT. Replaces a polling endpoint + cache + per-tenant auth check.

Usage Metering and Billing

Stream API calls, aggregate tokens and cost per customer, push to both customer-facing dashboards and rate limiters. Same source of truth, no sync issues.

Proactive Agents

AI agents subscribe to computed queries via MCP and act autonomously when conditions change. An agent watches sla_violations -- rows appear when an order exceeds its SLA, disappear when resolved. The agent escalates, notifies, or triggers a remediation. No polling, no cron -- the agent reacts to computed state, not raw events.

Quick Start

Install RisingWave and Wavelet:

curl -L https://risingwave.com/sh | sh   # install RisingWave
npm install @risingwave/wavelet           # install Wavelet

wavelet dev auto-starts RisingWave if the binary or Docker is available.

1. Define your config

// wavelet.config.ts
import { defineConfig, sql } from '@risingwave/wavelet'

export default defineConfig({
  database: 'postgres://root@localhost:4566/dev',

  events: {
    game_events: {
      columns: {
        player_id: 'string',
        score: 'int',
        event_type: 'string',
      }
    }
  },

  queries: {
    leaderboard: sql`
      SELECT player_id, SUM(score) AS total_score, COUNT(*) AS games_played
      FROM game_events
      GROUP BY player_id
      ORDER BY total_score DESC
      LIMIT 100
    `,
  },
})

2. Start dev server

npx wavelet dev

3. Try the example app

npm run build
npx vite --open /examples/sdk-leaderboard/

See examples/sdk-leaderboard for a working browser demo, or examples/react-leaderboard for a React version.

4. Subscribe from your own app

npx wavelet generate   # generates .wavelet/client.ts with full types
import { TypedWaveletClient } from './.wavelet/client'

const wavelet = new TypedWaveletClient({ url: 'http://localhost:8080' })

// read current state
const rows = await wavelet.queries.leaderboard.get()

// subscribe to live updates
wavelet.queries.leaderboard.subscribe({
  onData: (diff) => {
    console.log(diff.inserted, diff.updated, diff.deleted)
  }
})

// write events
await wavelet.events.game_events.emit({
  player_id: 'alice',
  score: 42,
  event_type: 'win',
})

Agent Integration (MCP)

AI agents query and write events as tool calls.

{
  "mcpServers": {
    "wavelet": {
      "command": "npx",
      "args": ["@risingwave/wavelet-mcp"],
      "env": {
        "WAVELET_DATABASE_URL": "postgres://root@localhost:4566/dev"
      }
    }
  }
}
Tool Description
list_queries List all queries (materialized views) with schemas
query Query a materialized view with optional filters
list_events List all event tables
emit_event Write an event
emit_batch Write a batch of events
run_sql Execute a read-only SQL query

Architecture

App / Agent  <-  WebSocket  <-  Wavelet Server  <-  SQL cursor  <-  RisingWave
                                      |                                |
                                JWT filtering                   Incremental
                                + fan-out                       computation

Write path. POST /v1/events/{name} inserts directly into RisingWave. No queue, no buffer. 200 means the row is persisted. RisingWave recomputes affected materialized views on its next barrier cycle (~1s by default), and Wavelet pushes the diff to subscribers. End-to-end latency from write to client update is typically 1-2 seconds.

Stateless server. Wavelet holds no persistent state. Cursor positions are in memory. On restart, cursors recover from RisingWave's subscription retention window (default 24h). During recovery, clients may receive duplicate diffs -- applications should handle updates idempotently (e.g. key by primary key, not append).

Single cursor per query. One subscription cursor feeds all connected clients. 1 client or 10,000 -- same RisingWave load.

Config-driven DDL. wavelet.config.ts is the source of truth. wavelet dev and wavelet push diff config against RisingWave and apply minimal changes (create/drop tables, materialized views, subscriptions).

JWT-scoped delivery. Queries with filterBy match the column value against a JWT claim. Filtering is enforced server-side -- clients cannot override it. Queries without filterBy broadcast all rows to all clients. For multi-tenant applications, omitting filterBy on a tenant-scoped query is a data leak -- Wavelet does not enforce this automatically.

Failure modes. If RisingWave goes down, cursor fetch returns an error and Wavelet retries after 1 second. Clients stay connected but receive no diffs until RisingWave recovers. If a WebSocket disconnects, the SDK reconnects with exponential backoff (1s to 30s) and resumes from the last cursor position. Each query has its own cursor and connection -- a slow query does not block other queries.

CLI

wavelet init       # Create wavelet.config.ts
wavelet dev        # Sync config + start dev server
wavelet push       # Sync config to RisingWave (no server)
wavelet generate   # Generate typed client at .wavelet/client.ts
wavelet status     # Show current config summary

All commands are idempotent. Supports --json for structured output.

HTTP API

GET  /v1/health                    -> { status: "ok" }
GET  /v1/queries                   -> list all queries
GET  /v1/queries/{name}            -> current rows
GET  /v1/queries/{name}?key=value  -> filtered rows
GET  /v1/events                    -> list all events
POST /v1/events/{name}             -> write single event
POST /v1/events/{name}/batch       -> write batch of events
WS   /subscribe/{name}             -> real-time diffs

License

Apache 2.0. See LICENSE.

About

The reactive backend for agents and apps.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors