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

@domternal-pro/extension-version-history

v1.0.0

Published

Document version history for Domternal: automatic and named versions, preview, author-attributed diff and safe restore on a pluggable version store

Readme

@domternal-pro/extension-version-history

Document version history for Domternal: automatic and named versions, read-only preview, author-attributed diff and safe restore. Versions live in a pluggable VersionStore; the bundled YjsVersionStore keeps them in a sibling Y.Doc on the server you already run.

Links

Documentation    •    Live examples

Licensing

This is a commercial package distributed through the public npm registry. A DOM-connected editor surface remains fully featured without a key for internal evaluation and shows a small evaluation badge. Guarded headless, standalone, server and worker operations have no badge surface, so they require an explicit evaluation mode or production mode with a covered DMP2 Commercial Key. Evaluation does not permit production use.

Keyless headless evaluation is explicit and remains fully functional:

import { configureProLicense } from '@domternal-pro/core';

configureProLicense({ mode: 'evaluation' });

Production uses the signed Commercial Key from the Order confirmation:

import { configureProLicense } from '@domternal-pro/core';

configureProLicense({
  mode: 'production',
  key: 'DMP2...',
});

A Commercial DMP2 Key carries exactly one signed Product Line. Every registered commercial package in an ordinary release must carry that same Product Line. A security-only release must carry it in both package metadata and its separate DMS1 certificate. Missing, conflicting or mismatched Product Line metadata remains uncovered and does not activate production. DMS1 never changes the key or grants another Product Line. Evaluation, Internal and legacy DMP1 keys do not use this Commercial Product Line field.

The signed key payload is not encrypted and may be readable in delivered browser code. Permitted embedding is expected, but the raw key must remain within the Customer scope allowed by the license. DMP2 omits Customer identity, contact details, Order reference, plan, price, separate payment and paid-through fields, and Developer limits. Commercial entitlement and per-key identifiers are random and opaque; the public status API does not expose them.

License validation runs entirely offline and never contacts Domternal: there is no online activation, license telemetry, metering, seat-counting or revocation request. A DMP2 Evaluation Key can remove visible evaluation notices only in explicitly selected evaluation mode and only through its signed UTC expiry day; it can never activate production mode. In production mode it remains visibly badged or marked. A DMP1 key is recognized only for legacy diagnostics: it leaves editor badges and marked-output notices in place and never authorizes guarded production use. A DMP2 Commercial Key does not activate before its signed not-before day. For ordinary and renewal keys that day is the Subscription start; an exceptional surviving-rights or remedy key can use the later decision or issuance day required by the license. During its paid Subscription and 14-day grace period, ordinary package coverage stops at its signed ordinaryReleaseThrough day; grace does not extend that boundary. After grace, a key without signed technical post-grace eligibility reports subscription-expired. A technically eligible key can continue coverage through ordinaryReleaseThrough, qualifying DMS1 releases and exact Section 10 remedy days, while the public status exposes a compatible derived fallbackThrough equal to ordinaryReleaseThrough. That technical marker does not prove 12 paid months or define the surviving legal scope: the Agreement and accepted Order define the legal scope, while the key and minimum durable entitlement state support later recognition of that scope, including whether it covers Qualified Builds, discontinuation final retained builds or another express surviving right. Post-expiry use is runtime-only for the exact permitted artifacts. Development, modification, rebuilding and new Applications require an active Subscription, subject only to the Agreement's narrow security, intellectual-property and replacement-key exceptions. A key-only reproduction changes no other code, dependency or feature and inherits the original build status. The contractual post-expiry Developer limit remains as a final derived value in the minimum durable entitlement state while it is relevant; supporting Order, payment and calculation records follow their separate retention periods; the runtime does not count people or expose that limit. Rights from different expiries remain attached to their own Commercial Keys. Coverage therefore uses both baked package release dates and the current UTC day reported by the runtime. Guarded headless production activates only when that Commercial Key covers every registered Pro package. Each JavaScript context that runs guarded headless work needs its own configuration. Activation is technical, not the license itself: permitted use is defined by LICENSE.md. See Installation and licensing for the complete setup.

Security-only releases use a separate signed DMS1 Security Release Certificate embedded in the package set; DMS1 does not add fields to or modify the Customer's DMP2 key. During the paid Subscription and grace, DMS1 requires a signed feature-baseline Release Date on or before ordinaryReleaseThrough; after grace, the key must also have technical post-grace eligibility. An exact Section 10 replacement date does not establish DMS1 baseline eligibility. A key without post-grace eligibility is never revived after grace. DMS1 does not renew a Subscription, move an ordinary-release boundary, change a Developer limit, unlock features or grant another Product Line. Post-expiry use is limited to the narrow security replacement of an eligible artifact, with no unrelated code, dependency or feature change, and the result inherits that artifact's status. The fixed Product Line Security Support End Date ends the obligation to issue further Security Updates, subject to mandatory law. An eligible DMS1 update signed and released during that period may remain usable afterward only within the same existing or surviving production right and limits; continued use does not make the Product Line supported or create, restore, renew or expand a right. The controlling terms are in Section 8 of LICENSE.md and the public security policy.

