@malloy-publisher/server 0.0.88 → 0.0.89
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/build.ts +1 -0
- package/dist/app/api-doc.yaml +43 -5
- package/dist/app/assets/RenderedResult-BAZuT25g-QakVAbYy.js +2 -0
- package/dist/app/assets/{index-CNRCklos.js → index-Bq29VQqL.js} +1 -1
- package/dist/app/assets/index-DZMePHJ5.js +251 -0
- package/dist/app/assets/{index-lhN38COk.js → index-TslDWlxH.js} +5 -5
- package/dist/app/assets/{index.umd-CSPhcx55.js → index.umd-BN4_E5KD.js} +1 -1
- package/dist/app/assets/mui-BEbinrI-.js +161 -0
- package/dist/app/assets/vendor-c5ypKtDW.js +17 -0
- package/dist/app/index.html +3 -1
- package/dist/instrumentation.js +28 -4
- package/dist/server.js +38461 -48955
- package/package.json +5 -2
- package/src/constants.ts +9 -1
- package/src/controller/connection.controller.ts +21 -4
- package/src/controller/package.controller.ts +12 -1
- package/src/controller/schedule.controller.ts +3 -3
- package/src/logger.ts +9 -0
- package/src/server.ts +36 -30
- package/src/service/connection.ts +152 -154
- package/src/service/package.spec.ts +12 -10
- package/src/service/package.ts +11 -4
- package/src/service/project.ts +72 -33
- package/src/service/project_store.spec.ts +73 -51
- package/src/service/project_store.ts +76 -11
- package/tests/harness/mcp_test_setup.ts +5 -5
- package/dist/app/assets/RenderedResult-BAZuT25g-DLMDvQic.js +0 -2
- package/dist/app/assets/index-CobAY3LE.js +0 -427
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@malloy-publisher/server",
|
|
3
3
|
"description": "Malloy Publisher Server",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.89",
|
|
5
5
|
"main": "dist/server.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"malloy-publisher": "dist/server.js"
|
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
"access": "public"
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
|
-
"test": "bun test
|
|
13
|
+
"test": "bun test --timeout 100000",
|
|
14
14
|
"build": "bun generate-api-types && bun build:app && NODE_ENV=production bun run build.ts",
|
|
15
|
+
"build:server-only": "bun generate-api-types && NODE_ENV=production bun run build.ts",
|
|
15
16
|
"start": "NODE_ENV=production node ./dist/server.js",
|
|
16
17
|
"start:dev": "NODE_ENV=development bun --watch src/server.ts",
|
|
17
18
|
"start:instrumented": "node --require ./dist/instrumentation.js ./dist/server.js",
|
|
@@ -37,6 +38,7 @@
|
|
|
37
38
|
"@opentelemetry/sdk-metrics": "^2.0.0",
|
|
38
39
|
"@opentelemetry/sdk-node": "^0.200.0",
|
|
39
40
|
"@opentelemetry/sdk-trace-node": "^2.0.0",
|
|
41
|
+
"adm-zip": "^0.5.16",
|
|
40
42
|
"async-mutex": "^0.5.0",
|
|
41
43
|
"aws-sdk": "^2.1692.0",
|
|
42
44
|
"body-parser": "^1.20.2",
|
|
@@ -60,6 +62,7 @@
|
|
|
60
62
|
"@eslint/eslintrc": "^3.3.1",
|
|
61
63
|
"@eslint/js": "^9.23.0",
|
|
62
64
|
"@faker-js/faker": "^9.4.0",
|
|
65
|
+
"@types/adm-zip": "^0.5.7",
|
|
63
66
|
"@types/cors": "^2.8.12",
|
|
64
67
|
"@types/express": "^4.17.14",
|
|
65
68
|
"@types/morgan": "^1.9.9",
|
package/src/constants.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fs from "fs";
|
|
1
2
|
export const API_PREFIX = "/api/v0";
|
|
2
3
|
export const README_NAME = "README.md";
|
|
3
4
|
export const PUBLISHER_CONFIG_NAME = "publisher.config.json";
|
|
@@ -5,4 +6,11 @@ export const PACKAGE_MANIFEST_NAME = "publisher.json";
|
|
|
5
6
|
export const CONNECTIONS_MANIFEST_NAME = "publisher.connections.json";
|
|
6
7
|
export const MODEL_FILE_SUFFIX = ".malloy";
|
|
7
8
|
export const NOTEBOOK_FILE_SUFFIX = ".malloynb";
|
|
8
|
-
export const ROW_LIMIT = 1000;
|
|
9
|
+
export const ROW_LIMIT = 1000;
|
|
10
|
+
|
|
11
|
+
export let publisherPath = "/etc/publisher";
|
|
12
|
+
try {
|
|
13
|
+
fs.accessSync(publisherPath, fs.constants.W_OK);
|
|
14
|
+
} catch (e) {
|
|
15
|
+
publisherPath = "/tmp/publisher";
|
|
16
|
+
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
RunSQLOptions,
|
|
3
|
+
TableSourceDef,
|
|
4
|
+
TestableConnection,
|
|
5
|
+
} from "@malloydata/malloy";
|
|
2
6
|
import { Connection, PersistSQLResults } from "@malloydata/malloy/connection";
|
|
3
|
-
import { TableSourceDef } from "../../../../../malloy/packages/malloy/dist";
|
|
4
7
|
import { components } from "../api";
|
|
5
8
|
import { ConnectionError } from "../errors";
|
|
6
9
|
import { logger } from "../logger";
|
|
@@ -11,6 +14,7 @@ import {
|
|
|
11
14
|
import { ProjectStore } from "../service/project_store";
|
|
12
15
|
|
|
13
16
|
type ApiConnection = components["schemas"]["Connection"];
|
|
17
|
+
type ApiConnectionStatus = components["schemas"]["ConnectionStatus"];
|
|
14
18
|
type ApiSqlSource = components["schemas"]["SqlSource"];
|
|
15
19
|
type ApiTableSource = components["schemas"]["TableSource"];
|
|
16
20
|
type ApiQueryData = components["schemas"]["QueryData"];
|
|
@@ -57,15 +61,28 @@ export class ConnectionController {
|
|
|
57
61
|
return getTablesForSchema(connection, schemaName);
|
|
58
62
|
}
|
|
59
63
|
|
|
60
|
-
public async testConnection(
|
|
64
|
+
public async testConnection(
|
|
65
|
+
projectName: string,
|
|
66
|
+
connectionName: string,
|
|
67
|
+
): Promise<ApiConnectionStatus> {
|
|
61
68
|
const project = await this.projectStore.getProject(projectName, false);
|
|
62
69
|
const connection = project.getMalloyConnection(
|
|
63
70
|
connectionName,
|
|
64
71
|
) as Connection;
|
|
65
72
|
try {
|
|
66
73
|
await (connection as TestableConnection).test();
|
|
74
|
+
return {
|
|
75
|
+
status: "ok",
|
|
76
|
+
errorMessage: "",
|
|
77
|
+
};
|
|
67
78
|
} catch (error) {
|
|
68
|
-
|
|
79
|
+
if (error instanceof ConnectionError) {
|
|
80
|
+
return {
|
|
81
|
+
status: "failed",
|
|
82
|
+
errorMessage: error.message,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
throw error;
|
|
69
86
|
}
|
|
70
87
|
}
|
|
71
88
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { components } from "../api";
|
|
2
|
+
import { publisherPath } from "../constants";
|
|
2
3
|
import { BadRequestError, FrozenConfigError } from "../errors";
|
|
3
4
|
import { ProjectStore } from "../service/project_store";
|
|
4
5
|
|
|
@@ -72,7 +73,7 @@ export class PackageController {
|
|
|
72
73
|
packageName: string,
|
|
73
74
|
packageLocation: string,
|
|
74
75
|
) {
|
|
75
|
-
const absoluteTargetPath =
|
|
76
|
+
const absoluteTargetPath = `${publisherPath}/${projectName}/${packageName}`;
|
|
76
77
|
if (
|
|
77
78
|
packageLocation.startsWith("https://") ||
|
|
78
79
|
packageLocation.startsWith("git@")
|
|
@@ -93,6 +94,16 @@ export class PackageController {
|
|
|
93
94
|
projectName,
|
|
94
95
|
absoluteTargetPath,
|
|
95
96
|
);
|
|
97
|
+
} else if (packageLocation.startsWith("/")) {
|
|
98
|
+
if (packageLocation.endsWith(".zip")) {
|
|
99
|
+
packageLocation =
|
|
100
|
+
await this.projectStore.unzipProject(packageLocation);
|
|
101
|
+
}
|
|
102
|
+
await this.projectStore.mountLocalDirectory(
|
|
103
|
+
packageLocation,
|
|
104
|
+
absoluteTargetPath,
|
|
105
|
+
projectName,
|
|
106
|
+
);
|
|
96
107
|
}
|
|
97
108
|
}
|
|
98
109
|
}
|
|
@@ -14,8 +14,8 @@ export class ScheduleController {
|
|
|
14
14
|
projectName: string,
|
|
15
15
|
packageName: string,
|
|
16
16
|
): Promise<ApiSchedule[]> {
|
|
17
|
-
const project = await this.projectStore
|
|
18
|
-
const p = await project
|
|
19
|
-
return p
|
|
17
|
+
const project = await this.projectStore?.getProject?.(projectName);
|
|
18
|
+
const p = await project?.getPackage?.(packageName);
|
|
19
|
+
return p?.listSchedules?.();
|
|
20
20
|
}
|
|
21
21
|
}
|
package/src/logger.ts
CHANGED
|
@@ -20,11 +20,20 @@ export const logger = winston.createLogger({
|
|
|
20
20
|
|
|
21
21
|
export const loggerMiddleware: RequestHandler = (req, res, next) => {
|
|
22
22
|
const startTime = performance.now();
|
|
23
|
+
const resJson = res.json;
|
|
24
|
+
res.json = (body: unknown) => {
|
|
25
|
+
res.locals.body = body;
|
|
26
|
+
return resJson.call(res, body);
|
|
27
|
+
};
|
|
23
28
|
res.on("finish", () => {
|
|
24
29
|
const endTime = performance.now();
|
|
25
30
|
logger.info(`${req.method} ${req.url}`, {
|
|
26
31
|
statusCode: res.statusCode,
|
|
27
32
|
duration: endTime - startTime,
|
|
33
|
+
payload: req.body,
|
|
34
|
+
response: res.locals.body,
|
|
35
|
+
params: req.params,
|
|
36
|
+
query: req.query,
|
|
28
37
|
});
|
|
29
38
|
});
|
|
30
39
|
next();
|
package/src/server.ts
CHANGED
|
@@ -92,7 +92,7 @@ const databaseController = new DatabaseController(projectStore);
|
|
|
92
92
|
const queryController = new QueryController(projectStore);
|
|
93
93
|
const scheduleController = new ScheduleController(projectStore);
|
|
94
94
|
|
|
95
|
-
const mcpApp = express();
|
|
95
|
+
export const mcpApp = express();
|
|
96
96
|
|
|
97
97
|
mcpApp.use(MCP_ENDPOINT, express.json());
|
|
98
98
|
mcpApp.use(MCP_ENDPOINT, cors());
|
|
@@ -208,6 +208,13 @@ const setVersionIdError = (res: express.Response) => {
|
|
|
208
208
|
app.use(cors());
|
|
209
209
|
app.use(bodyParser.json());
|
|
210
210
|
|
|
211
|
+
app.get(`${API_PREFIX}/status`, async (_req, res) => {
|
|
212
|
+
res.status(200).json({
|
|
213
|
+
timestamp: Date.now(),
|
|
214
|
+
projects: await projectStore.listProjects(),
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
211
218
|
app.get(`${API_PREFIX}/watch-mode/status`, watchModeController.getWatchStatus);
|
|
212
219
|
app.post(`${API_PREFIX}/watch-mode/start`, watchModeController.startWatching);
|
|
213
220
|
app.post(`${API_PREFIX}/watch-mode/stop`, watchModeController.stopWatchMode);
|
|
@@ -224,7 +231,9 @@ app.get(`${API_PREFIX}/projects`, async (_req, res) => {
|
|
|
224
231
|
|
|
225
232
|
app.post(`${API_PREFIX}/projects`, async (req, res) => {
|
|
226
233
|
try {
|
|
227
|
-
|
|
234
|
+
logger.info("Adding project", { body: req.body });
|
|
235
|
+
const project = await projectStore.addProject(req.body);
|
|
236
|
+
res.status(200).json(await project.serialize());
|
|
228
237
|
} catch (error) {
|
|
229
238
|
logger.error(error);
|
|
230
239
|
const { json, status } = internalErrorToHttpError(error as Error);
|
|
@@ -238,7 +247,7 @@ app.get(`${API_PREFIX}/projects/:projectName`, async (req, res) => {
|
|
|
238
247
|
req.params.projectName,
|
|
239
248
|
req.query.reload === "true",
|
|
240
249
|
);
|
|
241
|
-
res.status(200).json(project.
|
|
250
|
+
res.status(200).json(await project.serialize());
|
|
242
251
|
} catch (error) {
|
|
243
252
|
logger.error(error);
|
|
244
253
|
const { json, status } = internalErrorToHttpError(error as Error);
|
|
@@ -248,7 +257,8 @@ app.get(`${API_PREFIX}/projects/:projectName`, async (req, res) => {
|
|
|
248
257
|
|
|
249
258
|
app.patch(`${API_PREFIX}/projects/:projectName`, async (req, res) => {
|
|
250
259
|
try {
|
|
251
|
-
|
|
260
|
+
const project = await projectStore.updateProject(req.body);
|
|
261
|
+
res.status(200).json(await project.serialize());
|
|
252
262
|
} catch (error) {
|
|
253
263
|
logger.error(error);
|
|
254
264
|
const { json, status } = internalErrorToHttpError(error as Error);
|
|
@@ -258,9 +268,8 @@ app.patch(`${API_PREFIX}/projects/:projectName`, async (req, res) => {
|
|
|
258
268
|
|
|
259
269
|
app.delete(`${API_PREFIX}/projects/:projectName`, async (req, res) => {
|
|
260
270
|
try {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
);
|
|
271
|
+
const project = await projectStore.deleteProject(req.params.projectName);
|
|
272
|
+
res.status(200).json(await project.serialize());
|
|
264
273
|
} catch (error) {
|
|
265
274
|
logger.error(error);
|
|
266
275
|
const { json, status } = internalErrorToHttpError(error as Error);
|
|
@@ -302,12 +311,11 @@ app.get(
|
|
|
302
311
|
`${API_PREFIX}/projects/:projectName/connections/:connectionName/test`,
|
|
303
312
|
async (req, res) => {
|
|
304
313
|
try {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
req.params.connectionName,
|
|
309
|
-
),
|
|
314
|
+
const connectionStatus = await connectionController.testConnection(
|
|
315
|
+
req.params.projectName,
|
|
316
|
+
req.params.connectionName,
|
|
310
317
|
);
|
|
318
|
+
res.status(200).json(connectionStatus);
|
|
311
319
|
} catch (error) {
|
|
312
320
|
logger.error(error);
|
|
313
321
|
const { json, status } = internalErrorToHttpError(error as Error);
|
|
@@ -451,9 +459,11 @@ app.get(`${API_PREFIX}/projects/:projectName/packages`, async (req, res) => {
|
|
|
451
459
|
|
|
452
460
|
app.post(`${API_PREFIX}/projects/:projectName/packages`, async (req, res) => {
|
|
453
461
|
try {
|
|
454
|
-
|
|
455
|
-
|
|
462
|
+
const _package = await packageController.addPackage(
|
|
463
|
+
req.params.projectName,
|
|
464
|
+
req.body,
|
|
456
465
|
);
|
|
466
|
+
res.status(200).json(_package?.getPackageMetadata());
|
|
457
467
|
} catch (error) {
|
|
458
468
|
logger.error(error);
|
|
459
469
|
const { json, status } = internalErrorToHttpError(error as Error);
|
|
@@ -711,22 +721,18 @@ app.use(
|
|
|
711
721
|
);
|
|
712
722
|
|
|
713
723
|
const mainServer = http.createServer(app);
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
logger.info(
|
|
722
|
-
"Running in development mode - proxying to React dev server at http://localhost:5173",
|
|
723
|
-
);
|
|
724
|
-
}
|
|
725
|
-
});
|
|
726
|
-
mcpApp.listen(MCP_PORT, PUBLISHER_HOST, () => {
|
|
724
|
+
|
|
725
|
+
mainServer.listen(PUBLISHER_PORT, PUBLISHER_HOST, () => {
|
|
726
|
+
const address = mainServer.address() as AddressInfo;
|
|
727
|
+
logger.info(
|
|
728
|
+
`Publisher server listening at http://${address.address}:${address.port}`,
|
|
729
|
+
);
|
|
730
|
+
if (isDevelopment) {
|
|
727
731
|
logger.info(
|
|
728
|
-
|
|
732
|
+
"Running in development mode - proxying to React dev server at http://localhost:5173",
|
|
729
733
|
);
|
|
730
|
-
}
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
mcpApp.listen(MCP_PORT, PUBLISHER_HOST, () => {
|
|
737
|
+
logger.info(`MCP server listening at http://${PUBLISHER_HOST}:${MCP_PORT}`);
|
|
731
738
|
});
|
|
732
|
-
|
|
@@ -10,6 +10,7 @@ import path from "path";
|
|
|
10
10
|
import { v4 as uuidv4 } from "uuid";
|
|
11
11
|
import { components } from "../api";
|
|
12
12
|
import { CONNECTIONS_MANIFEST_NAME } from "../constants";
|
|
13
|
+
import { logger } from "../logger";
|
|
13
14
|
|
|
14
15
|
type ApiConnection = components["schemas"]["Connection"];
|
|
15
16
|
type ApiConnectionAttributes = components["schemas"]["ConnectionAttributes"];
|
|
@@ -42,184 +43,181 @@ export async function readConnectionConfig(
|
|
|
42
43
|
return JSON.parse(connectionFileContents.toString()) as ApiConnection[];
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
export async function createConnections(
|
|
46
|
+
export async function createConnections(
|
|
47
|
+
basePath: string,
|
|
48
|
+
defaultConnections: ApiConnection[] = [],
|
|
49
|
+
): Promise<{
|
|
46
50
|
malloyConnections: Map<string, BaseConnection>;
|
|
47
51
|
apiConnections: InternalConnection[];
|
|
48
52
|
}> {
|
|
49
53
|
const connectionMap = new Map<string, BaseConnection>();
|
|
50
54
|
const connectionConfig = await readConnectionConfig(basePath);
|
|
51
55
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
for (const connection of [...defaultConnections, ...connectionConfig]) {
|
|
57
|
+
logger.info(`Adding connection ${connection.name}`, {
|
|
58
|
+
connection,
|
|
59
|
+
});
|
|
60
|
+
// This case shouldn't happen. The package validation logic should
|
|
61
|
+
// catch it.
|
|
62
|
+
if (!connection.name) {
|
|
63
|
+
throw "Invalid connection configuration. No name.";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
switch (connection.type) {
|
|
67
|
+
case "postgres": {
|
|
68
|
+
const configReader = async () => {
|
|
69
|
+
if (!connection.postgresConnection) {
|
|
70
|
+
throw "Invalid connection configuration. No postgres connection.";
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
host: connection.postgresConnection.host,
|
|
74
|
+
port: connection.postgresConnection.port,
|
|
75
|
+
username: connection.postgresConnection.userName,
|
|
76
|
+
password: connection.postgresConnection.password,
|
|
77
|
+
databaseName: connection.postgresConnection.databaseName,
|
|
78
|
+
connectionString:
|
|
79
|
+
connection.postgresConnection.connectionString,
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
const postgresConnection = new PostgresConnection(
|
|
83
|
+
connection.name,
|
|
84
|
+
() => ({}),
|
|
85
|
+
configReader,
|
|
86
|
+
);
|
|
87
|
+
connectionMap.set(connection.name, postgresConnection);
|
|
88
|
+
connection.attributes = getConnectionAttributes(postgresConnection);
|
|
89
|
+
break;
|
|
58
90
|
}
|
|
59
91
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (!connection.postgresConnection) {
|
|
64
|
-
throw "Invalid connection configuration. No postgres connection.";
|
|
65
|
-
}
|
|
66
|
-
return {
|
|
67
|
-
host: connection.postgresConnection.host,
|
|
68
|
-
port: connection.postgresConnection.port,
|
|
69
|
-
username: connection.postgresConnection.userName,
|
|
70
|
-
password: connection.postgresConnection.password,
|
|
71
|
-
databaseName: connection.postgresConnection.databaseName,
|
|
72
|
-
connectionString:
|
|
73
|
-
connection.postgresConnection.connectionString,
|
|
74
|
-
};
|
|
75
|
-
};
|
|
76
|
-
const postgresConnection = new PostgresConnection(
|
|
77
|
-
connection.name,
|
|
78
|
-
() => ({}),
|
|
79
|
-
configReader,
|
|
80
|
-
);
|
|
81
|
-
connectionMap.set(connection.name, postgresConnection);
|
|
82
|
-
connection.attributes =
|
|
83
|
-
getConnectionAttributes(postgresConnection);
|
|
84
|
-
break;
|
|
92
|
+
case "mysql": {
|
|
93
|
+
if (!connection.mysqlConnection) {
|
|
94
|
+
throw "Invalid connection configuration. No mysql connection.";
|
|
85
95
|
}
|
|
96
|
+
const config = {
|
|
97
|
+
host: connection.mysqlConnection.host,
|
|
98
|
+
port: connection.mysqlConnection.port,
|
|
99
|
+
user: connection.mysqlConnection.user,
|
|
100
|
+
password: connection.mysqlConnection.password,
|
|
101
|
+
database: connection.mysqlConnection.database,
|
|
102
|
+
};
|
|
103
|
+
const mysqlConnection = new MySQLConnection(
|
|
104
|
+
connection.name,
|
|
105
|
+
config,
|
|
106
|
+
);
|
|
107
|
+
connectionMap.set(connection.name, mysqlConnection);
|
|
108
|
+
connection.attributes = getConnectionAttributes(mysqlConnection);
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
86
111
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
const config = {
|
|
92
|
-
host: connection.mysqlConnection.host,
|
|
93
|
-
port: connection.mysqlConnection.port,
|
|
94
|
-
user: connection.mysqlConnection.user,
|
|
95
|
-
password: connection.mysqlConnection.password,
|
|
96
|
-
database: connection.mysqlConnection.database,
|
|
97
|
-
};
|
|
98
|
-
const mysqlConnection = new MySQLConnection(
|
|
99
|
-
connection.name,
|
|
100
|
-
config,
|
|
101
|
-
);
|
|
102
|
-
connectionMap.set(connection.name, mysqlConnection);
|
|
103
|
-
connection.attributes = getConnectionAttributes(mysqlConnection);
|
|
104
|
-
break;
|
|
112
|
+
case "bigquery": {
|
|
113
|
+
if (!connection.bigqueryConnection) {
|
|
114
|
+
throw "Invalid connection configuration. No bigquery connection.";
|
|
105
115
|
}
|
|
106
116
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
serviceAccountKeyPath
|
|
117
|
-
|
|
118
|
-
`${connection.name}-${uuidv4()}-service-account-key.json`,
|
|
119
|
-
);
|
|
120
|
-
await fs.writeFile(
|
|
121
|
-
serviceAccountKeyPath,
|
|
122
|
-
connection.bigqueryConnection
|
|
123
|
-
.serviceAccountKeyJson as string,
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const bigqueryConnectionOptions = {
|
|
128
|
-
projectId: connection.bigqueryConnection.defaultProjectId,
|
|
129
|
-
serviceAccountKeyPath: serviceAccountKeyPath,
|
|
130
|
-
location: connection.bigqueryConnection.location,
|
|
131
|
-
maximumBytesBilled:
|
|
132
|
-
connection.bigqueryConnection.maximumBytesBilled,
|
|
133
|
-
timeoutMs:
|
|
134
|
-
connection.bigqueryConnection.queryTimeoutMilliseconds,
|
|
135
|
-
billingProjectId:
|
|
136
|
-
connection.bigqueryConnection.billingProjectId,
|
|
137
|
-
};
|
|
138
|
-
const bigqueryConnection = new BigQueryConnection(
|
|
139
|
-
connection.name,
|
|
140
|
-
() => ({}),
|
|
141
|
-
bigqueryConnectionOptions,
|
|
117
|
+
// If a service account key file is provided, we persist it to disk
|
|
118
|
+
// and pass the path to the BigQueryConnection.
|
|
119
|
+
let serviceAccountKeyPath = undefined;
|
|
120
|
+
if (connection.bigqueryConnection.serviceAccountKeyJson) {
|
|
121
|
+
serviceAccountKeyPath = path.join(
|
|
122
|
+
"/tmp",
|
|
123
|
+
`${connection.name}-${uuidv4()}-service-account-key.json`,
|
|
124
|
+
);
|
|
125
|
+
await fs.writeFile(
|
|
126
|
+
serviceAccountKeyPath,
|
|
127
|
+
connection.bigqueryConnection.serviceAccountKeyJson as string,
|
|
142
128
|
);
|
|
143
|
-
connectionMap.set(connection.name, bigqueryConnection);
|
|
144
|
-
connection.attributes =
|
|
145
|
-
getConnectionAttributes(bigqueryConnection);
|
|
146
|
-
break;
|
|
147
129
|
}
|
|
148
130
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
131
|
+
const bigqueryConnectionOptions = {
|
|
132
|
+
projectId: connection.bigqueryConnection.defaultProjectId,
|
|
133
|
+
serviceAccountKeyPath: serviceAccountKeyPath,
|
|
134
|
+
location: connection.bigqueryConnection.location,
|
|
135
|
+
maximumBytesBilled:
|
|
136
|
+
connection.bigqueryConnection.maximumBytesBilled,
|
|
137
|
+
timeoutMs:
|
|
138
|
+
connection.bigqueryConnection.queryTimeoutMilliseconds,
|
|
139
|
+
billingProjectId: connection.bigqueryConnection.billingProjectId,
|
|
140
|
+
};
|
|
141
|
+
const bigqueryConnection = new BigQueryConnection(
|
|
142
|
+
connection.name,
|
|
143
|
+
() => ({}),
|
|
144
|
+
bigqueryConnectionOptions,
|
|
145
|
+
);
|
|
146
|
+
connectionMap.set(connection.name, bigqueryConnection);
|
|
147
|
+
connection.attributes = getConnectionAttributes(bigqueryConnection);
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
162
150
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
151
|
+
case "snowflake": {
|
|
152
|
+
if (!connection.snowflakeConnection) {
|
|
153
|
+
throw new Error(
|
|
154
|
+
"Snowflake connection configuration is missing.",
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
if (!connection.snowflakeConnection.account) {
|
|
158
|
+
throw new Error("Snowflake account is required.");
|
|
159
|
+
}
|
|
166
160
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
161
|
+
if (!connection.snowflakeConnection.username) {
|
|
162
|
+
throw new Error("Snowflake username is required.");
|
|
163
|
+
}
|
|
170
164
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
account: connection.snowflakeConnection.account,
|
|
174
|
-
username: connection.snowflakeConnection.username,
|
|
175
|
-
password: connection.snowflakeConnection.password,
|
|
176
|
-
warehouse: connection.snowflakeConnection.warehouse,
|
|
177
|
-
database: connection.snowflakeConnection.database,
|
|
178
|
-
schema: connection.snowflakeConnection.schema,
|
|
179
|
-
timeout:
|
|
180
|
-
connection.snowflakeConnection
|
|
181
|
-
.responseTimeoutMilliseconds,
|
|
182
|
-
},
|
|
183
|
-
};
|
|
184
|
-
const snowflakeConnection = new SnowflakeConnection(
|
|
185
|
-
connection.name,
|
|
186
|
-
snowflakeConnectionOptions,
|
|
187
|
-
);
|
|
188
|
-
connectionMap.set(connection.name, snowflakeConnection);
|
|
189
|
-
connection.attributes =
|
|
190
|
-
getConnectionAttributes(snowflakeConnection);
|
|
191
|
-
break;
|
|
165
|
+
if (!connection.snowflakeConnection.password) {
|
|
166
|
+
throw new Error("Snowflake password is required.");
|
|
192
167
|
}
|
|
193
168
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
throw new Error("Trino connection configuration is missing.");
|
|
197
|
-
}
|
|
198
|
-
const trinoConnectionOptions = {
|
|
199
|
-
server: connection.trinoConnection.server,
|
|
200
|
-
port: connection.trinoConnection.port,
|
|
201
|
-
catalog: connection.trinoConnection.catalog,
|
|
202
|
-
schema: connection.trinoConnection.schema,
|
|
203
|
-
user: connection.trinoConnection.user,
|
|
204
|
-
password: connection.trinoConnection.password,
|
|
205
|
-
};
|
|
206
|
-
const trinoConnection = new TrinoConnection(
|
|
207
|
-
connection.name,
|
|
208
|
-
{},
|
|
209
|
-
trinoConnectionOptions,
|
|
210
|
-
);
|
|
211
|
-
connectionMap.set(connection.name, trinoConnection);
|
|
212
|
-
connection.attributes = getConnectionAttributes(trinoConnection);
|
|
213
|
-
break;
|
|
169
|
+
if (!connection.snowflakeConnection.warehouse) {
|
|
170
|
+
throw new Error("Snowflake warehouse is required.");
|
|
214
171
|
}
|
|
215
172
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
173
|
+
const snowflakeConnectionOptions = {
|
|
174
|
+
connOptions: {
|
|
175
|
+
account: connection.snowflakeConnection.account,
|
|
176
|
+
username: connection.snowflakeConnection.username,
|
|
177
|
+
password: connection.snowflakeConnection.password,
|
|
178
|
+
warehouse: connection.snowflakeConnection.warehouse,
|
|
179
|
+
database: connection.snowflakeConnection.database,
|
|
180
|
+
schema: connection.snowflakeConnection.schema,
|
|
181
|
+
timeout:
|
|
182
|
+
connection.snowflakeConnection.responseTimeoutMilliseconds,
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
const snowflakeConnection = new SnowflakeConnection(
|
|
186
|
+
connection.name,
|
|
187
|
+
snowflakeConnectionOptions,
|
|
188
|
+
);
|
|
189
|
+
connectionMap.set(connection.name, snowflakeConnection);
|
|
190
|
+
connection.attributes =
|
|
191
|
+
getConnectionAttributes(snowflakeConnection);
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
case "trino": {
|
|
196
|
+
if (!connection.trinoConnection) {
|
|
197
|
+
throw new Error("Trino connection configuration is missing.");
|
|
220
198
|
}
|
|
199
|
+
const trinoConnectionOptions = {
|
|
200
|
+
server: connection.trinoConnection.server,
|
|
201
|
+
port: connection.trinoConnection.port,
|
|
202
|
+
catalog: connection.trinoConnection.catalog,
|
|
203
|
+
schema: connection.trinoConnection.schema,
|
|
204
|
+
user: connection.trinoConnection.user,
|
|
205
|
+
password: connection.trinoConnection.password,
|
|
206
|
+
};
|
|
207
|
+
const trinoConnection = new TrinoConnection(
|
|
208
|
+
connection.name,
|
|
209
|
+
{},
|
|
210
|
+
trinoConnectionOptions,
|
|
211
|
+
);
|
|
212
|
+
connectionMap.set(connection.name, trinoConnection);
|
|
213
|
+
connection.attributes = getConnectionAttributes(trinoConnection);
|
|
214
|
+
break;
|
|
221
215
|
}
|
|
222
|
-
|
|
216
|
+
|
|
217
|
+
default: {
|
|
218
|
+
throw new Error(`Unsupported connection type: ${connection.type}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
223
221
|
}
|
|
224
222
|
|
|
225
223
|
return {
|