@wibus/jotai-session-history
v0.1.0
Published
Composable session history manager with undo/redo support for Jotai stores.
Maintainers
Readme
Jotai Session History
A composable session history manager with undo/redo support for Jotai stores.
Why Not Jotai History?
While Jotai-history utilities provide basic undo/redo functionality, they fall short for complex applications. Here's what makes Jotai Session History a superior choice, we provide full-featured history management with timeline navigation, session isolation, and state inspection.
Features
- 🔄 Advanced Undo/Redo - Not just undo/redo, but jump-to-any-state navigation with complete timeline control
- 🎯 Multi-Session Management - Create, switch, and manage multiple isolated history sessions (something Jotai History completely lacks)
- ⚡ Enhanced Jotai Integration - Built on Jotai's atom architecture but with advanced state management capabilities
- 🔧 Fully Configurable - Customizable history size, intelligent action filtering, debug mode, and lifecycle callbacks
- 📊 Complete Timeline API - Full visibility into past, present, and future states with rich metadata
- 🎛️ Comprehensive Hook API - Rich set of React hooks for every aspect of history management
- 🔍 Developer-Friendly - Built-in debugging, state inspection, and timeline visualization tools
- 🪶 Production Ready - Lightweight with tree-shaking support, but enterprise-grade functionality
Installation
npm install @wibus/jotai-session-historyQuick Start
import { createSessionHistory } from '@wibus/jotai-session-history'
import { atom, useAtom } from 'jotai'
// Create your state atom
const countAtom = atom(0)
// Create session history manager
const sessionHistory = createSessionHistory<number>()
function Counter() {
const [count, setCount] = useAtom(countAtom)
const [, initializeSession] = useAtom(sessionHistory.initializeSessionAtom)
const [, pushToHistory] = useAtom(sessionHistory.pushToHistoryActionAtom)
const [, undo] = useAtom(sessionHistory.undoActionAtom)
const [, redo] = useAtom(sessionHistory.redoActionAtom)
const history = sessionHistory.useSessionHistoryValue()
// Initialize session on mount
React.useEffect(() => {
initializeSession({ initialState: count })
}, [])
const handleIncrement = () => {
const newValue = count + 1
setCount(newValue)
pushToHistory({ state: newValue, actionType: 'INCREMENT' })
}
return (
<div>
<p>Count: {count}</p>
<button onClick={handleIncrement}>+</button>
<button onClick={undo} disabled={!history?.canUndo}>Undo</button>
<button onClick={redo} disabled={!history?.canRedo}>Redo</button>
</div>
)
}Core Concepts
Session History State
Each session maintains its own history state with:
past[]- Array of previous statespresent- Current statefuture[]- Array of future states (for redo)canUndo/canRedo- Boolean flags for UI statesessionId- Unique session identifiercurrentIndex- Current position in timeline
Actions
- Initialize - Create a new session with initial state
- Push - Add new state to history
- Undo - Move to previous state
- Redo - Move to next state
- Reset - Clear history, keep current state
- Jump - Navigate to specific timeline index
API Reference
createSessionHistory(options?)
Creates a new session history manager.
Options
interface CreateSessionHistoryOptions<TState, TAction> {
store?: Store // Custom Jotai store
config?: {
maxHistorySize?: number // Default: 50
debugMode?: boolean // Default: false
shouldRecord?: (action: TAction) => boolean // Default: () => true
}
sessionIdGenerator?: (input: { baseId?: string, proposedId?: string }) => string
logger?: (message: string, context?: Record<string, unknown>) => void
onStateChange?: HistoryChangeHandler<TState, TAction>
onSessionEnd?: SessionEndHandler<TState>
}Returns
{
// State atoms
sessionHistoryAtom: PrimitiveAtom<SessionHistoryState<TState> | null>
sessionMetaAtom: PrimitiveAtom<SessionMetaState>
sessionHistoryConfigAtom: PrimitiveAtom<SessionHistoryConfig<TAction>>
// Action atoms
initializeSessionAtom: WriteOnlyAtom<InitializeSessionParams<TState, TAction>>
pushToHistoryActionAtom: WriteOnlyAtom<PushToHistoryParams<TState, TAction>>
undoActionAtom: WriteOnlyAtom<void>
redoActionAtom: WriteOnlyAtom<void>
resetActionAtom: WriteOnlyAtom<void>
jumpToStateActionAtom: WriteOnlyAtom<JumpToStateParams>
// React hooks
useSessionHistory: () => [SessionHistoryState<TState> | null, (value: SessionHistoryState<TState> | null) => void]
useSessionHistoryValue: () => SessionHistoryState<TState> | null
useCompleteTimeline: () => TimelineItem<TState>[]
// Direct access methods
getSessionHistory: () => SessionHistoryState<TState> | null
setSessionHistory: (value: SessionHistoryState<TState> | null) => void
}Advanced Usage
Timeline Management & Navigation
Unlike Jotai History's limited undo/redo, you get full timeline control:
const timeline = sessionHistory.useCompleteTimeline()
const [history] = sessionHistory.useSessionHistory()
// Navigate to any point in history
const [, jumpToState] = useAtom(sessionHistory.jumpToStateActionAtom)
// Rich timeline information
console.log('Total states:', timeline.length)
console.log('Current position:', history?.currentIndex)
console.log('Can go back:', history?.canUndo)
console.log('Can go forward:', history?.canRedo)
// Jump to specific state
jumpToState({ index: 5 }) // Go directly to state #5Multi-Session Management
Manage separate history contexts - impossible with Jotai History:
const sessionHistory = createSessionHistory()
// Editor session
const editorSession = {
baseId: 'editor',
initialState: { content: '', cursor: 0 }
}
// Settings session
const settingsSession = {
baseId: 'settings',
initialState: { theme: 'light', language: 'en' }
}
// Switch between contexts
const [, initSession] = useAtom(sessionHistory.initializeSessionAtom)
// Each session maintains its own independent history
initSession(editorSession) // Editor has its own undo/redo stack
initSession(settingsSession) // Settings has separate undo/redo stackIntelligent Action Filtering
Control what gets recorded with smart filtering:
const sessionHistory = createSessionHistory({
config: {
shouldRecord: (actionType) => {
// Don't record cursor movements or temporary states
const skipActions = ['CURSOR_MOVE', 'HOVER', 'TEMP_UPDATE']
return !skipActions.includes(actionType)
}
}
})Real-time State Monitoring
Get insights that Jotai History can't provide:
const sessionHistory = createSessionHistory({
onStateChange: (event, { get, set }) => {
console.log(`History ${event.type}:`, {
sessionId: event.history.sessionId,
stateCount: event.history.past.length + event.history.future.length + 1,
currentIndex: event.history.currentIndex,
actionType: event.actionType
})
},
onSessionEnd: (finalState, sessionMeta) => {
console.log('Session ended:', {
duration: Date.now() - sessionMeta.createdAt,
totalActions: sessionMeta.actionCount
})
}
})Building Timeline UI Components
Create rich history visualizations:
function HistoryTimeline() {
const timeline = sessionHistory.useCompleteTimeline()
const [, jumpToState] = useAtom(sessionHistory.jumpToStateActionAtom)
return (
<div className="timeline">
{timeline.map((item, index) => (
<div
key={index}
className={`timeline-item ${item.isCurrentState ? 'active' : ''}`}
onClick={() => jumpToState({ index: item.index })}
>
<span className="state-index">{item.index}</span>
<span className="action-type">{item.actionType || 'Initial'}</span>
<span className="timestamp">{item.timestamp?.toLocaleTimeString()}</span>
</div>
))}
</div>
)
}Production-Ready History Management
const sessionHistory = createSessionHistory({
config: {
maxHistorySize: 100,
debugMode: process.env.NODE_ENV === 'development',
shouldRecord: (action) => action !== 'TRANSIENT_UI_STATE'
},
logger: (message, context) => {
if (process.env.NODE_ENV === 'development') {
console.log(`[SessionHistory] ${message}`, context)
}
},
sessionIdGenerator: ({ baseId, proposedId }) => {
return `${baseId}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
}
})Comparison with Jotai History
| Feature | Jotai History | @wibus/jotai-session-history | |---------|---------------|------------------------| | Basic Undo/Redo | ✅ | ✅ | | Timeline Navigation | ❌ | ✅ Jump to any state | | Session Management | ⚠️ | ✅ Multiple isolated sessions | | State Inspection | ❌ | ✅ Full past/present/future visibility | | History Configuration | ⚠️ | ✅ Size limits, filtering, debug mode | | Action Metadata | ❌ | ✅ Action types, timestamps, context | | Timeline UI Support | ❌ | ✅ Complete timeline API | | Lifecycle Hooks | ❌ | ✅ State change & session end callbacks | | Developer Tools | ⚠️ | ✅ Logging, debugging, state monitoring | | Custom Filtering | ❌ | ✅ Smart action filtering |
In summary: Jotai History gives you basic undo/redo. Session History gives you a complete time-travel debugging and state management solution.
TypeScript Support
The library is written in TypeScript and provides full type safety:
interface MyState {
count: number
name: string
}
type MyActions = 'INCREMENT' | 'DECREMENT' | 'RENAME'
const sessionHistory = createSessionHistory<MyState, MyActions>({
config: {
shouldRecord: (action: MyActions) => action !== 'RENAME'
}
})Author
Jotai Session History © Wibus, Released under MIT. Created on Sep 23, 2025
Personal Website · Blog · GitHub @wibus-wee · Telegram @wibus✪
