2025-08-26 12:32:21 -07:00
|
|
|
|
/**
|
|
|
|
|
|
* Backup & Restore System - Atomic Age Data Preservation Protocol
|
|
|
|
|
|
*
|
|
|
|
|
|
* 🧠 Complete backup/restore with compression and verification
|
|
|
|
|
|
* ⚛️ 1950s retro sci-fi aesthetic maintained throughout
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
2025-09-11 16:23:32 -07:00
|
|
|
|
import { Brainy } from '../brainy.js'
|
2025-08-26 12:32:21 -07:00
|
|
|
|
import * as fs from '../universal/fs.js'
|
|
|
|
|
|
import * as path from '../universal/path.js'
|
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
|
import chalk from 'chalk'
|
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
|
import ora from 'ora'
|
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
|
import boxen from 'boxen'
|
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
|
import prompts from 'prompts'
|
|
|
|
|
|
|
|
|
|
|
|
export interface BackupOptions {
|
|
|
|
|
|
compress?: boolean
|
|
|
|
|
|
output?: string
|
|
|
|
|
|
includeMetadata?: boolean
|
|
|
|
|
|
includeStatistics?: boolean
|
|
|
|
|
|
verify?: boolean
|
|
|
|
|
|
password?: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface RestoreOptions {
|
|
|
|
|
|
verify?: boolean
|
|
|
|
|
|
overwrite?: boolean
|
|
|
|
|
|
password?: string
|
|
|
|
|
|
dryRun?: boolean
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface BackupManifest {
|
|
|
|
|
|
version: string
|
|
|
|
|
|
timestamp: string
|
|
|
|
|
|
brainyVersion: string
|
|
|
|
|
|
entityCount: number
|
|
|
|
|
|
relationshipCount: number
|
|
|
|
|
|
storageType: string
|
|
|
|
|
|
compressed: boolean
|
|
|
|
|
|
encrypted: boolean
|
|
|
|
|
|
checksum: string
|
|
|
|
|
|
metadata: {
|
|
|
|
|
|
created: string
|
|
|
|
|
|
description?: string
|
|
|
|
|
|
tags?: string[]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Backup & Restore Engine - The Brain's Memory Preservation System
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class BackupRestore {
|
2025-09-11 16:23:32 -07:00
|
|
|
|
private brainy: Brainy
|
2025-08-26 12:32:21 -07:00
|
|
|
|
private colors = {
|
|
|
|
|
|
primary: chalk.hex('#3A5F4A'),
|
|
|
|
|
|
success: chalk.hex('#2D4A3A'),
|
|
|
|
|
|
warning: chalk.hex('#D67441'),
|
|
|
|
|
|
error: chalk.hex('#B85C35'),
|
|
|
|
|
|
info: chalk.hex('#4A6B5A'),
|
|
|
|
|
|
dim: chalk.hex('#8A9B8A'),
|
|
|
|
|
|
highlight: chalk.hex('#E88B5A'),
|
|
|
|
|
|
accent: chalk.hex('#F5E6D3'),
|
|
|
|
|
|
brain: chalk.hex('#E88B5A')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private emojis = {
|
|
|
|
|
|
brain: '🧠',
|
|
|
|
|
|
atom: '⚛️',
|
|
|
|
|
|
disk: '💾',
|
|
|
|
|
|
archive: '📦',
|
|
|
|
|
|
shield: '🛡️',
|
|
|
|
|
|
check: '✅',
|
|
|
|
|
|
warning: '⚠️',
|
|
|
|
|
|
sparkle: '✨',
|
|
|
|
|
|
rocket: '🚀',
|
|
|
|
|
|
gear: '⚙️',
|
|
|
|
|
|
time: '⏰'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-11 16:23:32 -07:00
|
|
|
|
constructor(brainy: Brainy) {
|
2025-08-26 12:32:21 -07:00
|
|
|
|
this.brainy = brainy
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Create a complete backup of Brainy data
|
|
|
|
|
|
*/
|
|
|
|
|
|
async createBackup(options: BackupOptions = {}): Promise<string> {
|
|
|
|
|
|
const outputPath = options.output || this.generateBackupPath()
|
|
|
|
|
|
|
|
|
|
|
|
console.log(boxen(
|
|
|
|
|
|
`${this.emojis.archive} ${this.colors.brain('ATOMIC DATA PRESERVATION PROTOCOL')} ${this.emojis.atom}\n\n` +
|
|
|
|
|
|
`${this.colors.accent('◆')} ${this.colors.dim('Initiating brain backup sequence')}\n` +
|
|
|
|
|
|
`${this.colors.accent('◆')} ${this.colors.dim('Output:')} ${this.colors.highlight(outputPath)}\n` +
|
|
|
|
|
|
`${this.colors.accent('◆')} ${this.colors.dim('Compression:')} ${this.colors.highlight(options.compress ? 'Enabled' : 'Disabled')}`,
|
|
|
|
|
|
{ padding: 1, borderStyle: 'round', borderColor: '#E88B5A' }
|
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
|
|
const spinner = ora(`${this.emojis.brain} Scanning neural pathways...`).start()
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// Phase 1: Collect data
|
|
|
|
|
|
spinner.text = `${this.emojis.gear} Extracting neural data...`
|
|
|
|
|
|
const backupData = await this.collectBackupData(options)
|
|
|
|
|
|
|
|
|
|
|
|
// Phase 2: Create manifest
|
|
|
|
|
|
spinner.text = `${this.emojis.atom} Generating quantum manifest...`
|
|
|
|
|
|
const manifest = await this.createManifest(backupData, options)
|
|
|
|
|
|
|
|
|
|
|
|
// Phase 3: Package data
|
|
|
|
|
|
spinner.text = `${this.emojis.archive} Packaging atomic data...`
|
|
|
|
|
|
const packagedData = {
|
|
|
|
|
|
manifest,
|
|
|
|
|
|
data: backupData
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Phase 4: Compress if requested
|
|
|
|
|
|
let finalData = JSON.stringify(packagedData, null, 2)
|
|
|
|
|
|
if (options.compress) {
|
|
|
|
|
|
spinner.text = `${this.emojis.gear} Applying quantum compression...`
|
|
|
|
|
|
finalData = await this.compressData(finalData)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Phase 5: Encrypt if password provided
|
|
|
|
|
|
if (options.password) {
|
|
|
|
|
|
spinner.text = `${this.emojis.shield} Applying atomic encryption...`
|
|
|
|
|
|
finalData = await this.encryptData(finalData, options.password)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Phase 6: Write to file
|
|
|
|
|
|
spinner.text = `${this.emojis.disk} Storing in atomic vault...`
|
|
|
|
|
|
await fs.writeFile(outputPath, finalData)
|
|
|
|
|
|
|
|
|
|
|
|
// Phase 7: Verify if requested
|
|
|
|
|
|
if (options.verify) {
|
|
|
|
|
|
spinner.text = `${this.emojis.check} Verifying atomic integrity...`
|
|
|
|
|
|
await this.verifyBackup(outputPath, options)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
spinner.succeed(this.colors.success(
|
|
|
|
|
|
`${this.emojis.sparkle} Backup complete! Neural pathways preserved in atomic vault.`
|
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
|
|
console.log(boxen(
|
|
|
|
|
|
`${this.emojis.brain} ${this.colors.brain('BACKUP SUMMARY')}\n\n` +
|
|
|
|
|
|
`${this.colors.accent('◆')} ${this.colors.dim('Entities:')} ${this.colors.primary(manifest.entityCount.toLocaleString())}\n` +
|
|
|
|
|
|
`${this.colors.accent('◆')} ${this.colors.dim('Relationships:')} ${this.colors.primary(manifest.relationshipCount.toLocaleString())}\n` +
|
|
|
|
|
|
`${this.colors.accent('◆')} ${this.colors.dim('Size:')} ${this.colors.highlight(this.formatFileSize(finalData.length))}\n` +
|
|
|
|
|
|
`${this.colors.accent('◆')} ${this.colors.dim('Location:')} ${this.colors.highlight(outputPath)}`,
|
|
|
|
|
|
{ padding: 1, borderStyle: 'round', borderColor: '#2D4A3A' }
|
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
|
|
return outputPath
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
spinner.fail('Backup failed - atomic vault compromised!')
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Restore Brainy data from backup
|
|
|
|
|
|
*/
|
|
|
|
|
|
async restoreBackup(backupPath: string, options: RestoreOptions = {}): Promise<void> {
|
|
|
|
|
|
console.log(boxen(
|
|
|
|
|
|
`${this.emojis.rocket} ${this.colors.brain('ATOMIC RESTORATION PROTOCOL')} ${this.emojis.atom}\n\n` +
|
|
|
|
|
|
`${this.colors.accent('◆')} ${this.colors.dim('Initiating neural restoration sequence')}\n` +
|
|
|
|
|
|
`${this.colors.accent('◆')} ${this.colors.dim('Source:')} ${this.colors.highlight(backupPath)}\n` +
|
|
|
|
|
|
`${this.colors.accent('◆')} ${this.colors.dim('Mode:')} ${this.colors.highlight(options.dryRun ? 'Simulation' : 'Full Restore')}`,
|
|
|
|
|
|
{ padding: 1, borderStyle: 'round', borderColor: '#E88B5A' }
|
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
|
|
const spinner = ora(`${this.emojis.brain} Loading atomic vault...`).start()
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// Phase 1: Load backup file
|
|
|
|
|
|
spinner.text = `${this.emojis.disk} Reading atomic data...`
|
|
|
|
|
|
let rawData = await fs.readFile(backupPath, 'utf8')
|
|
|
|
|
|
|
|
|
|
|
|
// Phase 2: Decrypt if needed
|
|
|
|
|
|
if (options.password) {
|
|
|
|
|
|
spinner.text = `${this.emojis.shield} Decrypting atomic data...`
|
|
|
|
|
|
rawData = await this.decryptData(rawData, options.password)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Phase 3: Decompress if needed
|
|
|
|
|
|
spinner.text = `${this.emojis.gear} Decompressing quantum data...`
|
|
|
|
|
|
const decompressedData = await this.decompressData(rawData)
|
|
|
|
|
|
|
|
|
|
|
|
// Phase 4: Parse backup data
|
|
|
|
|
|
const backupPackage = JSON.parse(decompressedData)
|
|
|
|
|
|
const { manifest, data } = backupPackage
|
|
|
|
|
|
|
|
|
|
|
|
// Phase 5: Verify integrity
|
|
|
|
|
|
if (options.verify) {
|
|
|
|
|
|
spinner.text = `${this.emojis.check} Verifying atomic integrity...`
|
|
|
|
|
|
await this.verifyRestoreData(data, manifest)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Phase 6: Display what will be restored
|
|
|
|
|
|
console.log('\n' + boxen(
|
|
|
|
|
|
`${this.emojis.brain} ${this.colors.brain('RESTORATION PREVIEW')}\n\n` +
|
|
|
|
|
|
`${this.colors.accent('◆')} ${this.colors.dim('Backup Date:')} ${this.colors.highlight(new Date(manifest.timestamp).toLocaleString())}\n` +
|
|
|
|
|
|
`${this.colors.accent('◆')} ${this.colors.dim('Entities:')} ${this.colors.primary(manifest.entityCount.toLocaleString())}\n` +
|
|
|
|
|
|
`${this.colors.accent('◆')} ${this.colors.dim('Relationships:')} ${this.colors.primary(manifest.relationshipCount.toLocaleString())}\n` +
|
|
|
|
|
|
`${this.colors.accent('◆')} ${this.colors.dim('Storage Type:')} ${this.colors.highlight(manifest.storageType)}`,
|
|
|
|
|
|
{ padding: 1, borderStyle: 'round', borderColor: '#D67441' }
|
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
|
|
if (options.dryRun) {
|
|
|
|
|
|
spinner.succeed(this.colors.success('Dry run complete - restoration simulation successful'))
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Phase 7: Confirm restoration
|
|
|
|
|
|
if (!options.overwrite) {
|
|
|
|
|
|
const { confirm } = await prompts({
|
|
|
|
|
|
type: 'confirm',
|
|
|
|
|
|
name: 'confirm',
|
|
|
|
|
|
message: `${this.emojis.warning} This will replace current data. Continue?`,
|
|
|
|
|
|
initial: false
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if (!confirm) {
|
|
|
|
|
|
spinner.info('Restoration cancelled by user')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Phase 8: Restore data
|
|
|
|
|
|
spinner.text = `${this.emojis.rocket} Restoring neural pathways...`
|
|
|
|
|
|
await this.executeRestore(data, manifest)
|
|
|
|
|
|
|
|
|
|
|
|
spinner.succeed(this.colors.success(
|
|
|
|
|
|
`${this.emojis.sparkle} Restoration complete! Neural pathways successfully reconstructed.`
|
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
spinner.fail('Restoration failed - atomic vault corrupted!')
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* List available backups in a directory
|
|
|
|
|
|
*/
|
|
|
|
|
|
async listBackups(directory: string = './backups'): Promise<BackupManifest[]> {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const files = await fs.readdir(directory)
|
|
|
|
|
|
const backupFiles = files.filter(f => f.endsWith('.brainy') || f.endsWith('.json'))
|
|
|
|
|
|
|
|
|
|
|
|
const manifests: BackupManifest[] = []
|
|
|
|
|
|
|
|
|
|
|
|
for (const file of backupFiles) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const filePath = path.join(directory, file)
|
|
|
|
|
|
const manifest = await this.getBackupManifest(filePath)
|
|
|
|
|
|
if (manifest) manifests.push(manifest)
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
// Skip invalid backup files
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return manifests.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
return []
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Get backup manifest without loading full backup
|
|
|
|
|
|
*/
|
|
|
|
|
|
private async getBackupManifest(backupPath: string): Promise<BackupManifest | null> {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const rawData = await fs.readFile(backupPath, 'utf8')
|
|
|
|
|
|
const decompressedData = await this.decompressData(rawData)
|
|
|
|
|
|
const backupPackage = JSON.parse(decompressedData)
|
|
|
|
|
|
return backupPackage.manifest || null
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Collect all data for backup
|
|
|
|
|
|
*/
|
|
|
|
|
|
private async collectBackupData(options: BackupOptions): Promise<any> {
|
|
|
|
|
|
const data: any = {
|
|
|
|
|
|
entities: [],
|
|
|
|
|
|
relationships: [],
|
|
|
|
|
|
metadata: {},
|
|
|
|
|
|
statistics: null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For now, we'll create a simplified backup that just captures the current state
|
|
|
|
|
|
// In a full implementation, this would use internal storage methods
|
|
|
|
|
|
|
|
|
|
|
|
console.log(this.colors.warning('Note: Backup system is in beta - captures basic data only'))
|
|
|
|
|
|
|
|
|
|
|
|
// Placeholder data collection
|
|
|
|
|
|
data.entities = []
|
|
|
|
|
|
data.relationships = []
|
|
|
|
|
|
|
|
|
|
|
|
// Collect metadata if requested
|
|
|
|
|
|
if (options.includeMetadata) {
|
|
|
|
|
|
data.metadata = await this.collectMetadata()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-11 16:23:32 -07:00
|
|
|
|
// Statistics not yet implemented
|
2025-08-26 12:32:21 -07:00
|
|
|
|
if (options.includeStatistics) {
|
2025-09-11 16:23:32 -07:00
|
|
|
|
console.warn('Statistics collection not yet implemented in backup')
|
2025-08-26 12:32:21 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return data
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Create backup manifest
|
|
|
|
|
|
*/
|
|
|
|
|
|
private async createManifest(data: any, options: BackupOptions): Promise<BackupManifest> {
|
|
|
|
|
|
return {
|
|
|
|
|
|
version: '1.0.0',
|
|
|
|
|
|
timestamp: new Date().toISOString(),
|
|
|
|
|
|
brainyVersion: '0.55.0', // Would come from package.json
|
|
|
|
|
|
entityCount: data.entities.length,
|
|
|
|
|
|
relationshipCount: data.relationships.length,
|
|
|
|
|
|
storageType: 'unknown', // Would detect from brainy instance
|
|
|
|
|
|
compressed: options.compress || false,
|
|
|
|
|
|
encrypted: !!options.password,
|
|
|
|
|
|
checksum: await this.calculateChecksum(JSON.stringify(data)),
|
|
|
|
|
|
metadata: {
|
|
|
|
|
|
created: new Date().toISOString(),
|
|
|
|
|
|
description: 'Atomic age brain backup',
|
|
|
|
|
|
tags: ['brainy', 'neural-backup', 'atomic-data']
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Helper methods
|
|
|
|
|
|
*/
|
|
|
|
|
|
private generateBackupPath(): string {
|
|
|
|
|
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
|
|
|
|
|
|
return `./brainy-backup-${timestamp}.brainy`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async compressData(data: string): Promise<string> {
|
2025-09-11 16:23:32 -07:00
|
|
|
|
// Use zlib gzip compression
|
feat: add node: protocol to all Node.js built-in imports for bundler compatibility
- Updated all fs, path, crypto, os, url, util, events, http, https, net, child_process, stream, and zlib imports
- Changed both static imports and dynamic imports to use node: protocol
- This makes Brainy more bundler-friendly by explicitly marking Node.js built-ins
- Prevents bundlers from attempting to polyfill or bundle these modules
- Reduces bundle size for web applications using Brainy
- Improves tree-shaking and dead code elimination
Benefits for external bundlers:
- Clear distinction between Node.js built-ins and external dependencies
- No ambiguity about what needs polyfilling
- Smaller bundles for browser builds
- Better compatibility with modern bundlers (Webpack 5, Vite, Rollup, esbuild)
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-17 14:20:21 -07:00
|
|
|
|
const { gzip } = await import('node:zlib')
|
|
|
|
|
|
const { promisify } = await import('node:util')
|
2025-09-11 16:23:32 -07:00
|
|
|
|
const gzipAsync = promisify(gzip)
|
|
|
|
|
|
|
|
|
|
|
|
const compressed = await gzipAsync(Buffer.from(data, 'utf-8'))
|
|
|
|
|
|
return compressed.toString('base64')
|
2025-08-26 12:32:21 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async decompressData(data: string): Promise<string> {
|
2025-09-11 16:23:32 -07:00
|
|
|
|
// Use zlib gunzip decompression
|
feat: add node: protocol to all Node.js built-in imports for bundler compatibility
- Updated all fs, path, crypto, os, url, util, events, http, https, net, child_process, stream, and zlib imports
- Changed both static imports and dynamic imports to use node: protocol
- This makes Brainy more bundler-friendly by explicitly marking Node.js built-ins
- Prevents bundlers from attempting to polyfill or bundle these modules
- Reduces bundle size for web applications using Brainy
- Improves tree-shaking and dead code elimination
Benefits for external bundlers:
- Clear distinction between Node.js built-ins and external dependencies
- No ambiguity about what needs polyfilling
- Smaller bundles for browser builds
- Better compatibility with modern bundlers (Webpack 5, Vite, Rollup, esbuild)
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-17 14:20:21 -07:00
|
|
|
|
const { gunzip } = await import('node:zlib')
|
|
|
|
|
|
const { promisify } = await import('node:util')
|
2025-09-11 16:23:32 -07:00
|
|
|
|
const gunzipAsync = promisify(gunzip)
|
|
|
|
|
|
|
|
|
|
|
|
const compressed = Buffer.from(data, 'base64')
|
|
|
|
|
|
const decompressed = await gunzipAsync(compressed)
|
|
|
|
|
|
return decompressed.toString('utf-8')
|
2025-08-26 12:32:21 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async encryptData(data: string, password: string): Promise<string> {
|
2025-09-11 16:23:32 -07:00
|
|
|
|
// Use crypto module for AES-256 encryption
|
feat: add node: protocol to all Node.js built-in imports for bundler compatibility
- Updated all fs, path, crypto, os, url, util, events, http, https, net, child_process, stream, and zlib imports
- Changed both static imports and dynamic imports to use node: protocol
- This makes Brainy more bundler-friendly by explicitly marking Node.js built-ins
- Prevents bundlers from attempting to polyfill or bundle these modules
- Reduces bundle size for web applications using Brainy
- Improves tree-shaking and dead code elimination
Benefits for external bundlers:
- Clear distinction between Node.js built-ins and external dependencies
- No ambiguity about what needs polyfilling
- Smaller bundles for browser builds
- Better compatibility with modern bundlers (Webpack 5, Vite, Rollup, esbuild)
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-17 14:20:21 -07:00
|
|
|
|
const crypto = await import('node:crypto')
|
2025-09-11 16:23:32 -07:00
|
|
|
|
|
|
|
|
|
|
// Generate key from password
|
|
|
|
|
|
const key = crypto.createHash('sha256').update(password).digest()
|
|
|
|
|
|
const iv = crypto.randomBytes(16)
|
|
|
|
|
|
|
|
|
|
|
|
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv)
|
|
|
|
|
|
let encrypted = cipher.update(data, 'utf8', 'base64')
|
|
|
|
|
|
encrypted += cipher.final('base64')
|
|
|
|
|
|
|
|
|
|
|
|
// Prepend IV to encrypted data for decryption
|
|
|
|
|
|
return iv.toString('base64') + ':' + encrypted
|
2025-08-26 12:32:21 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async decryptData(data: string, password: string): Promise<string> {
|
2025-09-11 16:23:32 -07:00
|
|
|
|
// Use crypto module for AES-256 decryption
|
feat: add node: protocol to all Node.js built-in imports for bundler compatibility
- Updated all fs, path, crypto, os, url, util, events, http, https, net, child_process, stream, and zlib imports
- Changed both static imports and dynamic imports to use node: protocol
- This makes Brainy more bundler-friendly by explicitly marking Node.js built-ins
- Prevents bundlers from attempting to polyfill or bundle these modules
- Reduces bundle size for web applications using Brainy
- Improves tree-shaking and dead code elimination
Benefits for external bundlers:
- Clear distinction between Node.js built-ins and external dependencies
- No ambiguity about what needs polyfilling
- Smaller bundles for browser builds
- Better compatibility with modern bundlers (Webpack 5, Vite, Rollup, esbuild)
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-17 14:20:21 -07:00
|
|
|
|
const crypto = await import('node:crypto')
|
2025-09-11 16:23:32 -07:00
|
|
|
|
|
|
|
|
|
|
// Split IV and encrypted data
|
|
|
|
|
|
const [ivString, encrypted] = data.split(':')
|
|
|
|
|
|
const iv = Buffer.from(ivString, 'base64')
|
|
|
|
|
|
|
|
|
|
|
|
// Generate key from password
|
|
|
|
|
|
const key = crypto.createHash('sha256').update(password).digest()
|
|
|
|
|
|
|
|
|
|
|
|
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv)
|
|
|
|
|
|
let decrypted = decipher.update(encrypted, 'base64', 'utf8')
|
|
|
|
|
|
decrypted += decipher.final('utf8')
|
|
|
|
|
|
|
|
|
|
|
|
return decrypted
|
2025-08-26 12:32:21 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async verifyBackup(backupPath: string, options: BackupOptions): Promise<void> {
|
|
|
|
|
|
// Placeholder - would verify backup integrity
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async verifyRestoreData(data: any, manifest: BackupManifest): Promise<void> {
|
|
|
|
|
|
const actualChecksum = await this.calculateChecksum(JSON.stringify(data))
|
|
|
|
|
|
if (actualChecksum !== manifest.checksum) {
|
|
|
|
|
|
throw new Error('Data integrity check failed - backup may be corrupted')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async executeRestore(data: any, manifest: BackupManifest): Promise<void> {
|
2025-09-11 16:23:32 -07:00
|
|
|
|
console.log(this.colors.info('🔄 Starting restore process...'))
|
2025-08-26 12:32:21 -07:00
|
|
|
|
|
|
|
|
|
|
// Phase 1: Validate data structure
|
|
|
|
|
|
if (!data.entities || !Array.isArray(data.entities)) {
|
|
|
|
|
|
throw new Error('Invalid backup data structure')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-11 16:23:32 -07:00
|
|
|
|
// Phase 2: Clear existing data if overwriting
|
|
|
|
|
|
console.log(this.colors.dim('Clearing existing data...'))
|
|
|
|
|
|
const dataAPI = await this.brainy.data()
|
|
|
|
|
|
await dataAPI.clear()
|
2025-08-26 12:32:21 -07:00
|
|
|
|
|
2025-09-11 16:23:32 -07:00
|
|
|
|
// Phase 3: Restore entities
|
|
|
|
|
|
console.log(this.colors.dim(`Restoring ${data.entities.length} entities...`))
|
|
|
|
|
|
const entityMap = new Map<string, string>() // old ID -> new ID mapping
|
2025-08-26 12:32:21 -07:00
|
|
|
|
|
2025-09-11 16:23:32 -07:00
|
|
|
|
for (const entity of data.entities) {
|
|
|
|
|
|
const newId = await this.brainy.add({
|
|
|
|
|
|
data: entity.metadata || entity.data,
|
|
|
|
|
|
type: entity.type,
|
|
|
|
|
|
metadata: entity.metadata,
|
|
|
|
|
|
vector: entity.vector, // Preserve original vectors if available
|
|
|
|
|
|
service: entity.service
|
|
|
|
|
|
})
|
|
|
|
|
|
entityMap.set(entity.id, newId)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Phase 4: Restore relationships if they exist
|
|
|
|
|
|
if (data.relationships && Array.isArray(data.relationships)) {
|
|
|
|
|
|
console.log(this.colors.dim(`Restoring ${data.relationships.length} relationships...`))
|
|
|
|
|
|
|
|
|
|
|
|
for (const rel of data.relationships) {
|
|
|
|
|
|
// Map old IDs to new IDs
|
|
|
|
|
|
const fromId = entityMap.get(rel.from) || rel.from
|
|
|
|
|
|
const toId = entityMap.get(rel.to) || rel.to
|
|
|
|
|
|
|
|
|
|
|
|
await this.brainy.relate({
|
|
|
|
|
|
from: fromId,
|
|
|
|
|
|
to: toId,
|
|
|
|
|
|
type: rel.type,
|
|
|
|
|
|
metadata: rel.metadata || {}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Phase 5: Restore metadata if present
|
2025-08-26 12:32:21 -07:00
|
|
|
|
if (data.metadata) {
|
|
|
|
|
|
await this.restoreMetadata(data.metadata)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-11 16:23:32 -07:00
|
|
|
|
console.log(this.colors.success('✅ Restore completed successfully'))
|
2025-08-26 12:32:21 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async collectMetadata(): Promise<any> {
|
2025-09-11 16:23:32 -07:00
|
|
|
|
// Collect global metadata like statistics, configuration, etc.
|
|
|
|
|
|
const dataApi = await this.brainy.data()
|
|
|
|
|
|
const stats = await dataApi.getStats()
|
|
|
|
|
|
return {
|
|
|
|
|
|
totalEntities: stats.entities || 0,
|
|
|
|
|
|
timestamp: new Date().toISOString(),
|
|
|
|
|
|
version: '3.0.0'
|
|
|
|
|
|
}
|
2025-08-26 12:32:21 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async restoreMetadata(metadata: any): Promise<void> {
|
2025-09-11 16:23:32 -07:00
|
|
|
|
// Store restored metadata for reference
|
|
|
|
|
|
// Could be saved to a config store or logged
|
|
|
|
|
|
console.log(this.colors.dim(`Restored from backup created at: ${metadata.timestamp}`))
|
2025-08-26 12:32:21 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async calculateChecksum(data: string): Promise<string> {
|
2025-09-11 16:23:32 -07:00
|
|
|
|
// Use crypto module for SHA-256 checksum
|
feat: add node: protocol to all Node.js built-in imports for bundler compatibility
- Updated all fs, path, crypto, os, url, util, events, http, https, net, child_process, stream, and zlib imports
- Changed both static imports and dynamic imports to use node: protocol
- This makes Brainy more bundler-friendly by explicitly marking Node.js built-ins
- Prevents bundlers from attempting to polyfill or bundle these modules
- Reduces bundle size for web applications using Brainy
- Improves tree-shaking and dead code elimination
Benefits for external bundlers:
- Clear distinction between Node.js built-ins and external dependencies
- No ambiguity about what needs polyfilling
- Smaller bundles for browser builds
- Better compatibility with modern bundlers (Webpack 5, Vite, Rollup, esbuild)
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-17 14:20:21 -07:00
|
|
|
|
const crypto = await import('node:crypto')
|
2025-09-11 16:23:32 -07:00
|
|
|
|
return crypto.createHash('sha256').update(data).digest('hex')
|
2025-08-26 12:32:21 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private formatFileSize(bytes: number): string {
|
|
|
|
|
|
const units = ['B', 'KB', 'MB', 'GB']
|
|
|
|
|
|
let size = bytes
|
|
|
|
|
|
let unitIndex = 0
|
|
|
|
|
|
|
|
|
|
|
|
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
|
|
|
|
size /= 1024
|
|
|
|
|
|
unitIndex++
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return `${size.toFixed(1)} ${units[unitIndex]}`
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|