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

colla-ot

v0.2.0

Published

JavaScript and WebAssembly bindings for Colla

Readme

colla-ot

colla-ot is the synchronous JavaScript facade for Colla's immutable nested values, canonical Change format, and Operational Transformation algebra. It uses the same Rust core and canonical bytes as the colla crate.

Install

pnpm add colla-ot

The package is ESM-only and supports Node.js 22+, Vite 5+, and Rollup 4+. Browser and Node entry points initialize the same Wasm binary synchronously; no public initialization function or Wasm bundler plugin is required.

Values

import { Value, richText, text } from "colla-ot"

using value = Value.fromJS({
  count: 1n,
  title: text("Draft"),
  body: richText([
    { type: "text", text: "Hello", attrs: { bold: true } },
    { type: "embed", value: { id: "mention-1" } },
  ]),
})

console.log(value.toJS())

ValueInput accepts null, booleans, signed 64-bit bigint, finite numbers, strings, arrays, plain records, text() markers, and richText() markers. ValueData returned by get() and toJS() is recursively frozen; map output uses null-prototype records.

Ordinary strings are atomic. Use text() when character-level OT is required. RichText embeds are atomic Core Values and count as one sequence unit.

Typed Change construction

Change.fromJS() is the low-level, Snapshot-independent construction API. Map entries remain an array so duplicate keys can be rejected, while sequence changes use ordered operation streams.

import { Change } from "colla-ot"

using change = Change.fromJS({
  type: "map",
  entries: [{
    key: "title",
    type: "modify",
    change: {
      type: "text",
      ops: [
        { type: "retain", length: 5 },
        { type: "insert", text: " v2" },
      ],
    },
  }],
})

Construction does not inspect a Snapshot. Key existence, target types, and sequence bounds are checked when the Change is applied.

TypeScript Builder

Change.build() is a pure TypeScript convenience layer over the same typed input. It does not own a Wasm handle, apply or compose intermediate changes, parse paths, perform map upserts, or convert coordinates.

import { Change, Value, apply, text } from "colla-ot"

using before = Value.fromJS({
  count: 1n,
  title: text("Draft"),
})

using change = Change.build(change => {
  change.map(map => {
    map.modify("count", count => count.intAdd(1n))
    map.modify("title", title => {
      title.text(text => text.retain(5).insert(" v2"))
    })
  })
})

using after = apply(before, change)

Each root or nested Change callback must select exactly one Change kind. Scoped builders are synchronous and close when their callback returns or throws; they cannot be retained for later mutation. Map operations explicitly choose insert, delete, or modify.

Raw operation streams may contain zero-length operations, empty inserts, adjacent operations, or trailing retains. Rust typed constructors perform the canonical normalization and checked length accumulation.

Text coordinates

Text and RichText retain and delete lengths use Unicode scalar values, not JavaScript UTF-16 code units. RichText embeds count as one in both coordinate systems.

import {
  Change,
  Value,
  resolveCodePointPosition,
  resolveUtf16Position,
  text,
} from "colla-ot"

using value = Value.fromJS(text("A😀B"))

resolveCodePointPosition(value, [], 3) // 2
resolveUtf16Position(value, [], 2)     // 3

using change = Change.build(change => {
  change.text(text => text.retain(2).insert("X"))
})

UTF-16 positions inside a surrogate pair are rejected. inspectChange() uses Snapshot-relative UTF-16 positions for JavaScript-facing views.

Codec and algebra

import {
  Change,
  Value,
  apply,
  compose,
  inspectChange,
  invert,
  transformPair,
} from "colla-ot"

using value = Value.decode(valueBytes)
using first = Change.decode(firstBytes)
using second = Change.decode(secondBytes)
using concurrent = Change.decode(concurrentBytes)

using combined = compose(first, second)
using inverse = invert(combined, value)
using next = apply(value, combined)
const [leftPrime, rightPrime] = transformPair(first, concurrent, {
  order: "left-first",
})
const view = inspectChange(combined, value)

leftPrime.dispose()
rightPrime.dispose()

encode() returns fresh JavaScript-owned canonical bytes. Algebra never consumes its inputs. ChangeView is a read-only projection for inspection; it is neither Change construction data nor a persistence format.

Input limits and errors

InputOptions may override DEFAULT_INPUT_LIMITS at untrusted input boundaries:

  • Value.fromJS(input, { limits })
  • Value.decode(bytes, { limits })
  • Change.fromJS(input, { limits })
  • Change.build(edit, { limits })
  • Change.decode(bytes, { limits })

Change construction limits are counted against raw input before normalization, so empty operations cannot bypass resource policy. Algebra, coordinate conversion, and Change inspection do not apply input limits.

Failures throw CollaError. Match its stable code, operation, optional path, and frozen details fields rather than message text.

import { CollaError, Value } from "colla-ot"

try {
  Value.decode(bytes)
} catch (error) {
  if (error instanceof CollaError && error.is("invalid_encoding")) {
    console.error(error.operation, error.details)
  }
}

Resource lifecycle

Value and Change are Wasm-backed handles. Call dispose() or use Symbol.dispose as soon as ownership ends. Disposal is idempotent, and clones have independent ownership. FinalizationRegistry is only a fallback for missed cleanup.

The callback builders used by Change.build() are ordinary TypeScript scopes; they have no handle, clone, finalizer, or disposal API.

More documentation

See the repository documentation index for the data model, OT properties, binary format, architecture decisions, compatibility policy, and release process.