94 lines
2.8 KiB
TypeScript
94 lines
2.8 KiB
TypeScript
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import { migrateV0ToV1 } from './migrators/v0_to_v1.js';
|
|
|
|
/**
|
|
* Detected storage format version.
|
|
* - v0: Legacy JSON format ({db}/{coll}.json files)
|
|
* - v1: Bitcask binary format ({db}/{coll}/data.rdb directories)
|
|
*/
|
|
type TStorageVersion = 0 | 1;
|
|
|
|
/**
|
|
* StorageMigrator — runs before the Rust engine starts.
|
|
*
|
|
* Detects the current storage format version and runs the appropriate
|
|
* migration chain. The Rust engine only knows the current format (v1).
|
|
*
|
|
* Migration is safe: original files are never modified or deleted.
|
|
* On success, a console hint is printed about which old files can be removed.
|
|
*/
|
|
export class StorageMigrator {
|
|
private storagePath: string;
|
|
|
|
constructor(storagePath: string) {
|
|
this.storagePath = storagePath;
|
|
}
|
|
|
|
/**
|
|
* Run any needed migrations. Safe to call even if storage is already current.
|
|
*/
|
|
async run(): Promise<void> {
|
|
if (!fs.existsSync(this.storagePath)) {
|
|
return; // No data yet — nothing to migrate
|
|
}
|
|
|
|
const version = this.detectVersion();
|
|
|
|
if (version === 1) {
|
|
return; // Already current
|
|
}
|
|
|
|
if (version === 0) {
|
|
console.log(`[smartdb] Detected v0 (JSON) storage format at ${this.storagePath}`);
|
|
console.log(`[smartdb] Running migration v0 → v1 (Bitcask binary format)...`);
|
|
|
|
const deletableFiles = await migrateV0ToV1(this.storagePath);
|
|
|
|
if (deletableFiles.length > 0) {
|
|
console.log(`[smartdb] Migration v0 → v1 complete.`);
|
|
console.log(`[smartdb] The following old files can be safely deleted:`);
|
|
for (const f of deletableFiles) {
|
|
console.log(`[smartdb] ${f}`);
|
|
}
|
|
} else {
|
|
console.log(`[smartdb] Migration v0 → v1 complete. No old files to clean up.`);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Detect the storage format version by inspecting the directory structure.
|
|
*
|
|
* v0: {db}/{coll}.json files exist
|
|
* v1: {db}/{coll}/data.rdb directories exist
|
|
*/
|
|
private detectVersion(): TStorageVersion {
|
|
const entries = fs.readdirSync(this.storagePath, { withFileTypes: true });
|
|
|
|
for (const entry of entries) {
|
|
if (!entry.isDirectory()) continue;
|
|
|
|
const dbDir = path.join(this.storagePath, entry.name);
|
|
const dbEntries = fs.readdirSync(dbDir, { withFileTypes: true });
|
|
|
|
for (const dbEntry of dbEntries) {
|
|
// v1: subdirectory with data.rdb
|
|
if (dbEntry.isDirectory()) {
|
|
const dataRdb = path.join(dbDir, dbEntry.name, 'data.rdb');
|
|
if (fs.existsSync(dataRdb)) {
|
|
return 1;
|
|
}
|
|
}
|
|
// v0: .json file (not .indexes.json)
|
|
if (dbEntry.isFile() && dbEntry.name.endsWith('.json') && !dbEntry.name.endsWith('.indexes.json')) {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Empty or unrecognized — treat as v1 (fresh start)
|
|
return 1;
|
|
}
|
|
}
|