LDflex is a domain-specific language for querying Linked Data on the Web as if you were browsing a local JavaScript graph.
You can write things like person.friends.firstName
to get a list of your friends.
Thanks to the power of JSON-LD contexts
and JavaScript's Proxy,
these properties are not hard-coded in LDflex,
but can be chosen at runtime.
They feel as if you're traversing a local object,
while you're actually querying the Web—without
pulling in all data first.
Tim Berners-Lee came up with the idea for such a fluid JavaScript interface to Linked Data, in a discussion on how to make Linked Data easier for developers.
- Tutorial slides and walkthrough
- Cheatsheet
- Designing a Linked Data developer experience, discussing the design of LDflex
- Solid Chess, an app built with LDflex
npm install ldflex
In order to execute queries, you will also need a query engine:
npm install @ldflex/comunica
When you have obtained a starting subject, you can navigate through its properties using standard JavaScript dot property syntax.
In order to query for the result,
use await
if you want a single value,
or for await
to iterate over all values.
const { PathFactory } = require('ldflex');
const { default: ComunicaEngine } = require('@ldflex/comunica');
const { namedNode } = require('@rdfjs/data-model');
// The JSON-LD context for resolving properties
const context = {
"@context": {
"@vocab": "http://xmlns.com/foaf/0.1/",
"friends": "knows",
"label": "http://www.w3.org/2000/01/rdf-schema#label",
"rbn": "https://ruben.verborgh.org/profile/#"
}
};
// The query engine and its source
const queryEngine = new ComunicaEngine('https://ruben.verborgh.org/profile/');
// The object that can create new paths
const path = new PathFactory({ context, queryEngine });
const ruben = path.create({ subject: namedNode('https://ruben.verborgh.org/profile/#me') });
showPerson(ruben);
async function showPerson(person) {
console.log(`This person is ${await person.name}`);
console.log(`${await person.givenName} is interested in:`);
for await (const name of person.interest.label)
console.log(`- ${name}`);
console.log(`${await person.givenName} is friends with:`);
for await (const name of person.friends.givenName)
console.log(`- ${name}`);
}
(async person => {
console.log(await person.friends.givenName.pathExpression);
})(ruben);
(async document => {
for await (const subject of document.subjects)
console.log(`${subject}`);
})(ruben);
(async subject => {
for await (const property of subject.properties)
console.log(`${property}`);
})(ruben);
(async person => {
console.log(await person.friends.givenName.sparql);
})(ruben);
(async person => {
for await (const uri of person.interest.sort('label'))
console.log(`- ${uri}`);
})(ruben);
The sort function takes multiple arguments,
creating a path that sorts on the last argument.
The path can also continue after the sort:
person.friends.sort('country', 'label').givenName
will sort the friends based on the label of their country,
and then return their names.
// Add a new value
await person['http://xmlns.com/foaf/0.1/name'].add(literal(name));
await person['http://xmlns.com/foaf/0.1/nick'].add(literal(nickname));
// Set a new value and override existing values
await person['http://xmlns.com/foaf/0.1/name'].set(literal(name));
await person['http://xmlns.com/foaf/0.1/nick'].set(literal(nickname));
// Delete object values
await person['http://xmlns.com/foaf/0.1/name'].delete();
await person['http://xmlns.com/foaf/0.1/nick'].delete();
// Replace object values
await person['http://xmlns.com/foaf/0.1/name'].replace(literal(oldName), literal(name));
Handle rdf:List
, rdf:Bag
, rdf:Alt
, rdf:Seq
and rdf:Container
.
For rdf:List
s
(async publication => {
// Returns an Array of Authors
const authors = await publication['bibo:authorList'].list();
})(ordonez_medellin_2014);
For rdf:Alt
, rdf:Seq
and rdf:Container
s
(async data => {
// Returns an Array of elements
const elements = await data['ex:myContainer'].container();
})(data);
For rdf:Bag
s
(async data => {
// Returns a Set of elements
const elements = await data['ex:myBag'].containerAsSet();
})(data);
Alternatively, .collection
can be used for any collection (i.e. rdf:List
, rdf:Bag
, rdf:Alt
, rdf:Seq
and rdf:Container
) provided the collection has the correct rdf:type
annotation in the data source
(async publication => {
// Returns an Array of Authors
const authors = await publication['bibo:authorList'].collection();
})(ordonez_medellin_2014);
ruben.namespace // 'https://ruben.verborgh.org/profile/#'
ruben.fragment // 'me'
await ruben.prefix // 'rbn'
The following libraries provide handlers that extend the functionality of LDflex:
- async-iteration-handlers Provides methods such as
.map
,.filter
and.reduce
for the async-iterable results returned by LDflex.
©2018–present Ruben Verborgh, Ruben Taelman. MIT License.