Skip to content

Latest commit

 

History

History
308 lines (248 loc) · 7.98 KB

File metadata and controls

308 lines (248 loc) · 7.98 KB
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.

Basic structure

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 */ },
})

database

Required. RisingWave connection string.

database: 'postgresql://root@localhost:4566/dev'

This is a standard PostgreSQL connection URL. Wavelet uses pg to connect.

events

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.

Column types

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

Queries define materialized SQL queries. Wavelet creates RisingWave materialized views and subscriptions from these, then pushes diffs to WebSocket subscribers.

Full form

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

Short form

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`,
}

filterBy

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"
  },
}

columns

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

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

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

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 tag

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
  `,
}
The `sql` tag does **not** parameterize values; it concatenates them as strings. This is safe because config files are authored by developers, not user input. Do not pass untrusted input into `sql` template literals.

Full example

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',
  },
})