@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.
Files changed (103) 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 +1954 -1318
  16. package/package.json +1 -1
  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 -85
  58. package/src/service/{project_store.ts → environment_store.ts} +368 -326
  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 +2 -2
  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 +24 -23
  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 +14 -14
  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 +1 -1
  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 +27 -48
  91. package/tests/integration/mcp/mcp_resource.integration.spec.ts +26 -35
  92. package/tests/unit/duckdb/attached_databases.test.ts +51 -33
  93. package/tests/unit/duckdb/legacy_schema_migration.test.ts +194 -0
  94. package/tests/unit/ducklake/ducklake.test.ts +24 -22
  95. package/tests/unit/mcp/prompt_happy.test.ts +8 -8
  96. package/dist/app/assets/HomePage-DbZS0N7G.js +0 -1
  97. package/dist/app/assets/MainPage-CBuWkbmr.js +0 -2
  98. package/dist/app/assets/ModelPage-Bt37smot.js +0 -1
  99. package/dist/app/assets/PackagePage-DLZe50WG.js +0 -1
  100. package/dist/app/assets/ProjectPage-FQTEPXP4.js +0 -1
  101. package/dist/app/assets/WorkbookPage-CkAo16ar.js +0 -1
  102. package/src/mcp/resources/project_resource.ts +0 -184
  103. package/src/service/resolve_project.ts +0 -13
@@ -1,73 +1,73 @@
1
1
  import {
2
2
  Connection,
3
+ Environment,
3
4
  ManifestEntry,
4
5
  Materialization,
5
6
  MaterializationStatus,
6
7
  Package,
7
- Project,
8
8
  ResourceRepository,
9
9
  } from "../DatabaseInterface";
10
10
  import { ConnectionRepository } from "./ConnectionRepository";
11
11
  import { DuckDBConnection } from "./DuckDBConnection";
12
+ import { EnvironmentRepository } from "./EnvironmentRepository";
12
13
  import { ManifestRepository } from "./ManifestRepository";
13
14
  import { MaterializationRepository } from "./MaterializationRepository";
14
15
  import { PackageRepository } from "./PackageRepository";
15
- import { ProjectRepository } from "./ProjectRepository";
16
16
 
