**Scope:**All files matching **/*.{ts,tsx}
Purpose: Code Style & Structure specifics
-
Write concise, functional code with proper types
// Good function mergeConfigs<T>(base: T, override: Partial<T>): T { return { ...base, ...override } } // Avoid class ConfigMerger { merge(base: any, override: any) { return Object.assign({}, base, override) } }
-
Use Bun native modules when available
// Good import { file } from 'bun' // Avoid import { readFile } from 'node:fs/promises' const config = await file('config.json').json() const config = JSON.parse(await readFile('config.json', 'utf-8'))
-
Use descriptive variable names with proper prefixes
// Good const isConfigValid = validateConfig(config) const hasCustomOptions = Boolean(options.custom) const shouldUseDefaults = !configExists || isConfigEmpty // Avoid const valid = check(cfg) const custom = !!options.custom const defaults = !exists || empty
-
Write proper JSDoc comments for public APIs
/** * Loads configuration from a file or remote endpoint * @param options - Configuration options * @param options.name - Name of the config file * @param options.cwd - Working directory (default: process.cwd()) * @returns Resolved configuration object * @throws {ConfigError} When config loading fails * @example * ```ts * const config = await loadConfig({ * name: 'myapp', * defaultConfig: { port: 3000 } * }) * ``` */ async function loadConfig<T>(options: Config<T>): Promise<T>
-
Use proper module organization
export { ConfigError } from './errors' // config.ts export { loadConfig } from './loader' export type { Config, ConfigOptions } from './types'
-
Follow consistent error handling patterns
// Good const result = await loadConfig(options).catch((error) => { console.error('Config loading failed:', error) return options.defaultConfig }) // Avoid try { const result = await loadConfig(options) } catch (e) { console.log('Error:', e) }
-
Use proper type assertions
// Good const config = result as Config if (!isValidConfig(config)) throw new Error('Invalid config') // Avoid const config = result as any
**Scope:**All files matching **/*.{ts,tsx,md}
Purpose: Documentation specific rules
- Document all public APIs thoroughly
- Include TypeScript type information
- Provide clear function signatures
- Document config options and defaults
- Include return type information
- Document async behavior
- Provide basic usage examples
- Include complex configuration examples
- Document all supported config formats
- Show browser usage examples
- Include TypeScript configuration examples
- Document config merging behavior
- Document generic type parameters
- Explain type constraints
- Document interface properties
- Include type union explanations
- Document type generation features
- Provide type utility examples
- Document common error scenarios
- Include error handling examples
- Document error recovery options
- Explain validation errors
- Document browser-specific errors
- Include troubleshooting guides
- Include runnable code examples
- Provide TypeScript examples
- Show error handling patterns
- Include browser environment examples
- Document testing approaches
- Include CLI usage examples
- Keep documentation up to date
- Use consistent formatting
- Include inline code comments
- Document breaking changes
- Maintain a changelog
- Include version information
- Maintain clear docs organization
- Use proper markdown formatting
- Include table of contents
- Organize by topic
- Keep related docs together
- Use proper headings
- Use clear and concise language
- Include proper code blocks
- Document all parameters
- Provide return value descriptions
- Include usage notes
- Document dependencies
- Keep examples current
**Scope:**All files matching **/*.{ts,tsx}
Purpose: Error Handling and Validation specifics
-
Use early returns and guard clauses for validation
function loadConfig<T>(options: Config<T>) { if (!options.name) throw new Error('Config name is required') if (!isObject(options.defaultConfig)) throw new Error('Default config must be an object') // Continue with valid input }
-
Implement proper error types
class ConfigError extends Error { constructor( message: string, public readonly code: string, public readonly details?: unknown ) { super(message) this.name = 'ConfigError' } }
-
Use descriptive error messages
throw new ConfigError( `Failed to load config file: ${filePath}`, 'CONFIG*LOAD*ERROR', { cause: error } )
-
Handle async errors properly
async function loadConfigFile(path: string) { try { const content = await Bun.file(path).text() return JSON.parse(content) } catch (error) { if (error instanceof SyntaxError) throw new ConfigError('Invalid JSON in config file', 'PARSE*ERROR') throw new ConfigError('Failed to read config file', 'READ*ERROR') } }
-
Implement proper error logging
function handleError(error: unknown) { if (error instanceof ConfigError) { console.error(`[${error.code}] ${error.message}`) if (error.details) console.debug('Error details:', error.details) } else { console.error('Unexpected error:', error) } }
-
Use error boundaries for unexpected errors
try { await loadConfig(options) } catch (error) { handleError(error) return options.defaultConfig ?? {} }
-
Ensure errors are typed when using Result types
import { err, ok, Result } from 'neverthrow' function validateConfig(config: unknown): Result<Config, ConfigError> { if (!isValidConfig(config)) return err(new ConfigError('Invalid config format', 'VALIDATION*ERROR')) return ok(config) }
**Scope:**All files matching **/*.{ts,tsx}
Purpose: Key Conventions specifics
-
Prefer browser-compatible implementations when possible
// Good - Browser compatible const config = await fetch('/api/config').then(r => r.json()) // Avoid - Node.js specific const config = require('./config')
-
Aim for comprehensive test coverage
// Test both success and failure cases describe('loadConfig', () => { it('success case - load config', async () => {}) it('failure case - handle errors', async () => {}) it('edge case - malformed config', async () => {}) })
-
Use proper TypeScript types instead of
any// Good function loadConfig<T extends Record<string, unknown>>(options: Config<T>): Promise<T> // Avoid function loadConfig(options: any): Promise<any>
-
Use consistent error handling and logging
// Good console.error('Failed to load config:', error) return options.defaultConfig // Avoid console.log('Error:', e) throw e
-
Follow file naming conventions
config.ts // Core functionality config.test.ts // Test files config.types.ts // Type definitions .{name}.config.ts // Config files -
Use proper exports and imports
// Good export { loadConfig } from './loader' export type { Config } from './types' // Avoid export default { loadConfig, Config, }
-
Maintain consistent directory structure
src/ // Source code ├─ index.ts // Main exports ├─ types.ts // Type definitions ├─ config.ts // Configuration ├─ merge.ts // Deep merge └─ utils/ // Utilities -
Follow ESLint rules and maintain consistent style
// Good - Follow ESLint config const config = { name: 'app', port: 3000, } // Avoid - Inconsistent style const config = { name: 'app', port: 3000 }
**Scope:**All files matching **/*
Purpose: Project Structure specifics
├─ package.json # Package configuration
├─ tsconfig.json # TypeScript configuration
├─ eslint.config.ts # ESLint configuration
├─ bunfig.toml # Bun configuration
├─ README.md # Project documentation
├─ CHANGELOG.md # Version history
└─ LICENSE.md # License information
src/
├─ index.ts # Main entry point
├─ types.ts # Type definitions
├─ config.ts # Configuration loading
├─ merge.ts # Deep merge implementation
├─ utils/ # Utility functions
└─ generated/ # Generated type files
test/
├─ bunfig.test.ts # Main test suite
├─ cli.test.ts # CLI tests
├─ tmp/ # Temporary test files
│ ├─ config/ # Test config files
│ └─ generated/ # Test generated files
└─ fixtures/ # Test fixtures
docs/
├─ intro.md # Introduction guide
├─ usage.md # Usage documentation
├─ api/ # API documentation
├─ .vitepress/ # VitePress configuration
└─ public/ # Static assets
.vscode/ # VS Code configuration
.github/ # GitHub configuration
├─ workflows/ # CI/CD workflows
└─ FUNDING.yml # Funding information
.cursor/ # Cursor IDE configuration
└─ rules/ # Project rules
dist/
├─ index.js # Main bundle
├─ index.d.ts # Type definitions
└─ cli.js # CLI bundle
- Keep related files together
- Use consistent file naming
- Follow module organization patterns
- Maintain clear separation of concerns
- Document directory purposes
- Keep directory structure flat when possible
-
Use consistent indentation (2 spaces)
// Good function loadConfig<T>(options: Config<T>) { if (!options.name) throw new Error('Config name is required') return options.defaultConfig } // Avoid function loadConfig<T>(options: Config<T>) { if (!options.name) throw new Error('Config name is required') return options.defaultConfig }
-
Use concise syntax for simple conditionals
// Good if (!options.name) throw new Error('Config name is required') // Avoid if (!options.name) { throw new Error('Config name is required') }
-
Format function declarations consistently
// Good async function loadConfig<T>( options: Config<T>, context?: Context ): Promise<T> { // Implementation } // Avoid async function loadConfig<T>(options: Config<T>, context?: Context): Promise<T> { // Implementation }
-
Format type definitions clearly
// Good interface Config<T = Record<string, any>> { name: string cwd?: string defaultConfig?: T endpoint?: string } // Avoid interface Config<T = Record<string, any>> { name: string, cwd?: string, defaultConfig?: T, endpoint?: string }
-
Use proper spacing in object literals
// Good const config = { name: 'app', options: { port: 3000, host: 'localhost', }, } // Avoid const config = { name: 'app', options: { port: 3000, host: 'localhost' } }
-
Format imports consistently
// Good import { describe, expect, it } from 'bun:test' // Avoid import { describe, expect, it } from 'bun:test' import { existsSync, readFileSync } from 'node:fs' import { resolve } from 'node:path' -
Use proper JSDoc formatting
// Good /** * Loads configuration from a file * @param options - Configuration options * @returns Resolved configuration */ function loadConfig(options: Config): Promise<unknown> // Avoid /** * Loads configuration from a file * @param options Configuration options * @returns Resolved configuration */ function loadConfig(options: Config): Promise<unknown>
-
Format test cases consistently
// Good describe('loadConfig', () => { it('should load default config', async () => { const result = await loadConfig(options) expect(result).toEqual(expected) }) }) // Avoid describe('loadConfig', () => { it('should load default config', async () => { const result = await loadConfig(options) expect(result).toEqual(expected) }) })
-
Write tests for all public APIs and utilities
describe('loadConfig', () => { it('should load default config when no file exists', async () => { const result = await loadConfig({ name: 'test', defaultConfig: { port: 3000 } }) expect(result).toEqual({ port: 3000 }) }) })
-
Use proper test organization with describe blocks
describe('bunfig', () => { describe('loadConfig', () => { // Config loading tests }) describe('deepMerge', () => { // Merge function tests }) })
-
Test edge cases and error scenarios
it('should handle malformed config files', async () => { const result = await loadConfig({ name: 'invalid', defaultConfig: { fallback: true } }) expect(result).toEqual({ fallback: true }) })
-
Use proper cleanup in tests
beforeEach(() => { // Setup test environment if (existsSync(testConfigDir)) rmSync(testConfigDir, { recursive: true }) mkdirSync(testConfigDir, { recursive: true }) }) afterEach(() => { // Cleanup test files if (existsSync(testConfigDir)) rmSync(testConfigDir, { recursive: true }) })
-
Use Bun's native test modules
import { describe, expect, it, mock } from 'bun:test'
-
Mock external dependencies properly
const mockFetch = mock(() => Promise.resolve({ ok: true, json: () => Promise.resolve({ config: 'value' }) }) ) globalThis.fetch = mockFetch
-
Test both success and failure paths
it('should handle network errors', async () => { mockFetch.mockImplementation(() => Promise.reject(new Error('Network error')) ) // Test error handling })
-
Use interfaces for configuration objects and public APIs
// Good interface Config<T = Record<string, any>> { name: string cwd?: string defaultConfig?: T endpoint?: string } // Avoid interface Config { name: string // ... }
-
Use
as constfor fixed values instead of enums// Good const CONFIG*EXTENSIONS = ['.ts', '.js', '.mjs', '.cjs', '.json'] as const // Avoid enum ConfigExtensions { TS = '.ts', JS = '.js' }
-
Use proper generic constraints for type safety
// Good function loadConfig<T extends Record<string, unknown>>(options: Config<T>): Promise<T> // Avoid function loadConfig<T>(options: Config<T>): Promise<T>
-
Implement strict type checking for config merging
// Good function deepMerge<T extends Record<string, any>>(target: T, source: Partial<T>): T // Avoid function deepMerge(target: any, source: any): any
-
Use type guards for runtime type checking
// Good function isObject(value: unknown): value is Record<string, unknown> { return typeof value === 'object' && value !== null }
-
Export types explicitly for public APIs
// Good export type { Config, ConfigOptions } export interface DeepMergeOptions { // ... }
Scope: General information based on the latest ./README.md content
Purpose: Documentation for the backupx package
A comprehensive TypeScript backup library for databases, files, and directories.
- 🗄️ Multi-Database Support - SQLite, PostgreSQL, and MySQL (via Bun's native drivers)
- 📁 File & Directory Backups - Backup individual files or entire directories with filtering
- 🗜️ Compression Support - Optional gzip compression for all backup types
- 🎯 Advanced Filtering - Include/exclude patterns with glob support for directory backups
- 🔄 Retention Policies - Configure backup retention by count or age
- 📁 Flexible Output - Customizable backup file naming and output directories
- ⚡ Performance - Built with Bun for fast execution
- 🧪 Thoroughly Tested - Comprehensive test suite with 95%+ coverage
- 🔧 TypeScript First - Fully typed APIs for better developer experience
- 📦 CLI & Programmatic - Use as a library or command-line tool
# Using Bun (recommended)
bun add backupximport { createBackup } from 'backupx'
const config = {
verbose: true,
outputPath: './backups',
databases: [
{
type: 'sqlite',
name: 'my-app-db',
path: './data/app.sqlite'
},
{
type: 'postgresql',
name: 'users-db',
connection: 'postgres://user:pass@localhost:5432/myapp'
}
],
files: [
{
name: 'uploads',
path: './public/uploads', // Automatically detected as directory
compress: true,
include: ['*.jpg', '*.png', '*.pdf'],
exclude: ['temp/*']
},
{
name: 'config',
path: './config/app.json', // Automatically detected as file
compress: false
}
],
retention: {
count: 5, // Keep 5 most recent backups
maxAge: 30 // Delete backups older than 30 days
}
}
const summary = await createBackup(config)
console.log(`✅ ${summary.successCount} backups completed`)
console.log(`📊 Database backups: ${summary.databaseBackups.length}`)
console.log(`📁 File backups: ${summary.fileBackups.length}`)# Create backups.config.ts file first
./backups start --verboseThe library includes a comprehensive test suite covering:
- ✅ Type Safety - TypeScript configuration validation
- ✅ SQLite Backups - Schema extraction, data export, special characters
- ✅ File Backups - Individual file backup with compression and metadata preservation
- ✅ Directory Backups - Full directory backup with glob filtering and size limits
- ✅ Backup Manager - Multi-database and file coordination, error handling
- ✅ Retention Policies - Count-based and age-based cleanup for all backup types
- ✅ Error Scenarios - Database connection failures, permission issues, missing files
- ✅ CLI Integration - Command-line interface functionality
# Run all tests
bun test
# Run tests in watch mode
bun run test:watch
# Run with coverage
bun run test:coverage
# Run specific test files
bun test test/sqlite.test.ts
bun test test/backup-manager.test.tstest/
├── types.test.ts # Type definitions and configurations
├── config.test.ts # Default configuration validation
├── sqlite.test.ts # SQLite backup functionality
├── file.test.ts # Individual file backup functionality
├── directory.test.ts # Directory backup with filtering
├── backup-manager.test.ts # Multi-database and file coordination
└── index.test.ts # Integration tests and exports
Create a backups.config.ts file:
import { BackupConfig } from 'backupx'
export const config: BackupConfig = {
verbose: true,
outputPath: './backups',
retention: {
count: 10, // Keep 10 most recent backups
maxAge: 90 // Delete backups older than 90 days
},
databases: [
// SQLite
{
type: 'sqlite',
name: 'app-database',
path: './data/app.sqlite',
compress: true
},
// PostgreSQL
{
type: 'postgresql',
name: 'user-data',
connection: {
hostname: 'localhost',
port: 5432,
database: 'myapp',
username: 'postgres',
password: 'secret'
},
tables: ['users', 'orders'], // Specific tables only
includeSchema: true,
includeData: true
},
// MySQL (when Bun adds support)
{
type: 'mysql',
name: 'analytics',
connection: 'mysql://user:pass@localhost:3306/analytics',
excludeTables: ['temp*logs', 'cache']
}
],
files: [
// Directory backups (automatically detected from path)
{
name: 'app-uploads',
path: './public/uploads',
compress: true,
include: ['*.jpg', '*.png', '*.pdf', '*.doc*'],
exclude: ['temp/*', '*.tmp'],
maxFileSize: 50 * 1024 * 1024, // 50MB max file size
preserveMetadata: true,
followSymlinks: false
},
{
name: 'user-data',
path: './data/users',
compress: false,
exclude: ['*.cache', 'temp/*', '*.log'],
preserveMetadata: true
},
// Individual file backups (automatically detected from path)
{
name: 'app-config',
path: './config/app.json',
compress: false,
preserveMetadata: true
},
{
name: 'env-file',
path: './.env.production',
compress: true,
filename: 'production-env' // Custom filename
}
]
}- Native
bun:sqlitedriver - Complete schema export (tables, indexes, triggers, views)
- Efficient data export with proper escaping
- Binary data support (BLOB fields)
- Native Bun SQL class
- Connection strings or configuration objects
- Schema extraction and data export
- Batch processing for large datasets
- Waiting for Bun's native MySQL driver
- Placeholder implementation ready
- Recursive directory scanning with configurable depth
- Advanced glob pattern filtering (include/exclude)
- File size limits and symbolic link handling
- Metadata preservation (timestamps, permissions)
- Custom archive format with compression support
- Single file backup with original extension preservation
- Optional gzip compression for any file type
- Metadata preservation in separate
.metafiles - Binary and text file support
- Custom filename and output path configuration
Creates backups for all configured databases.
const manager = new BackupManager(config)
const summary = await manager.createBackup()import { backupPostgreSQL, backupSQLite } from 'backupx'
const result = await backupSQLite(sqliteConfig, './output')
const result = await backupPostgreSQL(pgConfig, './output')- Use pickier for linting — never use eslint directly
- Run
bunx --bun pickier .to lint,bunx --bun pickier . --fixto auto-fix - When fixing unused variable warnings, prefer
// eslint-disable-next-linecomments over prefixing with*
- Use stx for templating — never write vanilla JS (
var,document.*,window.*) in stx templates - Use crosswind as the default CSS framework which enables standard Tailwind-like utility classes
- stx
<script>tags should only contain stx-compatible code (signals, composables, directives)
- buddy-bot handles dependency updates — not renovatebot
- better-dx provides shared dev tooling as peer dependencies — do not install its peers (e.g.,
typescript,pickier,bun-plugin-dtsx) separately ifbetter-dxis already inpackage.json - If
better-dxis inpackage.json, ensurebunfig.tomlincludeslinker = "hoisted"