@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/config.ts
CHANGED
|
@@ -18,7 +18,7 @@ export type Connection = {
|
|
|
18
18
|
type: string;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
-
export type
|
|
21
|
+
export type Environment = {
|
|
22
22
|
name: string;
|
|
23
23
|
packages: Package[];
|
|
24
24
|
connections?: Connection[];
|
|
@@ -26,10 +26,10 @@ export type Project = {
|
|
|
26
26
|
|
|
27
27
|
export type PublisherConfig = {
|
|
28
28
|
frozenConfig: boolean;
|
|
29
|
-
|
|
29
|
+
environments: Environment[];
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
-
export type
|
|
32
|
+
export type ProcessedEnvironment = {
|
|
33
33
|
name: string;
|
|
34
34
|
packages: Package[];
|
|
35
35
|
connections: ApiConnection[];
|
|
@@ -37,7 +37,7 @@ export type ProcessedProject = {
|
|
|
37
37
|
|
|
38
38
|
export type ProcessedPublisherConfig = {
|
|
39
39
|
frozenConfig: boolean;
|
|
40
|
-
|
|
40
|
+
environments: ProcessedEnvironment[];
|
|
41
41
|
};
|
|
42
42
|
|
|
43
43
|
function substituteEnvVars(value: string): string {
|
|
@@ -81,7 +81,7 @@ export const getPublisherConfig = (serverRoot: string): PublisherConfig => {
|
|
|
81
81
|
if (!fs.existsSync(publisherConfigPath)) {
|
|
82
82
|
return {
|
|
83
83
|
frozenConfig: false,
|
|
84
|
-
|
|
84
|
+
environments: [],
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
87
|
|
|
@@ -101,7 +101,7 @@ export const getPublisherConfig = (serverRoot: string): PublisherConfig => {
|
|
|
101
101
|
);
|
|
102
102
|
return {
|
|
103
103
|
frozenConfig: false,
|
|
104
|
-
|
|
104
|
+
environments: [],
|
|
105
105
|
};
|
|
106
106
|
}
|
|
107
107
|
|
|
@@ -111,29 +111,30 @@ export const getPublisherConfig = (serverRoot: string): PublisherConfig => {
|
|
|
111
111
|
if (
|
|
112
112
|
processedConfig &&
|
|
113
113
|
typeof processedConfig === "object" &&
|
|
114
|
-
"
|
|
115
|
-
processedConfig.
|
|
116
|
-
typeof processedConfig.
|
|
117
|
-
!Array.isArray(processedConfig.
|
|
114
|
+
"environments" in processedConfig &&
|
|
115
|
+
processedConfig.environments &&
|
|
116
|
+
typeof processedConfig.environments === "object" &&
|
|
117
|
+
!Array.isArray(processedConfig.environments)
|
|
118
118
|
) {
|
|
119
119
|
logger.error(
|
|
120
|
-
`Invalid ${PUBLISHER_CONFIG_NAME}:
|
|
120
|
+
`Invalid ${PUBLISHER_CONFIG_NAME}: the "environments" field must be a JSON array. Using default empty config.`,
|
|
121
121
|
);
|
|
122
122
|
return {
|
|
123
123
|
frozenConfig: false,
|
|
124
|
-
|
|
124
|
+
environments: [],
|
|
125
125
|
};
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
// Ensure
|
|
129
|
-
let
|
|
128
|
+
// Ensure environments is an array
|
|
129
|
+
let environments: unknown[] = [];
|
|
130
130
|
if (
|
|
131
131
|
processedConfig &&
|
|
132
132
|
typeof processedConfig === "object" &&
|
|
133
|
-
"
|
|
134
|
-
Array.isArray((processedConfig as {
|
|
133
|
+
"environments" in processedConfig &&
|
|
134
|
+
Array.isArray((processedConfig as { environments: unknown }).environments)
|
|
135
135
|
) {
|
|
136
|
-
|
|
136
|
+
environments = (processedConfig as { environments: unknown[] })
|
|
137
|
+
.environments;
|
|
137
138
|
}
|
|
138
139
|
|
|
139
140
|
let frozenConfig = false;
|
|
@@ -149,7 +150,7 @@ export const getPublisherConfig = (serverRoot: string): PublisherConfig => {
|
|
|
149
150
|
|
|
150
151
|
return {
|
|
151
152
|
frozenConfig,
|
|
152
|
-
|
|
153
|
+
environments,
|
|
153
154
|
} as PublisherConfig;
|
|
154
155
|
};
|
|
155
156
|
|
|
@@ -168,20 +169,22 @@ export const isPublisherConfigFrozen = (serverRoot: string) => {
|
|
|
168
169
|
|
|
169
170
|
export const getConnectionsFromPublisherConfig = (
|
|
170
171
|
serverRoot: string,
|
|
171
|
-
|
|
172
|
+
environmentName: string,
|
|
172
173
|
): Connection[] => {
|
|
173
174
|
try {
|
|
174
175
|
const publisherConfig = getPublisherConfig(serverRoot);
|
|
175
|
-
if (!Array.isArray(publisherConfig.
|
|
176
|
+
if (!Array.isArray(publisherConfig.environments)) {
|
|
176
177
|
return [];
|
|
177
178
|
}
|
|
178
|
-
const
|
|
179
|
-
(
|
|
179
|
+
const environment = publisherConfig.environments.find(
|
|
180
|
+
(e) => e && e.name === environmentName,
|
|
180
181
|
);
|
|
181
|
-
return Array.isArray(
|
|
182
|
+
return Array.isArray(environment?.connections)
|
|
183
|
+
? environment.connections
|
|
184
|
+
: [];
|
|
182
185
|
} catch (error) {
|
|
183
186
|
logger.error(
|
|
184
|
-
`Error getting connections for
|
|
187
|
+
`Error getting connections for environment "${environmentName}" from ${PUBLISHER_CONFIG_NAME}`,
|
|
185
188
|
{ error },
|
|
186
189
|
);
|
|
187
190
|
return [];
|
|
@@ -228,59 +231,59 @@ export const getProcessedPublisherConfig = (
|
|
|
228
231
|
): ProcessedPublisherConfig => {
|
|
229
232
|
const rawConfig = getPublisherConfig(serverRoot);
|
|
230
233
|
|
|
231
|
-
// Ensure
|
|
232
|
-
if (!Array.isArray(rawConfig.
|
|
234
|
+
// Ensure environments is an array
|
|
235
|
+
if (!Array.isArray(rawConfig.environments)) {
|
|
233
236
|
logger.warn(
|
|
234
|
-
`Invalid ${PUBLISHER_CONFIG_NAME}:
|
|
237
|
+
`Invalid ${PUBLISHER_CONFIG_NAME}: the "environments" field must be a JSON array. Using empty array.`,
|
|
235
238
|
);
|
|
236
239
|
return {
|
|
237
240
|
frozenConfig: rawConfig.frozenConfig ?? false,
|
|
238
|
-
|
|
241
|
+
environments: [],
|
|
239
242
|
};
|
|
240
243
|
}
|
|
241
244
|
|
|
242
|
-
// Filter and validate
|
|
243
|
-
const
|
|
244
|
-
for (const
|
|
245
|
-
if (!
|
|
245
|
+
// Filter and validate environments, skipping invalid ones
|
|
246
|
+
const validEnvironments: ProcessedEnvironment[] = [];
|
|
247
|
+
for (const environment of rawConfig.environments) {
|
|
248
|
+
if (!environment || typeof environment !== "object") {
|
|
246
249
|
logger.warn(
|
|
247
|
-
`Invalid
|
|
250
|
+
`Invalid environment in ${PUBLISHER_CONFIG_NAME}: entry must be an object. Skipping.`,
|
|
248
251
|
);
|
|
249
252
|
continue;
|
|
250
253
|
}
|
|
251
254
|
|
|
252
|
-
if (!
|
|
255
|
+
if (!environment.name || typeof environment.name !== "string") {
|
|
253
256
|
logger.warn(
|
|
254
|
-
`Invalid
|
|
255
|
-
{
|
|
257
|
+
`Invalid environment in ${PUBLISHER_CONFIG_NAME}: missing or invalid "name" field. Skipping entry.`,
|
|
258
|
+
{ environment },
|
|
256
259
|
);
|
|
257
260
|
continue;
|
|
258
261
|
}
|
|
259
262
|
|
|
260
|
-
if (!Array.isArray(
|
|
263
|
+
if (!Array.isArray(environment.packages)) {
|
|
261
264
|
logger.warn(
|
|
262
|
-
`Invalid
|
|
265
|
+
`Invalid environment "${environment.name}" in ${PUBLISHER_CONFIG_NAME}: missing or invalid "packages" field (must be an array). Skipping entry.`,
|
|
263
266
|
);
|
|
264
267
|
continue;
|
|
265
268
|
}
|
|
266
269
|
|
|
267
270
|
// Validate packages have required fields
|
|
268
|
-
const validPackages =
|
|
271
|
+
const validPackages = environment.packages.filter((pkg) => {
|
|
269
272
|
if (!pkg || typeof pkg !== "object") {
|
|
270
273
|
logger.warn(
|
|
271
|
-
`Invalid package in
|
|
274
|
+
`Invalid package in environment "${environment.name}": package must be an object. Skipping.`,
|
|
272
275
|
);
|
|
273
276
|
return false;
|
|
274
277
|
}
|
|
275
278
|
if (!pkg.name || typeof pkg.name !== "string") {
|
|
276
279
|
logger.warn(
|
|
277
|
-
`Invalid package in
|
|
280
|
+
`Invalid package in environment "${environment.name}": missing or invalid "name" field. Skipping.`,
|
|
278
281
|
);
|
|
279
282
|
return false;
|
|
280
283
|
}
|
|
281
284
|
if (!pkg.location || typeof pkg.location !== "string") {
|
|
282
285
|
logger.warn(
|
|
283
|
-
`Invalid package "${pkg.name}" in
|
|
286
|
+
`Invalid package "${pkg.name}" in environment "${environment.name}": missing or invalid "location" field. Skipping.`,
|
|
284
287
|
);
|
|
285
288
|
return false;
|
|
286
289
|
}
|
|
@@ -289,22 +292,22 @@ export const getProcessedPublisherConfig = (
|
|
|
289
292
|
|
|
290
293
|
if (validPackages.length === 0) {
|
|
291
294
|
logger.warn(
|
|
292
|
-
`
|
|
295
|
+
`Environment "${environment.name}" has no valid packages. Skipping entry.`,
|
|
293
296
|
);
|
|
294
297
|
continue;
|
|
295
298
|
}
|
|
296
299
|
|
|
297
|
-
|
|
298
|
-
name:
|
|
300
|
+
validEnvironments.push({
|
|
301
|
+
name: environment.name,
|
|
299
302
|
packages: validPackages,
|
|
300
303
|
connections: convertConnectionsToApiConnections(
|
|
301
|
-
|
|
304
|
+
environment.connections || [],
|
|
302
305
|
),
|
|
303
306
|
});
|
|
304
307
|
}
|
|
305
308
|
|
|
306
309
|
return {
|
|
307
310
|
frozenConfig: rawConfig.frozenConfig ?? false,
|
|
308
|
-
|
|
311
|
+
environments: validEnvironments,
|
|
309
312
|
};
|
|
310
313
|
};
|
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
import type { LogMessage } from "@malloydata/malloy";
|
|
2
|
-
import {
|
|
2
|
+
import { EnvironmentStore } from "../service/environment_store";
|
|
3
3
|
|
|
4
4
|
export class CompileController {
|
|
5
|
-
private
|
|
5
|
+
private environmentStore: EnvironmentStore;
|
|
6
6
|
|
|
7
|
-
constructor(
|
|
8
|
-
this.
|
|
7
|
+
constructor(environmentStore: EnvironmentStore) {
|
|
8
|
+
this.environmentStore = environmentStore;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
public async compile(
|
|
12
|
-
|
|
12
|
+
environmentName: string,
|
|
13
13
|
packageName: string,
|
|
14
14
|
modelName: string,
|
|
15
15
|
source: string,
|
|
16
16
|
includeSql: boolean = false,
|
|
17
17
|
): Promise<{ status: string; problems: LogMessage[]; sql?: string }> {
|
|
18
|
-
const
|
|
19
|
-
|
|
18
|
+
const environment = await this.environmentStore.getEnvironment(
|
|
19
|
+
environmentName,
|
|
20
|
+
false,
|
|
21
|
+
);
|
|
22
|
+
const { problems, sql } = await environment.compileSource(
|
|
20
23
|
packageName,
|
|
21
24
|
modelName,
|
|
22
25
|
source,
|
|
@@ -10,8 +10,8 @@ import {
|
|
|
10
10
|
getSchemasForConnection,
|
|
11
11
|
listTablesForSchema,
|
|
12
12
|
} from "../service/db_utils";
|
|
13
|
-
import type {
|
|
14
|
-
import {
|
|
13
|
+
import type { Environment } from "../service/environment";
|
|
14
|
+
import { EnvironmentStore } from "../service/environment_store";
|
|
15
15
|
|
|
16
16
|
type ApiConnection = components["schemas"]["Connection"];
|
|
17
17
|
type ApiConnectionStatus = components["schemas"]["ConnectionStatus"];
|
|
@@ -103,19 +103,19 @@ function validateAdminAuthoredConnection(
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
export class ConnectionController {
|
|
106
|
-
private
|
|
106
|
+
private environmentStore: EnvironmentStore;
|
|
107
107
|
private connectionService: ConnectionService;
|
|
108
|
-
constructor(
|
|
109
|
-
this.
|
|
110
|
-
this.connectionService = new ConnectionService(
|
|
108
|
+
constructor(environmentStore: EnvironmentStore) {
|
|
109
|
+
this.environmentStore = environmentStore;
|
|
110
|
+
this.connectionService = new ConnectionService(environmentStore);
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
/**
|
|
114
114
|
* Gets the appropriate Malloy connection for a given connection name.
|
|
115
|
-
* For DuckDB connections, retrieves from package level; for others, from
|
|
115
|
+
* For DuckDB connections, retrieves from package level; for others, from environment level.
|
|
116
116
|
*/
|
|
117
117
|
private getApiConnectionForLookup(
|
|
118
|
-
|
|
118
|
+
environment: Environment,
|
|
119
119
|
connectionName: string,
|
|
120
120
|
): ApiConnection {
|
|
121
121
|
if (connectionName === "duckdb") {
|
|
@@ -125,35 +125,38 @@ export class ConnectionController {
|
|
|
125
125
|
duckdbConnection: { attachedDatabases: [] },
|
|
126
126
|
};
|
|
127
127
|
}
|
|
128
|
-
return
|
|
128
|
+
return environment.getApiConnection(connectionName);
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
private async getMalloyConnection(
|
|
132
|
-
|
|
132
|
+
environmentName: string,
|
|
133
133
|
connectionName: string,
|
|
134
134
|
packageName?: string,
|
|
135
135
|
): Promise<Connection> {
|
|
136
|
-
const
|
|
136
|
+
const environment = await this.environmentStore.getEnvironment(
|
|
137
|
+
environmentName,
|
|
138
|
+
false,
|
|
139
|
+
);
|
|
137
140
|
|
|
138
141
|
// "duckdb" is the per-package sandbox; its rootDirectory is the
|
|
139
|
-
// package's directory. There is no
|
|
142
|
+
// package's directory. There is no environment-level "duckdb" — the name is
|
|
140
143
|
// reserved at config time. So the lookup is intrinsically per-package
|
|
141
144
|
// and the caller must say which package to use.
|
|
142
145
|
if (connectionName === "duckdb") {
|
|
143
|
-
const packages = await
|
|
146
|
+
const packages = await environment.listPackages();
|
|
144
147
|
if (packages.length === 0) {
|
|
145
|
-
// Fall through to
|
|
148
|
+
// Fall through to environment; this will surface the standard
|
|
146
149
|
// "connection not found" rather than silently inventing one.
|
|
147
|
-
return await
|
|
150
|
+
return await environment.getMalloyConnection(connectionName);
|
|
148
151
|
}
|
|
149
152
|
if (packageName) {
|
|
150
153
|
const known = packages.some((p) => p.name === packageName);
|
|
151
154
|
if (!known) {
|
|
152
155
|
throw new BadRequestError(
|
|
153
|
-
`Package "${packageName}" not found in
|
|
156
|
+
`Package "${packageName}" not found in environment "${environmentName}"`,
|
|
154
157
|
);
|
|
155
158
|
}
|
|
156
|
-
const pkg = await
|
|
159
|
+
const pkg = await environment.getPackage(packageName);
|
|
157
160
|
return await pkg.getMalloyConnection(connectionName);
|
|
158
161
|
}
|
|
159
162
|
if (packages.length === 1) {
|
|
@@ -161,15 +164,15 @@ export class ConnectionController {
|
|
|
161
164
|
if (!onlyPackage) {
|
|
162
165
|
throw new ConnectionError("Package name is undefined");
|
|
163
166
|
}
|
|
164
|
-
const pkg = await
|
|
167
|
+
const pkg = await environment.getPackage(onlyPackage);
|
|
165
168
|
return await pkg.getMalloyConnection(connectionName);
|
|
166
169
|
}
|
|
167
170
|
throw new BadRequestError(
|
|
168
|
-
`Ambiguous "duckdb" connection lookup:
|
|
169
|
-
`Use /
|
|
171
|
+
`Ambiguous "duckdb" connection lookup: environment "${environmentName}" has multiple packages. ` +
|
|
172
|
+
`Use /environments/${environmentName}/packages/{packageName}/connections/duckdb/... to disambiguate.`,
|
|
170
173
|
);
|
|
171
174
|
} else {
|
|
172
|
-
return await
|
|
175
|
+
return await environment.getMalloyConnection(connectionName);
|
|
173
176
|
}
|
|
174
177
|
}
|
|
175
178
|
|
|
@@ -188,7 +191,7 @@ export class ConnectionController {
|
|
|
188
191
|
fetchTableSchema: (
|
|
189
192
|
tableKey: string,
|
|
190
193
|
tablePath: string,
|
|
191
|
-
) => Promise<TableSourceDef | undefined>;
|
|
194
|
+
) => Promise<TableSourceDef | string | undefined>;
|
|
192
195
|
}
|
|
193
196
|
).fetchTableSchema(tableKey, tablePath);
|
|
194
197
|
if (!source) {
|
|
@@ -198,6 +201,7 @@ export class ConnectionController {
|
|
|
198
201
|
if (typeof source === "string") {
|
|
199
202
|
throw new ConnectionError(source);
|
|
200
203
|
}
|
|
204
|
+
|
|
201
205
|
return {
|
|
202
206
|
source: JSON.stringify(source),
|
|
203
207
|
resource: tablePath,
|
|
@@ -223,39 +227,50 @@ export class ConnectionController {
|
|
|
223
227
|
}
|
|
224
228
|
|
|
225
229
|
public async getConnection(
|
|
226
|
-
|
|
230
|
+
environmentName: string,
|
|
227
231
|
connectionName: string,
|
|
228
232
|
): Promise<ApiConnection> {
|
|
229
|
-
if (!
|
|
233
|
+
if (!environmentName || !connectionName) {
|
|
230
234
|
throw new BadRequestError("Connection payload is required");
|
|
231
235
|
}
|
|
232
236
|
// Prefer the in-memory API connection (which was materialized by the
|
|
233
|
-
//
|
|
237
|
+
// environment on load and carries `attributes`). The DB row stores the
|
|
234
238
|
// raw config and FK columns, which aren't the ApiConnection shape.
|
|
235
|
-
const
|
|
236
|
-
|
|
239
|
+
const environment = await this.environmentStore.getEnvironment(
|
|
240
|
+
environmentName,
|
|
241
|
+
false,
|
|
242
|
+
);
|
|
243
|
+
return environment.getApiConnection(connectionName);
|
|
237
244
|
}
|
|
238
245
|
|
|
239
|
-
public async listConnections(
|
|
240
|
-
|
|
241
|
-
|
|
246
|
+
public async listConnections(
|
|
247
|
+
environmentName: string,
|
|
248
|
+
): Promise<ApiConnection[]> {
|
|
249
|
+
const environment = await this.environmentStore.getEnvironment(
|
|
250
|
+
environmentName,
|
|
251
|
+
false,
|
|
252
|
+
);
|
|
253
|
+
return environment.listApiConnections();
|
|
242
254
|
}
|
|
243
255
|
|
|
244
256
|
// Lists schemas (namespaces) available in a connection.
|
|
245
257
|
// For "duckdb", the per-package sandbox, packageName disambiguates which
|
|
246
|
-
// package's DuckDB to browse in a multi-package
|
|
258
|
+
// package's DuckDB to browse in a multi-package environment.
|
|
247
259
|
public async listSchemas(
|
|
248
|
-
|
|
260
|
+
environmentName: string,
|
|
249
261
|
connectionName: string,
|
|
250
262
|
packageName?: string,
|
|
251
263
|
): Promise<ApiSchema[]> {
|
|
252
|
-
const
|
|
264
|
+
const environment = await this.environmentStore.getEnvironment(
|
|
265
|
+
environmentName,
|
|
266
|
+
false,
|
|
267
|
+
);
|
|
253
268
|
const connection = this.getApiConnectionForLookup(
|
|
254
|
-
|
|
269
|
+
environment,
|
|
255
270
|
connectionName,
|
|
256
271
|
);
|
|
257
272
|
const malloyConnection = await this.getMalloyConnection(
|
|
258
|
-
|
|
273
|
+
environmentName,
|
|
259
274
|
connectionName,
|
|
260
275
|
packageName,
|
|
261
276
|
);
|
|
@@ -266,19 +281,22 @@ export class ConnectionController {
|
|
|
266
281
|
// Lists tables available in a schema. For postgres the schema is usually "public".
|
|
267
282
|
// packageName disambiguates per-package "duckdb" lookups (see listSchemas).
|
|
268
283
|
public async listTables(
|
|
269
|
-
|
|
284
|
+
environmentName: string,
|
|
270
285
|
connectionName: string,
|
|
271
286
|
schemaName: string,
|
|
272
287
|
tableNames?: string[],
|
|
273
288
|
packageName?: string,
|
|
274
289
|
): Promise<ApiTable[]> {
|
|
275
|
-
const
|
|
290
|
+
const environment = await this.environmentStore.getEnvironment(
|
|
291
|
+
environmentName,
|
|
292
|
+
false,
|
|
293
|
+
);
|
|
276
294
|
const connection = this.getApiConnectionForLookup(
|
|
277
|
-
|
|
295
|
+
environment,
|
|
278
296
|
connectionName,
|
|
279
297
|
);
|
|
280
298
|
const malloyConnection = await this.getMalloyConnection(
|
|
281
|
-
|
|
299
|
+
environmentName,
|
|
282
300
|
connectionName,
|
|
283
301
|
packageName,
|
|
284
302
|
);
|
|
@@ -292,13 +310,13 @@ export class ConnectionController {
|
|
|
292
310
|
}
|
|
293
311
|
|
|
294
312
|
public async getConnectionSqlSource(
|
|
295
|
-
|
|
313
|
+
environmentName: string,
|
|
296
314
|
connectionName: string,
|
|
297
315
|
sqlStatement: string,
|
|
298
316
|
packageName?: string,
|
|
299
317
|
): Promise<ApiSqlSource> {
|
|
300
318
|
const malloyConnection = await this.getMalloyConnection(
|
|
301
|
-
|
|
319
|
+
environmentName,
|
|
302
320
|
connectionName,
|
|
303
321
|
packageName,
|
|
304
322
|
);
|
|
@@ -329,21 +347,24 @@ export class ConnectionController {
|
|
|
329
347
|
}
|
|
330
348
|
|
|
331
349
|
public async getTable(
|
|
332
|
-
|
|
350
|
+
environmentName: string,
|
|
333
351
|
connectionName: string,
|
|
334
352
|
schemaName: string,
|
|
335
353
|
tablePath: string,
|
|
336
354
|
packageName?: string,
|
|
337
355
|
): Promise<ApiTable> {
|
|
338
356
|
const malloyConnection = await this.getMalloyConnection(
|
|
339
|
-
|
|
357
|
+
environmentName,
|
|
340
358
|
connectionName,
|
|
341
359
|
packageName,
|
|
342
360
|
);
|
|
343
361
|
// Use getApiConnection to get the unwrapped ApiConnection config, consistent with listSchemas and listTables.
|
|
344
|
-
const
|
|
362
|
+
const environment = await this.environmentStore.getEnvironment(
|
|
363
|
+
environmentName,
|
|
364
|
+
false,
|
|
365
|
+
);
|
|
345
366
|
const connection = this.getApiConnectionForLookup(
|
|
346
|
-
|
|
367
|
+
environment,
|
|
347
368
|
connectionName,
|
|
348
369
|
);
|
|
349
370
|
|
|
@@ -415,14 +436,14 @@ export class ConnectionController {
|
|
|
415
436
|
}
|
|
416
437
|
|
|
417
438
|
public async getConnectionQueryData(
|
|
418
|
-
|
|
439
|
+
environmentName: string,
|
|
419
440
|
connectionName: string,
|
|
420
441
|
sqlStatement: string,
|
|
421
442
|
options: string,
|
|
422
443
|
packageName?: string,
|
|
423
444
|
): Promise<ApiQueryData> {
|
|
424
445
|
const malloyConnection = await this.getMalloyConnection(
|
|
425
|
-
|
|
446
|
+
environmentName,
|
|
426
447
|
connectionName,
|
|
427
448
|
packageName,
|
|
428
449
|
);
|
|
@@ -449,13 +470,13 @@ export class ConnectionController {
|
|
|
449
470
|
}
|
|
450
471
|
|
|
451
472
|
public async getConnectionTemporaryTable(
|
|
452
|
-
|
|
473
|
+
environmentName: string,
|
|
453
474
|
connectionName: string,
|
|
454
475
|
sqlStatement: string,
|
|
455
476
|
packageName?: string,
|
|
456
477
|
): Promise<ApiTemporaryTable> {
|
|
457
478
|
const malloyConnection = await this.getMalloyConnection(
|
|
458
|
-
|
|
479
|
+
environmentName,
|
|
459
480
|
connectionName,
|
|
460
481
|
packageName,
|
|
461
482
|
);
|
|
@@ -509,7 +530,7 @@ export class ConnectionController {
|
|
|
509
530
|
}
|
|
510
531
|
|
|
511
532
|
public async addConnection(
|
|
512
|
-
|
|
533
|
+
environmentName: string,
|
|
513
534
|
connectionName: string,
|
|
514
535
|
connectionConfig: ApiConnection,
|
|
515
536
|
): Promise<{ message: string }> {
|
|
@@ -529,11 +550,11 @@ export class ConnectionController {
|
|
|
529
550
|
validateAdminAuthoredConnection(connectionName, connectionConfig);
|
|
530
551
|
|
|
531
552
|
logger.info(
|
|
532
|
-
`Creating connection "${connectionName}" in
|
|
553
|
+
`Creating connection "${connectionName}" in environment "${environmentName}"`,
|
|
533
554
|
);
|
|
534
555
|
|
|
535
556
|
await this.connectionService.addConnection(
|
|
536
|
-
|
|
557
|
+
environmentName,
|
|
537
558
|
connectionName,
|
|
538
559
|
connectionConfig,
|
|
539
560
|
);
|
|
@@ -544,7 +565,7 @@ export class ConnectionController {
|
|
|
544
565
|
}
|
|
545
566
|
|
|
546
567
|
public async updateConnection(
|
|
547
|
-
|
|
568
|
+
environmentName: string,
|
|
548
569
|
connectionName: string,
|
|
549
570
|
connection: Partial<ApiConnection>,
|
|
550
571
|
): Promise<{ message: string }> {
|
|
@@ -556,11 +577,11 @@ export class ConnectionController {
|
|
|
556
577
|
validateAdminAuthoredConnection(connectionName, connection);
|
|
557
578
|
|
|
558
579
|
logger.info(
|
|
559
|
-
`Updating connection "${connectionName}" in
|
|
580
|
+
`Updating connection "${connectionName}" in environment "${environmentName}"`,
|
|
560
581
|
);
|
|
561
582
|
|
|
562
583
|
await this.connectionService.updateConnection(
|
|
563
|
-
|
|
584
|
+
environmentName,
|
|
564
585
|
connectionName,
|
|
565
586
|
connection,
|
|
566
587
|
);
|
|
@@ -571,15 +592,15 @@ export class ConnectionController {
|
|
|
571
592
|
}
|
|
572
593
|
|
|
573
594
|
public async deleteConnection(
|
|
574
|
-
|
|
595
|
+
environmentName: string,
|
|
575
596
|
connectionName: string,
|
|
576
597
|
): Promise<{ message: string }> {
|
|
577
598
|
logger.info(
|
|
578
|
-
`Deleting connection "${connectionName}" from
|
|
599
|
+
`Deleting connection "${connectionName}" from environment "${environmentName}"`,
|
|
579
600
|
);
|
|
580
601
|
|
|
581
602
|
await this.connectionService.deleteConnection(
|
|
582
|
-
|
|
603
|
+
environmentName,
|
|
583
604
|
connectionName,
|
|
584
605
|
);
|
|
585
606
|
|
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
import { components } from "../api";
|
|
2
|
-
import {
|
|
2
|
+
import { EnvironmentStore } from "../service/environment_store";
|
|
3
3
|
|
|
4
4
|
type ApiDatabase = components["schemas"]["Database"];
|
|
5
5
|
|
|
6
6
|
export class DatabaseController {
|
|
7
|
-
private
|
|
7
|
+
private environmentStore: EnvironmentStore;
|
|
8
8
|
|
|
9
|
-
constructor(
|
|
10
|
-
this.
|
|
9
|
+
constructor(environmentStore: EnvironmentStore) {
|
|
10
|
+
this.environmentStore = environmentStore;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
public async listDatabases(
|
|
14
|
-
|
|
14
|
+
environmentName: string,
|
|
15
15
|
packageName: string,
|
|
16
16
|
): Promise<ApiDatabase[]> {
|
|
17
|
-
const
|
|
18
|
-
|
|
17
|
+
const environment = await this.environmentStore.getEnvironment(
|
|
18
|
+
environmentName,
|
|
19
|
+
false,
|
|
20
|
+
);
|
|
21
|
+
const p = await environment.getPackage(packageName, false);
|
|
19
22
|
return p.listDatabases();
|
|
20
23
|
}
|
|
21
24
|
}
|