17
17
  export class DuckDBRepository implements ResourceRepository {
18
- private projectRepo: ProjectRepository;
18
+ private environmentRepo: EnvironmentRepository;
19
19
  private packageRepo: PackageRepository;
20
20
  private connectionRepo: ConnectionRepository;
21
21
  private materializationRepo: MaterializationRepository;
22
22
  private manifestRepo: ManifestRepository;
23
23
 
24
24
  constructor(public db: DuckDBConnection) {
25
- this.projectRepo = new ProjectRepository(db);
25
+ this.environmentRepo = new EnvironmentRepository(db);
26
26
  this.packageRepo = new PackageRepository(db);
27
27
  this.connectionRepo = new ConnectionRepository(db);
28
28
  this.materializationRepo = new MaterializationRepository(db);
29
29
  this.manifestRepo = new ManifestRepository(db);
30
30
  }
31
31
 
32
- // ==================== PROJECTS ====================
32
+ // ==================== ENVIRONMENTS ====================
33
33
 
34
- async listProjects(): Promise<Project[]> {
35
- return this.projectRepo.listProjects();
34
+ async listEnvironments(): Promise<Environment[]> {
35
+ return this.environmentRepo.listEnvironments();
36
36
  }
37
37
 
38
- async getProjectById(id: string): Promise<Project | null> {
39
- return this.projectRepo.getProjectById(id);
38
+ async getEnvironmentById(id: string): Promise<Environment | null> {
39
+ return this.environmentRepo.getEnvironmentById(id);
40
40
  }
41
41
 
42
- async getProjectByName(name: string): Promise<Project | null> {
43
- return this.projectRepo.getProjectByName(name);
42
+ async getEnvironmentByName(name: string): Promise<Environment | null> {
43
+ return this.environmentRepo.getEnvironmentByName(name);
44
44
  }
45
45
 
46
- async createProject(
47
- project: Omit<Project, "id" | "createdAt" | "updatedAt">,
48
- ): Promise<Project> {
49
- return this.projectRepo.createProject(project);
46
+ async createEnvironment(
47
+ environment: Omit<Environment, "id" | "createdAt" | "updatedAt">,
48
+ ): Promise<Environment> {
49
+ return this.environmentRepo.createEnvironment(environment);
50
50
  }
51
51
 
52
- async updateProject(
52
+ async updateEnvironment(
53
53
  id: string,
54
- updates: Partial<Project>,
55
- ): Promise<Project> {
56
- return this.projectRepo.updateProject(id, updates);
54
+ updates: Partial<Environment>,
55
+ ): Promise<Environment> {
56
+ return this.environmentRepo.updateEnvironment(id, updates);
57
57
  }
58
58
 
59
- async deleteProject(id: string): Promise<void> {
60
- await this.manifestRepo.deleteEntriesByProjectId(id);
61
- await this.materializationRepo.deleteByProjectId(id);
62
- await this.connectionRepo.deleteConnectionsByProjectId(id);
63
- await this.packageRepo.deletePackagesByProjectId(id);
64
- await this.projectRepo.deleteProject(id);
59
+ async deleteEnvironment(id: string): Promise<void> {
60
+ await this.manifestRepo.deleteEntriesByEnvironmentId(id);
61
+ await this.materializationRepo.deleteByEnvironmentId(id);
62
+ await this.connectionRepo.deleteConnectionsByEnvironmentId(id);
63
+ await this.packageRepo.deletePackagesByEnvironmentId(id);
64
+ await this.environmentRepo.deleteEnvironment(id);
65
65
  }
66
66
 
67
67
  // ==================== PACKAGES ====================
68
68
 
69
- async listPackages(projectId: string): Promise<Package[]> {
70
- return this.packageRepo.listPackages(projectId);
69
+ async listPackages(environmentId: string): Promise<Package[]> {
70
+ return this.packageRepo.listPackages(environmentId);
71
71
  }
72
72
 
73
73
  async getPackageById(id: string): Promise<Package | null> {
@@ -75,10 +75,10 @@ export class DuckDBRepository implements ResourceRepository {
75
75
  }
76
76
 
77
77
  async getPackageByName(
78
- projectId: string,
78
+ environmentId: string,
79
79
  name: string,
80
80
  ): Promise<Package | null> {
81
- return this.packageRepo.getPackageByName(projectId, name);
81
+ return this.packageRepo.getPackageByName(environmentId, name);
82
82
  }
83
83
 
84
84
  async createPackage(
@@ -98,25 +98,25 @@ export class DuckDBRepository implements ResourceRepository {
98
98
  const pkg = await this.packageRepo.getPackageById(id);
99
99
  if (pkg) {
100
100
  await this.manifestRepo.deleteEntriesByPackage(
101
- pkg.projectId,
101
+ pkg.environmentId,
102
102
  pkg.name,
103
103
  );
104
104
  await this.materializationRepo.deleteByPackage(
105
- pkg.projectId,
105
+ pkg.environmentId,
106
106
  pkg.name,
107
107
  );
108
108
  }
109
109
  await this.packageRepo.deletePackage(id);
110
110
  }
111
111
 
112
- async deletePackagesByProjectId(id: string): Promise<void> {
113
- return this.packageRepo.deletePackagesByProjectId(id);
112
+ async deletePackagesByEnvironmentId(id: string): Promise<void> {
113
+ return this.packageRepo.deletePackagesByEnvironmentId(id);
114
114
  }
115
115
 
116
116
  // ==================== CONNECTIONS ====================
117
117
 
118
- async listConnections(projectId: string): Promise<Connection[]> {
119
- return this.connectionRepo.listConnections(projectId);
118
+ async listConnections(environmentId: string): Promise<Connection[]> {
119
+ return this.connectionRepo.listConnections(environmentId);
120
120
  }
121
121
 
122
122
  async getConnectionById(id: string): Promise<Connection | null> {
@@ -124,10 +124,10 @@ export class DuckDBRepository implements ResourceRepository {
124
124
  }
125
125
 
126
126
  async getConnectionByName(
127
- projectId: string,
127
+ environmentId: string,
128
128
  name: string,
129
129
  ): Promise<Connection | null> {
130
- return this.connectionRepo.getConnectionByName(projectId, name);
130
+ return this.connectionRepo.getConnectionByName(environmentId, name);
131
131
  }
132
132
 
133
133
  async createConnection(
@@ -147,18 +147,18 @@ export class DuckDBRepository implements ResourceRepository {
147
147
  return this.connectionRepo.deleteConnection(id);
148
148
  }
149
149
 
150
- async deleteConnectionsByProjectId(id: string): Promise<void> {
151
- return this.connectionRepo.deleteConnectionsByProjectId(id);
150
+ async deleteConnectionsByEnvironmentId(id: string): Promise<void> {
151
+ return this.connectionRepo.deleteConnectionsByEnvironmentId(id);
152
152
  }
153
153
 
154
154
  // ==================== MATERIALIZATIONS ====================
155
155
 
156
156
  async listMaterializations(
157
- projectId: string,
157
+ environmentId: string,
158
158
  packageName: string,
159
159
  options?: { limit?: number; offset?: number },
160
160
  ): Promise<Materialization[]> {
161
- return this.materializationRepo.list(projectId, packageName, options);
161
+ return this.materializationRepo.list(environmentId, packageName, options);
162
162
  }
163
163
 
164
164
  async getMaterializationById(id: string): Promise<Materialization | null> {
@@ -166,20 +166,20 @@ export class DuckDBRepository implements ResourceRepository {
166
166
  }
167
167
 
168
168
  async getActiveMaterialization(
169
- projectId: string,
169
+ environmentId: string,
170
170
  packageName: string,
171
171
  ): Promise<Materialization | null> {
172
- return this.materializationRepo.getActive(projectId, packageName);
172
+ return this.materializationRepo.getActive(environmentId, packageName);
173
173
  }
174
174
 
175
175
  async createMaterialization(
176
- projectId: string,
176
+ environmentId: string,
177
177
  packageName: string,
178
178
  status: MaterializationStatus = "PENDING",
179
179
  metadata: Record<string, unknown> | null = null,
180
180
  ): Promise<Materialization> {
181
181
  return this.materializationRepo.create(
182
- projectId,
182
+ environmentId,
183
183
  packageName,
184
184
  status,
185
185
  metadata,
@@ -206,10 +206,10 @@ export class DuckDBRepository implements ResourceRepository {
206
206
  // ==================== BUILD MANIFESTS ====================
207
207
 
208
208
  async listManifestEntries(
209
- projectId: string,
209
+ environmentId: string,
210
210
  packageName: string,
211
211
  ): Promise<ManifestEntry[]> {
212
- return this.manifestRepo.listEntries(projectId, packageName);
212
+ return this.manifestRepo.listEntries(environmentId, packageName);
213
213
  }
214
214
 
215
215
  async upsertManifestEntry(
@@ -1,7 +1,7 @@
1
- import { Project } from "../DatabaseInterface";
1
+ import { Environment } from "../DatabaseInterface";
2
2
  import { DuckDBConnection } from "./DuckDBConnection";
3
3
 
4
- export class ProjectRepository {
4
+ export class EnvironmentRepository {
5
5
  constructor(private db: DuckDBConnection) {}
6
6
 
7
7
  private generateId(): string {
@@ -12,85 +12,85 @@ export class ProjectRepository {
12
12
  return new Date();
13
13
  }
14
14
 
15
- async listProjects(): Promise<Project[]> {
15
+ async listEnvironments(): Promise<Environment[]> {
16
16
  const rows = await this.db.all<Record<string, unknown>>(
17
- "SELECT * FROM projects ORDER BY name",
17
+ "SELECT * FROM environments ORDER BY name",
18
18
  );
19
- return rows.map(this.mapToProject);
19
+ return rows.map(this.mapToEnvironment);
20
20
  }
21
21
 
22
- async getProjectById(id: string): Promise<Project | null> {
22
+ async getEnvironmentById(id: string): Promise<Environment | null> {
23
23
  const row = await this.db.get<Record<string, unknown>>(
24
- "SELECT * FROM projects WHERE id = ?",
24
+ "SELECT * FROM environments WHERE id = ?",
25
25
  [id],
26
26
  );
27
- return row ? this.mapToProject(row) : null;
27
+ return row ? this.mapToEnvironment(row) : null;
28
28
  }
29
29
 
30
- async getProjectByName(name: string): Promise<Project | null> {
30
+ async getEnvironmentByName(name: string): Promise<Environment | null> {
31
31
  const row = await this.db.get<Record<string, unknown>>(
32
- "SELECT * FROM projects WHERE name = ?",
32
+ "SELECT * FROM environments WHERE name = ?",
33
33
  [name],
34
34
  );
35
- return row ? this.mapToProject(row) : null;
35
+ return row ? this.mapToEnvironment(row) : null;
36
36
  }
37
37
 
38
- async createProject(
39
- project: Omit<Project, "id" | "createdAt" | "updatedAt">,
40
- ): Promise<Project> {
38
+ async createEnvironment(
39
+ environment: Omit<Environment, "id" | "createdAt" | "updatedAt">,
40
+ ): Promise<Environment> {
41
41
  const id = this.generateId();
42
42
  const now = this.now();
43
43
 
44
44
  const params = [
45
45
  id,
46
- project.name,
47
- project.path,
48
- project.description || null,
49
- project.metadata ? JSON.stringify(project.metadata) : null,
46
+ environment.name,
47
+ environment.path,
48
+ environment.description || null,
49
+ environment.metadata ? JSON.stringify(environment.metadata) : null,
50
50
  now.toISOString(),
51
51
  now.toISOString(),
52
52
  ];
53
53
 
54
54
  try {
55
55
  await this.db.run(
56
- `INSERT INTO projects (id, name, path, description, metadata, created_at, updated_at)
56
+ `INSERT INTO environments (id, name, path, description, metadata, created_at, updated_at)
57
57
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
58
58
  params,
59
59
  );
60
60
 
61
61
  return {
62
62
  id,
63
- ...project,
63
+ ...environment,
64
64
  createdAt: now,
65
65
  updatedAt: now,
66
66
  };
67
67
  } catch (err: unknown) {
68
68
  const error = err as Error;
69
- // If unique constraint violation, return existing project
69
+ // If unique constraint violation, return existing environment
70
70
  if (
71
71
  error.message?.includes("UNIQUE") ||
72
72
  error.message?.includes("Constraint")
73
73
  ) {
74
74
  const existing = await this.db.get<Record<string, unknown>>(
75
- "SELECT * FROM projects WHERE name = ?",
76
- [project.name],
75
+ "SELECT * FROM environments WHERE name = ?",
76
+ [environment.name],
77
77
  );
78
78
  if (existing) {
79
- console.log("Returning existing project");
80
- return this.mapToProject(existing);
79
+ console.log("Returning existing environment");
80
+ return this.mapToEnvironment(existing);
81
81
  }
82
82
  }
83
83
  throw error;
84
84
  }
85
85
  }
86
86
 
87
- async updateProject(
87
+ async updateEnvironment(
88
88
  id: string,
89
- updates: Partial<Project>,
90
- ): Promise<Project> {
91
- const existing = await this.getProjectById(id);
89
+ updates: Partial<Environment>,
90
+ ): Promise<Environment> {
91
+ const existing = await this.getEnvironmentById(id);
92
92
  if (!existing) {
93
- throw new Error(`Project with id ${id} not found`);
93
+ throw new Error(`Environment with id ${id} not found`);
94
94
  }
95
95
 
96
96
  const now = this.now();
@@ -121,18 +121,18 @@ export class ProjectRepository {
121
121
  params.push(id);
122
122
 
123
123
  await this.db.run(
124
- `UPDATE projects SET ${setClauses.join(", ")} WHERE id = ?`,
124
+ `UPDATE environments SET ${setClauses.join(", ")} WHERE id = ?`,
125
125
  params,
126
126
  );
127
127
 
128
- return this.getProjectById(id) as Promise<Project>;
128
+ return this.getEnvironmentById(id) as Promise<Environment>;
129
129
  }
130
130
 
131
- async deleteProject(id: string): Promise<void> {
132
- await this.db.run("DELETE FROM projects WHERE id = ?", [id]);
131
+ async deleteEnvironment(id: string): Promise<void> {
132
+ await this.db.run("DELETE FROM environments WHERE id = ?", [id]);
133
133
  }
134
134
 
135
- private mapToProject(row: Record<string, unknown>): Project {
135
+ private mapToEnvironment(row: Record<string, unknown>): Environment {
136
136
  return {
137
137
  id: row.id as string,
138
138
  name: row.name as string,
@@ -6,7 +6,7 @@ import { DuckDBConnection } from "./DuckDBConnection";
6
6
  *
7
7
  * A build manifest records the materialized table produced by a specific build
8
8
  * of a package, linking it back to the Malloy source and connection that
9
- * generated it. Manifests are keyed by (project, package, build) and enable
9
+ * generated it. Manifests are keyed by (environment, package, build) and enable
10
10
  * downstream consumers to discover which physical tables correspond to a given
11
11
  * package version.
12
12
  */
@@ -19,32 +19,32 @@ export class ManifestRepository {
19
19
 
20
20
  /** Returns all manifest entries for a package, most recent first. */
21
21
  async listEntries(
22
- projectId: string,
22
+ environmentId: string,
23
23
  packageName: string,
24
24
  ): Promise<ManifestEntry[]> {
25
25
  const rows = await this.db.all<Record<string, unknown>>(
26
- "SELECT * FROM build_manifests WHERE project_id = ? AND package_name = ? ORDER BY created_at DESC",
27
- [projectId, packageName],
26
+ "SELECT * FROM build_manifests WHERE environment_id = ? AND package_name = ? ORDER BY created_at DESC",
27
+ [environmentId, packageName],
28
28
  );
29
29
  return rows.map(this.mapToEntry);
30
30
  }
31
31
 
32
32
  /** Looks up the manifest entry for a specific build, or null if none exists. */
33
33
  async getEntryByBuildId(
34
- projectId: string,
34
+ environmentId: string,
35
35
  packageName: string,
36
36
  buildId: string,
37
37
  ): Promise<ManifestEntry | null> {
38
38
  const row = await this.db.get<Record<string, unknown>>(
39
- "SELECT * FROM build_manifests WHERE project_id = ? AND package_name = ? AND build_id = ?",
40
- [projectId, packageName, buildId],
39
+ "SELECT * FROM build_manifests WHERE environment_id = ? AND package_name = ? AND build_id = ?",
40
+ [environmentId, packageName, buildId],
41
41
  );
42
42
  return row ? this.mapToEntry(row) : null;
43
43
  }
44
44
 
45
45
  /**
46
46
  * Inserts a new manifest entry, or updates the existing one if a row with
47
- * the same (project, package, build) triple already exists. Uses INSERT ON
47
+ * the same (environment, package, build) triple already exists. Uses INSERT ON
48
48
  * CONFLICT to avoid the TOCTOU race of SELECT-then-INSERT/UPDATE.
49
49
  */
50
50
  async upsertEntry(
@@ -55,9 +55,9 @@ export class ManifestRepository {
55
55
  const iso = now.toISOString();
56
56
 
57
57
  const rows = await this.db.all<Record<string, unknown>>(
58
- `INSERT INTO build_manifests (id, project_id, package_name, build_id, table_name, source_name, connection_name, created_at, updated_at)
58
+ `INSERT INTO build_manifests (id, environment_id, package_name, build_id, table_name, source_name, connection_name, created_at, updated_at)
59
59
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
60
- ON CONFLICT (project_id, package_name, build_id)
60
+ ON CONFLICT (environment_id, package_name, build_id)
61
61
  DO UPDATE SET table_name = EXCLUDED.table_name,
62
62
  source_name = EXCLUDED.source_name,
63
63
  connection_name = EXCLUDED.connection_name,
@@ -65,7 +65,7 @@ export class ManifestRepository {
65
65
  RETURNING *`,
66
66
  [
67
67
  id,
68
- entry.projectId,
68
+ entry.environmentId,
69
69
  entry.packageName,
70
70
  entry.buildId,
71
71
  entry.tableName,
@@ -84,21 +84,22 @@ export class ManifestRepository {
84
84
  await this.db.run("DELETE FROM build_manifests WHERE id = ?", [id]);
85
85
  }
86
86
 
87
- /** Removes all manifest entries belonging to a project (used on project deletion). */
88
- async deleteEntriesByProjectId(projectId: string): Promise<void> {
89
- await this.db.run("DELETE FROM build_manifests WHERE project_id = ?", [
90
- projectId,
91
- ]);
87
+ /** Removes all manifest entries belonging to an environment (used on environment deletion). */
88
+ async deleteEntriesByEnvironmentId(environmentId: string): Promise<void> {
89
+ await this.db.run(
90
+ "DELETE FROM build_manifests WHERE environment_id = ?",
91
+ [environmentId],
92
+ );
92
93
  }
93
94
 
94
95
  /** Removes all manifest entries for a specific package. */
95
96
  async deleteEntriesByPackage(
96
- projectId: string,
97
+ environmentId: string,
97
98
  packageName: string,
98
99
  ): Promise<void> {
99
100
  await this.db.run(
100
- "DELETE FROM build_manifests WHERE project_id = ? AND package_name = ?",
101
- [projectId, packageName],
101
+ "DELETE FROM build_manifests WHERE environment_id = ? AND package_name = ?",
102
+ [environmentId, packageName],
102
103
  );
103
104
  }
104
105
 
@@ -106,7 +107,7 @@ export class ManifestRepository {
106
107
  private mapToEntry(row: Record<string, unknown>): ManifestEntry {
107
108
  return {
108
109
  id: row.id as string,
109
- projectId: row.project_id as string,
110
+ environmentId: row.environment_id as string,
110
111
  packageName: row.package_name as string,
111
112
  buildId: row.build_id as string,
112
113
  tableName: row.table_name as string,
@@ -7,19 +7,19 @@ const TERMINAL_STATUSES: ReadonlySet<MaterializationStatus> = new Set([
7
7
  "CANCELLED",
8
8
  ]);
9
9
 
10
- function activeKeyFor(projectId: string, packageName: string): string {
11
- return `${projectId}|${packageName}`;
10
+ function activeKeyFor(environmentId: string, packageName: string): string {
11
+ return `${environmentId}|${packageName}`;
12
12
  }
13
13
 
14
14
  /**
15
- * Thrown when an atomic insert loses a race on (project, package) active
15
+ * Thrown when an atomic insert loses a race on (environment, package) active
16
16
  * materialization. Surfaced separately from a generic DB error so the service
17
17
  * layer can translate to `MaterializationConflictError`.
18
18
  */
19
19
  export class DuplicateActiveMaterializationError extends Error {
20
- constructor(projectId: string, packageName: string) {
20
+ constructor(environmentId: string, packageName: string) {
21
21
  super(
22
- `Active materialization already exists for (${projectId}, ${packageName})`,
22
+ `Active materialization already exists for (${environmentId}, ${packageName})`,
23
23
  );
24
24
  }
25
25
  }
@@ -27,7 +27,7 @@ export class DuplicateActiveMaterializationError extends Error {
27
27
  /**
28
28
  * DuckDB-backed repository for package materializations.
29
29
  *
30
- * A Materialization tracks a single build run for a (project, package) pair
30
+ * A Materialization tracks a single build run for an (environment, package) pair
31
31
  * through its lifecycle: PENDING -> RUNNING -> SUCCESS | FAILED | CANCELLED.
32
32
  */
33
33
  export class MaterializationRepository {
@@ -42,13 +42,13 @@ export class MaterializationRepository {
42
42
  }
43
43
 
44
44
  async list(
45
- projectId: string,
45
+ environmentId: string,
46
46
  packageName: string,
47
47
  options?: { limit?: number; offset?: number },
48
48
  ): Promise<Materialization[]> {
49
49
  let sql =
50
- "SELECT * FROM materializations WHERE project_id = ? AND package_name = ? ORDER BY created_at DESC";
51
- const params: unknown[] = [projectId, packageName];
50
+ "SELECT * FROM materializations WHERE environment_id = ? AND package_name = ? ORDER BY created_at DESC";
51
+ const params: unknown[] = [environmentId, packageName];
52
52
  if (options?.limit !== undefined) {
53
53
  sql += " LIMIT ?";
54
54
  params.push(options.limit);
@@ -70,18 +70,18 @@ export class MaterializationRepository {
70
70
  }
71
71
 
72
72
  async getActive(
73
- projectId: string,
73
+ environmentId: string,
74
74
  packageName: string,
75
75
  ): Promise<Materialization | null> {
76
76
  const row = await this.db.get<Record<string, unknown>>(
77
- "SELECT * FROM materializations WHERE project_id = ? AND package_name = ? AND status IN ('PENDING', 'RUNNING')",
78
- [projectId, packageName],
77
+ "SELECT * FROM materializations WHERE environment_id = ? AND package_name = ? AND status IN ('PENDING', 'RUNNING')",
78
+ [environmentId, packageName],
79
79
  );
80
80
  return row ? this.mapRow(row) : null;
81
81
  }
82
82
 
83
83
  async create(
84
- projectId: string,
84
+ environmentId: string,
85
85
  packageName: string,
86
86
  status: MaterializationStatus = "PENDING",
87
87
  metadata: Record<string, unknown> | null = null,
@@ -91,21 +91,21 @@ export class MaterializationRepository {
91
91
  const iso = now.toISOString();
92
92
  // Set active_key iff the row is in a non-terminal state. The unique
93
93
  // index on active_key makes the race-free conditional insert: a second
94
- // concurrent create on the same (project, package) fails here rather
94
+ // concurrent create on the same (environment, package) fails here rather
95
95
  // than in a check-then-write window.
96
96
  const activeKey = TERMINAL_STATUSES.has(status)
97
97
  ? null
98
- : activeKeyFor(projectId, packageName);
98
+ : activeKeyFor(environmentId, packageName);
99
99
  const metadataJson = metadata ? JSON.stringify(metadata) : null;
100
100
 
101
101
  try {
102
102
  const rows = await this.db.all<Record<string, unknown>>(
103
- `INSERT INTO materializations (id, project_id, package_name, status, active_key, metadata, created_at, updated_at)
103
+ `INSERT INTO materializations (id, environment_id, package_name, status, active_key, metadata, created_at, updated_at)
104
104
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)
105
105
  RETURNING *`,
106
106
  [
107
107
  id,
108
- projectId,
108
+ environmentId,
109
109
  packageName,
110
110
  status,
111
111
  activeKey,
@@ -118,7 +118,7 @@ export class MaterializationRepository {
118
118
  } catch (err) {
119
119
  if (isUniqueViolation(err, "idx_materializations_active_key")) {
120
120
  throw new DuplicateActiveMaterializationError(
121
- projectId,
121
+ environmentId,
122
122
  packageName,
123
123
  );
124
124
  }
@@ -146,11 +146,13 @@ export class MaterializationRepository {
146
146
  // Clear active_key on any transition to a terminal state; set it on
147
147
  // any transition to a non-terminal state. The unique index
148
148
  // guarantees we can never end up with two active rows for the same
149
- // (project, package).
149
+ // (environment, package).
150
150
  if (TERMINAL_STATUSES.has(updates.status)) {
151
151
  setClauses.push(`active_key = NULL`);
152
152
  } else {
153
- setClauses.push(`active_key = project_id || '|' || package_name`);
153
+ setClauses.push(
154
+ `active_key = environment_id || '|' || package_name`,
155
+ );
154
156
  }
155
157
  }
156
158
  if (updates.startedAt !== undefined) {
@@ -188,10 +190,11 @@ export class MaterializationRepository {
188
190
  return updated;
189
191
  }
190
192
 
191
- async deleteByProjectId(projectId: string): Promise<void> {
192
- await this.db.run("DELETE FROM materializations WHERE project_id = ?", [
193
- projectId,
194
- ]);
193
+ async deleteByEnvironmentId(environmentId: string): Promise<void> {
194
+ await this.db.run(
195
+ "DELETE FROM materializations WHERE environment_id = ?",
196
+ [environmentId],
197
+ );
195
198
  }
196
199
 
197
200
  async deleteById(id: string): Promise<void> {
@@ -199,12 +202,12 @@ export class MaterializationRepository {
199
202
  }
200
203
 
201
204
  async deleteByPackage(
202
- projectId: string,
205
+ environmentId: string,
203
206
  packageName: string,
204
207
  ): Promise<void> {
205
208
  await this.db.run(
206
- "DELETE FROM materializations WHERE project_id = ? AND package_name = ?",
207
- [projectId, packageName],
209
+ "DELETE FROM materializations WHERE environment_id = ? AND package_name = ?",
210
+ [environmentId, packageName],
208
211
  );
209
212
  }
210
213
 
@@ -220,7 +223,7 @@ export class MaterializationRepository {
220
223
 
221
224
  return {
222
225
  id: row.id as string,
223
- projectId: row.project_id as string,
226
+ environmentId: row.environment_id as string,
224
227
  packageName: row.package_name as string,
225
228
  status: row.status as MaterializationStatus,
226
229
  startedAt: row.started_at ? new Date(row.started_at as string) : null,