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

@matrajs/versions

v1.1.1

Published

Version history for Matra — snapshots, a real diff between them, and restore as one undo step.

Readme

@matrajs/versions

Version history for Matra: snapshots, a real diff between them, and restore as one undo step.

Part of the paid tier. The core stays MIT.

npm i @matrajs/versions
import { createEditor, starterKit } from '@matrajs/core'
import { versions, versionDiffCSS } from '@matrajs/versions'

const editor = createEditor({
  extensions: [
    ...starterKit,
    versions({
      // A snapshot when the document has been still for half a minute.
      idleMs: 30_000,
      keep: 50,
      onChange: (state) => render(state.versions, state.diff),
    }),
  ],
})

editor.commands.snapshotVersion('Before the rewrite')
editor.commands.previewVersion(id) // draw the diff over the document
editor.commands.restoreVersion(id) // one transaction, one undo
editor.commands.forgetVersion(id)

What is actually hard here

Keeping copies of a document is a one-liner. Answering what changed in the shape a person reads it is not.

diffDocs pairs blocks on their type and text first, so a paragraph that moved is the same paragraph rather than a deletion and an insertion at two unrelated places. A removal immediately followed by an addition is read as one block rewritten, and only then does it go word by word inside it. The result is "this paragraph is new, that one lost a sentence" rather than a wall of red and green.

It is guarded, too. The common prefix and suffix are trimmed before any table is built, which reduces almost every real edit to a handful of blocks; a document whose thousand blocks were all shuffled falls back to pairing by position, which is worse than optimal, still correct, and does not lock the tab.

import { diffDocs } from '@matrajs/versions'

const diff = diffDocs(before, after)
diff.added // 1
diff.blocks // [{ kind: 'same', … }, { kind: 'changed', words: [...] }, …]

The diff is exported on its own. It has no opinion about editors and works on any two Matra documents.

Previewing

previewVersion(id) puts the editor into a comparison against that version and recomputes the diff on every change, so the highlighting shrinks as you fix things. Blocks that are still in the document get node decorations; blocks that were deleted since the snapshot are not in the document to draw on, so they stay in state.diff.blocks for the application to list beside the editor — where the layout decision belongs.

versionDiffCSS is a starting stylesheet for the three classes.

Where the versions live

Without a store they live until the tab closes, which is undo with labels rather than history. store is a load and a save:

import { versions, localVersionStore } from '@matrajs/versions'

versions({ store: localVersionStore(`matra:${documentId}`) })

localVersionStore is there for the case that needs no server. For anything real, pass your own — the shape is two functions, and what is behind them is your database:

versions({
  store: {
    load: () => cache.get(documentId) ?? null,
    save: (list) => { cache.set(documentId, list); void fetch(`/docs/${documentId}/versions`, {
      method: 'PUT', body: JSON.stringify(list),
    }) },
  },
})

load is read once when the editor is created, so it is synchronous — hydrate your cache before you mount, or pass the list you already have. save is called only when the list changes, not on every keystroke, and a store that throws is logged rather than allowed to take the transaction down with it.

Restoring

restoreVersion replaces the document in one transaction and snapshots where you were on the way past, labelled Before restore. It also calls ctx.isolateUndo(), so it is its own undo step rather than being merged into whatever sentence was being typed a second earlier.

Licence

Commercial. See LICENSE.