@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,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL implementation of ProjectStore
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Pool, PoolConfig } from "pg";
|
|
6
|
+
import {
|
|
7
|
+
ProjectStore,
|
|
8
|
+
Project,
|
|
9
|
+
CreateProjectRequest,
|
|
10
|
+
UpdateProjectRequest,
|
|
11
|
+
} from "@axiom-lattice/protocols";
|
|
12
|
+
import { MigrationManager } from "../migrations/migration";
|
|
13
|
+
import { createProjectsTable } from "../migrations/project_migrations";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* PostgreSQL ProjectStore options
|
|
17
|
+
*/
|
|
18
|
+
export interface PostgreSQLProjectStoreOptions {
|
|
19
|
+
/**
|
|
20
|
+
* PostgreSQL connection pool configuration
|
|
21
|
+
* Can be a connection string or PoolConfig object
|
|
22
|
+
*/
|
|
23
|
+
poolConfig: string | PoolConfig;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Whether to run migrations automatically on initialization
|
|
27
|
+
* @default true
|
|
28
|
+
*/
|
|
29
|
+
autoMigrate?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* PostgreSQL implementation of ProjectStore
|
|
34
|
+
*/
|
|
35
|
+
export class PostgreSQLProjectStore implements ProjectStore {
|
|
36
|
+
private pool: Pool;
|
|
37
|
+
private migrationManager: MigrationManager;
|
|
38
|
+
private initialized: boolean = false;
|
|
39
|
+
private ownsPool: boolean = true;
|
|
40
|
+
|
|
41
|
+
constructor(options: PostgreSQLProjectStoreOptions) {
|
|
42
|
+
// Create Pool from config
|
|
43
|
+
if (typeof options.poolConfig === "string") {
|
|
44
|
+
this.pool = new Pool({ connectionString: options.poolConfig });
|
|
45
|
+
} else {
|
|
46
|
+
this.pool = new Pool(options.poolConfig);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
this.migrationManager = new MigrationManager(this.pool);
|
|
50
|
+
this.migrationManager.register(createProjectsTable);
|
|
51
|
+
|
|
52
|
+
// Auto-migrate by default
|
|
53
|
+
if (options.autoMigrate !== false) {
|
|
54
|
+
this.initialize().catch((error) => {
|
|
55
|
+
console.error("Failed to initialize PostgreSQLProjectStore:", error);
|
|
56
|
+
throw error;
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Dispose resources and close the connection pool
|
|
63
|
+
* Should be called when the store is no longer needed
|
|
64
|
+
*/
|
|
65
|
+
async dispose(): Promise<void> {
|
|
66
|
+
if (this.ownsPool && this.pool) {
|
|
67
|
+
await this.pool.end();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Initialize the store and run migrations
|
|
73
|
+
*/
|
|
74
|
+
async initialize(): Promise<void> {
|
|
75
|
+
if (this.initialized) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
await this.migrationManager.migrate();
|
|
80
|
+
this.initialized = true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Ensure store is initialized
|
|
85
|
+
*/
|
|
86
|
+
private async ensureInitialized(): Promise<void> {
|
|
87
|
+
if (!this.initialized) {
|
|
88
|
+
await this.initialize();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Map database row to Project object
|
|
94
|
+
*/
|
|
95
|
+
private mapRowToProject(row: {
|
|
96
|
+
id: string;
|
|
97
|
+
tenant_id: string;
|
|
98
|
+
workspace_id: string;
|
|
99
|
+
name: string;
|
|
100
|
+
description: string | null;
|
|
101
|
+
created_at: Date;
|
|
102
|
+
updated_at: Date;
|
|
103
|
+
}): Project {
|
|
104
|
+
return {
|
|
105
|
+
id: row.id,
|
|
106
|
+
tenantId: row.tenant_id,
|
|
107
|
+
workspaceId: row.workspace_id,
|
|
108
|
+
name: row.name,
|
|
109
|
+
description: row.description || undefined,
|
|
110
|
+
createdAt: row.created_at,
|
|
111
|
+
updatedAt: row.updated_at,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Get all projects for a specific workspace
|
|
117
|
+
*/
|
|
118
|
+
async getProjectsByWorkspace(tenantId: string, workspaceId: string): Promise<Project[]> {
|
|
119
|
+
await this.ensureInitialized();
|
|
120
|
+
|
|
121
|
+
const result = await this.pool.query<{
|
|
122
|
+
id: string;
|
|
123
|
+
tenant_id: string;
|
|
124
|
+
workspace_id: string;
|
|
125
|
+
name: string;
|
|
126
|
+
description: string | null;
|
|
127
|
+
created_at: Date;
|
|
128
|
+
updated_at: Date;
|
|
129
|
+
}>(
|
|
130
|
+
`
|
|
131
|
+
SELECT id, tenant_id, workspace_id, name, description, created_at, updated_at
|
|
132
|
+
FROM lattice_projects
|
|
133
|
+
WHERE tenant_id = $1 AND workspace_id = $2
|
|
134
|
+
ORDER BY created_at DESC
|
|
135
|
+
`,
|
|
136
|
+
[tenantId, workspaceId]
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
return result.rows.map(this.mapRowToProject);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get a project by ID for a specific tenant
|
|
144
|
+
*/
|
|
145
|
+
async getProjectById(tenantId: string, id: string): Promise<Project | null> {
|
|
146
|
+
await this.ensureInitialized();
|
|
147
|
+
|
|
148
|
+
const result = await this.pool.query<{
|
|
149
|
+
id: string;
|
|
150
|
+
tenant_id: string;
|
|
151
|
+
workspace_id: string;
|
|
152
|
+
name: string;
|
|
153
|
+
description: string | null;
|
|
154
|
+
created_at: Date;
|
|
155
|
+
updated_at: Date;
|
|
156
|
+
}>(
|
|
157
|
+
`
|
|
158
|
+
SELECT id, tenant_id, workspace_id, name, description, created_at, updated_at
|
|
159
|
+
FROM lattice_projects
|
|
160
|
+
WHERE id = $1 AND tenant_id = $2
|
|
161
|
+
`,
|
|
162
|
+
[id, tenantId]
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
if (result.rows.length === 0) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return this.mapRowToProject(result.rows[0]);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Create a new project
|
|
174
|
+
*/
|
|
175
|
+
async createProject(
|
|
176
|
+
tenantId: string,
|
|
177
|
+
workspaceId: string,
|
|
178
|
+
id: string,
|
|
179
|
+
data: CreateProjectRequest
|
|
180
|
+
): Promise<Project> {
|
|
181
|
+
await this.ensureInitialized();
|
|
182
|
+
|
|
183
|
+
const now = new Date();
|
|
184
|
+
|
|
185
|
+
await this.pool.query(
|
|
186
|
+
`
|
|
187
|
+
INSERT INTO lattice_projects (id, tenant_id, workspace_id, name, description, created_at, updated_at)
|
|
188
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
189
|
+
ON CONFLICT (id, tenant_id) DO UPDATE SET
|
|
190
|
+
workspace_id = EXCLUDED.workspace_id,
|
|
191
|
+
name = EXCLUDED.name,
|
|
192
|
+
description = EXCLUDED.description,
|
|
193
|
+
updated_at = EXCLUDED.updated_at
|
|
194
|
+
`,
|
|
195
|
+
[id, tenantId, workspaceId, data.name, data.description || null, now, now]
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
id,
|
|
200
|
+
tenantId,
|
|
201
|
+
workspaceId,
|
|
202
|
+
name: data.name,
|
|
203
|
+
description: data.description,
|
|
204
|
+
createdAt: now,
|
|
205
|
+
updatedAt: now,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Update an existing project
|
|
211
|
+
*/
|
|
212
|
+
async updateProject(
|
|
213
|
+
tenantId: string,
|
|
214
|
+
id: string,
|
|
215
|
+
updates: UpdateProjectRequest
|
|
216
|
+
): Promise<Project | null> {
|
|
217
|
+
await this.ensureInitialized();
|
|
218
|
+
|
|
219
|
+
// Get existing project
|
|
220
|
+
const existing = await this.getProjectById(tenantId, id);
|
|
221
|
+
if (!existing) {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Build update query dynamically based on provided fields
|
|
226
|
+
const updateFields: string[] = [];
|
|
227
|
+
const updateValues: any[] = [];
|
|
228
|
+
let paramIndex = 1;
|
|
229
|
+
|
|
230
|
+
if (updates.name !== undefined) {
|
|
231
|
+
updateFields.push(`name = $${paramIndex++}`);
|
|
232
|
+
updateValues.push(updates.name);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (updates.description !== undefined) {
|
|
236
|
+
updateFields.push(`description = $${paramIndex++}`);
|
|
237
|
+
updateValues.push(updates.description || null);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (updateFields.length === 0) {
|
|
241
|
+
// No fields to update
|
|
242
|
+
return existing;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Always update updated_at
|
|
246
|
+
updateFields.push(`updated_at = $${paramIndex++}`);
|
|
247
|
+
updateValues.push(new Date());
|
|
248
|
+
|
|
249
|
+
// Add id and tenant_id for WHERE clause
|
|
250
|
+
updateValues.push(id);
|
|
251
|
+
updateValues.push(tenantId);
|
|
252
|
+
|
|
253
|
+
await this.pool.query(
|
|
254
|
+
`
|
|
255
|
+
UPDATE lattice_projects
|
|
256
|
+
SET ${updateFields.join(", ")}
|
|
257
|
+
WHERE id = $${paramIndex} AND tenant_id = $${paramIndex + 1}
|
|
258
|
+
`,
|
|
259
|
+
updateValues
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
// Return updated project
|
|
263
|
+
return await this.getProjectById(tenantId, id);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Delete a project by ID
|
|
268
|
+
*/
|
|
269
|
+
async deleteProject(tenantId: string, id: string): Promise<boolean> {
|
|
270
|
+
await this.ensureInitialized();
|
|
271
|
+
|
|
272
|
+
const result = await this.pool.query(
|
|
273
|
+
`
|
|
274
|
+
DELETE FROM lattice_projects
|
|
275
|
+
WHERE id = $1 AND tenant_id = $2
|
|
276
|
+
`,
|
|
277
|
+
[id, tenantId]
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
return result.rowCount !== null && result.rowCount > 0;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL implementation of WorkspaceStore
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Pool, PoolConfig } from "pg";
|
|
6
|
+
import {
|
|
7
|
+
WorkspaceStore,
|
|
8
|
+
Workspace,
|
|
9
|
+
CreateWorkspaceRequest,
|
|
10
|
+
UpdateWorkspaceRequest,
|
|
11
|
+
} from "@axiom-lattice/protocols";
|
|
12
|
+
import { MigrationManager } from "../migrations/migration";
|
|
13
|
+
import { createWorkspacesTable } from "../migrations/workspace_migrations";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* PostgreSQL WorkspaceStore options
|
|
17
|
+
*/
|
|
18
|
+
export interface PostgreSQLWorkspaceStoreOptions {
|
|
19
|
+
/**
|
|
20
|
+
* PostgreSQL connection pool configuration
|
|
21
|
+
* Can be a connection string or PoolConfig object
|
|
22
|
+
*/
|
|
23
|
+
poolConfig: string | PoolConfig;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Whether to run migrations automatically on initialization
|
|
27
|
+
* @default true
|
|
28
|
+
*/
|
|
29
|
+
autoMigrate?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* PostgreSQL implementation of WorkspaceStore
|
|
34
|
+
*/
|
|
35
|
+
export class PostgreSQLWorkspaceStore implements WorkspaceStore {
|
|
36
|
+
private pool: Pool;
|
|
37
|
+
private migrationManager: MigrationManager;
|
|
38
|
+
private initialized: boolean = false;
|
|
39
|
+
private ownsPool: boolean = true;
|
|
40
|
+
|
|
41
|
+
constructor(options: PostgreSQLWorkspaceStoreOptions) {
|
|
42
|
+
// Create Pool from config
|
|
43
|
+
if (typeof options.poolConfig === "string") {
|
|
44
|
+
this.pool = new Pool({ connectionString: options.poolConfig });
|
|
45
|
+
} else {
|
|
46
|
+
this.pool = new Pool(options.poolConfig);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
this.migrationManager = new MigrationManager(this.pool);
|
|
50
|
+
this.migrationManager.register(createWorkspacesTable);
|
|
51
|
+
|
|
52
|
+
// Auto-migrate by default
|
|
53
|
+
if (options.autoMigrate !== false) {
|
|
54
|
+
this.initialize().catch((error) => {
|
|
55
|
+
console.error("Failed to initialize PostgreSQLWorkspaceStore:", error);
|
|
56
|
+
throw error;
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Dispose resources and close the connection pool
|
|
63
|
+
* Should be called when the store is no longer needed
|
|
64
|
+
*/
|
|
65
|
+
async dispose(): Promise<void> {
|
|
66
|
+
if (this.ownsPool && this.pool) {
|
|
67
|
+
await this.pool.end();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Initialize the store and run migrations
|
|
73
|
+
*/
|
|
74
|
+
async initialize(): Promise<void> {
|
|
75
|
+
if (this.initialized) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
await this.migrationManager.migrate();
|
|
80
|
+
this.initialized = true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Ensure store is initialized
|
|
85
|
+
*/
|
|
86
|
+
private async ensureInitialized(): Promise<void> {
|
|
87
|
+
if (!this.initialized) {
|
|
88
|
+
await this.initialize();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Map database row to Workspace object
|
|
94
|
+
*/
|
|
95
|
+
private mapRowToWorkspace(row: {
|
|
96
|
+
id: string;
|
|
97
|
+
tenant_id: string;
|
|
98
|
+
name: string;
|
|
99
|
+
description: string | null;
|
|
100
|
+
storage_type: string;
|
|
101
|
+
created_at: Date;
|
|
102
|
+
updated_at: Date;
|
|
103
|
+
}): Workspace {
|
|
104
|
+
return {
|
|
105
|
+
id: row.id,
|
|
106
|
+
tenantId: row.tenant_id,
|
|
107
|
+
name: row.name,
|
|
108
|
+
description: row.description || undefined,
|
|
109
|
+
storageType: row.storage_type as Workspace["storageType"],
|
|
110
|
+
createdAt: row.created_at,
|
|
111
|
+
updatedAt: row.updated_at,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Get all workspaces for a tenant
|
|
117
|
+
*/
|
|
118
|
+
async getAllWorkspaces(tenantId: string): Promise<Workspace[]> {
|
|
119
|
+
await this.ensureInitialized();
|
|
120
|
+
|
|
121
|
+
const result = await this.pool.query<{
|
|
122
|
+
id: string;
|
|
123
|
+
tenant_id: string;
|
|
124
|
+
name: string;
|
|
125
|
+
description: string | null;
|
|
126
|
+
storage_type: string;
|
|
127
|
+
created_at: Date;
|
|
128
|
+
updated_at: Date;
|
|
129
|
+
}>(
|
|
130
|
+
`
|
|
131
|
+
SELECT id, tenant_id, name, description, storage_type, created_at, updated_at
|
|
132
|
+
FROM lattice_workspaces
|
|
133
|
+
WHERE tenant_id = $1
|
|
134
|
+
ORDER BY created_at DESC
|
|
135
|
+
`,
|
|
136
|
+
[tenantId]
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
return result.rows.map(this.mapRowToWorkspace);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get a workspace by ID for a specific tenant
|
|
144
|
+
*/
|
|
145
|
+
async getWorkspaceById(tenantId: string, id: string): Promise<Workspace | null> {
|
|
146
|
+
await this.ensureInitialized();
|
|
147
|
+
|
|
148
|
+
const result = await this.pool.query<{
|
|
149
|
+
id: string;
|
|
150
|
+
tenant_id: string;
|
|
151
|
+
name: string;
|
|
152
|
+
description: string | null;
|
|
153
|
+
storage_type: string;
|
|
154
|
+
created_at: Date;
|
|
155
|
+
updated_at: Date;
|
|
156
|
+
}>(
|
|
157
|
+
`
|
|
158
|
+
SELECT id, tenant_id, name, description, storage_type, created_at, updated_at
|
|
159
|
+
FROM lattice_workspaces
|
|
160
|
+
WHERE id = $1 AND tenant_id = $2
|
|
161
|
+
`,
|
|
162
|
+
[id, tenantId]
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
if (result.rows.length === 0) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return this.mapRowToWorkspace(result.rows[0]);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Create a new workspace
|
|
174
|
+
*/
|
|
175
|
+
async createWorkspace(
|
|
176
|
+
tenantId: string,
|
|
177
|
+
id: string,
|
|
178
|
+
data: CreateWorkspaceRequest
|
|
179
|
+
): Promise<Workspace> {
|
|
180
|
+
await this.ensureInitialized();
|
|
181
|
+
|
|
182
|
+
const now = new Date();
|
|
183
|
+
|
|
184
|
+
await this.pool.query(
|
|
185
|
+
`
|
|
186
|
+
INSERT INTO lattice_workspaces (id, tenant_id, name, description, storage_type, created_at, updated_at)
|
|
187
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
188
|
+
ON CONFLICT (id, tenant_id) DO UPDATE SET
|
|
189
|
+
name = EXCLUDED.name,
|
|
190
|
+
description = EXCLUDED.description,
|
|
191
|
+
storage_type = EXCLUDED.storage_type,
|
|
192
|
+
updated_at = EXCLUDED.updated_at
|
|
193
|
+
`,
|
|
194
|
+
[id, tenantId, data.name, data.description || null, data.storageType, now, now]
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
id,
|
|
199
|
+
tenantId,
|
|
200
|
+
name: data.name,
|
|
201
|
+
description: data.description,
|
|
202
|
+
storageType: data.storageType,
|
|
203
|
+
createdAt: now,
|
|
204
|
+
updatedAt: now,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Update an existing workspace
|
|
210
|
+
*/
|
|
211
|
+
async updateWorkspace(
|
|
212
|
+
tenantId: string,
|
|
213
|
+
id: string,
|
|
214
|
+
updates: UpdateWorkspaceRequest
|
|
215
|
+
): Promise<Workspace | null> {
|
|
216
|
+
await this.ensureInitialized();
|
|
217
|
+
|
|
218
|
+
// Get existing workspace
|
|
219
|
+
const existing = await this.getWorkspaceById(tenantId, id);
|
|
220
|
+
if (!existing) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Build update query dynamically based on provided fields
|
|
225
|
+
const updateFields: string[] = [];
|
|
226
|
+
const updateValues: any[] = [];
|
|
227
|
+
let paramIndex = 1;
|
|
228
|
+
|
|
229
|
+
if (updates.name !== undefined) {
|
|
230
|
+
updateFields.push(`name = $${paramIndex++}`);
|
|
231
|
+
updateValues.push(updates.name);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (updates.description !== undefined) {
|
|
235
|
+
updateFields.push(`description = $${paramIndex++}`);
|
|
236
|
+
updateValues.push(updates.description || null);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (updates.storageType !== undefined) {
|
|
240
|
+
updateFields.push(`storage_type = $${paramIndex++}`);
|
|
241
|
+
updateValues.push(updates.storageType);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (updateFields.length === 0) {
|
|
245
|
+
// No fields to update
|
|
246
|
+
return existing;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Always update updated_at
|
|
250
|
+
updateFields.push(`updated_at = $${paramIndex++}`);
|
|
251
|
+
updateValues.push(new Date());
|
|
252
|
+
|
|
253
|
+
// Add id and tenant_id for WHERE clause
|
|
254
|
+
updateValues.push(id);
|
|
255
|
+
updateValues.push(tenantId);
|
|
256
|
+
|
|
257
|
+
await this.pool.query(
|
|
258
|
+
`
|
|
259
|
+
UPDATE lattice_workspaces
|
|
260
|
+
SET ${updateFields.join(", ")}
|
|
261
|
+
WHERE id = $${paramIndex} AND tenant_id = $${paramIndex + 1}
|
|
262
|
+
`,
|
|
263
|
+
updateValues
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
// Return updated workspace
|
|
267
|
+
return await this.getWorkspaceById(tenantId, id);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Delete a workspace by ID
|
|
272
|
+
*/
|
|
273
|
+
async deleteWorkspace(tenantId: string, id: string): Promise<boolean> {
|
|
274
|
+
await this.ensureInitialized();
|
|
275
|
+
|
|
276
|
+
const result = await this.pool.query(
|
|
277
|
+
`
|
|
278
|
+
DELETE FROM lattice_workspaces
|
|
279
|
+
WHERE id = $1 AND tenant_id = $2
|
|
280
|
+
`,
|
|
281
|
+
[id, tenantId]
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
return result.rowCount !== null && result.rowCount > 0;
|
|
285
|
+
}
|
|
286
|
+
}
|