@malloy-publisher/server 0.0.382-dev → 0.2.0

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 (175) hide show
  1. package/README.docker.md +145 -20
  2. package/README.md +65 -12
  3. package/dist/app/api-doc.html +5 -0
  4. package/dist/app/api-doc.yaml +3903 -583
  5. package/dist/app/assets/EnvironmentPage-DiUgLuCH.js +1 -0
  6. package/dist/app/assets/HomePage-MhhjhnFj.js +1 -0
  7. package/dist/app/assets/LightMode-BEyvNUzT.js +1 -0
  8. package/dist/app/assets/MainPage-D4Cfee9o.js +2 -0
  9. package/dist/app/assets/MaterializationsPage-nGgGqwSN.js +1 -0
  10. package/dist/app/assets/ModelPage-DLAcEw-F.js +1 -0
  11. package/dist/app/assets/PackagePage-Df0ikH7w.js +1 -0
  12. package/dist/app/assets/RouteError-BFQwtroH.js +1 -0
  13. package/dist/app/assets/ThemeEditorPage-OoskoQYN.js +1 -0
  14. package/dist/app/assets/WorkbookPage-CpFgvUDC.js +1 -0
  15. package/dist/app/assets/{core-CjeTkq8O.es-BqRc6yhC.js → core-lb28vQkR.es-BoVSOc75.js} +1 -1
  16. package/dist/app/assets/github-dark-DenFmJkN.es-DHJKELXO.js +1 -0
  17. package/dist/app/assets/index-5eLCcNmP.css +1 -0
  18. package/dist/app/assets/index-6ypuFH6g.js +1774 -0
  19. package/dist/app/assets/index-B8pnU_V_.js +615 -0
  20. package/dist/app/assets/index-BImcHmVP.js +1 -0
  21. package/dist/app/assets/index-DSoCUF-f.js +2725 -0
  22. package/dist/app/assets/index-DfqzSItA.js +2 -0
  23. package/dist/app/assets/index-w_0OQJgZ.js +23 -0
  24. package/dist/app/index.html +8 -4
  25. package/dist/cpufeatures-1yrn0vtw.node +0 -0
  26. package/dist/default-publisher.config.json +23 -0
  27. package/dist/{instrumentation.js → instrumentation.mjs} +10631 -10573
  28. package/dist/package_load_worker.mjs +15219 -0
  29. package/dist/runtime/publisher.js +331 -0
  30. package/dist/{server.js → server.mjs} +115548 -49590
  31. package/dist/sshcrypto-vd2k5hq9.node +0 -0
  32. package/package.json +41 -28
  33. package/publisher.config.example.bigquery.json +33 -0
  34. package/publisher.config.example.duckdb.json +23 -0
  35. package/.eslintrc.json +0 -44
  36. package/.prettierignore +0 -1
  37. package/.prettierrc +0 -4
  38. package/build.ts +0 -38
  39. package/dist/app/assets/HomePage-Dn3E4CuB.js +0 -1
  40. package/dist/app/assets/MainPage-BzB3yoqi.js +0 -2
  41. package/dist/app/assets/ModelPage-C9O_sAXT.js +0 -1
  42. package/dist/app/assets/PackagePage-DcxKEjBX.js +0 -1
  43. package/dist/app/assets/ProjectPage-BDj307rF.js +0 -1
  44. package/dist/app/assets/RouteError-DAShbVCG.js +0 -1
  45. package/dist/app/assets/WorkbookPage-Cs_XYEaB.js +0 -1
  46. package/dist/app/assets/index-15BOvhp0.js +0 -456
  47. package/dist/app/assets/index-Bb2jqquW.js +0 -676
  48. package/dist/app/assets/index-CMlGQMcl.css +0 -1
  49. package/dist/app/assets/index-D68X76-7.js +0 -1276
  50. package/dist/app/assets/index.umd-DGBekgSu.js +0 -1145
  51. package/dxt/malloy_bridge.py +0 -354
  52. package/dxt/manifest.json +0 -22
  53. package/eslint.config.mjs +0 -76
  54. package/k6-tests/README.md +0 -144
  55. package/k6-tests/bun.lock +0 -797
  56. package/k6-tests/load-test/load-test-crud-connections.ts +0 -263
  57. package/k6-tests/load-test/load-test-crud-packages.ts +0 -461
  58. package/k6-tests/load-test/load-test-crud-projects.ts +0 -160
  59. package/k6-tests/load-test/load-test-crud.ts +0 -142
  60. package/k6-tests/load-test/load-test.ts +0 -784
  61. package/k6-tests/package.json +0 -27
  62. package/k6-tests/scripts/run-with-otel.sh +0 -50
  63. package/k6-tests/smoke-test/smoke-test.ts +0 -158
  64. package/k6-tests/tsconfig.json +0 -29
  65. package/k6-tests/utils/client_factory.ts +0 -128
  66. package/k6-tests/utils/common.ts +0 -489
  67. package/k6-tests/utils/logger.ts +0 -62
  68. package/k6-tests/utils/types.d.ts +0 -35
  69. package/malloy_mcp.dxt +0 -0
  70. package/publisher.config.json +0 -33
  71. package/src/config.spec.ts +0 -850
  72. package/src/config.ts +0 -305
  73. package/src/constants.ts +0 -10
  74. package/src/controller/compile.controller.ts +0 -35
  75. package/src/controller/connection.controller.ts +0 -515
  76. package/src/controller/database.controller.ts +0 -21
  77. package/src/controller/manifest.controller.ts +0 -29
  78. package/src/controller/materialization.controller.ts +0 -125
  79. package/src/controller/model.controller.ts +0 -107
  80. package/src/controller/package.controller.ts +0 -182
  81. package/src/controller/query.controller.ts +0 -60
  82. package/src/controller/watch-mode.controller.ts +0 -98
  83. package/src/data_styles.ts +0 -71
  84. package/src/dto/connection.dto.spec.ts +0 -131
  85. package/src/dto/connection.dto.ts +0 -174
  86. package/src/dto/index.ts +0 -2
  87. package/src/dto/package.dto.spec.ts +0 -42
  88. package/src/dto/package.dto.ts +0 -12
  89. package/src/dto/validate.spec.ts +0 -76
  90. package/src/dto/validate.ts +0 -31
  91. package/src/errors.ts +0 -116
  92. package/src/health.ts +0 -189
  93. package/src/instrumentation.ts +0 -148
  94. package/src/logger.ts +0 -154
  95. package/src/mcp/error_messages.ts +0 -199
  96. package/src/mcp/handler_utils.ts +0 -239
  97. package/src/mcp/mcp_constants.ts +0 -22
  98. package/src/mcp/prompts/handlers.ts +0 -84
  99. package/src/mcp/prompts/index.ts +0 -11
  100. package/src/mcp/prompts/prompt_definitions.ts +0 -160
  101. package/src/mcp/prompts/prompt_service.ts +0 -67
  102. package/src/mcp/prompts/utils.ts +0 -62
  103. package/src/mcp/resource_metadata.ts +0 -47
  104. package/src/mcp/resources/model_resource.ts +0 -153
  105. package/src/mcp/resources/notebook_resource.ts +0 -137
  106. package/src/mcp/resources/package_resource.ts +0 -370
  107. package/src/mcp/resources/project_resource.ts +0 -184
  108. package/src/mcp/resources/query_resource.ts +0 -117
  109. package/src/mcp/resources/source_resource.ts +0 -141
  110. package/src/mcp/resources/view_resource.ts +0 -136
  111. package/src/mcp/server.ts +0 -62
  112. package/src/mcp/tools/discovery_tools.ts +0 -258
  113. package/src/mcp/tools/execute_query_tool.ts +0 -265
  114. package/src/server.ts +0 -1195
  115. package/src/service/connection.spec.ts +0 -1331
  116. package/src/service/connection.ts +0 -1511
  117. package/src/service/connection_service.spec.ts +0 -641
  118. package/src/service/connection_service.ts +0 -181
  119. package/src/service/db_utils.spec.ts +0 -714
  120. package/src/service/db_utils.ts +0 -1227
  121. package/src/service/filter.spec.ts +0 -447
  122. package/src/service/filter.ts +0 -337
  123. package/src/service/filter_integration.spec.ts +0 -825
  124. package/src/service/gcs_s3_utils.ts +0 -427
  125. package/src/service/manifest_service.spec.ts +0 -201
  126. package/src/service/manifest_service.ts +0 -106
  127. package/src/service/materialization_service.spec.ts +0 -648
  128. package/src/service/materialization_service.ts +0 -929
  129. package/src/service/materialized_table_gc.spec.ts +0 -383
  130. package/src/service/materialized_table_gc.ts +0 -279
  131. package/src/service/model.spec.ts +0 -257
  132. package/src/service/model.ts +0 -1069
  133. package/src/service/package.spec.ts +0 -236
  134. package/src/service/package.ts +0 -463
  135. package/src/service/project.ts +0 -681
  136. package/src/service/project_compile.spec.ts +0 -197
  137. package/src/service/project_store.spec.ts +0 -859
  138. package/src/service/project_store.ts +0 -1531
  139. package/src/service/quoting.ts +0 -41
  140. package/src/service/resolve_project.ts +0 -13
  141. package/src/storage/BaseRepository.ts +0 -31
  142. package/src/storage/DatabaseInterface.ts +0 -180
  143. package/src/storage/StorageManager.mock.ts +0 -59
  144. package/src/storage/StorageManager.ts +0 -210
  145. package/src/storage/duckdb/ConnectionRepository.ts +0 -141
  146. package/src/storage/duckdb/DuckDBConnection.ts +0 -177
  147. package/src/storage/duckdb/DuckDBManifestStore.ts +0 -70
  148. package/src/storage/duckdb/DuckDBRepository.ts +0 -224
  149. package/src/storage/duckdb/ManifestRepository.ts +0 -119
  150. package/src/storage/duckdb/MaterializationRepository.ts +0 -249
  151. package/src/storage/duckdb/PackageRepository.ts +0 -135
  152. package/src/storage/duckdb/ProjectRepository.ts +0 -148
  153. package/src/storage/duckdb/manifest_store.spec.ts +0 -133
  154. package/src/storage/duckdb/schema.ts +0 -147
  155. package/src/storage/ducklake/DuckLakeManifestStore.ts +0 -146
  156. package/src/utils.ts +0 -13
  157. package/tests/fixtures/persist-test/data/orders.csv +0 -5
  158. package/tests/fixtures/persist-test/persist_test.malloy +0 -11
  159. package/tests/fixtures/persist-test/publisher.json +0 -5
  160. package/tests/fixtures/publisher.config.json +0 -15
  161. package/tests/harness/e2e.ts +0 -96
  162. package/tests/harness/mcp_test_setup.ts +0 -344
  163. package/tests/harness/mocks.ts +0 -37
  164. package/tests/harness/rest_e2e.ts +0 -68
  165. package/tests/harness/uris.ts +0 -31
  166. package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +0 -470
  167. package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +0 -434
  168. package/tests/integration/mcp/mcp_resource.integration.spec.ts +0 -663
  169. package/tests/integration/mcp/mcp_transport.integration.spec.ts +0 -122
  170. package/tests/integration/mcp/setup.spec.ts +0 -5
  171. package/tests/unit/duckdb/attached_databases.test.ts +0 -1132
  172. package/tests/unit/ducklake/ducklake.test.ts +0 -950
  173. package/tests/unit/mcp/prompt_definitions.test.ts +0 -102
  174. package/tests/unit/mcp/prompt_happy.test.ts +0 -51
  175. package/tsconfig.json +0 -32
