@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.
Files changed (72) hide show
  1. package/build.ts +3 -3
  2. package/dist/app/api-doc.yaml +505 -52
  3. package/dist/app/assets/HomePage-Dn3E4CuB.js +1 -0
  4. package/dist/app/assets/{MainPage-BLhfzy47.js → MainPage-BzB3yoqi.js} +2 -2
  5. package/dist/app/assets/{ModelPage-bgdjxhyc.js → ModelPage-C9O_sAXT.js} +1 -1
  6. package/dist/app/assets/PackagePage-DcxKEjBX.js +1 -0
  7. package/dist/app/assets/ProjectPage-BDj307rF.js +1 -0
  8. package/dist/app/assets/{RouteError-CsFH2AdT.js → RouteError-DAShbVCG.js} +1 -1
  9. package/dist/app/assets/{WorkbookPage-CQ37Bfli.js → WorkbookPage-Cs_XYEaB.js} +1 -1
  10. package/dist/app/assets/core-CjeTkq8O.es-BqRc6yhC.js +148 -0
  11. package/dist/app/assets/engine-oniguruma-C4vnmooL.es-jdkXmgTr.js +1 -0
  12. package/dist/app/assets/github-light-JYsPkUQd.es-DAi9KRSo.js +1 -0
  13. package/dist/app/assets/index-15BOvhp0.js +456 -0
  14. package/dist/app/assets/{index-Cev5PtEG.js → index-Bb2jqquW.js} +1 -1
  15. package/dist/app/assets/{index-DcnbmCmI.js → index-D68X76-7.js} +168 -166
  16. package/dist/app/assets/index.umd-DGBekgSu.js +1145 -0
  17. package/dist/app/assets/json-71t8ZF9g.es-BQoSv7ci.js +1 -0
  18. package/dist/app/assets/sql-DCkt643-.es-COK4E0Yg.js +1 -0
  19. package/dist/app/assets/typescript-buWNZFwO.es-Dj6nwHGl.js +1 -0
  20. package/dist/app/index.html +1 -1
  21. package/dist/instrumentation.js +10567 -10584
  22. package/dist/server.js +16972 -15366
  23. package/package.json +17 -15
  24. package/src/controller/connection.controller.ts +27 -20
  25. package/src/controller/manifest.controller.ts +29 -0
  26. package/src/controller/materialization.controller.ts +125 -0
  27. package/src/controller/model.controller.ts +4 -3
  28. package/src/controller/package.controller.ts +53 -2
  29. package/src/controller/query.controller.ts +5 -0
  30. package/src/errors.ts +24 -0
  31. package/src/mcp/resources/model_resource.ts +12 -9
  32. package/src/mcp/resources/source_resource.ts +7 -6
  33. package/src/mcp/resources/view_resource.ts +0 -1
  34. package/src/mcp/tools/execute_query_tool.ts +9 -0
  35. package/src/server.ts +217 -5
  36. package/src/service/connection.ts +1 -4
  37. package/src/service/filter.spec.ts +447 -0
  38. package/src/service/filter.ts +337 -0
  39. package/src/service/filter_integration.spec.ts +825 -0
  40. package/src/service/manifest_service.spec.ts +201 -0
  41. package/src/service/manifest_service.ts +106 -0
  42. package/src/service/materialization_service.spec.ts +648 -0
  43. package/src/service/materialization_service.ts +929 -0
  44. package/src/service/materialized_table_gc.spec.ts +383 -0
  45. package/src/service/materialized_table_gc.ts +279 -0
  46. package/src/service/model.ts +221 -47
  47. package/src/service/package.ts +50 -0
  48. package/src/service/project_store.ts +21 -2
  49. package/src/service/quoting.ts +41 -0
  50. package/src/service/resolve_project.ts +13 -0
  51. package/src/storage/DatabaseInterface.ts +103 -1
  52. package/src/storage/{StorageManager.spec.ts → StorageManager.mock.ts} +9 -0
  53. package/src/storage/StorageManager.ts +119 -1
  54. package/src/storage/duckdb/DuckDBManifestStore.ts +70 -0
  55. package/src/storage/duckdb/DuckDBRepository.ts +99 -9
  56. package/src/storage/duckdb/ManifestRepository.ts +119 -0
  57. package/src/storage/duckdb/MaterializationRepository.ts +249 -0
  58. package/src/storage/duckdb/manifest_store.spec.ts +133 -0
  59. package/src/storage/duckdb/schema.ts +59 -1
  60. package/src/storage/ducklake/DuckLakeManifestStore.ts +146 -0
  61. package/tests/fixtures/persist-test/data/orders.csv +5 -0
  62. package/tests/fixtures/persist-test/persist_test.malloy +11 -0
  63. package/tests/fixtures/persist-test/publisher.json +5 -0
  64. package/tests/fixtures/publisher.config.json +15 -0
  65. package/tests/harness/rest_e2e.ts +68 -0
  66. package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +470 -0
  67. package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +2 -2
  68. package/dist/app/assets/HomePage-DRmAsRAP.js +0 -1
  69. package/dist/app/assets/PackagePage-rPw0OAJY.js +0 -1
  70. package/dist/app/assets/ProjectPage-D0DYloUr.js +0 -1
  71. package/dist/app/assets/index-C2IkGoJ8.js +0 -467
  72. 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 { internalErrorToHttpError, NotImplementedError } from "./errors";
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
- app.use(bodyParser.json());
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.query.options as string,
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: 5,
1097
- testOnBorrow: false,
1098
- testOnReturn: false,
1099
- testWhileIdle: true,
1096
+ max: 20,
1100
1097
  },
1101
1098
  };
1102
1099
  const snowflakeConnection = new SnowflakeConnection(