@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/dist/app/api-doc.yaml +7 -58
- package/dist/app/assets/{HomePage-_GXfGtrF.js → HomePage-CwUkFsA8.js} +1 -1
- package/dist/app/assets/{MainPage-UqGzQjP1.js → MainPage-JYvDXOkC.js} +1 -1
- package/dist/app/assets/{ModelPage-CnwgkZU6.js → ModelPage-TEQrhaqq.js} +1 -1
- package/dist/app/assets/{PackagePage-CqmgyERX.js → PackagePage-CgE-izLw.js} +1 -1
- package/dist/app/assets/{ProjectPage-5jrEp4pr.js → ProjectPage-PiMPpFX8.js} +1 -1
- package/dist/app/assets/{RouteError-CjrNNYGo.js → RouteError-DnSZEzkT.js} +1 -1
- package/dist/app/assets/{WorkbookPage-Cy2UHLkL.js → WorkbookPage-DjQ8u5DD.js} +1 -1
- package/dist/app/assets/{index-DfOHc8G8.js → index--80Q7qw1.js} +1 -1
- package/dist/app/assets/{index-ZpoRvcIi.js → index-BJUsHnGO.js} +40 -40
- package/dist/app/assets/{index-_xQHc-7Q.js → index-CZ4G_NMp.js} +1 -1
- package/dist/app/assets/{index.umd-Bq6CbCSn.js → index.umd-Cf-wqh-R.js} +1 -1
- package/dist/app/index.html +1 -1
- package/dist/server.js +514 -544
- package/package.json +1 -1
- package/src/controller/connection.controller.ts +56 -60
- package/src/server.ts +10 -25
- package/src/service/db_utils.spec.ts +712 -0
- package/src/service/db_utils.ts +786 -755
- package/src/service/project.ts +2 -2
- package/src/service/project_store.ts +6 -19
- package/tests/unit/ducklake/ducklake.test.ts +3 -3
package/package.json
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
217
|
+
return listTablesForSchema(
|
|
196
218
|
connection,
|
|
197
219
|
schemaName,
|
|
198
220
|
malloyConnection,
|
|
199
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
*/
|