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

@sovereignbase/schema-crdt

v1.0.0

Published

Schema.org schemas modeled as CRDTs.

Readme

npm version CI codecov license

schema-crdt

Schema.org classes modeled as convergent replicated data types.

Compatibility

  • Runtimes: Node >= 20, modern browsers, Bun, Deno, Cloudflare Workers, Edge Runtime.
  • Module format: ESM + CommonJS.
  • Required globals / APIs: EventTarget, CustomEvent, structuredClone.
  • TypeScript: bundled types.

Goals

  • Schema.org-shaped data that can be edited locally and merged through CRDT gossip.
  • A single event-driven API across scalar properties, nested CRText, CRSet, CRList, and CRMap properties.
  • Runtime class inheritance that follows the implemented Schema.org hierarchy.
  • Snapshot, delta, acknowledgement, and garbage-collection behavior aligned with the underlying Sovereignbase CRDT packages.
  • Property-specific lexical validation only where Schema.org gives a clear format.

Installation

npm install @sovereignbase/schema-crdt
# or
pnpm add @sovereignbase/schema-crdt
# or
yarn add @sovereignbase/schema-crdt
# or
bun add @sovereignbase/schema-crdt
# or
deno add jsr:@sovereignbase/schema-crdt
# or
vlt install jsr:@sovereignbase/schema-crdt

Usage

Copy-paste example

import { CRPerson, CRPostalAddress } from '@sovereignbase/schema-crdt'

const alice = new CRPerson()
const bob = new CRPerson(alice.toJSON())

alice.addEventListener('delta', (event) => {
  bob.merge(event.detail)
})

alice.givenName.insertAfter(-1, 'Jori')
alice.familyName.insertAfter(-1, 'Lehtinen')
alice.email.add('[email protected]')
alice.jobTitle.add('Software engineer')

const address = new CRPostalAddress()
address.streetAddress = 'Example Street 1'
address.addressLocality = 'Helsinki'
address.addressCountry = 'FI'
address.postalCode = '00100'

alice.address.add(address.toJSON())

console.log(bob.givenName.valueOf()) // 'Jori'
console.log(bob.familyName.valueOf()) // 'Lehtinen'
console.log(bob.address.size) // 1

Schema graphs

import {
  CRGeoCoordinates,
  CRImageObject,
  CRPlace,
  CRPostalAddress,
  CRWebPage,
} from '@sovereignbase/schema-crdt'

const place = new CRPlace()
place.name.insertAfter(-1, 'Sovereignbase Office')
place.url = 'https://example.test/place'
place.telephone = '+358 40 000 0000'

const address = new CRPostalAddress()
address.streetAddress = 'Example Street 1'
address.addressLocality = 'Helsinki'
address.addressCountry = 'FI'
address.postalCode = '00100'

const geo = new CRGeoCoordinates()
geo.latitude = '60.1699'
geo.longitude = '24.9384'
geo.addressCountry = 'FI'

const image = new CRImageObject()
image.contentUrl = 'https://example.test/place.jpg'
image.sha256 =
  '39494d28efb226824110570e19a618751878d3e45da58a2a0bfadcc0c4672abd'

const page = new CRWebPage()
page.primaryImageOfPage.add(image.toJSON())

place.address.add(address.toJSON())
place.geo.add(geo.toJSON())
place.image.add('https://example.test/place.jpg')
place.mainEntityOfPage.add('https://example.test/place')

console.log(place['@type']) // 'Place'
console.log(place.geo.size) // 1

Hydrating root scalar state

import { CRPlace, type CRPlaceSnapshot } from '@sovereignbase/schema-crdt'

const source = new CRPlace()
let snapshot!: CRPlaceSnapshot

source.addEventListener('snapshot', (event) => {
  snapshot = event.detail
})

source.url = 'https://example.test/market-square'
source.snapshot()

const restored = new CRPlace(snapshot)

console.log(restored.url) // 'https://example.test/market-square'

