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,420 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite Database Adapter
|
|
3
|
+
*
|
|
4
|
+
* Adapter for SQLite using better-sqlite3 for embedded database support.
|
|
5
|
+
* Provides zero-config database that works out of the box.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import Database from 'better-sqlite3';
|
|
9
|
+
import { drizzle } from 'drizzle-orm/better-sqlite3';
|
|
10
|
+
import type { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3';
|
|
11
|
+
import { existsSync, mkdirSync } from 'fs';
|
|
12
|
+
import { dirname } from 'path';
|
|
13
|
+
|
|
14
|
+
import { BaseAdapter } from './base-adapter.js';
|
|
15
|
+
import type {
|
|
16
|
+
QueryResult,
|
|
17
|
+
TransactionClient,
|
|
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 logger = createLogger('SQLiteAdapter');
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* SQLite database adapter with Drizzle ORM
|
|
29
|
+
*/
|
|
30
|
+
export class SQLiteAdapter extends BaseAdapter {
|
|
31
|
+
readonly type = 'sqlite' as const;
|
|
32
|
+
|
|
33
|
+
private db: Database.Database | null = null;
|
|
34
|
+
private drizzleInstance: BetterSQLite3Database<typeof schema> | null = null;
|
|
35
|
+
private dbPath: string;
|
|
36
|
+
private config: DatabaseConfig;
|
|
37
|
+
|
|
38
|
+
constructor(dbPath: string, config: DatabaseConfig) {
|
|
39
|
+
super();
|
|
40
|
+
this.dbPath = dbPath;
|
|
41
|
+
this.config = config;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get Drizzle ORM instance
|
|
46
|
+
*/
|
|
47
|
+
get drizzle(): BetterSQLite3Database<typeof schema> {
|
|
48
|
+
this.ensureInitialized();
|
|
49
|
+
return this.drizzleInstance!;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get raw database (for advanced operations)
|
|
54
|
+
*/
|
|
55
|
+
get rawDb(): Database.Database {
|
|
56
|
+
this.ensureInitialized();
|
|
57
|
+
return this.db!;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Initialize SQLite database
|
|
62
|
+
*/
|
|
63
|
+
async initialize(): Promise<void> {
|
|
64
|
+
if (this._isInitialized) return;
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
// Ensure directory exists
|
|
68
|
+
const dir = dirname(this.dbPath);
|
|
69
|
+
if (!existsSync(dir)) {
|
|
70
|
+
mkdirSync(dir, { recursive: true });
|
|
71
|
+
logger.debug('Created database directory', { path: dir });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Open database
|
|
75
|
+
this.db = new Database(this.dbPath);
|
|
76
|
+
|
|
77
|
+
// Configure for better performance
|
|
78
|
+
this.db.pragma('journal_mode = WAL');
|
|
79
|
+
this.db.pragma('synchronous = NORMAL');
|
|
80
|
+
this.db.pragma('cache_size = -64000'); // 64MB cache
|
|
81
|
+
this.db.pragma('foreign_keys = ON');
|
|
82
|
+
this.db.pragma('temp_store = MEMORY');
|
|
83
|
+
|
|
84
|
+
// Create Drizzle instance
|
|
85
|
+
this.drizzleInstance = drizzle(this.db, {
|
|
86
|
+
schema,
|
|
87
|
+
logger: process.env.NODE_ENV === 'development',
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Initialize schema
|
|
91
|
+
await this.initializeSchema();
|
|
92
|
+
|
|
93
|
+
this._isInitialized = true;
|
|
94
|
+
logger.info('SQLite adapter initialized', { path: this.dbPath });
|
|
95
|
+
} catch (error) {
|
|
96
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
97
|
+
logger.error('Failed to initialize SQLite', { error: message });
|
|
98
|
+
throw new MemoryError(`SQLite initialization failed: ${message}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Initialize database schema
|
|
104
|
+
*/
|
|
105
|
+
private async initializeSchema(): Promise<void> {
|
|
106
|
+
const statements = [
|
|
107
|
+
// Conversations
|
|
108
|
+
`CREATE TABLE IF NOT EXISTS conversations (
|
|
109
|
+
id TEXT PRIMARY KEY,
|
|
110
|
+
thread_id TEXT NOT NULL,
|
|
111
|
+
user_id TEXT,
|
|
112
|
+
created_at INTEGER NOT NULL,
|
|
113
|
+
updated_at INTEGER NOT NULL
|
|
114
|
+
)`,
|
|
115
|
+
`CREATE INDEX IF NOT EXISTS idx_conversations_thread ON conversations(thread_id)`,
|
|
116
|
+
`CREATE INDEX IF NOT EXISTS idx_conversations_user ON conversations(user_id)`,
|
|
117
|
+
|
|
118
|
+
// Messages
|
|
119
|
+
`CREATE TABLE IF NOT EXISTS messages (
|
|
120
|
+
id TEXT PRIMARY KEY,
|
|
121
|
+
conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
|
|
122
|
+
role TEXT NOT NULL,
|
|
123
|
+
content TEXT NOT NULL,
|
|
124
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
125
|
+
created_at INTEGER NOT NULL
|
|
126
|
+
)`,
|
|
127
|
+
`CREATE INDEX IF NOT EXISTS idx_messages_conversation ON messages(conversation_id)`,
|
|
128
|
+
|
|
129
|
+
// Long-term memories
|
|
130
|
+
`CREATE TABLE IF NOT EXISTS long_term_memories (
|
|
131
|
+
id TEXT PRIMARY KEY,
|
|
132
|
+
user_id TEXT,
|
|
133
|
+
memory_type TEXT NOT NULL,
|
|
134
|
+
content TEXT NOT NULL,
|
|
135
|
+
embedding TEXT NOT NULL,
|
|
136
|
+
importance REAL NOT NULL DEFAULT 0.5,
|
|
137
|
+
access_count INTEGER NOT NULL DEFAULT 0,
|
|
138
|
+
last_accessed INTEGER,
|
|
139
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
140
|
+
created_at INTEGER NOT NULL,
|
|
141
|
+
updated_at INTEGER NOT NULL
|
|
142
|
+
)`,
|
|
143
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_user ON long_term_memories(user_id)`,
|
|
144
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_type ON long_term_memories(memory_type)`,
|
|
145
|
+
|
|
146
|
+
// Documents
|
|
147
|
+
`CREATE TABLE IF NOT EXISTS documents (
|
|
148
|
+
id TEXT PRIMARY KEY,
|
|
149
|
+
user_id TEXT,
|
|
150
|
+
filename TEXT NOT NULL,
|
|
151
|
+
file_type TEXT,
|
|
152
|
+
file_size INTEGER,
|
|
153
|
+
content TEXT,
|
|
154
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
155
|
+
created_at INTEGER NOT NULL
|
|
156
|
+
)`,
|
|
157
|
+
`CREATE INDEX IF NOT EXISTS idx_documents_user ON documents(user_id)`,
|
|
158
|
+
|
|
159
|
+
// Document chunks
|
|
160
|
+
`CREATE TABLE IF NOT EXISTS document_chunks (
|
|
161
|
+
id TEXT PRIMARY KEY,
|
|
162
|
+
document_id TEXT NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
|
|
163
|
+
chunk_index INTEGER NOT NULL,
|
|
164
|
+
content TEXT NOT NULL,
|
|
165
|
+
embedding TEXT NOT NULL,
|
|
166
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
167
|
+
created_at INTEGER NOT NULL
|
|
168
|
+
)`,
|
|
169
|
+
`CREATE INDEX IF NOT EXISTS idx_chunks_document ON document_chunks(document_id)`,
|
|
170
|
+
|
|
171
|
+
// Checkpoints
|
|
172
|
+
`CREATE TABLE IF NOT EXISTS checkpoints (
|
|
173
|
+
thread_id TEXT NOT NULL,
|
|
174
|
+
checkpoint_ns TEXT NOT NULL DEFAULT '',
|
|
175
|
+
checkpoint_id TEXT NOT NULL,
|
|
176
|
+
parent_checkpoint_id TEXT,
|
|
177
|
+
type TEXT,
|
|
178
|
+
checkpoint TEXT NOT NULL,
|
|
179
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
180
|
+
created_at INTEGER NOT NULL,
|
|
181
|
+
PRIMARY KEY (thread_id, checkpoint_ns, checkpoint_id)
|
|
182
|
+
)`,
|
|
183
|
+
|
|
184
|
+
// Checkpoint writes
|
|
185
|
+
`CREATE TABLE IF NOT EXISTS checkpoint_writes (
|
|
186
|
+
thread_id TEXT NOT NULL,
|
|
187
|
+
checkpoint_ns TEXT NOT NULL DEFAULT '',
|
|
188
|
+
checkpoint_id TEXT NOT NULL,
|
|
189
|
+
task_id TEXT NOT NULL,
|
|
190
|
+
idx INTEGER NOT NULL,
|
|
191
|
+
channel TEXT NOT NULL,
|
|
192
|
+
type TEXT,
|
|
193
|
+
value TEXT,
|
|
194
|
+
created_at INTEGER NOT NULL,
|
|
195
|
+
PRIMARY KEY (thread_id, checkpoint_ns, checkpoint_id, task_id, idx)
|
|
196
|
+
)`,
|
|
197
|
+
|
|
198
|
+
// Audit logs
|
|
199
|
+
`CREATE TABLE IF NOT EXISTS audit_logs (
|
|
200
|
+
id TEXT PRIMARY KEY,
|
|
201
|
+
user_id TEXT,
|
|
202
|
+
agent_id TEXT,
|
|
203
|
+
action_type TEXT NOT NULL,
|
|
204
|
+
action_details TEXT NOT NULL DEFAULT '{}',
|
|
205
|
+
input_hash TEXT,
|
|
206
|
+
output_hash TEXT,
|
|
207
|
+
status TEXT NOT NULL DEFAULT 'success',
|
|
208
|
+
error_message TEXT,
|
|
209
|
+
execution_time_ms INTEGER,
|
|
210
|
+
ip_address TEXT,
|
|
211
|
+
user_agent TEXT,
|
|
212
|
+
success INTEGER NOT NULL DEFAULT 1,
|
|
213
|
+
created_at INTEGER NOT NULL
|
|
214
|
+
)`,
|
|
215
|
+
`CREATE INDEX IF NOT EXISTS idx_audit_user ON audit_logs(user_id)`,
|
|
216
|
+
`CREATE INDEX IF NOT EXISTS idx_audit_action ON audit_logs(action_type)`,
|
|
217
|
+
`CREATE INDEX IF NOT EXISTS idx_audit_created ON audit_logs(created_at)`,
|
|
218
|
+
|
|
219
|
+
// Tasks
|
|
220
|
+
`CREATE TABLE IF NOT EXISTS tasks (
|
|
221
|
+
id TEXT PRIMARY KEY,
|
|
222
|
+
user_id TEXT,
|
|
223
|
+
task_type TEXT NOT NULL,
|
|
224
|
+
priority INTEGER NOT NULL DEFAULT 0,
|
|
225
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
226
|
+
payload TEXT NOT NULL DEFAULT '{}',
|
|
227
|
+
result TEXT,
|
|
228
|
+
error_message TEXT,
|
|
229
|
+
scheduled_at INTEGER,
|
|
230
|
+
started_at INTEGER,
|
|
231
|
+
completed_at INTEGER,
|
|
232
|
+
retry_count INTEGER NOT NULL DEFAULT 0,
|
|
233
|
+
max_retries INTEGER NOT NULL DEFAULT 3,
|
|
234
|
+
created_at INTEGER NOT NULL,
|
|
235
|
+
updated_at INTEGER NOT NULL
|
|
236
|
+
)`,
|
|
237
|
+
`CREATE INDEX IF NOT EXISTS idx_tasks_user ON tasks(user_id)`,
|
|
238
|
+
`CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status)`,
|
|
239
|
+
|
|
240
|
+
// Schema version tracking
|
|
241
|
+
`CREATE TABLE IF NOT EXISTS schema_versions (
|
|
242
|
+
version INTEGER PRIMARY KEY,
|
|
243
|
+
name TEXT NOT NULL,
|
|
244
|
+
applied_at INTEGER NOT NULL,
|
|
245
|
+
checksum TEXT
|
|
246
|
+
)`,
|
|
247
|
+
];
|
|
248
|
+
|
|
249
|
+
for (const statement of statements) {
|
|
250
|
+
try {
|
|
251
|
+
this.db!.exec(statement);
|
|
252
|
+
} catch (error) {
|
|
253
|
+
// Ignore errors for existing objects
|
|
254
|
+
const message = error instanceof Error ? error.message : '';
|
|
255
|
+
if (!message.includes('already exists')) {
|
|
256
|
+
throw error;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
logger.debug('SQLite schema initialized');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Execute a raw SQL query
|
|
266
|
+
*/
|
|
267
|
+
async query<T extends Record<string, any> = Record<string, any>>(
|
|
268
|
+
sqlText: string,
|
|
269
|
+
params?: unknown[]
|
|
270
|
+
): Promise<QueryResult<T>> {
|
|
271
|
+
this.ensureInitialized();
|
|
272
|
+
const start = Date.now();
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
// Convert PostgreSQL placeholders to SQLite
|
|
276
|
+
const sqliteSql = this.convertToSqlite(sqlText);
|
|
277
|
+
const sqliteParams = params ?? [];
|
|
278
|
+
|
|
279
|
+
const isSelect = this.isSelectQuery(sqlText);
|
|
280
|
+
const hasReturning = this.hasReturning(sqlText);
|
|
281
|
+
|
|
282
|
+
let rows: T[] = [];
|
|
283
|
+
let rowCount: number | null = 0;
|
|
284
|
+
|
|
285
|
+
if (isSelect) {
|
|
286
|
+
const stmt = this.db!.prepare(sqliteSql);
|
|
287
|
+
rows = stmt.all(...sqliteParams) as T[];
|
|
288
|
+
rowCount = rows.length;
|
|
289
|
+
} else if (hasReturning) {
|
|
290
|
+
// Handle INSERT/UPDATE/DELETE with RETURNING
|
|
291
|
+
const returning = this.extractReturning(sqlText);
|
|
292
|
+
const tableName = this.extractTableName(sqlText);
|
|
293
|
+
|
|
294
|
+
// Remove RETURNING clause for SQLite
|
|
295
|
+
const sqlWithoutReturning = sqliteSql.replace(/\s+RETURNING\s+.+$/i, '');
|
|
296
|
+
const stmt = this.db!.prepare(sqlWithoutReturning);
|
|
297
|
+
const info = stmt.run(...sqliteParams);
|
|
298
|
+
rowCount = info.changes;
|
|
299
|
+
|
|
300
|
+
if (returning && tableName && info.lastInsertRowid) {
|
|
301
|
+
// Fetch the inserted/updated row
|
|
302
|
+
const selectStmt = this.db!.prepare(
|
|
303
|
+
`SELECT ${returning} FROM ${tableName} WHERE rowid = ?`
|
|
304
|
+
);
|
|
305
|
+
const row = selectStmt.get(info.lastInsertRowid);
|
|
306
|
+
if (row) {
|
|
307
|
+
rows = [row as T];
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
} else {
|
|
311
|
+
const stmt = this.db!.prepare(sqliteSql);
|
|
312
|
+
const info = stmt.run(...sqliteParams);
|
|
313
|
+
rowCount = info.changes;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const duration = Date.now() - start;
|
|
317
|
+
this.logQuery(sqlText, duration, rowCount);
|
|
318
|
+
|
|
319
|
+
return { rows, rowCount };
|
|
320
|
+
} catch (error) {
|
|
321
|
+
this.logError(sqlText, error);
|
|
322
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
323
|
+
throw new MemoryError(`Query failed: ${message}`);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Execute a transaction
|
|
329
|
+
*/
|
|
330
|
+
async transaction<T>(
|
|
331
|
+
callback: (client: TransactionClient) => Promise<T>
|
|
332
|
+
): Promise<T> {
|
|
333
|
+
this.ensureInitialized();
|
|
334
|
+
|
|
335
|
+
const transactionClient: TransactionClient = {
|
|
336
|
+
query: async <R extends Record<string, any> = Record<string, any>>(
|
|
337
|
+
sqlText: string,
|
|
338
|
+
params?: unknown[]
|
|
339
|
+
): Promise<QueryResult<R>> => {
|
|
340
|
+
return this.query<R>(sqlText, params);
|
|
341
|
+
},
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
try {
|
|
345
|
+
this.db!.exec('BEGIN');
|
|
346
|
+
const result = await callback(transactionClient);
|
|
347
|
+
this.db!.exec('COMMIT');
|
|
348
|
+
return result;
|
|
349
|
+
} catch (error) {
|
|
350
|
+
this.db!.exec('ROLLBACK');
|
|
351
|
+
throw error;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Check database health
|
|
357
|
+
*/
|
|
358
|
+
async healthCheck(): Promise<boolean> {
|
|
359
|
+
try {
|
|
360
|
+
const result = await this.query<{ ok: number }>('SELECT 1 as ok');
|
|
361
|
+
return result.rows.length > 0 && result.rows[0]!.ok === 1;
|
|
362
|
+
} catch {
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Close database connection
|
|
369
|
+
*/
|
|
370
|
+
async close(): Promise<void> {
|
|
371
|
+
if (this.db) {
|
|
372
|
+
this.db.close();
|
|
373
|
+
this.db = null;
|
|
374
|
+
this.drizzleInstance = null;
|
|
375
|
+
this._isInitialized = false;
|
|
376
|
+
logger.info('SQLite database closed');
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Get database file path
|
|
382
|
+
*/
|
|
383
|
+
getPath(): string {
|
|
384
|
+
return this.dbPath;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Get database file size in bytes
|
|
389
|
+
*/
|
|
390
|
+
getFileSize(): number {
|
|
391
|
+
if (!this.db) return 0;
|
|
392
|
+
try {
|
|
393
|
+
const stats = this.db.pragma('page_count') as Array<{ page_count: number }>;
|
|
394
|
+
const pageSize = this.db.pragma('page_size') as Array<{ page_size: number }>;
|
|
395
|
+
return (stats[0]?.page_count ?? 0) * (pageSize[0]?.page_size ?? 4096);
|
|
396
|
+
} catch {
|
|
397
|
+
return 0;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Vacuum database to reclaim space
|
|
403
|
+
*/
|
|
404
|
+
vacuum(): void {
|
|
405
|
+
this.ensureInitialized();
|
|
406
|
+
this.db!.exec('VACUUM');
|
|
407
|
+
logger.debug('Database vacuumed');
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Checkpoint WAL to main database
|
|
412
|
+
*/
|
|
413
|
+
checkpoint(): void {
|
|
414
|
+
this.ensureInitialized();
|
|
415
|
+
this.db!.pragma('wal_checkpoint(TRUNCATE)');
|
|
416
|
+
logger.debug('WAL checkpoint completed');
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
export default SQLiteAdapter;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache Key Generation
|
|
3
|
+
*
|
|
4
|
+
* Utilities for generating consistent cache keys.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Generate a cache key from operation, table, and parameters
|
|
9
|
+
*/
|
|
10
|
+
export function generateCacheKey(
|
|
11
|
+
operation: string,
|
|
12
|
+
table: string,
|
|
13
|
+
params: Record<string, unknown>
|
|
14
|
+
): string {
|
|
15
|
+
const sortedParams = Object.keys(params)
|
|
16
|
+
.sort()
|
|
17
|
+
.map((key) => `${key}:${JSON.stringify(params[key])}`)
|
|
18
|
+
.join('|');
|
|
19
|
+
|
|
20
|
+
return `${operation}:${table}:${sortedParams}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Cache key generators for common operations
|
|
25
|
+
*/
|
|
26
|
+
export const cacheKeys = {
|
|
27
|
+
/**
|
|
28
|
+
* Get conversation by thread ID
|
|
29
|
+
*/
|
|
30
|
+
conversation: (threadId: string): string =>
|
|
31
|
+
generateCacheKey('get', 'conversations', { threadId }),
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get conversation by ID
|
|
35
|
+
*/
|
|
36
|
+
conversationById: (id: string): string =>
|
|
37
|
+
generateCacheKey('get', 'conversations', { id }),
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* List messages for a conversation
|
|
41
|
+
*/
|
|
42
|
+
messages: (conversationId: string, limit: number, offset: number): string =>
|
|
43
|
+
generateCacheKey('list', 'messages', { conversationId, limit, offset }),
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get document by ID
|
|
47
|
+
*/
|
|
48
|
+
document: (id: string): string =>
|
|
49
|
+
generateCacheKey('get', 'documents', { id }),
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* List documents for a user
|
|
53
|
+
*/
|
|
54
|
+
userDocuments: (userId: string, limit: number, offset: number): string =>
|
|
55
|
+
generateCacheKey('list', 'documents', { userId, limit, offset }),
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get memory by ID
|
|
59
|
+
*/
|
|
60
|
+
memory: (id: string): string =>
|
|
61
|
+
generateCacheKey('get', 'long_term_memories', { id }),
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* List recent memories for a user
|
|
65
|
+
*/
|
|
66
|
+
userMemories: (userId: string, limit: number): string =>
|
|
67
|
+
generateCacheKey('list', 'long_term_memories', { userId, limit }),
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Vector search results
|
|
71
|
+
*/
|
|
72
|
+
vectorSearch: (
|
|
73
|
+
embeddingHash: string,
|
|
74
|
+
options: Record<string, unknown>
|
|
75
|
+
): string =>
|
|
76
|
+
generateCacheKey('search', 'embeddings', { hash: embeddingHash, ...options }),
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Task by ID
|
|
80
|
+
*/
|
|
81
|
+
task: (id: string): string =>
|
|
82
|
+
generateCacheKey('get', 'tasks', { id }),
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* List tasks for a user
|
|
86
|
+
*/
|
|
87
|
+
userTasks: (userId: string, status: string, limit: number): string =>
|
|
88
|
+
generateCacheKey('list', 'tasks', { userId, status, limit }),
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Invalidation patterns for write operations
|
|
93
|
+
*/
|
|
94
|
+
export const invalidationPatterns = {
|
|
95
|
+
/**
|
|
96
|
+
* Invalidate all conversation-related cache
|
|
97
|
+
*/
|
|
98
|
+
conversation: (conversationId: string): RegExp =>
|
|
99
|
+
new RegExp(`conversations.*${conversationId}`),
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Invalidate all message-related cache for a conversation
|
|
103
|
+
*/
|
|
104
|
+
messages: (conversationId: string): RegExp =>
|
|
105
|
+
new RegExp(`messages.*${conversationId}`),
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Invalidate all document-related cache
|
|
109
|
+
*/
|
|
110
|
+
document: (documentId: string): RegExp =>
|
|
111
|
+
new RegExp(`documents.*${documentId}`),
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Invalidate user's document list
|
|
115
|
+
*/
|
|
116
|
+
userDocuments: (userId: string): string => `list:documents:userId:"${userId}"`,
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Invalidate all memory-related cache
|
|
120
|
+
*/
|
|
121
|
+
memory: (memoryId: string): RegExp =>
|
|
122
|
+
new RegExp(`long_term_memories.*${memoryId}`),
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Invalidate user's memory list
|
|
126
|
+
*/
|
|
127
|
+
userMemories: (userId: string): string =>
|
|
128
|
+
`list:long_term_memories:userId:"${userId}"`,
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Invalidate all vector search results
|
|
132
|
+
*/
|
|
133
|
+
vectorSearchAll: (): string => 'search:embeddings',
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Invalidate all task-related cache
|
|
137
|
+
*/
|
|
138
|
+
task: (taskId: string): RegExp => new RegExp(`tasks.*${taskId}`),
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Invalidate user's task list
|
|
142
|
+
*/
|
|
143
|
+
userTasks: (userId: string): string => `list:tasks:userId:"${userId}"`,
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export default {
|
|
147
|
+
generateCacheKey,
|
|
148
|
+
cacheKeys,
|
|
149
|
+
invalidationPatterns,
|
|
150
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache Layer Exports
|
|
3
|
+
*
|
|
4
|
+
* Central export for query caching functionality.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { QueryCache } from './query-cache.js';
|
|
8
|
+
export {
|
|
9
|
+
generateCacheKey,
|
|
10
|
+
cacheKeys,
|
|
11
|
+
invalidationPatterns,
|
|
12
|
+
} from './cache-keys.js';
|
|
13
|
+
|
|
14
|
+
import { QueryCache } from './query-cache.js';
|
|
15
|
+
import type { QueryCacheOptions } from '../types.js';
|
|
16
|
+
|
|
17
|
+
// Singleton cache instance
|
|
18
|
+
let cacheInstance: QueryCache | null = null;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get the global query cache instance
|
|
22
|
+
*/
|
|
23
|
+
export function getQueryCache(options?: Partial<QueryCacheOptions>): QueryCache {
|
|
24
|
+
if (!cacheInstance) {
|
|
25
|
+
cacheInstance = new QueryCache(options);
|
|
26
|
+
}
|
|
27
|
+
return cacheInstance;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Reset the global cache instance
|
|
32
|
+
*/
|
|
33
|
+
export function resetQueryCache(): void {
|
|
34
|
+
if (cacheInstance) {
|
|
35
|
+
cacheInstance.clear();
|
|
36
|
+
cacheInstance = null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default {
|
|
41
|
+
QueryCache,
|
|
42
|
+
getQueryCache,
|
|
43
|
+
resetQueryCache,
|
|
44
|
+
};
|