@malloy-publisher/server 0.0.196-dev → 0.0.196
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +1954 -1318
- package/package.json +1 -1
- package/publisher.config.json +2 -2
- package/src/config.spec.ts +181 -66
- package/src/config.ts +68 -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 +1119 -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 +61 -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/harness/rest_e2e.ts +2 -2
- package/tests/integration/legacy_routes/legacy_routes.integration.spec.ts +259 -0
- 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/duckdb/legacy_schema_migration.test.ts +194 -0
- 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,39 +101,58 @@ export const getPublisherConfig = (serverRoot: string): PublisherConfig => {
|
|
|
101
101
|
);
|
|
102
102
|
return {
|
|
103
103
|
frozenConfig: false,
|
|
104
|
-
|
|
104
|
+
environments: [],
|
|
105
105
|
};
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
// Process environment variables in config values
|
|
109
109
|
const processedConfig = processConfigValue(rawConfig);
|
|
110
110
|
|
|
111
|
+
// TODO: Remove this during projects cleanup
|
|
112
|
+
// Back-compat: the top-level key was renamed `projects` → `environments`.
|
|
113
|
+
// If a config still uses the old key, accept it once with a deprecation
|
|
114
|
+
// warning so existing on-disk configs don't silently parse as empty.
|
|
111
115
|
if (
|
|
112
116
|
processedConfig &&
|
|
113
117
|
typeof processedConfig === "object" &&
|
|
114
|
-
"
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
+
!("environments" in processedConfig) &&
|
|
119
|
+
"projects" in processedConfig
|
|
120
|
+
) {
|
|
121
|
+
logger.warn(
|
|
122
|
+
`${PUBLISHER_CONFIG_NAME} uses deprecated "projects" key; rename to "environments".`,
|
|
123
|
+
);
|
|
124
|
+
(processedConfig as Record<string, unknown>).environments = (
|
|
125
|
+
processedConfig as Record<string, unknown>
|
|
126
|
+
).projects;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (
|
|
130
|
+
processedConfig &&
|
|
131
|
+
typeof processedConfig === "object" &&
|
|
132
|
+
"environments" in processedConfig &&
|
|
133
|
+
processedConfig.environments &&
|
|
134
|
+
typeof processedConfig.environments === "object" &&
|
|
135
|
+
!Array.isArray(processedConfig.environments)
|
|
118
136
|
) {
|
|
119
137
|
logger.error(
|
|
120
|
-
`Invalid ${PUBLISHER_CONFIG_NAME}:
|
|
138
|
+
`Invalid ${PUBLISHER_CONFIG_NAME}: the "environments" field must be a JSON array. Using default empty config.`,
|
|
121
139
|
);
|
|
122
140
|
return {
|
|
123
141
|
frozenConfig: false,
|
|
124
|
-
|
|
142
|
+
environments: [],
|
|
125
143
|
};
|
|
126
144
|
}
|
|
127
145
|
|
|
128
|
-
// Ensure
|
|
129
|
-
let
|
|
146
|
+
// Ensure environments is an array
|
|
147
|
+
let environments: unknown[] = [];
|
|
130
148
|
if (
|
|
131
149
|
processedConfig &&
|
|
132
150
|
typeof processedConfig === "object" &&
|
|
133
|
-
"
|
|
134
|
-
Array.isArray((processedConfig as {
|
|
151
|
+
"environments" in processedConfig &&
|
|
152
|
+
Array.isArray((processedConfig as { environments: unknown }).environments)
|
|
135
153
|
) {
|
|
136
|
-
|
|
154
|
+
environments = (processedConfig as { environments: unknown[] })
|
|
155
|
+
.environments;
|
|
137
156
|
}
|
|
138
157
|
|
|
139
158
|
let frozenConfig = false;
|
|
@@ -149,7 +168,7 @@ export const getPublisherConfig = (serverRoot: string): PublisherConfig => {
|
|
|
149
168
|
|
|
150
169
|
return {
|
|
151
170
|
frozenConfig,
|
|
152
|
-
|
|
171
|
+
environments,
|
|
153
172
|
} as PublisherConfig;
|
|
154
173
|
};
|
|
155
174
|
|
|
@@ -168,20 +187,22 @@ export const isPublisherConfigFrozen = (serverRoot: string) => {
|
|
|
168
187
|
|
|
169
188
|
export const getConnectionsFromPublisherConfig = (
|
|
170
189
|
serverRoot: string,
|
|
171
|
-
|
|
190
|
+
environmentName: string,
|
|
172
191
|
): Connection[] => {
|
|
173
192
|
try {
|
|
174
193
|
const publisherConfig = getPublisherConfig(serverRoot);
|
|
175
|
-
if (!Array.isArray(publisherConfig.
|
|
194
|
+
if (!Array.isArray(publisherConfig.environments)) {
|
|
176
195
|
return [];
|
|
177
196
|
}
|
|
178
|
-
const
|
|
179
|
-
(
|
|
197
|
+
const environment = publisherConfig.environments.find(
|
|
198
|
+
(e) => e && e.name === environmentName,
|
|
180
199
|
);
|
|
181
|
-
return Array.isArray(
|
|
200
|
+
return Array.isArray(environment?.connections)
|
|
201
|
+
? environment.connections
|
|
202
|
+
: [];
|
|
182
203
|
} catch (error) {
|
|
183
204
|
logger.error(
|
|
184
|
-
`Error getting connections for
|
|
205
|
+
`Error getting connections for environment "${environmentName}" from ${PUBLISHER_CONFIG_NAME}`,
|
|
185
206
|
{ error },
|
|
186
207
|
);
|
|
187
208
|
return [];
|
|
@@ -228,59 +249,59 @@ export const getProcessedPublisherConfig = (
|
|
|
228
249
|
): ProcessedPublisherConfig => {
|
|
229
250
|
const rawConfig = getPublisherConfig(serverRoot);
|
|
230
251
|
|
|
231
|
-
// Ensure
|
|
232
|
-
if (!Array.isArray(rawConfig.
|
|
252
|
+
// Ensure environments is an array
|
|
253
|
+
if (!Array.isArray(rawConfig.environments)) {
|
|
233
254
|
logger.warn(
|
|
234
|
-
`Invalid ${PUBLISHER_CONFIG_NAME}:
|
|
255
|
+
`Invalid ${PUBLISHER_CONFIG_NAME}: the "environments" field must be a JSON array. Using empty array.`,
|
|
235
256
|
);
|
|
236
257
|
return {
|
|
237
258
|
frozenConfig: rawConfig.frozenConfig ?? false,
|
|
238
|
-
|
|
259
|
+
environments: [],
|
|
239
260
|
};
|
|
240
261
|
}
|
|
241
262
|
|
|
242
|
-
// Filter and validate
|
|
243
|
-
const
|
|
244
|
-
for (const
|
|
245
|
-
if (!
|
|
263
|
+
// Filter and validate environments, skipping invalid ones
|
|
264
|
+
const validEnvironments: ProcessedEnvironment[] = [];
|
|
265
|
+
for (const environment of rawConfig.environments) {
|
|
266
|
+
if (!environment || typeof environment !== "object") {
|
|
246
267
|
logger.warn(
|
|
247
|
-
`Invalid
|
|
268
|
+
`Invalid environment in ${PUBLISHER_CONFIG_NAME}: entry must be an object. Skipping.`,
|
|
248
269
|
);
|
|
249
270
|
continue;
|
|
250
271
|
}
|
|
251
272
|
|
|
252
|
-
if (!
|
|
273
|
+
if (!environment.name || typeof environment.name !== "string") {
|
|
253
274
|
logger.warn(
|
|
254
|
-
`Invalid
|
|
255
|
-
{
|
|
275
|
+
`Invalid environment in ${PUBLISHER_CONFIG_NAME}: missing or invalid "name" field. Skipping entry.`,
|
|
276
|
+
{ environment },
|
|
256
277
|
);
|
|
257
278
|
continue;
|
|
258
279
|
}
|
|
259
280
|
|
|
260
|
-
if (!Array.isArray(
|
|
281
|
+
if (!Array.isArray(environment.packages)) {
|
|
261
282
|
logger.warn(
|
|
262
|
-
`Invalid
|
|
283
|
+
`Invalid environment "${environment.name}" in ${PUBLISHER_CONFIG_NAME}: missing or invalid "packages" field (must be an array). Skipping entry.`,
|
|
263
284
|
);
|
|
264
285
|
continue;
|
|
265
286
|
}
|
|
266
287
|
|
|
267
288
|
// Validate packages have required fields
|
|
268
|
-
const validPackages =
|
|
289
|
+
const validPackages = environment.packages.filter((pkg) => {
|
|
269
290
|
if (!pkg || typeof pkg !== "object") {
|
|
270
291
|
logger.warn(
|
|
271
|
-
`Invalid package in
|
|
292
|
+
`Invalid package in environment "${environment.name}": package must be an object. Skipping.`,
|
|
272
293
|
);
|
|
273
294
|
return false;
|
|
274
295
|
}
|
|
275
296
|
if (!pkg.name || typeof pkg.name !== "string") {
|
|
276
297
|
logger.warn(
|
|
277
|
-
`Invalid package in
|
|
298
|
+
`Invalid package in environment "${environment.name}": missing or invalid "name" field. Skipping.`,
|
|
278
299
|
);
|
|
279
300
|
return false;
|
|
280
301
|
}
|
|
281
302
|
if (!pkg.location || typeof pkg.location !== "string") {
|
|
282
303
|
logger.warn(
|
|
283
|
-
`Invalid package "${pkg.name}" in
|
|
304
|
+
`Invalid package "${pkg.name}" in environment "${environment.name}": missing or invalid "location" field. Skipping.`,
|
|
284
305
|
);
|
|
285
306
|
return false;
|
|
286
307
|
}
|
|
@@ -289,22 +310,22 @@ export const getProcessedPublisherConfig = (
|
|
|
289
310
|
|
|
290
311
|
if (validPackages.length === 0) {
|
|
291
312
|
logger.warn(
|
|
292
|
-
`
|
|
313
|
+
`Environment "${environment.name}" has no valid packages. Skipping entry.`,
|
|
293
314
|
);
|
|
294
315
|
continue;
|
|
295
316
|
}
|
|
296
317
|
|
|
297
|
-
|
|
298
|
-
name:
|
|
318
|
+
validEnvironments.push({
|
|
319
|
+
name: environment.name,
|
|
299
320
|
packages: validPackages,
|
|
300
321
|
connections: convertConnectionsToApiConnections(
|
|
301
|
-
|
|
322
|
+
environment.connections || [],
|
|
302
323
|
),
|
|
303
324
|
});
|
|
304
325
|
}
|
|
305
326
|
|
|
306
327
|
return {
|
|
307
328
|
frozenConfig: rawConfig.frozenConfig ?? false,
|
|
308
|
-
|
|
329
|
+
environments: validEnvironments,
|
|
309
330
|
};
|
|
310
331
|
};
|
|
@@ -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
|
}
|