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

@kyneta/loro-schema

v3.0.0

Published

Loro CRDT substrate for @kyneta/schema — collaborative data types with typed refs (formerly @kyneta/schema-loro)

Readme

@kyneta/loro-schema

Loro CRDT substrate for @kyneta/schema. Provides collaborative data types with typed refs — same schema, same API, but backed by a Loro document with automatic conflict resolution and sync.

Schemas are defined once with Schema.*, then bound to Loro via loro.bind(). Write once, bind anywhere.

Getting Started

import { Schema } from "@kyneta/schema"
import { createDoc, batch, subscribe, loro } from "@kyneta/loro-schema"

// Define a schema and bind to Loro
const TodoDoc = loro.bind(Schema.struct({
  title: Schema.text(),
  count: Schema.counter(),
  items: Schema.list(
    Schema.struct.json({
      name: Schema.string(),
      done: Schema.boolean()
    }),
  ),
}))

// Create a live, collaborative document
const doc = createDoc(TodoDoc, { title: "Hello" })

// Read and write through the typed ref API
doc.title()  // "Hello"

batch(doc, d => {
  d.title.insert(5, " World")
  d.count.increment(1)
  d.items.push({ name: "First task", done: false })
})

doc.title()              // "Hello World"
doc.count()              // 1
doc.items.at(0).name()   // "First task"

// Observe all mutations (local, remote, or external)
subscribe(doc, changeset => {
  console.log("Changed:", changeset)
})

Write Once, Bind Anywhere

Schemas are backend-agnostic. The same Schema.* definition works with any substrate — Loro, Yjs, or plain JSON:

import { Schema } from "@kyneta/schema"
import { loro } from "@kyneta/loro-schema"
import { json } from "@kyneta/schema"

const schema = Schema.struct({
  title: Schema.text(),
  count: Schema.counter(),
  items: Schema.list(
    Schema.struct.json({ name: Schema.string(), done: Schema.boolean() }),
  ),
})

// Bind to Loro for collaborative editing
const loroBound = loro.bind(schema)

// Bind to JSON for server-side or non-collaborative use
const jsonBound = json.bind(schema)

loro.bind() enforces Loro's composition-law constraints at compile time via LoroLaws. If your schema uses a composition law Loro doesn't support (e.g. Schema.set()), loro.bind() produces a type error.

Bring Your Own LoroDoc

Every createDoc document is backed by a real LoroDoc. Use loro.unwrap() to access it — e.g. for interop with a state bus or another Loro-aware library:

import { createDoc, subscribe, loro } from "@kyneta/loro-schema"

const doc = createDoc(myBoundSchema)

// Escape hatch — access the underlying LoroDoc
const loroDoc = loro.unwrap(doc)

// External mutations to the LoroDoc fire kyneta subscribers
subscribe(doc, () => console.log("Something changed"))

loroDoc.getText("title").insert(0, "External edit")
loroDoc.commit()
// → "Something changed"

Note for raw LoroDoc consumers: Subscribers attached directly to the underlying LoroDoc will newly see options.origin faithfully on batch.origin (where previously it was overwritten by a kyneta sentinel).

Sync

Two peers exchange state via exportSince / merge:

import {
  createDoc, batch, subscribe,
  version, exportSince, merge,
} from "@kyneta/loro-schema"

// Peer A
const docA = createDoc(myBoundSchema)
batch(docA, d => d.title.insert(0, "Hello from A"))

// Peer B
const docB = createDoc(myBoundSchema)
subscribe(docB, () => console.log("B updated"))

// Sync A → B
const sinceVersion = version(docB)
const delta = exportSince(docA, sinceVersion)
merge(docB, delta!, { origin: "sync" })
// → "B updated"
// docB.title() === "Hello from A"

For full state transfer (SSR, reconnection), use snapshots via the exchange:

import { exportEntirety, createDoc } from "@kyneta/loro-schema"

const snapshot = exportEntirety(docA)
const docB = createDoc(myBoundSchema)
// restore from snapshot via the exchange or substrate

