autosnippet
Version:
Extract code patterns into a knowledge base for AI coding assistants
82 lines (81 loc) • 3.2 kB
JavaScript
/**
* VectorMigration — JSON → HNSW 二进制索引自动迁移
*
* 场景:
* 1. 首次启动, 无任何索引 → 返回 'new'
* 2. 存在 vector_index.json → 读取 JSON, 批量插入 HNSW, 重命名旧文件
* 3. 存在 .asvec 二进制索引 → 返回 'binary' (已迁移)
*
* @module infrastructure/vector/VectorMigration
*/
import { existsSync, readFileSync, renameSync } from 'node:fs';
import { join } from 'node:path';
export class VectorMigration {
/**
* 检测并执行自动迁移
*
* @param indexDir 索引目录路径
* @param adapter HNSW 适配器实例
*/
static async migrate(indexDir, adapter) {
const jsonPath = join(indexDir, 'vector_index.json');
const hnswPath = join(indexDir, 'vector_index.asvec');
// 场景 3: 已有二进制索引 (需验证有效性)
if (existsSync(hnswPath)) {
// 如果 .asvec 损坏且同时存在 .json, 从 JSON 迁移
const { BinaryPersistence } = await import('./BinaryPersistence.js');
if (BinaryPersistence.isValid(hnswPath)) {
return 'binary';
}
// .asvec 损坏, 检查是否有 JSON 可迁移
if (!existsSync(jsonPath)) {
return 'binary'; // 无 JSON 可回退, 保持现状
}
// 有 JSON, 将从 JSON 迁移 (跳过这个 if, 进入下方迁移逻辑)
}
// 场景 2: 存在旧 JSON 索引
if (existsSync(jsonPath)) {
try {
const raw = readFileSync(jsonPath, 'utf-8');
const items = JSON.parse(raw);
const itemList = Array.isArray(items)
? items
: Object.entries(items).map(([id, item]) => ({
...item,
id,
}));
if (itemList.length > 0) {
// 过滤有效条目并批量插入
const validItems = itemList.filter((item) => item?.id);
if (validItems.length > 0) {
await adapter.batchUpsert(validItems.map((item) => ({
id: item.id,
content: item.content || '',
vector: item.vector || [],
metadata: item.metadata || {},
})));
}
// 重命名旧文件
try {
renameSync(jsonPath, `${jsonPath}.bak`);
}
catch {
/* 重命名失败不影响迁移 */
}
return 'migrated';
}
}
catch {
// JSON 解析失败, 视为新安装
}
}
// 场景 1: 全新安装
return 'new';
}
/** 检查是否需要迁移 */
static needsMigration(indexDir) {
const jsonPath = join(indexDir, 'vector_index.json');
const hnswPath = join(indexDir, 'vector_index.asvec');
return existsSync(jsonPath) && !existsSync(hnswPath);
}
}