Skip to content

muze-nl/oldm

Repository files navigation

OLDM: Object Linked Data Mapper

OLDM is now structured as a small npm-workspaces monorepo. The split keeps the core object mapping package tree-shakeable and dependency-light, while preserving a beginner-friendly package that exposes a single oldm object and installs globalThis.oldm for browser use.

Packages

Package Role
@muze-nl/oldm-core Core object/graph mapping, helpers, and classes. Explicit ESM exports only. No N3 dependency and no global side effects.
@muze-nl/oldm-n3 N3 parser/writer adapter for OLDM. Depends on n3 and @muze-nl/oldm-core.
@muze-nl/oldm Beginner-friendly package. Exports one default oldm object, provides default N3 parser/writer wiring, browser bundles, and sets globalThis.oldm.

Installation

For the friendly default package:

npm install @muze-nl/oldm
import oldm from '@muze-nl/oldm'

const context = oldm.context()

For explicit, tree-shakeable use:

npm install @muze-nl/oldm-core @muze-nl/oldm-n3
import oldm, { one, many, Collection } from '@muze-nl/oldm-core'
import { n3Parser, n3PatchWriter, n3Writer } from '@muze-nl/oldm-n3'

const context = oldm({
  parser: n3Parser,
  writer: n3Writer,
  patchWriter: n3PatchWriter
})

Prefix preference

OLDM shortens predicate and type IRIs with the prefixes configured on the context. Prefix declarations found in Turtle input are parser conveniences; they do not decide the JavaScript property names exposed by OLDM.

When multiple prefixes point at the same namespace, client-provided prefixes are preferred over OLDM defaults, and defaults are preferred over prefixes found in a parsed source document. For example, both pim: and space: are common aliases for http://www.w3.org/ns/pim/space#. Since OLDM prefers space for that namespace, profile data using either pim:storage or space:storage is exposed as space$storage in JavaScript.

const context = oldm({
  parser: n3Parser,
  prefixes: {
    space: 'http://www.w3.org/ns/pim/space#'
  }
})

const profile = context.parse(turtle, profileUrl, 'text/turtle')
const me = profile.subjects[`${profileUrl}#me`]

console.log(me.space$storage.id)

Multiple graphs in one context

A context keeps a registry of every parsed graph. Each context.parse() call still returns a Graph for the parsed resource, while the context exposes a combined view over all graphs loaded into that context.

const context = oldm.context()

const profile = context.parse(profileTurtle, profileUrl, 'text/turtle')
const settings = context.parse(settingsTurtle, settingsUrl, 'text/turtle')

profile.get(`${profileUrl}#me`)       // data from only the profile graph
context.get(`${profileUrl}#me`)       // merged data from all graphs
context.graphs                        // [profile, settings]
context.graph(profileUrl)             // profile
context.data                          // combined subject list
context.subjects                      // combined subject map by full URI
context.sources(context.get(`${profileUrl}#me`))
// [profile, settings] when both graphs contain data for that subject
context.sources(context.get(`${profileUrl}#me`), 'vcard$fn')
// [profile] when only the profile graph contains that property

The combined view merges named subjects by IRI and keeps the original graph views separate. context.sources(subject, predicate, value) can be used to inspect which graph contributed a subject, property, or specific property value.

For source-aware writes, use the graph-specific helpers when you know the resource you want to edit:

profile.set(`${profileUrl}#me`, 'vcard$fn', 'Auke')
profile.add(`${profileUrl}#me`, 'schema$knowsAbout', 'Linked Data')
profile.delete(`${profileUrl}#me`, 'schema$knowsAbout', 'Old value')

Or use context-level helpers with an explicit graph:

context.set(`${profileUrl}#me`, 'vcard$fn', 'Auke', { graph: profile })
context.add(`${profileUrl}#me`, 'schema$knowsAbout', 'Solid', { graph: profileUrl })

When no graph is passed, context.set/add/delete() uses a conservative default: the subject's exact graph URL, the subject document URL without a fragment, the only graph that currently contains the subject, the configured defaultGraph, or the only graph in the context. Direct property assignment on a named subject from context.get(...) uses the same resolver, so simple edits can stay object-like:

const me = context.get(`${profileUrl}#me`)
me.vcard$fn = 'Auke'
delete me.vcard$nickname

If there is no obvious source graph, OLDM throws and asks you to choose one explicitly with context.set/add/delete(..., { graph }) or graph.set/add/delete(...).

Solid PATCH output

When a context has a patch writer, graph.patch() returns a Solid N3 Patch string for the changes between the original parsed source and the graph's current Turtle output.

const profile = context.parse(profileTurtle, profileUrl, 'text/turtle')
profile.set(`${profileUrl}#me`, 'vcard$fn', 'Auke C.')

const patch = await profile.patch()
await fetch(profileUrl, {
  method: 'PATCH',
  headers: { 'Content-Type': 'text/n3' },
  body: patch
})

The default @muze-nl/oldm package wires in the N3 patch writer. Direct @muze-nl/oldm-core users can pass patchWriter: n3PatchWriter.

Owned anonymous values are handled conservatively. Fresh blank nodes and RDF collections can be inserted. Changing or deleting an existing owned blank node or collection replaces the named-subject edge into that anonymous value and the complete old anonymous closure. Shared or cyclic anonymous values are rejected so callers can fall back to full document replacement.

Browser bundles

The friendly package builds browser bundles into packages/oldm/dist/: an ESM bundle for module scripts and a classic global IIFE bundle for plain script tags.

For modern module scripts:

<script type="module">
  import oldm from 'https://cdn.jsdelivr.net/npm/@muze-nl/oldm/dist/oldm.min.js'

  const context = oldm.context()
</script>

For a classic script tag that creates globalThis.oldm:

<script src="https://cdn.jsdelivr.net/npm/@muze-nl/oldm/dist/oldm.global.min.js"></script>
<script>
  const context = oldm.context()
</script>

Development

Install dependencies from the repository root:

npm install

Run all package tests:

npm test

Build the browser bundles for @muze-nl/oldm:

npm run build-dev
npm run build

Run coverage for all workspaces:

npm run coverage

Example

import oldm from '@muze-nl/oldm'

const context = oldm.context({
  prefixes: {
    schema: 'https://schema.org/',
    vcard: 'http://www.w3.org/2006/vcard/ns#',
    foaf: 'http://xmlns.com/foaf/0.1/'
  }
})

const url = 'https://example.org/profile/card#me'
const response = await fetch(url)
const text = await response.text()
const source = context.parse(text, url, 'text/turtle')

console.log(source.primary.vcard$fn)

License

MIT. See LICENSE.

Turtle parser investigation

An experimental small Turtle 1.1 parser/writer adapter lives in packages/oldm-turtle as @muze-labs/oldm-turtle. It is not the default yet; see docs/TURTLE_PARSER_INVESTIGATION.md for supported syntax, size comparison, and small Solid-style benchmark results.

About

Object - Linked Data Mapper

Resources

Contributing

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages