@malloy-publisher/server 0.0.225 → 0.0.227

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 (98) hide show
  1. package/README.md +2 -11
  2. package/dist/app/api-doc.yaml +29 -24
  3. package/dist/app/assets/{EnvironmentPage-DYTeXDll.js → EnvironmentPage-DvOJ7L_b.js} +1 -1
  4. package/dist/app/assets/{HomePage-pDK2BPJY.js → HomePage-CXguJsXS.js} +1 -1
  5. package/dist/app/assets/{LightMode-C2bwGPY1.js → LightMode-ZsshUznu.js} +1 -1
  6. package/dist/app/assets/{MainPage-WtBulMH_.js → MainPage-BIe0VwBa.js} +2 -2
  7. package/dist/app/assets/{MaterializationsPage-hMgOtflG.js → MaterializationsPage-BuZ6UJVx.js} +1 -1
  8. package/dist/app/assets/{ModelPage-B2N5kYII.js → ModelPage-DsPf-s8B.js} +1 -1
  9. package/dist/app/assets/{PackagePage-CEN90nQG.js → PackagePage-CEVNAKZa.js} +1 -1
  10. package/dist/app/assets/{RouteError-BG2c5Zf0.js → RouteError-Chn7lL96.js} +1 -1
  11. package/dist/app/assets/{ThemeEditorPage-DNfeUwEZ.js → ThemeEditorPage-DWC_FdNU.js} +1 -1
  12. package/dist/app/assets/{WorkbookPage-NKI1BhFS.js → WorkbookPage-CGrsFz8p.js} +1 -1
  13. package/dist/app/assets/{core-C6anj5c0.es-DDLHqpzt.js → core-vVgoO8IR.es-BD_THWs_.js} +1 -1
  14. package/dist/app/assets/{index-DMQtnaf4.js → index-BioohWQj.js} +1 -1
  15. package/dist/app/assets/{index-CzNqKMVl.js → index-D6YtyiJ0.js} +1 -1
  16. package/dist/app/assets/{index-C6gZ6sSY.js → index-DNUZpnaa.js} +4 -4
  17. package/dist/app/assets/{index-JXgvyZna.js → index-gEWxu09x.js} +1 -1
  18. package/dist/app/index.html +1 -1
  19. package/dist/instrumentation.mjs +71 -15
  20. package/dist/package_load_worker.mjs +169 -40
  21. package/dist/server.mjs +782 -7050
  22. package/package.json +1 -4
  23. package/src/controller/package.controller.spec.ts +17 -17
  24. package/src/controller/package.controller.ts +7 -6
  25. package/src/health.ts +3 -8
  26. package/src/logger.spec.ts +193 -0
  27. package/src/logger.ts +115 -18
  28. package/src/mcp/error_messages.ts +8 -37
  29. package/src/mcp/handler_utils.ts +10 -129
  30. package/src/mcp/mcp_constants.ts +0 -16
  31. package/src/mcp/{agent_server.protocol.spec.ts → server.protocol.spec.ts} +14 -12
  32. package/src/mcp/server.ts +29 -37
  33. package/src/mcp/skills/build_skills_bundle.ts +1 -1
  34. package/src/mcp/skills/skills_bundle.json +1 -1
  35. package/src/mcp/tools/docs_search_tool.ts +1 -1
  36. package/src/mcp/tools/execute_query_tool.ts +2 -2
  37. package/src/mcp/tools/get_context_eval.ts +3 -3
  38. package/src/mcp/tools/get_context_tool.spec.ts +196 -1
  39. package/src/mcp/tools/get_context_tool.ts +165 -67
  40. package/src/package_load/package_load_pool.ts +8 -1
  41. package/src/package_load/package_load_worker.ts +75 -24
  42. package/src/package_load/protocol.ts +21 -1
  43. package/src/package_load/rpc_wait_accountant.spec.ts +109 -0
  44. package/src/package_load/rpc_wait_accountant.ts +76 -0
  45. package/src/package_load_metrics.spec.ts +114 -0
  46. package/src/package_load_metrics.ts +127 -0
  47. package/src/pg_helpers.spec.ts +2 -206
  48. package/src/pg_helpers.ts +4 -120
  49. package/src/server.ts +0 -16
  50. package/src/service/build_plan.spec.ts +42 -60
  51. package/src/service/build_plan.ts +37 -61
  52. package/src/service/environment.ts +5 -1
  53. package/src/service/materialization_test_fixtures.ts +6 -8
  54. package/src/service/package.ts +165 -86
  55. package/src/service/package_manifest.spec.ts +22 -1
  56. package/src/service/package_manifest.ts +29 -0
  57. package/src/service/persistence_policy.spec.ts +234 -0
  58. package/src/test_helpers/metrics_harness.ts +40 -0
  59. package/tests/harness/mcp_test_setup.ts +1 -1
  60. package/tests/integration/mcp/mcp_transport.integration.spec.ts +7 -31
  61. package/tests/integration/watch-mode/watch_mode.integration.spec.ts +0 -6
  62. package/dxt/malloy_bridge.py +0 -354
  63. package/dxt/manifest.json +0 -22
  64. package/src/dto/connection.dto.spec.ts +0 -186
  65. package/src/dto/connection.dto.ts +0 -308
  66. package/src/dto/index.ts +0 -2
  67. package/src/dto/package.dto.spec.ts +0 -42
  68. package/src/dto/package.dto.ts +0 -27
  69. package/src/dto/validate.spec.ts +0 -76
  70. package/src/dto/validate.ts +0 -31
  71. package/src/ducklake_version.spec.ts +0 -43
  72. package/src/ducklake_version.ts +0 -26
  73. package/src/mcp/agent_server.spec.ts +0 -18
  74. package/src/mcp/agent_server.ts +0 -144
  75. package/src/mcp/prompts/handlers.ts +0 -84
  76. package/src/mcp/prompts/index.ts +0 -11
  77. package/src/mcp/prompts/prompt_definitions.ts +0 -160
  78. package/src/mcp/prompts/prompt_service.ts +0 -67
  79. package/src/mcp/prompts/utils.ts +0 -62
  80. package/src/mcp/resource_metadata.ts +0 -47
  81. package/src/mcp/resources/environment_resource.ts +0 -187
  82. package/src/mcp/resources/model_resource.ts +0 -155
  83. package/src/mcp/resources/notebook_resource.ts +0 -137
  84. package/src/mcp/resources/package_resource.ts +0 -373
  85. package/src/mcp/resources/query_resource.ts +0 -122
  86. package/src/mcp/resources/source_resource.ts +0 -141
  87. package/src/mcp/resources/view_resource.ts +0 -136
  88. package/src/mcp/tools/discovery_tools.ts +0 -280
  89. package/src/service/materialization_cron_gate.spec.ts +0 -181
  90. package/src/storage/BaseRepository.ts +0 -31
  91. package/src/storage/StorageManager.mock.ts +0 -50
  92. package/tests/harness/e2e.ts +0 -96
  93. package/tests/harness/mocks.ts +0 -39
  94. package/tests/harness/uris.ts +0 -31
  95. package/tests/integration/mcp/mcp_resource.integration.spec.ts +0 -655
  96. package/tests/integration/mcp/setup.spec.ts +0 -5
  97. package/tests/unit/mcp/prompt_definitions.test.ts +0 -102
  98. package/tests/unit/mcp/prompt_happy.test.ts +0 -51