This example assumes the snapshot is kept as a structured-clone-compatible value. If you persist snapshots through JSON.stringify / JSON.parse, keep property values JSON-compatible. Nested CRText, CRSet, CRList, and CRMap properties emit their own routed snapshot and delta payloads; apply those routed payloads with merge().

Event channels

import { CRThing } from '@sovereignbase/schema-crdt'

const replica = new CRThing()

replica.addEventListener('delta', (event) => {
  console.log('delta', event.detail)
})

replica.addEventListener('change', (event) => {
  console.log('change', event.detail)
})

replica.addEventListener('snapshot', (event) => {
  console.log('snapshot', event.detail)
})

replica.addEventListener('ack', (event) => {
  console.log('ack', event.detail)
})

replica.name.insertAfter(-1, 'Example')
replica.sameAs.add('https://example.test/')
replica.url = 'https://example.test/thing'
replica.snapshot()
replica.acknowledge()

Nested CRDT events are routed through their Schema.org property key. For example, editing thing.name emits a delta whose detail is keyed by name, and adding to thing.sameAs emits a delta keyed by sameAs.

Acknowledgements and garbage collection

import { CRThing } from '@sovereignbase/schema-crdt'

const alice = new CRThing()
const bob = new CRThing(alice.toJSON())
const frontiers = new Map<string, unknown>()

alice.addEventListener('delta', (event) => {
  bob.merge(event.detail)
})

bob.addEventListener('delta', (event) => {
  alice.merge(event.detail)
})

alice.addEventListener('ack', (event) => {
  frontiers.set('alice', event.detail)
})

bob.addEventListener('ack', (event) => {
  frontiers.set('bob', event.detail)
})

alice.name.insertAfter(-1, 'first')
alice.name.removeAfter(0, alice.name.size)
alice.name.insertAfter(-1, 'second')

alice.acknowledge()
bob.acknowledge()

alice.garbageCollect([...frontiers.values()])
bob.garbageCollect([...frontiers.values()])

JSON-LD references

import { CRAction, CRIdReference, CRThing } from '@sovereignbase/schema-crdt'

const owner = new CRIdReference('urn:anbs:Person.alice')

const action = new CRAction()
action.agent.add({ '@id': owner['@id'], '@type': 'Person' })
action.object.add({ '@id': 'urn:anbs:Thing.document' })

const thing = new CRThing()
thing.owner.add({ '@id': owner['@id'], '@type': 'Person' })
thing.potentialAction.add({ '@id': 'urn:anbs:Action.read', '@type': 'Action' })

console.log(thing.owner.values()[0]?.['@id']) // 'urn:anbs:Person.alice'

The examples use stable application identifiers in the form urn:anbs:{@type}.{identifier}. Do not encode version numbers into @id unless the application intentionally treats every version as a separate Schema.org node.

Use typed JSON-LD references such as { '@id': '...', '@type': 'Person' } where a Schema.org property requires a more specific class. Use plain { '@id': '...' } where the property accepts an untyped node reference.

JSON-LD presentations

import { CRThing } from '@sovereignbase/schema-crdt'

const thing = await CRThing.fromJSONLD({
  '@context': 'https://schema.org',
  '@id': 'urn:anbs:Thing.example',
  '@type': 'Thing',
  name: 'Example',
  sameAs: ['https://example.test/'],
})

const jsonld = thing.toJSONLD()
const canonical = await thing.getCanonicalPresentation()

console.log(jsonld.name) // 'Example'
console.log(canonical.includes('https://schema.org/name')) // true

fromJSONLD() is an async constructor-style import for compacted or expanded Schema.org JSON-LD. It creates a fresh CRDT instance; it does not merge JSON-LD into an existing replica. toJSONLD() exports the current live Schema.org presentation as compacted JSON-LD. toJSON() remains the CRDT snapshot for replication and persistence.

getCanonicalPresentation() validates the live presentation with @adobe/structured-data-validator and returns jsonld.js URDNA2015 canonical N-Quads for signing or hashing. That canonical form covers the live JSON-LD presentation only. If an application needs to sign the whole CRDT state, canonicalize toJSON() separately in the application protocol.

