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

@ledgex/core

v0.5.0

Published

Key-value state management with efficient undo/redo

Readme

Ledgex

"Smart, memory-efficient state management for JS apps with built-in undo/redo. Inspired by Git and Photoshop"

React Ledgex Logo

npm License: MIT

@ledgex/react npm

Table of Contents

Features

  • Simple Global Key-Value Store A lightweight, globally shared state container with full type safety (when used with TypeScript).

  • Built-in Time Travel Native undo / redo support with minimal runtime and memory overhead.

  • Layered State Management Organize and isolate state changes using layers—similar to Photoshop layers—making complex state flows easier to manage.

  • Smart & Efficient History Storage

    • Only changed properties are recorded—never entire objects
    • Repeated updates that don’t alter state are automatically ignored
    • Large objects (30+ properties) remain memory-efficient because unchanged values reference existing state
  • No Redundant History Entries Only meaningful changes create history records. Setting the same value twice produces no extra entries.

  • Tiny Footprint Under 8KB gzipped, with no unnecessary dependencies.

  • Framework-Agnostic Designed for plain JavaScript and works equally well in any environment (React, Vue, Svelte, or no framework at all).


Installation

npm install @ledgex/core

Ledgex

Basic Usage

1. Create a Ledgex instance

import { Ledgex } from '@ledgex/core';

// Create a global or scoped instance
const store = new Ledgex({
  bufferSize: 100 // optional, default is unlimited
});

You can create:

  • one global instance for the entire app
  • or multiple instances for isolated state domains (e.g. editor, history, settings)

2. Use anywhere

const layer = store.get('background');

store.set('background', { color: '#202020' });

store.undo();
store.redo();

Real-World Example: Photo Editor Layer Backups (Undo / Redo)

This example shows how Ledgex can be used to efficiently back up complex UI state in applications like photo editors, diagram tools, or design software.

Concept

  • Use your own live state for immediate rendering and user feedback
  • Use Ledgex only for history snapshots
  • Apply throttling to avoid recording excessive intermediate states

Ledgex becomes a history engine, not your rendering bottleneck.


Example Code

import throttle from 'lodash/throttle';
import { Ledgex } from '@ledgex/core';
import { layers, setLayers } from './layersStore'; // your live state

const ledgex = new Ledgex({ bufferSize: 200 });

// Throttle backups to once every 300ms
const throttledSet = throttle(ledgex.set.bind(ledgex), 300);

function handlePropsChange(layerId, newProps) {
  setLayers(prevLayers =>
    prevLayers.map(layer => {
      if (layer.id === layerId) {
        const updatedLayer = layer.clone();
        updatedLayer.updateProps(newProps);

        // Backup only meaningful state changes
        throttledSet(layerId, updatedLayer.toObject());

        return updatedLayer;
      }
      return layer;
    })
  );
}

Why This Works So Well

  • Efficient Memory Usage Only meaningful diffs are stored — no full snapshots during rapid updates.

  • Smooth User Experience Live updates remain instant, while undo/redo history stays compact.

  • Throttle-Friendly Dragging, sliders, and key presses don’t flood history with noise.


Advanced Features

Batch Updates (Atomic State Changes)

Ledgex allows multiple updates to be grouped into one undo/redo step.

This is ideal when several changes should be treated as one user action.

ledgex.set({
  layer1: { x: 100, y: 200 },
  layer2: { x: 100, y: 200 },
  layer3: { x: 100, y: 200 }
}); // Single undo/redo entry

Use cases:

  • Aligning multiple layers
  • Applying a preset
  • Group transformations

Deeply Nested Object Support

Ledgex efficiently handles nested updates, storing only the deepest changes.

ledgex.set({
  layer1: {
    filters: {
      brightness: 1.2,
      contrast: 0.8
    }
  }
});

Why it matters:

  • Safe for complex structured data
  • No redundant history entries
  • No memory blow-up with deep objects

Efficient History Management

Ledgex gives you full control over history size using a configurable buffer.

const ledgex = new Ledgex({
  bufferSize: 100 // keep last 100 meaningful changes
});

How it works

  • Limits the number of undo/redo steps kept in memory
  • Oldest entries are automatically discarded
  • Works together with diff-based storage

Why This Matters

  • Memory Safety → history never grows unbounded
  • Performance → undo/redo remains fast
  • Flexibility → tune buffer size per app or feature

