@malloy-publisher/server 0.0.382-dev → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.docker.md +145 -20
- package/README.md +65 -12
- package/dist/app/api-doc.html +5 -0
- package/dist/app/api-doc.yaml +3929 -534
- package/dist/app/assets/EnvironmentPage-CHq5BF4l.js +1 -0
- package/dist/app/assets/HomePage-CIp-pJCa.js +1 -0
- package/dist/app/assets/LightMode-mHHPjEeA.js +1 -0
- package/dist/app/assets/MainPage-CylxO5Yu.js +2 -0
- package/dist/app/assets/MaterializationsPage-a0vjqxib.js +1 -0
- package/dist/app/assets/ModelPage-Cz-MrSZQ.js +1 -0
- package/dist/app/assets/PackagePage-Cdg5D7AN.js +1 -0
- package/dist/app/assets/RouteError-B5SIyn6G.js +1 -0
- package/dist/app/assets/ThemeEditorPage-B62rRc-y.js +1 -0
- package/dist/app/assets/WorkbookPage-45cG1Gx6.js +1 -0
- package/dist/app/assets/{core-CjeTkq8O.es-BqRc6yhC.js → core-DyU47reh.es-DY9b3WjS.js} +1 -1
- package/dist/app/assets/github-dark-DenFmJkN.es-DHJKELXO.js +1 -0
- package/dist/app/assets/index-5eLCcNmP.css +1 -0
- package/dist/app/assets/index-C5ClsnS7.js +1774 -0
- package/dist/app/assets/index-DF9tVqwN.js +1 -0
- package/dist/app/assets/index-DSoCUF-f.js +2725 -0
- package/dist/app/assets/index-DgVRz2wm.js +2 -0
- package/dist/app/assets/index-P-pF8nU5.js +632 -0
- package/dist/app/assets/index-w_0OQJgZ.js +23 -0
- package/dist/app/index.html +8 -4
- package/dist/cpufeatures-1yrn0vtw.node +0 -0
- package/dist/default-publisher.config.json +23 -0
- package/dist/{instrumentation.js → instrumentation.mjs} +10631 -10573
- package/dist/package_load_worker.mjs +15219 -0
- package/dist/runtime/publisher.js +331 -0
- package/dist/{server.js → server.mjs} +141276 -74705
- package/dist/sshcrypto-vd2k5hq9.node +0 -0
- package/package.json +41 -28
- package/publisher.config.example.bigquery.json +33 -0
- package/publisher.config.example.duckdb.json +23 -0
- package/.eslintrc.json +0 -44
- package/.prettierignore +0 -1
- package/.prettierrc +0 -4
- package/build.ts +0 -38
- package/dist/app/assets/HomePage-Dn3E4CuB.js +0 -1
- package/dist/app/assets/MainPage-BzB3yoqi.js +0 -2
- package/dist/app/assets/ModelPage-C9O_sAXT.js +0 -1
- package/dist/app/assets/PackagePage-DcxKEjBX.js +0 -1
- package/dist/app/assets/ProjectPage-BDj307rF.js +0 -1
- package/dist/app/assets/RouteError-DAShbVCG.js +0 -1
- package/dist/app/assets/WorkbookPage-Cs_XYEaB.js +0 -1
- package/dist/app/assets/index-15BOvhp0.js +0 -456
- package/dist/app/assets/index-Bb2jqquW.js +0 -676
- package/dist/app/assets/index-CMlGQMcl.css +0 -1
- package/dist/app/assets/index-D68X76-7.js +0 -1276
- package/dist/app/assets/index.umd-DGBekgSu.js +0 -1145
- package/dxt/malloy_bridge.py +0 -354
- package/dxt/manifest.json +0 -22
- package/eslint.config.mjs +0 -76
- package/k6-tests/README.md +0 -144
- package/k6-tests/bun.lock +0 -797
- package/k6-tests/load-test/load-test-crud-connections.ts +0 -263
- package/k6-tests/load-test/load-test-crud-packages.ts +0 -461
- package/k6-tests/load-test/load-test-crud-projects.ts +0 -160
- package/k6-tests/load-test/load-test-crud.ts +0 -142
- package/k6-tests/load-test/load-test.ts +0 -784
- package/k6-tests/package.json +0 -27
- package/k6-tests/scripts/run-with-otel.sh +0 -50
- package/k6-tests/smoke-test/smoke-test.ts +0 -158
- package/k6-tests/tsconfig.json +0 -29
- package/k6-tests/utils/client_factory.ts +0 -128
- package/k6-tests/utils/common.ts +0 -489
- package/k6-tests/utils/logger.ts +0 -62
- package/k6-tests/utils/types.d.ts +0 -35
- package/malloy_mcp.dxt +0 -0
- package/publisher.config.json +0 -33
- package/src/config.spec.ts +0 -850
- package/src/config.ts +0 -305
- package/src/constants.ts +0 -10
- package/src/controller/compile.controller.ts +0 -35
- package/src/controller/connection.controller.ts +0 -515
- package/src/controller/database.controller.ts +0 -21
- package/src/controller/manifest.controller.ts +0 -29
- package/src/controller/materialization.controller.ts +0 -125
- package/src/controller/model.controller.ts +0 -107
- package/src/controller/package.controller.ts +0 -182
- package/src/controller/query.controller.ts +0 -60
- package/src/controller/watch-mode.controller.ts +0 -98
- package/src/data_styles.ts +0 -71
- package/src/dto/connection.dto.spec.ts +0 -131
- package/src/dto/connection.dto.ts +0 -174
- package/src/dto/index.ts +0 -2
- package/src/dto/package.dto.spec.ts +0 -42
- package/src/dto/package.dto.ts +0 -12
- package/src/dto/validate.spec.ts +0 -76
- package/src/dto/validate.ts +0 -31
- package/src/errors.ts +0 -116
- package/src/health.ts +0 -189
- package/src/instrumentation.ts +0 -148
- package/src/logger.ts +0 -154
- package/src/mcp/error_messages.ts +0 -199
- package/src/mcp/handler_utils.ts +0 -239
- package/src/mcp/mcp_constants.ts +0 -22
- package/src/mcp/prompts/handlers.ts +0 -84
- package/src/mcp/prompts/index.ts +0 -11
- package/src/mcp/prompts/prompt_definitions.ts +0 -160
- package/src/mcp/prompts/prompt_service.ts +0 -67
- package/src/mcp/prompts/utils.ts +0 -62
- package/src/mcp/resource_metadata.ts +0 -47
- package/src/mcp/resources/model_resource.ts +0 -153
- package/src/mcp/resources/notebook_resource.ts +0 -137
- package/src/mcp/resources/package_resource.ts +0 -370
- package/src/mcp/resources/project_resource.ts +0 -184
- package/src/mcp/resources/query_resource.ts +0 -117
- package/src/mcp/resources/source_resource.ts +0 -141
- package/src/mcp/resources/view_resource.ts +0 -136
- package/src/mcp/server.ts +0 -62
- package/src/mcp/tools/discovery_tools.ts +0 -258
- package/src/mcp/tools/execute_query_tool.ts +0 -265
- package/src/server.ts +0 -1195
- package/src/service/connection.spec.ts +0 -1331
- package/src/service/connection.ts +0 -1511
- package/src/service/connection_service.spec.ts +0 -641
- package/src/service/connection_service.ts +0 -181
- package/src/service/db_utils.spec.ts +0 -714
- package/src/service/db_utils.ts +0 -1227
- package/src/service/filter.spec.ts +0 -447
- package/src/service/filter.ts +0 -337
- package/src/service/filter_integration.spec.ts +0 -825
- package/src/service/gcs_s3_utils.ts +0 -427
- package/src/service/manifest_service.spec.ts +0 -201
- package/src/service/manifest_service.ts +0 -106
- package/src/service/materialization_service.spec.ts +0 -648
- package/src/service/materialization_service.ts +0 -929
- package/src/service/materialized_table_gc.spec.ts +0 -383
- package/src/service/materialized_table_gc.ts +0 -279
- package/src/service/model.spec.ts +0 -257
- package/src/service/model.ts +0 -1069
- package/src/service/package.spec.ts +0 -236
- package/src/service/package.ts +0 -463
- package/src/service/project.ts +0 -681
- package/src/service/project_compile.spec.ts +0 -197
- package/src/service/project_store.spec.ts +0 -859
- package/src/service/project_store.ts +0 -1531
- package/src/service/quoting.ts +0 -41
- package/src/service/resolve_project.ts +0 -13
- package/src/storage/BaseRepository.ts +0 -31
- package/src/storage/DatabaseInterface.ts +0 -180
- package/src/storage/StorageManager.mock.ts +0 -59
- package/src/storage/StorageManager.ts +0 -210
- package/src/storage/duckdb/ConnectionRepository.ts +0 -141
- package/src/storage/duckdb/DuckDBConnection.ts +0 -177
- package/src/storage/duckdb/DuckDBManifestStore.ts +0 -70
- package/src/storage/duckdb/DuckDBRepository.ts +0 -224
- package/src/storage/duckdb/ManifestRepository.ts +0 -119
- package/src/storage/duckdb/MaterializationRepository.ts +0 -249
- package/src/storage/duckdb/PackageRepository.ts +0 -135
- package/src/storage/duckdb/ProjectRepository.ts +0 -148
- package/src/storage/duckdb/manifest_store.spec.ts +0 -133
- package/src/storage/duckdb/schema.ts +0 -147
- package/src/storage/ducklake/DuckLakeManifestStore.ts +0 -146
- package/src/utils.ts +0 -13
- package/tests/fixtures/persist-test/data/orders.csv +0 -5
- package/tests/fixtures/persist-test/persist_test.malloy +0 -11
- package/tests/fixtures/persist-test/publisher.json +0 -5
- package/tests/fixtures/publisher.config.json +0 -15
- package/tests/harness/e2e.ts +0 -96
- package/tests/harness/mcp_test_setup.ts +0 -344
- package/tests/harness/mocks.ts +0 -37
- package/tests/harness/rest_e2e.ts +0 -68
- package/tests/harness/uris.ts +0 -31
- package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +0 -470
- package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +0 -434
- package/tests/integration/mcp/mcp_resource.integration.spec.ts +0 -663
- package/tests/integration/mcp/mcp_transport.integration.spec.ts +0 -122
- package/tests/integration/mcp/setup.spec.ts +0 -5
- package/tests/unit/duckdb/attached_databases.test.ts +0 -1132
- package/tests/unit/ducklake/ducklake.test.ts +0 -950
- package/tests/unit/mcp/prompt_definitions.test.ts +0 -102
- package/tests/unit/mcp/prompt_happy.test.ts +0 -51
- package/tsconfig.json +0 -32
package/src/service/quoting.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Minimal identifier-quoting surface. Every `Dialect` in `@malloydata/malloy`
|
|
3
|
-
* implements this; we accept the duck type so tests can inject a fake without
|
|
4
|
-
* instantiating a full dialect.
|
|
5
|
-
*/
|
|
6
|
-
export interface Quoter {
|
|
7
|
-
quoteTablePath(seg: string): string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Quote a potentially schema-qualified table path (e.g. "schema.table")
|
|
12
|
-
* by quoting each segment individually with the dialect's quoteTablePath.
|
|
13
|
-
*/
|
|
14
|
-
export function quoteTablePath(path: string, dialect: Quoter): string {
|
|
15
|
-
return path
|
|
16
|
-
.split(".")
|
|
17
|
-
.map((seg) => dialect.quoteTablePath(seg))
|
|
18
|
-
.join(".");
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Split a possibly schema-qualified table name into its schema prefix
|
|
23
|
-
* (including the trailing dot) and the bare table name.
|
|
24
|
-
*
|
|
25
|
-
* Examples:
|
|
26
|
-
* "my_schema.my_table" -> { schemaPrefix: "my_schema.", bareName: "my_table" }
|
|
27
|
-
* "my_table" -> { schemaPrefix: "", bareName: "my_table" }
|
|
28
|
-
*/
|
|
29
|
-
export function splitTablePath(tableName: string): {
|
|
30
|
-
schemaPrefix: string;
|
|
31
|
-
bareName: string;
|
|
32
|
-
} {
|
|
33
|
-
const lastDot = tableName.lastIndexOf(".");
|
|
34
|
-
if (lastDot >= 0) {
|
|
35
|
-
return {
|
|
36
|
-
schemaPrefix: tableName.substring(0, lastDot + 1),
|
|
37
|
-
bareName: tableName.substring(lastDot + 1),
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
return { schemaPrefix: "", bareName: tableName };
|
|
41
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { ProjectNotFoundError } from "../errors";
|
|
2
|
-
import { ResourceRepository } from "../storage/DatabaseInterface";
|
|
3
|
-
|
|
4
|
-
export async function resolveProjectId(
|
|
5
|
-
repository: ResourceRepository,
|
|
6
|
-
projectName: string,
|
|
7
|
-
): Promise<string> {
|
|
8
|
-
const dbProject = await repository.getProjectByName(projectName);
|
|
9
|
-
if (!dbProject) {
|
|
10
|
-
throw new ProjectNotFoundError(`Project '${projectName}' not found`);
|
|
11
|
-
}
|
|
12
|
-
return dbProject.id;
|
|
13
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { DatabaseConnection } from "./DatabaseInterface";
|
|
2
|
-
|
|
3
|
-
export abstract class BaseRepository {
|
|
4
|
-
constructor(protected db: DatabaseConnection) {}
|
|
5
|
-
|
|
6
|
-
protected generateId(): string {
|
|
7
|
-
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
protected now(): Date {
|
|
11
|
-
return new Date();
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
protected async executeQuery<T>(
|
|
15
|
-
_query: string,
|
|
16
|
-
_params?: unknown[],
|
|
17
|
-
): Promise<T[]> {
|
|
18
|
-
// This will be implemented by specific database implementations
|
|
19
|
-
throw new Error(
|
|
20
|
-
"executeQuery must be implemented by database-specific class",
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
protected async executeOne<T>(
|
|
25
|
-
query: string,
|
|
26
|
-
params?: unknown[],
|
|
27
|
-
): Promise<T | null> {
|
|
28
|
-
const results = await this.executeQuery<T>(query, params);
|
|
29
|
-
return results.length > 0 ? results[0] : null;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generic database interface for storage abstraction
|
|
3
|
-
* This allows switching between DuckDB, PostgreSQL, MySQL, etc.
|
|
4
|
-
*/
|
|
5
|
-
export interface DatabaseConnection {
|
|
6
|
-
initialize(): Promise<void>;
|
|
7
|
-
close(): Promise<void>;
|
|
8
|
-
isInitialized(): Promise<boolean>;
|
|
9
|
-
}
|
|
10
|
-
|
|
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>;
|
|
21
|
-
|
|
22
|
-
// Packages
|
|
23
|
-
listPackages(projectId: string): Promise<Package[]>;
|
|
24
|
-
getPackageById(id: string): Promise<Package | null>;
|
|
25
|
-
getPackageByName(projectId: string, name: string): Promise<Package | null>;
|
|
26
|
-
createPackage(
|
|
27
|
-
pkg: Omit<Package, "id" | "createdAt" | "updatedAt">,
|
|
28
|
-
): Promise<Package>;
|
|
29
|
-
updatePackage(id: string, updates: Partial<Package>): Promise<Package>;
|
|
30
|
-
deletePackage(id: string): Promise<void>;
|
|
31
|
-
|
|
32
|
-
// Connections
|
|
33
|
-
listConnections(projectId: string): Promise<Connection[]>;
|
|
34
|
-
getConnectionById(id: string): Promise<Connection | null>;
|
|
35
|
-
getConnectionByName(
|
|
36
|
-
projectId: string,
|
|
37
|
-
name: string,
|
|
38
|
-
): Promise<Connection | null>;
|
|
39
|
-
createConnection(
|
|
40
|
-
connection: Omit<Connection, "id" | "createdAt" | "updatedAt">,
|
|
41
|
-
): Promise<Connection>;
|
|
42
|
-
updateConnection(
|
|
43
|
-
id: string,
|
|
44
|
-
updates: Partial<Connection>,
|
|
45
|
-
): Promise<Connection>;
|
|
46
|
-
deleteConnection(id: string): Promise<void>;
|
|
47
|
-
|
|
48
|
-
// Materializations
|
|
49
|
-
listMaterializations(
|
|
50
|
-
projectId: string,
|
|
51
|
-
packageName: string,
|
|
52
|
-
options?: { limit?: number; offset?: number },
|
|
53
|
-
): Promise<Materialization[]>;
|
|
54
|
-
getMaterializationById(id: string): Promise<Materialization | null>;
|
|
55
|
-
getActiveMaterialization(
|
|
56
|
-
projectId: string,
|
|
57
|
-
packageName: string,
|
|
58
|
-
): Promise<Materialization | null>;
|
|
59
|
-
createMaterialization(
|
|
60
|
-
projectId: string,
|
|
61
|
-
packageName: string,
|
|
62
|
-
status?: MaterializationStatus,
|
|
63
|
-
metadata?: Record<string, unknown> | null,
|
|
64
|
-
): Promise<Materialization>;
|
|
65
|
-
updateMaterialization(
|
|
66
|
-
id: string,
|
|
67
|
-
updates: {
|
|
68
|
-
status?: MaterializationStatus;
|
|
69
|
-
startedAt?: Date;
|
|
70
|
-
completedAt?: Date;
|
|
71
|
-
error?: string | null;
|
|
72
|
-
metadata?: Record<string, unknown> | null;
|
|
73
|
-
},
|
|
74
|
-
): Promise<Materialization>;
|
|
75
|
-
deleteMaterialization(id: string): Promise<void>;
|
|
76
|
-
// Build Manifests
|
|
77
|
-
listManifestEntries(
|
|
78
|
-
projectId: string,
|
|
79
|
-
packageName: string,
|
|
80
|
-
): Promise<ManifestEntry[]>;
|
|
81
|
-
upsertManifestEntry(
|
|
82
|
-
entry: Omit<ManifestEntry, "id" | "createdAt" | "updatedAt">,
|
|
83
|
-
): Promise<ManifestEntry>;
|
|
84
|
-
deleteManifestEntry(id: string): Promise<void>;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export interface Project {
|
|
88
|
-
id: string;
|
|
89
|
-
name: string;
|
|
90
|
-
path: string;
|
|
91
|
-
description?: string;
|
|
92
|
-
createdAt: Date;
|
|
93
|
-
updatedAt: Date;
|
|
94
|
-
metadata?: Record<string, unknown>;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export interface Package {
|
|
98
|
-
id: string;
|
|
99
|
-
projectId: string;
|
|
100
|
-
name: string;
|
|
101
|
-
description?: string;
|
|
102
|
-
manifestPath: string;
|
|
103
|
-
createdAt: Date;
|
|
104
|
-
updatedAt: Date;
|
|
105
|
-
metadata?: Record<string, unknown>;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export interface Connection {
|
|
109
|
-
id: string;
|
|
110
|
-
projectId: string;
|
|
111
|
-
name: string;
|
|
112
|
-
type: "bigquery" | "postgres" | "duckdb" | "mysql" | "snowflake" | "trino";
|
|
113
|
-
config: Record<string, unknown>;
|
|
114
|
-
createdAt: Date;
|
|
115
|
-
updatedAt: Date;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export type MaterializationStatus =
|
|
119
|
-
| "PENDING"
|
|
120
|
-
| "RUNNING"
|
|
121
|
-
| "SUCCESS"
|
|
122
|
-
| "FAILED"
|
|
123
|
-
| "CANCELLED";
|
|
124
|
-
|
|
125
|
-
export interface Materialization {
|
|
126
|
-
id: string;
|
|
127
|
-
projectId: string;
|
|
128
|
-
packageName: string;
|
|
129
|
-
status: MaterializationStatus;
|
|
130
|
-
startedAt: Date | null;
|
|
131
|
-
completedAt: Date | null;
|
|
132
|
-
error: string | null;
|
|
133
|
-
metadata: Record<string, unknown> | null;
|
|
134
|
-
createdAt: Date;
|
|
135
|
-
updatedAt: Date;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export interface ManifestEntry {
|
|
139
|
-
id: string;
|
|
140
|
-
projectId: string;
|
|
141
|
-
packageName: string;
|
|
142
|
-
buildId: string;
|
|
143
|
-
tableName: string;
|
|
144
|
-
sourceName: string;
|
|
145
|
-
connectionName: string;
|
|
146
|
-
createdAt: Date;
|
|
147
|
-
updatedAt: Date;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// ==================== MANIFEST STORE ====================
|
|
151
|
-
|
|
152
|
-
export interface BuildManifestEntry {
|
|
153
|
-
tableName: string;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
export interface BuildManifest {
|
|
157
|
-
entries: Record<string, BuildManifestEntry>;
|
|
158
|
-
strict?: boolean;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Abstraction for manifest storage. Standalone mode uses DuckDB;
|
|
163
|
-
* orchestrated mode swaps in a DuckLakeManifestStore.
|
|
164
|
-
*/
|
|
165
|
-
export interface ManifestStore {
|
|
166
|
-
getManifest(projectId: string, packageName: string): Promise<BuildManifest>;
|
|
167
|
-
writeEntry(
|
|
168
|
-
projectId: string,
|
|
169
|
-
packageName: string,
|
|
170
|
-
buildId: string,
|
|
171
|
-
tableName: string,
|
|
172
|
-
sourceName: string,
|
|
173
|
-
connectionName: string,
|
|
174
|
-
): Promise<void>;
|
|
175
|
-
deleteEntry(id: string): Promise<void>;
|
|
176
|
-
listEntries(
|
|
177
|
-
projectId: string,
|
|
178
|
-
packageName: string,
|
|
179
|
-
): Promise<ManifestEntry[]>;
|
|
180
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
type MockData = Record<string, unknown>;
|
|
2
|
-
|
|
3
|
-
export class StorageManager {
|
|
4
|
-
async initialize(): Promise<void> {
|
|
5
|
-
return;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
getRepository() {
|
|
9
|
-
return {
|
|
10
|
-
listProjects: async (): Promise<unknown[]> => [],
|
|
11
|
-
createProject: async (data: MockData): Promise<MockData> => ({
|
|
12
|
-
id: "test-id",
|
|
13
|
-
...data,
|
|
14
|
-
}),
|
|
15
|
-
updateProject: async (
|
|
16
|
-
id: string,
|
|
17
|
-
data: MockData,
|
|
18
|
-
): Promise<MockData> => ({
|
|
19
|
-
id,
|
|
20
|
-
...data,
|
|
21
|
-
}),
|
|
22
|
-
getPackages: async (): Promise<unknown[]> => [],
|
|
23
|
-
createPackage: async (data: MockData): Promise<MockData> => ({
|
|
24
|
-
id: "test-id",
|
|
25
|
-
...data,
|
|
26
|
-
}),
|
|
27
|
-
updatePackage: async (
|
|
28
|
-
id: string,
|
|
29
|
-
data: MockData,
|
|
30
|
-
): Promise<MockData> => ({
|
|
31
|
-
id,
|
|
32
|
-
...data,
|
|
33
|
-
}),
|
|
34
|
-
deletePackage: async (): Promise<void> => {},
|
|
35
|
-
getConnections: async (): Promise<unknown[]> => [],
|
|
36
|
-
createConnection: async (data: MockData): Promise<MockData> => ({
|
|
37
|
-
id: "test-id",
|
|
38
|
-
...data,
|
|
39
|
-
}),
|
|
40
|
-
updateConnection: async (
|
|
41
|
-
id: string,
|
|
42
|
-
data: MockData,
|
|
43
|
-
): Promise<MockData> => ({
|
|
44
|
-
id,
|
|
45
|
-
...data,
|
|
46
|
-
}),
|
|
47
|
-
deleteConnection: async (): Promise<void> => {},
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
getManifestStore() {
|
|
52
|
-
return {
|
|
53
|
-
getManifest: async () => ({ entries: {}, strict: false }),
|
|
54
|
-
writeEntry: async () => {},
|
|
55
|
-
deleteEntry: async () => {},
|
|
56
|
-
listEntries: async () => [],
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
}
|
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
import { logger } from "../logger";
|
|
2
|
-
import {
|
|
3
|
-
DatabaseConnection,
|
|
4
|
-
ManifestStore,
|
|
5
|
-
ResourceRepository,
|
|
6
|
-
} from "./DatabaseInterface";
|
|
7
|
-
import { DuckDBConnection } from "./duckdb/DuckDBConnection";
|
|
8
|
-
import { DuckDBManifestStore } from "./duckdb/DuckDBManifestStore";
|
|
9
|
-
import { DuckDBRepository } from "./duckdb/DuckDBRepository";
|
|
10
|
-
import { initializeSchema } from "./duckdb/schema";
|
|
11
|
-
import { DuckLakeManifestStore } from "./ducklake/DuckLakeManifestStore";
|
|
12
|
-
|
|
13
|
-
export type StorageType = "duckdb" | "postgres" | "mysql";
|
|
14
|
-
|
|
15
|
-
export interface DuckLakeManifestConfig {
|
|
16
|
-
catalogUrl: string;
|
|
17
|
-
dataPath: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface StorageConfig {
|
|
21
|
-
type: StorageType;
|
|
22
|
-
duckdb?: {
|
|
23
|
-
path?: string;
|
|
24
|
-
};
|
|
25
|
-
postgres?: {
|
|
26
|
-
host: string;
|
|
27
|
-
port: number;
|
|
28
|
-
database: string;
|
|
29
|
-
user: string;
|
|
30
|
-
password: string;
|
|
31
|
-
};
|
|
32
|
-
mysql?: {
|
|
33
|
-
host: string;
|
|
34
|
-
port: number;
|
|
35
|
-
database: string;
|
|
36
|
-
user: string;
|
|
37
|
-
password: string;
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function escapeSQL(value: string): string {
|
|
42
|
-
return value.replace(/'/g, "''");
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
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
|
|
49
|
-
* DuckLake-backed store attached lazily on first access.
|
|
50
|
-
*/
|
|
51
|
-
export class StorageManager {
|
|
52
|
-
private connection: DatabaseConnection | null = null;
|
|
53
|
-
private duckDbConnection: DuckDBConnection | null = null;
|
|
54
|
-
private repository: ResourceRepository | null = null;
|
|
55
|
-
private defaultManifestStore: ManifestStore | null = null;
|
|
56
|
-
|
|
57
|
-
/** Per-project DuckLake manifest stores, keyed by projectId. */
|
|
58
|
-
private projectManifestStores = new Map<string, ManifestStore>();
|
|
59
|
-
|
|
60
|
-
/** Tracks which catalogs have been attached to avoid duplicate ATTACHes. */
|
|
61
|
-
private attachedCatalogs = new Set<string>();
|
|
62
|
-
|
|
63
|
-
private config: StorageConfig;
|
|
64
|
-
|
|
65
|
-
constructor(config: StorageConfig) {
|
|
66
|
-
this.config = config;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async initialize(reinit: boolean = false): Promise<void> {
|
|
70
|
-
if (reinit) {
|
|
71
|
-
logger.info(
|
|
72
|
-
"Reinitialization mode: Database will be dropped and recreated",
|
|
73
|
-
);
|
|
74
|
-
} else {
|
|
75
|
-
logger.info("Normal mode: Loading from existing database");
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
switch (this.config.type) {
|
|
79
|
-
case "duckdb":
|
|
80
|
-
await this.initializeDuckDB(reinit);
|
|
81
|
-
break;
|
|
82
|
-
case "postgres":
|
|
83
|
-
throw new Error("PostgreSQL storage not yet implemented");
|
|
84
|
-
case "mysql":
|
|
85
|
-
throw new Error("MySQL storage not yet implemented");
|
|
86
|
-
default:
|
|
87
|
-
throw new Error(`Unsupported storage type: ${this.config.type}`);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
private async initializeDuckDB(reinit: boolean): Promise<void> {
|
|
92
|
-
const dbPath = this.config.duckdb?.path;
|
|
93
|
-
|
|
94
|
-
const connection = new DuckDBConnection(dbPath);
|
|
95
|
-
|
|
96
|
-
await connection.initialize();
|
|
97
|
-
|
|
98
|
-
await initializeSchema(connection, reinit);
|
|
99
|
-
|
|
100
|
-
this.connection = connection;
|
|
101
|
-
this.duckDbConnection = connection;
|
|
102
|
-
this.repository = new DuckDBRepository(connection);
|
|
103
|
-
this.defaultManifestStore = new DuckDBManifestStore(this.repository);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
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.
|
|
110
|
-
*/
|
|
111
|
-
async initializeDuckLakeForProject(
|
|
112
|
-
projectId: string,
|
|
113
|
-
config: DuckLakeManifestConfig,
|
|
114
|
-
): Promise<void> {
|
|
115
|
-
if (!this.duckDbConnection) {
|
|
116
|
-
throw new Error("Storage not initialized. Call initialize() first.");
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const catalogName = `manifest_lake_${projectId.replace(/[^a-zA-Z0-9_]/g, "_")}`;
|
|
120
|
-
|
|
121
|
-
if (!this.attachedCatalogs.has(catalogName)) {
|
|
122
|
-
await this.attachDuckLakeCatalog(config, catalogName);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const store = new DuckLakeManifestStore(
|
|
126
|
-
this.duckDbConnection,
|
|
127
|
-
catalogName,
|
|
128
|
-
);
|
|
129
|
-
await store.bootstrapSchema();
|
|
130
|
-
|
|
131
|
-
this.projectManifestStores.set(projectId, store);
|
|
132
|
-
logger.info("DuckLake manifest store initialized for project", {
|
|
133
|
-
projectId,
|
|
134
|
-
catalogName,
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
private async attachDuckLakeCatalog(
|
|
139
|
-
config: DuckLakeManifestConfig,
|
|
140
|
-
catalogName: string,
|
|
141
|
-
): Promise<void> {
|
|
142
|
-
const connection = this.duckDbConnection!;
|
|
143
|
-
|
|
144
|
-
await connection.run("INSTALL ducklake; LOAD ducklake;");
|
|
145
|
-
|
|
146
|
-
const isPostgres = config.catalogUrl.startsWith("postgres:");
|
|
147
|
-
if (isPostgres) {
|
|
148
|
-
await connection.run("INSTALL postgres; LOAD postgres;");
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const escapedCatalogUrl = escapeSQL(config.catalogUrl);
|
|
152
|
-
const escapedDataPath = escapeSQL(config.dataPath);
|
|
153
|
-
const isCloudStorage =
|
|
154
|
-
config.dataPath.startsWith("gs://") ||
|
|
155
|
-
config.dataPath.startsWith("s3://");
|
|
156
|
-
|
|
157
|
-
let attachCmd = `ATTACH 'ducklake:${escapedCatalogUrl}' AS ${catalogName}`;
|
|
158
|
-
const attachOpts: string[] = [`DATA_PATH '${escapedDataPath}'`];
|
|
159
|
-
if (isCloudStorage) {
|
|
160
|
-
attachOpts.push("OVERRIDE_DATA_PATH true");
|
|
161
|
-
}
|
|
162
|
-
attachCmd += ` (${attachOpts.join(", ")});`;
|
|
163
|
-
|
|
164
|
-
logger.info(`Attaching DuckLake manifest catalog: ${attachCmd}`);
|
|
165
|
-
await connection.run(attachCmd);
|
|
166
|
-
|
|
167
|
-
this.attachedCatalogs.add(catalogName);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
getRepository(): ResourceRepository {
|
|
171
|
-
if (!this.repository) {
|
|
172
|
-
throw new Error("Storage not initialized. Call initialize() first.");
|
|
173
|
-
}
|
|
174
|
-
return this.repository;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Returns the manifest store for a project. If the project has a
|
|
179
|
-
* DuckLake store configured, returns that; otherwise returns the
|
|
180
|
-
* default DuckDB-backed store.
|
|
181
|
-
*/
|
|
182
|
-
getManifestStore(projectId?: string): ManifestStore {
|
|
183
|
-
if (projectId) {
|
|
184
|
-
const projectStore = this.projectManifestStores.get(projectId);
|
|
185
|
-
if (projectStore) {
|
|
186
|
-
return projectStore;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
if (!this.defaultManifestStore) {
|
|
190
|
-
throw new Error("Storage not initialized. Call initialize() first.");
|
|
191
|
-
}
|
|
192
|
-
return this.defaultManifestStore;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
async close(): Promise<void> {
|
|
196
|
-
if (this.connection) {
|
|
197
|
-
await this.connection.close();
|
|
198
|
-
this.connection = null;
|
|
199
|
-
this.duckDbConnection = null;
|
|
200
|
-
this.repository = null;
|
|
201
|
-
this.defaultManifestStore = null;
|
|
202
|
-
this.projectManifestStores.clear();
|
|
203
|
-
this.attachedCatalogs.clear();
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
isInitialized(): boolean {
|
|
208
|
-
return this.connection !== null && this.repository !== null;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import { Connection } from "../DatabaseInterface";
|
|
2
|
-
import { DuckDBConnection } from "./DuckDBConnection";
|
|
3
|
-
|
|
4
|
-
export class ConnectionRepository {
|
|
5
|
-
constructor(private db: DuckDBConnection) {}
|
|
6
|
-
|
|
7
|
-
private generateId(): string {
|
|
8
|
-
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
private now(): Date {
|
|
12
|
-
return new Date();
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
async listConnections(projectId: string): Promise<Connection[]> {
|
|
16
|
-
try {
|
|
17
|
-
const rows = await this.db.all<Record<string, unknown>>(
|
|
18
|
-
"SELECT * FROM connections WHERE project_id = ? ORDER BY name",
|
|
19
|
-
[projectId],
|
|
20
|
-
);
|
|
21
|
-
return rows.map(this.mapToConnection);
|
|
22
|
-
} catch (err: unknown) {
|
|
23
|
-
const error = err as Error;
|
|
24
|
-
console.error("Failed to get connections:", error.message);
|
|
25
|
-
throw error;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async getConnectionById(id: string): Promise<Connection | null> {
|
|
30
|
-
const row = await this.db.get<Record<string, unknown>>(
|
|
31
|
-
"SELECT * FROM connections WHERE id = ?",
|
|
32
|
-
[id],
|
|
33
|
-
);
|
|
34
|
-
return row ? this.mapToConnection(row) : null;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
async getConnectionByName(
|
|
38
|
-
projectId: string,
|
|
39
|
-
name: string,
|
|
40
|
-
): Promise<Connection | null> {
|
|
41
|
-
const row = await this.db.get<Record<string, unknown>>(
|
|
42
|
-
"SELECT * FROM connections WHERE project_id = ? AND name = ?",
|
|
43
|
-
[projectId, name],
|
|
44
|
-
);
|
|
45
|
-
return row ? this.mapToConnection(row) : null;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async createConnection(
|
|
49
|
-
connection: Omit<Connection, "id" | "createdAt" | "updatedAt">,
|
|
50
|
-
): Promise<Connection> {
|
|
51
|
-
const id = this.generateId();
|
|
52
|
-
const now = this.now();
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
const configJson = JSON.stringify(connection.config);
|
|
56
|
-
|
|
57
|
-
await this.db.run(
|
|
58
|
-
`INSERT INTO connections (id, project_id, name, type, config, created_at, updated_at)
|
|
59
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
60
|
-
[
|
|
61
|
-
id,
|
|
62
|
-
connection.projectId,
|
|
63
|
-
connection.name,
|
|
64
|
-
connection.type,
|
|
65
|
-
configJson,
|
|
66
|
-
now.toISOString(),
|
|
67
|
-
now.toISOString(),
|
|
68
|
-
],
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
return {
|
|
72
|
-
id,
|
|
73
|
-
...connection,
|
|
74
|
-
createdAt: now,
|
|
75
|
-
updatedAt: now,
|
|
76
|
-
};
|
|
77
|
-
} catch (err: unknown) {
|
|
78
|
-
const error = err as Error;
|
|
79
|
-
console.error("Failed to create connection:", error.message);
|
|
80
|
-
throw error;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
async updateConnection(
|
|
85
|
-
id: string,
|
|
86
|
-
updates: Partial<Connection>,
|
|
87
|
-
): Promise<Connection> {
|
|
88
|
-
const existing = await this.getConnectionById(id);
|
|
89
|
-
if (!existing) {
|
|
90
|
-
throw new Error(`Connection with id ${id} not found`);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const now = this.now();
|
|
94
|
-
const setClauses: string[] = [];
|
|
95
|
-
const params: unknown[] = [];
|
|
96
|
-
|
|
97
|
-
if (updates.name !== undefined) {
|
|
98
|
-
setClauses.push(`name = ?`);
|
|
99
|
-
params.push(updates.name);
|
|
100
|
-
}
|
|
101
|
-
if (updates.type !== undefined) {
|
|
102
|
-
setClauses.push(`type = ?`);
|
|
103
|
-
params.push(updates.type);
|
|
104
|
-
}
|
|
105
|
-
if (updates.config !== undefined) {
|
|
106
|
-
setClauses.push(`config = ?`);
|
|
107
|
-
params.push(JSON.stringify(updates.config));
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
setClauses.push(`updated_at = ?`);
|
|
111
|
-
params.push(now.toISOString());
|
|
112
|
-
params.push(id);
|
|
113
|
-
|
|
114
|
-
await this.db.run(
|
|
115
|
-
`UPDATE connections SET ${setClauses.join(", ")} WHERE id = ?`,
|
|
116
|
-
params,
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
return this.getConnectionById(id) as Promise<Connection>;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
async deleteConnection(id: string): Promise<void> {
|
|
123
|
-
await this.db.run("DELETE FROM connections WHERE id = ?", [id]);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
async deleteConnectionsByProjectId(id: string): Promise<void> {
|
|
127
|
-
await this.db.run("DELETE FROM connections WHERE project_id = ?", [id]);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
private mapToConnection(row: Record<string, unknown>): Connection {
|
|
131
|
-
return {
|
|
132
|
-
id: row.id as string,
|
|
133
|
-
projectId: row.project_id as string,
|
|
134
|
-
name: row.name as string,
|
|
135
|
-
type: row.type as Connection["type"],
|
|
136
|
-
config: JSON.parse(row.config as string),
|
|
137
|
-
createdAt: new Date(row.created_at as string),
|
|
138
|
-
updatedAt: new Date(row.updated_at as string),
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
}
|