LDO’s New Stuff
Solid Support, React Support, API changes, NextGraph Support, Link Query, Svelte Support, and a New Data Browser
What is LDO (Linked Data Objects)?
1. Build from Schema
PREFIX ex: <https://example.com/>
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX ns: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
ex:FoafProfile EXTRA a {
foaf:name xsd:string
// rdfs:comment "A profile has 1 names" ;
foaf:title ns:langString *
// rdfs:comment "A profile has 0-∞ titles" ;
foaf:knows @ex:FoafProfile *
// rdfs:comment "A profile has 0-∞ friends." ;
}
export const foafProfileContext: ContextDefinition = {
name: {
"@id": "http://xmlns.com/foaf/0.1/name",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
title: {
"@id": "http://xmlns.com/foaf/0.1/title",
"@type": "http://www.w3.org/1999/02/22-rdf-syntax-ns#langString",
"@container": "@set",
},
knows: {
"@id": "http://xmlns.com/foaf/0.1/knows",
"@type": "@id",
"@container": "@set",
},
};
export interface FoafProfile {
"@id"?: string;
"@context"?: ContextDefinition;
name: string;
title?: string[];
knows?: FoafProfile[];
}
2. Parse RDF
const rawTurtle = `
@prefix example: <https://example.com/>.
@prefix foaf: <http://xmlns.com/foaf/0.1/>.
@prefix xsd: <http://www.w3.org/2001/XMLSchema#>.
@prefix ns: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
example:Taggart
foaf:name "Peter Quincy Taggart" ;
foaf:knows example:Lazarus .
example:Lazarus
foaf:name "Lazarus of Tev'Meck" ;
foaf:title "Doctor"^^ns:langString ;
foaf:title "Docteur"@fr ;
foaf:knows example:Taggart .
`;
const ldoDataset = await parseRdf(rawTurtle, { format: "Turtle" });
3. Create a Linked Data Object
import { FoafProfileShapeType } from
"./ldo/foafProfile.shapeTypes";
const taggart = ldoDataset
.usingType(FoafProfileShapeType)
.setLanguagePreferences("en", "@none")
.fromSubject("https://example.com/Taggart");
4. Reading and Modifying Data
console.log(taggart.name);
console.log(
ldoDataset.match(
namedNode("https://example.com/Taggart"),
namedNode("http://xmlns.com/foaf/0.1/name"),
null
).toArray()[0].object.value
);
ldoDataset.deleteMatches(
namedNode("https://example.com/Taggart"),
namedNode("http://xmlns.com/foaf/0.1/name")
);
ldoDataset.add(quad(
namedNode("https://example.com/Taggart"),
namedNode("http://xmlns.com/foaf/0.1/name"),
literal("Jason Nesmith")
));
taggart.name = "Jason Nesmith";
=
=
Arrays are now Sets
person.roommate = {
"@id": "https://example.com/Person2",
name: [
"Garrett",
"Bobby",
"Ferguson”
]
}
import { LdSet, set } from "@ldo/ldo";
�person.roommate = {
"@id": "https://example.com/Person2",
name: set(
"Garrett",
"Bobby",
"Ferguson”
)
}
�
OLD
NEW
Section Review!
@ldo/connected-solid
.getResource
import { createSolidLdoDataset } from "@ldo/connected-solid";
const solidLdoDataset = createSolidLdoDataset();
const profileResource =
solidLdoDataset.getResource("https://example.com/profile");
Fig. 6. The getResource method is used to retrieve a Resource from a SolidLdoDataset
Read + Error Handling
import { createSolidLdoDataset } from "@ldo/connected-solid";
import { ProfileShapeType } from "./.ldo/Profile.shapeType";
async function main() {
// Create a SolidLdoDataset
const solidLdoDataset = createSolidLdoDataset();
// Get a resource representation
const resource1 = solidLdoDataset.getResource("https://example.com/resource1");
// Read the resource
const readResult = await resource1.read();
// Handle possible errors
if (readResult.isError) {
// Different errors can be handled based on their error type. In this case,
// the "read" method could encounter one of 6 error types.
switch (readResult.type) {
case "serverError":
case "unauthenticatedError":
case "unauthorizedError":
case "noncompliantPodError":
case "unexpectedResourceError":
case "unexpectedHttpError":
console.error(result.message);
return;
}
}
Creating Resources and Modifying Data
// Create the resource if it was absent. (This could also be done with the
// createIfAbsent method.
if (resource1.isAbsent()) {
const createResult = await resource1.createAndOverwrite();
// ... Handle createResult
}
// Create a treansaction save some data to the resource
const transaction = solidLdoDataset.startTransaction();
// Create a linked data object for easy manipulation. Any changes on this
// linked data object will be written to resource1
const profile = solidLdoDataset
.usingType(ProfileShapeType)
.write(resource1.uri)
.fromSubject("https://example.com/resource1#profile");
profile.name = "John Doe";
// Commit the changes to the Pod
const commitResult = await transaction.commitToRemote();
// ... Handle commitResult
}
Problem: Two resources with the same subject
# Located at "https://example.com/profile"
@prefix prof: <https://example.com/profile#>.
@prefix foaf: <http://xmlns.com/foaf/0.1/>.
prof:me foaf:name "John Doe".
# Located at "https://otherSite.com/friendsList"
@prefix prof: <https://example.com/profile#>.
@prefix foaf: <http://xmlns.com/foaf/0.1/>.
prof:me foaf:knows <https://example.com/friend1#me>.
prof:me foaf:knows <https://example.com/friend2#me>.
Fig. 5. Two resources can contain information on the same subject.
Other Support
Section Review!
@ldo/connected
How createSolidLdoDataset works under the covers
export function createSolidLdoDataset() {
const solidLdoDataset = new ConnectedLdoDataset(
[solidConnectedPlugin],
createDatasetFactory(),
createTransactionDatasetFactory(),
);
return solidLdoDataset;
}
“Plugins” is an array…
export function createSolidLdoDataset() {
const solidLdoDataset = new ConnectedLdoDataset(
[solidConnectedPlugin, nextGraphConnectedPlugin],
createDatasetFactory(),
createTransactionDatasetFactory(),
);
return solidLdoDataset;
}
@ldo/connected-nextgraph
NextGraph works the same!
const resource = ldoDataset.getResource(
"did:ng:o:W6GCQRfQkNTLtSS_2-QhKPJPkhEtLVh-
B5lzpWMjGNEA:v:h8ViqyhCYMS2I6IKwPrY6UZi4ougUm1gpM4QnxlmNMQA"
);
const readResult = await resource.read();
�if (!readResult.isError) {
console.log("Resource loaded!", readResult.type);
}
Section Review!
@ldo/react and�@ldo/solid-react
Initializing React
import { solidConnectedPlugin } from "@ldo/connected-solid";
import { createLdoReactMethods } from "@ldo/react";
�/**
* Default exports for just Solid methods
*/
export const {
dataset,
useLdo,
useMatchObject,
useMatchSubject,
useResource,
useSubject,
useSubscribeToResource,
useLinkQuery,
} = createLdoReactMethods([solidConnectedPlugin]);
Or just use the solid-react library
import { solidConnectedPlugin } from "@ldo/connected-solid";
import { createLdoReactMethods } from "@ldo/react";
�/**
* Default exports for just Solid methods
*/
export const {
dataset,
useLdo,
useMatchObject,
useMatchSubject,
useResource,
useSubject,
useSubscribeToResource,
useLinkQuery,
} = createLdoReactMethods([solidConnectedPlugin]);
import {
dataset,
useLdo,
useResource,
useSubject
} from "@ldo/solid-react";
React Component
Hooks like useResource and useSubject
ldoDataset
useResource
import { useResource } from "@ldo/solid-react";
import React, { FunctionComponent } from "react";
export const Component: FunctionComponent = () => {
// Trigger the useResource hook
const resource = useResource("https://example.com/some_container/", {
// If true, this will not trigger a "read" operation automatically
suppressInitialRead: false,
// If true, this will trigger the "read" operation every time this component
// mounts.
reloadOnMount: false
});
// Use the "isLoading" API on the Resource class
if (resource.isLoading()) {
return <p>Loading...</p>
}
return (
<ul>
{/* Access the "children" API on the Container class */}
{resource.children().map((child) => (
<li>{child.uri}</li>
))}
</ul>
);
}
useSubject
import { useSubject } from "@ldo/solid-react";
import React, { FunctionComponent } from "react";
import { ProfileShapeType } from "./.ldo/Profile.shapeType";
export const Component: FunctionComponent = () => {
// Get a linked data object of type "Profile" with the id
// "https://example.com/profile#me"
const profile = useSubject(ProfileShapeType, "https://example.com/profile#me");
return <div>
{/* Render the name of the profile */}
<p>Name: {profile?.name}</p>
<p>Friends:</p>
<ul>
{/* Render the name of all the profile's friends */}
{profile?.knows((friend) => {
return <li>{friend.name}</li>
})}
</ul>
</div>
}
Section Review!
Link Query
Example Data
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix : <#> .
�:me a foaf:Person ;
foaf:name "Main User" ;
foaf:knows <http://localhost:3005/test-container/otherProfile.ttl#me> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix : <#> .
�:me a foaf:Person ;
foaf:name "Other User" .
http://localhost:3005/test-container/mainProfile.ttl
http://localhost:3005/test-container/otherProfile.ttl
Performing a Link Query
const data = await solidLdoDataset
.usingType(SolidProfileShapeShapeType)
.startLinkQuery(
mainProfileResource,
"http://localhost:3005/test-container/mainProfile.ttl",
{
name: true,
knows: {
name: true,
},
},
)
.run();
Intellisense
Link Query in React
const linkQuery = {
name: true,
knows: {
name: true,
},
} as const;
�const Component: FunctionComponent = () => {
const profile = useLinkQuery(
SolidProfileShapeShapeType,
MAIN_PROFILE_URI,
MAIN_PROFILE_SUBJECT,
linkQuery,
);
if (!profile) return <p>Loading</p>;
return (
<div>
<p role="profile-name">{profile.name}</p>
<ul role="list">
{profile.knows?.map((nestedProfile) => (
<li key={nestedProfile["@id"]}>{nestedProfile.name}</li>
))}
</ul>
</div>
);
};
Section Review!
@ldo/svelte
Initializing Svelte
import { createLdoSvelteMethods } from "@ldo/svelte";
import { solidConnectedPlugin } from "@ldo/connected-solid";
�export const {
dataset,
useLdo,
useMatchObject,
useMatchSubject,
useResource,
useSubject,
useSubscribeToResource,
useLinkQuery,
} = createLdoSvelteMethods([solidConnectedPlugin]);
Svelte Example
<script lang="ts">
import { SolidProfileShapeShapeType } from "./.ldo/solidProfile.shapeTypes.js";
import { useResource, useSubject } from "./ldoSvelteMethods.js";
const SAMPLE_DATA_URI =
"http://localhost:3004/example/link-query/main-profile.ttl";
const resource = useResource(SAMPLE_DATA_URI);
� const webId = `${SAMPLE_DATA_URI}#me`;
const profile = useSubject(SolidProfileShapeShapeType, webId);
$: friendArray = $profile?.knows?.toArray() || [];
$: firstFriendId =
friendArray.length > 0 ? friendArray[0]?.["@id"] : undefined;
</script>
�<h1>LDO Svelte Support Demo</h1>
{#if $resource.isLoading() || !$profile}
<p>loading</p>
{:else}
<div>
{#if firstFriendId}
<p>{firstFriendId}</p>
{:else}
<p>No friend found or friend has no @id.</p>
{/if}
Section Review!
Linked Data Browser (LDB)
In conclusion