@malloy-publisher/server 0.0.195 → 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.
Files changed (100) 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 +1352 -1310
  16. package/package.json +2 -2
  17. package/publisher.config.json +2 -2
  18. package/src/config.spec.ts +74 -66
  19. package/src/config.ts +50 -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.ts +175 -159
  48. package/src/service/connection.spec.ts +158 -133
  49. package/src/service/connection.ts +42 -39
  50. package/src/service/connection_config.spec.ts +13 -11
  51. package/src/service/connection_config.ts +28 -19
  52. package/src/service/connection_service.spec.ts +63 -43
  53. package/src/service/connection_service.ts +106 -89
  54. package/src/service/{project.ts → environment.ts} +92 -77
  55. package/src/service/{project_compile.spec.ts → environment_compile.spec.ts} +1 -1
  56. package/src/service/{project_store.spec.ts → environment_store.spec.ts} +99 -83
  57. package/src/service/{project_store.ts → environment_store.ts} +373 -327
  58. package/src/service/manifest_service.spec.ts +15 -15
  59. package/src/service/manifest_service.ts +26 -21
  60. package/src/service/materialization_service.spec.ts +93 -59
  61. package/src/service/materialization_service.ts +71 -62
  62. package/src/service/materialized_table_gc.spec.ts +15 -15
  63. package/src/service/materialized_table_gc.ts +3 -3
  64. package/src/service/model.ts +4 -4
  65. package/src/service/package.spec.ts +2 -2
  66. package/src/service/package.ts +23 -21
  67. package/src/service/resolve_environment.ts +15 -0
  68. package/src/storage/DatabaseInterface.ts +34 -25
  69. package/src/storage/StorageManager.mock.ts +3 -3
  70. package/src/storage/StorageManager.ts +64 -28
  71. package/src/storage/duckdb/ConnectionRepository.ts +13 -11
  72. package/src/storage/duckdb/DuckDBConnection.ts +1 -1
  73. package/src/storage/duckdb/DuckDBManifestStore.ts +6 -6
  74. package/src/storage/duckdb/DuckDBRepository.ts +47 -47
  75. package/src/storage/duckdb/{ProjectRepository.ts → EnvironmentRepository.ts} +35 -35
  76. package/src/storage/duckdb/ManifestRepository.ts +21 -20
  77. package/src/storage/duckdb/MaterializationRepository.ts +31 -28
  78. package/src/storage/duckdb/PackageRepository.ts +11 -11
  79. package/src/storage/duckdb/manifest_store.spec.ts +2 -2
  80. package/src/storage/duckdb/schema.ts +20 -20
  81. package/src/storage/ducklake/DuckLakeManifestStore.ts +20 -11
  82. package/tests/fixtures/publisher.config.json +1 -1
  83. package/tests/harness/e2e.ts +1 -1
  84. package/tests/harness/mcp_test_setup.ts +12 -24
  85. package/tests/harness/mocks.ts +10 -8
  86. package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +4 -4
  87. package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +28 -49
  88. package/tests/integration/mcp/mcp_resource.integration.spec.ts +39 -47
  89. package/tests/integration/mcp/mcp_transport.integration.spec.ts +1 -1
  90. package/tests/unit/duckdb/attached_databases.test.ts +51 -33
  91. package/tests/unit/ducklake/ducklake.test.ts +24 -22
  92. package/tests/unit/mcp/prompt_happy.test.ts +8 -8
  93. package/dist/app/assets/HomePage-DbZS0N7G.js +0 -1
  94. package/dist/app/assets/MainPage-CBuWkbmr.js +0 -2
  95. package/dist/app/assets/ModelPage-Bt37smot.js +0 -1
  96. package/dist/app/assets/PackagePage-DLZe50WG.js +0 -1
  97. package/dist/app/assets/ProjectPage-FQTEPXP4.js +0 -1
  98. package/dist/app/assets/WorkbookPage-CkAo16ar.js +0 -1
  99. package/src/mcp/resources/project_resource.ts +0 -184
  100. package/src/service/resolve_project.ts +0 -13
@@ -6,9 +6,9 @@ import "@malloydata/db-duckdb/native";
6
6
  import {
7
7
  Connection,
8
8
  ConnectionRuntime,
9
+ contextOverlay,
9
10
  EmptyURLReader,
10
11
  FixedConnectionMap,
11
- contextOverlay,
12
12
  MalloyConfig,
13
13
  SourceDef,
14
14
  } from "@malloydata/malloy";
