@openhi/ours
@openhi/ours is the reference reading of the format in TypeScript: the
resource shapes, a way to assemble an ontology from documents however you
obtained them, and the cross-resource checks a publisher should pass before
publishing.
npm install @openhi/oursIt works with bun, pnpm and yarn equally. The package is ESM, ships its own types, and depends only on zod.
Reading a published ontology
Section titled “Reading a published ontology”import { assembleBundle, wellKnownOntologyUrl } from "@openhi/ours";
const get = (url: string) => fetch(url, { redirect: "follow" }).then((r) => r.json());
const ontology = await get(wellKnownOntologyUrl("example.org"));const models = await get(ontology.models);const vocabularies = await get(ontology.vocabularies);
const bundle = assembleBundle({ ontology, models: [models], vocabularies: [vocabularies] });
for (const model of bundle.models.values()) { console.log(model.name, model.mapsTo?.map((m) => m.schema));}wellKnownOntologyUrl builds the
well-known URI from a domain, so a caller
that has only example.org needs nothing else. Follow redirects: a publisher
who serves the ontology elsewhere redirects from there to its url.
assembleBundle returns maps keyed by URL:
interface OursBundle { readonly ontology: Ontology; readonly models: ReadonlyMap<string, Model>; readonly vocabularies: ReadonlyMap<string, Vocabulary>; readonly schemas: ReadonlyMap<string, JsonSchema>;}Transport is not the library’s business
Section titled “Transport is not the library’s business”assembleBundle takes documents, not URLs or paths. The same ontology may be
read from disk, fetched over HTTP, or built in memory by a generator, and all
three should produce the same object. Fetching is yours to arrange, along with
the retries, caching and authentication that go with it.
One document or many
Section titled “One document or many”A document may be a single resource or a collection Bundle. resourcesIn
unwraps either, so a reader never has to care which a publisher chose:
import { resourcesIn } from "@openhi/ours";
for (const resource of resourcesIn(document)) { // resource is an Ontology, Model or Vocabulary, discriminated on resourceType}parseResource does the same for a document you already know is a single
resource, and throws if it is not an OURS resource at all.
Publishing nothing, explicitly
Section titled “Publishing nothing, explicitly”models, vocabularies and mappings are all required on an Ontology. A
publisher with no vocabularies serves an empty collection at the URL rather
than leaving the pointer out:
import { emptyBundle } from "@openhi/ours";
// GET https://ours.example.org/vocabularies.jsonserve(emptyBundle());An absent URL cannot be told apart from one that has not been published yet, so a consumer would have to guess whether to keep looking. An empty bundle at a live URL is a definite answer, and the point of the format is that nobody should have to ask.
Going back the other way
Section titled “Going back the other way”toPublishedBundles renders an assembled bundle back into the collection
Bundles a publisher serves, which is what a generator emitting an ontology
from some other source of truth wants at the end:
import { toPublishedBundles } from "@openhi/ours";
const { models, vocabularies } = toPublishedBundles(bundle);await Bun.write("public/models.json", JSON.stringify(models, null, 2));await Bun.write("public/vocabularies.json", JSON.stringify(vocabularies, null, 2));Vocabularies as schemas
Section titled “Vocabularies as schemas”Every vocabulary also serialises to an ordinary JSON Schema enumeration,
published beside it as .schema.json. A model binds a property to it with a
plain $ref, so any off-the-shelf validator enforces the codes without knowing
what OURS is.
import { vocabularySchemaFor, vocabularySchemaUrl } from "@openhi/ours";
for (const vocabulary of bundle.vocabularies.values()) { await write(vocabularySchemaUrl(vocabulary), vocabularySchemaFor(vocabulary));}assembleBundle derives these for you and puts them in bundle.schemas, so a
$ref to one resolves during validation without you
registering anything.
- Validating a bundle, and what each check covers.
- API reference for every export.