@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/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.88",
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 ./**/*.spec.ts",
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 { RunSQLOptions, TestableConnection } from "@malloydata/malloy";
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(projectName: string, connectionName: string) {
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
- throw new ConnectionError((error as Error).message);
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 = `/etc/publisher/${projectName}/${packageName}`;
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.getProject(projectName);
18
- const p = await project.getPackage(packageName);
19
- return p.listSchedules();
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
- res.status(200).json(await projectStore.addProject(req.body));
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.metadata);
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
- res.status(200).json(await projectStore.updateProject(req.body));
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
- res.status(200).json(
262
- await projectStore.deleteProject(req.params.projectName),
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
- res.status(200).json(
306
- await connectionController.testConnection(
307
- req.params.projectName,
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
- res.status(200).json(
455
- await packageController.addPackage(req.params.projectName, req.body),
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
- projectStore.finishedInitialization.then(() => {
715
- mainServer.listen(PUBLISHER_PORT, PUBLISHER_HOST, () => {
716
- const address = mainServer.address() as AddressInfo;
717
- logger.info(
718
- `Publisher server listening at http://${address.address}:${address.port}`,
719
- );
720
- if (isDevelopment) {
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
- `MCP server listening at http://${PUBLISHER_HOST}:${MCP_PORT}`,
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(basePath: string): Promise<{
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
- if (connectionConfig.length > 0) {
53
- connectionConfig.map(async (connection) => {
54
- // This case shouldn't happen. The package validation logic should
55
- // catch it.
56
- if (!connection.name) {
57
- throw "Invalid connection configuration. No name.";
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
- switch (connection.type) {
61
- case "postgres": {
62
- const configReader = async () => {
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
- case "mysql": {
88
- if (!connection.mysqlConnection) {
89
- throw "Invalid connection configuration. No mysql connection.";
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
- case "bigquery": {
108
- if (!connection.bigqueryConnection) {
109
- throw "Invalid connection configuration. No bigquery connection.";
110
- }
111
-
112
- // If a service account key file is provided, we persist it to disk
113
- // and pass the path to the BigQueryConnection.
114
- let serviceAccountKeyPath = undefined;
115
- if (connection.bigqueryConnection.serviceAccountKeyJson) {
116
- serviceAccountKeyPath = path.join(
117
- "/tmp",
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
- case "snowflake": {
150
- if (!connection.snowflakeConnection) {
151
- throw new Error(
152
- "Snowflake connection configuration is missing.",
153
- );
154
- }
155
- if (!connection.snowflakeConnection.account) {
156
- throw new Error("Snowflake account is required.");
157
- }
158
-
159
- if (!connection.snowflakeConnection.username) {
160
- throw new Error("Snowflake username is required.");
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
- if (!connection.snowflakeConnection.password) {
164
- throw new Error("Snowflake password is required.");
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
- if (!connection.snowflakeConnection.warehouse) {
168
- throw new Error("Snowflake warehouse is required.");
169
- }
161
+ if (!connection.snowflakeConnection.username) {
162
+ throw new Error("Snowflake username is required.");
163
+ }
170
164
 
171
- const snowflakeConnectionOptions = {
172
- connOptions: {
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
- case "trino": {
195
- if (!connection.trinoConnection) {
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
- default: {
217
- throw new Error(
218
- `Unsupported connection type: ${connection.type}`,
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 {