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.
Files changed (153) hide show
  1. package/.letta/settings.local.json +3 -0
  2. package/dist/cli/index.js +6 -4
  3. package/dist/cli/index.js.map +1 -1
  4. package/dist/database/adapters/base-adapter.d.ts +81 -0
  5. package/dist/database/adapters/base-adapter.d.ts.map +1 -0
  6. package/dist/database/adapters/base-adapter.js +105 -0
  7. package/dist/database/adapters/base-adapter.js.map +1 -0
  8. package/dist/database/adapters/index.d.ts +49 -0
  9. package/dist/database/adapters/index.d.ts.map +1 -0
  10. package/dist/database/adapters/index.js +200 -0
  11. package/dist/database/adapters/index.js.map +1 -0
  12. package/dist/database/adapters/postgres-adapter.d.ts +75 -0
  13. package/dist/database/adapters/postgres-adapter.d.ts.map +1 -0
  14. package/dist/database/adapters/postgres-adapter.js +225 -0
  15. package/dist/database/adapters/postgres-adapter.js.map +1 -0
  16. package/dist/database/adapters/sqlite-adapter.d.ts +72 -0
  17. package/dist/database/adapters/sqlite-adapter.d.ts.map +1 -0
  18. package/dist/database/adapters/sqlite-adapter.js +368 -0
  19. package/dist/database/adapters/sqlite-adapter.js.map +1 -0
  20. package/dist/database/cache/cache-keys.d.ts +180 -0
  21. package/dist/database/cache/cache-keys.d.ts.map +1 -0
  22. package/dist/database/cache/cache-keys.js +107 -0
  23. package/dist/database/cache/cache-keys.js.map +1 -0
  24. package/dist/database/cache/index.d.ts +24 -0
  25. package/dist/database/cache/index.d.ts.map +1 -0
  26. package/dist/database/cache/index.js +34 -0
  27. package/dist/database/cache/index.js.map +1 -0
  28. package/dist/database/cache/query-cache.d.ts +67 -0
  29. package/dist/database/cache/query-cache.d.ts.map +1 -0
  30. package/dist/database/cache/query-cache.js +177 -0
  31. package/dist/database/cache/query-cache.js.map +1 -0
  32. package/dist/database/client.d.ts +63 -4
  33. package/dist/database/client.d.ts.map +1 -1
  34. package/dist/database/client.js +147 -59
  35. package/dist/database/client.js.map +1 -1
  36. package/dist/database/db-config.d.ts +104 -0
  37. package/dist/database/db-config.d.ts.map +1 -0
  38. package/dist/database/db-config.js +167 -0
  39. package/dist/database/db-config.js.map +1 -0
  40. package/dist/database/drizzle/index.d.ts +42 -0
  41. package/dist/database/drizzle/index.d.ts.map +1 -0
  42. package/dist/database/drizzle/index.js +48 -0
  43. package/dist/database/drizzle/index.js.map +1 -0
  44. package/dist/database/drizzle/schema/audit.d.ts +533 -0
  45. package/dist/database/drizzle/schema/audit.d.ts.map +1 -0
  46. package/dist/database/drizzle/schema/audit.js +71 -0
  47. package/dist/database/drizzle/schema/audit.js.map +1 -0
  48. package/dist/database/drizzle/schema/checkpoints.d.ts +665 -0
  49. package/dist/database/drizzle/schema/checkpoints.d.ts.map +1 -0
  50. package/dist/database/drizzle/schema/checkpoints.js +110 -0
  51. package/dist/database/drizzle/schema/checkpoints.js.map +1 -0
  52. package/dist/database/drizzle/schema/conversations.d.ts +449 -0
  53. package/dist/database/drizzle/schema/conversations.d.ts.map +1 -0
  54. package/dist/database/drizzle/schema/conversations.js +91 -0
  55. package/dist/database/drizzle/schema/conversations.js.map +1 -0
  56. package/dist/database/drizzle/schema/documents.d.ts +600 -0
  57. package/dist/database/drizzle/schema/documents.d.ts.map +1 -0
  58. package/dist/database/drizzle/schema/documents.js +100 -0
  59. package/dist/database/drizzle/schema/documents.js.map +1 -0
  60. package/dist/database/drizzle/schema/index.d.ts +3084 -0
  61. package/dist/database/drizzle/schema/index.d.ts.map +1 -0
  62. package/dist/database/drizzle/schema/index.js +46 -0
  63. package/dist/database/drizzle/schema/index.js.map +1 -0
  64. package/dist/database/drizzle/schema/memories.d.ts +435 -0
  65. package/dist/database/drizzle/schema/memories.d.ts.map +1 -0
  66. package/dist/database/drizzle/schema/memories.js +73 -0
  67. package/dist/database/drizzle/schema/memories.js.map +1 -0
  68. package/dist/database/drizzle/schema/tasks.d.ts +565 -0
  69. package/dist/database/drizzle/schema/tasks.d.ts.map +1 -0
  70. package/dist/database/drizzle/schema/tasks.js +84 -0
  71. package/dist/database/drizzle/schema/tasks.js.map +1 -0
  72. package/dist/database/health/circuit-breaker.d.ts +81 -0
  73. package/dist/database/health/circuit-breaker.d.ts.map +1 -0
  74. package/dist/database/health/circuit-breaker.js +184 -0
  75. package/dist/database/health/circuit-breaker.js.map +1 -0
  76. package/dist/database/health/health-monitor.d.ts +69 -0
  77. package/dist/database/health/health-monitor.d.ts.map +1 -0
  78. package/dist/database/health/health-monitor.js +174 -0
  79. package/dist/database/health/health-monitor.js.map +1 -0
  80. package/dist/database/health/index.d.ts +27 -0
  81. package/dist/database/health/index.d.ts.map +1 -0
  82. package/dist/database/health/index.js +23 -0
  83. package/dist/database/health/index.js.map +1 -0
  84. package/dist/database/index.d.ts +16 -0
  85. package/dist/database/index.d.ts.map +1 -0
  86. package/dist/database/index.js +41 -0
  87. package/dist/database/index.js.map +1 -0
  88. package/dist/database/migrations/index.d.ts +34 -0
  89. package/dist/database/migrations/index.d.ts.map +1 -0
  90. package/dist/database/migrations/index.js +45 -0
  91. package/dist/database/migrations/index.js.map +1 -0
  92. package/dist/database/migrations/migrator.d.ts +77 -0
  93. package/dist/database/migrations/migrator.d.ts.map +1 -0
  94. package/dist/database/migrations/migrator.js +258 -0
  95. package/dist/database/migrations/migrator.js.map +1 -0
  96. package/dist/database/migrations/versions/001_initial.d.ts +9 -0
  97. package/dist/database/migrations/versions/001_initial.d.ts.map +1 -0
  98. package/dist/database/migrations/versions/001_initial.js +183 -0
  99. package/dist/database/migrations/versions/001_initial.js.map +1 -0
  100. package/dist/database/types.d.ts +255 -0
  101. package/dist/database/types.d.ts.map +1 -0
  102. package/dist/database/types.js +8 -0
  103. package/dist/database/types.js.map +1 -0
  104. package/dist/database/vector/embedding-cache.d.ts +92 -0
  105. package/dist/database/vector/embedding-cache.d.ts.map +1 -0
  106. package/dist/database/vector/embedding-cache.js +185 -0
  107. package/dist/database/vector/embedding-cache.js.map +1 -0
  108. package/dist/database/vector/hnsw-index.d.ts +111 -0
  109. package/dist/database/vector/hnsw-index.d.ts.map +1 -0
  110. package/dist/database/vector/hnsw-index.js +337 -0
  111. package/dist/database/vector/hnsw-index.js.map +1 -0
  112. package/dist/database/vector/index.d.ts +75 -0
  113. package/dist/database/vector/index.d.ts.map +1 -0
  114. package/dist/database/vector/index.js +213 -0
  115. package/dist/database/vector/index.js.map +1 -0
  116. package/dist/database/vector/similarity.d.ts +67 -0
  117. package/dist/database/vector/similarity.d.ts.map +1 -0
  118. package/dist/database/vector/similarity.js +176 -0
  119. package/dist/database/vector/similarity.js.map +1 -0
  120. package/dist/index.d.ts +1 -1
  121. package/dist/index.js +1 -1
  122. package/package.json +6 -3
  123. package/src/cli/index.ts +5 -5
  124. package/src/database/adapters/base-adapter.ts +171 -0
  125. package/src/database/adapters/index.ts +224 -0
  126. package/src/database/adapters/postgres-adapter.ts +285 -0
  127. package/src/database/adapters/sqlite-adapter.ts +420 -0
  128. package/src/database/cache/cache-keys.ts +150 -0
  129. package/src/database/cache/index.ts +44 -0
  130. package/src/database/cache/query-cache.ts +213 -0
  131. package/src/database/client.ts +166 -64
  132. package/src/database/db-config.ts +194 -0
  133. package/src/database/drizzle/index.ts +66 -0
  134. package/src/database/drizzle/schema/audit.ts +127 -0
  135. package/src/database/drizzle/schema/checkpoints.ts +164 -0
  136. package/src/database/drizzle/schema/conversations.ts +138 -0
  137. package/src/database/drizzle/schema/documents.ts +157 -0
  138. package/src/database/drizzle/schema/index.ts +139 -0
  139. package/src/database/drizzle/schema/memories.ts +127 -0
  140. package/src/database/drizzle/schema/tasks.ts +129 -0
  141. package/src/database/health/circuit-breaker.ts +214 -0
  142. package/src/database/health/health-monitor.ts +224 -0
  143. package/src/database/health/index.ts +41 -0
  144. package/src/database/index.ts +157 -0
  145. package/src/database/migrations/index.ts +52 -0
  146. package/src/database/migrations/migrator.ts +325 -0
  147. package/src/database/migrations/versions/001_initial.ts +198 -0
  148. package/src/database/types.ts +324 -0
  149. package/src/database/vector/embedding-cache.ts +234 -0
  150. package/src/database/vector/hnsw-index.ts +452 -0
  151. package/src/database/vector/index.ts +292 -0
  152. package/src/database/vector/similarity.ts +198 -0
  153. 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;
