@malloy-publisher/server 0.0.196-dev → 0.0.197-dev
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/api-doc.yaml +213 -214
- package/dist/app/assets/EnvironmentPage-1j6QDWAy.js +1 -0
- package/dist/app/assets/HomePage-DMop21VG.js +1 -0
- package/dist/app/assets/MainPage-BbE8ETz1.js +2 -0
- package/dist/app/assets/ModelPage-D2jvfe3t.js +1 -0
- package/dist/app/assets/PackagePage-BbnhGoD3.js +1 -0
- package/dist/app/assets/{RouteError-DefbDO7F.js → RouteError-D3LGEZ3i.js} +1 -1
- package/dist/app/assets/WorkbookPage-DttVIj4u.js +1 -0
- package/dist/app/assets/{core-BrfQApxh.es-DnvCX4oH.js → core-w79IMXAG.es-Bd0UlzOL.js} +1 -1
- package/dist/app/assets/{index-Bu0ub036.js → index-5K9YjIxF.js} +117 -117
- package/dist/app/assets/{index-CkzK3JIl.js → index-C513UodQ.js} +1 -1
- package/dist/app/assets/{index-CoA6HIGS.js → index-DIgzgp69.js} +1 -1
- package/dist/app/assets/{index.umd-B6Ms2PpL.js → index.umd-BMeMPq_9.js} +1 -1
- package/dist/app/index.html +1 -1
- package/dist/server.mjs +1328 -1304
- package/package.json +1 -1
- package/publisher.config.json +2 -2
- package/src/config.spec.ts +74 -66
- package/src/config.ts +50 -47
- package/src/controller/compile.controller.ts +10 -7
- package/src/controller/connection.controller.ts +79 -58
- package/src/controller/database.controller.ts +10 -7
- package/src/controller/manifest.controller.ts +23 -14
- package/src/controller/materialization.controller.ts +14 -14
- package/src/controller/model.controller.ts +35 -20
- package/src/controller/package.controller.ts +83 -49
- package/src/controller/query.controller.ts +11 -8
- package/src/controller/watch-mode.controller.ts +35 -29
- package/src/errors.ts +2 -2
- package/src/mcp/error_messages.ts +2 -2
- package/src/mcp/handler_utils.ts +23 -20
- package/src/mcp/mcp_constants.ts +1 -1
- package/src/mcp/prompts/handlers.ts +3 -3
- package/src/mcp/prompts/prompt_service.ts +5 -5
- package/src/mcp/prompts/utils.ts +12 -12
- package/src/mcp/resource_metadata.ts +3 -3
- package/src/mcp/resources/environment_resource.ts +187 -0
- package/src/mcp/resources/model_resource.ts +19 -17
- package/src/mcp/resources/notebook_resource.ts +13 -13
- package/src/mcp/resources/package_resource.ts +30 -27
- package/src/mcp/resources/query_resource.ts +15 -10
- package/src/mcp/resources/source_resource.ts +10 -10
- package/src/mcp/resources/view_resource.ts +11 -11
- package/src/mcp/server.ts +16 -14
- package/src/mcp/tools/discovery_tools.ts +67 -49
- package/src/mcp/tools/execute_query_tool.ts +14 -14
- package/src/server.ts +175 -159
- package/src/service/connection.spec.ts +158 -133
- package/src/service/connection.ts +42 -39
- package/src/service/connection_config.spec.ts +13 -11
- package/src/service/connection_config.ts +28 -19
- package/src/service/connection_service.spec.ts +63 -43
- package/src/service/connection_service.ts +106 -89
- package/src/service/{project.ts → environment.ts} +92 -77
- package/src/service/{project_compile.spec.ts → environment_compile.spec.ts} +1 -1
- package/src/service/{project_store.spec.ts → environment_store.spec.ts} +99 -85
- package/src/service/{project_store.ts → environment_store.ts} +368 -326
- package/src/service/manifest_service.spec.ts +15 -15
- package/src/service/manifest_service.ts +26 -21
- package/src/service/materialization_service.spec.ts +93 -59
- package/src/service/materialization_service.ts +71 -62
- package/src/service/materialized_table_gc.spec.ts +15 -15
- package/src/service/materialized_table_gc.ts +3 -3
- package/src/service/model.ts +2 -2
- package/src/service/package.spec.ts +2 -2
- package/src/service/package.ts +23 -21
- package/src/service/resolve_environment.ts +15 -0
- package/src/storage/DatabaseInterface.ts +34 -25
- package/src/storage/StorageManager.mock.ts +3 -3
- package/src/storage/StorageManager.ts +24 -23
- package/src/storage/duckdb/ConnectionRepository.ts +13 -11
- package/src/storage/duckdb/DuckDBConnection.ts +1 -1
- package/src/storage/duckdb/DuckDBManifestStore.ts +6 -6
- package/src/storage/duckdb/DuckDBRepository.ts +47 -47
- package/src/storage/duckdb/{ProjectRepository.ts → EnvironmentRepository.ts} +35 -35
- package/src/storage/duckdb/ManifestRepository.ts +21 -20
- package/src/storage/duckdb/MaterializationRepository.ts +31 -28
- package/src/storage/duckdb/PackageRepository.ts +11 -11
- package/src/storage/duckdb/manifest_store.spec.ts +2 -2
- package/src/storage/duckdb/schema.ts +20 -20
- package/src/storage/ducklake/DuckLakeManifestStore.ts +14 -14
- package/tests/fixtures/publisher.config.json +1 -1
- package/tests/harness/e2e.ts +1 -1
- package/tests/harness/mcp_test_setup.ts +1 -1
- package/tests/harness/mocks.ts +10 -8
- package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +4 -4
- package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +27 -48
- package/tests/integration/mcp/mcp_resource.integration.spec.ts +26 -35
- package/tests/unit/duckdb/attached_databases.test.ts +51 -33
- package/tests/unit/ducklake/ducklake.test.ts +24 -22
- package/tests/unit/mcp/prompt_happy.test.ts +8 -8
- package/dist/app/assets/HomePage-DbZS0N7G.js +0 -1
- package/dist/app/assets/MainPage-CBuWkbmr.js +0 -2
- package/dist/app/assets/ModelPage-Bt37smot.js +0 -1
- package/dist/app/assets/PackagePage-DLZe50WG.js +0 -1
- package/dist/app/assets/ProjectPage-FQTEPXP4.js +0 -1
- package/dist/app/assets/WorkbookPage-CkAo16ar.js +0 -1
- package/src/mcp/resources/project_resource.ts +0 -184
- package/src/service/resolve_project.ts +0 -13
package/src/service/package.ts
CHANGED
|
@@ -6,9 +6,9 @@ import "@malloydata/db-duckdb/native";
|
|
|
6
6
|
import {
|
|
7
7
|
Connection,
|
|
8
8
|
ConnectionRuntime,
|
|
9
|
+
contextOverlay,
|
|
9
10
|
EmptyURLReader,
|
|
10
11
|
FixedConnectionMap,
|
|
11
|
-
contextOverlay,
|
|
12
12
|
MalloyConfig,
|
|
13
13
|
SourceDef,
|
|
14
14
|
} from "@malloydata/malloy";
|
|
@@ -32,8 +32,8 @@ type ApiNotebook = components["schemas"]["Notebook"];
|
|
|
32
32
|
export type ApiPackage = components["schemas"]["Package"];
|
|
33
33
|
type ApiColumn = components["schemas"]["Column"];
|
|
34
34
|
type ApiTableDescription = components["schemas"]["TableDescription"];
|
|
35
|
-
// A thunk lets callers pass a live reference to the *current*
|
|
36
|
-
// MalloyConfig so the package wrapper resolves
|
|
35
|
+
// A thunk lets callers pass a live reference to the *current* environment
|
|
36
|
+
// MalloyConfig so the package wrapper resolves environment connections against the
|
|
37
37
|
// generation that's active at lookup time, not the one that was current when
|
|
38
38
|
// the package was first loaded.
|
|
39
39
|
type PackageConnectionInput =
|
|
@@ -43,7 +43,7 @@ type PackageConnectionInput =
|
|
|
43
43
|
|
|
44
44
|
const ENABLE_LIST_MODEL_COMPILATION = true;
|
|
45
45
|
export class Package {
|
|
46
|
-
private
|
|
46
|
+
private environmentName: string;
|
|
47
47
|
private packageName: string;
|
|
48
48
|
private packageMetadata: ApiPackage;
|
|
49
49
|
private databases: ApiDatabase[];
|
|
@@ -60,7 +60,7 @@ export class Package {
|
|
|
60
60
|
);
|
|
61
61
|
|
|
62
62
|
constructor(
|
|
63
|
-
|
|
63
|
+
environmentName: string,
|
|
64
64
|
packageName: string,
|
|
65
65
|
packagePath: string,
|
|
66
66
|
packageMetadata: ApiPackage,
|
|
@@ -68,7 +68,7 @@ export class Package {
|
|
|
68
68
|
models: Map<string, Model>,
|
|
69
69
|
malloyConfig: MalloyConfig = new MalloyConfig({ connections: {} }),
|
|
70
70
|
) {
|
|
71
|
-
this.
|
|
71
|
+
this.environmentName = environmentName;
|
|
72
72
|
this.packageName = packageName;
|
|
73
73
|
this.packagePath = packagePath;
|
|
74
74
|
this.packageMetadata = packageMetadata;
|
|
@@ -78,10 +78,10 @@ export class Package {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
static async create(
|
|
81
|
-
|
|
81
|
+
environmentName: string,
|
|
82
82
|
packageName: string,
|
|
83
83
|
packagePath: string,
|
|
84
|
-
|
|
84
|
+
environmentMalloyConfig: PackageConnectionInput,
|
|
85
85
|
): Promise<Package> {
|
|
86
86
|
const startTime = performance.now();
|
|
87
87
|
await Package.validatePackageManifestExistsOrThrowError(packagePath);
|
|
@@ -100,7 +100,7 @@ export class Package {
|
|
|
100
100
|
packageConfigTime - manifestValidationTime,
|
|
101
101
|
),
|
|
102
102
|
});
|
|
103
|
-
packageConfig.resource = `${API_PREFIX}/
|
|
103
|
+
packageConfig.resource = `${API_PREFIX}/environments/${environmentName}/packages/${packageName}`;
|
|
104
104
|
|
|
105
105
|
const databases = await Package.readDatabases(packagePath);
|
|
106
106
|
const databasesTime = performance.now();
|
|
@@ -111,9 +111,9 @@ export class Package {
|
|
|
111
111
|
});
|
|
112
112
|
const malloyConfig = Package.buildPackageMalloyConfig(
|
|
113
113
|
packagePath,
|
|
114
|
-
typeof
|
|
115
|
-
?
|
|
116
|
-
: () => Package.toMalloyConfig(
|
|
114
|
+
typeof environmentMalloyConfig === "function"
|
|
115
|
+
? environmentMalloyConfig
|
|
116
|
+
: () => Package.toMalloyConfig(environmentMalloyConfig),
|
|
117
117
|
);
|
|
118
118
|
|
|
119
119
|
const models = await Package.loadModels(
|
|
@@ -162,7 +162,7 @@ export class Package {
|
|
|
162
162
|
duration: formatDuration(executionTime),
|
|
163
163
|
});
|
|
164
164
|
return new Package(
|
|
165
|
-
|
|
165
|
+
environmentName,
|
|
166
166
|
packageName,
|
|
167
167
|
packagePath,
|
|
168
168
|
packageConfig,
|
|
@@ -283,7 +283,7 @@ export class Package {
|
|
|
283
283
|
}
|
|
284
284
|
}
|
|
285
285
|
return {
|
|
286
|
-
|
|
286
|
+
environmentName: this.environmentName,
|
|
287
287
|
path: modelPath,
|
|
288
288
|
packageName: this.packageName,
|
|
289
289
|
error,
|
|
@@ -305,7 +305,7 @@ export class Package {
|
|
|
305
305
|
error = this.models.get(modelPath)?.getNotebookError();
|
|
306
306
|
}
|
|
307
307
|
return {
|
|
308
|
-
|
|
308
|
+
environmentName: this.environmentName,
|
|
309
309
|
packageName: this.packageName,
|
|
310
310
|
path: modelPath,
|
|
311
311
|
error: error?.message,
|
|
@@ -330,7 +330,7 @@ export class Package {
|
|
|
330
330
|
|
|
331
331
|
private static buildPackageMalloyConfig(
|
|
332
332
|
packagePath: string,
|
|
333
|
-
|
|
333
|
+
getEnvironmentMalloyConfig: () => MalloyConfig,
|
|
334
334
|
): MalloyConfig {
|
|
335
335
|
const malloyConfig = new MalloyConfig(
|
|
336
336
|
{
|
|
@@ -351,10 +351,12 @@ export class Package {
|
|
|
351
351
|
if (!name || name === "duckdb") {
|
|
352
352
|
return base.lookupConnection(name);
|
|
353
353
|
}
|
|
354
|
-
// Resolve against the *current*
|
|
355
|
-
// connection-generation swap on
|
|
354
|
+
// Resolve against the *current* environment MalloyConfig so a
|
|
355
|
+
// connection-generation swap on Environment propagates without a
|
|
356
356
|
// package reload.
|
|
357
|
-
return
|
|
357
|
+
return getEnvironmentMalloyConfig().connections.lookupConnection(
|
|
358
|
+
name,
|
|
359
|
+
);
|
|
358
360
|
},
|
|
359
361
|
}));
|
|
360
362
|
|
|
@@ -495,8 +497,8 @@ export class Package {
|
|
|
495
497
|
this.packageName = name;
|
|
496
498
|
}
|
|
497
499
|
|
|
498
|
-
public
|
|
499
|
-
this.
|
|
500
|
+
public setEnvironmentName(environmentName: string) {
|
|
501
|
+
this.environmentName = environmentName;
|
|
500
502
|
}
|
|
501
503
|
|
|
502
504
|
public setPackageMetadata(packageMetadata: ApiPackage) {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { EnvironmentNotFoundError } from "../errors";
|
|
2
|
+
import { ResourceRepository } from "../storage/DatabaseInterface";
|
|
3
|
+
|
|
4
|
+
export async function resolveEnvironmentId(
|
|
5
|
+
repository: ResourceRepository,
|
|
6
|
+
environmentName: string,
|
|
7
|
+
): Promise<string> {
|
|
8
|
+
const dbEnvironment = await repository.getEnvironmentByName(environmentName);
|
|
9
|
+
if (!dbEnvironment) {
|
|
10
|
+
throw new EnvironmentNotFoundError(
|
|
11
|
+
`Environment '${environmentName}' not found`,
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
return dbEnvironment.id;
|
|
15
|
+
}
|
|
@@ -9,20 +9,26 @@ export interface DatabaseConnection {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export interface ResourceRepository {
|
|
12
|
-
//
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
): Promise<
|
|
19
|
-
|
|
20
|
-
|
|
12
|
+
// Environments
|
|
13
|
+
listEnvironments(): Promise<Environment[]>;
|
|
14
|
+
getEnvironmentById(id: string): Promise<Environment | null>;
|
|
15
|
+
getEnvironmentByName(name: string): Promise<Environment | null>;
|
|
16
|
+
createEnvironment(
|
|
17
|
+
environment: Omit<Environment, "id" | "createdAt" | "updatedAt">,
|
|
18
|
+
): Promise<Environment>;
|
|
19
|
+
updateEnvironment(
|
|
20
|
+
id: string,
|
|
21
|
+
updates: Partial<Environment>,
|
|
22
|
+
): Promise<Environment>;
|
|
23
|
+
deleteEnvironment(id: string): Promise<void>;
|
|
21
24
|
|
|
22
25
|
// Packages
|
|
23
|
-
listPackages(
|
|
26
|
+
listPackages(environmentId: string): Promise<Package[]>;
|
|
24
27
|
getPackageById(id: string): Promise<Package | null>;
|
|
25
|
-
getPackageByName(
|
|
28
|
+
getPackageByName(
|
|
29
|
+
environmentId: string,
|
|
30
|
+
name: string,
|
|
31
|
+
): Promise<Package | null>;
|
|
26
32
|
createPackage(
|
|
27
33
|
pkg: Omit<Package, "id" | "createdAt" | "updatedAt">,
|
|
28
34
|
): Promise<Package>;
|
|
@@ -30,10 +36,10 @@ export interface ResourceRepository {
|
|
|
30
36
|
deletePackage(id: string): Promise<void>;
|
|
31
37
|
|
|
32
38
|
// Connections
|
|
33
|
-
listConnections(
|
|
39
|
+
listConnections(environmentId: string): Promise<Connection[]>;
|
|
34
40
|
getConnectionById(id: string): Promise<Connection | null>;
|
|
35
41
|
getConnectionByName(
|
|
36
|
-
|
|
42
|
+
environmentId: string,
|
|
37
43
|
name: string,
|
|
38
44
|
): Promise<Connection | null>;
|
|
39
45
|
createConnection(
|
|
@@ -47,17 +53,17 @@ export interface ResourceRepository {
|
|
|
47
53
|
|
|
48
54
|
// Materializations
|
|
49
55
|
listMaterializations(
|
|
50
|
-
|
|
56
|
+
environmentId: string,
|
|
51
57
|
packageName: string,
|
|
52
58
|
options?: { limit?: number; offset?: number },
|
|
53
59
|
): Promise<Materialization[]>;
|
|
54
60
|
getMaterializationById(id: string): Promise<Materialization | null>;
|
|
55
61
|
getActiveMaterialization(
|
|
56
|
-
|
|
62
|
+
environmentId: string,
|
|
57
63
|
packageName: string,
|
|
58
64
|
): Promise<Materialization | null>;
|
|
59
65
|
createMaterialization(
|
|
60
|
-
|
|
66
|
+
environmentId: string,
|
|
61
67
|
packageName: string,
|
|
62
68
|
status?: MaterializationStatus,
|
|
63
69
|
metadata?: Record<string, unknown> | null,
|
|
@@ -75,7 +81,7 @@ export interface ResourceRepository {
|
|
|
75
81
|
deleteMaterialization(id: string): Promise<void>;
|
|
76
82
|
// Build Manifests
|
|
77
83
|
listManifestEntries(
|
|
78
|
-
|
|
84
|
+
environmentId: string,
|
|
79
85
|
packageName: string,
|
|
80
86
|
): Promise<ManifestEntry[]>;
|
|
81
87
|
upsertManifestEntry(
|
|
@@ -84,7 +90,7 @@ export interface ResourceRepository {
|
|
|
84
90
|
deleteManifestEntry(id: string): Promise<void>;
|
|
85
91
|
}
|
|
86
92
|
|
|
87
|
-
export interface
|
|
93
|
+
export interface Environment {
|
|
88
94
|
id: string;
|
|
89
95
|
name: string;
|
|
90
96
|
path: string;
|
|
@@ -96,7 +102,7 @@ export interface Project {
|
|
|
96
102
|
|
|
97
103
|
export interface Package {
|
|
98
104
|
id: string;
|
|
99
|
-
|
|
105
|
+
environmentId: string;
|
|
100
106
|
name: string;
|
|
101
107
|
description?: string;
|
|
102
108
|
manifestPath: string;
|
|
@@ -107,7 +113,7 @@ export interface Package {
|
|
|
107
113
|
|
|
108
114
|
export interface Connection {
|
|
109
115
|
id: string;
|
|
110
|
-
|
|
116
|
+
environmentId: string;
|
|
111
117
|
name: string;
|
|
112
118
|
type: "bigquery" | "postgres" | "duckdb" | "mysql" | "snowflake" | "trino";
|
|
113
119
|
config: Record<string, unknown>;
|
|
@@ -124,7 +130,7 @@ export type MaterializationStatus =
|
|
|
124
130
|
|
|
125
131
|
export interface Materialization {
|
|
126
132
|
id: string;
|
|
127
|
-
|
|
133
|
+
environmentId: string;
|
|
128
134
|
packageName: string;
|
|
129
135
|
status: MaterializationStatus;
|
|
130
136
|
startedAt: Date | null;
|
|
@@ -137,7 +143,7 @@ export interface Materialization {
|
|
|
137
143
|
|
|
138
144
|
export interface ManifestEntry {
|
|
139
145
|
id: string;
|
|
140
|
-
|
|
146
|
+
environmentId: string;
|
|
141
147
|
packageName: string;
|
|
142
148
|
buildId: string;
|
|
143
149
|
tableName: string;
|
|
@@ -163,9 +169,12 @@ export interface BuildManifest {
|
|
|
163
169
|
* orchestrated mode swaps in a DuckLakeManifestStore.
|
|
164
170
|
*/
|
|
165
171
|
export interface ManifestStore {
|
|
166
|
-
getManifest(
|
|
172
|
+
getManifest(
|
|
173
|
+
environmentId: string,
|
|
174
|
+
packageName: string,
|
|
175
|
+
): Promise<BuildManifest>;
|
|
167
176
|
writeEntry(
|
|
168
|
-
|
|
177
|
+
environmentId: string,
|
|
169
178
|
packageName: string,
|
|
170
179
|
buildId: string,
|
|
171
180
|
tableName: string,
|
|
@@ -174,7 +183,7 @@ export interface ManifestStore {
|
|
|
174
183
|
): Promise<void>;
|
|
175
184
|
deleteEntry(id: string): Promise<void>;
|
|
176
185
|
listEntries(
|
|
177
|
-
|
|
186
|
+
environmentId: string,
|
|
178
187
|
packageName: string,
|
|
179
188
|
): Promise<ManifestEntry[]>;
|
|
180
189
|
}
|
|
@@ -7,12 +7,12 @@ export class StorageManager {
|
|
|
7
7
|
|
|
8
8
|
getRepository() {
|
|
9
9
|
return {
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
listEnvironments: async (): Promise<unknown[]> => [],
|
|
11
|
+
createEnvironment: async (data: MockData): Promise<MockData> => ({
|
|
12
12
|
id: "test-id",
|
|
13
13
|
...data,
|
|
14
14
|
}),
|
|
15
|
-
|
|
15
|
+
updateEnvironment: async (
|
|
16
16
|
id: string,
|
|
17
17
|
data: MockData,
|
|
18
18
|
): Promise<MockData> => ({
|
|
@@ -57,9 +57,9 @@ function catalogNameForConfig(c: DuckLakeManifestConfig): string {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
/**
|
|
60
|
-
* Manages the storage backend (DuckDB, Postgres, etc.) and per-
|
|
61
|
-
* manifest stores.
|
|
62
|
-
* the default DuckDB manifest store.
|
|
60
|
+
* Manages the storage backend (DuckDB, Postgres, etc.) and per-environment
|
|
61
|
+
* manifest stores. Environments without `materializationStorage` config use
|
|
62
|
+
* the default DuckDB manifest store. Environments with the config get a
|
|
63
63
|
* DuckLake-backed store attached lazily on first access.
|
|
64
64
|
*/
|
|
65
65
|
export class StorageManager {
|
|
@@ -68,8 +68,8 @@ export class StorageManager {
|
|
|
68
68
|
private repository: ResourceRepository | null = null;
|
|
69
69
|
private defaultManifestStore: ManifestStore | null = null;
|
|
70
70
|
|
|
71
|
-
/** Per-
|
|
72
|
-
private
|
|
71
|
+
/** Per-environment DuckLake manifest stores, keyed by environmentId. */
|
|
72
|
+
private environmentManifestStores = new Map<string, ManifestStore>();
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
75
|
* Tracks attached DuckLake catalogs as `configKey -> catalogName`. Each
|
|
@@ -122,18 +122,18 @@ export class StorageManager {
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
/**
|
|
125
|
-
* Lazily initializes a DuckLake manifest store for
|
|
125
|
+
* Lazily initializes a DuckLake manifest store for an environment.
|
|
126
126
|
*
|
|
127
|
-
* One shared catalog per materializationStorage config: every
|
|
127
|
+
* One shared catalog per materializationStorage config: every environment
|
|
128
128
|
* pointing at the same (catalogUrl, dataPath) shares one `build_manifests`
|
|
129
|
-
* table inside it, partitioned by `
|
|
129
|
+
* table inside it, partitioned by `environment_id` (set to the environment's name
|
|
130
130
|
* so it's stable across worker replicas — required for cross-pod manifest
|
|
131
131
|
* visibility in orchestrated mode). Different configs (e.g. different
|
|
132
132
|
* orgs) attach as separate catalogs under distinct deterministic aliases.
|
|
133
133
|
*/
|
|
134
|
-
async
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
async initializeDuckLakeForEnvironment(
|
|
135
|
+
environmentId: string,
|
|
136
|
+
environmentName: string,
|
|
137
137
|
config: DuckLakeManifestConfig,
|
|
138
138
|
): Promise<void> {
|
|
139
139
|
if (!this.duckDbConnection) {
|
|
@@ -153,14 +153,14 @@ export class StorageManager {
|
|
|
153
153
|
const store = new DuckLakeManifestStore(
|
|
154
154
|
this.duckDbConnection,
|
|
155
155
|
catalogName,
|
|
156
|
-
|
|
156
|
+
environmentName,
|
|
157
157
|
);
|
|
158
158
|
await store.bootstrapSchema();
|
|
159
159
|
|
|
160
|
-
this.
|
|
161
|
-
logger.info("DuckLake manifest store initialized for
|
|
162
|
-
|
|
163
|
-
|
|
160
|
+
this.environmentManifestStores.set(environmentId, store);
|
|
161
|
+
logger.info("DuckLake manifest store initialized for environment", {
|
|
162
|
+
environmentId,
|
|
163
|
+
environmentName,
|
|
164
164
|
catalogName,
|
|
165
165
|
});
|
|
166
166
|
}
|
|
@@ -210,15 +210,16 @@ export class StorageManager {
|
|
|
210
210
|
}
|
|
211
211
|
|
|
212
212
|
/**
|
|
213
|
-
* Returns the manifest store for
|
|
213
|
+
* Returns the manifest store for an environment. If the environment has a
|
|
214
214
|
* DuckLake store configured, returns that; otherwise returns the
|
|
215
215
|
* default DuckDB-backed store.
|
|
216
216
|
*/
|
|
217
|
-
getManifestStore(
|
|
218
|
-
if (
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
217
|
+
getManifestStore(environmentId?: string): ManifestStore {
|
|
218
|
+
if (environmentId) {
|
|
219
|
+
const environmentStore =
|
|
220
|
+
this.environmentManifestStores.get(environmentId);
|
|
221
|
+
if (environmentStore) {
|
|
222
|
+
return environmentStore;
|
|
222
223
|
}
|
|
223
224
|
}
|
|
224
225
|
if (!this.defaultManifestStore) {
|
|
@@ -234,7 +235,7 @@ export class StorageManager {
|
|
|
234
235
|
this.duckDbConnection = null;
|
|
235
236
|
this.repository = null;
|
|
236
237
|
this.defaultManifestStore = null;
|
|
237
|
-
this.
|
|
238
|
+
this.environmentManifestStores.clear();
|
|
238
239
|
this.attachedCatalogs.clear();
|
|
239
240
|
}
|
|
240
241
|
}
|
|
@@ -12,11 +12,11 @@ export class ConnectionRepository {
|
|
|
12
12
|
return new Date();
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
async listConnections(
|
|
15
|
+
async listConnections(environmentId: string): Promise<Connection[]> {
|
|
16
16
|
try {
|
|
17
17
|
const rows = await this.db.all<Record<string, unknown>>(
|
|
18
|
-
"SELECT * FROM connections WHERE
|
|
19
|
-
[
|
|
18
|
+
"SELECT * FROM connections WHERE environment_id = ? ORDER BY name",
|
|
19
|
+
[environmentId],
|
|
20
20
|
);
|
|
21
21
|
return rows.map(this.mapToConnection);
|
|
22
22
|
} catch (err: unknown) {
|
|
@@ -35,12 +35,12 @@ export class ConnectionRepository {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
async getConnectionByName(
|
|
38
|
-
|
|
38
|
+
environmentId: string,
|
|
39
39
|
name: string,
|
|
40
40
|
): Promise<Connection | null> {
|
|
41
41
|
const row = await this.db.get<Record<string, unknown>>(
|
|
42
|
-
"SELECT * FROM connections WHERE
|
|
43
|
-
[
|
|
42
|
+
"SELECT * FROM connections WHERE environment_id = ? AND name = ?",
|
|
43
|
+
[environmentId, name],
|
|
44
44
|
);
|
|
45
45
|
return row ? this.mapToConnection(row) : null;
|
|
46
46
|
}
|
|
@@ -55,11 +55,11 @@ export class ConnectionRepository {
|
|
|
55
55
|
const configJson = JSON.stringify(connection.config);
|
|
56
56
|
|
|
57
57
|
await this.db.run(
|
|
58
|
-
`INSERT INTO connections (id,
|
|
58
|
+
`INSERT INTO connections (id, environment_id, name, type, config, created_at, updated_at)
|
|
59
59
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
60
60
|
[
|
|
61
61
|
id,
|
|
62
|
-
connection.
|
|
62
|
+
connection.environmentId,
|
|
63
63
|
connection.name,
|
|
64
64
|
connection.type,
|
|
65
65
|
configJson,
|
|
@@ -123,14 +123,16 @@ export class ConnectionRepository {
|
|
|
123
123
|
await this.db.run("DELETE FROM connections WHERE id = ?", [id]);
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
async
|
|
127
|
-
await this.db.run("DELETE FROM connections WHERE
|
|
126
|
+
async deleteConnectionsByEnvironmentId(id: string): Promise<void> {
|
|
127
|
+
await this.db.run("DELETE FROM connections WHERE environment_id = ?", [
|
|
128
|
+
id,
|
|
129
|
+
]);
|
|
128
130
|
}
|
|
129
131
|
|
|
130
132
|
private mapToConnection(row: Record<string, unknown>): Connection {
|
|
131
133
|
return {
|
|
132
134
|
id: row.id as string,
|
|
133
|
-
|
|
135
|
+
environmentId: row.environment_id as string,
|
|
134
136
|
name: row.name as string,
|
|
135
137
|
type: row.type as Connection["type"],
|
|
136
138
|
config: JSON.parse(row.config as string),
|
|
@@ -93,7 +93,7 @@ export class DuckDBConnection implements DatabaseConnection {
|
|
|
93
93
|
return this.mutex.runExclusive(async () => {
|
|
94
94
|
return new Promise<boolean>((resolve) => {
|
|
95
95
|
this.connection!.all(
|
|
96
|
-
"SELECT name FROM sqlite_master WHERE type='table' AND name='
|
|
96
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name='environments'",
|
|
97
97
|
(err, rows) => {
|
|
98
98
|
if (err) {
|
|
99
99
|
resolve(false);
|
|
@@ -22,11 +22,11 @@ export class DuckDBManifestStore implements ManifestStore {
|
|
|
22
22
|
* reference has no manifest entry (e.g. before the first materialization).
|
|
23
23
|
*/
|
|
24
24
|
async getManifest(
|
|
25
|
-
|
|
25
|
+
environmentId: string,
|
|
26
26
|
packageName: string,
|
|
27
27
|
): Promise<BuildManifest> {
|
|
28
28
|
const entries = await this.repository.listManifestEntries(
|
|
29
|
-
|
|
29
|
+
environmentId,
|
|
30
30
|
packageName,
|
|
31
31
|
);
|
|
32
32
|
const manifest: BuildManifest = {
|
|
@@ -40,7 +40,7 @@ export class DuckDBManifestStore implements ManifestStore {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
async writeEntry(
|
|
43
|
-
|
|
43
|
+
environmentId: string,
|
|
44
44
|
packageName: string,
|
|
45
45
|
buildId: string,
|
|
46
46
|
tableName: string,
|
|
@@ -48,7 +48,7 @@ export class DuckDBManifestStore implements ManifestStore {
|
|
|
48
48
|
connectionName: string,
|
|
49
49
|
): Promise<void> {
|
|
50
50
|
await this.repository.upsertManifestEntry({
|
|
51
|
-
|
|
51
|
+
environmentId,
|
|
52
52
|
packageName,
|
|
53
53
|
buildId,
|
|
54
54
|
tableName,
|
|
@@ -62,9 +62,9 @@ export class DuckDBManifestStore implements ManifestStore {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
async listEntries(
|
|
65
|
-
|
|
65
|
+
environmentId: string,
|
|
66
66
|
packageName: string,
|
|
67
67
|
): Promise<ManifestEntry[]> {
|
|
68
|
-
return this.repository.listManifestEntries(
|
|
68
|
+
return this.repository.listManifestEntries(environmentId, packageName);
|
|
69
69
|
}
|
|
70
70
|
}
|