@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
|
@@ -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
|
-
}
|
package/src/data_styles.ts
DELETED
|
@@ -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
|
-
});
|