@risingwave/wavelet-server 0.2.1 → 0.2.5
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/__tests__/cursor-parsing.test.d.ts +2 -0
- package/dist/__tests__/cursor-parsing.test.d.ts.map +1 -0
- package/dist/__tests__/cursor-parsing.test.js +64 -0
- package/dist/__tests__/cursor-parsing.test.js.map +1 -0
- package/dist/__tests__/http-api.test.d.ts +2 -0
- package/dist/__tests__/http-api.test.d.ts.map +1 -0
- package/dist/__tests__/http-api.test.js +135 -0
- package/dist/__tests__/http-api.test.js.map +1 -0
- package/dist/__tests__/integration.test.d.ts +2 -0
- package/dist/__tests__/integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration.test.js +229 -0
- package/dist/__tests__/integration.test.js.map +1 -0
- package/dist/__tests__/jwt.test.d.ts +2 -0
- package/dist/__tests__/jwt.test.d.ts.map +1 -0
- package/dist/__tests__/jwt.test.js +86 -0
- package/dist/__tests__/jwt.test.js.map +1 -0
- package/dist/__tests__/ws-fanout.test.d.ts +2 -0
- package/dist/__tests__/ws-fanout.test.d.ts.map +1 -0
- package/dist/__tests__/ws-fanout.test.js +127 -0
- package/dist/__tests__/ws-fanout.test.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 +25 -0
- package/dist/config-loader.js.map +1 -0
- package/dist/cursor-manager.d.ts +54 -0
- package/dist/cursor-manager.d.ts.map +1 -0
- package/dist/cursor-manager.js +263 -0
- package/dist/cursor-manager.js.map +1 -0
- package/dist/ddl-manager.d.ts +33 -0
- package/dist/ddl-manager.d.ts.map +1 -0
- package/dist/ddl-manager.js +364 -0
- package/dist/ddl-manager.js.map +1 -0
- package/dist/http-api.d.ts +21 -0
- package/dist/http-api.d.ts.map +1 -0
- package/dist/http-api.js +242 -0
- package/dist/http-api.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/jwt.d.ts +16 -0
- package/dist/jwt.d.ts.map +1 -0
- package/dist/jwt.js +87 -0
- package/dist/jwt.js.map +1 -0
- package/dist/server.d.ts +24 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +82 -0
- package/dist/server.js.map +1 -0
- package/dist/webhook.d.ts +9 -0
- package/dist/webhook.d.ts.map +1 -0
- package/dist/webhook.js +63 -0
- package/dist/webhook.js.map +1 -0
- package/dist/ws-fanout.d.ts +24 -0
- package/dist/ws-fanout.d.ts.map +1 -0
- package/dist/ws-fanout.js +198 -0
- package/dist/ws-fanout.js.map +1 -0
- package/package.json +16 -3
- package/src/__tests__/cursor-parsing.test.ts +0 -68
- package/src/__tests__/http-api.test.ts +0 -130
- package/src/__tests__/integration.test.ts +0 -217
- package/src/__tests__/jwt.test.ts +0 -62
- package/src/config-loader.ts +0 -28
- package/src/cursor-manager.ts +0 -209
- package/src/ddl-manager.ts +0 -408
- package/src/http-api.ts +0 -278
- package/src/index.ts +0 -31
- package/src/jwt.ts +0 -56
- package/src/server.ts +0 -89
- package/src/webhook.ts +0 -67
- package/src/ws-fanout.ts +0 -171
- package/tsconfig.json +0 -8
|
@@ -0,0 +1,364 @@
|
|
|
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.DdlManager = void 0;
|
|
7
|
+
const pg_1 = __importDefault(require("pg"));
|
|
8
|
+
const { Client } = pg_1.default;
|
|
9
|
+
const COLUMN_TYPE_MAP = {
|
|
10
|
+
string: 'VARCHAR',
|
|
11
|
+
int: 'INT',
|
|
12
|
+
float: 'DOUBLE',
|
|
13
|
+
boolean: 'BOOLEAN',
|
|
14
|
+
timestamp: 'TIMESTAMPTZ',
|
|
15
|
+
json: 'JSONB',
|
|
16
|
+
};
|
|
17
|
+
function normalizeSql(sql) {
|
|
18
|
+
// RisingWave stores definitions as "CREATE MATERIALIZED VIEW name AS SELECT ..."
|
|
19
|
+
// Strip the prefix to compare just the query part
|
|
20
|
+
const stripped = sql.replace(/^create\s+materialized\s+view\s+\S+\s+as\s+/i, '');
|
|
21
|
+
return stripped.replace(/\s+/g, ' ').trim().toLowerCase();
|
|
22
|
+
}
|
|
23
|
+
function getQuerySql(queryDef) {
|
|
24
|
+
if ('_tag' in queryDef && queryDef._tag === 'sql')
|
|
25
|
+
return queryDef.text;
|
|
26
|
+
return queryDef.query.text;
|
|
27
|
+
}
|
|
28
|
+
function buildCreateTableSql(name, eventDef) {
|
|
29
|
+
const cols = Object.entries(eventDef.columns)
|
|
30
|
+
.map(([colName, colType]) => {
|
|
31
|
+
const sqlType = COLUMN_TYPE_MAP[colType];
|
|
32
|
+
if (!sqlType)
|
|
33
|
+
throw new Error(`Unknown column type "${colType}" for column "${colName}"`);
|
|
34
|
+
return `${colName} ${sqlType}`;
|
|
35
|
+
})
|
|
36
|
+
.join(', ');
|
|
37
|
+
return `CREATE TABLE ${name} (${cols})`;
|
|
38
|
+
}
|
|
39
|
+
class DdlManager {
|
|
40
|
+
connectionString;
|
|
41
|
+
client = null;
|
|
42
|
+
constructor(connectionString) {
|
|
43
|
+
this.connectionString = connectionString;
|
|
44
|
+
}
|
|
45
|
+
async connect() {
|
|
46
|
+
this.client = new Client({ connectionString: this.connectionString });
|
|
47
|
+
await this.client.connect();
|
|
48
|
+
console.log('[ddl-manager] Connected to RisingWave');
|
|
49
|
+
}
|
|
50
|
+
async close() {
|
|
51
|
+
await this.client?.end();
|
|
52
|
+
this.client = null;
|
|
53
|
+
console.log('[ddl-manager] Connection closed');
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Sync all events, queries, and subscriptions to match the config.
|
|
57
|
+
* Returns a list of actions taken.
|
|
58
|
+
* Idempotent - safe to call multiple times.
|
|
59
|
+
*/
|
|
60
|
+
async sync(config) {
|
|
61
|
+
if (!this.client)
|
|
62
|
+
throw new Error('Not connected - call connect() first');
|
|
63
|
+
const actions = [];
|
|
64
|
+
// 1. Fetch existing state from RisingWave
|
|
65
|
+
const existingTables = await this.getExistingTables();
|
|
66
|
+
const existingMVs = await this.getExistingMaterializedViews();
|
|
67
|
+
const existingSubscriptions = await this.getExistingSubscriptions();
|
|
68
|
+
const desiredEvents = config.events ?? config.streams ?? {};
|
|
69
|
+
const desiredSources = config.sources ?? {};
|
|
70
|
+
const desiredQueries = config.queries ?? config.views ?? {};
|
|
71
|
+
// 2. Determine which events (tables) to create or remove
|
|
72
|
+
const desiredEventNames = new Set(Object.keys(desiredEvents));
|
|
73
|
+
const desiredQueryNames = new Set(Object.keys(desiredQueries));
|
|
74
|
+
// 3. Sync events - create missing tables
|
|
75
|
+
for (const [eventName, eventDef] of Object.entries(desiredEvents)) {
|
|
76
|
+
if (existingTables.has(eventName)) {
|
|
77
|
+
actions.push({ type: 'unchanged', resource: 'event', name: eventName });
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
await this.createTable(eventName, eventDef);
|
|
81
|
+
actions.push({ type: 'create', resource: 'event', name: eventName });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// 3b. Sync CDC sources - create Postgres CDC tables
|
|
85
|
+
const existingSources = await this.getExistingSources();
|
|
86
|
+
for (const [sourceName, sourceDef] of Object.entries(desiredSources)) {
|
|
87
|
+
if (sourceDef.type === 'postgres') {
|
|
88
|
+
for (const tableName of sourceDef.tables) {
|
|
89
|
+
const cdcTableName = `${sourceName}_${tableName}`;
|
|
90
|
+
if (existingSources.has(cdcTableName) || existingTables.has(cdcTableName)) {
|
|
91
|
+
actions.push({ type: 'unchanged', resource: 'source', name: cdcTableName });
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
await this.createCdcSource(sourceName, tableName, sourceDef);
|
|
95
|
+
actions.push({ type: 'create', resource: 'source', name: cdcTableName });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// 4. Sync queries - create, update, or leave unchanged
|
|
101
|
+
for (const [queryName, queryDef] of Object.entries(desiredQueries)) {
|
|
102
|
+
const subName = `wavelet_sub_${queryName}`;
|
|
103
|
+
const desiredSql = getQuerySql(queryDef);
|
|
104
|
+
const existingSql = existingMVs.get(queryName);
|
|
105
|
+
if (existingSql === undefined) {
|
|
106
|
+
// MV does not exist - create MV and subscription
|
|
107
|
+
await this.createMaterializedView(queryName, desiredSql);
|
|
108
|
+
actions.push({ type: 'create', resource: 'query', name: queryName });
|
|
109
|
+
await this.createSubscription(subName, queryName);
|
|
110
|
+
actions.push({ type: 'create', resource: 'subscription', name: subName });
|
|
111
|
+
}
|
|
112
|
+
else if (normalizeSql(existingSql) !== normalizeSql(desiredSql)) {
|
|
113
|
+
// Query SQL changed - drop subscription, drop MV, recreate
|
|
114
|
+
if (existingSubscriptions.has(subName)) {
|
|
115
|
+
await this.dropSubscription(subName);
|
|
116
|
+
actions.push({ type: 'delete', resource: 'subscription', name: subName, detail: 'dropped for query update' });
|
|
117
|
+
}
|
|
118
|
+
await this.dropMaterializedView(queryName);
|
|
119
|
+
actions.push({ type: 'delete', resource: 'query', name: queryName, detail: 'dropped for update' });
|
|
120
|
+
await this.createMaterializedView(queryName, desiredSql);
|
|
121
|
+
actions.push({ type: 'create', resource: 'query', name: queryName, detail: 'recreated with updated SQL' });
|
|
122
|
+
await this.createSubscription(subName, queryName);
|
|
123
|
+
actions.push({ type: 'create', resource: 'subscription', name: subName, detail: 'recreated after query update' });
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
// Query SQL unchanged
|
|
127
|
+
actions.push({ type: 'unchanged', resource: 'query', name: queryName });
|
|
128
|
+
// Ensure subscription exists even if the query is unchanged
|
|
129
|
+
if (!existingSubscriptions.has(subName)) {
|
|
130
|
+
await this.createSubscription(subName, queryName);
|
|
131
|
+
actions.push({ type: 'create', resource: 'subscription', name: subName });
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
actions.push({ type: 'unchanged', resource: 'subscription', name: subName });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// 5. Remove queries that are no longer in the config
|
|
139
|
+
for (const [existingMVName] of existingMVs) {
|
|
140
|
+
if (!desiredQueryNames.has(existingMVName)) {
|
|
141
|
+
const subName = `wavelet_sub_${existingMVName}`;
|
|
142
|
+
// Drop subscription first
|
|
143
|
+
if (existingSubscriptions.has(subName)) {
|
|
144
|
+
await this.dropSubscription(subName);
|
|
145
|
+
actions.push({ type: 'delete', resource: 'subscription', name: subName, detail: 'query removed from config' });
|
|
146
|
+
}
|
|
147
|
+
await this.dropMaterializedView(existingMVName);
|
|
148
|
+
actions.push({ type: 'delete', resource: 'query', name: existingMVName, detail: 'removed from config' });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// 6. Remove orphaned subscriptions that are no longer needed
|
|
152
|
+
for (const existingSubName of existingSubscriptions) {
|
|
153
|
+
// Only manage wavelet-prefixed subscriptions
|
|
154
|
+
if (!existingSubName.startsWith('wavelet_sub_'))
|
|
155
|
+
continue;
|
|
156
|
+
const queryName = existingSubName.slice('wavelet_sub_'.length);
|
|
157
|
+
if (!desiredQueryNames.has(queryName)) {
|
|
158
|
+
// Already handled in step 5 if the MV existed, but handle dangling subs too
|
|
159
|
+
if (!existingMVs.has(queryName)) {
|
|
160
|
+
await this.dropSubscription(existingSubName);
|
|
161
|
+
actions.push({ type: 'delete', resource: 'subscription', name: existingSubName, detail: 'orphaned subscription' });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// 7. Remove events (tables) that are no longer in the config
|
|
166
|
+
for (const existingTableName of existingTables) {
|
|
167
|
+
if (!desiredEventNames.has(existingTableName)) {
|
|
168
|
+
// Only drop if no MV depends on it
|
|
169
|
+
const hasDependents = await this.tableHasDependentMVs(existingTableName);
|
|
170
|
+
if (hasDependents) {
|
|
171
|
+
console.log(`[ddl-manager] Skipping drop of table "${existingTableName}" - materialized views depend on it`);
|
|
172
|
+
actions.push({
|
|
173
|
+
type: 'unchanged',
|
|
174
|
+
resource: 'event',
|
|
175
|
+
name: existingTableName,
|
|
176
|
+
detail: 'kept because dependent materialized views exist',
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
await this.dropTable(existingTableName);
|
|
181
|
+
actions.push({ type: 'delete', resource: 'event', name: existingTableName, detail: 'removed from config' });
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return actions;
|
|
186
|
+
}
|
|
187
|
+
// ── Query helpers ─────────────────────────────────────────────────────
|
|
188
|
+
async getExistingTables() {
|
|
189
|
+
const result = await this.client.query(`SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type = 'BASE TABLE'`);
|
|
190
|
+
return new Set(result.rows.map((r) => r.table_name));
|
|
191
|
+
}
|
|
192
|
+
async getExistingMaterializedViews() {
|
|
193
|
+
const result = await this.client.query(`SELECT name, definition FROM rw_catalog.rw_materialized_views WHERE schema_id = (SELECT id FROM rw_catalog.rw_schemas WHERE name = 'public')`);
|
|
194
|
+
const views = new Map();
|
|
195
|
+
for (const row of result.rows) {
|
|
196
|
+
views.set(row.name, row.definition);
|
|
197
|
+
}
|
|
198
|
+
return views;
|
|
199
|
+
}
|
|
200
|
+
async getExistingSources() {
|
|
201
|
+
try {
|
|
202
|
+
const result = await this.client.query(`SELECT name FROM rw_catalog.rw_tables WHERE schema_id = (SELECT id FROM rw_catalog.rw_schemas WHERE name = 'public') AND is_index = false`);
|
|
203
|
+
return new Set(result.rows.map((r) => r.name));
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
// Fallback: if catalog query fails, return empty set
|
|
207
|
+
return new Set();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
async getExistingSubscriptions() {
|
|
211
|
+
const result = await this.client.query(`SELECT name FROM rw_catalog.rw_subscriptions WHERE schema_id = (SELECT id FROM rw_catalog.rw_schemas WHERE name = 'public')`);
|
|
212
|
+
return new Set(result.rows.map((r) => r.name));
|
|
213
|
+
}
|
|
214
|
+
async tableHasDependentMVs(tableName) {
|
|
215
|
+
const result = await this.client.query(`SELECT name FROM rw_catalog.rw_materialized_views WHERE schema_id = (SELECT id FROM rw_catalog.rw_schemas WHERE name = 'public') AND definition ILIKE $1`, [`%${tableName}%`]);
|
|
216
|
+
return result.rows.length > 0;
|
|
217
|
+
}
|
|
218
|
+
// ── DDL operations ────────────────────────────────────────────────────
|
|
219
|
+
async createTable(name, eventDef) {
|
|
220
|
+
const sql = buildCreateTableSql(name, eventDef);
|
|
221
|
+
try {
|
|
222
|
+
await this.client.query(sql);
|
|
223
|
+
console.log(`[ddl-manager] Created table: ${name}`);
|
|
224
|
+
}
|
|
225
|
+
catch (err) {
|
|
226
|
+
if (err.message?.includes('already exists')) {
|
|
227
|
+
console.log(`[ddl-manager] Table already exists: ${name}`);
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
throw err;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
async dropTable(name) {
|
|
235
|
+
try {
|
|
236
|
+
await this.client.query(`DROP TABLE ${name}`);
|
|
237
|
+
console.log(`[ddl-manager] Dropped table: ${name}`);
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
if (err.message?.includes('does not exist')) {
|
|
241
|
+
console.log(`[ddl-manager] Table already gone: ${name}`);
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
throw err;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
async createMaterializedView(name, sql) {
|
|
249
|
+
try {
|
|
250
|
+
await this.client.query(`CREATE MATERIALIZED VIEW ${name} AS ${sql}`);
|
|
251
|
+
console.log(`[ddl-manager] Created materialized view: ${name}`);
|
|
252
|
+
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
if (err.message?.includes('already exists')) {
|
|
255
|
+
console.log(`[ddl-manager] Materialized view already exists: ${name}`);
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
throw err;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
async dropMaterializedView(name) {
|
|
263
|
+
try {
|
|
264
|
+
await this.client.query(`DROP MATERIALIZED VIEW ${name}`);
|
|
265
|
+
console.log(`[ddl-manager] Dropped materialized view: ${name}`);
|
|
266
|
+
}
|
|
267
|
+
catch (err) {
|
|
268
|
+
if (err.message?.includes('does not exist')) {
|
|
269
|
+
console.log(`[ddl-manager] Materialized view already gone: ${name}`);
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
throw err;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
async createSubscription(subName, viewName) {
|
|
277
|
+
try {
|
|
278
|
+
await this.client.query(`CREATE SUBSCRIPTION ${subName} FROM ${viewName} WITH (retention = '24h')`);
|
|
279
|
+
console.log(`[ddl-manager] Created subscription: ${subName}`);
|
|
280
|
+
}
|
|
281
|
+
catch (err) {
|
|
282
|
+
if (err.message?.includes('already exists')) {
|
|
283
|
+
console.log(`[ddl-manager] Subscription already exists: ${subName}`);
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
throw err;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
async dropSubscription(subName) {
|
|
291
|
+
try {
|
|
292
|
+
await this.client.query(`DROP SUBSCRIPTION ${subName}`);
|
|
293
|
+
console.log(`[ddl-manager] Dropped subscription: ${subName}`);
|
|
294
|
+
}
|
|
295
|
+
catch (err) {
|
|
296
|
+
if (err.message?.includes('does not exist')) {
|
|
297
|
+
console.log(`[ddl-manager] Subscription already gone: ${subName}`);
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
throw err;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
async createCdcSource(sourceName, tableName, source) {
|
|
305
|
+
const cdcTableName = `${sourceName}_${tableName}`;
|
|
306
|
+
const slotName = source.slotName ?? `wavelet_${sourceName}`;
|
|
307
|
+
const pubName = source.publicationName ?? `wavelet_${sourceName}_pub`;
|
|
308
|
+
// RisingWave CDC source syntax:
|
|
309
|
+
// CREATE TABLE table_name ( ... ) WITH (
|
|
310
|
+
// connector = 'postgres-cdc',
|
|
311
|
+
// hostname = '...',
|
|
312
|
+
// port = '...',
|
|
313
|
+
// username = '...',
|
|
314
|
+
// password = '...',
|
|
315
|
+
// database.name = '...',
|
|
316
|
+
// table.name = '...',
|
|
317
|
+
// slot.name = '...',
|
|
318
|
+
// publication.name = '...'
|
|
319
|
+
// )
|
|
320
|
+
// We parse the connection string to extract components.
|
|
321
|
+
const parsed = parsePostgresUrl(source.connection);
|
|
322
|
+
const esc = (s) => s.replace(/'/g, "''");
|
|
323
|
+
try {
|
|
324
|
+
await this.client.query(`
|
|
325
|
+
CREATE TABLE IF NOT EXISTS ${cdcTableName} (*)
|
|
326
|
+
WITH (
|
|
327
|
+
connector = 'postgres-cdc',
|
|
328
|
+
hostname = '${esc(parsed.host)}',
|
|
329
|
+
port = '${esc(parsed.port)}',
|
|
330
|
+
username = '${esc(parsed.user)}',
|
|
331
|
+
password = '${esc(parsed.password)}',
|
|
332
|
+
database.name = '${esc(parsed.database)}',
|
|
333
|
+
schema.name = '${esc(parsed.schema)}',
|
|
334
|
+
table.name = '${esc(tableName)}',
|
|
335
|
+
slot.name = '${esc(slotName)}',
|
|
336
|
+
publication.name = '${esc(pubName)}'
|
|
337
|
+
)
|
|
338
|
+
`);
|
|
339
|
+
console.log(`[ddl-manager] Created CDC source: ${cdcTableName} (from ${parsed.host}/${parsed.database}.${tableName})`);
|
|
340
|
+
}
|
|
341
|
+
catch (err) {
|
|
342
|
+
if (err.message?.includes('already exists')) {
|
|
343
|
+
console.log(`[ddl-manager] CDC source already exists: ${cdcTableName}`);
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
throw err;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
exports.DdlManager = DdlManager;
|
|
352
|
+
function parsePostgresUrl(url) {
|
|
353
|
+
// Parse postgresql://user:password@host:port/database?schema=xxx
|
|
354
|
+
const u = new URL(url);
|
|
355
|
+
return {
|
|
356
|
+
host: u.hostname,
|
|
357
|
+
port: u.port || '5432',
|
|
358
|
+
user: decodeURIComponent(u.username),
|
|
359
|
+
password: decodeURIComponent(u.password),
|
|
360
|
+
database: u.pathname.replace(/^\//, '') || 'postgres',
|
|
361
|
+
schema: u.searchParams.get('schema') ?? 'public',
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
//# sourceMappingURL=ddl-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ddl-manager.js","sourceRoot":"","sources":["../src/ddl-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import type { EventDef, QueryDef, SqlFragment } from '@risingwave/wavelet';
|
|
3
|
+
import type { JwtVerifier } from './jwt.js';
|
|
4
|
+
export declare class HttpApi {
|
|
5
|
+
private connectionString;
|
|
6
|
+
private events;
|
|
7
|
+
private queries;
|
|
8
|
+
private jwt?;
|
|
9
|
+
private pool;
|
|
10
|
+
constructor(connectionString: string, events: Record<string, EventDef>, queries: Record<string, QueryDef | SqlFragment>, jwt?: JwtVerifier | undefined);
|
|
11
|
+
handle(req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
12
|
+
private ensurePool;
|
|
13
|
+
private handleWrite;
|
|
14
|
+
private handleBatchWrite;
|
|
15
|
+
private handleRead;
|
|
16
|
+
private getFilterBy;
|
|
17
|
+
private getQueryColumns;
|
|
18
|
+
private json;
|
|
19
|
+
private readBody;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=http-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-api.d.ts","sourceRoot":"","sources":["../src/http-api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAEhE,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAC1E,OAAO,KAAK,EAAE,WAAW,EAAa,MAAM,UAAU,CAAA;AAMtD,qBAAa,OAAO;IAIhB,OAAO,CAAC,gBAAgB;IACxB,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,GAAG,CAAC;IANd,OAAO,CAAC,IAAI,CAAyC;gBAG3C,gBAAgB,EAAE,MAAM,EACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,EAChC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,GAAG,WAAW,CAAC,EAC/C,GAAG,CAAC,EAAE,WAAW,YAAA;IAGrB,MAAM,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAwEtE,OAAO,CAAC,UAAU;YAOJ,WAAW;YA2BX,gBAAgB;YAiDhB,UAAU;IAqExB,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,IAAI;IAKZ,OAAO,CAAC,QAAQ;CAiBjB"}
|
package/dist/http-api.js
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
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.HttpApi = void 0;
|
|
7
|
+
const pg_1 = __importDefault(require("pg"));
|
|
8
|
+
const { Pool } = pg_1.default;
|
|
9
|
+
const MAX_BODY_SIZE = 10 * 1024 * 1024; // 10MB
|
|
10
|
+
class HttpApi {
|
|
11
|
+
connectionString;
|
|
12
|
+
events;
|
|
13
|
+
queries;
|
|
14
|
+
jwt;
|
|
15
|
+
pool = null;
|
|
16
|
+
constructor(connectionString, events, queries, jwt) {
|
|
17
|
+
this.connectionString = connectionString;
|
|
18
|
+
this.events = events;
|
|
19
|
+
this.queries = queries;
|
|
20
|
+
this.jwt = jwt;
|
|
21
|
+
}
|
|
22
|
+
async handle(req, res) {
|
|
23
|
+
// CORS
|
|
24
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
25
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
26
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
27
|
+
if (req.method === 'OPTIONS') {
|
|
28
|
+
res.writeHead(204);
|
|
29
|
+
res.end();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const url = new URL(req.url ?? '/', `http://${req.headers.host}`);
|
|
33
|
+
try {
|
|
34
|
+
// POST /v1/events/{name}
|
|
35
|
+
const eventMatch = url.pathname.match(/^\/v1\/events\/([^/]+)$/);
|
|
36
|
+
if (eventMatch && req.method === 'POST') {
|
|
37
|
+
await this.handleWrite(eventMatch[1], req, res);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
// POST /v1/events/{name}/batch
|
|
41
|
+
const batchMatch = url.pathname.match(/^\/v1\/events\/([^/]+)\/batch$/);
|
|
42
|
+
if (batchMatch && req.method === 'POST') {
|
|
43
|
+
await this.handleBatchWrite(batchMatch[1], req, res);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
// GET /v1/queries/{name}
|
|
47
|
+
const queryMatch = url.pathname.match(/^\/v1\/queries\/([^/]+)$/);
|
|
48
|
+
if (queryMatch && req.method === 'GET') {
|
|
49
|
+
await this.handleRead(queryMatch[1], url, req, res);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
// GET /v1/health
|
|
53
|
+
if (url.pathname === '/v1/health') {
|
|
54
|
+
this.json(res, 200, { status: 'ok' });
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
// GET /v1/queries - list available queries
|
|
58
|
+
if (url.pathname === '/v1/queries' && req.method === 'GET') {
|
|
59
|
+
this.json(res, 200, { queries: Object.keys(this.queries) });
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
// GET /v1/events - list available events
|
|
63
|
+
if (url.pathname === '/v1/events' && req.method === 'GET') {
|
|
64
|
+
this.json(res, 200, { events: Object.keys(this.events) });
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
this.json(res, 404, {
|
|
68
|
+
error: 'Not found',
|
|
69
|
+
message: `${req.method} ${url.pathname} does not match any route.`,
|
|
70
|
+
routes: [
|
|
71
|
+
'GET /v1/health',
|
|
72
|
+
'GET /v1/queries',
|
|
73
|
+
'GET /v1/queries/{name}',
|
|
74
|
+
'GET /v1/events',
|
|
75
|
+
'POST /v1/events/{name}',
|
|
76
|
+
'POST /v1/events/{name}/batch',
|
|
77
|
+
],
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
console.error('HTTP error:', err);
|
|
82
|
+
this.json(res, 500, { error: err.message });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
ensurePool() {
|
|
86
|
+
if (!this.pool) {
|
|
87
|
+
this.pool = new Pool({ connectionString: this.connectionString, max: 10 });
|
|
88
|
+
}
|
|
89
|
+
return this.pool;
|
|
90
|
+
}
|
|
91
|
+
async handleWrite(eventName, req, res) {
|
|
92
|
+
const eventDef = this.events[eventName];
|
|
93
|
+
if (!eventDef) {
|
|
94
|
+
const available = Object.keys(this.events);
|
|
95
|
+
this.json(res, 404, {
|
|
96
|
+
error: `Event '${eventName}' not found.`,
|
|
97
|
+
available_events: available,
|
|
98
|
+
});
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const body = await this.readBody(req);
|
|
102
|
+
const data = JSON.parse(body);
|
|
103
|
+
const pool = this.ensurePool();
|
|
104
|
+
const columns = Object.keys(eventDef.columns);
|
|
105
|
+
const values = columns.map((col) => data[col]);
|
|
106
|
+
const placeholders = columns.map((_, i) => `$${i + 1}`);
|
|
107
|
+
await pool.query(`INSERT INTO ${eventName} (${columns.join(', ')}) VALUES (${placeholders.join(', ')})`, values);
|
|
108
|
+
this.json(res, 200, { ok: true });
|
|
109
|
+
}
|
|
110
|
+
async handleBatchWrite(eventName, req, res) {
|
|
111
|
+
const eventDef = this.events[eventName];
|
|
112
|
+
if (!eventDef) {
|
|
113
|
+
const available = Object.keys(this.events);
|
|
114
|
+
this.json(res, 404, {
|
|
115
|
+
error: `Event '${eventName}' not found.`,
|
|
116
|
+
available_events: available,
|
|
117
|
+
});
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const body = await this.readBody(req);
|
|
121
|
+
const items = JSON.parse(body);
|
|
122
|
+
if (!Array.isArray(items)) {
|
|
123
|
+
this.json(res, 400, { error: 'Batch endpoint expects a JSON array.' });
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (items.length === 0) {
|
|
127
|
+
this.json(res, 200, { ok: true, count: 0 });
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const pool = this.ensurePool();
|
|
131
|
+
const columns = Object.keys(eventDef.columns);
|
|
132
|
+
// Build a single INSERT with multiple VALUE rows
|
|
133
|
+
const allValues = [];
|
|
134
|
+
const rowPlaceholders = [];
|
|
135
|
+
for (let i = 0; i < items.length; i++) {
|
|
136
|
+
const row = items[i];
|
|
137
|
+
const offset = i * columns.length;
|
|
138
|
+
const ph = columns.map((_, j) => `$${offset + j + 1}`);
|
|
139
|
+
rowPlaceholders.push(`(${ph.join(', ')})`);
|
|
140
|
+
for (const col of columns) {
|
|
141
|
+
allValues.push(row[col]);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
await pool.query(`INSERT INTO ${eventName} (${columns.join(', ')}) VALUES ${rowPlaceholders.join(', ')}`, allValues);
|
|
145
|
+
this.json(res, 200, { ok: true, count: items.length });
|
|
146
|
+
}
|
|
147
|
+
async handleRead(queryName, url, req, res) {
|
|
148
|
+
if (!this.queries[queryName]) {
|
|
149
|
+
const available = Object.keys(this.queries);
|
|
150
|
+
this.json(res, 404, {
|
|
151
|
+
error: `Query '${queryName}' not found.`,
|
|
152
|
+
available_queries: available,
|
|
153
|
+
});
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
// JWT verification for queries with filterBy
|
|
157
|
+
const queryDef = this.queries[queryName];
|
|
158
|
+
const filterBy = this.getFilterBy(queryDef);
|
|
159
|
+
let claims = null;
|
|
160
|
+
if (filterBy && this.jwt?.isConfigured()) {
|
|
161
|
+
const token = url.searchParams.get('token')
|
|
162
|
+
?? req.headers.authorization?.replace('Bearer ', '');
|
|
163
|
+
if (!token) {
|
|
164
|
+
this.json(res, 401, { error: 'Authentication required for filtered queries.' });
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
claims = await this.jwt.verify(token);
|
|
168
|
+
}
|
|
169
|
+
const pool = this.ensurePool();
|
|
170
|
+
// Build WHERE clause: start with filterBy if applicable
|
|
171
|
+
const params = [];
|
|
172
|
+
const values = [];
|
|
173
|
+
if (filterBy && claims) {
|
|
174
|
+
const claimValue = claims[filterBy];
|
|
175
|
+
if (claimValue === undefined) {
|
|
176
|
+
// No matching claim -- return empty result, not all data
|
|
177
|
+
this.json(res, 200, { query: queryName, rows: [] });
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
values.push(String(claimValue));
|
|
181
|
+
params.push(`${filterBy} = $${values.length}`);
|
|
182
|
+
}
|
|
183
|
+
// Add query params as additional filters, validating column names
|
|
184
|
+
const knownColumns = this.getQueryColumns(queryDef);
|
|
185
|
+
for (const [key, value] of url.searchParams.entries()) {
|
|
186
|
+
if (key === 'token')
|
|
187
|
+
continue; // skip JWT token param
|
|
188
|
+
if (knownColumns && !knownColumns.includes(key)) {
|
|
189
|
+
this.json(res, 400, { error: `Unknown column '${key}'. Known columns: ${knownColumns.join(', ')}` });
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key)) {
|
|
193
|
+
this.json(res, 400, { error: `Invalid column name: '${key}'` });
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
values.push(value);
|
|
197
|
+
params.push(`${key} = $${values.length}`);
|
|
198
|
+
}
|
|
199
|
+
let sql = `SELECT * FROM ${queryName}`;
|
|
200
|
+
if (params.length > 0) {
|
|
201
|
+
sql += ` WHERE ${params.join(' AND ')}`;
|
|
202
|
+
}
|
|
203
|
+
const result = await pool.query(sql, values);
|
|
204
|
+
this.json(res, 200, { query: queryName, rows: result.rows });
|
|
205
|
+
}
|
|
206
|
+
getFilterBy(queryDef) {
|
|
207
|
+
if ('_tag' in queryDef && queryDef._tag === 'sql')
|
|
208
|
+
return undefined;
|
|
209
|
+
return queryDef.filterBy;
|
|
210
|
+
}
|
|
211
|
+
getQueryColumns(queryDef) {
|
|
212
|
+
if ('_tag' in queryDef && queryDef._tag === 'sql')
|
|
213
|
+
return null;
|
|
214
|
+
const qd = queryDef;
|
|
215
|
+
if (qd.columns)
|
|
216
|
+
return Object.keys(qd.columns);
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
json(res, status, data) {
|
|
220
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
221
|
+
res.end(JSON.stringify(data));
|
|
222
|
+
}
|
|
223
|
+
readBody(req) {
|
|
224
|
+
return new Promise((resolve, reject) => {
|
|
225
|
+
let body = '';
|
|
226
|
+
let size = 0;
|
|
227
|
+
req.on('data', (chunk) => {
|
|
228
|
+
size += chunk.length;
|
|
229
|
+
if (size > MAX_BODY_SIZE) {
|
|
230
|
+
req.destroy();
|
|
231
|
+
reject(new Error(`Request body exceeds ${MAX_BODY_SIZE / 1024 / 1024}MB limit`));
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
body += chunk;
|
|
235
|
+
});
|
|
236
|
+
req.on('end', () => resolve(body));
|
|
237
|
+
req.on('error', reject);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
exports.HttpApi = HttpApi;
|
|
242
|
+
//# sourceMappingURL=http-api.js.map
|