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.
npm install @oxog/statekeeper
yarn add @oxog/statekeeper
pnpm add @oxog/statekeeper
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 }
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>
)
}
StateKeeper supports three different strategies for managing history:
Stores complete state copies. Simple and fast for small to medium state.
const history = createHistory({
initialState: { count: 0 },
strategy: 'snapshot' // or omit (default)
})
Pros:
Cons:
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:
Cons:
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:
Cons:
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
})
history.getState() // Get current state
history.setState(state) // Set state directly (bypasses history)
history.getInitialState() // Get initial state
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 position
history.getHistory() // Get complete history stack
history.getPosition() // Get current position
history.getLength() // Get number of entries
// 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'
history.destroy() // Clean up and destroy instance
history.isDestroyed() // Check if destroyed
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()]
})
Full documentation website coming soon at statekeeper.oxog.dev
Contributions are welcome! Please read our contributing guidelines first.
MIT © 2025 Ersin KOÇ
Made with ❤️ by Ersin KOÇ