Skip to content

Trails

Trails provide a way to navigate through interconnected resources in a Solid Pod, automatically creating any missing resources along the path. They’re particularly useful for ensuring that a sequence of linked resources exists, creating them with sensible defaults if they don’t.

A trail consists of a series of steps that define how to move from one resource to the next, with fallback behaviour to create missing data.

To create a trail, start with buildTrail(), define a starting point using .startFrom(url), then define the path through the user’s Pod using .via() calls:

trail.ts
import { buildTrail } from "benno/trail";
import { profileModel, typeIndexModel } from "./trail-models";
import { schema, solid } from "rdf-namespaces";
export const trail = buildTrail()
.via({
model: profileModel,
getTargetUrl: (profile) => profile.publicTypeIndex ?? null,
fallback: (rootUrl, existingProfile) => {
if (!existingProfile) {
// If the profile was not found at the user's WebID altogether,
// something is seriously wrong with the user's Pod.
// We can't fix that, so bail out:
throw new Error("Profiles should always exist");
}
return {
...existingProfile,
publicTypeIndex: `${rootUrl}/settings/publicTypeIndex`,
};
},
})
.via({
model: typeIndexModel,
findPointer: (typeIndexes) => {
return (
typeIndexes.find(
(typeIndex) =>
typeIndex.type === solid.TypeRegistration &&
typeIndex.forClass === schema.Movie,
) ?? null
);
},
getTargetUrl: (typeIndex) => typeIndex.instance,
fallback: (rootUrl) => {
return {
type: solid.TypeRegistration,
forClass: schema.Movie,
instance: `${rootUrl}/movies/`,
};
},
});

via takes one argument, an object with the following possible keys:

RequiredType
YesA model

The model that defines the structure of the Things you’re looking for at this step in the trail.

RequiredType
Yes(thing) => string | null

A function that extracts the URL of the next resource from a Thing that matches your model. If no target URL can be determined, it should return null.

RequiredType
Yes(rootUrl, existingThing?) => newThing

A function that creates a new Thing when the current step in the trail doesn’t exist. The rootUrl parameter is the Pod root URL, and existingThing is provided if a Thing was found but didn’t have a target URL. The returned Thing should point to a URL under the root URL (e.g. ${rootUrl}/example); Benno will create that resource if it does not yet exist.

RequiredType
No(things) => thing | null

An optional function for finding a specific Thing among all the Things in the target resource. If not provided, the trail will look for a Thing with the current resource’s URL.

To get the URL at the end of a trail, call the .follow method.

Executes the trail, navigating through each .via call and creating any missing resources along the way.

RequiredType
No{ fetch?: typeof fetch }

Optionally allows passing a custom fetch function. Primarily used to send authenticated requests.

A Promise resolving to the URL of the final resource in the trail.

Trails will throw errors in the following situations:

  • No Pod root found: If the server doesn’t expose a Pod root URL through the appropriate headers.
  • Invalid fallback: If a .via call’s fallback function creates a Thing that its getTargetUrl cannot extract a target URL from.
  • Network errors: Standard fetch errors when accessing or creating resources.