@risingwave/wavelet-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ import type { WaveletConfig } from '@risingwave/wavelet';
2
+ export declare function generateClient(config: WaveletConfig): Promise<void>;
3
+ //# sourceMappingURL=codegen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codegen.d.ts","sourceRoot":"","sources":["../src/codegen.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAwB,MAAM,qBAAqB,CAAA;AAI9E,wBAAsB,cAAc,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CA0CzE"}
@@ -0,0 +1,146 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.generateClient = generateClient;
7
+ const node_fs_1 = require("node:fs");
8
+ const pg_1 = __importDefault(require("pg"));
9
+ const { Client } = pg_1.default;
10
+ async function generateClient(config) {
11
+ const client = new Client({ connectionString: config.database });
12
+ await client.connect();
13
+ const viewTypes = [];
14
+ for (const viewName of Object.keys(config.views ?? {})) {
15
+ try {
16
+ // Query column info from RisingWave
17
+ const result = await client.query(`SELECT column_name, data_type FROM information_schema.columns WHERE table_name = $1 ORDER BY ordinal_position`, [viewName]);
18
+ const columns = result.rows.map((row) => ({
19
+ name: row.column_name,
20
+ tsType: pgTypeToTs(row.data_type),
21
+ }));
22
+ viewTypes.push({ name: viewName, columns });
23
+ }
24
+ catch {
25
+ // If we can't query schema, generate generic types
26
+ viewTypes.push({ name: viewName, columns: [] });
27
+ }
28
+ }
29
+ const streamTypes = [];
30
+ for (const [streamName, streamDef] of Object.entries(config.streams ?? {})) {
31
+ const columns = Object.entries(streamDef.columns).map(([name, type]) => ({
32
+ name,
33
+ tsType: columnTypeToTs(type),
34
+ }));
35
+ streamTypes.push({ name: streamName, columns });
36
+ }
37
+ await client.end();
38
+ const code = generateCode(viewTypes, streamTypes);
39
+ (0, node_fs_1.mkdirSync)('.wavelet', { recursive: true });
40
+ (0, node_fs_1.writeFileSync)('.wavelet/client.ts', code);
41
+ }
42
+ function generateCode(views, streams) {
43
+ let code = `// Auto-generated by wavelet generate - do not edit\n`;
44
+ code += `// Run 'npx wavelet generate' to regenerate\n\n`;
45
+ code += `import { WaveletClient } from '@risingwave/wavelet-sdk'\n`;
46
+ code += `import type { ViewHandle, StreamHandle, Diff, WaveletClientOptions } from '@risingwave/wavelet-sdk'\n\n`;
47
+ // View row types
48
+ for (const view of views) {
49
+ const typeName = pascalCase(view.name) + 'Row';
50
+ if (view.columns.length > 0) {
51
+ code += `export interface ${typeName} {\n`;
52
+ for (const col of view.columns) {
53
+ code += ` ${col.name}: ${col.tsType}\n`;
54
+ }
55
+ code += `}\n\n`;
56
+ }
57
+ else {
58
+ code += `export type ${typeName} = Record<string, unknown>\n\n`;
59
+ }
60
+ }
61
+ // Stream event types
62
+ for (const stream of streams) {
63
+ const typeName = pascalCase(stream.name) + 'Event';
64
+ code += `export interface ${typeName} {\n`;
65
+ for (const col of stream.columns) {
66
+ code += ` ${col.name}: ${col.tsType}\n`;
67
+ }
68
+ code += `}\n\n`;
69
+ }
70
+ // Typed client class
71
+ code += `export class TypedWaveletClient {\n`;
72
+ code += ` private client: WaveletClient\n\n`;
73
+ code += ` constructor(options: WaveletClientOptions) {\n`;
74
+ code += ` this.client = new WaveletClient(options)\n`;
75
+ code += ` }\n\n`;
76
+ // View accessors
77
+ code += ` views = {\n`;
78
+ for (const view of views) {
79
+ const typeName = pascalCase(view.name) + 'Row';
80
+ code += ` ${view.name}: this.client.view<${typeName}>('${view.name}'),\n`;
81
+ }
82
+ code += ` }\n\n`;
83
+ // Stream accessors
84
+ code += ` streams = {\n`;
85
+ for (const stream of streams) {
86
+ const typeName = pascalCase(stream.name) + 'Event';
87
+ code += ` ${stream.name}: this.client.stream<${typeName}>('${stream.name}'),\n`;
88
+ }
89
+ code += ` }\n`;
90
+ code += `}\n\n`;
91
+ // View name literal type
92
+ const viewNames = views.map(v => `'${v.name}'`).join(' | ');
93
+ code += `export type ViewName = ${viewNames || 'never'}\n\n`;
94
+ // React hook overloads
95
+ code += `// React hook overloads for type-safe useWavelet\n`;
96
+ code += `import type { UseWaveletResult } from '@risingwave/wavelet-sdk/react'\n\n`;
97
+ for (const view of views) {
98
+ const typeName = pascalCase(view.name) + 'Row';
99
+ code += `export declare function useWavelet(view: '${view.name}'): UseWaveletResult<${typeName}>\n`;
100
+ }
101
+ return code;
102
+ }
103
+ function pgTypeToTs(pgType) {
104
+ switch (pgType.toLowerCase()) {
105
+ case 'integer':
106
+ case 'bigint':
107
+ case 'smallint':
108
+ case 'real':
109
+ case 'double precision':
110
+ case 'numeric':
111
+ case 'float':
112
+ case 'int':
113
+ return 'number';
114
+ case 'boolean':
115
+ return 'boolean';
116
+ case 'jsonb':
117
+ case 'json':
118
+ return 'unknown';
119
+ case 'timestamp with time zone':
120
+ case 'timestamp without time zone':
121
+ case 'timestamptz':
122
+ return 'string';
123
+ default:
124
+ return 'string';
125
+ }
126
+ }
127
+ function columnTypeToTs(colType) {
128
+ switch (colType) {
129
+ case 'int':
130
+ case 'float':
131
+ return 'number';
132
+ case 'boolean':
133
+ return 'boolean';
134
+ case 'json':
135
+ return 'unknown';
136
+ default:
137
+ return 'string';
138
+ }
139
+ }
140
+ function pascalCase(str) {
141
+ return str
142
+ .split(/[_\s-]+/)
143
+ .map(s => s.charAt(0).toUpperCase() + s.slice(1))
144
+ .join('');
145
+ }
146
+ //# sourceMappingURL=codegen.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codegen.js","sourceRoot":"","sources":["../src/codegen.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { WaveletConfig } from '@risingwave/wavelet';
2
+ export declare function loadConfig(configPath: string): Promise<WaveletConfig>;
3
+ //# sourceMappingURL=config-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../src/config-loader.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAExD,wBAAsB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAe3E"}
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadConfig = loadConfig;
4
+ const node_url_1 = require("node:url");
5
+ const node_path_1 = require("node:path");
6
+ async function loadConfig(configPath) {
7
+ const abs = (0, node_path_1.resolve)(configPath);
8
+ const mod = await import((0, node_url_1.pathToFileURL)(abs).href);
9
+ const config = mod.default ?? mod;
10
+ if (!config.database) {
11
+ throw new Error(`wavelet.config.ts must export a config with a 'database' field.\n` +
12
+ `Example:\n` +
13
+ ` import { defineConfig } from '@risingwave/wavelet'\n` +
14
+ ` export default defineConfig({ database: 'postgres://...' })`);
15
+ }
16
+ return config;
17
+ }
18
+ //# sourceMappingURL=config-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-loader.js","sourceRoot":"","sources":["../src/config-loader.ts"],"names":[],"mappings":";;AAIA,gCAeC;AAnBD,uCAAwC;AACxC,yCAAmC;AAG5B,KAAK,UAAU,UAAU,CAAC,UAAkB;IACjD,MAAM,GAAG,GAAG,IAAA,mBAAO,EAAC,UAAU,CAAC,CAAA;IAC/B,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAA,wBAAa,EAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAA;IACjD,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,IAAI,GAAG,CAAA;IAEjC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CACb,mEAAmE;YACnE,YAAY;YACZ,wDAAwD;YACxD,+DAA+D,CAChE,CAAA;IACH,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,208 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const command = process.argv[2];
5
+ const HELP = `
6
+ wavelet - Subscribe to computed results, not raw rows.
7
+
8
+ Usage:
9
+ wavelet <command> [options]
10
+
11
+ Commands:
12
+ dev Start local development server
13
+ generate Generate typed client from view definitions
14
+ push Sync view definitions to Wavelet server
15
+ status Show current configuration and connection status
16
+ init Initialize a new Wavelet project
17
+
18
+ Options:
19
+ --config Path to wavelet.config.ts (default: ./wavelet.config.ts)
20
+ --json Output in JSON format
21
+ --help Show this help message
22
+
23
+ Examples:
24
+ wavelet init
25
+ wavelet dev
26
+ wavelet generate
27
+ wavelet push
28
+ `;
29
+ async function main() {
30
+ switch (command) {
31
+ case 'init':
32
+ await runInit();
33
+ break;
34
+ case 'dev':
35
+ await runDev();
36
+ break;
37
+ case 'generate':
38
+ await runGenerate();
39
+ break;
40
+ case 'push':
41
+ await runPush();
42
+ break;
43
+ case 'status':
44
+ await runStatus();
45
+ break;
46
+ case '--help':
47
+ case '-h':
48
+ case undefined:
49
+ console.log(HELP);
50
+ break;
51
+ default:
52
+ console.error(`Unknown command: '${command}'`);
53
+ console.error(`Run 'wavelet --help' for available commands.`);
54
+ process.exit(1);
55
+ }
56
+ }
57
+ async function runInit() {
58
+ const { writeFileSync, existsSync } = await import('node:fs');
59
+ if (existsSync('wavelet.config.ts')) {
60
+ console.log('wavelet.config.ts already exists. Skipping.');
61
+ return;
62
+ }
63
+ writeFileSync('wavelet.config.ts', `import { defineConfig, sql } from '@risingwave/wavelet'
64
+
65
+ export default defineConfig({
66
+ database: process.env.WAVELET_DATABASE_URL ?? 'postgres://root@localhost:4566/dev',
67
+
68
+ streams: {
69
+ // Define your event streams here
70
+ // events: {
71
+ // columns: {
72
+ // user_id: 'string',
73
+ // action: 'string',
74
+ // value: 'int',
75
+ // }
76
+ // }
77
+ },
78
+
79
+ views: {
80
+ // Define your materialized views here
81
+ // leaderboard: sql\`
82
+ // SELECT user_id, SUM(value) as total
83
+ // FROM events
84
+ // GROUP BY user_id
85
+ // ORDER BY total DESC
86
+ // LIMIT 100
87
+ // \`,
88
+ },
89
+ })
90
+ `);
91
+ console.log('Created wavelet.config.ts');
92
+ console.log('');
93
+ console.log('Next steps:');
94
+ console.log(' 1. Edit wavelet.config.ts to define your streams and views');
95
+ console.log(' 2. Run: wavelet dev');
96
+ }
97
+ async function runDev() {
98
+ const { loadConfig } = await import('./config-loader.js');
99
+ const { ensureRisingWave } = await import('./risingwave-launcher.js');
100
+ const configPath = getConfigPath();
101
+ console.log(`Loading config from ${configPath}...`);
102
+ const config = await loadConfig(configPath);
103
+ // Ensure RisingWave is running
104
+ const rwProcess = await ensureRisingWave(config.database);
105
+ // Sync DDL before starting server
106
+ const { DdlManager, WaveletServer } = await import('@risingwave/wavelet-server');
107
+ const ddl = new DdlManager(config.database);
108
+ await ddl.connect();
109
+ console.log('\nSyncing streams and views...');
110
+ const actions = await ddl.sync(config);
111
+ printDdlActions(actions);
112
+ await ddl.close();
113
+ // Start server
114
+ const server = new WaveletServer(config);
115
+ await server.start();
116
+ const shutdown = async () => {
117
+ console.log('\nShutting down...');
118
+ await server.stop();
119
+ if (rwProcess) {
120
+ console.log('Stopping RisingWave...');
121
+ rwProcess.kill();
122
+ }
123
+ process.exit(0);
124
+ };
125
+ process.on('SIGINT', shutdown);
126
+ process.on('SIGTERM', shutdown);
127
+ }
128
+ async function runGenerate() {
129
+ const { loadConfig } = await import('./config-loader.js');
130
+ const { generateClient } = await import('./codegen.js');
131
+ const configPath = getConfigPath();
132
+ console.log(`Loading config from ${configPath}...`);
133
+ const config = await loadConfig(configPath);
134
+ await generateClient(config);
135
+ console.log('Generated .wavelet/client.ts');
136
+ }
137
+ async function runPush() {
138
+ const { loadConfig } = await import('./config-loader.js');
139
+ const configPath = getConfigPath();
140
+ console.log(`Loading config from ${configPath}...`);
141
+ const config = await loadConfig(configPath);
142
+ const { DdlManager } = await import('@risingwave/wavelet-server');
143
+ const ddl = new DdlManager(config.database);
144
+ await ddl.connect();
145
+ const actions = await ddl.sync(config);
146
+ await ddl.close();
147
+ const isJson = process.argv.includes('--json');
148
+ if (isJson) {
149
+ console.log(JSON.stringify({ actions }));
150
+ }
151
+ else {
152
+ printDdlActions(actions);
153
+ }
154
+ }
155
+ async function runStatus() {
156
+ const { loadConfig } = await import('./config-loader.js');
157
+ const configPath = getConfigPath();
158
+ try {
159
+ const config = await loadConfig(configPath);
160
+ const streamCount = Object.keys(config.streams ?? {}).length;
161
+ const viewCount = Object.keys(config.views ?? {}).length;
162
+ console.log(`Config: ${configPath}`);
163
+ console.log(`Database: ${config.database.replace(/\/\/[^@]+@/, '//***@')}`);
164
+ console.log(`Streams: ${streamCount}`);
165
+ console.log(`Views: ${viewCount}`);
166
+ if (viewCount > 0) {
167
+ console.log('\nViews:');
168
+ for (const name of Object.keys(config.views ?? {})) {
169
+ console.log(` - ${name}`);
170
+ }
171
+ }
172
+ }
173
+ catch (err) {
174
+ console.error(`Error: ${err.message}`);
175
+ process.exit(1);
176
+ }
177
+ }
178
+ function printDdlActions(actions) {
179
+ const changed = actions.filter(a => a.type !== 'unchanged');
180
+ const unchanged = actions.filter(a => a.type === 'unchanged');
181
+ for (const action of actions) {
182
+ const icon = action.type === 'create' ? '+' : action.type === 'delete' ? '-' : ' ';
183
+ const label = `${action.resource} '${action.name}'`;
184
+ const detail = action.detail ? ` (${action.detail})` : '';
185
+ if (action.type === 'unchanged') {
186
+ console.log(` ${icon} ${label}`);
187
+ }
188
+ else if (action.type === 'create') {
189
+ console.log(` ${icon} ${label} - created${detail}`);
190
+ }
191
+ else if (action.type === 'delete') {
192
+ console.log(` ${icon} ${label} - removed${detail}`);
193
+ }
194
+ }
195
+ console.log(`\n${changed.length} changed, ${unchanged.length} unchanged`);
196
+ }
197
+ function getConfigPath() {
198
+ const idx = process.argv.indexOf('--config');
199
+ if (idx !== -1 && process.argv[idx + 1]) {
200
+ return process.argv[idx + 1];
201
+ }
202
+ return './wavelet.config.ts';
203
+ }
204
+ main().catch((err) => {
205
+ console.error('Error:', err.message);
206
+ process.exit(1);
207
+ });
208
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { type ChildProcess } from 'node:child_process';
2
+ export declare function ensureRisingWave(connectionString: string): Promise<ChildProcess | null>;
3
+ //# sourceMappingURL=risingwave-launcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"risingwave-launcher.d.ts","sourceRoot":"","sources":["../src/risingwave-launcher.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAKvE,wBAAsB,gBAAgB,CAAC,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CA2B7F"}
@@ -0,0 +1,163 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ensureRisingWave = ensureRisingWave;
7
+ const node_child_process_1 = require("node:child_process");
8
+ const pg_1 = __importDefault(require("pg"));
9
+ const { Client } = pg_1.default;
10
+ async function ensureRisingWave(connectionString) {
11
+ // Try to connect to existing RisingWave
12
+ if (await isReachable(connectionString)) {
13
+ console.log('RisingWave is already running.');
14
+ return null;
15
+ }
16
+ console.log('RisingWave is not reachable. Attempting to start...');
17
+ // Try native binary first, then docker
18
+ const binary = findBinary();
19
+ if (binary) {
20
+ return startNative(binary);
21
+ }
22
+ if (hasDocker()) {
23
+ return startDocker();
24
+ }
25
+ console.error('Could not start RisingWave.\n\n' +
26
+ 'Install one of:\n' +
27
+ ' brew tap risingwavelabs/risingwave && brew install risingwave\n' +
28
+ ' docker pull risingwavelabs/risingwave:latest\n\n' +
29
+ 'Or start RisingWave manually and re-run wavelet dev.');
30
+ process.exit(1);
31
+ }
32
+ async function isReachable(connectionString) {
33
+ const client = new Client({ connectionString, connectionTimeoutMillis: 3000 });
34
+ try {
35
+ await client.connect();
36
+ await client.query('SELECT 1');
37
+ await client.end();
38
+ return true;
39
+ }
40
+ catch {
41
+ return false;
42
+ }
43
+ }
44
+ function findBinary() {
45
+ try {
46
+ const path = (0, node_child_process_1.execSync)('which risingwave', { encoding: 'utf-8' }).trim();
47
+ return path || null;
48
+ }
49
+ catch {
50
+ return null;
51
+ }
52
+ }
53
+ function hasDocker() {
54
+ try {
55
+ (0, node_child_process_1.execSync)('docker info', { stdio: 'ignore' });
56
+ return true;
57
+ }
58
+ catch {
59
+ return false;
60
+ }
61
+ }
62
+ function startNative(binaryPath) {
63
+ return new Promise((resolve, reject) => {
64
+ console.log(`Starting RisingWave (${binaryPath})...`);
65
+ const child = (0, node_child_process_1.spawn)(binaryPath, ['playground'], {
66
+ stdio: ['ignore', 'pipe', 'pipe'],
67
+ detached: false,
68
+ });
69
+ let started = false;
70
+ const onData = (data) => {
71
+ const text = data.toString();
72
+ if (!started && text.includes('ready to accept connections')) {
73
+ started = true;
74
+ console.log('RisingWave started (playground mode).');
75
+ resolve(child);
76
+ }
77
+ };
78
+ child.stdout?.on('data', onData);
79
+ child.stderr?.on('data', onData);
80
+ child.on('error', (err) => {
81
+ if (!started)
82
+ reject(err);
83
+ });
84
+ child.on('exit', (code) => {
85
+ if (!started)
86
+ reject(new Error(`RisingWave exited with code ${code}`));
87
+ });
88
+ // Fallback: poll for connectivity
89
+ const poll = setInterval(async () => {
90
+ if (started) {
91
+ clearInterval(poll);
92
+ return;
93
+ }
94
+ if (await isReachable('postgres://root@localhost:4566/dev')) {
95
+ started = true;
96
+ clearInterval(poll);
97
+ console.log('RisingWave started (playground mode).');
98
+ resolve(child);
99
+ }
100
+ }, 1000);
101
+ // Timeout after 30 seconds
102
+ setTimeout(() => {
103
+ if (!started) {
104
+ clearInterval(poll);
105
+ child.kill();
106
+ reject(new Error('RisingWave failed to start within 30 seconds.'));
107
+ }
108
+ }, 30000);
109
+ });
110
+ }
111
+ function startDocker() {
112
+ return new Promise((resolve, reject) => {
113
+ console.log('Starting RisingWave via Docker...');
114
+ // Remove stale container if exists
115
+ try {
116
+ (0, node_child_process_1.execSync)('docker rm -f wavelet-risingwave', { stdio: 'ignore' });
117
+ }
118
+ catch { }
119
+ const child = (0, node_child_process_1.spawn)('docker', [
120
+ 'run', '--name', 'wavelet-risingwave',
121
+ '-p', '4566:4566',
122
+ 'risingwavelabs/risingwave:latest',
123
+ 'playground',
124
+ ], {
125
+ stdio: ['ignore', 'pipe', 'pipe'],
126
+ detached: false,
127
+ });
128
+ let started = false;
129
+ // Poll for connectivity
130
+ const poll = setInterval(async () => {
131
+ if (started) {
132
+ clearInterval(poll);
133
+ return;
134
+ }
135
+ if (await isReachable('postgres://root@localhost:4566/dev')) {
136
+ started = true;
137
+ clearInterval(poll);
138
+ console.log('RisingWave started via Docker.');
139
+ resolve(child);
140
+ }
141
+ }, 1000);
142
+ child.on('error', (err) => {
143
+ if (!started) {
144
+ clearInterval(poll);
145
+ reject(err);
146
+ }
147
+ });
148
+ child.on('exit', (code) => {
149
+ if (!started) {
150
+ clearInterval(poll);
151
+ reject(new Error(`Docker exited with code ${code}`));
152
+ }
153
+ });
154
+ setTimeout(() => {
155
+ if (!started) {
156
+ clearInterval(poll);
157
+ child.kill();
158
+ reject(new Error('RisingWave Docker container failed to start within 60 seconds.'));
159
+ }
160
+ }, 60000);
161
+ });
162
+ }
163
+ //# sourceMappingURL=risingwave-launcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"risingwave-launcher.js","sourceRoot":"","sources":["../src/risingwave-launcher.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@risingwave/wavelet-cli",
3
+ "version": "0.1.0",
4
+ "description": "Wavelet CLI - manage views, generate types, run dev server",
5
+ "bin": {
6
+ "wavelet": "./dist/index.js"
7
+ },
8
+ "scripts": {
9
+ "build": "tsc",
10
+ "dev": "tsx src/index.ts"
11
+ },
12
+ "dependencies": {
13
+ "@risingwave/wavelet": "0.1.0",
14
+ "@risingwave/wavelet-server": "0.1.0",
15
+ "pg": "^8.13.0"
16
+ },
17
+ "devDependencies": {
18
+ "tsx": "^4.0.0",
19
+ "typescript": "^5.7.0",
20
+ "@types/pg": "^8.11.0"
21
+ },
22
+ "license": "Apache-2.0"
23
+ }
package/src/codegen.ts ADDED
@@ -0,0 +1,167 @@
1
+ import { writeFileSync, mkdirSync } from 'node:fs'
2
+ import pg from 'pg'
3
+ import type { WaveletConfig, SqlFragment, ViewDef } from '@risingwave/wavelet'
4
+
5
+ const { Client } = pg
6
+
7
+ export async function generateClient(config: WaveletConfig): Promise<void> {
8
+ const client = new Client({ connectionString: config.database })
9
+ await client.connect()
10
+
11
+ const viewTypes: { name: string; columns: { name: string; tsType: string }[] }[] = []
12
+
13
+ for (const viewName of Object.keys(config.views ?? {})) {
14
+ try {
15
+ // Query column info from RisingWave
16
+ const result = await client.query(
17
+ `SELECT column_name, data_type FROM information_schema.columns WHERE table_name = $1 ORDER BY ordinal_position`,
18
+ [viewName]
19
+ )
20
+
21
+ const columns = result.rows.map((row: any) => ({
22
+ name: row.column_name,
23
+ tsType: pgTypeToTs(row.data_type),
24
+ }))
25
+
26
+ viewTypes.push({ name: viewName, columns })
27
+ } catch {
28
+ // If we can't query schema, generate generic types
29
+ viewTypes.push({ name: viewName, columns: [] })
30
+ }
31
+ }
32
+
33
+ const streamTypes: { name: string; columns: { name: string; tsType: string }[] }[] = []
34
+
35
+ for (const [streamName, streamDef] of Object.entries(config.streams ?? {})) {
36
+ const columns = Object.entries(streamDef.columns).map(([name, type]) => ({
37
+ name,
38
+ tsType: columnTypeToTs(type),
39
+ }))
40
+ streamTypes.push({ name: streamName, columns })
41
+ }
42
+
43
+ await client.end()
44
+
45
+ const code = generateCode(viewTypes, streamTypes)
46
+
47
+ mkdirSync('.wavelet', { recursive: true })
48
+ writeFileSync('.wavelet/client.ts', code)
49
+ }
50
+
51
+ function generateCode(
52
+ views: { name: string; columns: { name: string; tsType: string }[] }[],
53
+ streams: { name: string; columns: { name: string; tsType: string }[] }[]
54
+ ): string {
55
+ let code = `// Auto-generated by wavelet generate - do not edit\n`
56
+ code += `// Run 'npx wavelet generate' to regenerate\n\n`
57
+ code += `import { WaveletClient } from '@risingwave/wavelet-sdk'\n`
58
+ code += `import type { ViewHandle, StreamHandle, Diff, WaveletClientOptions } from '@risingwave/wavelet-sdk'\n\n`
59
+
60
+ // View row types
61
+ for (const view of views) {
62
+ const typeName = pascalCase(view.name) + 'Row'
63
+ if (view.columns.length > 0) {
64
+ code += `export interface ${typeName} {\n`
65
+ for (const col of view.columns) {
66
+ code += ` ${col.name}: ${col.tsType}\n`
67
+ }
68
+ code += `}\n\n`
69
+ } else {
70
+ code += `export type ${typeName} = Record<string, unknown>\n\n`
71
+ }
72
+ }
73
+
74
+ // Stream event types
75
+ for (const stream of streams) {
76
+ const typeName = pascalCase(stream.name) + 'Event'
77
+ code += `export interface ${typeName} {\n`
78
+ for (const col of stream.columns) {
79
+ code += ` ${col.name}: ${col.tsType}\n`
80
+ }
81
+ code += `}\n\n`
82
+ }
83
+
84
+ // Typed client class
85
+ code += `export class TypedWaveletClient {\n`
86
+ code += ` private client: WaveletClient\n\n`
87
+ code += ` constructor(options: WaveletClientOptions) {\n`
88
+ code += ` this.client = new WaveletClient(options)\n`
89
+ code += ` }\n\n`
90
+
91
+ // View accessors
92
+ code += ` views = {\n`
93
+ for (const view of views) {
94
+ const typeName = pascalCase(view.name) + 'Row'
95
+ code += ` ${view.name}: this.client.view<${typeName}>('${view.name}'),\n`
96
+ }
97
+ code += ` }\n\n`
98
+
99
+ // Stream accessors
100
+ code += ` streams = {\n`
101
+ for (const stream of streams) {
102
+ const typeName = pascalCase(stream.name) + 'Event'
103
+ code += ` ${stream.name}: this.client.stream<${typeName}>('${stream.name}'),\n`
104
+ }
105
+ code += ` }\n`
106
+ code += `}\n\n`
107
+
108
+ // View name literal type
109
+ const viewNames = views.map(v => `'${v.name}'`).join(' | ')
110
+ code += `export type ViewName = ${viewNames || 'never'}\n\n`
111
+
112
+ // React hook overloads
113
+ code += `// React hook overloads for type-safe useWavelet\n`
114
+ code += `import type { UseWaveletResult } from '@risingwave/wavelet-sdk/react'\n\n`
115
+ for (const view of views) {
116
+ const typeName = pascalCase(view.name) + 'Row'
117
+ code += `export declare function useWavelet(view: '${view.name}'): UseWaveletResult<${typeName}>\n`
118
+ }
119
+
120
+ return code
121
+ }
122
+
123
+ function pgTypeToTs(pgType: string): string {
124
+ switch (pgType.toLowerCase()) {
125
+ case 'integer':
126
+ case 'bigint':
127
+ case 'smallint':
128
+ case 'real':
129
+ case 'double precision':
130
+ case 'numeric':
131
+ case 'float':
132
+ case 'int':
133
+ return 'number'
134
+ case 'boolean':
135
+ return 'boolean'
136
+ case 'jsonb':
137
+ case 'json':
138
+ return 'unknown'
139
+ case 'timestamp with time zone':
140
+ case 'timestamp without time zone':
141
+ case 'timestamptz':
142
+ return 'string'
143
+ default:
144
+ return 'string'
145
+ }
146
+ }
147
+
148
+ function columnTypeToTs(colType: string): string {
149
+ switch (colType) {
150
+ case 'int':
151
+ case 'float':
152
+ return 'number'
153
+ case 'boolean':
154
+ return 'boolean'
155
+ case 'json':
156
+ return 'unknown'
157
+ default:
158
+ return 'string'
159
+ }
160
+ }
161
+
162
+ function pascalCase(str: string): string {
163
+ return str
164
+ .split(/[_\s-]+/)
165
+ .map(s => s.charAt(0).toUpperCase() + s.slice(1))
166
+ .join('')
167
+ }
@@ -0,0 +1,20 @@
1
+ import { pathToFileURL } from 'node:url'
2
+ import { resolve } from 'node:path'
3
+ import type { WaveletConfig } from '@risingwave/wavelet'
4
+
5
+ export async function loadConfig(configPath: string): Promise<WaveletConfig> {
6
+ const abs = resolve(configPath)
7
+ const mod = await import(pathToFileURL(abs).href)
8
+ const config = mod.default ?? mod
9
+
10
+ if (!config.database) {
11
+ throw new Error(
12
+ `wavelet.config.ts must export a config with a 'database' field.\n` +
13
+ `Example:\n` +
14
+ ` import { defineConfig } from '@risingwave/wavelet'\n` +
15
+ ` export default defineConfig({ database: 'postgres://...' })`
16
+ )
17
+ }
18
+
19
+ return config
20
+ }
package/src/index.ts ADDED
@@ -0,0 +1,233 @@
1
+ #!/usr/bin/env node
2
+
3
+ const command = process.argv[2]
4
+
5
+ const HELP = `
6
+ wavelet - Subscribe to computed results, not raw rows.
7
+
8
+ Usage:
9
+ wavelet <command> [options]
10
+
11
+ Commands:
12
+ dev Start local development server
13
+ generate Generate typed client from view definitions
14
+ push Sync view definitions to Wavelet server
15
+ status Show current configuration and connection status
16
+ init Initialize a new Wavelet project
17
+
18
+ Options:
19
+ --config Path to wavelet.config.ts (default: ./wavelet.config.ts)
20
+ --json Output in JSON format
21
+ --help Show this help message
22
+
23
+ Examples:
24
+ wavelet init
25
+ wavelet dev
26
+ wavelet generate
27
+ wavelet push
28
+ `
29
+
30
+ async function main() {
31
+ switch (command) {
32
+ case 'init':
33
+ await runInit()
34
+ break
35
+ case 'dev':
36
+ await runDev()
37
+ break
38
+ case 'generate':
39
+ await runGenerate()
40
+ break
41
+ case 'push':
42
+ await runPush()
43
+ break
44
+ case 'status':
45
+ await runStatus()
46
+ break
47
+ case '--help':
48
+ case '-h':
49
+ case undefined:
50
+ console.log(HELP)
51
+ break
52
+ default:
53
+ console.error(`Unknown command: '${command}'`)
54
+ console.error(`Run 'wavelet --help' for available commands.`)
55
+ process.exit(1)
56
+ }
57
+ }
58
+
59
+ async function runInit() {
60
+ const { writeFileSync, existsSync } = await import('node:fs')
61
+
62
+ if (existsSync('wavelet.config.ts')) {
63
+ console.log('wavelet.config.ts already exists. Skipping.')
64
+ return
65
+ }
66
+
67
+ writeFileSync('wavelet.config.ts', `import { defineConfig, sql } from '@risingwave/wavelet'
68
+
69
+ export default defineConfig({
70
+ database: process.env.WAVELET_DATABASE_URL ?? 'postgres://root@localhost:4566/dev',
71
+
72
+ streams: {
73
+ // Define your event streams here
74
+ // events: {
75
+ // columns: {
76
+ // user_id: 'string',
77
+ // action: 'string',
78
+ // value: 'int',
79
+ // }
80
+ // }
81
+ },
82
+
83
+ views: {
84
+ // Define your materialized views here
85
+ // leaderboard: sql\`
86
+ // SELECT user_id, SUM(value) as total
87
+ // FROM events
88
+ // GROUP BY user_id
89
+ // ORDER BY total DESC
90
+ // LIMIT 100
91
+ // \`,
92
+ },
93
+ })
94
+ `)
95
+
96
+ console.log('Created wavelet.config.ts')
97
+ console.log('')
98
+ console.log('Next steps:')
99
+ console.log(' 1. Edit wavelet.config.ts to define your streams and views')
100
+ console.log(' 2. Run: wavelet dev')
101
+ }
102
+
103
+ async function runDev() {
104
+ const { loadConfig } = await import('./config-loader.js')
105
+ const { ensureRisingWave } = await import('./risingwave-launcher.js')
106
+ const configPath = getConfigPath()
107
+
108
+ console.log(`Loading config from ${configPath}...`)
109
+ const config = await loadConfig(configPath)
110
+
111
+ // Ensure RisingWave is running
112
+ const rwProcess = await ensureRisingWave(config.database)
113
+
114
+ // Sync DDL before starting server
115
+ const { DdlManager, WaveletServer } = await import('@risingwave/wavelet-server')
116
+ const ddl = new DdlManager(config.database)
117
+ await ddl.connect()
118
+
119
+ console.log('\nSyncing streams and views...')
120
+ const actions = await ddl.sync(config)
121
+ printDdlActions(actions)
122
+ await ddl.close()
123
+
124
+ // Start server
125
+ const server = new WaveletServer(config)
126
+ await server.start()
127
+
128
+ const shutdown = async () => {
129
+ console.log('\nShutting down...')
130
+ await server.stop()
131
+ if (rwProcess) {
132
+ console.log('Stopping RisingWave...')
133
+ rwProcess.kill()
134
+ }
135
+ process.exit(0)
136
+ }
137
+ process.on('SIGINT', shutdown)
138
+ process.on('SIGTERM', shutdown)
139
+ }
140
+
141
+ async function runGenerate() {
142
+ const { loadConfig } = await import('./config-loader.js')
143
+ const { generateClient } = await import('./codegen.js')
144
+ const configPath = getConfigPath()
145
+
146
+ console.log(`Loading config from ${configPath}...`)
147
+ const config = await loadConfig(configPath)
148
+
149
+ await generateClient(config)
150
+ console.log('Generated .wavelet/client.ts')
151
+ }
152
+
153
+ async function runPush() {
154
+ const { loadConfig } = await import('./config-loader.js')
155
+ const configPath = getConfigPath()
156
+
157
+ console.log(`Loading config from ${configPath}...`)
158
+ const config = await loadConfig(configPath)
159
+
160
+ const { DdlManager } = await import('@risingwave/wavelet-server')
161
+ const ddl = new DdlManager(config.database)
162
+ await ddl.connect()
163
+
164
+ const actions = await ddl.sync(config)
165
+ await ddl.close()
166
+
167
+ const isJson = process.argv.includes('--json')
168
+ if (isJson) {
169
+ console.log(JSON.stringify({ actions }))
170
+ } else {
171
+ printDdlActions(actions)
172
+ }
173
+ }
174
+
175
+ async function runStatus() {
176
+ const { loadConfig } = await import('./config-loader.js')
177
+ const configPath = getConfigPath()
178
+
179
+ try {
180
+ const config = await loadConfig(configPath)
181
+ const streamCount = Object.keys(config.streams ?? {}).length
182
+ const viewCount = Object.keys(config.views ?? {}).length
183
+
184
+ console.log(`Config: ${configPath}`)
185
+ console.log(`Database: ${config.database.replace(/\/\/[^@]+@/, '//***@')}`)
186
+ console.log(`Streams: ${streamCount}`)
187
+ console.log(`Views: ${viewCount}`)
188
+
189
+ if (viewCount > 0) {
190
+ console.log('\nViews:')
191
+ for (const name of Object.keys(config.views ?? {})) {
192
+ console.log(` - ${name}`)
193
+ }
194
+ }
195
+ } catch (err: any) {
196
+ console.error(`Error: ${err.message}`)
197
+ process.exit(1)
198
+ }
199
+ }
200
+
201
+ function printDdlActions(actions: { type: string; resource: string; name: string; detail?: string }[]): void {
202
+ const changed = actions.filter(a => a.type !== 'unchanged')
203
+ const unchanged = actions.filter(a => a.type === 'unchanged')
204
+
205
+ for (const action of actions) {
206
+ const icon = action.type === 'create' ? '+' : action.type === 'delete' ? '-' : ' '
207
+ const label = `${action.resource} '${action.name}'`
208
+ const detail = action.detail ? ` (${action.detail})` : ''
209
+
210
+ if (action.type === 'unchanged') {
211
+ console.log(` ${icon} ${label}`)
212
+ } else if (action.type === 'create') {
213
+ console.log(` ${icon} ${label} - created${detail}`)
214
+ } else if (action.type === 'delete') {
215
+ console.log(` ${icon} ${label} - removed${detail}`)
216
+ }
217
+ }
218
+
219
+ console.log(`\n${changed.length} changed, ${unchanged.length} unchanged`)
220
+ }
221
+
222
+ function getConfigPath(): string {
223
+ const idx = process.argv.indexOf('--config')
224
+ if (idx !== -1 && process.argv[idx + 1]) {
225
+ return process.argv[idx + 1]
226
+ }
227
+ return './wavelet.config.ts'
228
+ }
229
+
230
+ main().catch((err) => {
231
+ console.error('Error:', err.message)
232
+ process.exit(1)
233
+ })
@@ -0,0 +1,177 @@
1
+ import { execSync, spawn, type ChildProcess } from 'node:child_process'
2
+ import pg from 'pg'
3
+
4
+ const { Client } = pg
5
+
6
+ export async function ensureRisingWave(connectionString: string): Promise<ChildProcess | null> {
7
+ // Try to connect to existing RisingWave
8
+ if (await isReachable(connectionString)) {
9
+ console.log('RisingWave is already running.')
10
+ return null
11
+ }
12
+
13
+ console.log('RisingWave is not reachable. Attempting to start...')
14
+
15
+ // Try native binary first, then docker
16
+ const binary = findBinary()
17
+ if (binary) {
18
+ return startNative(binary)
19
+ }
20
+
21
+ if (hasDocker()) {
22
+ return startDocker()
23
+ }
24
+
25
+ console.error(
26
+ 'Could not start RisingWave.\n\n' +
27
+ 'Install one of:\n' +
28
+ ' brew tap risingwavelabs/risingwave && brew install risingwave\n' +
29
+ ' docker pull risingwavelabs/risingwave:latest\n\n' +
30
+ 'Or start RisingWave manually and re-run wavelet dev.'
31
+ )
32
+ process.exit(1)
33
+ }
34
+
35
+ async function isReachable(connectionString: string): Promise<boolean> {
36
+ const client = new Client({ connectionString, connectionTimeoutMillis: 3000 })
37
+ try {
38
+ await client.connect()
39
+ await client.query('SELECT 1')
40
+ await client.end()
41
+ return true
42
+ } catch {
43
+ return false
44
+ }
45
+ }
46
+
47
+ function findBinary(): string | null {
48
+ try {
49
+ const path = execSync('which risingwave', { encoding: 'utf-8' }).trim()
50
+ return path || null
51
+ } catch {
52
+ return null
53
+ }
54
+ }
55
+
56
+ function hasDocker(): boolean {
57
+ try {
58
+ execSync('docker info', { stdio: 'ignore' })
59
+ return true
60
+ } catch {
61
+ return false
62
+ }
63
+ }
64
+
65
+ function startNative(binaryPath: string): Promise<ChildProcess> {
66
+ return new Promise((resolve, reject) => {
67
+ console.log(`Starting RisingWave (${binaryPath})...`)
68
+ const child = spawn(binaryPath, ['playground'], {
69
+ stdio: ['ignore', 'pipe', 'pipe'],
70
+ detached: false,
71
+ })
72
+
73
+ let started = false
74
+
75
+ const onData = (data: Buffer) => {
76
+ const text = data.toString()
77
+ if (!started && text.includes('ready to accept connections')) {
78
+ started = true
79
+ console.log('RisingWave started (playground mode).')
80
+ resolve(child)
81
+ }
82
+ }
83
+
84
+ child.stdout?.on('data', onData)
85
+ child.stderr?.on('data', onData)
86
+
87
+ child.on('error', (err) => {
88
+ if (!started) reject(err)
89
+ })
90
+
91
+ child.on('exit', (code) => {
92
+ if (!started) reject(new Error(`RisingWave exited with code ${code}`))
93
+ })
94
+
95
+ // Fallback: poll for connectivity
96
+ const poll = setInterval(async () => {
97
+ if (started) {
98
+ clearInterval(poll)
99
+ return
100
+ }
101
+ if (await isReachable('postgres://root@localhost:4566/dev')) {
102
+ started = true
103
+ clearInterval(poll)
104
+ console.log('RisingWave started (playground mode).')
105
+ resolve(child)
106
+ }
107
+ }, 1000)
108
+
109
+ // Timeout after 30 seconds
110
+ setTimeout(() => {
111
+ if (!started) {
112
+ clearInterval(poll)
113
+ child.kill()
114
+ reject(new Error('RisingWave failed to start within 30 seconds.'))
115
+ }
116
+ }, 30000)
117
+ })
118
+ }
119
+
120
+ function startDocker(): Promise<ChildProcess> {
121
+ return new Promise((resolve, reject) => {
122
+ console.log('Starting RisingWave via Docker...')
123
+
124
+ // Remove stale container if exists
125
+ try {
126
+ execSync('docker rm -f wavelet-risingwave', { stdio: 'ignore' })
127
+ } catch {}
128
+
129
+ const child = spawn('docker', [
130
+ 'run', '--name', 'wavelet-risingwave',
131
+ '-p', '4566:4566',
132
+ 'risingwavelabs/risingwave:latest',
133
+ 'playground',
134
+ ], {
135
+ stdio: ['ignore', 'pipe', 'pipe'],
136
+ detached: false,
137
+ })
138
+
139
+ let started = false
140
+
141
+ // Poll for connectivity
142
+ const poll = setInterval(async () => {
143
+ if (started) {
144
+ clearInterval(poll)
145
+ return
146
+ }
147
+ if (await isReachable('postgres://root@localhost:4566/dev')) {
148
+ started = true
149
+ clearInterval(poll)
150
+ console.log('RisingWave started via Docker.')
151
+ resolve(child)
152
+ }
153
+ }, 1000)
154
+
155
+ child.on('error', (err) => {
156
+ if (!started) {
157
+ clearInterval(poll)
158
+ reject(err)
159
+ }
160
+ })
161
+
162
+ child.on('exit', (code) => {
163
+ if (!started) {
164
+ clearInterval(poll)
165
+ reject(new Error(`Docker exited with code ${code}`))
166
+ }
167
+ })
168
+
169
+ setTimeout(() => {
170
+ if (!started) {
171
+ clearInterval(poll)
172
+ child.kill()
173
+ reject(new Error('RisingWave Docker container failed to start within 60 seconds.'))
174
+ }
175
+ }, 60000)
176
+ })
177
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": ["src"]
8
+ }