@malloy-publisher/server 0.0.382-dev → 0.2.1

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 +3929 -534
  5. package/dist/app/assets/EnvironmentPage-CHq5BF4l.js +1 -0
  6. package/dist/app/assets/HomePage-CIp-pJCa.js +1 -0
  7. package/dist/app/assets/LightMode-mHHPjEeA.js +1 -0
  8. package/dist/app/assets/MainPage-CylxO5Yu.js +2 -0
  9. package/dist/app/assets/MaterializationsPage-a0vjqxib.js +1 -0
  10. package/dist/app/assets/ModelPage-Cz-MrSZQ.js +1 -0
  11. package/dist/app/assets/PackagePage-Cdg5D7AN.js +1 -0
  12. package/dist/app/assets/RouteError-B5SIyn6G.js +1 -0
  13. package/dist/app/assets/ThemeEditorPage-B62rRc-y.js +1 -0
  14. package/dist/app/assets/WorkbookPage-45cG1Gx6.js +1 -0
  15. package/dist/app/assets/{core-CjeTkq8O.es-BqRc6yhC.js → core-DyU47reh.es-DY9b3WjS.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-C5ClsnS7.js +1774 -0
  19. package/dist/app/assets/index-DF9tVqwN.js +1 -0
  20. package/dist/app/assets/index-DSoCUF-f.js +2725 -0
  21. package/dist/app/assets/index-DgVRz2wm.js +2 -0
  22. package/dist/app/assets/index-P-pF8nU5.js +632 -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} +141276 -74705
  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
