-
-
Notifications
You must be signed in to change notification settings - Fork 402
Expand file tree
/
Copy pathindex.ts
More file actions
171 lines (145 loc) 路 5.77 KB
/
Copy pathindex.ts
File metadata and controls
171 lines (145 loc) 路 5.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
import type { AnyOrama, OramaPluginSync } from '@orama/orama'
import { remove } from '@orama/orama'
import { PgvectorManager } from './pgvector-manager.js'
// Global map to store managers by Orama instance ID
const pgvectorManagers = new Map<string, PgvectorManager>()
// Store original documents with vectors before Orama processes them
const originalDocuments = new Map<string, any>()
// Flag to prevent afterRemove when removing from memory
let removingFromMemory = false
export type PgvectorPluginOptions = {
connectionString: string
tableName?: string
dimension: number
memoryLimit?: number | string // Memory limit for document caching
lazyLoad?: boolean // Load documents on demand
maxMemoryDocs?: number // Maximum documents to keep in memory
vectorSearchOnly?: boolean // Skip loading full documents for vector search (IDs + scores only)
}
export function pluginPgvector(options: PgvectorPluginOptions): OramaPluginSync {
if (!options.connectionString) {
throw new Error('PostgreSQL connection string is required')
}
if (!options.dimension || options.dimension <= 0) {
throw new Error('Vector dimension must be a positive number')
}
const tableName = options.tableName || 'orama_documents'
const memoryLimit = options.memoryLimit || '500MB'
const lazyLoad = options.lazyLoad ?? true
const maxMemoryDocs = options.maxMemoryDocs || 10000
const vectorSearchOnly = options.vectorSearchOnly ?? false
return {
name: 'orama-plugin-pgvector',
beforeInsert: function beforeInsert(orama: AnyOrama, id: string, doc: any) {
// 馃毇 PREVENT: Remove ALL properties including id before Orama indexes them (zero memory mode)
// Store original document with all properties for PostgreSQL storage
originalDocuments.set(id, { ...doc })
// Remove ALL properties from the document Orama will index - ZERO indexing
for (const prop in doc) {
delete doc[prop]
}
// Modify in place, no return needed
},
afterCreate: async function afterCreate(orama: AnyOrama) {
// Store manager synchronously first
const manager = new PgvectorManager({
connectionString: options.connectionString,
tableName,
dimension: options.dimension,
memoryLimit,
lazyLoad,
maxMemoryDocs,
vectorSearchOnly
})
pgvectorManagers.set(orama.id, manager)
// Initialize the database table asynchronously
try {
await manager.initializeTable(orama.schema)
// Load existing documents based on configuration
if (!lazyLoad) {
await manager.loadAllDocuments(orama)
}
} catch (error) {
// Remove manager if initialization failed
pgvectorManagers.delete(orama.id)
throw error
}
},
afterInsert: async function afterInsert(orama: AnyOrama, id: string, doc: any) {
const manager = pgvectorManagers.get(orama.id)
if (!manager) return
// Use original document with vectors for PostgreSQL storage
const originalDoc = originalDocuments.get(id) || doc
await manager.insertDocument(id, originalDoc)
// 馃毇 MEMORY: Remove document from Orama's memory to save space (vector-only mode)
removingFromMemory = true
await remove(orama, id)
removingFromMemory = false
// Clean up stored document
originalDocuments.delete(id)
},
afterRemove: async function afterRemove(orama: AnyOrama, id: string, doc: any) {
// Skip if we're removing from memory (no PG operation needed)
if (removingFromMemory) return
const manager = pgvectorManagers.get(orama.id)
if (!manager) return
// Remove document from PostgreSQL
await manager.removeDocument(id)
// Clean up any stored original document
originalDocuments.delete(id)
},
afterSearch: async function afterSearch(orama: AnyOrama, params: any, language: string | undefined, results: any) {
const manager = pgvectorManagers.get(orama.id)
if (!manager) return
// Handle vector search
if (params.mode === 'vector' && params.vector) {
const vectorProperty = params.vector.property
const queryVector = params.vector.value
const similarity = params.similarity || 0.8
const limit = params.limit || 10
// Perform vector search in pgvector
const searchResults = await manager.vectorSearch(
vectorProperty,
queryVector,
limit,
similarity
)
if (vectorSearchOnly) {
// Memory-efficient mode: return only IDs and scores
results.hits = searchResults.map(([docId, score]: [string, number]) => ({
id: docId,
score
// No document field - saves memory
}))
} else {
// Load full documents and format results
results.hits = await Promise.all(
searchResults.map(async ([docId, score]: [string, number]) => {
const document = await manager.getDocument(docId, orama)
return {
id: docId,
score,
document
}
})
)
}
results.count = results.hits.length
return
} // For text search, let Orama handle it with documents in memory
// The manager ensures frequently searched documents are cached
if (manager.getLazyLoad() && results.hits) {
// Preload documents that were found in search results
await Promise.all(
results.hits.map(async (hit: any) => {
if (!manager.isDocumentInMemory(hit.id)) {
await manager.loadDocumentIntoMemory(hit.id, orama)
}
})
)
}
}
// Custom methods for advanced usage
// getPgvectorManager: (orama: AnyOrama) => pgvectorManagers.get(orama)
}
}