@traqr/memory 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +135 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/auth.d.ts +18 -0
- package/dist/lib/auth.d.ts.map +1 -0
- package/dist/lib/auth.js +31 -0
- package/dist/lib/auth.js.map +1 -0
- package/dist/lib/auto-derive.d.ts +35 -0
- package/dist/lib/auto-derive.js +261 -0
- package/dist/lib/auto-derive.js.map +1 -0
- package/dist/lib/borderline.d.ts +26 -0
- package/dist/lib/borderline.js +121 -0
- package/dist/lib/borderline.js.map +1 -0
- package/dist/lib/client.d.ts +28 -0
- package/dist/lib/client.d.ts.map +1 -0
- package/dist/lib/client.js +60 -0
- package/dist/lib/client.js.map +1 -0
- package/dist/lib/context.d.ts +38 -0
- package/dist/lib/context.d.ts.map +1 -0
- package/dist/lib/context.js +334 -0
- package/dist/lib/context.js.map +1 -0
- package/dist/lib/embeddings.d.ts +60 -0
- package/dist/lib/embeddings.d.ts.map +1 -0
- package/dist/lib/embeddings.js +229 -0
- package/dist/lib/embeddings.js.map +1 -0
- package/dist/lib/entity-pipeline.d.ts +23 -0
- package/dist/lib/entity-pipeline.js +151 -0
- package/dist/lib/entity-pipeline.js.map +1 -0
- package/dist/lib/formatting.d.ts +13 -0
- package/dist/lib/formatting.d.ts.map +1 -0
- package/dist/lib/formatting.js +60 -0
- package/dist/lib/formatting.js.map +1 -0
- package/dist/lib/learning-extractor.d.ts +144 -0
- package/dist/lib/learning-extractor.d.ts.map +1 -0
- package/dist/lib/learning-extractor.js +921 -0
- package/dist/lib/learning-extractor.js.map +1 -0
- package/dist/lib/lifecycle.d.ts +45 -0
- package/dist/lib/lifecycle.js +84 -0
- package/dist/lib/lifecycle.js.map +1 -0
- package/dist/lib/memory.d.ts +128 -0
- package/dist/lib/memory.d.ts.map +1 -0
- package/dist/lib/memory.js +590 -0
- package/dist/lib/memory.js.map +1 -0
- package/dist/lib/quality-gate.d.ts +32 -0
- package/dist/lib/quality-gate.js +158 -0
- package/dist/lib/quality-gate.js.map +1 -0
- package/dist/lib/quality-gate.test.d.ts +7 -0
- package/dist/lib/quality-gate.test.js +75 -0
- package/dist/lib/quality-gate.test.js.map +1 -0
- package/dist/lib/rerank.d.ts +22 -0
- package/dist/lib/rerank.js +61 -0
- package/dist/lib/rerank.js.map +1 -0
- package/dist/lib/retrieval.d.ts +75 -0
- package/dist/lib/retrieval.js +380 -0
- package/dist/lib/retrieval.js.map +1 -0
- package/dist/migrate.d.ts +17 -0
- package/dist/migrate.d.ts.map +1 -0
- package/dist/migrate.js +81 -0
- package/dist/migrate.js.map +1 -0
- package/dist/routes/analyze-codebase.d.ts +9 -0
- package/dist/routes/analyze-codebase.d.ts.map +1 -0
- package/dist/routes/analyze-codebase.js +70 -0
- package/dist/routes/analyze-codebase.js.map +1 -0
- package/dist/routes/analyze-voice.d.ts +9 -0
- package/dist/routes/analyze-voice.d.ts.map +1 -0
- package/dist/routes/analyze-voice.js +63 -0
- package/dist/routes/analyze-voice.js.map +1 -0
- package/dist/routes/assemble-context.d.ts +9 -0
- package/dist/routes/assemble-context.d.ts.map +1 -0
- package/dist/routes/assemble-context.js +68 -0
- package/dist/routes/assemble-context.js.map +1 -0
- package/dist/routes/bootstrap.d.ts +12 -0
- package/dist/routes/bootstrap.d.ts.map +1 -0
- package/dist/routes/bootstrap.js +102 -0
- package/dist/routes/bootstrap.js.map +1 -0
- package/dist/routes/browse.d.ts +11 -0
- package/dist/routes/browse.js +85 -0
- package/dist/routes/browse.js.map +1 -0
- package/dist/routes/capture-thought.d.ts +13 -0
- package/dist/routes/capture-thought.d.ts.map +1 -0
- package/dist/routes/capture-thought.js +178 -0
- package/dist/routes/capture-thought.js.map +1 -0
- package/dist/routes/capture.d.ts +13 -0
- package/dist/routes/capture.d.ts.map +1 -0
- package/dist/routes/capture.js +86 -0
- package/dist/routes/capture.js.map +1 -0
- package/dist/routes/cite.d.ts +9 -0
- package/dist/routes/cite.d.ts.map +1 -0
- package/dist/routes/cite.js +49 -0
- package/dist/routes/cite.js.map +1 -0
- package/dist/routes/crud.d.ts +11 -0
- package/dist/routes/crud.d.ts.map +1 -0
- package/dist/routes/crud.js +176 -0
- package/dist/routes/crud.js.map +1 -0
- package/dist/routes/dashboard.d.ts +9 -0
- package/dist/routes/dashboard.d.ts.map +1 -0
- package/dist/routes/dashboard.js +85 -0
- package/dist/routes/dashboard.js.map +1 -0
- package/dist/routes/entity-cron.d.ts +8 -0
- package/dist/routes/entity-cron.js +31 -0
- package/dist/routes/entity-cron.js.map +1 -0
- package/dist/routes/export.d.ts +8 -0
- package/dist/routes/export.d.ts.map +1 -0
- package/dist/routes/export.js +69 -0
- package/dist/routes/export.js.map +1 -0
- package/dist/routes/extract-pr-learnings.d.ts +12 -0
- package/dist/routes/extract-pr-learnings.d.ts.map +1 -0
- package/dist/routes/extract-pr-learnings.js +127 -0
- package/dist/routes/extract-pr-learnings.js.map +1 -0
- package/dist/routes/forget-cron.d.ts +9 -0
- package/dist/routes/forget-cron.js +30 -0
- package/dist/routes/forget-cron.js.map +1 -0
- package/dist/routes/learnings.d.ts +9 -0
- package/dist/routes/learnings.d.ts.map +1 -0
- package/dist/routes/learnings.js +237 -0
- package/dist/routes/learnings.js.map +1 -0
- package/dist/routes/pulse.d.ts +9 -0
- package/dist/routes/pulse.d.ts.map +1 -0
- package/dist/routes/pulse.js +133 -0
- package/dist/routes/pulse.js.map +1 -0
- package/dist/routes/search.d.ts +8 -0
- package/dist/routes/search.d.ts.map +1 -0
- package/dist/routes/search.js +107 -0
- package/dist/routes/search.js.map +1 -0
- package/dist/routes/store.d.ts +8 -0
- package/dist/routes/store.d.ts.map +1 -0
- package/dist/routes/store.js +89 -0
- package/dist/routes/store.js.map +1 -0
- package/dist/routes/sync.d.ts +12 -0
- package/dist/routes/sync.d.ts.map +1 -0
- package/dist/routes/sync.js +83 -0
- package/dist/routes/sync.js.map +1 -0
- package/dist/routes/voice-profile.d.ts +9 -0
- package/dist/routes/voice-profile.d.ts.map +1 -0
- package/dist/routes/voice-profile.js +124 -0
- package/dist/routes/voice-profile.js.map +1 -0
- package/dist/server.d.ts +37 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +99 -0
- package/dist/server.js.map +1 -0
- package/dist/vectordb/index.d.ts +17 -0
- package/dist/vectordb/index.d.ts.map +1 -0
- package/dist/vectordb/index.js +39 -0
- package/dist/vectordb/index.js.map +1 -0
- package/dist/vectordb/supabase.d.ts +62 -0
- package/dist/vectordb/supabase.d.ts.map +1 -0
- package/dist/vectordb/supabase.js +711 -0
- package/dist/vectordb/supabase.js.map +1 -0
- package/dist/vectordb/types.d.ts +217 -0
- package/dist/vectordb/types.d.ts.map +1 -0
- package/dist/vectordb/types.js +28 -0
- package/dist/vectordb/types.js.map +1 -0
- package/package.json +49 -0
- package/setup.sql +1037 -0
|
@@ -0,0 +1,711 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supabase VectorDB Provider
|
|
3
|
+
*
|
|
4
|
+
* Implementation of VectorDBProvider using Supabase with pgvector.
|
|
5
|
+
* All memory operations go through the configured Supabase instance.
|
|
6
|
+
*/
|
|
7
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
8
|
+
import { getMemoryClient, getUserId, getProjectId, getTableName } from '../lib/client.js';
|
|
9
|
+
import { generateEmbedding, formatEmbeddingForPgVector } from '../lib/embeddings.js';
|
|
10
|
+
// Convert database row to Memory type
|
|
11
|
+
function rowToMemory(row) {
|
|
12
|
+
return {
|
|
13
|
+
id: row.id,
|
|
14
|
+
content: row.content,
|
|
15
|
+
summary: row.summary ?? undefined,
|
|
16
|
+
category: row.category,
|
|
17
|
+
tags: row.tags || [],
|
|
18
|
+
contextTags: row.context_tags || [],
|
|
19
|
+
sourceType: row.source_type,
|
|
20
|
+
sourceRef: row.source_ref ?? undefined,
|
|
21
|
+
sourceProject: row.source_project,
|
|
22
|
+
originalConfidence: row.original_confidence,
|
|
23
|
+
lastValidated: new Date(row.last_validated),
|
|
24
|
+
relatedTo: row.related_to || [],
|
|
25
|
+
isContradiction: row.is_contradiction,
|
|
26
|
+
isArchived: row.is_archived,
|
|
27
|
+
archiveReason: row.archive_reason ?? undefined,
|
|
28
|
+
archivedAt: row.archived_at ? new Date(row.archived_at) : undefined,
|
|
29
|
+
embeddingModel: row.embedding_model,
|
|
30
|
+
embeddingModelVersion: row.embedding_model_version,
|
|
31
|
+
createdAt: new Date(row.created_at),
|
|
32
|
+
updatedAt: new Date(row.updated_at),
|
|
33
|
+
durability: row.durability || 'permanent',
|
|
34
|
+
expiresAt: row.expires_at ? new Date(row.expires_at) : undefined,
|
|
35
|
+
domain: row.domain ?? undefined,
|
|
36
|
+
topic: row.topic ?? undefined,
|
|
37
|
+
isUniversal: row.is_universal ?? false,
|
|
38
|
+
agentType: row.agent_type ?? undefined,
|
|
39
|
+
timesReturned: row.times_returned ?? 0,
|
|
40
|
+
timesCited: row.times_cited ?? 0,
|
|
41
|
+
lastReturnedAt: row.last_returned_at ? new Date(row.last_returned_at) : undefined,
|
|
42
|
+
lastCitedAt: row.last_cited_at ? new Date(row.last_cited_at) : undefined,
|
|
43
|
+
// v2: Memory lifecycle
|
|
44
|
+
memoryType: row.memory_type ?? undefined,
|
|
45
|
+
validAt: row.valid_at ? new Date(row.valid_at) : undefined,
|
|
46
|
+
invalidAt: row.invalid_at ? new Date(row.invalid_at) : undefined,
|
|
47
|
+
isLatest: row.is_latest ?? true,
|
|
48
|
+
isForgotten: row.is_forgotten ?? false,
|
|
49
|
+
forgottenAt: row.forgotten_at ? new Date(row.forgotten_at) : undefined,
|
|
50
|
+
forgetAfter: row.forget_after ? new Date(row.forget_after) : undefined,
|
|
51
|
+
sourceTool: row.source_tool ?? undefined,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function rowToSearchResult(row) {
|
|
55
|
+
return {
|
|
56
|
+
...rowToMemory(row),
|
|
57
|
+
currentConfidence: row.current_confidence,
|
|
58
|
+
similarity: row.similarity,
|
|
59
|
+
relevanceScore: row.relevance_score,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export class SupabaseVectorProvider {
|
|
63
|
+
async store(input, domainId) {
|
|
64
|
+
const client = getMemoryClient();
|
|
65
|
+
const projectId = domainId || getProjectId();
|
|
66
|
+
// Use pre-computed embedding if available (saves one OpenAI API call in triage flow)
|
|
67
|
+
let embeddingStr;
|
|
68
|
+
let embeddingModel = 'text-embedding-3-small';
|
|
69
|
+
let embeddingModelVersion = '1';
|
|
70
|
+
if (input.precomputedEmbedding) {
|
|
71
|
+
embeddingStr = input.precomputedEmbedding;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
const result = await generateEmbedding(input.content);
|
|
75
|
+
embeddingStr = formatEmbeddingForPgVector(result.embedding);
|
|
76
|
+
embeddingModel = result.model;
|
|
77
|
+
embeddingModelVersion = result.modelVersion;
|
|
78
|
+
}
|
|
79
|
+
const insertData = {
|
|
80
|
+
user_id: getUserId(),
|
|
81
|
+
project_id: projectId,
|
|
82
|
+
content: input.content,
|
|
83
|
+
summary: input.summary,
|
|
84
|
+
category: input.category,
|
|
85
|
+
tags: input.tags || [],
|
|
86
|
+
context_tags: input.contextTags || [],
|
|
87
|
+
embedding: embeddingStr,
|
|
88
|
+
embedding_model: embeddingModel,
|
|
89
|
+
embedding_model_version: embeddingModelVersion,
|
|
90
|
+
source_type: input.sourceType,
|
|
91
|
+
source_ref: input.sourceRef,
|
|
92
|
+
source_project: input.sourceProject || 'default',
|
|
93
|
+
original_confidence: input.confidence ?? 1.0,
|
|
94
|
+
related_to: input.relatedTo || [],
|
|
95
|
+
is_contradiction: input.isContradiction || false,
|
|
96
|
+
is_universal: input.isUniversal || false,
|
|
97
|
+
agent_type: input.agentType || null,
|
|
98
|
+
durability: input.durability || 'permanent',
|
|
99
|
+
expires_at: input.expiresAt ? input.expiresAt.toISOString() : null,
|
|
100
|
+
is_portable: true,
|
|
101
|
+
domain: input.domain || null,
|
|
102
|
+
topic: input.topic || null,
|
|
103
|
+
// v2: Memory lifecycle
|
|
104
|
+
memory_type: input.memoryType || null,
|
|
105
|
+
source_tool: input.sourceTool || null,
|
|
106
|
+
valid_at: input.validAt ? input.validAt.toISOString() : new Date().toISOString(),
|
|
107
|
+
forget_after: input.forgetAfter ? input.forgetAfter.toISOString() : null,
|
|
108
|
+
is_latest: true,
|
|
109
|
+
is_forgotten: false,
|
|
110
|
+
};
|
|
111
|
+
const { data, error } = await client
|
|
112
|
+
.from(getTableName())
|
|
113
|
+
.insert(insertData)
|
|
114
|
+
.select()
|
|
115
|
+
.single();
|
|
116
|
+
if (error) {
|
|
117
|
+
console.error('[VectorDB] Error storing memory:', error);
|
|
118
|
+
throw new Error(`Failed to store memory: ${error.message}`);
|
|
119
|
+
}
|
|
120
|
+
return rowToMemory(data);
|
|
121
|
+
}
|
|
122
|
+
async search(query, options = {}) {
|
|
123
|
+
const client = getMemoryClient();
|
|
124
|
+
const embeddingStr = options.precomputedEmbedding
|
|
125
|
+
?? formatEmbeddingForPgVector((await generateEmbedding(query)).embedding);
|
|
126
|
+
if (options.includeUniversal || options.sourceProject || options.agentType) {
|
|
127
|
+
const { data, error } = await client.rpc('search_memories_cross_project', {
|
|
128
|
+
p_query_embedding: embeddingStr,
|
|
129
|
+
p_project_id: options.domainId || null,
|
|
130
|
+
p_source_project: options.sourceProject || null,
|
|
131
|
+
p_category: options.category || null,
|
|
132
|
+
p_tags: options.tags || null,
|
|
133
|
+
p_include_archived: options.includeArchived || false,
|
|
134
|
+
p_include_portable: options.includeUniversal ?? true,
|
|
135
|
+
p_agent_type: options.agentType || null,
|
|
136
|
+
p_limit: options.limit || 10,
|
|
137
|
+
p_similarity_threshold: options.similarityThreshold || 0.3,
|
|
138
|
+
});
|
|
139
|
+
if (error) {
|
|
140
|
+
console.error('[VectorDB] Error searching memories (cross-project):', error);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
return (data || []).map((row) => rowToSearchResult(row));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const { data, error } = await client.rpc('search_memories', {
|
|
147
|
+
p_query_embedding: embeddingStr,
|
|
148
|
+
p_project_id: options.domainId || null,
|
|
149
|
+
p_category: options.category || null,
|
|
150
|
+
p_tags: options.tags || null,
|
|
151
|
+
p_include_archived: options.includeArchived || false,
|
|
152
|
+
p_limit: options.limit || 10,
|
|
153
|
+
p_similarity_threshold: options.similarityThreshold || 0.3,
|
|
154
|
+
p_latest_only: options.latestOnly ?? true,
|
|
155
|
+
});
|
|
156
|
+
if (error) {
|
|
157
|
+
console.error('[VectorDB] Error searching memories:', error);
|
|
158
|
+
throw new Error(`Failed to search memories: ${error.message}`);
|
|
159
|
+
}
|
|
160
|
+
return (data || []).map((row) => rowToSearchResult(row));
|
|
161
|
+
}
|
|
162
|
+
async getById(id) {
|
|
163
|
+
const client = getMemoryClient();
|
|
164
|
+
const { data, error } = await client
|
|
165
|
+
.from(getTableName())
|
|
166
|
+
.select()
|
|
167
|
+
.eq('id', id)
|
|
168
|
+
.single();
|
|
169
|
+
if (error) {
|
|
170
|
+
if (error.code === 'PGRST116')
|
|
171
|
+
return null;
|
|
172
|
+
console.error('[VectorDB] Error getting memory:', error);
|
|
173
|
+
throw new Error(`Failed to get memory: ${error.message}`);
|
|
174
|
+
}
|
|
175
|
+
return rowToMemory(data);
|
|
176
|
+
}
|
|
177
|
+
async update(id, updates) {
|
|
178
|
+
const client = getMemoryClient();
|
|
179
|
+
const current = await this.getById(id);
|
|
180
|
+
if (!current) {
|
|
181
|
+
throw new Error(`Memory not found: ${id}`);
|
|
182
|
+
}
|
|
183
|
+
const updateData = {
|
|
184
|
+
updated_at: new Date().toISOString(),
|
|
185
|
+
};
|
|
186
|
+
if (updates.content !== undefined) {
|
|
187
|
+
updateData.content = updates.content;
|
|
188
|
+
const embeddingResult = await generateEmbedding(updates.content);
|
|
189
|
+
updateData.embedding = formatEmbeddingForPgVector(embeddingResult.embedding);
|
|
190
|
+
updateData.embedding_model = embeddingResult.model;
|
|
191
|
+
updateData.embedding_model_version = embeddingResult.modelVersion;
|
|
192
|
+
}
|
|
193
|
+
if (updates.summary !== undefined)
|
|
194
|
+
updateData.summary = updates.summary;
|
|
195
|
+
if (updates.category !== undefined)
|
|
196
|
+
updateData.category = updates.category;
|
|
197
|
+
if (updates.tags !== undefined)
|
|
198
|
+
updateData.tags = updates.tags;
|
|
199
|
+
if (updates.contextTags !== undefined)
|
|
200
|
+
updateData.context_tags = updates.contextTags;
|
|
201
|
+
if (updates.confidence !== undefined)
|
|
202
|
+
updateData.original_confidence = updates.confidence;
|
|
203
|
+
if (updates.relatedTo !== undefined)
|
|
204
|
+
updateData.related_to = updates.relatedTo;
|
|
205
|
+
if (updates.isContradiction !== undefined)
|
|
206
|
+
updateData.is_contradiction = updates.isContradiction;
|
|
207
|
+
if (updates.content && updates.content !== current.content) {
|
|
208
|
+
await client.from('traqr_memory_history').insert({
|
|
209
|
+
memory_id: id,
|
|
210
|
+
previous_content: current.content,
|
|
211
|
+
previous_confidence: current.originalConfidence,
|
|
212
|
+
change_reason: updates.changeReason || 'Content updated',
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
const { data, error } = await client
|
|
216
|
+
.from(getTableName())
|
|
217
|
+
.update(updateData)
|
|
218
|
+
.eq('id', id)
|
|
219
|
+
.select()
|
|
220
|
+
.single();
|
|
221
|
+
if (error) {
|
|
222
|
+
console.error('[VectorDB] Error updating memory:', error);
|
|
223
|
+
throw new Error(`Failed to update memory: ${error.message}`);
|
|
224
|
+
}
|
|
225
|
+
return rowToMemory(data);
|
|
226
|
+
}
|
|
227
|
+
async delete(id) {
|
|
228
|
+
const client = getMemoryClient();
|
|
229
|
+
const { error } = await client
|
|
230
|
+
.from(getTableName())
|
|
231
|
+
.delete()
|
|
232
|
+
.eq('id', id);
|
|
233
|
+
if (error) {
|
|
234
|
+
console.error('[VectorDB] Error deleting memory:', error);
|
|
235
|
+
throw new Error(`Failed to delete memory: ${error.message}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
async validate(id) {
|
|
239
|
+
const client = getMemoryClient();
|
|
240
|
+
const { data, error } = await client
|
|
241
|
+
.from(getTableName())
|
|
242
|
+
.update({
|
|
243
|
+
last_validated: new Date().toISOString(),
|
|
244
|
+
updated_at: new Date().toISOString(),
|
|
245
|
+
})
|
|
246
|
+
.eq('id', id)
|
|
247
|
+
.select()
|
|
248
|
+
.single();
|
|
249
|
+
if (error) {
|
|
250
|
+
console.error('[VectorDB] Error validating memory:', error);
|
|
251
|
+
throw new Error(`Failed to validate memory: ${error.message}`);
|
|
252
|
+
}
|
|
253
|
+
return rowToMemory(data);
|
|
254
|
+
}
|
|
255
|
+
async invalidate(id) {
|
|
256
|
+
const client = getMemoryClient();
|
|
257
|
+
const { error } = await client
|
|
258
|
+
.from(getTableName())
|
|
259
|
+
.update({
|
|
260
|
+
invalid_at: new Date().toISOString(),
|
|
261
|
+
is_latest: false,
|
|
262
|
+
updated_at: new Date().toISOString(),
|
|
263
|
+
})
|
|
264
|
+
.eq('id', id);
|
|
265
|
+
if (error) {
|
|
266
|
+
console.warn('[VectorDB] Error invalidating memory:', error.message);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
async supersede(id) {
|
|
270
|
+
const client = getMemoryClient();
|
|
271
|
+
const { error } = await client
|
|
272
|
+
.from(getTableName())
|
|
273
|
+
.update({
|
|
274
|
+
is_latest: false,
|
|
275
|
+
updated_at: new Date().toISOString(),
|
|
276
|
+
})
|
|
277
|
+
.eq('id', id);
|
|
278
|
+
if (error) {
|
|
279
|
+
console.warn('[VectorDB] Error superseding memory:', error.message);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
async archive(id, reason) {
|
|
283
|
+
const client = getMemoryClient();
|
|
284
|
+
const { data, error } = await client
|
|
285
|
+
.from(getTableName())
|
|
286
|
+
.update({
|
|
287
|
+
is_archived: true,
|
|
288
|
+
archived_at: new Date().toISOString(),
|
|
289
|
+
archive_reason: reason || 'manual',
|
|
290
|
+
updated_at: new Date().toISOString(),
|
|
291
|
+
})
|
|
292
|
+
.eq('id', id)
|
|
293
|
+
.select()
|
|
294
|
+
.single();
|
|
295
|
+
if (error) {
|
|
296
|
+
console.error('[VectorDB] Error archiving memory:', error);
|
|
297
|
+
throw new Error(`Failed to archive memory: ${error.message}`);
|
|
298
|
+
}
|
|
299
|
+
return rowToMemory(data);
|
|
300
|
+
}
|
|
301
|
+
async unarchive(id) {
|
|
302
|
+
const client = getMemoryClient();
|
|
303
|
+
const { data, error } = await client
|
|
304
|
+
.from(getTableName())
|
|
305
|
+
.update({
|
|
306
|
+
is_archived: false,
|
|
307
|
+
archived_at: null,
|
|
308
|
+
archive_reason: null,
|
|
309
|
+
updated_at: new Date().toISOString(),
|
|
310
|
+
})
|
|
311
|
+
.eq('id', id)
|
|
312
|
+
.select()
|
|
313
|
+
.single();
|
|
314
|
+
if (error) {
|
|
315
|
+
console.error('[VectorDB] Error unarchiving memory:', error);
|
|
316
|
+
throw new Error(`Failed to unarchive memory: ${error.message}`);
|
|
317
|
+
}
|
|
318
|
+
return rowToMemory(data);
|
|
319
|
+
}
|
|
320
|
+
async exportAll(domainId) {
|
|
321
|
+
const client = getMemoryClient();
|
|
322
|
+
let query = client.from(getTableName()).select('*');
|
|
323
|
+
if (domainId) {
|
|
324
|
+
query = query.eq('project_id', domainId);
|
|
325
|
+
}
|
|
326
|
+
const { data, error } = await query;
|
|
327
|
+
if (error) {
|
|
328
|
+
console.error('[VectorDB] Error exporting memories:', error);
|
|
329
|
+
throw new Error(`Failed to export memories: ${error.message}`);
|
|
330
|
+
}
|
|
331
|
+
return (data || []).map((row) => ({
|
|
332
|
+
id: row.id,
|
|
333
|
+
content: row.content,
|
|
334
|
+
summary: row.summary ?? undefined,
|
|
335
|
+
category: row.category,
|
|
336
|
+
tags: row.tags || [],
|
|
337
|
+
contextTags: row.context_tags || [],
|
|
338
|
+
sourceType: row.source_type,
|
|
339
|
+
sourceRef: row.source_ref ?? undefined,
|
|
340
|
+
sourceProject: row.source_project,
|
|
341
|
+
originalConfidence: row.original_confidence,
|
|
342
|
+
lastValidated: row.last_validated,
|
|
343
|
+
relatedTo: row.related_to || [],
|
|
344
|
+
isContradiction: row.is_contradiction,
|
|
345
|
+
isArchived: row.is_archived,
|
|
346
|
+
archiveReason: row.archive_reason ?? undefined,
|
|
347
|
+
durability: row.durability,
|
|
348
|
+
expiresAt: row.expires_at ?? undefined,
|
|
349
|
+
embeddingModel: row.embedding_model,
|
|
350
|
+
embeddingModelVersion: row.embedding_model_version,
|
|
351
|
+
createdAt: row.created_at,
|
|
352
|
+
updatedAt: row.updated_at,
|
|
353
|
+
domainName: undefined,
|
|
354
|
+
userEmail: undefined,
|
|
355
|
+
}));
|
|
356
|
+
}
|
|
357
|
+
async importBulk(memories, domainId) {
|
|
358
|
+
const client = getMemoryClient();
|
|
359
|
+
let importedCount = 0;
|
|
360
|
+
const BATCH_SIZE = 10;
|
|
361
|
+
for (let i = 0; i < memories.length; i += BATCH_SIZE) {
|
|
362
|
+
const batch = memories.slice(i, i + BATCH_SIZE);
|
|
363
|
+
const embeddings = await Promise.all(batch.map(m => generateEmbedding(m.content)));
|
|
364
|
+
const insertData = batch.map((memory, idx) => ({
|
|
365
|
+
user_id: getUserId(),
|
|
366
|
+
project_id: domainId,
|
|
367
|
+
content: memory.content,
|
|
368
|
+
summary: memory.summary,
|
|
369
|
+
category: memory.category,
|
|
370
|
+
tags: memory.tags,
|
|
371
|
+
context_tags: memory.contextTags,
|
|
372
|
+
embedding: formatEmbeddingForPgVector(embeddings[idx].embedding),
|
|
373
|
+
embedding_model: embeddings[idx].model,
|
|
374
|
+
embedding_model_version: embeddings[idx].modelVersion,
|
|
375
|
+
source_type: memory.sourceType,
|
|
376
|
+
source_ref: memory.sourceRef,
|
|
377
|
+
source_project: memory.sourceProject,
|
|
378
|
+
original_confidence: memory.originalConfidence,
|
|
379
|
+
last_validated: memory.lastValidated,
|
|
380
|
+
related_to: memory.relatedTo,
|
|
381
|
+
is_contradiction: memory.isContradiction,
|
|
382
|
+
is_archived: memory.isArchived,
|
|
383
|
+
archive_reason: memory.archiveReason,
|
|
384
|
+
created_at: memory.createdAt,
|
|
385
|
+
updated_at: memory.updatedAt,
|
|
386
|
+
is_portable: true,
|
|
387
|
+
}));
|
|
388
|
+
const { error } = await client.from(getTableName()).insert(insertData);
|
|
389
|
+
if (error) {
|
|
390
|
+
console.error(`[VectorDB] Error importing batch ${i / BATCH_SIZE}:`, error);
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
importedCount += batch.length;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return importedCount;
|
|
397
|
+
}
|
|
398
|
+
async createDomain(name, description, userId) {
|
|
399
|
+
const client = getMemoryClient();
|
|
400
|
+
const { data, error } = await client
|
|
401
|
+
.from('traqr_projects')
|
|
402
|
+
.insert({
|
|
403
|
+
user_id: userId || getUserId(),
|
|
404
|
+
name,
|
|
405
|
+
slug: name.toLowerCase().replace(/[^a-z0-9]+/g, '-'),
|
|
406
|
+
description,
|
|
407
|
+
is_active: true,
|
|
408
|
+
})
|
|
409
|
+
.select()
|
|
410
|
+
.single();
|
|
411
|
+
if (error) {
|
|
412
|
+
console.error('[VectorDB] Error creating domain:', error);
|
|
413
|
+
throw new Error(`Failed to create domain: ${error.message}`);
|
|
414
|
+
}
|
|
415
|
+
return {
|
|
416
|
+
id: data.id,
|
|
417
|
+
userId: data.user_id,
|
|
418
|
+
name: data.name,
|
|
419
|
+
description: data.description ?? undefined,
|
|
420
|
+
isShareable: data.is_active,
|
|
421
|
+
createdAt: new Date(data.created_at),
|
|
422
|
+
updatedAt: new Date(data.last_activity),
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
async getDomain(name) {
|
|
426
|
+
const client = getMemoryClient();
|
|
427
|
+
const { data, error } = await client
|
|
428
|
+
.from('traqr_projects')
|
|
429
|
+
.select()
|
|
430
|
+
.eq('slug', name.toLowerCase().replace(/[^a-z0-9]+/g, '-'))
|
|
431
|
+
.single();
|
|
432
|
+
if (error) {
|
|
433
|
+
if (error.code === 'PGRST116')
|
|
434
|
+
return null;
|
|
435
|
+
console.error('[VectorDB] Error getting domain:', error);
|
|
436
|
+
throw new Error(`Failed to get domain: ${error.message}`);
|
|
437
|
+
}
|
|
438
|
+
return {
|
|
439
|
+
id: data.id,
|
|
440
|
+
userId: data.user_id,
|
|
441
|
+
name: data.name,
|
|
442
|
+
description: data.description ?? undefined,
|
|
443
|
+
isShareable: data.is_active,
|
|
444
|
+
createdAt: new Date(data.created_at),
|
|
445
|
+
updatedAt: new Date(data.last_activity),
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
async getDefaultDomain() {
|
|
449
|
+
const client = getMemoryClient();
|
|
450
|
+
const { data, error } = await client
|
|
451
|
+
.from('traqr_projects')
|
|
452
|
+
.select()
|
|
453
|
+
.eq('id', getProjectId())
|
|
454
|
+
.single();
|
|
455
|
+
if (error) {
|
|
456
|
+
console.error('[VectorDB] Error getting default domain:', error);
|
|
457
|
+
throw new Error(`Failed to get default domain: ${error.message}`);
|
|
458
|
+
}
|
|
459
|
+
return {
|
|
460
|
+
id: data.id,
|
|
461
|
+
userId: data.user_id,
|
|
462
|
+
name: data.name,
|
|
463
|
+
description: data.description ?? undefined,
|
|
464
|
+
isShareable: data.is_active,
|
|
465
|
+
createdAt: new Date(data.created_at),
|
|
466
|
+
updatedAt: new Date(data.last_activity),
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
// ============================================================
|
|
470
|
+
// v2 Search Strategies (I-M4) — additive, graceful degradation
|
|
471
|
+
// ============================================================
|
|
472
|
+
async bm25Search(queryText, options) {
|
|
473
|
+
const client = getMemoryClient();
|
|
474
|
+
const { data, error } = await client.rpc('bm25_search', {
|
|
475
|
+
p_query_text: queryText,
|
|
476
|
+
p_project_id: options?.projectId || null,
|
|
477
|
+
p_domain: options?.domain || null,
|
|
478
|
+
p_category: options?.category || null,
|
|
479
|
+
p_limit: options?.limit || 20,
|
|
480
|
+
p_min_score: options?.minScore || 0.01,
|
|
481
|
+
});
|
|
482
|
+
if (error) {
|
|
483
|
+
console.error('[VectorDB] BM25 search error:', error);
|
|
484
|
+
return [];
|
|
485
|
+
}
|
|
486
|
+
return (data || []).map((row) => ({
|
|
487
|
+
id: row.id,
|
|
488
|
+
content: row.content,
|
|
489
|
+
summary: row.summary ?? undefined,
|
|
490
|
+
bm25Score: row.bm25_score,
|
|
491
|
+
domain: row.domain ?? undefined,
|
|
492
|
+
category: row.category ?? undefined,
|
|
493
|
+
memoryType: row.memory_type ?? undefined,
|
|
494
|
+
}));
|
|
495
|
+
}
|
|
496
|
+
async temporalSearch(query, dateStart, dateEnd, options) {
|
|
497
|
+
const client = getMemoryClient();
|
|
498
|
+
const embeddingStr = options?.precomputedEmbedding
|
|
499
|
+
?? formatEmbeddingForPgVector((await generateEmbedding(query)).embedding);
|
|
500
|
+
const { data, error } = await client.rpc('temporal_search', {
|
|
501
|
+
p_query_embedding: embeddingStr,
|
|
502
|
+
p_date_start: dateStart.toISOString(),
|
|
503
|
+
p_date_end: dateEnd.toISOString(),
|
|
504
|
+
p_project_id: options?.projectId || null,
|
|
505
|
+
p_similarity_threshold: options?.similarityThreshold || 0.3,
|
|
506
|
+
p_limit: options?.limit || 20,
|
|
507
|
+
});
|
|
508
|
+
if (error) {
|
|
509
|
+
console.error('[VectorDB] Temporal search error:', error);
|
|
510
|
+
return [];
|
|
511
|
+
}
|
|
512
|
+
return (data || []).map((row) => ({
|
|
513
|
+
id: row.id,
|
|
514
|
+
content: row.content,
|
|
515
|
+
summary: row.summary ?? undefined,
|
|
516
|
+
similarity: row.similarity,
|
|
517
|
+
temporalProximity: row.temporal_proximity,
|
|
518
|
+
validAt: new Date(row.valid_at),
|
|
519
|
+
}));
|
|
520
|
+
}
|
|
521
|
+
async graphSearch(seedIds, options) {
|
|
522
|
+
const client = getMemoryClient();
|
|
523
|
+
const { data, error } = await client.rpc('graph_search', {
|
|
524
|
+
p_seed_ids: seedIds,
|
|
525
|
+
p_edge_types: options?.edgeTypes || ['updates', 'extends', 'derives', 'related'],
|
|
526
|
+
p_max_depth: options?.maxDepth || 2,
|
|
527
|
+
p_limit: options?.limit || 20,
|
|
528
|
+
});
|
|
529
|
+
if (error) {
|
|
530
|
+
console.error('[VectorDB] Graph search error:', error);
|
|
531
|
+
return [];
|
|
532
|
+
}
|
|
533
|
+
return (data || []).map((row) => ({
|
|
534
|
+
id: row.id,
|
|
535
|
+
content: row.content,
|
|
536
|
+
summary: row.summary ?? undefined,
|
|
537
|
+
graphScore: row.graph_score,
|
|
538
|
+
edgeType: row.edge_type,
|
|
539
|
+
depth: row.depth,
|
|
540
|
+
}));
|
|
541
|
+
}
|
|
542
|
+
// ============================================================
|
|
543
|
+
// ENTITY OPERATIONS
|
|
544
|
+
// ============================================================
|
|
545
|
+
async findEntityByName(name, entityType) {
|
|
546
|
+
const client = getMemoryClient();
|
|
547
|
+
const { data, error } = await client.from('memory_entities')
|
|
548
|
+
.select('*')
|
|
549
|
+
.eq('user_id', getUserId())
|
|
550
|
+
.ilike('name', name.trim())
|
|
551
|
+
.eq('entity_type', entityType)
|
|
552
|
+
.eq('is_archived', false)
|
|
553
|
+
.limit(1)
|
|
554
|
+
.maybeSingle();
|
|
555
|
+
if (error || !data)
|
|
556
|
+
return null;
|
|
557
|
+
return data;
|
|
558
|
+
}
|
|
559
|
+
async findEntityByNameFuzzy(name, entityType) {
|
|
560
|
+
const client = getMemoryClient();
|
|
561
|
+
const { data, error } = await client.from('memory_entities')
|
|
562
|
+
.select('*')
|
|
563
|
+
.eq('user_id', getUserId())
|
|
564
|
+
.ilike('name', `%${name.trim()}%`)
|
|
565
|
+
.eq('entity_type', entityType)
|
|
566
|
+
.eq('is_archived', false)
|
|
567
|
+
.limit(1)
|
|
568
|
+
.maybeSingle();
|
|
569
|
+
if (error || !data)
|
|
570
|
+
return null;
|
|
571
|
+
return data;
|
|
572
|
+
}
|
|
573
|
+
async findEntityByEmbedding(embeddingStr, entityType, threshold = 0.85) {
|
|
574
|
+
const client = getMemoryClient();
|
|
575
|
+
const { data, error } = await client.rpc('search_entities', {
|
|
576
|
+
p_query_embedding: embeddingStr,
|
|
577
|
+
p_user_id: getUserId(),
|
|
578
|
+
p_entity_type: entityType,
|
|
579
|
+
p_threshold: threshold,
|
|
580
|
+
p_limit: 1,
|
|
581
|
+
});
|
|
582
|
+
if (error || !data || data.length === 0)
|
|
583
|
+
return null;
|
|
584
|
+
return data[0];
|
|
585
|
+
}
|
|
586
|
+
async createEntity(entity) {
|
|
587
|
+
const client = getMemoryClient();
|
|
588
|
+
const { data, error } = await client.from('memory_entities')
|
|
589
|
+
.insert({
|
|
590
|
+
user_id: entity.userId || getUserId(),
|
|
591
|
+
name: entity.name,
|
|
592
|
+
entity_type: entity.entityType,
|
|
593
|
+
embedding: entity.embedding || null,
|
|
594
|
+
mentions_count: 1,
|
|
595
|
+
first_seen_at: new Date().toISOString(),
|
|
596
|
+
last_seen_at: new Date().toISOString(),
|
|
597
|
+
})
|
|
598
|
+
.select()
|
|
599
|
+
.single();
|
|
600
|
+
if (error) {
|
|
601
|
+
// UNIQUE constraint violation = entity already exists, find and return it
|
|
602
|
+
if (error.code === '23505') {
|
|
603
|
+
return this.findEntityByName(entity.name, entity.entityType);
|
|
604
|
+
}
|
|
605
|
+
console.warn('[VectorDB] Error creating entity:', error.message);
|
|
606
|
+
return null;
|
|
607
|
+
}
|
|
608
|
+
return data;
|
|
609
|
+
}
|
|
610
|
+
async incrementEntityMentions(entityId) {
|
|
611
|
+
const client = getMemoryClient();
|
|
612
|
+
// Read current count, increment, write back
|
|
613
|
+
const { data } = await client.from('memory_entities')
|
|
614
|
+
.select('mentions_count')
|
|
615
|
+
.eq('id', entityId)
|
|
616
|
+
.single();
|
|
617
|
+
const newCount = (data?.mentions_count || 0) + 1;
|
|
618
|
+
const { error } = await client.from('memory_entities')
|
|
619
|
+
.update({
|
|
620
|
+
mentions_count: newCount,
|
|
621
|
+
last_seen_at: new Date().toISOString(),
|
|
622
|
+
})
|
|
623
|
+
.eq('id', entityId);
|
|
624
|
+
if (error) {
|
|
625
|
+
console.warn('[VectorDB] Error incrementing entity mentions:', error.message);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
async linkMemoryToEntity(memoryId, entityId, role = 'mentions') {
|
|
629
|
+
const client = getMemoryClient();
|
|
630
|
+
const { error } = await client.from('memory_entity_links')
|
|
631
|
+
.insert({
|
|
632
|
+
memory_id: memoryId,
|
|
633
|
+
entity_id: entityId,
|
|
634
|
+
role,
|
|
635
|
+
});
|
|
636
|
+
if (error && error.code !== '23505') { // ignore duplicate links
|
|
637
|
+
console.warn('[VectorDB] Error linking memory to entity:', error.message);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
async findEntitiesByNames(names) {
|
|
641
|
+
if (names.length === 0)
|
|
642
|
+
return [];
|
|
643
|
+
const client = getMemoryClient();
|
|
644
|
+
const lowerNames = names.map((n) => n.toLowerCase().trim()).filter(Boolean);
|
|
645
|
+
const { data, error } = await client.from('memory_entities')
|
|
646
|
+
.select('id, name')
|
|
647
|
+
.eq('user_id', getUserId())
|
|
648
|
+
.eq('is_archived', false)
|
|
649
|
+
.in('name', lowerNames); // exact match on lowercase names
|
|
650
|
+
if (error || !data) {
|
|
651
|
+
// Fallback: try ILIKE for case-insensitive matching
|
|
652
|
+
const results = [];
|
|
653
|
+
for (const name of lowerNames.slice(0, 10)) { // limit to avoid N+1 explosion
|
|
654
|
+
const { data: d } = await client.from('memory_entities')
|
|
655
|
+
.select('id, name')
|
|
656
|
+
.eq('user_id', getUserId())
|
|
657
|
+
.eq('is_archived', false)
|
|
658
|
+
.ilike('name', name)
|
|
659
|
+
.limit(1)
|
|
660
|
+
.maybeSingle();
|
|
661
|
+
if (d)
|
|
662
|
+
results.push({ id: d.id, name: d.name });
|
|
663
|
+
}
|
|
664
|
+
return results;
|
|
665
|
+
}
|
|
666
|
+
return (data || []).map((d) => ({ id: d.id, name: d.name }));
|
|
667
|
+
}
|
|
668
|
+
async findOrphanedEntities() {
|
|
669
|
+
const client = getMemoryClient();
|
|
670
|
+
// Find entities with zero non-archived memory links
|
|
671
|
+
const { data, error } = await client.from('memory_entities')
|
|
672
|
+
.select('id')
|
|
673
|
+
.eq('user_id', getUserId())
|
|
674
|
+
.eq('is_archived', false);
|
|
675
|
+
if (error || !data)
|
|
676
|
+
return [];
|
|
677
|
+
const orphaned = [];
|
|
678
|
+
for (const entity of data) {
|
|
679
|
+
const { count } = await client.from('memory_entity_links')
|
|
680
|
+
.select('*', { count: 'exact', head: true })
|
|
681
|
+
.eq('entity_id', entity.id);
|
|
682
|
+
if (count === 0)
|
|
683
|
+
orphaned.push(entity.id);
|
|
684
|
+
}
|
|
685
|
+
return orphaned;
|
|
686
|
+
}
|
|
687
|
+
async archiveEntities(ids) {
|
|
688
|
+
if (ids.length === 0)
|
|
689
|
+
return 0;
|
|
690
|
+
const client = getMemoryClient();
|
|
691
|
+
const { error } = await client.from('memory_entities')
|
|
692
|
+
.update({ is_archived: true })
|
|
693
|
+
.in('id', ids);
|
|
694
|
+
if (error) {
|
|
695
|
+
console.warn('[VectorDB] Error archiving entities:', error.message);
|
|
696
|
+
return 0;
|
|
697
|
+
}
|
|
698
|
+
return ids.length;
|
|
699
|
+
}
|
|
700
|
+
async ping() {
|
|
701
|
+
try {
|
|
702
|
+
const client = getMemoryClient();
|
|
703
|
+
const { error } = await client.from('traqr_users').select('id').limit(1);
|
|
704
|
+
return !error;
|
|
705
|
+
}
|
|
706
|
+
catch {
|
|
707
|
+
return false;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
//# sourceMappingURL=supabase.js.map
|