@open-mercato/search 0.6.7-develop.6842.1.8cd8a8883c → 0.6.7-develop.6859.1.e4727b568a
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 +2 -0
- package/dist/modules/search/api/openapi.js +2 -0
- package/dist/modules/search/api/openapi.js.map +2 -2
- package/dist/modules/search/api/settings/vector-store/route.js +34 -0
- package/dist/modules/search/api/settings/vector-store/route.js.map +2 -2
- package/dist/modules/search/frontend/components/SearchSettingsPageClient.js.map +2 -2
- package/dist/modules/search/frontend/components/sections/VectorSearchSection.js +14 -2
- package/dist/modules/search/frontend/components/sections/VectorSearchSection.js.map +2 -2
- package/dist/modules/search/i18n/de.json +2 -0
- package/dist/modules/search/i18n/en.json +2 -0
- package/dist/modules/search/i18n/es.json +2 -0
- package/dist/modules/search/i18n/ko.json +2 -0
- package/dist/modules/search/i18n/pl.json +2 -0
- package/dist/strategies/vector.strategy.js +3 -1
- package/dist/strategies/vector.strategy.js.map +2 -2
- package/dist/vector/drivers/pgvector/index.js +55 -1
- package/dist/vector/drivers/pgvector/index.js.map +2 -2
- package/dist/vector/types.js.map +2 -2
- package/package.json +4 -4
- package/src/__tests__/pgvector-extension-availability.test.ts +158 -0
- package/src/modules/search/api/openapi.ts +2 -0
- package/src/modules/search/api/settings/vector-store/route.ts +43 -0
- package/src/modules/search/frontend/components/SearchSettingsPageClient.tsx +2 -0
- package/src/modules/search/frontend/components/sections/VectorSearchSection.tsx +31 -1
- package/src/modules/search/frontend/components/sections/__tests__/VectorSearchSection.test.tsx +36 -1
- package/src/modules/search/i18n/de.json +2 -0
- package/src/modules/search/i18n/en.json +2 -0
- package/src/modules/search/i18n/es.json +2 -0
- package/src/modules/search/i18n/ko.json +2 -0
- package/src/modules/search/i18n/pl.json +2 -0
- package/src/strategies/vector.strategy.ts +7 -1
- package/src/vector/drivers/pgvector/index.ts +77 -1
- package/src/vector/types.ts +14 -0
|
@@ -214,6 +214,8 @@
|
|
|
214
214
|
"search.settings.vector.sectionTitle": "Vektorsuche",
|
|
215
215
|
"search.settings.vector.setEnvVar": "Umgebungsvariable setzen",
|
|
216
216
|
"search.settings.vector.store": "Vektorspeicher",
|
|
217
|
+
"search.settings.vector.storeUnavailable": "Vektorspeicher ist nicht verfügbar",
|
|
218
|
+
"search.settings.vector.storeUnavailableHint": "Vektorindizierung und semantische Suche werden übersprungen, bis der Speicher erreichbar ist. Installieren Sie die pgvector-Erweiterung auf Ihrem PostgreSQL-Server; der Status wird etwa einmal pro Minute neu geprüft.",
|
|
217
219
|
"search.settings.vector.usingTenantConfig": "Dieser Mandant verwendet eigene Embedding-Einstellungen.",
|
|
218
220
|
"search.settings.vectorDocumentsLabel": "Vektordokumente",
|
|
219
221
|
"search.settings.vectorNotConfigured": "Vektorsuche ist nicht konfiguriert.",
|
|
@@ -214,6 +214,8 @@
|
|
|
214
214
|
"search.settings.vector.sectionTitle": "Vector Search",
|
|
215
215
|
"search.settings.vector.setEnvVar": "Set environment variable",
|
|
216
216
|
"search.settings.vector.store": "Vector store",
|
|
217
|
+
"search.settings.vector.storeUnavailable": "Vector store is not available",
|
|
218
|
+
"search.settings.vector.storeUnavailableHint": "Vector indexing and semantic search are skipped until the store is reachable. Install the pgvector extension on your PostgreSQL server; the status is rechecked about once a minute.",
|
|
217
219
|
"search.settings.vector.usingTenantConfig": "This tenant is using its own embedding settings.",
|
|
218
220
|
"search.settings.vectorDocumentsLabel": "Vector documents",
|
|
219
221
|
"search.settings.vectorNotConfigured": "Vector search is not configured.",
|
|
@@ -214,6 +214,8 @@
|
|
|
214
214
|
"search.settings.vector.sectionTitle": "Búsqueda vectorial",
|
|
215
215
|
"search.settings.vector.setEnvVar": "Configurar variable de entorno",
|
|
216
216
|
"search.settings.vector.store": "Almacén vectorial",
|
|
217
|
+
"search.settings.vector.storeUnavailable": "El almacén vectorial no está disponible",
|
|
218
|
+
"search.settings.vector.storeUnavailableHint": "La indexación vectorial y la búsqueda semántica se omiten hasta que el almacén esté disponible. Instala la extensión pgvector en tu servidor PostgreSQL; el estado se vuelve a comprobar aproximadamente cada minuto.",
|
|
217
219
|
"search.settings.vector.usingTenantConfig": "Este inquilino usa su propia configuración de incrustaciones.",
|
|
218
220
|
"search.settings.vectorDocumentsLabel": "Documentos vectoriales",
|
|
219
221
|
"search.settings.vectorNotConfigured": "La búsqueda vectorial no está configurada.",
|
|
@@ -214,6 +214,8 @@
|
|
|
214
214
|
"search.settings.vector.sectionTitle": "벡터 검색",
|
|
215
215
|
"search.settings.vector.setEnvVar": "환경 변수 설정",
|
|
216
216
|
"search.settings.vector.store": "벡터 저장소",
|
|
217
|
+
"search.settings.vector.storeUnavailable": "벡터 저장소를 사용할 수 없습니다",
|
|
218
|
+
"search.settings.vector.storeUnavailableHint": "저장소에 연결할 수 있을 때까지 벡터 인덱싱과 의미 기반 검색은 건너뜁니다. PostgreSQL 서버에 pgvector 확장을 설치하세요. 상태는 약 1분마다 다시 확인됩니다.",
|
|
217
219
|
"search.settings.vector.usingTenantConfig": "이 테넌트는 자체 임베딩 설정을 사용하고 있습니다.",
|
|
218
220
|
"search.settings.vectorDocumentsLabel": "벡터 문서",
|
|
219
221
|
"search.settings.vectorNotConfigured": "벡터 검색이 구성되지 않았습니다.",
|
|
@@ -214,6 +214,8 @@
|
|
|
214
214
|
"search.settings.vector.sectionTitle": "Wyszukiwanie wektorowe",
|
|
215
215
|
"search.settings.vector.setEnvVar": "Ustaw zmienną środowiskową",
|
|
216
216
|
"search.settings.vector.store": "Magazyn wektorów",
|
|
217
|
+
"search.settings.vector.storeUnavailable": "Magazyn wektorów jest niedostępny",
|
|
218
|
+
"search.settings.vector.storeUnavailableHint": "Indeksowanie wektorowe i wyszukiwanie semantyczne są pomijane, dopóki magazyn nie będzie dostępny. Zainstaluj rozszerzenie pgvector na serwerze PostgreSQL; status jest sprawdzany ponownie mniej więcej co minutę.",
|
|
217
219
|
"search.settings.vector.usingTenantConfig": "Ten najemca używa własnych ustawień osadzania.",
|
|
218
220
|
"search.settings.vectorDocumentsLabel": "Dokumenty wektorowe",
|
|
219
221
|
"search.settings.vectorNotConfigured": "Wyszukiwanie wektorowe nie jest skonfigurowane.",
|
|
@@ -59,7 +59,13 @@ export class VectorSearchStrategy implements SearchStrategy {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
async isAvailable(): Promise<boolean> {
|
|
62
|
-
|
|
62
|
+
if (!this.embeddingService.available) return false
|
|
63
|
+
// A configured embedding provider says nothing about the vector store. When the
|
|
64
|
+
// store cannot serve writes (pgvector extension missing), reporting availability
|
|
65
|
+
// here makes SearchService call ensureReady() for every record and fail the whole
|
|
66
|
+
// index/delete operation. Drivers without a probe keep the previous behavior.
|
|
67
|
+
if (!this.vectorDriver.isHealthy) return true
|
|
68
|
+
return this.vectorDriver.isHealthy()
|
|
63
69
|
}
|
|
64
70
|
|
|
65
71
|
async ensureReady(): Promise<void> {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Pool } from 'pg'
|
|
2
|
-
import { searchDebugWarn } from '../../../lib/debug'
|
|
2
|
+
import { searchDebugWarn, searchWarn } from '../../../lib/debug'
|
|
3
3
|
|
|
4
4
|
type PgPoolQueryResult<T> = { rows: T[]; rowCount?: number }
|
|
5
5
|
type PgPoolClient = {
|
|
@@ -22,6 +22,7 @@ import type {
|
|
|
22
22
|
VectorDriverRemoveOrphansParams,
|
|
23
23
|
VectorResultPresenter,
|
|
24
24
|
VectorLinkDescriptor,
|
|
25
|
+
VectorDriverStatus,
|
|
25
26
|
} from '../../types'
|
|
26
27
|
|
|
27
28
|
type PgVectorDriverOptions = {
|
|
@@ -38,6 +39,16 @@ const DEFAULT_MIGRATIONS_TABLE = 'vector_search_migrations'
|
|
|
38
39
|
const DEFAULT_DIMENSION = 1536
|
|
39
40
|
const DRIVER_ID = 'pgvector' as const
|
|
40
41
|
|
|
42
|
+
// `undefined_file` is what Postgres raises for `extension "vector" is not available`
|
|
43
|
+
// (the pgvector shared library is not installed on the server); `feature_not_supported`
|
|
44
|
+
// covers managed providers that refuse the extension outright, and `undefined_object`
|
|
45
|
+
// the case where the extension record is missing. None of these can be resolved by
|
|
46
|
+
// retrying, so the driver reports itself unhealthy instead of failing every record.
|
|
47
|
+
const EXTENSION_MISSING_CODES = new Set(['58P01', '0A000', '42704'])
|
|
48
|
+
const EXTENSION_UNAVAILABLE_REASON = 'extension "vector" is not available on this PostgreSQL server'
|
|
49
|
+
// Re-probe occasionally so installing pgvector recovers without restarting the process.
|
|
50
|
+
const UNAVAILABLE_RECHECK_MS = 60_000
|
|
51
|
+
|
|
41
52
|
function assertIdentifier(name: string, defaultName: string): string {
|
|
42
53
|
const candidate = name ?? defaultName
|
|
43
54
|
if (!candidate || !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(candidate)) return defaultName
|
|
@@ -102,8 +113,60 @@ export function createPgVectorDriver(opts: PgVectorDriverOptions = {}): VectorDr
|
|
|
102
113
|
})()
|
|
103
114
|
|
|
104
115
|
let ready: Promise<void> | null = null
|
|
116
|
+
let status: { value: VectorDriverStatus; at: number } | null = null
|
|
117
|
+
let reportedReason: string | null = null
|
|
118
|
+
|
|
119
|
+
const cacheStatus = (value: VectorDriverStatus): VectorDriverStatus => {
|
|
120
|
+
status = { value, at: Date.now() }
|
|
121
|
+
if (value.available) {
|
|
122
|
+
reportedReason = null
|
|
123
|
+
} else if (value.reason && value.reason !== reportedReason) {
|
|
124
|
+
reportedReason = value.reason
|
|
125
|
+
// Always visible: replacing a per-record error storm with silence would hide the fact
|
|
126
|
+
// that vector indexing has stopped until someone opens Settings > Search.
|
|
127
|
+
searchWarn('vector.pgvector', `vector store unavailable — skipping vector indexing and search (${value.reason})`)
|
|
128
|
+
}
|
|
129
|
+
return value
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// The operator-facing reason is curated rather than the raw Postgres message, which the
|
|
133
|
+
// settings API surfaces to the browser.
|
|
134
|
+
const markExtensionMissing = () => {
|
|
135
|
+
cacheStatus({ available: false, reason: EXTENSION_UNAVAILABLE_REASON })
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const readCachedStatus = (): VectorDriverStatus | null => {
|
|
139
|
+
if (!status) return null
|
|
140
|
+
return Date.now() - status.at < UNAVAILABLE_RECHECK_MS ? status.value : null
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const getStatus = async (): Promise<VectorDriverStatus> => {
|
|
144
|
+
const cached = readCachedStatus()
|
|
145
|
+
if (cached) return cached
|
|
146
|
+
try {
|
|
147
|
+
const res = await pool.query<{ installed: boolean; installable: boolean }>(
|
|
148
|
+
`SELECT
|
|
149
|
+
EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'vector') AS installed,
|
|
150
|
+
EXISTS (SELECT 1 FROM pg_available_extensions WHERE name = 'vector') AS installable`,
|
|
151
|
+
)
|
|
152
|
+
const row = res.rows[0]
|
|
153
|
+
if (row?.installed || row?.installable) return cacheStatus({ available: true })
|
|
154
|
+
return cacheStatus({ available: false, reason: EXTENSION_UNAVAILABLE_REASON })
|
|
155
|
+
} catch {
|
|
156
|
+
// A probe that cannot run (connection refused, permissions on the catalogs)
|
|
157
|
+
// says nothing about pgvector itself — stay optimistic so genuinely transient
|
|
158
|
+
// database problems keep their existing retry semantics.
|
|
159
|
+
return { available: true }
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const isHealthy = async (): Promise<boolean> => (await getStatus()).available
|
|
105
164
|
|
|
106
165
|
const ensureReady = async () => {
|
|
166
|
+
const cached = readCachedStatus()
|
|
167
|
+
if (cached && !cached.available) {
|
|
168
|
+
throw new Error(`[vector.pgvector] ${cached.reason ?? 'vector store unavailable'}`)
|
|
169
|
+
}
|
|
107
170
|
if (!ready) {
|
|
108
171
|
ready = withClient(pool, async (client) => {
|
|
109
172
|
const ensureExtension = async (extension: 'pgcrypto' | 'vector') => {
|
|
@@ -116,6 +179,9 @@ export function createPgVectorDriver(opts: PgVectorDriverOptions = {}): VectorDr
|
|
|
116
179
|
searchDebugWarn('vector.pgvector', `skipping ${extension} extension creation; requires superuser${details}`)
|
|
117
180
|
return
|
|
118
181
|
}
|
|
182
|
+
if (extension === 'vector' && pgError?.code && EXTENSION_MISSING_CODES.has(pgError.code)) {
|
|
183
|
+
markExtensionMissing()
|
|
184
|
+
}
|
|
119
185
|
throw error
|
|
120
186
|
}
|
|
121
187
|
}
|
|
@@ -219,8 +285,16 @@ export function createPgVectorDriver(opts: PgVectorDriverOptions = {}): VectorDr
|
|
|
219
285
|
`INSERT INTO ${migrationsIdent} (id, applied_at) VALUES ($1, now()) ON CONFLICT (id) DO NOTHING`,
|
|
220
286
|
['0001_init'],
|
|
221
287
|
)
|
|
288
|
+
}).then(() => {
|
|
289
|
+
cacheStatus({ available: true })
|
|
222
290
|
}).catch((err) => {
|
|
223
291
|
ready = null
|
|
292
|
+
const pgError = err as { code?: string; message?: string }
|
|
293
|
+
// Without the extension the `vector` column type does not exist either, so the
|
|
294
|
+
// table DDL fails even when `CREATE EXTENSION` was skipped rather than raised.
|
|
295
|
+
if (pgError?.code === '42704' && /\bvector\b/.test(pgError.message ?? '')) {
|
|
296
|
+
markExtensionMissing()
|
|
297
|
+
}
|
|
224
298
|
throw err
|
|
225
299
|
})
|
|
226
300
|
}
|
|
@@ -633,6 +707,8 @@ export function createPgVectorDriver(opts: PgVectorDriverOptions = {}): VectorDr
|
|
|
633
707
|
return {
|
|
634
708
|
id: 'pgvector',
|
|
635
709
|
ensureReady,
|
|
710
|
+
isHealthy,
|
|
711
|
+
getStatus,
|
|
636
712
|
upsert,
|
|
637
713
|
delete: remove,
|
|
638
714
|
getChecksum,
|
package/src/vector/types.ts
CHANGED
|
@@ -96,9 +96,23 @@ export type VectorDriverRemoveOrphansParams = {
|
|
|
96
96
|
olderThan: Date
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
export type VectorDriverStatus = {
|
|
100
|
+
available: boolean
|
|
101
|
+
reason?: string
|
|
102
|
+
}
|
|
103
|
+
|
|
99
104
|
export interface VectorDriver {
|
|
100
105
|
readonly id: VectorDriverId
|
|
101
106
|
ensureReady(): Promise<void>
|
|
107
|
+
/**
|
|
108
|
+
* Cheap, cached probe answering whether the backing store can serve reads and
|
|
109
|
+
* writes at all. Drivers that cannot satisfy their hard requirements (for
|
|
110
|
+
* pgvector: the `vector` extension) MUST report `false` here so callers skip
|
|
111
|
+
* the strategy instead of failing every record. Optional for backward
|
|
112
|
+
* compatibility — an absent implementation is treated as available.
|
|
113
|
+
*/
|
|
114
|
+
isHealthy?(): Promise<boolean>
|
|
115
|
+
getStatus?(): Promise<VectorDriverStatus>
|
|
102
116
|
upsert(doc: VectorDriverDocument): Promise<void>
|
|
103
117
|
delete(entityId: EntityId, recordId: string, tenantId: string): Promise<void>
|
|
104
118
|
query(input: VectorDriverQuery): Promise<VectorDriverQueryResult[]>
|