@malloy-publisher/server 0.0.84 → 0.0.86
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/dist/app/api-doc.yaml +187 -0
- package/dist/app/assets/{RenderedResult-BAZuT25g-l7gGLqsb.js → RenderedResult-BAZuT25g-_eLjVkSo.js} +2 -2
- package/dist/app/assets/{index-DJXyqbuQ.js → index-BvRGZn2o.js} +1 -1
- package/dist/app/assets/{index-9VijFY1i.js → index-C-lp8bCy.js} +1 -1
- package/dist/app/assets/{index-CEiaO_E2.js → index-EI2L5apg.js} +119 -119
- package/dist/app/assets/{index.umd-DHTBUjVU.js → index.umd-DBkg1U_t.js} +1 -1
- package/dist/app/index.html +1 -1
- package/dist/instrumentation.js +2 -1
- package/dist/server.js +321 -108
- package/package.json +1 -1
- package/publisher.config.json +1 -0
- package/src/controller/package.controller.ts +32 -0
- package/src/errors.ts +14 -0
- package/src/logger.ts +12 -5
- package/src/mcp/tools/discovery_tools.ts +2 -2
- package/src/server.ts +95 -19
- package/src/service/db_utils.ts +3 -6
- package/src/service/package.ts +12 -0
- package/src/service/project.ts +85 -3
- package/src/service/project_store.spec.ts +173 -0
- package/src/service/project_store.ts +97 -22
- package/src/utils.ts +14 -2
|
@@ -2,40 +2,62 @@ import * as fs from "fs/promises";
|
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import { components } from "../api";
|
|
4
4
|
import { API_PREFIX } from "../constants";
|
|
5
|
-
import { ProjectNotFoundError } from "../errors";
|
|
5
|
+
import { FrozenConfigError, ProjectNotFoundError } from "../errors";
|
|
6
6
|
import { logger } from "../logger";
|
|
7
|
+
import { isPublisherConfigFrozen } from "../utils";
|
|
7
8
|
import { Project } from "./project";
|
|
8
9
|
type ApiProject = components["schemas"]["Project"];
|
|
9
10
|
|
|
10
11
|
export class ProjectStore {
|
|
11
12
|
private serverRootPath: string;
|
|
12
13
|
private projects: Map<string, Project> = new Map();
|
|
14
|
+
public publisherConfigIsFrozen: boolean;
|
|
13
15
|
|
|
14
16
|
constructor(serverRootPath: string) {
|
|
15
17
|
this.serverRootPath = serverRootPath;
|
|
18
|
+
void this.initialize();
|
|
16
19
|
}
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
this.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
21
|
+
private async initialize() {
|
|
22
|
+
try {
|
|
23
|
+
this.publisherConfigIsFrozen = isPublisherConfigFrozen(
|
|
24
|
+
this.serverRootPath,
|
|
25
|
+
);
|
|
26
|
+
const projectManifest = await ProjectStore.reloadProjectManifest(
|
|
27
|
+
this.serverRootPath,
|
|
28
|
+
);
|
|
29
|
+
for (const projectName of Object.keys(projectManifest.projects)) {
|
|
30
|
+
const projectPath = projectManifest.projects[projectName];
|
|
31
|
+
const absoluteProjectPath = path.join(
|
|
32
|
+
this.serverRootPath,
|
|
33
|
+
projectPath,
|
|
34
|
+
);
|
|
35
|
+
const project = await Project.create(
|
|
36
|
+
projectName,
|
|
37
|
+
absoluteProjectPath,
|
|
38
|
+
);
|
|
39
|
+
this.projects.set(projectName, project);
|
|
40
|
+
}
|
|
41
|
+
logger.info("Project store successfully initialized");
|
|
42
|
+
} catch (error) {
|
|
43
|
+
logger.error("Error initializing project store", { error });
|
|
44
|
+
process.exit(1);
|
|
29
45
|
}
|
|
30
46
|
}
|
|
31
47
|
|
|
48
|
+
public listProjects() {
|
|
49
|
+
return Array.from(this.projects.values()).map(
|
|
50
|
+
(project) => project.metadata,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
32
54
|
public async getProject(
|
|
33
55
|
projectName: string,
|
|
34
56
|
reload: boolean,
|
|
35
57
|
): Promise<Project> {
|
|
36
58
|
let project = this.projects.get(projectName);
|
|
37
59
|
if (project === undefined || reload) {
|
|
38
|
-
const projectManifest = await ProjectStore.
|
|
60
|
+
const projectManifest = await ProjectStore.reloadProjectManifest(
|
|
39
61
|
this.serverRootPath,
|
|
40
62
|
);
|
|
41
63
|
if (
|
|
@@ -43,22 +65,75 @@ export class ProjectStore {
|
|
|
43
65
|
!projectManifest.projects[projectName]
|
|
44
66
|
) {
|
|
45
67
|
throw new ProjectNotFoundError(
|
|
46
|
-
`Project ${projectName} not found in publisher
|
|
68
|
+
`Project "${projectName}" not found in publisher`,
|
|
47
69
|
);
|
|
48
70
|
}
|
|
49
|
-
project = await
|
|
50
|
-
projectName,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
71
|
+
project = await this.addProject({
|
|
72
|
+
name: projectName,
|
|
73
|
+
resource: `${API_PREFIX}/projects/${projectName}`,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return project;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
public async addProject(project: ApiProject) {
|
|
80
|
+
if (this.publisherConfigIsFrozen) {
|
|
81
|
+
throw new FrozenConfigError();
|
|
82
|
+
}
|
|
83
|
+
const projectName = project.name;
|
|
84
|
+
if (!projectName) {
|
|
85
|
+
throw new Error("Project name is required");
|
|
86
|
+
}
|
|
87
|
+
const projectManifest = await ProjectStore.reloadProjectManifest(
|
|
88
|
+
this.serverRootPath,
|
|
89
|
+
);
|
|
90
|
+
const projectPath = projectManifest.projects[projectName];
|
|
91
|
+
if (!projectPath) {
|
|
92
|
+
throw new ProjectNotFoundError(
|
|
93
|
+
`Project "${projectName}" not found in publisher.config.json`,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
const absoluteProjectPath = path.join(this.serverRootPath, projectPath);
|
|
97
|
+
if (!(await fs.stat(absoluteProjectPath)).isDirectory()) {
|
|
98
|
+
throw new ProjectNotFoundError(
|
|
99
|
+
`Project ${projectName} not found in ${absoluteProjectPath}`,
|
|
55
100
|
);
|
|
56
|
-
this.projects.set(projectName, project);
|
|
57
101
|
}
|
|
102
|
+
const newProject = await Project.create(projectName, absoluteProjectPath);
|
|
103
|
+
this.projects.set(projectName, newProject);
|
|
104
|
+
return newProject;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
public async updateProject(project: ApiProject) {
|
|
108
|
+
if (this.publisherConfigIsFrozen) {
|
|
109
|
+
throw new FrozenConfigError();
|
|
110
|
+
}
|
|
111
|
+
const projectName = project.name;
|
|
112
|
+
if (!projectName) {
|
|
113
|
+
throw new Error("Project name is required");
|
|
114
|
+
}
|
|
115
|
+
const existingProject = this.projects.get(projectName);
|
|
116
|
+
if (!existingProject) {
|
|
117
|
+
throw new ProjectNotFoundError(`Project ${projectName} not found`);
|
|
118
|
+
}
|
|
119
|
+
const updatedProject = await existingProject.update(project);
|
|
120
|
+
this.projects.set(projectName, updatedProject);
|
|
121
|
+
return updatedProject;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
public async deleteProject(projectName: string) {
|
|
125
|
+
if (this.publisherConfigIsFrozen) {
|
|
126
|
+
throw new FrozenConfigError();
|
|
127
|
+
}
|
|
128
|
+
const project = this.projects.get(projectName);
|
|
129
|
+
if (!project) {
|
|
130
|
+
throw new ProjectNotFoundError(`Project ${projectName} not found`);
|
|
131
|
+
}
|
|
132
|
+
this.projects.delete(projectName);
|
|
58
133
|
return project;
|
|
59
134
|
}
|
|
60
135
|
|
|
61
|
-
private static async
|
|
136
|
+
private static async reloadProjectManifest(
|
|
62
137
|
serverRootPath: string,
|
|
63
138
|
): Promise<{ projects: { [key: string]: string } }> {
|
|
64
139
|
try {
|
package/src/utils.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { URLReader } from "@malloydata/malloy";
|
|
2
|
-
import
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import path from "path";
|
|
3
4
|
import { fileURLToPath } from "url";
|
|
4
5
|
|
|
5
6
|
export const PACKAGE_MANIFEST_NAME = "publisher.json";
|
|
@@ -15,6 +16,17 @@ export const URL_READER: URLReader = {
|
|
|
15
16
|
if (url.protocol == "file:") {
|
|
16
17
|
path = fileURLToPath(url);
|
|
17
18
|
}
|
|
18
|
-
return fs.readFile(path, "utf8");
|
|
19
|
+
return fs.promises.readFile(path, "utf8");
|
|
19
20
|
},
|
|
20
21
|
};
|
|
22
|
+
|
|
23
|
+
export const isPublisherConfigFrozen = (serverRoot: string) => {
|
|
24
|
+
const publisherConfigPath = path.join(serverRoot, "publisher.config.json");
|
|
25
|
+
if (!fs.existsSync(publisherConfigPath)) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
const publisherConfig = JSON.parse(
|
|
29
|
+
fs.readFileSync(publisherConfigPath, "utf8"),
|
|
30
|
+
);
|
|
31
|
+
return Boolean(publisherConfig.frozenConfig);
|
|
32
|
+
};
|