Validation

import { CRGeoCoordinates, SchemaCRDTError } from '@sovereignbase/schema-crdt'

const geo = new CRGeoCoordinates()

geo.latitude = '60.1699'
geo.longitude = '24.9384'
geo.addressCountry = 'FI'

try {
  geo.latitude = '91'
} catch (error) {
  if (error instanceof SchemaCRDTError) {
    console.log(error.code) // 'VALIDATION_FAILED'
  }
}

Validation is property-specific. The underlying CRDT struct already checks broad runtime shape compatibility; schema-crdt only adds format checks where this package defines a clear lexical format, such as geo coordinates, ISO-like date/time and duration values, HTTP methods, and SHA-256 hex digests.

Implemented classes

Core hierarchy

  • CRThing
  • CRIntangible
  • CRStructuredValue
  • CREnumeration
  • CRStatusEnumeration
  • CRIdReference

Actions and entry points

  • CRAction
  • CRActionStatusType
  • CREntryPoint
  • CRSoftwareApplication
  • CRDigitalPlatformEnumeration
  • CRVirtualLocation

Creative work and web

  • CRCreativeWork
  • CRMediaObject
  • CRImageObject
  • CRWebPage
  • CRWebPageElement
  • CRWebContent
  • CRSpeakableSpecification
  • CRReview
  • CRDefinedTermSet

Events and schedules

  • CREvent
  • CREventStatusType
  • CREventAttendanceModeEnumeration
  • CRSchedule

HowTo and lists

  • CRHowTo
  • CRHowToDirection
  • CRHowToItem
  • CRHowToSection
  • CRHowToStep
  • CRHowToSupply
  • CRHowToTip
  • CRHowToTool
  • CRItemList
  • CRListItem
  • CRBreadcrumbList
  • CRItemListOrderType

Places and contact data

  • CRPlace
  • CRAdministrativeArea
  • CRCountry
  • CRPostalAddress
  • CRContactPoint
  • CRGeoCoordinates
  • CRGeoShape
  • CRGeospatialGeometry
  • CROpeningHoursSpecification

Values, ratings, people, and organizations

  • CRPerson
  • CROrganization
  • CRAudience
  • CRDefinedTerm
  • CRSpecialty
  • CRPropertyValue
  • CRQuantitativeValue
  • CRMonetaryAmount
  • CRLocationFeatureSpecification
  • CRRating
  • CRAggregateRating

Runtime behavior

Property model

  • Scalar Schema.org properties use direct getters and setters.
  • Natural-language text properties use CRText.
  • Code, identifier, contact, coordinate, currency, unit, version, and address component properties stay scalar instead of pretending to be collaborative text.
  • Multi-value unordered properties use CRSet.
  • Ordered list properties use CRList.
  • Custom extension properties can be wired through the CRThing constructor's CRDT property map.
  • Every class exposes merge(), snapshot(), acknowledge(), garbageCollect(), toJSON(), clone(), values(), entries(), and iteration through the inherited CRThing API.

Validation and errors

Public field writes and incoming merge payloads can throw SchemaCRDTError:

  • VALIDATION_FAILED
  • CANONICALIZATION_FAILED

Validation is intentionally narrow. Deprecated and superseded Schema.org property aliases are omitted instead of being implemented as runtime aliases.

Safety and copying semantics

  • Snapshots are detached structured-clone payloads keyed by Schema.org property name.
  • delta events are the gossip payloads to send to another replica's merge().
  • change events describe visible projection changes keyed by Schema.org property name.
  • ack events are acknowledgement frontiers for garbageCollect().
  • toJSON() returns the root struct snapshot, not a Schema.org JSON-LD document.
  • Nested CRText, CRSet, CRList, and CRMap properties expose their own snapshots through routed event payloads.
  • Direct CRText, CRSet, CRList, and CRMap properties retain their own CRDT semantics.

