family-ai-agent 1.0.5 → 1.0.7
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/.letta/settings.local.json +3 -0
- package/dist/cli/index.js +6 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/database/adapters/base-adapter.d.ts +81 -0
- package/dist/database/adapters/base-adapter.d.ts.map +1 -0
- package/dist/database/adapters/base-adapter.js +105 -0
- package/dist/database/adapters/base-adapter.js.map +1 -0
- package/dist/database/adapters/index.d.ts +49 -0
- package/dist/database/adapters/index.d.ts.map +1 -0
- package/dist/database/adapters/index.js +200 -0
- package/dist/database/adapters/index.js.map +1 -0
- package/dist/database/adapters/postgres-adapter.d.ts +75 -0
- package/dist/database/adapters/postgres-adapter.d.ts.map +1 -0
- package/dist/database/adapters/postgres-adapter.js +225 -0
- package/dist/database/adapters/postgres-adapter.js.map +1 -0
- package/dist/database/adapters/sqlite-adapter.d.ts +72 -0
- package/dist/database/adapters/sqlite-adapter.d.ts.map +1 -0
- package/dist/database/adapters/sqlite-adapter.js +368 -0
- package/dist/database/adapters/sqlite-adapter.js.map +1 -0
- package/dist/database/cache/cache-keys.d.ts +180 -0
- package/dist/database/cache/cache-keys.d.ts.map +1 -0
- package/dist/database/cache/cache-keys.js +107 -0
- package/dist/database/cache/cache-keys.js.map +1 -0
- package/dist/database/cache/index.d.ts +24 -0
- package/dist/database/cache/index.d.ts.map +1 -0
- package/dist/database/cache/index.js +34 -0
- package/dist/database/cache/index.js.map +1 -0
- package/dist/database/cache/query-cache.d.ts +67 -0
- package/dist/database/cache/query-cache.d.ts.map +1 -0
- package/dist/database/cache/query-cache.js +177 -0
- package/dist/database/cache/query-cache.js.map +1 -0
- package/dist/database/client.d.ts +63 -4
- package/dist/database/client.d.ts.map +1 -1
- package/dist/database/client.js +147 -59
- package/dist/database/client.js.map +1 -1
- package/dist/database/db-config.d.ts +104 -0
- package/dist/database/db-config.d.ts.map +1 -0
- package/dist/database/db-config.js +167 -0
- package/dist/database/db-config.js.map +1 -0
- package/dist/database/drizzle/index.d.ts +42 -0
- package/dist/database/drizzle/index.d.ts.map +1 -0
- package/dist/database/drizzle/index.js +48 -0
- package/dist/database/drizzle/index.js.map +1 -0
- package/dist/database/drizzle/schema/audit.d.ts +533 -0
- package/dist/database/drizzle/schema/audit.d.ts.map +1 -0
- package/dist/database/drizzle/schema/audit.js +71 -0
- package/dist/database/drizzle/schema/audit.js.map +1 -0
- package/dist/database/drizzle/schema/checkpoints.d.ts +665 -0
- package/dist/database/drizzle/schema/checkpoints.d.ts.map +1 -0
- package/dist/database/drizzle/schema/checkpoints.js +110 -0
- package/dist/database/drizzle/schema/checkpoints.js.map +1 -0
- package/dist/database/drizzle/schema/conversations.d.ts +449 -0
- package/dist/database/drizzle/schema/conversations.d.ts.map +1 -0
- package/dist/database/drizzle/schema/conversations.js +91 -0
- package/dist/database/drizzle/schema/conversations.js.map +1 -0
- package/dist/database/drizzle/schema/documents.d.ts +600 -0
- package/dist/database/drizzle/schema/documents.d.ts.map +1 -0
- package/dist/database/drizzle/schema/documents.js +100 -0
- package/dist/database/drizzle/schema/documents.js.map +1 -0
- package/dist/database/drizzle/schema/index.d.ts +3084 -0
- package/dist/database/drizzle/schema/index.d.ts.map +1 -0
- package/dist/database/drizzle/schema/index.js +46 -0
- package/dist/database/drizzle/schema/index.js.map +1 -0
- package/dist/database/drizzle/schema/memories.d.ts +435 -0
- package/dist/database/drizzle/schema/memories.d.ts.map +1 -0
- package/dist/database/drizzle/schema/memories.js +73 -0
- package/dist/database/drizzle/schema/memories.js.map +1 -0
- package/dist/database/drizzle/schema/tasks.d.ts +565 -0
- package/dist/database/drizzle/schema/tasks.d.ts.map +1 -0
- package/dist/database/drizzle/schema/tasks.js +84 -0
- package/dist/database/drizzle/schema/tasks.js.map +1 -0
- package/dist/database/health/circuit-breaker.d.ts +81 -0
- package/dist/database/health/circuit-breaker.d.ts.map +1 -0
- package/dist/database/health/circuit-breaker.js +184 -0
- package/dist/database/health/circuit-breaker.js.map +1 -0
- package/dist/database/health/health-monitor.d.ts +69 -0
- package/dist/database/health/health-monitor.d.ts.map +1 -0
- package/dist/database/health/health-monitor.js +174 -0
- package/dist/database/health/health-monitor.js.map +1 -0
- package/dist/database/health/index.d.ts +27 -0
- package/dist/database/health/index.d.ts.map +1 -0
- package/dist/database/health/index.js +23 -0
- package/dist/database/health/index.js.map +1 -0
- package/dist/database/index.d.ts +16 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/index.js +41 -0
- package/dist/database/index.js.map +1 -0
- package/dist/database/migrations/index.d.ts +34 -0
- package/dist/database/migrations/index.d.ts.map +1 -0
- package/dist/database/migrations/index.js +45 -0
- package/dist/database/migrations/index.js.map +1 -0
- package/dist/database/migrations/migrator.d.ts +77 -0
- package/dist/database/migrations/migrator.d.ts.map +1 -0
- package/dist/database/migrations/migrator.js +258 -0
- package/dist/database/migrations/migrator.js.map +1 -0
- package/dist/database/migrations/versions/001_initial.d.ts +9 -0
- package/dist/database/migrations/versions/001_initial.d.ts.map +1 -0
- package/dist/database/migrations/versions/001_initial.js +183 -0
- package/dist/database/migrations/versions/001_initial.js.map +1 -0
- package/dist/database/types.d.ts +255 -0
- package/dist/database/types.d.ts.map +1 -0
- package/dist/database/types.js +8 -0
- package/dist/database/types.js.map +1 -0
- package/dist/database/vector/embedding-cache.d.ts +92 -0
- package/dist/database/vector/embedding-cache.d.ts.map +1 -0
- package/dist/database/vector/embedding-cache.js +185 -0
- package/dist/database/vector/embedding-cache.js.map +1 -0
- package/dist/database/vector/hnsw-index.d.ts +111 -0
- package/dist/database/vector/hnsw-index.d.ts.map +1 -0
- package/dist/database/vector/hnsw-index.js +337 -0
- package/dist/database/vector/hnsw-index.js.map +1 -0
- package/dist/database/vector/index.d.ts +75 -0
- package/dist/database/vector/index.d.ts.map +1 -0
- package/dist/database/vector/index.js +213 -0
- package/dist/database/vector/index.js.map +1 -0
- package/dist/database/vector/similarity.d.ts +67 -0
- package/dist/database/vector/similarity.d.ts.map +1 -0
- package/dist/database/vector/similarity.js +176 -0
- package/dist/database/vector/similarity.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +6 -3
- package/src/cli/index.ts +5 -5
- package/src/database/adapters/base-adapter.ts +171 -0
- package/src/database/adapters/index.ts +224 -0
- package/src/database/adapters/postgres-adapter.ts +285 -0
- package/src/database/adapters/sqlite-adapter.ts +420 -0
- package/src/database/cache/cache-keys.ts +150 -0
- package/src/database/cache/index.ts +44 -0
- package/src/database/cache/query-cache.ts +213 -0
- package/src/database/client.ts +166 -64
- package/src/database/db-config.ts +194 -0
- package/src/database/drizzle/index.ts +66 -0
- package/src/database/drizzle/schema/audit.ts +127 -0
- package/src/database/drizzle/schema/checkpoints.ts +164 -0
- package/src/database/drizzle/schema/conversations.ts +138 -0
- package/src/database/drizzle/schema/documents.ts +157 -0
- package/src/database/drizzle/schema/index.ts +139 -0
- package/src/database/drizzle/schema/memories.ts +127 -0
- package/src/database/drizzle/schema/tasks.ts +129 -0
- package/src/database/health/circuit-breaker.ts +214 -0
- package/src/database/health/health-monitor.ts +224 -0
- package/src/database/health/index.ts +41 -0
- package/src/database/index.ts +157 -0
- package/src/database/migrations/index.ts +52 -0
- package/src/database/migrations/migrator.ts +325 -0
- package/src/database/migrations/versions/001_initial.ts +198 -0
- package/src/database/types.ts +324 -0
- package/src/database/vector/embedding-cache.ts +234 -0
- package/src/database/vector/hnsw-index.ts +452 -0
- package/src/database/vector/index.ts +292 -0
- package/src/database/vector/similarity.ts +198 -0
- package/src/index.ts +1 -1
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database Adapter Factory
|
|
3
|
+
*
|
|
4
|
+
* Factory for creating database adapters with auto-detection support.
|
|
5
|
+
* Tries PostgreSQL first, falls back to SQLite automatically.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import pg from 'pg';
|
|
9
|
+
|
|
10
|
+
import { PostgresAdapter } from './postgres-adapter.js';
|
|
11
|
+
import { SQLiteAdapter } from './sqlite-adapter.js';
|
|
12
|
+
import { BaseAdapter } from './base-adapter.js';
|
|
13
|
+
import { getDatabaseConfig, buildPostgresUrl, getDefaultSqlitePath } from '../db-config.js';
|
|
14
|
+
import type { DatabaseAdapter, DetectionResult, DatabaseConfig } from '../types.js';
|
|
15
|
+
import { createLogger } from '../../utils/logger.js';
|
|
16
|
+
|
|
17
|
+
const { Pool } = pg;
|
|
18
|
+
const logger = createLogger('AdapterFactory');
|
|
19
|
+
|
|
20
|
+
// Singleton adapter instance
|
|
21
|
+
let adapterInstance: DatabaseAdapter | null = null;
|
|
22
|
+
let initPromise: Promise<DatabaseAdapter> | null = null;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Detect which database to use
|
|
26
|
+
*/
|
|
27
|
+
export async function detectDatabase(config: DatabaseConfig): Promise<DetectionResult> {
|
|
28
|
+
// 1. Check for explicit configuration
|
|
29
|
+
if (config.type !== 'auto') {
|
|
30
|
+
if (config.type === 'postgresql') {
|
|
31
|
+
return {
|
|
32
|
+
type: 'postgresql',
|
|
33
|
+
connectionString: config.postgresUrl ?? buildPostgresUrl({
|
|
34
|
+
DATABASE_URL: config.postgresUrl,
|
|
35
|
+
DB_HOST: config.postgresHost,
|
|
36
|
+
DB_PORT: config.postgresPort,
|
|
37
|
+
DB_USER: config.postgresUser,
|
|
38
|
+
DB_PASSWORD: config.postgresPassword,
|
|
39
|
+
DB_NAME: config.postgresDatabase,
|
|
40
|
+
DB_POOL_MAX: config.postgresPoolMax,
|
|
41
|
+
DB_POOL_IDLE_TIMEOUT_MS: config.postgresIdleTimeoutMs,
|
|
42
|
+
DB_POOL_CONNECTION_TIMEOUT_MS: config.postgresConnectionTimeoutMs,
|
|
43
|
+
DATABASE_TYPE: 'postgresql',
|
|
44
|
+
SQLITE_PATH: config.sqlitePath,
|
|
45
|
+
DB_HEALTH_CHECK_INTERVAL_MS: config.healthCheckIntervalMs,
|
|
46
|
+
DB_CIRCUIT_BREAKER_THRESHOLD: config.circuitBreakerThreshold,
|
|
47
|
+
DB_CIRCUIT_BREAKER_TIMEOUT_MS: config.circuitBreakerTimeoutMs,
|
|
48
|
+
DB_CACHE_MAX_SIZE: config.cacheMaxSize,
|
|
49
|
+
DB_CACHE_TTL_MS: config.cacheTtlMs,
|
|
50
|
+
VECTOR_DIMENSION: config.vectorDimension,
|
|
51
|
+
VECTOR_MIN_SIMILARITY: config.vectorMinSimilarity,
|
|
52
|
+
}),
|
|
53
|
+
reason: 'Explicit PostgreSQL configuration',
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
type: 'sqlite',
|
|
58
|
+
sqlitePath: config.sqlitePath ?? getDefaultSqlitePath(),
|
|
59
|
+
reason: 'Explicit SQLite configuration',
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 2. Check for DATABASE_URL (usually means PostgreSQL is intended)
|
|
64
|
+
if (config.postgresUrl) {
|
|
65
|
+
return {
|
|
66
|
+
type: 'postgresql',
|
|
67
|
+
connectionString: config.postgresUrl,
|
|
68
|
+
reason: 'DATABASE_URL environment variable set',
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 3. Try PostgreSQL connection
|
|
73
|
+
try {
|
|
74
|
+
const connectionString = buildPostgresUrl({
|
|
75
|
+
DATABASE_URL: config.postgresUrl,
|
|
76
|
+
DB_HOST: config.postgresHost,
|
|
77
|
+
DB_PORT: config.postgresPort,
|
|
78
|
+
DB_USER: config.postgresUser,
|
|
79
|
+
DB_PASSWORD: config.postgresPassword,
|
|
80
|
+
DB_NAME: config.postgresDatabase,
|
|
81
|
+
DB_POOL_MAX: config.postgresPoolMax,
|
|
82
|
+
DB_POOL_IDLE_TIMEOUT_MS: config.postgresIdleTimeoutMs,
|
|
83
|
+
DB_POOL_CONNECTION_TIMEOUT_MS: config.postgresConnectionTimeoutMs,
|
|
84
|
+
DATABASE_TYPE: 'auto',
|
|
85
|
+
SQLITE_PATH: config.sqlitePath,
|
|
86
|
+
DB_HEALTH_CHECK_INTERVAL_MS: config.healthCheckIntervalMs,
|
|
87
|
+
DB_CIRCUIT_BREAKER_THRESHOLD: config.circuitBreakerThreshold,
|
|
88
|
+
DB_CIRCUIT_BREAKER_TIMEOUT_MS: config.circuitBreakerTimeoutMs,
|
|
89
|
+
DB_CACHE_MAX_SIZE: config.cacheMaxSize,
|
|
90
|
+
DB_CACHE_TTL_MS: config.cacheTtlMs,
|
|
91
|
+
VECTOR_DIMENSION: config.vectorDimension,
|
|
92
|
+
VECTOR_MIN_SIMILARITY: config.vectorMinSimilarity,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const pool = new Pool({
|
|
96
|
+
connectionString,
|
|
97
|
+
connectionTimeoutMillis: 3000, // Quick timeout for detection
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const client = await pool.connect();
|
|
102
|
+
await client.query('SELECT 1');
|
|
103
|
+
client.release();
|
|
104
|
+
await pool.end();
|
|
105
|
+
|
|
106
|
+
logger.info('PostgreSQL connection successful');
|
|
107
|
+
return {
|
|
108
|
+
type: 'postgresql',
|
|
109
|
+
connectionString,
|
|
110
|
+
reason: 'PostgreSQL connection successful',
|
|
111
|
+
};
|
|
112
|
+
} finally {
|
|
113
|
+
await pool.end().catch(() => {});
|
|
114
|
+
}
|
|
115
|
+
} catch (error) {
|
|
116
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
117
|
+
logger.debug('PostgreSQL not available, falling back to SQLite', { error: message });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 4. Fall back to SQLite
|
|
121
|
+
const sqlitePath = config.sqlitePath ?? getDefaultSqlitePath();
|
|
122
|
+
return {
|
|
123
|
+
type: 'sqlite',
|
|
124
|
+
sqlitePath,
|
|
125
|
+
reason: 'PostgreSQL unavailable, using SQLite fallback',
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Create database adapter based on detection result
|
|
131
|
+
*/
|
|
132
|
+
export async function createAdapter(detection: DetectionResult, config: DatabaseConfig): Promise<DatabaseAdapter> {
|
|
133
|
+
let adapter: DatabaseAdapter;
|
|
134
|
+
|
|
135
|
+
if (detection.type === 'postgresql') {
|
|
136
|
+
adapter = new PostgresAdapter(detection.connectionString!, config);
|
|
137
|
+
} else {
|
|
138
|
+
adapter = new SQLiteAdapter(detection.sqlitePath!, config);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
await adapter.initialize();
|
|
142
|
+
return adapter;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Get or create the database adapter singleton
|
|
147
|
+
*/
|
|
148
|
+
export async function getAdapter(): Promise<DatabaseAdapter> {
|
|
149
|
+
if (adapterInstance?.isInitialized) {
|
|
150
|
+
return adapterInstance;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (!initPromise) {
|
|
154
|
+
initPromise = initializeAdapter();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return initPromise;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Initialize the database adapter
|
|
162
|
+
*/
|
|
163
|
+
async function initializeAdapter(): Promise<DatabaseAdapter> {
|
|
164
|
+
const config = getDatabaseConfig();
|
|
165
|
+
const detection = await detectDatabase(config);
|
|
166
|
+
|
|
167
|
+
logger.info('Database detected', {
|
|
168
|
+
type: detection.type,
|
|
169
|
+
reason: detection.reason,
|
|
170
|
+
path: detection.sqlitePath,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
adapterInstance = await createAdapter(detection, config);
|
|
174
|
+
return adapterInstance;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Close the database adapter
|
|
179
|
+
*/
|
|
180
|
+
export async function closeAdapter(): Promise<void> {
|
|
181
|
+
if (adapterInstance) {
|
|
182
|
+
await adapterInstance.close();
|
|
183
|
+
adapterInstance = null;
|
|
184
|
+
initPromise = null;
|
|
185
|
+
logger.info('Database adapter closed');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Reset adapter (for testing)
|
|
191
|
+
*/
|
|
192
|
+
export function resetAdapter(): void {
|
|
193
|
+
adapterInstance = null;
|
|
194
|
+
initPromise = null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Check if adapter is initialized
|
|
199
|
+
*/
|
|
200
|
+
export function isAdapterInitialized(): boolean {
|
|
201
|
+
return adapterInstance?.isInitialized ?? false;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Get current adapter type
|
|
206
|
+
*/
|
|
207
|
+
export function getAdapterType(): 'postgresql' | 'sqlite' | null {
|
|
208
|
+
return adapterInstance?.type ?? null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Re-export adapter classes
|
|
212
|
+
export { PostgresAdapter } from './postgres-adapter.js';
|
|
213
|
+
export { SQLiteAdapter } from './sqlite-adapter.js';
|
|
214
|
+
export { BaseAdapter } from './base-adapter.js';
|
|
215
|
+
|
|
216
|
+
export default {
|
|
217
|
+
getAdapter,
|
|
218
|
+
closeAdapter,
|
|
219
|
+
resetAdapter,
|
|
220
|
+
detectDatabase,
|
|
221
|
+
createAdapter,
|
|
222
|
+
isAdapterInitialized,
|
|
223
|
+
getAdapterType,
|
|
224
|
+
};
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL Database Adapter
|
|
3
|
+
*
|
|
4
|
+
* Adapter for PostgreSQL with connection pooling and pgvector support.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import pg from 'pg';
|
|
8
|
+
import type { QueryResultRow } from 'pg';
|
|
9
|
+
import { drizzle } from 'drizzle-orm/node-postgres';
|
|
10
|
+
import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
|
|
11
|
+
import { sql } from 'drizzle-orm';
|
|
12
|
+
|
|
13
|
+
import { BaseAdapter } from './base-adapter.js';
|
|
14
|
+
import type {
|
|
15
|
+
QueryResult,
|
|
16
|
+
TransactionClient,
|
|
17
|
+
PoolStats,
|
|
18
|
+
DatabaseConfig,
|
|
19
|
+
} from '../types.js';
|
|
20
|
+
import { createLogger } from '../../utils/logger.js';
|
|
21
|
+
import { MemoryError } from '../../utils/errors.js';
|
|
22
|
+
|
|
23
|
+
import * as schema from '../drizzle/schema/index.js';
|
|
24
|
+
|
|
25
|
+
const { Pool } = pg;
|
|
26
|
+
const logger = createLogger('PostgresAdapter');
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* PostgreSQL database adapter with Drizzle ORM
|
|
30
|
+
*/
|
|
31
|
+
export class PostgresAdapter extends BaseAdapter {
|
|
32
|
+
readonly type = 'postgresql' as const;
|
|
33
|
+
|
|
34
|
+
private pool: pg.Pool | null = null;
|
|
35
|
+
private drizzleInstance: NodePgDatabase<typeof schema> | null = null;
|
|
36
|
+
private connectionString: string;
|
|
37
|
+
private config: DatabaseConfig;
|
|
38
|
+
|
|
39
|
+
constructor(connectionString: string, config: DatabaseConfig) {
|
|
40
|
+
super();
|
|
41
|
+
this.connectionString = connectionString;
|
|
42
|
+
this.config = config;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get Drizzle ORM instance
|
|
47
|
+
*/
|
|
48
|
+
get drizzle(): NodePgDatabase<typeof schema> {
|
|
49
|
+
this.ensureInitialized();
|
|
50
|
+
return this.drizzleInstance!;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get raw pool (for advanced operations)
|
|
55
|
+
*/
|
|
56
|
+
get rawPool(): pg.Pool {
|
|
57
|
+
this.ensureInitialized();
|
|
58
|
+
return this.pool!;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Initialize PostgreSQL connection pool
|
|
63
|
+
*/
|
|
64
|
+
async initialize(): Promise<void> {
|
|
65
|
+
if (this._isInitialized) return;
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
this.pool = new Pool({
|
|
69
|
+
connectionString: this.connectionString,
|
|
70
|
+
max: this.config.postgresPoolMax,
|
|
71
|
+
idleTimeoutMillis: this.config.postgresIdleTimeoutMs,
|
|
72
|
+
connectionTimeoutMillis: this.config.postgresConnectionTimeoutMs,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Set up event handlers
|
|
76
|
+
this.pool.on('error', (err) => {
|
|
77
|
+
logger.error('Pool error', { error: err.message });
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
this.pool.on('connect', () => {
|
|
81
|
+
logger.debug('New client connected to pool');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Create Drizzle instance
|
|
85
|
+
this.drizzleInstance = drizzle(this.pool, {
|
|
86
|
+
schema,
|
|
87
|
+
logger: process.env.NODE_ENV === 'development',
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Test connection and enable pgvector
|
|
91
|
+
await this.testConnection();
|
|
92
|
+
await this.enablePgVector();
|
|
93
|
+
|
|
94
|
+
this._isInitialized = true;
|
|
95
|
+
logger.info('PostgreSQL adapter initialized', {
|
|
96
|
+
host: this.config.postgresHost,
|
|
97
|
+
database: this.config.postgresDatabase,
|
|
98
|
+
});
|
|
99
|
+
} catch (error) {
|
|
100
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
101
|
+
logger.error('Failed to initialize PostgreSQL', { error: message });
|
|
102
|
+
throw new MemoryError(`PostgreSQL initialization failed: ${message}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Test database connection
|
|
108
|
+
*/
|
|
109
|
+
private async testConnection(): Promise<void> {
|
|
110
|
+
const client = await this.pool!.connect();
|
|
111
|
+
try {
|
|
112
|
+
await client.query('SELECT 1');
|
|
113
|
+
} finally {
|
|
114
|
+
client.release();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Enable pgvector extension if not already enabled
|
|
120
|
+
*/
|
|
121
|
+
private async enablePgVector(): Promise<void> {
|
|
122
|
+
try {
|
|
123
|
+
await this.pool!.query('CREATE EXTENSION IF NOT EXISTS vector');
|
|
124
|
+
logger.debug('pgvector extension enabled');
|
|
125
|
+
} catch (error) {
|
|
126
|
+
logger.warn('Could not enable pgvector extension', {
|
|
127
|
+
error: error instanceof Error ? error.message : 'Unknown'
|
|
128
|
+
});
|
|
129
|
+
// Don't fail - extension might already exist or user might not have permissions
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Execute a raw SQL query
|
|
135
|
+
*/
|
|
136
|
+
async query<T extends QueryResultRow = Record<string, any>>(
|
|
137
|
+
sqlText: string,
|
|
138
|
+
params?: unknown[]
|
|
139
|
+
): Promise<QueryResult<T>> {
|
|
140
|
+
this.ensureInitialized();
|
|
141
|
+
const start = Date.now();
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
const result = await this.pool!.query<T>(sqlText, params);
|
|
145
|
+
const duration = Date.now() - start;
|
|
146
|
+
this.logQuery(sqlText, duration, result.rowCount);
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
rows: result.rows,
|
|
150
|
+
rowCount: result.rowCount,
|
|
151
|
+
};
|
|
152
|
+
} catch (error) {
|
|
153
|
+
this.logError(sqlText, error);
|
|
154
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
155
|
+
throw new MemoryError(`Query failed: ${message}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Execute a transaction
|
|
161
|
+
*/
|
|
162
|
+
async transaction<T>(
|
|
163
|
+
callback: (client: TransactionClient) => Promise<T>
|
|
164
|
+
): Promise<T> {
|
|
165
|
+
this.ensureInitialized();
|
|
166
|
+
const client = await this.pool!.connect();
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
await client.query('BEGIN');
|
|
170
|
+
|
|
171
|
+
const transactionClient: TransactionClient = {
|
|
172
|
+
query: async <R extends QueryResultRow = Record<string, any>>(
|
|
173
|
+
sqlText: string,
|
|
174
|
+
params?: unknown[]
|
|
175
|
+
): Promise<QueryResult<R>> => {
|
|
176
|
+
const result = await client.query<R>(sqlText, params);
|
|
177
|
+
return {
|
|
178
|
+
rows: result.rows,
|
|
179
|
+
rowCount: result.rowCount,
|
|
180
|
+
};
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const result = await callback(transactionClient);
|
|
185
|
+
await client.query('COMMIT');
|
|
186
|
+
return result;
|
|
187
|
+
} catch (error) {
|
|
188
|
+
await client.query('ROLLBACK');
|
|
189
|
+
throw error;
|
|
190
|
+
} finally {
|
|
191
|
+
client.release();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Check database health
|
|
197
|
+
*/
|
|
198
|
+
async healthCheck(): Promise<boolean> {
|
|
199
|
+
try {
|
|
200
|
+
const result = await this.query<{ ok: number }>('SELECT 1 as ok');
|
|
201
|
+
return result.rows.length > 0 && result.rows[0]!.ok === 1;
|
|
202
|
+
} catch {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Get connection pool statistics
|
|
209
|
+
*/
|
|
210
|
+
getPoolStats(): PoolStats {
|
|
211
|
+
if (!this.pool) {
|
|
212
|
+
return { total: 0, idle: 0, waiting: 0 };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
total: this.pool.totalCount,
|
|
217
|
+
idle: this.pool.idleCount,
|
|
218
|
+
waiting: this.pool.waitingCount,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Close all connections
|
|
224
|
+
*/
|
|
225
|
+
async close(): Promise<void> {
|
|
226
|
+
if (this.pool) {
|
|
227
|
+
await this.pool.end();
|
|
228
|
+
this.pool = null;
|
|
229
|
+
this.drizzleInstance = null;
|
|
230
|
+
this._isInitialized = false;
|
|
231
|
+
logger.info('PostgreSQL pool closed');
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Execute vector similarity search
|
|
237
|
+
*/
|
|
238
|
+
async vectorSearch<T = Record<string, unknown>>(
|
|
239
|
+
table: string,
|
|
240
|
+
embeddingColumn: string,
|
|
241
|
+
queryEmbedding: number[],
|
|
242
|
+
options: {
|
|
243
|
+
limit?: number;
|
|
244
|
+
minSimilarity?: number;
|
|
245
|
+
filters?: Record<string, unknown>;
|
|
246
|
+
selectColumns?: string[];
|
|
247
|
+
} = {}
|
|
248
|
+
): Promise<Array<T & { similarity: number }>> {
|
|
249
|
+
const {
|
|
250
|
+
limit = 5,
|
|
251
|
+
minSimilarity = 0.7,
|
|
252
|
+
filters = {},
|
|
253
|
+
selectColumns = ['*'],
|
|
254
|
+
} = options;
|
|
255
|
+
|
|
256
|
+
const embeddingStr = `[${queryEmbedding.join(',')}]`;
|
|
257
|
+
|
|
258
|
+
// Build WHERE clause
|
|
259
|
+
const whereConditions = [`1 - (${embeddingColumn} <=> $1::vector) >= $2`];
|
|
260
|
+
const params: unknown[] = [embeddingStr, minSimilarity];
|
|
261
|
+
let paramIndex = 3;
|
|
262
|
+
|
|
263
|
+
for (const [key, value] of Object.entries(filters)) {
|
|
264
|
+
whereConditions.push(`${key} = $${paramIndex}`);
|
|
265
|
+
params.push(value);
|
|
266
|
+
paramIndex++;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
params.push(limit);
|
|
270
|
+
|
|
271
|
+
const columns = selectColumns.join(', ');
|
|
272
|
+
const sql = `
|
|
273
|
+
SELECT ${columns}, 1 - (${embeddingColumn} <=> $1::vector) as similarity
|
|
274
|
+
FROM ${table}
|
|
275
|
+
WHERE ${whereConditions.join(' AND ')}
|
|
276
|
+
ORDER BY similarity DESC
|
|
277
|
+
LIMIT $${paramIndex}
|
|
278
|
+
`;
|
|
279
|
+
|
|
280
|
+
const result = await this.query<T & { similarity: number }>(sql, params);
|
|
281
|
+
return result.rows;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export default PostgresAdapter;
|