@malloy-publisher/server 0.0.175 → 0.0.177

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.175",
4
+ "version": "0.0.177",
5
5
  "main": "dist/server.js",
6
6
  "bin": {
7
7
  "malloy-publisher": "dist/server.js"
@@ -1,4 +1,4 @@
1
- import { Connection, RunSQLOptions } from "@malloydata/malloy";
1
+ import { Connection, RunSQLOptions, TableSourceDef } from "@malloydata/malloy";
2
2
  import { PersistSQLResults } from "@malloydata/malloy/connection";
3
3
  import { components } from "../api";
4
4
  import { BadRequestError, ConnectionError } from "../errors";
@@ -6,16 +6,14 @@ import { logger } from "../logger";
6
6
  import { testConnectionConfig } from "../service/connection";
7
7
  import { ConnectionService } from "../service/connection_service";
8
8
  import {
9
- getConnectionTableSource,
10
9
  getSchemasForConnection,
11
- getTablesForSchema,
10
+ listTablesForSchema,
12
11
  } from "../service/db_utils";
13
12
  import { ProjectStore } from "../service/project_store";
14
13
 
15
14
  type ApiConnection = components["schemas"]["Connection"];
16
15
  type ApiConnectionStatus = components["schemas"]["ConnectionStatus"];
17
16
  type ApiSqlSource = components["schemas"]["SqlSource"];
18
- type ApiTableSource = components["schemas"]["TableSource"];
19
17
  type ApiTable = components["schemas"]["Table"];
20
18
  type ApiQueryData = components["schemas"]["QueryData"];
21
19
  type ApiTemporaryTable = components["schemas"]["TemporaryTable"];
@@ -29,28 +27,6 @@ const AZURE_DATA_EXTENSIONS = [
29
27
  ".ndjson",
30
28
  ];
31
29
 
32
- /**
33
- * `fetchTableSchema` query param: default true when omitted, null, or empty.
34
- * Only explicit false/0 disables schema fetch.
35
- */
36
- export function parseFetchTableSchemaQueryParam(raw: unknown): boolean {
37
- if (raw === undefined || raw === null) {
38
- return true;
39
- }
40
- const v = Array.isArray(raw) ? raw[0] : raw;
41
- if (v === "" || v === undefined) {
42
- return true;
43
- }
44
- if (typeof v === "boolean") {
45
- return v;
46
- }
47
- const s = String(v).trim().toLowerCase();
48
- if (s === "false" || s === "0") {
49
- return false;
50
- }
51
- return true;
52
- }
53
-
54
30
  /**
55
31
  * Validates an Azure URL against the three supported patterns:
56
32
  * 1. Single file: path/file.parquet
@@ -144,6 +120,52 @@ export class ConnectionController {
144
120
  }
145
121
  }
146
122
 
123
+ /**
124
+ * Fetches a table's schema via the Malloy connection's fetchTableSchema,
125
+ * returning an ApiTable with columns and the raw source JSON.
126
+ */
127
+ private async fetchTable(
128
+ malloyConnection: Connection,
129
+ tableKey: string,
130
+ tablePath: string,
131
+ ): Promise<ApiTable> {
132
+ try {
133
+ const source = await (
134
+ malloyConnection as Connection & {
135
+ fetchTableSchema: (
136
+ tableKey: string,
137
+ tablePath: string,
138
+ ) => Promise<TableSourceDef | undefined>;
139
+ }
140
+ ).fetchTableSchema(tableKey, tablePath);
141
+ if (!source) {
142
+ throw new ConnectionError(`Table ${tablePath} not found`);
143
+ }
144
+
145
+ return {
146
+ source: JSON.stringify(source),
147
+ resource: tablePath,
148
+ columns: (source.fields || []).map((f) => ({
149
+ name: f.name,
150
+ type: f.type,
151
+ })),
152
+ };
153
+ } catch (error) {
154
+ const errorMessage =
155
+ error instanceof Error
156
+ ? error.message
157
+ : typeof error === "string"
158
+ ? error
159
+ : JSON.stringify(error);
160
+ logger.error("fetchTableSchema error", {
161
+ error,
162
+ tableKey,
163
+ tablePath,
164
+ });
165
+ throw new ConnectionError(errorMessage);
166
+ }
167
+ }
168
+
147
169
  public async getConnection(
148
170
  projectName: string,
149
171
  connectionName: string,
@@ -183,7 +205,7 @@ export class ConnectionController {
183
205
  projectName: string,
184
206
  connectionName: string,
185
207
  schemaName: string,
186
- fetchTableSchema = true,
208
+ tableNames?: string[],
187
209
  ): Promise<ApiTable[]> {
188
210
  const project = await this.projectStore.getProject(projectName, false);
189
211
  const connection = project.getApiConnection(connectionName);
@@ -192,11 +214,11 @@ export class ConnectionController {
192
214
  connectionName,
193
215
  );
194
216
 
195
- return getTablesForSchema(
217
+ return listTablesForSchema(
196
218
  connection,
197
219
  schemaName,
198
220
  malloyConnection,
199
- fetchTableSchema,
221
+ tableNames,
200
222
  );
201
223
  }
202
224
 
@@ -231,20 +253,6 @@ export class ConnectionController {
231
253
  }
232
254
  }
233
255
 
234
- public async getConnectionTableSource(
235
- projectName: string,
236
- connectionName: string,
237
- tableKey: string,
238
- tablePath: string,
239
- ): Promise<ApiTableSource> {
240
- const malloyConnection = await this.getMalloyConnection(
241
- projectName,
242
- connectionName,
243
- );
244
-
245
- return getConnectionTableSource(malloyConnection, tableKey, tablePath);
246
- }
247
-
248
256
  public async getTable(
249
257
  projectName: string,
250
258
  connectionName: string,
@@ -259,6 +267,8 @@ export class ConnectionController {
259
267
  const project = await this.projectStore.getProject(projectName, false);
260
268
  const connection = project.getApiConnection(connectionName);
261
269
 
270
+ // TODO: Move this database connection logic to the db_utils.ts file -- and
271
+ // ultimately into a connection-specific class.
262
272
  if (connection.type === "ducklake") {
263
273
  if (tablePath.split(".").length === 1) {
264
274
  // tablePath is just the table name, construct full path
@@ -306,16 +316,12 @@ export class ConnectionController {
306
316
  );
307
317
  const fullFileUrl = `${dirPath}${fileName}${queryString}`;
308
318
 
309
- const tableSource = await getConnectionTableSource(
319
+ const table = await this.fetchTable(
310
320
  malloyConnection,
311
321
  fileName,
312
322
  fullFileUrl,
313
323
  );
314
- return {
315
- resource: tablePath,
316
- columns: tableSource.columns,
317
- source: tableSource.source,
318
- };
324
+ return { ...table, resource: tablePath };
319
325
  }
320
326
  }
321
327
  }
@@ -325,17 +331,7 @@ export class ConnectionController {
325
331
  throw new Error(`Invalid tablePath: ${tablePath}`);
326
332
  }
327
333
 
328
- const tableSource = await getConnectionTableSource(
329
- malloyConnection,
330
- tableKey, // tableKey is the table name
331
- tablePath,
332
- );
333
-
334
- return {
335
- resource: tablePath,
336
- columns: tableSource.columns,
337
- source: tableSource.source,
338
- };
334
+ return this.fetchTable(malloyConnection, tableKey, tablePath);
339
335
  }
340
336
 
341
337
  public async getConnectionQueryData(
package/src/server.ts CHANGED
@@ -14,10 +14,7 @@ import { createProxyMiddleware } from "http-proxy-middleware";
14
14
  import { AddressInfo } from "net";
15
15
  import * as path from "path";
16
16
  import { CompileController } from "./controller/compile.controller";
17
- import {
18
- ConnectionController,
19
- parseFetchTableSchemaQueryParam,
20
- } from "./controller/connection.controller";
17
+ import { ConnectionController } from "./controller/connection.controller";
21
18
  import { DatabaseController } from "./controller/database.controller";
22
19
  import { ModelController } from "./controller/model.controller";
23
20
  import { PackageController } from "./controller/package.controller";
@@ -33,6 +30,14 @@ import { logger, loggerMiddleware } from "./logger";
33
30
 
34
31
  import { initializeMcpServer } from "./mcp/server";
35
32
  import { ProjectStore } from "./service/project_store";
33
+
34
+ /** Normalize an Express query param into a string[] or undefined. */
35
+ export function normalizeQueryArray(value: unknown): string[] | undefined {
36
+ if (value === undefined || value === null) return undefined;
37
+ if (Array.isArray(value)) return value.map(String);
38
+ return [String(value)];
39
+ }
40
+
36
41
  // Parse command line arguments
37
42
  function parseArgs() {
38
43
  const args = process.argv.slice(2);
@@ -470,7 +475,7 @@ app.get(
470
475
  req.params.projectName,
471
476
  req.params.connectionName,
472
477
  req.params.schemaName,
473
- parseFetchTableSchemaQueryParam(req.query.fetchTableSchema),
478
+ normalizeQueryArray(req.query.tableNames),
474
479
  );
475
480
  res.status(200).json(results);
476
481
  } catch (error) {
@@ -542,26 +547,6 @@ app.post(
542
547
  },
543
548
  );
544
549
 
545
- app.get(
546
- `${API_PREFIX}/projects/:projectName/connections/:connectionName/tableSource`,
547
- async (req, res) => {
548
- try {
549
- res.status(200).json(
550
- await connectionController.getConnectionTableSource(
551
- req.params.projectName,
552
- req.params.connectionName,
553
- req.query.tableKey as string,
554
- req.query.tablePath as string,
555
- ),
556
- );
557
- } catch (error) {
558
- logger.error(error);
559
- const { json, status } = internalErrorToHttpError(error as Error);
560
- res.status(status).json(json);
561
- }
562
- },
563
- );
564
-
565
550
  /**
566
551
  * @deprecated Use /projects/:projectName/connections/:connectionName/queryData POST method instead
567
552
  */