Cheatsheet
Most examples on these page are for simple operations. For a more extensive comparison bringing many of these operations together, see the example Setting up a data model.
Reading a single value for a property
Tripledoc
import { fetchDocument } from "tripledoc";
async function getName(webId) {
const profileDoc = await fetchDocument(webId);
const profile = profileDoc.getSubject(webId);
return profile.getString('http://xmlns.com/foaf/0.1/name');
}
https://codesandbox.io/s/goofy-darwin-shvq5?fontsize=14
rdflib
import { graph, Fetcher, sym } from "rdflib";
async function getName(webId) {
const store = graph();
const fetcher = new Fetcher(store, {});
await fetcher.load(webId);
const me = sym(webId);
const name = store.any(me, sym('http://xmlns.com/foaf/0.1/name'), null, me.doc());
// Note that this will also return invalid Literal data (integers, dates, etc.)
return (name && name.termType === 'Literal') ? name.value : null;
}
https://codesandbox.io/s/vigilant-napier-i3tf4?fontsize=14
LDflex for Solid
import data from "@solid/query-ldflex";
async function getName(webId) {
// Note that this will also return invalid data (e.g. non-Literals, integers, etc.)
return data[webId].name.value;
}
which is a condensed version of:
import data from "@solid/query-ldflex";
async function getName(webId) {
const person = data[webId];
const name = await person['http://xmlns.com/foaf/0.1/name'];
return (name && name.termType === 'Literal' && name.datatype.value === 'http://www.w3.org/2001/XMLSchema#string')
? name.value
: null;
}
Reading values for multiple properties
Tripledoc
import { fetchDocument } from "tripledoc";
async function getNameAndNick(webId) {
const profileDoc = await fetchDocument(webId);
const profile = profileDoc.getSubject(webId);
return {
name: profile.getString('http://xmlns.com/foaf/0.1/name'),
nick: profile.getString('http://xmlns.com/foaf/0.1/nick'),
};
}
https://codesandbox.io/s/gracious-feather-msref?fontsize=14
rdflib
import { graph, Fetcher, sym } from "rdflib";
async function getName(webId) {
const store = graph();
const fetcher = new Fetcher(store, {});
await fetcher.load(webId);
const me = sym(webId);
const name = store.any(me, sym('http://xmlns.com/foaf/0.1/name'), null, me.doc());
const nick = store.any(me, sym('http://xmlns.com/foaf/0.1/nick'), null, me.doc());
return {
// Note that this will also return invalid Literal data (integers, dates, etc.)
name: (name && name.termType === 'Literal') ? name.value : null,
nick: (nick && nick.termType === 'Literal') ? nick.value : null,
};
}
https://codesandbox.io/s/polished-brook-j03uo?fontsize=14
LDflex for Solid
import data from "@solid/query-ldflex";
async function getNameAndNick(webId) {
return {
// The following two lines will perform just one HTTP request; the response is cached by LDflex.
// Also note that this will also return invalid data (e.g. non-Literals, integers, etc.)
name: await data[webId].name.value,
nick: await data[webId].nick.value,
};
}
which is a condensed version of:
import data from "@solid/query-ldflex";
async function getNameAndNick(webId) {
const person = data[webId];
// The following two lines will perform just one HTTP request; the response is cached by LDflex.
const name = await person['http://xmlns.com/foaf/0.1/name'];
const nick = await person['http://xmlns.com/foaf/0.1/nick'];
return {
name: (name && name.termType === 'Literal' && name.datatype.value === 'http://www.w3.org/2001/XMLSchema#string')
? name.value
: null,
nick: (nick && nick.termType === 'Literal' && nick.datatype.value === 'http://www.w3.org/2001/XMLSchema#string')
? nick.value
: null,
};
}
Reading multiple values for a property
Tripledoc
import { fetchDocument } from "tripledoc";
async function getNicknames(webId) {
const profileDoc = await fetchDocument(webId);
const profile = profileDoc.getSubject(webId);
return profile.getAllStrings('http://xmlns.com/foaf/0.1/nick');
}
https://codesandbox.io/s/peaceful-payne-su5t6?fontsize=14
rdflib
import { graph, Fetcher, sym } from "rdflib";
async function getNicknames(webId) {
const store = graph();
const fetcher = new Fetcher(store, {});
await fetcher.load(webId);
const me = sym(webId);
const nicknames = store.each(me, sym('http://xmlns.com/foaf/0.1/nick'), null, me.doc());
return nicknames
// Note that this will also return invalid Literal data (integers, dates, etc.)
.filter(node => node.termType === "Literal")
.map(nickname => nickname.value);
}
https://codesandbox.io/s/festive-currying-z6s3n?fontsize=14
LDflex for Solid
import data from "@solid/query-ldflex";
async function getNicknames(webId) {
// Note that this will also return invalid data (e.g. non-Literals, integers, etc.)
return data[webId].nick.values;
}
which is a condensed version of:
import data from "@solid/query-ldflex";
async function getNicknames(webId) {
const person = data[webId];
const nicknames = [];
for await (const nickname of person['http://xmlns.com/foaf/0.1/nick']) {
nicknames.push(nickname);
}
return nicknames
.filter(node => node.termType === 'Literal' && node.datatype.value === 'http://www.w3.org/2001/XMLSchema#string')
.map(nickname => nickname.value);
}
Adding multiple literals for the same property
Note: the examples below assume the user is logged in and is allowed to write to their profile.
Tripledoc
import { fetchDocument } from "tripledoc";
async function addNicknames(webId, nicknames) {
const profileDoc = await fetchDocument(webId);
const profile = profileDoc.getSubject(webId);
nicknames.forEach((nickname) => profile.addString('http://xmlns.com/foaf/0.1/nick', nickname));
await webIdDoc.save();
}
rdflib
import { graph, Fetcher, sym, UpdateManager, Literal } from "rdflib";
async function addNicknames(webId, nicknames) {
const store = graph();
const fetcher = new Fetcher(store, {});
await fetcher.load(currentSession.webId);
const me = sym(currentSession.webId);
const updater = new UpdateManager(store);
const updatePromise = new Promise((resolve) => {
const deletions = [];
const additions = nicknames.map(nickname => st(me, sym('http://xmlns.com/foaf/0.1/nick'), new Literal(nickname), me.doc()));
updater.update(deletions, additions, resolve);
});
await updatePromise;
}
LDflex for Solid
import data from "@solid/query-ldflex";
async function addNicknames(webId, nicknames) {
return data[webId].nick.add(...nicknames);
}
which is a condensed version of:
import data from "@solid/query-ldflex";
import { literal } from "@rdfjs/data-model";
async function addNicknames(webId, nicknames) {
const person = data[webId];
await person['http://xmlns.com/foaf/0.1/nick'].add(...nicknames.map(nickname => literal(nickname)));
}
Adding values for multiple properties
Note: the examples below assume the user is logged in and is allowed to write to their profile.
Tripledoc
import { fetchDocument } from "tripledoc";
async function addNameAndNickname(webId, name, nickname) {
const profileDoc = await fetchDocument(webId);
const profile = profileDoc.getSubject(webId);
profile.addString('http://xmlns.com/foaf/0.1/name', name);
profile.addString('http://xmlns.com/foaf/0.1/nick', nickname);
await webIdDoc.save();
}
rdflib
import { graph, Fetcher, sym, UpdateManager, Literal } from "rdflib";
async function addNameAndNickname(webId, name, nickname) {
const store = graph();
const fetcher = new Fetcher(store, {});
await fetcher.load(currentSession.webId);
const me = sym(currentSession.webId);
const updater = new UpdateManager(store);
const updatePromise = new Promise((resolve) => {
const deletions = [];
const additions = [
st(me, sym('http://xmlns.com/foaf/0.1/name'), new Literal(name), me.doc()),
st(me, sym('http://xmlns.com/foaf/0.1/nick'), new Literal(nickname), me.doc()),
];
updater.update(deletions, additions, resolve);
});
await updatePromise;
}
LDflex for Solid
import data from "@solid/query-ldflex";
async function addNameAndNickname(webId, name, nickname) {
// Note: this will execute two HTTP requests instead of one:
await data[webId].name.add(name);
await data[webId].nick.add(nickname);
}
which is a condensed version of:
import data from "@solid/query-ldflex";
import { literal } from "@rdfjs/data-model";
async function addNameAndNickname(webId, name, nickname) {
const person = data[webId];
// Note: this will execute two HTTP requests instead of one:
await person['http://xmlns.com/foaf/0.1/name'].add(literal(name));
await person['http://xmlns.com/foaf/0.1/nick'].add(literal(nickname));
}
Replacing existing values with new ones
Note: the examples below assume the user is logged in and is allowed to write to their profile.
Tripledoc
import { fetchDocument } from "tripledoc";
async function setNicknames(webId, nicknames) {
const profileDoc = await fetchDocument(webId);
const profile = profileDoc.getSubject(webId);
profile.removeAll('http://xmlns.com/foaf/0.1/nick');
nicknames.forEach((nickname) => profile.addString('http://xmlns.com/foaf/0.1/nick', nickname));
await webIdDoc.save();
}
rdflib
import { graph, Fetcher, sym, UpdateManager, Literal } from "rdflib";
async function setNicknames(webId, nicknames) {
const store = graph();
const fetcher = new Fetcher(store, {});
await fetcher.load(currentSession.webId);
const me = sym(currentSession.webId);
const updater = new UpdateManager(store);
const updatePromise = new Promise((resolve) => {
const deletions = store.statementsMatching(me, sym('http://xmlns.com/foaf/0.1/nick'), null, me.doc());
const additions = nicknames.map(nickname => st(me, sym('http://xmlns.com/foaf/0.1/nick'), new Literal(nickname), me.doc()));
updater.update(deletions, additions, resolve);
});
await updatePromise;
}
LDflex for Solid
import data from "@solid/query-ldflex";
async function setNicknames(webId, nicknames) {
return data[webId].nick.set(...nicknames);
}
which is a condensed version of:
import data from "@solid/query-ldflex";
import { literal } from "@rdfjs/data-model";
async function setNicknames(webId, nicknames) {
const person = data[webId];
await person['http://xmlns.com/foaf/0.1/nick'].set(...nicknames.map(nickname => literal(nickname)));
}
Removing all values for a property
Note: the examples below assume the user is logged in and is allowed to write to their profile.
Tripledoc
import { fetchDocument } from "tripledoc";
async function removeNicknames(webId) {
const profileDoc = await fetchDocument(webId);
const profile = profileDoc.getSubject(webId);
profile.removeAll('http://xmlns.com/foaf/0.1/nick');
await webIdDoc.save();
}
rdflib
import { graph, Fetcher, sym, UpdateManager } from "rdflib";
async function removeNicknames(webId) {
const store = graph();
const fetcher = new Fetcher(store, {});
await fetcher.load(currentSession.webId);
const me = sym(currentSession.webId);
const updater = new UpdateManager(store);
const updatePromise = new Promise((resolve) => {
const deletions = store.statementsMatching(me, sym('http://xmlns.com/foaf/0.1/nick'), null, me.doc());
const additions = [];
updater.update(deletions, additions, resolve);
});
await updatePromise;
}
LDflex for Solid
import data from "@solid/query-ldflex";
async function removeNicknames(webId) {
return data[webId].nick.delete();
}
which is a condensed version of:
import data from "@solid/query-ldflex";
async function removeNicknames(webId) {
const person = data[webId];
await person['http://xmlns.com/foaf/0.1/nick'].delete();
}
Removing a single specific value for a property
Note: the examples below assume the user is logged in and is allowed to write to their profile.
Tripledoc
import { fetchDocument } from "tripledoc";
async function removeNickname(webId, nickname) {
const profileDoc = await fetchDocument(webId);
const profile = profileDoc.getSubject(webId);
profile.removeString('http://xmlns.com/foaf/0.1/nick', nickname);
await webIdDoc.save();
}
rdflib
import { graph, Fetcher, sym, UpdateManager, Literal } from "rdflib";
async function removeNickname(webId, nickname) {
const store = graph();
const fetcher = new Fetcher(store, {});
await fetcher.load(currentSession.webId);
const me = sym(currentSession.webId);
const updater = new UpdateManager(store);
const updatePromise = new Promise((resolve) => {
const deletions = store.statementsMatching(me, sym('http://xmlns.com/foaf/0.1/nick'), Literal(nickname), me.doc());
const additions = [];
updater.update(deletions, additions, resolve);
});
await updatePromise;
}
LDflex for Solid
import data from "@solid/query-ldflex";
async function removeNickname(webId, nickname) {
return data[webId].nick.delete(nickname);
}
which is a condensed version of:
import data from "@solid/query-ldflex";
import { literal } from "@rdfjs/data-model";
async function removeNickname(webId, nickname) {
const person = data[webId];
await person['http://xmlns.com/foaf/0.1/nick'].delete(literal(nickname));
}
Create a new Document
Note: the examples below assume the user is logged in and is allowed to write to their profile.
Tripledoc
import { createDocument } from "tripledoc";
async function createEmptyDocument(location) {
const document = createDocument(location);
await document.save();
}
rdflib
import { graph, sym, UpdateManager } from "rdflib";
async function createEmptyDocument(location) {
const store = graph();
const updater = new UpdateManager(store);
const creationPromise = new Promise((resolve, reject) => {
updater.put(sym(location), [], 'text/turtle', (_url, success, message) => {
if (success) {
resolve();
} else {
reject(new Error(message));
}
});
});
await creationPromise;
}
LDflex for Solid
import data from "@solid/query-ldflex";
async function createEmptyDocument(location) {
return data[location].put();
}
Setting up a data model
The following examples bring together many different concepts used to perform a common operation:
finding a Document to store data of a specific type in (in this case schema:TextDigitalDocument
),
creating it if it doesn't exist.
At a high level, each example performs the following tasks:
- On the user's profile, find a reference to their Public Type Index, and fetch that Document.
- In that type index, see if there's a type registration for the type
schema:TextDigitalDocument
. If there is, return the reference listed as itssolid:instance
. - If no such type registration exists, find a reference to the user's storage root on their
profile. In that root, create a new Document. Then, add a type registration to the Public Type
Index referring to that Document as its
solid:instance
.
(This might look familiar — indeed, it's the steps described in the documentation on setting up your data model.)
Tripledoc
Note that there's a companion project to Tripledoc called Plandoc that streamlines the fetching and creation of Documents. An example of an alternative approach using Plandoc can be found after this one.
import { fetchDocument, createDocumentInContainer } from "tripledoc";
import { solid, schema, space, rdf } from "rdf-namespaces";
async function getReviewDocUrl(webId) {
// 1. Find the Public Type Index, then fetch it.
const profileDocument = await fetchDocument(webId);
const profile = profileDocument.getSubject(webId);
const publicTypeIndexRef = profile.getRef(solid.publicTypeIndex);
if (typeof publicTypeIndexRef !== "string") {
return null;
}
const publicTypeIndexDocument = await fetchDocument(publicTypeIndexRef);
// 2. If there is a type registration for TextDigitalDocuments, return the instance reference.
const existingTypeRegistration = publicTypeIndexDocument.findSubject(
solid.forClass,
schema.TextDigitalDocument
);
if (existingTypeRegistration) {
return existingTypeRegistration.getRef(solid.instance);
} else {
// 3. If no type registration exists, create a new Document in the storage root, and register it
// for TextDigitalDocuments.
const storageRef = profile.getRef(space.storage);
const document = createDocumentInContainer(storageRef);
const newDocument = await document.save();
const newTypeRegistration = publicTypeIndexDocument.addSubject();
newTypeRegistration.addRef(rdf.type, solid.TypeRegistration);
newTypeRegistration.addRef(solid.instance, newDocument.asRef());
newTypeRegistration.addRef(solid.forClass, schema.TextDigitalDocument);
publicTypeIndexDocument.save([newTypeRegistration]);
return newDocument.asRef();
}
}
Tripledoc with Plandoc
import { fetchDocument, describeSubject, describeContainer, describeDocument } from "plandoc";
import { solid, schema, space, rdf } from "rdf-namespaces";
async function getReviewDocUrl(webId) {
// 1. Tell Plandoc where the public type index can be found
const virtualProfile = describeSubject().isFoundAt(webId);
const virtualStorage = describeContainer().isFoundOn(virtualProfile, space.storage);
const virtualPublicTypeIndex = describeDocument().isFoundOn(virtualProfile, solid.publicTypeIndex);
// 2. Tell Plandoc what the type registration should look like,
// 3. and that it should be created if it does not exist yet (`isEnsured…`).
const virtualNotesTypeRegistration = describeSubject()
.isEnsuredIn(virtualPublicTypeIndex)
.withRef(rdf.type, solid.TypeRegistration)
.withRef(solid.forClass, schema.TextDigitalDocument);
const virtualNotesDoc = describeDocument()
.isEnsuredOn(virtualNotesTypeRegistration, solid.instance, virtualStorage);
// Let Plandoc fetch the described Documents, creating everything that's needed:
const notesDoc = await fetchDocument(virtualNotesDoc);
return notesDoc.asRef();
}
rdflib
Still to do — contributions welcome.
LDflex for Solid
import data from "@solid/query-ldflex";
import { namedNode } from "@rdfjs/data-model";
import { solid, schema } from "rdf-namespaces";
async function getReviewDocUrl(webId) {
// 1. Find the Public Type Index, then fetch it.
const publicTypeIndex = await data[webId].publicTypeIndex;
// 2. If there is a type registration for TextDigitalDocuments, return the instance reference.
let notesDoc = undefined;
for await (const typeRegistration of data[publicTypeIndex].subjects) {
const forClass = await data[typeRegistration.value][solid.forClass];
if (forClass && forClass.value === schema.TextDigitalDocument) {
notesDoc = await typeRegistration[solid.instance];
break;
}
}
if (notesDoc) {
return notesDoc.value;
} else {
// 3. If no type registration exists, create a new Document in the storage root, and register it
// for TextDigitalDocuments.
const storageContainer = await data[webId].storage;
// Generate a random name for the Document that is to contain the Reviews:
const documentName = Math.random().toString().substring(2);
const newDocumentUrl = new URL(documentName, storageContainer.value).href;
// Create a dummy value, then remove it, to create the Document:
await data[newDocumentUrl].name.add('Dummy value');
await data[newDocumentUrl].name.delete();
// Generate a random name for the Subject that links from the Public Type Index
// to the Document that is to contain the Reviews:
const typeRegistrationName = new URL(
'#' + Math.random().toString().substring(2),
publicTypeIndex.value
).href;
await data[typeRegistrationName][solid.forClass].set(schema.TextDigitalDocument)
await data[typeRegistrationName][solid.instance].set(namedNode(newDocumentUrl));
await data[typeRegistrationName].type.set(solid.TypeRegistration);
return newDocumentUrl;
}
}