@axiom-lattice/pg-stores 1.0.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/.turbo/turbo-build.log +20 -0
- package/CHANGELOG.md +10 -0
- package/LICENSE +201 -0
- package/MIGRATION_GUIDE.md +310 -0
- package/README.md +341 -0
- package/dist/index.d.mts +211 -0
- package/dist/index.d.ts +211 -0
- package/dist/index.js +614 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +583 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +40 -0
- package/src/index.ts +25 -0
- package/src/migrations/assistant_migrations.ts +44 -0
- package/src/migrations/migration.ts +184 -0
- package/src/migrations/thread_migrations.ts +42 -0
- package/src/stores/PostgreSQLAssistantStore.ts +303 -0
- package/src/stores/PostgreSQLThreadStore.ts +276 -0
- package/src/types.ts +10 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL implementation of ThreadStore
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Pool, PoolClient, PoolConfig } from "pg";
|
|
6
|
+
import {
|
|
7
|
+
ThreadStore,
|
|
8
|
+
Thread,
|
|
9
|
+
CreateThreadRequest,
|
|
10
|
+
} from "@axiom-lattice/protocols";
|
|
11
|
+
import { MigrationManager } from "../migrations/migration";
|
|
12
|
+
import { createThreadsTable } from "../migrations/thread_migrations";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* PostgreSQL ThreadStore options
|
|
16
|
+
*/
|
|
17
|
+
export interface PostgreSQLThreadStoreOptions {
|
|
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 ThreadStore
|
|
33
|
+
*/
|
|
34
|
+
export class PostgreSQLThreadStore implements ThreadStore {
|
|
35
|
+
private pool: Pool;
|
|
36
|
+
private migrationManager: MigrationManager;
|
|
37
|
+
private initialized: boolean = false;
|
|
38
|
+
private ownsPool: boolean = true;
|
|
39
|
+
|
|
40
|
+
constructor(options: PostgreSQLThreadStoreOptions) {
|
|
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(createThreadsTable);
|
|
50
|
+
|
|
51
|
+
// Auto-migrate by default
|
|
52
|
+
if (options.autoMigrate !== false) {
|
|
53
|
+
this.initialize().catch((error) => {
|
|
54
|
+
console.error("Failed to initialize PostgreSQLThreadStore:", 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
|
+
* Get all threads for a specific assistant
|
|
84
|
+
*/
|
|
85
|
+
async getThreadsByAssistantId(assistantId: string): Promise<Thread[]> {
|
|
86
|
+
await this.ensureInitialized();
|
|
87
|
+
|
|
88
|
+
const result = await this.pool.query<{
|
|
89
|
+
id: string;
|
|
90
|
+
assistant_id: string;
|
|
91
|
+
metadata: any;
|
|
92
|
+
created_at: Date;
|
|
93
|
+
updated_at: Date;
|
|
94
|
+
}>(
|
|
95
|
+
`
|
|
96
|
+
SELECT id, assistant_id, metadata, created_at, updated_at
|
|
97
|
+
FROM lattice_threads
|
|
98
|
+
WHERE assistant_id = $1
|
|
99
|
+
ORDER BY created_at DESC
|
|
100
|
+
`,
|
|
101
|
+
[assistantId]
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
return result.rows.map(this.mapRowToThread);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Get a thread by ID for a specific assistant
|
|
109
|
+
*/
|
|
110
|
+
async getThreadById(
|
|
111
|
+
assistantId: string,
|
|
112
|
+
threadId: string
|
|
113
|
+
): Promise<Thread | undefined> {
|
|
114
|
+
await this.ensureInitialized();
|
|
115
|
+
|
|
116
|
+
const result = await this.pool.query<{
|
|
117
|
+
id: string;
|
|
118
|
+
assistant_id: string;
|
|
119
|
+
metadata: any;
|
|
120
|
+
created_at: Date;
|
|
121
|
+
updated_at: Date;
|
|
122
|
+
}>(
|
|
123
|
+
`
|
|
124
|
+
SELECT id, assistant_id, metadata, created_at, updated_at
|
|
125
|
+
FROM lattice_threads
|
|
126
|
+
WHERE id = $1 AND assistant_id = $2
|
|
127
|
+
`,
|
|
128
|
+
[threadId, assistantId]
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
if (result.rows.length === 0) {
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return this.mapRowToThread(result.rows[0]);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Create a new thread for an assistant
|
|
140
|
+
*/
|
|
141
|
+
async createThread(
|
|
142
|
+
assistantId: string,
|
|
143
|
+
threadId: string,
|
|
144
|
+
data: CreateThreadRequest
|
|
145
|
+
): Promise<Thread> {
|
|
146
|
+
await this.ensureInitialized();
|
|
147
|
+
|
|
148
|
+
const now = new Date();
|
|
149
|
+
const metadata = data.metadata || {};
|
|
150
|
+
|
|
151
|
+
await this.pool.query(
|
|
152
|
+
`
|
|
153
|
+
INSERT INTO lattice_threads (id, assistant_id, metadata, created_at, updated_at)
|
|
154
|
+
VALUES ($1, $2, $3, $4, $5)
|
|
155
|
+
ON CONFLICT (id, assistant_id) DO UPDATE SET
|
|
156
|
+
metadata = EXCLUDED.metadata,
|
|
157
|
+
updated_at = EXCLUDED.updated_at
|
|
158
|
+
`,
|
|
159
|
+
[threadId, assistantId, JSON.stringify(metadata), now, now]
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
id: threadId,
|
|
164
|
+
assistantId,
|
|
165
|
+
metadata,
|
|
166
|
+
createdAt: now,
|
|
167
|
+
updatedAt: now,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Update an existing thread
|
|
173
|
+
*/
|
|
174
|
+
async updateThread(
|
|
175
|
+
assistantId: string,
|
|
176
|
+
threadId: string,
|
|
177
|
+
updates: Partial<CreateThreadRequest>
|
|
178
|
+
): Promise<Thread | null> {
|
|
179
|
+
await this.ensureInitialized();
|
|
180
|
+
|
|
181
|
+
// Get existing thread
|
|
182
|
+
const existing = await this.getThreadById(assistantId, threadId);
|
|
183
|
+
if (!existing) {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Merge metadata
|
|
188
|
+
const updatedMetadata = {
|
|
189
|
+
...existing.metadata,
|
|
190
|
+
...(updates.metadata || {}),
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const now = new Date();
|
|
194
|
+
|
|
195
|
+
await this.pool.query(
|
|
196
|
+
`
|
|
197
|
+
UPDATE lattice_threads
|
|
198
|
+
SET metadata = $1, updated_at = $2
|
|
199
|
+
WHERE id = $3 AND assistant_id = $4
|
|
200
|
+
`,
|
|
201
|
+
[JSON.stringify(updatedMetadata), now, threadId, assistantId]
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
...existing,
|
|
206
|
+
metadata: updatedMetadata,
|
|
207
|
+
updatedAt: now,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Delete a thread by ID
|
|
213
|
+
*/
|
|
214
|
+
async deleteThread(assistantId: string, threadId: string): Promise<boolean> {
|
|
215
|
+
await this.ensureInitialized();
|
|
216
|
+
|
|
217
|
+
const result = await this.pool.query(
|
|
218
|
+
`
|
|
219
|
+
DELETE FROM lattice_threads
|
|
220
|
+
WHERE id = $1 AND assistant_id = $2
|
|
221
|
+
`,
|
|
222
|
+
[threadId, assistantId]
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
return result.rowCount !== null && result.rowCount > 0;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Check if thread exists
|
|
230
|
+
*/
|
|
231
|
+
async hasThread(assistantId: string, threadId: string): Promise<boolean> {
|
|
232
|
+
await this.ensureInitialized();
|
|
233
|
+
|
|
234
|
+
const result = await this.pool.query(
|
|
235
|
+
`
|
|
236
|
+
SELECT 1 FROM lattice_threads
|
|
237
|
+
WHERE id = $1 AND assistant_id = $2
|
|
238
|
+
LIMIT 1
|
|
239
|
+
`,
|
|
240
|
+
[threadId, assistantId]
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
return result.rows.length > 0;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Ensure store is initialized
|
|
248
|
+
*/
|
|
249
|
+
private async ensureInitialized(): Promise<void> {
|
|
250
|
+
if (!this.initialized) {
|
|
251
|
+
await this.initialize();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Map database row to Thread object
|
|
257
|
+
*/
|
|
258
|
+
private mapRowToThread(row: {
|
|
259
|
+
id: string;
|
|
260
|
+
assistant_id: string;
|
|
261
|
+
metadata: any;
|
|
262
|
+
created_at: Date;
|
|
263
|
+
updated_at: Date;
|
|
264
|
+
}): Thread {
|
|
265
|
+
return {
|
|
266
|
+
id: row.id,
|
|
267
|
+
assistantId: row.assistant_id,
|
|
268
|
+
metadata:
|
|
269
|
+
typeof row.metadata === "string"
|
|
270
|
+
? JSON.parse(row.metadata)
|
|
271
|
+
: row.metadata || {},
|
|
272
|
+
createdAt: row.created_at,
|
|
273
|
+
updatedAt: row.updated_at,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
}
|
package/src/types.ts
ADDED
package/tsconfig.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "preserve",
|
|
5
|
+
"lib": ["ES2020"],
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"moduleResolution": "Bundler",
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"declaration": true,
|
|
15
|
+
"declarationMap": true,
|
|
16
|
+
"types": ["node"],
|
|
17
|
+
"sourceMap": true,
|
|
18
|
+
"incremental": true,
|
|
19
|
+
"tsBuildInfoFile": "./.tsbuildinfo"
|
|
20
|
+
},
|
|
21
|
+
"include": ["src/index.ts"],
|
|
22
|
+
"exclude": ["node_modules", "dist"]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|