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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@oxog/statekeeper

v1.0.0

Published

Zero-dependency undo/redo history manager with micro-kernel plugin architecture

Readme

StateKeeper

Zero-dependency undo/redo history manager with micro-kernel plugin architecture

npm version License: MIT TypeScript

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/statekeeper
yarn add @oxog/statekeeper
pnpm 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 state

History 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 position

History Access

history.getHistory()     // Get complete history stack
history.getPosition()    // Get current position
history.getLength()      // Get number of entries

Events

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


Made with ❤️ by Ersin KOÇ