(This step as a Git commit.)

Now it’s time to start working on some actual functionality: we’ll be making an app that allows people to keep notes. In this step, we’ll prepare their Pod for our data model — much as you might prepare database tables in a traditional application with a relational back-end.

At a high level, we’ll set up the data model as follows:

  1. Check if a Document tracking our notes already exists.
  2. If it doesn’t exist, create it.
  3. Fetch that Document.

So to start with the first step: in which Document should we track notes? The answer can be found in the concept of the Public Type Index.

The Public Type Index is itself a publicly accessible Document stored in the user’s Pod. This Document contains a list of links to other Documents, along with the type of data that is to be included in those Documents. To store notes, the data type we will use is the TextDigitalDocument, defined by Schema.org. Every time the user saves a note, we will store it as a TextDigitalDocument.

If the Document containing these notes was located in the user’s Pod at /public/notes.ttl, their Public Type Index could refer to it like this:

Subject Predicate Object
#notes rdf:type solid:TypeRegistration
#notes solid:forClass schema:TextDigitalDocument
#notes solid:instance /public/notes.ttl

The above Type Index includes one Type Registration, identified by #notes, that registers /public/notes.ttl for the data type schema:TextDigitalDocument.

So how do we find the user’s Public Type Index? It’s usually listed in their profile, i.e. the Document accessible at their WebID:

Subject Predicate Object
#me solid:publicTypeIndex /settings/publicTypeIndex.ttl

This is a pattern you’ll often encounter when writing Solid apps: you start with the user’s WebID, read the profile Document located there, and from there you can find the data you need.

So let’s see what this looks like in code:

import { fetchDocument } from 'tripledoc';
import { solid, schema } from 'rdf-namespaces';

async function getNotesList(profile) {
  /* 1. Check if a Document tracking our notes already exists. */
  const publicTypeIndexRef = profile.getRef(solid.publicTypeIndex);
  const publicTypeIndex = await fetchDocument(publicTypeIndexRef);
  const notesListEntry = publicTypeIndex.findSubject(solid.forClass, schema.TextDigitalDocument);

  /* 2. If it doesn't exist, create it. */
  if (notesListEntry === null) {
    // We will define this function later:
    return initialiseNotesList(profile, publicTypeIndex);
  }

  /* 3. If it does exist, fetch that Document. */
  const notesListRef = notesListEntry.getRef(solid.instance);
  return await fetchDocument(notesListRef);
}

Edit on CodeSandbox

Given the profile we fetched before, we can find a reference to the public Type Index under solid:publicTypeIndex. Then in the public Type Index, we can find the #notes entry by looking for the Subject that has a Statement saying it is for the class schema:TextDigitalDocument.

If that Subject is not found, we initialise a new Document to contain the notes — more on that later. If it is found, we find the URL of the Document that should contain notes under the solid:instance key. All we then have to do is to call fetchDocument to retrieve the Document at that URL.

One thing to keep in mind, again, is that you can make no assumptions about the data in the user’s Pod. Thus, to be safe you should always check whether the return value of getRef() is null. Consider using TypeScript to get a warning when you forget to do so.

That leaves us with one loose thread: the function initialiseNotesList(), which creates a new Document that will contain the notes, and adds it to the Public Type Index. Let’s take a look:

import { createDocument } from 'tripledoc';
import { space, rdf, solid, schema } from 'rdf-namespaces';

async function initialiseNotesList(profile, typeIndex) {
  // Get the root URL of the user's Pod:
  const storage = profile.getRef(space.storage);

  // Decide at what URL within the user's Pod the new Document should be stored:
  const notesListRef = storage + 'public/notes.ttl';
  // Create the new Document:
  const notesList = createDocument(notesListRef);
  await notesList.save();

  // Store a reference to that Document in the public Type Index for `schema:TextDigitalDocument`:
  const typeRegistration = typeIndex.addSubject();
  typeRegistration.addRef(rdf.type, solid.TypeRegistration)
  typeRegistration.addRef(solid.instance, notesList.asRef())
  typeRegistration.addRef(solid.forClass, schema.TextDigitalDocument)
  await typeIndex.save([ typeRegistration ]);

  // And finally, return our newly created (currently empty) notes Document:
  return notesList;
}

Edit on CodeSandbox

(Keep in mind that, in order to write to the user’s Pod, the user will need to have given your app explicit permission to write when they signed in.)

If all the above looks terribly complicated: that’s because it is. Work is underway to make this easier in the future, allowing you to point a library at any data model, and having the library make sure that the user’s Pod is automatically prepared to handle that model. For now though, you’ll have to work through these step-by-step instructions.

Next: Writing data