@bagaking/history.tsx
v1.1.0
Published
Elegant TypeScript undo/redo management system with branching, time travel, and React integration
Maintainers
Readme
History.tsx
Features ✨
- 🌍 Universal: Works in browser and Node.js environments
- ⚛️ React Ready: Dedicated hooks for seamless React integration
- 🌿 Branching: Auto-branching when inserting mid-history
- 🎯 Time Travel: Jump to any point in history with hash-based navigation
- ⚡ Debounce: Built-in debouncing for rapid changes
- 🏷️ Metadata: Rich metadata support for each history entry
- 🔍 Query: Powerful querying capabilities
- 📡 Events: Real-time event notifications
- 🎛️ Configurable: Flexible configuration options
- 📦 TypeScript: Full TypeScript support with excellent type inference
Quick Start 🚀
Installation
npm install @bagaking/history.tsxTry Demo
npm run demoBasic Usage
import { UniversalHistoryManager } from '@bagaking/history.tsx'
// Create history manager
const history = new UniversalHistoryManager<string>()
// Record states
history.record('Hello')
history.record('Hello World')
history.record('Hello World!')
// Undo/Redo
history.undo() // Back to 'Hello World'
history.redo() // Forward to 'Hello World!'
// Time travel
const entries = history.query(() => true)
history.jumpTo(entries[0].hash) // Jump to first entryReact Integration
import { useHistory, useUndo } from '@bagaking/history.tsx'
function TextEditor() {
const history = useHistory<string>({
initialData: 'Hello World',
debounceDelay: 500
})
return (
<div>
<textarea
value={history.current || ''}
onChange={(e) => history.record(e.target.value)}
/>
<button onClick={history.undo} disabled={!history.canUndo}>
Undo
</button>
<button onClick={history.redo} disabled={!history.canRedo}>
Redo
</button>
</div>
)
}Core Concepts 🧠
History Structure
Main Branch: [A] → [B] → [C] → [D]
↓
Feature Branch: [C'] → [D'] → [E']- unified_data: Your application state as a snapshot
- snapshot: Immutable data record with hash, timestamp, and metadata
- history: Chronological sequence of snapshots
- branches: Parallel timelines created when inserting mid-history
Philosophy: unified_data → snapshot → history → undo/redo
- unified_data: Your app state (any serializable data)
- snapshot: Immutable record with unique hash
- history: Collection of snapshots in branches
- undo/redo: Higher-level operations using time-travel
API Reference 📚
UniversalHistoryManager
Configuration
const history = new UniversalHistoryManager({
maxEntries: 1000, // Maximum entries per branch
debounceDelay: 300, // Debounce delay in ms
enableCompression: true, // Enable data compression
autoCleanup: true // Auto cleanup old entries
})Core Methods
// Record new state
record(data: T, options?: RecordOptions): string
// Navigate history
undo(): HistoryEntry<T> | null
redo(): HistoryEntry<T> | null
jumpTo(hash: string, options?: TimeravelOptions): HistoryEntry<T> | null
jumpToPosition(position: number, branchName?: string): HistoryEntry<T> | null
// Branch management
createBranch(name: string, fromHash?: string): boolean
switchBranch(name: string): boolean
// Query and state
query(predicate: (entry: HistoryEntry<T>) => boolean): readonly HistoryEntry<T>[]
getState(): HistoryState<T>
getCurrent(): HistoryEntry<T> | null
canUndo(): boolean
canRedo(): boolean
// Events
on(listener: HistoryListener<T>): () => voidReact Hooks
useHistory
Full-featured history management:
const {
current, // Current data
state, // Full history state
record, // Record new state
undo, // Undo operation
redo, // Redo operation
jumpTo, // Time travel
createBranch, // Create branch
switchBranch, // Switch branch
canUndo, // Can undo?
canRedo, // Can redo?
query, // Query entries
clear, // Clear history
manager // Raw manager instance
} = useHistory<T>(options)useUndo
Simplified undo/redo:
const {
data, // Current data
record, // Record new state
undo, // Undo
redo, // Redo
canUndo, // Can undo?
canRedo, // Can redo?
clear // Clear history
} = useUndo<T>(options)useHistoryState
Read-only state observer:
const {
current, // Current data
state, // Full state
canUndo, // Can undo?
canRedo, // Can redo?
branches, // Branch names
activeBranch // Active branch
} = useHistoryState(manager)Advanced Features 🔧
Time Travel Modes
// Readonly mode - just move cursor
history.jumpTo(hash, { mode: 'readonly' })
// Branch mode - create new branch from target
history.jumpTo(hash, { mode: 'branch', branchName: 'experiment' })Metadata Support
history.record(data, {
metadata: {
user: 'alice',
action: 'edit_text',
timestamp: Date.now()
}
})Debouncing
// These rapid changes will be debounced
history.record('t')
history.record('te')
history.record('tes')
history.record('test') // Only this will be recordedEvent System
history.on(event => {
console.log(event.type) // 'record' | 'undo' | 'redo' | 'jump' | ...
console.log(event.entry) // History entry
console.log(event.metadata) // Event metadata
})Querying
// Find all entries with specific data
const textEntries = history.query(entry =>
entry.data.text?.includes('hello')
)
// Find entries by time range
const recentEntries = history.query(entry =>
entry.timestamp > Date.now() - 3600000 // Last hour
)
// Find entries by metadata
const userEntries = history.query(entry =>
entry.metadata?.user === 'alice'
)Examples 💡
See the /examples directory for complete examples:
universal-usage.ts- Core API usagereact-usage.tsx- React integration examples
Text Editor
function TextEditor() {
const history = useHistory<string>({
initialData: '',
debounceDelay: 500
})
return (
<div>
<textarea
value={history.current || ''}
onChange={(e) => history.record(e.target.value)}
/>
<div>
<button onClick={history.undo} disabled={!history.canUndo}>
⟲ Undo
</button>
<button onClick={history.redo} disabled={!history.canRedo}>
⟳ Redo
</button>
</div>
</div>
)
}Todo App with Branching
function TodoApp() {
const history = useHistory<TodoState>({
initialData: { todos: [], filter: 'all' }
})
const addTodo = (text: string) => {
const newState = {
...history.current,
todos: [...history.current.todos, { id: Date.now(), text, done: false }]
}
history.record(newState, {
metadata: { action: 'add_todo' }
})
}
const createExperiment = () => {
history.createBranch('experiment')
history.switchBranch('experiment')
}
// ... rest of component
}Performance 🚀
- Immutable snapshots: No accidental mutations
- Efficient cloning: JSON-based deep cloning with optional compression
- Memory management: Auto-cleanup with configurable limits
- Debounced recording: Prevents excessive history entries
- Hash-based navigation: O(1) lookup for time travel
- Event-driven updates: Minimal re-renders in React
Browser Support 🌐
- Modern browsers (ES2020+)
- Node.js 14+
- React 16.8+ (for hooks)
Design Philosophy 🎨
Based on the principles of:
- Immutability: All snapshots are immutable
- Predictability: Deterministic behavior with pure functions
- Flexibility: Support for complex workflows and branching
- Performance: Optimized for real-world usage patterns
- Developer Experience: Intuitive APIs with excellent TypeScript support
- Modular Architecture: High-cohesion, low-coupling design with specialized managers
The design follows the flow: unified_data → snapshot → history → undo/redo, where each step adds structure and capabilities while maintaining simplicity.
v1.1.0+ Architecture: The core system now uses a modular approach with CursorManager for intelligent cursor validation and BranchManager for branch operations, ensuring robust state management with automatic error correction.
Contributing 🤝
We welcome contributions! Please see our Contributing Guide for details.
License 📄
MIT License - see LICENSE file for details.
