@malloy-publisher/server 0.0.196-dev → 0.0.197-dev
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 +213 -214
- package/dist/app/assets/EnvironmentPage-1j6QDWAy.js +1 -0
- package/dist/app/assets/HomePage-DMop21VG.js +1 -0
- package/dist/app/assets/MainPage-BbE8ETz1.js +2 -0
- package/dist/app/assets/ModelPage-D2jvfe3t.js +1 -0
- package/dist/app/assets/PackagePage-BbnhGoD3.js +1 -0
- package/dist/app/assets/{RouteError-DefbDO7F.js → RouteError-D3LGEZ3i.js} +1 -1
- package/dist/app/assets/WorkbookPage-DttVIj4u.js +1 -0
- package/dist/app/assets/{core-BrfQApxh.es-DnvCX4oH.js → core-w79IMXAG.es-Bd0UlzOL.js} +1 -1
- package/dist/app/assets/{index-Bu0ub036.js → index-5K9YjIxF.js} +117 -117
- package/dist/app/assets/{index-CkzK3JIl.js → index-C513UodQ.js} +1 -1
- package/dist/app/assets/{index-CoA6HIGS.js → index-DIgzgp69.js} +1 -1
- package/dist/app/assets/{index.umd-B6Ms2PpL.js → index.umd-BMeMPq_9.js} +1 -1
- package/dist/app/index.html +1 -1
- package/dist/server.mjs +1328 -1304
- package/package.json +1 -1
- package/publisher.config.json +2 -2
- package/src/config.spec.ts +74 -66
- package/src/config.ts +50 -47
- package/src/controller/compile.controller.ts +10 -7
- package/src/controller/connection.controller.ts +79 -58
- package/src/controller/database.controller.ts +10 -7
- package/src/controller/manifest.controller.ts +23 -14
- package/src/controller/materialization.controller.ts +14 -14
- package/src/controller/model.controller.ts +35 -20
- package/src/controller/package.controller.ts +83 -49
- package/src/controller/query.controller.ts +11 -8
- package/src/controller/watch-mode.controller.ts +35 -29
- package/src/errors.ts +2 -2
- package/src/mcp/error_messages.ts +2 -2
- package/src/mcp/handler_utils.ts +23 -20
- package/src/mcp/mcp_constants.ts +1 -1
- package/src/mcp/prompts/handlers.ts +3 -3
- package/src/mcp/prompts/prompt_service.ts +5 -5
- package/src/mcp/prompts/utils.ts +12 -12
- package/src/mcp/resource_metadata.ts +3 -3
- package/src/mcp/resources/environment_resource.ts +187 -0
- package/src/mcp/resources/model_resource.ts +19 -17
- package/src/mcp/resources/notebook_resource.ts +13 -13
- package/src/mcp/resources/package_resource.ts +30 -27
- package/src/mcp/resources/query_resource.ts +15 -10
- package/src/mcp/resources/source_resource.ts +10 -10
- package/src/mcp/resources/view_resource.ts +11 -11
- package/src/mcp/server.ts +16 -14
- package/src/mcp/tools/discovery_tools.ts +67 -49
- package/src/mcp/tools/execute_query_tool.ts +14 -14
- package/src/server.ts +175 -159
- package/src/service/connection.spec.ts +158 -133
- package/src/service/connection.ts +42 -39
- package/src/service/connection_config.spec.ts +13 -11
- package/src/service/connection_config.ts +28 -19
- package/src/service/connection_service.spec.ts +63 -43
- package/src/service/connection_service.ts +106 -89
- package/src/service/{project.ts → environment.ts} +92 -77
- package/src/service/{project_compile.spec.ts → environment_compile.spec.ts} +1 -1
- package/src/service/{project_store.spec.ts → environment_store.spec.ts} +99 -85
- package/src/service/{project_store.ts → environment_store.ts} +368 -326
- package/src/service/manifest_service.spec.ts +15 -15
- package/src/service/manifest_service.ts +26 -21
- package/src/service/materialization_service.spec.ts +93 -59
- package/src/service/materialization_service.ts +71 -62
- package/src/service/materialized_table_gc.spec.ts +15 -15
- package/src/service/materialized_table_gc.ts +3 -3
- package/src/service/model.ts +2 -2
- package/src/service/package.spec.ts +2 -2
- package/src/service/package.ts +23 -21
- package/src/service/resolve_environment.ts +15 -0
- package/src/storage/DatabaseInterface.ts +34 -25
- package/src/storage/StorageManager.mock.ts +3 -3
- package/src/storage/StorageManager.ts +24 -23
- package/src/storage/duckdb/ConnectionRepository.ts +13 -11
- package/src/storage/duckdb/DuckDBConnection.ts +1 -1
- package/src/storage/duckdb/DuckDBManifestStore.ts +6 -6
- package/src/storage/duckdb/DuckDBRepository.ts +47 -47
- package/src/storage/duckdb/{ProjectRepository.ts → EnvironmentRepository.ts} +35 -35
- package/src/storage/duckdb/ManifestRepository.ts +21 -20
- package/src/storage/duckdb/MaterializationRepository.ts +31 -28
- package/src/storage/duckdb/PackageRepository.ts +11 -11
- package/src/storage/duckdb/manifest_store.spec.ts +2 -2
- package/src/storage/duckdb/schema.ts +20 -20
- package/src/storage/ducklake/DuckLakeManifestStore.ts +14 -14
- package/tests/fixtures/publisher.config.json +1 -1
- package/tests/harness/e2e.ts +1 -1
- package/tests/harness/mcp_test_setup.ts +1 -1
- package/tests/harness/mocks.ts +10 -8
- package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +4 -4
- package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +27 -48
- package/tests/integration/mcp/mcp_resource.integration.spec.ts +26 -35
- package/tests/unit/duckdb/attached_databases.test.ts +51 -33
- package/tests/unit/ducklake/ducklake.test.ts +24 -22
- package/tests/unit/mcp/prompt_happy.test.ts +8 -8
- package/dist/app/assets/HomePage-DbZS0N7G.js +0 -1
- package/dist/app/assets/MainPage-CBuWkbmr.js +0 -2
- package/dist/app/assets/ModelPage-Bt37smot.js +0 -1
- package/dist/app/assets/PackagePage-DLZe50WG.js +0 -1
- package/dist/app/assets/ProjectPage-FQTEPXP4.js +0 -1
- package/dist/app/assets/WorkbookPage-CkAo16ar.js +0 -1
- package/src/mcp/resources/project_resource.ts +0 -184
- package/src/service/resolve_project.ts +0 -13
|
@@ -37,29 +37,29 @@ describe.serial("MCP Resource Handlers (E2E Integration)", () => {
|
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
// --- Test Constants ---
|
|
40
|
-
const homeProjectUri = "malloy://
|
|
41
|
-
const faaPackageUri = "malloy://
|
|
40
|
+
const homeProjectUri = "malloy://environment/malloy-samples";
|
|
41
|
+
const faaPackageUri = "malloy://environment/malloy-samples/package/faa";
|
|
42
42
|
const flightsModelUri =
|
|
43
|
-
"malloy://
|
|
43
|
+
"malloy://environment/malloy-samples/package/faa/models/flights.malloy";
|
|
44
44
|
const FLIGHTS_SOURCE = "flights";
|
|
45
45
|
const FLIGHTS_CARRIER_QUERY = "flights_by_carrier";
|
|
46
46
|
const FLIGHTS_MONTH_VIEW = "flights_by_month";
|
|
47
47
|
const OVERVIEW_NOTEBOOK = "overview.malloynb";
|
|
48
48
|
const nonExistentPackageUri =
|
|
49
|
-
"malloy://
|
|
49
|
+
"malloy://environment/malloy-samples/package/nonexistent";
|
|
50
50
|
const nonExistentModelUri =
|
|
51
|
-
"malloy://
|
|
52
|
-
const nonExistentProjectUri = "malloy://
|
|
51
|
+
"malloy://environment/malloy-samples/package/faa/models/nonexistent.malloy";
|
|
52
|
+
const nonExistentProjectUri = "malloy://environment/invalid_project";
|
|
53
53
|
const invalidUri = "invalid://format";
|
|
54
54
|
|
|
55
|
-
const validSourceUri = `malloy://
|
|
56
|
-
const validQueryUri = `malloy://
|
|
57
|
-
const validViewUri = `malloy://
|
|
58
|
-
const validNotebookUri = `malloy://
|
|
59
|
-
const nonExistentSourceUri = `malloy://
|
|
60
|
-
const nonExistentQueryUri = `malloy://
|
|
61
|
-
const nonExistentViewUri = `malloy://
|
|
62
|
-
const nonExistentNotebookUri = `malloy://
|
|
55
|
+
const validSourceUri = `malloy://environment/malloy-samples/package/faa/models/flights.malloy/sources/${FLIGHTS_SOURCE}`;
|
|
56
|
+
const validQueryUri = `malloy://environment/malloy-samples/package/faa/models/flights.malloy/queries/${FLIGHTS_CARRIER_QUERY}`;
|
|
57
|
+
const validViewUri = `malloy://environment/malloy-samples/package/faa/models/flights.malloy/sources/${FLIGHTS_SOURCE}/views/${FLIGHTS_MONTH_VIEW}`;
|
|
58
|
+
const validNotebookUri = `malloy://environment/malloy-samples/package/faa/notebooks/${OVERVIEW_NOTEBOOK}`;
|
|
59
|
+
const nonExistentSourceUri = `malloy://environment/malloy-samples/package/faa/models/flights.malloy/sources/non_existent_source`;
|
|
60
|
+
const nonExistentQueryUri = `malloy://environment/malloy-samples/package/faa/models/flights.malloy/queries/non_existent_query`;
|
|
61
|
+
const nonExistentViewUri = `malloy://environment/malloy-samples/package/faa/models/flights.malloy/sources/${FLIGHTS_SOURCE}/views/non_existent_view`;
|
|
62
|
+
const nonExistentNotebookUri = `malloy://environment/malloy-samples/package/faa/notebooks/non_existent.malloynb`;
|
|
63
63
|
|
|
64
64
|
describe("client.listResources", () => {
|
|
65
65
|
it(
|
|
@@ -217,7 +217,7 @@ describe.serial("MCP Resource Handlers (E2E Integration)", () => {
|
|
|
217
217
|
);
|
|
218
218
|
if (flightsEntry) {
|
|
219
219
|
expect(flightsEntry.uri).toBe(
|
|
220
|
-
"malloy://
|
|
220
|
+
"malloy://environment/malloy-samples/package/faa/sources/flights.malloy",
|
|
221
221
|
);
|
|
222
222
|
expect(flightsEntry.metadata).toBeDefined();
|
|
223
223
|
expect(flightsEntry.metadata!.description).toContain(
|
|
@@ -232,7 +232,7 @@ describe.serial("MCP Resource Handlers (E2E Integration)", () => {
|
|
|
232
232
|
);
|
|
233
233
|
if (notebookEntry) {
|
|
234
234
|
expect(notebookEntry.uri).toBe(
|
|
235
|
-
"malloy://
|
|
235
|
+
"malloy://environment/malloy-samples/package/faa/notebooks/aircraft_analysis.malloynb",
|
|
236
236
|
);
|
|
237
237
|
expect(notebookEntry.metadata).toBeDefined();
|
|
238
238
|
expect(notebookEntry.metadata!.description).toContain(
|
|
@@ -505,7 +505,7 @@ describe.serial("MCP Resource Handlers (E2E Integration)", () => {
|
|
|
505
505
|
// Adjust test to expect the generic "Resource not found" error, as the specific
|
|
506
506
|
// "not a notebook" detail isn't easily surfaced in the standard error format.
|
|
507
507
|
expect(errorPayload.error).toMatch(/Notebook 'overview.malloynb'/);
|
|
508
|
-
expect(errorPayload.error).toMatch(/
|
|
508
|
+
expect(errorPayload.error).toMatch(/environment 'malloy-samples'/);
|
|
509
509
|
expect(errorPayload.suggestions).toBeDefined();
|
|
510
510
|
expect(Array.isArray(errorPayload.suggestions)).toBe(true);
|
|
511
511
|
expect(errorPayload.suggestions.length).toBeGreaterThan(0);
|
|
@@ -532,8 +532,7 @@ describe.serial("MCP Resource Handlers (E2E Integration)", () => {
|
|
|
532
532
|
expect(errorPayload.error).toMatch(/Resource not found/i);
|
|
533
533
|
// Check for the specific source name in the message
|
|
534
534
|
expect(errorPayload.error).toMatch(/Source 'non_existent_source'/);
|
|
535
|
-
|
|
536
|
-
expect(errorPayload.error).toMatch(/project 'malloy-samples'/);
|
|
535
|
+
expect(errorPayload.error).toMatch(/environment 'malloy-samples'/);
|
|
537
536
|
expect(errorPayload.suggestions).toBeDefined();
|
|
538
537
|
expect(Array.isArray(errorPayload.suggestions)).toBe(true);
|
|
539
538
|
expect(errorPayload.suggestions.length).toBeGreaterThan(0);
|
|
@@ -558,8 +557,7 @@ describe.serial("MCP Resource Handlers (E2E Integration)", () => {
|
|
|
558
557
|
expect(errorPayload.error).toMatch(/Resource not found/i);
|
|
559
558
|
// Check for the specific query name in the message
|
|
560
559
|
expect(errorPayload.error).toMatch(/Query 'non_existent_query'/);
|
|
561
|
-
|
|
562
|
-
expect(errorPayload.error).toMatch(/project 'malloy-samples'/);
|
|
560
|
+
expect(errorPayload.error).toMatch(/environment 'malloy-samples'/);
|
|
563
561
|
expect(errorPayload.suggestions).toBeDefined();
|
|
564
562
|
expect(Array.isArray(errorPayload.suggestions)).toBe(true);
|
|
565
563
|
expect(errorPayload.suggestions.length).toBeGreaterThan(0);
|
|
@@ -584,8 +582,7 @@ describe.serial("MCP Resource Handlers (E2E Integration)", () => {
|
|
|
584
582
|
expect(errorPayload.error).toMatch(/Resource not found/i);
|
|
585
583
|
// Check for the specific view name in the message
|
|
586
584
|
expect(errorPayload.error).toMatch(/View 'non_existent_view'/);
|
|
587
|
-
|
|
588
|
-
expect(errorPayload.error).toMatch(/project 'malloy-samples'/);
|
|
585
|
+
expect(errorPayload.error).toMatch(/environment 'malloy-samples'/);
|
|
589
586
|
expect(errorPayload.suggestions).toBeDefined();
|
|
590
587
|
expect(Array.isArray(errorPayload.suggestions)).toBe(true);
|
|
591
588
|
expect(errorPayload.suggestions.length).toBeGreaterThan(0);
|
|
@@ -611,8 +608,7 @@ describe.serial("MCP Resource Handlers (E2E Integration)", () => {
|
|
|
611
608
|
// Check for the specific notebook name and context in the message
|
|
612
609
|
// Adjust test to expect the generic "Resource not found" error
|
|
613
610
|
expect(errorPayload.error).toMatch(/Notebook 'non_existent.malloynb'/);
|
|
614
|
-
|
|
615
|
-
expect(errorPayload.error).toMatch(/project 'malloy-samples'/);
|
|
611
|
+
expect(errorPayload.error).toMatch(/environment 'malloy-samples'/);
|
|
616
612
|
expect(errorPayload.suggestions).toBeDefined();
|
|
617
613
|
expect(Array.isArray(errorPayload.suggestions)).toBe(true);
|
|
618
614
|
expect(errorPayload.suggestions.length).toBeGreaterThan(0);
|
|
@@ -623,7 +619,7 @@ describe.serial("MCP Resource Handlers (E2E Integration)", () => {
|
|
|
623
619
|
|
|
624
620
|
it("should return structured app error when requesting view from wrong source", async () => {
|
|
625
621
|
if (!env) throw new Error("Test environment not initialized");
|
|
626
|
-
const wrongSourceUri = `malloy://
|
|
622
|
+
const wrongSourceUri = `malloy://environment/malloy-samples/package/faa/models/flights.malloy/sources/aircraft/views/${FLIGHTS_MONTH_VIEW}`;
|
|
627
623
|
const result = await mcpClient.readResource({ uri: wrongSourceUri });
|
|
628
624
|
expect(result.isError).toBe(true);
|
|
629
625
|
expect(result.contents).toBeDefined();
|
|
@@ -633,22 +629,17 @@ describe.serial("MCP Resource Handlers (E2E Integration)", () => {
|
|
|
633
629
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
634
630
|
const errorPayload = JSON.parse((result.contents![0] as any).text);
|
|
635
631
|
expect(errorPayload.error).toBeDefined();
|
|
636
|
-
// Adjust expectation: The primary error should be the project not found
|
|
637
632
|
expect(errorPayload.error).toMatch(/Resource not found/i);
|
|
638
|
-
expect(errorPayload.error).toMatch(/
|
|
639
|
-
|
|
633
|
+
expect(errorPayload.error).toMatch(/View 'flights_by_month'/);
|
|
634
|
+
expect(errorPayload.error).toMatch(/source 'aircraft'/);
|
|
635
|
+
expect(errorPayload.error).toMatch(/environment 'malloy-samples'/);
|
|
640
636
|
expect(errorPayload.suggestions).toBeDefined();
|
|
641
637
|
expect(Array.isArray(errorPayload.suggestions)).toBe(true);
|
|
642
|
-
expect(errorPayload.suggestions.length).toBeGreaterThan(0);
|
|
643
|
-
|
|
644
|
-
// The suggestions come as full sentences, so we check for expected content
|
|
645
638
|
expect(errorPayload.suggestions.length).toBe(3);
|
|
646
639
|
expect(errorPayload.suggestions[0]).toContain(
|
|
647
640
|
"Verify the identifier or URI",
|
|
648
641
|
);
|
|
649
|
-
expect(errorPayload.suggestions[0]).toContain(
|
|
650
|
-
"project 'malloy-samples'",
|
|
651
|
-
);
|
|
642
|
+
expect(errorPayload.suggestions[0]).toContain("flights_by_month");
|
|
652
643
|
expect(errorPayload.suggestions[0]).toContain("is spelled correctly");
|
|
653
644
|
expect(errorPayload.suggestions[0]).toContain(
|
|
654
645
|
"Check capitalization and path separators",
|
|
@@ -12,7 +12,7 @@ import fs from "fs/promises";
|
|
|
12
12
|
import os from "os";
|
|
13
13
|
import path from "path";
|
|
14
14
|
import type { components } from "../../../src/api";
|
|
15
|
-
import {
|
|
15
|
+
import { createEnvironmentConnections } from "../../../src/service/connection";
|
|
16
16
|
|
|
17
17
|
type ApiConnection = components["schemas"]["Connection"];
|
|
18
18
|
|
|
@@ -355,7 +355,7 @@ describe("DuckDB Attached Databases", () => {
|
|
|
355
355
|
});
|
|
356
356
|
});
|
|
357
357
|
|
|
358
|
-
describe("
|
|
358
|
+
describe("createEnvironmentConnections - DuckDB", () => {
|
|
359
359
|
const PROJECT_TEST_DIR = path.join(os.tmpdir(), "duckdb-project-tests");
|
|
360
360
|
let createdConnections: Map<string, unknown> = new Map();
|
|
361
361
|
|
|
@@ -402,7 +402,7 @@ describe("createProjectConnections - DuckDB", () => {
|
|
|
402
402
|
];
|
|
403
403
|
|
|
404
404
|
const { malloyConnections, apiConnections } =
|
|
405
|
-
await
|
|
405
|
+
await createEnvironmentConnections(connections, PROJECT_TEST_DIR);
|
|
406
406
|
|
|
407
407
|
createdConnections = malloyConnections;
|
|
408
408
|
|
|
@@ -433,7 +433,7 @@ describe("createProjectConnections - DuckDB", () => {
|
|
|
433
433
|
];
|
|
434
434
|
|
|
435
435
|
const { malloyConnections, apiConnections } =
|
|
436
|
-
await
|
|
436
|
+
await createEnvironmentConnections(connections, PROJECT_TEST_DIR);
|
|
437
437
|
|
|
438
438
|
createdConnections = malloyConnections;
|
|
439
439
|
|
|
@@ -462,7 +462,7 @@ describe("createProjectConnections - DuckDB", () => {
|
|
|
462
462
|
];
|
|
463
463
|
|
|
464
464
|
const { malloyConnections, apiConnections } =
|
|
465
|
-
await
|
|
465
|
+
await createEnvironmentConnections(connections, PROJECT_TEST_DIR);
|
|
466
466
|
|
|
467
467
|
createdConnections = malloyConnections;
|
|
468
468
|
|
|
@@ -502,7 +502,7 @@ describe("createProjectConnections - DuckDB", () => {
|
|
|
502
502
|
},
|
|
503
503
|
];
|
|
504
504
|
|
|
505
|
-
const { malloyConnections } = await
|
|
505
|
+
const { malloyConnections } = await createEnvironmentConnections(
|
|
506
506
|
connections,
|
|
507
507
|
PROJECT_TEST_DIR,
|
|
508
508
|
);
|
|
@@ -533,7 +533,7 @@ describe("createProjectConnections - DuckDB", () => {
|
|
|
533
533
|
},
|
|
534
534
|
];
|
|
535
535
|
|
|
536
|
-
const { malloyConnections } = await
|
|
536
|
+
const { malloyConnections } = await createEnvironmentConnections(
|
|
537
537
|
connections,
|
|
538
538
|
PROJECT_TEST_DIR,
|
|
539
539
|
);
|
|
@@ -591,7 +591,7 @@ describe("createProjectConnections - DuckDB", () => {
|
|
|
591
591
|
},
|
|
592
592
|
];
|
|
593
593
|
|
|
594
|
-
const { malloyConnections } = await
|
|
594
|
+
const { malloyConnections } = await createEnvironmentConnections(
|
|
595
595
|
connections,
|
|
596
596
|
PROJECT_TEST_DIR,
|
|
597
597
|
);
|
|
@@ -624,7 +624,7 @@ describe("createProjectConnections - DuckDB", () => {
|
|
|
624
624
|
];
|
|
625
625
|
|
|
626
626
|
await expect(
|
|
627
|
-
|
|
627
|
+
createEnvironmentConnections(connections, PROJECT_TEST_DIR),
|
|
628
628
|
).rejects.toThrow();
|
|
629
629
|
});
|
|
630
630
|
|
|
@@ -637,7 +637,7 @@ describe("createProjectConnections - DuckDB", () => {
|
|
|
637
637
|
] as ApiConnection[];
|
|
638
638
|
|
|
639
639
|
await expect(
|
|
640
|
-
|
|
640
|
+
createEnvironmentConnections(connections, PROJECT_TEST_DIR),
|
|
641
641
|
).rejects.toThrow("DuckDB connection configuration is missing");
|
|
642
642
|
});
|
|
643
643
|
|
|
@@ -658,7 +658,7 @@ describe("createProjectConnections - DuckDB", () => {
|
|
|
658
658
|
];
|
|
659
659
|
|
|
660
660
|
await expect(
|
|
661
|
-
|
|
661
|
+
createEnvironmentConnections(connections, PROJECT_TEST_DIR),
|
|
662
662
|
).rejects.toThrow("Unsupported database type");
|
|
663
663
|
});
|
|
664
664
|
|
|
@@ -682,7 +682,7 @@ describe("createProjectConnections - DuckDB", () => {
|
|
|
682
682
|
];
|
|
683
683
|
|
|
684
684
|
await expect(
|
|
685
|
-
|
|
685
|
+
createEnvironmentConnections(connections, PROJECT_TEST_DIR),
|
|
686
686
|
).rejects.toThrow("service account key required");
|
|
687
687
|
});
|
|
688
688
|
|
|
@@ -704,7 +704,7 @@ describe("createProjectConnections - DuckDB", () => {
|
|
|
704
704
|
] as ApiConnection[];
|
|
705
705
|
|
|
706
706
|
await expect(
|
|
707
|
-
|
|
707
|
+
createEnvironmentConnections(connections, PROJECT_TEST_DIR),
|
|
708
708
|
).rejects.toThrow("keyId and secret are required");
|
|
709
709
|
});
|
|
710
710
|
|
|
@@ -726,7 +726,7 @@ describe("createProjectConnections - DuckDB", () => {
|
|
|
726
726
|
] as ApiConnection[];
|
|
727
727
|
|
|
728
728
|
await expect(
|
|
729
|
-
|
|
729
|
+
createEnvironmentConnections(connections, PROJECT_TEST_DIR),
|
|
730
730
|
).rejects.toThrow("accessKeyId and secretAccessKey are required");
|
|
731
731
|
});
|
|
732
732
|
|
|
@@ -748,7 +748,7 @@ describe("createProjectConnections - DuckDB", () => {
|
|
|
748
748
|
] as ApiConnection[];
|
|
749
749
|
|
|
750
750
|
await expect(
|
|
751
|
-
|
|
751
|
+
createEnvironmentConnections(connections, PROJECT_TEST_DIR),
|
|
752
752
|
).rejects.toThrow("PostgreSQL connection configuration is required");
|
|
753
753
|
});
|
|
754
754
|
|
|
@@ -761,7 +761,7 @@ describe("createProjectConnections - DuckDB", () => {
|
|
|
761
761
|
] as ApiConnection[];
|
|
762
762
|
|
|
763
763
|
await expect(
|
|
764
|
-
|
|
764
|
+
createEnvironmentConnections(connections, PROJECT_TEST_DIR),
|
|
765
765
|
).rejects.toThrow("DuckLake connection configuration is missing");
|
|
766
766
|
});
|
|
767
767
|
|
|
@@ -785,7 +785,7 @@ describe("createProjectConnections - DuckDB", () => {
|
|
|
785
785
|
];
|
|
786
786
|
|
|
787
787
|
await expect(
|
|
788
|
-
|
|
788
|
+
createEnvironmentConnections(connections, PROJECT_TEST_DIR),
|
|
789
789
|
).rejects.toThrow("username is required");
|
|
790
790
|
});
|
|
791
791
|
|
|
@@ -806,7 +806,7 @@ describe("createProjectConnections - DuckDB", () => {
|
|
|
806
806
|
];
|
|
807
807
|
|
|
808
808
|
await expect(
|
|
809
|
-
|
|
809
|
+
createEnvironmentConnections(connections, PROJECT_TEST_DIR),
|
|
810
810
|
).rejects.toThrow("PostgreSQL connection configuration missing");
|
|
811
811
|
});
|
|
812
812
|
|
|
@@ -836,7 +836,7 @@ describe("createProjectConnections - DuckDB", () => {
|
|
|
836
836
|
];
|
|
837
837
|
|
|
838
838
|
await expect(
|
|
839
|
-
|
|
839
|
+
createEnvironmentConnections(connections, PROJECT_TEST_DIR),
|
|
840
840
|
).rejects.toThrow(
|
|
841
841
|
"DuckDB attached database names cannot conflict with connection name",
|
|
842
842
|
);
|
|
@@ -854,7 +854,7 @@ describe("createProjectConnections - DuckDB", () => {
|
|
|
854
854
|
];
|
|
855
855
|
|
|
856
856
|
await expect(
|
|
857
|
-
|
|
857
|
+
createEnvironmentConnections(connections, PROJECT_TEST_DIR),
|
|
858
858
|
).rejects.toThrow("DuckDB connection name cannot be 'duckdb'");
|
|
859
859
|
});
|
|
860
860
|
|
|
@@ -884,10 +884,28 @@ describe("createProjectConnections - DuckDB", () => {
|
|
|
884
884
|
];
|
|
885
885
|
|
|
886
886
|
await expect(
|
|
887
|
-
|
|
887
|
+
createEnvironmentConnections(connections, PROJECT_TEST_DIR),
|
|
888
888
|
).rejects.toThrow("DuckDB connection name cannot be 'duckdb'");
|
|
889
889
|
});
|
|
890
890
|
|
|
891
|
+
it("should throw when DuckDB connection has no attached databases", async () => {
|
|
892
|
+
const connections: ApiConnection[] = [
|
|
893
|
+
{
|
|
894
|
+
name: "no_attached_db",
|
|
895
|
+
type: "duckdb",
|
|
896
|
+
duckdbConnection: {
|
|
897
|
+
attachedDatabases: [],
|
|
898
|
+
},
|
|
899
|
+
},
|
|
900
|
+
];
|
|
901
|
+
|
|
902
|
+
await expect(
|
|
903
|
+
createEnvironmentConnections(connections, PROJECT_TEST_DIR),
|
|
904
|
+
).rejects.toThrow(
|
|
905
|
+
"DuckDB connection must have at least one attached database",
|
|
906
|
+
);
|
|
907
|
+
});
|
|
908
|
+
|
|
891
909
|
it("should throw on unsupported connection type", async () => {
|
|
892
910
|
const connections = [
|
|
893
911
|
{
|
|
@@ -897,7 +915,7 @@ describe("createProjectConnections - DuckDB", () => {
|
|
|
897
915
|
] as ApiConnection[];
|
|
898
916
|
|
|
899
917
|
await expect(
|
|
900
|
-
|
|
918
|
+
createEnvironmentConnections(connections, PROJECT_TEST_DIR),
|
|
901
919
|
).rejects.toThrow("Unsupported connection type");
|
|
902
920
|
});
|
|
903
921
|
|
|
@@ -921,13 +939,13 @@ describe("createProjectConnections - DuckDB", () => {
|
|
|
921
939
|
] as ApiConnection[];
|
|
922
940
|
|
|
923
941
|
await expect(
|
|
924
|
-
|
|
942
|
+
createEnvironmentConnections(connections, PROJECT_TEST_DIR),
|
|
925
943
|
).rejects.toThrow();
|
|
926
944
|
});
|
|
927
945
|
});
|
|
928
946
|
});
|
|
929
947
|
|
|
930
|
-
describe("
|
|
948
|
+
describe("createEnvironmentConnections - Other Connection Types", () => {
|
|
931
949
|
const PROJECT_TEST_DIR = path.join(
|
|
932
950
|
os.tmpdir(),
|
|
933
951
|
"connection-validation-tests",
|
|
@@ -970,7 +988,7 @@ describe("createProjectConnections - Other Connection Types", () => {
|
|
|
970
988
|
] as ApiConnection[];
|
|
971
989
|
|
|
972
990
|
await expect(
|
|
973
|
-
|
|
991
|
+
createEnvironmentConnections(connections, PROJECT_TEST_DIR),
|
|
974
992
|
).rejects.toThrow("Snowflake connection configuration is missing");
|
|
975
993
|
});
|
|
976
994
|
|
|
@@ -988,7 +1006,7 @@ describe("createProjectConnections - Other Connection Types", () => {
|
|
|
988
1006
|
];
|
|
989
1007
|
|
|
990
1008
|
await expect(
|
|
991
|
-
|
|
1009
|
+
createEnvironmentConnections(connections, PROJECT_TEST_DIR),
|
|
992
1010
|
).rejects.toThrow("Snowflake account is required");
|
|
993
1011
|
});
|
|
994
1012
|
|
|
@@ -1006,7 +1024,7 @@ describe("createProjectConnections - Other Connection Types", () => {
|
|
|
1006
1024
|
];
|
|
1007
1025
|
|
|
1008
1026
|
await expect(
|
|
1009
|
-
|
|
1027
|
+
createEnvironmentConnections(connections, PROJECT_TEST_DIR),
|
|
1010
1028
|
).rejects.toThrow("Snowflake username is required");
|
|
1011
1029
|
});
|
|
1012
1030
|
|
|
@@ -1024,7 +1042,7 @@ describe("createProjectConnections - Other Connection Types", () => {
|
|
|
1024
1042
|
];
|
|
1025
1043
|
|
|
1026
1044
|
await expect(
|
|
1027
|
-
|
|
1045
|
+
createEnvironmentConnections(connections, PROJECT_TEST_DIR),
|
|
1028
1046
|
).rejects.toThrow(
|
|
1029
1047
|
"Snowflake password or private key or private key path is required",
|
|
1030
1048
|
);
|
|
@@ -1044,7 +1062,7 @@ describe("createProjectConnections - Other Connection Types", () => {
|
|
|
1044
1062
|
];
|
|
1045
1063
|
|
|
1046
1064
|
await expect(
|
|
1047
|
-
|
|
1065
|
+
createEnvironmentConnections(connections, PROJECT_TEST_DIR),
|
|
1048
1066
|
).rejects.toThrow("Snowflake warehouse is required");
|
|
1049
1067
|
});
|
|
1050
1068
|
});
|
|
@@ -1059,7 +1077,7 @@ describe("createProjectConnections - Other Connection Types", () => {
|
|
|
1059
1077
|
] as ApiConnection[];
|
|
1060
1078
|
|
|
1061
1079
|
await expect(
|
|
1062
|
-
|
|
1080
|
+
createEnvironmentConnections(connections, PROJECT_TEST_DIR),
|
|
1063
1081
|
).rejects.toThrow("Trino connection configuration is missing");
|
|
1064
1082
|
});
|
|
1065
1083
|
|
|
@@ -1076,7 +1094,7 @@ describe("createProjectConnections - Other Connection Types", () => {
|
|
|
1076
1094
|
];
|
|
1077
1095
|
|
|
1078
1096
|
await expect(
|
|
1079
|
-
|
|
1097
|
+
createEnvironmentConnections(connections, PROJECT_TEST_DIR),
|
|
1080
1098
|
).rejects.toThrow(
|
|
1081
1099
|
'Invalid Trino connection: expected "http://server:port" or "https://server:port"',
|
|
1082
1100
|
);
|
|
@@ -1093,7 +1111,7 @@ describe("createProjectConnections - Other Connection Types", () => {
|
|
|
1093
1111
|
] as ApiConnection[];
|
|
1094
1112
|
|
|
1095
1113
|
await expect(
|
|
1096
|
-
|
|
1114
|
+
createEnvironmentConnections(connections, PROJECT_TEST_DIR),
|
|
1097
1115
|
).rejects.toThrow("MotherDuck connection configuration is missing");
|
|
1098
1116
|
});
|
|
1099
1117
|
|
|
@@ -1107,7 +1125,7 @@ describe("createProjectConnections - Other Connection Types", () => {
|
|
|
1107
1125
|
];
|
|
1108
1126
|
|
|
1109
1127
|
await expect(
|
|
1110
|
-
|
|
1128
|
+
createEnvironmentConnections(connections, PROJECT_TEST_DIR),
|
|
1111
1129
|
).rejects.toThrow("MotherDuck access token is required");
|
|
1112
1130
|
});
|
|
1113
1131
|
});
|
|
@@ -4,7 +4,7 @@ import fs from "fs/promises";
|
|
|
4
4
|
import path from "path";
|
|
5
5
|
import { components } from "../../../src/api";
|
|
6
6
|
import {
|
|
7
|
-
|
|
7
|
+
createEnvironmentConnections,
|
|
8
8
|
deleteDuckLakeConnectionFile,
|
|
9
9
|
testConnectionConfig,
|
|
10
10
|
} from "../../../src/service/connection";
|
|
@@ -143,7 +143,7 @@ describe("DuckLake Connection Tests", () => {
|
|
|
143
143
|
};
|
|
144
144
|
|
|
145
145
|
const { malloyConnections, apiConnections } =
|
|
146
|
-
await
|
|
146
|
+
await createEnvironmentConnections(
|
|
147
147
|
[ducklakeConnection],
|
|
148
148
|
testProjectPath,
|
|
149
149
|
);
|
|
@@ -212,7 +212,7 @@ describe("DuckLake Connection Tests", () => {
|
|
|
212
212
|
},
|
|
213
213
|
};
|
|
214
214
|
|
|
215
|
-
const { malloyConnections } = await
|
|
215
|
+
const { malloyConnections } = await createEnvironmentConnections(
|
|
216
216
|
[ducklakeConnection],
|
|
217
217
|
testProjectPath,
|
|
218
218
|
);
|
|
@@ -268,7 +268,7 @@ describe("DuckLake Connection Tests", () => {
|
|
|
268
268
|
},
|
|
269
269
|
};
|
|
270
270
|
|
|
271
|
-
const { malloyConnections } = await
|
|
271
|
+
const { malloyConnections } = await createEnvironmentConnections(
|
|
272
272
|
[ducklakeConnection],
|
|
273
273
|
testProjectPath,
|
|
274
274
|
);
|
|
@@ -333,7 +333,7 @@ describe("DuckLake Connection Tests", () => {
|
|
|
333
333
|
},
|
|
334
334
|
};
|
|
335
335
|
|
|
336
|
-
const { malloyConnections } = await
|
|
336
|
+
const { malloyConnections } = await createEnvironmentConnections(
|
|
337
337
|
[ducklakeConnection],
|
|
338
338
|
testProjectPath,
|
|
339
339
|
);
|
|
@@ -400,7 +400,7 @@ describe("DuckLake Connection Tests", () => {
|
|
|
400
400
|
},
|
|
401
401
|
};
|
|
402
402
|
|
|
403
|
-
const { malloyConnections } = await
|
|
403
|
+
const { malloyConnections } = await createEnvironmentConnections(
|
|
404
404
|
[ducklakeConnection],
|
|
405
405
|
testProjectPath,
|
|
406
406
|
);
|
|
@@ -470,7 +470,7 @@ describe("DuckLake Connection Tests", () => {
|
|
|
470
470
|
},
|
|
471
471
|
};
|
|
472
472
|
|
|
473
|
-
const { malloyConnections } = await
|
|
473
|
+
const { malloyConnections } = await createEnvironmentConnections(
|
|
474
474
|
[ducklakeConnection],
|
|
475
475
|
testProjectPath,
|
|
476
476
|
);
|
|
@@ -529,7 +529,7 @@ describe("DuckLake Connection Tests", () => {
|
|
|
529
529
|
},
|
|
530
530
|
};
|
|
531
531
|
|
|
532
|
-
const { malloyConnections } = await
|
|
532
|
+
const { malloyConnections } = await createEnvironmentConnections(
|
|
533
533
|
[ducklakeConnection],
|
|
534
534
|
testProjectPath,
|
|
535
535
|
);
|
|
@@ -553,7 +553,7 @@ describe("DuckLake Connection Tests", () => {
|
|
|
553
553
|
describe("Error Handling", () => {
|
|
554
554
|
it("should throw error if DuckLake catalog connection is missing", async () => {
|
|
555
555
|
await expect(
|
|
556
|
-
|
|
556
|
+
createEnvironmentConnections(
|
|
557
557
|
[
|
|
558
558
|
{
|
|
559
559
|
name: "ducklake_no_catalog",
|
|
@@ -576,7 +576,7 @@ describe("DuckLake Connection Tests", () => {
|
|
|
576
576
|
|
|
577
577
|
it("should throw error if DuckLake storage bucketUrl is missing", async () => {
|
|
578
578
|
await expect(
|
|
579
|
-
|
|
579
|
+
createEnvironmentConnections(
|
|
580
580
|
[
|
|
581
581
|
{
|
|
582
582
|
name: "ducklake_no_bucket",
|
|
@@ -607,7 +607,7 @@ describe("DuckLake Connection Tests", () => {
|
|
|
607
607
|
|
|
608
608
|
it("should throw error if DuckLake connection config is missing", async () => {
|
|
609
609
|
await expect(
|
|
610
|
-
|
|
610
|
+
createEnvironmentConnections(
|
|
611
611
|
[
|
|
612
612
|
{
|
|
613
613
|
name: "ducklake_missing_config",
|
|
@@ -653,19 +653,21 @@ describe("DuckLake Connection Tests", () => {
|
|
|
653
653
|
};
|
|
654
654
|
|
|
655
655
|
// Create connection twice - second should handle already attached gracefully
|
|
656
|
-
const { malloyConnections: conn1 } =
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
656
|
+
const { malloyConnections: conn1 } =
|
|
657
|
+
await createEnvironmentConnections(
|
|
658
|
+
[ducklakeConnection],
|
|
659
|
+
testProjectPath,
|
|
660
|
+
);
|
|
660
661
|
const connection1 = conn1.get(
|
|
661
662
|
"ducklake_duplicate_test",
|
|
662
663
|
) as DuckDBConnection;
|
|
663
664
|
createdConnections.push(connection1);
|
|
664
665
|
|
|
665
|
-
const { malloyConnections: conn2 } =
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
666
|
+
const { malloyConnections: conn2 } =
|
|
667
|
+
await createEnvironmentConnections(
|
|
668
|
+
[ducklakeConnection],
|
|
669
|
+
testProjectPath,
|
|
670
|
+
);
|
|
669
671
|
const connection2 = conn2.get(
|
|
670
672
|
"ducklake_duplicate_test",
|
|
671
673
|
) as DuckDBConnection;
|
|
@@ -714,7 +716,7 @@ describe("DuckLake Connection Tests", () => {
|
|
|
714
716
|
},
|
|
715
717
|
};
|
|
716
718
|
|
|
717
|
-
const { malloyConnections } = await
|
|
719
|
+
const { malloyConnections } = await createEnvironmentConnections(
|
|
718
720
|
[ducklakeConnection],
|
|
719
721
|
testProjectPath,
|
|
720
722
|
);
|
|
@@ -771,7 +773,7 @@ describe("DuckLake Connection Tests", () => {
|
|
|
771
773
|
},
|
|
772
774
|
};
|
|
773
775
|
|
|
774
|
-
const { malloyConnections } = await
|
|
776
|
+
const { malloyConnections } = await createEnvironmentConnections(
|
|
775
777
|
[ducklakeConnection],
|
|
776
778
|
testProjectPath,
|
|
777
779
|
);
|
|
@@ -903,7 +905,7 @@ describe("DuckLake Connection Tests", () => {
|
|
|
903
905
|
return;
|
|
904
906
|
}
|
|
905
907
|
|
|
906
|
-
const { apiConnections } = await
|
|
908
|
+
const { apiConnections } = await createEnvironmentConnections(
|
|
907
909
|
[
|
|
908
910
|
{
|
|
909
911
|
name: "ducklake_attrs_test",
|
|
@@ -3,13 +3,13 @@ import { describe, it, expect } from "bun:test";
|
|
|
3
3
|
import { promptHandlerMap } from "../../../src/mcp/prompts/handlers";
|
|
4
4
|
|
|
5
5
|
describe("Prompt Handlers – happy paths", () => {
|
|
6
|
-
const
|
|
6
|
+
const environmentStore = undefined; // no EnvironmentStore needed for minimal tests
|
|
7
7
|
|
|
8
8
|
it("explain handler returns text message", async () => {
|
|
9
9
|
const handler = promptHandlerMap["explain-malloy-query@1.0.0"];
|
|
10
10
|
const res = await handler(
|
|
11
11
|
{ query_code: "run: t -> { aggregate: c is count() }" },
|
|
12
|
-
|
|
12
|
+
environmentStore as any,
|
|
13
13
|
);
|
|
14
14
|
expect(res.messages.length).toBe(1);
|
|
15
15
|
expect(res.messages[0].role).toBe("user");
|
|
@@ -21,9 +21,9 @@ describe("Prompt Handlers – happy paths", () => {
|
|
|
21
21
|
const res = await handler(
|
|
22
22
|
{
|
|
23
23
|
natural_language_goal: "total sales by day",
|
|
24
|
-
target_model_uri: "malloy://
|
|
24
|
+
target_model_uri: "malloy://environment/e/pkg/models/m.malloy",
|
|
25
25
|
},
|
|
26
|
-
|
|
26
|
+
environmentStore as any,
|
|
27
27
|
);
|
|
28
28
|
expect(res.messages[0].role).toBe("user");
|
|
29
29
|
});
|
|
@@ -33,9 +33,9 @@ describe("Prompt Handlers – happy paths", () => {
|
|
|
33
33
|
const res = await handler(
|
|
34
34
|
{
|
|
35
35
|
sql_query: "SELECT 1",
|
|
36
|
-
target_model_uri: "malloy://
|
|
36
|
+
target_model_uri: "malloy://environment/e/pkg/models/m.malloy",
|
|
37
37
|
},
|
|
38
|
-
|
|
38
|
+
environmentStore as any,
|
|
39
39
|
);
|
|
40
40
|
expect(res.messages[0].content.type).toBe("text");
|
|
41
41
|
});
|
|
@@ -43,8 +43,8 @@ describe("Prompt Handlers – happy paths", () => {
|
|
|
43
43
|
it("summarize handler returns message", async () => {
|
|
44
44
|
const handler = promptHandlerMap["summarize-malloy-model@1.0.0"];
|
|
45
45
|
const res = await handler(
|
|
46
|
-
{ model_uri: "malloy://
|
|
47
|
-
|
|
46
|
+
{ model_uri: "malloy://environment/e/pkg/models/m.malloy" },
|
|
47
|
+
environmentStore as any,
|
|
48
48
|
);
|
|
49
49
|
expect(res.messages[0].content.type).toBe("text");
|
|
50
50
|
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{d as o,j as a,i as e}from"./index-Bu0ub036.js";function i(){const t=o();return a.jsx(e,{onClickProject:t})}export{i as default};
|