@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.
- package/dist/codegen.d.ts +3 -0
- package/dist/codegen.d.ts.map +1 -0
- package/dist/codegen.js +146 -0
- package/dist/codegen.js.map +1 -0
- package/dist/config-loader.d.ts +3 -0
- package/dist/config-loader.d.ts.map +1 -0
- package/dist/config-loader.js +18 -0
- package/dist/config-loader.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +208 -0
- package/dist/index.js.map +1 -0
- package/dist/risingwave-launcher.d.ts +3 -0
- package/dist/risingwave-launcher.d.ts.map +1 -0
- package/dist/risingwave-launcher.js +163 -0
- package/dist/risingwave-launcher.js.map +1 -0
- package/package.json +23 -0
- package/src/codegen.ts +167 -0
- package/src/config-loader.ts +20 -0
- package/src/index.ts +233 -0
- package/src/risingwave-launcher.ts +177 -0
- package/tsconfig.json +8 -0
|
@@ -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"}
|
package/dist/codegen.js
ADDED
|
@@ -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 @@
|
|
|
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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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 @@
|
|
|
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
|
+
}
|