@axiom-lattice/pg-stores 1.0.13 → 1.0.14
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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +9 -0
- package/README_DATABASE_CONFIG.md +217 -0
- package/dist/index.d.mts +253 -3
- package/dist/index.d.ts +253 -3
- package/dist/index.js +759 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +752 -1
- package/dist/index.mjs.map +1 -1
- package/examples/database-config-store.example.ts +178 -0
- package/examples/workspace-project-store.example.ts +151 -0
- package/package.json +3 -3
- package/src/__tests__/workspace-project-store.test.ts +235 -0
- package/src/index.ts +24 -0
- package/src/migrations/database_config_migrations.ts +47 -0
- package/src/migrations/project_migrations.ts +50 -0
- package/src/migrations/workspace_migrations.ts +44 -0
- package/src/stores/PostgreSQLDatabaseConfigStore.ts +434 -0
- package/src/stores/PostgreSQLProjectStore.ts +282 -0
- package/src/stores/PostgreSQLWorkspaceStore.ts +286 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DatabaseConfigStore 使用示例
|
|
3
|
+
*
|
|
4
|
+
* 示例代码展示如何使用 DatabaseConfigStore 管理数据库配置
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// 示例 1: 使用 InMemory 存储(默认)
|
|
9
|
+
// ============================================================================
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
storeLatticeManager,
|
|
13
|
+
InMemoryDatabaseConfigStore,
|
|
14
|
+
} from '@axiom-lattice/core';
|
|
15
|
+
import type { DatabaseConfig } from '@axiom-lattice/protocols';
|
|
16
|
+
|
|
17
|
+
async function exampleInMemory() {
|
|
18
|
+
// 获取默认的 InMemory Store
|
|
19
|
+
const store = await storeLatticeManager.getStoreLattice('default', 'database').store;
|
|
20
|
+
|
|
21
|
+
const tenantId = 'tenant-123';
|
|
22
|
+
|
|
23
|
+
// 创建数据库配置
|
|
24
|
+
const dbConfig: DatabaseConfig = {
|
|
25
|
+
type: 'postgres',
|
|
26
|
+
host: 'localhost',
|
|
27
|
+
port: 5432,
|
|
28
|
+
database: 'mydb',
|
|
29
|
+
user: 'admin',
|
|
30
|
+
password: 'secret123', // 会自动加密存储(PG 实现)
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
await store.createConfig(tenantId, 'config-1', {
|
|
34
|
+
key: 'main-db',
|
|
35
|
+
config: dbConfig,
|
|
36
|
+
name: 'Main Database',
|
|
37
|
+
description: 'Primary PostgreSQL database',
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// 获取配置
|
|
41
|
+
const config = await store.getConfigByKey(tenantId, 'main-db');
|
|
42
|
+
console.log('Database config:', config);
|
|
43
|
+
|
|
44
|
+
// 获取所有配置
|
|
45
|
+
const allConfigs = await store.getAllConfigs(tenantId);
|
|
46
|
+
console.log('All configs:', allConfigs);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ============================================================================
|
|
50
|
+
// 示例 2: 使用 PostgreSQL 存储
|
|
51
|
+
// ============================================================================
|
|
52
|
+
|
|
53
|
+
import { PostgreSQLDatabaseConfigStore } from '@axiom-lattice/pg-stores';
|
|
54
|
+
|
|
55
|
+
async function examplePostgreSQL() {
|
|
56
|
+
// 创建 PostgreSQL Store
|
|
57
|
+
const pgStore = new PostgreSQLDatabaseConfigStore({
|
|
58
|
+
poolConfig: process.env.DATABASE_URL || 'postgresql://localhost:5432/lattice',
|
|
59
|
+
autoMigrate: true, // 自动运行迁移
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const tenantId = 'tenant-123';
|
|
63
|
+
|
|
64
|
+
// 创建数据库配置
|
|
65
|
+
await pgStore.createConfig(tenantId, 'config-2', {
|
|
66
|
+
key: 'analytics-db',
|
|
67
|
+
config: {
|
|
68
|
+
type: 'postgres',
|
|
69
|
+
host: 'analytics.db',
|
|
70
|
+
port: 5432,
|
|
71
|
+
database: 'analytics',
|
|
72
|
+
user: 'analyst',
|
|
73
|
+
password: 'analytics-pass',
|
|
74
|
+
ssl: true,
|
|
75
|
+
},
|
|
76
|
+
name: 'Analytics Database',
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// 获取配置(密码会自动解密)
|
|
80
|
+
const config = await pgStore.getConfigByKey(tenantId, 'analytics-db');
|
|
81
|
+
console.log('Analytics DB password (decrypted):', config?.config.password);
|
|
82
|
+
|
|
83
|
+
// 更新配置
|
|
84
|
+
await pgStore.updateConfig(tenantId, 'config-2', {
|
|
85
|
+
config: {
|
|
86
|
+
...config!.config,
|
|
87
|
+
password: 'new-password',
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// 删除配置
|
|
92
|
+
await pgStore.deleteConfig(tenantId, 'config-2');
|
|
93
|
+
|
|
94
|
+
// 清理资源
|
|
95
|
+
await pgStore.dispose();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ============================================================================
|
|
99
|
+
// 示例 3: 集成到 SqlDatabaseManager
|
|
100
|
+
// ============================================================================
|
|
101
|
+
|
|
102
|
+
import { sqlDatabaseManager } from '@axiom-lattice/core';
|
|
103
|
+
|
|
104
|
+
async function exampleWithSqlManager() {
|
|
105
|
+
const store = await storeLatticeManager.getStoreLattice('default', 'database').store;
|
|
106
|
+
const tenantId = 'tenant-123';
|
|
107
|
+
|
|
108
|
+
// 从 Store 加载所有数据库配置并注册到 SqlDatabaseManager
|
|
109
|
+
await sqlDatabaseManager.loadConfigsFromStore(store, tenantId);
|
|
110
|
+
|
|
111
|
+
// 现在可以使用注册的数据库
|
|
112
|
+
const db = sqlDatabaseManager.getDatabase('main-db');
|
|
113
|
+
const tables = await db.listTables();
|
|
114
|
+
console.log('Tables:', tables);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ============================================================================
|
|
118
|
+
// 示例 4: 多租户场景
|
|
119
|
+
// ============================================================================
|
|
120
|
+
|
|
121
|
+
async function exampleMultiTenant() {
|
|
122
|
+
const store = await storeLatticeManager.getStoreLattice('default', 'database').store;
|
|
123
|
+
|
|
124
|
+
// 为不同租户创建配置
|
|
125
|
+
await store.createConfig('tenant-a', 'config-1', {
|
|
126
|
+
key: 'db',
|
|
127
|
+
config: { type: 'postgres', database: 'db-a', user: 'user-a', password: 'pass-a' },
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
await store.createConfig('tenant-b', 'config-1', {
|
|
131
|
+
key: 'db',
|
|
132
|
+
config: { type: 'postgres', database: 'db-b', user: 'user-b', password: 'pass-b' },
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// 获取租户 A 的配置
|
|
136
|
+
const tenantAConfigs = await store.getAllConfigs('tenant-a');
|
|
137
|
+
console.log('Tenant A configs:', tenantAConfigs);
|
|
138
|
+
|
|
139
|
+
// 获取租户 B 的配置
|
|
140
|
+
const tenantBConfigs = await store.getAllConfigs('tenant-b');
|
|
141
|
+
console.log('Tenant B configs:', tenantBConfigs);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ============================================================================
|
|
145
|
+
// 示例 5: 环境变量配置
|
|
146
|
+
// ============================================================================
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* 设置加密密钥(生产环境必需)
|
|
150
|
+
*
|
|
151
|
+
* 方法 1: 环境变量
|
|
152
|
+
* export LATTICE_ENCRYPTION_KEY="your-secret-key-at-least-32-characters"
|
|
153
|
+
*
|
|
154
|
+
* 方法 2: 代码中设置(不推荐,仅用于测试)
|
|
155
|
+
* process.env.LATTICE_ENCRYPTION_KEY = 'your-secret-key';
|
|
156
|
+
*
|
|
157
|
+
* 注意:
|
|
158
|
+
* - 密钥长度建议 32 字符以上
|
|
159
|
+
* - 生产环境必须设置,否则会有警告
|
|
160
|
+
* - 密钥丢失将导致无法解密已存储的密码
|
|
161
|
+
*/
|
|
162
|
+
|
|
163
|
+
// ============================================================================
|
|
164
|
+
// 运行示例
|
|
165
|
+
// ============================================================================
|
|
166
|
+
|
|
167
|
+
async function main() {
|
|
168
|
+
try {
|
|
169
|
+
await exampleInMemory();
|
|
170
|
+
await examplePostgreSQL();
|
|
171
|
+
await exampleWithSqlManager();
|
|
172
|
+
await exampleMultiTenant();
|
|
173
|
+
} catch (error) {
|
|
174
|
+
console.error('Error running examples:', error);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// main();
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL Workspace and Project Store Example
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates how to use PostgreSQLWorkspaceStore and PostgreSQLProjectStore
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
PostgreSQLWorkspaceStore,
|
|
9
|
+
PostgreSQLProjectStore,
|
|
10
|
+
} from "@axiom-lattice/pg-stores";
|
|
11
|
+
import { storeLatticeManager } from "@axiom-lattice/core";
|
|
12
|
+
|
|
13
|
+
async function main() {
|
|
14
|
+
// PostgreSQL connection configuration
|
|
15
|
+
const poolConfig = process.env.DATABASE_URL || "postgresql://user:password@localhost:5432/axiom";
|
|
16
|
+
|
|
17
|
+
console.log("=== PostgreSQL Workspace and Project Store Example ===\n");
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
// Create store instances
|
|
21
|
+
const workspaceStore = new PostgreSQLWorkspaceStore({
|
|
22
|
+
poolConfig,
|
|
23
|
+
autoMigrate: true,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const projectStore = new PostgreSQLProjectStore({
|
|
27
|
+
poolConfig,
|
|
28
|
+
autoMigrate: true,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Wait for stores to initialize
|
|
32
|
+
await workspaceStore.initialize();
|
|
33
|
+
await projectStore.initialize();
|
|
34
|
+
|
|
35
|
+
console.log("✓ Stores initialized successfully\n");
|
|
36
|
+
|
|
37
|
+
const tenantId = "tenant-001";
|
|
38
|
+
|
|
39
|
+
// ============ Workspace Operations ============
|
|
40
|
+
console.log("--- Workspace Operations ---");
|
|
41
|
+
|
|
42
|
+
// Create workspaces
|
|
43
|
+
const workspace1 = await workspaceStore.createWorkspace(tenantId, "ws-001", {
|
|
44
|
+
name: "Development Workspace",
|
|
45
|
+
description: "For development team",
|
|
46
|
+
storageType: "filesystem",
|
|
47
|
+
});
|
|
48
|
+
console.log("Created workspace:", workspace1.name);
|
|
49
|
+
|
|
50
|
+
const workspace2 = await workspaceStore.createWorkspace(tenantId, "ws-002", {
|
|
51
|
+
name: "Production Workspace",
|
|
52
|
+
description: "For production systems",
|
|
53
|
+
storageType: "sandbox",
|
|
54
|
+
});
|
|
55
|
+
console.log("Created workspace:", workspace2.name);
|
|
56
|
+
|
|
57
|
+
// Get all workspaces
|
|
58
|
+
const allWorkspaces = await workspaceStore.getAllWorkspaces(tenantId);
|
|
59
|
+
console.log(`\nTotal workspaces: ${allWorkspaces.length}`);
|
|
60
|
+
allWorkspaces.forEach((ws) => {
|
|
61
|
+
console.log(` - ${ws.name} (${ws.storageType})`);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Update workspace
|
|
65
|
+
const updated = await workspaceStore.updateWorkspace(tenantId, "ws-001", {
|
|
66
|
+
description: "Updated description for dev team",
|
|
67
|
+
});
|
|
68
|
+
console.log("\nUpdated workspace:", updated?.description);
|
|
69
|
+
|
|
70
|
+
// ============ Project Operations ============
|
|
71
|
+
console.log("\n--- Project Operations ---");
|
|
72
|
+
|
|
73
|
+
// Create projects in workspace1
|
|
74
|
+
const project1 = await projectStore.createProject(tenantId, "ws-001", "proj-001", {
|
|
75
|
+
name: "API Service",
|
|
76
|
+
description: "REST API development",
|
|
77
|
+
});
|
|
78
|
+
console.log("Created project:", project1.name, "in workspace:", workspace1.name);
|
|
79
|
+
|
|
80
|
+
const project2 = await projectStore.createProject(tenantId, "ws-001", "proj-002", {
|
|
81
|
+
name: "Web Frontend",
|
|
82
|
+
description: "React frontend application",
|
|
83
|
+
});
|
|
84
|
+
console.log("Created project:", project2.name, "in workspace:", workspace1.name);
|
|
85
|
+
|
|
86
|
+
// Create project in workspace2
|
|
87
|
+
const project3 = await projectStore.createProject(tenantId, "ws-002", "proj-003", {
|
|
88
|
+
name: "Monitoring System",
|
|
89
|
+
description: "System monitoring and alerting",
|
|
90
|
+
});
|
|
91
|
+
console.log("Created project:", project3.name, "in workspace:", workspace2.name);
|
|
92
|
+
|
|
93
|
+
// Get projects by workspace
|
|
94
|
+
const ws1Projects = await projectStore.getProjectsByWorkspace(tenantId, "ws-001");
|
|
95
|
+
console.log(`\nProjects in '${workspace1.name}':`);
|
|
96
|
+
ws1Projects.forEach((proj) => {
|
|
97
|
+
console.log(` - ${proj.name}: ${proj.description}`);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Get single project
|
|
101
|
+
const fetchedProject = await projectStore.getProjectById(tenantId, "proj-001");
|
|
102
|
+
console.log("\nFetched project by ID:", fetchedProject?.name);
|
|
103
|
+
|
|
104
|
+
// Update project
|
|
105
|
+
const updatedProject = await projectStore.updateProject(tenantId, "proj-001", {
|
|
106
|
+
description: "REST API development - Version 2.0",
|
|
107
|
+
});
|
|
108
|
+
console.log("Updated project description:", updatedProject?.description);
|
|
109
|
+
|
|
110
|
+
// ============ Register to StoreLatticeManager ============
|
|
111
|
+
console.log("\n--- Registering to StoreLatticeManager ---");
|
|
112
|
+
|
|
113
|
+
storeLatticeManager.registerLattice("production", "workspace", workspaceStore);
|
|
114
|
+
storeLatticeManager.registerLattice("production", "project", projectStore);
|
|
115
|
+
|
|
116
|
+
console.log("✓ Registered PostgreSQL stores as 'production' in StoreLatticeManager");
|
|
117
|
+
|
|
118
|
+
// Verify registration
|
|
119
|
+
const registeredWsStore = storeLatticeManager.getStoreLattice("production", "workspace");
|
|
120
|
+
const registeredProjStore = storeLatticeManager.getStoreLattice("production", "project");
|
|
121
|
+
console.log("✓ Can retrieve stores from manager");
|
|
122
|
+
|
|
123
|
+
// ============ Cleanup ============
|
|
124
|
+
console.log("\n--- Cleanup ---");
|
|
125
|
+
|
|
126
|
+
// Delete projects
|
|
127
|
+
await projectStore.deleteProject(tenantId, "proj-001");
|
|
128
|
+
await projectStore.deleteProject(tenantId, "proj-002");
|
|
129
|
+
await projectStore.deleteProject(tenantId, "proj-003");
|
|
130
|
+
console.log("✓ Deleted all projects");
|
|
131
|
+
|
|
132
|
+
// Delete workspaces
|
|
133
|
+
await workspaceStore.deleteWorkspace(tenantId, "ws-001");
|
|
134
|
+
await workspaceStore.deleteWorkspace(tenantId, "ws-002");
|
|
135
|
+
console.log("✓ Deleted all workspaces");
|
|
136
|
+
|
|
137
|
+
// Dispose stores
|
|
138
|
+
await workspaceStore.dispose();
|
|
139
|
+
await projectStore.dispose();
|
|
140
|
+
console.log("✓ Disposed store connections");
|
|
141
|
+
|
|
142
|
+
console.log("\n=== Example completed successfully! ===");
|
|
143
|
+
|
|
144
|
+
} catch (error) {
|
|
145
|
+
console.error("Error:", error);
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Run the example
|
|
151
|
+
main();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axiom-lattice/pg-stores",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.14",
|
|
4
4
|
"description": "PG stores implementation for Axiom Lattice framework",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"pg": "^8.16.3",
|
|
24
|
-
"@axiom-lattice/protocols": "2.1.
|
|
25
|
-
"@axiom-lattice/core": "2.1.
|
|
24
|
+
"@axiom-lattice/protocols": "2.1.14",
|
|
25
|
+
"@axiom-lattice/core": "2.1.24"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/node": "^20.11.24",
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for PostgreSQLWorkspaceStore and PostgreSQLProjectStore
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { PostgreSQLWorkspaceStore } from "../stores/PostgreSQLWorkspaceStore";
|
|
6
|
+
import { PostgreSQLProjectStore } from "../stores/PostgreSQLProjectStore";
|
|
7
|
+
import { StorageType } from "@axiom-lattice/protocols";
|
|
8
|
+
|
|
9
|
+
// Skip tests if no database URL is provided
|
|
10
|
+
const TEST_DATABASE_URL = process.env.TEST_DATABASE_URL;
|
|
11
|
+
|
|
12
|
+
const describeIfDb = TEST_DATABASE_URL ? describe : describe.skip;
|
|
13
|
+
|
|
14
|
+
describeIfDb("PostgreSQLWorkspaceStore", () => {
|
|
15
|
+
let store: PostgreSQLWorkspaceStore;
|
|
16
|
+
const tenantId = "test-tenant";
|
|
17
|
+
|
|
18
|
+
beforeAll(async () => {
|
|
19
|
+
store = new PostgreSQLWorkspaceStore({
|
|
20
|
+
poolConfig: TEST_DATABASE_URL!,
|
|
21
|
+
autoMigrate: true,
|
|
22
|
+
});
|
|
23
|
+
await store.initialize();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
afterAll(async () => {
|
|
27
|
+
await store.dispose();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
beforeEach(async () => {
|
|
31
|
+
// Clean up any existing test data
|
|
32
|
+
const workspaces = await store.getAllWorkspaces(tenantId);
|
|
33
|
+
for (const ws of workspaces) {
|
|
34
|
+
if (ws.id.startsWith("test-")) {
|
|
35
|
+
await store.deleteWorkspace(tenantId, ws.id);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("should create a workspace", async () => {
|
|
41
|
+
const workspace = await store.createWorkspace(tenantId, "test-ws-001", {
|
|
42
|
+
name: "Test Workspace",
|
|
43
|
+
description: "Test description",
|
|
44
|
+
storageType: "filesystem" as StorageType,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
expect(workspace.id).toBe("test-ws-001");
|
|
48
|
+
expect(workspace.name).toBe("Test Workspace");
|
|
49
|
+
expect(workspace.description).toBe("Test description");
|
|
50
|
+
expect(workspace.storageType).toBe("filesystem");
|
|
51
|
+
expect(workspace.tenantId).toBe(tenantId);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("should get a workspace by ID", async () => {
|
|
55
|
+
await store.createWorkspace(tenantId, "test-ws-002", {
|
|
56
|
+
name: "Test Workspace 2",
|
|
57
|
+
storageType: "sandbox" as StorageType,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const fetched = await store.getWorkspaceById(tenantId, "test-ws-002");
|
|
61
|
+
expect(fetched).not.toBeNull();
|
|
62
|
+
expect(fetched?.name).toBe("Test Workspace 2");
|
|
63
|
+
expect(fetched?.storageType).toBe("sandbox");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("should return null for non-existent workspace", async () => {
|
|
67
|
+
const fetched = await store.getWorkspaceById(tenantId, "non-existent");
|
|
68
|
+
expect(fetched).toBeNull();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("should get all workspaces for a tenant", async () => {
|
|
72
|
+
await store.createWorkspace(tenantId, "test-ws-003", {
|
|
73
|
+
name: "Workspace 3",
|
|
74
|
+
storageType: "sandbox" as StorageType,
|
|
75
|
+
});
|
|
76
|
+
await store.createWorkspace(tenantId, "test-ws-004", {
|
|
77
|
+
name: "Workspace 4",
|
|
78
|
+
storageType: "filesystem" as StorageType,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const workspaces = await store.getAllWorkspaces(tenantId);
|
|
82
|
+
const testWorkspaces = workspaces.filter((ws) => ws.id.startsWith("test-"));
|
|
83
|
+
expect(testWorkspaces.length).toBeGreaterThanOrEqual(2);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("should update a workspace", async () => {
|
|
87
|
+
await store.createWorkspace(tenantId, "test-ws-005", {
|
|
88
|
+
name: "Original Name",
|
|
89
|
+
storageType: "sandbox" as StorageType,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const updated = await store.updateWorkspace(tenantId, "test-ws-005", {
|
|
93
|
+
name: "Updated Name",
|
|
94
|
+
description: "Updated description",
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
expect(updated).not.toBeNull();
|
|
98
|
+
expect(updated?.name).toBe("Updated Name");
|
|
99
|
+
expect(updated?.description).toBe("Updated description");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("should return null when updating non-existent workspace", async () => {
|
|
103
|
+
const updated = await store.updateWorkspace(tenantId, "non-existent", {
|
|
104
|
+
name: "New Name",
|
|
105
|
+
});
|
|
106
|
+
expect(updated).toBeNull();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("should delete a workspace", async () => {
|
|
110
|
+
await store.createWorkspace(tenantId, "test-ws-006", {
|
|
111
|
+
name: "To Delete",
|
|
112
|
+
storageType: "sandbox" as StorageType,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const deleted = await store.deleteWorkspace(tenantId, "test-ws-006");
|
|
116
|
+
expect(deleted).toBe(true);
|
|
117
|
+
|
|
118
|
+
const fetched = await store.getWorkspaceById(tenantId, "test-ws-006");
|
|
119
|
+
expect(fetched).toBeNull();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("should return false when deleting non-existent workspace", async () => {
|
|
123
|
+
const deleted = await store.deleteWorkspace(tenantId, "non-existent");
|
|
124
|
+
expect(deleted).toBe(false);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describeIfDb("PostgreSQLProjectStore", () => {
|
|
129
|
+
let store: PostgreSQLProjectStore;
|
|
130
|
+
const tenantId = "test-tenant";
|
|
131
|
+
const workspaceId = "test-workspace";
|
|
132
|
+
|
|
133
|
+
beforeAll(async () => {
|
|
134
|
+
store = new PostgreSQLProjectStore({
|
|
135
|
+
poolConfig: TEST_DATABASE_URL!,
|
|
136
|
+
autoMigrate: true,
|
|
137
|
+
});
|
|
138
|
+
await store.initialize();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
afterAll(async () => {
|
|
142
|
+
await store.dispose();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
beforeEach(async () => {
|
|
146
|
+
// Clean up any existing test data
|
|
147
|
+
const projects = await store.getProjectsByWorkspace(tenantId, workspaceId);
|
|
148
|
+
for (const proj of projects) {
|
|
149
|
+
if (proj.id.startsWith("test-")) {
|
|
150
|
+
await store.deleteProject(tenantId, proj.id);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("should create a project", async () => {
|
|
156
|
+
const project = await store.createProject(tenantId, workspaceId, "test-proj-001", {
|
|
157
|
+
name: "Test Project",
|
|
158
|
+
description: "Test project description",
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
expect(project.id).toBe("test-proj-001");
|
|
162
|
+
expect(project.name).toBe("Test Project");
|
|
163
|
+
expect(project.description).toBe("Test project description");
|
|
164
|
+
expect(project.workspaceId).toBe(workspaceId);
|
|
165
|
+
expect(project.tenantId).toBe(tenantId);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("should get a project by ID", async () => {
|
|
169
|
+
await store.createProject(tenantId, workspaceId, "test-proj-002", {
|
|
170
|
+
name: "Test Project 2",
|
|
171
|
+
description: "Description 2",
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const fetched = await store.getProjectById(tenantId, "test-proj-002");
|
|
175
|
+
expect(fetched).not.toBeNull();
|
|
176
|
+
expect(fetched?.name).toBe("Test Project 2");
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("should return null for non-existent project", async () => {
|
|
180
|
+
const fetched = await store.getProjectById(tenantId, "non-existent");
|
|
181
|
+
expect(fetched).toBeNull();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test("should get projects by workspace", async () => {
|
|
185
|
+
await store.createProject(tenantId, workspaceId, "test-proj-003", {
|
|
186
|
+
name: "Project 3",
|
|
187
|
+
});
|
|
188
|
+
await store.createProject(tenantId, workspaceId, "test-proj-004", {
|
|
189
|
+
name: "Project 4",
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const projects = await store.getProjectsByWorkspace(tenantId, workspaceId);
|
|
193
|
+
const testProjects = projects.filter((p) => p.id.startsWith("test-"));
|
|
194
|
+
expect(testProjects.length).toBeGreaterThanOrEqual(2);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("should update a project", async () => {
|
|
198
|
+
await store.createProject(tenantId, workspaceId, "test-proj-005", {
|
|
199
|
+
name: "Original Name",
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const updated = await store.updateProject(tenantId, "test-proj-005", {
|
|
203
|
+
name: "Updated Name",
|
|
204
|
+
description: "Updated description",
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
expect(updated).not.toBeNull();
|
|
208
|
+
expect(updated?.name).toBe("Updated Name");
|
|
209
|
+
expect(updated?.description).toBe("Updated description");
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("should return null when updating non-existent project", async () => {
|
|
213
|
+
const updated = await store.updateProject(tenantId, "non-existent", {
|
|
214
|
+
name: "New Name",
|
|
215
|
+
});
|
|
216
|
+
expect(updated).toBeNull();
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test("should delete a project", async () => {
|
|
220
|
+
await store.createProject(tenantId, workspaceId, "test-proj-006", {
|
|
221
|
+
name: "To Delete",
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const deleted = await store.deleteProject(tenantId, "test-proj-006");
|
|
225
|
+
expect(deleted).toBe(true);
|
|
226
|
+
|
|
227
|
+
const fetched = await store.getProjectById(tenantId, "test-proj-006");
|
|
228
|
+
expect(fetched).toBeNull();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test("should return false when deleting non-existent project", async () => {
|
|
232
|
+
const deleted = await store.deleteProject(tenantId, "non-existent");
|
|
233
|
+
expect(deleted).toBe(false);
|
|
234
|
+
});
|
|
235
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -8,17 +8,26 @@ export * from "./stores/PostgreSQLThreadStore";
|
|
|
8
8
|
export * from "./stores/PostgreSQLAssistantStore";
|
|
9
9
|
export * from "./stores/PostgreSQLScheduleStorage";
|
|
10
10
|
export * from "./stores/PostgreSQLSkillStore";
|
|
11
|
+
export * from "./stores/PostgreSQLDatabaseConfigStore";
|
|
12
|
+
export * from "./stores/PostgreSQLWorkspaceStore";
|
|
13
|
+
export * from "./stores/PostgreSQLProjectStore";
|
|
11
14
|
export * from "./migrations/migration";
|
|
12
15
|
export * from "./migrations/thread_migrations";
|
|
13
16
|
export * from "./migrations/assistant_migrations";
|
|
14
17
|
export * from "./migrations/schedule_migrations";
|
|
15
18
|
export * from "./migrations/skill_migrations";
|
|
19
|
+
export * from "./migrations/database_config_migrations";
|
|
20
|
+
export * from "./migrations/workspace_migrations";
|
|
21
|
+
export * from "./migrations/project_migrations";
|
|
16
22
|
|
|
17
23
|
// Re-export for convenience
|
|
18
24
|
export { PostgreSQLThreadStore } from "./stores/PostgreSQLThreadStore";
|
|
19
25
|
export { PostgreSQLAssistantStore } from "./stores/PostgreSQLAssistantStore";
|
|
20
26
|
export { PostgreSQLScheduleStorage } from "./stores/PostgreSQLScheduleStorage";
|
|
21
27
|
export { PostgreSQLSkillStore } from "./stores/PostgreSQLSkillStore";
|
|
28
|
+
export { PostgreSQLDatabaseConfigStore } from "./stores/PostgreSQLDatabaseConfigStore";
|
|
29
|
+
export { PostgreSQLWorkspaceStore } from "./stores/PostgreSQLWorkspaceStore";
|
|
30
|
+
export { PostgreSQLProjectStore } from "./stores/PostgreSQLProjectStore";
|
|
22
31
|
|
|
23
32
|
// Re-export types from protocols
|
|
24
33
|
export type {
|
|
@@ -35,4 +44,19 @@ export type {
|
|
|
35
44
|
SkillStore,
|
|
36
45
|
Skill,
|
|
37
46
|
CreateSkillRequest,
|
|
47
|
+
DatabaseConfigStore,
|
|
48
|
+
DatabaseConfigEntry,
|
|
49
|
+
CreateDatabaseConfigRequest,
|
|
50
|
+
UpdateDatabaseConfigRequest,
|
|
51
|
+
DatabaseConfig,
|
|
52
|
+
DatabaseType,
|
|
53
|
+
WorkspaceStore,
|
|
54
|
+
Workspace,
|
|
55
|
+
CreateWorkspaceRequest,
|
|
56
|
+
UpdateWorkspaceRequest,
|
|
57
|
+
StorageType,
|
|
58
|
+
ProjectStore,
|
|
59
|
+
Project,
|
|
60
|
+
CreateProjectRequest,
|
|
61
|
+
UpdateProjectRequest,
|
|
38
62
|
} from "@axiom-lattice/protocols";
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database configuration table migrations
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { PoolClient } from 'pg';
|
|
6
|
+
import { Migration } from './migration';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Initial migration: Create database configs table
|
|
10
|
+
*/
|
|
11
|
+
export const createDatabaseConfigsTable: Migration = {
|
|
12
|
+
version: 4,
|
|
13
|
+
name: 'create_database_configs_table',
|
|
14
|
+
up: async (client: PoolClient) => {
|
|
15
|
+
await client.query(`
|
|
16
|
+
CREATE TABLE IF NOT EXISTS lattice_database_configs (
|
|
17
|
+
id VARCHAR(255) NOT NULL,
|
|
18
|
+
tenant_id VARCHAR(255) NOT NULL,
|
|
19
|
+
key VARCHAR(255) NOT NULL,
|
|
20
|
+
name VARCHAR(255),
|
|
21
|
+
description TEXT,
|
|
22
|
+
config JSONB NOT NULL,
|
|
23
|
+
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
24
|
+
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
25
|
+
|
|
26
|
+
PRIMARY KEY (tenant_id, id),
|
|
27
|
+
CONSTRAINT uk_lattice_database_configs_tenant_key UNIQUE (tenant_id, key)
|
|
28
|
+
)
|
|
29
|
+
`);
|
|
30
|
+
|
|
31
|
+
// Create indexes for better query performance
|
|
32
|
+
await client.query(`
|
|
33
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_database_configs_tenant_id
|
|
34
|
+
ON lattice_database_configs(tenant_id)
|
|
35
|
+
`);
|
|
36
|
+
|
|
37
|
+
await client.query(`
|
|
38
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_database_configs_key
|
|
39
|
+
ON lattice_database_configs(key)
|
|
40
|
+
`);
|
|
41
|
+
},
|
|
42
|
+
down: async (client: PoolClient) => {
|
|
43
|
+
await client.query('DROP INDEX IF EXISTS idx_lattice_database_configs_key');
|
|
44
|
+
await client.query('DROP INDEX IF EXISTS idx_lattice_database_configs_tenant_id');
|
|
45
|
+
await client.query('DROP TABLE IF EXISTS lattice_database_configs');
|
|
46
|
+
},
|
|
47
|
+
};
|