Real-time computed results for app developers. Built on RisingWave.
Monorepo using npm workspaces (packages/*, examples/*). Node >= 20.
| Package | Published name | Purpose |
|---|---|---|
packages/config |
@risingwave/wavelet |
defineConfig, sql tag, shared types |
packages/server |
@risingwave/wavelet-server |
WebSocket fanout, subscription cursor polling, JWT auth, HTTP API |
packages/sdk |
@risingwave/wavelet-sdk |
TypeScript client + React hooks (@wavelet/sdk/react) |
packages/cli |
@risingwave/wavelet-cli |
CLI binary (wavelet push, wavelet dev, codegen) |
packages/mcp |
@risingwave/wavelet-mcp |
MCP server - exposes queries and events as AI agent tools |
Dependency graph: config is the leaf. server and sdk depend on config. cli depends on server and config. mcp depends on config and pg.
npm install # install all deps
npm run build # build all packages
npm run typecheck # type-check all packages
npm run lint # eslint across packages/
# Single package
npx tsc --build packages/config/tsconfig.json
# Dev server (tsx watch)
npx tsx packages/server/src/index.ts
# or
npm run dev # runs server dev modeBuild order matters: config -> server/sdk -> cli (cli depends on server and config).
- Wavelet server wraps RisingWave's native Subscription cursor mechanism.
- One cursor per materialized view; server fans out diffs to WebSocket clients.
- JWT claims are matched against query
filterBycolumns for per-client row filtering. - HTTP API: POST for event writes, GET for query reads.
- All persistent state lives in RisingWave. Wavelet server is stateless -- cursor positions are in memory and recover from RisingWave's subscription retention window on restart.
- Single-tenant process: One Wavelet server = one RisingWave connection = one user's queries. Multi-tenancy is handled externally (by Wavelet Cloud), not inside this codebase.
- Config-driven DDL:
wavelet.config.tsis the source of truth.DdlManager.sync()diffs config against RisingWave state and applies minimal changes (create/update/delete tables, MVs, subscriptions).wavelet pushandwavelet devboth use this. - Agent-native DX: Types over docs. Codegen output (
.wavelet/client.ts) is the primary interface for app developers and AI agents. All query/event names are literal types, not strings. - Idempotent operations: All CLI commands and DDL operations are safe to run multiple times.
- Stateless server: No local SQLite, no state files.
Every major change must have a corresponding spec or design document in docs/specs/. This includes:
- New features
- Major refactors
- Architectural changes
- API changes (breaking or significant additions)
- New integrations
Each spec should include: motivation, design overview, key decisions, and any trade-offs. File naming: YYYY-MM-DD-short-description.md (e.g. 2026-03-17-ddl-manager.md).
This is mandatory, not optional. Code without documentation for significant changes should not be considered complete.
- TypeScript strict mode, ES2022 target, NodeNext module resolution.
- Imports use
.jsextension for NodeNext resolution:import { foo } from './bar.js' - Use default import for pg:
import pg from 'pg' - No
console.login library code (server, sdk, config). Only allowed in CLI entry points and server startup. - Error messages must be actionable -- include what went wrong AND how to fix it.
- License: Apache-2.0.
-- Create subscription from a materialized view
CREATE SUBSCRIPTION wavelet_sub_{viewName} FROM {viewName} WITH (retention = '24h');
-- Declare and fetch from subscription cursor
DECLARE cursor_name SUBSCRIPTION CURSOR FOR wavelet_sub_{viewName} SINCE {rw_timestamp};
FETCH 1000 FROM cursor_name;
CLOSE cursor_name;
-- op column in subscription rows indicates change type:
-- op=1: insert, op=2: delete, op=3: update (old value), op=4: update (new value)Wavelet depends on RisingWave as its compute backend. When Wavelet needs a feature, bug fix, or behavior change in RisingWave that does not exist yet, file an issue directly at https://github.com/risingwavelabs/risingwave.
The issue should include:
- What Wavelet needs and why
- The specific RisingWave behavior that is missing or insufficient
- Suggested approach if known
This is the standard workflow - Wavelet is a first-party project in the RisingWave ecosystem and can request upstream changes.
The closed-source wavelet-cloud project (risingwavelabs/wavelet-cloud)
uses this codebase as a library. It imports WaveletServer and DdlManager
to run a managed multi-tenant service. Changes to the interfaces below will
break wavelet-cloud.
| Import | Used for |
|---|---|
WaveletServer from @risingwave/wavelet-server |
Create per-project server instances |
WaveletServer.attachTo(server, { pathPrefix }) |
Attach to shared HTTP server with path prefix like /p/{projectId} |
WaveletServer.stop() |
Clean shutdown |
DdlManager from @risingwave/wavelet-server |
connect() → sync(config) → close() per-user DDL sync |
DdlAction type from @risingwave/wavelet-server |
Track DDL changes |
WaveletConfig type from @risingwave/wavelet |
Config serialization between CLI and cloud API |
- User runs
wavelet-cloud deploywith awavelet.config.ts - Cloud creates a RisingWave database per user
- Cloud calls
DdlManager.sync(config)against that database - Cloud calls
WaveletServer.attachTo(sharedHttpServer, { pathPrefix: '/p/{projectId}' }) - End users access
https://wavelet-cloud.fly.dev/p/{projectId}/v1/...and/subscribe/...
- File an issue at https://github.com/risingwavelabs/wavelet-cloud/issues
- Describe what changes and why
- Coordinate before merging
wavelet-cloud's vendor/ directory pins specific builds of:
@risingwave/wavelet(config types)@risingwave/wavelet-server(server + DDL manager)
If you rename packages again, wavelet-cloud's imports and vendor will break.
This agent uses Stream0 for cross-repo coordination with wavelet-cloud.
- Stream0 URL:
http://3.94.39.251:8080 - Agent ID:
wavelet-agent - Peer:
wavelet-cloud - Task IDs:
cross-repo-sync- general status updates and interface changesbreaking-change- urgent: something will break if not addressedfeature-request- one repo requesting a feature from the otherbug-report- cross-repo bugs
Check inbox for messages from wavelet-cloud:
curl -s "http://3.94.39.251:8080/agents/wavelet-agent/inbox?status=unread" \
-H "X-API-Key: $STREAM0_API_KEY"Acknowledge processed messages:
curl -s -X POST "http://3.94.39.251:8080/inbox/messages/{id}/ack" \
-H "X-API-Key: $STREAM0_API_KEY"Send a notification to wavelet-cloud:
curl -s -X POST "http://3.94.39.251:8080/agents/wavelet-cloud/inbox" \
-H "Content-Type: application/json" \
-H "X-API-Key: $STREAM0_API_KEY" \
-d '{"task_id":"cross-repo-sync","from":"wavelet-agent","type":"request","content":{"change":"description of what changed"}}'The API key is stored in the environment variable STREAM0_API_KEY. Do not hardcode it.
Framework: Vitest. Run with npm test or npx vitest run.
npm test # run all tests
npx vitest # watch mode
npx vitest run # single runExisting tests (in packages/*/src/__tests__/):
config: sql template tag, defineConfigserver: cursor diff parsing, JWT verification, HTTP API routes
Tests that require a running RisingWave (DDL manager, end-to-end WebSocket) are not yet automated.