@malloy-publisher/server 0.0.382-dev → 0.2.0

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 (175) hide show
  1. package/README.docker.md +145 -20
  2. package/README.md +65 -12
  3. package/dist/app/api-doc.html +5 -0
  4. package/dist/app/api-doc.yaml +3903 -583
  5. package/dist/app/assets/EnvironmentPage-DiUgLuCH.js +1 -0
  6. package/dist/app/assets/HomePage-MhhjhnFj.js +1 -0
  7. package/dist/app/assets/LightMode-BEyvNUzT.js +1 -0
  8. package/dist/app/assets/MainPage-D4Cfee9o.js +2 -0
  9. package/dist/app/assets/MaterializationsPage-nGgGqwSN.js +1 -0
  10. package/dist/app/assets/ModelPage-DLAcEw-F.js +1 -0
  11. package/dist/app/assets/PackagePage-Df0ikH7w.js +1 -0
  12. package/dist/app/assets/RouteError-BFQwtroH.js +1 -0
  13. package/dist/app/assets/ThemeEditorPage-OoskoQYN.js +1 -0
  14. package/dist/app/assets/WorkbookPage-CpFgvUDC.js +1 -0
  15. package/dist/app/assets/{core-CjeTkq8O.es-BqRc6yhC.js → core-lb28vQkR.es-BoVSOc75.js} +1 -1
  16. package/dist/app/assets/github-dark-DenFmJkN.es-DHJKELXO.js +1 -0
  17. package/dist/app/assets/index-5eLCcNmP.css +1 -0
  18. package/dist/app/assets/index-6ypuFH6g.js +1774 -0
  19. package/dist/app/assets/index-B8pnU_V_.js +615 -0
  20. package/dist/app/assets/index-BImcHmVP.js +1 -0
  21. package/dist/app/assets/index-DSoCUF-f.js +2725 -0
  22. package/dist/app/assets/index-DfqzSItA.js +2 -0
  23. package/dist/app/assets/index-w_0OQJgZ.js +23 -0
  24. package/dist/app/index.html +8 -4
  25. package/dist/cpufeatures-1yrn0vtw.node +0 -0
  26. package/dist/default-publisher.config.json +23 -0
  27. package/dist/{instrumentation.js → instrumentation.mjs} +10631 -10573
  28. package/dist/package_load_worker.mjs +15219 -0
  29. package/dist/runtime/publisher.js +331 -0
  30. package/dist/{server.js → server.mjs} +115548 -49590
  31. package/dist/sshcrypto-vd2k5hq9.node +0 -0
  32. package/package.json +41 -28
  33. package/publisher.config.example.bigquery.json +33 -0
  34. package/publisher.config.example.duckdb.json +23 -0
  35. package/.eslintrc.json +0 -44
  36. package/.prettierignore +0 -1
  37. package/.prettierrc +0 -4
  38. package/build.ts +0 -38
  39. package/dist/app/assets/HomePage-Dn3E4CuB.js +0 -1
  40. package/dist/app/assets/MainPage-BzB3yoqi.js +0 -2
  41. package/dist/app/assets/ModelPage-C9O_sAXT.js +0 -1
  42. package/dist/app/assets/PackagePage-DcxKEjBX.js +0 -1
  43. package/dist/app/assets/ProjectPage-BDj307rF.js +0 -1
  44. package/dist/app/assets/RouteError-DAShbVCG.js +0 -1
  45. package/dist/app/assets/WorkbookPage-Cs_XYEaB.js +0 -1
  46. package/dist/app/assets/index-15BOvhp0.js +0 -456
  47. package/dist/app/assets/index-Bb2jqquW.js +0 -676
  48. package/dist/app/assets/index-CMlGQMcl.css +0 -1
  49. package/dist/app/assets/index-D68X76-7.js +0 -1276
  50. package/dist/app/assets/index.umd-DGBekgSu.js +0 -1145
  51. package/dxt/malloy_bridge.py +0 -354
  52. package/dxt/manifest.json +0 -22
  53. package/eslint.config.mjs +0 -76
  54. package/k6-tests/README.md +0 -144
  55. package/k6-tests/bun.lock +0 -797
  56. package/k6-tests/load-test/load-test-crud-connections.ts +0 -263
  57. package/k6-tests/load-test/load-test-crud-packages.ts +0 -461
  58. package/k6-tests/load-test/load-test-crud-projects.ts +0 -160
  59. package/k6-tests/load-test/load-test-crud.ts +0 -142
  60. package/k6-tests/load-test/load-test.ts +0 -784
  61. package/k6-tests/package.json +0 -27
  62. package/k6-tests/scripts/run-with-otel.sh +0 -50
  63. package/k6-tests/smoke-test/smoke-test.ts +0 -158
  64. package/k6-tests/tsconfig.json +0 -29
  65. package/k6-tests/utils/client_factory.ts +0 -128
  66. package/k6-tests/utils/common.ts +0 -489
  67. package/k6-tests/utils/logger.ts +0 -62
  68. package/k6-tests/utils/types.d.ts +0 -35
  69. package/malloy_mcp.dxt +0 -0
  70. package/publisher.config.json +0 -33
  71. package/src/config.spec.ts +0 -850
  72. package/src/config.ts +0 -305
  73. package/src/constants.ts +0 -10
  74. package/src/controller/compile.controller.ts +0 -35
  75. package/src/controller/connection.controller.ts +0 -515
  76. package/src/controller/database.controller.ts +0 -21
  77. package/src/controller/manifest.controller.ts +0 -29
  78. package/src/controller/materialization.controller.ts +0 -125
  79. package/src/controller/model.controller.ts +0 -107
  80. package/src/controller/package.controller.ts +0 -182
  81. package/src/controller/query.controller.ts +0 -60
  82. package/src/controller/watch-mode.controller.ts +0 -98
  83. package/src/data_styles.ts +0 -71
  84. package/src/dto/connection.dto.spec.ts +0 -131
  85. package/src/dto/connection.dto.ts +0 -174
  86. package/src/dto/index.ts +0 -2
  87. package/src/dto/package.dto.spec.ts +0 -42
  88. package/src/dto/package.dto.ts +0 -12
  89. package/src/dto/validate.spec.ts +0 -76
  90. package/src/dto/validate.ts +0 -31
  91. package/src/errors.ts +0 -116
  92. package/src/health.ts +0 -189
  93. package/src/instrumentation.ts +0 -148
  94. package/src/logger.ts +0 -154
  95. package/src/mcp/error_messages.ts +0 -199
  96. package/src/mcp/handler_utils.ts +0 -239
  97. package/src/mcp/mcp_constants.ts +0 -22
  98. package/src/mcp/prompts/handlers.ts +0 -84
  99. package/src/mcp/prompts/index.ts +0 -11
  100. package/src/mcp/prompts/prompt_definitions.ts +0 -160
  101. package/src/mcp/prompts/prompt_service.ts +0 -67
  102. package/src/mcp/prompts/utils.ts +0 -62
  103. package/src/mcp/resource_metadata.ts +0 -47
  104. package/src/mcp/resources/model_resource.ts +0 -153
  105. package/src/mcp/resources/notebook_resource.ts +0 -137
  106. package/src/mcp/resources/package_resource.ts +0 -370
  107. package/src/mcp/resources/project_resource.ts +0 -184
  108. package/src/mcp/resources/query_resource.ts +0 -117
  109. package/src/mcp/resources/source_resource.ts +0 -141
  110. package/src/mcp/resources/view_resource.ts +0 -136
  111. package/src/mcp/server.ts +0 -62
  112. package/src/mcp/tools/discovery_tools.ts +0 -258
  113. package/src/mcp/tools/execute_query_tool.ts +0 -265
  114. package/src/server.ts +0 -1195
  115. package/src/service/connection.spec.ts +0 -1331
  116. package/src/service/connection.ts +0 -1511
  117. package/src/service/connection_service.spec.ts +0 -641
  118. package/src/service/connection_service.ts +0 -181
  119. package/src/service/db_utils.spec.ts +0 -714
  120. package/src/service/db_utils.ts +0 -1227
  121. package/src/service/filter.spec.ts +0 -447
  122. package/src/service/filter.ts +0 -337
  123. package/src/service/filter_integration.spec.ts +0 -825
  124. package/src/service/gcs_s3_utils.ts +0 -427
  125. package/src/service/manifest_service.spec.ts +0 -201
  126. package/src/service/manifest_service.ts +0 -106
  127. package/src/service/materialization_service.spec.ts +0 -648
  128. package/src/service/materialization_service.ts +0 -929
  129. package/src/service/materialized_table_gc.spec.ts +0 -383
  130. package/src/service/materialized_table_gc.ts +0 -279
  131. package/src/service/model.spec.ts +0 -257
  132. package/src/service/model.ts +0 -1069
  133. package/src/service/package.spec.ts +0 -236
  134. package/src/service/package.ts +0 -463
  135. package/src/service/project.ts +0 -681
  136. package/src/service/project_compile.spec.ts +0 -197
  137. package/src/service/project_store.spec.ts +0 -859
  138. package/src/service/project_store.ts +0 -1531
  139. package/src/service/quoting.ts +0 -41
  140. package/src/service/resolve_project.ts +0 -13
  141. package/src/storage/BaseRepository.ts +0 -31
  142. package/src/storage/DatabaseInterface.ts +0 -180
  143. package/src/storage/StorageManager.mock.ts +0 -59
  144. package/src/storage/StorageManager.ts +0 -210
  145. package/src/storage/duckdb/ConnectionRepository.ts +0 -141
  146. package/src/storage/duckdb/DuckDBConnection.ts +0 -177
  147. package/src/storage/duckdb/DuckDBManifestStore.ts +0 -70
  148. package/src/storage/duckdb/DuckDBRepository.ts +0 -224
  149. package/src/storage/duckdb/ManifestRepository.ts +0 -119
  150. package/src/storage/duckdb/MaterializationRepository.ts +0 -249
  151. package/src/storage/duckdb/PackageRepository.ts +0 -135
  152. package/src/storage/duckdb/ProjectRepository.ts +0 -148
  153. package/src/storage/duckdb/manifest_store.spec.ts +0 -133
  154. package/src/storage/duckdb/schema.ts +0 -147
  155. package/src/storage/ducklake/DuckLakeManifestStore.ts +0 -146
  156. package/src/utils.ts +0 -13
  157. package/tests/fixtures/persist-test/data/orders.csv +0 -5
  158. package/tests/fixtures/persist-test/persist_test.malloy +0 -11
  159. package/tests/fixtures/persist-test/publisher.json +0 -5
  160. package/tests/fixtures/publisher.config.json +0 -15
  161. package/tests/harness/e2e.ts +0 -96
  162. package/tests/harness/mcp_test_setup.ts +0 -344
  163. package/tests/harness/mocks.ts +0 -37
  164. package/tests/harness/rest_e2e.ts +0 -68
  165. package/tests/harness/uris.ts +0 -31
  166. package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +0 -470
  167. package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +0 -434
  168. package/tests/integration/mcp/mcp_resource.integration.spec.ts +0 -663
  169. package/tests/integration/mcp/mcp_transport.integration.spec.ts +0 -122
  170. package/tests/integration/mcp/setup.spec.ts +0 -5
  171. package/tests/unit/duckdb/attached_databases.test.ts +0 -1132
  172. package/tests/unit/ducklake/ducklake.test.ts +0 -950
  173. package/tests/unit/mcp/prompt_definitions.test.ts +0 -102
  174. package/tests/unit/mcp/prompt_happy.test.ts +0 -51
  175. package/tsconfig.json +0 -32
