@malloy-publisher/server 0.0.195 → 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.
Files changed (104) hide show
  1. package/dist/app/api-doc.yaml +213 -214
  2. package/dist/app/assets/EnvironmentPage-1j6QDWAy.js +1 -0
  3. package/dist/app/assets/HomePage-DMop21VG.js +1 -0
  4. package/dist/app/assets/MainPage-BbE8ETz1.js +2 -0
  5. package/dist/app/assets/ModelPage-D2jvfe3t.js +1 -0
  6. package/dist/app/assets/PackagePage-BbnhGoD3.js +1 -0
  7. package/dist/app/assets/{RouteError-DefbDO7F.js → RouteError-D3LGEZ3i.js} +1 -1
  8. package/dist/app/assets/WorkbookPage-DttVIj4u.js +1 -0
  9. package/dist/app/assets/{core-BrfQApxh.es-DnvCX4oH.js → core-w79IMXAG.es-Bd0UlzOL.js} +1 -1
  10. package/dist/app/assets/{index-Bu0ub036.js → index-5K9YjIxF.js} +117 -117
  11. package/dist/app/assets/{index-CkzK3JIl.js → index-C513UodQ.js} +1 -1
  12. package/dist/app/assets/{index-CoA6HIGS.js → index-DIgzgp69.js} +1 -1
  13. package/dist/app/assets/{index.umd-B6Ms2PpL.js → index.umd-BMeMPq_9.js} +1 -1
  14. package/dist/app/index.html +1 -1
  15. package/dist/server.mjs +1976 -1322
  16. package/package.json +2 -2
  17. package/publisher.config.json +2 -2
  18. package/src/config.spec.ts +181 -66
  19. package/src/config.ts +68 -47
  20. package/src/controller/compile.controller.ts +10 -7
  21. package/src/controller/connection.controller.ts +79 -58
  22. package/src/controller/database.controller.ts +10 -7
  23. package/src/controller/manifest.controller.ts +23 -14
  24. package/src/controller/materialization.controller.ts +14 -14
  25. package/src/controller/model.controller.ts +35 -20
  26. package/src/controller/package.controller.ts +83 -49
  27. package/src/controller/query.controller.ts +11 -8
  28. package/src/controller/watch-mode.controller.ts +35 -29
  29. package/src/errors.ts +2 -2
  30. package/src/mcp/error_messages.ts +2 -2
  31. package/src/mcp/handler_utils.ts +23 -20
  32. package/src/mcp/mcp_constants.ts +1 -1
  33. package/src/mcp/prompts/handlers.ts +3 -3
  34. package/src/mcp/prompts/prompt_service.ts +5 -5
  35. package/src/mcp/prompts/utils.ts +12 -12
  36. package/src/mcp/resource_metadata.ts +3 -3
  37. package/src/mcp/resources/environment_resource.ts +187 -0
  38. package/src/mcp/resources/model_resource.ts +19 -17
  39. package/src/mcp/resources/notebook_resource.ts +13 -13
  40. package/src/mcp/resources/package_resource.ts +30 -27
  41. package/src/mcp/resources/query_resource.ts +15 -10
  42. package/src/mcp/resources/source_resource.ts +10 -10
  43. package/src/mcp/resources/view_resource.ts +11 -11
  44. package/src/mcp/server.ts +16 -14
  45. package/src/mcp/tools/discovery_tools.ts +67 -49
  46. package/src/mcp/tools/execute_query_tool.ts +14 -14
  47. package/src/server-old.ts +1119 -0
  48. package/src/server.ts +191 -159
  49. package/src/service/connection.spec.ts +158 -133
  50. package/src/service/connection.ts +42 -39
  51. package/src/service/connection_config.spec.ts +13 -11
  52. package/src/service/connection_config.ts +28 -19
  53. package/src/service/connection_service.spec.ts +63 -43
  54. package/src/service/connection_service.ts +106 -89
  55. package/src/service/{project.ts → environment.ts} +92 -77
  56. package/src/service/{project_compile.spec.ts → environment_compile.spec.ts} +1 -1
  57. package/src/service/{project_store.spec.ts → environment_store.spec.ts} +99 -83
  58. package/src/service/{project_store.ts → environment_store.ts} +373 -327
  59. package/src/service/manifest_service.spec.ts +15 -15
  60. package/src/service/manifest_service.ts +26 -21
  61. package/src/service/materialization_service.spec.ts +93 -59
  62. package/src/service/materialization_service.ts +71 -62
  63. package/src/service/materialized_table_gc.spec.ts +15 -15
  64. package/src/service/materialized_table_gc.ts +3 -3
  65. package/src/service/model.ts +4 -4
  66. package/src/service/package.spec.ts +2 -2
  67. package/src/service/package.ts +23 -21
  68. package/src/service/resolve_environment.ts +15 -0
  69. package/src/storage/DatabaseInterface.ts +34 -25
  70. package/src/storage/StorageManager.mock.ts +3 -3
  71. package/src/storage/StorageManager.ts +64 -28
  72. package/src/storage/duckdb/ConnectionRepository.ts +13 -11
  73. package/src/storage/duckdb/DuckDBConnection.ts +1 -1
  74. package/src/storage/duckdb/DuckDBManifestStore.ts +6 -6
  75. package/src/storage/duckdb/DuckDBRepository.ts +47 -47
  76. package/src/storage/duckdb/{ProjectRepository.ts → EnvironmentRepository.ts} +35 -35
  77. package/src/storage/duckdb/ManifestRepository.ts +21 -20
  78. package/src/storage/duckdb/MaterializationRepository.ts +31 -28
  79. package/src/storage/duckdb/PackageRepository.ts +11 -11
  80. package/src/storage/duckdb/manifest_store.spec.ts +2 -2
  81. package/src/storage/duckdb/schema.ts +61 -20
  82. package/src/storage/ducklake/DuckLakeManifestStore.ts +20 -11
  83. package/tests/fixtures/publisher.config.json +1 -1
  84. package/tests/harness/e2e.ts +1 -1
  85. package/tests/harness/mcp_test_setup.ts +12 -24
  86. package/tests/harness/mocks.ts +10 -8
  87. package/tests/harness/rest_e2e.ts +2 -2
  88. package/tests/integration/legacy_routes/legacy_routes.integration.spec.ts +259 -0
  89. package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +4 -4
  90. package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +28 -49
  91. package/tests/integration/mcp/mcp_resource.integration.spec.ts +39 -47
  92. package/tests/integration/mcp/mcp_transport.integration.spec.ts +1 -1
  93. package/tests/unit/duckdb/attached_databases.test.ts +51 -33
  94. package/tests/unit/duckdb/legacy_schema_migration.test.ts +194 -0
  95. package/tests/unit/ducklake/ducklake.test.ts +24 -22
  96. package/tests/unit/mcp/prompt_happy.test.ts +8 -8
  97. package/dist/app/assets/HomePage-DbZS0N7G.js +0 -1
  98. package/dist/app/assets/MainPage-CBuWkbmr.js +0 -2
  99. package/dist/app/assets/ModelPage-Bt37smot.js +0 -1
  100. package/dist/app/assets/PackagePage-DLZe50WG.js +0 -1
  101. package/dist/app/assets/ProjectPage-FQTEPXP4.js +0 -1
  102. package/dist/app/assets/WorkbookPage-CkAo16ar.js +0 -1
  103. package/src/mcp/resources/project_resource.ts +0 -184
  104. 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 Project = {
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
- projects: Project[];
29
+ environments: Environment[];
30
30
  };
