@oxog/statekeeper
v1.0.0
Published
Zero-dependency undo/redo history manager with micro-kernel plugin architecture
Maintainers
Readme
StateKeeper
Zero-dependency undo/redo history manager with micro-kernel plugin architecture
StateKeeper is a powerful and flexible history management library that provides undo/redo functionality for any JavaScript application. Built on a micro-kernel architecture with zero runtime dependencies.
🎯 Features
- Zero Dependencies - No external runtime dependencies
- Multiple Strategies - Choose between Snapshot, Command, or Patch strategies
- Micro-Kernel Architecture - Extend with plugins
- Framework Agnostic - Works with vanilla JS, React, Vue, Svelte, or any framework
- Full TypeScript Support - Written in TypeScript with strict mode
- Branching Support - Git-like branching for alternative timelines (via plugin)
- Persistence - Save/restore history to any storage (via plugin)
- Time Travel Debugging - Visual timeline UI (via plugin)
- Tree-Shakeable - Only bundle what you use
📦 Installation
npm install @oxog/statekeeperyarn add @oxog/statekeeperpnpm add @oxog/statekeeper🚀 Quick Start
Basic Usage
import { createHistory } from '@oxog/statekeeper'
// Create a history manager
const history = createHistory({
initialState: { count: 0 },
limit: 100, // Maximum 100 history entries
})
// Make changes
history.push({ count: 1 })
history.push({ count: 2 })
history.push({ count: 3 })
console.log(history.getState()) // { count: 3 }
// Undo
history.undo()
console.log(history.getState()) // { count: 2 }
// Redo
history.redo()
console.log(history.getState()) // { count: 3 }React Integration
import { createHistory } from '@oxog/statekeeper'
import { useState, useEffect } from 'react'
function Counter() {
const [history] = useState(() =>
createHistory({ initialState: { count: 0 } })
)
const [state, setState] = useState(history.getState())
useEffect(() => {
return history.on('state-change', (event) => {
setState(event.state)
})
}, [history])
const increment = () => {
history.push({ count: state.count + 1 })
}
return (
<div>
<h1>{state.count}</h1>
<button onClick={increment}>Increment</button>
<button onClick={() => history.undo()} disabled={!history.canUndo()}>
Undo
</button>
<button onClick={() => history.redo()} disabled={!history.canRedo()}>
Redo
</button>
</div>
)
}📖 Strategies
StateKeeper supports three different strategies for managing history:
Snapshot Strategy (Default)
Stores complete state copies. Simple and fast for small to medium state.
const history = createHistory({
initialState: { count: 0 },
strategy: 'snapshot' // or omit (default)
})Pros:
- Simple to understand
- Fast undo/redo
- Works with any state structure
Cons:
- Higher memory usage for large states
Command Strategy
Stores executable commands with undo/redo logic. Most memory efficient.
import { createHistory, defineCommand } from '@oxog/statekeeper'
// Define commands
const incrementCommand = defineCommand({
name: 'increment',
execute: (state, amount: number) => ({
...state,
count: state.count + amount
}),
undo: (state, amount: number) => ({
...state,
count: state.count - amount
})
})
const history = createHistory({
initialState: { count: 0 },
strategy: 'command'
})
const commandStrategy = history.getStrategy()
commandStrategy.registerCommand(incrementCommand)
// Execute command
const newState = commandStrategy.execute(incrementCommand, 5, history.getState())
history.push(newState)Pros:
- Memory efficient
- Explicit intent
- Easy to debug
Cons:
- Requires command definitions
- More setup code
Patch Strategy
Stores diffs using RFC 6902 JSON Patch. Efficient for large states with small changes.
const history = createHistory({
initialState: { count: 0, name: 'John', items: [] },
strategy: 'patch'
})Pros:
- Memory efficient for large states
- Automatic diff calculation
- Small payload size
Cons:
- Slower than snapshot for small states
- Complex for debugging
🎨 API Reference
Core API
createHistory(options)
Creates a history manager instance.
const history = createHistory({
initialState: T, // Required: Initial state
strategy?: StrategyName, // Optional: 'snapshot' | 'command' | 'patch'
limit?: number, // Optional: Max entries (default: 100)
plugins?: Plugin[] // Optional: Plugins to register
})State Management
history.getState() // Get current state
history.setState(state) // Set state directly (bypasses history)
history.getInitialState() // Get initial stateHistory Operations
history.push(state, metadata?) // Push new state
history.undo() // Undo to previous state
history.redo() // Redo to next state
history.canUndo() // Check if undo is possible
history.canRedo() // Check if redo is possible
history.clear() // Clear all history
history.goTo(position) // Jump to specific positionHistory Access
history.getHistory() // Get complete history stack
history.getPosition() // Get current position
history.getLength() // Get number of entriesEvents
// Subscribe to events
const unsubscribe = history.on('state-change', (event) => {
console.log('State changed:', event.state)
})
// Unsubscribe
unsubscribe()
// Available events:
// - 'push'
// - 'undo'
// - 'redo'
// - 'state-change'
// - 'clear'
// - 'destroy'Lifecycle
history.destroy() // Clean up and destroy instance
history.isDestroyed() // Check if destroyed🔌 Plugins
StateKeeper can be extended with plugins. Full plugin system with branching, persistence, keyboard shortcuts, compression, and time-travel UI will be available soon.
import { createHistory } from '@oxog/statekeeper'
// Plugins coming soon:
// import { branching, persistence, keyboardShortcuts } from '@oxog/statekeeper/plugins'
const history = createHistory({
initialState: { count: 0 },
// plugins: [branching(), persistence({ key: 'my-app' }), keyboardShortcuts()]
})📚 Documentation
Full documentation website coming soon at statekeeper.oxog.dev
🤝 Contributing
Contributions are welcome! Please read our contributing guidelines first.
📄 License
MIT © 2025 Ersin KOÇ
🔗 Links
- GitHub Repository
- NPM Package
- Documentation (Coming Soon)
- Issue Tracker
Made with ❤️ by Ersin KOÇ