Convergence and compaction

  • The convergence target is the visible Schema.org-shaped projection.
  • Nested CRDT property events are routed through their owning Schema.org key.
  • A root class snapshot can hydrate scalar struct state in a fresh class instance.
  • Routed nested snapshots and deltas can be merged into the corresponding nested CRDT instance.
  • Acknowledgement frontiers are routed by property key so garbage collection can compact nested CRDT history without a second external index.
  • fromJSONLD() imports compacted or expanded JSON-LD into a new instance, toJSONLD() exports the live presentation, and getCanonicalPresentation() canonicalizes that presentation for signatures or hashes.

Tests

npm run test

What the current test suite covers:

  • Coverage on built dist/**/*.js: 100% statements, 100% branches, 100% functions, and 100% lines via c8.
  • Runtime export surface for every public constructor and every snapshot type.
  • Runtime class hierarchy against the implemented Schema.org inheritance model.
  • Exhaustive property mutation across every CRDT-backed class.
  • Scalar setters, nested CRText, CRSet, CRList, and CRMap property paths.
  • Snapshot hydration, merge, routed deltas, acknowledgements, and garbage collection.
  • CRIdReference value snapshots, CRDT snapshots, events, listener removal, and merge.
  • Property-specific validator success and failure paths.
  • Package root exports and source directory shape invariants.
  • End-to-end runtime matrix for:
    • Node ESM
    • Node CJS
    • Bun ESM
    • Bun CJS
    • Deno ESM
    • Cloudflare Workers ESM
    • Edge Runtime ESM
    • Browsers via Playwright: Chromium, Firefox, WebKit, mobile Chrome, mobile Safari
  • Current status: npm run test passes on Node v22.14.0 (win32 x64).

Benchmarks

npm run bench

The benchmark suite measures schema-crdt orchestration paths rather than the primitive CRDT operations already covered by the lower-level packages.

Last measured on Node v22.14.0 (win32 x64):

| group | scenario | classes / keys | ops | ms | ms/op | ops/sec | | ------------ | ------------------------------------------ | -------------: | ---: | -----: | ----: | --------: | | construct | CRThing empty constructor | 1 | 250 | 27.44 | 0.11 | 9,111.02 | | construct | CRWebPage empty constructor | 1 | 250 | 156.22 | 0.62 | 1,600.36 | | construct | all schema classes empty constructors | 56 | 448 | 155.46 | 0.35 | 2,881.83 | | populate | CRPerson full setter sweep | 77 | 50 | 261.44 | 5.23 | 191.25 | | populate | CRImageObject full setter sweep | 147 | 50 | 413.33 | 8.27 | 120.97 | | populate | all schema classes full setter sweep | 56 | 224 | 726.75 | 3.24 | 308.22 | | hydrate | CRPlace hydrate populated snapshot | 55 | 50 | 14.07 | 0.28 | 3,553.48 | | hydrate | CRImageObject hydrate populated snapshot | 147 | 50 | 46.82 | 0.94 | 1,068.03 | | replica | CRThing merge populated snapshot | 14 | 50 | 5.91 | 0.12 | 8,456.23 | | replica | CRWebPage merge populated snapshot | 134 | 50 | 48.82 | 0.98 | 1,024.22 | | events | CRAction nested event routing | 18 | 750 | 43.03 | 0.06 | 17,431.40 | | lifecycle | CRPerson acknowledge | 77 | 80 | 12.44 | 0.16 | 6,429.73 | | lifecycle | CRImageObject garbage collect | 147 | 80 | 33.46 | 0.42 | 2,391.09 | | reference | CRIdReference set / snapshot / hydrate | 1 | 250 | 17.82 | 0.07 | 14,031.78 | | validation | format validators accepted values | 16 | 4000 | 328.01 | 0.08 | 12,194.79 | | validation | format validators rejected values | 16 | 3250 | 880.75 | 0.27 | 3,690.02 |

License

Apache-2.0