@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.
@@ -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
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Re-export ThreadStore types from protocols package
3
+ * This ensures all packages use the same type definitions
4
+ */
5
+
6
+ export type {
7
+ Thread,
8
+ CreateThreadRequest,
9
+ ThreadStore,
10
+ } from "@axiom-lattice/protocols";
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
+