package/src/server.ts DELETED
@@ -1,1195 +0,0 @@
1
- // Pre-load the instrumentation module; the instrumentation module must be loaded before the other imports.
2
- import "./instrumentation";
3
- import {
4
- getPrometheusMetricsHandler,
5
- httpMetricsMiddleware,
6
- } from "./instrumentation";
7
-
8
- import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
9
- import * as bodyParser from "body-parser";
10
- import cors from "cors";
11
- import express from "express";
12
- import * as http from "http";
13
- import { createProxyMiddleware } from "http-proxy-middleware";
14
- import { AddressInfo } from "net";
15
- import * as path from "path";
16
- import { CompileController } from "./controller/compile.controller";
17
- import { ConnectionController } from "./controller/connection.controller";
18
- import { DatabaseController } from "./controller/database.controller";
19
- import { ModelController } from "./controller/model.controller";
20
- import { PackageController } from "./controller/package.controller";
21
- import { QueryController } from "./controller/query.controller";
22
- import { WatchModeController } from "./controller/watch-mode.controller";
23
- import {
24
- BadRequestError,
25
- internalErrorToHttpError,
26
- NotImplementedError,
27
- } from "./errors";
28
- import {
29
- drainingGuard,
30
- registerHealthEndpoints,
31
- registerSignalHandlers,
32
- } from "./health";
33
- import { logger, loggerMiddleware } from "./logger";
34
-
35
- import { ManifestController } from "./controller/manifest.controller";
36
- import { MaterializationController } from "./controller/materialization.controller";
37
- import { initializeMcpServer } from "./mcp/server";
38
- import { ManifestService } from "./service/manifest_service";
39
- import { MaterializationService } from "./service/materialization_service";
40
- import { ProjectStore } from "./service/project_store";
41
-
42
- /** Normalize an Express query param into a string[] or undefined. */
43
- export function normalizeQueryArray(value: unknown): string[] | undefined {
44
- if (value === undefined || value === null) return undefined;
45
- if (Array.isArray(value)) return value.map(String);
46
- return [String(value)];
47
- }
48
-
49
- // Parse command line arguments
50
- function parseArgs() {
51
- const args = process.argv.slice(2);
52
- for (let i = 0; i < args.length; i++) {
53
- const arg = args[i];
54
- if (arg === "--port" && args[i + 1]) {
55
- process.env.PUBLISHER_PORT = args[i + 1];
56
- i++;
57
- } else if (arg === "--host" && args[i + 1]) {
58
- process.env.PUBLISHER_HOST = args[i + 1];
59
- i++;
60
- } else if (arg === "--server_root" && args[i + 1]) {
61
- process.env.SERVER_ROOT = args[i + 1];
62
- i++;
63
- } else if (arg === "--mcp_port" && args[i + 1]) {
64
- process.env.MCP_PORT = args[i + 1];
65
- i++;
66
- } else if (arg === "--shutdown_drain_duration_seconds" && args[i + 1]) {
67
- process.env.SHUTDOWN_DRAIN_DURATION_SECONDS = args[i + 1];
68
- i++;
69
- } else if (
70
- arg === "--shutdown_graceful_close_timeout_seconds" &&
71
- args[i + 1]
72
- ) {
73
- process.env.SHUTDOWN_GRACEFUL_CLOSE_TIMEOUT_SECONDS = args[i + 1];
74
- i++;
75
- } else if (arg === "--init") {
76
- process.env.INITIALIZE_STORAGE = "true";
77
- } else if (arg === "--help" || arg === "-h") {
78
- console.log("Malloy Publisher Server");
79
- console.log("");
80
- console.log("Usage: malloy-publisher [options]");
81
- console.log("");
82
- console.log("Options:");
83
- console.log(
84
- " --port <number> Port to run the server on (default: 4000)",
85
- );
86
- console.log(
87
- " --host <string> Host to bind the server to (default: localhost)",
88
- );
89
- console.log(
90
- " --server_root <path> Root directory to serve files from (default: .)",
91
- );
92
- console.log(
93
- " --mcp_port <number> Port for MCP server (default: 4040)",
94
- );
95
- console.log(
96
- " --shutdown_drain_duration_seconds <number> Time in seconds to keep service in draining state before closing servers (default: 0)",
97
- );
98
- console.log(
99
- " --shutdown_graceful_close_timeout_seconds <number> Time in seconds to wait after closing servers before exit (default: 0)",
100
- );
101
- console.log(
102
- " --init Initialize the storage (default: false)",
103
- );
104
- console.log(" --help, -h Show this help message");
105
- process.exit(0);
106
- }
107
- }
108
- }
109
-
110
- // Parse CLI arguments before setting up constants
111
- parseArgs();
112
-
113
- const PUBLISHER_PORT = Number(process.env.PUBLISHER_PORT || 4000);
114
- const PUBLISHER_HOST = process.env.PUBLISHER_HOST || "0.0.0.0";
115
- const MCP_PORT = Number(process.env.MCP_PORT || 4040);
116
- const MCP_ENDPOINT = "/mcp";
117
- const SHUTDOWN_DRAIN_DURATION_SECONDS = Number(
118
- process.env.SHUTDOWN_DRAIN_DURATION_SECONDS || 0,
119
- );
120
- const SHUTDOWN_GRACEFUL_CLOSE_TIMEOUT_SECONDS = Number(
121
- process.env.SHUTDOWN_GRACEFUL_CLOSE_TIMEOUT_SECONDS || 0,
122
- );
123
- // Find the app directory - handle NPX vs local execution
124
- let ROOT: string;
125
- if (require.main) {
126
- // Use the main module's directory (works for NPX and direct execution)
127
- ROOT = path.join(path.dirname(require.main.filename), "app");
128
- } else {
129
- // Fallback to current script directory
130
- ROOT = path.join(path.dirname(process.argv[1] || __filename), "app");
131
- }
132
- const SERVER_ROOT = path.resolve(process.cwd(), process.env.SERVER_ROOT || ".");
133
- const API_PREFIX = "/api/v0";
134
- const isDevelopment = process.env["NODE_ENV"] === "development";
135
-
136
- export const app = express();
137
- app.use(loggerMiddleware);
138
- app.use(httpMetricsMiddleware);
139
- const projectStore = new ProjectStore(SERVER_ROOT);
140
- const manifestService = new ManifestService(projectStore);
141
- const watchModeController = new WatchModeController(projectStore);
142
- const connectionController = new ConnectionController(projectStore);
143
- const modelController = new ModelController(projectStore);
144
- const packageController = new PackageController(projectStore, manifestService);
145
- const databaseController = new DatabaseController(projectStore);
146
- const queryController = new QueryController(projectStore);
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
- );
159
-
160
- export const mcpApp = express();
161
-
162
- // Register health endpoints on mcpApp (for E2E tests)
163
- registerHealthEndpoints(mcpApp);
164
-
165
- mcpApp.use(MCP_ENDPOINT, express.json());
166
- mcpApp.use(MCP_ENDPOINT, cors());
167
-
168
- mcpApp.all(MCP_ENDPOINT, async (req, res) => {
169
- logger.info(`[MCP Debug] Handling ${req.method} (Stateless)`);
170
-
171
- try {
172
- if (req.method === "POST") {
173
- const transport = new StreamableHTTPServerTransport({
174
- sessionIdGenerator: undefined,
175
- });
176
-
177
- transport.onclose = () => {
178
- logger.info(
179
- `[MCP Transport Info] Stateless transport closed for a request.`,
180
- );
181
- };
182
- transport.onerror = (err: Error) => {
183
- logger.error(`[MCP Transport Error] Stateless transport error:`, {
184
- error: err,
185
- });
186
- };
187
-
188
- const requestMcpServer = initializeMcpServer(projectStore);
189
- await requestMcpServer.connect(transport);
190
-
191
- res.on("close", () => {
192
- logger.info(
193
- "[MCP Transport Info] Response closed, cleaning up stateless transport.",
194
- );
195
- transport.close().catch((err) => {
196
- logger.error(
197
- "[MCP Transport Error] Error closing stateless transport on response close:",
198
- { error: err },
199
- );
200
- });
201
- });
202
-
203
- await transport.handleRequest(req, res, req.body);
204
- } else if (req.method === "GET" || req.method === "DELETE") {
205
- logger.warn(
206
- `[MCP Transport Warn] Method Not Allowed in Stateless Mode: ${req.method}`,
207
- );
208
- res.setHeader("Allow", "POST");
209
- res.status(405).json({
210
- jsonrpc: "2.0",
211
- error: {
212
- code: -32601,
213
- message: "Method Not Allowed in Stateless Mode",
214
- },
215
- id: null,
216
- });
217
- return;
218
- } else {
219
- logger.warn(`[MCP Transport Warn] Method Not Allowed: ${req.method}`);
220
- res.setHeader("Allow", "POST");
221
- res.status(405).json({
222
- jsonrpc: "2.0",
223
- error: { code: -32601, message: "Method Not Allowed" },
224
- id: null,
225
- });
226
- return;
227
- }
228
- } catch (error) {
229
- logger.error(
230
- `[MCP Transport Error] Unhandled error in ${req.method} handler (Stateless):`,
231
- { error },
232
- );
233
- if (!res.headersSent) {
234
- res.status(500).json({
235
- jsonrpc: "2.0",
236
- error: { code: -32603, message: "Internal server error" },
237
- id:
238
- typeof req.body === "object" &&
239
- req.body !== null &&
240
- "id" in req.body
241
- ? req.body.id
242
- : null,
243
- });
244
- }
245
- }
246
- });
247
-
248
- // Only serve static files in production mode
249
- // Otherwise we proxy to the React dev server
250
- if (!isDevelopment) {
251
- app.use("/", express.static(ROOT));
252
- app.use("/api-doc.html", express.static(path.join(ROOT, "api-doc.html")));
253
- } else {
254
- // In development mode, proxy requests to React dev server
255
- // Handle API routes first
256
- app.use(`${API_PREFIX}`, loggerMiddleware);
257
-
258
- // Proxy everything else to Vite
259
- app.use(
260
- createProxyMiddleware({
261
- target: "http://localhost:5173",
262
- changeOrigin: true,
263
- ws: true,
264
- pathFilter: (path) =>
265
- !path.startsWith("/api/") &&
266
- !path.startsWith("/metrics") &&
267
- !path.startsWith("/health"),
268
- }),
269
- );
270
- }
271
-
272
- const setVersionIdError = (res: express.Response) => {
273
- const { json, status } = internalErrorToHttpError(
274
- new NotImplementedError("Version IDs not implemented."),
275
- );
276
- res.status(status).json(json);
277
- };
278
-
279
- app.use(
280
- cors({
281
- origin: "http://localhost:5173",
282
- credentials: true,
283
- }),
284
- );
285
-
286
- // Set body-parser JSON limit to 1Mb (default: 100kb)
287
- app.use(bodyParser.json({ limit: "1mb" }));
288
-
289
- // Register health check endpoints on main app:
290
- // - Required for production/Kubernetes monitoring (main server on PUBLISHER_PORT)
291
- registerHealthEndpoints(app);
292
-
293
- // Register Prometheus metrics endpoint
294
- try {
295
- const metricsHandler = getPrometheusMetricsHandler();
296
- app.get("/metrics", metricsHandler);
297
- logger.info("Prometheus metrics endpoint registered at /metrics");
298
- } catch (error) {
299
- logger.warn("Failed to register Prometheus metrics endpoint", { error });
300
- }
301
-
302
- // Register draining guard middleware - must be after health endpoints but before other routes
303
- app.use(drainingGuard);
304
-
305
- app.get(`${API_PREFIX}/status`, async (_req, res) => {
306
- try {
307
- const status = await projectStore.getStatus();
308
- res.status(200).json(status);
309
- } catch (error) {
310
- logger.error("Error getting status", { error });
311
- const { json, status } = internalErrorToHttpError(error as Error);
312
- res.status(status).json(json);
313
- }
314
- });
315
-
316
- app.get(`${API_PREFIX}/watch-mode/status`, watchModeController.getWatchStatus);
317
- app.post(`${API_PREFIX}/watch-mode/start`, watchModeController.startWatching);
318
- app.post(`${API_PREFIX}/watch-mode/stop`, watchModeController.stopWatchMode);
319
-
320
- app.get(`${API_PREFIX}/projects`, async (_req, res) => {
321
- try {
322
- res.status(200).json(await projectStore.listProjects());
323
- } catch (error) {
324
- logger.error(error);
325
- const { json, status } = internalErrorToHttpError(error as Error);
326
- res.status(status).json(json);
327
- }
328
- });
329
-
330
- app.post(`${API_PREFIX}/projects`, async (req, res) => {
331
- try {
332
- logger.info("Adding project", { body: req.body });
333
- const project = await projectStore.addProject(req.body);
334
- res.status(200).json(await project.serialize());
335
- } catch (error) {
336
- logger.error(error);
337
- const { json, status } = internalErrorToHttpError(error as Error);
338
- res.status(status).json(json);
339
- }
340
- });
341
-
342
- app.get(`${API_PREFIX}/projects/:projectName`, async (req, res) => {
343
- try {
344
- const project = await projectStore.getProject(
345
- req.params.projectName,
346
- req.query.reload === "true",
347
- );
348
- res.status(200).json(await project.serialize());
349
- } catch (error) {
350
- logger.error(error);
351
- const { json, status } = internalErrorToHttpError(error as Error);
352
- res.status(status).json(json);
353
- }
354
- });
355
-
356
- app.patch(`${API_PREFIX}/projects/:projectName`, async (req, res) => {
357
- try {
358
- const project = await projectStore.updateProject(req.body);
359
- res.status(200).json(await project.serialize());
360
- } catch (error) {
361
- logger.error(error);
362
- const { json, status } = internalErrorToHttpError(error as Error);
363
- res.status(status).json(json);
364
- }
365
- });
366
-
367
- app.delete(`${API_PREFIX}/projects/:projectName`, async (req, res) => {
368
- try {
369
- const project = await projectStore.deleteProject(req.params.projectName);
370
- res.status(200).json(await project?.serialize());
371
- } catch (error) {
372
- logger.error(error);
373
- const { json, status } = internalErrorToHttpError(error as Error);
374
- res.status(status).json(json);
375
- }
376
- });
377
-
378
- app.get(`${API_PREFIX}/projects/:projectName/connections`, async (req, res) => {
379
- try {
380
- res.status(200).json(
381
- await connectionController.listConnections(req.params.projectName),
382
- );
383
- } catch (error) {
384
- logger.error(error);
385
- const { json, status } = internalErrorToHttpError(error as Error);
386
- res.status(status).json(json);
387
- }
388
- });
389
-
390
- app.get(
391
- `${API_PREFIX}/projects/:projectName/connections/:connectionName`,
392
- async (req, res) => {
393
- try {
394
- res.status(200).json(
395
- await connectionController.getConnection(
396
- req.params.projectName,
397
- req.params.connectionName,
398
- ),
399
- );
400
- } catch (error) {
401
- logger.error(error);
402
- const { json, status } = internalErrorToHttpError(error as Error);
403
- res.status(status).json(json);
404
- }
405
- },
406
- );
407
-
408
- app.post(
409
- `${API_PREFIX}/projects/:projectName/connections/:connectionName`,
410
- async (req, res) => {
411
- try {
412
- const result = await connectionController.addConnection(
413
- req.params.projectName,
414
- req.params.connectionName,
415
- req.body,
416
- );
417
- res.status(201).json(result);
418
- } catch (error) {
419
- logger.error("Error creating connection", { error });
420
- const { json, status } = internalErrorToHttpError(error as Error);
421
- res.status(status).json(json);
422
- }
423
- },
424
- );
425
-
426
- app.patch(
427
- `${API_PREFIX}/projects/:projectName/connections/:connectionName`,
428
- async (req, res) => {
429
- try {
430
- const result = await connectionController.updateConnection(
431
- req.params.projectName,
432
- req.params.connectionName,
433
- req.body,
434
- );
435
- res.status(200).json(result);
436
- } catch (error) {
437
- logger.error("Error updating connection", { error });
438
- const { json, status } = internalErrorToHttpError(error as Error);
439
- res.status(status).json(json);
440
- }
441
- },
442
- );
443
-
444
- app.delete(
445
- `${API_PREFIX}/projects/:projectName/connections/:connectionName`,
446
- async (req, res) => {
447
- try {
448
- const result = await connectionController.deleteConnection(
449
- req.params.projectName,
450
- req.params.connectionName,
451
- );
452
- res.status(200).json(result);
453
- } catch (error) {
454
- logger.error("Error deleting connection", { error });
455
- const { json, status } = internalErrorToHttpError(error as Error);
456
- res.status(status).json(json);
457
- }
458
- },
459
- );
460
-
461
- app.post(`${API_PREFIX}/connections/test`, async (req, res) => {
462
- try {
463
- const connectionStatus =
464
- await connectionController.testConnectionConfiguration(req.body);
465
- res.status(200).json(connectionStatus);
466
- } catch (error) {
467
- logger.error(error);
468
- const { json, status } = internalErrorToHttpError(error as Error);
469
- res.status(status).json(json);
470
- }
471
- });
472
-
473
- app.get(
474
- `${API_PREFIX}/projects/:projectName/connections/:connectionName/schemas`,
475
- async (req, res) => {
476
- try {
477
- res.status(200).json(
478
- await connectionController.listSchemas(
479
- req.params.projectName,
480
- req.params.connectionName,
481
- ),
482
- );
483
- } catch (error) {
484
- logger.error(error);
485
- const { json, status } = internalErrorToHttpError(error as Error);
486
- res.status(status).json(json);
487
- }
488
- },
489
- );
490
-
491
- app.get(
492
- `${API_PREFIX}/projects/:projectName/connections/:connectionName/schemas/:schemaName/tables`,
493
- async (req, res) => {
494
- logger.info("req.params", { params: req.params });
495
- try {
496
- const results = await connectionController.listTables(
497
- req.params.projectName,
498
- req.params.connectionName,
499
- req.params.schemaName,
500
- normalizeQueryArray(req.query.tableNames),
501
- );
502
- res.status(200).json(results);
503
- } catch (error) {
504
- logger.error(error);
505
- const { json, status } = internalErrorToHttpError(error as Error);
506
- res.status(status).json(json);
507
- }
508
- },
509
- );
510
-
511
- app.get(
512
- `${API_PREFIX}/projects/:projectName/connections/:connectionName/schemas/:schemaName/tables/:tablePath`,
513
- async (req, res) => {
514
- logger.info("req.params", { params: req.params });
515
- try {
516
- const results = await connectionController.getTable(
517
- req.params.projectName,
518
- req.params.connectionName,
519
- req.params.schemaName,
520
- req.params.tablePath,
521
- );
522
- res.status(200).json(results);
523
- } catch (error) {
524
- logger.error(error);
525
- const { json, status } = internalErrorToHttpError(error as Error);
526
- res.status(status).json(json);
527
- }
528
- },
529
- );
530
-
531
- /**
532
- * @deprecated Use /projects/:projectName/connections/:connectionName/sqlSource POST method instead
533
- */
534
- app.get(
535
- `${API_PREFIX}/projects/:projectName/connections/:connectionName/sqlSource`,
536
- async (req, res) => {
537
- try {
538
- res.status(200).json(
539
- await connectionController.getConnectionSqlSource(
540
- req.params.projectName,
541
- req.params.connectionName,
542
- req.query.sqlStatement as string,
543
- ),
544
- );
545
- } catch (error) {
546
- logger.error(error);
547
- const { json, status } = internalErrorToHttpError(error as Error);
548
- res.status(status).json(json);
549
- }
550
- },
551
- );
552
-
553
- app.post(
554
- `${API_PREFIX}/projects/:projectName/connections/:connectionName/sqlSource`,
555
- async (req, res) => {
556
- try {
557
- res.status(200).json(
558
- await connectionController.getConnectionSqlSource(
559
- req.params.projectName,
560
- req.params.connectionName,
561
- req.body.sqlStatement as string,
562
- ),
563
- );
564
- } catch (error) {
565
- logger.error(error);
566
- const { json, status } = internalErrorToHttpError(error as Error);
567
- res.status(status).json(json);
568
- }
569
- },
570
- );
571
-
572
- /**
573
- * @deprecated Use /projects/:projectName/connections/:connectionName/queryData POST method instead
574
- */
575
- app.get(
576
- `${API_PREFIX}/projects/:projectName/connections/:connectionName/queryData`,
577
- async (req, res) => {
578
- try {
579
- res.status(200).json(
580
- await connectionController.getConnectionQueryData(
581
- req.params.projectName,
582
- req.params.connectionName,
583
- req.query.sqlStatement as string,
584
- req.query.options as string,
585
- ),
586
- );
587
- } catch (error) {
588
- logger.error(error);
589
- const { json, status } = internalErrorToHttpError(error as Error);
590
- res.status(status).json(json);
591
- }
592
- },
593
- );
594
-
595
- app.post(
596
- `${API_PREFIX}/projects/:projectName/connections/:connectionName/sqlQuery`,
597
- async (req, res) => {
598
- try {
599
- res.status(200).json(
600
- await connectionController.getConnectionQueryData(
601
- req.params.projectName,
602
- req.params.connectionName,
603
- req.body.sqlStatement as string,
604
- req.body.options as string,
605
- ),
606
- );
607
- } catch (error) {
608
- logger.error(error);
609
- const { json, status } = internalErrorToHttpError(error as Error);
610
- res.status(status).json(json);
611
- }
612
- },
613
- );
614
-
615
- /**
616
- * @deprecated Use /projects/:projectName/connections/:connectionName/temporaryTable POST method instead
617
- */
618
- app.get(
619
- `${API_PREFIX}/projects/:projectName/connections/:connectionName/temporaryTable`,
620
- async (req, res) => {
621
- try {
622
- res.status(200).json(
623
- await connectionController.getConnectionTemporaryTable(
624
- req.params.projectName,
625
- req.params.connectionName,
626
- req.query.sqlStatement as string,
627
- ),
628
- );
629
- } catch (error) {
630
- logger.error(error);
631
- const { json, status } = internalErrorToHttpError(error as Error);
632
- res.status(status).json(json);
633
- }
634
- },
635
- );
636
-
637
- app.post(
638
- `${API_PREFIX}/projects/:projectName/connections/:connectionName/sqlTemporaryTable`,
639
- async (req, res) => {
640
- try {
641
- res.status(200).json(
642
- await connectionController.getConnectionTemporaryTable(
643
- req.params.projectName,
644
- req.params.connectionName,
645
- req.body.sqlStatement as string,
646
- ),
647
- );
648
- } catch (error) {
649
- logger.error(error);
650
- const { json, status } = internalErrorToHttpError(error as Error);
651
- res.status(status).json(json);
652
- }
653
- },
654
- );
655
-
656
- app.get(`${API_PREFIX}/projects/:projectName/packages`, async (req, res) => {
657
- if (req.query.versionId) {
658
- setVersionIdError(res);
659
- return;
660
- }
661
-
662
- try {
663
- res.status(200).json(
664
- await packageController.listPackages(req.params.projectName),
665
- );
666
- } catch (error) {
667
- logger.error(error);
668
- const { json, status } = internalErrorToHttpError(error as Error);
669
- res.status(status).json(json);
670
- }
671
- });
672
-
673
- app.post(`${API_PREFIX}/projects/:projectName/packages`, async (req, res) => {
674
- try {
675
- const autoLoadManifest = req.query.autoLoadManifest === "true";
676
- const _package = await packageController.addPackage(
677
- req.params.projectName,
678
- req.body,
679
- { autoLoadManifest },
680
- );
681
- res.status(200).json(_package?.getPackageMetadata());
682
- } catch (error) {
683
- logger.error(error);
684
- const { json, status } = internalErrorToHttpError(error as Error);
685
- res.status(status).json(json);
686
- }
687
- });
688
-
689
- app.get(
690
- `${API_PREFIX}/projects/:projectName/packages/:packageName`,
691
- async (req, res) => {
692
- if (req.query.versionId) {
693
- setVersionIdError(res);
694
- return;
695
- }
696
-
697
- try {
698
- res.status(200).json(
699
- await packageController.getPackage(
700
- req.params.projectName,
701
- req.params.packageName,
702
- req.query.reload === "true",
703
- ),
704
- );
705
- } catch (error) {
706
- logger.error(error);
707
- const { json, status } = internalErrorToHttpError(error as Error);
708
- res.status(status).json(json);
709
- }
710
- },
711
- );
712
-
713
- app.patch(
714
- `${API_PREFIX}/projects/:projectName/packages/:packageName`,
715
- async (req, res) => {
716
- try {
717
- res.status(200).json(
718
- await packageController.updatePackage(
719
- req.params.projectName,
720
- req.params.packageName,
721
- req.body,
722
- ),
723
- );
724
- } catch (error) {
725
- logger.error(error);
726
- const { json, status } = internalErrorToHttpError(error as Error);
727
- res.status(status).json(json);
728
- }
729
- },
730
- );
731
-
732
- app.delete(
733
- `${API_PREFIX}/projects/:projectName/packages/:packageName`,
734
- async (req, res) => {
735
- try {
736
- res.status(200).json(
737
- await packageController.deletePackage(
738
- req.params.projectName,
739
- req.params.packageName,
740
- ),
741
- );
742
- } catch (error) {
743
- logger.error(error);
744
- const { json, status } = internalErrorToHttpError(error as Error);
745
- res.status(status).json(json);
746
- }
747
- },
748
- );
749
-
750
- app.get(
751
- `${API_PREFIX}/projects/:projectName/packages/:packageName/models`,
752
- async (req, res) => {
753
- if (req.query.versionId) {
754
- setVersionIdError(res);
755
- return;
756
- }
757
-
758
- try {
759
- res.status(200).json(
760
- await modelController.listModels(
761
- req.params.projectName,
762
- req.params.packageName,
763
- ),
764
- );
765
- } catch (error) {
766
- logger.error(error);
767
- const { json, status } = internalErrorToHttpError(error as Error);
768
- res.status(status).json(json);
769
- }
770
- },
771
- );
772
-
773
- app.get(
774
- `${API_PREFIX}/projects/:projectName/packages/:packageName/models/*?`,
775
- async (req, res) => {
776
- if (req.query.versionId) {
777
- setVersionIdError(res);
778
- return;
779
- }
780
-
781
- try {
782
- // Express stores wildcard matches in params['0']
783
- const modelPath = (req.params as Record<string, string>)["0"];
784
- res.status(200).json(
785
- await modelController.getModel(
786
- req.params.projectName,
787
- req.params.packageName,
788
- modelPath,
789
- ),
790
- );
791
- } catch (error) {
792
- logger.error(error);
793
- const { json, status } = internalErrorToHttpError(error as Error);
794
- res.status(status).json(json);
795
- }
796
- },
797
- );
798
-
799
- app.get(
800
- `${API_PREFIX}/projects/:projectName/packages/:packageName/notebooks`,
801
- async (req, res) => {
802
- if (req.query.versionId) {
803
- setVersionIdError(res);
804
- return;
805
- }
806
-
807
- try {
808
- res.status(200).json(
809
- await modelController.listNotebooks(
810
- req.params.projectName,
811
- req.params.packageName,
812
- ),
813
- );
814
- } catch (error) {
815
- logger.error(error);
816
- const { json, status } = internalErrorToHttpError(error as Error);
817
- res.status(status).json(json);
818
- }
819
- },
820
- );
821
-
822
- // Execute notebook cell route must come BEFORE the general get notebook route
823
- // to avoid the wildcard matching incorrectly
824
- app.get(
825
- `${API_PREFIX}/projects/:projectName/packages/:packageName/notebooks/*/cells/:cellIndex`,
826
- async (req, res) => {
827
- if (req.query.versionId) {
828
- setVersionIdError(res);
829
- return;
830
- }
831
-
832
- try {
833
- const cellIndex = parseInt(req.params.cellIndex, 10);
834
- if (isNaN(cellIndex)) {
835
- res.status(400).json({
836
- error: "Invalid cell index",
837
- });
838
- return;
839
- }
840
-
841
- // Express stores wildcard matches in params['0']
842
- const notebookPath = (req.params as Record<string, string>)["0"];
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
-
859
- res.status(200).json(
860
- await modelController.executeNotebookCell(
861
- req.params.projectName,
862
- req.params.packageName,
863
- notebookPath,
864
- cellIndex,
865
- filterParams,
866
- bypassFilters,
867
- ),
868
- );
869
- } catch (error) {
870
- logger.error(error);
871
- const { json, status } = internalErrorToHttpError(error as Error);
872
- res.status(status).json(json);
873
- }
874
- },
875
- );
876
-
877
- app.get(
878
- `${API_PREFIX}/projects/:projectName/packages/:packageName/notebooks/*?`,
879
- async (req, res) => {
880
- if (req.query.versionId) {
881
- setVersionIdError(res);
882
- return;
883
- }
884
-
885
- try {
886
- // Express stores wildcard matches in params['0']
887
- const notebookPath = (req.params as Record<string, string>)["0"];
888
- res.status(200).json(
889
- await modelController.getNotebook(
890
- req.params.projectName,
891
- req.params.packageName,
892
- notebookPath,
893
- ),
894
- );
895
- } catch (error) {
896
- logger.error(error);
897
- const { json, status } = internalErrorToHttpError(error as Error);
898
- res.status(status).json(json);
899
- }
900
- },
901
- );
902
-
903
- app.post(
904
- `${API_PREFIX}/projects/:projectName/packages/:packageName/models/*?/query`,
905
- async (req, res) => {
906
- if (req.body.versionId) {
907
- setVersionIdError(res);
908
- return;
909
- }
910
-
911
- try {
912
- // Express stores wildcard matches in params['0']
913
- const modelPath = (req.params as Record<string, string>)["0"];
914
- res.status(200).json(
915
- await queryController.getQuery(
916
- req.params.projectName,
917
- req.params.packageName,
918
- modelPath,
919
- req.body.sourceName as string,
920
- req.body.queryName as string,
921
- req.body.query as string,
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,
927
- ),
928
- );
929
- } catch (error) {
930
- logger.error(error);
931
- const { json, status } = internalErrorToHttpError(error as Error);
932
- res.status(status).json(json);
933
- }
934
- },
935
- );
936
-
937
- app.get(
938
- `${API_PREFIX}/projects/:projectName/packages/:packageName/databases`,
939
- async (req, res) => {
940
- if (req.query.versionId) {
941
- setVersionIdError(res);
942
- return;
943
- }
944
-
945
- try {
946
- res.status(200).json(
947
- await databaseController.listDatabases(
948
- req.params.projectName,
949
- req.params.packageName,
950
- ),
951
- );
952
- } catch (error) {
953
- logger.error(error);
954
- const { json, status } = internalErrorToHttpError(error as Error);
955
- res.status(status).json(json);
956
- }
957
- },
958
- );
959
-
960
- app.post(
961
- `${API_PREFIX}/projects/:projectName/packages/:packageName/models/:modelName/compile`,
962
- async (req, res) => {
963
- try {
964
- const result = await compileController.compile(
965
- req.params.projectName,
966
- req.params.packageName,
967
- req.params.modelName,
968
- req.body.source,
969
- req.body.includeSql === true,
970
- );
971
- res.status(200).json(result);
972
- } catch (error) {
973
- logger.error("Compilation error", { error });
974
- const { json, status } = internalErrorToHttpError(error as Error);
975
- res.status(status).json(json);
976
- }
977
- },
978
- );
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
-
1147
- // Modify the catch-all route to only serve index.html in production
1148
- if (!isDevelopment) {
1149
- app.get("*", (_req, res) => res.sendFile(path.resolve(ROOT, "index.html")));
1150
- }
1151
-
1152
- app.use(
1153
- (
1154
- err: Error,
1155
- _req: express.Request,
1156
- res: express.Response,
1157
- _next: express.NextFunction,
1158
- ) => {
1159
- logger.error("Unhandled error:", err);
1160
- const { json, status } = internalErrorToHttpError(err);
1161
- res.status(status).json(json);
1162
- },
1163
- );
1164
-
1165
- const mainServer = http.createServer({ maxHeaderSize: 262144 }, app);
1166
-
1167
- mainServer.timeout = 600000;
1168
- mainServer.keepAliveTimeout = 600000;
1169
- mainServer.headersTimeout = 600000;
1170
-
1171
- mainServer.listen(PUBLISHER_PORT, PUBLISHER_HOST, () => {
1172
- const address = mainServer.address() as AddressInfo;
1173
- logger.info(
1174
- `Publisher server listening at http://${address.address}:${address.port}`,
1175
- );
1176
- if (isDevelopment) {
1177
- logger.info(
1178
- "Running in development mode - proxying to React dev server at http://localhost:5173",
1179
- );
1180
- }
1181
- });
1182
- const mcpServer = mcpApp.listen(MCP_PORT, PUBLISHER_HOST, () => {
1183
- logger.info(`MCP server listening at http://${PUBLISHER_HOST}:${MCP_PORT}`);
1184
- });
1185
-
1186
- mcpServer.timeout = 600000;
1187
- mcpServer.keepAliveTimeout = 600000;
1188
- mcpServer.headersTimeout = 600000;
1189
-
1190
- registerSignalHandlers(
1191
- mainServer,
1192
- mcpServer,
1193
- SHUTDOWN_DRAIN_DURATION_SECONDS,
1194
- SHUTDOWN_GRACEFUL_CLOSE_TIMEOUT_SECONDS,
1195
- );