claude-memory-layer 1.0.10 → 1.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +60 -0
- package/README.md +166 -2
- package/bootstrap-kb/decisions/decisions.md +244 -0
- package/bootstrap-kb/glossary/glossary.md +46 -0
- package/bootstrap-kb/modules/.claude-plugin.md +22 -0
- package/bootstrap-kb/modules/agents.md.md +15 -0
- package/bootstrap-kb/modules/claude.md.md +15 -0
- package/bootstrap-kb/modules/context.md.md +15 -0
- package/bootstrap-kb/modules/docs.md +18 -0
- package/bootstrap-kb/modules/handoff.md.md +15 -0
- package/bootstrap-kb/modules/package-lock.json.md +15 -0
- package/bootstrap-kb/modules/package.json.md +15 -0
- package/bootstrap-kb/modules/plan.md.md +15 -0
- package/bootstrap-kb/modules/readme.md.md +15 -0
- package/bootstrap-kb/modules/scripts.md +26 -0
- package/bootstrap-kb/modules/spec.md.md +15 -0
- package/bootstrap-kb/modules/specs.md +20 -0
- package/bootstrap-kb/modules/src.md +51 -0
- package/bootstrap-kb/modules/tests.md +42 -0
- package/bootstrap-kb/modules/tsconfig.json.md +15 -0
- package/bootstrap-kb/modules/vitest.config.ts.md +15 -0
- package/bootstrap-kb/overview/overview.md +40 -0
- package/bootstrap-kb/sources/manifest.json +950 -0
- package/bootstrap-kb/sources/manifest.md +227 -0
- package/bootstrap-kb/timeline/timeline.md +57 -0
- package/d.sh +3 -0
- package/deploy.sh +3 -0
- package/dist/cli/index.js +3577 -389
- package/dist/cli/index.js.map +4 -4
- package/dist/core/index.js +1383 -138
- package/dist/core/index.js.map +4 -4
- package/dist/hooks/post-tool-use.js +1917 -214
- package/dist/hooks/post-tool-use.js.map +4 -4
- package/dist/hooks/session-end.js +1813 -231
- package/dist/hooks/session-end.js.map +4 -4
- package/dist/hooks/session-start.js +1802 -205
- package/dist/hooks/session-start.js.map +4 -4
- package/dist/hooks/stop.js +1909 -248
- package/dist/hooks/stop.js.map +4 -4
- package/dist/hooks/user-prompt-submit.js +1861 -206
- package/dist/hooks/user-prompt-submit.js.map +4 -4
- package/dist/server/api/index.js +2341 -217
- package/dist/server/api/index.js.map +4 -4
- package/dist/server/index.js +2350 -226
- package/dist/server/index.js.map +4 -4
- package/dist/services/memory-service.js +1805 -206
- package/dist/services/memory-service.js.map +4 -4
- package/dist/ui/app.js +1447 -55
- package/dist/ui/index.html +318 -147
- package/dist/ui/style.css +892 -0
- package/docs/MCP_MEMORY_SERVICE_COMPARATIVE_REVIEW.md +271 -0
- package/docs/MEMU_ADOPTION.md +40 -0
- package/docs/OPERATIONS.md +18 -0
- package/memory/.claude-plugin/commands/2026-02-25.md +263 -0
- package/memory/_index.md +405 -0
- package/memory/default/uncategorized/2026-02-25.md +4839 -0
- package/memory/specs/20260207-dashboard-upgrade/2026-02-25.md +142 -0
- package/memory/specs/citations-system/2026-02-25.md +1121 -0
- package/memory/specs/endless-mode/2026-02-25.md +1392 -0
- package/memory/specs/entity-edge-model/2026-02-25.md +1263 -0
- package/memory/specs/evidence-aligner-v2/2026-02-25.md +1028 -0
- package/memory/specs/mcp-desktop-integration/2026-02-25.md +1334 -0
- package/memory/specs/post-tool-use-hook/2026-02-25.md +1164 -0
- package/memory/specs/private-tags/2026-02-25.md +1057 -0
- package/memory/specs/progressive-disclosure/2026-02-25.md +1436 -0
- package/memory/specs/task-entity-system/2026-02-25.md +924 -0
- package/memory/specs/vector-outbox-v2/2026-02-25.md +1510 -0
- package/memory/specs/web-viewer-ui/2026-02-25.md +1709 -0
- package/package.json +9 -2
- package/scripts/build.ts +6 -0
- package/scripts/fix-sync-gap.js +32 -0
- package/scripts/heartbeat-memory-orchestrator.sh +28 -0
- package/scripts/report-sync-gap.js +26 -0
- package/scripts/review-queue-auto-resolve.js +21 -0
- package/scripts/sync-gap-auto-heal.sh +17 -0
- package/specs/20260207-dashboard-upgrade/context.md +38 -0
- package/specs/20260207-dashboard-upgrade/spec.md +96 -0
- package/src/cli/index.ts +391 -60
- package/src/core/consolidated-store.ts +63 -1
- package/src/core/consolidation-worker.ts +115 -6
- package/src/core/event-store.ts +14 -0
- package/src/core/index.ts +1 -0
- package/src/core/ingest-interceptor.ts +80 -0
- package/src/core/markdown-mirror.ts +70 -0
- package/src/core/md-mirror.ts +92 -0
- package/src/core/mongo-sync-config.ts +165 -0
- package/src/core/mongo-sync-worker.ts +381 -0
- package/src/core/retriever.ts +540 -150
- package/src/core/sqlite-event-store.ts +794 -7
- package/src/core/sqlite-wrapper.ts +8 -0
- package/src/core/tag-taxonomy.ts +51 -0
- package/src/core/turn-state.ts +159 -0
- package/src/core/types.ts +51 -8
- package/src/core/vector-store.ts +21 -3
- package/src/hooks/post-tool-use.ts +68 -23
- package/src/hooks/session-end.ts +8 -3
- package/src/hooks/stop.ts +96 -25
- package/src/hooks/user-prompt-submit.ts +44 -5
- package/src/server/api/chat.ts +244 -0
- package/src/server/api/citations.ts +3 -3
- package/src/server/api/events.ts +30 -5
- package/src/server/api/health.ts +53 -0
- package/src/server/api/index.ts +9 -1
- package/src/server/api/projects.ts +74 -0
- package/src/server/api/search.ts +3 -3
- package/src/server/api/sessions.ts +3 -3
- package/src/server/api/stats.ts +89 -8
- package/src/server/api/turns.ts +143 -0
- package/src/server/api/utils.ts +46 -0
- package/src/services/bootstrap-organizer.ts +443 -0
- package/src/services/codex-session-history-importer.ts +474 -0
- package/src/services/memory-service.ts +508 -71
- package/src/services/session-history-importer.ts +215 -51
- package/src/ui/app.js +1447 -55
- package/src/ui/index.html +318 -147
- package/src/ui/style.css +892 -0
- package/tests/bootstrap-organizer.test.ts +111 -0
- package/tests/consolidation-worker.test.ts +75 -0
- package/tests/ingest-interceptor.test.ts +38 -0
- package/tests/markdown-mirror.test.ts +85 -0
- package/tests/md-mirror.test.ts +50 -0
- package/tests/retriever-fallback-chain.test.ts +223 -0
- package/tests/retriever-strategy-scope.test.ts +97 -0
- package/tests/retriever.memu-adoption.test.ts +122 -0
- package/tests/sqlite-event-store-replication.test.ts +92 -0
- package/.claude/settings.local.json +0 -27
- package/.claude-memory/test.sqlite +0 -0
- package/.history/package_20260201112328.json +0 -45
- package/.history/package_20260201113602.json +0 -45
- package/.history/package_20260201113713.json +0 -45
- package/.history/package_20260201114110.json +0 -45
- package/.history/package_20260201114632.json +0 -46
- package/.history/package_20260201133143.json +0 -45
- package/.history/package_20260201134319.json +0 -45
- package/.history/package_20260201134326.json +0 -45
- package/.history/package_20260201134334.json +0 -45
- package/.history/package_20260201134912.json +0 -45
- package/.history/package_20260201142928.json +0 -46
- package/.history/package_20260201192048.json +0 -47
- package/.history/package_20260202114053.json +0 -49
- package/.history/package_20260202121115.json +0 -49
- package/test_access.js +0 -49
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mongo Sync Worker
|
|
3
|
+
* Optional: sync per-project SQLite events to a shared MongoDB database.
|
|
4
|
+
*
|
|
5
|
+
* Design goals:
|
|
6
|
+
* - Optional and decoupled (doesn't affect default local-only flow)
|
|
7
|
+
* - Idempotent (safe to retry)
|
|
8
|
+
* - Incremental push (SQLite rowid) and incremental pull (Mongo seq per project)
|
|
9
|
+
*
|
|
10
|
+
* NOTE:
|
|
11
|
+
* - We only sync immutable L0 events (events table). Derived tables can be rebuilt.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { randomUUID } from 'crypto';
|
|
15
|
+
import * as os from 'os';
|
|
16
|
+
import { MongoClient } from 'mongodb';
|
|
17
|
+
import type { Collection, Db } from 'mongodb';
|
|
18
|
+
|
|
19
|
+
import type { MemoryEvent } from './types.js';
|
|
20
|
+
import { SQLiteEventStore } from './sqlite-event-store.js';
|
|
21
|
+
|
|
22
|
+
export type MongoSyncDirection = 'push' | 'pull' | 'both';
|
|
23
|
+
|
|
24
|
+
export interface MongoSyncWorkerConfig {
|
|
25
|
+
uri: string;
|
|
26
|
+
dbName: string;
|
|
27
|
+
projectKey: string;
|
|
28
|
+
direction?: MongoSyncDirection;
|
|
29
|
+
intervalMs?: number;
|
|
30
|
+
batchSize?: number;
|
|
31
|
+
instanceId?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface MongoSyncStats {
|
|
35
|
+
lastSyncAt: Date | null;
|
|
36
|
+
pushedEvents: number;
|
|
37
|
+
pulledEvents: number;
|
|
38
|
+
errors: number;
|
|
39
|
+
status: 'idle' | 'syncing' | 'error' | 'stopped';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface CounterDoc {
|
|
43
|
+
_id: string;
|
|
44
|
+
seq: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface RemoteEventDoc {
|
|
48
|
+
_id: string;
|
|
49
|
+
projectKey: string;
|
|
50
|
+
seq: number;
|
|
51
|
+
eventId: string;
|
|
52
|
+
eventType: string;
|
|
53
|
+
sessionId: string;
|
|
54
|
+
timestamp: Date;
|
|
55
|
+
content: string;
|
|
56
|
+
canonicalKey: string;
|
|
57
|
+
dedupeKey: string;
|
|
58
|
+
metadata?: Record<string, unknown> | null;
|
|
59
|
+
insertedAt: Date;
|
|
60
|
+
updatedAt: Date;
|
|
61
|
+
source?: {
|
|
62
|
+
hostname?: string;
|
|
63
|
+
instanceId?: string;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function redactMongoUri(uri: string): string {
|
|
68
|
+
// mongodb://user:pass@host:port/ -> mongodb://user:***@host:port/
|
|
69
|
+
// mongodb+srv://user:pass@host/ -> mongodb+srv://user:***@host/
|
|
70
|
+
const schemeIdx = uri.indexOf('://');
|
|
71
|
+
if (schemeIdx === -1) return uri;
|
|
72
|
+
const atIdx = uri.indexOf('@', schemeIdx + 3);
|
|
73
|
+
if (atIdx === -1) return uri;
|
|
74
|
+
|
|
75
|
+
const creds = uri.slice(schemeIdx + 3, atIdx); // user:pass
|
|
76
|
+
const colonIdx = creds.indexOf(':');
|
|
77
|
+
if (colonIdx === -1) return uri;
|
|
78
|
+
|
|
79
|
+
const prefix = uri.slice(0, schemeIdx + 3 + colonIdx + 1);
|
|
80
|
+
const suffix = uri.slice(atIdx);
|
|
81
|
+
return `${prefix}***${suffix}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function parseIntOrZero(value: string | null | undefined): number {
|
|
85
|
+
if (!value) return 0;
|
|
86
|
+
const n = parseInt(value, 10);
|
|
87
|
+
return Number.isFinite(n) ? n : 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export class MongoSyncWorker {
|
|
91
|
+
private readonly config: Required<Omit<MongoSyncWorkerConfig, 'instanceId'>> & { instanceId: string };
|
|
92
|
+
private intervalHandle: NodeJS.Timeout | null = null;
|
|
93
|
+
private running = false;
|
|
94
|
+
|
|
95
|
+
private client: MongoClient | null = null;
|
|
96
|
+
private db: Db | null = null;
|
|
97
|
+
private counters: Collection<CounterDoc> | null = null;
|
|
98
|
+
private events: Collection<RemoteEventDoc> | null = null;
|
|
99
|
+
private indexesEnsured = false;
|
|
100
|
+
|
|
101
|
+
private stats: MongoSyncStats = {
|
|
102
|
+
lastSyncAt: null,
|
|
103
|
+
pushedEvents: 0,
|
|
104
|
+
pulledEvents: 0,
|
|
105
|
+
errors: 0,
|
|
106
|
+
status: 'idle'
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
constructor(
|
|
110
|
+
private readonly sqliteStore: SQLiteEventStore,
|
|
111
|
+
config: MongoSyncWorkerConfig
|
|
112
|
+
) {
|
|
113
|
+
this.config = {
|
|
114
|
+
uri: config.uri,
|
|
115
|
+
dbName: config.dbName,
|
|
116
|
+
projectKey: config.projectKey,
|
|
117
|
+
direction: config.direction ?? 'both',
|
|
118
|
+
intervalMs: config.intervalMs ?? 30000,
|
|
119
|
+
batchSize: config.batchSize ?? 500,
|
|
120
|
+
instanceId: config.instanceId ?? randomUUID()
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
start(): void {
|
|
125
|
+
if (this.running) return;
|
|
126
|
+
this.running = true;
|
|
127
|
+
this.stats.status = 'idle';
|
|
128
|
+
|
|
129
|
+
// Initial sync
|
|
130
|
+
this.syncNow().catch((err) => {
|
|
131
|
+
console.error('[MongoSyncWorker] Initial sync failed:', err);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Periodic sync
|
|
135
|
+
this.intervalHandle = setInterval(() => {
|
|
136
|
+
this.syncNow().catch((err) => {
|
|
137
|
+
console.error('[MongoSyncWorker] Periodic sync failed:', err);
|
|
138
|
+
});
|
|
139
|
+
}, this.config.intervalMs);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
stop(): void {
|
|
143
|
+
this.running = false;
|
|
144
|
+
this.stats.status = 'stopped';
|
|
145
|
+
|
|
146
|
+
if (this.intervalHandle) {
|
|
147
|
+
clearInterval(this.intervalHandle);
|
|
148
|
+
this.intervalHandle = null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async shutdown(): Promise<void> {
|
|
153
|
+
this.stop();
|
|
154
|
+
await this.disconnect();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
getStats(): MongoSyncStats {
|
|
158
|
+
return { ...this.stats };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
isRunning(): boolean {
|
|
162
|
+
return this.running;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async syncNow(): Promise<{ pushed: number; pulled: number }> {
|
|
166
|
+
if (this.stats.status === 'syncing') return { pushed: 0, pulled: 0 };
|
|
167
|
+
|
|
168
|
+
this.stats.status = 'syncing';
|
|
169
|
+
let pushed = 0;
|
|
170
|
+
let pulled = 0;
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
await this.sqliteStore.initialize();
|
|
174
|
+
await this.ensureConnected();
|
|
175
|
+
await this.ensureIndexes();
|
|
176
|
+
|
|
177
|
+
if (this.config.direction === 'push' || this.config.direction === 'both') {
|
|
178
|
+
pushed = await this.pushEvents();
|
|
179
|
+
this.stats.pushedEvents += pushed;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (this.config.direction === 'pull' || this.config.direction === 'both') {
|
|
183
|
+
pulled = await this.pullEvents();
|
|
184
|
+
this.stats.pulledEvents += pulled;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
this.stats.lastSyncAt = new Date();
|
|
188
|
+
this.stats.status = 'idle';
|
|
189
|
+
return { pushed, pulled };
|
|
190
|
+
} catch (error) {
|
|
191
|
+
this.stats.errors++;
|
|
192
|
+
this.stats.status = 'error';
|
|
193
|
+
throw error;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private async ensureConnected(): Promise<void> {
|
|
198
|
+
if (this.client && this.db && this.counters && this.events) return;
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
this.client = new MongoClient(this.config.uri, {
|
|
202
|
+
appName: 'claude-memory-layer',
|
|
203
|
+
serverSelectionTimeoutMS: 5000
|
|
204
|
+
});
|
|
205
|
+
await this.client.connect();
|
|
206
|
+
this.db = this.client.db(this.config.dbName);
|
|
207
|
+
this.counters = this.db.collection<CounterDoc>('cml_counters');
|
|
208
|
+
this.events = this.db.collection<RemoteEventDoc>('cml_events');
|
|
209
|
+
} catch (err) {
|
|
210
|
+
// Avoid leaking credentials in logs
|
|
211
|
+
const safeUri = redactMongoUri(this.config.uri);
|
|
212
|
+
throw new Error(`MongoDB connection failed (${safeUri}, db=${this.config.dbName}): ${String(err)}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private async disconnect(): Promise<void> {
|
|
217
|
+
try {
|
|
218
|
+
await this.client?.close();
|
|
219
|
+
} finally {
|
|
220
|
+
this.client = null;
|
|
221
|
+
this.db = null;
|
|
222
|
+
this.counters = null;
|
|
223
|
+
this.events = null;
|
|
224
|
+
this.indexesEnsured = false;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private async ensureIndexes(): Promise<void> {
|
|
229
|
+
if (this.indexesEnsured) return;
|
|
230
|
+
if (!this.events || !this.counters) throw new Error('Mongo not connected');
|
|
231
|
+
|
|
232
|
+
// Best-effort: if the user lacks index privileges, sync can still work (slower)
|
|
233
|
+
try {
|
|
234
|
+
await this.events.createIndex({ projectKey: 1, seq: 1 }, { unique: true });
|
|
235
|
+
await this.events.createIndex({ projectKey: 1, eventId: 1 }, { unique: true });
|
|
236
|
+
await this.events.createIndex({ projectKey: 1, dedupeKey: 1 });
|
|
237
|
+
} catch (err) {
|
|
238
|
+
console.warn('[MongoSyncWorker] Failed to ensure indexes (continuing):', err);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
this.indexesEnsured = true;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
private counterKey(kind: 'events'): string {
|
|
245
|
+
return `${kind}:${this.config.projectKey}`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
private async allocateSeqRange(kind: 'events', count: number): Promise<number> {
|
|
249
|
+
if (!this.counters) throw new Error('Mongo not connected');
|
|
250
|
+
if (count <= 0) return 1;
|
|
251
|
+
|
|
252
|
+
const key = this.counterKey(kind);
|
|
253
|
+
const doc = await this.counters.findOneAndUpdate(
|
|
254
|
+
{ _id: key },
|
|
255
|
+
{ $inc: { seq: count } },
|
|
256
|
+
{ upsert: true, returnDocument: 'after' }
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
const endSeq = doc?.seq;
|
|
260
|
+
if (typeof endSeq !== 'number') {
|
|
261
|
+
throw new Error(`Failed to allocate seq range for ${key}`);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return endSeq - count + 1;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
private pushTargetName(): string {
|
|
268
|
+
return `mongo_push_events_rowid:${this.config.projectKey}`;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private pullTargetName(): string {
|
|
272
|
+
return `mongo_pull_events_seq:${this.config.projectKey}`;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
private async pushEvents(): Promise<number> {
|
|
276
|
+
if (!this.events) throw new Error('Mongo not connected');
|
|
277
|
+
|
|
278
|
+
const position = await this.sqliteStore.getSyncPosition(this.pushTargetName());
|
|
279
|
+
let lastRowid = parseIntOrZero(position.lastEventId);
|
|
280
|
+
|
|
281
|
+
let pushed = 0;
|
|
282
|
+
|
|
283
|
+
while (true) {
|
|
284
|
+
const batch = await this.sqliteStore.getEventsSinceRowid(lastRowid, this.config.batchSize);
|
|
285
|
+
if (batch.length === 0) break;
|
|
286
|
+
|
|
287
|
+
const startSeq = await this.allocateSeqRange('events', batch.length);
|
|
288
|
+
const now = new Date();
|
|
289
|
+
const hostname = os.hostname();
|
|
290
|
+
|
|
291
|
+
const ops = batch.map((item, idx) => {
|
|
292
|
+
const event = item.event as unknown as MemoryEvent;
|
|
293
|
+
const seq = startSeq + idx;
|
|
294
|
+
const docId = `${this.config.projectKey}:${event.id}`;
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
updateOne: {
|
|
298
|
+
filter: { _id: docId },
|
|
299
|
+
update: {
|
|
300
|
+
$setOnInsert: {
|
|
301
|
+
_id: docId,
|
|
302
|
+
projectKey: this.config.projectKey,
|
|
303
|
+
seq,
|
|
304
|
+
eventId: event.id,
|
|
305
|
+
eventType: event.eventType,
|
|
306
|
+
sessionId: event.sessionId,
|
|
307
|
+
timestamp: event.timestamp,
|
|
308
|
+
content: event.content,
|
|
309
|
+
canonicalKey: event.canonicalKey,
|
|
310
|
+
dedupeKey: event.dedupeKey,
|
|
311
|
+
metadata: event.metadata ?? null,
|
|
312
|
+
insertedAt: now,
|
|
313
|
+
updatedAt: now,
|
|
314
|
+
source: { hostname, instanceId: this.config.instanceId }
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
upsert: true
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
await this.events.bulkWrite(ops, { ordered: false });
|
|
323
|
+
|
|
324
|
+
const last = batch[batch.length - 1];
|
|
325
|
+
lastRowid = last.rowid;
|
|
326
|
+
await this.sqliteStore.updateSyncPosition(
|
|
327
|
+
this.pushTargetName(),
|
|
328
|
+
String(lastRowid),
|
|
329
|
+
last.event.timestamp.toISOString()
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
pushed += batch.length;
|
|
333
|
+
if (batch.length < this.config.batchSize) break;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return pushed;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
private async pullEvents(): Promise<number> {
|
|
340
|
+
if (!this.events) throw new Error('Mongo not connected');
|
|
341
|
+
|
|
342
|
+
const position = await this.sqliteStore.getSyncPosition(this.pullTargetName());
|
|
343
|
+
let lastSeq = parseIntOrZero(position.lastEventId);
|
|
344
|
+
|
|
345
|
+
let pulled = 0;
|
|
346
|
+
|
|
347
|
+
while (true) {
|
|
348
|
+
const docs = await this.events.find(
|
|
349
|
+
{ projectKey: this.config.projectKey, seq: { $gt: lastSeq } },
|
|
350
|
+
{ sort: { seq: 1 }, limit: this.config.batchSize }
|
|
351
|
+
).toArray();
|
|
352
|
+
|
|
353
|
+
if (docs.length === 0) break;
|
|
354
|
+
|
|
355
|
+
const events: MemoryEvent[] = docs.map((d) => ({
|
|
356
|
+
id: d.eventId,
|
|
357
|
+
eventType: d.eventType as any,
|
|
358
|
+
sessionId: d.sessionId,
|
|
359
|
+
timestamp: d.timestamp instanceof Date ? d.timestamp : new Date(d.timestamp),
|
|
360
|
+
content: d.content,
|
|
361
|
+
canonicalKey: d.canonicalKey,
|
|
362
|
+
dedupeKey: d.dedupeKey,
|
|
363
|
+
metadata: d.metadata ?? undefined
|
|
364
|
+
}));
|
|
365
|
+
|
|
366
|
+
const result = await this.sqliteStore.importEvents(events);
|
|
367
|
+
pulled += result.inserted;
|
|
368
|
+
|
|
369
|
+
lastSeq = docs[docs.length - 1].seq;
|
|
370
|
+
await this.sqliteStore.updateSyncPosition(
|
|
371
|
+
this.pullTargetName(),
|
|
372
|
+
String(lastSeq),
|
|
373
|
+
new Date().toISOString()
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
if (docs.length < this.config.batchSize) break;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return pulled;
|
|
380
|
+
}
|
|
381
|
+
}
|