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