Files
memind/memory-v2-pgvector-schema.mjs
2026-07-03 09:46:03 +08:00

79 lines
2.5 KiB
JavaScript

const DEFAULT_TABLE = 'memory_embeddings';
const DEFAULT_DIMENSIONS = 1536;
function isSafeIdentifier(value) {
return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(String(value ?? ''));
}
function quoteIdent(value) {
const normalized = String(value ?? '').trim();
if (!isSafeIdentifier(normalized)) {
throw new Error(`Invalid PostgreSQL identifier: ${normalized}`);
}
return `"${normalized}"`;
}
function resolveDimensions(value) {
const dimensions = Number(value ?? DEFAULT_DIMENSIONS);
if (!Number.isInteger(dimensions) || dimensions < 1 || dimensions > 16_384) {
throw new Error(`Invalid pgvector dimensions: ${value}`);
}
return dimensions;
}
export function buildPgvectorMemorySchemaSql({
tableName = DEFAULT_TABLE,
dimensions = DEFAULT_DIMENSIONS,
createExtension = false,
createVectorIndex = false,
} = {}) {
const table = quoteIdent(tableName);
const dim = resolveDimensions(dimensions);
const statements = [];
if (createExtension) {
statements.push('CREATE EXTENSION IF NOT EXISTS vector');
}
statements.push(`
CREATE TABLE IF NOT EXISTS ${table} (
id BIGSERIAL PRIMARY KEY,
user_id TEXT NOT NULL,
content TEXT NOT NULL,
embedding vector(${dim}) NOT NULL,
type TEXT NOT NULL DEFAULT 'fact',
source_memory_id TEXT,
source_session_id TEXT,
source_message_id TEXT,
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)`.trim());
statements.push(
`CREATE INDEX IF NOT EXISTS ${quoteIdent(`${tableName}_user_created_idx`)} ON ${table} (user_id, created_at DESC)`,
);
statements.push(
`CREATE UNIQUE INDEX IF NOT EXISTS ${quoteIdent(`${tableName}_source_memory_id_uidx`)} ON ${table} (source_memory_id) WHERE source_memory_id IS NOT NULL`,
);
if (createVectorIndex) {
statements.push(
`CREATE INDEX IF NOT EXISTS ${quoteIdent(`${tableName}_embedding_idx`)} ON ${table} USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100)`,
);
}
return statements;
}
export async function ensurePgvectorMemorySchema(pool, options = {}) {
if (!pool?.query) {
throw new Error('ensurePgvectorMemorySchema requires a PostgreSQL pool with query(sql)');
}
const statements = buildPgvectorMemorySchemaSql(options);
for (const statement of statements) {
await pool.query(statement);
}
return {
ok: true,
statements: statements.length,
tableName: options.tableName ?? DEFAULT_TABLE,
dimensions: resolveDimensions(options.dimensions),
};
}