@axiom-lattice/pg-stores 1.0.4 → 1.0.5
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/dist/index.d.mts +99 -3
- package/dist/index.d.ts +99 -3
- package/dist/index.js +340 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +338 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +6 -0
- package/src/migrations/skill_migrations.ts +65 -0
- package/src/stores/PostgreSQLSkillStore.ts +455 -0
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL implementation of SkillStore
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Pool, PoolConfig } from "pg";
|
|
6
|
+
import {
|
|
7
|
+
SkillStore,
|
|
8
|
+
Skill,
|
|
9
|
+
CreateSkillRequest,
|
|
10
|
+
} from "@axiom-lattice/protocols";
|
|
11
|
+
import { MigrationManager } from "../migrations/migration";
|
|
12
|
+
import { createSkillsTable } from "../migrations/skill_migrations";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* PostgreSQL SkillStore options
|
|
16
|
+
*/
|
|
17
|
+
export interface PostgreSQLSkillStoreOptions {
|
|
18
|
+
/**
|
|
19
|
+
* PostgreSQL connection pool configuration
|
|
20
|
+
* Can be a connection string or PoolConfig object
|
|
21
|
+
*/
|
|
22
|
+
poolConfig: string | PoolConfig;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Whether to run migrations automatically on initialization
|
|
26
|
+
* @default true
|
|
27
|
+
*/
|
|
28
|
+
autoMigrate?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* PostgreSQL implementation of SkillStore
|
|
33
|
+
*/
|
|
34
|
+
export class PostgreSQLSkillStore implements SkillStore {
|
|
35
|
+
private pool: Pool;
|
|
36
|
+
private migrationManager: MigrationManager;
|
|
37
|
+
private initialized: boolean = false;
|
|
38
|
+
private ownsPool: boolean = true;
|
|
39
|
+
|
|
40
|
+
constructor(options: PostgreSQLSkillStoreOptions) {
|
|
41
|
+
// Create Pool from config
|
|
42
|
+
if (typeof options.poolConfig === "string") {
|
|
43
|
+
this.pool = new Pool({ connectionString: options.poolConfig });
|
|
44
|
+
} else {
|
|
45
|
+
this.pool = new Pool(options.poolConfig);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this.migrationManager = new MigrationManager(this.pool);
|
|
49
|
+
this.migrationManager.register(createSkillsTable);
|
|
50
|
+
|
|
51
|
+
// Auto-migrate by default
|
|
52
|
+
if (options.autoMigrate !== false) {
|
|
53
|
+
this.initialize().catch((error) => {
|
|
54
|
+
console.error("Failed to initialize PostgreSQLSkillStore:", error);
|
|
55
|
+
throw error;
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Dispose resources and close the connection pool
|
|
62
|
+
* Should be called when the store is no longer needed
|
|
63
|
+
*/
|
|
64
|
+
async dispose(): Promise<void> {
|
|
65
|
+
if (this.ownsPool && this.pool) {
|
|
66
|
+
await this.pool.end();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Initialize the store and run migrations
|
|
72
|
+
*/
|
|
73
|
+
async initialize(): Promise<void> {
|
|
74
|
+
if (this.initialized) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
await this.migrationManager.migrate();
|
|
79
|
+
this.initialized = true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Ensure store is initialized
|
|
84
|
+
*/
|
|
85
|
+
private async ensureInitialized(): Promise<void> {
|
|
86
|
+
if (!this.initialized) {
|
|
87
|
+
await this.initialize();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Map database row to Skill object
|
|
93
|
+
*/
|
|
94
|
+
private mapRowToSkill(row: {
|
|
95
|
+
id: string;
|
|
96
|
+
name: string;
|
|
97
|
+
description: string;
|
|
98
|
+
license: string | null;
|
|
99
|
+
compatibility: string | null;
|
|
100
|
+
metadata: any;
|
|
101
|
+
content: string | null;
|
|
102
|
+
sub_skills: any;
|
|
103
|
+
created_at: Date;
|
|
104
|
+
updated_at: Date;
|
|
105
|
+
}): Skill {
|
|
106
|
+
return {
|
|
107
|
+
id: row.id,
|
|
108
|
+
name: row.name,
|
|
109
|
+
description: row.description,
|
|
110
|
+
license: row.license || undefined,
|
|
111
|
+
compatibility: row.compatibility || undefined,
|
|
112
|
+
metadata: row.metadata || {},
|
|
113
|
+
content: row.content || undefined,
|
|
114
|
+
subSkills: Array.isArray(row.sub_skills) && row.sub_skills.length > 0
|
|
115
|
+
? row.sub_skills
|
|
116
|
+
: undefined,
|
|
117
|
+
createdAt: row.created_at,
|
|
118
|
+
updatedAt: row.updated_at,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Get all skills
|
|
124
|
+
*/
|
|
125
|
+
async getAllSkills(): Promise<Skill[]> {
|
|
126
|
+
await this.ensureInitialized();
|
|
127
|
+
|
|
128
|
+
const result = await this.pool.query<{
|
|
129
|
+
id: string;
|
|
130
|
+
name: string;
|
|
131
|
+
description: string;
|
|
132
|
+
license: string | null;
|
|
133
|
+
compatibility: string | null;
|
|
134
|
+
metadata: any;
|
|
135
|
+
content: string | null;
|
|
136
|
+
sub_skills: any;
|
|
137
|
+
created_at: Date;
|
|
138
|
+
updated_at: Date;
|
|
139
|
+
}>(
|
|
140
|
+
`
|
|
141
|
+
SELECT id, name, description, license, compatibility, metadata, content, sub_skills, created_at, updated_at
|
|
142
|
+
FROM lattice_skills
|
|
143
|
+
ORDER BY created_at DESC
|
|
144
|
+
`
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
return result.rows.map(this.mapRowToSkill);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Get skill by ID
|
|
152
|
+
*/
|
|
153
|
+
async getSkillById(id: string): Promise<Skill | null> {
|
|
154
|
+
await this.ensureInitialized();
|
|
155
|
+
|
|
156
|
+
const result = await this.pool.query<{
|
|
157
|
+
id: string;
|
|
158
|
+
name: string;
|
|
159
|
+
description: string;
|
|
160
|
+
license: string | null;
|
|
161
|
+
compatibility: string | null;
|
|
162
|
+
metadata: any;
|
|
163
|
+
content: string | null;
|
|
164
|
+
sub_skills: any;
|
|
165
|
+
created_at: Date;
|
|
166
|
+
updated_at: Date;
|
|
167
|
+
}>(
|
|
168
|
+
`
|
|
169
|
+
SELECT id, name, description, license, compatibility, metadata, content, sub_skills, created_at, updated_at
|
|
170
|
+
FROM lattice_skills
|
|
171
|
+
WHERE id = $1
|
|
172
|
+
`,
|
|
173
|
+
[id]
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
if (result.rows.length === 0) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return this.mapRowToSkill(result.rows[0]);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Create a new skill
|
|
185
|
+
*/
|
|
186
|
+
async createSkill(id: string, data: CreateSkillRequest): Promise<Skill> {
|
|
187
|
+
await this.ensureInitialized();
|
|
188
|
+
|
|
189
|
+
const now = new Date();
|
|
190
|
+
const metadata = data.metadata || {};
|
|
191
|
+
|
|
192
|
+
await this.pool.query(
|
|
193
|
+
`
|
|
194
|
+
INSERT INTO lattice_skills (id, name, description, license, compatibility, metadata, content, sub_skills, created_at, updated_at)
|
|
195
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
196
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
197
|
+
name = EXCLUDED.name,
|
|
198
|
+
description = EXCLUDED.description,
|
|
199
|
+
license = EXCLUDED.license,
|
|
200
|
+
compatibility = EXCLUDED.compatibility,
|
|
201
|
+
metadata = EXCLUDED.metadata,
|
|
202
|
+
content = EXCLUDED.content,
|
|
203
|
+
sub_skills = EXCLUDED.sub_skills,
|
|
204
|
+
updated_at = EXCLUDED.updated_at
|
|
205
|
+
`,
|
|
206
|
+
[
|
|
207
|
+
id,
|
|
208
|
+
data.name,
|
|
209
|
+
data.description,
|
|
210
|
+
data.license || null,
|
|
211
|
+
data.compatibility || null,
|
|
212
|
+
JSON.stringify(metadata),
|
|
213
|
+
data.content || null,
|
|
214
|
+
JSON.stringify(data.subSkills || []),
|
|
215
|
+
now,
|
|
216
|
+
now,
|
|
217
|
+
]
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
return this.getSkillById(id) as Promise<Skill>;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Update an existing skill
|
|
225
|
+
*/
|
|
226
|
+
async updateSkill(
|
|
227
|
+
id: string,
|
|
228
|
+
updates: Partial<CreateSkillRequest>
|
|
229
|
+
): Promise<Skill | null> {
|
|
230
|
+
await this.ensureInitialized();
|
|
231
|
+
|
|
232
|
+
const updateFields: string[] = [];
|
|
233
|
+
const values: any[] = [];
|
|
234
|
+
let paramIndex = 1;
|
|
235
|
+
|
|
236
|
+
if (updates.name !== undefined) {
|
|
237
|
+
updateFields.push(`name = $${paramIndex++}`);
|
|
238
|
+
values.push(updates.name);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (updates.description !== undefined) {
|
|
242
|
+
updateFields.push(`description = $${paramIndex++}`);
|
|
243
|
+
values.push(updates.description);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (updates.license !== undefined) {
|
|
247
|
+
updateFields.push(`license = $${paramIndex++}`);
|
|
248
|
+
values.push(updates.license || null);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (updates.compatibility !== undefined) {
|
|
252
|
+
updateFields.push(`compatibility = $${paramIndex++}`);
|
|
253
|
+
values.push(updates.compatibility || null);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (updates.metadata !== undefined) {
|
|
257
|
+
updateFields.push(`metadata = $${paramIndex++}`);
|
|
258
|
+
values.push(JSON.stringify(updates.metadata || {}));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (updates.content !== undefined) {
|
|
262
|
+
updateFields.push(`content = $${paramIndex++}`);
|
|
263
|
+
values.push(updates.content || null);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (updates.subSkills !== undefined) {
|
|
267
|
+
updateFields.push(`sub_skills = $${paramIndex++}`);
|
|
268
|
+
values.push(JSON.stringify(updates.subSkills || []));
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (updateFields.length === 0) {
|
|
272
|
+
return this.getSkillById(id);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
updateFields.push(`updated_at = $${paramIndex++}`);
|
|
276
|
+
values.push(new Date());
|
|
277
|
+
values.push(id);
|
|
278
|
+
|
|
279
|
+
await this.pool.query(
|
|
280
|
+
`
|
|
281
|
+
UPDATE lattice_skills
|
|
282
|
+
SET ${updateFields.join(", ")}
|
|
283
|
+
WHERE id = $${paramIndex}
|
|
284
|
+
`,
|
|
285
|
+
values
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
return this.getSkillById(id);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Delete a skill by ID
|
|
293
|
+
*/
|
|
294
|
+
async deleteSkill(id: string): Promise<boolean> {
|
|
295
|
+
await this.ensureInitialized();
|
|
296
|
+
|
|
297
|
+
const result = await this.pool.query(
|
|
298
|
+
`
|
|
299
|
+
DELETE FROM lattice_skills
|
|
300
|
+
WHERE id = $1
|
|
301
|
+
`,
|
|
302
|
+
[id]
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
return result.rowCount !== null && result.rowCount > 0;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Check if skill exists
|
|
310
|
+
*/
|
|
311
|
+
async hasSkill(id: string): Promise<boolean> {
|
|
312
|
+
await this.ensureInitialized();
|
|
313
|
+
|
|
314
|
+
const result = await this.pool.query<{ count: string }>(
|
|
315
|
+
`
|
|
316
|
+
SELECT COUNT(*) as count
|
|
317
|
+
FROM lattice_skills
|
|
318
|
+
WHERE id = $1
|
|
319
|
+
`,
|
|
320
|
+
[id]
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
return parseInt(result.rows[0].count, 10) > 0;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Search skills by metadata
|
|
328
|
+
*/
|
|
329
|
+
async searchByMetadata(
|
|
330
|
+
metadataKey: string,
|
|
331
|
+
metadataValue: string
|
|
332
|
+
): Promise<Skill[]> {
|
|
333
|
+
await this.ensureInitialized();
|
|
334
|
+
|
|
335
|
+
const result = await this.pool.query<{
|
|
336
|
+
id: string;
|
|
337
|
+
name: string;
|
|
338
|
+
description: string;
|
|
339
|
+
license: string | null;
|
|
340
|
+
compatibility: string | null;
|
|
341
|
+
metadata: any;
|
|
342
|
+
content: string | null;
|
|
343
|
+
sub_skills: any;
|
|
344
|
+
created_at: Date;
|
|
345
|
+
updated_at: Date;
|
|
346
|
+
}>(
|
|
347
|
+
`
|
|
348
|
+
SELECT id, name, description, license, compatibility, metadata, content, sub_skills, created_at, updated_at
|
|
349
|
+
FROM lattice_skills
|
|
350
|
+
WHERE metadata->>$1 = $2
|
|
351
|
+
ORDER BY created_at DESC
|
|
352
|
+
`,
|
|
353
|
+
[metadataKey, metadataValue]
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
return result.rows.map(this.mapRowToSkill);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Filter skills by compatibility
|
|
361
|
+
*/
|
|
362
|
+
async filterByCompatibility(compatibility: string): Promise<Skill[]> {
|
|
363
|
+
await this.ensureInitialized();
|
|
364
|
+
|
|
365
|
+
const result = await this.pool.query<{
|
|
366
|
+
id: string;
|
|
367
|
+
name: string;
|
|
368
|
+
description: string;
|
|
369
|
+
license: string | null;
|
|
370
|
+
compatibility: string | null;
|
|
371
|
+
metadata: any;
|
|
372
|
+
content: string | null;
|
|
373
|
+
sub_skills: any;
|
|
374
|
+
created_at: Date;
|
|
375
|
+
updated_at: Date;
|
|
376
|
+
}>(
|
|
377
|
+
`
|
|
378
|
+
SELECT id, name, description, license, compatibility, metadata, content, sub_skills, created_at, updated_at
|
|
379
|
+
FROM lattice_skills
|
|
380
|
+
WHERE compatibility = $1
|
|
381
|
+
ORDER BY created_at DESC
|
|
382
|
+
`,
|
|
383
|
+
[compatibility]
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
return result.rows.map(this.mapRowToSkill);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Filter skills by license
|
|
391
|
+
*/
|
|
392
|
+
async filterByLicense(license: string): Promise<Skill[]> {
|
|
393
|
+
await this.ensureInitialized();
|
|
394
|
+
|
|
395
|
+
const result = await this.pool.query<{
|
|
396
|
+
id: string;
|
|
397
|
+
name: string;
|
|
398
|
+
description: string;
|
|
399
|
+
license: string | null;
|
|
400
|
+
compatibility: string | null;
|
|
401
|
+
metadata: any;
|
|
402
|
+
content: string | null;
|
|
403
|
+
sub_skills: any;
|
|
404
|
+
created_at: Date;
|
|
405
|
+
updated_at: Date;
|
|
406
|
+
}>(
|
|
407
|
+
`
|
|
408
|
+
SELECT id, name, description, license, compatibility, metadata, content, sub_skills, created_at, updated_at
|
|
409
|
+
FROM lattice_skills
|
|
410
|
+
WHERE license = $1
|
|
411
|
+
ORDER BY created_at DESC
|
|
412
|
+
`,
|
|
413
|
+
[license]
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
return result.rows.map(this.mapRowToSkill);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Get sub-skills of a parent skill
|
|
421
|
+
*/
|
|
422
|
+
async getSubSkills(parentSkillName: string): Promise<Skill[]> {
|
|
423
|
+
await this.ensureInitialized();
|
|
424
|
+
|
|
425
|
+
const parentSkill = await this.getSkillById(parentSkillName);
|
|
426
|
+
if (!parentSkill || !parentSkill.subSkills || parentSkill.subSkills.length === 0) {
|
|
427
|
+
return [];
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Query all sub-skills in one query
|
|
431
|
+
const placeholders = parentSkill.subSkills.map((_, index) => `$${index + 1}`).join(", ");
|
|
432
|
+
const result = await this.pool.query<{
|
|
433
|
+
id: string;
|
|
434
|
+
name: string;
|
|
435
|
+
description: string;
|
|
436
|
+
license: string | null;
|
|
437
|
+
compatibility: string | null;
|
|
438
|
+
metadata: any;
|
|
439
|
+
content: string | null;
|
|
440
|
+
sub_skills: any;
|
|
441
|
+
created_at: Date;
|
|
442
|
+
updated_at: Date;
|
|
443
|
+
}>(
|
|
444
|
+
`
|
|
445
|
+
SELECT id, name, description, license, compatibility, metadata, content, sub_skills, created_at, updated_at
|
|
446
|
+
FROM lattice_skills
|
|
447
|
+
WHERE name IN (${placeholders})
|
|
448
|
+
ORDER BY created_at DESC
|
|
449
|
+
`,
|
|
450
|
+
parentSkill.subSkills
|
|
451
|
+
);
|
|
452
|
+
|
|
453
|
+
return result.rows.map(this.mapRowToSkill);
|
|
454
|
+
}
|
|
455
|
+
}
|