@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
@@ -1,107 +0,0 @@
1
- import { components } from "../api";
2
- import { ModelNotFoundError } from "../errors";
3
- import { ProjectStore } from "../service/project_store";
4
- import type { FilterParams } from "../service/filter";
5
-
6
- type ApiNotebook = components["schemas"]["Notebook"];
7
- type ApiModel = components["schemas"]["Model"];
8
- type ApiCompiledModel = components["schemas"]["CompiledModel"];
9
- type ApiRawNotebook = components["schemas"]["RawNotebook"];
10
- export class ModelController {
11
- private projectStore: ProjectStore;
12
-
13
- constructor(projectStore: ProjectStore) {
14
- this.projectStore = projectStore;
15
- }
16
-
17
- public async listModels(
18
- projectName: string,
19
- packageName: string,
20
- ): Promise<ApiModel[]> {
21
- const project = await this.projectStore.getProject(projectName, false);
22
- const p = await project.getPackage(packageName, false);
23
- return p.listModels();
24
- }
25
-
26
- public async listNotebooks(
27
- projectName: string,
28
- packageName: string,
29
- ): Promise<ApiNotebook[]> {
30
- const project = await this.projectStore.getProject(projectName, false);
31
- const p = await project.getPackage(packageName, false);
32
- return p.listNotebooks();
33
- }
34
-
35
- public async getModel(
36
- projectName: string,
37
- packageName: string,
38
- modelPath: string,
39
- ): Promise<ApiCompiledModel> {
40
- try {
41
- const project = await this.projectStore.getProject(projectName, false);
42
- const p = await project.getPackage(packageName, false);
43
- const model = p.getModel(modelPath);
44
- if (!model) {
45
- throw new ModelNotFoundError(`${modelPath} does not exist`);
46
- }
47
- if (model.getType() === "notebook") {
48
- throw new ModelNotFoundError(`${modelPath} is a notebook`);
49
- }
50
- return await model.getModel();
51
- } catch (error) {
52
- // Re-throw ModelNotFoundError as-is
53
- if (error instanceof ModelNotFoundError) {
54
- throw error;
55
- }
56
- // Wrap other errors with more context
57
- throw new Error(
58
- `Failed to get model ${modelPath} from package ${packageName} in project ${projectName}: ${error}`,
59
- );
60
- }
61
- }
62
-
63
- public async getNotebook(
64
- projectName: string,
65
- packageName: string,
66
- notebookPath: string,
67
- ): Promise<ApiRawNotebook> {
68
- const project = await this.projectStore.getProject(projectName, false);
69
- const p = await project.getPackage(packageName, false);
70
- const model = p.getModel(notebookPath);
71
- if (!model) {
72
- throw new ModelNotFoundError(`${notebookPath} does not exist`);
73
- }
74
- if (model.getType() === "model") {
75
- throw new ModelNotFoundError(`${notebookPath} is a model`);
76
- }
77
-
78
- return model.getNotebook();
79
- }
80
-
81
- public async executeNotebookCell(
82
- projectName: string,
83
- packageName: string,
84
- notebookPath: string,
85
- cellIndex: number,
86
- filterParams?: FilterParams,
87
- bypassFilters?: boolean,
88
- ): Promise<{
89
- type: "code" | "markdown";
90
- text: string;
91
- queryName?: string;
92
- result?: string;
93
- newSources?: string[];
94
- }> {
95
- const project = await this.projectStore.getProject(projectName, false);
96
- const p = await project.getPackage(packageName, false);
97
- const model = p.getModel(notebookPath);
98
- if (!model) {
99
- throw new ModelNotFoundError(`${notebookPath} does not exist`);
100
- }
101
- if (model.getType() === "model") {
102
- throw new ModelNotFoundError(`${notebookPath} is a model`);
103
- }
104
-
105
- return model.executeNotebookCell(cellIndex, filterParams, bypassFilters);
106
- }
107
- }
@@ -1,182 +0,0 @@
1
- import * as path from "path";
2
- import { components } from "../api";
3
- import { PUBLISHER_DATA_DIR } from "../constants";
4
- import { BadRequestError, FrozenConfigError } from "../errors";
5
- import { logger } from "../logger";
6
- import { ManifestService } from "../service/manifest_service";
7
- import { ProjectStore } from "../service/project_store";
8
-
9
- type ApiPackage = components["schemas"]["Package"];
10
-
11
- export class PackageController {
12
- private projectStore: ProjectStore;
13
- private manifestService: ManifestService;
14
-
15
- constructor(projectStore: ProjectStore, manifestService: ManifestService) {
16
- this.projectStore = projectStore;
17
- this.manifestService = manifestService;
18
- }
19
-
20
- public async listPackages(projectName: string): Promise<ApiPackage[]> {
21
- const project = await this.projectStore.getProject(projectName, false);
22
- return project.listPackages();
23
- }
24
-
25
- public async getPackage(
26
- projectName: string,
27
- packageName: string,
28
- reload: boolean,
29
- ): Promise<ApiPackage> {
30
- const project = await this.projectStore.getProject(projectName, false);
31
- const _package = await project.getPackage(packageName, reload);
32
- const packageLocation = _package.getPackageMetadata().location;
33
- if (reload && packageLocation) {
34
- await this.downloadPackage(projectName, packageName, packageLocation);
35
- }
36
- return _package.getPackageMetadata();
37
- }
38
-
39
- async addPackage(
40
- projectName: string,
41
- body: ApiPackage,
42
- options?: { autoLoadManifest?: boolean },
43
- ) {
44
- if (this.projectStore.publisherConfigIsFrozen) {
45
- throw new FrozenConfigError();
46
- }
47
- if (!body.name) {
48
- throw new BadRequestError("Package name is required");
49
- }
50
- const project = await this.projectStore.getProject(projectName, false);
51
- if (body.location) {
52
- await this.downloadPackage(projectName, body.name, body.location);
53
- }
54
- const result = await project.addPackage(body.name);
55
- await this.projectStore.addPackageToDatabase(projectName, body.name);
56
-
57
- if (options?.autoLoadManifest === true) {
58
- await this.tryLoadExistingManifest(projectName, body.name);
59
- }
60
-
61
- return result;
62
- }
63
-
64
- /**
65
- * If there are already manifest entries for this package (e.g. from a
66
- * previous materialization run), reload all models with the manifest so
67
- * persist references resolve to the materialized tables immediately.
68
- */
69
- private async tryLoadExistingManifest(
70
- projectName: string,
71
- packageName: string,
72
- ): Promise<void> {
73
- try {
74
- const repository = this.projectStore.storageManager.getRepository();
75
- const dbProject = await repository.getProjectByName(projectName);
76
- if (!dbProject) return;
77
-
78
- const manifest = await this.manifestService.getManifest(
79
- dbProject.id,
80
- packageName,
81
- );
82
- if (Object.keys(manifest.entries).length === 0) return;
83
-
84
- await this.manifestService.reloadManifest(
85
- dbProject.id,
86
- packageName,
87
- projectName,
88
- );
89
- logger.info("Auto-loaded existing manifest for added package", {
90
- projectName,
91
- packageName,
92
- entryCount: Object.keys(manifest.entries).length,
93
- });
94
- } catch (error) {
95
- logger.warn("Failed to auto-load manifest for package", {
96
- projectName,
97
- packageName,
98
- error,
99
- });
100
- }
101
- }
102
-
103
- public async deletePackage(projectName: string, packageName: string) {
104
- if (this.projectStore.publisherConfigIsFrozen) {
105
- throw new FrozenConfigError();
106
- }
107
- const project = await this.projectStore.getProject(projectName, false);
108
- const result = await project.deletePackage(packageName);
109
- await this.projectStore.deletePackageFromDatabase(
110
- projectName,
111
- packageName,
112
- );
113
-
114
- return result;
115
- }
116
-
117
- public async updatePackage(
118
- projectName: string,
119
- packageName: string,
120
- body: ApiPackage,
121
- ) {
122
- if (this.projectStore.publisherConfigIsFrozen) {
123
- throw new FrozenConfigError();
124
- }
125
- const project = await this.projectStore.getProject(projectName, false);
126
- if (body.location) {
127
- await this.downloadPackage(projectName, packageName, body.location);
128
- }
129
- const result = await project.updatePackage(packageName, body);
130
- await this.projectStore.addPackageToDatabase(projectName, packageName);
131
-
132
- return result;
133
- }
134
-
135
- private async downloadPackage(
136
- projectName: string,
137
- packageName: string,
138
- packageLocation: string,
139
- ) {
140
- const absoluteTargetPath = path.join(
141
- this.projectStore.serverRootPath,
142
- PUBLISHER_DATA_DIR,
143
- projectName,
144
- packageName,
145
- );
146
- const isCompressedFile = packageLocation.endsWith(".zip");
147
- if (
148
- packageLocation.startsWith("https://") ||
149
- packageLocation.startsWith("git@")
150
- ) {
151
- await this.projectStore.downloadGitHubDirectory(
152
- packageLocation,
153
- absoluteTargetPath,
154
- );
155
- } else if (packageLocation.startsWith("gs://")) {
156
- await this.projectStore.downloadGcsDirectory(
157
- packageLocation,
158
- projectName,
159
- absoluteTargetPath,
160
- isCompressedFile,
161
- );
162
- } else if (packageLocation.startsWith("s3://")) {
163
- await this.projectStore.downloadS3Directory(
164
- packageLocation,
165
- projectName,
166
- absoluteTargetPath,
167
- isCompressedFile,
168
- );
169
- }
170
-
171
- if (packageLocation.startsWith("/")) {
172
- // Absolute paths from the publisher.config could be placed outside of /etc/publisher,
173
- // so we need to mount them on the right place.
174
- await this.projectStore.mountLocalDirectory(
175
- packageLocation,
176
- absoluteTargetPath,
177
- projectName,
178
- packageName,
179
- );
180
- }
181
- }
182
- }
@@ -1,60 +0,0 @@
1
- import { validateRenderTags } from "@malloydata/render-validator";
2
- import { components } from "../api";
3
- import { API_PREFIX } from "../constants";
4
- import { ModelNotFoundError } from "../errors";
5
- import { ProjectStore } from "../service/project_store";
6
- import type { FilterParams } from "../service/filter";
7
-
8
- type ApiQuery = components["schemas"]["QueryResult"];
9
-
10
- // Replacer function to handle BigInt serialization
11
- function bigIntReplacer(_key: string, value: unknown): unknown {
12
- if (typeof value === "bigint") {
13
- return Number(value);
14
- }
15
- return value;
16
- }
17
-
18
- export class QueryController {
19
- private projectStore: ProjectStore;
20
-
21
- constructor(projectStore: ProjectStore) {
22
- this.projectStore = projectStore;
23
- }
24
-
25
- public async getQuery(
26
- projectName: string,
27
- packageName: string,
28
- modelPath: string,
29
- sourceName: string,
30
- queryName: string,
31
- query: string,
32
- compactJson: boolean = false,
33
- filterParams?: FilterParams,
34
- bypassFilters?: boolean,
35
- ): Promise<ApiQuery> {
36
- const project = await this.projectStore.getProject(projectName, false);
37
- const p = await project.getPackage(packageName, false);
38
- const model = p.getModel(modelPath);
39
-
40
- if (!model) {
41
- throw new ModelNotFoundError(`${modelPath} does not exist`);
42
- } else {
43
- const { result, compactResult } = await model.getQueryResults(
44
- sourceName,
45
- queryName,
46
- query,
47
- filterParams,
48
- bypassFilters,
49
- );
50
- const renderLogs = validateRenderTags(result);
51
- return {
52
- result: compactJson
53
- ? JSON.stringify(compactResult, bigIntReplacer)
54
- : JSON.stringify(result),
55
- resource: `${API_PREFIX}/projects/${projectName}/packages/${packageName}/models/${modelPath}/query`,
56
- renderLogs: renderLogs.length > 0 ? renderLogs : undefined,
57
- } as ApiQuery;
58
- }
59
- }
60
- }
@@ -1,98 +0,0 @@
1
- import chokidar, { FSWatcher } from "chokidar";
2
- import { RequestHandler } from "express";
3
- import path from "path";
4
- import { components } from "../api";
5
- import { logger } from "../logger";
6
- import { ProjectStore } from "../service/project_store";
7
-
8
- type StartWatchReq = components["schemas"]["StartWatchRequest"];
9
- type WatchStatusRes = components["schemas"]["WatchStatus"];
10
- type Handler<Req = object, Res = void> = RequestHandler<object, Res, Req>;
11
-
12
- export class WatchModeController {
13
- watchingPath: string | null;
14
- watchingProjectName: string | null;
15
- watcher: FSWatcher;
16
-
17
- constructor(private projectStore: ProjectStore) {
18
- this.watchingPath = null;
19
- this.watchingProjectName = null;
20
- }
21
-
22
- public getWatchStatus: Handler<void, WatchStatusRes> = async (_req, res) => {
23
- return res.json({
24
- enabled: !!this.watchingPath,
25
- watchingPath: this.watchingPath ?? "",
26
- projectName: this.watchingProjectName ?? "",
27
- });
28
- };
29
-
30
- public startWatching: Handler<StartWatchReq, { error: string }> = async (
31
- req,
32
- res,
33
- ) => {
34
- const projectManifest = await ProjectStore.reloadProjectManifest(
35
- this.projectStore.serverRootPath,
36
- );
37
- this.watchingProjectName = req.body.projectName;
38
-
39
- // Find the project in the new array structure
40
- const project = projectManifest.projects.find(
41
- (p) => p.name === req.body.projectName,
42
- );
43
- if (!project || !project.packages || project.packages.length === 0) {
44
- res.status(404).json({
45
- error: `Project ${req.body.projectName} not found or has no packages`,
46
- });
47
- return;
48
- }
49
-
50
- this.watchingPath = path.join(
51
- this.projectStore.serverRootPath,
52
- req.body.projectName,
53
- );
54
- this.watcher = chokidar.watch(this.watchingPath, {
55
- ignored: (path, stats) =>
56
- !!stats?.isFile() &&
57
- !path.endsWith(".malloy") &&
58
- !path.endsWith(".md"),
59
- ignoreInitial: true,
60
- });
61
- const reloadProject = async () => {
62
- // Overwrite the project with it's existing metadata to trigger a re-read
63
- const project = await this.projectStore.getProject(
64
- req.body.projectName,
65
- true,
66
- );
67
- await this.projectStore.addProject(project.metadata);
68
- logger.info(`Reloaded ${req.body.projectName}`);
69
- };
70
-
71
- this.watcher.on("add", async (path) => {
72
- logger.info(
73
- `Detected new file ${path}, reloading ${req.body.projectName}`,
74
- );
75
- await reloadProject();
76
- });
77
- this.watcher.on("unlink", async (path) => {
78
- logger.info(
79
- `Detected deletion of ${path}, reloading ${req.body.projectName}`,
80
- );
81
- await reloadProject();
82
- });
83
- this.watcher.on("change", async (path) => {
84
- logger.info(
85
- `Detected change on ${path}, reloading ${req.body.projectName}`,
86
- );
87
- await reloadProject();
88
- });
89
- res.json();
90
- };
91
-
92
- public stopWatchMode: Handler = async (_req, res) => {
93
- this.watcher.close();
94
- this.watchingPath = null;
95
- this.watchingProjectName = null;
96
- res.json();
97
- };
98
- }
@@ -1,71 +0,0 @@
1
- import { URLReader } from "@malloydata/malloy";
2
- import { DataStyles } from "@malloydata/render";
3
- import { logger } from "./logger";
4
-
5
- export function compileDataStyles(styles: string): DataStyles {
6
- try {
7
- return JSON.parse(styles) as DataStyles;
8
- } catch (error) {
9
- throw new Error(`Error compiling data styles: ${error}`);
10
- }
11
- }
12
-
13
- // TODO replace this with actual JSON metadata import functionality, when it exists
14
- export async function dataStylesForFile(
15
- url: string,
16
- text: string,
17
- urlReader: URLReader,
18
- ): Promise<DataStyles> {
19
- const PREFIX = "--! styles ";
20
- let styles: DataStyles = {};
21
- for (const line of text.split("\n")) {
22
- if (line.startsWith(PREFIX)) {
23
- const fileName = line.trimEnd().substring(PREFIX.length);
24
- // TODO instead of failing silently when the file does not exist, perform this after the WebView has been
25
- // created, so that the error can be shown there.
26
- let stylesText;
27
- try {
28
- stylesText = (await urlReader.readURL(
29
- new URL(fileName, url),
30
- )) as string;
31
- } catch (error) {
32
- logger.error(`Error loading data style '${fileName}': ${error}`);
33
- stylesText = "{}";
34
- }
35
- styles = { ...styles, ...compileDataStyles(stylesText) };
36
- }
37
- }
38
-
39
- return styles;
40
- }
41
-
42
- // TODO Come up with a better way to handle data styles. Perhaps this is
43
- // an in-language understanding of model "metadata". For now,
44
- // we abuse the `URLReader` API to keep track of requested URLs
45
- // and accumulate data styles for those files.
46
- export class HackyDataStylesAccumulator implements URLReader {
47
- private urlReader: URLReader;
48
- private dataStyles: DataStyles = {};
49
-
50
- constructor(urlReader: URLReader) {
51
- this.urlReader = urlReader;
52
- }
53
-
54
- async readURL(url: URL): Promise<string> {
55
- const contents = await this.urlReader.readURL(url);
56
- this.dataStyles = {
57
- ...this.dataStyles,
58
- ...(await dataStylesForFile(
59
- url.toString(),
60
- contents as string,
61
- this.urlReader,
62
- )),
63
- };
64
-
65
- return contents as string;
66
- }
67
-
68
- getHackyAccumulatedDataStyles(): DataStyles {
69
- return this.dataStyles;
70
- }
71
- }
@@ -1,131 +0,0 @@
1
- import { expect, it, describe } from "bun:test";
2
-
3
- import { plainToInstance } from "class-transformer";
4
- import { validate } from "class-validator";
5
- import {
6
- BigqueryConnectionDto,
7
- ConnectionDto,
8
- PostgresConnectionDto,
9
- SnowflakeConnectionDto,
10
- MysqlConnectionDto,
11
- } from "./connection.dto";
12
-
13
- describe("dto/connection", () => {
14
- describe("Connection Validation", () => {
15
- it("should validate a valid MysqlConnection object", async () => {
16
- const validData = {
17
- host: "localhost",
18
- port: 3306,
19
- database: "testdb",
20
- user: "user",
21
- password: "pass",
22
- };
23
- const mysqlConnection = plainToInstance(MysqlConnectionDto, validData);
24
-
25
- const errors = await validate(mysqlConnection);
26
- expect(errors).toHaveLength(0);
27
- });
28
-
29
- it("should validate a valid PostgresConnection object", async () => {
30
- const validData = {
31
- host: "localhost",
32
- port: 5432,
33
- databaseName: "testdb",
34
- userName: "user",
35
- password: "pass",
36
- connectionString: "postgres://user:pass@localhost:5432/testdb",
37
- };
38
- const postgresConnection = plainToInstance(
39
- PostgresConnectionDto,
40
- validData,
41
- );
42
-
43
- const errors = await validate(postgresConnection);
44
- expect(errors).toHaveLength(0);
45
- });
46
-
47
- it("should return errors for invalid PostgresConnection object", async () => {
48
- const invalidData = {
49
- host: 123, // Invalid type
50
- port: "not-a-number", // Invalid type
51
- };
52
- const postgresConnection = plainToInstance(
53
- PostgresConnectionDto,
54
- invalidData,
55
- );
56
-
57
- const errors = await validate(postgresConnection);
58
- expect(errors).not.toHaveLength(0);
59
- expect(errors).toHaveLength(2);
60
- });
61
-
62
- it("should validate a valid BigqueryConnection object", async () => {
63
- const validData = {
64
- defaultProjectId: "default-project",
65
- billingProjectId: "billing-project",
66
- location: "US",
67
- serviceAccountKeyJson: "{}",
68
- maximumBytesBilled: "1000000",
69
- queryTimeoutMilliseconds: "1000",
70
- };
71
- const bigqueryConnection = plainToInstance(
72
- BigqueryConnectionDto,
73
- validData,
74
- );
75
-
76
- const errors = await validate(bigqueryConnection);
77
- expect(errors).toHaveLength(0);
78
- });
79
-
80
- it("should validate a valid SnowflakeConnection object", async () => {
81
- const validData = {
82
- account: "my-account",
83
- username: "user",
84
- password: "pass",
85
- warehouse: "my-warehouse",
86
- database: "my-database",
87
- schema: "my-schema",
88
- responseTimeoutMilliseconds: 5000,
89
- };
90
- const snowflakeConnection = plainToInstance(
91
- SnowflakeConnectionDto,
92
- validData,
93
- );
94
-
95
- const errors = await validate(snowflakeConnection);
96
- expect(errors).toHaveLength(0);
97
- });
98
-
99
- it("should validate a valid Connection object with postgres type", async () => {
100
- const validData = {
101
- name: "My Postgres Connection",
102
- type: "postgres",
103
- postgresConnection: {
104
- host: "localhost",
105
- port: 5432,
106
- databaseName: "testdb",
107
- userName: "user",
108
- password: "pass",
109
- },
110
- };
111
- const connection = plainToInstance(ConnectionDto, validData);
112
-
113
- const errors = await validate(connection);
114
- expect(errors).toHaveLength(0);
115
- });
116
-
117
- it("should return errors for invalid Connection object", async () => {
118
- const invalidData = {
119
- type: "invalid-type", // Invalid enum value
120
- postgresConnection: {
121
- port: "invalid-port", // Invalid type
122
- },
123
- };
124
- const connection = plainToInstance(ConnectionDto, invalidData);
125
-
126
- const errors = await validate(connection);
127
- expect(errors).not.toHaveLength(0);
128
- expect(errors.length).toBeGreaterThan(0);
129
- });
130
- });
131
- });