Read/Write Data

This page provides information on reading and writing structured data stored in Solid Pods. For information on reading and writing files (e.g., .jpg or .json) stored in Solid Pods, see Read/Write Files.

When accessing data with solid-client,

  • A Thing refers to a data entity; e.g., a person. Each Thing’s URL acts as its identifier.

    The use of the Thing’s URL facilitates interoperability. That is, to combine data, you can use a Thing’s URL to link to other data.

  • A SolidDataset is a set of Things. Each SolidDataset’s URL acts as its identifier.

    Typically, all Things in a SolidDataset have URLs relative to the SolidDataset’s URL.

From a Thing, you can access specific properties (i.e., data) about the Thing. For example, if a Thing represents a person’s contact information, specific properties for a person may include the person’s name, email addresses, etc. Each property’s URL acts as its identifier.

Vocabulary

To encourage interoperability, existing Vocabularies define reusable terms describing common data (e.g., name, title, address, url). For example, http://www.w3.org/2006/vcard/ns#fn is part of the vCard Vocabulary.

Inrupt’s vocab-common-rdf library bundles constants that refer to terms in popular pre-existing vocabularies. Applications can use these constants to read and write data. For more information on Vocabulary usage, see Vocabulary.

Prerequisite for Restricted Data

By default, solid-client only enables access to public data on Solid Pods. To access other data, you must authenticate as a user who has been granted appropriate access to that data. For example, to write to a resource, the user must have Write Access on the Resource.

To authenticate, you can use the Inrupt solid-client-authn-browser library.

Note

Use solid-client-authn-browser for client-side code only.

After authentication, the Session object obtained from solid-client-authn-browser provides a fetch function. You can pass this function as an option to solid-client functions to include the user’s credentials with a request.

import { Session } from '@inrupt/solid-client-authn-browser'
import { getSolidDataset, saveSolidDatasetAt } from "@inrupt/solid-client";

async function loginAndFetch() {
  // 1. Build a session
  const session = new Session();

  // 2. Check if the page is being loaded after a redirect from the Identity
  // Provider. If it is the case, the login process is complete. The following
  // collects information in the URL where the Identity Provider redirected
  // the user, and updates session.info accordingly.
  // Remember: a session **cannot** be logged in before calling this.
  await session.handleIncomingRedirect(window.location.href);

  // 3. Start the Login Process if not already logged in.
  if (!session.info.isLoggedIn) {
    // Redirect the user to their identity provider:
    // (This **moves the user away from the current page**.)
    await session.login({
      // Specify the URL of the user's OIDC issuer.
      oidcIssuer: 'https://inrupt.net',
      // Specify the URL the system should redirect to after login,
      // e.g. the current page for a single-page app.
      redirectUrl: window.location.href,
    });
  }

  // 4. Make authenticated requests by passing `session.fetch` to solid-client functions.
  // The user must have logged in as someone with the appropriate access to the specified URL.
  
  // For example, the user must be someone with Read access to the specified URL.
  const myDataset = await getSolidDataset(
    "https://docs-example.inrupt.net/profile/card", {
    fetch: session.fetch 
  });

  // ...
  
  // For example, the user must be someone with Write access to the specified URL.
  const savedSolidDataset = await saveSolidDatasetAt(
    "https://docs-example.inrupt.net/profile/card",
    myChangedDataset, {
    fetch: session.fetch 
  });
}

loginAndFetch();

For more information on access management, see Manage Access to Data (WAC).

Read Data

To read data with solid-client,

  1. You first use getSolidDataset to fetch the dataset from which you want to read your data.

  2. Then, use either:

    • getThing to get a single data entity from the dataset, or

    • getThingAll to get all data entities from the dataset.

  3. Then, from the data entity, you can get specific data. For a list of the get functions, see thing/get module.

0. Import

Import from the client libraries those objects to be used in the example:

import {
  getSolidDataset,
  getThing,
  getStringNoLocale,
  getUrlAll
} from "@inrupt/solid-client";

import { FOAF, VCARD } from "@inrupt/vocab-common-rdf";

1. Fetch the Dataset

To access data, first fetch the dataset (SolidDataset) that contains the data. To fetch a SolidDataset, pass its URL to getSolidDataset as in the following example:

const myDataset = await getSolidDataset(
  "https://docs-example.inrupt.net/profile/card"
);

The examples retrieves a SolidDataset that is accessible by the public. To make an authenticated request for a SolidDataset that requires the user to have read access, pass in the authenticated Session’s fetch function as well. For an example, see the Prerequisite section.

2. Get the Data Entity Thing

From the fetched dataset, you can use either:

  • getThing with the Thing’s URL to get a single data entity, or

  • getThingAll to get all data entities from the dataset.

The following passes in a example entity’s URL to getThing to retrieve the entity, specifically the profile, from the previously fetched dataset.

const profile = getThing(
  myDataset,
  "https://docs-example.inrupt.net/profile/card#me"
);