Ledgex API Reference

The Ledgex class is a time-travel–enabled, layered state manager with efficient history tracking, undo/redo support, and subscription-based updates.

It is framework-agnostic and designed for applications that require precise state history, such as editors, design tools, and complex UIs.


Import

import { Ledgex } from '@ledgex/core';

Constructor

new Ledgex(options?)

Parameters

| Name | Type | Default | Description | | ------------------------- | -------- | ------- | -------------------------------------------------- | | options | Object | {} | Optional configuration | | options.bufferSize | number | 100 | Maximum number of meaningful history steps to keep | | options.toleranceWindow | number | 20 | Time window for collapsing intermediate updates |

Example

const ledger = new Ledgex({
  bufferSize: 200,
  toleranceWindow: 50
});

Core Concepts

Layers

  • Each layer is an independent key-value state container.
  • Layers can be activated, deactivated, and updated independently.
  • History is tracked per layer, but undo/redo operates globally.

Time Travel

  • Every meaningful change advances time.
  • Undo and redo move backward or forward through meaningful states only.
  • Repeated updates that do not change state are ignored.

Methods

set(updates)

Applies state updates at the current time.

  • Automatically ignores non-meaningful changes
  • Supports batch updates
  • Creates a single undo/redo step
ledger.set({
  background: { color: '#202020' },
  layer1: { x: 100, y: 200 }
});

Parameters

| Name | Type | Description | | --------- | ------------------------ | -------------------------------- | | updates | Object<string, Object> | Map of layerId → partial state |

Notes

  • Nested objects are merged deeply.
  • Only changed properties are recorded.
  • If nothing meaningful changes, no history entry is created.

get(layerIds?)

Returns the current state of one or more layers.

ledger.get(); // all active layers
ledger.get(['background', 'layer1']);

Parameters

| Name | Type | Description | | ---------- | --------------------- | ----------------------- | | layerIds | string[] (optional) | Specific layers to read |

Returns

Object<string, Object>

Only active layers are included.


undo()

Moves to the previous meaningful state.

ledger.undo();

Returns

Object<string, Object> | undefined

The current state after undo, or undefined if undo is not possible.


redo()

Moves to the next meaningful state.

ledger.redo();

Returns

Object<string, Object> | undefined

The current state after redo, or undefined if redo is not possible.


remove(layerId)

Deactivates a layer at the current time.

ledger.remove('background');

Parameters

| Name | Type | Description | | --------- | -------- | ------------------- | | layerId | string | Layer to deactivate |

Notes

  • Deactivation is recorded in history
  • Undo will restore the layer

prune(minTime)

Removes history entries older than minTime.

ledger.prune(50);

Parameters

| Name | Type | Description | | --------- | -------- | ----------------------- | | minTime | number | Earliest time to retain |

Notes

  • Layers with no remaining state are removed
  • Useful for manual memory management

flush()

Automatically prunes history based on bufferSize.

ledger.flush();

Behavior

  • Keeps only the last bufferSize meaningful steps
  • Updates internal flush time
  • Invoked automatically when history grows too large

subscribe(callback)

Subscribes to state changes.

const unsubscribe = ledger.subscribe(() => {
  console.log('State changed:', ledger.get());
});

Parameters

| Name | Type | Description | | ---------- | ---------- | ----------------------------- | | callback | Function | Called after any state change |

Returns

() => void

Unsubscribe function.

Notes

  • Callbacks are triggered asynchronously
  • Safe to read state inside callback

Automatic Behavior

Auto-Flushing

Ledgex automatically flushes history when:

currentTime - lastFlushTime > bufferSize + toleranceWindow

This prevents unbounded memory growth during rapid updates.


Usage Example

const ledger = new Ledgex({ bufferSize: 100 });

ledger.set({
  layer1: { x: 10, y: 20 }
});

ledger.set({
  layer1: { x: 15 }
});

ledger.undo(); // reverts x to 10
ledger.redo(); // reapplies x = 15

Guarantees

  • Only meaningful changes are stored
  • Undo/redo is deterministic
  • Memory usage scales with change size, not object size
  • No duplicate or empty history entries

Intended Use Cases

  • Photo / video editors
  • Diagram & design tools
  • Complex form editors
  • Any application requiring efficient undo/redo

Support

Star the repo if you find it useful! 🐞 Report issues on GitHub