@@ -1,308 +0,0 @@
1
- import { Type } from "class-transformer";
2
- import {
3
- IsDefined,
4
- IsEnum,
5
- IsArray,
6
- IsNumber,
7
- IsOptional,
8
- IsString,
9
- ValidateIf,
10
- ValidateNested,
11
- } from "class-validator";
12
- import "reflect-metadata";
13
- import { components } from "../api";
14
- import { ApiConnection } from "../service/model";
15
-
16
- type AttachedDatabase = components["schemas"]["AttachedDatabase"];
17
-
18
- export class PostgresConnectionDto {
19
- @IsOptional()
20
- @IsString()
21
- host?: string;
22
-
23
- @IsOptional()
24
- @IsNumber()
25
- port?: number;
26
-
27
- @IsOptional()
28
- @IsString()
29
- databaseName?: string;
30
-
31
- @IsOptional()
32
- @IsString()
33
- userName?: string;
34
-
35
- @IsOptional()
36
- @IsString()
37
- password?: string;
38
-
39
- @IsOptional()
40
- @IsString()
41
- connectionString?: string;
42
- }
43
-
44
- export class MysqlConnectionDto {
45
- @IsOptional()
46
- @IsString()
47
- host?: string;
48
-
49
- @IsOptional()
50
- @IsNumber()
51
- port?: number;
52
-
53
- @IsOptional()
54
- @IsString()
55
- database?: string;
56
-
57
- @IsOptional()
58
- @IsString()
59
- user?: string;
60
-
61
- @IsOptional()
62
- @IsString()
63
- password?: string;
64
- }
65
-
66
- export class BigqueryConnectionDto {
67
- @IsOptional()
68
- @IsString()
69
- defaultProjectId?: string;
70
-
71
- @IsOptional()
72
- @IsString()
73
- billingProjectId?: string;
74
-
75
- @IsOptional()
76
- @IsString()
77
- location?: string;
78
-
79
- @IsOptional()
80
- @IsString()
81
- serviceAccountKeyJson?: string;
82
-
83
- @IsOptional()
84
- @IsString()
85
- maximumBytesBilled?: string;
86
-
87
- @IsOptional()
88
- @IsString()
89
- queryTimeoutMilliseconds?: string;
90
- }
91
-
92
- export class SnowflakeConnectionDto {
93
- @IsOptional()
94
- @IsString()
95
- account?: string;
96
-
97
- @IsOptional()
98
- @IsString()
99
- username?: string;
100
-
101
- @IsOptional()
102
- @IsString()
103
- password?: string;
104
-
105
- @IsOptional()
106
- @IsString()
107
- privateKey?: string;
108
-
109
- @IsOptional()
110
- @IsString()
111
- privateKeyPass?: string;
112
-
113
- @IsOptional()
114
- @IsString()
115
- warehouse?: string;
116
-
117
- @IsOptional()
118
- @IsString()
119
- database?: string;
120
-
121
- @IsOptional()
122
- @IsString()
123
- schema?: string;
124
-
125
- @IsOptional()
126
- @IsString()
127
- role?: string;
128
-
129
- @IsOptional()
130
- @IsNumber()
131
- responseTimeoutMilliseconds?: number;
132
- }
133
-
134
- export class DatabricksConnectionDto {
135
- @IsOptional()
136
- @IsString()
137
- host?: string;
138
-
139
- @IsOptional()
140
- @IsString()
141
- path?: string;
142
-
143
- @IsOptional()
144
- @IsString()
145
- token?: string;
146
-
147
- @IsOptional()
148
- @IsString()
149
- oauthClientId?: string;
150
-
151
- @IsOptional()
152
- @IsString()
153
- oauthClientSecret?: string;
154
-
155
- @IsOptional()
156
- @IsString()
157
- defaultCatalog?: string;
158
-
159
- @IsOptional()
160
- @IsString()
161
- defaultSchema?: string;
162
-
163
- @IsOptional()
164
- @IsString()
165
- setupSQL?: string;
166
- }
167
-
168
- export class TrinoConnectionDto {
169
- @IsOptional()
170
- @IsString()
171
- server?: string;
172
-
173
- @IsOptional()
174
- @IsString()
175
- port?: number;
176
-
177
- @IsOptional()
178
- @IsString()
179
- catalog?: string;
180
-
181
- @IsOptional()
182
- @IsString()
183
- schema?: string;
184
-
185
- @IsOptional()
186
- @IsString()
187
- user?: string;
188
-
189
- @IsOptional()
190
- @IsString()
191
- password?: string;
192
-
193
- @IsOptional()
194
- @IsString()
195
- peakaKey?: string;
196
- }
197
-
198
- export class DuckdbConnectionDto {
199
- @IsOptional()
200
- @IsArray()
201
- attachedDatabases?: AttachedDatabase[];
202
- }
203
-
204
- export class SshProxyConfigDto {
205
- @IsString()
206
- host!: string;
207
-
208
- @IsOptional()
209
- @IsNumber()
210
- port?: number;
211
-
212
- @IsString()
213
- username!: string;
214
-
215
- @IsString()
216
- privateKey!: string;
217
-
218
- @IsOptional()
219
- @IsString()
220
- privateKeyPass?: string;
221
-
222
- @IsOptional()
223
- @IsString()
224
- hostKey?: string;
225
- }
226
-
227
- export class ConnectionProxyDto {
228
- @IsEnum(["ssh"])
229
- type!: "ssh";
230
-
231
- // ssh config is required for the ssh proxy type (the only type today); keep
232
- // the conditional so a future proxy type isn't forced to supply `ssh`.
233
- @ValidateIf((o) => o.type === "ssh")
234
- @IsDefined()
235
- @ValidateNested()
236
- @Type(() => SshProxyConfigDto)
237
- ssh?: SshProxyConfigDto;
238
- }
239
-
240
- export class ConnectionDto implements ApiConnection {
241
- @IsOptional()
242
- @IsString()
243
- name?: string;
244
-
245
- @IsOptional()
246
- @IsEnum([
247
- "postgres",
248
- "bigquery",
249
- "snowflake",
250
- "trino",
251
- "databricks",
252
- "mysql",
253
- "duckdb",
254
- "motherduck",
255
- "ducklake",
256
- ])
257
- type?:
258
- | "postgres"
259
- | "bigquery"
260
- | "snowflake"
261
- | "trino"
262
- | "databricks"
263
- | "mysql"
264
- | "duckdb"
265
- | "motherduck"
266
- | "ducklake";
267
-
268
- // Opaque, secret-free data-identity token; carried verbatim (never parsed
269
- // or re-hashed) into content-addressed source ids. See api-doc.yaml.
270
- @IsOptional()
271
- @IsString()
272
- fingerprint?: string;
273
-
274
- @IsOptional()
275
- @ValidateNested()
276
- @Type(() => PostgresConnectionDto)
277
- postgresConnection?: PostgresConnectionDto;
278
-
279
- @IsOptional()
280
- @ValidateNested()
281
- @Type(() => BigqueryConnectionDto)
282
- bigqueryConnection?: BigqueryConnectionDto;
283
-
284
- @IsOptional()
285
- @ValidateNested()
286
- @Type(() => SnowflakeConnectionDto)
287
- snowflakeConnection?: SnowflakeConnectionDto;
288
-
289
- @IsOptional()
290
- @ValidateNested()
291
- @Type(() => TrinoConnectionDto)
292
- TrinoConnection?: TrinoConnectionDto;
293
-
294
- @IsOptional()
295
- @ValidateNested()
296
- @Type(() => DatabricksConnectionDto)
297
- databricksConnection?: DatabricksConnectionDto;
298
-
299
- @IsOptional()
300
- @ValidateNested()
301
- @Type(() => DuckdbConnectionDto)
302
- duckdbConnection?: DuckdbConnectionDto;
303
-
304
- @IsOptional()
305
- @ValidateNested()
306
- @Type(() => ConnectionProxyDto)
307
- proxy?: ConnectionProxyDto;
308
- }
package/src/dto/index.ts DELETED
@@ -1,2 +0,0 @@
1
- export * from "./package.dto";
2
- export * from "./connection.dto";
@@ -1,42 +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 { PackageDto } from "./package.dto";
6
- import { faker } from "@faker-js/faker";
7
-
8
- describe("dto/package", () => {
9
- it("should not throw when valid object is provided", async () => {
10
- const errors = await validate(
11
- plainToInstance(PackageDto, {
12
- name: faker.person.firstName(),
13
- description: faker.person.lastName(),
14
- }),
15
- );
16
-
17
- expect(errors).toHaveLength(0);
18
- });
19
-
20
- it("should throw when no name is specified", async () => {
21
- const errors = await validate(
22
- plainToInstance(PackageDto, {
23
- name: "",
24
- description: faker.person.lastName(),
25
- }),
26
- );
27
-
28
- expect(errors.length).toBeGreaterThan(0);
29
- expect(errors).toHaveLength(1);
30
- });
31
-
32
- it("should throw when no description is specified", async () => {
33
- const errors = await validate(
34
- plainToInstance(PackageDto, {
35
- name: faker.person.firstName(),
36
- }),
37
- );
38
-
39
- expect(errors.length).toBeGreaterThan(0);
40
- expect(errors).toHaveLength(1);
41
- });
42
- });
@@ -1,27 +0,0 @@
1
- import {
2
- IsString,
3
- IsNotEmpty,
4
- IsOptional,
5
- IsArray,
6
- IsIn,
7
- } from "class-validator";
8
- import { ApiPackage } from "../service/package";
9
-
10
- export class PackageDto implements ApiPackage {
11
- @IsString()
12
- @IsNotEmpty()
13
- name: string;
14
-
15
- @IsString()
16
- @IsNotEmpty()
17
- description: string;
18
-
19
- @IsOptional()
20
- @IsArray()
21
- @IsString({ each: true })
22
- explores?: string[];
23
-
24
- @IsOptional()
25
- @IsIn(["declared", "all"])
26
- queryableSources?: "declared" | "all";
27
- }
@@ -1,76 +0,0 @@
1
- import { expect, it, describe } from "bun:test";
2
- import request from "supertest";
3
- import express from "express";
4
- import { validateSchema } from "./validate";
5
- import { PackageDto } from "./package.dto";
6
- import { faker } from "@faker-js/faker";
7
-
8
- const app = express();
9
- app.use(express.json());
10
-
11
- app.post("/packages", validateSchema(PackageDto), (req, res) => {
12
- res.status(201).json({ message: "Package created", data: req.body });
13
- });
14
-
15
- describe("dto/validate", () => {
16
- describe("validateSchema", () => {
17
- it("should pass validation for a valid request", async () => {
18
- const dummyPayload = {
19
- name: faker.person.firstName(),
20
- description: faker.person.lastName(),
21
- };
22
-
23
- const response = await request(app)
24
- .post("/packages")
25
- .send(dummyPayload);
26
-
27
- expect(response.status).toBe(201);
28
- expect(response.body.message).toBe("Package created");
29
- expect(response.body.data).toEqual(dummyPayload);
30
- });
31
-
32
- it("should return 400 for missing required fields", async () => {
33
- const response = await request(app).post("/packages").send({
34
- name: "",
35
- description: "",
36
- });
37
-
38
- expect(response.status).toBe(400);
39
- expect(Array.isArray(response.body.errors)).toBe(true);
40
-
41
- const expectedErrors = [
42
- {
43
- property: "name",
44
- constraints: { isNotEmpty: "name should not be empty" },
45
- },
46
- {
47
- property: "description",
48
- constraints: {
49
- isNotEmpty: "description should not be empty",
50
- },
51
- },
52
- ];
53
- expect(response.body.errors).toEqual(
54
- expect.arrayContaining(expectedErrors),
55
- );
56
- });
57
-
58
- it("should return 400 for invalid description format", async () => {
59
- const response = await request(app).post("/packages").send({
60
- name: faker.person.firstName(),
61
- description: false,
62
- });
63
-
64
- expect(response.status).toBe(400);
65
- const expectedErrors = [
66
- {
67
- property: "description",
68
- constraints: { isString: "description must be a string" },
69
- },
70
- ];
71
- expect(response.body.errors).toEqual(
72
- expect.arrayContaining(expectedErrors),
73
- );
74
- });
75
- });
76
- });
@@ -1,31 +0,0 @@
1
- import { Request, Response, NextFunction } from "express";
2
- import { plainToInstance, ClassConstructor } from "class-transformer";
3
- import { validate } from "class-validator";
4
-
5
- /**
6
- * @description validates incoming requests based on the provided dto schema
7
- * @param dtoClass to validate against
8
- * @example
9
- * app.post('/users', validateSchema(UserDto), (req, res) => {
10
- * res.status(201).json({ message: 'User created', data: req.body });
11
- * });
12
- */
13
- export function validateSchema<T extends object>(
14
- dtoClass: ClassConstructor<T>,
15
- ) {
16
- return async (req: Request, res: Response, next: NextFunction) => {
17
- const dtoInstance = plainToInstance(dtoClass, req.body);
18
- const errors = await validate(dtoInstance);
19
-
20
- if (errors.length > 0) {
21
- const formattedErrors = errors.map((err) => ({
22
- property: err.property,
23
- constraints: err.constraints,
24
- }));
25
-
26
- return res.status(400).json({ errors: formattedErrors });
27
- }
28
-
29
- next();
30
- };
31
- }
@@ -1,43 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import {
3
- isCatalogVersionSupported,
4
- SUPPORTED_CATALOG_VERSIONS,
5
- } from "./ducklake_version";
6
-
7
- describe("ducklake_version", () => {
8
- describe("SUPPORTED_CATALOG_VERSIONS", () => {
9
- it("matches the 0.3-line DuckLake extension's internal list", () => {
10
- expect([...SUPPORTED_CATALOG_VERSIONS]).toEqual([
11
- "0.1",
12
- "0.2",
13
- "0.3-dev1",
14
- "0.3",
15
- ]);
16
- });
17
- });
18
-
19
- describe("isCatalogVersionSupported", () => {
20
- it("accepts each documented 0.3-line version", () => {
21
- for (const v of SUPPORTED_CATALOG_VERSIONS) {
22
- expect(isCatalogVersionSupported(v)).toBe(true);
23
- }
24
- });
25
-
26
- it("rejects 1.0-line versions", () => {
27
- expect(isCatalogVersionSupported("1.0")).toBe(false);
28
- expect(isCatalogVersionSupported("1.0.0")).toBe(false);
29
- expect(isCatalogVersionSupported("1.1.0")).toBe(false);
30
- });
31
-
32
- it("rejects newer pre-1.0 versions not in the supported list", () => {
33
- expect(isCatalogVersionSupported("0.4")).toBe(false);
34
- expect(isCatalogVersionSupported("0.3-dev2")).toBe(false);
35
- });
36
-
37
- it("rejects empty and malformed input without throwing", () => {
38
- expect(isCatalogVersionSupported("")).toBe(false);
39
- expect(isCatalogVersionSupported("not a version")).toBe(false);
40
- expect(isCatalogVersionSupported("0.3 ")).toBe(false);
41
- });
42
- });
43
- });
@@ -1,26 +0,0 @@
1
- // DuckLake catalog format compatibility check. Lives at `src/` root so
2
- // both `service/` (user-facing connections) and `storage/` (materialization
3
- // catalog) can pre-flight a catalog's recorded version without bringing in
4
- // the other layer's helpers. See CLAUDE.md's "Two parallel DuckLake/PG
5
- // attach paths" note.
6
- //
7
- // The Publisher Docker image bakes a specific DuckLake extension version at
8
- // build time (see Dockerfile). If a catalog's on-disk format version is
9
- // newer than what the baked extension supports, ATTACH fails deep inside
10
- // DuckDB with a generic 500. The preflight catches that case and surfaces
11
- // it as a non-retryable 422 instead.
12
-
13
- // Versions the currently-baked DuckLake extension (0.3 line, paired with
14
- // duckdb@1.4.4) can read. When the Publisher upgrades to the DuckLake 1.0
15
- // line (separate PR, requires duckdb@1.5+), extend this list to include
16
- // "1.0" and later format versions.
17
- export const SUPPORTED_CATALOG_VERSIONS: readonly string[] = [
18
- "0.1",
19
- "0.2",
20
- "0.3-dev1",
21
- "0.3",
22
- ];
23
-
24
- export function isCatalogVersionSupported(version: string): boolean {
25
- return SUPPORTED_CATALOG_VERSIONS.includes(version);
26
- }
@@ -1,18 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import { agentServerInfo, initializeAgentMcpServer } from "./agent_server";
3
- import type { EnvironmentStore } from "../service/environment_store";
4
-
5
- describe("agent MCP server", () => {
6
- it("advertises a distinct server name from the core MCP server", () => {
7
- expect(agentServerInfo.name).toBe("malloy-publisher-agent-mcp-server");
8
- });
9
-
10
- it("initializes and registers its tools and skill prompts without throwing", () => {
11
- // The store is captured for later handler calls, not used during
12
- // registration, so a placeholder is sufficient for this smoke test.
13
- const server = initializeAgentMcpServer(
14
- {} as unknown as EnvironmentStore,
15
- );
16
- expect(server).toBeTruthy();
17
- });
18
- });