@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,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