@malloy-publisher/server 0.0.195 → 0.0.196

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 (104) hide show
  1. package/dist/app/api-doc.yaml +213 -214
  2. package/dist/app/assets/EnvironmentPage-1j6QDWAy.js +1 -0
  3. package/dist/app/assets/HomePage-DMop21VG.js +1 -0
  4. package/dist/app/assets/MainPage-BbE8ETz1.js +2 -0
  5. package/dist/app/assets/ModelPage-D2jvfe3t.js +1 -0
  6. package/dist/app/assets/PackagePage-BbnhGoD3.js +1 -0
  7. package/dist/app/assets/{RouteError-DefbDO7F.js → RouteError-D3LGEZ3i.js} +1 -1
  8. package/dist/app/assets/WorkbookPage-DttVIj4u.js +1 -0
  9. package/dist/app/assets/{core-BrfQApxh.es-DnvCX4oH.js → core-w79IMXAG.es-Bd0UlzOL.js} +1 -1
  10. package/dist/app/assets/{index-Bu0ub036.js → index-5K9YjIxF.js} +117 -117
  11. package/dist/app/assets/{index-CkzK3JIl.js → index-C513UodQ.js} +1 -1
  12. package/dist/app/assets/{index-CoA6HIGS.js → index-DIgzgp69.js} +1 -1
  13. package/dist/app/assets/{index.umd-B6Ms2PpL.js → index.umd-BMeMPq_9.js} +1 -1
  14. package/dist/app/index.html +1 -1
  15. package/dist/server.mjs +1976 -1322
  16. package/package.json +2 -2
  17. package/publisher.config.json +2 -2
  18. package/src/config.spec.ts +181 -66
  19. package/src/config.ts +68 -47
  20. package/src/controller/compile.controller.ts +10 -7
  21. package/src/controller/connection.controller.ts +79 -58
  22. package/src/controller/database.controller.ts +10 -7
  23. package/src/controller/manifest.controller.ts +23 -14
  24. package/src/controller/materialization.controller.ts +14 -14
  25. package/src/controller/model.controller.ts +35 -20
  26. package/src/controller/package.controller.ts +83 -49
  27. package/src/controller/query.controller.ts +11 -8
  28. package/src/controller/watch-mode.controller.ts +35 -29
  29. package/src/errors.ts +2 -2
  30. package/src/mcp/error_messages.ts +2 -2
  31. package/src/mcp/handler_utils.ts +23 -20
  32. package/src/mcp/mcp_constants.ts +1 -1
  33. package/src/mcp/prompts/handlers.ts +3 -3
  34. package/src/mcp/prompts/prompt_service.ts +5 -5
  35. package/src/mcp/prompts/utils.ts +12 -12
  36. package/src/mcp/resource_metadata.ts +3 -3
  37. package/src/mcp/resources/environment_resource.ts +187 -0
  38. package/src/mcp/resources/model_resource.ts +19 -17
  39. package/src/mcp/resources/notebook_resource.ts +13 -13
  40. package/src/mcp/resources/package_resource.ts +30 -27
  41. package/src/mcp/resources/query_resource.ts +15 -10
  42. package/src/mcp/resources/source_resource.ts +10 -10
  43. package/src/mcp/resources/view_resource.ts +11 -11
  44. package/src/mcp/server.ts +16 -14
  45. package/src/mcp/tools/discovery_tools.ts +67 -49
  46. package/src/mcp/tools/execute_query_tool.ts +14 -14
  47. package/src/server-old.ts +1119 -0
  48. package/src/server.ts +191 -159
  49. package/src/service/connection.spec.ts +158 -133
  50. package/src/service/connection.ts +42 -39
  51. package/src/service/connection_config.spec.ts +13 -11
  52. package/src/service/connection_config.ts +28 -19
  53. package/src/service/connection_service.spec.ts +63 -43
  54. package/src/service/connection_service.ts +106 -89
  55. package/src/service/{project.ts → environment.ts} +92 -77
  56. package/src/service/{project_compile.spec.ts → environment_compile.spec.ts} +1 -1
  57. package/src/service/{project_store.spec.ts → environment_store.spec.ts} +99 -83
  58. package/src/service/{project_store.ts → environment_store.ts} +373 -327
  59. package/src/service/manifest_service.spec.ts +15 -15
  60. package/src/service/manifest_service.ts +26 -21
  61. package/src/service/materialization_service.spec.ts +93 -59
  62. package/src/service/materialization_service.ts +71 -62
  63. package/src/service/materialized_table_gc.spec.ts +15 -15
  64. package/src/service/materialized_table_gc.ts +3 -3
  65. package/src/service/model.ts +4 -4
  66. package/src/service/package.spec.ts +2 -2
  67. package/src/service/package.ts +23 -21
  68. package/src/service/resolve_environment.ts +15 -0
  69. package/src/storage/DatabaseInterface.ts +34 -25
  70. package/src/storage/StorageManager.mock.ts +3 -3
  71. package/src/storage/StorageManager.ts +64 -28
  72. package/src/storage/duckdb/ConnectionRepository.ts +13 -11
  73. package/src/storage/duckdb/DuckDBConnection.ts +1 -1
  74. package/src/storage/duckdb/DuckDBManifestStore.ts +6 -6
  75. package/src/storage/duckdb/DuckDBRepository.ts +47 -47
  76. package/src/storage/duckdb/{ProjectRepository.ts → EnvironmentRepository.ts} +35 -35
  77. package/src/storage/duckdb/ManifestRepository.ts +21 -20
  78. package/src/storage/duckdb/MaterializationRepository.ts +31 -28
  79. package/src/storage/duckdb/PackageRepository.ts +11 -11
  80. package/src/storage/duckdb/manifest_store.spec.ts +2 -2
  81. package/src/storage/duckdb/schema.ts +61 -20
  82. package/src/storage/ducklake/DuckLakeManifestStore.ts +20 -11
  83. package/tests/fixtures/publisher.config.json +1 -1
  84. package/tests/harness/e2e.ts +1 -1
  85. package/tests/harness/mcp_test_setup.ts +12 -24
  86. package/tests/harness/mocks.ts +10 -8
  87. package/tests/harness/rest_e2e.ts +2 -2
  88. package/tests/integration/legacy_routes/legacy_routes.integration.spec.ts +259 -0
  89. package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +4 -4
  90. package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +28 -49
  91. package/tests/integration/mcp/mcp_resource.integration.spec.ts +39 -47
  92. package/tests/integration/mcp/mcp_transport.integration.spec.ts +1 -1
  93. package/tests/unit/duckdb/attached_databases.test.ts +51 -33
  94. package/tests/unit/duckdb/legacy_schema_migration.test.ts +194 -0
  95. package/tests/unit/ducklake/ducklake.test.ts +24 -22
  96. package/tests/unit/mcp/prompt_happy.test.ts +8 -8
  97. package/dist/app/assets/HomePage-DbZS0N7G.js +0 -1
  98. package/dist/app/assets/MainPage-CBuWkbmr.js +0 -2
  99. package/dist/app/assets/ModelPage-Bt37smot.js +0 -1
  100. package/dist/app/assets/PackagePage-DLZe50WG.js +0 -1
  101. package/dist/app/assets/ProjectPage-FQTEPXP4.js +0 -1
  102. package/dist/app/assets/WorkbookPage-CkAo16ar.js +0 -1
  103. package/src/mcp/resources/project_resource.ts +0 -184
  104. package/src/service/resolve_project.ts +0 -13
