@malloy-publisher/server 0.0.181 → 0.0.183-dev
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 +7 -3
- package/dist/app/api-doc.yaml +505 -52
- package/dist/app/assets/HomePage-Dn3E4CuB.js +1 -0
- package/dist/app/assets/{MainPage-B53xidTF.js → MainPage-BzB3yoqi.js} +2 -2
- package/dist/app/assets/{ModelPage-UMuQe8qY.js → ModelPage-C9O_sAXT.js} +1 -1
- package/dist/app/assets/PackagePage-DcxKEjBX.js +1 -0
- package/dist/app/assets/ProjectPage-BDj307rF.js +1 -0
- package/dist/app/assets/{RouteError-Cv58zNpb.js → RouteError-DAShbVCG.js} +1 -1
- package/dist/app/assets/{WorkbookPage-DZ1StqsX.js → WorkbookPage-Cs_XYEaB.js} +1 -1
- package/dist/app/assets/core-CjeTkq8O.es-BqRc6yhC.js +148 -0
- package/dist/app/assets/engine-oniguruma-C4vnmooL.es-jdkXmgTr.js +1 -0
- package/dist/app/assets/github-light-JYsPkUQd.es-DAi9KRSo.js +1 -0
- package/dist/app/assets/index-15BOvhp0.js +456 -0
- package/dist/app/assets/{index-DPThhVfX.js → index-Bb2jqquW.js} +1 -1
- package/dist/app/assets/{index-M3Zo817E.js → index-D68X76-7.js} +98 -98
- package/dist/app/assets/{index.umd-DnfBsVqO.js → index.umd-DGBekgSu.js} +1 -1
- package/dist/app/assets/json-71t8ZF9g.es-BQoSv7ci.js +1 -0
- package/dist/app/assets/sql-DCkt643-.es-COK4E0Yg.js +1 -0
- package/dist/app/assets/typescript-buWNZFwO.es-Dj6nwHGl.js +1 -0
- package/dist/app/index.html +1 -1
- package/dist/{instrumentation.js → instrumentation.mjs} +10567 -10584
- package/dist/{server.js → server.mjs} +16959 -15357
- package/package.json +19 -17
- package/src/controller/connection.controller.ts +27 -20
- package/src/controller/manifest.controller.ts +29 -0
- package/src/controller/materialization.controller.ts +125 -0
- package/src/controller/model.controller.ts +4 -3
- package/src/controller/package.controller.ts +53 -2
- package/src/controller/query.controller.ts +5 -0
- package/src/errors.ts +24 -0
- package/src/mcp/prompts/handlers.ts +1 -1
- package/src/mcp/resources/model_resource.ts +12 -9
- package/src/mcp/resources/source_resource.ts +7 -6
- package/src/mcp/resources/view_resource.ts +0 -1
- package/src/mcp/tools/execute_query_tool.ts +9 -0
- package/src/server.ts +223 -15
- package/src/service/connection.ts +1 -4
- package/src/service/filter.spec.ts +447 -0
- package/src/service/filter.ts +337 -0
- package/src/service/filter_integration.spec.ts +825 -0
- package/src/service/manifest_service.spec.ts +201 -0
- package/src/service/manifest_service.ts +106 -0
- package/src/service/materialization_service.spec.ts +648 -0
- package/src/service/materialization_service.ts +929 -0
- package/src/service/materialized_table_gc.spec.ts +383 -0
- package/src/service/materialized_table_gc.ts +279 -0
- package/src/service/model.ts +227 -49
- package/src/service/package.ts +50 -0
- package/src/service/project_store.ts +21 -2
- package/src/service/quoting.ts +41 -0
- package/src/service/resolve_project.ts +13 -0
- package/src/storage/DatabaseInterface.ts +103 -1
- package/src/storage/{StorageManager.spec.ts → StorageManager.mock.ts} +9 -0
- package/src/storage/StorageManager.ts +119 -1
- package/src/storage/duckdb/DuckDBConnection.ts +1 -1
- package/src/storage/duckdb/DuckDBManifestStore.ts +70 -0
- package/src/storage/duckdb/DuckDBRepository.ts +99 -9
- package/src/storage/duckdb/ManifestRepository.ts +119 -0
- package/src/storage/duckdb/MaterializationRepository.ts +249 -0
- package/src/storage/duckdb/manifest_store.spec.ts +133 -0
- package/src/storage/duckdb/schema.ts +59 -1
- package/src/storage/ducklake/DuckLakeManifestStore.ts +146 -0
- package/tests/fixtures/persist-test/data/orders.csv +5 -0
- package/tests/fixtures/persist-test/persist_test.malloy +11 -0
- package/tests/fixtures/persist-test/publisher.json +5 -0
- package/tests/fixtures/publisher.config.json +15 -0
- package/tests/harness/rest_e2e.ts +68 -0
- package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +470 -0
- package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +2 -2
- package/tsconfig.json +1 -1
- package/dist/app/assets/HomePage-B0C6gwGj.js +0 -1
- package/dist/app/assets/PackagePage-BEDvm_je.js +0 -1
- package/dist/app/assets/ProjectPage-DzN4P86H.js +0 -1
- package/dist/app/assets/index-D-xPyBUA.js +0 -467
package/src/server.ts
CHANGED
|
@@ -6,13 +6,14 @@ import {
|
|
|
6
6
|
} from "./instrumentation";
|
|
7
7
|
|
|
8
8
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
9
|
-
import
|
|
9
|
+
import bodyParser from "body-parser";
|
|
10
10
|
import cors from "cors";
|
|
11
11
|
import express from "express";
|
|
12
12
|
import * as http from "http";
|
|
13
13
|
import { createProxyMiddleware } from "http-proxy-middleware";
|
|
14
14
|
import { AddressInfo } from "net";
|
|
15
15
|
import * as path from "path";
|
|
16
|
+
import { fileURLToPath } from "url";
|
|
16
17
|
import { CompileController } from "./controller/compile.controller";
|
|
17
18
|
import { ConnectionController } from "./controller/connection.controller";
|
|
18
19
|
import { DatabaseController } from "./controller/database.controller";
|
|
@@ -20,7 +21,11 @@ import { ModelController } from "./controller/model.controller";
|
|
|
20
21
|
import { PackageController } from "./controller/package.controller";
|
|
21
22
|
import { QueryController } from "./controller/query.controller";
|
|
22
23
|
import { WatchModeController } from "./controller/watch-mode.controller";
|
|
23
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
BadRequestError,
|
|
26
|
+
internalErrorToHttpError,
|
|
27
|
+
NotImplementedError,
|
|
28
|
+
} from "./errors";
|
|
24
29
|
import {
|
|
25
30
|
drainingGuard,
|
|
26
31
|
registerHealthEndpoints,
|
|
@@ -28,7 +33,11 @@ import {
|
|
|
28
33
|
} from "./health";
|
|
29
34
|
import { logger, loggerMiddleware } from "./logger";
|
|
30
35
|
|
|
36
|
+
import { ManifestController } from "./controller/manifest.controller";
|
|
37
|
+
import { MaterializationController } from "./controller/materialization.controller";
|
|
31
38
|
import { initializeMcpServer } from "./mcp/server";
|
|
39
|
+
import { ManifestService } from "./service/manifest_service";
|
|
40
|
+
import { MaterializationService } from "./service/materialization_service";
|
|
32
41
|
import { ProjectStore } from "./service/project_store";
|
|
33
42
|
|
|
34
43
|
/** Normalize an Express query param into a string[] or undefined. */
|
|
@@ -112,30 +121,37 @@ const SHUTDOWN_DRAIN_DURATION_SECONDS = Number(
|
|
|
112
121
|
const SHUTDOWN_GRACEFUL_CLOSE_TIMEOUT_SECONDS = Number(
|
|
113
122
|
process.env.SHUTDOWN_GRACEFUL_CLOSE_TIMEOUT_SECONDS || 0,
|
|
114
123
|
);
|
|
115
|
-
// Find the app directory
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
ROOT = path.join(path.dirname(require.main.filename), "app");
|
|
120
|
-
} else {
|
|
121
|
-
// Fallback to current script directory
|
|
122
|
-
ROOT = path.join(path.dirname(process.argv[1] || __filename), "app");
|
|
123
|
-
}
|
|
124
|
+
// Find the app directory relative to this bundled server file.
|
|
125
|
+
// Works under both ESM (import.meta.url) and when invoked via NPX.
|
|
126
|
+
const __filename_esm = fileURLToPath(import.meta.url);
|
|
127
|
+
const ROOT = path.join(path.dirname(__filename_esm), "app");
|
|
124
128
|
const SERVER_ROOT = path.resolve(process.cwd(), process.env.SERVER_ROOT || ".");
|
|
125
129
|
const API_PREFIX = "/api/v0";
|
|
126
130
|
const isDevelopment = process.env["NODE_ENV"] === "development";
|
|
127
131
|
|
|
128
|
-
const app = express();
|
|
132
|
+
export const app = express();
|
|
129
133
|
app.use(loggerMiddleware);
|
|
130
134
|
app.use(httpMetricsMiddleware);
|
|
131
135
|
const projectStore = new ProjectStore(SERVER_ROOT);
|
|
136
|
+
const manifestService = new ManifestService(projectStore);
|
|
132
137
|
const watchModeController = new WatchModeController(projectStore);
|
|
133
138
|
const connectionController = new ConnectionController(projectStore);
|
|
134
139
|
const modelController = new ModelController(projectStore);
|
|
135
|
-
const packageController = new PackageController(projectStore);
|
|
140
|
+
const packageController = new PackageController(projectStore, manifestService);
|
|
136
141
|
const databaseController = new DatabaseController(projectStore);
|
|
137
142
|
const queryController = new QueryController(projectStore);
|
|
138
143
|
const compileController = new CompileController(projectStore);
|
|
144
|
+
const materializationService = new MaterializationService(
|
|
145
|
+
projectStore,
|
|
146
|
+
manifestService,
|
|
147
|
+
);
|
|
148
|
+
const materializationController = new MaterializationController(
|
|
149
|
+
materializationService,
|
|
150
|
+
);
|
|
151
|
+
const manifestController = new ManifestController(
|
|
152
|
+
projectStore,
|
|
153
|
+
manifestService,
|
|
154
|
+
);
|
|
139
155
|
|
|
140
156
|
export const mcpApp = express();
|
|
141
157
|
|
|
@@ -262,7 +278,9 @@ app.use(
|
|
|
262
278
|
credentials: true,
|
|
263
279
|
}),
|
|
264
280
|
);
|
|
265
|
-
|
|
281
|
+
|
|
282
|
+
// Set body-parser JSON limit to 1Mb (default: 100kb)
|
|
283
|
+
app.use(bodyParser.json({ limit: "1mb" }));
|
|
266
284
|
|
|
267
285
|
// Register health check endpoints on main app:
|
|
268
286
|
// - Required for production/Kubernetes monitoring (main server on PUBLISHER_PORT)
|
|
@@ -579,7 +597,7 @@ app.post(
|
|
|
579
597
|
req.params.projectName,
|
|
580
598
|
req.params.connectionName,
|
|
581
599
|
req.body.sqlStatement as string,
|
|
582
|
-
req.
|
|
600
|
+
req.body.options as string,
|
|
583
601
|
),
|
|
584
602
|
);
|
|
585
603
|
} catch (error) {
|
|
@@ -650,9 +668,11 @@ app.get(`${API_PREFIX}/projects/:projectName/packages`, async (req, res) => {
|
|
|
650
668
|
|
|
651
669
|
app.post(`${API_PREFIX}/projects/:projectName/packages`, async (req, res) => {
|
|
652
670
|
try {
|
|
671
|
+
const autoLoadManifest = req.query.autoLoadManifest === "true";
|
|
653
672
|
const _package = await packageController.addPackage(
|
|
654
673
|
req.params.projectName,
|
|
655
674
|
req.body,
|
|
675
|
+
{ autoLoadManifest },
|
|
656
676
|
);
|
|
657
677
|
res.status(200).json(_package?.getPackageMetadata());
|
|
658
678
|
} catch (error) {
|
|
@@ -817,12 +837,29 @@ app.get(
|
|
|
817
837
|
// Express stores wildcard matches in params['0']
|
|
818
838
|
const notebookPath = (req.params as Record<string, string>)["0"];
|
|
819
839
|
|
|
840
|
+
// Parse optional filter_params (JSON query string) and bypass_filters
|
|
841
|
+
let filterParams: Record<string, string | string[]> | undefined;
|
|
842
|
+
if (typeof req.query.filter_params === "string") {
|
|
843
|
+
try {
|
|
844
|
+
filterParams = JSON.parse(req.query.filter_params);
|
|
845
|
+
} catch {
|
|
846
|
+
res.status(400).json({
|
|
847
|
+
error: "Invalid filter_params: must be valid JSON",
|
|
848
|
+
});
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
const bypassFilters =
|
|
853
|
+
req.query.bypass_filters === "true" ? true : undefined;
|
|
854
|
+
|
|
820
855
|
res.status(200).json(
|
|
821
856
|
await modelController.executeNotebookCell(
|
|
822
857
|
req.params.projectName,
|
|
823
858
|
req.params.packageName,
|
|
824
859
|
notebookPath,
|
|
825
860
|
cellIndex,
|
|
861
|
+
filterParams,
|
|
862
|
+
bypassFilters,
|
|
826
863
|
),
|
|
827
864
|
);
|
|
828
865
|
} catch (error) {
|
|
@@ -879,6 +916,10 @@ app.post(
|
|
|
879
916
|
req.body.queryName as string,
|
|
880
917
|
req.body.query as string,
|
|
881
918
|
req.body.compactJson === true,
|
|
919
|
+
(req.body.filterParams ?? req.body.sourceFilters) as
|
|
920
|
+
| Record<string, string | string[]>
|
|
921
|
+
| undefined,
|
|
922
|
+
req.body.bypassFilters === true ? true : undefined,
|
|
882
923
|
),
|
|
883
924
|
);
|
|
884
925
|
} catch (error) {
|
|
@@ -932,6 +973,173 @@ app.post(
|
|
|
932
973
|
},
|
|
933
974
|
);
|
|
934
975
|
|
|
976
|
+
// ==================== MATERIALIZATION ROUTES ====================
|
|
977
|
+
|
|
978
|
+
app.post(
|
|
979
|
+
`${API_PREFIX}/projects/:projectName/packages/:packageName/materializations`,
|
|
980
|
+
async (req, res) => {
|
|
981
|
+
try {
|
|
982
|
+
const build = await materializationController.createMaterialization(
|
|
983
|
+
req.params.projectName,
|
|
984
|
+
req.params.packageName,
|
|
985
|
+
req.body || {},
|
|
986
|
+
);
|
|
987
|
+
res.status(201).json(build);
|
|
988
|
+
} catch (error) {
|
|
989
|
+
const { json, status } = internalErrorToHttpError(error as Error);
|
|
990
|
+
res.status(status).json(json);
|
|
991
|
+
}
|
|
992
|
+
},
|
|
993
|
+
);
|
|
994
|
+
|
|
995
|
+
app.get(
|
|
996
|
+
`${API_PREFIX}/projects/:projectName/packages/:packageName/materializations`,
|
|
997
|
+
async (req, res) => {
|
|
998
|
+
try {
|
|
999
|
+
const limit = req.query.limit
|
|
1000
|
+
? parseInt(req.query.limit as string, 10)
|
|
1001
|
+
: undefined;
|
|
1002
|
+
const offset = req.query.offset
|
|
1003
|
+
? parseInt(req.query.offset as string, 10)
|
|
1004
|
+
: undefined;
|
|
1005
|
+
const builds = await materializationController.listMaterializations(
|
|
1006
|
+
req.params.projectName,
|
|
1007
|
+
req.params.packageName,
|
|
1008
|
+
{ limit, offset },
|
|
1009
|
+
);
|
|
1010
|
+
res.status(200).json(builds);
|
|
1011
|
+
} catch (error) {
|
|
1012
|
+
const { json, status } = internalErrorToHttpError(error as Error);
|
|
1013
|
+
res.status(status).json(json);
|
|
1014
|
+
}
|
|
1015
|
+
},
|
|
1016
|
+
);
|
|
1017
|
+
|
|
1018
|
+
app.get(
|
|
1019
|
+
`${API_PREFIX}/projects/:projectName/packages/:packageName/materializations/:materializationId`,
|
|
1020
|
+
async (req, res) => {
|
|
1021
|
+
try {
|
|
1022
|
+
const build = await materializationController.getMaterialization(
|
|
1023
|
+
req.params.projectName,
|
|
1024
|
+
req.params.packageName,
|
|
1025
|
+
req.params.materializationId,
|
|
1026
|
+
);
|
|
1027
|
+
res.status(200).json(build);
|
|
1028
|
+
} catch (error) {
|
|
1029
|
+
const { json, status } = internalErrorToHttpError(error as Error);
|
|
1030
|
+
res.status(status).json(json);
|
|
1031
|
+
}
|
|
1032
|
+
},
|
|
1033
|
+
);
|
|
1034
|
+
|
|
1035
|
+
app.post(
|
|
1036
|
+
`${API_PREFIX}/projects/:projectName/packages/:packageName/materializations/teardown`,
|
|
1037
|
+
async (req, res) => {
|
|
1038
|
+
try {
|
|
1039
|
+
const result = await materializationController.teardownPackage(
|
|
1040
|
+
req.params.projectName,
|
|
1041
|
+
req.params.packageName,
|
|
1042
|
+
req.body || {},
|
|
1043
|
+
);
|
|
1044
|
+
res.status(200).json(result);
|
|
1045
|
+
} catch (error) {
|
|
1046
|
+
const { json, status } = internalErrorToHttpError(error as Error);
|
|
1047
|
+
res.status(status).json(json);
|
|
1048
|
+
}
|
|
1049
|
+
},
|
|
1050
|
+
);
|
|
1051
|
+
|
|
1052
|
+
app.post(
|
|
1053
|
+
`${API_PREFIX}/projects/:projectName/packages/:packageName/materializations/:materializationId`,
|
|
1054
|
+
async (req, res) => {
|
|
1055
|
+
try {
|
|
1056
|
+
const action = req.query.action;
|
|
1057
|
+
if (action === "start") {
|
|
1058
|
+
const build = await materializationController.startMaterialization(
|
|
1059
|
+
req.params.projectName,
|
|
1060
|
+
req.params.packageName,
|
|
1061
|
+
req.params.materializationId,
|
|
1062
|
+
);
|
|
1063
|
+
res.status(202).json(build);
|
|
1064
|
+
} else if (action === "stop") {
|
|
1065
|
+
const build = await materializationController.stopMaterialization(
|
|
1066
|
+
req.params.projectName,
|
|
1067
|
+
req.params.packageName,
|
|
1068
|
+
req.params.materializationId,
|
|
1069
|
+
);
|
|
1070
|
+
res.status(200).json(build);
|
|
1071
|
+
} else {
|
|
1072
|
+
throw new BadRequestError(
|
|
1073
|
+
`Unsupported action '${String(action ?? "")}'. Expected 'start' or 'stop'.`,
|
|
1074
|
+
);
|
|
1075
|
+
}
|
|
1076
|
+
} catch (error) {
|
|
1077
|
+
const { json, status } = internalErrorToHttpError(error as Error);
|
|
1078
|
+
res.status(status).json(json);
|
|
1079
|
+
}
|
|
1080
|
+
},
|
|
1081
|
+
);
|
|
1082
|
+
|
|
1083
|
+
app.delete(
|
|
1084
|
+
`${API_PREFIX}/projects/:projectName/packages/:packageName/materializations/:materializationId`,
|
|
1085
|
+
async (req, res) => {
|
|
1086
|
+
try {
|
|
1087
|
+
await materializationController.deleteMaterialization(
|
|
1088
|
+
req.params.projectName,
|
|
1089
|
+
req.params.packageName,
|
|
1090
|
+
req.params.materializationId,
|
|
1091
|
+
);
|
|
1092
|
+
res.status(204).send();
|
|
1093
|
+
} catch (error) {
|
|
1094
|
+
const { json, status } = internalErrorToHttpError(error as Error);
|
|
1095
|
+
res.status(status).json(json);
|
|
1096
|
+
}
|
|
1097
|
+
},
|
|
1098
|
+
);
|
|
1099
|
+
|
|
1100
|
+
// ==================== MANIFEST ROUTES ====================
|
|
1101
|
+
|
|
1102
|
+
app.get(
|
|
1103
|
+
`${API_PREFIX}/projects/:projectName/packages/:packageName/manifest`,
|
|
1104
|
+
async (req, res) => {
|
|
1105
|
+
try {
|
|
1106
|
+
const manifest = await manifestController.getManifest(
|
|
1107
|
+
req.params.projectName,
|
|
1108
|
+
req.params.packageName,
|
|
1109
|
+
);
|
|
1110
|
+
res.status(200).json(manifest);
|
|
1111
|
+
} catch (error) {
|
|
1112
|
+
logger.error("Get manifest error", { error });
|
|
1113
|
+
const { json, status } = internalErrorToHttpError(error as Error);
|
|
1114
|
+
res.status(status).json(json);
|
|
1115
|
+
}
|
|
1116
|
+
},
|
|
1117
|
+
);
|
|
1118
|
+
|
|
1119
|
+
app.post(
|
|
1120
|
+
`${API_PREFIX}/projects/:projectName/packages/:packageName/manifest`,
|
|
1121
|
+
async (req, res) => {
|
|
1122
|
+
try {
|
|
1123
|
+
const action = req.query.action;
|
|
1124
|
+
if (action === "reload") {
|
|
1125
|
+
const manifest = await manifestController.reloadManifest(
|
|
1126
|
+
req.params.projectName,
|
|
1127
|
+
req.params.packageName,
|
|
1128
|
+
);
|
|
1129
|
+
res.status(200).json(manifest);
|
|
1130
|
+
} else {
|
|
1131
|
+
throw new BadRequestError(
|
|
1132
|
+
`Unsupported action '${String(action ?? "")}'. Expected 'reload'.`,
|
|
1133
|
+
);
|
|
1134
|
+
}
|
|
1135
|
+
} catch (error) {
|
|
1136
|
+
logger.error("Manifest action error", { error });
|
|
1137
|
+
const { json, status } = internalErrorToHttpError(error as Error);
|
|
1138
|
+
res.status(status).json(json);
|
|
1139
|
+
}
|
|
1140
|
+
},
|
|
1141
|
+
);
|
|
1142
|
+
|
|
935
1143
|
// Modify the catch-all route to only serve index.html in production
|
|
936
1144
|
if (!isDevelopment) {
|
|
937
1145
|
app.get("*", (_req, res) => res.sendFile(path.resolve(ROOT, "index.html")));
|
|
@@ -1093,10 +1093,7 @@ export async function createProjectConnections(
|
|
|
1093
1093
|
},
|
|
1094
1094
|
poolOptions: {
|
|
1095
1095
|
min: 1,
|
|
1096
|
-
max:
|
|
1097
|
-
testOnBorrow: false,
|
|
1098
|
-
testOnReturn: false,
|
|
1099
|
-
testWhileIdle: true,
|
|
1096
|
+
max: 20,
|
|
1100
1097
|
},
|
|
1101
1098
|
};
|
|
1102
1099
|
const snowflakeConnection = new SnowflakeConnection(
|