@malloy-publisher/server 0.0.180 → 0.0.181-dev-v1
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 +3 -3
- package/dist/app/api-doc.yaml +505 -52
- package/dist/app/assets/HomePage-Dn3E4CuB.js +1 -0
- package/dist/app/assets/{MainPage-BLhfzy47.js → MainPage-BzB3yoqi.js} +2 -2
- package/dist/app/assets/{ModelPage-bgdjxhyc.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-CsFH2AdT.js → RouteError-DAShbVCG.js} +1 -1
- package/dist/app/assets/{WorkbookPage-CQ37Bfli.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-Cev5PtEG.js → index-Bb2jqquW.js} +1 -1
- package/dist/app/assets/{index-DcnbmCmI.js → index-D68X76-7.js} +168 -166
- package/dist/app/assets/index.umd-DGBekgSu.js +1145 -0
- 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 +10567 -10584
- package/dist/server.js +16972 -15366
- package/package.json +17 -15
- 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/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 +217 -5
- 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 +221 -47
- 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/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/dist/app/assets/HomePage-DRmAsRAP.js +0 -1
- package/dist/app/assets/PackagePage-rPw0OAJY.js +0 -1
- package/dist/app/assets/ProjectPage-D0DYloUr.js +0 -1
- package/dist/app/assets/index-C2IkGoJ8.js +0 -467
- package/dist/app/assets/index.umd-BwIMLH79.js +0 -1145
package/src/server.ts
CHANGED
|
@@ -20,7 +20,11 @@ import { ModelController } from "./controller/model.controller";
|
|
|
20
20
|
import { PackageController } from "./controller/package.controller";
|
|
21
21
|
import { QueryController } from "./controller/query.controller";
|
|
22
22
|
import { WatchModeController } from "./controller/watch-mode.controller";
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
BadRequestError,
|
|
25
|
+
internalErrorToHttpError,
|
|
26
|
+
NotImplementedError,
|
|
27
|
+
} from "./errors";
|
|
24
28
|
import {
|
|
25
29
|
drainingGuard,
|
|
26
30
|
registerHealthEndpoints,
|
|
@@ -28,7 +32,11 @@ import {
|
|
|
28
32
|
} from "./health";
|
|
29
33
|
import { logger, loggerMiddleware } from "./logger";
|
|
30
34
|
|
|
35
|
+
import { ManifestController } from "./controller/manifest.controller";
|
|
36
|
+
import { MaterializationController } from "./controller/materialization.controller";
|
|
31
37
|
import { initializeMcpServer } from "./mcp/server";
|
|
38
|
+
import { ManifestService } from "./service/manifest_service";
|
|
39
|
+
import { MaterializationService } from "./service/materialization_service";
|
|
32
40
|
import { ProjectStore } from "./service/project_store";
|
|
33
41
|
|
|
34
42
|
/** Normalize an Express query param into a string[] or undefined. */
|
|
@@ -125,17 +133,29 @@ const SERVER_ROOT = path.resolve(process.cwd(), process.env.SERVER_ROOT || ".");
|
|
|
125
133
|
const API_PREFIX = "/api/v0";
|
|
126
134
|
const isDevelopment = process.env["NODE_ENV"] === "development";
|
|
127
135
|
|
|
128
|
-
const app = express();
|
|
136
|
+
export const app = express();
|
|
129
137
|
app.use(loggerMiddleware);
|
|
130
138
|
app.use(httpMetricsMiddleware);
|
|
131
139
|
const projectStore = new ProjectStore(SERVER_ROOT);
|
|
140
|
+
const manifestService = new ManifestService(projectStore);
|
|
132
141
|
const watchModeController = new WatchModeController(projectStore);
|
|
133
142
|
const connectionController = new ConnectionController(projectStore);
|
|
134
143
|
const modelController = new ModelController(projectStore);
|
|
135
|
-
const packageController = new PackageController(projectStore);
|
|
144
|
+
const packageController = new PackageController(projectStore, manifestService);
|
|
136
145
|
const databaseController = new DatabaseController(projectStore);
|
|
137
146
|
const queryController = new QueryController(projectStore);
|
|
138
147
|
const compileController = new CompileController(projectStore);
|
|
148
|
+
const materializationService = new MaterializationService(
|
|
149
|
+
projectStore,
|
|
150
|
+
manifestService,
|
|
151
|
+
);
|
|
152
|
+
const materializationController = new MaterializationController(
|
|
153
|
+
materializationService,
|
|
154
|
+
);
|
|
155
|
+
const manifestController = new ManifestController(
|
|
156
|
+
projectStore,
|
|
157
|
+
manifestService,
|
|
158
|
+
);
|
|
139
159
|
|
|
140
160
|
export const mcpApp = express();
|
|
141
161
|
|
|
@@ -262,7 +282,9 @@ app.use(
|
|
|
262
282
|
credentials: true,
|
|
263
283
|
}),
|
|
264
284
|
);
|
|
265
|
-
|
|
285
|
+
|
|
286
|
+
// Set body-parser JSON limit to 1Mb (default: 100kb)
|
|
287
|
+
app.use(bodyParser.json({ limit: "1mb" }));
|
|
266
288
|
|
|
267
289
|
// Register health check endpoints on main app:
|
|
268
290
|
// - Required for production/Kubernetes monitoring (main server on PUBLISHER_PORT)
|
|
@@ -579,7 +601,7 @@ app.post(
|
|
|
579
601
|
req.params.projectName,
|
|
580
602
|
req.params.connectionName,
|
|
581
603
|
req.body.sqlStatement as string,
|
|
582
|
-
req.
|
|
604
|
+
req.body.options as string,
|
|
583
605
|
),
|
|
584
606
|
);
|
|
585
607
|
} catch (error) {
|
|
@@ -650,9 +672,11 @@ app.get(`${API_PREFIX}/projects/:projectName/packages`, async (req, res) => {
|
|
|
650
672
|
|
|
651
673
|
app.post(`${API_PREFIX}/projects/:projectName/packages`, async (req, res) => {
|
|
652
674
|
try {
|
|
675
|
+
const autoLoadManifest = req.query.autoLoadManifest === "true";
|
|
653
676
|
const _package = await packageController.addPackage(
|
|
654
677
|
req.params.projectName,
|
|
655
678
|
req.body,
|
|
679
|
+
{ autoLoadManifest },
|
|
656
680
|
);
|
|
657
681
|
res.status(200).json(_package?.getPackageMetadata());
|
|
658
682
|
} catch (error) {
|
|
@@ -817,12 +841,29 @@ app.get(
|
|
|
817
841
|
// Express stores wildcard matches in params['0']
|
|
818
842
|
const notebookPath = (req.params as Record<string, string>)["0"];
|
|
819
843
|
|
|
844
|
+
// Parse optional filter_params (JSON query string) and bypass_filters
|
|
845
|
+
let filterParams: Record<string, string | string[]> | undefined;
|
|
846
|
+
if (typeof req.query.filter_params === "string") {
|
|
847
|
+
try {
|
|
848
|
+
filterParams = JSON.parse(req.query.filter_params);
|
|
849
|
+
} catch {
|
|
850
|
+
res.status(400).json({
|
|
851
|
+
error: "Invalid filter_params: must be valid JSON",
|
|
852
|
+
});
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
const bypassFilters =
|
|
857
|
+
req.query.bypass_filters === "true" ? true : undefined;
|
|
858
|
+
|
|
820
859
|
res.status(200).json(
|
|
821
860
|
await modelController.executeNotebookCell(
|
|
822
861
|
req.params.projectName,
|
|
823
862
|
req.params.packageName,
|
|
824
863
|
notebookPath,
|
|
825
864
|
cellIndex,
|
|
865
|
+
filterParams,
|
|
866
|
+
bypassFilters,
|
|
826
867
|
),
|
|
827
868
|
);
|
|
828
869
|
} catch (error) {
|
|
@@ -879,6 +920,10 @@ app.post(
|
|
|
879
920
|
req.body.queryName as string,
|
|
880
921
|
req.body.query as string,
|
|
881
922
|
req.body.compactJson === true,
|
|
923
|
+
(req.body.filterParams ?? req.body.sourceFilters) as
|
|
924
|
+
| Record<string, string | string[]>
|
|
925
|
+
| undefined,
|
|
926
|
+
req.body.bypassFilters === true ? true : undefined,
|
|
882
927
|
),
|
|
883
928
|
);
|
|
884
929
|
} catch (error) {
|
|
@@ -932,6 +977,173 @@ app.post(
|
|
|
932
977
|
},
|
|
933
978
|
);
|
|
934
979
|
|
|
980
|
+
// ==================== MATERIALIZATION ROUTES ====================
|
|
981
|
+
|
|
982
|
+
app.post(
|
|
983
|
+
`${API_PREFIX}/projects/:projectName/packages/:packageName/materializations`,
|
|
984
|
+
async (req, res) => {
|
|
985
|
+
try {
|
|
986
|
+
const build = await materializationController.createMaterialization(
|
|
987
|
+
req.params.projectName,
|
|
988
|
+
req.params.packageName,
|
|
989
|
+
req.body || {},
|
|
990
|
+
);
|
|
991
|
+
res.status(201).json(build);
|
|
992
|
+
} catch (error) {
|
|
993
|
+
const { json, status } = internalErrorToHttpError(error as Error);
|
|
994
|
+
res.status(status).json(json);
|
|
995
|
+
}
|
|
996
|
+
},
|
|
997
|
+
);
|
|
998
|
+
|
|
999
|
+
app.get(
|
|
1000
|
+
`${API_PREFIX}/projects/:projectName/packages/:packageName/materializations`,
|
|
1001
|
+
async (req, res) => {
|
|
1002
|
+
try {
|
|
1003
|
+
const limit = req.query.limit
|
|
1004
|
+
? parseInt(req.query.limit as string, 10)
|
|
1005
|
+
: undefined;
|
|
1006
|
+
const offset = req.query.offset
|
|
1007
|
+
? parseInt(req.query.offset as string, 10)
|
|
1008
|
+
: undefined;
|
|
1009
|
+
const builds = await materializationController.listMaterializations(
|
|
1010
|
+
req.params.projectName,
|
|
1011
|
+
req.params.packageName,
|
|
1012
|
+
{ limit, offset },
|
|
1013
|
+
);
|
|
1014
|
+
res.status(200).json(builds);
|
|
1015
|
+
} catch (error) {
|
|
1016
|
+
const { json, status } = internalErrorToHttpError(error as Error);
|
|
1017
|
+
res.status(status).json(json);
|
|
1018
|
+
}
|
|
1019
|
+
},
|
|
1020
|
+
);
|
|
1021
|
+
|
|
1022
|
+
app.get(
|
|
1023
|
+
`${API_PREFIX}/projects/:projectName/packages/:packageName/materializations/:materializationId`,
|
|
1024
|
+
async (req, res) => {
|
|
1025
|
+
try {
|
|
1026
|
+
const build = await materializationController.getMaterialization(
|
|
1027
|
+
req.params.projectName,
|
|
1028
|
+
req.params.packageName,
|
|
1029
|
+
req.params.materializationId,
|
|
1030
|
+
);
|
|
1031
|
+
res.status(200).json(build);
|
|
1032
|
+
} catch (error) {
|
|
1033
|
+
const { json, status } = internalErrorToHttpError(error as Error);
|
|
1034
|
+
res.status(status).json(json);
|
|
1035
|
+
}
|
|
1036
|
+
},
|
|
1037
|
+
);
|
|
1038
|
+
|
|
1039
|
+
app.post(
|
|
1040
|
+
`${API_PREFIX}/projects/:projectName/packages/:packageName/materializations/teardown`,
|
|
1041
|
+
async (req, res) => {
|
|
1042
|
+
try {
|
|
1043
|
+
const result = await materializationController.teardownPackage(
|
|
1044
|
+
req.params.projectName,
|
|
1045
|
+
req.params.packageName,
|
|
1046
|
+
req.body || {},
|
|
1047
|
+
);
|
|
1048
|
+
res.status(200).json(result);
|
|
1049
|
+
} catch (error) {
|
|
1050
|
+
const { json, status } = internalErrorToHttpError(error as Error);
|
|
1051
|
+
res.status(status).json(json);
|
|
1052
|
+
}
|
|
1053
|
+
},
|
|
1054
|
+
);
|
|
1055
|
+
|
|
1056
|
+
app.post(
|
|
1057
|
+
`${API_PREFIX}/projects/:projectName/packages/:packageName/materializations/:materializationId`,
|
|
1058
|
+
async (req, res) => {
|
|
1059
|
+
try {
|
|
1060
|
+
const action = req.query.action;
|
|
1061
|
+
if (action === "start") {
|
|
1062
|
+
const build = await materializationController.startMaterialization(
|
|
1063
|
+
req.params.projectName,
|
|
1064
|
+
req.params.packageName,
|
|
1065
|
+
req.params.materializationId,
|
|
1066
|
+
);
|
|
1067
|
+
res.status(202).json(build);
|
|
1068
|
+
} else if (action === "stop") {
|
|
1069
|
+
const build = await materializationController.stopMaterialization(
|
|
1070
|
+
req.params.projectName,
|
|
1071
|
+
req.params.packageName,
|
|
1072
|
+
req.params.materializationId,
|
|
1073
|
+
);
|
|
1074
|
+
res.status(200).json(build);
|
|
1075
|
+
} else {
|
|
1076
|
+
throw new BadRequestError(
|
|
1077
|
+
`Unsupported action '${String(action ?? "")}'. Expected 'start' or 'stop'.`,
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
} catch (error) {
|
|
1081
|
+
const { json, status } = internalErrorToHttpError(error as Error);
|
|
1082
|
+
res.status(status).json(json);
|
|
1083
|
+
}
|
|
1084
|
+
},
|
|
1085
|
+
);
|
|
1086
|
+
|
|
1087
|
+
app.delete(
|
|
1088
|
+
`${API_PREFIX}/projects/:projectName/packages/:packageName/materializations/:materializationId`,
|
|
1089
|
+
async (req, res) => {
|
|
1090
|
+
try {
|
|
1091
|
+
await materializationController.deleteMaterialization(
|
|
1092
|
+
req.params.projectName,
|
|
1093
|
+
req.params.packageName,
|
|
1094
|
+
req.params.materializationId,
|
|
1095
|
+
);
|
|
1096
|
+
res.status(204).send();
|
|
1097
|
+
} catch (error) {
|
|
1098
|
+
const { json, status } = internalErrorToHttpError(error as Error);
|
|
1099
|
+
res.status(status).json(json);
|
|
1100
|
+
}
|
|
1101
|
+
},
|
|
1102
|
+
);
|
|
1103
|
+
|
|
1104
|
+
// ==================== MANIFEST ROUTES ====================
|
|
1105
|
+
|
|
1106
|
+
app.get(
|
|
1107
|
+
`${API_PREFIX}/projects/:projectName/packages/:packageName/manifest`,
|
|
1108
|
+
async (req, res) => {
|
|
1109
|
+
try {
|
|
1110
|
+
const manifest = await manifestController.getManifest(
|
|
1111
|
+
req.params.projectName,
|
|
1112
|
+
req.params.packageName,
|
|
1113
|
+
);
|
|
1114
|
+
res.status(200).json(manifest);
|
|
1115
|
+
} catch (error) {
|
|
1116
|
+
logger.error("Get manifest error", { error });
|
|
1117
|
+
const { json, status } = internalErrorToHttpError(error as Error);
|
|
1118
|
+
res.status(status).json(json);
|
|
1119
|
+
}
|
|
1120
|
+
},
|
|
1121
|
+
);
|
|
1122
|
+
|
|
1123
|
+
app.post(
|
|
1124
|
+
`${API_PREFIX}/projects/:projectName/packages/:packageName/manifest`,
|
|
1125
|
+
async (req, res) => {
|
|
1126
|
+
try {
|
|
1127
|
+
const action = req.query.action;
|
|
1128
|
+
if (action === "reload") {
|
|
1129
|
+
const manifest = await manifestController.reloadManifest(
|
|
1130
|
+
req.params.projectName,
|
|
1131
|
+
req.params.packageName,
|
|
1132
|
+
);
|
|
1133
|
+
res.status(200).json(manifest);
|
|
1134
|
+
} else {
|
|
1135
|
+
throw new BadRequestError(
|
|
1136
|
+
`Unsupported action '${String(action ?? "")}'. Expected 'reload'.`,
|
|
1137
|
+
);
|
|
1138
|
+
}
|
|
1139
|
+
} catch (error) {
|
|
1140
|
+
logger.error("Manifest action error", { error });
|
|
1141
|
+
const { json, status } = internalErrorToHttpError(error as Error);
|
|
1142
|
+
res.status(status).json(json);
|
|
1143
|
+
}
|
|
1144
|
+
},
|
|
1145
|
+
);
|
|
1146
|
+
|
|
935
1147
|
// Modify the catch-all route to only serve index.html in production
|
|
936
1148
|
if (!isDevelopment) {
|
|
937
1149
|
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(
|