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,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query Cache
|
|
3
|
+
*
|
|
4
|
+
* LRU cache for database query results.
|
|
5
|
+
* Improves performance by avoiding redundant queries.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { CacheStats, CacheEntry, QueryCacheOptions } from '../types.js';
|
|
9
|
+
import { createLogger } from '../../utils/logger.js';
|
|
10
|
+
|
|
11
|
+
const logger = createLogger('QueryCache');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Default cache options
|
|
15
|
+
*/
|
|
16
|
+
const DEFAULT_OPTIONS: QueryCacheOptions = {
|
|
17
|
+
maxSize: 1000,
|
|
18
|
+
defaultTtlMs: 300000, // 5 minutes
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* LRU query cache
|
|
23
|
+
*/
|
|
24
|
+
export class QueryCache {
|
|
25
|
+
private cache: Map<string, CacheEntry<unknown>> = new Map();
|
|
26
|
+
private options: QueryCacheOptions;
|
|
27
|
+
private hitCount: number = 0;
|
|
28
|
+
private missCount: number = 0;
|
|
29
|
+
|
|
30
|
+
constructor(options: Partial<QueryCacheOptions> = {}) {
|
|
31
|
+
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get value from cache
|
|
36
|
+
*/
|
|
37
|
+
get<T>(key: string): T | undefined {
|
|
38
|
+
const entry = this.cache.get(key);
|
|
39
|
+
|
|
40
|
+
if (!entry) {
|
|
41
|
+
this.missCount++;
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Check expiration
|
|
46
|
+
if (Date.now() > entry.expiresAt) {
|
|
47
|
+
this.cache.delete(key);
|
|
48
|
+
this.missCount++;
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Update access metadata and move to end for LRU
|
|
53
|
+
entry.hits++;
|
|
54
|
+
this.cache.delete(key);
|
|
55
|
+
this.cache.set(key, entry);
|
|
56
|
+
|
|
57
|
+
this.hitCount++;
|
|
58
|
+
return entry.value as T;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Set value in cache
|
|
63
|
+
*/
|
|
64
|
+
set<T>(key: string, value: T, ttlMs?: number): void {
|
|
65
|
+
// Evict oldest if at capacity
|
|
66
|
+
if (this.cache.size >= this.options.maxSize && !this.cache.has(key)) {
|
|
67
|
+
this.evictLRU();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const entry: CacheEntry<T> = {
|
|
71
|
+
value,
|
|
72
|
+
expiresAt: Date.now() + (ttlMs ?? this.options.defaultTtlMs),
|
|
73
|
+
hits: 0,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Delete first to ensure it's at the end
|
|
77
|
+
this.cache.delete(key);
|
|
78
|
+
this.cache.set(key, entry);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Check if key exists (without updating access)
|
|
83
|
+
*/
|
|
84
|
+
has(key: string): boolean {
|
|
85
|
+
const entry = this.cache.get(key);
|
|
86
|
+
if (!entry) return false;
|
|
87
|
+
if (Date.now() > entry.expiresAt) {
|
|
88
|
+
this.cache.delete(key);
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Delete key from cache
|
|
96
|
+
*/
|
|
97
|
+
delete(key: string): boolean {
|
|
98
|
+
return this.cache.delete(key);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Invalidate entries matching a pattern
|
|
103
|
+
*/
|
|
104
|
+
invalidate(pattern: string | RegExp): number {
|
|
105
|
+
let count = 0;
|
|
106
|
+
for (const key of this.cache.keys()) {
|
|
107
|
+
const matches =
|
|
108
|
+
typeof pattern === 'string'
|
|
109
|
+
? key.includes(pattern)
|
|
110
|
+
: pattern.test(key);
|
|
111
|
+
|
|
112
|
+
if (matches) {
|
|
113
|
+
this.cache.delete(key);
|
|
114
|
+
count++;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (count > 0) {
|
|
119
|
+
logger.debug('Cache invalidated', { pattern: String(pattern), count });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return count;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Clear all entries
|
|
127
|
+
*/
|
|
128
|
+
clear(): void {
|
|
129
|
+
this.cache.clear();
|
|
130
|
+
logger.debug('Cache cleared');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Evict least recently used entry
|
|
135
|
+
*/
|
|
136
|
+
private evictLRU(): void {
|
|
137
|
+
const firstKey = this.cache.keys().next().value;
|
|
138
|
+
if (firstKey) {
|
|
139
|
+
this.cache.delete(firstKey);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Remove expired entries
|
|
145
|
+
*/
|
|
146
|
+
prune(): number {
|
|
147
|
+
const now = Date.now();
|
|
148
|
+
let count = 0;
|
|
149
|
+
|
|
150
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
151
|
+
if (now > entry.expiresAt) {
|
|
152
|
+
this.cache.delete(key);
|
|
153
|
+
count++;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (count > 0) {
|
|
158
|
+
logger.debug('Cache pruned', { removed: count });
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return count;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Get cache statistics
|
|
166
|
+
*/
|
|
167
|
+
getStats(): CacheStats {
|
|
168
|
+
const totalRequests = this.hitCount + this.missCount;
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
size: this.cache.size,
|
|
172
|
+
maxSize: this.options.maxSize,
|
|
173
|
+
hitCount: this.hitCount,
|
|
174
|
+
missCount: this.missCount,
|
|
175
|
+
hitRate: totalRequests > 0 ? this.hitCount / totalRequests : 0,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Get cache size
|
|
181
|
+
*/
|
|
182
|
+
get size(): number {
|
|
183
|
+
return this.cache.size;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Reset statistics
|
|
188
|
+
*/
|
|
189
|
+
resetStats(): void {
|
|
190
|
+
this.hitCount = 0;
|
|
191
|
+
this.missCount = 0;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Get or set pattern - fetch from cache or execute function and cache result
|
|
196
|
+
*/
|
|
197
|
+
async getOrSet<T>(
|
|
198
|
+
key: string,
|
|
199
|
+
fn: () => Promise<T>,
|
|
200
|
+
ttlMs?: number
|
|
201
|
+
): Promise<T> {
|
|
202
|
+
const cached = this.get<T>(key);
|
|
203
|
+
if (cached !== undefined) {
|
|
204
|
+
return cached;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const result = await fn();
|
|
208
|
+
this.set(key, result, ttlMs);
|
|
209
|
+
return result;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export default QueryCache;
|
package/src/database/client.ts
CHANGED
|
@@ -1,103 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database Client
|
|
3
|
+
*
|
|
4
|
+
* Unified database client with backward-compatible API.
|
|
5
|
+
* Automatically detects and uses PostgreSQL or SQLite.
|
|
6
|
+
*/
|
|
7
|
+
|
|
1
8
|
import pg from 'pg';
|
|
2
|
-
import {
|
|
9
|
+
import { getAdapter, closeAdapter, isAdapterInitialized, getAdapterType } from './adapters/index.js';
|
|
10
|
+
import { createMigrator } from './migrations/index.js';
|
|
11
|
+
import { createHealthSystem } from './health/index.js';
|
|
12
|
+
import { getDatabaseConfig } from './db-config.js';
|
|
13
|
+
import type { DatabaseAdapter, QueryResult, TransactionClient } from './types.js';
|
|
3
14
|
import { createLogger } from '../utils/logger.js';
|
|
4
15
|
import { MemoryError } from '../utils/errors.js';
|
|
5
16
|
|
|
6
|
-
const { Pool } = pg;
|
|
7
17
|
const logger = createLogger('Database');
|
|
8
18
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export function getPool(): pg.Pool {
|
|
12
|
-
if (!pool) {
|
|
13
|
-
pool = new Pool({
|
|
14
|
-
connectionString: getDatabaseUrl(),
|
|
15
|
-
max: 20,
|
|
16
|
-
idleTimeoutMillis: 30000,
|
|
17
|
-
connectionTimeoutMillis: 5000,
|
|
18
|
-
});
|
|
19
|
+
// Health system instance
|
|
20
|
+
let healthSystem: ReturnType<typeof createHealthSystem> | null = null;
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Get the current database adapter
|
|
24
|
+
*/
|
|
25
|
+
async function getAdapterInstance(): Promise<DatabaseAdapter> {
|
|
26
|
+
return getAdapter();
|
|
27
|
+
}
|
|
23
28
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Get PostgreSQL pool (for backward compatibility)
|
|
31
|
+
* Returns null if using SQLite
|
|
32
|
+
*/
|
|
33
|
+
export function getPool(): pg.Pool | null {
|
|
34
|
+
if (getAdapterType() === 'postgresql') {
|
|
35
|
+
// Access the raw pool from PostgreSQL adapter
|
|
36
|
+
try {
|
|
37
|
+
const adapter = require('./adapters/postgres-adapter.js').PostgresAdapter;
|
|
38
|
+
// This is a simplified compatibility layer
|
|
39
|
+
return null; // Actual pool access requires async initialization
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
27
43
|
}
|
|
28
|
-
|
|
29
|
-
return pool;
|
|
44
|
+
return null;
|
|
30
45
|
}
|
|
31
46
|
|
|
32
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Execute a database query
|
|
49
|
+
* Compatible with both PostgreSQL and SQLite
|
|
50
|
+
*/
|
|
51
|
+
export async function query<T extends Record<string, any> = Record<string, any>>(
|
|
33
52
|
text: string,
|
|
34
53
|
params?: unknown[]
|
|
35
|
-
): Promise<
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
const result = await pool.query<T>(text, params);
|
|
41
|
-
const duration = Date.now() - start;
|
|
42
|
-
logger.debug('Query executed', { duration, rows: result.rowCount });
|
|
43
|
-
return result;
|
|
44
|
-
} catch (error) {
|
|
45
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
46
|
-
logger.error('Query failed', { error: errorMessage, query: text.slice(0, 100) });
|
|
47
|
-
throw new MemoryError(`Database query failed: ${errorMessage}`);
|
|
48
|
-
}
|
|
54
|
+
): Promise<QueryResult<T>> {
|
|
55
|
+
const adapter = await getAdapterInstance();
|
|
56
|
+
return adapter.query<T>(text, params);
|
|
49
57
|
}
|
|
50
58
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Get a transaction client
|
|
61
|
+
* For PostgreSQL: returns PoolClient
|
|
62
|
+
* For SQLite: returns a wrapper
|
|
63
|
+
*/
|
|
64
|
+
export async function getClient(): Promise<TransactionClient> {
|
|
65
|
+
const adapter = await getAdapterInstance();
|
|
66
|
+
return {
|
|
67
|
+
query: async <T extends Record<string, any> = Record<string, any>>(
|
|
68
|
+
sql: string,
|
|
69
|
+
params?: unknown[]
|
|
70
|
+
): Promise<QueryResult<T>> => {
|
|
71
|
+
return adapter.query<T>(sql, params);
|
|
72
|
+
},
|
|
73
|
+
};
|
|
54
74
|
}
|
|
55
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Execute a transaction
|
|
78
|
+
*/
|
|
56
79
|
export async function transaction<T>(
|
|
57
|
-
callback: (client:
|
|
80
|
+
callback: (client: TransactionClient) => Promise<T>
|
|
58
81
|
): Promise<T> {
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
try {
|
|
62
|
-
await client.query('BEGIN');
|
|
63
|
-
const result = await callback(client);
|
|
64
|
-
await client.query('COMMIT');
|
|
65
|
-
return result;
|
|
66
|
-
} catch (error) {
|
|
67
|
-
await client.query('ROLLBACK');
|
|
68
|
-
throw error;
|
|
69
|
-
} finally {
|
|
70
|
-
client.release();
|
|
71
|
-
}
|
|
82
|
+
const adapter = await getAdapterInstance();
|
|
83
|
+
return adapter.transaction(callback);
|
|
72
84
|
}
|
|
73
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Check database health
|
|
88
|
+
*/
|
|
74
89
|
export async function healthCheck(): Promise<boolean> {
|
|
75
90
|
try {
|
|
76
|
-
const
|
|
77
|
-
return
|
|
91
|
+
const adapter = await getAdapterInstance();
|
|
92
|
+
return adapter.healthCheck();
|
|
78
93
|
} catch {
|
|
79
94
|
return false;
|
|
80
95
|
}
|
|
81
96
|
}
|
|
82
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Close the database connection
|
|
100
|
+
*/
|
|
83
101
|
export async function closePool(): Promise<void> {
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
logger.info('Database pool closed');
|
|
102
|
+
if (healthSystem) {
|
|
103
|
+
healthSystem.healthMonitor.stop();
|
|
104
|
+
healthSystem = null;
|
|
88
105
|
}
|
|
106
|
+
await closeAdapter();
|
|
107
|
+
logger.info('Database connection closed');
|
|
89
108
|
}
|
|
90
109
|
|
|
91
|
-
|
|
110
|
+
/**
|
|
111
|
+
* Initialize the database
|
|
112
|
+
* Performs auto-detection, runs migrations, and starts health monitoring
|
|
113
|
+
*/
|
|
92
114
|
export async function initDatabase(): Promise<void> {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
115
|
+
try {
|
|
116
|
+
const adapter = await getAdapterInstance();
|
|
117
|
+
const config = getDatabaseConfig();
|
|
118
|
+
|
|
119
|
+
logger.info('Database initialized', {
|
|
120
|
+
type: adapter.type,
|
|
121
|
+
path: adapter.type === 'sqlite' ? config.sqlitePath : undefined,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Run migrations
|
|
125
|
+
const migrator = createMigrator(adapter);
|
|
126
|
+
const pending = await migrator.getPendingMigrations();
|
|
127
|
+
|
|
128
|
+
if (pending.length > 0) {
|
|
129
|
+
logger.info('Running pending migrations', { count: pending.length });
|
|
130
|
+
const results = await migrator.migrate();
|
|
131
|
+
const failed = results.filter((r) => !r.success);
|
|
132
|
+
if (failed.length > 0) {
|
|
133
|
+
logger.error('Some migrations failed', { failed });
|
|
134
|
+
throw new MemoryError(`Migration failed: ${failed[0]?.error}`);
|
|
135
|
+
}
|
|
136
|
+
logger.info('Migrations completed', { count: results.length });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Start health monitoring
|
|
140
|
+
healthSystem = createHealthSystem(adapter, {
|
|
141
|
+
circuitBreaker: {
|
|
142
|
+
failureThreshold: config.circuitBreakerThreshold,
|
|
143
|
+
resetTimeoutMs: config.circuitBreakerTimeoutMs,
|
|
144
|
+
},
|
|
145
|
+
healthMonitor: {
|
|
146
|
+
checkIntervalMs: config.healthCheckIntervalMs,
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
healthSystem.healthMonitor.start();
|
|
150
|
+
|
|
151
|
+
// Verify connection
|
|
152
|
+
const healthy = await adapter.healthCheck();
|
|
153
|
+
if (!healthy) {
|
|
154
|
+
throw new MemoryError('Database health check failed after initialization');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
logger.info('Database ready', { type: adapter.type });
|
|
158
|
+
} catch (error) {
|
|
159
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
160
|
+
logger.error('Database initialization failed', { error: message });
|
|
161
|
+
throw new MemoryError(`Failed to initialize database: ${message}`);
|
|
98
162
|
}
|
|
99
163
|
}
|
|
100
164
|
|
|
165
|
+
/**
|
|
166
|
+
* Get database type
|
|
167
|
+
*/
|
|
168
|
+
export function getDatabaseType(): 'postgresql' | 'sqlite' | null {
|
|
169
|
+
return getAdapterType();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Check if database is initialized
|
|
174
|
+
*/
|
|
175
|
+
export function isDatabaseInitialized(): boolean {
|
|
176
|
+
return isAdapterInitialized();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Get health status
|
|
181
|
+
*/
|
|
182
|
+
export function getHealthStatus() {
|
|
183
|
+
return healthSystem?.healthMonitor.getStatus() ?? null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Get health summary
|
|
188
|
+
*/
|
|
189
|
+
export function getHealthSummary() {
|
|
190
|
+
return healthSystem?.healthMonitor.getSummary() ?? {
|
|
191
|
+
healthy: false,
|
|
192
|
+
status: 'unknown',
|
|
193
|
+
database: 'unknown',
|
|
194
|
+
latencyMs: -1,
|
|
195
|
+
circuitBreaker: 'unknown',
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
101
199
|
export default {
|
|
102
200
|
getPool,
|
|
103
201
|
query,
|
|
@@ -106,4 +204,8 @@ export default {
|
|
|
106
204
|
healthCheck,
|
|
107
205
|
closePool,
|
|
108
206
|
initDatabase,
|
|
207
|
+
getDatabaseType,
|
|
208
|
+
isDatabaseInitialized,
|
|
209
|
+
getHealthStatus,
|
|
210
|
+
getHealthSummary,
|
|
109
211
|
};
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database Configuration
|
|
3
|
+
*
|
|
4
|
+
* Configuration management for the database layer.
|
|
5
|
+
* Supports both PostgreSQL and SQLite with validation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import { homedir } from 'os';
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
import type { DatabaseConfig, DatabaseDetectionMode } from './types.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Database configuration schema with Zod validation
|
|
15
|
+
*/
|
|
16
|
+
export const databaseConfigSchema = z.object({
|
|
17
|
+
// Detection mode
|
|
18
|
+
DATABASE_TYPE: z
|
|
19
|
+
.enum(['auto', 'postgresql', 'sqlite'])
|
|
20
|
+
.default('auto')
|
|
21
|
+
.describe('Database type selection mode'),
|
|
22
|
+
|
|
23
|
+
// PostgreSQL settings
|
|
24
|
+
DATABASE_URL: z
|
|
25
|
+
.string()
|
|
26
|
+
.url()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe('Full PostgreSQL connection URL'),
|
|
29
|
+
DB_HOST: z.string().default('localhost').describe('PostgreSQL host'),
|
|
30
|
+
DB_PORT: z.coerce.number().default(5432).describe('PostgreSQL port'),
|
|
31
|
+
DB_USER: z.string().default('familyai').describe('PostgreSQL user'),
|
|
32
|
+
DB_PASSWORD: z.string().default('familyai123').describe('PostgreSQL password'),
|
|
33
|
+
DB_NAME: z.string().default('familyai').describe('PostgreSQL database name'),
|
|
34
|
+
DB_POOL_MAX: z.coerce.number().default(20).describe('Maximum pool connections'),
|
|
35
|
+
DB_POOL_IDLE_TIMEOUT_MS: z.coerce
|
|
36
|
+
.number()
|
|
37
|
+
.default(30000)
|
|
38
|
+
.describe('Pool idle timeout'),
|
|
39
|
+
DB_POOL_CONNECTION_TIMEOUT_MS: z.coerce
|
|
40
|
+
.number()
|
|
41
|
+
.default(5000)
|
|
42
|
+
.describe('Connection timeout'),
|
|
43
|
+
|
|
44
|
+
// SQLite settings
|
|
45
|
+
SQLITE_PATH: z
|
|
46
|
+
.string()
|
|
47
|
+
.optional()
|
|
48
|
+
.describe('SQLite database file path'),
|
|
49
|
+
|
|
50
|
+
// Health monitoring
|
|
51
|
+
DB_HEALTH_CHECK_INTERVAL_MS: z.coerce
|
|
52
|
+
.number()
|
|
53
|
+
.default(30000)
|
|
54
|
+
.describe('Health check interval'),
|
|
55
|
+
DB_CIRCUIT_BREAKER_THRESHOLD: z.coerce
|
|
56
|
+
.number()
|
|
57
|
+
.default(5)
|
|
58
|
+
.describe('Circuit breaker failure threshold'),
|
|
59
|
+
DB_CIRCUIT_BREAKER_TIMEOUT_MS: z.coerce
|
|
60
|
+
.number()
|
|
61
|
+
.default(60000)
|
|
62
|
+
.describe('Circuit breaker reset timeout'),
|
|
63
|
+
|
|
64
|
+
// Cache settings
|
|
65
|
+
DB_CACHE_MAX_SIZE: z.coerce
|
|
66
|
+
.number()
|
|
67
|
+
.default(1000)
|
|
68
|
+
.describe('Maximum cache entries'),
|
|
69
|
+
DB_CACHE_TTL_MS: z.coerce
|
|
70
|
+
.number()
|
|
71
|
+
.default(300000)
|
|
72
|
+
.describe('Cache TTL in milliseconds'),
|
|
73
|
+
|
|
74
|
+
// Vector settings
|
|
75
|
+
VECTOR_DIMENSION: z.coerce
|
|
76
|
+
.number()
|
|
77
|
+
.default(1536)
|
|
78
|
+
.describe('Embedding vector dimension'),
|
|
79
|
+
VECTOR_MIN_SIMILARITY: z.coerce
|
|
80
|
+
.number()
|
|
81
|
+
.default(0.7)
|
|
82
|
+
.describe('Minimum similarity threshold'),
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
export type RawDatabaseConfig = z.infer<typeof databaseConfigSchema>;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Default SQLite database path
|
|
89
|
+
*/
|
|
90
|
+
export function getDefaultSqlitePath(): string {
|
|
91
|
+
return join(homedir(), '.family-ai-agent', 'data', 'family-ai.db');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Build PostgreSQL connection URL from config
|
|
96
|
+
*/
|
|
97
|
+
export function buildPostgresUrl(config: RawDatabaseConfig): string {
|
|
98
|
+
if (config.DATABASE_URL) {
|
|
99
|
+
return config.DATABASE_URL;
|
|
100
|
+
}
|
|
101
|
+
return `postgresql://${config.DB_USER}:${config.DB_PASSWORD}@${config.DB_HOST}:${config.DB_PORT}/${config.DB_NAME}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Load and validate database configuration from environment
|
|
106
|
+
*/
|
|
107
|
+
export function loadDatabaseConfig(
|
|
108
|
+
env: Record<string, string | undefined> = process.env
|
|
109
|
+
): DatabaseConfig {
|
|
110
|
+
const parsed = databaseConfigSchema.safeParse(env);
|
|
111
|
+
|
|
112
|
+
if (!parsed.success) {
|
|
113
|
+
// Return defaults on validation failure
|
|
114
|
+
const defaults = databaseConfigSchema.parse({});
|
|
115
|
+
return convertToConfig(defaults);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return convertToConfig(parsed.data);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Convert raw config to DatabaseConfig interface
|
|
123
|
+
*/
|
|
124
|
+
function convertToConfig(raw: RawDatabaseConfig): DatabaseConfig {
|
|
125
|
+
return {
|
|
126
|
+
type: raw.DATABASE_TYPE as DatabaseDetectionMode,
|
|
127
|
+
|
|
128
|
+
// PostgreSQL
|
|
129
|
+
postgresUrl: raw.DATABASE_URL,
|
|
130
|
+
postgresHost: raw.DB_HOST,
|
|
131
|
+
postgresPort: raw.DB_PORT,
|
|
132
|
+
postgresUser: raw.DB_USER,
|
|
133
|
+
postgresPassword: raw.DB_PASSWORD,
|
|
134
|
+
postgresDatabase: raw.DB_NAME,
|
|
135
|
+
postgresPoolMax: raw.DB_POOL_MAX,
|
|
136
|
+
postgresIdleTimeoutMs: raw.DB_POOL_IDLE_TIMEOUT_MS,
|
|
137
|
+
postgresConnectionTimeoutMs: raw.DB_POOL_CONNECTION_TIMEOUT_MS,
|
|
138
|
+
|
|
139
|
+
// SQLite
|
|
140
|
+
sqlitePath: raw.SQLITE_PATH ?? getDefaultSqlitePath(),
|
|
141
|
+
|
|
142
|
+
// Health monitoring
|
|
143
|
+
healthCheckIntervalMs: raw.DB_HEALTH_CHECK_INTERVAL_MS,
|
|
144
|
+
circuitBreakerThreshold: raw.DB_CIRCUIT_BREAKER_THRESHOLD,
|
|
145
|
+
circuitBreakerTimeoutMs: raw.DB_CIRCUIT_BREAKER_TIMEOUT_MS,
|
|
146
|
+
|
|
147
|
+
// Cache
|
|
148
|
+
cacheMaxSize: raw.DB_CACHE_MAX_SIZE,
|
|
149
|
+
cacheTtlMs: raw.DB_CACHE_TTL_MS,
|
|
150
|
+
|
|
151
|
+
// Vector
|
|
152
|
+
vectorDimension: raw.VECTOR_DIMENSION,
|
|
153
|
+
vectorMinSimilarity: raw.VECTOR_MIN_SIMILARITY,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Global database configuration instance
|
|
159
|
+
*/
|
|
160
|
+
let dbConfig: DatabaseConfig | null = null;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Get database configuration
|
|
164
|
+
*/
|
|
165
|
+
export function getDatabaseConfig(): DatabaseConfig {
|
|
166
|
+
if (!dbConfig) {
|
|
167
|
+
dbConfig = loadDatabaseConfig();
|
|
168
|
+
}
|
|
169
|
+
return dbConfig;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Reset configuration (for testing)
|
|
174
|
+
*/
|
|
175
|
+
export function resetDatabaseConfig(): void {
|
|
176
|
+
dbConfig = null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Set custom configuration (for testing)
|
|
181
|
+
*/
|
|
182
|
+
export function setDatabaseConfig(config: Partial<DatabaseConfig>): void {
|
|
183
|
+
const defaults = loadDatabaseConfig();
|
|
184
|
+
dbConfig = { ...defaults, ...config };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export default {
|
|
188
|
+
loadDatabaseConfig,
|
|
189
|
+
getDatabaseConfig,
|
|
190
|
+
resetDatabaseConfig,
|
|
191
|
+
setDatabaseConfig,
|
|
192
|
+
getDefaultSqlitePath,
|
|
193
|
+
buildPostgresUrl,
|
|
194
|
+
};
|