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

chrova

v0.2.0

Published

Transactional in-memory objects for JavaScript. Mutate freely; save checkpoints; rollback like a database savepoint.

Readme

chrova

npm version npm downloads zero dependencies types included license MIT

Transactional in-memory objects for TypeScript and JavaScript. Mutate freely, save named checkpoints, roll back like a database savepoint. No Proxy, no immutability requirement, no framework.

  • Wraps any plain object or class instance, returns it with $save / $rollback methods attached
  • Named checkpoints with insertion-ordered eviction (maxCheckpoints)
  • $rollback(name) follows SQL savepoint semantics, dropping the named checkpoint and everything after it
  • $rollback() with no name returns to the original state
  • Preserves prototype chain, instanceof, and class methods
  • Snapshot-based via structuredClone, supports primitives, plain objects, arrays, Date, Map, Set, RegExp
  • Function-valued properties pass through untouched, never restored on rollback
  • Zero runtime dependencies, ships with TypeScript types
  • Tiny surface area, one entry point, five methods
import { tx } from "chrova";

// Wrap any object. The original is never mutated.
const order = tx({ items: [] as Item[], total: 0 });

order.items.push({ id: 1, price: 10 });
order.total += 10;
order.$save("after-first-item");

order.items.push({ id: 2, price: 9999 }); // oops
order.total += 9999;

order.$rollback("after-first-item");
// order is back to { items: [{ id: 1, price: 10 }], total: 10 }

order.$rollback();
// order is back to the original { items: [], total: 0 }

Install

npm install chrova
# or
pnpm add chrova
# or
yarn add chrova
# or
bun add chrova

Both ESM and CommonJS are shipped:

import { tx } from "chrova";          // ESM / TypeScript
const { tx } = require("chrova");     // CommonJS

Requires Node >= 20 (uses the built-in structuredClone).

Why

Most of the time you can mutate an object freely. Sometimes, midway through a multi-step routine, you discover the steps were wrong and you want everything to go back. Without a primitive for this you end up scattering deep clones, dirty flags, and rollback bookkeeping across your code.

A circuit breaker for state. A savepoint for plain objects. That is chrova.

The name is chrono (time) + vault (safekeeping). Like a database savepoint, but for the in-memory objects you already have.

API

tx(original, options?)

Wrap an object so that its property mutations can be rolled back to a saved checkpoint, or to the original state.

The original object is never mutated; chrova operates on an internal deep clone with the prototype of the input preserved.

Options

| Option | Type | Default | Description | | ---------------- | --------------------------- | ----------- | ---------------------------------------------------------------------- | | maxCheckpoints | number (positive integer) | Infinity | Cap on named checkpoints. When exceeded, the oldest is evicted (FIFO). |

Use maxCheckpoints to cap memory automatically when $save runs in a loop, since each checkpoint holds a structuredClone of your data.

Returned object

The returned object has the same shape and prototype as the input, plus five non-enumerable methods:

type Tx<T extends object> = T & {
  $save(name: string): void;
  $rollback(name?: string): void;
  $checkpoints(): string[];
  $plain(): T;
  $clearCheckpoints(): void;
};

$save(name)

Save the current state as a named checkpoint. Saving with an existing name overwrites the previous checkpoint and moves it to the newest position in insertion order. If maxCheckpoints is set and exceeded, the oldest entry is evicted.

$rollback(name?)

Restore state to a saved checkpoint, or to the original state if no name is given.

Rolling back to a named checkpoint follows SQL ROLLBACK TO SAVEPOINT semantics: the named checkpoint and every checkpoint saved after it are discarded. The original state is always reachable via $rollback() with no argument.

Throws Error("Unknown checkpoint: NAME") if the named checkpoint does not exist (including names that were consumed by an earlier rollback).

$checkpoints()

Return the current checkpoint names in insertion order.

$plain()

Return a deep clone of the current data state with no $- methods. Suitable for JSON.stringify, returning to callers, or assertions in tests. Mutating the result does not affect the transaction.

$clearCheckpoints()

Clear all named checkpoints. The original state remains reachable via $rollback() with no argument.

Behavior contract

What gets restored on rollback

Own enumerable properties whose values are:

  • Primitives (string, number, boolean, bigint, null, undefined)
  • Plain objects and arrays (recursively)
  • Date, Map, Set, RegExp instances (cloned via structuredClone)

What passes through untouched

  • Properties whose values are functions
  • Symbol-keyed properties
  • The prototype chain (class methods, getters, setters)
  • Non-enumerable properties

This means class instances keep their methods after rollback, and instanceof keeps working. It also means that if you mutate a function-valued property, that mutation survives rollback.

class User {
  constructor(public name: string) {}
  greet() { return `Hi, ${this.name}`; }    // on prototype, untouched
}

const user = tx(new User("John Doe"));
user.onClick = () => console.log("click");  // own function, NOT restored
user.$save("c");
user.name = "Edited";
user.onClick = () => console.log("new");
user.$rollback("c");

user.name;        // "John Doe" — restored
user.onClick();   // logs "new" — not restored
user.greet();     // "Hi, John Doe" — prototype method, always works

Nested references diverge after rollback

References to nested objects or arrays acquired before $rollback are stale afterwards. Always access nested values through the root transaction object.

const obj = tx({ user: { name: "John Doe", age: 30 } });
const ref = obj.user;
obj.$save("c");
obj.user.age = 99;
obj.$rollback("c");

obj.user.age;       // 30
ref.age;            // 99 (stale)
ref === obj.user;   // false

$save with an existing name

Overwrites the previous checkpoint of that name and moves it to the newest position in insertion order.

const obj = tx({ n: 0 });
obj.$save("a");
obj.$save("b");
obj.$save("a");                // re-save
obj.$checkpoints();            // ["b", "a"]

License

MIT.