@malloy-publisher/server 0.0.205 → 0.0.207

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 (69) hide show
  1. package/dist/app/api-doc.yaml +394 -395
  2. package/dist/app/assets/{EnvironmentPage-CAge6UHD.js → EnvironmentPage-BScgHmkw.js} +1 -1
  3. package/dist/app/assets/{HomePage-DhTe8qpa.js → HomePage-CGedji_w.js} +1 -1
  4. package/dist/app/assets/{MainPage-CeTxxGex.js → MainPage-DWfF4jXW.js} +2 -2
  5. package/dist/app/assets/{MaterializationsPage-CpDHB70t.js → MaterializationsPage-B9PDlk8c.js} +1 -1
  6. package/dist/app/assets/{ModelPage-D9sSMb75.js → ModelPage-BiNOgK_e.js} +1 -1
  7. package/dist/app/assets/{PackagePage-LRqQWrFY.js → PackagePage-DAN5V7gu.js} +1 -1
  8. package/dist/app/assets/{RouteError-xT6kuCNw.js → RouteError-CEnIzuKN.js} +1 -1
  9. package/dist/app/assets/{WorkbookPage-DsIh9svZ.js → WorkbookPage-gA1ceqHP.js} +1 -1
  10. package/dist/app/assets/{core-C2sQrwVu.es-Bjem0hym.js → core-AOmIKwkc.es-Dclu1Fga.js} +1 -1
  11. package/dist/app/assets/{index-BdOZDcce.js → index-DGGe8UpP.js} +1 -1
  12. package/dist/app/assets/{index-RX3QOTde.js → index-DtlPzNxc.js} +127 -127
  13. package/dist/app/assets/{index-DHHAcY5o.js → index-uu6UpHd2.js} +1 -1
  14. package/dist/app/assets/{index.umd-D2WH3D-f.js → index.umd-DDq93YX4.js} +1 -1
  15. package/dist/app/index.html +1 -1
  16. package/dist/instrumentation.mjs +18 -8
  17. package/dist/package_load_worker.mjs +19 -2
  18. package/dist/server.mjs +1272 -1299
  19. package/package.json +1 -1
  20. package/src/constants.ts +12 -0
  21. package/src/controller/materialization.controller.ts +79 -35
  22. package/src/controller/package.controller.spec.ts +179 -0
  23. package/src/controller/package.controller.ts +60 -73
  24. package/src/dto/package.dto.ts +16 -1
  25. package/src/errors.spec.ts +12 -0
  26. package/src/errors.ts +18 -0
  27. package/src/health.spec.ts +34 -1
  28. package/src/instrumentation.ts +33 -17
  29. package/src/materialization_metrics.ts +121 -0
  30. package/src/package_load/package_load_pool.ts +7 -1
  31. package/src/package_load/package_load_worker.ts +44 -4
  32. package/src/package_load/protocol.ts +7 -1
  33. package/src/server-old.ts +7 -149
  34. package/src/server.ts +9 -188
  35. package/src/service/authorize_integration.spec.ts +67 -0
  36. package/src/service/environment.ts +270 -12
  37. package/src/service/environment_store.spec.ts +0 -81
  38. package/src/service/environment_store.ts +0 -23
  39. package/src/service/explore_visibility.spec.ts +434 -0
  40. package/src/service/exports_probe.spec.ts +107 -0
  41. package/src/service/manifest_loader.spec.ts +99 -0
  42. package/src/service/manifest_loader.ts +95 -0
  43. package/src/service/materialization_service.spec.ts +584 -500
  44. package/src/service/materialization_service.ts +839 -657
  45. package/src/service/model.ts +419 -15
  46. package/src/service/package.spec.ts +14 -2
  47. package/src/service/package.ts +339 -29
  48. package/src/service/package_rollback.spec.ts +190 -0
  49. package/src/service/package_worker_path.spec.ts +223 -0
  50. package/src/service/query_boundary.spec.ts +470 -0
  51. package/src/storage/DatabaseInterface.ts +35 -57
  52. package/src/storage/StorageManager.mock.ts +0 -9
  53. package/src/storage/StorageManager.ts +7 -290
  54. package/src/storage/duckdb/DuckDBRepository.ts +2 -35
  55. package/src/storage/duckdb/MaterializationRepository.ts +52 -27
  56. package/src/storage/duckdb/schema.ts +4 -20
  57. package/tests/integration/materialization/manifest_binding.integration.spec.ts +194 -0
  58. package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +369 -264
  59. package/tests/unit/duckdb/legacy_schema_migration.test.ts +7 -4
  60. package/src/controller/manifest.controller.ts +0 -38
  61. package/src/service/manifest_service.spec.ts +0 -206
  62. package/src/service/manifest_service.ts +0 -117
  63. package/src/service/materialized_table_gc.spec.ts +0 -384
  64. package/src/service/materialized_table_gc.ts +0 -231
  65. package/src/storage/duckdb/DuckDBManifestStore.ts +0 -70
  66. package/src/storage/duckdb/ManifestRepository.ts +0 -120
  67. package/src/storage/duckdb/manifest_store.spec.ts +0 -133
  68. package/src/storage/ducklake/DuckLakeManifestStore.ts +0 -155
  69. package/tests/unit/storage/StorageManager.test.ts +0 -166
