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

@chronicle.app/schema

v0.5.0

Published

Minimal Chronicle extraction vocabulary and generated TypeScript/Zod schemas

Readme

@chronicle.app/schema

A deliberately small extraction vocabulary with generated TypeScript interfaces and Zod validators. The canonical file is chronicle.ttl; edit it and run npm run schema:generate from the repository root to regenerate the TypeScript/Zod schemas. npm run schema:check verifies that the committed output matches the ontology without rewriting it. Write each term's rdfs:comment by the rules in WRITING.md.

Documentation site

The schema is documented at https://schema.chronicle.app, built from chronicle.ttl and the example records in examples.ttl. The site lives in apps/schema-site, which also describes the examples format.

Other vocabularies

Chronicle's vocabulary stands on its own. It borrows schema.org's model and names, and chronicle.ttl points each term to the closest schema.org term as a hint for mapping data between them: skos:exactMatch for the same meaning, skos:closeMatch for a similar one, and skos:broadMatch when schema.org's term is broader. A reasoner draws nothing from SKOS mappings, so the hints don't claim the vocabularies are compatible. They aren't: values, scope, and structure differ in places. No rdfs:subClassOf, rdfs:subPropertyOf, owl:equivalentClass, or owl:equivalentProperty points to another vocabulary, and the schema site's build fails on one.

alignments/ holds one file for each vocabulary that Chronicle's terms relate to, schemaorg.ttl and activitystreams.ttl. Each names the release its terms were checked against, and holds:

  • doc:note: how a Chronicle term differs from the term it maps to, written by the rules in WRITING.md.
  • doc:unlike: a term with the same name as a Chronicle term but a different meaning, such as schema.org's Project.
  • The mappings to any vocabulary other than schema.org. They can change without a vocabulary release.

<name>.terms.json lists the classes and properties of the release. To move to a newer one, run node scripts/snapshot-vocabulary.js schemaorg <release> (or activitystreams), update doc:release, and fix what the schema site's build reports. The site shows each term's relations, has a page for each vocabulary, and fails when a mapping names a term the release does not have, or when a Chronicle term shares a name with a term it neither maps to nor marks unlike. Names are also compared without the endings in doc:nameEnding, so LikeAction is compared with Activity Streams' Like. To relate the vocabulary to another one, add its alignment, its .terms.json, and a comparison in the site's vocabularies/.

Vocabulary

  • Base: an identity-bearing node; carries sourceId.
  • Entity: a thing described by a source; adds name and url.
  • Action: an occurrence; adds a single entity-valued object.

Plugins extend these roots only where their outputs require it: Things adds task lifecycle actions and collections/tags; shell adds commands, people, and machine realms; iMessage adds messages and attachment media; Claude Code adds threads, software agents, and model instruments; Safari adds page views; Arc Timeline adds visits, trips, places, and journeys; WhatsApp adds group channels, members, and quoted replies. Shared properties cover source identity, event time, agents, membership, authors, and recipients.

Text, URL, DateTime, and Number are datatypes. A DateTime is a Date or an EDTF string at the source's precision, such as 1987 or XXXX-03-12; isDateTime checks a string. A property takes a list of values unless its SHACL shape sets sh:maxCount 1. Records carry @type and at least one of @key (a nonempty list of identity fields or computed key entries) or @id (an existing identity). A record can also carry @assertedAt, the instant its source observed it, and @asserts, the predicates it states completely ('*' for all of them). Identity validation checks that declaration, not the existence or hash of the referenced fields. Source-specific extraction owns the values; never invent IDs to make a record pass validation.

import { ActionSchema, EntitySchema } from '@chronicle.app/schema';

// Synthetic example data.
const entity = EntitySchema.parse({
  '@type': 'Entity',
  '@key': ['sourceId'],
  sourceId: 'example-record-1',
  name: 'Example record',
  url: 'https://example.com/record/1',
});

const action = ActionSchema.parse({
  '@type': 'Action',
  '@key': ['sourceId'],
  sourceId: 'example-action-1',
  object: entity,
});