@@ -32,8 +32,8 @@ type ApiNotebook = components["schemas"]["Notebook"];
32
32
  export type ApiPackage = components["schemas"]["Package"];
33
33
  type ApiColumn = components["schemas"]["Column"];
34
34
  type ApiTableDescription = components["schemas"]["TableDescription"];
35
- // A thunk lets callers pass a live reference to the *current* project
36
- // MalloyConfig so the package wrapper resolves project connections against the
35
+ // A thunk lets callers pass a live reference to the *current* environment
36
+ // MalloyConfig so the package wrapper resolves environment connections against the
37
37
  // generation that's active at lookup time, not the one that was current when
38
38
  // the package was first loaded.
39
39
  type PackageConnectionInput =
@@ -43,7 +43,7 @@ type PackageConnectionInput =
43
43
 
44
44
  const ENABLE_LIST_MODEL_COMPILATION = true;
45
45
  export class Package {
46
- private projectName: string;
46
+ private environmentName: string;
47
47
  private packageName: string;
48
48
  private packageMetadata: ApiPackage;
49
49
  private databases: ApiDatabase[];
@@ -60,7 +60,7 @@ export class Package {
60
60
  );
61
61
 
62
62
  constructor(
63
- projectName: string,
63
+ environmentName: string,
64
64
  packageName: string,
65
65
  packagePath: string,
66
66
  packageMetadata: ApiPackage,
@@ -68,7 +68,7 @@ export class Package {
68
68
  models: Map<string, Model>,
69
69
  malloyConfig: MalloyConfig = new MalloyConfig({ connections: {} }),
70
70
  ) {
71
- this.projectName = projectName;
71
+ this.environmentName = environmentName;
72
72
  this.packageName = packageName;
73
73
  this.packagePath = packagePath;
74
74
  this.packageMetadata = packageMetadata;
@@ -78,10 +78,10 @@ export class Package {
78
78
  }
79
79
 
80
80
  static async create(
81
- projectName: string,
81
+ environmentName: string,
82
82
  packageName: string,
83
83
  packagePath: string,
84
- projectMalloyConfig: PackageConnectionInput,
84
+ environmentMalloyConfig: PackageConnectionInput,
85
85
  ): Promise<Package> {
86
86
  const startTime = performance.now();
87
87
  await Package.validatePackageManifestExistsOrThrowError(packagePath);
@@ -100,7 +100,7 @@ export class Package {
100
100
  packageConfigTime - manifestValidationTime,
101
101
  ),
102
102
  });
103
- packageConfig.resource = `${API_PREFIX}/projects/${projectName}/packages/${packageName}`;
103
+ packageConfig.resource = `${API_PREFIX}/environments/${environmentName}/packages/${packageName}`;
104
104
 
105
105
  const databases = await Package.readDatabases(packagePath);
106
106
  const databasesTime = performance.now();
@@ -111,9 +111,9 @@ export class Package {
111
111
  });
112
112
  const malloyConfig = Package.buildPackageMalloyConfig(
113
113
  packagePath,
114
- typeof projectMalloyConfig === "function"
115
- ? projectMalloyConfig
116
- : () => Package.toMalloyConfig(projectMalloyConfig),
114
+ typeof environmentMalloyConfig === "function"
115
+ ? environmentMalloyConfig
116
+ : () => Package.toMalloyConfig(environmentMalloyConfig),
117
117
  );
118
118
 
119
119
  const models = await Package.loadModels(
@@ -162,7 +162,7 @@ export class Package {
162
162
  duration: formatDuration(executionTime),
163
163
  });
164
164
  return new Package(
165
- projectName,
165
+ environmentName,
166
166
  packageName,
167
167
  packagePath,
168
168
  packageConfig,
@@ -283,7 +283,7 @@ export class Package {
283
283
  }
284
284
  }
285
285
  return {
286
- projectName: this.projectName,
286
+ environmentName: this.environmentName,
287
287
  path: modelPath,
288
288
  packageName: this.packageName,
289
289
  error,
@@ -305,7 +305,7 @@ export class Package {
305
305
  error = this.models.get(modelPath)?.getNotebookError();
306
306
  }
307
307
  return {
308
- projectName: this.projectName,
308
+ environmentName: this.environmentName,
309
309
  packageName: this.packageName,
310
310
  path: modelPath,
311
311
  error: error?.message,
@@ -330,7 +330,7 @@ export class Package {
330
330
 
331
331
  private static buildPackageMalloyConfig(
332
332
  packagePath: string,
333
- getProjectMalloyConfig: () => MalloyConfig,
333
+ getEnvironmentMalloyConfig: () => MalloyConfig,
334
334
  ): MalloyConfig {
335
335
  const malloyConfig = new MalloyConfig(
336
336
  {
@@ -351,10 +351,12 @@ export class Package {
351
351
  if (!name || name === "duckdb") {
352
352
  return base.lookupConnection(name);
353
353
  }
354
- // Resolve against the *current* project MalloyConfig so a
355
- // connection-generation swap on Project propagates without a
354
+ // Resolve against the *current* environment MalloyConfig so a
355
+ // connection-generation swap on Environment propagates without a
356
356
  // package reload.
357
- return getProjectMalloyConfig().connections.lookupConnection(name);
357
+ return getEnvironmentMalloyConfig().connections.lookupConnection(
358
+ name,
359
+ );
358
360
  },
359
361
  }));
360
362
 
@@ -495,8 +497,8 @@ export class Package {
495
497
  this.packageName = name;
496
498
  }
497
499
 
498
- public setProjectName(projectName: string) {
499
- this.projectName = projectName;
500
+ public setEnvironmentName(environmentName: string) {
501
+ this.environmentName = environmentName;
500
502
  }
501
503
 
502
504
  public setPackageMetadata(packageMetadata: ApiPackage) {
@@ -0,0 +1,15 @@
1
+ import { EnvironmentNotFoundError } from "../errors";
2
+ import { ResourceRepository } from "../storage/DatabaseInterface";
3
+
4
+ export async function resolveEnvironmentId(
5
+ repository: ResourceRepository,
6
+ environmentName: string,
7
+ ): Promise<string> {
8
+ const dbEnvironment = await repository.getEnvironmentByName(environmentName);
9
+ if (!dbEnvironment) {
10
+ throw new EnvironmentNotFoundError(
11
+ `Environment '${environmentName}' not found`,
12
+ );
13
+ }
14
+ return dbEnvironment.id;
15
+ }
@@ -9,20 +9,26 @@ export interface DatabaseConnection {
9
9
  }
10
10
 
11
11
  export interface ResourceRepository {
12
- // Projects
13
- listProjects(): Promise<Project[]>;
14
- getProjectById(id: string): Promise<Project | null>;
15
- getProjectByName(name: string): Promise<Project | null>;
16
- createProject(
17
- project: Omit<Project, "id" | "createdAt" | "updatedAt">,
18
- ): Promise<Project>;
19
- updateProject(id: string, updates: Partial<Project>): Promise<Project>;
20
- deleteProject(id: string): Promise<void>;
12
+ // Environments
13
+ listEnvironments(): Promise<Environment[]>;
14
+ getEnvironmentById(id: string): Promise<Environment | null>;
15
+ getEnvironmentByName(name: string): Promise<Environment | null>;
16
+ createEnvironment(
17
+ environment: Omit<Environment, "id" | "createdAt" | "updatedAt">,
18
+ ): Promise<Environment>;
19
+ updateEnvironment(
20
+ id: string,
21
+ updates: Partial<Environment>,
22
+ ): Promise<Environment>;
23
+ deleteEnvironment(id: string): Promise<void>;
21
24
 
22
25
  // Packages
23
- listPackages(projectId: string): Promise<Package[]>;
26
+ listPackages(environmentId: string): Promise<Package[]>;
24
27
  getPackageById(id: string): Promise<Package | null>;
25
- getPackageByName(projectId: string, name: string): Promise<Package | null>;
28
+ getPackageByName(
29
+ environmentId: string,
30
+ name: string,
31
+ ): Promise<Package | null>;
26
32
  createPackage(
27
33
  pkg: Omit<Package, "id" | "createdAt" | "updatedAt">,
28
34
  ): Promise<Package>;
@@ -30,10 +36,10 @@ export interface ResourceRepository {
30
36
  deletePackage(id: string): Promise<void>;
31
37
 
32
38
  // Connections
33
- listConnections(projectId: string): Promise<Connection[]>;
39
+ listConnections(environmentId: string): Promise<Connection[]>;
34
40
  getConnectionById(id: string): Promise<Connection | null>;
35
41
  getConnectionByName(
36
- projectId: string,
42
+ environmentId: string,
37
43
  name: string,
38
44
  ): Promise<Connection | null>;
39
45
  createConnection(
@@ -47,17 +53,17 @@ export interface ResourceRepository {
47
53
 
48
54
  // Materializations
49
55
  listMaterializations(
50
- projectId: string,
56
+ environmentId: string,
51
57
  packageName: string,
52
58
  options?: { limit?: number; offset?: number },
53
59
  ): Promise<Materialization[]>;
54
60
  getMaterializationById(id: string): Promise<Materialization | null>;
55
61
  getActiveMaterialization(
56
- projectId: string,
62
+ environmentId: string,
57
63
  packageName: string,
58
64
  ): Promise<Materialization | null>;
59
65
  createMaterialization(
60
- projectId: string,
66
+ environmentId: string,
61
67
  packageName: string,
62
68
  status?: MaterializationStatus,
63
69
  metadata?: Record<string, unknown> | null,
@@ -75,7 +81,7 @@ export interface ResourceRepository {
75
81
  deleteMaterialization(id: string): Promise<void>;
76
82
  // Build Manifests
77
83
  listManifestEntries(
78
- projectId: string,
84
+ environmentId: string,
79
85
  packageName: string,
80
86
  ): Promise<ManifestEntry[]>;
81
87
  upsertManifestEntry(
@@ -84,7 +90,7 @@ export interface ResourceRepository {
84
90
  deleteManifestEntry(id: string): Promise<void>;
85
91
  }
86
92
 
87
- export interface Project {
93
+ export interface Environment {
88
94
  id: string;
89
95
  name: string;
90
96
  path: string;
@@ -96,7 +102,7 @@ export interface Project {
96
102
 
97
103
  export interface Package {
98
104
  id: string;
99
- projectId: string;
105
+ environmentId: string;
100
106
  name: string;
101
107
  description?: string;
102
108
  manifestPath: string;
@@ -107,7 +113,7 @@ export interface Package {
107
113
 
108
114
  export interface Connection {
109
115
  id: string;
110
- projectId: string;
116
+ environmentId: string;
111
117
  name: string;
112
118
  type: "bigquery" | "postgres" | "duckdb" | "mysql" | "snowflake" | "trino";
113
119
  config: Record<string, unknown>;
@@ -124,7 +130,7 @@ export type MaterializationStatus =
124
130
 
125
131
  export interface Materialization {
126
132
  id: string;
127
- projectId: string;
133
+ environmentId: string;
128
134
  packageName: string;
129
135
  status: MaterializationStatus;
130
136
  startedAt: Date | null;
@@ -137,7 +143,7 @@ export interface Materialization {
137
143
 
138
144
  export interface ManifestEntry {
139
145
  id: string;
140
- projectId: string;
146
+ environmentId: string;
141
147
  packageName: string;
142
148
  buildId: string;
143
149
  tableName: string;
@@ -163,9 +169,12 @@ export interface BuildManifest {
163
169
  * orchestrated mode swaps in a DuckLakeManifestStore.
164
170
  */
165
171
  export interface ManifestStore {
166
- getManifest(projectId: string, packageName: string): Promise<BuildManifest>;
172
+ getManifest(
173
+ environmentId: string,
174
+ packageName: string,
175
+ ): Promise<BuildManifest>;
167
176
  writeEntry(
168
- projectId: string,
177
+ environmentId: string,
169
178
  packageName: string,
170
179
  buildId: string,
171
180
  tableName: string,
@@ -174,7 +183,7 @@ export interface ManifestStore {
174
183
  ): Promise<void>;
175
184
  deleteEntry(id: string): Promise<void>;
176
185
  listEntries(
177
- projectId: string,
186
+ environmentId: string,
178
187
  packageName: string,
179
188
  ): Promise<ManifestEntry[]>;
180
189
  }
@@ -7,12 +7,12 @@ export class StorageManager {
7
7
 
8
8
  getRepository() {
9
9
  return {
10
- listProjects: async (): Promise<unknown[]> => [],
11
- createProject: async (data: MockData): Promise<MockData> => ({
10
+ listEnvironments: async (): Promise<unknown[]> => [],
11
+ createEnvironment: async (data: MockData): Promise<MockData> => ({
12
12
  id: "test-id",
13
13
  ...data,
14
14
  }),
15
- updateProject: async (
15
+ updateEnvironment: async (
16
16
  id: string,
17
17
  data: MockData,
18
18
  ): Promise<MockData> => ({
@@ -1,3 +1,4 @@
1
+ import * as crypto from "crypto";
1
2
  import { logger } from "../logger";
2
3
  import {
3
4
  DatabaseConnection,
@@ -42,10 +43,23 @@ function escapeSQL(value: string): string {
42
43
  return value.replace(/'/g, "''");
43
44
  }
44
45
 
46
+ function configKey(c: DuckLakeManifestConfig): string {
47
+ return `${c.catalogUrl}|${c.dataPath}`;
48
+ }
49
+
50
+ function catalogNameForConfig(c: DuckLakeManifestConfig): string {
51
+ const hash = crypto
52
+ .createHash("sha256")
53
+ .update(configKey(c))
54
+ .digest("hex")
55
+ .slice(0, 8);
56
+ return `manifest_lake_${hash}`;
57
+ }
58
+
45
59
  /**
46
- * Manages the storage backend (DuckDB, Postgres, etc.) and per-project
47
- * manifest stores. Projects without `materializationStorage` config use
48
- * the default DuckDB manifest store. Projects with the config get a
60
+ * Manages the storage backend (DuckDB, Postgres, etc.) and per-environment
61
+ * manifest stores. Environments without `materializationStorage` config use
62
+ * the default DuckDB manifest store. Environments with the config get a
49
63
  * DuckLake-backed store attached lazily on first access.
50
64
  */
51
65
  export class StorageManager {
@@ -54,11 +68,15 @@ export class StorageManager {
54
68
  private repository: ResourceRepository | null = null;
55
69
  private defaultManifestStore: ManifestStore | null = null;
56
70
 
57
- /** Per-project DuckLake manifest stores, keyed by projectId. */
58
- private projectManifestStores = new Map<string, ManifestStore>();
71
+ /** Per-environment DuckLake manifest stores, keyed by environmentId. */
72
+ private environmentManifestStores = new Map<string, ManifestStore>();
59
73
 
60
- /** Tracks which catalogs have been attached to avoid duplicate ATTACHes. */
61
- private attachedCatalogs = new Set<string>();
74
+ /**
75
+ * Tracks attached DuckLake catalogs as `configKey -> catalogName`. Each
76
+ * unique materializationStorage config gets its own ATTACHment under a
77
+ * deterministic catalog name, so multiple configs can coexist on one worker.
78
+ */
79
+ private attachedCatalogs = new Map<string, string>();
62
80
 
63
81
  private config: StorageConfig;
64
82
 
@@ -104,33 +122,45 @@ export class StorageManager {
104
122
  }
105
123
 
106
124
  /**
107
- * Lazily initializes a DuckLake manifest store for a project.
108
- * The DuckLake catalog is attached to the shared DuckDB connection
109
- * and persists for the lifetime of the process.
125
+ * Lazily initializes a DuckLake manifest store for an environment.
126
+ *
127
+ * One shared catalog per materializationStorage config: every environment
128
+ * pointing at the same (catalogUrl, dataPath) shares one `build_manifests`
129
+ * table inside it, partitioned by `environment_id` (set to the environment's name
130
+ * so it's stable across worker replicas — required for cross-pod manifest
131
+ * visibility in orchestrated mode). Different configs (e.g. different
132
+ * orgs) attach as separate catalogs under distinct deterministic aliases.
110
133
  */
111
- async initializeDuckLakeForProject(
112
- projectId: string,
134
+ async initializeDuckLakeForEnvironment(
135
+ environmentId: string,
136
+ environmentName: string,
113
137
  config: DuckLakeManifestConfig,
114
138
  ): Promise<void> {
115
139
  if (!this.duckDbConnection) {
116
140
  throw new Error("Storage not initialized. Call initialize() first.");
117
141
  }
118
142
 
119
- const catalogName = `manifest_lake_${projectId.replace(/[^a-zA-Z0-9_]/g, "_")}`;
120
-
121
- if (!this.attachedCatalogs.has(catalogName)) {
143
+ const key = configKey(config);
144
+ let catalogName = this.attachedCatalogs.get(key);
145
+ if (!catalogName) {
146
+ // Catalog name derived from the config so multiple configs can coexist as
147
+ // separate ATTACHments without colliding on the name.
148
+ catalogName = catalogNameForConfig(config);
122
149
  await this.attachDuckLakeCatalog(config, catalogName);
150
+ this.attachedCatalogs.set(key, catalogName);
123
151
  }
124
152
 
125
153
  const store = new DuckLakeManifestStore(
126
154
  this.duckDbConnection,
127
155
  catalogName,
156
+ environmentName,
128
157
  );
129
158
  await store.bootstrapSchema();
130
159
 
131
- this.projectManifestStores.set(projectId, store);
132
- logger.info("DuckLake manifest store initialized for project", {
133
- projectId,
160
+ this.environmentManifestStores.set(environmentId, store);
161
+ logger.info("DuckLake manifest store initialized for environment", {
162
+ environmentId,
163
+ environmentName,
134
164
  catalogName,
135
165
  });
136
166
  }
@@ -155,7 +185,14 @@ export class StorageManager {
155
185
  config.dataPath.startsWith("s3://");
156
186
 
157
187
  let attachCmd = `ATTACH 'ducklake:${escapedCatalogUrl}' AS ${catalogName}`;
158
- const attachOpts: string[] = [`DATA_PATH '${escapedDataPath}'`];
188
+ const attachOpts: string[] = [
189
+ `DATA_PATH '${escapedDataPath}'`,
190
+ // The manifest table is small relational metadata (one row per build).
191
+ // Set a high inlining limit so writes always land transactionally in
192
+ // the postgres catalog rather than as parquet files in object storage,
193
+ // sidestepping object-storage auth issues entirely for this path.
194
+ "DATA_INLINING_ROW_LIMIT 100000",
195
+ ];
159
196
  if (isCloudStorage) {
160
197
  attachOpts.push("OVERRIDE_DATA_PATH true");
161
198
  }
@@ -163,8 +200,6 @@ export class StorageManager {
163
200
 
164
201
  logger.info(`Attaching DuckLake manifest catalog: ${attachCmd}`);
165
202
  await connection.run(attachCmd);
166
-
167
- this.attachedCatalogs.add(catalogName);
168
203
  }
169
204
 
170
205
  getRepository(): ResourceRepository {
@@ -175,15 +210,16 @@ export class StorageManager {
175
210
  }
176
211
 
177
212
  /**
178
- * Returns the manifest store for a project. If the project has a
213
+ * Returns the manifest store for an environment. If the environment has a
179
214
  * DuckLake store configured, returns that; otherwise returns the
180
215
  * default DuckDB-backed store.
181
216
  */
182
- getManifestStore(projectId?: string): ManifestStore {
183
- if (projectId) {
184
- const projectStore = this.projectManifestStores.get(projectId);
185
- if (projectStore) {
186
- return projectStore;
217
+ getManifestStore(environmentId?: string): ManifestStore {
218
+ if (environmentId) {
219
+ const environmentStore =
220
+ this.environmentManifestStores.get(environmentId);
221
+ if (environmentStore) {
222
+ return environmentStore;
187
223
  }
188
224
  }
189
225
  if (!this.defaultManifestStore) {
@@ -199,7 +235,7 @@ export class StorageManager {
199
235
  this.duckDbConnection = null;
200
236
  this.repository = null;
201
237
  this.defaultManifestStore = null;
202
- this.projectManifestStores.clear();
238
+ this.environmentManifestStores.clear();
203
239
  this.attachedCatalogs.clear();
204
240
  }
205
241
  }
@@ -12,11 +12,11 @@ export class ConnectionRepository {
12
12
  return new Date();
13
13
  }
14
14
 
15
- async listConnections(projectId: string): Promise<Connection[]> {
15
+ async listConnections(environmentId: string): Promise<Connection[]> {
16
16
  try {
17
17
  const rows = await this.db.all<Record<string, unknown>>(
18
- "SELECT * FROM connections WHERE project_id = ? ORDER BY name",
19
- [projectId],
18
+ "SELECT * FROM connections WHERE environment_id = ? ORDER BY name",
19
+ [environmentId],
20
20
  );
21
21
  return rows.map(this.mapToConnection);
22
22
  } catch (err: unknown) {
@@ -35,12 +35,12 @@ export class ConnectionRepository {
35
35
  }
36
36
 
37
37
  async getConnectionByName(
38
- projectId: string,
38
+ environmentId: string,
39
39
  name: string,
40
40
  ): Promise<Connection | null> {
41
41
  const row = await this.db.get<Record<string, unknown>>(
42
- "SELECT * FROM connections WHERE project_id = ? AND name = ?",
43
- [projectId, name],
42
+ "SELECT * FROM connections WHERE environment_id = ? AND name = ?",
43
+ [environmentId, name],
44
44
  );
45
45
  return row ? this.mapToConnection(row) : null;
46
46
  }
@@ -55,11 +55,11 @@ export class ConnectionRepository {
55
55
  const configJson = JSON.stringify(connection.config);
56
56
 
57
57
  await this.db.run(
58
- `INSERT INTO connections (id, project_id, name, type, config, created_at, updated_at)
58
+ `INSERT INTO connections (id, environment_id, name, type, config, created_at, updated_at)
59
59
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
60
60
  [
61
61
  id,
62
- connection.projectId,
62
+ connection.environmentId,
63
63
  connection.name,
64
64
  connection.type,
65
65
  configJson,
@@ -123,14 +123,16 @@ export class ConnectionRepository {
123
123
  await this.db.run("DELETE FROM connections WHERE id = ?", [id]);
124
124
  }
125
125
 
126
- async deleteConnectionsByProjectId(id: string): Promise<void> {
127
- await this.db.run("DELETE FROM connections WHERE project_id = ?", [id]);
126
+ async deleteConnectionsByEnvironmentId(id: string): Promise<void> {
127
+ await this.db.run("DELETE FROM connections WHERE environment_id = ?", [
128
+ id,
129
+ ]);
128
130
  }
129
131
 
130
132
  private mapToConnection(row: Record<string, unknown>): Connection {
131
133
  return {
132
134
  id: row.id as string,
133
- projectId: row.project_id as string,
135
+ environmentId: row.environment_id as string,
134
136
  name: row.name as string,
135
137
  type: row.type as Connection["type"],
136
138
  config: JSON.parse(row.config as string),
@@ -93,7 +93,7 @@ export class DuckDBConnection implements DatabaseConnection {
93
93
  return this.mutex.runExclusive(async () => {
94
94
  return new Promise<boolean>((resolve) => {
95
95
  this.connection!.all(
96
- "SELECT name FROM sqlite_master WHERE type='table' AND name='projects'",
96
+ "SELECT name FROM sqlite_master WHERE type='table' AND name='environments'",
97
97
  (err, rows) => {
98
98
  if (err) {
99
99
  resolve(false);
@@ -22,11 +22,11 @@ export class DuckDBManifestStore implements ManifestStore {
22
22
  * reference has no manifest entry (e.g. before the first materialization).
23
23
  */
24
24
  async getManifest(
25
- projectId: string,
25
+ environmentId: string,
26
26
  packageName: string,
27
27
  ): Promise<BuildManifest> {
28
28
  const entries = await this.repository.listManifestEntries(
29
- projectId,
29
+ environmentId,
30
30
  packageName,
31
31
  );
32
32
  const manifest: BuildManifest = {
@@ -40,7 +40,7 @@ export class DuckDBManifestStore implements ManifestStore {
40
40
  }
41
41
 
42
42
  async writeEntry(
43
- projectId: string,
43
+ environmentId: string,
44
44
  packageName: string,
45
45
  buildId: string,
46
46
  tableName: string,
@@ -48,7 +48,7 @@ export class DuckDBManifestStore implements ManifestStore {
48
48
  connectionName: string,
49
49
  ): Promise<void> {
50
50
  await this.repository.upsertManifestEntry({
51
- projectId,
51
+ environmentId,
52
52
  packageName,
53
53
  buildId,
54
54
  tableName,
@@ -62,9 +62,9 @@ export class DuckDBManifestStore implements ManifestStore {
62
62
  }
63
63
 
64
64
  async listEntries(
65
- projectId: string,
65
+ environmentId: string,
66
66
  packageName: string,
67
67
  ): Promise<ManifestEntry[]> {
68
- return this.repository.listManifestEntries(projectId, packageName);
68
+ return this.repository.listManifestEntries(environmentId, packageName);
69
69
  }
70
70
  }