npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@wibus/jotai-session-history

v0.1.0

Published

Composable session history manager with undo/redo support for Jotai stores.

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-history

Quick 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 states
  • present - Current state
  • future[] - Array of future states (for redo)
  • canUndo/canRedo - Boolean flags for UI state
  • sessionId - Unique session identifier
  • currentIndex - 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 #5

Multi-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 stack

Intelligent 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✪