package/src/server.ts CHANGED
@@ -8,7 +8,6 @@ import * as http from "http";
8
8
  import { createProxyMiddleware } from "http-proxy-middleware";
9
9
  import { AddressInfo } from "net";
10
10
  import * as path from "path";
11
- import { ParsedQs } from "qs";
12
11
  import { fileURLToPath } from "url";
13
12
  import { CompileController } from "./controller/compile.controller";
14
13
  import { ConnectionController } from "./controller/connection.controller";
@@ -38,12 +37,10 @@ import { getMemoryGovernorConfig } from "./config";
38
37
  import { setFilterDeprecationHeaders } from "./filter_deprecation";
39
38
  import { checkHeapConfiguration } from "./heap_check";
40
39
  import { queryConcurrency } from "./query_concurrency";
41
- import { ManifestController } from "./controller/manifest.controller";
42
40
  import { MaterializationController } from "./controller/materialization.controller";
43
41
  import { initializeMcpServer } from "./mcp/server";
44
42
  import { registerLegacyRoutes } from "./server-old";
45
43
  import { EnvironmentStore } from "./service/environment_store";
46
- import { ManifestService } from "./service/manifest_service";
47
44
  import { MaterializationService } from "./service/materialization_service";
48
45
  import { normalizeQueryArray } from "./query_param_utils";
49
46
  import { PackageMemoryGovernor } from "./service/package_memory_governor";
@@ -188,7 +185,6 @@ app.use(httpMetricsMiddleware);
188
185
  // chase OOMKills before checking the obvious config.
189
186
  checkHeapConfiguration();
190
187
  const environmentStore = new EnvironmentStore(SERVER_ROOT);
191
- const manifestService = new ManifestService(environmentStore);
192
188
  const watchModeController = new WatchModeController(environmentStore);
193
189
  const connectionController = new ConnectionController(environmentStore);
194
190
  const modelController = new ModelController(environmentStore);
@@ -203,24 +199,14 @@ const memoryGovernor = memoryGovernorConfig
203
199
  : null;
204
200
  memoryGovernor?.start();
205
201
  environmentStore.setMemoryGovernor(memoryGovernor);
206
- const packageController = new PackageController(
207
- environmentStore,
208
- manifestService,
209
- );
202
+ const packageController = new PackageController(environmentStore);
210
203
  const databaseController = new DatabaseController(environmentStore);
211
204
  const queryController = new QueryController(environmentStore);
212
205
  const compileController = new CompileController(environmentStore);
213
- const materializationService = new MaterializationService(
214
- environmentStore,
215
- manifestService,
216
- );
206
+ const materializationService = new MaterializationService(environmentStore);
217
207
  const materializationController = new MaterializationController(
218
208
  materializationService,
219
209
  );
220
- const manifestController = new ManifestController(
221
- environmentStore,
222
- manifestService,
223
- );
224
210
 
225
211
  export const mcpApp = express();
226
212
 
@@ -1056,28 +1042,6 @@ app.get(
1056
1042
  },
1057
1043
  );
1058
1044
 