@@ -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 { getDatabaseUrl } from '../config/index.js';
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
- let pool: pg.Pool | null = null;
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
- pool.on('error', (err) => {
21
- logger.error('Unexpected pool error', { error: err.message });
22
- });
22
+ /**
23
+ * Get the current database adapter
24
+ */
25
+ async function getAdapterInstance(): Promise<DatabaseAdapter> {
26
+ return getAdapter();
27
+ }
23
28
 
24
- pool.on('connect', () => {
25
- logger.debug('New client connected to pool');
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
- export async function query<T extends pg.QueryResultRow = pg.QueryResultRow>(
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<pg.QueryResult<T>> {
36
- const pool = getPool();
37
- const start = Date.now();
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
- export async function getClient(): Promise<pg.PoolClient> {
52
- const pool = getPool();
53
- return pool.connect();
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: pg.PoolClient) => Promise<T>
80
+ callback: (client: TransactionClient) => Promise<T>
58
81
  ): Promise<T> {
59
- const client = await getClient();
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 result = await query('SELECT 1 as health');
77
- return result.rows.length > 0;
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 (pool) {
85
- await pool.end();
86
- pool = null;
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
- // Initialize the database connection
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
- const healthy = await healthCheck();
94
- if (healthy) {
95
- logger.info('Database connection established');
96
- } else {
97
- throw new MemoryError('Failed to connect to database');
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
+ };