@@ -0,0 +1,194 @@
1
+ /// <reference types="bun-types" />
2
+
3
+ // TODO: Remove this during projects cleanup
4
+
5
+ import { afterEach, beforeEach, describe, expect, it } from "bun:test";
6
+ import fs from "fs/promises";
7
+ import os from "os";
8
+ import path from "path";
9
+ import { DuckDBConnection } from "../../../src/storage/duckdb/DuckDBConnection";
10
+ import { initializeSchema } from "../../../src/storage/duckdb/schema";
11
+
12
+ const TEST_DB_DIR = path.join(os.tmpdir(), "duckdb-legacy-migration-tests");
13
+
14
+ // Seed a pre-rename schema on an *already-open* connection. We deliberately
15
+ // avoid opening, closing, and reopening the same DuckDB file within a test:
16
+ // on Windows runners the second `duckdb.Database(path)` call sometimes fails
17
+ // with `Invalid Error` because the OS hasn't released the file handle yet.
18
+ // Sharing one connection between seed and assertion sidesteps that entirely.
19
+ async function seedLegacySchema(db: DuckDBConnection): Promise<void> {
20
+ // Seed a pre-rename schema: parent table named `projects` and child
21
+ // tables with `project_id` foreign-key columns. Mirrors what an existing
22
+ // installation looked like before the projects→environments rename.
23
+ await db.run(`
24
+ CREATE TABLE projects (
25
+ id VARCHAR PRIMARY KEY,
26
+ name VARCHAR NOT NULL UNIQUE,
27
+ path VARCHAR NOT NULL,
28
+ description VARCHAR,
29
+ metadata JSON,
30
+ created_at TIMESTAMP NOT NULL,
31
+ updated_at TIMESTAMP NOT NULL
32
+ )
33
+ `);
34
+ await db.run(`
35
+ CREATE TABLE packages (
36
+ id VARCHAR PRIMARY KEY,
37
+ project_id VARCHAR NOT NULL,
38
+ name VARCHAR NOT NULL,
39
+ description VARCHAR,
40
+ manifest_path VARCHAR NOT NULL,
41
+ metadata JSON,
42
+ created_at TIMESTAMP NOT NULL,
43
+ updated_at TIMESTAMP NOT NULL,
44
+ FOREIGN KEY (project_id) REFERENCES projects(id)
45
+ )
46
+ `);
47
+ await db.run(`
48
+ CREATE TABLE connections (
49
+ id VARCHAR PRIMARY KEY,
50
+ project_id VARCHAR NOT NULL,
51
+ name VARCHAR NOT NULL,
52
+ type VARCHAR NOT NULL,
53
+ config JSON NOT NULL,
54
+ created_at TIMESTAMP NOT NULL,
55
+ updated_at TIMESTAMP NOT NULL,
56
+ FOREIGN KEY (project_id) REFERENCES projects(id)
57
+ )
58
+ `);
59
+ await db.run(`
60
+ CREATE TABLE materializations (
61
+ id VARCHAR PRIMARY KEY,
62
+ project_id VARCHAR NOT NULL,
63
+ package_name VARCHAR NOT NULL,
64
+ status VARCHAR NOT NULL,
65
+ active_key VARCHAR,
66
+ started_at TIMESTAMP,
67
+ completed_at TIMESTAMP,
68
+ error TEXT,
69
+ metadata JSON,
70
+ created_at TIMESTAMP NOT NULL,
71
+ updated_at TIMESTAMP NOT NULL,
72
+ FOREIGN KEY (project_id) REFERENCES projects(id)
73
+ )
74
+ `);
75
+ await db.run(`
76
+ CREATE TABLE build_manifests (
77
+ id VARCHAR PRIMARY KEY,
78
+ project_id VARCHAR NOT NULL,
79
+ package_name VARCHAR NOT NULL,
80
+ build_id VARCHAR NOT NULL,
81
+ table_name VARCHAR NOT NULL,
82
+ source_name VARCHAR NOT NULL,
83
+ connection_name VARCHAR NOT NULL,
84
+ created_at TIMESTAMP NOT NULL,
85
+ updated_at TIMESTAMP NOT NULL,
86
+ FOREIGN KEY (project_id) REFERENCES projects(id)
87
+ )
88
+ `);
89
+
90
+ await db.run(
91
+ `INSERT INTO projects VALUES ('p1', 'proj-one', '/p1', 'd1', NULL,
92
+ TIMESTAMP '2024-01-01 00:00:00', TIMESTAMP '2024-01-01 00:00:00')`,
93
+ );
94
+ await db.run(
95
+ `INSERT INTO packages VALUES ('pkg1', 'p1', 'pkg-one', NULL, '/m', NULL,
96
+ TIMESTAMP '2024-01-01 00:00:00', TIMESTAMP '2024-01-01 00:00:00')`,
97
+ );
98
+ }
99
+
100
+ describe("DuckDB legacy projects schema cleanup", () => {
101
+ beforeEach(async () => {
102
+ await fs.mkdir(TEST_DB_DIR, { recursive: true });
103
+ });
104
+
105
+ afterEach(async () => {
106
+ try {
107
+ await fs.rm(TEST_DB_DIR, { recursive: true, force: true });
108
+ } catch {
109
+ // ignore
110
+ }
111
+ });
112
+
113
+ it("drops legacy projects schema and creates the new environments schema cleanly", async () => {
114
+ const dbPath = path.join(TEST_DB_DIR, "legacy.duckdb");
115
+ const db = new DuckDBConnection(dbPath);
116
+ await db.initialize();
117
+
118
+ // Seed the legacy schema on the same connection, then run the
119
+ // production schema-init path. This mirrors a server upgrade
120
+ // (legacy data on disk, new code starting up) without forcing a
121
+ // close+reopen, which is unreliable on Windows runners.
122
+ await seedLegacySchema(db);
123
+ await initializeSchema(db);
124
+
125
+ // Legacy parent table is gone.
126
+ const legacyProjects = await db.all<{ name: string }>(
127
+ "SELECT name FROM sqlite_master WHERE type='table' AND name='projects'",
128
+ );
129
+ expect(legacyProjects.length).toBe(0);
130
+
131
+ // New `environments` table exists and is empty (legacy data dropped).
132
+ const envs = await db.all<{ id: string }>("SELECT id FROM environments");
133
+ expect(envs.length).toBe(0);
134
+
135
+ // Child tables are queryable by `environment_id` (the new column),
136
+ // proving they were recreated with the new schema rather than left on
137
+ // the old `project_id` column.
138
+ const pkgs = await db.all<{ id: string }>(
139
+ "SELECT id FROM packages WHERE environment_id = ?",
140
+ ["p1"],
141
+ );
142
+ expect(pkgs.length).toBe(0);
143
+ const conns = await db.all<{ id: string }>(
144
+ "SELECT id FROM connections WHERE environment_id = ?",
145
+ ["p1"],
146
+ );
147
+ expect(conns.length).toBe(0);
148
+ const mats = await db.all<{ id: string }>(
149
+ "SELECT id FROM materializations WHERE environment_id = ?",
150
+ ["p1"],
151
+ );
152
+ expect(mats.length).toBe(0);
153
+ const manifests = await db.all<{ id: string }>(
154
+ "SELECT id FROM build_manifests WHERE environment_id = ?",
155
+ ["p1"],
156
+ );
157
+ expect(manifests.length).toBe(0);
158
+
159
+ await db.close();
160
+ });
161
+
162
+ it("is idempotent: running initializeSchema twice on a migrated DB is a no-op", async () => {
163
+ const dbPath = path.join(TEST_DB_DIR, "legacy_idempotent.duckdb");
164
+ const db = new DuckDBConnection(dbPath);
165
+ await db.initialize();
166
+
167
+ await seedLegacySchema(db);
168
+ await initializeSchema(db);
169
+ // Second call should hit the early-return path (isInitialized() === true).
170
+ await initializeSchema(db);
171
+
172
+ const envs = await db.all<{ id: string }>("SELECT id FROM environments");
173
+ expect(envs.length).toBe(0);
174
+
175
+ await db.close();
176
+ });
177
+
178
+ it("creates a fresh schema unchanged when no legacy projects table is present", async () => {
179
+ const dbPath = path.join(TEST_DB_DIR, "fresh.duckdb");
180
+ const db = new DuckDBConnection(dbPath);
181
+ await db.initialize();
182
+ await initializeSchema(db);
183
+
184
+ const envs = await db.all<{ id: string }>("SELECT id FROM environments");
185
+ expect(envs.length).toBe(0);
186
+
187
+ const legacy = await db.all<{ name: string }>(
188
+ "SELECT name FROM sqlite_master WHERE type='table' AND name='projects'",
189
+ );
190
+ expect(legacy.length).toBe(0);
191
+
192
+ await db.close();
193
+ });
194
+ });
@@ -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
- createProjectConnections,
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 createProjectConnections(
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 createProjectConnections(
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 createProjectConnections(
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 createProjectConnections(
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 createProjectConnections(
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 createProjectConnections(
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 createProjectConnections(
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
- createProjectConnections(
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
- createProjectConnections(
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
- createProjectConnections(
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 } = await createProjectConnections(
657
- [ducklakeConnection],
658
- testProjectPath,
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 } = await createProjectConnections(
666
- [ducklakeConnection],
667
- testProjectPath,
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 createProjectConnections(
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 createProjectConnections(
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 createProjectConnections(
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 projectStore = undefined; // no ProjectStore needed for minimal tests
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
- projectStore as any,
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://project/p/pkg/models/m.malloy",
24
+ target_model_uri: "malloy://environment/e/pkg/models/m.malloy",
25
25
  },
26
- projectStore as any,
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://project/p/pkg/models/m.malloy",
36
+ target_model_uri: "malloy://environment/e/pkg/models/m.malloy",
37
37
  },
38
- projectStore as any,
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://project/p/pkg/models/m.malloy" },
47
- projectStore as any,
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};
@@ -1,2 +0,0 @@
1
- import{u as V,g as F,r as g,R as _,a as X,b as S,c as M,e as A,j as e,s as m,f as w,h as v,k as I,P as Y,m as R,l as J,n as z,B as K,o as N,p as Z,T as U,q,t as oo,d as ro,v as f,C as j,w as eo,x as to,I as ao,M as so,y as $,S as no,z as lo,A as io,O as co,D as po}from"./index-Bu0ub036.js";function uo(o,r,t,a,n){const[s,i]=g.useState(()=>n&&t?t(o).matches:a?a(o).matches:r);return X(()=>{if(!t)return;const p=t(o),u=()=>{i(p.matches)};return u(),p.addEventListener("change",u),()=>{p.removeEventListener("change",u)}},[o,t]),s}const go={..._},L=go.useSyncExternalStore;function xo(o,r,t,a,n){const s=g.useCallback(()=>r,[r]),i=g.useMemo(()=>{if(n&&t)return()=>t(o).matches;if(a!==null){const{matches:c}=a(o);return()=>c}return s},[s,o,a,n,t]),[p,u]=g.useMemo(()=>{if(t===null)return[s,()=>()=>{}];const c=t(o);return[()=>c.matches,l=>(c.addEventListener("change",l),()=>{c.removeEventListener("change",l)})]},[s,t,o]);return L(u,p,i)}function O(o={}){const{themeId:r}=o;return function(a,n={}){let s=V();s&&r&&(s=s[r]||s);const i=typeof window<"u"&&typeof window.matchMedia<"u",{defaultMatches:p=!1,matchMedia:u=i?window.matchMedia:null,ssrMatchMedia:d=null,noSsr:c=!1}=F({name:"MuiUseMediaQuery",props:n,theme:s});let l=typeof a=="function"?a(s):a;return l=l.replace(/^@media( ?)/m,""),l.includes("print")&&console.warn(["MUI: You have provided a `print` query to the `useMediaQuery` hook.","Using the print media query to modify print styles can lead to unexpected results.","Consider using the `displayPrint` field in the `sx` prop instead.","More information about `displayPrint` on our docs: https://mui.com/system/display/#display-in-print."].join(`
2
- `)),(L!==void 0?xo:uo)(l,p,u,d,c)}}O();function mo(o){return S("MuiAppBar",o)}M("MuiAppBar",["root","positionFixed","positionAbsolute","positionSticky","positionStatic","positionRelative","colorDefault","colorPrimary","colorSecondary","colorInherit","colorTransparent","colorError","colorInfo","colorSuccess","colorWarning"]);const fo=o=>{const{color:r,position:t,classes:a}=o,n={root:["root",`color${v(r)}`,`position${v(t)}`]};return I(n,mo,a)},D=(o,r)=>o?`${o?.replace(")","")}, ${r})`:r,bo=m(Y,{name:"MuiAppBar",slot:"Root",overridesResolver:(o,r)=>{const{ownerState:t}=o;return[r.root,r[`position${v(t.position)}`],r[`color${v(t.color)}`]]}})(R(({theme:o})=>({display:"flex",flexDirection:"column",width:"100%",boxSizing:"border-box",flexShrink:0,variants:[{props:{position:"fixed"},style:{position:"fixed",zIndex:(o.vars||o).zIndex.appBar,top:0,left:"auto",right:0,"@media print":{position:"absolute"}}},{props:{position:"absolute"},style:{position:"absolute",zIndex:(o.vars||o).zIndex.appBar,top:0,left:"auto",right:0}},{props:{position:"sticky"},style:{position:"sticky",zIndex:(o.vars||o).zIndex.appBar,top:0,left:"auto",right:0}},{props:{position:"static"},style:{position:"static"}},{props:{position:"relative"},style:{position:"relative"}},{props:{color:"inherit"},style:{"--AppBar-color":"inherit"}},{props:{color:"default"},style:{"--AppBar-background":o.vars?o.vars.palette.AppBar.defaultBg:o.palette.grey[100],"--AppBar-color":o.vars?o.vars.palette.text.primary:o.palette.getContrastText(o.palette.grey[100]),...o.applyStyles("dark",{"--AppBar-background":o.vars?o.vars.palette.AppBar.defaultBg:o.palette.grey[900],"--AppBar-color":o.vars?o.vars.palette.text.primary:o.palette.getContrastText(o.palette.grey[900])})}},...Object.entries(o.palette).filter(J(["contrastText"])).map(([r])=>({props:{color:r},style:{"--AppBar-background":(o.vars??o).palette[r].main,"--AppBar-color":(o.vars??o).palette[r].contrastText}})),{props:r=>r.enableColorOnDark===!0&&!["inherit","transparent"].includes(r.color),style:{backgroundColor:"var(--AppBar-background)",color:"var(--AppBar-color)"}},{props:r=>r.enableColorOnDark===!1&&!["inherit","transparent"].includes(r.color),style:{backgroundColor:"var(--AppBar-background)",color:"var(--AppBar-color)",...o.applyStyles("dark",{backgroundColor:o.vars?D(o.vars.palette.AppBar.darkBg,"var(--AppBar-background)"):null,color:o.vars?D(o.vars.palette.AppBar.darkColor,"var(--AppBar-color)"):null})}},{props:{color:"transparent"},style:{"--AppBar-background":"transparent","--AppBar-color":"inherit",backgroundColor:"var(--AppBar-background)",color:"var(--AppBar-color)",...o.applyStyles("dark",{backgroundImage:"none"})}}]}))),ho=g.forwardRef(function(r,t){const a=A({props:r,name:"MuiAppBar"}),{className:n,color:s="primary",enableColorOnDark:i=!1,position:p="fixed",...u}=a,d={...a,color:s,position:p,enableColorOnDark:i},c=fo(d);return e.jsx(bo,{square:!0,component:"header",ownerState:d,elevation:4,className:w(c.root,n,p==="fixed"&&"mui-fixed"),ref:t,...u})}),yo=z(e.jsx("path",{d:"M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"})),vo=m(K,{name:"MuiBreadcrumbCollapsed"})(R(({theme:o})=>({display:"flex",marginLeft:`calc(${o.spacing(1)} * 0.5)`,marginRight:`calc(${o.spacing(1)} * 0.5)`,...o.palette.mode==="light"?{backgroundColor:o.palette.grey[100],color:o.palette.grey[700]}:{backgroundColor:o.palette.grey[700],color:o.palette.grey[100]},borderRadius:2,"&:hover, &:focus":{...o.palette.mode==="light"?{backgroundColor:o.palette.grey[200]}:{backgroundColor:o.palette.grey[600]}},"&:active":{boxShadow:o.shadows[0],...o.palette.mode==="light"?{backgroundColor:N(o.palette.grey[200],.12)}:{backgroundColor:N(o.palette.grey[600],.12)}}}))),ko=m(yo)({width:24,height:16});function Bo(o){const{slots:r={},slotProps:t={},...a}=o,n=o;return e.jsx("li",{children:e.jsx(vo,{focusRipple:!0,...a,ownerState:n,children:e.jsx(ko,{as:r.CollapsedIcon,ownerState:n,...t.collapsedIcon})})})}function Co(o){return S("MuiBreadcrumbs",o)}const jo=M("MuiBreadcrumbs",["root","ol","li","separator"]),So=o=>{const{classes:r}=o;return I({root:["root"],li:["li"],ol:["ol"],separator:["separator"]},Co,r)},Mo=m(U,{name:"MuiBreadcrumbs",slot:"Root",overridesResolver:(o,r)=>[{[`& .${jo.li}`]:r.li},r.root]})({}),Ao=m("ol",{name:"MuiBreadcrumbs",slot:"Ol"})({display:"flex",flexWrap:"wrap",alignItems:"center",padding:0,margin:0,listStyle:"none"}),wo=m("li",{name:"MuiBreadcrumbs",slot:"Separator"})({display:"flex",userSelect:"none",marginLeft:8,marginRight:8});function Io(o,r,t,a){return o.reduce((n,s,i)=>(i<o.length-1?n=n.concat(s,e.jsx(wo,{"aria-hidden":!0,className:r,ownerState:a,children:t},`separator-${i}`)):n.push(s),n),[])}const Ro=g.forwardRef(function(r,t){const a=A({props:r,name:"MuiBreadcrumbs"}),{children:n,className:s,component:i="nav",slots:p={},slotProps:u={},expandText:d="Show path",itemsAfterCollapse:c=1,itemsBeforeCollapse:l=1,maxItems:h=8,separator:k="/",...Q}=a,[T,W]=g.useState(!1),b={...a,component:i,expanded:T,expandText:d,itemsAfterCollapse:c,itemsBeforeCollapse:l,maxItems:h,separator:k},y=So(b),H=Z({elementType:p.CollapsedIcon,externalSlotProps:u.collapsedIcon,ownerState:b}),P=g.useRef(null),G=x=>{const C=()=>{W(!0);const E=P.current.querySelector("a[href],button,[tabindex]");E&&E.focus()};return l+c>=x.length?x:[...x.slice(0,l),e.jsx(Bo,{"aria-label":d,slots:{CollapsedIcon:p.CollapsedIcon},slotProps:{collapsedIcon:H},onClick:C},"ellipsis"),...x.slice(x.length-c,x.length)]},B=g.Children.toArray(n).filter(x=>g.isValidElement(x)).map((x,C)=>e.jsx("li",{className:y.li,children:x},`child-${C}`));return e.jsx(Mo,{ref:t,component:i,color:"textSecondary",className:w(y.root,s),ownerState:b,...Q,children:e.jsx(Ao,{className:y.ol,ref:P,ownerState:b,children:Io(T||h&&B.length<=h?B:G(B),y.separator,k,b)})})});function zo(o){return S("MuiToolbar",o)}M("MuiToolbar",["root","gutters","regular","dense"]);const To=o=>{const{classes:r,disableGutters:t,variant:a}=o;return I({root:["root",!t&&"gutters",a]},zo,r)},Po=m("div",{name:"MuiToolbar",slot:"Root",overridesResolver:(o,r)=>{const{ownerState:t}=o;return[r.root,!t.disableGutters&&r.gutters,r[t.variant]]}})(R(({theme:o})=>({position:"relative",display:"flex",alignItems:"center",variants:[{props:({ownerState:r})=>!r.disableGutters,style:{paddingLeft:o.spacing(2),paddingRight:o.spacing(2),[o.breakpoints.up("sm")]:{paddingLeft:o.spacing(3),paddingRight:o.spacing(3)}}},{props:{variant:"dense"},style:{minHeight:48}},{props:{variant:"regular"},style:o.mixins.toolbar}]}))),Eo=g.forwardRef(function(r,t){const a=A({props:r,name:"MuiToolbar"}),{className:n,component:s="div",disableGutters:i=!1,variant:p="regular",...u}=a,d={...a,component:s,disableGutters:i,variant:p},c=To(d);return e.jsx(Po,{as:s,className:w(c.root,n),ref:t,ownerState:d,...u})}),No=O({themeId:q}),$o=z(e.jsx("path",{d:"M10 6 8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"})),Do=z(e.jsx("path",{d:"M3 18h18v-2H3zm0-5h18v-2H3zm0-7v2h18V6z"}));function Uo(){const o=oo(),r=o["*"],t=ro();return e.jsx(f,{sx:{display:"flex",alignItems:"center"},children:e.jsxs(Ro,{"aria-label":"breadcrumb",separator:e.jsx($o,{sx:{fontSize:14,color:"text.secondary"}}),sx:{"& .MuiBreadcrumbs-separator":{margin:"0 6px"}},children:[o.projectName&&e.jsx(j,{onClick:a=>t(`/${o.projectName}/`,a),label:o.projectName,size:"medium",sx:{backgroundColor:"background.paper",color:"primary.main",fontWeight:500,height:"32px",fontSize:"1rem",cursor:"pointer","&:hover":{backgroundColor:"primary.100"}}}),o.packageName&&e.jsx(j,{onClick:a=>t(`/${o.projectName}/${o.packageName}/`,a),label:o.packageName,size:"medium",sx:{backgroundColor:"background.paper",color:"primary.main",fontWeight:500,height:"32px",fontSize:"1rem",cursor:"pointer","&:hover":{backgroundColor:"secondary.100"}}}),r&&e.jsx(j,{onClick:a=>t(`/${o.projectName}/${o.packageName}/${r}`,a),label:r,size:"medium",sx:{backgroundColor:"background.paper",color:"primary.main",fontWeight:500,height:"32px",fontSize:"1rem",cursor:"pointer","&:hover":{backgroundColor:"grey.200"}}})]})})}function Lo({logoHeader:o,endCap:r}){const t=eo(),a=to(),n=No(a.breakpoints.down("sm")),[s,i]=g.useState(null),p=!!s,u=l=>{i(l.currentTarget)},d=()=>i(null),c=[{label:"Malloy Docs",link:"https://docs.malloydata.dev/documentation/",sx:{color:"primary.main"}},{label:"Publisher Docs",link:"https://github.com/malloydata/publisher/blob/main/README.md",sx:{color:"primary.main"}},{label:"Publisher API",link:"/api-doc.html",sx:{color:"primary.main"}}];return e.jsxs(ho,{position:"sticky",elevation:0,sx:{backgroundColor:"background.paper",borderBottom:"1px solid",borderColor:"divider"},children:[e.jsxs(Eo,{sx:{justifyContent:"space-between",flexWrap:"nowrap",minHeight:44},children:[o||e.jsxs(f,{sx:{display:"flex",alignItems:"center",gap:1,cursor:"pointer"},onClick:()=>t("/"),children:[e.jsx(f,{component:"img",src:"/logo.svg",alt:"Malloy",sx:{width:28,height:28}}),e.jsx(U,{variant:"h6",sx:{color:"text.primary",fontWeight:700,letterSpacing:"-0.025em",fontSize:{xs:"1.1rem",sm:"1.25rem"}},children:"Malloy Publisher"})]}),n?e.jsxs(e.Fragment,{children:[e.jsx(ao,{color:"inherit",onClick:u,children:e.jsx(Do,{})}),e.jsxs(so,{anchorEl:s,open:p,onClose:d,anchorOrigin:{vertical:"bottom",horizontal:"right"},children:[c.map(l=>e.jsx($,{onClick:()=>{d(),window.location.href=l.link},sx:l.sx,children:l.label},l.label)),r&&e.jsx($,{children:r})]})]}):e.jsxs(no,{direction:"row",spacing:2,alignItems:"center",children:[c.map(l=>e.jsx(lo,{href:l.link,sx:l.sx,children:l.label},l.label)),r]})]}),e.jsx(f,{sx:{borderTop:"1px solid white",paddingLeft:"16px",paddingRight:"16px",marginBottom:"1px",overflowX:"auto"},children:e.jsx(Uo,{})})]})}function Qo({headerProps:o}){return e.jsxs(f,{sx:{display:"flex",flexDirection:"column",minHeight:"100vh"},children:[e.jsx(Lo,{...o}),e.jsx(io,{maxWidth:"xl",component:"main",sx:{flex:1,display:"flex",flexDirection:"column",py:2,gap:2},children:e.jsx(f,{sx:{flex:1},children:e.jsx(g.Suspense,{fallback:e.jsx(po,{}),children:e.jsx(co,{})})})})]})}export{Qo as default};
@@ -1 +0,0 @@
1
- import{t as n,j as e,E as i,F as t,G as c}from"./index-Bu0ub036.js";function o(){const a=n(),r=a["*"];if(!a.projectName)return e.jsx("div",{children:e.jsx("h2",{children:"Missing project name"})});if(!a.packageName)return e.jsx("div",{children:e.jsx("h2",{children:"Missing package name"})});const s=i({projectName:a.projectName,packageName:a.packageName,modelPath:r});return r?.endsWith(".malloy")?e.jsx(t,{resourceUri:s,runOnDemand:!0,maxResultSize:512*1024}):r?.endsWith(".malloynb")?e.jsx(c,{resourceUri:s,maxResultSize:1024*1024}):e.jsx("div",{children:e.jsxs("h2",{children:["Unrecognized file type: ",r]})})}export{o as default};
@@ -1 +0,0 @@
1
- import{t as n,d as c,j as e,E as t,H as o}from"./index-Bu0ub036.js";function l(){const{projectName:s,packageName:a}=n(),r=c();if(s)if(a){const i=t({projectName:s,packageName:a});return e.jsx(o,{onClickPackageFile:r,resourceUri:i})}else return e.jsx("div",{children:e.jsx("h2",{children:"Missing package name"})});else return e.jsx("div",{children:e.jsx("h2",{children:"Missing project name"})})}export{l as default};
@@ -1 +0,0 @@
1
- import{d as a,t as n,j as e,E as o,J as c}from"./index-Bu0ub036.js";function u(){const r=a(),{projectName:s}=n();if(s){const t=o({projectName:s});return e.jsx(c,{onSelectPackage:r,resourceUri:t})}else return e.jsx("div",{children:e.jsx("h2",{children:"Missing project name"})})}export{u as default};
@@ -1 +0,0 @@
1
- import{t as a,j as e,E as t,Z as c}from"./index-Bu0ub036.js";function d(){const{workspace:r,workbookPath:s,projectName:i,packageName:n}=a();if(r)if(s)if(i)if(n){const o=t({projectName:i,packageName:n});return e.jsx(c,{workbookPath:{path:s,workspace:r},resourceUri:o},`${s}`)}else return e.jsx("div",{children:e.jsx("h2",{children:"Missing package name"})});else return e.jsx("div",{children:e.jsx("h2",{children:"Missing project name"})});else return e.jsx("div",{children:e.jsx("h2",{children:"Missing workbook path"})});else return e.jsx("div",{children:e.jsx("h2",{children:"Missing workspace"})})}export{d as default};
@@ -1,184 +0,0 @@
1
- import {
2
- McpServer,
3
- ResourceTemplate,
4
- } from "@modelcontextprotocol/sdk/server/mcp.js";
5
- import type { ListResourcesResult } from "@modelcontextprotocol/sdk/types.js"; // Needed for list return type
6
- import { logger } from "../../logger";
7
- import { ProjectStore } from "../../service/project_store";
8
- import { getInternalError, getNotFoundError } from "../error_messages"; // Needed for error handling in list AND get
9
- import { handleResourceGet, McpGetResourceError } from "../handler_utils";
10
- import { RESOURCE_METADATA } from "../resource_metadata";
11
-
12
- // Define an interface for the package object augmented with project name
13
- interface PackageWithProject {
14
- name?: string;
15
- // Add other relevant package properties if needed
16
- projectName: string;
17
- }
18
-
19
- /**
20
- * Registers the Malloy Project resource type with the MCP server.
21
- * Handles getting details for the hardcoded 'home' project and listing packages within it.
22
- */
23
- export function registerProjectResource(
24
- mcpServer: McpServer,
25
- projectStore: ProjectStore,
26
- ): void {
27
- mcpServer.resource(
28
- "project",
29
- new ResourceTemplate("malloy://project/{projectName}", {
30
- /**
31
- * Handles ListResources requests.
32
- * If projectName is specified, lists packages for that project (only 'home' supported).
33
- * If projectName is not specified (general ListResources call), lists packages for the default 'home' project.
34
- */
35
- list: async (/* extra: ListProjectExtra - Deleted */): Promise<ListResourcesResult> => {
36
- logger.info(
37
- "[MCP LOG] Entering ListResources (project) handler (listing ALL packages)...",
38
- );
39
- // Ignore parameters from 'extra' as URI path params aren't passed to list handlers.
40
-
41
- try {
42
- const allProjects = await projectStore.listProjects();
43
- logger.info(
44
- `[MCP LOG] Found ${allProjects.length} projects defined.`,
45
- );
46
-
47
- const packagePromises = allProjects.map(async (proj) => {
48
- try {
49
- logger.info(
50
- `[MCP LOG] Getting project '${proj.name}' to list its packages...`,
51
- );
52
- const projectInstance = await projectStore.getProject(
53
- proj.name!,
54
- false,
55
- ); // Use proj.name
56
- const packages = await projectInstance.listPackages();
57
- logger.info(
58
- `[MCP LOG] Found ${packages.length} packages in project '${proj.name}'.`,
59
- );
60
- // Return packages along with their project name for URI construction
61
- return packages.map((pkg) => ({
62
- ...pkg,
63
- projectName: proj.name,
64
- }));
65
- } catch (projectError) {
66
- logger.error(
67
- `[MCP Server Error] Error getting/listing packages for project ${proj.name}:`,
68
- { error: projectError },
69
- );
70
- return []; // Return empty array for this project on error
71
- }
72
- });
73
-
74
- const results = await Promise.allSettled(packagePromises);
75
- const allPackagesWithProjectName = results
76
- .filter((result) => result.status === "fulfilled")
77
- // Use the specific interface instead of any[]
78
- .flatMap(
79
- (result) =>
80
- (result as PromiseFulfilledResult<PackageWithProject[]>)
81
- .value,
82
- );
83
-
84
- logger.info(
85
- `[MCP LOG] Total packages found across all projects: ${allPackagesWithProjectName.length}`,
86
- );
87
-
88
- const packageMetadata = RESOURCE_METADATA.package;
89
- const mappedResources = allPackagesWithProjectName.map((pkg) => {
90
- const name = pkg.name || "unknown";
91
- // Construct URI using the package's specific projectName
92
- const uri = `malloy://project/${pkg.projectName}/package/${name}`;
93
- return {
94
- uri: uri,
95
- name: name,
96
- type: "package",
97
- description: packageMetadata?.description as
98
- | string
99
- | undefined,
100
- metadata: packageMetadata,
101
- };
102
- });
103
-
104
- logger.info(
105
- `[MCP LOG] ListResources (project): Returning ${mappedResources.length} package resources.`,
106
- );
107
- return {
108
- resources: mappedResources,
109
- };
110
- } catch (error) {
111
- // Catch errors from projectStore.listProjects() itself
112
- logger.error(`[MCP Server Error] Error listing projects:`, {
113
- error,
114
- });
115
- const errorDetails = getInternalError(
116
- `ListResources (project - initial list)`,
117
- error,
118
- );
119
- logger.error("MCP ListResources (project) error:", {
120
- error: errorDetails.message,
121
- });
122
- logger.info(
123
- "[MCP LOG] ListResources (project): Returning empty on error listing projects.",
124
- );
125
- return { resources: [] };
126
- }
127
- },
128
- }),
129
- /** Handles GetResource requests for Malloy Projects */
130
- (uri, params) =>
131
- handleResourceGet(
132
- uri,
133
- params,
134
- "project",
135
- async ({ projectName }: { projectName?: unknown }) => {
136
- logger.info(
137
- `[MCP LOG] Entering GetResource (project) handler for projectName: ${projectName}`,
138
- );
139
- // Validate project name parameter
140
- if (typeof projectName !== "string") {
141
- logger.error(
142
- "[MCP LOG] GetResource (project): Invalid project name param.",
143
- );
144
- throw new Error("Invalid project name parameter.");
145
- }
146
-
147
- try {
148
- logger.info(
149
- `[MCP LOG] GetResource: Getting project '${projectName}'...`,
150
- );
151
- // Get the project instance, but we might not need its metadata directly
152
- await projectStore.getProject(projectName, false);
153
- // Construct the definition object expected by the test
154
- const definition = { name: projectName };
155
- logger.info(
156
- `[MCP LOG] GetResource (project): Returning definition for '${projectName}'.`,
157
- );
158
- // Return the explicit definition structure
159
- return definition;
160
- } catch (error) {
161
- logger.error(
162
- `[MCP LOG] GetResource (project): Error caught for '${projectName}':`,
163
- { error },
164
- );
165
- // Catch expected errors from this specific resource logic
166
- if (error instanceof Error) {
167
- // Use getNotFoundError for the specific project not found case
168
- // or a generic message for the invalid param case.
169
- const errorDetails = getNotFoundError(
170
- error.message.includes("not found")
171
- ? `Project '${projectName}'` // More specific context
172
- : `Invalid project identifier provided for URI ${uri.href}`, // Generic but informative
173
- );
174
- // Re-throw structured error for handleResourceGet to catch
175
- throw new McpGetResourceError(errorDetails);
176
- }
177
- // Re-throw unexpected errors to be caught by handleResourceGet's generic handler
178
- throw error;
179
- }
180
- },
181
- RESOURCE_METADATA.project,
182
- ),
183
- );
184
- }
@@ -1,13 +0,0 @@
1
- import { ProjectNotFoundError } from "../errors";
2
- import { ResourceRepository } from "../storage/DatabaseInterface";
3
-
4
- export async function resolveProjectId(
5
- repository: ResourceRepository,
6
- projectName: string,
7
- ): Promise<string> {
8
- const dbProject = await repository.getProjectByName(projectName);
9
- if (!dbProject) {
10
- throw new ProjectNotFoundError(`Project '${projectName}' not found`);
11
- }
12
- return dbProject.id;
13
- }