3. Read Data Attribute of a Thing

A property (i.e. data) about a Thing is identified by a URL. A property can have zero, one or more values, and the value is typed; e.g., a string, an integer, or an URL if pointing to other Things.

Note

These types are specific to Solid, i.e. they are not JavaScript types. This is the reason there is a distinction between integers and decimals, and why there is a separate type for URLs.

To encourage interoperability, various Vocabularies exist to identify common data. For example:

  • http://www.w3.org/2006/vcard/ns#fn is part of the vCard Vocabulary. http://www.w3.org/2006/vcard/ns#fn is understood to be a string representing a formatted name.

  • http://xmlns.com/foaf/0.1/knows is part of the Friend of a Friend (FOAF) Vocabulary. http://xmlns.com/foaf/0.1/knows is understood to be a URL and a Thing can have more than one knows relationship.

To access data, use the appropriate get function depending on the data type and the number of values for the property. Pass the function the URL that identifies the property to fetch.

From the retrieved profile (i.e. the Thing), the following operations gets the following properties:

  • http://www.w3.org/2006/vcard/ns#fn

  • http://xmlns.com/foaf/0.1/knows.

For simplicity, the example uses the corresponding constants from the @inrupt/vocab-common-rdf library for the URLs.

// 3. Retrieve specific data from the retrieved thing; i.e. profile.

// 3a. Get a single string data value from the profile.
// Specifically, the operation returns the profile's VCARD.fn 
// (i.e., "http://www.w3.org/2006/vcard/ns#fn") value as a string.

const fn = getStringNoLocale(profile, VCARD.fn);

// 3b. Get multiple URL data from the profile.
// Specifically , the operation returns the profile's FOAF.knows
// (i.e., "http://xmlns.com/foaf/0.1/knows") values as an array of URLs. 
// The URLs point to the Things representing Persons.

const acquaintances = getUrlAll(profile, FOAF.knows);

For a list of the get functions, see the thing/get.

Write Data

Write Access

To write data to a Pod requires that the user have appropriate access. That is, to identify the user as someone with the appropriate access, the user must first authenticate. To make an authenticated requests (such as read and write), pass in the authenticated Session’s fetch function to the various read/write functions.

For an example, see the Prerequisite section.

To write data with solid-client,

  1. Create a data entity (i.e., a Thing) with the updates.

    1. Start with a Thing to modify.

      • Use getThing to start with an existing data entity, or

      • Use createThing to create a new data entity.

    2. With this Thing as a starting point, use the following functions to create a new Thing with the modifications:

      • thing/add functions to return a new Thing modified with new data added,

      • thing/set functions to return a new Thing modified with the specified data, and

      • thing/remove functions return a new Thing modified with specified data removed.

  2. Use setThing to return a new dataset with the updated Thing.

  3. Use saveSolidDataSetAt to save the dataset to the Pod and return a new SolidDataSet that is

Important

  • The solid-client library’s functions (such as the various add/set/remove functions) do not modify the objects that are passed in as arguments. Instead, the library’s functions return a new object with the requested changes, and do not modify the passed-in objects.

  • A SolidDataset keeps track of the data changes compared to the data in the Pod. For set operations on exisitng properties, the changelog tracks both the old value and new values of the property being modified.

    The save operation applies the changes from the changelog to the current SolidDataset. If the old value specified in the changelog does not correspond to the value currently in the Pod, the save operation will error.

Assumptions

The following examples assumes:

  • The application has used the solid-client-authn-browser library to handle login and has a Session object session.

  • The logged-in user has permission to read and write the specified SolidDataset.

See the Prerequisite section for details on obtaining an authenticated session.

0. Import

Import from the client libraries those objects to be used in the example:

import {
  getSolidDataset,
  getThing
  setStringNoLocale,
  addStringNoLocale
  setThing,
  saveSolidDatasetAt,
} from "@inrupt/solid-client";
import { FOAF, VCARD } from "@inrupt/vocab-common-rdf";

1. Create Thing with Updated Data

To start, you need a data entity (a Thing) entity from which to create the updated Thing:

  • Use getThing to start with an existing data entity, or

  • Use createThing to create a new data entity.

The following example uses an existing profile as the Thing to update:

// 1a. Start with an existing Thing (i.e., profile).
// Note: Login code has been omitted for brevity. See the Prerequisite section above.
// ...

const myDataset = await getSolidDataset( "https://docs-example.inrupt.net/profile/card", { fetch: session.fetch });
const profile = getThing( myDataset, "https://docs-example.inrupt.net/profile/card#me");

With this profile as a starting point, use the following functions to create a new Thing with the modifications:

For example, the following example creates a new Thing updatedProfile with the following changes:

  • Update http://www.w3.org/2006/vcard/ns#fn to "Docs Example".

  • Add http://xmlns.com/foaf/0.1/nick to "docs".

  • Add http://xmlns.com/foaf/0.1/nick to "example".

