@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.
- package/README.docker.md +145 -20
- package/README.md +65 -12
- package/dist/app/api-doc.html +5 -0
- package/dist/app/api-doc.yaml +3903 -583
- package/dist/app/assets/EnvironmentPage-DiUgLuCH.js +1 -0
- package/dist/app/assets/HomePage-MhhjhnFj.js +1 -0
- package/dist/app/assets/LightMode-BEyvNUzT.js +1 -0
- package/dist/app/assets/MainPage-D4Cfee9o.js +2 -0
- package/dist/app/assets/MaterializationsPage-nGgGqwSN.js +1 -0
- package/dist/app/assets/ModelPage-DLAcEw-F.js +1 -0
- package/dist/app/assets/PackagePage-Df0ikH7w.js +1 -0
- package/dist/app/assets/RouteError-BFQwtroH.js +1 -0
- package/dist/app/assets/ThemeEditorPage-OoskoQYN.js +1 -0
- package/dist/app/assets/WorkbookPage-CpFgvUDC.js +1 -0
- package/dist/app/assets/{core-CjeTkq8O.es-BqRc6yhC.js → core-lb28vQkR.es-BoVSOc75.js} +1 -1
- package/dist/app/assets/github-dark-DenFmJkN.es-DHJKELXO.js +1 -0
- package/dist/app/assets/index-5eLCcNmP.css +1 -0
- package/dist/app/assets/index-6ypuFH6g.js +1774 -0
- package/dist/app/assets/index-B8pnU_V_.js +615 -0
- package/dist/app/assets/index-BImcHmVP.js +1 -0
- package/dist/app/assets/index-DSoCUF-f.js +2725 -0
- package/dist/app/assets/index-DfqzSItA.js +2 -0
- package/dist/app/assets/index-w_0OQJgZ.js +23 -0
- package/dist/app/index.html +8 -4
- package/dist/cpufeatures-1yrn0vtw.node +0 -0
- package/dist/default-publisher.config.json +23 -0
- package/dist/{instrumentation.js → instrumentation.mjs} +10631 -10573
- package/dist/package_load_worker.mjs +15219 -0
- package/dist/runtime/publisher.js +331 -0
- package/dist/{server.js → server.mjs} +115548 -49590
- package/dist/sshcrypto-vd2k5hq9.node +0 -0
- package/package.json +41 -28
- package/publisher.config.example.bigquery.json +33 -0
- package/publisher.config.example.duckdb.json +23 -0
- package/.eslintrc.json +0 -44
- package/.prettierignore +0 -1
- package/.prettierrc +0 -4
- package/build.ts +0 -38
- package/dist/app/assets/HomePage-Dn3E4CuB.js +0 -1
- package/dist/app/assets/MainPage-BzB3yoqi.js +0 -2
- package/dist/app/assets/ModelPage-C9O_sAXT.js +0 -1
- package/dist/app/assets/PackagePage-DcxKEjBX.js +0 -1
- package/dist/app/assets/ProjectPage-BDj307rF.js +0 -1
- package/dist/app/assets/RouteError-DAShbVCG.js +0 -1
- package/dist/app/assets/WorkbookPage-Cs_XYEaB.js +0 -1
- package/dist/app/assets/index-15BOvhp0.js +0 -456
- package/dist/app/assets/index-Bb2jqquW.js +0 -676
- package/dist/app/assets/index-CMlGQMcl.css +0 -1
- package/dist/app/assets/index-D68X76-7.js +0 -1276
- package/dist/app/assets/index.umd-DGBekgSu.js +0 -1145
- package/dxt/malloy_bridge.py +0 -354
- package/dxt/manifest.json +0 -22
- package/eslint.config.mjs +0 -76
- package/k6-tests/README.md +0 -144
- package/k6-tests/bun.lock +0 -797
- package/k6-tests/load-test/load-test-crud-connections.ts +0 -263
- package/k6-tests/load-test/load-test-crud-packages.ts +0 -461
- package/k6-tests/load-test/load-test-crud-projects.ts +0 -160
- package/k6-tests/load-test/load-test-crud.ts +0 -142
- package/k6-tests/load-test/load-test.ts +0 -784
- package/k6-tests/package.json +0 -27
- package/k6-tests/scripts/run-with-otel.sh +0 -50
- package/k6-tests/smoke-test/smoke-test.ts +0 -158
- package/k6-tests/tsconfig.json +0 -29
- package/k6-tests/utils/client_factory.ts +0 -128
- package/k6-tests/utils/common.ts +0 -489
- package/k6-tests/utils/logger.ts +0 -62
- package/k6-tests/utils/types.d.ts +0 -35
- package/malloy_mcp.dxt +0 -0
- package/publisher.config.json +0 -33
- package/src/config.spec.ts +0 -850
- package/src/config.ts +0 -305
- package/src/constants.ts +0 -10
- package/src/controller/compile.controller.ts +0 -35
- package/src/controller/connection.controller.ts +0 -515
- package/src/controller/database.controller.ts +0 -21
- package/src/controller/manifest.controller.ts +0 -29
- package/src/controller/materialization.controller.ts +0 -125
- package/src/controller/model.controller.ts +0 -107
- package/src/controller/package.controller.ts +0 -182
- package/src/controller/query.controller.ts +0 -60
- package/src/controller/watch-mode.controller.ts +0 -98
- package/src/data_styles.ts +0 -71
- package/src/dto/connection.dto.spec.ts +0 -131
- package/src/dto/connection.dto.ts +0 -174
- package/src/dto/index.ts +0 -2
- package/src/dto/package.dto.spec.ts +0 -42
- package/src/dto/package.dto.ts +0 -12
- package/src/dto/validate.spec.ts +0 -76
- package/src/dto/validate.ts +0 -31
- package/src/errors.ts +0 -116
- package/src/health.ts +0 -189
- package/src/instrumentation.ts +0 -148
- package/src/logger.ts +0 -154
- package/src/mcp/error_messages.ts +0 -199
- package/src/mcp/handler_utils.ts +0 -239
- package/src/mcp/mcp_constants.ts +0 -22
- package/src/mcp/prompts/handlers.ts +0 -84
- package/src/mcp/prompts/index.ts +0 -11
- package/src/mcp/prompts/prompt_definitions.ts +0 -160
- package/src/mcp/prompts/prompt_service.ts +0 -67
- package/src/mcp/prompts/utils.ts +0 -62
- package/src/mcp/resource_metadata.ts +0 -47
- package/src/mcp/resources/model_resource.ts +0 -153
- package/src/mcp/resources/notebook_resource.ts +0 -137
- package/src/mcp/resources/package_resource.ts +0 -370
- package/src/mcp/resources/project_resource.ts +0 -184
- package/src/mcp/resources/query_resource.ts +0 -117
- package/src/mcp/resources/source_resource.ts +0 -141
- package/src/mcp/resources/view_resource.ts +0 -136
- package/src/mcp/server.ts +0 -62
- package/src/mcp/tools/discovery_tools.ts +0 -258
- package/src/mcp/tools/execute_query_tool.ts +0 -265
- package/src/server.ts +0 -1195
- package/src/service/connection.spec.ts +0 -1331
- package/src/service/connection.ts +0 -1511
- package/src/service/connection_service.spec.ts +0 -641
- package/src/service/connection_service.ts +0 -181
- package/src/service/db_utils.spec.ts +0 -714
- package/src/service/db_utils.ts +0 -1227
- package/src/service/filter.spec.ts +0 -447
- package/src/service/filter.ts +0 -337
- package/src/service/filter_integration.spec.ts +0 -825
- package/src/service/gcs_s3_utils.ts +0 -427
- package/src/service/manifest_service.spec.ts +0 -201
- package/src/service/manifest_service.ts +0 -106
- package/src/service/materialization_service.spec.ts +0 -648
- package/src/service/materialization_service.ts +0 -929
- package/src/service/materialized_table_gc.spec.ts +0 -383
- package/src/service/materialized_table_gc.ts +0 -279
- package/src/service/model.spec.ts +0 -257
- package/src/service/model.ts +0 -1069
- package/src/service/package.spec.ts +0 -236
- package/src/service/package.ts +0 -463
- package/src/service/project.ts +0 -681
- package/src/service/project_compile.spec.ts +0 -197
- package/src/service/project_store.spec.ts +0 -859
- package/src/service/project_store.ts +0 -1531
- package/src/service/quoting.ts +0 -41
- package/src/service/resolve_project.ts +0 -13
- package/src/storage/BaseRepository.ts +0 -31
- package/src/storage/DatabaseInterface.ts +0 -180
- package/src/storage/StorageManager.mock.ts +0 -59
- package/src/storage/StorageManager.ts +0 -210
- package/src/storage/duckdb/ConnectionRepository.ts +0 -141
- package/src/storage/duckdb/DuckDBConnection.ts +0 -177
- package/src/storage/duckdb/DuckDBManifestStore.ts +0 -70
- package/src/storage/duckdb/DuckDBRepository.ts +0 -224
- package/src/storage/duckdb/ManifestRepository.ts +0 -119
- package/src/storage/duckdb/MaterializationRepository.ts +0 -249
- package/src/storage/duckdb/PackageRepository.ts +0 -135
- package/src/storage/duckdb/ProjectRepository.ts +0 -148
- package/src/storage/duckdb/manifest_store.spec.ts +0 -133
- package/src/storage/duckdb/schema.ts +0 -147
- package/src/storage/ducklake/DuckLakeManifestStore.ts +0 -146
- package/src/utils.ts +0 -13
- package/tests/fixtures/persist-test/data/orders.csv +0 -5
- package/tests/fixtures/persist-test/persist_test.malloy +0 -11
- package/tests/fixtures/persist-test/publisher.json +0 -5
- package/tests/fixtures/publisher.config.json +0 -15
- package/tests/harness/e2e.ts +0 -96
- package/tests/harness/mcp_test_setup.ts +0 -344
- package/tests/harness/mocks.ts +0 -37
- package/tests/harness/rest_e2e.ts +0 -68
- package/tests/harness/uris.ts +0 -31
- package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +0 -470
- package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +0 -434
- package/tests/integration/mcp/mcp_resource.integration.spec.ts +0 -663
- package/tests/integration/mcp/mcp_transport.integration.spec.ts +0 -122
- package/tests/integration/mcp/setup.spec.ts +0 -5
- package/tests/unit/duckdb/attached_databases.test.ts +0 -1132
- package/tests/unit/ducklake/ducklake.test.ts +0 -950
- package/tests/unit/mcp/prompt_definitions.test.ts +0 -102
- package/tests/unit/mcp/prompt_happy.test.ts +0 -51
- 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
|
-
);
|