RDFine /rɪdɪˈfaɪn/
RDF/JS idiomatic, native, enjoyable
About
Idiomatic
RDFine greatly simplifies the manipulation of data in RDF graph (RDF/JS datasets)
by wrapping low-level node handling of triples in plain JavaScript objects.
It is also possible to share the JS-RDF bindings between projects as npm packages.
Native
While plain JS objects are the preferred way to access the graphs, they do not completely
replace the underlying RDF/JS dataset. Both RDFine objects and the dataset can
be modified simultaneously, with changes to one immediately reflected in the other.
Effective
RDFine makes it super easy to bootstrap a triple-backed project without the need
to drink up the RDF Kool-Aid. Novices will use the idiomatic JS interface to get
the job done quickly, while power users still can take advantage of seamless
integration with @rdfjs libraries.
TL;DR; overview
You have RDF triples in an RDF/JS Dataset object
@prefix ex: <http://rdfine.ggg/> .
@prefix schema: <http://schema.org/> .
ex:john a schema:Person ;
schema:name "John Doe" ;
schema:spouse ex:jane ;
schema:nationality [
schema:name "USA"
] .
You want to create a JS object model to access that data
import {schema} from '@tpluscode/rdf-ns-builders'
import { namedNode } from '@rdfjs/data-model'
import { RdfResourceImpl, fromObject } from '@tpluscode/rdfine'
import { loadData } from './data/person'
import { Person, PersonMixin } from './model/Person'
RdfResourceImpl.factory.addMixin(PersonMixin)
const john = RdfResourceImpl.factory.createEntity<Person>({
dataset: await loadData(),
term: namedNode('http://rdfine.ggg/john'),
})
john.nationality = "United States of America"
john.spouse = fromObject({
types: [schema.Person],
name: 'Jane Doe',
})
const dataset = john._selfGraph.dataset
Installation
npm i @tpluscode/rdfine
Also peer @types
dependencies:
npm i --save-dev @types/clownface @types/rdf-ext @types/rdfjs__namespace
Usage
Define resource interfaces
While it is possible to inherit a base resource class, it's best to create partial mixin classes
which implement part of the RDF model. Mixins are dynamically applied to compose a JavaScript object model
closely matching the actual quad data.
import { Constructor, namespace, property, RdfResource } from '@tplusode/rdfine'
import { Term } from 'rdf-js'
import { namedNode } from 'rdf-data-model'
export interface Person extends RdfResource {
name: string
friends: Person[]
}
export function PersonMixin<Base extends Constructor>(base: Base) {
@namespace('http://schema.org/')
class P extends base implements Person {
@property.literal()
name!: string
@property.resource({
path: 'knows',
values: 'array'
})
friends!: Person[]
@property({
path: 'http://www.w3.org/2000/01/rdf-schema#label'
})
label: Term
get hasOccupation(): boolean {
return this._selfGraph
.out(namedNode('http://schema.org/hasOccupation'))
.terms.length > 0
}
}
return P
}
PersonMixin.shouldApply = (res: RdfResource) => {
return res.hasType('http://schema.org/Person')
}
Creating resource instances
Instead of directly creating resource types, which would require deciding up-front, which
mixins to add to the constructed class, a factory can be used.
import { DatasetCore } from 'rdf-js'
import { namedNode } from 'rdf-data-model'
import { RdfResourceImpl } from '@tpluscode/rdfine'
import { Person, PersonMixin } from './Person'
factory.addMixin(PersonMixin)
let dataset: DatasetCore = loadRdfData()
const person = RdfResourceImpl.factory.createEntity<Person>({
dataset,
term: namedNode('http://example.com/gh/tpluscode')
})
Use it!
The setters are immediately reflected in the underlying dataset.
Note that any property can also be set with raw RDF term matching the annotated type
import { namedNode } from 'rdf-data-model'
person.name = "Tomasz"
person.friends = [
namedNode('http://example.com/gh/bergos'),
namedNode('http://example.com/gh/ktk')
] as any
console.log(person._selfGraph.dataset.toString())
The last line above will print triples equivalent to those below
@prefix schema: <http://schema.org/> .
<http://example.com/gh/tpluscode>
schema:name "Tomasz" ;
schema:knows <http://example.com/gh/bergos> , <http://example.com/gh/ktk> .
Using with babel
npm i -D @babel/preset-env @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties
- Configure babel (for example with
.babelrc
)
{
"presets": [
"@babel/env"
],
"plugins": [
[
"@babel/plugin-proposal-decorators",
{
"decoratorsBeforeExport": true
}
],
[
"@babel/plugin-proposal-class-properties"
]
]
}