1059
- /**
1060
- * @deprecated Use /environments/:environmentName/connections/:connectionName/sqlSource POST method instead
1061
- */
1062
- app.get(
1063
- `${API_PREFIX}/environments/:environmentName/connections/:connectionName/sqlSource`,
1064
- async (req, res) => {
1065
- try {
1066
- res.status(200).json(
1067
- await connectionController.getConnectionSqlSource(
1068
- req.params.environmentName,
1069
- req.params.connectionName,
1070
- req.query.sqlStatement as string,
1071
- ),
1072
- );
1073
- } catch (error) {
1074
- logger.error(error);
1075
- const { json, status } = internalErrorToHttpError(error as Error);
1076
- res.status(status).json(json);
1077
- }
1078
- },
1079
- );
1080
-
1081
1045
  app.post(
1082
1046
  `${API_PREFIX}/environments/:environmentName/connections/:connectionName/sqlSource`,
1083
1047
  async (req, res) => {
@@ -1098,26 +1062,6 @@ app.post(
1098
1062
  );
1099
1063
 
1100
1064
  // Per-package versions
1101
- app.get(
1102
- `${API_PREFIX}/environments/:environmentName/packages/:packageName/connections/:connectionName/sqlSource`,
1103
- async (req, res) => {
1104
- try {
1105
- res.status(200).json(
1106
- await connectionController.getConnectionSqlSource(
1107
- req.params.environmentName,
1108
- req.params.connectionName,
1109
- req.query.sqlStatement as string,
1110
- req.params.packageName,
1111
- ),
1112
- );
1113
- } catch (error) {
1114
- logger.error(error);
1115
- const { json, status } = internalErrorToHttpError(error as Error);
1116
- res.status(status).json(json);
1117
- }
1118
- },
1119
- );
1120
-
1121
1065
  app.post(
1122
1066
  `${API_PREFIX}/environments/:environmentName/packages/:packageName/connections/:connectionName/sqlSource`,
1123
1067
  async (req, res) => {
@@ -1152,21 +1096,12 @@ app.post(
1152
1096
  queryConcurrency(),
1153
1097
  async (req, res) => {
1154
1098
  try {
1155
- let options: string | ParsedQs | (string | ParsedQs)[] | undefined;
1156
-
1157
- // Support both body and query parameters for options for backwards compatibility
1158
- // TODO: To be removed in the future
1159
- if (req.body?.options) {
1160
- options = req.body.options;
1161
- } else {
1162
- options = req.query.options;
1163
- }
1164
1099
  res.status(200).json(
1165
1100
  await connectionController.getConnectionQueryData(
1166
1101
  req.params.environmentName,
1167
1102
  req.params.connectionName,
1168
1103
  req.body.sqlStatement as string,
1169
- options as string,
1104
+ req.body?.options as string,
1170
1105
  ),
1171
1106
  );
1172
1107
  } catch (error) {
@@ -1182,65 +1117,12 @@ app.post(
1182
1117
  queryConcurrency(),
1183
1118
  async (req, res) => {
1184
1119
  try {
1185
- let options: string | ParsedQs | (string | ParsedQs)[] | undefined;
1186
- if (req.body?.options) {
1187
- options = req.body.options;
1188
- } else {
1189
- options = req.query.options;
1190
- }
1191
1120
  res.status(200).json(
1192
1121
  await connectionController.getConnectionQueryData(
1193
1122
  req.params.environmentName,
1194
1123
  req.params.connectionName,
1195
1124
  req.body.sqlStatement as string,
1196
- options as string,
1197
- req.params.packageName,
1198
- ),
1199
- );
1200
- } catch (error) {
1201
- logger.error(error);
1202
- const { json, status } = internalErrorToHttpError(error as Error);
1203
- res.status(status).json(json);
1204
- }
1205
- },
1206
- );
1207
-
1208
- /**
1209
- * @deprecated Use environments/:environmentName/connections/:connectionName/sqlTemporaryTable POST method instead
1210
- */
1211
- app.get(
1212
- `${API_PREFIX}/environments/:environmentName/connections/:connectionName/temporaryTable`,
1213
- queryConcurrency(),
1214
- async (req, res) => {
1215
- try {
1216
- res.status(200).json(
1217
- await connectionController.getConnectionTemporaryTable(
1218
- req.params.environmentName,
1219
- req.params.connectionName,
1220
- req.query.sqlStatement as string,
1221
- ),
1222
- );
1223
- } catch (error) {
1224
- logger.error(error);
1225
- const { json, status } = internalErrorToHttpError(error as Error);
1226
- res.status(status).json(json);
1227
- }
1228
- },
1229
- );
1230
-
1231
- /**
1232
- * @deprecated Use /environments/:environmentName/packages/:packageName/connections/:connectionName/sqlTemporaryTable
1233
- */
1234
- app.get(
1235
- `${API_PREFIX}/environments/:environmentName/packages/:packageName/connections/:connectionName/temporaryTable`,
1236
- queryConcurrency(),
1237
- async (req, res) => {
1238
- try {
1239
- res.status(200).json(
1240
- await connectionController.getConnectionTemporaryTable(
1241
- req.params.environmentName,
1242
- req.params.connectionName,
1243
- req.query.sqlStatement as string,
1125
+ req.body?.options as string,
1244
1126
  req.params.packageName,
1245
1127
  ),
1246
1128
  );
@@ -1317,11 +1199,9 @@ app.post(
1317
1199
  `${API_PREFIX}/environments/:environmentName/packages`,
1318
1200
  async (req, res) => {
1319
1201
  try {
1320
- const autoLoadManifest = req.query.autoLoadManifest === "true";
1321
1202
  const _package = await packageController.addPackage(
1322
1203
  req.params.environmentName,
1323
1204
  req.body,
1324
- { autoLoadManifest },
1325
1205
  );
1326
1206
  res.status(200).json(_package?.getPackageMetadata());
1327
1207
  } catch (error) {
@@ -1705,33 +1585,17 @@ app.get(
1705
1585
  },
1706
1586
  );
1707
1587
 
1708
- app.post(
1709
- `${API_PREFIX}/environments/:environmentName/packages/:packageName/materializations/teardown`,
1710
- async (req, res) => {
1711
- try {
1712
- const result = await materializationController.teardownPackage(
1713
- req.params.environmentName,
1714
- req.params.packageName,
1715
- req.body || {},
1716
- );
1717
- res.status(200).json(result);
1718
- } catch (error) {
1719
- const { json, status } = internalErrorToHttpError(error as Error);
1720
- res.status(status).json(json);
1721
- }
1722
- },
1723
- );
1724
-
1725
1588
  app.post(
1726
1589
  `${API_PREFIX}/environments/:environmentName/packages/:packageName/materializations/:materializationId`,
1727
1590
  async (req, res) => {
1728
1591
  try {
1729
1592
  const action = req.query.action;
1730
- if (action === "start") {
1731
- const build = await materializationController.startMaterialization(
1593
+ if (action === "build") {
1594
+ const build = await materializationController.buildMaterialization(
1732
1595
  req.params.environmentName,
1733
1596
  req.params.packageName,
1734
1597
  req.params.materializationId,
1598
+ req.body || {},
1735
1599
  );
1736
1600
  res.status(202).json(build);
1737
1601
  } else if (action === "stop") {
@@ -1743,7 +1607,7 @@ app.post(
1743
1607
  res.status(200).json(build);
1744
1608
  } else {
1745
1609
  throw new BadRequestError(
1746
- `Unsupported action '${String(action ?? "")}'. Expected 'start' or 'stop'.`,
1610
+ `Unsupported action '${String(action ?? "")}'. Expected 'build' or 'stop'.`,
1747
1611
  );
1748
1612
  }
1749
1613
  } catch (error) {
@@ -1761,6 +1625,7 @@ app.delete(
1761
1625
  req.params.environmentName,
1762
1626
  req.params.packageName,
1763
1627
  req.params.materializationId,
1628
+ { dropTables: req.query.dropTables === "true" },
1764
1629
  );
1765
1630
  res.status(204).send();
1766
1631
  } catch (error) {
@@ -1770,49 +1635,6 @@ app.delete(
1770
1635
  },
1771
1636
  );
1772
1637
 
1773
- // ==================== MANIFEST ROUTES ====================
1774
-
1775
- app.get(
1776
- `${API_PREFIX}/environments/:environmentName/packages/:packageName/manifest`,
1777
- async (req, res) => {
1778
- try {
1779
- const manifest = await manifestController.getManifest(
1780
- req.params.environmentName,
1781
- req.params.packageName,
1782
- );
1783
- res.status(200).json(manifest);
1784
- } catch (error) {
1785
- logger.error("Get manifest error", { error });
1786
- const { json, status } = internalErrorToHttpError(error as Error);
1787
- res.status(status).json(json);
1788
- }
1789
- },
1790
- );
1791
-
1792
- app.post(
1793
- `${API_PREFIX}/environments/:environmentName/packages/:packageName/manifest`,
1794
- async (req, res) => {
1795
- try {
1796
- const action = req.query.action;
1797
- if (action === "reload") {
1798
- const manifest = await manifestController.reloadManifest(
1799
- req.params.environmentName,
1800
- req.params.packageName,
1801
- );
1802
- res.status(200).json(manifest);
1803
- } else {
1804
- throw new BadRequestError(
1805
- `Unsupported action '${String(action ?? "")}'. Expected 'reload'.`,
1806
- );
1807
- }
1808
- } catch (error) {
1809
- logger.error("Manifest action error", { error });
1810
- const { json, status } = internalErrorToHttpError(error as Error);
1811
- res.status(status).json(json);
1812
- }
1813
- },
1814
- );
1815
-
1816
1638
  // Register legacy `/projects/...` routes for backwards compatibility with
1817
1639
  // clients that haven't migrated to `/environments/...` yet. Must be added
1818
1640
  // before the SPA catch-all below.
@@ -1825,7 +1647,6 @@ registerLegacyRoutes(app, {
1825
1647
  queryController,
1826
1648
  compileController,
1827
1649
  materializationController,
1828
- manifestController,
1829
1650
  });
1830
1651
 
1831
1652
  // Modify the catch-all route to only serve index.html in production
@@ -930,3 +930,70 @@ source: declared is duckdb.table('customers') extend { measure: c is count() }
930
930
  ).resolves.toBeUndefined();
931
931
  });
932
932
  });
933
+
934
+ // A given can be both a runtime query parameter (substituted into a view's
935
+ // `where`) AND the subject of an `#(authorize)` gate on the same source. These
936
+ // two layers compose: the gate fires first (before any filtering), and once it
937
+ // passes the parameterized filter applies to the result. This is the layered
938
+ // case the end-to-end story rests on.
939
+ describe("authorize composes with given parameters", () => {
940
+ // `ROLE` gates access; `REGION` is a runtime filter parameter on the view.
941
+ const COMPOSED = `##! experimental.givens
942
+
943
+ given:
944
+ ROLE :: string
945
+ REGION :: string
946
+
947
+ #(authorize) "$ROLE = 'analyst'"
948
+ source: regional is duckdb.table('customers') extend {
949
+ measure: c is count()
950
+ view: in_region is {
951
+ where: region = $REGION
952
+ aggregate: c
953
+ }
954
+ }
955
+ `;
956
+
957
+ async function runComposed(givens: Record<string, GivenValue>) {
958
+ await writeModel("compose.malloy", COMPOSED);
959
+ const model = await Model.create(
960
+ "test-pkg",
961
+ TEST_PKG_DIR,
962
+ "compose.malloy",
963
+ getConnections(),
964
+ );
965
+ return model.getQueryResults(
966
+ undefined,
967
+ undefined,
968
+ "run: regional -> in_region",
969
+ undefined,
970
+ undefined,
971
+ givens,
972
+ );
973
+ }
974
+
975
+ function countOf(compactResult: unknown): number {
976
+ const rows = compactResult as Array<Record<string, unknown>>;
977
+ return Number(rows[0]?.c);
978
+ }
979
+
980
+ it("denies before filtering when the gate fails, even with the filter given supplied", async () => {
981
+ await expect(runComposed({ REGION: "us-west" })).rejects.toBeInstanceOf(
982
+ AccessDeniedError,
983
+ );
984
+ });
985
+
986
+ it("applies the parameterized filter once the gate passes", async () => {
987
+ // Seed: one 'us-west' customer, one 'us-east'.
988
+ const west = await runComposed({ ROLE: "analyst", REGION: "us-west" });
989
+ expect(countOf(west.compactResult)).toBe(1);
990
+
991
+ const east = await runComposed({ ROLE: "analyst", REGION: "us-east" });
992
+ expect(countOf(east.compactResult)).toBe(1);
993
+
994
+ // A region matching no rows proves the given genuinely filters (not a
995
+ // gate-only pass-through).
996
+ const none = await runComposed({ ROLE: "analyst", REGION: "nowhere" });
997
+ expect(countOf(none.compactResult)).toBe(0);
998
+ });
999
+ });