feat: ID-first storage architecture + remove memory-unsafe APIs (v6.0.0)
BREAKING CHANGES:
**ID-First Storage Paths**
- Direct O(1) entity access without type lookups
- Before: entities/nouns/{TYPE}/metadata/{SHARD}/{ID}.json
- After: entities/nouns/{SHARD}/{ID}/metadata.json
- Migration handled automatically on first init()
**Removed Memory-Unsafe APIs**
- Removed brain.merge() - loaded all entities into memory
- Removed brain.diff() - loaded all entities into memory
- Removed brain.data().backup() - loaded all entities into memory
- Removed brain.data().restore() - depended on backup()
- Removed CLI commands: backup, restore, cow merge
**Migration Paths**
- merge() → Use checkout() or manually copy entities with pagination
- diff() → Use asOf() with manual paginated comparison
- backup() → Use fork() for instant COW snapshots
- restore() → Use checkout() to switch to snapshot branch
Core Improvements:
- ✅ All 8 storage adapters properly call super.init()
- ✅ GraphAdjacencyIndex integration in BaseStorage.init()
- ✅ Fixed ID-first path bugs (vector.json → vectors.json)
- ✅ Fixed MemoryStorage.initializeCounts() for ID-first paths
- ✅ New VFS APIs: du(), access(), find()
- ✅ Comprehensive documentation with migration guides
Storage Adapters Fixed:
- MemoryStorage, FileSystemStorage, AzureBlobStorage
- GCSStorage, R2Storage, S3CompatibleStorage
- OPFSStorage, HistoricalStorageAdapter
Files Changed: 28 files, +1,075/-1,933 lines (net -858)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
9730ca41e5
commit
42ae5be455
28 changed files with 1079 additions and 1937 deletions
|
|
@ -274,74 +274,7 @@ ${chalk.cyan('Fork Statistics:')}
|
|||
},
|
||||
|
||||
/**
|
||||
* Merge a fork/branch into current branch
|
||||
*/
|
||||
async merge(source: string | undefined, target: string | undefined, options: MergeOptions) {
|
||||
let spinner: any = null
|
||||
try {
|
||||
const brain = getBrainy()
|
||||
await brain.init()
|
||||
|
||||
// Interactive mode if parameters missing
|
||||
if (!source || !target) {
|
||||
const branches = await brain.listBranches()
|
||||
const currentBranch = await brain.getCurrentBranch()
|
||||
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'source',
|
||||
message: 'Merge FROM branch:',
|
||||
choices: branches.map(b => ({ name: b, value: b })),
|
||||
when: !source
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'target',
|
||||
message: 'Merge INTO branch:',
|
||||
choices: branches.map(b => ({
|
||||
name: b === currentBranch ? `${b} (current)` : b,
|
||||
value: b
|
||||
})),
|
||||
default: currentBranch,
|
||||
when: !target
|
||||
}
|
||||
])
|
||||
|
||||
source = source || answers.source
|
||||
target = target || answers.target
|
||||
}
|
||||
|
||||
spinner = ora(`Merging ${chalk.cyan(source)} → ${chalk.green(target)}...`).start()
|
||||
|
||||
const result = await brain.merge(source!, target!, {
|
||||
strategy: options.strategy || 'last-write-wins'
|
||||
})
|
||||
|
||||
spinner.succeed(`Merged ${chalk.cyan(source)} → ${chalk.green(target)}`)
|
||||
|
||||
console.log(`
|
||||
${chalk.cyan('Merge Summary:')}
|
||||
${chalk.green('Added:')} ${result.added} entities
|
||||
${chalk.yellow('Modified:')} ${result.modified} entities
|
||||
${chalk.red('Deleted:')} ${result.deleted} entities
|
||||
${chalk.magenta('Conflicts:')} ${result.conflicts} (resolved)
|
||||
`.trim())
|
||||
|
||||
if (options.json) {
|
||||
formatOutput(result, options)
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
if (spinner) spinner.fail('Merge failed')
|
||||
console.error(chalk.red('Error:'), error.message)
|
||||
if (options.verbose) console.error(error)
|
||||
process.exit(1)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get commit history
|
||||
* View commit history
|
||||
*/
|
||||
async history(options: CoreOptions & { limit?: string }) {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* Data Management Commands
|
||||
*
|
||||
* Backup, restore, import, export operations
|
||||
* Import, export, and statistics operations
|
||||
*/
|
||||
|
||||
import chalk from 'chalk'
|
||||
|
|
@ -31,87 +31,6 @@ const formatOutput = (data: any, options: DataOptions): void => {
|
|||
}
|
||||
|
||||
export const dataCommands = {
|
||||
/**
|
||||
* Backup database
|
||||
*/
|
||||
async backup(file: string, options: DataOptions & { compress?: boolean }) {
|
||||
const spinner = ora('Creating backup...').start()
|
||||
|
||||
try {
|
||||
const brain = getBrainy()
|
||||
const dataApi = await brain.data()
|
||||
|
||||
const backup = await dataApi.backup({
|
||||
compress: options.compress
|
||||
})
|
||||
|
||||
spinner.text = 'Writing backup file...'
|
||||
|
||||
// Write backup to file
|
||||
const content = typeof backup === 'string'
|
||||
? backup
|
||||
: JSON.stringify(backup, null, options.pretty ? 2 : 0)
|
||||
|
||||
writeFileSync(file, content)
|
||||
|
||||
spinner.succeed('Backup created')
|
||||
|
||||
if (!options.json) {
|
||||
console.log(chalk.green(`✓ Backup saved to: ${file}`))
|
||||
if ((backup as any).compressed) {
|
||||
console.log(chalk.dim(` Original size: ${formatBytes((backup as any).originalSize)}`))
|
||||
console.log(chalk.dim(` Compressed size: ${formatBytes((backup as any).compressedSize)}`))
|
||||
console.log(chalk.dim(` Compression ratio: ${(((backup as any).compressedSize / (backup as any).originalSize) * 100).toFixed(1)}%`))
|
||||
}
|
||||
} else {
|
||||
formatOutput({ file, backup: true }, options)
|
||||
}
|
||||
} catch (error: any) {
|
||||
spinner.fail('Backup failed')
|
||||
console.error(chalk.red(error.message))
|
||||
process.exit(1)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Restore from backup
|
||||
*/
|
||||
async restore(file: string, options: DataOptions & { merge?: boolean }) {
|
||||
const spinner = ora('Restoring from backup...').start()
|
||||
|
||||
try {
|
||||
const brain = getBrainy()
|
||||
const dataApi = await brain.data()
|
||||
|
||||
// Read backup file
|
||||
const content = readFileSync(file, 'utf-8')
|
||||
const backup = JSON.parse(content)
|
||||
|
||||
// Restore
|
||||
await dataApi.restore({
|
||||
backup,
|
||||
merge: options.merge || false
|
||||
})
|
||||
|
||||
spinner.succeed('Restore complete')
|
||||
|
||||
if (!options.json) {
|
||||
console.log(chalk.green(`✓ Restored from: ${file}`))
|
||||
if (options.merge) {
|
||||
console.log(chalk.dim(' Mode: Merged with existing data'))
|
||||
} else {
|
||||
console.log(chalk.dim(' Mode: Replaced all data'))
|
||||
}
|
||||
} else {
|
||||
formatOutput({ file, restored: true }, options)
|
||||
}
|
||||
} catch (error: any) {
|
||||
spinner.fail('Restore failed')
|
||||
console.error(chalk.red(error.message))
|
||||
process.exit(1)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get database statistics
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -536,18 +536,8 @@ program
|
|||
|
||||
// ===== Data Management Commands =====
|
||||
|
||||
program
|
||||
.command('backup <file>')
|
||||
.description('Create database backup')
|
||||
.option('--compress', 'Compress backup')
|
||||
.action(dataCommands.backup)
|
||||
|
||||
program
|
||||
.command('restore <file>')
|
||||
.description('Restore from backup')
|
||||
.option('--merge', 'Merge with existing data (default: replace)')
|
||||
.action(dataCommands.restore)
|
||||
|
||||
program
|
||||
.command('data-stats')
|
||||
.description('Show detailed database statistics')
|
||||
|
|
@ -657,13 +647,6 @@ program
|
|||
.description('Switch to a different branch')
|
||||
.action(cowCommands.checkout)
|
||||
|
||||
program
|
||||
.command('merge [source] [target]')
|
||||
.description('Merge a fork/branch into another branch')
|
||||
.option('--strategy <type>', 'Merge strategy (last-write-wins|custom)', 'last-write-wins')
|
||||
.option('-f, --force', 'Force merge on conflicts')
|
||||
.action(cowCommands.merge)
|
||||
|
||||
program
|
||||
.command('history')
|
||||
.alias('log')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue