@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.
Files changed (99) 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 +1328 -1304
  16. package/package.json +1 -1
  17. package/publisher.config.json +2 -2
  18. package/src/config.spec.ts +74 -66
  19. package/src/config.ts +50 -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.ts +175 -159
  48. package/src/service/connection.spec.ts +158 -133
  49. package/src/service/connection.ts +42 -39
  50. package/src/service/connection_config.spec.ts +13 -11
  51. package/src/service/connection_config.ts +28 -19
  52. package/src/service/connection_service.spec.ts +63 -43
  53. package/src/service/connection_service.ts +106 -89
  54. package/src/service/{project.ts → environment.ts} +92 -77
  55. package/src/service/{project_compile.spec.ts → environment_compile.spec.ts} +1 -1
  56. package/src/service/{project_store.spec.ts → environment_store.spec.ts} +99 -85
  57. package/src/service/{project_store.ts → environment_store.ts} +368 -326
  58. package/src/service/manifest_service.spec.ts +15 -15
  59. package/src/service/manifest_service.ts +26 -21
  60. package/src/service/materialization_service.spec.ts +93 -59
  61. package/src/service/materialization_service.ts +71 -62
  62. package/src/service/materialized_table_gc.spec.ts +15 -15
  63. package/src/service/materialized_table_gc.ts +3 -3
  64. package/src/service/model.ts +2 -2
  65. package/src/service/package.spec.ts +2 -2
  66. package/src/service/package.ts +23 -21
  67. package/src/service/resolve_environment.ts +15 -0
  68. package/src/storage/DatabaseInterface.ts +34 -25
  69. package/src/storage/StorageManager.mock.ts +3 -3
  70. package/src/storage/StorageManager.ts +24 -23
  71. package/src/storage/duckdb/ConnectionRepository.ts +13 -11
  72. package/src/storage/duckdb/DuckDBConnection.ts +1 -1
  73. package/src/storage/duckdb/DuckDBManifestStore.ts +6 -6
  74. package/src/storage/duckdb/DuckDBRepository.ts +47 -47
  75. package/src/storage/duckdb/{ProjectRepository.ts → EnvironmentRepository.ts} +35 -35
  76. package/src/storage/duckdb/ManifestRepository.ts +21 -20
  77. package/src/storage/duckdb/MaterializationRepository.ts +31 -28
  78. package/src/storage/duckdb/PackageRepository.ts +11 -11
  79. package/src/storage/duckdb/manifest_store.spec.ts +2 -2
  80. package/src/storage/duckdb/schema.ts +20 -20
  81. package/src/storage/ducklake/DuckLakeManifestStore.ts +14 -14
  82. package/tests/fixtures/publisher.config.json +1 -1
  83. package/tests/harness/e2e.ts +1 -1
  84. package/tests/harness/mcp_test_setup.ts +1 -1
  85. package/tests/harness/mocks.ts +10 -8
  86. package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +4 -4
  87. package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +27 -48
  88. package/tests/integration/mcp/mcp_resource.integration.spec.ts +26 -35
  89. package/tests/unit/duckdb/attached_databases.test.ts +51 -33
  90. package/tests/unit/ducklake/ducklake.test.ts +24 -22
  91. package/tests/unit/mcp/prompt_happy.test.ts +8 -8
  92. package/dist/app/assets/HomePage-DbZS0N7G.js +0 -1
  93. package/dist/app/assets/MainPage-CBuWkbmr.js +0 -2
  94. package/dist/app/assets/ModelPage-Bt37smot.js +0 -1
  95. package/dist/app/assets/PackagePage-DLZe50WG.js +0 -1
  96. package/dist/app/assets/ProjectPage-FQTEPXP4.js +0 -1
  97. package/dist/app/assets/WorkbookPage-CkAo16ar.js +0 -1
  98. package/src/mcp/resources/project_resource.ts +0 -184
  99. package/src/service/resolve_project.ts +0 -13
@@ -1,18 +1,18 @@
1
- import type { BigQueryConnection } from "@malloydata/db-bigquery";
2
1
  import "@malloydata/db-bigquery";
2
+ import type { BigQueryConnection } from "@malloydata/db-bigquery";
3
+ import "@malloydata/db-databricks";
3
4
  import { DuckDBConnection } from "@malloydata/db-duckdb";
4
5
  import "@malloydata/db-duckdb/native";
5
- import type { MySQLConnection } from "@malloydata/db-mysql";
6
6
  import "@malloydata/db-mysql";