Each type has a TypeSchema validator and a TypeAndChildrenSchema validator that also accepts its descendants. BaseAndChildrenSchema accepts all declared record types. Validation checks nested identities, property types, URL syntax, and cardinality. Unknown object fields are stripped by Zod; undeclared record types are rejected. Import interfaces such as Entity and Action for static typing; use validators at runtime to enforce identity requirements.

Writing the ontology

chronicle.ttl says what terms mean in OWL, and how records use them in SHACL:

  • A class is an owl:Class. :Base is the disjoint union of :Entity and :Action, so nothing is both.
  • A property is an owl:ObjectProperty when it links to records and an owl:DatatypeProperty when it holds values. :sameAs takes either, so it is an rdf:Property.
  • A property's rdfs:domain and rdfs:range name one term, or several in one owl:unionOf. Don't add a second rdfs:domain or rdfs:range: OWL reads two as both at once, so the generator rejects it.
  • A datatype is an rdfs:Datatype defined over XSD. Each accepts the plain strings that Chronicle JSON becomes in JSON-LD as well as typed values, such as an xsd:dateTime for an instant.
  • A property can name its inverse with :inverseName, such as hasPart on :isPartOf. A record can give the relation under either name, so a whole can list its parts, and only the forward edge is stored. The generated INVERSE_PROPERTIES maps each inverse name to its forward property.
  • A number property can say what form its values take with :metric. A gauge is a level or a rate, such as a heart rate, and combines as a mean, minimum, or maximum. A sum is an amount, such as a number of steps, and adds up. A metric's :unit is the one unit all its values are in, as a UCUM code such as /min. The generated METRIC_PROPERTIES gives each metric's form and unit.
  • A SHACL property shape after a property holds its record rules. sh:maxCount 1 makes it take one value. A shape with sh:targetClass and sh:minCount 1 makes it required on that class.

Generation and compatibility

The generator turns the ontology into TypeScript types and Zod validators. It reads terms through scripts/terms.js, which the schema site also uses. It handles inheritance, including a class with two parents, domains and ranges, and SHACL counts, and it fails for cyclic inheritance, undeclared referenced classes, a property with two domains or ranges, or a metric that isn't a gauge or a sum of numbers in one unit. The package has no persistence metadata, derived effects, or runtime filesystem parser. Generated runtime code depends only on Zod; the TTL is also included in the package.

Schema versions

The npm package follows Chronicle’s shared software version in package.json. The ontology has an independent version declared with owl:versionInfo in chronicle.ttl, exported as the generated SCHEMA_VERSION constant. Software releases can advance without changing the vocabulary version. chronicle.ttl is the only editable ontology; historical versions are preserved by immutable software Git tags named v<package-version> and versioned npm packages. Release notes map software versions to vocabulary versions. The package exports @chronicle.app/schema/chronicle.ttl; the former @chronicle.app/schema/schema.ttl export remains an alias for compatibility.

Use patch releases for description corrections that preserve meaning and minor releases for anything else. Until 1.0, a minor release can rename or remove terms, change what they mean, or add constraints, so data that validates against one minor version may not validate against the next. From 1.0, those changes need a major release. Term identifiers such as https://schema.chronicle.app/Task are unversioned, so don't reuse one for a different concept. Give that concept a new term.

Snapshot paths use the vocabulary version, not the npm package version. The publication convention is https://schema.chronicle.app/releases/<version>/chronicle.ttl, with matching documentation under the same release path. Published snapshots must never be overwritten or removed by later deployments. The unversioned /chronicle.ttl and term pages serve main, so main holds only vocabulary that is ready to publish: work on new terms in a branch until it is ready. Any change to chronicle.ttl after a release must raise owl:versionInfo. While the vocabulary is in flux, npm run schema:check doesn't check this, so run npm run version:check before a release to compare against the latest release tag. See Preparing a release for the release procedure.

The vocabulary is intentionally small. Add terms only when a plugin needs them; regenerate and add focused tests alongside each addition.

Node.js 22.13+. MIT covers the ontology, generator, generated code, and docs; see LICENSE.