@@ -1,177 +0,0 @@
1
- import { Mutex } from "async-mutex";
2
- import * as duckdb from "duckdb";
3
- import * as path from "path";
4
- import { DatabaseConnection } from "../DatabaseInterface";
5
-
6
- export class DuckDBConnection implements DatabaseConnection {
7
- private db: duckdb.Database | null = null;
8
- private connection: duckdb.Connection | null = null;
9
- private dbPath: string;
10
- private mutex: Mutex = new Mutex();
11
-
12
- constructor(dbPath?: string) {
13
- // Default to storing in the server root directory
14
- this.dbPath = dbPath || path.join(process.cwd(), "publisher.db");
15
- }
16
-
17
- async initialize(): Promise<void> {
18
- return new Promise((resolve, reject) => {
19
- this.db = new duckdb.Database(this.dbPath, {}, (err) => {
20
- if (err) {
21
- console.error("Failed to create DuckDB database:", err);
22
- reject(new Error(`Failed to initialize DuckDB: ${err.message}`));
23
- return;
24
- }
25
-
26
- // Connect synchronously
27
- this.connection = (
28
- this.db as duckdb.Database & { connect(): duckdb.Connection }
29
- ).connect();
30
-
31
- if (!this.connection) {
32
- reject(new Error("Failed to create connection object"));
33
- return;
34
- }
35
-
36
- // Verify connection works
37
- this.connection.all("SELECT 42 as answer", (testErr, _rows) => {
38
- if (testErr) {
39
- console.error("Connection test failed:", testErr);
40
- reject(
41
- new Error(
42
- `Failed to verify DuckDB connection: ${testErr.message}`,
43
- ),
44
- );
45
- return;
46
- }
47
-
48
- resolve();
49
- });
50
- });
51
- });
52
- }
53
-
54
- async close(): Promise<void> {
55
- return new Promise((resolve, reject) => {
56
- if (this.connection) {
57
- this.connection.close((err) => {
58
- if (err) {
59
- reject(
60
- new Error(
61
- `Failed to close DuckDB connection: ${err.message}`,
62
- ),
63
- );
64
- return;
65
- }
66
-
67
- if (this.db) {
68
- this.db.close((dbErr) => {
69
- if (dbErr) {
70
- reject(
71
- new Error(
72
- `Failed to close DuckDB: ${dbErr.message}`,
73
- ),
74
- );
75
- return;
76
- }
77
- console.log("DuckDB connection closed");
78
- resolve();
79
- });
80
- } else {
81
- resolve();
82
- }
83
- });
84
- } else {
85
- resolve();
86
- }
87
- });
88
- }
89
-
90
- async isInitialized(): Promise<boolean> {
91
- if (!this.connection) return false;
92
-
93
- return this.mutex.runExclusive(async () => {
94
- return new Promise<boolean>((resolve) => {
95
- this.connection!.all(
96
- "SELECT name FROM sqlite_master WHERE type='table' AND name='projects'",
97
- (err, rows) => {
98
- if (err) {
99
- resolve(false);
100
- return;
101
- }
102
- resolve(rows && rows.length > 0);
103
- },
104
- );
105
- });
106
- });
107
- }
108
-
109
- async run(query: string, params?: unknown[]): Promise<void> {
110
- if (!this.connection) {
111
- throw new Error("Database not initialized");
112
- }
113
-
114
- return this.mutex.runExclusive(async () => {
115
- return new Promise<void>((resolve, reject) => {
116
- const callback = (err: Error | null) => {
117
- if (err) {
118
- reject(
119
- new Error(
120
- `Query execution failed: ${err.message}\nQuery: ${query}`,
121
- ),
122
- );
123
- return;
124
- }
125
- resolve();
126
- };
127
-
128
- // Pass params directly without the params argument if empty
129
- if (params && params.length > 0) {
130
- this.connection!.run(query, ...params, callback);
131
- } else {
132
- this.connection!.run(query, callback);
133
- }
134
- });
135
- });
136
- }
137
-
138
- async all<T>(query: string, params?: unknown[]): Promise<T[]> {
139
- if (!this.connection) {
140
- throw new Error("Database not initialized");
141
- }
142
-
143
- return this.mutex.runExclusive(async () => {
144
- return new Promise<T[]>((resolve, reject) => {
145
- const callback = (err: Error | null, rows: unknown[]) => {
146
- if (err) {
147
- reject(
148
- new Error(
149
- `Query execution failed: ${err.message}\nQuery: ${query}`,
150
- ),
151
- );
152
- return;
153
- }
154
- resolve((rows || []) as T[]);
155
- };
156
-
157
- if (params && params.length > 0) {
158
- this.connection!.all(query, ...params, callback);
159
- } else {
160
- this.connection!.all(query, callback);
161
- }
162
- });
163
- });
164
- }
165
-
166
- async get<T>(query: string, params?: unknown[]): Promise<T | null> {
167
- const rows = await this.all<T>(query, params);
168
- return rows.length > 0 ? rows[0] : null;
169
- }
170
-
171
- getConnection(): duckdb.Connection {
172
- if (!this.connection) {
173
- throw new Error("Database not initialized");
174
- }
175
- return this.connection;
176
- }
177
- }
@@ -1,70 +0,0 @@
1
- import {
2
- BuildManifest,
3
- ManifestEntry,
4
- ManifestStore,
5
- ResourceRepository,
6
- } from "../DatabaseInterface";
7
-
8
- /**
9
- * DuckDB-backed ManifestStore that delegates to the local ResourceRepository.
10
- *
11
- * In standalone mode this is the active store. Orchestrated deployments swap
12
- * in a DuckLake-backed implementation that reads/writes manifests through the
13
- * shared catalog instead of the local DuckDB build_manifests table.
14
- */
15
- export class DuckDBManifestStore implements ManifestStore {
16
- constructor(private repository: ResourceRepository) {}
17
-
18
- /**
19
- * Assembles a {@link BuildManifest} by folding all manifest rows for the
20
- * given package into a build-ID-keyed map. `strict: false` lets the Malloy
21
- * runtime fall back to executing the underlying query when a persist
22
- * reference has no manifest entry (e.g. before the first materialization).
23
- */
24
- async getManifest(
25
- projectId: string,
26
- packageName: string,
27
- ): Promise<BuildManifest> {
28
- const entries = await this.repository.listManifestEntries(
29
- projectId,
30
- packageName,
31
- );
32
- const manifest: BuildManifest = {
33
- entries: {},
34
- strict: false,
35
- };
36
- for (const entry of entries) {
37
- manifest.entries[entry.buildId] = { tableName: entry.tableName };
38
- }
39
- return manifest;
40
- }
41
-
42
- async writeEntry(
43
- projectId: string,
44
- packageName: string,
45
- buildId: string,
46
- tableName: string,
47
- sourceName: string,
48
- connectionName: string,
49
- ): Promise<void> {
50
- await this.repository.upsertManifestEntry({
51
- projectId,
52
- packageName,
53
- buildId,
54
- tableName,
55
- sourceName,
56
- connectionName,
57
- });
58
- }
59
-
60
- async deleteEntry(id: string): Promise<void> {
61
- await this.repository.deleteManifestEntry(id);
62
- }
63
-
64
- async listEntries(
65
- projectId: string,
66
- packageName: string,
67
- ): Promise<ManifestEntry[]> {
68
- return this.repository.listManifestEntries(projectId, packageName);
69
- }
70
- }
@@ -1,224 +0,0 @@
1
- import {
2
- Connection,
3
- ManifestEntry,
4
- Materialization,
5
- MaterializationStatus,
6
- Package,
7
- Project,
8
- ResourceRepository,
9
- } from "../DatabaseInterface";
10
- import { ConnectionRepository } from "./ConnectionRepository";
11
- import { DuckDBConnection } from "./DuckDBConnection";
12
- import { ManifestRepository } from "./ManifestRepository";
13
- import { MaterializationRepository } from "./MaterializationRepository";
14
- import { PackageRepository } from "./PackageRepository";
15
- import { ProjectRepository } from "./ProjectRepository";
16
-
17
- export class DuckDBRepository implements ResourceRepository {
18
- private projectRepo: ProjectRepository;
19
- private packageRepo: PackageRepository;
20
- private connectionRepo: ConnectionRepository;
21
- private materializationRepo: MaterializationRepository;
22
- private manifestRepo: ManifestRepository;
23
-
24
- constructor(public db: DuckDBConnection) {
25
- this.projectRepo = new ProjectRepository(db);
26
- this.packageRepo = new PackageRepository(db);
27
- this.connectionRepo = new ConnectionRepository(db);
28
- this.materializationRepo = new MaterializationRepository(db);
29
- this.manifestRepo = new ManifestRepository(db);
30
- }
31
-
32
- // ==================== PROJECTS ====================
33
-
34
- async listProjects(): Promise<Project[]> {
35
- return this.projectRepo.listProjects();
36
- }
37
-
38
- async getProjectById(id: string): Promise<Project | null> {
39
- return this.projectRepo.getProjectById(id);
40
- }
41
-
42
- async getProjectByName(name: string): Promise<Project | null> {
43
- return this.projectRepo.getProjectByName(name);
44
- }
45
-
46
- async createProject(
47
- project: Omit<Project, "id" | "createdAt" | "updatedAt">,
48
- ): Promise<Project> {
49
- return this.projectRepo.createProject(project);
50
- }
51
-
52
- async updateProject(
53
- id: string,
54
- updates: Partial<Project>,
55
- ): Promise<Project> {
56
- return this.projectRepo.updateProject(id, updates);
57
- }
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);
65
- }
66
-
67
- // ==================== PACKAGES ====================
68
-
69
- async listPackages(projectId: string): Promise<Package[]> {
70
- return this.packageRepo.listPackages(projectId);
71
- }
72
-
73
- async getPackageById(id: string): Promise<Package | null> {
74
- return this.packageRepo.getPackageById(id);
75
- }
76
-
77
- async getPackageByName(
78
- projectId: string,
79
- name: string,
80
- ): Promise<Package | null> {
81
- return this.packageRepo.getPackageByName(projectId, name);
82
- }
83
-
84
- async createPackage(
85
- pkg: Omit<Package, "id" | "createdAt" | "updatedAt">,
86
- ): Promise<Package> {
87
- return this.packageRepo.createPackage(pkg);
88
- }
89
-
90
- async updatePackage(
91
- id: string,
92
- updates: Partial<Package>,
93
- ): Promise<Package> {
94
- return this.packageRepo.updatePackage(id, updates);
95
- }
96
-
97
- async deletePackage(id: string): Promise<void> {
98
- const pkg = await this.packageRepo.getPackageById(id);
99
- if (pkg) {
100
- await this.manifestRepo.deleteEntriesByPackage(
101
- pkg.projectId,
102
- pkg.name,
103
- );
104
- await this.materializationRepo.deleteByPackage(
105
- pkg.projectId,
106
- pkg.name,
107
- );
108
- }
109
- await this.packageRepo.deletePackage(id);
110
- }
111
-
112
- async deletePackagesByProjectId(id: string): Promise<void> {
113
- return this.packageRepo.deletePackagesByProjectId(id);
114
- }
115
-
116
- // ==================== CONNECTIONS ====================
117
-
118
- async listConnections(projectId: string): Promise<Connection[]> {
119
- return this.connectionRepo.listConnections(projectId);
120
- }
121
-
122
- async getConnectionById(id: string): Promise<Connection | null> {
123
- return this.connectionRepo.getConnectionById(id);
124
- }
125
-
126
- async getConnectionByName(
127
- projectId: string,
128
- name: string,
129
- ): Promise<Connection | null> {
130
- return this.connectionRepo.getConnectionByName(projectId, name);
131
- }
132
-
133
- async createConnection(
134
- connection: Omit<Connection, "id" | "createdAt" | "updatedAt">,
135
- ): Promise<Connection> {
136
- return this.connectionRepo.createConnection(connection);
137
- }
138
-
139
- async updateConnection(
140
- id: string,
141
- updates: Partial<Connection>,
142
- ): Promise<Connection> {
143
- return this.connectionRepo.updateConnection(id, updates);
144
- }
145
-
146
- async deleteConnection(id: string): Promise<void> {
147
- return this.connectionRepo.deleteConnection(id);
148
- }
149
-
150
- async deleteConnectionsByProjectId(id: string): Promise<void> {
151
- return this.connectionRepo.deleteConnectionsByProjectId(id);
152
- }
153
-
154
- // ==================== MATERIALIZATIONS ====================
155
-
156
- async listMaterializations(
157
- projectId: string,
158
- packageName: string,
159
- options?: { limit?: number; offset?: number },
160
- ): Promise<Materialization[]> {
161
- return this.materializationRepo.list(projectId, packageName, options);
162
- }
163
-
164
- async getMaterializationById(id: string): Promise<Materialization | null> {
165
- return this.materializationRepo.getById(id);
166
- }
167
-
168
- async getActiveMaterialization(
169
- projectId: string,
170
- packageName: string,
171
- ): Promise<Materialization | null> {
172
- return this.materializationRepo.getActive(projectId, packageName);
173
- }
174
-
175
- async createMaterialization(
176
- projectId: string,
177
- packageName: string,
178
- status: MaterializationStatus = "PENDING",
179
- metadata: Record<string, unknown> | null = null,
180
- ): Promise<Materialization> {
181
- return this.materializationRepo.create(
182
- projectId,
183
- packageName,
184
- status,
185
- metadata,
186
- );
187
- }
188
-
189
- async updateMaterialization(
190
- id: string,
191
- updates: {
192
- status?: MaterializationStatus;
193
- startedAt?: Date;
194
- completedAt?: Date;
195
- error?: string | null;
196
- metadata?: Record<string, unknown> | null;
197
- },
198
- ): Promise<Materialization> {
199
- return this.materializationRepo.update(id, updates);
200
- }
201
-
202
- async deleteMaterialization(id: string): Promise<void> {
203
- return this.materializationRepo.deleteById(id);
204
- }
205
-
206
- // ==================== BUILD MANIFESTS ====================
207
-
208
- async listManifestEntries(
209
- projectId: string,
210
- packageName: string,
211
- ): Promise<ManifestEntry[]> {
212
- return this.manifestRepo.listEntries(projectId, packageName);
213
- }
214
-
215
- async upsertManifestEntry(
216
- entry: Omit<ManifestEntry, "id" | "createdAt" | "updatedAt">,
217
- ): Promise<ManifestEntry> {
218
- return this.manifestRepo.upsertEntry(entry);
219
- }
220
-
221
- async deleteManifestEntry(id: string): Promise<void> {
222
- return this.manifestRepo.deleteEntry(id);
223
- }
224
- }
@@ -1,119 +0,0 @@
1
- import { ManifestEntry } from "../DatabaseInterface";
2
- import { DuckDBConnection } from "./DuckDBConnection";
3
-
4
- /**
5
- * Repository for build manifest entries stored in the `build_manifests` table.
6
- *
7
- * A build manifest records the materialized table produced by a specific build
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
10
- * downstream consumers to discover which physical tables correspond to a given
11
- * package version.
12
- */
13
- export class ManifestRepository {
14
- constructor(private db: DuckDBConnection) {}
15
-
16
- private generateId(): string {
17
- return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
18
- }
19
-
20
- /** Returns all manifest entries for a package, most recent first. */
21
- async listEntries(
22
- projectId: string,
23
- packageName: string,
24
- ): Promise<ManifestEntry[]> {
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],
28
- );
29
- return rows.map(this.mapToEntry);
30
- }
31
-
32
- /** Looks up the manifest entry for a specific build, or null if none exists. */
33
- async getEntryByBuildId(
34
- projectId: string,
35
- packageName: string,
36
- buildId: string,
37
- ): Promise<ManifestEntry | null> {
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],
41
- );
42
- return row ? this.mapToEntry(row) : null;
43
- }
44
-
45
- /**
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
48
- * CONFLICT to avoid the TOCTOU race of SELECT-then-INSERT/UPDATE.
49
- */
50
- async upsertEntry(
51
- entry: Omit<ManifestEntry, "id" | "createdAt" | "updatedAt">,
52
- ): Promise<ManifestEntry> {
53
- const id = this.generateId();
54
- const now = new Date();
55
- const iso = now.toISOString();
56
-
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)
59
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
60
- ON CONFLICT (project_id, package_name, build_id)
61
- DO UPDATE SET table_name = EXCLUDED.table_name,
62
- source_name = EXCLUDED.source_name,
63
- connection_name = EXCLUDED.connection_name,
64
- updated_at = EXCLUDED.updated_at
65
- RETURNING *`,
66
- [
67
- id,
68
- entry.projectId,
69
- entry.packageName,
70
- entry.buildId,
71
- entry.tableName,
72
- entry.sourceName,
73
- entry.connectionName,
74
- iso,
75
- iso,
76
- ],
77
- );
78
-
79
- return this.mapToEntry(rows[0]);
80
- }
81
-
82
- /** Deletes a single manifest entry by ID. */
83
- async deleteEntry(id: string): Promise<void> {
84
- await this.db.run("DELETE FROM build_manifests WHERE id = ?", [id]);
85
- }
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
- ]);
92
- }
93
-
94
- /** Removes all manifest entries for a specific package. */
95
- async deleteEntriesByPackage(
96
- projectId: string,
97
- packageName: string,
98
- ): Promise<void> {
99
- await this.db.run(
100
- "DELETE FROM build_manifests WHERE project_id = ? AND package_name = ?",
101
- [projectId, packageName],
102
- );
103
- }
104
-
105
- /** Maps a raw DB row (snake_case columns) to a {@link ManifestEntry}. */
106
- private mapToEntry(row: Record<string, unknown>): ManifestEntry {
107
- return {
108
- id: row.id as string,
109
- projectId: row.project_id as string,
110
- packageName: row.package_name as string,
111
- buildId: row.build_id as string,
112
- tableName: row.table_name as string,
113
- sourceName: row.source_name as string,
114
- connectionName: row.connection_name as string,
115
- createdAt: new Date(row.created_at as string),
116
- updatedAt: new Date(row.updated_at as string),
117
- };
118
- }
119
- }