@@ -1,515 +0,0 @@
1
- import { Connection, RunSQLOptions, TableSourceDef } from "@malloydata/malloy";
2
- import { PersistSQLResults } from "@malloydata/malloy/connection";
3
- import { components } from "../api";
4
- import { BadRequestError, ConnectionError } from "../errors";
5
- import { logger } from "../logger";
6
- import { testConnectionConfig } from "../service/connection";
7
- import { ConnectionService } from "../service/connection_service";
8
- import {
9
- getSchemasForConnection,
10
- listTablesForSchema,
11
- } from "../service/db_utils";
12
- import { ProjectStore } from "../service/project_store";
13
-
14
- type ApiConnection = components["schemas"]["Connection"];
15
- type ApiConnectionStatus = components["schemas"]["ConnectionStatus"];
16
- type ApiSqlSource = components["schemas"]["SqlSource"];
17
- type ApiTable = components["schemas"]["Table"];
18
- type ApiQueryData = components["schemas"]["QueryData"];
19
- type ApiTemporaryTable = components["schemas"]["TemporaryTable"];
20
- type ApiSchema = components["schemas"]["Schema"];
21
- const AZURE_SUPPORTED_SCHEMES = ["https://", "http://", "abfss://", "az://"];
22
- const AZURE_DATA_EXTENSIONS = [
23
- ".parquet",
24
- ".csv",
25
- ".json",
26
- ".jsonl",
27
- ".ndjson",
28
- ];
29
-
30
- /**
31
- * Validates an Azure URL against the three supported patterns:
32
- * 1. Single file: path/file.parquet
33
- * 2. Directory glob: path/*.ext (direct children only, no sub-dirs)
34
- * 3. Recursive: path/** (all data files recursively)
35
- */
36
- function validateAzureUrl(url: string, fieldName: string): void {
37
- if (!AZURE_SUPPORTED_SCHEMES.some((s) => url.startsWith(s))) {
38
- throw new BadRequestError(
39
- `Azure ${fieldName} must use one of: ${AZURE_SUPPORTED_SCHEMES.join(", ")}`,
40
- );
41
- }
42
-
43
- const pathWithoutQuery = url.split("?")[0];
44
- const stars = (pathWithoutQuery.match(/\*/g) || []).length;
45
-
46
- if (stars === 0) {
47
- // Single file — must end with a data extension
48
- const lower = pathWithoutQuery.toLowerCase();
49
- if (!AZURE_DATA_EXTENSIONS.some((ext) => lower.endsWith(ext))) {
50
- throw new BadRequestError(
51
- `Azure ${fieldName}: a single-file URL must end with a data file extension (${AZURE_DATA_EXTENSIONS.join(", ")})`,
52
- );
53
- }
54
- } else if (pathWithoutQuery.endsWith("**")) {
55
- // Recursive — valid, no further checks needed
56
- } else {
57
- // Must be exactly path/*.ext — one star, in the last path segment only
58
- const lastSegment = pathWithoutQuery.split("/").pop() || "";
59
- if (stars !== 1 || !lastSegment.startsWith("*")) {
60
- throw new BadRequestError(
61
- `Azure ${fieldName}: only three URL patterns are supported:\n` +
62
- ` • Single file: path/file.parquet\n` +
63
- ` • Directory glob: path/*.ext (direct children only)\n` +
64
- ` • Recursive: path/** (all data files in subtree)\n` +
65
- `Multi-level globs such as "sub_dir/*/*.parquet" are not supported.`,
66
- );
67
- }
68
- }
69
- }
70
-
71
- function validateAzureAttachedDatabases(connectionConfig: ApiConnection): void {
72
- if (connectionConfig.type !== "duckdb") return;
73
- const attachedDbs =
74
- connectionConfig.duckdbConnection?.attachedDatabases || [];
75
- for (const db of attachedDbs) {
76
- if (db.type !== "azure" || !db.azureConnection) continue;
77
- const { authType, sasUrl, fileUrl } = db.azureConnection;
78
- if (authType === "sas_token" && sasUrl) {
79
- validateAzureUrl(sasUrl, `"${db.name}" sasUrl`);
80
- } else if (authType === "service_principal" && fileUrl) {
81
- validateAzureUrl(fileUrl, `"${db.name}" fileUrl`);
82
- }
83
- }
84
- }
85
-
86
- export class ConnectionController {
87
- private projectStore: ProjectStore;
88
- private connectionService: ConnectionService;
89
- constructor(projectStore: ProjectStore) {
90
- this.projectStore = projectStore;
91
- this.connectionService = new ConnectionService(projectStore);
92
- }
93
-
94
- /**
95
- * Gets the appropriate Malloy connection for a given connection name.
96
- * For DuckDB connections, retrieves from package level; for others, from project level.
97
- */
98
- private async getMalloyConnection(
99
- projectName: string,
100
- connectionName: string,
101
- ): Promise<Connection> {
102
- const project = await this.projectStore.getProject(projectName, false);
103
- const connection = project.getApiConnection(connectionName);
104
-
105
- // For DuckDB connections, get the connection from a package
106
- if (connection.name === "duckdb" && connection.type === "duckdb") {
107
- const packages = await project.listPackages();
108
- if (packages.length === 0) {
109
- return project.getMalloyConnection(connectionName);
110
- }
111
- // For now, use the first package's DuckDB connection
112
- const packageName = packages[0].name;
113
- if (!packageName) {
114
- throw new ConnectionError("Package name is undefined");
115
- }
116
- const pkg = await project.getPackage(packageName);
117
- return pkg.getMalloyConnection(connectionName);
118
- } else {
119
- return project.getMalloyConnection(connectionName);
120
- }
121
- }
122
-
123
- /**
124
- * Fetches a table's schema via the Malloy connection's fetchTableSchema,
125
- * returning an ApiTable with columns and the raw source JSON.
126
- */
127
- private async fetchTable(
128
- malloyConnection: Connection,
129
- tableKey: string,
130
- tablePath: string,
131
- ): Promise<ApiTable> {
132
- try {
133
- const source = await (
134
- malloyConnection as Connection & {
135
- fetchTableSchema: (
136
- tableKey: string,
137
- tablePath: string,
138
- ) => Promise<TableSourceDef | undefined>;
139
- }
140
- ).fetchTableSchema(tableKey, tablePath);
141
- if (!source) {
142
- throw new ConnectionError(`Table ${tablePath} not found`);
143
- }
144
- // BigQueryConnection returns `error.message` as a string on failure instead of throwing.
145
- if (typeof source === "string") {
146
- throw new ConnectionError(source);
147
- }
148
- return {
149
- source: JSON.stringify(source),
150
- resource: tablePath,
151
- columns: (source.fields || []).map((f) => ({
152
- name: f.name,
153
- type: f.type,
154
- })),
155
- };
156
- } catch (error) {
157
- const errorMessage =
158
- error instanceof Error
159
- ? error.message
160
- : typeof error === "string"
161
- ? error
162
- : JSON.stringify(error);
163
- logger.error("fetchTableSchema error", {
164
- error,
165
- tableKey,
166
- tablePath,
167
- });
168
- throw new ConnectionError(errorMessage);
169
- }
170
- }
171
-
172
- public async getConnection(
173
- projectName: string,
174
- connectionName: string,
175
- ): Promise<ApiConnection> {
176
- if (!projectName || !connectionName) {
177
- throw new BadRequestError("Connection payload is required");
178
- }
179
- // Prefer the in-memory API connection (which was materialized by the
180
- // project on load and carries `attributes`). The DB row stores the
181
- // raw config and FK columns, which aren't the ApiConnection shape.
182
- const project = await this.projectStore.getProject(projectName, false);
183
- return project.getApiConnection(connectionName);
184
- }
185
-
186
- public async listConnections(projectName: string): Promise<ApiConnection[]> {
187
- const project = await this.projectStore.getProject(projectName, false);
188
- return project.listApiConnections();
189
- }
190
-
191
- // Lists schemas (namespaces) available in a connection
192
- public async listSchemas(
193
- projectName: string,
194
- connectionName: string,
195
- ): Promise<ApiSchema[]> {
196
- const project = await this.projectStore.getProject(projectName, false);
197
- const connection = project.getApiConnection(connectionName);
198
- const malloyConnection = await this.getMalloyConnection(
199
- projectName,
200
- connectionName,
201
- );
202
-
203
- return getSchemasForConnection(connection, malloyConnection);
204
- }
205
-
206
- // Lists tables available in a schema. For postgres the schema is usually "public"
207
- public async listTables(
208
- projectName: string,
209
- connectionName: string,
210
- schemaName: string,
211
- tableNames?: string[],
212
- ): Promise<ApiTable[]> {
213
- const project = await this.projectStore.getProject(projectName, false);
214
- const connection = project.getApiConnection(connectionName);
215
- const malloyConnection = await this.getMalloyConnection(
216
- projectName,
217
- connectionName,
218
- );
219
-
220
- return listTablesForSchema(
221
- connection,
222
- schemaName,
223
- malloyConnection,
224
- tableNames,
225
- );
226
- }
227
-
228
- public async getConnectionSqlSource(
229
- projectName: string,
230
- connectionName: string,
231
- sqlStatement: string,
232
- ): Promise<ApiSqlSource> {
233
- const malloyConnection = await this.getMalloyConnection(
234
- projectName,
235
- connectionName,
236
- );
237
- try {
238
- const schema = await (
239
- malloyConnection as Connection & {
240
- fetchSelectSchema: (params: {
241
- connection: string;
242
- selectStr: string;
243
- }) => Promise<unknown>;
244
- }
245
- ).fetchSelectSchema({
246
- connection: connectionName,
247
- selectStr: sqlStatement,
248
- });
249
-
250
- // BigQueryConnection returns `error.message` as a string on failure instead of throwing.
251
- if (typeof schema === "string") {
252
- throw new ConnectionError(schema);
253
- }
254
-
255
- return {
256
- source: JSON.stringify(schema),
257
- };
258
- } catch (error) {
259
- throw new ConnectionError((error as Error).message);
260
- }
261
- }
262
-
263
- public async getTable(
264
- projectName: string,
265
- connectionName: string,
266
- schemaName: string,
267
- tablePath: string,
268
- ): Promise<ApiTable> {
269
- const malloyConnection = await this.getMalloyConnection(
270
- projectName,
271
- connectionName,
272
- );
273
- // Use getApiConnection to get the unwrapped ApiConnection config, consistent with listSchemas and listTables.
274
- const project = await this.projectStore.getProject(projectName, false);
275
- const connection = project.getApiConnection(connectionName);
276
-
277
- // TODO: Move this database connection logic to the db_utils.ts file -- and
278
- // ultimately into a connection-specific class.
279
- if (connection.type === "ducklake") {
280
- if (tablePath.split(".").length === 1) {
281
- // tablePath is just the table name, construct full path
282
- tablePath = `${connectionName}.${schemaName}.${tablePath}`;
283
- } else if (
284
- tablePath.split(".").length === 2 &&
285
- !tablePath.startsWith(connectionName)
286
- ) {
287
- // tablePath is schemaName.tableName but missing connection prefix
288
- tablePath = `${connectionName}.${tablePath}`;
289
- }
290
- // If tablePath already has 3+ parts or starts with connection name, use as-is
291
- }
292
-
293
- // Check if this is an Azure attached database
294
- if (connection.type === "duckdb") {
295
- const attachedDbs =
296
- connection.duckdbConnection?.attachedDatabases || [];
297
- const azureDb = attachedDbs.find(
298
- (db) =>
299
- db.type === "azure" &&
300
- db.name === schemaName &&
301
- db.azureConnection,
302
- );
303
- if (azureDb && azureDb.azureConnection) {
304
- // Reconstruct the full SAS URL for the specific file
305
- const azureConn = azureDb.azureConnection;
306
- const baseUrl =
307
- azureConn.authType === "sas_token"
308
- ? azureConn.sasUrl
309
- : azureConn.fileUrl;
310
- if (baseUrl) {
311
- // Extract the file name from tablePath (e.g., "a.aircraft.parquet" -> "aircraft.parquet")
312
- const fileName = tablePath.includes(".")
313
- ? tablePath.split(".").slice(1).join(".")
314
- : tablePath;
315
- // Replace the file portion in the base URL with the specific file name
316
- const urlParts = baseUrl.split("?");
317
- const basePath = urlParts[0];
318
- const queryString = urlParts[1] ? `?${urlParts[1]}` : "";
319
- // Replace the last path segment (or glob) with the actual file name
320
- const dirPath = basePath.substring(
321
- 0,
322
- basePath.lastIndexOf("/") + 1,
323
- );
324
- const fullFileUrl = `${dirPath}${fileName}${queryString}`;
325
-
326
- const table = await this.fetchTable(
327
- malloyConnection,
328
- fileName,
329
- fullFileUrl,
330
- );
331
- return { ...table, resource: tablePath };
332
- }
333
- }
334
- }
335
-
336
- const tableKey = tablePath.split(".").pop();
337
- if (!tableKey) {
338
- throw new Error(`Invalid tablePath: ${tablePath}`);
339
- }
340
-
341
- return this.fetchTable(malloyConnection, tableKey, tablePath);
342
- }
343
-
344
- public async getConnectionQueryData(
345
- projectName: string,
346
- connectionName: string,
347
- sqlStatement: string,
348
- options: string,
349
- ): Promise<ApiQueryData> {
350
- const malloyConnection = await this.getMalloyConnection(
351
- projectName,
352
- connectionName,
353
- );
354
-
355
- let runSQLOptions: RunSQLOptions = {};
356
- if (options) {
357
- runSQLOptions = JSON.parse(options) as RunSQLOptions;
358
- }
359
- if (runSQLOptions.abortSignal) {
360
- // Add support for abortSignal in the future
361
- logger.info("Clearing unsupported abortSignal");
362
- runSQLOptions.abortSignal = undefined;
363
- }
364
-
365
- try {
366
- return {
367
- data: JSON.stringify(
368
- await malloyConnection.runSQL(sqlStatement, runSQLOptions),
369
- ),
370
- };
371
- } catch (error) {
372
- throw new ConnectionError((error as Error).message);
373
- }
374
- }
375
-
376
- public async getConnectionTemporaryTable(
377
- projectName: string,
378
- connectionName: string,
379
- sqlStatement: string,
380
- ): Promise<ApiTemporaryTable> {
381
- const malloyConnection = await this.getMalloyConnection(
382
- projectName,
383
- connectionName,
384
- );
385
-
386
- try {
387
- return {
388
- table: JSON.stringify(
389
- await (
390
- malloyConnection as PersistSQLResults
391
- ).manifestTemporaryTable(sqlStatement),
392
- ),
393
- };
394
- } catch (error) {
395
- throw new ConnectionError((error as Error).message);
396
- }
397
- }
398
-
399
- public async testConnectionConfiguration(
400
- connectionConfig: ApiConnection,
401
- ): Promise<ApiConnectionStatus> {
402
- if (
403
- connectionConfig &&
404
- "config" in connectionConfig &&
405
- typeof (connectionConfig as Record<string, unknown>).config ===
406
- "object"
407
- ) {
408
- connectionConfig = (connectionConfig as Record<string, unknown>)
409
- .config as ApiConnection;
410
- }
411
-
412
- if (
413
- !connectionConfig ||
414
- typeof connectionConfig !== "object" ||
415
- Object.keys(connectionConfig).length === 0
416
- ) {
417
- throw new BadRequestError(
418
- "Connection configuration is required and cannot be empty",
419
- );
420
- }
421
-
422
- if (!connectionConfig.type || typeof connectionConfig.type !== "string") {
423
- throw new BadRequestError(
424
- "Connection type is required and must be a string",
425
- );
426
- }
427
-
428
- try {
429
- return await testConnectionConfig(connectionConfig);
430
- } catch (error) {
431
- return {
432
- status: "failed",
433
- errorMessage: `Connection test failed: ${(error as Error).message}`,
434
- };
435
- }
436
- }
437
-
438
- public async addConnection(
439
- projectName: string,
440
- connectionName: string,
441
- connectionConfig: ApiConnection,
442
- ): Promise<{ message: string }> {
443
- if (!connectionConfig || typeof connectionConfig !== "object") {
444
- throw new BadRequestError("Connection configuration is required");
445
- }
446
-
447
- if (!connectionConfig) {
448
- throw new BadRequestError("Connection name is required");
449
- }
450
-
451
- if (!connectionConfig.type) {
452
- throw new BadRequestError("Connection type is required");
453
- }
454
-
455
- validateAzureAttachedDatabases(connectionConfig);
456
-
457
- logger.info(
458
- `Creating connection "${connectionName}" in project "${projectName}"`,
459
- );
460
-
461
- await this.connectionService.addConnection(
462
- projectName,
463
- connectionName,
464
- connectionConfig,
465
- );
466
-
467
- return {
468
- message: `Connection "${connectionName}" created successfully`,
469
- };
470
- }
471
-
472
- public async updateConnection(
473
- projectName: string,
474
- connectionName: string,
475
- connection: Partial<ApiConnection>,
476
- ): Promise<{ message: string }> {
477
- if (!connection || typeof connection !== "object") {
478
- throw new BadRequestError("Connection payload is required");
479
- }
480
-
481
- validateAzureAttachedDatabases(connection as ApiConnection);
482
-
483
- logger.info(
484
- `Updating connection "${connectionName}" in project "${projectName}"`,
485
- );
486
-
487
- await this.connectionService.updateConnection(
488
- projectName,
489
- connectionName,
490
- connection,
491
- );
492
-
493
- return {
494
- message: `Connection "${connectionName}" updated successfully`,
495
- };
496
- }
497
-
498
- public async deleteConnection(
499
- projectName: string,
500
- connectionName: string,
501
- ): Promise<{ message: string }> {
502
- logger.info(
503
- `Deleting connection "${connectionName}" from project "${projectName}"`,
504
- );
505
-
506
- await this.connectionService.deleteConnection(
507
- projectName,
508
- connectionName,
509
- );
510
-
511
- return {
512
- message: `Connection "${connectionName}" deleted successfully`,
513
- };
514
- }
515
- }
@@ -1,21 +0,0 @@
1
- import { components } from "../api";
2
- import { ProjectStore } from "../service/project_store";
3
-
4
- type ApiDatabase = components["schemas"]["Database"];
5
-
6
- export class DatabaseController {
7
- private projectStore: ProjectStore;
8
-
9
- constructor(projectStore: ProjectStore) {
10
- this.projectStore = projectStore;
11
- }
12
-
13
- public async listDatabases(
14
- projectName: string,
15
- packageName: string,
16
- ): Promise<ApiDatabase[]> {
17
- const project = await this.projectStore.getProject(projectName, false);
18
- const p = await project.getPackage(packageName, false);
19
- return p.listDatabases();
20
- }
21
- }
@@ -1,29 +0,0 @@
1
- import { ManifestService } from "../service/manifest_service";
2
- import { ProjectStore } from "../service/project_store";
3
- import { resolveProjectId } from "../service/resolve_project";
4
-
5
- export class ManifestController {
6
- constructor(
7
- private projectStore: ProjectStore,
8
- private manifestService: ManifestService,
9
- ) {}
10
-
11
- async getManifest(projectName: string, packageName: string) {
12
- const repository = this.projectStore.storageManager.getRepository();
13
- const projectId = await resolveProjectId(repository, projectName);
14
- // Verify the package exists so we return 404 instead of an empty manifest.
15
- const project = await this.projectStore.getProject(projectName, false);
16
- await project.getPackage(packageName, false);
17
- return this.manifestService.getManifest(projectId, packageName);
18
- }
19
-
20
- async reloadManifest(projectName: string, packageName: string) {
21
- const repository = this.projectStore.storageManager.getRepository();
22
- const projectId = await resolveProjectId(repository, projectName);
23
- return this.manifestService.reloadManifest(
24
- projectId,
25
- packageName,
26
- projectName,
27
- );
28
- }
29
- }
@@ -1,125 +0,0 @@
1
- import { BadRequestError } from "../errors";
2
- import { MaterializationService } from "../service/materialization_service";
3
-
4
- export class MaterializationController {
5
- constructor(private materializationService: MaterializationService) {}
6
-
7
- async createMaterialization(
8
- projectName: string,
9
- packageName: string,
10
- body: Record<string, unknown>,
11
- ) {
12
- const options = this.validateCreateBody(body);
13
- return this.materializationService.createMaterialization(
14
- projectName,
15
- packageName,
16
- options,
17
- );
18
- }
19
-
20
- private validateCreateBody(body: Record<string, unknown>): {
21
- forceRefresh?: boolean;
22
- autoLoadManifest?: boolean;
23
- } {
24
- const result: { forceRefresh?: boolean; autoLoadManifest?: boolean } = {};
25
- if (body.forceRefresh !== undefined) {
26
- if (typeof body.forceRefresh !== "boolean") {
27
- throw new BadRequestError("forceRefresh must be a boolean");
28
- }
29
- result.forceRefresh = body.forceRefresh;
30
- }
31
- if (body.autoLoadManifest !== undefined) {
32
- if (typeof body.autoLoadManifest !== "boolean") {
33
- throw new BadRequestError("autoLoadManifest must be a boolean");
34
- }
35
- result.autoLoadManifest = body.autoLoadManifest;
36
- }
37
- return result;
38
- }
39
-
40
- async startMaterialization(
41
- projectName: string,
42
- packageName: string,
43
- materializationId: string,
44
- ) {
45
- return this.materializationService.startMaterialization(
46
- projectName,
47
- packageName,
48
- materializationId,
49
- );
50
- }
51
-
52
- async stopMaterialization(
53
- projectName: string,
54
- packageName: string,
55
- materializationId: string,
56
- ) {
57
- return this.materializationService.stopMaterialization(
58
- projectName,
59
- packageName,
60
- materializationId,
61
- );
62
- }
63
-
64
- async listMaterializations(
65
- projectName: string,
66
- packageName: string,
67
- options?: { limit?: number; offset?: number },
68
- ) {
69
- return this.materializationService.listMaterializations(
70
- projectName,
71
- packageName,
72
- options,
73
- );
74
- }
75
-
76
- async getMaterialization(
77
- projectName: string,
78
- packageName: string,
79
- materializationId: string,
80
- ) {
81
- return this.materializationService.getMaterialization(
82
- projectName,
83
- packageName,
84
- materializationId,
85
- );
86
- }
87
-
88
- async deleteMaterialization(
89
- projectName: string,
90
- packageName: string,
91
- materializationId: string,
92
- ) {
93
- return this.materializationService.deleteMaterialization(
94
- projectName,
95
- packageName,
96
- materializationId,
97
- );
98
- }
99
-
100
- async teardownPackage(
101
- projectName: string,
102
- packageName: string,
103
- body: Record<string, unknown>,
104
- ) {
105
- const options = this.validateTeardownBody(body);
106
- return this.materializationService.teardownPackage(
107
- projectName,
108
- packageName,
109
- options,
110
- );
111
- }
112
-
113
- private validateTeardownBody(body: Record<string, unknown>): {
114
- dryRun?: boolean;
115
- } {
116
- const options: { dryRun?: boolean } = {};
117
- if (body.dryRun !== undefined) {
118
- if (typeof body.dryRun !== "boolean") {
119
- throw new BadRequestError("dryRun must be a boolean");
120
- }
121
- options.dryRun = body.dryRun;
122
- }
123
- return options;
124
- }
125
- }