API Reference

Bind & Escape Hatch

| Export | Description | |--------|-------------| | loro.bind(schema) | Bind a schema to the Loro CRDT substrate. Enforces LoroLaws constraints at compile time — schemas containing unsupported composition laws (e.g. Schema.set()) are rejected. Returns a BoundSchema<S> for use with exchange.get(). The factory builder injects a deterministic numeric Loro PeerID derived from the exchange's string peerId. | | loro.unwrap(ref) | Escape hatch — returns the LoroDoc backing a root document ref. Throws if the ref is not backed by a Loro substrate. Currently supports root refs only; child-level resolution is future work. |

Batteries-Included (most users)

| Export | Description | |--------|-------------| | createDoc(boundSchema, seed?) | Create a live Loro-backed document. Pass a bound schema (result of loro.bind()) and an optional seed object. (re-exported from @kyneta/schema) |

| version(doc) | Current version as a LoroVersion. | | exportEntirety(doc) | Full state as a binary SubstratePayload. | | exportSince(doc, since) | Delta payload since a version. | | merge(doc, payload, options?) | Apply a delta from another peer. | | batch(doc, fn) | Run mutations in a transaction. (re-exported from @kyneta/schema) | | subscribe(doc, cb) | Observe all mutations. (re-exported from @kyneta/schema) | | applyChanges(doc, ops, opts?) | Apply a list of ops declaratively. (re-exported from @kyneta/schema) |

Schema Constructors

Schemas are defined with Schema.* from @kyneta/schema. All constructors are backend-agnostic:

| Constructor | Description | |-------------|-------------| | Schema.struct(fields) | Product type → LoroMap container | | Schema.list(item) | Sequence type → LoroList container | | Schema.record(item) | Map type → LoroMap container | | Schema.text() | Collaborative text → LoroText | | Schema.counter() | CRDT counter → LoroCounter | | Schema.movableList(item) | Movable list → LoroMovableList | | Schema.tree(nodeData) | Tree → LoroTree | | Schema.struct.json(fields) | JSON merge boundary — struct stored as opaque JSON in parent container | | Schema.list.json(item) | JSON merge boundary — array stored as opaque JSON | | Schema.record.json(item) | JSON merge boundary — record stored as opaque JSON | | Schema.string() | Plain string scalar (stored in _props at root) | | Schema.number() | Plain number scalar | | Schema.boolean() | Plain boolean scalar | | Schema.nullable(inner) | Nullable wrapper |

Low-Level Primitives (power users)

| Export | Description | |--------|-------------| | createLoroSubstrate(doc, schema) | Wrap a LoroDoc in a Substrate<LoroVersion>. | | loroSubstrateFactory | SubstrateFactory<LoroVersion> with create, fromSnapshot, parseVersion. | | loroStoreReader(doc, schema) | Create a StoreReader over a Loro container tree. | | resolveContainer(doc, schema, path) | Resolve a Loro container at a kyneta path. | | changeToDiff(path, change, schema, doc) | Convert a kyneta Change to Loro [ContainerID, Diff][] tuples. | | batchToOps(batch, schema) | Convert a Loro event batch to kyneta Op[]. | | LoroVersion | Version implementation wrapping Loro's VersionVector. | | LoroLaws | Composition-law type: "lww" | "additive" | "positional-ot" | "positional-ot-move" | "lww-per-key" | "tree-move" | "lww-tag-replaced". |

Event Bridge Contract

Wrapping a LoroDoc in a kyneta substrate means subscribe() observes all mutations to the underlying doc, regardless of source:

  • Mutations via batch() — the normal path
  • Mutations via merge() — remote sync
  • External doc.import() — e.g. from a state bus
  • External raw Loro API calls + doc.commit() — e.g. from another library

Peer Dependencies

{
  "peerDependencies": {
    "@kyneta/schema": ">=0.0.1",
    "loro-crdt": ">=1.8.0"
  }
}

License

MIT