@vivantel/virage-store-postgres 0.2.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/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/query-perf.d.ts +4 -0
- package/dist/query-perf.d.ts.map +1 -0
- package/dist/query-perf.js +76 -0
- package/dist/query-perf.js.map +1 -0
- package/dist/query-perf.test.d.ts +2 -0
- package/dist/query-perf.test.d.ts.map +1 -0
- package/dist/query-perf.test.js +133 -0
- package/dist/query-perf.test.js.map +1 -0
- package/dist/stats.d.ts +4 -0
- package/dist/stats.d.ts.map +1 -0
- package/dist/stats.js +102 -0
- package/dist/stats.js.map +1 -0
- package/dist/stats.test.d.ts +2 -0
- package/dist/stats.test.d.ts.map +1 -0
- package/dist/stats.test.js +274 -0
- package/dist/stats.test.js.map +1 -0
- package/dist/store.d.ts +54 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +142 -0
- package/dist/store.js.map +1 -0
- package/package.json +64 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { PostgresVectorStore, type PostgresVectorStoreOptions, type IndexType, type IVFFlatParams, type HNSWParams, } from "./store.js";
|
|
2
|
+
import type { VectorStore } from "@vivantel/virage-core";
|
|
3
|
+
/** Factory used by the JSON config loader. */
|
|
4
|
+
export declare function createVectorStore(config: Record<string, unknown>): VectorStore;
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,KAAK,0BAA0B,EAC/B,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,UAAU,GAChB,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAGzD,8CAA8C;AAC9C,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,WAAW,CAcb"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export { PostgresVectorStore, } from "./store.js";
|
|
2
|
+
import { PostgresVectorStore } from "./store.js";
|
|
3
|
+
/** Factory used by the JSON config loader. */
|
|
4
|
+
export function createVectorStore(config) {
|
|
5
|
+
const connectionString = config.connectionString;
|
|
6
|
+
if (typeof connectionString !== "string" || !connectionString) {
|
|
7
|
+
throw new Error("@vivantel/virage-store-postgres: config.connectionString is required");
|
|
8
|
+
}
|
|
9
|
+
return new PostgresVectorStore({
|
|
10
|
+
connectionString,
|
|
11
|
+
table: typeof config.table === "string" ? config.table : undefined,
|
|
12
|
+
dimensions: typeof config.dimensions === "number" ? config.dimensions : undefined,
|
|
13
|
+
ssl: typeof config.ssl === "boolean" ? config.ssl : undefined,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,GAKpB,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEjD,8CAA8C;AAC9C,MAAM,UAAU,iBAAiB,CAC/B,MAA+B;IAE/B,MAAM,gBAAgB,GAAG,MAAM,CAAC,gBAAgB,CAAC;IACjD,IAAI,OAAO,gBAAgB,KAAK,QAAQ,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC9D,MAAM,IAAI,KAAK,CACb,sEAAsE,CACvE,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,mBAAmB,CAAC;QAC7B,gBAAgB;QAChB,KAAK,EAAE,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS;QAClE,UAAU,EACR,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS;QACvE,GAAG,EAAE,OAAO,MAAM,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS;KAC9D,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-perf.d.ts","sourceRoot":"","sources":["../src/query-perf.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,MAAM,IAAI,CAAC;AAEpB,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,EAAE,CAAC,IAAI,EACb,KAAK,EAAE,MAAM,EACb,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,eAAe,CAAC,CAkG1B"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export async function getQueryPerfReport(pool, table, timeframeHours) {
|
|
2
|
+
const client = await pool.connect();
|
|
3
|
+
try {
|
|
4
|
+
// Check if pg_stat_statements extension is available
|
|
5
|
+
const extRes = await client.query(`SELECT COUNT(*) AS count FROM pg_extension WHERE extname = 'pg_stat_statements'`);
|
|
6
|
+
const hasExtension = parseInt(extRes.rows[0]?.count ?? "0", 10) > 0;
|
|
7
|
+
if (!hasExtension) {
|
|
8
|
+
return {
|
|
9
|
+
timeframeHours,
|
|
10
|
+
p50LatencyMs: -1,
|
|
11
|
+
p95LatencyMs: -1,
|
|
12
|
+
p99LatencyMs: -1,
|
|
13
|
+
slowQueryCount: -1,
|
|
14
|
+
suggestedIndexes: [
|
|
15
|
+
`Enable pg_stat_statements for query performance monitoring: ` +
|
|
16
|
+
`ALTER SYSTEM SET shared_preload_libraries = 'pg_stat_statements'; ` +
|
|
17
|
+
`SELECT pg_reload_conf();`,
|
|
18
|
+
],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
// pg_stat_statements doesn't filter by time natively, we use what's available
|
|
22
|
+
const res = await client.query(`SELECT mean_exec_time, stddev_exec_time, calls, query
|
|
23
|
+
FROM pg_stat_statements
|
|
24
|
+
WHERE query ILIKE $1
|
|
25
|
+
AND query NOT ILIKE '%pg_stat_statements%'
|
|
26
|
+
AND query NOT ILIKE '%pg_indexes%'
|
|
27
|
+
ORDER BY mean_exec_time DESC
|
|
28
|
+
LIMIT 100`, [`%${table}%`]);
|
|
29
|
+
if (res.rows.length === 0) {
|
|
30
|
+
return {
|
|
31
|
+
timeframeHours,
|
|
32
|
+
p50LatencyMs: 0,
|
|
33
|
+
p95LatencyMs: 0,
|
|
34
|
+
p99LatencyMs: 0,
|
|
35
|
+
slowQueryCount: 0,
|
|
36
|
+
suggestedIndexes: [
|
|
37
|
+
`No queries found for table "${table}" in pg_stat_statements`,
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
// Build latency distribution by expanding mean ± stddev approximation
|
|
42
|
+
const latencies = [];
|
|
43
|
+
for (const row of res.rows) {
|
|
44
|
+
// Include each query weighted by its call count (capped at 10 for memory)
|
|
45
|
+
const weight = Math.min(row.calls, 10);
|
|
46
|
+
for (let i = 0; i < weight; i++) {
|
|
47
|
+
latencies.push(row.mean_exec_time);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
latencies.sort((a, b) => a - b);
|
|
51
|
+
const percentile = (p) => latencies[Math.floor((p / 100) * latencies.length)] ?? 0;
|
|
52
|
+
const slowQueryCount = res.rows.filter((r) => r.mean_exec_time > 100).length;
|
|
53
|
+
const suggestedIndexes = [];
|
|
54
|
+
if (slowQueryCount > 0) {
|
|
55
|
+
suggestedIndexes.push(`${slowQueryCount} slow queries (>100ms) found. Consider reviewing index configuration.`);
|
|
56
|
+
}
|
|
57
|
+
if (res.rows.some((r) => r.query.toLowerCase().includes("seq scan"))) {
|
|
58
|
+
suggestedIndexes.push(`Sequential scans detected. Ensure the embedding index is being used.`);
|
|
59
|
+
}
|
|
60
|
+
if (suggestedIndexes.length === 0) {
|
|
61
|
+
suggestedIndexes.push("Query performance looks healthy.");
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
timeframeHours,
|
|
65
|
+
p50LatencyMs: Math.round(percentile(50) * 10) / 10,
|
|
66
|
+
p95LatencyMs: Math.round(percentile(95) * 10) / 10,
|
|
67
|
+
p99LatencyMs: Math.round(percentile(99) * 10) / 10,
|
|
68
|
+
slowQueryCount,
|
|
69
|
+
suggestedIndexes,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
client.release();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=query-perf.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-perf.js","sourceRoot":"","sources":["../src/query-perf.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-perf.test.d.ts","sourceRoot":"","sources":["../src/query-perf.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { getQueryPerfReport } from "./query-perf.js";
|
|
3
|
+
function makePool(queryResults) {
|
|
4
|
+
const client = {
|
|
5
|
+
query: vi.fn(),
|
|
6
|
+
release: vi.fn(),
|
|
7
|
+
};
|
|
8
|
+
for (const result of queryResults) {
|
|
9
|
+
client.query.mockResolvedValueOnce(result);
|
|
10
|
+
}
|
|
11
|
+
const pool = {
|
|
12
|
+
connect: vi.fn().mockResolvedValue(client),
|
|
13
|
+
};
|
|
14
|
+
return { pool, client };
|
|
15
|
+
}
|
|
16
|
+
const EXTENSION_ABSENT = [{ rows: [{ count: "0" }] }];
|
|
17
|
+
const EXTENSION_PRESENT = { rows: [{ count: "1" }] };
|
|
18
|
+
function makeStatRow(mean_exec_time, calls, query = "SELECT id FROM documents") {
|
|
19
|
+
return { mean_exec_time, stddev_exec_time: 5, calls, query };
|
|
20
|
+
}
|
|
21
|
+
describe("getQueryPerfReport (PostgreSQL)", () => {
|
|
22
|
+
describe("pg_stat_statements not installed", () => {
|
|
23
|
+
it("returns all -1 latencies and an enable-extension suggestion", async () => {
|
|
24
|
+
const { pool } = makePool(EXTENSION_ABSENT);
|
|
25
|
+
const report = await getQueryPerfReport(pool, "documents", 24);
|
|
26
|
+
expect(report.p50LatencyMs).toBe(-1);
|
|
27
|
+
expect(report.p95LatencyMs).toBe(-1);
|
|
28
|
+
expect(report.p99LatencyMs).toBe(-1);
|
|
29
|
+
expect(report.slowQueryCount).toBe(-1);
|
|
30
|
+
expect(report.suggestedIndexes[0]).toMatch(/pg_stat_statements/i);
|
|
31
|
+
});
|
|
32
|
+
it("preserves the requested timeframeHours in the response", async () => {
|
|
33
|
+
const { pool } = makePool(EXTENSION_ABSENT);
|
|
34
|
+
const report = await getQueryPerfReport(pool, "documents", 48);
|
|
35
|
+
expect(report.timeframeHours).toBe(48);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
describe("pg_stat_statements installed, no rows for the table", () => {
|
|
39
|
+
it("returns zero latencies and a no-queries message", async () => {
|
|
40
|
+
const { pool } = makePool([EXTENSION_PRESENT, { rows: [] }]);
|
|
41
|
+
const report = await getQueryPerfReport(pool, "documents", 24);
|
|
42
|
+
expect(report.p50LatencyMs).toBe(0);
|
|
43
|
+
expect(report.p95LatencyMs).toBe(0);
|
|
44
|
+
expect(report.p99LatencyMs).toBe(0);
|
|
45
|
+
expect(report.slowQueryCount).toBe(0);
|
|
46
|
+
expect(report.suggestedIndexes[0]).toMatch(/No queries found/i);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
describe("query stats present", () => {
|
|
50
|
+
it("returns valid percentiles (p50 ≤ p95 ≤ p99)", async () => {
|
|
51
|
+
const { pool } = makePool([
|
|
52
|
+
EXTENSION_PRESENT,
|
|
53
|
+
{ rows: [makeStatRow(10, 5), makeStatRow(30, 3), makeStatRow(80, 8)] },
|
|
54
|
+
]);
|
|
55
|
+
const report = await getQueryPerfReport(pool, "documents", 24);
|
|
56
|
+
expect(report.p50LatencyMs).toBeGreaterThanOrEqual(0);
|
|
57
|
+
expect(report.p95LatencyMs).toBeGreaterThanOrEqual(report.p50LatencyMs);
|
|
58
|
+
expect(report.p99LatencyMs).toBeGreaterThanOrEqual(report.p95LatencyMs);
|
|
59
|
+
});
|
|
60
|
+
it("timeframeHours is passed through to the report", async () => {
|
|
61
|
+
const { pool } = makePool([
|
|
62
|
+
EXTENSION_PRESENT,
|
|
63
|
+
{ rows: [makeStatRow(5, 1)] },
|
|
64
|
+
]);
|
|
65
|
+
const report = await getQueryPerfReport(pool, "documents", 72);
|
|
66
|
+
expect(report.timeframeHours).toBe(72);
|
|
67
|
+
});
|
|
68
|
+
it("identifies slow queries (mean_exec_time > 100 ms)", async () => {
|
|
69
|
+
const { pool } = makePool([
|
|
70
|
+
EXTENSION_PRESENT,
|
|
71
|
+
{
|
|
72
|
+
rows: [
|
|
73
|
+
makeStatRow(50, 3), // fast
|
|
74
|
+
makeStatRow(150, 2), // slow
|
|
75
|
+
makeStatRow(200, 1), // slow
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
]);
|
|
79
|
+
const report = await getQueryPerfReport(pool, "documents", 24);
|
|
80
|
+
expect(report.slowQueryCount).toBe(2);
|
|
81
|
+
expect(report.suggestedIndexes[0]).toMatch(/slow queries/i);
|
|
82
|
+
});
|
|
83
|
+
it("suggests healthy when all queries are fast", async () => {
|
|
84
|
+
const { pool } = makePool([
|
|
85
|
+
EXTENSION_PRESENT,
|
|
86
|
+
{ rows: [makeStatRow(20, 5), makeStatRow(40, 3)] },
|
|
87
|
+
]);
|
|
88
|
+
const report = await getQueryPerfReport(pool, "documents", 24);
|
|
89
|
+
expect(report.slowQueryCount).toBe(0);
|
|
90
|
+
expect(report.suggestedIndexes[0]).toMatch(/healthy/i);
|
|
91
|
+
});
|
|
92
|
+
it("detects sequential scans in query text", async () => {
|
|
93
|
+
const { pool } = makePool([
|
|
94
|
+
EXTENSION_PRESENT,
|
|
95
|
+
{
|
|
96
|
+
rows: [
|
|
97
|
+
{
|
|
98
|
+
mean_exec_time: 50,
|
|
99
|
+
stddev_exec_time: 5,
|
|
100
|
+
calls: 1,
|
|
101
|
+
query: "seq scan on documents",
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
]);
|
|
106
|
+
const report = await getQueryPerfReport(pool, "documents", 24);
|
|
107
|
+
expect(report.suggestedIndexes.some((s) => /sequential/i.test(s))).toBe(true);
|
|
108
|
+
});
|
|
109
|
+
it("caps per-row weight at 10 samples regardless of call count", async () => {
|
|
110
|
+
const { pool } = makePool([
|
|
111
|
+
EXTENSION_PRESENT,
|
|
112
|
+
{ rows: [makeStatRow(10, 10_000)] }, // very high call count
|
|
113
|
+
]);
|
|
114
|
+
// Should not throw or run out of memory
|
|
115
|
+
const report = await getQueryPerfReport(pool, "documents", 24);
|
|
116
|
+
expect(report.p50LatencyMs).toBeGreaterThanOrEqual(0);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
describe("client lifecycle", () => {
|
|
120
|
+
it("releases the client even when a query throws", async () => {
|
|
121
|
+
const client = {
|
|
122
|
+
query: vi.fn().mockRejectedValue(new Error("connection reset")),
|
|
123
|
+
release: vi.fn(),
|
|
124
|
+
};
|
|
125
|
+
const pool = {
|
|
126
|
+
connect: vi.fn().mockResolvedValue(client),
|
|
127
|
+
};
|
|
128
|
+
await expect(getQueryPerfReport(pool, "documents", 24)).rejects.toThrow("connection reset");
|
|
129
|
+
expect(client.release).toHaveBeenCalledOnce();
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
//# sourceMappingURL=query-perf.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-perf.test.js","sourceRoot":"","sources":["../src/query-perf.test.ts"],"names":[],"mappings":"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"}
|
package/dist/stats.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../src/stats.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,MAAM,IAAI,CAAC;AAEpB,wBAAsB,aAAa,CACjC,IAAI,EAAE,EAAE,CAAC,IAAI,EACb,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,UAAU,CAAC,CAOrB"}
|
package/dist/stats.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
export async function getIndexStats(pool, table) {
|
|
2
|
+
const client = await pool.connect();
|
|
3
|
+
try {
|
|
4
|
+
return await computeIndexStats(client, table);
|
|
5
|
+
}
|
|
6
|
+
finally {
|
|
7
|
+
client.release();
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
async function computeIndexStats(client, table) {
|
|
11
|
+
const suggestions = [];
|
|
12
|
+
// Total vector count
|
|
13
|
+
const countRes = await client.query(`SELECT COUNT(*) AS count FROM ${table}`);
|
|
14
|
+
const totalVectors = parseInt(countRes.rows[0]?.count ?? "0", 10);
|
|
15
|
+
// Index type and definition
|
|
16
|
+
const indexRes = await client.query(`SELECT indexname, indexdef FROM pg_indexes WHERE tablename = $1`, [table]);
|
|
17
|
+
let indexType = "unknown";
|
|
18
|
+
for (const row of indexRes.rows) {
|
|
19
|
+
const def = row.indexdef.toLowerCase();
|
|
20
|
+
if (def.includes("hnsw")) {
|
|
21
|
+
indexType = "hnsw";
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
else if (def.includes("ivfflat")) {
|
|
25
|
+
indexType = "ivfflat";
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
else if (def.includes("btree") || def.includes("flat")) {
|
|
29
|
+
indexType = "flat";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// Dead tuple fraction and index age from pg_stat_user_tables
|
|
33
|
+
const statRes = await client.query(`SELECT n_live_tup, n_dead_tup, last_vacuum, last_autovacuum
|
|
34
|
+
FROM pg_stat_user_tables WHERE relname = $1`, [table]);
|
|
35
|
+
const statRow = statRes.rows[0];
|
|
36
|
+
const nLive = parseInt(statRow?.n_live_tup ?? "0", 10);
|
|
37
|
+
const nDead = parseInt(statRow?.n_dead_tup ?? "0", 10);
|
|
38
|
+
const deadTupleFraction = nLive + nDead > 0 ? nDead / (nLive + nDead) : 0;
|
|
39
|
+
const lastVacuum = statRow?.last_vacuum ?? statRow?.last_autovacuum ?? null;
|
|
40
|
+
const indexAgeHours = lastVacuum
|
|
41
|
+
? (Date.now() - new Date(lastVacuum).getTime()) / 3_600_000
|
|
42
|
+
: -1;
|
|
43
|
+
// ANN recall@10: compare exact vs index results for a zero vector
|
|
44
|
+
const annRecallAt10 = await computeAnnRecall(client, table);
|
|
45
|
+
// Build suggestions
|
|
46
|
+
if (deadTupleFraction > 0.1) {
|
|
47
|
+
suggestions.push(`REINDEX recommended: ${(deadTupleFraction * 100).toFixed(0)}% dead tuples in "${table}"`);
|
|
48
|
+
}
|
|
49
|
+
if (indexType === "ivfflat" && totalVectors > 100_000) {
|
|
50
|
+
suggestions.push(`Consider switching to HNSW index for better recall at ${totalVectors.toLocaleString()} vectors`);
|
|
51
|
+
}
|
|
52
|
+
if (indexAgeHours > 168) {
|
|
53
|
+
suggestions.push(`Index age ${Math.round(indexAgeHours)} hours — consider VACUUM ANALYZE`);
|
|
54
|
+
}
|
|
55
|
+
if (suggestions.length === 0) {
|
|
56
|
+
suggestions.push("Index looks healthy");
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
totalVectors,
|
|
60
|
+
indexType,
|
|
61
|
+
annRecallAt10,
|
|
62
|
+
indexAgeHours: Math.round(indexAgeHours * 10) / 10,
|
|
63
|
+
deadTupleFraction: Math.round(deadTupleFraction * 1000) / 1000,
|
|
64
|
+
suggestions,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
async function computeAnnRecall(client, table) {
|
|
68
|
+
// Check if table has any rows and an embedding column
|
|
69
|
+
const colRes = await client.query(`SELECT data_type FROM information_schema.columns
|
|
70
|
+
WHERE table_name = $1 AND column_name = 'embedding'`, [table]);
|
|
71
|
+
if (colRes.rows.length === 0)
|
|
72
|
+
return -1;
|
|
73
|
+
const countRes = await client.query(`SELECT COUNT(*) AS count FROM ${table}`);
|
|
74
|
+
const total = parseInt(countRes.rows[0]?.count ?? "0", 10);
|
|
75
|
+
if (total < 10)
|
|
76
|
+
return -1; // Not enough data
|
|
77
|
+
try {
|
|
78
|
+
// Get dimension from a sample embedding
|
|
79
|
+
const sampleRes = await client.query(`SELECT array_length(embedding::real[], 1) AS dim FROM ${table} LIMIT 1`);
|
|
80
|
+
const dim = sampleRes.rows[0]?.dim;
|
|
81
|
+
if (!dim)
|
|
82
|
+
return -1;
|
|
83
|
+
// Use a zero vector for comparison
|
|
84
|
+
const zeroVec = `[${Array(dim).fill(0).join(",")}]`;
|
|
85
|
+
// Exact results (no index, brute-force)
|
|
86
|
+
const exactRes = await client.query(`SELECT id FROM ${table}
|
|
87
|
+
ORDER BY embedding::vector <=> $1::vector
|
|
88
|
+
LIMIT 10`, [zeroVec]);
|
|
89
|
+
const exactIds = new Set(exactRes.rows.map((r) => r.id));
|
|
90
|
+
// ANN results (uses index)
|
|
91
|
+
const annRes = await client.query(`SELECT id FROM ${table}
|
|
92
|
+
ORDER BY embedding <=> $1::vector
|
|
93
|
+
LIMIT 10`, [zeroVec]);
|
|
94
|
+
const annIds = annRes.rows.map((r) => r.id);
|
|
95
|
+
const hits = annIds.filter((id) => exactIds.has(id)).length;
|
|
96
|
+
return hits / Math.max(exactIds.size, 1);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return -1;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=stats.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stats.js","sourceRoot":"","sources":["../src/stats.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stats.test.d.ts","sourceRoot":"","sources":["../src/stats.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { getIndexStats } from "./stats.js";
|
|
3
|
+
function makePool(queryResults) {
|
|
4
|
+
const client = {
|
|
5
|
+
query: vi.fn(),
|
|
6
|
+
release: vi.fn(),
|
|
7
|
+
};
|
|
8
|
+
for (const result of queryResults) {
|
|
9
|
+
client.query.mockResolvedValueOnce(result);
|
|
10
|
+
}
|
|
11
|
+
const pool = {
|
|
12
|
+
connect: vi.fn().mockResolvedValue(client),
|
|
13
|
+
};
|
|
14
|
+
return { pool, client };
|
|
15
|
+
}
|
|
16
|
+
const STAT_ROW_CLEAN = {
|
|
17
|
+
n_live_tup: "500",
|
|
18
|
+
n_dead_tup: "0",
|
|
19
|
+
last_vacuum: null,
|
|
20
|
+
last_autovacuum: null,
|
|
21
|
+
};
|
|
22
|
+
// Minimal 4-query sequence: no ANN recall (embedding column absent)
|
|
23
|
+
function noAnnResponses(count, indexRows = [], statRow = STAT_ROW_CLEAN) {
|
|
24
|
+
return [
|
|
25
|
+
{ rows: [{ count: String(count) }] },
|
|
26
|
+
{ rows: indexRows },
|
|
27
|
+
{ rows: [statRow] },
|
|
28
|
+
{ rows: [] }, // no embedding column
|
|
29
|
+
];
|
|
30
|
+
}
|
|
31
|
+
// 8-query sequence including full ANN recall
|
|
32
|
+
function withAnnResponses(count, indexRows, statRow, dim, exactIds, annIds) {
|
|
33
|
+
return [
|
|
34
|
+
{ rows: [{ count: String(count) }] },
|
|
35
|
+
{ rows: indexRows },
|
|
36
|
+
{ rows: [statRow] },
|
|
37
|
+
{ rows: [{ data_type: "USER-DEFINED" }] }, // embedding column present
|
|
38
|
+
{ rows: [{ count: String(count) }] },
|
|
39
|
+
{ rows: [{ dim }] },
|
|
40
|
+
{ rows: exactIds.map((id) => ({ id })) },
|
|
41
|
+
{ rows: annIds.map((id) => ({ id })) },
|
|
42
|
+
];
|
|
43
|
+
}
|
|
44
|
+
describe("getIndexStats (PostgreSQL)", () => {
|
|
45
|
+
describe("totalVectors", () => {
|
|
46
|
+
it("reflects the COUNT query result", async () => {
|
|
47
|
+
const { pool } = makePool(noAnnResponses(1234));
|
|
48
|
+
const stats = await getIndexStats(pool, "documents");
|
|
49
|
+
expect(stats.totalVectors).toBe(1234);
|
|
50
|
+
});
|
|
51
|
+
it("is 0 when the table is empty", async () => {
|
|
52
|
+
const { pool } = makePool(noAnnResponses(0));
|
|
53
|
+
const stats = await getIndexStats(pool, "documents");
|
|
54
|
+
expect(stats.totalVectors).toBe(0);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
describe("indexType detection", () => {
|
|
58
|
+
it("detects hnsw from pg_indexes indexdef", async () => {
|
|
59
|
+
const { pool } = makePool(noAnnResponses(100, [
|
|
60
|
+
{
|
|
61
|
+
indexname: "emb_idx",
|
|
62
|
+
indexdef: "CREATE INDEX emb_idx USING hnsw (embedding)",
|
|
63
|
+
},
|
|
64
|
+
]));
|
|
65
|
+
const stats = await getIndexStats(pool, "documents");
|
|
66
|
+
expect(stats.indexType).toBe("hnsw");
|
|
67
|
+
});
|
|
68
|
+
it("detects ivfflat from pg_indexes indexdef", async () => {
|
|
69
|
+
const { pool } = makePool(noAnnResponses(100, [
|
|
70
|
+
{
|
|
71
|
+
indexname: "emb_idx",
|
|
72
|
+
indexdef: "CREATE INDEX emb_idx USING ivfflat (embedding)",
|
|
73
|
+
},
|
|
74
|
+
]));
|
|
75
|
+
const stats = await getIndexStats(pool, "documents");
|
|
76
|
+
expect(stats.indexType).toBe("ivfflat");
|
|
77
|
+
});
|
|
78
|
+
it("detects flat from btree indexdef", async () => {
|
|
79
|
+
const { pool } = makePool(noAnnResponses(100, [
|
|
80
|
+
{
|
|
81
|
+
indexname: "pkey",
|
|
82
|
+
indexdef: "CREATE UNIQUE INDEX pkey USING btree (id)",
|
|
83
|
+
},
|
|
84
|
+
]));
|
|
85
|
+
const stats = await getIndexStats(pool, "documents");
|
|
86
|
+
expect(stats.indexType).toBe("flat");
|
|
87
|
+
});
|
|
88
|
+
it("is unknown when pg_indexes returns no rows", async () => {
|
|
89
|
+
const { pool } = makePool(noAnnResponses(100, []));
|
|
90
|
+
const stats = await getIndexStats(pool, "documents");
|
|
91
|
+
expect(stats.indexType).toBe("unknown");
|
|
92
|
+
});
|
|
93
|
+
it("uses the first matching index row (first-match order)", async () => {
|
|
94
|
+
// hnsw row comes first → detected as hnsw even though ivfflat row also present
|
|
95
|
+
const { pool } = makePool(noAnnResponses(100, [
|
|
96
|
+
{
|
|
97
|
+
indexname: "hnsw_idx",
|
|
98
|
+
indexdef: "CREATE INDEX USING hnsw (embedding)",
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
indexname: "ivf_idx",
|
|
102
|
+
indexdef: "CREATE INDEX USING ivfflat (embedding)",
|
|
103
|
+
},
|
|
104
|
+
]));
|
|
105
|
+
const stats = await getIndexStats(pool, "documents");
|
|
106
|
+
expect(stats.indexType).toBe("hnsw");
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
describe("deadTupleFraction", () => {
|
|
110
|
+
it("is computed as dead / (live + dead)", async () => {
|
|
111
|
+
const { pool } = makePool(noAnnResponses(100, [], {
|
|
112
|
+
n_live_tup: "300",
|
|
113
|
+
n_dead_tup: "100",
|
|
114
|
+
last_vacuum: null,
|
|
115
|
+
last_autovacuum: null,
|
|
116
|
+
}));
|
|
117
|
+
const stats = await getIndexStats(pool, "documents");
|
|
118
|
+
expect(stats.deadTupleFraction).toBeCloseTo(0.25, 2);
|
|
119
|
+
});
|
|
120
|
+
it("is 0 when no tuples exist", async () => {
|
|
121
|
+
const { pool } = makePool(noAnnResponses(0, [], {
|
|
122
|
+
n_live_tup: "0",
|
|
123
|
+
n_dead_tup: "0",
|
|
124
|
+
last_vacuum: null,
|
|
125
|
+
last_autovacuum: null,
|
|
126
|
+
}));
|
|
127
|
+
const stats = await getIndexStats(pool, "documents");
|
|
128
|
+
expect(stats.deadTupleFraction).toBe(0);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
describe("indexAgeHours", () => {
|
|
132
|
+
it("is computed from last_vacuum when present", async () => {
|
|
133
|
+
const lastVacuum = new Date(Date.now() - 2 * 3_600_000).toISOString();
|
|
134
|
+
const { pool } = makePool(noAnnResponses(100, [], { ...STAT_ROW_CLEAN, last_vacuum: lastVacuum }));
|
|
135
|
+
const stats = await getIndexStats(pool, "documents");
|
|
136
|
+
expect(stats.indexAgeHours).toBeGreaterThanOrEqual(1.9);
|
|
137
|
+
expect(stats.indexAgeHours).toBeLessThanOrEqual(2.1);
|
|
138
|
+
});
|
|
139
|
+
it("falls back to last_autovacuum when last_vacuum is null", async () => {
|
|
140
|
+
const lastAutoVacuum = new Date(Date.now() - 3 * 3_600_000).toISOString();
|
|
141
|
+
const { pool } = makePool(noAnnResponses(100, [], {
|
|
142
|
+
...STAT_ROW_CLEAN,
|
|
143
|
+
last_vacuum: null,
|
|
144
|
+
last_autovacuum: lastAutoVacuum,
|
|
145
|
+
}));
|
|
146
|
+
const stats = await getIndexStats(pool, "documents");
|
|
147
|
+
expect(stats.indexAgeHours).toBeGreaterThanOrEqual(2.9);
|
|
148
|
+
expect(stats.indexAgeHours).toBeLessThanOrEqual(3.1);
|
|
149
|
+
});
|
|
150
|
+
it("is -1 when both vacuum timestamps are null", async () => {
|
|
151
|
+
const { pool } = makePool(noAnnResponses(100));
|
|
152
|
+
const stats = await getIndexStats(pool, "documents");
|
|
153
|
+
expect(stats.indexAgeHours).toBe(-1);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
describe("ANN recall", () => {
|
|
157
|
+
it("is -1 when embedding column is absent", async () => {
|
|
158
|
+
const { pool } = makePool(noAnnResponses(500));
|
|
159
|
+
const stats = await getIndexStats(pool, "documents");
|
|
160
|
+
expect(stats.annRecallAt10).toBe(-1);
|
|
161
|
+
});
|
|
162
|
+
it("is -1 when table has fewer than 10 rows", async () => {
|
|
163
|
+
const { pool } = makePool([
|
|
164
|
+
{ rows: [{ count: "5" }] },
|
|
165
|
+
{ rows: [] },
|
|
166
|
+
{ rows: [STAT_ROW_CLEAN] },
|
|
167
|
+
{ rows: [{ data_type: "USER-DEFINED" }] }, // embedding col present
|
|
168
|
+
{ rows: [{ count: "5" }] }, // but count < 10
|
|
169
|
+
]);
|
|
170
|
+
const stats = await getIndexStats(pool, "documents");
|
|
171
|
+
expect(stats.annRecallAt10).toBe(-1);
|
|
172
|
+
});
|
|
173
|
+
it("is -1 when dim query returns null", async () => {
|
|
174
|
+
const { pool } = makePool([
|
|
175
|
+
{ rows: [{ count: "100" }] },
|
|
176
|
+
{ rows: [] },
|
|
177
|
+
{ rows: [STAT_ROW_CLEAN] },
|
|
178
|
+
{ rows: [{ data_type: "USER-DEFINED" }] },
|
|
179
|
+
{ rows: [{ count: "100" }] },
|
|
180
|
+
{ rows: [{ dim: null }] }, // no dim
|
|
181
|
+
]);
|
|
182
|
+
const stats = await getIndexStats(pool, "documents");
|
|
183
|
+
expect(stats.annRecallAt10).toBe(-1);
|
|
184
|
+
});
|
|
185
|
+
it("is 1.0 when ANN and exact results are identical", async () => {
|
|
186
|
+
const ids = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
|
187
|
+
const { pool } = makePool(withAnnResponses(100, [], STAT_ROW_CLEAN, 4, ids, ids));
|
|
188
|
+
const stats = await getIndexStats(pool, "documents");
|
|
189
|
+
expect(stats.annRecallAt10).toBe(1);
|
|
190
|
+
});
|
|
191
|
+
it("computes partial recall correctly", async () => {
|
|
192
|
+
const exactIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
|
193
|
+
const annIds = [1, 2, 3, 4, 5, 11, 12, 13, 14, 15]; // 5 hits
|
|
194
|
+
const { pool } = makePool(withAnnResponses(100, [], STAT_ROW_CLEAN, 4, exactIds, annIds));
|
|
195
|
+
const stats = await getIndexStats(pool, "documents");
|
|
196
|
+
expect(stats.annRecallAt10).toBeCloseTo(0.5, 5);
|
|
197
|
+
});
|
|
198
|
+
it("is -1 when the search query throws", async () => {
|
|
199
|
+
const client = {
|
|
200
|
+
query: vi
|
|
201
|
+
.fn()
|
|
202
|
+
.mockResolvedValueOnce({ rows: [{ count: "100" }] })
|
|
203
|
+
.mockResolvedValueOnce({ rows: [] })
|
|
204
|
+
.mockResolvedValueOnce({ rows: [STAT_ROW_CLEAN] })
|
|
205
|
+
.mockResolvedValueOnce({ rows: [{ data_type: "USER-DEFINED" }] })
|
|
206
|
+
.mockResolvedValueOnce({ rows: [{ count: "100" }] })
|
|
207
|
+
.mockResolvedValueOnce({ rows: [{ dim: 4 }] })
|
|
208
|
+
.mockRejectedValueOnce(new Error("operator does not exist")),
|
|
209
|
+
release: vi.fn(),
|
|
210
|
+
};
|
|
211
|
+
const pool = {
|
|
212
|
+
connect: vi.fn().mockResolvedValue(client),
|
|
213
|
+
};
|
|
214
|
+
const stats = await getIndexStats(pool, "documents");
|
|
215
|
+
expect(stats.annRecallAt10).toBe(-1);
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
describe("suggestions", () => {
|
|
219
|
+
it("recommends REINDEX when dead tuple fraction > 10%", async () => {
|
|
220
|
+
const { pool } = makePool(noAnnResponses(100, [], {
|
|
221
|
+
n_live_tup: "800",
|
|
222
|
+
n_dead_tup: "200",
|
|
223
|
+
last_vacuum: null,
|
|
224
|
+
last_autovacuum: null,
|
|
225
|
+
}));
|
|
226
|
+
const stats = await getIndexStats(pool, "documents");
|
|
227
|
+
expect(stats.suggestions.some((s) => /REINDEX/i.test(s))).toBe(true);
|
|
228
|
+
});
|
|
229
|
+
it("suggests switching to HNSW for ivfflat index with > 100k vectors", async () => {
|
|
230
|
+
const { pool } = makePool(noAnnResponses(150_000, [
|
|
231
|
+
{
|
|
232
|
+
indexname: "idx",
|
|
233
|
+
indexdef: "CREATE INDEX USING ivfflat (embedding)",
|
|
234
|
+
},
|
|
235
|
+
]));
|
|
236
|
+
const stats = await getIndexStats(pool, "documents");
|
|
237
|
+
expect(stats.suggestions.some((s) => /HNSW/i.test(s))).toBe(true);
|
|
238
|
+
});
|
|
239
|
+
it("does not suggest HNSW for hnsw index with > 100k vectors", async () => {
|
|
240
|
+
const { pool } = makePool(noAnnResponses(150_000, [
|
|
241
|
+
{ indexname: "idx", indexdef: "CREATE INDEX USING hnsw (embedding)" },
|
|
242
|
+
]));
|
|
243
|
+
const stats = await getIndexStats(pool, "documents");
|
|
244
|
+
expect(stats.suggestions.every((s) => !/switch.*HNSW/i.test(s))).toBe(true);
|
|
245
|
+
});
|
|
246
|
+
it("suggests VACUUM ANALYZE when index is older than 168 hours", async () => {
|
|
247
|
+
const old = new Date(Date.now() - 200 * 3_600_000).toISOString();
|
|
248
|
+
const { pool } = makePool(noAnnResponses(100, [], { ...STAT_ROW_CLEAN, last_vacuum: old }));
|
|
249
|
+
const stats = await getIndexStats(pool, "documents");
|
|
250
|
+
expect(stats.suggestions.some((s) => /VACUUM/i.test(s))).toBe(true);
|
|
251
|
+
});
|
|
252
|
+
it("returns healthy suggestion when no issues detected", async () => {
|
|
253
|
+
const { pool } = makePool(noAnnResponses(100, [
|
|
254
|
+
{ indexname: "idx", indexdef: "CREATE INDEX USING hnsw (embedding)" },
|
|
255
|
+
]));
|
|
256
|
+
const stats = await getIndexStats(pool, "documents");
|
|
257
|
+
expect(stats.suggestions[0]).toMatch(/healthy/i);
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
describe("client lifecycle", () => {
|
|
261
|
+
it("releases the client even when a query throws", async () => {
|
|
262
|
+
const client = {
|
|
263
|
+
query: vi.fn().mockRejectedValue(new Error("connection lost")),
|
|
264
|
+
release: vi.fn(),
|
|
265
|
+
};
|
|
266
|
+
const pool = {
|
|
267
|
+
connect: vi.fn().mockResolvedValue(client),
|
|
268
|
+
};
|
|
269
|
+
await expect(getIndexStats(pool, "documents")).rejects.toThrow("connection lost");
|
|
270
|
+
expect(client.release).toHaveBeenCalledOnce();
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
//# sourceMappingURL=stats.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stats.test.js","sourceRoot":"","sources":["../src/stats.test.ts"],"names":[],"mappings":"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"}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { VectorStore, VectorSearchResult, IndexStats, QueryPerfReport, Logger } from "@vivantel/virage-core";
|
|
2
|
+
export type IndexType = "ivfflat" | "hnsw";
|
|
3
|
+
export interface IVFFlatParams {
|
|
4
|
+
/** Number of IVFFlat lists. Defaults to 100. */
|
|
5
|
+
lists?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface HNSWParams {
|
|
8
|
+
/** HNSW M parameter (connections per layer). Defaults to 16. */
|
|
9
|
+
m?: number;
|
|
10
|
+
/** HNSW ef_construction parameter. Defaults to 64. */
|
|
11
|
+
efConstruction?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface PostgresVectorStoreOptions {
|
|
14
|
+
connectionString: string;
|
|
15
|
+
/** Table name. Defaults to "documents". */
|
|
16
|
+
table?: string;
|
|
17
|
+
/** Vector dimensions — must match your embedder. Defaults to 1536. */
|
|
18
|
+
dimensions?: number;
|
|
19
|
+
/** Enable SSL. Defaults to false. */
|
|
20
|
+
ssl?: boolean;
|
|
21
|
+
/** Index algorithm. Defaults to "ivfflat". */
|
|
22
|
+
indexType?: IndexType;
|
|
23
|
+
/** Index-specific parameters. */
|
|
24
|
+
indexParams?: IVFFlatParams | HNSWParams;
|
|
25
|
+
}
|
|
26
|
+
export declare class PostgresVectorStore implements VectorStore {
|
|
27
|
+
readonly name = "postgres";
|
|
28
|
+
private readonly table;
|
|
29
|
+
private readonly dimensions;
|
|
30
|
+
private readonly connectionString;
|
|
31
|
+
private readonly ssl;
|
|
32
|
+
private readonly indexType;
|
|
33
|
+
private readonly indexParams;
|
|
34
|
+
private _pool;
|
|
35
|
+
private logger;
|
|
36
|
+
constructor(options: PostgresVectorStoreOptions);
|
|
37
|
+
setLogger(logger: Logger): void;
|
|
38
|
+
private get pool();
|
|
39
|
+
initialize(): Promise<void>;
|
|
40
|
+
upsert(docs: Array<{
|
|
41
|
+
content: string;
|
|
42
|
+
embedding: number[];
|
|
43
|
+
metadata: Record<string, unknown>;
|
|
44
|
+
sourceFile: string;
|
|
45
|
+
commitHash: string;
|
|
46
|
+
}>): Promise<void>;
|
|
47
|
+
deleteBySourceFile(files: string[]): Promise<void>;
|
|
48
|
+
getCurrentState(): Promise<Map<string, string>>;
|
|
49
|
+
private buildIndexSQL;
|
|
50
|
+
search(embedding: number[], topK: number): Promise<VectorSearchResult[]>;
|
|
51
|
+
getIndexStats(): Promise<IndexStats>;
|
|
52
|
+
getQueryPerfReport(timeframeHours: number): Promise<QueryPerfReport>;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EACX,kBAAkB,EAClB,UAAU,EACV,eAAe,EACf,MAAM,EACP,MAAM,uBAAuB,CAAC;AAQ/B,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;AAE3C,MAAM,WAAW,aAAa;IAC5B,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,gEAAgE;IAChE,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,sDAAsD;IACtD,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,0BAA0B;IACzC,gBAAgB,EAAE,MAAM,CAAC;IACzB,2CAA2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qCAAqC;IACrC,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,8CAA8C;IAC9C,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,iCAAiC;IACjC,WAAW,CAAC,EAAE,aAAa,GAAG,UAAU,CAAC;CAC1C;AAED,qBAAa,mBAAoB,YAAW,WAAW;IACrD,QAAQ,CAAC,IAAI,cAAc;IAE3B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAU;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IACzD,OAAO,CAAC,KAAK,CAA0C;IACvD,OAAO,CAAC,MAAM,CAAuB;gBAEzB,OAAO,EAAE,0BAA0B;IAY/C,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAI/B,OAAO,KAAK,IAAI,GAQf;IAEK,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IA2B3B,MAAM,CACV,IAAI,EAAE,KAAK,CAAC;QACV,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,EAAE,CAAC;QACpB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAClC,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC,GACD,OAAO,CAAC,IAAI,CAAC;IA2BV,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IASlD,eAAe,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAUrD,OAAO,CAAC,aAAa;IAqBf,MAAM,CACV,SAAS,EAAE,MAAM,EAAE,EACnB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,kBAAkB,EAAE,CAAC;IA+B1B,aAAa,IAAI,OAAO,CAAC,UAAU,CAAC;IAIpC,kBAAkB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;CAG3E"}
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import pg from "pg";
|
|
2
|
+
import pgvector from "pgvector/pg";
|
|
3
|
+
import { getIndexStats } from "./stats.js";
|
|
4
|
+
import { getQueryPerfReport } from "./query-perf.js";
|
|
5
|
+
const { Pool } = pg;
|
|
6
|
+
export class PostgresVectorStore {
|
|
7
|
+
name = "postgres";
|
|
8
|
+
table;
|
|
9
|
+
dimensions;
|
|
10
|
+
connectionString;
|
|
11
|
+
ssl;
|
|
12
|
+
indexType;
|
|
13
|
+
indexParams;
|
|
14
|
+
_pool = null;
|
|
15
|
+
logger = null;
|
|
16
|
+
constructor(options) {
|
|
17
|
+
if (!options.connectionString) {
|
|
18
|
+
throw new Error("PostgresVectorStore: connectionString is required");
|
|
19
|
+
}
|
|
20
|
+
this.connectionString = options.connectionString;
|
|
21
|
+
this.table = options.table ?? "documents";
|
|
22
|
+
this.dimensions = options.dimensions ?? 1536;
|
|
23
|
+
this.ssl = options.ssl ?? false;
|
|
24
|
+
this.indexType = options.indexType ?? "ivfflat";
|
|
25
|
+
this.indexParams = options.indexParams ?? {};
|
|
26
|
+
}
|
|
27
|
+
setLogger(logger) {
|
|
28
|
+
this.logger = logger.withTag("postgres");
|
|
29
|
+
}
|
|
30
|
+
get pool() {
|
|
31
|
+
if (!this._pool) {
|
|
32
|
+
this._pool = new Pool({
|
|
33
|
+
connectionString: this.connectionString,
|
|
34
|
+
ssl: this.ssl ? { rejectUnauthorized: false } : false,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return this._pool;
|
|
38
|
+
}
|
|
39
|
+
async initialize() {
|
|
40
|
+
this.logger?.info(`Connecting to postgres, table: ${this.table} (${this.dimensions}d, ${this.indexType})`);
|
|
41
|
+
const client = await this.pool.connect();
|
|
42
|
+
try {
|
|
43
|
+
await pgvector.registerTypes(client);
|
|
44
|
+
await client.query("CREATE EXTENSION IF NOT EXISTS vector");
|
|
45
|
+
await client.query(`
|
|
46
|
+
CREATE TABLE IF NOT EXISTS ${this.table} (
|
|
47
|
+
id SERIAL PRIMARY KEY,
|
|
48
|
+
content TEXT NOT NULL,
|
|
49
|
+
embedding vector(${this.dimensions}),
|
|
50
|
+
metadata JSONB,
|
|
51
|
+
source_file TEXT NOT NULL,
|
|
52
|
+
commit_hash TEXT NOT NULL
|
|
53
|
+
)
|
|
54
|
+
`);
|
|
55
|
+
await client.query(this.buildIndexSQL());
|
|
56
|
+
this.logger?.debug(`Index type: ${this.indexType}, params: ${JSON.stringify(this.indexParams)}`);
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
client.release();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async upsert(docs) {
|
|
63
|
+
if (docs.length === 0)
|
|
64
|
+
return;
|
|
65
|
+
this.logger?.verbose(`Upserting ${docs.length} docs into ${this.table}`);
|
|
66
|
+
const client = await this.pool.connect();
|
|
67
|
+
try {
|
|
68
|
+
await pgvector.registerTypes(client);
|
|
69
|
+
for (const doc of docs) {
|
|
70
|
+
this.logger?.trace(` source_file: ${doc.sourceFile}`);
|
|
71
|
+
await client.query(`INSERT INTO ${this.table} (content, embedding, metadata, source_file, commit_hash)
|
|
72
|
+
VALUES ($1, $2, $3, $4, $5)`, [
|
|
73
|
+
doc.content,
|
|
74
|
+
pgvector.toSql(doc.embedding),
|
|
75
|
+
JSON.stringify(doc.metadata),
|
|
76
|
+
doc.sourceFile,
|
|
77
|
+
doc.commitHash,
|
|
78
|
+
]);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
client.release();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async deleteBySourceFile(files) {
|
|
86
|
+
if (files.length === 0)
|
|
87
|
+
return;
|
|
88
|
+
this.logger?.verbose(`Deleting docs for ${files.length} source file(s)`);
|
|
89
|
+
await this.pool.query(`DELETE FROM ${this.table} WHERE source_file = ANY($1)`, [files]);
|
|
90
|
+
}
|
|
91
|
+
async getCurrentState() {
|
|
92
|
+
const { rows } = await this.pool.query(`SELECT source_file, commit_hash FROM ${this.table}`);
|
|
93
|
+
const state = new Map(rows.map((r) => [r.source_file, r.commit_hash]));
|
|
94
|
+
this.logger?.verbose(`getCurrentState: ${state.size} source version(s)`);
|
|
95
|
+
return state;
|
|
96
|
+
}
|
|
97
|
+
buildIndexSQL() {
|
|
98
|
+
const idxName = `${this.table}_embedding_idx`;
|
|
99
|
+
if (this.indexType === "hnsw") {
|
|
100
|
+
const p = this.indexParams;
|
|
101
|
+
const m = p.m ?? 16;
|
|
102
|
+
const efConstruction = p.efConstruction ?? 64;
|
|
103
|
+
return (`CREATE INDEX IF NOT EXISTS ${idxName} ` +
|
|
104
|
+
`ON ${this.table} USING hnsw (embedding vector_cosine_ops) ` +
|
|
105
|
+
`WITH (m = ${m}, ef_construction = ${efConstruction})`);
|
|
106
|
+
}
|
|
107
|
+
const p = this.indexParams;
|
|
108
|
+
const lists = p.lists ?? 100;
|
|
109
|
+
return (`CREATE INDEX IF NOT EXISTS ${idxName} ` +
|
|
110
|
+
`ON ${this.table} USING ivfflat (embedding vector_cosine_ops) ` +
|
|
111
|
+
`WITH (lists = ${lists})`);
|
|
112
|
+
}
|
|
113
|
+
async search(embedding, topK) {
|
|
114
|
+
this.logger?.debug(`Search: topK=${topK}`);
|
|
115
|
+
const client = await this.pool.connect();
|
|
116
|
+
try {
|
|
117
|
+
await pgvector.registerTypes(client);
|
|
118
|
+
const { rows } = await client.query(`SELECT id, content, metadata, source_file,
|
|
119
|
+
1 - (embedding <=> $1) AS similarity
|
|
120
|
+
FROM ${this.table}
|
|
121
|
+
ORDER BY embedding <=> $1
|
|
122
|
+
LIMIT $2`, [pgvector.toSql(embedding), topK]);
|
|
123
|
+
return rows.map((r) => ({
|
|
124
|
+
id: String(r.id),
|
|
125
|
+
content: r.content,
|
|
126
|
+
metadata: r.metadata,
|
|
127
|
+
sourceFile: r.source_file,
|
|
128
|
+
similarity: r.similarity,
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
client.release();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async getIndexStats() {
|
|
136
|
+
return getIndexStats(this.pool, this.table);
|
|
137
|
+
}
|
|
138
|
+
async getQueryPerfReport(timeframeHours) {
|
|
139
|
+
return getQueryPerfReport(this.pool, this.table, timeframeHours);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,QAAQ,MAAM,aAAa,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAErD,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;AA8BpB,MAAM,OAAO,mBAAmB;IACrB,IAAI,GAAG,UAAU,CAAC;IAEV,KAAK,CAAS;IACd,UAAU,CAAS;IACnB,gBAAgB,CAAS;IACzB,GAAG,CAAU;IACb,SAAS,CAAY;IACrB,WAAW,CAA6B;IACjD,KAAK,GAAqC,IAAI,CAAC;IAC/C,MAAM,GAAkB,IAAI,CAAC;IAErC,YAAY,OAAmC;QAC7C,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;QACvE,CAAC;QACD,IAAI,CAAC,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC;QACjD,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,WAAW,CAAC;QAC1C,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,IAAI,CAAC;QAC7C,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,KAAK,CAAC;QAChC,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,SAAS,CAAC;QAChD,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,EAAE,CAAC;IAC/C,CAAC;IAED,SAAS,CAAC,MAAc;QACtB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3C,CAAC;IAED,IAAY,IAAI;QACd,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC,KAAK,GAAG,IAAI,IAAI,CAAC;gBACpB,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;gBACvC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,kBAAkB,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK;aACtD,CAAC,CAAC;QACL,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,KAAK,CAAC,UAAU;QACd,IAAI,CAAC,MAAM,EAAE,IAAI,CACf,kCAAkC,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,UAAU,MAAM,IAAI,CAAC,SAAS,GAAG,CACxF,CAAC;QACF,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QACzC,IAAI,CAAC;YACH,MAAM,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;YACrC,MAAM,MAAM,CAAC,KAAK,CAAC,uCAAuC,CAAC,CAAC;YAC5D,MAAM,MAAM,CAAC,KAAK,CAAC;qCACY,IAAI,CAAC,KAAK;;;6BAGlB,IAAI,CAAC,UAAU;;;;;OAKrC,CAAC,CAAC;YACH,MAAM,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC;YACzC,IAAI,CAAC,MAAM,EAAE,KAAK,CAChB,eAAe,IAAI,CAAC,SAAS,aAAa,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAC7E,CAAC;QACJ,CAAC;gBAAS,CAAC;YACT,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,CAAC;IACH,CAAC;IAED,KAAK,CAAC,MAAM,CACV,IAME;QAEF,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAE9B,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,aAAa,IAAI,CAAC,MAAM,cAAc,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;QAEzE,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QACzC,IAAI,CAAC;YACH,MAAM,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;YACrC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;gBACvB,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,kBAAkB,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC;gBACvD,MAAM,MAAM,CAAC,KAAK,CAChB,eAAe,IAAI,CAAC,KAAK;uCACI,EAC7B;oBACE,GAAG,CAAC,OAAO;oBACX,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC;oBAC7B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC;oBAC5B,GAAG,CAAC,UAAU;oBACd,GAAG,CAAC,UAAU;iBACf,CACF,CAAC;YACJ,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,CAAC;IACH,CAAC;IAED,KAAK,CAAC,kBAAkB,CAAC,KAAe;QACtC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAC/B,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,qBAAqB,KAAK,CAAC,MAAM,iBAAiB,CAAC,CAAC;QACzE,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CACnB,eAAe,IAAI,CAAC,KAAK,8BAA8B,EACvD,CAAC,KAAK,CAAC,CACR,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAGnC,wCAAwC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;QACzD,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;QACvE,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,oBAAoB,KAAK,CAAC,IAAI,oBAAoB,CAAC,CAAC;QACzE,OAAO,KAAK,CAAC;IACf,CAAC;IAEO,aAAa;QACnB,MAAM,OAAO,GAAG,GAAG,IAAI,CAAC,KAAK,gBAAgB,CAAC;QAC9C,IAAI,IAAI,CAAC,SAAS,KAAK,MAAM,EAAE,CAAC;YAC9B,MAAM,CAAC,GAAG,IAAI,CAAC,WAAyB,CAAC;YACzC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YACpB,MAAM,cAAc,GAAG,CAAC,CAAC,cAAc,IAAI,EAAE,CAAC;YAC9C,OAAO,CACL,8BAA8B,OAAO,GAAG;gBACxC,MAAM,IAAI,CAAC,KAAK,4CAA4C;gBAC5D,aAAa,CAAC,uBAAuB,cAAc,GAAG,CACvD,CAAC;QACJ,CAAC;QACD,MAAM,CAAC,GAAG,IAAI,CAAC,WAA4B,CAAC;QAC5C,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC;QAC7B,OAAO,CACL,8BAA8B,OAAO,GAAG;YACxC,MAAM,IAAI,CAAC,KAAK,+CAA+C;YAC/D,iBAAiB,KAAK,GAAG,CAC1B,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,MAAM,CACV,SAAmB,EACnB,IAAY;QAEZ,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,gBAAgB,IAAI,EAAE,CAAC,CAAC;QAC3C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QACzC,IAAI,CAAC;YACH,MAAM,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;YACrC,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,KAAK,CAOjC;;gBAEQ,IAAI,CAAC,KAAK;;kBAER,EACV,CAAC,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC,CAClC,CAAC;YACF,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACtB,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;gBAChB,OAAO,EAAE,CAAC,CAAC,OAAO;gBAClB,QAAQ,EAAE,CAAC,CAAC,QAAQ;gBACpB,UAAU,EAAE,CAAC,CAAC,WAAW;gBACzB,UAAU,EAAE,CAAC,CAAC,UAAU;aACzB,CAAC,CAAC,CAAC;QACN,CAAC;gBAAS,CAAC;YACT,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,CAAC;IACH,CAAC;IAED,KAAK,CAAC,aAAa;QACjB,OAAO,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9C,CAAC;IAED,KAAK,CAAC,kBAAkB,CAAC,cAAsB;QAC7C,OAAO,kBAAkB,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC;IACnE,CAAC;CACF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vivantel/virage-store-postgres",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "PostgreSQL + pgvector store for @vivantel/rag-core",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"sideEffects": false,
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc",
|
|
24
|
+
"type-check": "tsc --noEmit",
|
|
25
|
+
"test": "vitest run",
|
|
26
|
+
"prepublishOnly": "npm run build",
|
|
27
|
+
"lint": "eslint src/",
|
|
28
|
+
"lint:fix": "eslint src/ --fix",
|
|
29
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
30
|
+
"fix": "npm run lint:fix && npm run format"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"rag",
|
|
34
|
+
"vector-store",
|
|
35
|
+
"postgres",
|
|
36
|
+
"postgresql",
|
|
37
|
+
"pgvector"
|
|
38
|
+
],
|
|
39
|
+
"author": "Vivantel",
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "https://github.com/vivantel/virage",
|
|
44
|
+
"directory": "packages/virage-store-postgres"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"pg": "^8.0.0",
|
|
48
|
+
"pgvector": "^0.3.0"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"@vivantel/virage-core": "*"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@types/node": "^25.9.1",
|
|
55
|
+
"@types/pg": "^8.0.0",
|
|
56
|
+
"@vitest/coverage-v8": "^4.1.8",
|
|
57
|
+
"@vivantel/virage-core": "0.2.0",
|
|
58
|
+
"typescript": "^6.0.3",
|
|
59
|
+
"vitest": "^4.1.8"
|
|
60
|
+
},
|
|
61
|
+
"engines": {
|
|
62
|
+
"node": ">=18.0.0"
|
|
63
|
+
}
|
|
64
|
+
}
|