The bundled YjsVersionStore guards creation and rename at their final mutation boundaries. Built-in manual and automatic capture, rename and restore flows mint an opaque, one-use VersionStoreWriteContext for one operation from the exact initiating Editor and configured outer store. The final writer consumes that context with assertVersionHistoryAuthoringAllowed. Consumption rechecks the live exact Editor/store registration and its current DOM connection, so a detached editor cannot borrow the badge surface of a connected sibling that happens to share the store. Within supported integrations, a context forged through ordinary object construction, copied, serialized, replayed, used for the wrong operation, made stale or left unregistered is classified as explicit headless use and never falls back to a connected sibling.

A custom VersionStore is application code and direct calls to it cannot be intercepted by this package. Every custom creation or rename mutator must re-read its current record and authorization after all awaits, then pass the optional context to assertVersionHistoryAuthoringAllowed(store, operation, writeContext) as the last synchronous check before its first durable, remote or shared-memory side effect. A custom restore flow must consume a restore-version context immediately before it first changes the live document or writes restore data. Do not await, invoke application callbacks or yield between that assertion and the write. Snapshot and metadata reads, preview, comparison, deletion, automatic pruning, teardown, recovery and data-exit operations stay available without a production assertion and must not call the authoring guard.

An explicitly supplied detached editor element is a pending framework-wrapper mount. Version History can initialize its read-only attribution object during that interval, but it defers the durable Y.PermanentUserData author mapping until the editor connects or detached use is allowed by explicit evaluation mode or a covered Commercial Key. Attaching later writes the mapping once. Detaching does not delete an existing mapping because historical changes still depend on it, while all new detached authoring remains guarded at its own operation boundary.

import {
  assertVersionHistoryAuthoringAllowed,
  createVersionHistoryEditorWriteContext,
  delegateVersionHistoryEditorWriteContext,
  type VersionStoreWriteContext,
} from '@domternal-pro/extension-version-history';

// Inside your VersionStore.createVersion implementation:
async createVersion(options, writeContext?: VersionStoreWriteContext) {
  await this.ready();
  // Read current records and permissions after every connection wait.
  const request = validateAndAuthorizeVersion(options);
  assertVersionHistoryAuthoringAllowed(this, 'create-version', writeContext);
  return api.createVersion(request);
}

// From custom UI bound to one exact editor:
const writeContext = createVersionHistoryEditorWriteContext(
  editor,
  store,
  'create-version',
);
await store.createVersion(options, writeContext);

Create a fresh context for each call and pass it unchanged when the configured store performs the final write. It is an opaque bearer for one operation and one current target store, not a serializable credential: object spread, JSON cloning, structured cloning, replay, use against another store and reuse for another operation all invalidate it.

A transparent wrapper or decorator must explicitly transfer the context at each wrapper boundary. Delegation consumes the source context immediately and returns a new one-use context for the named inner writer:

const innerContext = delegateVersionHistoryEditorWriteContext(
  writeContext,
  this,
  innerStore,
);
return innerStore.createVersion(options, innerContext);

Nested wrappers repeat that step. The final guard checks that the store consuming the context is exactly its current target. A wrong source, a skipped delegation, the original context after delegation, or a replay of the delegated context fails as explicit headless use and never falls back to a connected sibling. The context does not replace your store's own authentication, authorization or input validation. Direct calls without a context use aggregate policy for the exact store passed to the helper: they are editor-surface use while at least one registered editor for that store remains connected, and headless otherwise. TypeScript exposes the context parameter but cannot prove that arbitrary custom code consumes it, so the exact-editor guarantee applies to YjsVersionStore and conforming custom stores. A shared store remains attached until its last document-attached editor leaves only for direct store API classification. Application code that controls the same JavaScript realm can inspect or mutate process-wide runtime state, deliberately delegate a bearer, or patch distributed JavaScript, so this context is a fail-closed integration boundary rather than cryptographic DRM against hostile host code. Intentionally hiding or disabling the evaluation notice does not convert editor-surface evaluation into permitted production use.

Install

pnpm add @domternal-pro/core @domternal-pro/extension-version-history @domternal-pro/extension-collaboration yjs@^13.6.8 y-prosemirror@~1.3.7 prosemirror-model@^1.25.11

With the default YjsVersionStore, versions are stored in their own Y.Doc synced under a derived document name (for example mydoc-versions) on the same server as the document. They persist through your existing setup (for example Hocuspocus with the SQLite extension) with zero backend changes. The sibling versions document stays disconnected until version history is actually used: the panel opening, a manual save, or the first automatic capture. When attribution is enabled, a connected editable editor can separately write and sync its Y.PermanentUserData author mapping in the live document during mount.

Requires the Collaboration extension (versions are encoded from its Y.Doc). The y-prosemirror peer is intentionally ~1.3.7 (the 1.3 minor line only, the same range used by the sibling collaboration packages): the restore flow drives binding internals to work around an upstream bug in the documented restore path, and a future 1.4 could move those internals with no compile error. An exact pin would be stricter still, but it invites duplicate y-prosemirror installs next to the sibling packages, which silently breaks the shared plugin key: worse than the risk it removes.

