Read/Write Structured 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 (Non-RDF Resources).
Structured data, in this context, refer to data structured as
Things in SolidDatasets. A
Thing is saved as part of a SolidDataset, where a
SolidDataset
contains a set of Things
. That is, you do not save
a Thing
independently from its SolidDataset
.
For example, if you want to save information (such as the required
books, etc.) about a course, you can have a SolidDataset
that
corresponds to the course, and within that SolidDataset
, contain
the Things
that correspond to various data about the course, such as
the books.
For more information, see Structured Data (RDF Resources).
Required Access#
Note
Inrupt also supports access requests and grants. For details, see Access Requests and Grants.
By default, solid-client
functions make unauthenticated requests.
To perform read/write operations on restricted Resources (i.e., not
open to the general public), the user must first authenticate as a user who has appropriate access to that Resource. Then, to make
authenticated requests, pass to the various read/write functions the
authenticated Session’s fetch
function.
To create a resource, the user requires either an Append
or
Write
access.
Note
The creation operation creates the resource and updates the content of the parent Container with the new resource’s metadata.
Although, a Container is itself a SolidDataset, the table separates out the access for the Container and SolidDataset.
Target Resource |
Description |
---|---|
Either
|
|
Either |
For read operations, the user requires Read
access.
Note
Although, a Container is itself a SolidDataset, the table separates out the access for the Container and SolidDataset.
Target Resource |
Description |
---|---|
|
|
Reading a Container (which stores metadata about the
resources contained within the Container) allows a client
to discover what resources are contained inside the
Container and their resource type (i.e., analogous to an
|
For update operations, the user requires Append
or Write
access, depending on the specific update operation.
Note
Although, a Container is itself a SolidDataset, the table separates out the access for the Container and SolidDataset.
Target Resource |
Description |
---|---|
|
|
To add resources to the Container, see the To delete resources from the Container, see the |
For delete operations, the user requires Write
access.
Note
Although, a Container is itself a SolidDataset, the table separates out the access for the Container and SolidDataset.
Target |
Description |
---|---|
To delete a SolidDataset, |
|
Tip To delete a Container, the target Container must be empty. |
For more information on access control, see Access Policies: Universal API. For more information on authentication, see Authentication.
Prerequisite for Restricted Data#
Note
Inrupt also supports access requests and grants. For details, see Access Requests and Grants.
To make authenticated requests, you can use one of Inrupt’s
authentication libraries to login and pass the
fetch() function as an
option to solid-client
functions. Inrupt provides the following
libraries for authentication:
solid-client-authn-browser
library to authenticate in a browser.solid-client-authn-node
library to authenticate in Node.js.
The following example uses solid-client-authn-browser
to
authenticate a user and use the authenticated Session’s
fetch() function to
make authenticated requests:
import { handleIncomingRedirect, login, fetch, getDefaultSession } from '@inrupt/solid-client-authn-browser'
import { getSolidDataset, saveSolidDatasetAt } from "@inrupt/solid-client";
async function loginAndFetch() {
// 1. Call the handleIncomingRedirect() function,
// - Which completes the login flow if redirected back to this page as part of login; or
// - Which is a No-op if not part of login.
await handleIncomingRedirect();
// 2. Start the Login Process if not already logged in.
if (!getDefaultSession().info.isLoggedIn) {
await login({
oidcIssuer: "https://login.inrupt.com",
redirectUrl: new URL("/", window.location.href).toString(),
clientName: "My application"
});
}
// ...
// const exampleSolidDatasetURL = ...;
// 3. Make authenticated requests by passing `fetch` to the solid-client functions.
// For example, the user must be someone with Read access to the specified URL.
const myDataset = await getSolidDataset(
exampleSolidDatasetURL,
{ fetch: fetch } // fetch function from authenticated session
);
// ...
// For example, the user must be someone with Write access to the specified URL.
const savedSolidDataset = await saveSolidDatasetAt(
exampleSolidDatasetURL,
myChangedDataset,
{ fetch: fetch } // fetch function from authenticated session
);
}
loginAndFetch();
For more information on authentication, see Authentication.
Pod URL#
For Containers, SolidDatasets, and Things, their URL acts as the unique identifier. Their URLs are relative to the Pod’s URL. For example:
https://storage.inrupt.com/{someIdentifier}/myContainer/
https://storage.inrupt.com/{someIdentifier}/myContainer/mySolidDataset
https://storage.inrupt.com/{someIdentifier}/myContainer/mySolidDataset#someThing
where https://storage.inrupt.com/{someIdentifier}/
is the Pod’s URL.
Inrupt’s solid-client
library provides
getPodUrlAll to get the Pod’s URL or if
the user has multiple Pods, the list of Pod URLs.
import { getPodUrlAll } from "@inrupt/solid-client";
// Returns a list of URLs
const mypods = await getPodUrlAll(webID, { fetch: fetch });
Assumptions#
The following examples assumes:
The application has used the
solid-client-authn-browser
library to handle login and has an authenticated fetch function. See the Prerequisite section for details on authenticating the user’s session.The logged-in user has the appropriate permissions to perform the specified SolidDataset operations. See the Required Access for the access required for specific actions.
Read Data#
To read data with solid-client
:
Use getSolidDataset to fetch the SolidDataset. You must have
Read
access to the SolidDataset.Then, use either:
getThing to get a single data entity from the fetched SolidDataset, or
getThingAll to get all data entities from the fetched SolidDataset.
Then, from the data entity (i.e.,
Thing
), you can get specific data. For a list of theget
functions, see thing/get module.
0. Import#
For the examples, import the following objects from the client libraries:
import {
getSolidDataset,
getThing,
getStringNoLocale,
getUrlAll
} from "@inrupt/solid-client";
import { SCHEMA_INRUPT } from "@inrupt/vocab-common-rdf";
1. Fetch the SolidDataset
#
Use getSolidDataset to fetch the
SolidDataset
that contains the data:
Pass the
SolidDataset
URL to getSolidDataset. In the following example, theSolidDataset
is the reading list document created as part of Getting Started.To make an authenticated request, the example includes a
fetch
function associated with a logged in Session.
const myDataset = await getSolidDataset(
readingListUrl,
{ fetch: fetch } // fetch from authenticated session
);
2. Get the Data Entity Thing
#
Note
The example uses the reading list document created as part of Getting Started.
From the fetched SolidDataset, 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.
Typically, but not always, a Thing’s URL is the SolidDataset’s URL
appended with a hash fragment #<something>
; that is,
<SolidDataset URL>#<something>
.
In the reading list example, the titles
(Things) use the hash
fragment #title<number>
.
The following example uses getThing to retrieve a book title from the previously fetched SolidDataset (i.e., the reading list document).
const itemForWeek = getThing(
myDataset,
`${readingListUrl}#title${weekNum}`
);
3. Read Data Attribute of a Thing#
Like the Thing
and SolidDataset
, each property (such as
name
, etc.) of a Thing
is also identified by a URL. That is,
when storing the name
of a book, you do not store the data under
the string identifier name
. Instead, you use a URL identifier, such as
https://schema.org/name
from the https://schema.org
Vocabulary
(or some other URL).
A property can have zero, one or more values, and the value is typed; e.g., a string, an integer, or a URL if pointing to other Things. To retrieve the data, use the appropriate get function depending on the data type and the number of values for the property.
Note
These types are specific to Solid, i.e. they are not JavaScript types. As such, there is a distinction between integers and decimals as well as a separate type for URLs.
To the get function, pass the URL that identifies the property to fetch. To encourage interoperability, various Vocabularies exist to identify common data. For example:
https://schema.org/name
identifies a string that represents a name of a Thing.
Tip
Inrupt’s vocab-common-rdf
library bundles convenience objects
that refer to terms in popular pre-existing vocabularies.
Applications can use these convenience objects instead of specifying
the identifier’s full URL. For more information on Vocabulary usage, see
Vocabulary.
For simplicity, the example uses the corresponding constants
(SCHEMA_INRUPT.name
) from the @inrupt/vocab-common-rdf
library
for the URL.
// In this example, use `getStringNoLocale` to get a single string data value from the Thing.
// Specifically, the operation returns the SCHEMA_INRUPT.name
// (i.e., "https://schema.org/name") value as a string.
const title = getStringNoLocale(itemForWeek, SCHEMA_INRUPT.name);
Write Data#
Write a New SolidDataset#
0. Import#
For the examples, import the following objects from the client libraries:
import { login, handleIncomingRedirect, getDefaultSession, fetch } from "@inrupt/solid-client-authn-browser";
import {
addUrl,
addStringNoLocale,
buildThing,
createSolidDataset,
createThing,
setThing,
saveSolidDatasetAt,
} from "@inrupt/solid-client";
import { SCHEMA_INRUPT } from "@inrupt/vocab-common-rdf";
1. Create a new SolidDataset
#
You can use createSolidDataset to create a new SolidDataset. For example:
// Create a new SolidDataset for Writing 101
let courseSolidDataset = createSolidDataset();
2. Create a new Thing
#
First, use createThing to create a new Thing (i.e.,
data entity) for the SolidDataset. Pass the function a name
for the
Thing. Typically, a Thing’s URL is the SolidDataset’s URL appended with
a #
hash fragment. Upon save, the specified name
becomes the
#
hash fragment; i.e., the Thing’s URL becomes
<SolidDatasetURL>#<name>
.
Then, you can use the buildThing() and the ThingBuilder functions (Fluent API) to generate the Thing with the data modifications.
For example:
// Create a new Thing for "book1"; Thing's URL will include the hash #book1.
// Use Fluent API to add properties to the new Thing, and
// build a new Thing with the properties.
// Note: solid-client functions do not modify objects passed in as arguments.
// Instead the functions return new objects with the modifications.
const newBookThing1 = buildThing(createThing({ name: "book1" }))
.addStringNoLocale(SCHEMA_INRUPT.name, "ABC123 of Example Literature")
.addUrl(RDF.type, "https://schema.org/Book")
.build();
The example creates a new Thing with name book1
(which will be part
of its URL), and using the Fluent API, adds the following Thing
properties and value:
|
|
|
|
Note
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.
First, use createThing to create a new Thing (i.e.,
data entity) for the SolidDataset. Pass the function a name
for the
Thing. Typically, a Thing’s URL is the SolidDataset’s URL appended with
a #
hash fragment. Upon save, the specified name
becomes the
#
hash fragment; i.e., the Thing’s URL becomes
<SolidDatasetURL>#<name>
.
Then, use the series of add/set/remove functions to create a new Thing with the data modifications:
thing/add functions that return a new Thing modified with new data added,
thing/set functions that return a new Thing modified with the specified data, and
thing/remove functions that return a new Thing modified with specified data removed.
// Create a new Thing for "book2"; Thing's URL will include the hash #book2.
// Use various add functions to add properties to the Thing
// Note: solid-client functions do not modify objects passed in as arguments.
// Instead the functions return new objects with the modifications.
let newBookThing2 = createThing({ name: "book2" });
newBookThing2 = addStringNoLocale(newBookThing2, SCHEMA_INRUPT.name, "ZYX987 of Example Poetry");
newBookThing2 = addUrl(newBookThing2, RDF.type, "https://schema.org/Book");
The example creates a new Thing with name book2
(which will be part
of its URL), and using a sequence of thing/add functions to add the following Thing
properties:
|
|
|
|
Note
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.
3. Insert Things into SolidDataset#
Use setThing to create a SolidDataset that contains the Things.
For example:
// Update SolidDataset with the book1 and book2 Things.
// Note: solid-client functions do not modify objects passed in as arguments.
// Instead the functions return new objects with the modifications.
courseSolidDataset = setThing(courseSolidDataset, newBookThing1);
courseSolidDataset = setThing(courseSolidDataset, newBookThing2);
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.
4. Save the SolidDataset#
Use saveSolidDataSetAt to save the SolidDataset to the Pod and return the SolidDataset. For example:
// Save the SolidDataset at the specified URL.
// The function returns a SolidDataset that reflects your sent data
const savedSolidDataset = await saveSolidDatasetAt(
"https://pod.example.com/universityZ/fall2021/courses/Writing101",
courseSolidDataset,
{ fetch: fetch } // fetch from authenticated Session
);
When using saveSolidDataSetAt, the Solid server creates any intermediary Containers as needed.
See also Save Considerations.
Modify an Existing SolidDataset#
0. Import#
For the examples, import the following objects from the client libraries:
import { login, handleIncomingRedirect, getDefaultSession, fetch } from "@inrupt/solid-client-authn-browser";
import {
buildThing,
createThing,
getSolidDataset,
getThing,
setStringNoLocale,
setThing,
saveSolidDatasetAt,
} from "@inrupt/solid-client";
import { SCHEMA_INRUPT, RDF } from "@inrupt/vocab-common-rdf";
1. Retrieve the SolidDataset.#
You can use getSolidDataset to fetch an existing SolidDataset. For example:
// Get the SolidDataset for Writing 101 at the specified URL
const resourceURL = "https://pod.example.com/universityZ/fall2021/courses/Writing101";
let courseSolidDataset = await getSolidDataset(
resourceURL,
{ fetch: fetch }
);
2. Get the Thing to Modify#
First, you can use getThing to get an existing Thing from the
fetched SolidDataset. Pass the function the Thing’s URL. Typically, a
Thing’s URL is <SolidDatasetURL>#<name>
.
Then, you can use the buildThing() and the ThingBuilder functions (Fluent API) to generate the Thing with the data modifications.
For example:
// Get the "book1" Thing from the retrieved SolidDataset; the Thing's URL will be the SolidDatatset URL with hash #book1.
// Use Fluent API to add a new property to the Thing, and
// build a new Thing with the added property.
// Note: solid-client functions do not modify objects passed in as arguments.
// Instead the functions return new objects with the modifications.
let book1Thing = getThing(courseSolidDataset, `${resourceURL}#book1`);
book1Thing = buildThing(book1Thing)
.addInteger("https://schema.org/numberOfPages", 30)
.build();
The example gets the book1
Thing, and using the Fluent API, adds
the following Thing property and value:
|
|
Note
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.
First, you can use getThing to get an existing Thing from the
fetched SolidDataset. Pass the function the Thing’s URL. Typically, a
Thing’s URL is <SolidDatasetURL>#<name>
.
Then, use the series of add/set/remove functions to modify the Thing with the data modifications:
thing/add functions that return a new Thing modified with new data added,
thing/set functions that return a new Thing modified with the specified data, and
thing/remove functions that return a new Thing modified with specified data removed.
For example:
// Get the "book2" Thing from the retrieved SolidDataset; the Thing's URL will be the SolidDatatset URL with hash #book2.
// Use setStringNoLocale to return a new Thing with the name property set to "ZYX987 of Example Poesy"
// Note: solid-client functions do not modify objects passed in as arguments.
// Instead the functions return new objects with the modifications.
let book2Thing = getThing(courseSolidDataset, `${resourceURL}#book2`);
book2Thing = setStringNoLocale(book2Thing, SCHEMA_INRUPT.name, "ZYX987 of Example Poesy");
The example gets the book2
Thing, and uses a
setStringNoLocale function to update the
Thing’s SCHEMA_INRUPT.name
value to "ZYX987 of Example Poesy"
.
Note
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.
3. Create a New Thing
to Add#
In addition to being able to modify existing Things in the
SolidDataset, you can add a new Thing
to the existing SolidDataset.
First, use createThing to create a new Thing (i.e.,
data entity) for the SolidDataset. Pass the function a name
for the
Thing. Typically, a Thing’s URL is the SolidDataset’s URL appended with
a #
hash fragment. Upon save, the specified name
becomes the
#
hash fragment; i.e., the Thing’s URL becomes
<SolidDatasetURL>#<name>
.
Then, you can use the buildThing() and the ThingBuilder functions (Fluent API) to generate the Thing with the data modifications.
The following example creates a new Thing with the name location
(which will be part of its URL), and uses the Fluent API to add various
properties and values.
// Create a new Thing for "location"; Thing's URL will include the hash #location.
// Use Fluent API to add properties to the new Thing, and
// build a new Thing with the properties.
// Note: solid-client functions do not modify objects passed in as arguments.
// Instead the functions return new objects with the modifications.
const locationThing = buildThing(createThing({ name: "location" }))
.addStringNoLocale(SCHEMA_INRUPT.name, "Sample Lecture Hall")
.addUrl(RDF.type, "https://schema.org/Place")
.build();
See also 2. Create a new Thing.
4. Update the SolidDataset with Modified/New Things#
Use setThing to create a SolidDataset with Thing modifications.
For example:
// Update SolidDataset with the book1 and book2 and location Things.
// Note: solid-client functions do not modify objects passed in as arguments.
// Instead the functions return new objects with the modifications.
courseSolidDataset = setThing(courseSolidDataset, book1Thing);
courseSolidDataset = setThing(courseSolidDataset, book2Thing);
courseSolidDataset = setThing(courseSolidDataset, locationThing);
In the returned SolidDataset, the book1
and book2
Things have
been overwritten and the new location
Thing has been added.
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.
5. Save the SolidDataset#
Use saveSolidDataSetAt to save the SolidDataset to the Pod and return the SolidDataset. For example:
// Save the SolidDataset at the specified URL.
// The function returns a SolidDataset that reflects your sent data
const savedSolidDataset = await saveSolidDatasetAt(
resourceURL,
courseSolidDataset,
{ fetch: fetch } // fetch from authenticated Session
);
A SolidDataset keeps track of the data changes compared to the data in
the Pod. For set
operations on existing 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. See Save Considerations.
Save Considerations#
Create vs. Update Operations#
saveSolidDatasetAt can be used to create new Resource or update an existing Resource in a Pod. If the passed-in Resource has an identifying URL, the function performs an update. If the passed-in Resource has no identifying URL, the function attempts to create a new Resource.
When creating a new Resource, the function adds the precondition that the Resource must not already exist. If the Resource already exists, the server will return a 412 Precondition Failed error.
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 a 409 Conflict.
In the Modify an Existing SolidDataset example, the locally modified
courseSolidDataset
has a changelog that reflects:
addition of a number of pages property for
book1
modification of the name property field (from old value to new value) for
book2
.addition of a new Thing
location
Scenario 1: If another operation has modified the name property of
book2
after you have retrieved courseSolidDataset
but before
you save the locally modified courseSolidDataset
, the save
operation errors with 409 Conflict.
Scenario 2: If another operation has separately
added/modified/removed a property from book1
after you have retrieved
courseSolidDataset
but before you the locally modified
courseSolidDataset
, the save operation succeeds, but the returned
SolidDataset only reflects your changes. To make sure the SolidDataset
accurately reflects all changes to date, call
getSolidDataset again after
saving the data.
Returned SolidDataset#
saveSolidDatasetAt returns a SolidDataset that reflects only 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.
To make sure the SolidDataset reflects not just your changes, call getSolidDataset again after saving the data.
Delete an Existing SolidDataset#
Delete API#
To delete a SolidDataset from a Pod, you can use deleteSolidDataset to remove the SolidDataset at the specified URL. To use, pass the function the following parameters:
Parameter |
Value |
||
---|---|---|---|
SolidDataset URL |
The URL of the SolidDataset to delete. |
||
Options object |
An object that includes the following option:
|
The following example uses deleteSolidDataset to delete the specified SolidDataset.
import { login, handleIncomingRedirect, getDefaultSession, fetch } from "@inrupt/solid-client-authn-browser";
import {
deleteSolidDataset
} from "@inrupt/solid-client";
// ... Various logic, including login logic, omitted for brevity.
try {
await deleteSolidDataset(
"https://pod.example.com/universityZ/fall2021/courses/Writing101",
{ fetch: fetch } // fetch function from authenticated session
);
} catch (error) {
//...
}