@malloy-publisher/server 0.0.196-dev → 0.0.198-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 +1947 -1317
- 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-old.ts +1139 -0
- package/src/server.ts +191 -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
|
@@ -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 {
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
810
|
+
environmentPath: string,
|
|
811
811
|
): Promise<void> {
|
|
812
812
|
const ducklakePath = path.join(
|
|
813
|
-
|
|
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 ${
|
|
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 ${
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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,
|
|
935
|
+
metadata: Map<string, EnvironmentConnectionMetadata>,
|
|
936
936
|
name?: string,
|
|
937
|
-
):
|
|
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
|
|
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
|
|
957
|
+
export function buildEnvironmentMalloyConfig(
|
|
958
958
|
connections: ApiConnection[] = [],
|
|
959
|
-
|
|
959
|
+
environmentPath: string = "",
|
|
960
960
|
isUpdateConnectionRequest: boolean = false,
|
|
961
|
-
):
|
|
962
|
-
const assembled =
|
|
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:
|
|
976
|
+
config: contextOverlay({ rootDirectory: environmentPath }),
|
|
974
977
|
});
|
|
975
978
|
|
|
976
979
|
async function attachOnce(
|
|
977
980
|
connection: Connection,
|
|
978
|
-
metadata:
|
|
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
|
|
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
|
|
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
|
|
1106
|
+
export async function createEnvironmentConnections(
|
|
1104
1107
|
connections: ApiConnection[] = [],
|
|
1105
|
-
|
|
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
|
|
1116
|
+
const environmentConfig = buildEnvironmentMalloyConfig(
|
|
1114
1117
|
connections,
|
|
1115
|
-
|
|
1118
|
+
environmentPath,
|
|
1116
1119
|
isUpdateConnectionRequest,
|
|
1117
1120
|
);
|
|
1118
1121
|
|
|
1119
|
-
for (const connection of
|
|
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
|
|
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:
|
|
1133
|
-
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
|
|
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
|
-
|
|
1293
|
+
environmentConfig = buildEnvironmentMalloyConfig([connectionConfig]);
|
|
1291
1294
|
const connection =
|
|
1292
|
-
await
|
|
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 (
|
|
1352
|
+
if (environmentConfig) {
|
|
1350
1353
|
try {
|
|
1351
|
-
await
|
|
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
|
-
|
|
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("
|
|
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 } =
|
|
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 } =
|
|
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(() =>
|
|
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(() =>
|
|
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(() =>
|
|
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(() =>
|
|
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(() =>
|
|
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
|
|
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
|
|
27
|
+
export type AssembledEnvironmentConnections = {
|
|
28
28
|
pojo: CoreConnectionsPojo;
|
|
29
|
-
metadata: Map<string,
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
251
|
+
environmentPath: string,
|
|
252
252
|
databaseFilename = `${name}.duckdb`,
|
|
253
253
|
): CoreConnectionEntry {
|
|
254
254
|
return {
|
|
255
255
|
is: "duckdb",
|
|
256
|
-
databasePath: path.join(
|
|
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
|
|
342
|
+
export function assembleEnvironmentConnections(
|
|
334
343
|
connections: ApiConnection[] = [],
|
|
335
|
-
|
|
336
|
-
):
|
|
344
|
+
environmentPath = "",
|
|
345
|
+
): AssembledEnvironmentConnections {
|
|
337
346
|
const pojo: CoreConnectionsPojo = { connections: {} };
|
|
338
|
-
const metadata = new Map<string,
|
|
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(
|
|
377
|
+
? path.join(environmentPath, `${connection.name}_ducklake.duckdb`)
|
|
369
378
|
: isDuckdb
|
|
370
|
-
? path.join(
|
|
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:
|
|
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?.
|
|
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
|
-
|
|
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
|
-
|
|
543
|
+
environmentPath,
|
|
535
544
|
`${connection.name}_ducklake.duckdb`,
|
|
536
545
|
);
|
|
537
546
|
break;
|