One copy of y-prosemirror, Yjs and the core

This package finds the collaboration binding by the object identity of ySyncPluginKey, so a second copy of y-prosemirror does not error, it misses, and every restore and diff then reads an empty document: the feature offers to replace your document with nothing rather than reporting a problem. A second yjs gives two Snapshot hierarchies with the same result, and a second @domternal/core means this extension is not the class your editor's manager binds.

All three are checked at construction and throw with a message naming both packages and the fix. Keep y-prosemirror inside ~1.3.7, the range all three collaboration packages declare; an unsatisfied peer is how npm and yarn end up nesting a second copy. Full dedupe recipe per package manager and bundler: https://domternal.dev/v1/guides/single-prosemirror-copy/

Usage

import { Collaboration } from '@domternal-pro/extension-collaboration';
import {
  DefaultVersionStoreAuth,
  VersionHistory,
  YjsVersionStore,
} from '@domternal-pro/extension-version-history';
import '@domternal-pro/core/panel.css';
import '@domternal-pro/extension-version-history/version-history.css';

const user = { id: 'u1', name: 'Ana' };
const store = new YjsVersionStore(
  user.id,
  // Lazy: the versions doc only connects when history is used.
  () => connectVersionsDoc().getArray('versions'),
  new DefaultVersionStoreAuth('editor'),
);

const editor = new Editor({
  extensions: [
    StarterKit.configure({ history: false }),
    Collaboration.configure({ document: ydoc }),
    VersionHistory.configure({ store, user }),
  ],
});

The toolbar gains a version history button that opens the version panel: named versions promoted, automatic captures grouped per day, an "only named versions" filter, preview with a "show changes" diff toggle, and restore that never rewrites history (a backup version is saved first, and the restore itself is recorded with provenance).

Options

| Option | Default | Description | | --- | --- | --- | | store | null | Where versions live. Required. | | user | null | The acting user { id, name, color? }; id must match the store's userId. Required. | | resolveUsers | null | Resolves author ids to display users for the panel (sync or async). | | onVersionsChange | null | Fresh snapshot after every store change, local or remote. | | onPreviewChange | null | Fires with the previewed version id, or null when back live. | | onStoreError | console.error | Failures of fire-and-forget store operations. | | attribution | true | Track authorship via Y.PermanentUserData so diffs carry names and colors. | | colors | caret palette | Author color pairs for the diff view. | | autoVersioning | 10 min / 2 min | { activeIntervalMs?, idleMs? } capture cadence, or false to disable. | | autoPruning | { keepAuto: 200 } | Bounds the store by deleting the oldest anonymous automatic captures; false disables it. Full rules under Semantics. | | diffTypes | common blocks | Node types that display block-level diff states; unknown names are ignored. | | diffIgnoredMarks | comment and ychange marks | Mark names the content comparison treats as annotation rather than content. | | defaultUI | true | Mount the built-in version panel and preview banner. | | panelPush | true | The panel docks inside the editor's top corner. The content column slides sideways within the editor to make room for it and back on close. The editor itself never moves. |

Commands

saveVersion({ name?, metadata? }), previewVersion({ id, diff?, againstId?, againstCurrent? }), exitVersionPreview(), restoreVersion({ id }), renameVersion({ id, name }) (a null name clears it), deleteVersion({ id }), openVersionPanel() / closeVersionPanel() / toggleVersionPanel().

Storage

editor.storage.versionHistory exposes { versions, previewVersionId, diffActive, diffAgainstCurrent, diffPair, panelOpen }. Together with the callbacks this is the complete API for custom UIs (defaultUI: false). A DOM-connected editor remains a badge-capable editor surface even when the built-in UI is disabled. With a lazy store source, call store.ready() when your UI opens. The built-in panel does this in openVersionPanel.

Semantics

  • Capture follows activity: versions are taken while someone edits plus once after idle, skipped when nothing changed, damped when a peer just captured.
  • Restore never rewrites history: backup version first, then the old content as one regular undoable edit, then a provenance entry. Comments survive.
  • Preview is read-only. Remote edits keep landing in the live document underneath.
  • Automatic captures are bounded by default: autoPruning deletes anonymous automatic versions beyond keepAuto (200) once they are older than minAgeMs (ten minutes), after a store change, and only in sessions whose store auth may delete. Named, promoted, manual, backup and restore entries are never touched, and autoPruning: false turns it off. YjsVersionStore.pruneAutoVersions({ keep, minAgeMs? }) runs the same cleanup on demand.

Roles

DefaultVersionStoreAuth implements a two-role model: viewers browse, preview and compare versions; editors additionally create, rename, delete and restore. Custom rules implement the VersionStoreAuth interface. Server-enforced permissions implement a VersionStore whose writes go through your API.

Support

Report a defect that reproduces with synthetic content on the public tracker, using the Pro bug report template. Anything involving your own documents, your data or a collaboration update goes to email instead, and security vulnerabilities go to email only.

The Support and reporting issues policy explains where confirmed defects may be documented and how material notices are handled. Public channels are not a complete record of internal reports, investigations or other issues. Security vulnerabilities are handled through coordinated private disclosure.