()
| 82 | } |
| 83 | |
| 84 | export async function initPgvector() { |
| 85 | const dims = getEmbeddingDimensions(); |
| 86 | vectorDims = dims; |
| 87 | vectorMode = halfvecMode(dims); |
| 88 | |
| 89 | pool = new pg.Pool({ |
| 90 | connectionString: POSTGRES_URL, |
| 91 | max: parseInt(process.env.PGPOOL_MAX) || 10, |
| 92 | idleTimeoutMillis: 30000, |
| 93 | connectionTimeoutMillis: 5000, |
| 94 | statement_timeout: parseInt(process.env.PG_STATEMENT_TIMEOUT_MS) || 30000, |
| 95 | }); |
| 96 | pool.on('error', (err) => console.error('[pgvector] Idle client error:', err.message)); |
| 97 | |
| 98 | // Register the pgvector type as array-of-float so values round-trip cleanly. |
| 99 | // Without this, pg returns the raw '[1,2,3]' string and upserts fail on type mismatch. |
| 100 | const vectorTypeOid = await registerVectorType(pool); |
| 101 | |
| 102 | // Create extension + table (idempotent) |
| 103 | await pool.query('CREATE EXTENSION IF NOT EXISTS vector'); |
| 104 | |
| 105 | // Capability probe — cache the installed pgvector version once so searchPoints |
| 106 | // knows whether it can enable iterative_scan (relaxed_order), which needs 0.8+. |
| 107 | const verRes = await pool.query("SELECT extversion FROM pg_extension WHERE extname = 'vector'"); |
| 108 | const pgvectorVersion = parseVectorVersion(verRes.rows[0]?.extversion); |
| 109 | iterativeScanSupported = supportsIterativeScan(pgvectorVersion); |
| 110 | |
| 111 | await pool.query(` |
| 112 | CREATE TABLE IF NOT EXISTS memories ( |
| 113 | id TEXT PRIMARY KEY, |
| 114 | vector vector(${dims}), |
| 115 | type TEXT NOT NULL, |
| 116 | source_agent TEXT, |
| 117 | client_id TEXT DEFAULT 'global', |
| 118 | content_hash TEXT, |
| 119 | key TEXT, |
| 120 | subject TEXT, |
| 121 | active BOOLEAN DEFAULT true, |
| 122 | consolidated BOOLEAN DEFAULT false, |
| 123 | importance TEXT, |
| 124 | confidence REAL DEFAULT 1.0, |
| 125 | access_count INTEGER DEFAULT 0, |
| 126 | created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), |
| 127 | last_accessed_at TIMESTAMPTZ, |
| 128 | payload JSONB NOT NULL, |
| 129 | collection TEXT DEFAULT 'shared_memories' |
| 130 | ) |
| 131 | `); |
| 132 | |
| 133 | // Dims guard — if the table pre-exists with a vector column of a different |
| 134 | // declared dimension than the provider now reports, every write would fail |
| 135 | // with an opaque dimension-mismatch. Fail fast at startup with a fix instead. |
| 136 | const colRes = await pool.query( |
| 137 | `SELECT atttypmod FROM pg_attribute |
| 138 | WHERE attrelid = 'memories'::regclass AND attname = 'vector'` |
| 139 | ); |
| 140 | const atttypmod = colRes.rows[0]?.atttypmod; |
| 141 | if (dimsGuardShouldExit(atttypmod, dims)) { |
no test coverage detected