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

@hecto/crdt

v0.2.0

Published

Conflict-free replicated data types for collaborative text: site identity, fractional indexing, the Fugue sequence CRDT, an event graph, and a binary encoding.

Readme

@hecto/crdt

Conflict-free replicated data types for collaborative text: site identity, fractional indexing, a Fugue sequence, an event graph, binary encoding, text and JSON merge, and an operation-log client. hecto's own implementation — no third-party CRDT library.

pnpm add @hecto/crdt
import { createText } from '@hecto/crdt/fugue'

const alice = createText('alice')
const bob = createText('bob')

const hello = alice.insert(0, 'hello')
for (const op of hello) bob.apply(op)

const fromAlice = alice.insert(5, ' there')
const fromBob = bob.insert(5, ' world')
for (const op of fromBob) alice.apply(op)
for (const op of fromAlice) bob.apply(op)

alice.toString() === bob.toString() // true

What's in the box

  • . — SiteId, OpId, compareOpId, StateVector
  • ./fractional — between(a, b, site)
  • ./fugue — createText, insert, delete, apply, toString, idAt, indexOfId, stateVector, opsSince
  • ./graph — createEventGraph: append-only DAG, frontier, topological order, diffVersions, nearest common ancestors
  • ./encoding — encodeOps / decodeOps, snapshots, state vectors
  • ./merge — createTextDocument: event-graph text with localSplice, receive, idAt, indexOfId
  • ./json-merge — createJsonDocument: the same graph over any @hecto/diff patch
  • ./client — createDocClient: offline queue, SSE reconnect, presence

Indices are UTF-16 code units, matching @hecto/diff splices.

Identity

import { compareOpId } from '@hecto/crdt'
import type { OpId } from '@hecto/crdt'

const id: OpId = { site: 'alice', seq: 3 }
compareOpId({ site: 'alice', seq: 1 }, { site: 'bob', seq: 1 }) // -1

SiteId names one replica. Each site hands out seq in strictly increasing order, so (site, seq) is unique. StateVector maps a site to the highest contiguous seq received from it.

Fractional indexing

import { between } from '@hecto/crdt/fractional'

const first = between(null, null, 'alice')
const second = between(first, null, 'alice')
const middle = between(first, second, 'bob')
first < middle && middle < second // true

between returns a base-62 key with a !<site> suffix. Two sites minting in the same gap share a core and stay distinct by suffix. Plain string comparison is the order. Repeated inserts at one spot grow the key; 10,000 insertions at a fixed spot stay under 2,100 characters.

Fugue

createText(site) implements Weidner & Kleppmann, The Art of the Fugue (2023), Section 4, Algorithm 1. Deletes are tombstones. apply is idempotent and buffers an op whose parent, delete target, or earlier same-site seq has not arrived.

createText(site) stores forward typing runs as spans and looks up positions in O(log n). createText(site, { spans: false }) is the per-character oracle used by merge replay.

Two sites typing aaaa and bbbb at one position converge on aaaabbbb or bbbbaaaa, never a mixture.

Event graph

import { createEventGraph } from '@hecto/crdt/graph'

const graph = createEventGraph<string>()
graph.add({ id: { site: 'alice', seq: 1 }, parents: [], patch: 'root' })
graph.frontier() // [{ site: 'alice', seq: 1 }]

add accepts an exact duplicate idempotently, rejects the same id with different content (DuplicateEvent), rejects an unknown parent (UnknownParent — the caller buffers), and requires id.seq to be exactly one more than the highest seq already accepted from that site (OutOfOrderSeq).

Encoding

import { createText } from '@hecto/crdt/fugue'
import { decodeOps, encodeOps } from '@hecto/crdt/encoding'

const alice = createText('alice')
const ops = alice.insert(0, 'hello')
const bytes = encodeOps(ops)
decodeOps(bytes) // deep-equal to `ops`

Structure (ids, parents, tombstones) and content (inserted text) are separate sections so a host can encrypt content and still merge. splitEncoded / joinEncoded slice that boundary. Decoding untrusted bytes throws only a typed DecodeError.

Text merge

import { isOk } from '@hecto/fp'
import { createTextDocument } from '@hecto/crdt/merge'

const alice = createTextDocument('alice')
const hello = alice.localSplice(0, 0, 'hello')
const bob = createTextDocument('bob')
const received = bob.receive(hello)
isOk(received) // true
bob.text // 'hello'
bob.idAt(0) // { site: 'alice', seq: 1 }

If event.parents equals the frontier, the splice applies as written. Otherwise both branches replay through a temporary Fugue replica and the incoming event is emitted as splices already transformed onto the current text. receive of an unknown parent returns UnknownParent and holds the event. receive of an event already in the graph is idempotent. idAt / indexOfId are the shared character ids presence carets use. Empty text has no id.

JSON merge

import { isOk } from '@hecto/fp'
import { patch } from '@hecto/diff'
import { createJsonDocument } from '@hecto/crdt/json-merge'

const alice = createJsonDocument('alice', { value: { n: 1 } })
const bump = alice.localPatch(
  patch([{ op: 'replace', path: ['n'], oldValue: 1, newValue: 2 }]),
)
const bob = createJsonDocument('bob', { value: { n: 1 } })
const received = bob.receive(bump)
isOk(received) // true
bob.value // { n: 2 }

Text splices merge through Fugue. Concurrent replaces resolve last-writer-wins by (lamport, site). Concurrent array adds at one index order by site. remove tombstones the path. Lamport is 1 + max(parent lamports) and is never stored on the event.

Client

import { createDocClient } from '@hecto/crdt/client'
import { createTextDocument } from '@hecto/crdt/merge'

const text = createTextDocument('alice')
const hello = text.localSplice(0, 0, 'hello')
const client = createDocClient({
  url: 'https://docs.example/',
  docId: 'note-1',
  clientId: 'alice',
  sessionId: 'session-alice',
  userId: 'alice',
  displayName: 'Alice',
})
void client.send([hello])
client.disconnect()

Talks to @hecto/doc-server without importing it. send queues while offline. reconnect() opens the SSE stream from the current state vector (?sv= and ?clientId=). Presence POST is { activity, cursor } only — identity comes from the host options, never from publish(). Subscribe listeners never see an event this client minted.