| title | Wavelet configuration reference |
|---|---|
| sidebarTitle | Configuration |
| description | Complete reference for wavelet.config.ts. events, queries, sources, JWT, server options, column types, and the sql template tag. |
All Wavelet behavior is driven by a single config file: wavelet.config.ts. This file is the source of truth for your events, queries, sources, and server settings. The CLI reads it, syncs it to RisingWave, and generates typed clients from it.
import { defineConfig, sql } from '@risingwave/wavelet'
export default defineConfig({
database: 'postgresql://root@localhost:4566/dev',
events: { /* input tables */ },
sources: { /* CDC sources */ },
queries: { /* materialized SQL queries */ },
jwt: { /* authentication */ },
server: { /* host and port */ },
})Required. RisingWave connection string.
database: 'postgresql://root@localhost:4566/dev'This is a standard PostgreSQL connection URL. Wavelet uses pg to connect.
Events define input tables. You write events to event tables via HTTP POST, and reference them in query definitions.
events: {
page_views: {
columns: {
user_id: 'string',
url: 'string',
duration_ms: 'int',
metadata: 'json',
viewed_at: 'timestamp',
},
},
}Each event becomes a RisingWave table. Column names map directly to SQL column names.
| Type | RisingWave SQL type | TypeScript type |
|---|---|---|
'string' |
VARCHAR |
string |
'int' |
INT |
number |
'float' |
DOUBLE |
number |
'boolean' |
BOOLEAN |
boolean |
'timestamp' |
TIMESTAMPTZ |
string |
'json' |
JSONB |
unknown |
Queries define materialized SQL queries. Wavelet creates RisingWave materialized views and subscriptions from these, then pushes diffs to WebSocket subscribers.
queries: {
active_users: {
query: sql`
SELECT user_id, COUNT(*) AS view_count
FROM page_views
WHERE viewed_at > NOW() - INTERVAL '1 hour'
GROUP BY user_id
`,
filterBy: 'user_id',
columns: {
user_id: 'string',
view_count: 'int',
},
},
}| Field | Type | Required | Description |
|---|---|---|---|
query |
SqlFragment |
Yes | SQL query using the sql tag |
filterBy |
string |
No | Column name for JWT-based per-tenant filtering |
columns |
Record<string, ColumnType> |
No | Column type hints for offline codegen |
If you only need a query (no filterBy or columns), pass the sql fragment directly:
queries: {
total_views: sql`SELECT COUNT(*) AS total FROM page_views`,
}When set, Wavelet matches the JWT token's claims against this column to filter diffs per-client. See JWT auth for details.
queries: {
my_orders: {
query: sql`SELECT * FROM orders`,
filterBy: 'customer_id',
// Client with JWT claim { customer_id: "cust_123" }
// only receives rows where customer_id = "cust_123"
},
}Optional type hints used by wavelet generate for offline codegen. Without these, codegen falls back to RisingWave introspection (requires a running instance) or Record<string, unknown>.
queries: {
leaderboard: {
query: sql`SELECT player_id, SUM(score) AS total FROM events GROUP BY player_id`,
columns: {
player_id: 'string',
total: 'int',
},
},
}Sources connect to external databases via CDC. Currently supports PostgreSQL.
sources: {
main_db: {
type: 'postgres',
connection: 'postgresql://user:pass@db.example.com:5432/myapp',
tables: ['users', 'orders', 'products'],
slotName: 'wavelet_main',
publicationName: 'wavelet_main_pub',
},
}| Field | Type | Required | Default | Description |
|---|---|---|---|---|
type |
'postgres' |
Yes | . | Source type |
connection |
string |
Yes | . | PostgreSQL connection string |
tables |
string[] |
Yes | . | Tables to capture |
slotName |
string |
No | wavelet_{sourceName} |
Replication slot name |
publicationName |
string |
No | wavelet_{sourceName}_pub |
Publication name |
CDC tables are created in RisingWave as {sourceName}_{tableName}. For example, main_db source with table users becomes main_db_users in RisingWave, which you can reference in query definitions:
queries: {
user_order_summary: {
query: sql`
SELECT u.id, u.name, COUNT(o.id) AS order_count
FROM main_db_users u
JOIN main_db_orders o ON u.id = o.user_id
GROUP BY u.id, u.name
`,
},
}See Postgres CDC sources for prerequisites and setup.
JWT configuration for multi-tenant filtering. When configured, WebSocket clients must provide a valid token, and query diffs are filtered based on token claims.
jwt: {
// Option A: shared secret (HS256)
secret: process.env.JWT_SECRET,
// Option B: JWKS endpoint (RS256, for Auth0/Clerk/etc.)
jwksUrl: 'https://your-auth.com/.well-known/jwks.json',
// Optional claim validation
issuer: 'https://your-auth.com/',
audience: 'your-app',
}| Field | Type | Required | Description |
|---|---|---|---|
secret |
string |
No | HS256 symmetric key for token verification |
jwksUrl |
string |
No | JWKS endpoint URL for RS256/ES256 verification |
issuer |
string |
No | Expected iss claim value |
audience |
string |
No | Expected aud claim value |
Provide either secret or jwksUrl, not both. See JWT auth for the full guide.
Server host and port configuration.
server: {
port: 3000,
host: '0.0.0.0',
}| Field | Type | Default | Description |
|---|---|---|---|
port |
number |
8080 |
HTTP/WebSocket server port |
host |
string |
'0.0.0.0' |
Bind address |
The sql template tag creates SqlFragment objects. It supports interpolation:
import { sql } from '@risingwave/wavelet'
const interval = '1 hour'
const queryDef = {
query: sql`
SELECT user_id, COUNT(*) AS cnt
FROM events
WHERE ts > NOW() - INTERVAL '${interval}'
GROUP BY user_id
`,
}import { defineConfig, sql } from '@risingwave/wavelet'
export default defineConfig({
database: process.env.RISINGWAVE_URL ?? 'postgresql://root@localhost:4566/dev',
events: {
llm_events: {
columns: {
tenant_id: 'string',
model: 'string',
tokens_in: 'int',
tokens_out: 'int',
cost_usd: 'float',
latency_ms: 'int',
},
},
},
sources: {
app_db: {
type: 'postgres',
connection: process.env.APP_DATABASE_URL!,
tables: ['tenants'],
},
},
queries: {
tenant_usage: {
query: sql`
SELECT
tenant_id,
SUM(tokens_in + tokens_out) AS total_tokens,
SUM(cost_usd) AS total_cost,
COUNT(*) AS request_count
FROM llm_events
GROUP BY tenant_id
`,
filterBy: 'tenant_id',
columns: {
tenant_id: 'string',
total_tokens: 'int',
total_cost: 'float',
request_count: 'int',
},
},
model_stats: sql`
SELECT model, COUNT(*) AS calls, AVG(latency_ms) AS avg_latency
FROM llm_events
GROUP BY model
`,
},
jwt: {
jwksUrl: process.env.JWKS_URL,
issuer: process.env.JWT_ISSUER,
},
server: {
port: Number(process.env.PORT ?? 8080),
host: '0.0.0.0',
},
})