7
- import type { PostgresConnection } from "@malloydata/db-postgres";
7
+ import type { MySQLConnection } from "@malloydata/db-mysql";
8
8
  import "@malloydata/db-postgres";
9
+ import type { PostgresConnection } from "@malloydata/db-postgres";
9
10
  import {
10
11
  buildPoolOptions,
11
12
  SnowflakeConnection,
12
13
  } from "@malloydata/db-snowflake";
13
- import type { TrinoConnection } from "@malloydata/db-trino";
14
14
  import "@malloydata/db-trino";
15
- import "@malloydata/db-databricks";
15
+ import type { TrinoConnection } from "@malloydata/db-trino";
16
16
  import {
17
17
  Connection,
18
18
  contextOverlay,
@@ -26,10 +26,10 @@ import path from "path";
26
26
  import { components } from "../api";
27
27
  import { logAxiosError, logger } from "../logger";
28
28
  import {
29
- assembleProjectConnections,
29
+ assembleEnvironmentConnections,
30
30
  CoreConnectionEntry,
31
+ EnvironmentConnectionMetadata,
31
32
  normalizeSnowflakePrivateKey,
32
- ProjectConnectionMetadata,
33
33
  } from "./connection_config";
34
34
  import { CloudStorageCredentials } from "./gcs_s3_utils";
35
35
 
@@ -807,17 +807,17 @@ class DuckLakeConnection extends DuckDBConnection {
807
807
 
808
808
  export async function deleteDuckLakeConnectionFile(
809
809
  connectionName: string,
810
- projectPath: string,
810
+ environmentPath: string,
811
811
  ): Promise<void> {
812
812
  const ducklakePath = path.join(
813
- projectPath,
813
+ environmentPath,
814
814
  `${connectionName}_ducklake.duckdb`,
815
815
  );
816
816
  try {
817
817
  await fs.access(ducklakePath);
818
818
  await fs.rm(ducklakePath);
819
819
  logger.info(
820
- `Removed DuckLake connection file ${connectionName}_ducklake.duckdb from ${projectPath}`,
820
+ `Removed DuckLake connection file ${connectionName}_ducklake.duckdb from ${environmentPath}`,
821
821
  );
822
822
  } catch (error) {
823
823
  if ((error as NodeJS.ErrnoException).code === "ENOENT") {
@@ -826,14 +826,14 @@ export async function deleteDuckLakeConnectionFile(
826
826
  );
827
827
  } else {
828
828
  logger.error(
829
- `Failed to remove DuckLake connection file ${connectionName}_ducklake.duckdb from ${projectPath}`,
829
+ `Failed to remove DuckLake connection file ${connectionName}_ducklake.duckdb from ${environmentPath}`,
830
830
  { error },
831
831
  );
832
832
  }
833
833
  }
834
834
  }
835
835
 
836
- export type ProjectMalloyConfig = {
836
+ export type EnvironmentMalloyConfig = {
837
837
  malloyConfig: MalloyConfig;
838
838
  apiConnections: InternalConnection[];
839
839
  // Releases both core-managed connections and Publisher wrapper-managed
@@ -863,7 +863,7 @@ function removeUndefined<T extends object>(value: T): Partial<T> {
863
863
  }
864
864
 
865
865
  function buildSnowflakePrivateKeyConnection(
866
- metadata: ProjectConnectionMetadata,
866
+ metadata: EnvironmentConnectionMetadata,
867
867
  ): SnowflakeConnection {
868
868
  const name = metadata.apiConnection.name!;
869
869
  const snowflake = metadata.apiConnection.snowflakeConnection;
@@ -905,7 +905,7 @@ function buildSnowflakePrivateKeyConnection(
905
905
  }
906
906
 
907
907
  function buildDuckLakeConnection(
908
- metadata: ProjectConnectionMetadata,
908
+ metadata: EnvironmentConnectionMetadata,
909
909
  entry: CoreConnectionEntry,
910
910
  ): DuckLakeConnection {
911
911
  return new DuckLakeConnection(
@@ -918,7 +918,7 @@ function buildDuckLakeConnection(
918
918
  }
919
919
 
920
920
  function buildAzureDuckDBConnection(
921
- metadata: ProjectConnectionMetadata,
921
+ metadata: EnvironmentConnectionMetadata,
922
922
  entry: CoreConnectionEntry,
923
923
  ): AzureDuckDBConnection {
924
924
  return new AzureDuckDBConnection(
@@ -932,9 +932,9 @@ function buildAzureDuckDBConnection(
932
932
  }
933
933
 
934
934
  function getMetadataForLookup(
935
- metadata: Map<string, ProjectConnectionMetadata>,
935
+ metadata: Map<string, EnvironmentConnectionMetadata>,
936
936
  name?: string,
937
- ): ProjectConnectionMetadata | undefined {
937
+ ): EnvironmentConnectionMetadata | undefined {
938
938
  return name ? metadata.get(name) : undefined;
939
939
  }
940
940
 
@@ -951,15 +951,18 @@ function isDuckDBConnection(
951
951
  * to a connection-config change, so the new generation rebinds against the
952
952
  * updated DuckLake settings (catalog DSN, storage secrets) rather than
953
953
  * trusting whatever attach state the prior generation left behind. On a
954
- * fresh project the existing-attach check fails anyway, so the flag is a
954
+ * fresh environment the existing-attach check fails anyway, so the flag is a
955
955
  * no-op there. Only the DuckLake branch consults it today.
956
956
  */
957
- export function buildProjectMalloyConfig(
957
+ export function buildEnvironmentMalloyConfig(
958
958
  connections: ApiConnection[] = [],
959
- projectPath: string = "",
959
+ environmentPath: string = "",
960
960
  isUpdateConnectionRequest: boolean = false,
961
- ): ProjectMalloyConfig {
962
- const assembled = assembleProjectConnections(connections, projectPath);
961
+ ): EnvironmentMalloyConfig {
962
+ const assembled = assembleEnvironmentConnections(
963
+ connections,
964
+ environmentPath,
965
+ );
963
966
  // Cache the build Promise rather than the resolved Connection so two
964
967
  // concurrent lookupConnection() calls for the same name share one build
965
968
  // and we never leak a losing-instance pool/handle. Per-branch caches
@@ -970,12 +973,12 @@ export function buildProjectMalloyConfig(
970
973
  const attachPromises = new WeakMap<Connection, Promise<void>>();
971
974
 
972
975
  const malloyConfig = new MalloyConfig(assembled.pojo, {
973
- config: contextOverlay({ rootDirectory: projectPath }),
976
+ config: contextOverlay({ rootDirectory: environmentPath }),
974
977
  });
975
978
 
976
979
  async function attachOnce(
977
980
  connection: Connection,
978
- metadata: ProjectConnectionMetadata,
981
+ metadata: EnvironmentConnectionMetadata,
979
982
  ): Promise<void> {
980
983
  if (
981
984
  metadata.attachedDatabases.length === 0 ||
@@ -1086,23 +1089,23 @@ export function buildProjectMalloyConfig(
1086
1089
  // already let every branch run, and a single AggregateError otherwise
1087
1090
  // hides per-branch detail from callers that just log the error.
1088
1091
  for (const failure of failures) {
1089
- logger.error("Failed to release project connection", {
1092
+ logger.error("Failed to release environment connection", {
1090
1093
  error: failure.reason,
1091
1094
  });
1092
1095
  }
1093
1096
  if (failures.length > 0) {
1094
1097
  throw new AggregateError(
1095
1098
  failures.map((failure) => failure.reason),
1096
- "Failed to release one or more project connections",
1099
+ "Failed to release one or more environment connections",
1097
1100
  );
1098
1101
  }
1099
1102
  },
1100
1103
  };
1101
1104
  }
1102
1105
 
1103
- export async function createProjectConnections(
1106
+ export async function createEnvironmentConnections(
1104
1107
  connections: ApiConnection[] = [],
1105
- projectPath: string = "",
1108
+ environmentPath: string = "",
1106
1109
  isUpdateConnectionRequest: boolean = false,
1107
1110
  ): Promise<{
1108
1111
  malloyConnections: Map<string, Connection>;
@@ -1110,17 +1113,17 @@ export async function createProjectConnections(
1110
1113
  releaseConnections: () => Promise<void>;
1111
1114
  }> {
1112
1115
  const connectionMap = new Map<string, Connection>();
1113
- const projectConfig = buildProjectMalloyConfig(
1116
+ const environmentConfig = buildEnvironmentMalloyConfig(
1114
1117
  connections,
1115
- projectPath,
1118
+ environmentPath,
1116
1119
  isUpdateConnectionRequest,
1117
1120
  );
1118
1121
 
1119
- for (const connection of projectConfig.apiConnections) {
1122
+ for (const connection of environmentConfig.apiConnections) {
1120
1123
  if (!connection.name) continue;
1121
1124
  logger.info(`Adding connection ${connection.name}`, { connection });
1122
1125
  const malloyConnection =
1123
- await projectConfig.malloyConfig.connections.lookupConnection(
1126
+ await environmentConfig.malloyConfig.connections.lookupConnection(
1124
1127
  connection.name,
1125
1128
  );
1126
1129
  connection.attributes = getConnectionAttributes(malloyConnection);
@@ -1129,8 +1132,8 @@ export async function createProjectConnections(
1129
1132
 
1130
1133
  return {
1131
1134
  malloyConnections: connectionMap,
1132
- apiConnections: projectConfig.apiConnections,
1133
- releaseConnections: projectConfig.releaseConnections,
1135
+ apiConnections: environmentConfig.apiConnections,
1136
+ releaseConnections: environmentConfig.releaseConnections,
1134
1137
  };
1135
1138
  }
1136
1139
 
@@ -1280,16 +1283,16 @@ async function testDuckDBConnection(
1280
1283
  export async function testConnectionConfig(
1281
1284
  connectionConfig: ApiConnection,
1282
1285
  ): Promise<ApiConnectionStatus> {
1283
- let projectConfig: ProjectMalloyConfig | null = null;
1286
+ let environmentConfig: EnvironmentMalloyConfig | null = null;
1284
1287
  try {
1285
1288
  // Validate that connection name is provided
1286
1289
  if (!connectionConfig.name) {
1287
1290
  throw new Error("Connection name is required");
1288
1291
  }
1289
1292
 
1290
- projectConfig = buildProjectMalloyConfig([connectionConfig]);
1293
+ environmentConfig = buildEnvironmentMalloyConfig([connectionConfig]);
1291
1294
  const connection =
1292
- await projectConfig.malloyConfig.connections.lookupConnection(
1295
+ await environmentConfig.malloyConfig.connections.lookupConnection(
1293
1296
  connectionConfig.name,
1294
1297
  );
1295
1298
 
@@ -1346,9 +1349,9 @@ export async function testConnectionConfig(
1346
1349
  errorMessage: (error as Error).message,
1347
1350
  };
1348
1351
  } finally {
1349
- if (projectConfig) {
1352
+ if (environmentConfig) {
1350
1353
  try {
1351
- await projectConfig.releaseConnections();
1354
+ await environmentConfig.releaseConnections();
1352
1355
  } catch (closeError) {
1353
1356
  logger.warn("Error releasing temporary connection test config", {
1354
1357
  error: closeError,
@@ -1,14 +1,14 @@
1
- import { generateKeyPairSync } from "crypto";
2
1
  import { describe, expect, it } from "bun:test";
2
+ import { generateKeyPairSync } from "crypto";
3
+ import { components } from "../api";
3
4
  import {
4
- assembleProjectConnections,
5
+ assembleEnvironmentConnections,
5
6
  normalizeSnowflakePrivateKey,
6
7
  } from "./connection_config";
7
- import { components } from "../api";
8
8
 
9
9
  type ApiConnection = components["schemas"]["Connection"];
10
10
 
11
- describe("assembleProjectConnections — databricks", () => {
11
+ describe("assembleEnvironmentConnections — databricks", () => {
12
12
  const validBase: ApiConnection = {
13
13
  name: "dbx",
14
14
  type: "databricks",
@@ -22,7 +22,9 @@ describe("assembleProjectConnections — databricks", () => {
22
22
  };
23
23
 
24
24
  it("emits a databricks core entry with all known fields preserved", () => {
25
- const { pojo, apiConnections } = assembleProjectConnections([validBase]);
25
+ const { pojo, apiConnections } = assembleEnvironmentConnections([
26
+ validBase,
27
+ ]);
26
28
 
27
29
  const entry = pojo.connections["dbx"];
28
30
  expect(entry.is).toBe("databricks");
@@ -48,7 +50,7 @@ describe("assembleProjectConnections — databricks", () => {
48
50
  defaultCatalog: "main",
49
51
  },
50
52
  };
51
- const { pojo } = assembleProjectConnections([conn]);
53
+ const { pojo } = assembleEnvironmentConnections([conn]);
52
54
  const entry = pojo.connections["dbx-oauth"];
53
55
  expect(entry.is).toBe("databricks");
54
56
  expect(entry.oauthClientId).toBe("client-id");
@@ -61,7 +63,7 @@ describe("assembleProjectConnections — databricks", () => {
61
63
  name: "dbx",
62
64
  type: "databricks",
63
65
  };
64
- expect(() => assembleProjectConnections([conn])).toThrow(
66
+ expect(() => assembleEnvironmentConnections([conn])).toThrow(
65
67
  "Databricks connection configuration is missing.",
66
68
  );
67
69
  });
@@ -74,7 +76,7 @@ describe("assembleProjectConnections — databricks", () => {
74
76
  host: undefined,
75
77
  },
76
78
  };
77
- expect(() => assembleProjectConnections([conn])).toThrow(
79
+ expect(() => assembleEnvironmentConnections([conn])).toThrow(
78
80
  "Databricks host is required",
79
81
  );
80
82
  });
@@ -87,7 +89,7 @@ describe("assembleProjectConnections — databricks", () => {
87
89
  path: undefined,
88
90
  },
89
91
  };
90
- expect(() => assembleProjectConnections([conn])).toThrow(
92
+ expect(() => assembleEnvironmentConnections([conn])).toThrow(
91
93
  "Databricks SQL warehouse HTTP path is required",
92
94
  );
93
95
  });
@@ -103,7 +105,7 @@ describe("assembleProjectConnections — databricks", () => {
103
105
  // defaultCatalog deliberately omitted
104
106
  },
105
107
  };
106
- expect(() => assembleProjectConnections([conn])).toThrow(
108
+ expect(() => assembleEnvironmentConnections([conn])).toThrow(
107
109
  "Databricks default catalog is required",
108
110
  );
109
111
  });
@@ -120,7 +122,7 @@ describe("assembleProjectConnections — databricks", () => {
120
122
  defaultCatalog: "main",
121
123
  },
122
124
  };
123
- expect(() => assembleProjectConnections([conn])).toThrow(
125
+ expect(() => assembleEnvironmentConnections([conn])).toThrow(
124
126
  "Databricks requires",
125
127
  );
126
128
  });
@@ -14,7 +14,7 @@ export type CoreConnectionsPojo = {
14
14
  connections: Record<string, CoreConnectionEntry>;
15
15
  };
16
16
 
17
- export type ProjectConnectionMetadata = {
17
+ export type EnvironmentConnectionMetadata = {
18
18
  apiConnection: ApiConnection;
19
19
  attachedDatabases: AttachedDatabase[];
20
20
  hasAzureAttachment: boolean;
@@ -24,9 +24,9 @@ export type ProjectConnectionMetadata = {
24
24
  workingDirectory: string;
25
25
  };
26
26
 
27
- export type AssembledProjectConnections = {
27
+ export type AssembledEnvironmentConnections = {
28
28
  pojo: CoreConnectionsPojo;
29
- metadata: Map<string, ProjectConnectionMetadata>;
29
+ metadata: Map<string, EnvironmentConnectionMetadata>;
30
30
  apiConnections: ApiConnection[];
31
31
  };
32
32
 
@@ -105,10 +105,10 @@ export function normalizeSnowflakePrivateKey(privateKey: string): string {
105
105
  return privateKeyContent;
106
106
  }
107
107
 
108
- // NOTE: This narrows the project-author API surface (it rejects securityPolicy,
108
+ // NOTE: This narrows the environment-author API surface (it rejects securityPolicy,
109
109
  // allowedDirectories, setupSQL, etc.). It is NOT a filesystem isolation
110
110
  // boundary: attachedDatabases[].path is not normalized or constrained to stay
111
- // under the project root, and DuckDB's local-file access is unchanged.
111
+ // under the environment root, and DuckDB's local-file access is unchanged.
112
112
  // Adversarial filesystem isolation is explicit non-goal of the MalloyConfig
113
113
  // adoption — see PR #682 release notes ("DuckDB hardening knobs are not
114
114
  // exposed", "no adversarial DuckDB filesystem isolation"). Future work owns
@@ -127,7 +127,7 @@ export function validateDuckdbApiSurface(connection: ApiConnection): void {
127
127
  throw new Error(
128
128
  `Unsupported DuckDB connection field(s): ${unsupportedFields.join(
129
129
  ", ",
130
- )}. Publisher only supports attachedDatabases for project-authored DuckDB connections.`,
130
+ )}. Publisher only supports attachedDatabases for environment-authored DuckDB connections.`,
131
131
  );
132
132
  }
133
133
  }
@@ -198,7 +198,7 @@ function getStaticConnectionAttributes(
198
198
 
199
199
  type ServiceAccountKey = {
200
200
  type?: string;
201
- project_id?: string;
201
+ environment_id?: string;
202
202
  private_key?: string;
203
203
  client_email?: string;
204
204
  [key: string]: unknown;
@@ -248,12 +248,12 @@ function buildPostgresConnectionString(
248
248
 
249
249
  function buildDuckdbEntry(
250
250
  name: string,
251
- projectPath: string,
251
+ environmentPath: string,
252
252
  databaseFilename = `${name}.duckdb`,
253
253
  ): CoreConnectionEntry {
254
254
  return {
255
255
  is: "duckdb",
256
- databasePath: path.join(projectPath, databaseFilename),
256
+ databasePath: path.join(environmentPath, databaseFilename),
257
257
  };
258
258
  }
259
259
 
@@ -267,6 +267,15 @@ function validateConnectionShape(connection: ApiConnection): void {
267
267
  if (!connection.duckdbConnection) {
268
268
  throw new Error("DuckDB connection configuration is missing.");
269
269
  }
270
+ {
271
+ const attached =
272
+ connection.duckdbConnection.attachedDatabases ?? [];
273
+ if (attached.length === 0) {
274
+ throw new Error(
275
+ "DuckDB connection must have at least one attached database",
276
+ );
277
+ }
278
+ }
270
279
  break;
271
280
  case "motherduck":
272
281
  if (!connection.motherduckConnection) {
@@ -330,12 +339,12 @@ function validateConnectionShape(connection: ApiConnection): void {
330
339
  }
331
340
  }
332
341
 
333
- export function assembleProjectConnections(
342
+ export function assembleEnvironmentConnections(
334
343
  connections: ApiConnection[] = [],
335
- projectPath = "",
336
- ): AssembledProjectConnections {
344
+ environmentPath = "",
345
+ ): AssembledEnvironmentConnections {
337
346
  const pojo: CoreConnectionsPojo = { connections: {} };
338
- const metadata = new Map<string, ProjectConnectionMetadata>();
347
+ const metadata = new Map<string, EnvironmentConnectionMetadata>();
339
348
  const apiConnections: ApiConnection[] = [];
340
349
  const processedConnections = new Set<string>();
341
350
 
@@ -365,9 +374,9 @@ export function assembleProjectConnections(
365
374
  const isDuckLake = connection.type === "ducklake";
366
375
  const isDuckdb = connection.type === "duckdb";
367
376
  const databasePath = isDuckLake
368
- ? path.join(projectPath, `${connection.name}_ducklake.duckdb`)
377
+ ? path.join(environmentPath, `${connection.name}_ducklake.duckdb`)
369
378
  : isDuckdb
370
- ? path.join(projectPath, `${connection.name}.duckdb`)
379
+ ? path.join(environmentPath, `${connection.name}.duckdb`)
371
380
  : undefined;
372
381
 
373
382
  metadata.set(connection.name, {
@@ -381,7 +390,7 @@ export function assembleProjectConnections(
381
390
  !!connection.snowflakeConnection?.privateKey,
382
391
  isDuckLake,
383
392
  databasePath,
384
- workingDirectory: projectPath,
393
+ workingDirectory: environmentPath,
385
394
  });
386
395
 
387
396
  switch (connection.type) {
@@ -423,7 +432,7 @@ export function assembleProjectConnections(
423
432
  is: "bigquery",
424
433
  projectId:
425
434
  connection.bigqueryConnection?.defaultProjectId ??
426
- serviceAccountKey?.project_id,
435
+ serviceAccountKey?.environment_id,
427
436
  serviceAccountKey,
428
437
  location: connection.bigqueryConnection?.location,
429
438
  maximumBytesBilled:
@@ -499,7 +508,7 @@ export function assembleProjectConnections(
499
508
  }
500
509
  pojo.connections[connection.name] = buildDuckdbEntry(
501
510
  connection.name,
502
- projectPath,
511
+ environmentPath,
503
512
  `${connection.name}.duckdb`,
504
513
  );
505
514
  break;
@@ -531,7 +540,7 @@ export function assembleProjectConnections(
531
540
  }
532
541
  pojo.connections[connection.name] = buildDuckdbEntry(
533
542
  connection.name,
534
- projectPath,
543
+ environmentPath,
535
544
  `${connection.name}_ducklake.duckdb`,
536
545
  );
537
546
  break;