For simplicity, the example uses the corresponding constants from the @inrupt/vocab-common-rdf library for the URLs.

// 1b. Modify the thing; specifically, update the name and add two nicknames.
// Note: solid-client functions do not modify objects passed in as arguments. 
// Instead the functions return new objects with the modifications.
// That is, setStringNoLocale and addStringNoLocale return a new Thing and
// - profile remains unchanged and 
// - updatedProfile is changed only because it is explicitly set to the object returned from addStringNoLocale.

let updatedProfile = setStringNoLocale(profile, VCARD.fn, "Docs Example");
updatedProfile = addStringNoLocale(updatedProfile, FOAF.nick, "docs");
updatedProfile = addStringNoLocale(updatedProfile, FOAF.nick, "example");

The solid-client library’s functions (such as the various add/set/remove functions) do not modify the objects that are passed in as arguments. Instead, the library’s functions return a new object with the requested changes, and the functions do not modify the passed-in objects.

In the above example:

  • profile remains unchanged after the setStringNoLocale, and

  • updatedProfile is explicitly set to the new object returned from the addStringNoLocale calls. Otherwise, the updatedProfile would remain unchanged from the calls.

2. Update SolidDataset with the Updated Thing

After creating a new Thing with updated data, use setThing to insert the Thing into a SolidDataset.

If the Thing was based on an existing Thing from the SolidDataset, the function replaces the existing Thing with the new Thing.

// 2. Update SolidDataset with the updated Thing (i.e., updatedProfile). 
// Note:  solid-client functions do not modify objects passed in as arguments. 
// Instead the functions return new objects with the modifications.
// That is, setThing returns a new SolidDataset and
// - myDataset remains unchanged.
// - updatedProfile remains unchanged.

const myChangedDataset = setThing(myDataset, updatedProfile);

3. Save the SolidDataset to a Pod

To save the updated dataset to a Pod, use saveSolidDatasetAt, passing in its URL as well as the updated dataset. The saveSolidDatasetAt function creates any intermediate folders/containers as needed for the URL. 1

// 3. Save the new dataset.
// The function returns a SolidDatset that reflects its state after the save.
const savedProfileResource = await saveSolidDatasetAt(
  "https://docs-example.inrupt.net/profile/card",
  myChangedDataset,
  { fetch: session.fetch }

If a SolidDataset exists at the specified URL, the changes from the updated SolidDataset are applied.

Note

A SolidDataset keeps track of the data changes compared to the data in the Pod. The save operation applies the changes from the changelog to the current SolidDataset.

For set operations on existing properties, the changelog tracks both the old value and new values of the property being modified. If the old value specified in the changelog does not correspond to the value currently in the Pod, the save operation will error.

Upon successful save, saveSolidDatasetAt returns a SolidDatset that reflects the sent data. That means, if another process made separate non-conflicting modifications to the SolidDataset after you retrieved the SolidDataset but before you successfully saved, the returned SolidDataset only reflects your changes.

1

The solid-client library also provides the saveSolidDatasetInContainer. However, unlike saveSolidDatasetAt which creates any intermediate folders/containers as needed, saveSolidDatasetInContainer requires the specified destination container already exists.

Changelog Considerations

A SolidDataset keeps track of the data changes compared to the data in the Pod. The save operation applies the changes from the changelog to the current SolidDataset:

  • For add operations, the changelog keeps track of additions.

  • For remove operations, the changelog keeps track of the deletions.

  • For set operations, the changelog keeps track of the old and new values. As such, if the old value specified in the changelog does not correspond to the value currently in the Pod, the save operation will error with 409 Conflict response.

Consider the previous example and the changes it makes to the profile:

const myDataset = await getSolidDataset( "https://docs-example.inrupt.net/profile/card", { fetch: session.fetch });
const profile = getThing( myDataset, "https://docs-example.inrupt.net/profile/card#me");

let updatedProfile = setStringNoLocale(profile, VCARD.fn, "Docs Example");
updatedProfile = addStringNoLocale(updatedProfile, FOAF.nick, "docs");
updatedProfile = addStringNoLocale(updatedProfile, FOAF.nick, "example");

const myChangedDataset = setThing(myDataset, updatedProfile);

const savedProfileResource = await saveSolidDatasetAt(
  "https://docs-example.inrupt.net/profile/card",
  myChangedDataset,
  { fetch: session.fetch }
);

The updatedDataset has a changelog that reflects:

  • modification of the formatted name (from old value to new value).

  • addition of the two nicknames.

Scenario 1: If another operation has modified the formatted after you have retrieved myDataset but before you save updatedDataSet, the save operation errors.

Scenario 2: If another operation has separately added the nickname docs after you have retrieved myDataset but before you save updatedDataSet, the save operation succeeds, and the SolidDataset returned from the function reflects all three nickname additions.