Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ jobs:
- run: npm i
- run: npx tsc
- run: npm test
- run: npm run check-doc-examples

summary:
if: always()
Expand Down
43 changes: 43 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Deploy Documentation

on:
push:
branches:
- main
pull_request:
workflow_dispatch:
Comment on lines +3 to +8

Copilot AI Mar 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This workflow only runs on push to main, so broken docs can be merged without CI catching it first. Consider adding a pull_request trigger (build-only; no deploy) so mkdocs build --strict runs in PRs before merge.

Copilot uses AI. Check for mistakes.

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: pages
cancel-in-progress: false

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v5
with:
python-version: "3.x"
- run: pip install mkdocs-material
- run: mkdocs build --strict
- if: github.event_name != 'pull_request'
uses: actions/upload-pages-artifact@v3
with:
path: site

deploy:
if: github.event_name != 'pull_request'
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- uses: actions/deploy-pages@v4
id: deployment
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package-lock.json

# Build output
dist
site

# Generic ignore
.local
Expand Down
222 changes: 98 additions & 124 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,197 +5,171 @@

An [RDF/JS](https://rdf.js.org/data-model-spec/) object mapping library.

Full documentation is available at **[rdfjs.github.io/wrapper](https://rdfjs.github.io/wrapper)**.


## Purpose

The purpose of the RDF/JS Wrapper library is to enable idiomatic JavaScript object-oriented programming over RDF with type system support (TypeScript compatible).

In other words, [RDF data](https://en.wikipedia.org/wiki/Resource_Description_Framework) is abstracted away and developers can define standard mapping classes to program over it.

Additionally, standard mapping classes can be defined and reused in any number of context where they are relevant (see for example [@solid/object](https://github.com/solid/object)).


## How To?

### Publish the package

1. Run `npm version major | minor | patch` locally (see [npm-version](https://docs.npmjs.com/cli/v8/commands/npm-version))
1. [Draft a new release](https://github.com/theodi/wrapper/releases)
1. The [Continuous Deployment action](https://github.com/theodi/wrapper/actions/workflows/cd.yml) will be triggered and automatically publish to npm


## Background

RDF/JS Wrapper uses the interfaces described in the [RDF/JS](https://rdf.js.org/) specifications.

Practically, to map RDF to objects, you need to:
1. Write a class or use an existing class that extends TermWrapper
1. Each class needs a Term, a Dataset, and a DataFactory to be instantiated
1. Each class property will have an associated RDF Property (a string, generally a URL, that is defined by an ontology/vocabulary)
1. Each class property will have an associated arity (singular, singular nullable or set)
1. Each class property depending on its type can have:
1. a corresponding ValueMapping to get values, that is translating RDF Terms to JavaScript [primitive values](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Data_structures#primitive_values) (string, number, boolean...)
1. a corresponding TermMapping to set values, that is translating Javascript primitive values to RDF Terms
1. a corresponding ObjectMapping to wrap child objects as a TermWrapper
1. a corresponding ValueMapping and TermMapping for sets of primitive values (both can be an ObjectMapping)
1. Each class mutates the underlying Dataset that is passed to it at instantiation time
[RDF data](https://en.wikipedia.org/wiki/Resource_Description_Framework) is abstracted away so developers can define standard mapping classes to work over it. These classes can be defined once and reused in any context where they are relevant (see for example [@solid/object](https://github.com/solid/object)).


## Wrapping RDF
## Installation

In order to wrap RDF, one needs an underlying data structure. Therefore, both `TermWrapper` and `DatasetWrapper` take an RDF/JS [Dataset](https://rdf.js.org/dataset-spec/#datasetcore-interface) and [Datafactory](https://rdf.js.org/data-model-spec/#datafactory-interface) as constructor parameters.


### Wrapping Terms
```sh
npm install @rdfjs/wrapper
```

Term wrapping lets you manipulate data in a graph via class properties.
Requires Node.js ≥ 24.

A [term](https://www.w3.org/TR/rdf12-concepts/#section-terms) wrapper instantiates a class from a term.

For example you can write a `Person` class with one `name` property:
## Quick Example

```javascript
import { TermWrapper, ValueMapping, TermMapping } from "https://unpkg.com/@rdfjs/wrapper"
import { TermWrapper, ValueMapping, TermMapping } from "@rdfjs/wrapper"

class Person extends TermWrapper {
get name() {
return this.singularNullable("https://example.org/name", ValueMapping.literalToString)
}
get name() {
return this.singularNullable("https://schema.org/name", ValueMapping.literalToString)
}

set name(value) {
this.overwriteNullable("https://example.org/name", value, TermMapping.literalToString)
}
set name(value) {
this.overwriteNullable("https://schema.org/name", value, TermMapping.stringToLiteral)
}
}
```

Assuming the following RDF has been loaded in a dataset `dataset_x`:
Given an RDF dataset containing:

```turtle
PREFIX ex: <https://example.org/>

ex:person1 ex:name "Alice" .
<https://example.org/alice> <https://schema.org/name> "Alice" .
```

Class usage:

```javascript
const person1 = new Person("https://example.org/person1", dataset_x, DataFactory)
const alice = new Person("https://example.org/alice", dataset, DataFactory)

// Get property
console.log(person1.name)
// outputs "Alice"
console.log(alice.name) // "Alice"

// Set property
person1.name = [...person1].reverse().join("")
console.log(person1.name)
// outputs "ecilA"
alice.name = "Alicia"
console.log(alice.name) // "Alicia"
```


### Wrapping Datasets

Dataset wrapping lets you find data in a graph that is meant to be wrapped.
## Wrapping Terms

For example, you can write a `People` dataset wrapper to find each `Person` in a graph:
`TermWrapper` wraps a single RDF resource (a named node or blank node). Properties on the class correspond to RDF predicates via **value mappings** (RDF → JS) and **term mappings** (JS → RDF).

```javascript
class People extends DatasetWrapper {
[Symbol.iterator]() {
return this.subjectsOf("https://example.org/name", Person)
}
}
```
import { TermWrapper, ValueMapping, TermMapping } from "@rdfjs/wrapper"

Assuming the following RDF has been loaded in a dataset `dataset_y`:
class Book extends TermWrapper {
get title() {
return this.singular("https://schema.org/name", ValueMapping.literalToString)
}

```turtle
PREFIX ex: <https://example.org/>
set title(value) {
this.overwrite("https://schema.org/name", value, TermMapping.stringToLiteral)
}

get isbn() {
return this.singularNullable("https://schema.org/isbn", ValueMapping.literalToString)
}

ex:person1 ex:name "Alice" .
ex:person2 ex:name "Bob" .
set isbn(value) {
this.overwriteNullable("https://schema.org/isbn", value, TermMapping.stringToLiteral)
}
}
```

Dataset Wrapper usage:

## Wrapping Datasets

`DatasetWrapper` wraps an entire RDF dataset and lets you query it, returning typed `TermWrapper` instances.

```javascript
const people = new People(dataset_y, DataFactory)
import { DatasetWrapper } from "@rdfjs/wrapper"

class Library extends DatasetWrapper {
get books() {
return this.instancesOf("https://schema.org/Book", Book)
}
}

for (const person of people) {
console.log(person.name)
const library = new Library(dataset, DataFactory)

for (const book of library.books) {
console.log(book.title)
}
// outputs
// Alice
// Bob
```


### Wrapping objects
## Nested Objects

For example you can write a `Person` class with one `name` and one `mum` property:
Use `ObjectMapping.as` to wrap related resources as typed objects:

```javascript
import { TermWrapper, ValueMapping, TermMapping, ObjectMapping } from "https://unpkg.com/@rdfjs/wrapper"

class Person extends TermWrapper {
get name() {
return this.singularNullable("https://example.org/name", ValueMapping.literalToString)
}
import { TermWrapper, ObjectMapping } from "@rdfjs/wrapper"

set name(value) {
this.singularNullable("https://example.org/name", value, TermMapping.literalToString)
}
class Address extends TermWrapper {
// Define address-related mappings here, e.g. street, city, etc.
}

get mum() {
return this.singularNullable("https://example.org/mum", ObjectMapping.as(Person))
}
class Person extends TermWrapper {
get address() {
return this.singularNullable("https://schema.org/address", ObjectMapping.as(Address))
}

set mum(value) {
this.overwriteNullable("https://example.org/mum", value, ObjectMapping.as(Person))
}
set address(value) {
this.overwriteNullable("https://schema.org/address", value, ObjectMapping.as(Address))
}
}
```

Assuming the following RDF has been loaded in a dataset `dataset_z`:

```turtle
PREFIX ex: <https://example.org/>
## Decorators

ex:person1 ex:name "Alice" .
A decorator-based alternative is available for a more declarative style:

ex:person2
ex:name "Bob" ;
ex:mum ex:person2 ;
.
```
```typescript
import { TermWrapper, getter, setter, GetterArity, SetterArity, ValueMapping, TermMapping } from "@rdfjs/wrapper"

Class usage:
class Person extends TermWrapper {
@getter("https://schema.org/name", GetterArity.SingularNullable, ValueMapping.literalToString)
get name(): string | undefined { throw new Error() }

```javascript
const person2 = new Person("https://example.org/person2", dataset_z, DataFactory)

// Get property
console.log(person2.name)
// outputs "Bob"

// Get property from child class
console.log(person2.mum.name)
// outputs "Alice"

// Set class properties
const person3 = new Person("https://example.org/person3", dataset_z, DataFactory)
person3.name = "Joanne"
person1.mum = person3
console.log(person1.mum.name)
// outputs "Joanne"
console.log(person2.mum.mum.name)
// outputs "Joanne"
@setter("https://schema.org/name", SetterArity.SingularNullable, TermMapping.stringToLiteral)
set name(_: string | undefined) {}
}
```


## Documentation

- [Getting Started](https://rdfjs.github.io/wrapper/getting-started/)
- [Wrapping Terms](https://rdfjs.github.io/wrapper/guides/term-wrapper/)
- [Wrapping Datasets](https://rdfjs.github.io/wrapper/guides/dataset-wrapper/)
- [Nested Objects](https://rdfjs.github.io/wrapper/guides/nested-objects/)
- [Sets and Maps](https://rdfjs.github.io/wrapper/guides/sets-and-maps/)
- [RDF Lists](https://rdfjs.github.io/wrapper/guides/rdf-lists/)
- [Decorators](https://rdfjs.github.io/wrapper/guides/decorators/)
- [API Reference](https://rdfjs.github.io/wrapper/api/term-wrapper/)


## See also

- [RDF](https://en.wikipedia.org/wiki/Resource_Description_Framework)
- [Knowledge Graph](https://en.wikipedia.org/wiki/Knowledge_graph)
- [RDF/JS Specifications](https://rdf.js.org/)


## How to Publish

1. Run `npm version major | minor | patch` locally (see [npm-version](https://docs.npmjs.com/cli/v8/commands/npm-version))
1. [Draft a new release](https://github.com/rdfjs/wrapper/releases)
1. The [Continuous Deployment action](https://github.com/rdfjs/wrapper/actions/workflows/cd.yml) will be triggered and automatically publish to npm


## License
Expand Down
Loading
Loading