31
31
 
32
- export type ProcessedProject = {
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
- projects: ProcessedProject[];
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
- projects: [],
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
- projects: [],
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
- "projects" in processedConfig &&
115
- processedConfig.projects &&
116
- typeof processedConfig.projects === "object" &&
117
- !Array.isArray(processedConfig.projects)
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}: projects must be an array. Using default empty config.`,
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
- projects: [],
142
+ environments: [],
125
143
  };
126
144
  }
127
145
 
128
- // Ensure projects is an array
129
- let projects: unknown[] = [];
146
+ // Ensure environments is an array
147
+ let environments: unknown[] = [];
130
148
  if (
131
149
  processedConfig &&
132
150
  typeof processedConfig === "object" &&
133
- "projects" in processedConfig &&
134
- Array.isArray((processedConfig as { projects: unknown }).projects)
151
+ "environments" in processedConfig &&
152
+ Array.isArray((processedConfig as { environments: unknown }).environments)
135
153
  ) {
136
- projects = (processedConfig as { projects: unknown[] }).projects;
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
- projects,
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
- projectName: string,
190
+ environmentName: string,
172
191
  ): Connection[] => {
173
192
  try {
174
193
  const publisherConfig = getPublisherConfig(serverRoot);
175
- if (!Array.isArray(publisherConfig.projects)) {
194
+ if (!Array.isArray(publisherConfig.environments)) {
176
195
  return [];
177
196
  }
178
- const project = publisherConfig.projects.find(
179
- (p) => p && p.name === projectName,
197
+ const environment = publisherConfig.environments.find(
198
+ (e) => e && e.name === environmentName,
180
199
  );
181
- return Array.isArray(project?.connections) ? project.connections : [];
200
+ return Array.isArray(environment?.connections)
201
+ ? environment.connections
202
+ : [];
182
203
  } catch (error) {
183
204
  logger.error(
184
- `Error getting connections for project "${projectName}" from ${PUBLISHER_CONFIG_NAME}`,
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 projects is an array
232
- if (!Array.isArray(rawConfig.projects)) {
252
+ // Ensure environments is an array
253
+ if (!Array.isArray(rawConfig.environments)) {
233
254
  logger.warn(
234
- `Invalid ${PUBLISHER_CONFIG_NAME}: projects must be an array. Using empty array.`,
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
- projects: [],
259
+ environments: [],
239
260
  };
240
261
  }
241
262
 
242
- // Filter and validate projects, skipping invalid ones
243
- const validProjects: ProcessedProject[] = [];
244
- for (const project of rawConfig.projects) {
245
- if (!project || typeof project !== "object") {
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 project in ${PUBLISHER_CONFIG_NAME}: project must be an object. Skipping.`,
268
+ `Invalid environment in ${PUBLISHER_CONFIG_NAME}: entry must be an object. Skipping.`,
248
269
  );
249
270
  continue;
250
271
  }
251
272
 
252
- if (!project.name || typeof project.name !== "string") {
273
+ if (!environment.name || typeof environment.name !== "string") {
253
274
  logger.warn(
254
- `Invalid project in ${PUBLISHER_CONFIG_NAME}: missing or invalid "name" field. Skipping project.`,
255
- { project },
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(project.packages)) {
281
+ if (!Array.isArray(environment.packages)) {
261
282
  logger.warn(
262
- `Invalid project "${project.name}" in ${PUBLISHER_CONFIG_NAME}: missing or invalid "packages" field (must be an array). Skipping project.`,
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 = project.packages.filter((pkg) => {
289
+ const validPackages = environment.packages.filter((pkg) => {
269
290
  if (!pkg || typeof pkg !== "object") {
270
291
  logger.warn(
271
- `Invalid package in project "${project.name}": package must be an object. Skipping.`,
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 project "${project.name}": missing or invalid "name" field. Skipping.`,
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 project "${project.name}": missing or invalid "location" field. Skipping.`,
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
- `Project "${project.name}" has no valid packages. Skipping project.`,
313
+ `Environment "${environment.name}" has no valid packages. Skipping entry.`,
293
314
  );
294
315
  continue;
295
316
  }
296
317
 
297
- validProjects.push({
298
- name: project.name,
318
+ validEnvironments.push({
319
+ name: environment.name,
299
320
  packages: validPackages,
300
321
  connections: convertConnectionsToApiConnections(
301
- project.connections || [],
322
+ environment.connections || [],
302
323
  ),
303
324
  });
304
325
  }
305
326
 
306
327
  return {
307
328
  frozenConfig: rawConfig.frozenConfig ?? false,
308
- projects: validProjects,
329
+ environments: validEnvironments,
309
330
  };
310
331
  };
@@ -1,22 +1,25 @@
1
1
  import type { LogMessage } from "@malloydata/malloy";
2
- import { ProjectStore } from "../service/project_store";
2
+ import { EnvironmentStore } from "../service/environment_store";
3
3
 
4
4
  export class CompileController {
5
- private projectStore: ProjectStore;
5
+ private environmentStore: EnvironmentStore;
6
6
 
7
- constructor(projectStore: ProjectStore) {
8
- this.projectStore = projectStore;
7
+ constructor(environmentStore: EnvironmentStore) {
8
+ this.environmentStore = environmentStore;
9
9
  }
10
10
 
11
11
  public async compile(
12
- projectName: string,
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 project = await this.projectStore.getProject(projectName, false);
19
- const { problems, sql } = await project.compileSource(
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 { Project } from "../service/project";
14
- import { ProjectStore } from "../service/project_store";
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 projectStore: ProjectStore;
106
+ private environmentStore: EnvironmentStore;
107
107
  private connectionService: ConnectionService;
108
- constructor(projectStore: ProjectStore) {
109
- this.projectStore = projectStore;
110
- this.connectionService = new ConnectionService(projectStore);
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 project level.
115
+ * For DuckDB connections, retrieves from package level; for others, from environment level.
116
116
  */
117
117
  private getApiConnectionForLookup(
118
- project: Project,
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 project.getApiConnection(connectionName);
128
+ return environment.getApiConnection(connectionName);
129
129
  }
130
130
 
131
131
  private async getMalloyConnection(
132
- projectName: string,
132
+ environmentName: string,
133
133
  connectionName: string,
134
134
  packageName?: string,
135
135
  ): Promise<Connection> {
136
- const project = await this.projectStore.getProject(projectName, false);
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 project-level "duckdb" — the name is
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 project.listPackages();
146
+ const packages = await environment.listPackages();
144
147
  if (packages.length === 0) {
145
- // Fall through to project; this will surface the standard
148
+ // Fall through to environment; this will surface the standard
146
149
  // "connection not found" rather than silently inventing one.
147
- return await project.getMalloyConnection(connectionName);
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 project "${projectName}"`,
156
+ `Package "${packageName}" not found in environment "${environmentName}"`,
154
157
  );
155
158
  }
156
- const pkg = await project.getPackage(packageName);
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 project.getPackage(onlyPackage);
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: project "${projectName}" has multiple packages. ` +
169
- `Use /projects/${projectName}/packages/{packageName}/connections/duckdb/... to disambiguate.`,
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 project.getMalloyConnection(connectionName);
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
- projectName: string,
230
+ environmentName: string,
227
231
  connectionName: string,
228
232
  ): Promise<ApiConnection> {
229
- if (!projectName || !connectionName) {
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
- // project on load and carries `attributes`). The DB row stores the
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 project = await this.projectStore.getProject(projectName, false);
236
- return project.getApiConnection(connectionName);
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(projectName: string): Promise<ApiConnection[]> {
240
- const project = await this.projectStore.getProject(projectName, false);
241
- return project.listApiConnections();
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 project.
258
+ // package's DuckDB to browse in a multi-package environment.
247
259
  public async listSchemas(
248
- projectName: string,
260
+ environmentName: string,
249
261
  connectionName: string,
250
262
  packageName?: string,
251
263
  ): Promise<ApiSchema[]> {
252
- const project = await this.projectStore.getProject(projectName, false);
264
+ const environment = await this.environmentStore.getEnvironment(
265
+ environmentName,
266
+ false,
267
+ );
253
268
  const connection = this.getApiConnectionForLookup(
254
- project,
269
+ environment,
255
270
  connectionName,
256
271
  );
257
272
  const malloyConnection = await this.getMalloyConnection(
258
- projectName,
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
- projectName: string,
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 project = await this.projectStore.getProject(projectName, false);
290
+ const environment = await this.environmentStore.getEnvironment(
291
+ environmentName,
292
+ false,
293
+ );
276
294
  const connection = this.getApiConnectionForLookup(
277
- project,
295
+ environment,
278
296
  connectionName,
279
297
  );
280
298
  const malloyConnection = await this.getMalloyConnection(
281
- projectName,
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
- projectName: string,
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
- projectName,
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
- projectName: string,
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
- projectName,
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 project = await this.projectStore.getProject(projectName, false);
362
+ const environment = await this.environmentStore.getEnvironment(
363
+ environmentName,
364
+ false,
365
+ );
345
366
  const connection = this.getApiConnectionForLookup(
346
- project,
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
- projectName: string,
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
- projectName,
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
- projectName: string,
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
- projectName,
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
- projectName: string,
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 project "${projectName}"`,
553
+ `Creating connection "${connectionName}" in environment "${environmentName}"`,
533
554
  );
534
555
 
535
556
  await this.connectionService.addConnection(
536
- projectName,
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
- projectName: string,
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 project "${projectName}"`,
580
+ `Updating connection "${connectionName}" in environment "${environmentName}"`,
560
581
  );
561
582
 
562
583
  await this.connectionService.updateConnection(
563
- projectName,
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
- projectName: string,
595
+ environmentName: string,
575
596
  connectionName: string,
576
597
  ): Promise<{ message: string }> {
577
598
  logger.info(
578
- `Deleting connection "${connectionName}" from project "${projectName}"`,
599
+ `Deleting connection "${connectionName}" from environment "${environmentName}"`,
579
600
  );
580
601
 
581
602
  await this.connectionService.deleteConnection(
582
- projectName,
603
+ environmentName,
583
604
  connectionName,
584
605
  );
585
606
 
@@ -1,21 +1,24 @@
1
1
  import { components } from "../api";
2
- import { ProjectStore } from "../service/project_store";
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 projectStore: ProjectStore;
7
+ private environmentStore: EnvironmentStore;
8
8
 
9
- constructor(projectStore: ProjectStore) {
10
- this.projectStore = projectStore;
9
+ constructor(environmentStore: EnvironmentStore) {
10
+ this.environmentStore = environmentStore;
11
11
  }
12
12
 
13
13
  public async listDatabases(
14
- projectName: string,
14
+ environmentName: string,
15
15
  packageName: string,
16
16
  ): Promise<ApiDatabase[]> {
17
- const project = await this.projectStore.getProject(projectName, false);
18
- const p = await project.getPackage(packageName, false);
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
  }