@mzhub/mem-ts 0.1.0
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/README.md +335 -0
- package/dist/BaseAdapter-BoRh1T7O.d.mts +75 -0
- package/dist/BaseAdapter-CQVX-gcA.d.ts +75 -0
- package/dist/BaseProvider-CEoiLGj5.d.ts +34 -0
- package/dist/BaseProvider-edMh_R9t.d.mts +34 -0
- package/dist/adapters/index.d.mts +259 -0
- package/dist/adapters/index.d.ts +259 -0
- package/dist/adapters/index.js +1570 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/index.mjs +1542 -0
- package/dist/adapters/index.mjs.map +1 -0
- package/dist/index-Ci5Q9G9H.d.mts +289 -0
- package/dist/index-Dl-Q2au9.d.ts +289 -0
- package/dist/index.d.mts +1206 -0
- package/dist/index.d.ts +1206 -0
- package/dist/index.js +5126 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +5058 -0
- package/dist/index.mjs.map +1 -0
- package/dist/middleware/index.d.mts +4 -0
- package/dist/middleware/index.d.ts +4 -0
- package/dist/middleware/index.js +63 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/index.mjs +59 -0
- package/dist/middleware/index.mjs.map +1 -0
- package/dist/providers/index.d.mts +96 -0
- package/dist/providers/index.d.ts +96 -0
- package/dist/providers/index.js +379 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/index.mjs +370 -0
- package/dist/providers/index.mjs.map +1 -0
- package/dist/types-G9qmfSeZ.d.mts +260 -0
- package/dist/types-G9qmfSeZ.d.ts +260 -0
- package/logo.png +0 -0
- package/package.json +114 -0
|
@@ -0,0 +1,1570 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var uuid = require('uuid');
|
|
4
|
+
var fs = require('fs/promises');
|
|
5
|
+
var path = require('path');
|
|
6
|
+
|
|
7
|
+
function _interopNamespace(e) {
|
|
8
|
+
if (e && e.__esModule) return e;
|
|
9
|
+
var n = Object.create(null);
|
|
10
|
+
if (e) {
|
|
11
|
+
Object.keys(e).forEach(function (k) {
|
|
12
|
+
if (k !== 'default') {
|
|
13
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
14
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
get: function () { return e[k]; }
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
n.default = e;
|
|
22
|
+
return Object.freeze(n);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
|
|
26
|
+
var path__namespace = /*#__PURE__*/_interopNamespace(path);
|
|
27
|
+
|
|
28
|
+
// src/adapters/BaseAdapter.ts
|
|
29
|
+
var BaseAdapter = class {
|
|
30
|
+
initialized = false;
|
|
31
|
+
// =========================================================================
|
|
32
|
+
// Utility Methods
|
|
33
|
+
// =========================================================================
|
|
34
|
+
/**
|
|
35
|
+
* Check if adapter is initialized
|
|
36
|
+
*/
|
|
37
|
+
isInitialized() {
|
|
38
|
+
return this.initialized;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Ensure adapter is initialized before operations
|
|
42
|
+
*/
|
|
43
|
+
async ensureInitialized() {
|
|
44
|
+
if (!this.initialized) {
|
|
45
|
+
await this.initialize();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
var InMemoryAdapter = class extends BaseAdapter {
|
|
50
|
+
users = /* @__PURE__ */ new Map();
|
|
51
|
+
async initialize() {
|
|
52
|
+
this.initialized = true;
|
|
53
|
+
}
|
|
54
|
+
async close() {
|
|
55
|
+
this.users.clear();
|
|
56
|
+
this.initialized = false;
|
|
57
|
+
}
|
|
58
|
+
getUserData(userId) {
|
|
59
|
+
let userData = this.users.get(userId);
|
|
60
|
+
if (!userData) {
|
|
61
|
+
userData = {
|
|
62
|
+
facts: /* @__PURE__ */ new Map(),
|
|
63
|
+
conversations: [],
|
|
64
|
+
sessions: /* @__PURE__ */ new Map()
|
|
65
|
+
};
|
|
66
|
+
this.users.set(userId, userData);
|
|
67
|
+
}
|
|
68
|
+
return userData;
|
|
69
|
+
}
|
|
70
|
+
// =========================================================================
|
|
71
|
+
// Fact Operations
|
|
72
|
+
// =========================================================================
|
|
73
|
+
async getFacts(userId, filter) {
|
|
74
|
+
await this.ensureInitialized();
|
|
75
|
+
const userData = this.getUserData(userId);
|
|
76
|
+
let facts = Array.from(userData.facts.values());
|
|
77
|
+
if (filter) {
|
|
78
|
+
if (filter.subject) {
|
|
79
|
+
facts = facts.filter((f) => f.subject === filter.subject);
|
|
80
|
+
}
|
|
81
|
+
if (filter.predicate) {
|
|
82
|
+
facts = facts.filter((f) => f.predicate === filter.predicate);
|
|
83
|
+
}
|
|
84
|
+
if (filter.predicates && filter.predicates.length > 0) {
|
|
85
|
+
facts = facts.filter((f) => filter.predicates.includes(f.predicate));
|
|
86
|
+
}
|
|
87
|
+
if (filter.validOnly !== false) {
|
|
88
|
+
facts = facts.filter((f) => f.invalidatedAt === null);
|
|
89
|
+
}
|
|
90
|
+
if (filter.orderBy) {
|
|
91
|
+
facts.sort((a, b) => {
|
|
92
|
+
const aVal = a[filter.orderBy];
|
|
93
|
+
const bVal = b[filter.orderBy];
|
|
94
|
+
if (aVal instanceof Date && bVal instanceof Date) {
|
|
95
|
+
return filter.orderDir === "desc" ? bVal.getTime() - aVal.getTime() : aVal.getTime() - bVal.getTime();
|
|
96
|
+
}
|
|
97
|
+
if (typeof aVal === "number" && typeof bVal === "number") {
|
|
98
|
+
return filter.orderDir === "desc" ? bVal - aVal : aVal - bVal;
|
|
99
|
+
}
|
|
100
|
+
return 0;
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
if (filter.limit) {
|
|
104
|
+
facts = facts.slice(0, filter.limit);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return facts;
|
|
108
|
+
}
|
|
109
|
+
async getFactById(userId, factId) {
|
|
110
|
+
await this.ensureInitialized();
|
|
111
|
+
const userData = this.getUserData(userId);
|
|
112
|
+
return userData.facts.get(factId) || null;
|
|
113
|
+
}
|
|
114
|
+
async upsertFact(userId, fact) {
|
|
115
|
+
await this.ensureInitialized();
|
|
116
|
+
const userData = this.getUserData(userId);
|
|
117
|
+
const existingFact = Array.from(userData.facts.values()).find(
|
|
118
|
+
(f) => f.subject === fact.subject && f.predicate === fact.predicate && f.invalidatedAt === null
|
|
119
|
+
);
|
|
120
|
+
const now = /* @__PURE__ */ new Date();
|
|
121
|
+
if (existingFact) {
|
|
122
|
+
const updated = {
|
|
123
|
+
...existingFact,
|
|
124
|
+
...fact,
|
|
125
|
+
updatedAt: now
|
|
126
|
+
};
|
|
127
|
+
userData.facts.set(existingFact.id, updated);
|
|
128
|
+
return updated;
|
|
129
|
+
}
|
|
130
|
+
const newFact = {
|
|
131
|
+
...fact,
|
|
132
|
+
id: uuid.v4(),
|
|
133
|
+
createdAt: now,
|
|
134
|
+
updatedAt: now
|
|
135
|
+
};
|
|
136
|
+
userData.facts.set(newFact.id, newFact);
|
|
137
|
+
return newFact;
|
|
138
|
+
}
|
|
139
|
+
async updateFact(userId, factId, updates) {
|
|
140
|
+
await this.ensureInitialized();
|
|
141
|
+
const userData = this.getUserData(userId);
|
|
142
|
+
const fact = userData.facts.get(factId);
|
|
143
|
+
if (!fact) {
|
|
144
|
+
throw new Error(`Fact not found: ${factId}`);
|
|
145
|
+
}
|
|
146
|
+
const updated = {
|
|
147
|
+
...fact,
|
|
148
|
+
...updates,
|
|
149
|
+
id: fact.id,
|
|
150
|
+
// Prevent ID change
|
|
151
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
152
|
+
};
|
|
153
|
+
userData.facts.set(factId, updated);
|
|
154
|
+
return updated;
|
|
155
|
+
}
|
|
156
|
+
async deleteFact(userId, factId, _reason) {
|
|
157
|
+
await this.ensureInitialized();
|
|
158
|
+
const userData = this.getUserData(userId);
|
|
159
|
+
const fact = userData.facts.get(factId);
|
|
160
|
+
if (fact) {
|
|
161
|
+
fact.invalidatedAt = /* @__PURE__ */ new Date();
|
|
162
|
+
userData.facts.set(factId, fact);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
async hardDeleteFact(userId, factId) {
|
|
166
|
+
await this.ensureInitialized();
|
|
167
|
+
const userData = this.getUserData(userId);
|
|
168
|
+
userData.facts.delete(factId);
|
|
169
|
+
}
|
|
170
|
+
// =========================================================================
|
|
171
|
+
// Conversation Operations
|
|
172
|
+
// =========================================================================
|
|
173
|
+
async getConversationHistory(userId, limit, sessionId) {
|
|
174
|
+
await this.ensureInitialized();
|
|
175
|
+
const userData = this.getUserData(userId);
|
|
176
|
+
let conversations = [...userData.conversations];
|
|
177
|
+
if (sessionId) {
|
|
178
|
+
conversations = conversations.filter((c) => c.sessionId === sessionId);
|
|
179
|
+
}
|
|
180
|
+
conversations.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
|
|
181
|
+
if (limit) {
|
|
182
|
+
conversations = conversations.slice(0, limit);
|
|
183
|
+
}
|
|
184
|
+
return conversations;
|
|
185
|
+
}
|
|
186
|
+
async saveConversation(userId, exchange) {
|
|
187
|
+
await this.ensureInitialized();
|
|
188
|
+
const userData = this.getUserData(userId);
|
|
189
|
+
const newExchange = {
|
|
190
|
+
...exchange,
|
|
191
|
+
id: uuid.v4()
|
|
192
|
+
};
|
|
193
|
+
userData.conversations.push(newExchange);
|
|
194
|
+
const session = userData.sessions.get(exchange.sessionId);
|
|
195
|
+
if (session) {
|
|
196
|
+
session.messageCount++;
|
|
197
|
+
}
|
|
198
|
+
return newExchange;
|
|
199
|
+
}
|
|
200
|
+
// =========================================================================
|
|
201
|
+
// Session Operations
|
|
202
|
+
// =========================================================================
|
|
203
|
+
async getSessions(userId, limit) {
|
|
204
|
+
await this.ensureInitialized();
|
|
205
|
+
const userData = this.getUserData(userId);
|
|
206
|
+
let sessions = Array.from(userData.sessions.values());
|
|
207
|
+
sessions.sort((a, b) => b.startedAt.getTime() - a.startedAt.getTime());
|
|
208
|
+
if (limit) {
|
|
209
|
+
sessions = sessions.slice(0, limit);
|
|
210
|
+
}
|
|
211
|
+
return sessions;
|
|
212
|
+
}
|
|
213
|
+
async getSession(userId, sessionId) {
|
|
214
|
+
await this.ensureInitialized();
|
|
215
|
+
const userData = this.getUserData(userId);
|
|
216
|
+
return userData.sessions.get(sessionId) || null;
|
|
217
|
+
}
|
|
218
|
+
async createSession(userId) {
|
|
219
|
+
await this.ensureInitialized();
|
|
220
|
+
const userData = this.getUserData(userId);
|
|
221
|
+
const session = {
|
|
222
|
+
id: uuid.v4(),
|
|
223
|
+
userId,
|
|
224
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
225
|
+
endedAt: null,
|
|
226
|
+
messageCount: 0
|
|
227
|
+
};
|
|
228
|
+
userData.sessions.set(session.id, session);
|
|
229
|
+
return session;
|
|
230
|
+
}
|
|
231
|
+
async endSession(userId, sessionId, summary) {
|
|
232
|
+
await this.ensureInitialized();
|
|
233
|
+
const userData = this.getUserData(userId);
|
|
234
|
+
const session = userData.sessions.get(sessionId);
|
|
235
|
+
if (!session) {
|
|
236
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
237
|
+
}
|
|
238
|
+
session.endedAt = /* @__PURE__ */ new Date();
|
|
239
|
+
if (summary) {
|
|
240
|
+
session.summary = summary;
|
|
241
|
+
}
|
|
242
|
+
userData.sessions.set(sessionId, session);
|
|
243
|
+
return session;
|
|
244
|
+
}
|
|
245
|
+
// =========================================================================
|
|
246
|
+
// Utility Methods
|
|
247
|
+
// =========================================================================
|
|
248
|
+
/**
|
|
249
|
+
* Clear all data for a user (useful for testing)
|
|
250
|
+
*/
|
|
251
|
+
async clearUser(userId) {
|
|
252
|
+
this.users.delete(userId);
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Clear all data (useful for testing)
|
|
256
|
+
*/
|
|
257
|
+
async clearAll() {
|
|
258
|
+
this.users.clear();
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Export all data for a user (for portability)
|
|
262
|
+
*/
|
|
263
|
+
async exportUser(userId) {
|
|
264
|
+
const userData = this.getUserData(userId);
|
|
265
|
+
return {
|
|
266
|
+
facts: Array.from(userData.facts.values()),
|
|
267
|
+
conversations: userData.conversations,
|
|
268
|
+
sessions: Array.from(userData.sessions.values())
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Import data for a user
|
|
273
|
+
*/
|
|
274
|
+
async importUser(userId, data) {
|
|
275
|
+
const userData = this.getUserData(userId);
|
|
276
|
+
for (const fact of data.facts) {
|
|
277
|
+
userData.facts.set(fact.id, fact);
|
|
278
|
+
}
|
|
279
|
+
userData.conversations.push(...data.conversations);
|
|
280
|
+
for (const session of data.sessions) {
|
|
281
|
+
userData.sessions.set(session.id, session);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
var JSONFileAdapter = class extends BaseAdapter {
|
|
286
|
+
basePath;
|
|
287
|
+
prettyPrint;
|
|
288
|
+
constructor(config = {}) {
|
|
289
|
+
super();
|
|
290
|
+
this.basePath = config.path || "./.mem-ts";
|
|
291
|
+
this.prettyPrint = config.prettyPrint ?? process.env.NODE_ENV !== "production";
|
|
292
|
+
}
|
|
293
|
+
async initialize() {
|
|
294
|
+
await fs__namespace.mkdir(path__namespace.join(this.basePath, "users"), { recursive: true });
|
|
295
|
+
this.initialized = true;
|
|
296
|
+
}
|
|
297
|
+
async close() {
|
|
298
|
+
this.initialized = false;
|
|
299
|
+
}
|
|
300
|
+
// =========================================================================
|
|
301
|
+
// File I/O Helpers
|
|
302
|
+
// =========================================================================
|
|
303
|
+
getUserPath(userId) {
|
|
304
|
+
const safeUserId = userId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
305
|
+
return path__namespace.join(this.basePath, "users", safeUserId);
|
|
306
|
+
}
|
|
307
|
+
async ensureUserDir(userId) {
|
|
308
|
+
await fs__namespace.mkdir(this.getUserPath(userId), { recursive: true });
|
|
309
|
+
}
|
|
310
|
+
async readFile(userId, filename, defaultValue) {
|
|
311
|
+
const filePath = path__namespace.join(this.getUserPath(userId), filename);
|
|
312
|
+
try {
|
|
313
|
+
const data = await fs__namespace.readFile(filePath, "utf-8");
|
|
314
|
+
return JSON.parse(data, this.dateReviver);
|
|
315
|
+
} catch (error) {
|
|
316
|
+
if (error.code === "ENOENT") {
|
|
317
|
+
return defaultValue;
|
|
318
|
+
}
|
|
319
|
+
throw error;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
async writeFile(userId, filename, data) {
|
|
323
|
+
await this.ensureUserDir(userId);
|
|
324
|
+
const filePath = path__namespace.join(this.getUserPath(userId), filename);
|
|
325
|
+
const json = this.prettyPrint ? JSON.stringify(data, null, 2) : JSON.stringify(data);
|
|
326
|
+
await fs__namespace.writeFile(filePath, json, "utf-8");
|
|
327
|
+
}
|
|
328
|
+
// Date reviver for JSON.parse
|
|
329
|
+
dateReviver(_key, value) {
|
|
330
|
+
if (typeof value === "string") {
|
|
331
|
+
const dateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/;
|
|
332
|
+
if (dateRegex.test(value)) {
|
|
333
|
+
return new Date(value);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return value;
|
|
337
|
+
}
|
|
338
|
+
// =========================================================================
|
|
339
|
+
// Fact Operations
|
|
340
|
+
// =========================================================================
|
|
341
|
+
async getFacts(userId, filter) {
|
|
342
|
+
await this.ensureInitialized();
|
|
343
|
+
let facts = await this.readFile(userId, "facts.json", []);
|
|
344
|
+
if (filter) {
|
|
345
|
+
if (filter.subject) {
|
|
346
|
+
facts = facts.filter((f) => f.subject === filter.subject);
|
|
347
|
+
}
|
|
348
|
+
if (filter.predicate) {
|
|
349
|
+
facts = facts.filter((f) => f.predicate === filter.predicate);
|
|
350
|
+
}
|
|
351
|
+
if (filter.predicates && filter.predicates.length > 0) {
|
|
352
|
+
facts = facts.filter((f) => filter.predicates.includes(f.predicate));
|
|
353
|
+
}
|
|
354
|
+
if (filter.validOnly !== false) {
|
|
355
|
+
facts = facts.filter((f) => f.invalidatedAt === null);
|
|
356
|
+
}
|
|
357
|
+
if (filter.orderBy) {
|
|
358
|
+
facts.sort((a, b) => {
|
|
359
|
+
const aVal = a[filter.orderBy];
|
|
360
|
+
const bVal = b[filter.orderBy];
|
|
361
|
+
if (aVal instanceof Date && bVal instanceof Date) {
|
|
362
|
+
return filter.orderDir === "desc" ? bVal.getTime() - aVal.getTime() : aVal.getTime() - bVal.getTime();
|
|
363
|
+
}
|
|
364
|
+
if (typeof aVal === "number" && typeof bVal === "number") {
|
|
365
|
+
return filter.orderDir === "desc" ? bVal - aVal : aVal - bVal;
|
|
366
|
+
}
|
|
367
|
+
return 0;
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
if (filter.limit) {
|
|
371
|
+
facts = facts.slice(0, filter.limit);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return facts;
|
|
375
|
+
}
|
|
376
|
+
async getFactById(userId, factId) {
|
|
377
|
+
await this.ensureInitialized();
|
|
378
|
+
const facts = await this.readFile(userId, "facts.json", []);
|
|
379
|
+
return facts.find((f) => f.id === factId) || null;
|
|
380
|
+
}
|
|
381
|
+
async upsertFact(userId, fact) {
|
|
382
|
+
await this.ensureInitialized();
|
|
383
|
+
const facts = await this.readFile(userId, "facts.json", []);
|
|
384
|
+
const existingIndex = facts.findIndex(
|
|
385
|
+
(f) => f.subject === fact.subject && f.predicate === fact.predicate && f.invalidatedAt === null
|
|
386
|
+
);
|
|
387
|
+
const now = /* @__PURE__ */ new Date();
|
|
388
|
+
if (existingIndex >= 0) {
|
|
389
|
+
const updated = {
|
|
390
|
+
...facts[existingIndex],
|
|
391
|
+
...fact,
|
|
392
|
+
updatedAt: now
|
|
393
|
+
};
|
|
394
|
+
facts[existingIndex] = updated;
|
|
395
|
+
await this.writeFile(userId, "facts.json", facts);
|
|
396
|
+
return updated;
|
|
397
|
+
}
|
|
398
|
+
const newFact = {
|
|
399
|
+
...fact,
|
|
400
|
+
id: uuid.v4(),
|
|
401
|
+
createdAt: now,
|
|
402
|
+
updatedAt: now
|
|
403
|
+
};
|
|
404
|
+
facts.push(newFact);
|
|
405
|
+
await this.writeFile(userId, "facts.json", facts);
|
|
406
|
+
return newFact;
|
|
407
|
+
}
|
|
408
|
+
async updateFact(userId, factId, updates) {
|
|
409
|
+
await this.ensureInitialized();
|
|
410
|
+
const facts = await this.readFile(userId, "facts.json", []);
|
|
411
|
+
const index = facts.findIndex((f) => f.id === factId);
|
|
412
|
+
if (index === -1) {
|
|
413
|
+
throw new Error(`Fact not found: ${factId}`);
|
|
414
|
+
}
|
|
415
|
+
const updated = {
|
|
416
|
+
...facts[index],
|
|
417
|
+
...updates,
|
|
418
|
+
id: facts[index].id,
|
|
419
|
+
// Prevent ID change
|
|
420
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
421
|
+
};
|
|
422
|
+
facts[index] = updated;
|
|
423
|
+
await this.writeFile(userId, "facts.json", facts);
|
|
424
|
+
return updated;
|
|
425
|
+
}
|
|
426
|
+
async deleteFact(userId, factId, _reason) {
|
|
427
|
+
await this.ensureInitialized();
|
|
428
|
+
const facts = await this.readFile(userId, "facts.json", []);
|
|
429
|
+
const index = facts.findIndex((f) => f.id === factId);
|
|
430
|
+
if (index >= 0) {
|
|
431
|
+
facts[index].invalidatedAt = /* @__PURE__ */ new Date();
|
|
432
|
+
await this.writeFile(userId, "facts.json", facts);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
async hardDeleteFact(userId, factId) {
|
|
436
|
+
await this.ensureInitialized();
|
|
437
|
+
const facts = await this.readFile(userId, "facts.json", []);
|
|
438
|
+
const filtered = facts.filter((f) => f.id !== factId);
|
|
439
|
+
await this.writeFile(userId, "facts.json", filtered);
|
|
440
|
+
}
|
|
441
|
+
// =========================================================================
|
|
442
|
+
// Conversation Operations
|
|
443
|
+
// =========================================================================
|
|
444
|
+
async getConversationHistory(userId, limit, sessionId) {
|
|
445
|
+
await this.ensureInitialized();
|
|
446
|
+
let conversations = await this.readFile(
|
|
447
|
+
userId,
|
|
448
|
+
"conversations.json",
|
|
449
|
+
[]
|
|
450
|
+
);
|
|
451
|
+
if (sessionId) {
|
|
452
|
+
conversations = conversations.filter((c) => c.sessionId === sessionId);
|
|
453
|
+
}
|
|
454
|
+
conversations.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
|
|
455
|
+
if (limit) {
|
|
456
|
+
conversations = conversations.slice(0, limit);
|
|
457
|
+
}
|
|
458
|
+
return conversations;
|
|
459
|
+
}
|
|
460
|
+
async saveConversation(userId, exchange) {
|
|
461
|
+
await this.ensureInitialized();
|
|
462
|
+
const conversations = await this.readFile(
|
|
463
|
+
userId,
|
|
464
|
+
"conversations.json",
|
|
465
|
+
[]
|
|
466
|
+
);
|
|
467
|
+
const newExchange = {
|
|
468
|
+
...exchange,
|
|
469
|
+
id: uuid.v4()
|
|
470
|
+
};
|
|
471
|
+
conversations.push(newExchange);
|
|
472
|
+
await this.writeFile(userId, "conversations.json", conversations);
|
|
473
|
+
const sessions = await this.readFile(
|
|
474
|
+
userId,
|
|
475
|
+
"sessions.json",
|
|
476
|
+
[]
|
|
477
|
+
);
|
|
478
|
+
const sessionIndex = sessions.findIndex((s) => s.id === exchange.sessionId);
|
|
479
|
+
if (sessionIndex >= 0) {
|
|
480
|
+
sessions[sessionIndex].messageCount++;
|
|
481
|
+
await this.writeFile(userId, "sessions.json", sessions);
|
|
482
|
+
}
|
|
483
|
+
return newExchange;
|
|
484
|
+
}
|
|
485
|
+
// =========================================================================
|
|
486
|
+
// Session Operations
|
|
487
|
+
// =========================================================================
|
|
488
|
+
async getSessions(userId, limit) {
|
|
489
|
+
await this.ensureInitialized();
|
|
490
|
+
let sessions = await this.readFile(userId, "sessions.json", []);
|
|
491
|
+
sessions.sort((a, b) => b.startedAt.getTime() - a.startedAt.getTime());
|
|
492
|
+
if (limit) {
|
|
493
|
+
sessions = sessions.slice(0, limit);
|
|
494
|
+
}
|
|
495
|
+
return sessions;
|
|
496
|
+
}
|
|
497
|
+
async getSession(userId, sessionId) {
|
|
498
|
+
await this.ensureInitialized();
|
|
499
|
+
const sessions = await this.readFile(
|
|
500
|
+
userId,
|
|
501
|
+
"sessions.json",
|
|
502
|
+
[]
|
|
503
|
+
);
|
|
504
|
+
return sessions.find((s) => s.id === sessionId) || null;
|
|
505
|
+
}
|
|
506
|
+
async createSession(userId) {
|
|
507
|
+
await this.ensureInitialized();
|
|
508
|
+
const sessions = await this.readFile(
|
|
509
|
+
userId,
|
|
510
|
+
"sessions.json",
|
|
511
|
+
[]
|
|
512
|
+
);
|
|
513
|
+
const session = {
|
|
514
|
+
id: uuid.v4(),
|
|
515
|
+
userId,
|
|
516
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
517
|
+
endedAt: null,
|
|
518
|
+
messageCount: 0
|
|
519
|
+
};
|
|
520
|
+
sessions.push(session);
|
|
521
|
+
await this.writeFile(userId, "sessions.json", sessions);
|
|
522
|
+
return session;
|
|
523
|
+
}
|
|
524
|
+
async endSession(userId, sessionId, summary) {
|
|
525
|
+
await this.ensureInitialized();
|
|
526
|
+
const sessions = await this.readFile(
|
|
527
|
+
userId,
|
|
528
|
+
"sessions.json",
|
|
529
|
+
[]
|
|
530
|
+
);
|
|
531
|
+
const index = sessions.findIndex((s) => s.id === sessionId);
|
|
532
|
+
if (index === -1) {
|
|
533
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
534
|
+
}
|
|
535
|
+
sessions[index].endedAt = /* @__PURE__ */ new Date();
|
|
536
|
+
if (summary) {
|
|
537
|
+
sessions[index].summary = summary;
|
|
538
|
+
}
|
|
539
|
+
await this.writeFile(userId, "sessions.json", sessions);
|
|
540
|
+
return sessions[index];
|
|
541
|
+
}
|
|
542
|
+
// =========================================================================
|
|
543
|
+
// Utility Methods
|
|
544
|
+
// =========================================================================
|
|
545
|
+
/**
|
|
546
|
+
* Export all data for a user (for portability)
|
|
547
|
+
*/
|
|
548
|
+
async exportUser(userId) {
|
|
549
|
+
return {
|
|
550
|
+
facts: await this.readFile(userId, "facts.json", []),
|
|
551
|
+
conversations: await this.readFile(
|
|
552
|
+
userId,
|
|
553
|
+
"conversations.json",
|
|
554
|
+
[]
|
|
555
|
+
),
|
|
556
|
+
sessions: await this.readFile(userId, "sessions.json", [])
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Import data for a user
|
|
561
|
+
*/
|
|
562
|
+
async importUser(userId, data) {
|
|
563
|
+
await this.writeFile(userId, "facts.json", data.facts);
|
|
564
|
+
await this.writeFile(userId, "conversations.json", data.conversations);
|
|
565
|
+
await this.writeFile(userId, "sessions.json", data.sessions);
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Delete all data for a user
|
|
569
|
+
*/
|
|
570
|
+
async deleteUser(userId) {
|
|
571
|
+
const userPath = this.getUserPath(userId);
|
|
572
|
+
await fs__namespace.rm(userPath, { recursive: true, force: true });
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
// src/adapters/MongoDBAdapter.ts
|
|
577
|
+
var MongoDBAdapter = class extends BaseAdapter {
|
|
578
|
+
config;
|
|
579
|
+
client;
|
|
580
|
+
db;
|
|
581
|
+
collectionPrefix;
|
|
582
|
+
constructor(config) {
|
|
583
|
+
super();
|
|
584
|
+
this.config = config;
|
|
585
|
+
this.collectionPrefix = config.collectionPrefix || "memts_";
|
|
586
|
+
}
|
|
587
|
+
async getClient() {
|
|
588
|
+
if (this.client) return this.client;
|
|
589
|
+
try {
|
|
590
|
+
const { MongoClient } = await import('mongodb');
|
|
591
|
+
this.client = new MongoClient(this.config.uri);
|
|
592
|
+
await this.client.connect();
|
|
593
|
+
return this.client;
|
|
594
|
+
} catch {
|
|
595
|
+
throw new Error("MongoDB driver not installed. Run: npm install mongodb");
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
getCollection(name) {
|
|
599
|
+
return this.db.collection(`${this.collectionPrefix}${name}`);
|
|
600
|
+
}
|
|
601
|
+
async initialize() {
|
|
602
|
+
const client = await this.getClient();
|
|
603
|
+
const dbName = this.config.database || "memts";
|
|
604
|
+
this.db = client.db(dbName);
|
|
605
|
+
const facts = this.getCollection("facts");
|
|
606
|
+
await facts.createIndex({ userId: 1, subject: 1, predicate: 1 });
|
|
607
|
+
await facts.createIndex({ userId: 1, invalidatedAt: 1 });
|
|
608
|
+
await facts.createIndex({ userId: 1, updatedAt: -1 });
|
|
609
|
+
const conversations = this.getCollection("conversations");
|
|
610
|
+
await conversations.createIndex({ userId: 1, sessionId: 1 });
|
|
611
|
+
await conversations.createIndex({ userId: 1, timestamp: -1 });
|
|
612
|
+
const sessions = this.getCollection("sessions");
|
|
613
|
+
await sessions.createIndex({ userId: 1, startedAt: -1 });
|
|
614
|
+
this.initialized = true;
|
|
615
|
+
}
|
|
616
|
+
async close() {
|
|
617
|
+
if (this.client) {
|
|
618
|
+
await this.client.close();
|
|
619
|
+
this.client = void 0;
|
|
620
|
+
this.db = void 0;
|
|
621
|
+
}
|
|
622
|
+
this.initialized = false;
|
|
623
|
+
}
|
|
624
|
+
// =========================================================================
|
|
625
|
+
// Fact Operations
|
|
626
|
+
// =========================================================================
|
|
627
|
+
async getFacts(userId, filter) {
|
|
628
|
+
await this.ensureInitialized();
|
|
629
|
+
const collection = this.getCollection("facts");
|
|
630
|
+
const query = { userId };
|
|
631
|
+
if (filter?.subject) query.subject = filter.subject;
|
|
632
|
+
if (filter?.predicate) query.predicate = filter.predicate;
|
|
633
|
+
if (filter?.predicates?.length) {
|
|
634
|
+
query.predicate = { $in: filter.predicates };
|
|
635
|
+
}
|
|
636
|
+
if (filter?.validOnly !== false) {
|
|
637
|
+
query.invalidatedAt = null;
|
|
638
|
+
}
|
|
639
|
+
let cursor = collection.find(query);
|
|
640
|
+
if (filter?.orderBy) {
|
|
641
|
+
const dir = filter.orderDir === "asc" ? 1 : -1;
|
|
642
|
+
cursor = cursor.sort({ [filter.orderBy]: dir });
|
|
643
|
+
}
|
|
644
|
+
if (filter?.limit) {
|
|
645
|
+
cursor = cursor.limit(filter.limit);
|
|
646
|
+
}
|
|
647
|
+
const docs = await cursor.toArray();
|
|
648
|
+
return docs.map(this.docToFact);
|
|
649
|
+
}
|
|
650
|
+
async getFactById(userId, factId) {
|
|
651
|
+
await this.ensureInitialized();
|
|
652
|
+
const collection = this.getCollection("facts");
|
|
653
|
+
const doc = await collection.findOne({ userId, id: factId });
|
|
654
|
+
return doc ? this.docToFact(doc) : null;
|
|
655
|
+
}
|
|
656
|
+
async upsertFact(userId, fact) {
|
|
657
|
+
await this.ensureInitialized();
|
|
658
|
+
const collection = this.getCollection("facts");
|
|
659
|
+
const { v4: uuidv43 } = await import('uuid');
|
|
660
|
+
const now = /* @__PURE__ */ new Date();
|
|
661
|
+
const existing = await collection.findOne({
|
|
662
|
+
userId,
|
|
663
|
+
subject: fact.subject,
|
|
664
|
+
predicate: fact.predicate,
|
|
665
|
+
invalidatedAt: null
|
|
666
|
+
});
|
|
667
|
+
if (existing) {
|
|
668
|
+
await collection.updateOne(
|
|
669
|
+
{ _id: existing._id },
|
|
670
|
+
{
|
|
671
|
+
$set: {
|
|
672
|
+
object: fact.object,
|
|
673
|
+
confidence: fact.confidence,
|
|
674
|
+
source: fact.source,
|
|
675
|
+
updatedAt: now,
|
|
676
|
+
metadata: fact.metadata
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
);
|
|
680
|
+
return this.docToFact({
|
|
681
|
+
...existing,
|
|
682
|
+
object: fact.object,
|
|
683
|
+
updatedAt: now
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
const newFact = {
|
|
687
|
+
id: uuidv43(),
|
|
688
|
+
userId,
|
|
689
|
+
...fact,
|
|
690
|
+
createdAt: now,
|
|
691
|
+
updatedAt: now
|
|
692
|
+
};
|
|
693
|
+
await collection.insertOne(newFact);
|
|
694
|
+
return this.docToFact(newFact);
|
|
695
|
+
}
|
|
696
|
+
async updateFact(userId, factId, updates) {
|
|
697
|
+
await this.ensureInitialized();
|
|
698
|
+
const collection = this.getCollection("facts");
|
|
699
|
+
const result = await collection.findOneAndUpdate(
|
|
700
|
+
{ userId, id: factId },
|
|
701
|
+
{
|
|
702
|
+
$set: {
|
|
703
|
+
...updates,
|
|
704
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
705
|
+
}
|
|
706
|
+
},
|
|
707
|
+
{ returnDocument: "after" }
|
|
708
|
+
);
|
|
709
|
+
if (!result) {
|
|
710
|
+
throw new Error(`Fact not found: ${factId}`);
|
|
711
|
+
}
|
|
712
|
+
return this.docToFact(result);
|
|
713
|
+
}
|
|
714
|
+
async deleteFact(userId, factId, _reason) {
|
|
715
|
+
await this.ensureInitialized();
|
|
716
|
+
const collection = this.getCollection("facts");
|
|
717
|
+
await collection.updateOne(
|
|
718
|
+
{ userId, id: factId },
|
|
719
|
+
{ $set: { invalidatedAt: /* @__PURE__ */ new Date() } }
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
async hardDeleteFact(userId, factId) {
|
|
723
|
+
await this.ensureInitialized();
|
|
724
|
+
const collection = this.getCollection("facts");
|
|
725
|
+
await collection.deleteOne({ userId, id: factId });
|
|
726
|
+
}
|
|
727
|
+
// =========================================================================
|
|
728
|
+
// Conversation Operations
|
|
729
|
+
// =========================================================================
|
|
730
|
+
async getConversationHistory(userId, limit, sessionId) {
|
|
731
|
+
await this.ensureInitialized();
|
|
732
|
+
const collection = this.getCollection("conversations");
|
|
733
|
+
const query = { userId };
|
|
734
|
+
if (sessionId) query.sessionId = sessionId;
|
|
735
|
+
let cursor = collection.find(query).sort({ timestamp: -1 });
|
|
736
|
+
if (limit) {
|
|
737
|
+
cursor = cursor.limit(limit);
|
|
738
|
+
}
|
|
739
|
+
const docs = await cursor.toArray();
|
|
740
|
+
return docs.map(this.docToConversation);
|
|
741
|
+
}
|
|
742
|
+
async saveConversation(userId, exchange) {
|
|
743
|
+
await this.ensureInitialized();
|
|
744
|
+
const collection = this.getCollection("conversations");
|
|
745
|
+
const { v4: uuidv43 } = await import('uuid');
|
|
746
|
+
const newExchange = {
|
|
747
|
+
id: uuidv43(),
|
|
748
|
+
...exchange
|
|
749
|
+
};
|
|
750
|
+
await collection.insertOne(newExchange);
|
|
751
|
+
const sessionsCollection = this.getCollection("sessions");
|
|
752
|
+
await sessionsCollection.updateOne(
|
|
753
|
+
{ userId, id: exchange.sessionId },
|
|
754
|
+
{ $inc: { messageCount: 1 } }
|
|
755
|
+
);
|
|
756
|
+
return this.docToConversation(newExchange);
|
|
757
|
+
}
|
|
758
|
+
// =========================================================================
|
|
759
|
+
// Session Operations
|
|
760
|
+
// =========================================================================
|
|
761
|
+
async getSessions(userId, limit) {
|
|
762
|
+
await this.ensureInitialized();
|
|
763
|
+
const collection = this.getCollection("sessions");
|
|
764
|
+
let cursor = collection.find({ userId }).sort({ startedAt: -1 });
|
|
765
|
+
if (limit) {
|
|
766
|
+
cursor = cursor.limit(limit);
|
|
767
|
+
}
|
|
768
|
+
const docs = await cursor.toArray();
|
|
769
|
+
return docs.map(this.docToSession);
|
|
770
|
+
}
|
|
771
|
+
async getSession(userId, sessionId) {
|
|
772
|
+
await this.ensureInitialized();
|
|
773
|
+
const collection = this.getCollection("sessions");
|
|
774
|
+
const doc = await collection.findOne({ userId, id: sessionId });
|
|
775
|
+
return doc ? this.docToSession(doc) : null;
|
|
776
|
+
}
|
|
777
|
+
async createSession(userId) {
|
|
778
|
+
await this.ensureInitialized();
|
|
779
|
+
const collection = this.getCollection("sessions");
|
|
780
|
+
const { v4: uuidv43 } = await import('uuid');
|
|
781
|
+
const session = {
|
|
782
|
+
id: uuidv43(),
|
|
783
|
+
userId,
|
|
784
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
785
|
+
endedAt: null,
|
|
786
|
+
messageCount: 0
|
|
787
|
+
};
|
|
788
|
+
await collection.insertOne(session);
|
|
789
|
+
return this.docToSession(session);
|
|
790
|
+
}
|
|
791
|
+
async endSession(userId, sessionId, summary) {
|
|
792
|
+
await this.ensureInitialized();
|
|
793
|
+
const collection = this.getCollection("sessions");
|
|
794
|
+
const result = await collection.findOneAndUpdate(
|
|
795
|
+
{ userId, id: sessionId },
|
|
796
|
+
{
|
|
797
|
+
$set: {
|
|
798
|
+
endedAt: /* @__PURE__ */ new Date(),
|
|
799
|
+
...summary && { summary }
|
|
800
|
+
}
|
|
801
|
+
},
|
|
802
|
+
{ returnDocument: "after" }
|
|
803
|
+
);
|
|
804
|
+
if (!result) {
|
|
805
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
806
|
+
}
|
|
807
|
+
return this.docToSession(result);
|
|
808
|
+
}
|
|
809
|
+
// =========================================================================
|
|
810
|
+
// Helper Methods
|
|
811
|
+
// =========================================================================
|
|
812
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
813
|
+
docToFact(doc) {
|
|
814
|
+
return {
|
|
815
|
+
id: doc.id,
|
|
816
|
+
subject: doc.subject,
|
|
817
|
+
predicate: doc.predicate,
|
|
818
|
+
object: doc.object,
|
|
819
|
+
confidence: doc.confidence,
|
|
820
|
+
importance: doc.importance ?? 5,
|
|
821
|
+
source: doc.source,
|
|
822
|
+
sourceConversationId: doc.sourceConversationId,
|
|
823
|
+
createdAt: new Date(doc.createdAt),
|
|
824
|
+
updatedAt: new Date(doc.updatedAt),
|
|
825
|
+
invalidatedAt: doc.invalidatedAt ? new Date(doc.invalidatedAt) : null,
|
|
826
|
+
accessCount: doc.accessCount ?? 0,
|
|
827
|
+
lastAccessedAt: doc.lastAccessedAt ? new Date(doc.lastAccessedAt) : void 0,
|
|
828
|
+
metadata: doc.metadata
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
832
|
+
docToConversation(doc) {
|
|
833
|
+
return {
|
|
834
|
+
id: doc.id,
|
|
835
|
+
userId: doc.userId,
|
|
836
|
+
sessionId: doc.sessionId,
|
|
837
|
+
userMessage: doc.userMessage,
|
|
838
|
+
assistantResponse: doc.assistantResponse,
|
|
839
|
+
timestamp: new Date(doc.timestamp),
|
|
840
|
+
metadata: doc.metadata
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
844
|
+
docToSession(doc) {
|
|
845
|
+
return {
|
|
846
|
+
id: doc.id,
|
|
847
|
+
userId: doc.userId,
|
|
848
|
+
startedAt: new Date(doc.startedAt),
|
|
849
|
+
endedAt: doc.endedAt ? new Date(doc.endedAt) : null,
|
|
850
|
+
messageCount: doc.messageCount,
|
|
851
|
+
summary: doc.summary
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
};
|
|
855
|
+
|
|
856
|
+
// src/adapters/PostgresAdapter.ts
|
|
857
|
+
var PostgresAdapter = class extends BaseAdapter {
|
|
858
|
+
config;
|
|
859
|
+
pool;
|
|
860
|
+
schema;
|
|
861
|
+
constructor(config) {
|
|
862
|
+
super();
|
|
863
|
+
this.config = config;
|
|
864
|
+
this.schema = config.schema || "memts";
|
|
865
|
+
}
|
|
866
|
+
async getPool() {
|
|
867
|
+
if (this.pool) return this.pool;
|
|
868
|
+
try {
|
|
869
|
+
const { Pool } = await import('pg');
|
|
870
|
+
this.pool = new Pool({
|
|
871
|
+
connectionString: this.config.connectionString
|
|
872
|
+
});
|
|
873
|
+
return this.pool;
|
|
874
|
+
} catch {
|
|
875
|
+
throw new Error("PostgreSQL driver not installed. Run: npm install pg");
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
879
|
+
async query(sql, params) {
|
|
880
|
+
const pool = await this.getPool();
|
|
881
|
+
return pool.query(sql, params);
|
|
882
|
+
}
|
|
883
|
+
async initialize() {
|
|
884
|
+
await this.getPool();
|
|
885
|
+
await this.query(`CREATE SCHEMA IF NOT EXISTS ${this.schema}`);
|
|
886
|
+
await this.query(`
|
|
887
|
+
CREATE TABLE IF NOT EXISTS ${this.schema}.facts (
|
|
888
|
+
id UUID PRIMARY KEY,
|
|
889
|
+
user_id VARCHAR(255) NOT NULL,
|
|
890
|
+
subject VARCHAR(255) NOT NULL,
|
|
891
|
+
predicate VARCHAR(255) NOT NULL,
|
|
892
|
+
object TEXT NOT NULL,
|
|
893
|
+
confidence REAL DEFAULT 0.8,
|
|
894
|
+
source VARCHAR(255),
|
|
895
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
896
|
+
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
897
|
+
invalidated_at TIMESTAMPTZ,
|
|
898
|
+
metadata JSONB
|
|
899
|
+
)
|
|
900
|
+
`);
|
|
901
|
+
await this.query(`
|
|
902
|
+
CREATE INDEX IF NOT EXISTS idx_facts_user_subject_predicate
|
|
903
|
+
ON ${this.schema}.facts (user_id, subject, predicate)
|
|
904
|
+
`);
|
|
905
|
+
await this.query(`
|
|
906
|
+
CREATE INDEX IF NOT EXISTS idx_facts_user_valid
|
|
907
|
+
ON ${this.schema}.facts (user_id, invalidated_at)
|
|
908
|
+
`);
|
|
909
|
+
await this.query(`
|
|
910
|
+
CREATE TABLE IF NOT EXISTS ${this.schema}.conversations (
|
|
911
|
+
id UUID PRIMARY KEY,
|
|
912
|
+
user_id VARCHAR(255) NOT NULL,
|
|
913
|
+
session_id VARCHAR(255) NOT NULL,
|
|
914
|
+
user_message TEXT NOT NULL,
|
|
915
|
+
assistant_response TEXT NOT NULL,
|
|
916
|
+
timestamp TIMESTAMPTZ DEFAULT NOW(),
|
|
917
|
+
metadata JSONB
|
|
918
|
+
)
|
|
919
|
+
`);
|
|
920
|
+
await this.query(`
|
|
921
|
+
CREATE INDEX IF NOT EXISTS idx_conversations_user_session
|
|
922
|
+
ON ${this.schema}.conversations (user_id, session_id)
|
|
923
|
+
`);
|
|
924
|
+
await this.query(`
|
|
925
|
+
CREATE TABLE IF NOT EXISTS ${this.schema}.sessions (
|
|
926
|
+
id UUID PRIMARY KEY,
|
|
927
|
+
user_id VARCHAR(255) NOT NULL,
|
|
928
|
+
started_at TIMESTAMPTZ DEFAULT NOW(),
|
|
929
|
+
ended_at TIMESTAMPTZ,
|
|
930
|
+
message_count INTEGER DEFAULT 0,
|
|
931
|
+
summary TEXT
|
|
932
|
+
)
|
|
933
|
+
`);
|
|
934
|
+
await this.query(`
|
|
935
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_user
|
|
936
|
+
ON ${this.schema}.sessions (user_id, started_at DESC)
|
|
937
|
+
`);
|
|
938
|
+
this.initialized = true;
|
|
939
|
+
}
|
|
940
|
+
async close() {
|
|
941
|
+
if (this.pool) {
|
|
942
|
+
await this.pool.end();
|
|
943
|
+
this.pool = void 0;
|
|
944
|
+
}
|
|
945
|
+
this.initialized = false;
|
|
946
|
+
}
|
|
947
|
+
// =========================================================================
|
|
948
|
+
// Fact Operations
|
|
949
|
+
// =========================================================================
|
|
950
|
+
async getFacts(userId, filter) {
|
|
951
|
+
await this.ensureInitialized();
|
|
952
|
+
let sql = `SELECT * FROM ${this.schema}.facts WHERE user_id = $1`;
|
|
953
|
+
const params = [userId];
|
|
954
|
+
let paramIndex = 2;
|
|
955
|
+
if (filter?.subject) {
|
|
956
|
+
sql += ` AND subject = $${paramIndex++}`;
|
|
957
|
+
params.push(filter.subject);
|
|
958
|
+
}
|
|
959
|
+
if (filter?.predicate) {
|
|
960
|
+
sql += ` AND predicate = $${paramIndex++}`;
|
|
961
|
+
params.push(filter.predicate);
|
|
962
|
+
}
|
|
963
|
+
if (filter?.predicates?.length) {
|
|
964
|
+
const placeholders = filter.predicates.map((_, i) => `$${paramIndex + i}`).join(", ");
|
|
965
|
+
sql += ` AND predicate IN (${placeholders})`;
|
|
966
|
+
params.push(...filter.predicates);
|
|
967
|
+
paramIndex += filter.predicates.length;
|
|
968
|
+
}
|
|
969
|
+
if (filter?.validOnly !== false) {
|
|
970
|
+
sql += ` AND invalidated_at IS NULL`;
|
|
971
|
+
}
|
|
972
|
+
if (filter?.orderBy) {
|
|
973
|
+
const column = this.camelToSnake(filter.orderBy);
|
|
974
|
+
const dir = filter.orderDir === "asc" ? "ASC" : "DESC";
|
|
975
|
+
sql += ` ORDER BY ${column} ${dir}`;
|
|
976
|
+
}
|
|
977
|
+
if (filter?.limit) {
|
|
978
|
+
sql += ` LIMIT $${paramIndex}`;
|
|
979
|
+
params.push(filter.limit);
|
|
980
|
+
}
|
|
981
|
+
const result = await this.query(sql, params);
|
|
982
|
+
return result.rows.map(this.rowToFact.bind(this));
|
|
983
|
+
}
|
|
984
|
+
async getFactById(userId, factId) {
|
|
985
|
+
await this.ensureInitialized();
|
|
986
|
+
const result = await this.query(
|
|
987
|
+
`SELECT * FROM ${this.schema}.facts WHERE user_id = $1 AND id = $2`,
|
|
988
|
+
[userId, factId]
|
|
989
|
+
);
|
|
990
|
+
return result.rows[0] ? this.rowToFact(result.rows[0]) : null;
|
|
991
|
+
}
|
|
992
|
+
async upsertFact(userId, fact) {
|
|
993
|
+
await this.ensureInitialized();
|
|
994
|
+
const { v4: uuidv43 } = await import('uuid');
|
|
995
|
+
const existing = await this.query(
|
|
996
|
+
`SELECT id FROM ${this.schema}.facts
|
|
997
|
+
WHERE user_id = $1 AND subject = $2 AND predicate = $3 AND invalidated_at IS NULL`,
|
|
998
|
+
[userId, fact.subject, fact.predicate]
|
|
999
|
+
);
|
|
1000
|
+
if (existing.rows[0]) {
|
|
1001
|
+
const result2 = await this.query(
|
|
1002
|
+
`UPDATE ${this.schema}.facts
|
|
1003
|
+
SET object = $1, confidence = $2, source = $3, updated_at = NOW(), metadata = $4
|
|
1004
|
+
WHERE id = $5 RETURNING *`,
|
|
1005
|
+
[
|
|
1006
|
+
fact.object,
|
|
1007
|
+
fact.confidence,
|
|
1008
|
+
fact.source,
|
|
1009
|
+
fact.metadata || null,
|
|
1010
|
+
existing.rows[0].id
|
|
1011
|
+
]
|
|
1012
|
+
);
|
|
1013
|
+
return this.rowToFact(result2.rows[0]);
|
|
1014
|
+
}
|
|
1015
|
+
const id = uuidv43();
|
|
1016
|
+
const result = await this.query(
|
|
1017
|
+
`INSERT INTO ${this.schema}.facts
|
|
1018
|
+
(id, user_id, subject, predicate, object, confidence, source, invalidated_at, metadata)
|
|
1019
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING *`,
|
|
1020
|
+
[
|
|
1021
|
+
id,
|
|
1022
|
+
userId,
|
|
1023
|
+
fact.subject,
|
|
1024
|
+
fact.predicate,
|
|
1025
|
+
fact.object,
|
|
1026
|
+
fact.confidence,
|
|
1027
|
+
fact.source,
|
|
1028
|
+
null,
|
|
1029
|
+
fact.metadata || null
|
|
1030
|
+
]
|
|
1031
|
+
);
|
|
1032
|
+
return this.rowToFact(result.rows[0]);
|
|
1033
|
+
}
|
|
1034
|
+
async updateFact(userId, factId, updates) {
|
|
1035
|
+
await this.ensureInitialized();
|
|
1036
|
+
const setClauses = ["updated_at = NOW()"];
|
|
1037
|
+
const params = [];
|
|
1038
|
+
let paramIndex = 1;
|
|
1039
|
+
if (updates.object !== void 0) {
|
|
1040
|
+
setClauses.push(`object = $${paramIndex++}`);
|
|
1041
|
+
params.push(updates.object);
|
|
1042
|
+
}
|
|
1043
|
+
if (updates.confidence !== void 0) {
|
|
1044
|
+
setClauses.push(`confidence = $${paramIndex++}`);
|
|
1045
|
+
params.push(updates.confidence);
|
|
1046
|
+
}
|
|
1047
|
+
if (updates.metadata !== void 0) {
|
|
1048
|
+
setClauses.push(`metadata = $${paramIndex++}`);
|
|
1049
|
+
params.push(updates.metadata);
|
|
1050
|
+
}
|
|
1051
|
+
params.push(userId, factId);
|
|
1052
|
+
const result = await this.query(
|
|
1053
|
+
`UPDATE ${this.schema}.facts SET ${setClauses.join(", ")}
|
|
1054
|
+
WHERE user_id = $${paramIndex++} AND id = $${paramIndex} RETURNING *`,
|
|
1055
|
+
params
|
|
1056
|
+
);
|
|
1057
|
+
if (!result.rows[0]) {
|
|
1058
|
+
throw new Error(`Fact not found: ${factId}`);
|
|
1059
|
+
}
|
|
1060
|
+
return this.rowToFact(result.rows[0]);
|
|
1061
|
+
}
|
|
1062
|
+
async deleteFact(userId, factId, _reason) {
|
|
1063
|
+
await this.ensureInitialized();
|
|
1064
|
+
await this.query(
|
|
1065
|
+
`UPDATE ${this.schema}.facts SET invalidated_at = NOW() WHERE user_id = $1 AND id = $2`,
|
|
1066
|
+
[userId, factId]
|
|
1067
|
+
);
|
|
1068
|
+
}
|
|
1069
|
+
async hardDeleteFact(userId, factId) {
|
|
1070
|
+
await this.ensureInitialized();
|
|
1071
|
+
await this.query(
|
|
1072
|
+
`DELETE FROM ${this.schema}.facts WHERE user_id = $1 AND id = $2`,
|
|
1073
|
+
[userId, factId]
|
|
1074
|
+
);
|
|
1075
|
+
}
|
|
1076
|
+
// =========================================================================
|
|
1077
|
+
// Conversation Operations
|
|
1078
|
+
// =========================================================================
|
|
1079
|
+
async getConversationHistory(userId, limit, sessionId) {
|
|
1080
|
+
await this.ensureInitialized();
|
|
1081
|
+
let sql = `SELECT * FROM ${this.schema}.conversations WHERE user_id = $1`;
|
|
1082
|
+
const params = [userId];
|
|
1083
|
+
if (sessionId) {
|
|
1084
|
+
sql += ` AND session_id = $2`;
|
|
1085
|
+
params.push(sessionId);
|
|
1086
|
+
}
|
|
1087
|
+
sql += ` ORDER BY timestamp DESC`;
|
|
1088
|
+
if (limit) {
|
|
1089
|
+
sql += ` LIMIT $${params.length + 1}`;
|
|
1090
|
+
params.push(limit);
|
|
1091
|
+
}
|
|
1092
|
+
const result = await this.query(sql, params);
|
|
1093
|
+
return result.rows.map(this.rowToConversation.bind(this));
|
|
1094
|
+
}
|
|
1095
|
+
async saveConversation(userId, exchange) {
|
|
1096
|
+
await this.ensureInitialized();
|
|
1097
|
+
const { v4: uuidv43 } = await import('uuid');
|
|
1098
|
+
const id = uuidv43();
|
|
1099
|
+
const result = await this.query(
|
|
1100
|
+
`INSERT INTO ${this.schema}.conversations
|
|
1101
|
+
(id, user_id, session_id, user_message, assistant_response, timestamp, metadata)
|
|
1102
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *`,
|
|
1103
|
+
[
|
|
1104
|
+
id,
|
|
1105
|
+
userId,
|
|
1106
|
+
exchange.sessionId,
|
|
1107
|
+
exchange.userMessage,
|
|
1108
|
+
exchange.assistantResponse,
|
|
1109
|
+
exchange.timestamp,
|
|
1110
|
+
exchange.metadata || null
|
|
1111
|
+
]
|
|
1112
|
+
);
|
|
1113
|
+
await this.query(
|
|
1114
|
+
`UPDATE ${this.schema}.sessions SET message_count = message_count + 1 WHERE id = $1`,
|
|
1115
|
+
[exchange.sessionId]
|
|
1116
|
+
);
|
|
1117
|
+
return this.rowToConversation(result.rows[0]);
|
|
1118
|
+
}
|
|
1119
|
+
// =========================================================================
|
|
1120
|
+
// Session Operations
|
|
1121
|
+
// =========================================================================
|
|
1122
|
+
async getSessions(userId, limit) {
|
|
1123
|
+
await this.ensureInitialized();
|
|
1124
|
+
let sql = `SELECT * FROM ${this.schema}.sessions WHERE user_id = $1 ORDER BY started_at DESC`;
|
|
1125
|
+
const params = [userId];
|
|
1126
|
+
if (limit) {
|
|
1127
|
+
sql += ` LIMIT $2`;
|
|
1128
|
+
params.push(limit);
|
|
1129
|
+
}
|
|
1130
|
+
const result = await this.query(sql, params);
|
|
1131
|
+
return result.rows.map(this.rowToSession.bind(this));
|
|
1132
|
+
}
|
|
1133
|
+
async getSession(userId, sessionId) {
|
|
1134
|
+
await this.ensureInitialized();
|
|
1135
|
+
const result = await this.query(
|
|
1136
|
+
`SELECT * FROM ${this.schema}.sessions WHERE user_id = $1 AND id = $2`,
|
|
1137
|
+
[userId, sessionId]
|
|
1138
|
+
);
|
|
1139
|
+
return result.rows[0] ? this.rowToSession(result.rows[0]) : null;
|
|
1140
|
+
}
|
|
1141
|
+
async createSession(userId) {
|
|
1142
|
+
await this.ensureInitialized();
|
|
1143
|
+
const { v4: uuidv43 } = await import('uuid');
|
|
1144
|
+
const id = uuidv43();
|
|
1145
|
+
const result = await this.query(
|
|
1146
|
+
`INSERT INTO ${this.schema}.sessions (id, user_id) VALUES ($1, $2) RETURNING *`,
|
|
1147
|
+
[id, userId]
|
|
1148
|
+
);
|
|
1149
|
+
return this.rowToSession(result.rows[0]);
|
|
1150
|
+
}
|
|
1151
|
+
async endSession(userId, sessionId, summary) {
|
|
1152
|
+
await this.ensureInitialized();
|
|
1153
|
+
const result = await this.query(
|
|
1154
|
+
`UPDATE ${this.schema}.sessions
|
|
1155
|
+
SET ended_at = NOW(), summary = COALESCE($1, summary)
|
|
1156
|
+
WHERE user_id = $2 AND id = $3 RETURNING *`,
|
|
1157
|
+
[summary || null, userId, sessionId]
|
|
1158
|
+
);
|
|
1159
|
+
if (!result.rows[0]) {
|
|
1160
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
1161
|
+
}
|
|
1162
|
+
return this.rowToSession(result.rows[0]);
|
|
1163
|
+
}
|
|
1164
|
+
// =========================================================================
|
|
1165
|
+
// Helper Methods
|
|
1166
|
+
// =========================================================================
|
|
1167
|
+
camelToSnake(str) {
|
|
1168
|
+
return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
1169
|
+
}
|
|
1170
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1171
|
+
rowToFact(row) {
|
|
1172
|
+
return {
|
|
1173
|
+
id: row.id,
|
|
1174
|
+
subject: row.subject,
|
|
1175
|
+
predicate: row.predicate,
|
|
1176
|
+
object: row.object,
|
|
1177
|
+
confidence: row.confidence,
|
|
1178
|
+
importance: row.importance ?? 5,
|
|
1179
|
+
source: row.source,
|
|
1180
|
+
sourceConversationId: row.source_conversation_id,
|
|
1181
|
+
createdAt: new Date(row.created_at),
|
|
1182
|
+
updatedAt: new Date(row.updated_at),
|
|
1183
|
+
invalidatedAt: row.invalidated_at ? new Date(row.invalidated_at) : null,
|
|
1184
|
+
accessCount: row.access_count ?? 0,
|
|
1185
|
+
lastAccessedAt: row.last_accessed_at ? new Date(row.last_accessed_at) : void 0,
|
|
1186
|
+
metadata: row.metadata
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1190
|
+
rowToConversation(row) {
|
|
1191
|
+
return {
|
|
1192
|
+
id: row.id,
|
|
1193
|
+
userId: row.user_id,
|
|
1194
|
+
sessionId: row.session_id,
|
|
1195
|
+
userMessage: row.user_message,
|
|
1196
|
+
assistantResponse: row.assistant_response,
|
|
1197
|
+
timestamp: new Date(row.timestamp),
|
|
1198
|
+
metadata: row.metadata
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1202
|
+
rowToSession(row) {
|
|
1203
|
+
return {
|
|
1204
|
+
id: row.id,
|
|
1205
|
+
userId: row.user_id,
|
|
1206
|
+
startedAt: new Date(row.started_at),
|
|
1207
|
+
endedAt: row.ended_at ? new Date(row.ended_at) : null,
|
|
1208
|
+
messageCount: row.message_count,
|
|
1209
|
+
summary: row.summary
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
};
|
|
1213
|
+
|
|
1214
|
+
// src/adapters/UpstashRedisAdapter.ts
|
|
1215
|
+
var UpstashRedisAdapter = class extends BaseAdapter {
|
|
1216
|
+
config;
|
|
1217
|
+
prefix;
|
|
1218
|
+
defaultTtl;
|
|
1219
|
+
constructor(config) {
|
|
1220
|
+
super();
|
|
1221
|
+
this.config = config;
|
|
1222
|
+
this.prefix = config.prefix || "memts:";
|
|
1223
|
+
this.defaultTtl = config.cacheTtl || 3600;
|
|
1224
|
+
}
|
|
1225
|
+
async redis(command) {
|
|
1226
|
+
const response = await fetch(this.config.url, {
|
|
1227
|
+
method: "POST",
|
|
1228
|
+
headers: {
|
|
1229
|
+
Authorization: `Bearer ${this.config.token}`,
|
|
1230
|
+
"Content-Type": "application/json"
|
|
1231
|
+
},
|
|
1232
|
+
body: JSON.stringify(command)
|
|
1233
|
+
});
|
|
1234
|
+
if (!response.ok) {
|
|
1235
|
+
throw new Error(`Upstash Redis error: ${response.statusText}`);
|
|
1236
|
+
}
|
|
1237
|
+
const data = await response.json();
|
|
1238
|
+
return data.result;
|
|
1239
|
+
}
|
|
1240
|
+
key(userId, type) {
|
|
1241
|
+
return `${this.prefix}${userId}:${type}`;
|
|
1242
|
+
}
|
|
1243
|
+
async initialize() {
|
|
1244
|
+
await this.redis(["PING"]);
|
|
1245
|
+
this.initialized = true;
|
|
1246
|
+
}
|
|
1247
|
+
async close() {
|
|
1248
|
+
this.initialized = false;
|
|
1249
|
+
}
|
|
1250
|
+
// =========================================================================
|
|
1251
|
+
// Fact Operations
|
|
1252
|
+
// =========================================================================
|
|
1253
|
+
async getFacts(userId, filter) {
|
|
1254
|
+
await this.ensureInitialized();
|
|
1255
|
+
const data = await this.redis([
|
|
1256
|
+
"HGETALL",
|
|
1257
|
+
this.key(userId, "facts")
|
|
1258
|
+
]);
|
|
1259
|
+
if (!data) return [];
|
|
1260
|
+
let dataObj;
|
|
1261
|
+
if (Array.isArray(data)) {
|
|
1262
|
+
dataObj = {};
|
|
1263
|
+
for (let i = 0; i < data.length; i += 2) {
|
|
1264
|
+
if (data[i + 1]) {
|
|
1265
|
+
dataObj[data[i]] = data[i + 1];
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
} else if (typeof data === "object") {
|
|
1269
|
+
dataObj = data;
|
|
1270
|
+
} else {
|
|
1271
|
+
return [];
|
|
1272
|
+
}
|
|
1273
|
+
let facts = Object.values(dataObj).filter((v) => typeof v === "string").map((v) => JSON.parse(v)).map((f) => ({
|
|
1274
|
+
...f,
|
|
1275
|
+
createdAt: new Date(f.createdAt),
|
|
1276
|
+
updatedAt: new Date(f.updatedAt),
|
|
1277
|
+
invalidatedAt: f.invalidatedAt ? new Date(f.invalidatedAt) : null
|
|
1278
|
+
}));
|
|
1279
|
+
if (filter) {
|
|
1280
|
+
if (filter.subject) {
|
|
1281
|
+
facts = facts.filter((f) => f.subject === filter.subject);
|
|
1282
|
+
}
|
|
1283
|
+
if (filter.predicate) {
|
|
1284
|
+
facts = facts.filter((f) => f.predicate === filter.predicate);
|
|
1285
|
+
}
|
|
1286
|
+
if (filter.predicates?.length) {
|
|
1287
|
+
facts = facts.filter((f) => filter.predicates.includes(f.predicate));
|
|
1288
|
+
}
|
|
1289
|
+
if (filter.validOnly !== false) {
|
|
1290
|
+
facts = facts.filter((f) => f.invalidatedAt === null);
|
|
1291
|
+
}
|
|
1292
|
+
if (filter.orderBy) {
|
|
1293
|
+
facts.sort((a, b) => {
|
|
1294
|
+
const aVal = a[filter.orderBy];
|
|
1295
|
+
const bVal = b[filter.orderBy];
|
|
1296
|
+
if (aVal instanceof Date && bVal instanceof Date) {
|
|
1297
|
+
return filter.orderDir === "desc" ? bVal.getTime() - aVal.getTime() : aVal.getTime() - bVal.getTime();
|
|
1298
|
+
}
|
|
1299
|
+
if (typeof aVal === "number" && typeof bVal === "number") {
|
|
1300
|
+
return filter.orderDir === "desc" ? bVal - aVal : aVal - bVal;
|
|
1301
|
+
}
|
|
1302
|
+
return 0;
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1305
|
+
if (filter.limit) {
|
|
1306
|
+
facts = facts.slice(0, filter.limit);
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
return facts;
|
|
1310
|
+
}
|
|
1311
|
+
async getFactById(userId, factId) {
|
|
1312
|
+
await this.ensureInitialized();
|
|
1313
|
+
const data = await this.redis([
|
|
1314
|
+
"HGET",
|
|
1315
|
+
this.key(userId, "facts"),
|
|
1316
|
+
factId
|
|
1317
|
+
]);
|
|
1318
|
+
if (!data) return null;
|
|
1319
|
+
const fact = JSON.parse(data);
|
|
1320
|
+
return {
|
|
1321
|
+
...fact,
|
|
1322
|
+
createdAt: new Date(fact.createdAt),
|
|
1323
|
+
updatedAt: new Date(fact.updatedAt),
|
|
1324
|
+
invalidatedAt: fact.invalidatedAt ? new Date(fact.invalidatedAt) : null
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1327
|
+
async upsertFact(userId, fact) {
|
|
1328
|
+
await this.ensureInitialized();
|
|
1329
|
+
const { v4: uuidv43 } = await import('uuid');
|
|
1330
|
+
const existingFacts = await this.getFacts(userId, {
|
|
1331
|
+
subject: fact.subject,
|
|
1332
|
+
predicate: fact.predicate,
|
|
1333
|
+
validOnly: true
|
|
1334
|
+
});
|
|
1335
|
+
const now = /* @__PURE__ */ new Date();
|
|
1336
|
+
if (existingFacts.length > 0) {
|
|
1337
|
+
const existing = existingFacts[0];
|
|
1338
|
+
const updated = {
|
|
1339
|
+
...existing,
|
|
1340
|
+
...fact,
|
|
1341
|
+
updatedAt: now
|
|
1342
|
+
};
|
|
1343
|
+
await this.redis([
|
|
1344
|
+
"HSET",
|
|
1345
|
+
this.key(userId, "facts"),
|
|
1346
|
+
existing.id,
|
|
1347
|
+
JSON.stringify(updated)
|
|
1348
|
+
]);
|
|
1349
|
+
return updated;
|
|
1350
|
+
}
|
|
1351
|
+
const newFact = {
|
|
1352
|
+
...fact,
|
|
1353
|
+
id: uuidv43(),
|
|
1354
|
+
createdAt: now,
|
|
1355
|
+
updatedAt: now
|
|
1356
|
+
};
|
|
1357
|
+
await this.redis([
|
|
1358
|
+
"HSET",
|
|
1359
|
+
this.key(userId, "facts"),
|
|
1360
|
+
newFact.id,
|
|
1361
|
+
JSON.stringify(newFact)
|
|
1362
|
+
]);
|
|
1363
|
+
return newFact;
|
|
1364
|
+
}
|
|
1365
|
+
async updateFact(userId, factId, updates) {
|
|
1366
|
+
await this.ensureInitialized();
|
|
1367
|
+
const existing = await this.getFactById(userId, factId);
|
|
1368
|
+
if (!existing) {
|
|
1369
|
+
throw new Error(`Fact not found: ${factId}`);
|
|
1370
|
+
}
|
|
1371
|
+
const updated = {
|
|
1372
|
+
...existing,
|
|
1373
|
+
...updates,
|
|
1374
|
+
id: existing.id,
|
|
1375
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1376
|
+
};
|
|
1377
|
+
await this.redis([
|
|
1378
|
+
"HSET",
|
|
1379
|
+
this.key(userId, "facts"),
|
|
1380
|
+
factId,
|
|
1381
|
+
JSON.stringify(updated)
|
|
1382
|
+
]);
|
|
1383
|
+
return updated;
|
|
1384
|
+
}
|
|
1385
|
+
async deleteFact(userId, factId, _reason) {
|
|
1386
|
+
await this.ensureInitialized();
|
|
1387
|
+
const existing = await this.getFactById(userId, factId);
|
|
1388
|
+
if (existing) {
|
|
1389
|
+
existing.invalidatedAt = /* @__PURE__ */ new Date();
|
|
1390
|
+
await this.redis([
|
|
1391
|
+
"HSET",
|
|
1392
|
+
this.key(userId, "facts"),
|
|
1393
|
+
factId,
|
|
1394
|
+
JSON.stringify(existing)
|
|
1395
|
+
]);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
async hardDeleteFact(userId, factId) {
|
|
1399
|
+
await this.ensureInitialized();
|
|
1400
|
+
await this.redis(["HDEL", this.key(userId, "facts"), factId]);
|
|
1401
|
+
}
|
|
1402
|
+
// =========================================================================
|
|
1403
|
+
// Conversation Operations
|
|
1404
|
+
// =========================================================================
|
|
1405
|
+
async getConversationHistory(userId, limit, sessionId) {
|
|
1406
|
+
await this.ensureInitialized();
|
|
1407
|
+
const data = await this.redis([
|
|
1408
|
+
"LRANGE",
|
|
1409
|
+
this.key(userId, "conversations"),
|
|
1410
|
+
"0",
|
|
1411
|
+
"-1"
|
|
1412
|
+
]);
|
|
1413
|
+
if (!data || !Array.isArray(data)) return [];
|
|
1414
|
+
let conversations = data.map((v) => JSON.parse(v)).map((c) => ({
|
|
1415
|
+
...c,
|
|
1416
|
+
timestamp: new Date(c.timestamp)
|
|
1417
|
+
}));
|
|
1418
|
+
if (sessionId) {
|
|
1419
|
+
conversations = conversations.filter((c) => c.sessionId === sessionId);
|
|
1420
|
+
}
|
|
1421
|
+
conversations.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
|
|
1422
|
+
if (limit) {
|
|
1423
|
+
conversations = conversations.slice(0, limit);
|
|
1424
|
+
}
|
|
1425
|
+
return conversations;
|
|
1426
|
+
}
|
|
1427
|
+
async saveConversation(userId, exchange) {
|
|
1428
|
+
await this.ensureInitialized();
|
|
1429
|
+
const { v4: uuidv43 } = await import('uuid');
|
|
1430
|
+
const newExchange = {
|
|
1431
|
+
...exchange,
|
|
1432
|
+
id: uuidv43()
|
|
1433
|
+
};
|
|
1434
|
+
await this.redis([
|
|
1435
|
+
"LPUSH",
|
|
1436
|
+
this.key(userId, "conversations"),
|
|
1437
|
+
JSON.stringify(newExchange)
|
|
1438
|
+
]);
|
|
1439
|
+
const session = await this.getSession(userId, exchange.sessionId);
|
|
1440
|
+
if (session) {
|
|
1441
|
+
session.messageCount++;
|
|
1442
|
+
await this.redis([
|
|
1443
|
+
"HSET",
|
|
1444
|
+
this.key(userId, "sessions"),
|
|
1445
|
+
session.id,
|
|
1446
|
+
JSON.stringify(session)
|
|
1447
|
+
]);
|
|
1448
|
+
}
|
|
1449
|
+
return newExchange;
|
|
1450
|
+
}
|
|
1451
|
+
// =========================================================================
|
|
1452
|
+
// Session Operations
|
|
1453
|
+
// =========================================================================
|
|
1454
|
+
async getSessions(userId, limit) {
|
|
1455
|
+
await this.ensureInitialized();
|
|
1456
|
+
const data = await this.redis([
|
|
1457
|
+
"HGETALL",
|
|
1458
|
+
this.key(userId, "sessions")
|
|
1459
|
+
]);
|
|
1460
|
+
if (!data) return [];
|
|
1461
|
+
let dataObj;
|
|
1462
|
+
if (Array.isArray(data)) {
|
|
1463
|
+
dataObj = {};
|
|
1464
|
+
for (let i = 0; i < data.length; i += 2) {
|
|
1465
|
+
if (data[i + 1]) {
|
|
1466
|
+
dataObj[data[i]] = data[i + 1];
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
} else if (typeof data === "object") {
|
|
1470
|
+
dataObj = data;
|
|
1471
|
+
} else {
|
|
1472
|
+
return [];
|
|
1473
|
+
}
|
|
1474
|
+
let sessions = Object.values(dataObj).filter((v) => typeof v === "string").map((v) => JSON.parse(v)).map((s) => ({
|
|
1475
|
+
...s,
|
|
1476
|
+
startedAt: new Date(s.startedAt),
|
|
1477
|
+
endedAt: s.endedAt ? new Date(s.endedAt) : null
|
|
1478
|
+
}));
|
|
1479
|
+
sessions.sort((a, b) => b.startedAt.getTime() - a.startedAt.getTime());
|
|
1480
|
+
if (limit) {
|
|
1481
|
+
sessions = sessions.slice(0, limit);
|
|
1482
|
+
}
|
|
1483
|
+
return sessions;
|
|
1484
|
+
}
|
|
1485
|
+
async getSession(userId, sessionId) {
|
|
1486
|
+
await this.ensureInitialized();
|
|
1487
|
+
const data = await this.redis([
|
|
1488
|
+
"HGET",
|
|
1489
|
+
this.key(userId, "sessions"),
|
|
1490
|
+
sessionId
|
|
1491
|
+
]);
|
|
1492
|
+
if (!data) return null;
|
|
1493
|
+
const session = JSON.parse(data);
|
|
1494
|
+
return {
|
|
1495
|
+
...session,
|
|
1496
|
+
startedAt: new Date(session.startedAt),
|
|
1497
|
+
endedAt: session.endedAt ? new Date(session.endedAt) : null
|
|
1498
|
+
};
|
|
1499
|
+
}
|
|
1500
|
+
async createSession(userId) {
|
|
1501
|
+
await this.ensureInitialized();
|
|
1502
|
+
const { v4: uuidv43 } = await import('uuid');
|
|
1503
|
+
const session = {
|
|
1504
|
+
id: uuidv43(),
|
|
1505
|
+
userId,
|
|
1506
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
1507
|
+
endedAt: null,
|
|
1508
|
+
messageCount: 0
|
|
1509
|
+
};
|
|
1510
|
+
await this.redis([
|
|
1511
|
+
"HSET",
|
|
1512
|
+
this.key(userId, "sessions"),
|
|
1513
|
+
session.id,
|
|
1514
|
+
JSON.stringify(session)
|
|
1515
|
+
]);
|
|
1516
|
+
return session;
|
|
1517
|
+
}
|
|
1518
|
+
async endSession(userId, sessionId, summary) {
|
|
1519
|
+
await this.ensureInitialized();
|
|
1520
|
+
const session = await this.getSession(userId, sessionId);
|
|
1521
|
+
if (!session) {
|
|
1522
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
1523
|
+
}
|
|
1524
|
+
session.endedAt = /* @__PURE__ */ new Date();
|
|
1525
|
+
if (summary) {
|
|
1526
|
+
session.summary = summary;
|
|
1527
|
+
}
|
|
1528
|
+
await this.redis([
|
|
1529
|
+
"HSET",
|
|
1530
|
+
this.key(userId, "sessions"),
|
|
1531
|
+
sessionId,
|
|
1532
|
+
JSON.stringify(session)
|
|
1533
|
+
]);
|
|
1534
|
+
return session;
|
|
1535
|
+
}
|
|
1536
|
+
// =========================================================================
|
|
1537
|
+
// Utility Methods for Hot Cache
|
|
1538
|
+
// =========================================================================
|
|
1539
|
+
/**
|
|
1540
|
+
* Set TTL on a user's data (useful for expiring cache)
|
|
1541
|
+
* If no TTL is provided, uses the configured default TTL.
|
|
1542
|
+
*/
|
|
1543
|
+
async setUserTtl(userId, ttlSeconds) {
|
|
1544
|
+
const ttl = ttlSeconds ?? this.defaultTtl;
|
|
1545
|
+
await this.redis(["EXPIRE", this.key(userId, "facts"), String(ttl)]);
|
|
1546
|
+
await this.redis([
|
|
1547
|
+
"EXPIRE",
|
|
1548
|
+
this.key(userId, "conversations"),
|
|
1549
|
+
String(ttl)
|
|
1550
|
+
]);
|
|
1551
|
+
await this.redis(["EXPIRE", this.key(userId, "sessions"), String(ttl)]);
|
|
1552
|
+
}
|
|
1553
|
+
/**
|
|
1554
|
+
* Clear all data for a user
|
|
1555
|
+
*/
|
|
1556
|
+
async clearUser(userId) {
|
|
1557
|
+
await this.redis(["DEL", this.key(userId, "facts")]);
|
|
1558
|
+
await this.redis(["DEL", this.key(userId, "conversations")]);
|
|
1559
|
+
await this.redis(["DEL", this.key(userId, "sessions")]);
|
|
1560
|
+
}
|
|
1561
|
+
};
|
|
1562
|
+
|
|
1563
|
+
exports.BaseAdapter = BaseAdapter;
|
|
1564
|
+
exports.InMemoryAdapter = InMemoryAdapter;
|
|
1565
|
+
exports.JSONFileAdapter = JSONFileAdapter;
|
|
1566
|
+
exports.MongoDBAdapter = MongoDBAdapter;
|
|
1567
|
+
exports.PostgresAdapter = PostgresAdapter;
|
|
1568
|
+
exports.UpstashRedisAdapter = UpstashRedisAdapter;
|
|
1569
|
+
//# sourceMappingURL=index.js.map
|
|
1570
|
+
//# sourceMappingURL=index.js.map
|