Skip to content

Custom Extensions

This guide covers how to extend backupx with custom backup types, storage providers, compression algorithms, and transformation pipelines.

Custom Backup Types

Creating a Custom Database Backup

ts
import type { BackupResult, DatabaseConfig } from 'backupx'
import { BackupType } from 'backupx'

// Define custom backup type
export enum CustomBackupType {
  REDIS = 'REDIS',
  MONGODB = 'MONGODB',
}

interface RedisConfig extends DatabaseConfig {
  type: CustomBackupType.REDIS
  host: string
  port: number
  password?: string
  db?: number
  keyPattern?: string
}

class RedisBackupProvider {
  async backup(config: RedisConfig, outputPath: string): Promise<BackupResult> {
    const startTime = Date.now()
    const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
    const filename = `${config.name}_${timestamp}.json`
    const fullPath = `${outputPath}/${filename}`

    try {
      // Connect to Redis and export data
      const data = await this.exportRedisData(config)

      // Save to file
      const fs = await import('node:fs/promises')
      await fs.writeFile(fullPath, JSON.stringify(data, null, 2))

      const stats = await fs.stat(fullPath)
      const duration = Date.now() - startTime

      return {
        name: config.name,
        type: CustomBackupType.REDIS,
        filename,
        size: stats.size,
        duration,
        success: true,
      }
    }
    catch (error) {
      return {
        name: config.name,
        type: CustomBackupType.REDIS,
        filename: '',
        size: 0,
        duration: Date.now() - startTime,
        success: false,
        error: error instanceof Error ? error.message : String(error),
      }
    }
  }

  private async exportRedisData(config: RedisConfig): Promise<any> {
    // Implementation would connect to Redis and export data
    // This is a simplified example
    return {
      timestamp: new Date().toISOString(),
      database: config.db || 0,
      keys: [], // Would contain actual Redis data
    }
  }
}

Custom Storage Providers

Abstract Storage Interface

ts
interface StorageProvider {
  upload: (localPath: string, remotePath: string) => Promise<void>
  download: (remotePath: string, localPath: string) => Promise<void>
  list: (prefix?: string) => Promise<string[]>
  delete: (remotePath: string) => Promise<void>
}

class FTPStorageProvider implements StorageProvider {
  constructor(
    private config: {
      host: string
      port: number
      username: string
      password: string
    },
  ) {}

  async upload(localPath: string, remotePath: string): Promise<void> {
    // Implementation would use FTP client to upload file
    console.log(`Uploading ${localPath} to ${remotePath}`)
  }

  async download(remotePath: string, localPath: string): Promise<void> {
    // Implementation would download from FTP
    console.log(`Downloading ${remotePath} to ${localPath}`)
  }

  async list(prefix = ''): Promise<string[]> {
    // Implementation would list FTP directory contents
    return []
  }

  async delete(remotePath: string): Promise<void> {
    // Implementation would delete file from FTP
    console.log(`Deleting ${remotePath}`)
  }
}

Custom Compression

Compression Provider Interface

ts
import { Buffer } from 'node:buffer'

interface CompressionProvider {
  compress: (input: Buffer | string) => Promise<Buffer>
  decompress: (input: Buffer) => Promise<Buffer>
  getExtension: () => string
}

class BrotliCompressionProvider implements CompressionProvider {
  async compress(input: Buffer | string): Promise<Buffer> {
    const zlib = await import('node:zlib')
    const buffer = Buffer.isBuffer(input) ? input : Buffer.from(input, 'utf-8')

    return new Promise((resolve, reject) => {
      zlib.brotliCompress(buffer, (error, result) => {
        if (error)
          reject(error)
        else resolve(result)
      })
    })
  }

  async decompress(input: Buffer): Promise<Buffer> {
    const zlib = await import('node:zlib')

    return new Promise((resolve, reject) => {
      zlib.brotliDecompress(input, (error, result) => {
        if (error)
          reject(error)
        else resolve(result)
      })
    })
  }

  getExtension(): string {
    return '.br'
  }
}

Plugin System

Extensible Backup Manager

ts
interface BackupPlugin {
  name: string
  version: string
  providers?: {
    database?: Map<string, any>
    storage?: Map<string, any>
    compression?: Map<string, any>
  }
  hooks?: {
    beforeBackup?: (config: any) => Promise<void>
    afterBackup?: (result: any) => Promise<void>
    onError?: (error: any) => Promise<void>
  }
}

class ExtensibleBackupManager extends BackupManager {
  private plugins: BackupPlugin[] = []
  private providers = {
    database: new Map<string, any>(),
    storage: new Map<string, any>(),
    compression: new Map<string, any>(),
  }

  registerPlugin(plugin: BackupPlugin): void {
    this.plugins.push(plugin)

    // Register providers
    if (plugin.providers?.database) {
      plugin.providers.database.forEach((provider, type) => {
        this.providers.database.set(type, provider)
      })
    }
  }

  async createBackup(): Promise<BackupSummary> {
    // Execute before hooks
    for (const plugin of this.plugins) {
      if (plugin.hooks?.beforeBackup) {
        await plugin.hooks.beforeBackup(this.config)
      }
    }

    try {
      const summary = await super.createBackup()

      // Execute after hooks
      for (const plugin of this.plugins) {
        if (plugin.hooks?.afterBackup) {
          await plugin.hooks.afterBackup(summary)
        }
      }

      return summary
    }
    catch (error) {
      // Execute error hooks
      for (const plugin of this.plugins) {
        if (plugin.hooks?.onError) {
          await plugin.hooks.onError(error)
        }
      }

      throw error
    }
  }
}

// Usage example
const manager = new ExtensibleBackupManager(config)

// Register Redis plugin
manager.registerPlugin({
  name: 'redis-backup',
  version: '1.0.0',
  providers: {
    database: new Map([
      [CustomBackupType.REDIS, new RedisBackupProvider()],
    ]),
  },
  hooks: {
    beforeBackup: async (config) => {
      console.log('Starting Redis backup...')
    },
    afterBackup: async (result) => {
      console.log('Redis backup completed!')
    },
  },
})

This extension system allows backupx to be highly customizable and adaptable to specific use cases while maintaining a clean, modular architecture.

Released under the MIT License.