@open-mercato/search 0.4.2-canary-c02407ff85
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 +678 -0
- package/build.mjs +92 -0
- package/dist/di.js +157 -0
- package/dist/di.js.map +7 -0
- package/dist/fulltext/drivers/index.js +21 -0
- package/dist/fulltext/drivers/index.js.map +7 -0
- package/dist/fulltext/drivers/meilisearch/index.js +320 -0
- package/dist/fulltext/drivers/meilisearch/index.js.map +7 -0
- package/dist/fulltext/index.js +7 -0
- package/dist/fulltext/index.js.map +7 -0
- package/dist/fulltext/types.js +1 -0
- package/dist/fulltext/types.js.map +7 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +7 -0
- package/dist/indexer/index.js +8 -0
- package/dist/indexer/index.js.map +7 -0
- package/dist/indexer/search-indexer.js +848 -0
- package/dist/indexer/search-indexer.js.map +7 -0
- package/dist/indexer/subscribers/delete.js +41 -0
- package/dist/indexer/subscribers/delete.js.map +7 -0
- package/dist/lib/debug.js +34 -0
- package/dist/lib/debug.js.map +7 -0
- package/dist/lib/fallback-presenter.js +107 -0
- package/dist/lib/fallback-presenter.js.map +7 -0
- package/dist/lib/field-policy.js +75 -0
- package/dist/lib/field-policy.js.map +7 -0
- package/dist/lib/index.js +19 -0
- package/dist/lib/index.js.map +7 -0
- package/dist/lib/merger.js +93 -0
- package/dist/lib/merger.js.map +7 -0
- package/dist/lib/presenter-enricher.js +192 -0
- package/dist/lib/presenter-enricher.js.map +7 -0
- package/dist/modules/search/acl.js +14 -0
- package/dist/modules/search/acl.js.map +7 -0
- package/dist/modules/search/ai-tools.js +284 -0
- package/dist/modules/search/ai-tools.js.map +7 -0
- package/dist/modules/search/api/embeddings/reindex/cancel/route.js +65 -0
- package/dist/modules/search/api/embeddings/reindex/cancel/route.js.map +7 -0
- package/dist/modules/search/api/embeddings/reindex/route.js +165 -0
- package/dist/modules/search/api/embeddings/reindex/route.js.map +7 -0
- package/dist/modules/search/api/embeddings/route.js +246 -0
- package/dist/modules/search/api/embeddings/route.js.map +7 -0
- package/dist/modules/search/api/index/route.js +245 -0
- package/dist/modules/search/api/index/route.js.map +7 -0
- package/dist/modules/search/api/reindex/cancel/route.js +65 -0
- package/dist/modules/search/api/reindex/cancel/route.js.map +7 -0
- package/dist/modules/search/api/reindex/route.js +332 -0
- package/dist/modules/search/api/reindex/route.js.map +7 -0
- package/dist/modules/search/api/search/global/route.js +100 -0
- package/dist/modules/search/api/search/global/route.js.map +7 -0
- package/dist/modules/search/api/search/route.js +101 -0
- package/dist/modules/search/api/search/route.js.map +7 -0
- package/dist/modules/search/api/settings/fulltext/route.js +55 -0
- package/dist/modules/search/api/settings/fulltext/route.js.map +7 -0
- package/dist/modules/search/api/settings/global-search/route.js +80 -0
- package/dist/modules/search/api/settings/global-search/route.js.map +7 -0
- package/dist/modules/search/api/settings/route.js +118 -0
- package/dist/modules/search/api/settings/route.js.map +7 -0
- package/dist/modules/search/api/settings/vector-store/route.js +77 -0
- package/dist/modules/search/api/settings/vector-store/route.js.map +7 -0
- package/dist/modules/search/backend/config/search/page.js +10 -0
- package/dist/modules/search/backend/config/search/page.js.map +7 -0
- package/dist/modules/search/backend/config/search/page.meta.js +24 -0
- package/dist/modules/search/backend/config/search/page.meta.js.map +7 -0
- package/dist/modules/search/cli.js +698 -0
- package/dist/modules/search/cli.js.map +7 -0
- package/dist/modules/search/di.js +32 -0
- package/dist/modules/search/di.js.map +7 -0
- package/dist/modules/search/frontend/components/GlobalSearchDialog.js +357 -0
- package/dist/modules/search/frontend/components/GlobalSearchDialog.js.map +7 -0
- package/dist/modules/search/frontend/components/HybridSearchTable.js +343 -0
- package/dist/modules/search/frontend/components/HybridSearchTable.js.map +7 -0
- package/dist/modules/search/frontend/components/SearchSettingsPageClient.js +303 -0
- package/dist/modules/search/frontend/components/SearchSettingsPageClient.js.map +7 -0
- package/dist/modules/search/frontend/components/sections/FulltextSearchSection.js +360 -0
- package/dist/modules/search/frontend/components/sections/FulltextSearchSection.js.map +7 -0
- package/dist/modules/search/frontend/components/sections/GlobalSearchSection.js +101 -0
- package/dist/modules/search/frontend/components/sections/GlobalSearchSection.js.map +7 -0
- package/dist/modules/search/frontend/components/sections/VectorSearchSection.js +608 -0
- package/dist/modules/search/frontend/components/sections/VectorSearchSection.js.map +7 -0
- package/dist/modules/search/frontend/index.js +9 -0
- package/dist/modules/search/frontend/index.js.map +7 -0
- package/dist/modules/search/frontend/utils.js +41 -0
- package/dist/modules/search/frontend/utils.js.map +7 -0
- package/dist/modules/search/i18n/de.json +61 -0
- package/dist/modules/search/i18n/en.json +72 -0
- package/dist/modules/search/i18n/es.json +61 -0
- package/dist/modules/search/i18n/pl.json +61 -0
- package/dist/modules/search/index.js +11 -0
- package/dist/modules/search/index.js.map +7 -0
- package/dist/modules/search/lib/auto-indexing.js +29 -0
- package/dist/modules/search/lib/auto-indexing.js.map +7 -0
- package/dist/modules/search/lib/embedding-config.js +131 -0
- package/dist/modules/search/lib/embedding-config.js.map +7 -0
- package/dist/modules/search/lib/global-search-config.js +45 -0
- package/dist/modules/search/lib/global-search-config.js.map +7 -0
- package/dist/modules/search/lib/reindex-lock.js +99 -0
- package/dist/modules/search/lib/reindex-lock.js.map +7 -0
- package/dist/modules/search/subscribers/fulltext_upsert.js +64 -0
- package/dist/modules/search/subscribers/fulltext_upsert.js.map +7 -0
- package/dist/modules/search/subscribers/vector_delete.js +58 -0
- package/dist/modules/search/subscribers/vector_delete.js.map +7 -0
- package/dist/modules/search/subscribers/vector_purge.js +142 -0
- package/dist/modules/search/subscribers/vector_purge.js.map +7 -0
- package/dist/modules/search/subscribers/vector_upsert.js +58 -0
- package/dist/modules/search/subscribers/vector_upsert.js.map +7 -0
- package/dist/modules/search/workers/fulltext-index.worker.js +240 -0
- package/dist/modules/search/workers/fulltext-index.worker.js.map +7 -0
- package/dist/modules/search/workers/vector-index.worker.js +234 -0
- package/dist/modules/search/workers/vector-index.worker.js.map +7 -0
- package/dist/queue/fulltext-indexing.js +15 -0
- package/dist/queue/fulltext-indexing.js.map +7 -0
- package/dist/queue/index.js +3 -0
- package/dist/queue/index.js.map +7 -0
- package/dist/queue/vector-indexing.js +15 -0
- package/dist/queue/vector-indexing.js.map +7 -0
- package/dist/service.js +286 -0
- package/dist/service.js.map +7 -0
- package/dist/strategies/fulltext.strategy.js +116 -0
- package/dist/strategies/fulltext.strategy.js.map +7 -0
- package/dist/strategies/index.js +12 -0
- package/dist/strategies/index.js.map +7 -0
- package/dist/strategies/token.strategy.js +80 -0
- package/dist/strategies/token.strategy.js.map +7 -0
- package/dist/strategies/vector.strategy.js +137 -0
- package/dist/strategies/vector.strategy.js.map +7 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +7 -0
- package/dist/vector/drivers/chromadb/index.js +44 -0
- package/dist/vector/drivers/chromadb/index.js.map +7 -0
- package/dist/vector/drivers/index.js +9 -0
- package/dist/vector/drivers/index.js.map +7 -0
- package/dist/vector/drivers/pgvector/index.js +509 -0
- package/dist/vector/drivers/pgvector/index.js.map +7 -0
- package/dist/vector/drivers/qdrant/index.js +44 -0
- package/dist/vector/drivers/qdrant/index.js.map +7 -0
- package/dist/vector/index.js +4 -0
- package/dist/vector/index.js.map +7 -0
- package/dist/vector/lib/vector-logs.js +33 -0
- package/dist/vector/lib/vector-logs.js.map +7 -0
- package/dist/vector/services/checksum.js +20 -0
- package/dist/vector/services/checksum.js.map +7 -0
- package/dist/vector/services/embedding.js +222 -0
- package/dist/vector/services/embedding.js.map +7 -0
- package/dist/vector/services/index.js +4 -0
- package/dist/vector/services/index.js.map +7 -0
- package/dist/vector/services/vector-index.service.js +960 -0
- package/dist/vector/services/vector-index.service.js.map +7 -0
- package/dist/vector/types/pg.d.js +1 -0
- package/dist/vector/types/pg.d.js.map +7 -0
- package/dist/vector/types.js +75 -0
- package/dist/vector/types.js.map +7 -0
- package/jest.config.cjs +19 -0
- package/package.json +142 -0
- package/src/__tests__/queue.test.ts +148 -0
- package/src/__tests__/service.test.ts +345 -0
- package/src/__tests__/workers.test.ts +319 -0
- package/src/di.ts +291 -0
- package/src/fulltext/drivers/index.ts +41 -0
- package/src/fulltext/drivers/meilisearch/index.ts +410 -0
- package/src/fulltext/index.ts +13 -0
- package/src/fulltext/types.ts +115 -0
- package/src/index.ts +36 -0
- package/src/indexer/index.ts +13 -0
- package/src/indexer/search-indexer.ts +1141 -0
- package/src/indexer/subscribers/delete.ts +49 -0
- package/src/lib/debug.ts +46 -0
- package/src/lib/fallback-presenter.ts +106 -0
- package/src/lib/field-policy.ts +169 -0
- package/src/lib/index.ts +13 -0
- package/src/lib/merger.ts +159 -0
- package/src/lib/presenter-enricher.ts +323 -0
- package/src/modules/search/README.md +694 -0
- package/src/modules/search/acl.ts +10 -0
- package/src/modules/search/ai-tools.ts +467 -0
- package/src/modules/search/api/embeddings/reindex/cancel/route.ts +77 -0
- package/src/modules/search/api/embeddings/reindex/route.ts +197 -0
- package/src/modules/search/api/embeddings/route.ts +304 -0
- package/src/modules/search/api/index/route.ts +297 -0
- package/src/modules/search/api/reindex/cancel/route.ts +77 -0
- package/src/modules/search/api/reindex/route.ts +419 -0
- package/src/modules/search/api/search/global/route.ts +120 -0
- package/src/modules/search/api/search/route.ts +121 -0
- package/src/modules/search/api/settings/fulltext/route.ts +82 -0
- package/src/modules/search/api/settings/global-search/route.ts +91 -0
- package/src/modules/search/api/settings/route.ts +187 -0
- package/src/modules/search/api/settings/vector-store/route.ts +105 -0
- package/src/modules/search/backend/config/search/page.meta.ts +22 -0
- package/src/modules/search/backend/config/search/page.tsx +12 -0
- package/src/modules/search/cli.ts +818 -0
- package/src/modules/search/di.ts +50 -0
- package/src/modules/search/frontend/components/GlobalSearchDialog.tsx +436 -0
- package/src/modules/search/frontend/components/HybridSearchTable.tsx +418 -0
- package/src/modules/search/frontend/components/SearchSettingsPageClient.tsx +476 -0
- package/src/modules/search/frontend/components/sections/FulltextSearchSection.tsx +624 -0
- package/src/modules/search/frontend/components/sections/GlobalSearchSection.tsx +124 -0
- package/src/modules/search/frontend/components/sections/VectorSearchSection.tsx +943 -0
- package/src/modules/search/frontend/index.ts +3 -0
- package/src/modules/search/frontend/utils.ts +82 -0
- package/src/modules/search/i18n/de.json +61 -0
- package/src/modules/search/i18n/en.json +72 -0
- package/src/modules/search/i18n/es.json +61 -0
- package/src/modules/search/i18n/pl.json +61 -0
- package/src/modules/search/index.ts +9 -0
- package/src/modules/search/lib/auto-indexing.ts +35 -0
- package/src/modules/search/lib/embedding-config.ts +161 -0
- package/src/modules/search/lib/global-search-config.ts +69 -0
- package/src/modules/search/lib/reindex-lock.ts +201 -0
- package/src/modules/search/subscribers/fulltext_upsert.ts +83 -0
- package/src/modules/search/subscribers/vector_delete.ts +75 -0
- package/src/modules/search/subscribers/vector_purge.ts +161 -0
- package/src/modules/search/subscribers/vector_upsert.ts +75 -0
- package/src/modules/search/workers/fulltext-index.worker.ts +318 -0
- package/src/modules/search/workers/vector-index.worker.ts +292 -0
- package/src/queue/fulltext-indexing.ts +87 -0
- package/src/queue/index.ts +2 -0
- package/src/queue/vector-indexing.ts +66 -0
- package/src/service.ts +397 -0
- package/src/strategies/fulltext.strategy.ts +155 -0
- package/src/strategies/index.ts +17 -0
- package/src/strategies/token.strategy.ts +153 -0
- package/src/strategies/vector.strategy.ts +234 -0
- package/src/types.ts +38 -0
- package/src/vector/drivers/chromadb/index.ts +49 -0
- package/src/vector/drivers/index.ts +4 -0
- package/src/vector/drivers/pgvector/index.ts +627 -0
- package/src/vector/drivers/qdrant/index.ts +49 -0
- package/src/vector/index.ts +3 -0
- package/src/vector/lib/vector-logs.ts +46 -0
- package/src/vector/services/checksum.ts +18 -0
- package/src/vector/services/embedding.ts +275 -0
- package/src/vector/services/index.ts +3 -0
- package/src/vector/services/vector-index.service.ts +1234 -0
- package/src/vector/types/pg.d.ts +1 -0
- package/src/vector/types.ts +220 -0
- package/tsconfig.json +9 -0
- package/watch.mjs +6 -0
|
@@ -0,0 +1,627 @@
|
|
|
1
|
+
import { Pool } from 'pg'
|
|
2
|
+
import { searchDebugWarn } from '../../../lib/debug'
|
|
3
|
+
|
|
4
|
+
type PgPoolQueryResult<T> = { rows: T[]; rowCount?: number }
|
|
5
|
+
type PgPoolClient = {
|
|
6
|
+
query<T = any>(text: string, params?: any[]): Promise<PgPoolQueryResult<T>>
|
|
7
|
+
release(): void
|
|
8
|
+
}
|
|
9
|
+
type PgPool = {
|
|
10
|
+
connect(): Promise<PgPoolClient>
|
|
11
|
+
query<T = any>(text: string, params?: any[]): Promise<PgPoolQueryResult<T>>
|
|
12
|
+
end(): Promise<void>
|
|
13
|
+
}
|
|
14
|
+
import type {
|
|
15
|
+
VectorDriver,
|
|
16
|
+
VectorDriverDocument,
|
|
17
|
+
VectorDriverQuery,
|
|
18
|
+
VectorDriverQueryResult,
|
|
19
|
+
VectorDriverListParams,
|
|
20
|
+
VectorDriverCountParams,
|
|
21
|
+
VectorIndexEntry,
|
|
22
|
+
VectorDriverRemoveOrphansParams,
|
|
23
|
+
VectorResultPresenter,
|
|
24
|
+
VectorLinkDescriptor,
|
|
25
|
+
} from '../../types'
|
|
26
|
+
|
|
27
|
+
type PgVectorDriverOptions = {
|
|
28
|
+
pool?: PgPool
|
|
29
|
+
connectionString?: string
|
|
30
|
+
tableName?: string
|
|
31
|
+
migrationsTable?: string
|
|
32
|
+
dimension?: number
|
|
33
|
+
distanceMetric?: 'cosine' | 'euclidean' | 'inner'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const DEFAULT_TABLE = 'vector_search'
|
|
37
|
+
const DEFAULT_MIGRATIONS_TABLE = 'vector_search_migrations'
|
|
38
|
+
const DEFAULT_DIMENSION = 1536
|
|
39
|
+
const DRIVER_ID = 'pgvector' as const
|
|
40
|
+
|
|
41
|
+
function assertIdentifier(name: string, defaultName: string): string {
|
|
42
|
+
const candidate = name ?? defaultName
|
|
43
|
+
if (!candidate || !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(candidate)) return defaultName
|
|
44
|
+
return candidate
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function quoteIdent(name: string): string {
|
|
48
|
+
return `"${name}"`
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function toVectorLiteral(values: number[]): string {
|
|
52
|
+
const formatted = values.map((n) => {
|
|
53
|
+
if (!Number.isFinite(n)) return '0'
|
|
54
|
+
const rounded = Math.fround(n)
|
|
55
|
+
return Number.isInteger(rounded) ? `${rounded}.0` : `${rounded}`
|
|
56
|
+
})
|
|
57
|
+
return `[${formatted.join(',')}]`
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function parseJsonColumn<T>(value: unknown): T | null {
|
|
61
|
+
if (value === null || value === undefined) return null
|
|
62
|
+
if (typeof value === 'string') {
|
|
63
|
+
try {
|
|
64
|
+
return JSON.parse(value) as T
|
|
65
|
+
} catch {
|
|
66
|
+
// When `jsonb` stores a JSON string, node-postgres parses it into a plain JS string.
|
|
67
|
+
// In that case, there is nothing to JSON.parse — return the raw string value.
|
|
68
|
+
return value as unknown as T
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (typeof value === 'object') {
|
|
72
|
+
return value as T
|
|
73
|
+
}
|
|
74
|
+
return null
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function withClient<T>(pool: PgPool, fn: (client: PgPoolClient) => Promise<T>): Promise<T> {
|
|
78
|
+
const client = await pool.connect()
|
|
79
|
+
try {
|
|
80
|
+
return await fn(client)
|
|
81
|
+
} finally {
|
|
82
|
+
client.release()
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function createPgVectorDriver(opts: PgVectorDriverOptions = {}): VectorDriver {
|
|
87
|
+
const tableName = assertIdentifier(opts.tableName ?? DEFAULT_TABLE, DEFAULT_TABLE)
|
|
88
|
+
const migrationsTable = assertIdentifier(opts.migrationsTable ?? DEFAULT_MIGRATIONS_TABLE, DEFAULT_MIGRATIONS_TABLE)
|
|
89
|
+
let dimension = opts.dimension ?? DEFAULT_DIMENSION
|
|
90
|
+
const distanceMetric = opts.distanceMetric ?? 'cosine'
|
|
91
|
+
const tableIdent = quoteIdent(tableName)
|
|
92
|
+
const migrationsIdent = quoteIdent(migrationsTable)
|
|
93
|
+
|
|
94
|
+
const pool: PgPool =
|
|
95
|
+
opts.pool ??
|
|
96
|
+
(() => {
|
|
97
|
+
const conn = opts.connectionString ?? process.env.DATABASE_URL
|
|
98
|
+
if (!conn) {
|
|
99
|
+
throw new Error('[vector.pgvector] DATABASE_URL is not configured')
|
|
100
|
+
}
|
|
101
|
+
return new Pool({ connectionString: conn }) as unknown as PgPool
|
|
102
|
+
})()
|
|
103
|
+
|
|
104
|
+
let ready: Promise<void> | null = null
|
|
105
|
+
|
|
106
|
+
const ensureReady = async () => {
|
|
107
|
+
if (!ready) {
|
|
108
|
+
ready = withClient(pool, async (client) => {
|
|
109
|
+
const ensureExtension = async (extension: 'pgcrypto' | 'vector') => {
|
|
110
|
+
try {
|
|
111
|
+
await client.query(`CREATE EXTENSION IF NOT EXISTS ${extension}`)
|
|
112
|
+
} catch (error) {
|
|
113
|
+
const pgError = error as { code?: string; message?: string }
|
|
114
|
+
if (pgError?.code === '42501') {
|
|
115
|
+
const details = pgError.message ? ` (${pgError.message})` : ''
|
|
116
|
+
searchDebugWarn('vector.pgvector', `skipping ${extension} extension creation; requires superuser${details}`)
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
throw error
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
await ensureExtension('pgcrypto')
|
|
124
|
+
await ensureExtension('vector')
|
|
125
|
+
|
|
126
|
+
await client.query(
|
|
127
|
+
`CREATE TABLE IF NOT EXISTS ${migrationsIdent} (
|
|
128
|
+
id text primary key,
|
|
129
|
+
applied_at timestamptz not null default now()
|
|
130
|
+
)`,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
await client.query(
|
|
134
|
+
`CREATE TABLE IF NOT EXISTS ${tableIdent} (
|
|
135
|
+
id uuid primary key default gen_random_uuid(),
|
|
136
|
+
driver_id text not null,
|
|
137
|
+
entity_id text not null,
|
|
138
|
+
record_id text not null,
|
|
139
|
+
tenant_id uuid not null,
|
|
140
|
+
organization_id uuid null,
|
|
141
|
+
checksum text not null,
|
|
142
|
+
embedding vector(${dimension}) not null,
|
|
143
|
+
url text null,
|
|
144
|
+
presenter jsonb null,
|
|
145
|
+
links jsonb null,
|
|
146
|
+
payload jsonb null,
|
|
147
|
+
result_title text null,
|
|
148
|
+
result_subtitle text null,
|
|
149
|
+
result_icon text null,
|
|
150
|
+
result_badge text null,
|
|
151
|
+
result_snapshot text null,
|
|
152
|
+
primary_link_href text null,
|
|
153
|
+
primary_link_label text null,
|
|
154
|
+
created_at timestamptz not null default now(),
|
|
155
|
+
updated_at timestamptz not null default now()
|
|
156
|
+
)`,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
await client.query(
|
|
160
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS ${tableName}_uniq ON ${tableIdent} (driver_id, entity_id, record_id, tenant_id)`,
|
|
161
|
+
)
|
|
162
|
+
await client.query(
|
|
163
|
+
`CREATE INDEX IF NOT EXISTS ${tableName}_lookup ON ${tableIdent} (tenant_id, organization_id, entity_id)`,
|
|
164
|
+
)
|
|
165
|
+
// ivfflat index only supports up to 2000 dimensions
|
|
166
|
+
// For higher dimensions, skip the index (uses sequential scan, slower but works)
|
|
167
|
+
// Also check actual table dimension in case driver was initialized with different value
|
|
168
|
+
let actualDimension = dimension
|
|
169
|
+
try {
|
|
170
|
+
const dimResult = await client.query<{ atttypmod: number }>(
|
|
171
|
+
`SELECT a.atttypmod
|
|
172
|
+
FROM pg_attribute a
|
|
173
|
+
JOIN pg_class c ON a.attrelid = c.oid
|
|
174
|
+
WHERE c.relname = $1
|
|
175
|
+
AND a.attname = 'embedding'
|
|
176
|
+
AND a.atttypmod > 0`,
|
|
177
|
+
[tableName]
|
|
178
|
+
)
|
|
179
|
+
if (dimResult.rows.length > 0 && dimResult.rows[0].atttypmod > 0) {
|
|
180
|
+
actualDimension = dimResult.rows[0].atttypmod
|
|
181
|
+
}
|
|
182
|
+
} catch {
|
|
183
|
+
// Ignore errors reading dimension, use configured value
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (actualDimension <= 2000) {
|
|
187
|
+
try {
|
|
188
|
+
await client.query(
|
|
189
|
+
`CREATE INDEX IF NOT EXISTS ${tableName}_embedding_idx ON ${tableIdent}
|
|
190
|
+
USING ivfflat (embedding vector_${distanceMetric}_ops) WITH (lists = 100)`,
|
|
191
|
+
)
|
|
192
|
+
} catch (indexErr: unknown) {
|
|
193
|
+
// Handle case where dimension exceeds ivfflat limit
|
|
194
|
+
const errorMessage = indexErr instanceof Error ? indexErr.message : String(indexErr)
|
|
195
|
+
if (errorMessage.includes('2000 dimensions')) {
|
|
196
|
+
searchDebugWarn('pgvector', 'Skipping ivfflat index - dimension exceeds 2000 limit. Searches will use sequential scan.')
|
|
197
|
+
} else {
|
|
198
|
+
throw indexErr
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
searchDebugWarn('pgvector', `Skipping ivfflat index - dimension ${actualDimension} exceeds 2000 limit. Searches will use sequential scan.`)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const columnAlters = [
|
|
206
|
+
`ALTER TABLE ${tableIdent} ADD COLUMN IF NOT EXISTS result_title text`,
|
|
207
|
+
`ALTER TABLE ${tableIdent} ADD COLUMN IF NOT EXISTS result_subtitle text`,
|
|
208
|
+
`ALTER TABLE ${tableIdent} ADD COLUMN IF NOT EXISTS result_icon text`,
|
|
209
|
+
`ALTER TABLE ${tableIdent} ADD COLUMN IF NOT EXISTS result_badge text`,
|
|
210
|
+
`ALTER TABLE ${tableIdent} ADD COLUMN IF NOT EXISTS result_snapshot text`,
|
|
211
|
+
`ALTER TABLE ${tableIdent} ADD COLUMN IF NOT EXISTS primary_link_href text`,
|
|
212
|
+
`ALTER TABLE ${tableIdent} ADD COLUMN IF NOT EXISTS primary_link_label text`,
|
|
213
|
+
]
|
|
214
|
+
for (const statement of columnAlters) {
|
|
215
|
+
await client.query(statement)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
await client.query(
|
|
219
|
+
`INSERT INTO ${migrationsIdent} (id, applied_at) VALUES ($1, now()) ON CONFLICT (id) DO NOTHING`,
|
|
220
|
+
['0001_init'],
|
|
221
|
+
)
|
|
222
|
+
}).catch((err) => {
|
|
223
|
+
ready = null
|
|
224
|
+
throw err
|
|
225
|
+
})
|
|
226
|
+
}
|
|
227
|
+
return ready
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const upsert = async (doc: VectorDriverDocument) => {
|
|
231
|
+
await ensureReady()
|
|
232
|
+
const vectorLiteral = toVectorLiteral(doc.embedding)
|
|
233
|
+
await pool.query(
|
|
234
|
+
`
|
|
235
|
+
INSERT INTO ${tableIdent} (
|
|
236
|
+
driver_id, entity_id, record_id, tenant_id, organization_id, checksum,
|
|
237
|
+
embedding, url, presenter, links, payload,
|
|
238
|
+
result_title, result_subtitle, result_icon, result_badge, result_snapshot,
|
|
239
|
+
primary_link_href, primary_link_label,
|
|
240
|
+
created_at, updated_at
|
|
241
|
+
)
|
|
242
|
+
VALUES (
|
|
243
|
+
$1, $2, $3, $4::uuid, $5::uuid, $6, $7::vector, $8, $9::jsonb, $10::jsonb, $11::jsonb,
|
|
244
|
+
$12, $13, $14, $15, $16, $17, $18,
|
|
245
|
+
now(), now()
|
|
246
|
+
)
|
|
247
|
+
ON CONFLICT (driver_id, entity_id, record_id, tenant_id)
|
|
248
|
+
DO UPDATE SET
|
|
249
|
+
organization_id = EXCLUDED.organization_id,
|
|
250
|
+
checksum = EXCLUDED.checksum,
|
|
251
|
+
embedding = EXCLUDED.embedding,
|
|
252
|
+
url = EXCLUDED.url,
|
|
253
|
+
presenter = EXCLUDED.presenter,
|
|
254
|
+
links = EXCLUDED.links,
|
|
255
|
+
payload = EXCLUDED.payload,
|
|
256
|
+
result_title = EXCLUDED.result_title,
|
|
257
|
+
result_subtitle = EXCLUDED.result_subtitle,
|
|
258
|
+
result_icon = EXCLUDED.result_icon,
|
|
259
|
+
result_badge = EXCLUDED.result_badge,
|
|
260
|
+
result_snapshot = EXCLUDED.result_snapshot,
|
|
261
|
+
primary_link_href = EXCLUDED.primary_link_href,
|
|
262
|
+
primary_link_label = EXCLUDED.primary_link_label,
|
|
263
|
+
updated_at = now()
|
|
264
|
+
`,
|
|
265
|
+
[
|
|
266
|
+
doc.driverId ?? DRIVER_ID,
|
|
267
|
+
doc.entityId,
|
|
268
|
+
doc.recordId,
|
|
269
|
+
doc.tenantId,
|
|
270
|
+
doc.organizationId ?? null,
|
|
271
|
+
doc.checksum,
|
|
272
|
+
vectorLiteral,
|
|
273
|
+
doc.url ?? null,
|
|
274
|
+
doc.presenter ? JSON.stringify(doc.presenter) : null,
|
|
275
|
+
doc.links ? JSON.stringify(doc.links) : null,
|
|
276
|
+
doc.payload ? JSON.stringify(doc.payload) : null,
|
|
277
|
+
doc.resultTitle,
|
|
278
|
+
doc.resultSubtitle ?? null,
|
|
279
|
+
doc.resultIcon ?? null,
|
|
280
|
+
doc.resultBadge ?? null,
|
|
281
|
+
doc.resultSnapshot ?? null,
|
|
282
|
+
doc.primaryLinkHref ?? null,
|
|
283
|
+
doc.primaryLinkLabel ?? null,
|
|
284
|
+
],
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const remove = async (entityId: string, recordId: string, tenantId: string) => {
|
|
289
|
+
await ensureReady()
|
|
290
|
+
await pool.query(
|
|
291
|
+
`DELETE FROM ${tableIdent} WHERE driver_id = $1 AND entity_id = $2 AND record_id = $3 AND tenant_id = $4::uuid`,
|
|
292
|
+
[DRIVER_ID, entityId, recordId, tenantId],
|
|
293
|
+
)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const getChecksum = async (entityId: string, recordId: string, tenantId: string): Promise<string | null> => {
|
|
297
|
+
await ensureReady()
|
|
298
|
+
const res = await pool.query<{ checksum: string }>(
|
|
299
|
+
`SELECT checksum FROM ${tableIdent} WHERE driver_id = $1 AND entity_id = $2 AND record_id = $3 AND tenant_id = $4::uuid`,
|
|
300
|
+
[DRIVER_ID, entityId, recordId, tenantId],
|
|
301
|
+
)
|
|
302
|
+
return res.rowCount ? res.rows[0].checksum : null
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const purge = async (entityId: string, tenantId: string) => {
|
|
306
|
+
await ensureReady()
|
|
307
|
+
await pool.query(
|
|
308
|
+
`DELETE FROM ${tableIdent} WHERE driver_id = $1 AND entity_id = $2 AND tenant_id = $3::uuid`,
|
|
309
|
+
[DRIVER_ID, entityId, tenantId],
|
|
310
|
+
)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const query = async (input: VectorDriverQuery): Promise<VectorDriverQueryResult[]> => {
|
|
314
|
+
await ensureReady()
|
|
315
|
+
const vectorLiteral = toVectorLiteral(input.vector)
|
|
316
|
+
const filter = input.filter ?? { tenantId: '' }
|
|
317
|
+
// Check if organizationId is explicitly set in filter (vs undefined/missing)
|
|
318
|
+
// undefined = no org filter, null = filter for records with null org_id
|
|
319
|
+
const hasOrgFilter = 'organizationId' in filter
|
|
320
|
+
const params: any[] = [
|
|
321
|
+
vectorLiteral,
|
|
322
|
+
DRIVER_ID,
|
|
323
|
+
filter.tenantId,
|
|
324
|
+
hasOrgFilter ? (filter.organizationId ?? null) : null,
|
|
325
|
+
Array.isArray(filter.entityIds) && filter.entityIds.length ? filter.entityIds : null,
|
|
326
|
+
input.limit ?? 20,
|
|
327
|
+
hasOrgFilter, // $7: whether to apply org filter
|
|
328
|
+
]
|
|
329
|
+
const res = await pool.query<{
|
|
330
|
+
entity_id: string
|
|
331
|
+
record_id: string
|
|
332
|
+
organization_id: string | null
|
|
333
|
+
checksum: string
|
|
334
|
+
url: string | null
|
|
335
|
+
presenter: string | null
|
|
336
|
+
links: string | null
|
|
337
|
+
payload: string | null
|
|
338
|
+
result_title: string | null
|
|
339
|
+
result_subtitle: string | null
|
|
340
|
+
result_icon: string | null
|
|
341
|
+
result_badge: string | null
|
|
342
|
+
result_snapshot: string | null
|
|
343
|
+
primary_link_href: string | null
|
|
344
|
+
primary_link_label: string | null
|
|
345
|
+
distance: number
|
|
346
|
+
}>(
|
|
347
|
+
`
|
|
348
|
+
SELECT
|
|
349
|
+
entity_id,
|
|
350
|
+
record_id,
|
|
351
|
+
organization_id,
|
|
352
|
+
checksum,
|
|
353
|
+
url,
|
|
354
|
+
presenter,
|
|
355
|
+
links,
|
|
356
|
+
payload,
|
|
357
|
+
result_title,
|
|
358
|
+
result_subtitle,
|
|
359
|
+
result_icon,
|
|
360
|
+
result_badge,
|
|
361
|
+
result_snapshot,
|
|
362
|
+
primary_link_href,
|
|
363
|
+
primary_link_label,
|
|
364
|
+
embedding <=> $1::vector AS distance
|
|
365
|
+
FROM ${tableIdent}
|
|
366
|
+
WHERE driver_id = $2
|
|
367
|
+
AND tenant_id = $3::uuid
|
|
368
|
+
AND (
|
|
369
|
+
$7::boolean = false
|
|
370
|
+
OR ($4::uuid IS NULL AND organization_id IS NULL)
|
|
371
|
+
OR ($4::uuid IS NOT NULL AND (organization_id = $4::uuid OR organization_id IS NULL))
|
|
372
|
+
)
|
|
373
|
+
AND (
|
|
374
|
+
$5::text[] IS NULL OR entity_id = ANY($5::text[])
|
|
375
|
+
)
|
|
376
|
+
ORDER BY embedding <=> $1::vector
|
|
377
|
+
LIMIT $6
|
|
378
|
+
`,
|
|
379
|
+
params,
|
|
380
|
+
)
|
|
381
|
+
return res.rows.map<VectorDriverQueryResult>((row) => {
|
|
382
|
+
const distance = typeof row.distance === 'number' ? row.distance : Number(row.distance || 1)
|
|
383
|
+
const score = 1 - distance
|
|
384
|
+
return {
|
|
385
|
+
entityId: row.entity_id,
|
|
386
|
+
recordId: row.record_id,
|
|
387
|
+
organizationId: row.organization_id ?? null,
|
|
388
|
+
checksum: row.checksum,
|
|
389
|
+
url: row.url ?? null,
|
|
390
|
+
presenter: parseJsonColumn<VectorResultPresenter>(row.presenter),
|
|
391
|
+
links: parseJsonColumn<VectorLinkDescriptor[]>(row.links),
|
|
392
|
+
payload: parseJsonColumn<Record<string, unknown>>(row.payload),
|
|
393
|
+
resultTitle: row.result_title ?? '',
|
|
394
|
+
resultSubtitle: row.result_subtitle ?? null,
|
|
395
|
+
resultIcon: row.result_icon ?? null,
|
|
396
|
+
resultBadge: row.result_badge ?? null,
|
|
397
|
+
resultSnapshot: row.result_snapshot ?? null,
|
|
398
|
+
primaryLinkHref: row.primary_link_href ?? null,
|
|
399
|
+
primaryLinkLabel: row.primary_link_label ?? null,
|
|
400
|
+
score,
|
|
401
|
+
}
|
|
402
|
+
})
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const list = async (params: VectorDriverListParams): Promise<VectorIndexEntry[]> => {
|
|
406
|
+
await ensureReady()
|
|
407
|
+
const limit = Math.max(1, Math.min(params.limit ?? 50, 200))
|
|
408
|
+
const offset = Math.max(0, params.offset ?? 0)
|
|
409
|
+
const orderColumn = params.orderBy === 'created' ? 'created_at' : 'updated_at'
|
|
410
|
+
const conditions: string[] = [
|
|
411
|
+
'driver_id = $1',
|
|
412
|
+
'tenant_id = $2::uuid',
|
|
413
|
+
]
|
|
414
|
+
const values: any[] = [DRIVER_ID, params.tenantId]
|
|
415
|
+
let nextParam = 3
|
|
416
|
+
|
|
417
|
+
if (params.organizationId === null) {
|
|
418
|
+
conditions.push('organization_id IS NULL')
|
|
419
|
+
} else if (typeof params.organizationId === 'string' && params.organizationId.length) {
|
|
420
|
+
conditions.push(`(organization_id = $${nextParam}::uuid OR organization_id IS NULL)`)
|
|
421
|
+
values.push(params.organizationId)
|
|
422
|
+
nextParam += 1
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (params.entityId) {
|
|
426
|
+
conditions.push(`entity_id = $${nextParam}::text`)
|
|
427
|
+
values.push(params.entityId)
|
|
428
|
+
nextParam += 1
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const limitParam = nextParam
|
|
432
|
+
const offsetParam = nextParam + 1
|
|
433
|
+
values.push(limit, offset)
|
|
434
|
+
|
|
435
|
+
const sql = `
|
|
436
|
+
SELECT
|
|
437
|
+
entity_id,
|
|
438
|
+
record_id,
|
|
439
|
+
tenant_id,
|
|
440
|
+
organization_id,
|
|
441
|
+
checksum,
|
|
442
|
+
url,
|
|
443
|
+
presenter,
|
|
444
|
+
links,
|
|
445
|
+
payload,
|
|
446
|
+
result_title,
|
|
447
|
+
result_subtitle,
|
|
448
|
+
result_icon,
|
|
449
|
+
result_badge,
|
|
450
|
+
result_snapshot,
|
|
451
|
+
primary_link_href,
|
|
452
|
+
primary_link_label,
|
|
453
|
+
created_at,
|
|
454
|
+
updated_at
|
|
455
|
+
FROM ${tableIdent}
|
|
456
|
+
WHERE ${conditions.join('\n AND ')}
|
|
457
|
+
ORDER BY ${orderColumn} DESC
|
|
458
|
+
LIMIT $${limitParam} OFFSET $${offsetParam}
|
|
459
|
+
`
|
|
460
|
+
|
|
461
|
+
const res = await pool.query<{
|
|
462
|
+
entity_id: string
|
|
463
|
+
record_id: string
|
|
464
|
+
tenant_id: string
|
|
465
|
+
organization_id: string | null
|
|
466
|
+
checksum: string
|
|
467
|
+
url: string | null
|
|
468
|
+
presenter: string | null
|
|
469
|
+
links: string | null
|
|
470
|
+
payload: string | null
|
|
471
|
+
result_title: string | null
|
|
472
|
+
result_subtitle: string | null
|
|
473
|
+
result_icon: string | null
|
|
474
|
+
result_badge: string | null
|
|
475
|
+
result_snapshot: string | null
|
|
476
|
+
primary_link_href: string | null
|
|
477
|
+
primary_link_label: string | null
|
|
478
|
+
created_at: Date | string
|
|
479
|
+
updated_at: Date | string
|
|
480
|
+
}>(sql, values)
|
|
481
|
+
return res.rows.map<VectorIndexEntry>((row) => {
|
|
482
|
+
const presenter = parseJsonColumn<VectorResultPresenter>(row.presenter)
|
|
483
|
+
const links = parseJsonColumn<VectorLinkDescriptor[]>(row.links)
|
|
484
|
+
const payload = parseJsonColumn<Record<string, unknown>>(row.payload)
|
|
485
|
+
const createdAt =
|
|
486
|
+
row.created_at instanceof Date
|
|
487
|
+
? row.created_at.toISOString()
|
|
488
|
+
: new Date(row.created_at ?? Date.now()).toISOString()
|
|
489
|
+
const updatedAt =
|
|
490
|
+
row.updated_at instanceof Date
|
|
491
|
+
? row.updated_at.toISOString()
|
|
492
|
+
: new Date(row.updated_at ?? Date.now()).toISOString()
|
|
493
|
+
return {
|
|
494
|
+
entityId: row.entity_id,
|
|
495
|
+
recordId: row.record_id,
|
|
496
|
+
driverId: DRIVER_ID,
|
|
497
|
+
tenantId: row.tenant_id,
|
|
498
|
+
organizationId: row.organization_id ?? null,
|
|
499
|
+
checksum: row.checksum,
|
|
500
|
+
url: row.url ?? null,
|
|
501
|
+
presenter,
|
|
502
|
+
links,
|
|
503
|
+
payload,
|
|
504
|
+
metadata: payload,
|
|
505
|
+
resultTitle: row.result_title ?? '',
|
|
506
|
+
resultSubtitle: row.result_subtitle ?? null,
|
|
507
|
+
resultIcon: row.result_icon ?? null,
|
|
508
|
+
resultBadge: row.result_badge ?? null,
|
|
509
|
+
resultSnapshot: row.result_snapshot ?? null,
|
|
510
|
+
primaryLinkHref: row.primary_link_href ?? null,
|
|
511
|
+
primaryLinkLabel: row.primary_link_label ?? null,
|
|
512
|
+
createdAt,
|
|
513
|
+
updatedAt,
|
|
514
|
+
score: null,
|
|
515
|
+
}
|
|
516
|
+
})
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const count = async (params: VectorDriverCountParams): Promise<number> => {
|
|
520
|
+
await ensureReady()
|
|
521
|
+
const conditions: string[] = [
|
|
522
|
+
'driver_id = $1',
|
|
523
|
+
'tenant_id = $2::uuid',
|
|
524
|
+
]
|
|
525
|
+
const values: any[] = [DRIVER_ID, params.tenantId]
|
|
526
|
+
let nextParam = 3
|
|
527
|
+
|
|
528
|
+
if (params.organizationId === null) {
|
|
529
|
+
conditions.push('organization_id IS NULL')
|
|
530
|
+
} else if (typeof params.organizationId === 'string' && params.organizationId.length) {
|
|
531
|
+
conditions.push(`(organization_id = $${nextParam}::uuid OR organization_id IS NULL)`)
|
|
532
|
+
values.push(params.organizationId)
|
|
533
|
+
nextParam += 1
|
|
534
|
+
}
|
|
535
|
+
if (params.entityId) {
|
|
536
|
+
conditions.push(`entity_id = $${nextParam}::text`)
|
|
537
|
+
values.push(params.entityId)
|
|
538
|
+
nextParam += 1
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
const sql = `
|
|
542
|
+
SELECT count(*)::bigint AS total
|
|
543
|
+
FROM ${tableIdent}
|
|
544
|
+
WHERE ${conditions.join('\n AND ')}
|
|
545
|
+
`
|
|
546
|
+
const res = await pool.query<{ total: string }>(sql, values)
|
|
547
|
+
const raw = res.rows?.[0]?.total
|
|
548
|
+
if (!raw) return 0
|
|
549
|
+
const parsed = Number(raw)
|
|
550
|
+
return Number.isFinite(parsed) ? parsed : 0
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const removeOrphans = async (params: VectorDriverRemoveOrphansParams): Promise<number> => {
|
|
554
|
+
await ensureReady()
|
|
555
|
+
const conditions: string[] = [
|
|
556
|
+
'driver_id = $1',
|
|
557
|
+
'entity_id = $2',
|
|
558
|
+
'updated_at < $3::timestamptz',
|
|
559
|
+
]
|
|
560
|
+
const values: any[] = [DRIVER_ID, params.entityId, (params.olderThan instanceof Date ? params.olderThan : new Date(params.olderThan)).toISOString()]
|
|
561
|
+
let nextParam = 4
|
|
562
|
+
|
|
563
|
+
if (params.tenantId !== undefined) {
|
|
564
|
+
conditions.push(`tenant_id is not distinct from $${nextParam}::uuid`)
|
|
565
|
+
values.push(params.tenantId)
|
|
566
|
+
nextParam += 1
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
if (params.organizationId !== undefined) {
|
|
570
|
+
conditions.push(`organization_id is not distinct from $${nextParam}::uuid`)
|
|
571
|
+
values.push(params.organizationId)
|
|
572
|
+
nextParam += 1
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const sql = `
|
|
576
|
+
DELETE FROM ${tableIdent}
|
|
577
|
+
WHERE ${conditions.join('\n AND ')}
|
|
578
|
+
`
|
|
579
|
+
const res = await pool.query(sql, values)
|
|
580
|
+
return res.rowCount ?? 0
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
const getTableDimension = async (): Promise<number | null> => {
|
|
584
|
+
try {
|
|
585
|
+
const res = await pool.query<{ atttypmod: number }>(
|
|
586
|
+
`SELECT a.atttypmod
|
|
587
|
+
FROM pg_attribute a
|
|
588
|
+
JOIN pg_class c ON a.attrelid = c.oid
|
|
589
|
+
WHERE c.relname = $1
|
|
590
|
+
AND a.attname = 'embedding'
|
|
591
|
+
AND a.atttypmod > 0`,
|
|
592
|
+
[tableName]
|
|
593
|
+
)
|
|
594
|
+
if (res.rows.length > 0 && res.rows[0].atttypmod > 0) {
|
|
595
|
+
return res.rows[0].atttypmod
|
|
596
|
+
}
|
|
597
|
+
return null
|
|
598
|
+
} catch {
|
|
599
|
+
return null
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
const recreateWithDimension = async (newDimension: number): Promise<void> => {
|
|
604
|
+
await withClient(pool, async (client) => {
|
|
605
|
+
await client.query(`DROP TABLE IF EXISTS ${tableIdent} CASCADE`)
|
|
606
|
+
await client.query(`DROP TABLE IF EXISTS ${migrationsIdent} CASCADE`)
|
|
607
|
+
})
|
|
608
|
+
ready = null
|
|
609
|
+
dimension = newDimension
|
|
610
|
+
await ensureReady()
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
return {
|
|
614
|
+
id: 'pgvector',
|
|
615
|
+
ensureReady,
|
|
616
|
+
upsert,
|
|
617
|
+
delete: remove,
|
|
618
|
+
getChecksum,
|
|
619
|
+
purge,
|
|
620
|
+
query,
|
|
621
|
+
list,
|
|
622
|
+
count,
|
|
623
|
+
removeOrphans,
|
|
624
|
+
getTableDimension,
|
|
625
|
+
recreateWithDimension,
|
|
626
|
+
}
|
|
627
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
VectorDriver,
|
|
3
|
+
VectorDriverDocument,
|
|
4
|
+
VectorDriverQuery,
|
|
5
|
+
VectorDriverQueryResult,
|
|
6
|
+
VectorDriverCountParams,
|
|
7
|
+
} from '../../types'
|
|
8
|
+
|
|
9
|
+
function notImplemented(method: string): never {
|
|
10
|
+
throw new Error(`[vector.qdrant] ${method} not implemented yet`)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function createQdrantDriver(): VectorDriver {
|
|
14
|
+
return {
|
|
15
|
+
id: 'qdrant',
|
|
16
|
+
async ensureReady() {
|
|
17
|
+
notImplemented('ensureReady')
|
|
18
|
+
},
|
|
19
|
+
async upsert(doc: VectorDriverDocument) {
|
|
20
|
+
void doc
|
|
21
|
+
notImplemented('upsert')
|
|
22
|
+
},
|
|
23
|
+
async delete(entityId: string, recordId: string, tenantId: string) {
|
|
24
|
+
void entityId
|
|
25
|
+
void recordId
|
|
26
|
+
void tenantId
|
|
27
|
+
notImplemented('delete')
|
|
28
|
+
},
|
|
29
|
+
async getChecksum(entityId: string, recordId: string, tenantId: string) {
|
|
30
|
+
void entityId
|
|
31
|
+
void recordId
|
|
32
|
+
void tenantId
|
|
33
|
+
notImplemented('getChecksum')
|
|
34
|
+
},
|
|
35
|
+
async query(input: VectorDriverQuery): Promise<VectorDriverQueryResult[]> {
|
|
36
|
+
void input
|
|
37
|
+
notImplemented('query')
|
|
38
|
+
},
|
|
39
|
+
async purge(entityId: string, tenantId: string) {
|
|
40
|
+
void entityId
|
|
41
|
+
void tenantId
|
|
42
|
+
notImplemented('purge')
|
|
43
|
+
},
|
|
44
|
+
async count(params: VectorDriverCountParams) {
|
|
45
|
+
void params
|
|
46
|
+
notImplemented('count')
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
}
|