commit
1def1750f4
@ -1,4 +1,4 @@ |
|||||||
{ |
{ |
||||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json", |
"$schema": "node_modules/lerna/schemas/lerna-schema.json", |
||||||
"version": "0.0.1-alpha.17" |
"version": "0.0.1-alpha.18" |
||||||
} |
} |
||||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,13 @@ |
|||||||
|
import type { FunctionComponent } from "react"; |
||||||
|
import React from "react"; |
||||||
|
import { Router } from "./Layout"; |
||||||
|
import { BrowserSolidLdoProvider } from "@ldo/solid-react"; |
||||||
|
|
||||||
|
const ProfileApp: FunctionComponent = () => { |
||||||
|
return ( |
||||||
|
<BrowserSolidLdoProvider> |
||||||
|
<Router /> |
||||||
|
</BrowserSolidLdoProvider> |
||||||
|
); |
||||||
|
}; |
||||||
|
export default ProfileApp; |
@ -1,13 +1,65 @@ |
|||||||
import type { FunctionComponent } from "react"; |
import type { FunctionComponent } from "react"; |
||||||
import React from "react"; |
import React, { useCallback } from "react"; |
||||||
import { Router } from "./Layout"; |
import { |
||||||
import { BrowserSolidLdoProvider } from "@ldo/solid-react"; |
BrowserSolidLdoProvider, |
||||||
|
useResource, |
||||||
|
useSolidAuth, |
||||||
|
useSubject, |
||||||
|
} from "@ldo/solid-react"; |
||||||
|
import { SolidProfileShapeShapeType } from "./.ldo/solidProfile.shapeTypes"; |
||||||
|
import { changeData, commitData } from "@ldo/solid"; |
||||||
|
|
||||||
const ProfileApp: FunctionComponent = () => { |
// The base component for the app
|
||||||
|
const App: FunctionComponent = () => { |
||||||
return ( |
return ( |
||||||
|
/* The application should be surrounded with the BrowserSolidLdoProvider |
||||||
|
this will set up all the underlying infrastructure for the application */ |
||||||
<BrowserSolidLdoProvider> |
<BrowserSolidLdoProvider> |
||||||
<Router /> |
<Login /> |
||||||
</BrowserSolidLdoProvider> |
</BrowserSolidLdoProvider> |
||||||
); |
); |
||||||
}; |
}; |
||||||
export default ProfileApp; |
|
||||||
|
// A component that handles login
|
||||||
|
const Login: FunctionComponent = () => { |
||||||
|
// Get login information using the "useSolidAuth" hook
|
||||||
|
const { login, logout, session } = useSolidAuth(); |
||||||
|
|
||||||
|
const onLogin = useCallback(() => { |
||||||
|
const issuer = prompt("What is your Solid IDP?"); |
||||||
|
// Call the "login" function to initiate login
|
||||||
|
if (issuer) login(issuer); |
||||||
|
}, []); |
||||||
|
|
||||||
|
// You can use session.isLoggedIn to check if the user is logged in
|
||||||
|
if (session.isLoggedIn) { |
||||||
|
return ( |
||||||
|
<div> |
||||||
|
{/* Get the user's webId from session.webId */} |
||||||
|
<p>Logged in as {session.webId}</p> |
||||||
|
{/* Use the logout function to log out */} |
||||||
|
<button onClick={logout}>Log Out</button> |
||||||
|
<Profile /> |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
||||||
|
return <button onClick={onLogin}>Log In</button>; |
||||||
|
}; |
||||||
|
|
||||||
|
const Profile: FunctionComponent = () => { |
||||||
|
const { session } = useSolidAuth(); |
||||||
|
const resource = useResource(session.webId); |
||||||
|
const profile = useSubject(SolidProfileShapeShapeType, session.webId); |
||||||
|
|
||||||
|
const onNameChange = useCallback(async (e) => { |
||||||
|
// Ensure that the
|
||||||
|
if (!profile || !resource) return; |
||||||
|
const cProfile = changeData(profile, resource); |
||||||
|
cProfile.name = e.target.value; |
||||||
|
await commitData(cProfile); |
||||||
|
}, []); |
||||||
|
|
||||||
|
return <input type="text" value={profile?.name} onChange={onNameChange} />; |
||||||
|
}; |
||||||
|
|
||||||
|
export default App; |
||||||
|
@ -1,77 +0,0 @@ |
|||||||
import { parseRdf, startTransaction, toSparqlUpdate, toTurtle } from "../src"; |
|
||||||
import { FoafProfileShapeType } from "./ldo/foafProfile.shapeTypes"; |
|
||||||
|
|
||||||
async function run() { |
|
||||||
const rawTurtle = ` |
|
||||||
<#me> a <http://xmlns.com/foaf/0.1/Person>; |
|
||||||
<http://xmlns.com/foaf/0.1/name> "Jane Doe". |
|
||||||
`;
|
|
||||||
|
|
||||||
/** |
|
||||||
* Step 1: Convert Raw RDF into a Linked Data Object |
|
||||||
*/ |
|
||||||
const ldoDataset = await parseRdf(rawTurtle, { |
|
||||||
baseIRI: "https://solidweb.me/jane_doe/profile/card", |
|
||||||
}); |
|
||||||
// Create a linked data object by telling the dataset the type and subject of
|
|
||||||
// the object
|
|
||||||
const janeProfile = ldoDataset |
|
||||||
// Tells the LDO dataset that we're looking for a FoafProfile
|
|
||||||
.usingType(FoafProfileShapeType) |
|
||||||
// Says the subject of the FoafProfile
|
|
||||||
.fromSubject("https://solidweb.me/jane_doe/profile/card#me"); |
|
||||||
|
|
||||||
/** |
|
||||||
* Step 2: Manipulate the Linked Data Object |
|
||||||
*/ |
|
||||||
// Logs "Jane Doe"
|
|
||||||
console.log(janeProfile.name); |
|
||||||
// Logs "Person"
|
|
||||||
console.log(janeProfile.type); |
|
||||||
// Logs 0
|
|
||||||
console.log(janeProfile.knows?.length); |
|
||||||
|
|
||||||
// Begins a transaction that tracks your changes
|
|
||||||
startTransaction(janeProfile); |
|
||||||
janeProfile.name = "Jane Smith"; |
|
||||||
janeProfile.knows?.push({ |
|
||||||
"@id": "https://solidweb.me/john_smith/profile/card#me", |
|
||||||
type: { |
|
||||||
"@id": "Person", |
|
||||||
}, |
|
||||||
name: "John Smith", |
|
||||||
knows: [janeProfile], |
|
||||||
}); |
|
||||||
|
|
||||||
// Logs "Jane Smith"
|
|
||||||
console.log(janeProfile.name); |
|
||||||
// Logs "John Smith"
|
|
||||||
console.log(janeProfile.knows?.[0].name); |
|
||||||
// Logs "Jane Smith"
|
|
||||||
console.log(janeProfile.knows?.[0].knows?.[0].name); |
|
||||||
|
|
||||||
/** |
|
||||||
* Step 3: Convert it back to RDF |
|
||||||
*/ |
|
||||||
// Logs:
|
|
||||||
// <https://solidweb.me/jane_doe/profile/card#me> a <http://xmlns.com/foaf/0.1/Person>;
|
|
||||||
// <http://xmlns.com/foaf/0.1/name> "Jane Smith";
|
|
||||||
// <http://xmlns.com/foaf/0.1/knows> <https://solidweb.me/john_smith/profile/card#me>.
|
|
||||||
// <https://solidweb.me/john_smith/profile/card#me> a <http://xmlns.com/foaf/0.1/Person>;
|
|
||||||
// <http://xmlns.com/foaf/0.1/name> "John Smith";
|
|
||||||
// <http://xmlns.com/foaf/0.1/knows> <https://solidweb.me/jane_doe/profile/card#me>.
|
|
||||||
console.log(await toTurtle(janeProfile)); |
|
||||||
// Logs:
|
|
||||||
// DELETE DATA {
|
|
||||||
// <https://solidweb.me/jane_doe/profile/card#me> <http://xmlns.com/foaf/0.1/name> "Jane Doe" .
|
|
||||||
// };
|
|
||||||
// INSERT DATA {
|
|
||||||
// <https://solidweb.me/jane_doe/profile/card#me> <http://xmlns.com/foaf/0.1/name> "Jane Smith" .
|
|
||||||
// <https://solidweb.me/jane_doe/profile/card#me> <http://xmlns.com/foaf/0.1/knows> <https://solidweb.me/john_smith/profile/card#me> .
|
|
||||||
// <https://solidweb.me/john_smith/profile/card#me> <http://xmlns.com/foaf/0.1/name> "John Smith" .
|
|
||||||
// <https://solidweb.me/john_smith/profile/card#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://xmlns.com/foaf/0.1/Person> .
|
|
||||||
// <https://solidweb.me/john_smith/profile/card#me> <http://xmlns.com/foaf/0.1/knows> <https://solidweb.me/jane_doe/profile/card#me> .
|
|
||||||
// }
|
|
||||||
console.log(await toSparqlUpdate(janeProfile)); |
|
||||||
} |
|
||||||
run(); |
|
@ -1,26 +0,0 @@ |
|||||||
import type { ContextDefinition } from "jsonld"; |
|
||||||
|
|
||||||
/** |
|
||||||
* ============================================================================= |
|
||||||
* foafProfileContext: JSONLD Context for foafProfile |
|
||||||
* ============================================================================= |
|
||||||
*/ |
|
||||||
export const foafProfileContext: ContextDefinition = { |
|
||||||
type: { |
|
||||||
"@id": "@type", |
|
||||||
}, |
|
||||||
Person: "http://xmlns.com/foaf/0.1/Person", |
|
||||||
name: { |
|
||||||
"@id": "http://xmlns.com/foaf/0.1/name", |
|
||||||
"@type": "http://www.w3.org/2001/XMLSchema#string", |
|
||||||
}, |
|
||||||
img: { |
|
||||||
"@id": "http://xmlns.com/foaf/0.1/img", |
|
||||||
"@type": "http://www.w3.org/2001/XMLSchema#string", |
|
||||||
}, |
|
||||||
knows: { |
|
||||||
"@id": "http://xmlns.com/foaf/0.1/knows", |
|
||||||
"@type": "@id", |
|
||||||
"@container": "@set", |
|
||||||
}, |
|
||||||
}; |
|
@ -1,93 +0,0 @@ |
|||||||
import { Schema } from "shexj"; |
|
||||||
|
|
||||||
/** |
|
||||||
* ============================================================================= |
|
||||||
* foafProfileSchema: ShexJ Schema for foafProfile |
|
||||||
* ============================================================================= |
|
||||||
*/ |
|
||||||
export const foafProfileSchema: Schema = { |
|
||||||
type: "Schema", |
|
||||||
shapes: [ |
|
||||||
{ |
|
||||||
id: "https://example.com/FoafProfile", |
|
||||||
type: "Shape", |
|
||||||
expression: { |
|
||||||
type: "EachOf", |
|
||||||
expressions: [ |
|
||||||
{ |
|
||||||
type: "TripleConstraint", |
|
||||||
predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", |
|
||||||
valueExpr: { |
|
||||||
type: "NodeConstraint", |
|
||||||
values: ["http://xmlns.com/foaf/0.1/Person"], |
|
||||||
}, |
|
||||||
annotations: [ |
|
||||||
{ |
|
||||||
type: "Annotation", |
|
||||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
|
||||||
object: { |
|
||||||
value: "Defines the node as a Person (from foaf)", |
|
||||||
}, |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
{ |
|
||||||
type: "TripleConstraint", |
|
||||||
predicate: "http://xmlns.com/foaf/0.1/name", |
|
||||||
valueExpr: { |
|
||||||
type: "NodeConstraint", |
|
||||||
datatype: "http://www.w3.org/2001/XMLSchema#string", |
|
||||||
}, |
|
||||||
min: 0, |
|
||||||
max: 1, |
|
||||||
annotations: [ |
|
||||||
{ |
|
||||||
type: "Annotation", |
|
||||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
|
||||||
object: { |
|
||||||
value: "Define a person's name.", |
|
||||||
}, |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
{ |
|
||||||
type: "TripleConstraint", |
|
||||||
predicate: "http://xmlns.com/foaf/0.1/img", |
|
||||||
valueExpr: { |
|
||||||
type: "NodeConstraint", |
|
||||||
datatype: "http://www.w3.org/2001/XMLSchema#string", |
|
||||||
}, |
|
||||||
min: 0, |
|
||||||
max: 1, |
|
||||||
annotations: [ |
|
||||||
{ |
|
||||||
type: "Annotation", |
|
||||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
|
||||||
object: { |
|
||||||
value: "Photo link but in string form", |
|
||||||
}, |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
{ |
|
||||||
type: "TripleConstraint", |
|
||||||
predicate: "http://xmlns.com/foaf/0.1/knows", |
|
||||||
valueExpr: "https://example.com/FoafProfile", |
|
||||||
min: 0, |
|
||||||
max: -1, |
|
||||||
annotations: [ |
|
||||||
{ |
|
||||||
type: "Annotation", |
|
||||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
|
||||||
object: { |
|
||||||
value: "A list of WebIds for all the people this user knows.", |
|
||||||
}, |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
extra: ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"], |
|
||||||
}, |
|
||||||
], |
|
||||||
}; |
|
@ -1,19 +0,0 @@ |
|||||||
import { ShapeType } from "../../lib"; |
|
||||||
import { foafProfileSchema } from "./foafProfile.schema"; |
|
||||||
import { foafProfileContext } from "./foafProfile.context"; |
|
||||||
import { FoafProfile } from "./foafProfile.typings"; |
|
||||||
|
|
||||||
/** |
|
||||||
* ============================================================================= |
|
||||||
* LDO ShapeTypes foafProfile |
|
||||||
* ============================================================================= |
|
||||||
*/ |
|
||||||
|
|
||||||
/** |
|
||||||
* FoafProfile ShapeType |
|
||||||
*/ |
|
||||||
export const FoafProfileShapeType: ShapeType<FoafProfile> = { |
|
||||||
schema: foafProfileSchema, |
|
||||||
shape: "https://example.com/FoafProfile", |
|
||||||
context: foafProfileContext, |
|
||||||
}; |
|
@ -1,33 +0,0 @@ |
|||||||
import { ContextDefinition } from "jsonld"; |
|
||||||
|
|
||||||
/** |
|
||||||
* ============================================================================= |
|
||||||
* Typescript Typings for foafProfile |
|
||||||
* ============================================================================= |
|
||||||
*/ |
|
||||||
|
|
||||||
/** |
|
||||||
* FoafProfile Type |
|
||||||
*/ |
|
||||||
export interface FoafProfile { |
|
||||||
"@id"?: string; |
|
||||||
"@context"?: ContextDefinition; |
|
||||||
/** |
|
||||||
* Defines the node as a Person (from foaf) |
|
||||||
*/ |
|
||||||
type: { |
|
||||||
"@id": "Person"; |
|
||||||
}; |
|
||||||
/** |
|
||||||
* Define a person's name. |
|
||||||
*/ |
|
||||||
name?: string; |
|
||||||
/** |
|
||||||
* Photo link but in string form |
|
||||||
*/ |
|
||||||
img?: string; |
|
||||||
/** |
|
||||||
* A list of WebIds for all the people this user knows. |
|
||||||
*/ |
|
||||||
knows?: FoafProfile[]; |
|
||||||
} |
|
@ -1,15 +0,0 @@ |
|||||||
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#> |
|
||||||
|
|
||||||
ex:FoafProfile EXTRA a { |
|
||||||
a [ foaf:Person ] |
|
||||||
// rdfs:comment "Defines the node as a Person (from foaf)" ; |
|
||||||
foaf:name xsd:string ? |
|
||||||
// rdfs:comment "Define a person's name." ; |
|
||||||
foaf:img xsd:string ? |
|
||||||
// rdfs:comment "Photo link but in string form" ; |
|
||||||
foaf:knows @ex:FoafProfile * |
|
||||||
// rdfs:comment "A list of WebIds for all the people this user knows." ; |
|
||||||
} |
|
@ -0,0 +1,6 @@ |
|||||||
|
{ |
||||||
|
"entryPoints": ["src/index.ts"], |
||||||
|
"out": "docs", |
||||||
|
"allReflectionsHaveOwnDocument": true, |
||||||
|
"hideInPageTOC": true |
||||||
|
} |
@ -1,5 +1,6 @@ |
|||||||
const sharedConfig = require('../../jest.config.js'); |
const sharedConfig = require("../../jest.config.js"); |
||||||
module.exports = { |
module.exports = { |
||||||
...sharedConfig, |
...sharedConfig, |
||||||
'rootDir': './', |
rootDir: "./", |
||||||
} |
testEnvironment: "jsdom", |
||||||
|
}; |
||||||
|
@ -0,0 +1,2 @@ |
|||||||
|
import "@inrupt/jest-jsdom-polyfills"; |
||||||
|
globalThis.fetch = async () => new Response(); |
@ -0,0 +1,62 @@ |
|||||||
|
/* istanbul ignore file */ |
||||||
|
import React, { useCallback, useMemo } from "react"; |
||||||
|
import type { FunctionComponent, PropsWithChildren } from "react"; |
||||||
|
import type { LoginOptions, SessionInfo } from "./SolidAuthContext"; |
||||||
|
import { SolidAuthContext } from "./SolidAuthContext"; |
||||||
|
import libraryFetch from "cross-fetch"; |
||||||
|
import { SolidLdoProvider } from "./SolidLdoProvider"; |
||||||
|
|
||||||
|
const DUMMY_SESSION: SessionInfo = { |
||||||
|
isLoggedIn: false, |
||||||
|
webId: undefined, |
||||||
|
clientAppId: undefined, |
||||||
|
sessionId: "no_session", |
||||||
|
expirationDate: undefined, |
||||||
|
}; |
||||||
|
|
||||||
|
export const UnauthenticatedSolidLdoProvider: FunctionComponent< |
||||||
|
PropsWithChildren |
||||||
|
> = ({ children }) => { |
||||||
|
const login = useCallback( |
||||||
|
async (_issuer: string, _options?: LoginOptions) => { |
||||||
|
throw new Error( |
||||||
|
"login is not available for a UnauthenticatedSolidLdoProvider", |
||||||
|
); |
||||||
|
}, |
||||||
|
[], |
||||||
|
); |
||||||
|
|
||||||
|
const logout = useCallback(async () => { |
||||||
|
throw new Error( |
||||||
|
"logout is not available for a UnauthenticatedSolidLdoProvider", |
||||||
|
); |
||||||
|
}, []); |
||||||
|
|
||||||
|
const signUp = useCallback( |
||||||
|
async (_issuer: string, _options?: LoginOptions) => { |
||||||
|
throw new Error( |
||||||
|
"signUp is not available for a UnauthenticatedSolidLdoProvider", |
||||||
|
); |
||||||
|
}, |
||||||
|
[], |
||||||
|
); |
||||||
|
|
||||||
|
const solidAuthFunctions = useMemo( |
||||||
|
() => ({ |
||||||
|
runInitialAuthCheck: () => {}, |
||||||
|
login, |
||||||
|
logout, |
||||||
|
signUp, |
||||||
|
session: DUMMY_SESSION, |
||||||
|
ranInitialAuthCheck: true, |
||||||
|
fetch: libraryFetch, |
||||||
|
}), |
||||||
|
[login, logout, signUp], |
||||||
|
); |
||||||
|
|
||||||
|
return ( |
||||||
|
<SolidAuthContext.Provider value={solidAuthFunctions}> |
||||||
|
<SolidLdoProvider>{children}</SolidLdoProvider> |
||||||
|
</SolidAuthContext.Provider> |
||||||
|
); |
||||||
|
}; |
@ -1,7 +0,0 @@ |
|||||||
import type { Resource } from "@ldo/solid"; |
|
||||||
|
|
||||||
export function createWrapperProxy<ResourceType extends Resource>( |
|
||||||
target: ResourceType, |
|
||||||
): ResourceType { |
|
||||||
return new Proxy(target, {}); |
|
||||||
} |
|
@ -1,6 +0,0 @@ |
|||||||
import { useState, useCallback } from "react"; |
|
||||||
|
|
||||||
export function useForceReload() { |
|
||||||
const [, setValue] = useState(0); |
|
||||||
return useCallback(() => setValue((value) => value + 1), []); |
|
||||||
} |
|
@ -0,0 +1,32 @@ |
|||||||
|
import { ContextDefinition } from "jsonld"; |
||||||
|
|
||||||
|
/** |
||||||
|
* ============================================================================= |
||||||
|
* postContext: JSONLD Context for post |
||||||
|
* ============================================================================= |
||||||
|
*/ |
||||||
|
export const postContext: ContextDefinition = { |
||||||
|
type: { |
||||||
|
"@id": "@type", |
||||||
|
}, |
||||||
|
SocialMediaPosting: "http://schema.org/SocialMediaPosting", |
||||||
|
CreativeWork: "http://schema.org/CreativeWork", |
||||||
|
Thing: "http://schema.org/Thing", |
||||||
|
articleBody: { |
||||||
|
"@id": "http://schema.org/articleBody", |
||||||
|
"@type": "http://www.w3.org/2001/XMLSchema#string", |
||||||
|
}, |
||||||
|
uploadDate: { |
||||||
|
"@id": "http://schema.org/uploadDate", |
||||||
|
"@type": "http://www.w3.org/2001/XMLSchema#date", |
||||||
|
}, |
||||||
|
image: { |
||||||
|
"@id": "http://schema.org/image", |
||||||
|
"@type": "@id", |
||||||
|
}, |
||||||
|
publisher: { |
||||||
|
"@id": "http://schema.org/publisher", |
||||||
|
"@type": "@id", |
||||||
|
"@container": "@set", |
||||||
|
}, |
||||||
|
}; |
@ -0,0 +1,155 @@ |
|||||||
|
import { Schema } from "shexj"; |
||||||
|
|
||||||
|
/** |
||||||
|
* ============================================================================= |
||||||
|
* postSchema: ShexJ Schema for post |
||||||
|
* ============================================================================= |
||||||
|
*/ |
||||||
|
export const postSchema: Schema = { |
||||||
|
type: "Schema", |
||||||
|
shapes: [ |
||||||
|
{ |
||||||
|
id: "https://example.com/PostSh", |
||||||
|
type: "ShapeDecl", |
||||||
|
shapeExpr: { |
||||||
|
type: "Shape", |
||||||
|
expression: { |
||||||
|
type: "EachOf", |
||||||
|
expressions: [ |
||||||
|
{ |
||||||
|
type: "TripleConstraint", |
||||||
|
predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", |
||||||
|
valueExpr: { |
||||||
|
type: "NodeConstraint", |
||||||
|
values: [ |
||||||
|
"http://schema.org/SocialMediaPosting", |
||||||
|
"http://schema.org/CreativeWork", |
||||||
|
"http://schema.org/Thing", |
||||||
|
], |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "TripleConstraint", |
||||||
|
predicate: "http://schema.org/articleBody", |
||||||
|
valueExpr: { |
||||||
|
type: "NodeConstraint", |
||||||
|
datatype: "http://www.w3.org/2001/XMLSchema#string", |
||||||
|
}, |
||||||
|
min: 0, |
||||||
|
max: 1, |
||||||
|
annotations: [ |
||||||
|
{ |
||||||
|
type: "Annotation", |
||||||
|
predicate: "http://www.w3.org/2000/01/rdf-schema#label", |
||||||
|
object: { |
||||||
|
value: "articleBody", |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "Annotation", |
||||||
|
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||||
|
object: { |
||||||
|
value: "The actual body of the article. ", |
||||||
|
}, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "TripleConstraint", |
||||||
|
predicate: "http://schema.org/uploadDate", |
||||||
|
valueExpr: { |
||||||
|
type: "NodeConstraint", |
||||||
|
datatype: "http://www.w3.org/2001/XMLSchema#date", |
||||||
|
}, |
||||||
|
annotations: [ |
||||||
|
{ |
||||||
|
type: "Annotation", |
||||||
|
predicate: "http://www.w3.org/2000/01/rdf-schema#label", |
||||||
|
object: { |
||||||
|
value: "uploadDate", |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "Annotation", |
||||||
|
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||||
|
object: { |
||||||
|
value: |
||||||
|
"Date when this media object was uploaded to this site.", |
||||||
|
}, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "TripleConstraint", |
||||||
|
predicate: "http://schema.org/image", |
||||||
|
valueExpr: { |
||||||
|
type: "NodeConstraint", |
||||||
|
nodeKind: "iri", |
||||||
|
}, |
||||||
|
min: 0, |
||||||
|
max: 1, |
||||||
|
annotations: [ |
||||||
|
{ |
||||||
|
type: "Annotation", |
||||||
|
predicate: "http://www.w3.org/2000/01/rdf-schema#label", |
||||||
|
object: { |
||||||
|
value: "image", |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "Annotation", |
||||||
|
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||||
|
object: { |
||||||
|
value: |
||||||
|
"A media object that encodes this CreativeWork. This property is a synonym for encoding.", |
||||||
|
}, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "TripleConstraint", |
||||||
|
predicate: "http://schema.org/publisher", |
||||||
|
valueExpr: { |
||||||
|
type: "NodeConstraint", |
||||||
|
nodeKind: "iri", |
||||||
|
}, |
||||||
|
annotations: [ |
||||||
|
{ |
||||||
|
type: "Annotation", |
||||||
|
predicate: "http://www.w3.org/2000/01/rdf-schema#label", |
||||||
|
object: { |
||||||
|
value: "publisher", |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "Annotation", |
||||||
|
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||||
|
object: { |
||||||
|
value: "The publisher of the creative work.", |
||||||
|
}, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
annotations: [ |
||||||
|
{ |
||||||
|
type: "Annotation", |
||||||
|
predicate: "http://www.w3.org/2000/01/rdf-schema#label", |
||||||
|
object: { |
||||||
|
value: "SocialMediaPost", |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "Annotation", |
||||||
|
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||||
|
object: { |
||||||
|
value: |
||||||
|
"A post to a social media platform, including blog posts, tweets, Facebook posts, etc.", |
||||||
|
}, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
}, |
||||||
|
], |
||||||
|
}; |
@ -0,0 +1,19 @@ |
|||||||
|
import { ShapeType } from "@ldo/ldo"; |
||||||
|
import { postSchema } from "./post.schema"; |
||||||
|
import { postContext } from "./post.context"; |
||||||
|
import { PostSh } from "./post.typings"; |
||||||
|
|
||||||
|
/** |
||||||
|
* ============================================================================= |
||||||
|
* LDO ShapeTypes post |
||||||
|
* ============================================================================= |
||||||
|
*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* PostSh ShapeType |
||||||
|
*/ |
||||||
|
export const PostShShapeType: ShapeType<PostSh> = { |
||||||
|
schema: postSchema, |
||||||
|
shape: "https://example.com/PostSh", |
||||||
|
context: postContext, |
||||||
|
}; |
@ -0,0 +1,45 @@ |
|||||||
|
import { ContextDefinition } from "jsonld"; |
||||||
|
|
||||||
|
/** |
||||||
|
* ============================================================================= |
||||||
|
* Typescript Typings for post |
||||||
|
* ============================================================================= |
||||||
|
*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* PostSh Type |
||||||
|
*/ |
||||||
|
export interface PostSh { |
||||||
|
"@id"?: string; |
||||||
|
"@context"?: ContextDefinition; |
||||||
|
type: |
||||||
|
| { |
||||||
|
"@id": "SocialMediaPosting"; |
||||||
|
} |
||||||
|
| { |
||||||
|
"@id": "CreativeWork"; |
||||||
|
} |
||||||
|
| { |
||||||
|
"@id": "Thing"; |
||||||
|
}; |
||||||
|
/** |
||||||
|
* The actual body of the article. |
||||||
|
*/ |
||||||
|
articleBody?: string; |
||||||
|
/** |
||||||
|
* Date when this media object was uploaded to this site. |
||||||
|
*/ |
||||||
|
uploadDate: string; |
||||||
|
/** |
||||||
|
* A media object that encodes this CreativeWork. This property is a synonym for encoding. |
||||||
|
*/ |
||||||
|
image?: { |
||||||
|
"@id": string; |
||||||
|
}; |
||||||
|
/** |
||||||
|
* The publisher of the creative work. |
||||||
|
*/ |
||||||
|
publisher: { |
||||||
|
"@id": string; |
||||||
|
}[]; |
||||||
|
} |
@ -0,0 +1,358 @@ |
|||||||
|
import React, { useEffect, useState } from "react"; |
||||||
|
import type { FunctionComponent } from "react"; |
||||||
|
import { render, screen, fireEvent } from "@testing-library/react"; |
||||||
|
import { |
||||||
|
SAMPLE_BINARY_URI, |
||||||
|
SAMPLE_DATA_URI, |
||||||
|
SERVER_DOMAIN, |
||||||
|
setUpServer, |
||||||
|
} from "./setUpServer"; |
||||||
|
import { UnauthenticatedSolidLdoProvider } from "../src/UnauthenticatedSolidLdoProvider"; |
||||||
|
import { useResource } from "../src/useResource"; |
||||||
|
import { useRootContainerFor } from "../src/useRootContainer"; |
||||||
|
import { useLdo } from "../src/SolidLdoProvider"; |
||||||
|
import { PostShShapeType } from "./.ldo/post.shapeTypes"; |
||||||
|
import type { PostSh } from "./.ldo/post.typings"; |
||||||
|
import { useSubject } from "../src/useSubject"; |
||||||
|
|
||||||
|
// Use an increased timeout, since the CSS server takes too much setup time.
|
||||||
|
jest.setTimeout(40_000); |
||||||
|
|
||||||
|
describe("Integration Tests", () => { |
||||||
|
setUpServer(); |
||||||
|
|
||||||
|
/** |
||||||
|
* =========================================================================== |
||||||
|
* useResource |
||||||
|
* =========================================================================== |
||||||
|
*/ |
||||||
|
describe("useResource", () => { |
||||||
|
it("Fetches a resource and indicates it is loading while doing so", async () => { |
||||||
|
const UseResourceTest: FunctionComponent = () => { |
||||||
|
const resource = useResource(SAMPLE_DATA_URI); |
||||||
|
if (resource?.isLoading()) return <p>Loading</p>; |
||||||
|
return <p role="status">{resource.status.type}</p>; |
||||||
|
}; |
||||||
|
render( |
||||||
|
<UnauthenticatedSolidLdoProvider> |
||||||
|
<UseResourceTest /> |
||||||
|
</UnauthenticatedSolidLdoProvider>, |
||||||
|
); |
||||||
|
await screen.findByText("Loading"); |
||||||
|
const resourceStatus = await screen.findByRole("status"); |
||||||
|
expect(resourceStatus.innerHTML).toBe("dataReadSuccess"); |
||||||
|
}); |
||||||
|
|
||||||
|
it("returns undefined when no uri is provided, then rerenders when one is", async () => { |
||||||
|
const UseResourceUndefinedTest: FunctionComponent = () => { |
||||||
|
const [uri, setUri] = useState<string | undefined>(undefined); |
||||||
|
const resource = useResource(uri, { suppressInitialRead: true }); |
||||||
|
if (!resource) |
||||||
|
return ( |
||||||
|
<div> |
||||||
|
<p>Undefined</p> |
||||||
|
<button onClick={() => setUri(SAMPLE_DATA_URI)}>Next</button> |
||||||
|
</div> |
||||||
|
); |
||||||
|
return <p role="status">{resource.status.type}</p>; |
||||||
|
}; |
||||||
|
render( |
||||||
|
<UnauthenticatedSolidLdoProvider> |
||||||
|
<UseResourceUndefinedTest /> |
||||||
|
</UnauthenticatedSolidLdoProvider>, |
||||||
|
); |
||||||
|
await screen.findByText("Undefined"); |
||||||
|
fireEvent.click(screen.getByText("Next")); |
||||||
|
const resourceStatus = await screen.findByRole("status"); |
||||||
|
expect(resourceStatus.innerHTML).toBe("unfetched"); |
||||||
|
}); |
||||||
|
|
||||||
|
it("Reloads the data on mount", async () => { |
||||||
|
const ReloadTest: FunctionComponent = () => { |
||||||
|
const resource = useResource(SAMPLE_DATA_URI, { reloadOnMount: true }); |
||||||
|
if (resource?.isLoading()) return <p>Loading</p>; |
||||||
|
return <p role="status">{resource.status.type}</p>; |
||||||
|
}; |
||||||
|
const ReloadParent: FunctionComponent = () => { |
||||||
|
const [showComponent, setShowComponent] = useState(true); |
||||||
|
return ( |
||||||
|
<div> |
||||||
|
<button onClick={() => setShowComponent(!showComponent)}> |
||||||
|
Show Component |
||||||
|
</button> |
||||||
|
{showComponent ? <ReloadTest /> : <p>Hidden</p>} |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
render( |
||||||
|
<UnauthenticatedSolidLdoProvider> |
||||||
|
<ReloadParent /> |
||||||
|
</UnauthenticatedSolidLdoProvider>, |
||||||
|
); |
||||||
|
await screen.findByText("Loading"); |
||||||
|
const resourceStatus1 = await screen.findByRole("status"); |
||||||
|
expect(resourceStatus1.innerHTML).toBe("dataReadSuccess"); |
||||||
|
fireEvent.click(screen.getByText("Show Component")); |
||||||
|
await screen.findByText("Hidden"); |
||||||
|
fireEvent.click(screen.getByText("Show Component")); |
||||||
|
await screen.findByText("Loading"); |
||||||
|
const resourceStatus2 = await screen.findByRole("status"); |
||||||
|
expect(resourceStatus2.innerHTML).toBe("dataReadSuccess"); |
||||||
|
}); |
||||||
|
|
||||||
|
it("handles swapping to a new resource", async () => { |
||||||
|
const SwapResourceTest: FunctionComponent = () => { |
||||||
|
const [uri, setUri] = useState(SAMPLE_DATA_URI); |
||||||
|
const resource = useResource(uri); |
||||||
|
if (resource?.isLoading()) return <p>Loading</p>; |
||||||
|
return ( |
||||||
|
<div> |
||||||
|
<p role="status">{resource.status.type}</p> |
||||||
|
<button onClick={() => setUri(SAMPLE_BINARY_URI)}> |
||||||
|
Update URI |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
render( |
||||||
|
<UnauthenticatedSolidLdoProvider> |
||||||
|
<SwapResourceTest /> |
||||||
|
</UnauthenticatedSolidLdoProvider>, |
||||||
|
); |
||||||
|
await screen.findByText("Loading"); |
||||||
|
const resourceStatus1 = await screen.findByRole("status"); |
||||||
|
expect(resourceStatus1.innerHTML).toBe("dataReadSuccess"); |
||||||
|
fireEvent.click(screen.getByText("Update URI")); |
||||||
|
await screen.findByText("Loading"); |
||||||
|
const resourceStatus2 = await screen.findByRole("status"); |
||||||
|
expect(resourceStatus2.innerHTML).toBe("binaryReadSuccess"); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe("useRootContainer", () => { |
||||||
|
it("gets the root container for a sub-resource", async () => { |
||||||
|
const RootContainerTest: FunctionComponent = () => { |
||||||
|
const rootContainer = useRootContainerFor(SAMPLE_DATA_URI, { |
||||||
|
suppressInitialRead: true, |
||||||
|
}); |
||||||
|
return rootContainer ? ( |
||||||
|
<p role="root">{rootContainer?.uri}</p> |
||||||
|
) : undefined; |
||||||
|
}; |
||||||
|
render( |
||||||
|
<UnauthenticatedSolidLdoProvider> |
||||||
|
<RootContainerTest /> |
||||||
|
</UnauthenticatedSolidLdoProvider>, |
||||||
|
); |
||||||
|
const container = await screen.findByRole("root"); |
||||||
|
expect(container.innerHTML).toBe(SERVER_DOMAIN); |
||||||
|
}); |
||||||
|
|
||||||
|
it("returns undefined when a URI is not provided", async () => { |
||||||
|
const RootContainerTest: FunctionComponent = () => { |
||||||
|
const rootContainer = useRootContainerFor(undefined, { |
||||||
|
suppressInitialRead: true, |
||||||
|
}); |
||||||
|
return rootContainer ? ( |
||||||
|
<p role="root">{rootContainer?.uri}</p> |
||||||
|
) : ( |
||||||
|
<p role="undefined">Undefined</p> |
||||||
|
); |
||||||
|
}; |
||||||
|
render( |
||||||
|
<UnauthenticatedSolidLdoProvider> |
||||||
|
<RootContainerTest /> |
||||||
|
</UnauthenticatedSolidLdoProvider>, |
||||||
|
); |
||||||
|
const container = await screen.findByRole("undefined"); |
||||||
|
expect(container.innerHTML).toBe("Undefined"); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe("useLdoMethod", () => { |
||||||
|
it("uses get subject to get a linked data object", async () => { |
||||||
|
const GetSubjectTest: FunctionComponent = () => { |
||||||
|
const [subject, setSubject] = useState<PostSh | undefined>(); |
||||||
|
const { getSubject } = useLdo(); |
||||||
|
useEffect(() => { |
||||||
|
const someSubject = getSubject( |
||||||
|
PostShShapeType, |
||||||
|
"https://example.com/subject", |
||||||
|
); |
||||||
|
setSubject(someSubject); |
||||||
|
}, []); |
||||||
|
return subject ? <p role="subject">{subject["@id"]}</p> : undefined; |
||||||
|
}; |
||||||
|
render( |
||||||
|
<UnauthenticatedSolidLdoProvider> |
||||||
|
<GetSubjectTest /> |
||||||
|
</UnauthenticatedSolidLdoProvider>, |
||||||
|
); |
||||||
|
const container = await screen.findByRole("subject"); |
||||||
|
expect(container.innerHTML).toBe("https://example.com/subject"); |
||||||
|
}); |
||||||
|
|
||||||
|
it("uses createData to create a new data object", async () => { |
||||||
|
const GetSubjectTest: FunctionComponent = () => { |
||||||
|
const [subject, setSubject] = useState<PostSh | undefined>(); |
||||||
|
const { createData, getResource } = useLdo(); |
||||||
|
useEffect(() => { |
||||||
|
const someSubject = createData( |
||||||
|
PostShShapeType, |
||||||
|
"https://example.com/subject", |
||||||
|
getResource("https://example.com/"), |
||||||
|
); |
||||||
|
someSubject.articleBody = "Cool Article"; |
||||||
|
setSubject(someSubject); |
||||||
|
}, []); |
||||||
|
return subject ? ( |
||||||
|
<p role="subject">{subject.articleBody}</p> |
||||||
|
) : undefined; |
||||||
|
}; |
||||||
|
render( |
||||||
|
<UnauthenticatedSolidLdoProvider> |
||||||
|
<GetSubjectTest /> |
||||||
|
</UnauthenticatedSolidLdoProvider>, |
||||||
|
); |
||||||
|
const container = await screen.findByRole("subject"); |
||||||
|
expect(container.innerHTML).toBe("Cool Article"); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe("useSubject", () => { |
||||||
|
it("renders the article body from the useSubject value", async () => { |
||||||
|
const UseSubjectTest: FunctionComponent = () => { |
||||||
|
useResource(SAMPLE_DATA_URI); |
||||||
|
const post = useSubject(PostShShapeType, `${SAMPLE_DATA_URI}#Post1`); |
||||||
|
|
||||||
|
return <p role="article">{post.articleBody}</p>; |
||||||
|
}; |
||||||
|
render( |
||||||
|
<UnauthenticatedSolidLdoProvider> |
||||||
|
<UseSubjectTest /> |
||||||
|
</UnauthenticatedSolidLdoProvider>, |
||||||
|
); |
||||||
|
|
||||||
|
await screen.findByText("test"); |
||||||
|
}); |
||||||
|
|
||||||
|
it("renders the array value from the useSubject value", async () => { |
||||||
|
const UseSubjectTest: FunctionComponent = () => { |
||||||
|
const resource = useResource(SAMPLE_DATA_URI); |
||||||
|
const post = useSubject(PostShShapeType, `${SAMPLE_DATA_URI}#Post1`); |
||||||
|
if (resource.isLoading() || !post) return <p>loading</p>; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div> |
||||||
|
<p role="single">{post.publisher[0]["@id"]}</p> |
||||||
|
<ul role="list"> |
||||||
|
{post.publisher.map((publisher) => { |
||||||
|
return <li key={publisher["@id"]}>{publisher["@id"]}</li>; |
||||||
|
})} |
||||||
|
</ul> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
render( |
||||||
|
<UnauthenticatedSolidLdoProvider> |
||||||
|
<UseSubjectTest /> |
||||||
|
</UnauthenticatedSolidLdoProvider>, |
||||||
|
); |
||||||
|
|
||||||
|
const single = await screen.findByRole("single"); |
||||||
|
expect(single.innerHTML).toBe("https://example.com/Publisher1"); |
||||||
|
const list = await screen.findByRole("list"); |
||||||
|
expect(list.children[0].innerHTML).toBe("https://example.com/Publisher1"); |
||||||
|
expect(list.children[1].innerHTML).toBe("https://example.com/Publisher2"); |
||||||
|
}); |
||||||
|
|
||||||
|
it("returns undefined in the subject URI is undefined", async () => { |
||||||
|
const UseSubjectTest: FunctionComponent = () => { |
||||||
|
useResource(SAMPLE_DATA_URI, { suppressInitialRead: true }); |
||||||
|
const post = useSubject(PostShShapeType, undefined); |
||||||
|
|
||||||
|
return ( |
||||||
|
<p role="article"> |
||||||
|
{post === undefined ? "Undefined" : "Not Undefined"} |
||||||
|
</p> |
||||||
|
); |
||||||
|
}; |
||||||
|
render( |
||||||
|
<UnauthenticatedSolidLdoProvider> |
||||||
|
<UseSubjectTest /> |
||||||
|
</UnauthenticatedSolidLdoProvider>, |
||||||
|
); |
||||||
|
|
||||||
|
const article = await screen.findByRole("article"); |
||||||
|
expect(article.innerHTML).toBe("Undefined"); |
||||||
|
}); |
||||||
|
|
||||||
|
it("returns nothing if a symbol key is provided", async () => { |
||||||
|
const UseSubjectTest: FunctionComponent = () => { |
||||||
|
const resource = useResource(SAMPLE_DATA_URI); |
||||||
|
const post = useSubject(PostShShapeType, `${SAMPLE_DATA_URI}#Post1`); |
||||||
|
if (resource.isLoading() || !post) return <p>loading</p>; |
||||||
|
|
||||||
|
return <p role="value">{typeof post[Symbol.hasInstance]}</p>; |
||||||
|
}; |
||||||
|
render( |
||||||
|
<UnauthenticatedSolidLdoProvider> |
||||||
|
<UseSubjectTest /> |
||||||
|
</UnauthenticatedSolidLdoProvider>, |
||||||
|
); |
||||||
|
|
||||||
|
const article = await screen.findByRole("value"); |
||||||
|
expect(article.innerHTML).toBe("undefined"); |
||||||
|
}); |
||||||
|
|
||||||
|
it("returns an id if an id key is provided", async () => { |
||||||
|
const UseSubjectTest: FunctionComponent = () => { |
||||||
|
const resource = useResource(SAMPLE_DATA_URI); |
||||||
|
const post = useSubject(PostShShapeType, `${SAMPLE_DATA_URI}#Post1`); |
||||||
|
if (resource.isLoading() || !post) return <p>loading</p>; |
||||||
|
|
||||||
|
return <p role="value">{post["@id"]}</p>; |
||||||
|
}; |
||||||
|
render( |
||||||
|
<UnauthenticatedSolidLdoProvider> |
||||||
|
<UseSubjectTest /> |
||||||
|
</UnauthenticatedSolidLdoProvider>, |
||||||
|
); |
||||||
|
|
||||||
|
const article = await screen.findByRole("value"); |
||||||
|
expect(article.innerHTML).toBe(`${SAMPLE_DATA_URI}#Post1`); |
||||||
|
}); |
||||||
|
|
||||||
|
it("does not set a value if a value is attempted to be set", async () => { |
||||||
|
const warn = jest.spyOn(console, "warn").mockImplementation(() => {}); |
||||||
|
const UseSubjectTest: FunctionComponent = () => { |
||||||
|
const resource = useResource(SAMPLE_DATA_URI); |
||||||
|
const post = useSubject(PostShShapeType, `${SAMPLE_DATA_URI}#Post1`); |
||||||
|
if (resource.isLoading() || !post) return <p>loading</p>; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div> |
||||||
|
<p role="value">{post.articleBody}</p> |
||||||
|
<button onClick={() => (post.articleBody = "bad")}> |
||||||
|
Attempt Change |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
render( |
||||||
|
<UnauthenticatedSolidLdoProvider> |
||||||
|
<UseSubjectTest /> |
||||||
|
</UnauthenticatedSolidLdoProvider>, |
||||||
|
); |
||||||
|
|
||||||
|
const article = await screen.findByRole("value"); |
||||||
|
expect(article.innerHTML).toBe(`test`); |
||||||
|
fireEvent.click(screen.getByText("Attempt Change")); |
||||||
|
expect(article.innerHTML).not.toBe("bad"); |
||||||
|
expect(warn).toHaveBeenCalledWith( |
||||||
|
"You've attempted to set a value on a Linked Data Object from the useSubject, useMatchingSubject, or useMatchingObject hooks. These linked data objects should only be used to render data, not modify it. To modify data, use the `changeData` function.", |
||||||
|
); |
||||||
|
warn.mockReset(); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,110 @@ |
|||||||
|
import type { ContainerUri, LeafUri } from "@ldo/solid"; |
||||||
|
import fetch from "cross-fetch"; |
||||||
|
|
||||||
|
export const SERVER_DOMAIN = process.env.SERVER || "http://localhost:3001/"; |
||||||
|
export const ROOT_ROUTE = process.env.ROOT_CONTAINER || "example/"; |
||||||
|
export const ROOT_CONTAINER = `${SERVER_DOMAIN}${ROOT_ROUTE}`; |
||||||
|
export const TEST_CONTAINER_SLUG = "test_ldo/"; |
||||||
|
export const TEST_CONTAINER_URI = |
||||||
|
`${ROOT_CONTAINER}${TEST_CONTAINER_SLUG}` as ContainerUri; |
||||||
|
export const SAMPLE_DATA_URI = `${TEST_CONTAINER_URI}sample.ttl` as LeafUri; |
||||||
|
export const SAMPLE2_DATA_SLUG = "sample2.ttl"; |
||||||
|
export const SAMPLE2_DATA_URI = |
||||||
|
`${TEST_CONTAINER_URI}${SAMPLE2_DATA_SLUG}` as LeafUri; |
||||||
|
export const SAMPLE_BINARY_URI = `${TEST_CONTAINER_URI}sample.txt` as LeafUri; |
||||||
|
export const SAMPLE2_BINARY_SLUG = `sample2.txt`; |
||||||
|
export const SAMPLE2_BINARY_URI = |
||||||
|
`${TEST_CONTAINER_URI}${SAMPLE2_BINARY_SLUG}` as LeafUri; |
||||||
|
export const SAMPLE_CONTAINER_URI = |
||||||
|
`${TEST_CONTAINER_URI}sample_container/` as ContainerUri; |
||||||
|
export const EXAMPLE_POST_TTL = `@prefix schema: <http://schema.org/> .
|
||||||
|
|
||||||
|
<#Post1> |
||||||
|
a schema:CreativeWork, schema:Thing, schema:SocialMediaPosting ; |
||||||
|
schema:image <https://example.com/postImage.jpg> ; |
||||||
|
schema:articleBody "test" ; |
||||||
|
schema:publisher <https://example.com/Publisher1>, <https://example.com/Publisher2> .`;
|
||||||
|
export const TEST_CONTAINER_TTL = `@prefix dc: <http://purl.org/dc/terms/>.
|
||||||
|
@prefix ldp: <http://www.w3.org/ns/ldp#>. |
||||||
|
@prefix posix: <http://www.w3.org/ns/posix/stat#>. |
||||||
|
@prefix xsd: <http://www.w3.org/2001/XMLSchema#>. |
||||||
|
|
||||||
|
<> <urn:npm:solid:community-server:http:slug> "sample.txt"; |
||||||
|
a ldp:Container, ldp:BasicContainer, ldp:Resource; |
||||||
|
dc:modified "2023-10-20T13:57:14.000Z"^^xsd:dateTime. |
||||||
|
<sample.ttl> a ldp:Resource, <http://www.w3.org/ns/iana/media-types/text/turtle#Resource>; |
||||||
|
dc:modified "2023-10-20T13:57:14.000Z"^^xsd:dateTime. |
||||||
|
<sample.txt> a ldp:Resource, <http://www.w3.org/ns/iana/media-types/text/plain#Resource>; |
||||||
|
dc:modified "2023-10-20T13:57:14.000Z"^^xsd:dateTime. |
||||||
|
<> posix:mtime 1697810234; |
||||||
|
ldp:contains <sample.ttl>, <sample.txt>. |
||||||
|
<sample.ttl> posix:mtime 1697810234; |
||||||
|
posix:size 522. |
||||||
|
<sample.txt> posix:mtime 1697810234; |
||||||
|
posix:size 10.`;
|
||||||
|
|
||||||
|
export interface SetUpServerReturn { |
||||||
|
authFetch: typeof fetch; |
||||||
|
fetchMock: jest.Mock< |
||||||
|
Promise<Response>, |
||||||
|
[input: RequestInfo | URL, init?: RequestInit | undefined] |
||||||
|
>; |
||||||
|
} |
||||||
|
|
||||||
|
export function setUpServer(): SetUpServerReturn { |
||||||
|
// Ignore to build s
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
const s: SetUpServerReturn = {}; |
||||||
|
|
||||||
|
beforeAll(async () => { |
||||||
|
// s.authFetch = await getAuthenticatedFetch();
|
||||||
|
s.authFetch = fetch; |
||||||
|
}); |
||||||
|
|
||||||
|
beforeEach(async () => { |
||||||
|
s.fetchMock = jest.fn(s.authFetch); |
||||||
|
// Create a new document called sample.ttl
|
||||||
|
await s.authFetch(ROOT_CONTAINER, { |
||||||
|
method: "POST", |
||||||
|
headers: { |
||||||
|
link: '<http://www.w3.org/ns/ldp#Container>; rel="type"', |
||||||
|
slug: TEST_CONTAINER_SLUG, |
||||||
|
}, |
||||||
|
}); |
||||||
|
await Promise.all([ |
||||||
|
s.authFetch(TEST_CONTAINER_URI, { |
||||||
|
method: "POST", |
||||||
|
headers: { "content-type": "text/turtle", slug: "sample.ttl" }, |
||||||
|
body: EXAMPLE_POST_TTL, |
||||||
|
}), |
||||||
|
s.authFetch(TEST_CONTAINER_URI, { |
||||||
|
method: "POST", |
||||||
|
headers: { "content-type": "text/plain", slug: "sample.txt" }, |
||||||
|
body: "some text.", |
||||||
|
}), |
||||||
|
]); |
||||||
|
}); |
||||||
|
|
||||||
|
afterEach(async () => { |
||||||
|
await Promise.all([ |
||||||
|
s.authFetch(SAMPLE_DATA_URI, { |
||||||
|
method: "DELETE", |
||||||
|
}), |
||||||
|
s.authFetch(SAMPLE2_DATA_URI, { |
||||||
|
method: "DELETE", |
||||||
|
}), |
||||||
|
s.authFetch(SAMPLE_BINARY_URI, { |
||||||
|
method: "DELETE", |
||||||
|
}), |
||||||
|
s.authFetch(SAMPLE2_BINARY_URI, { |
||||||
|
method: "DELETE", |
||||||
|
}), |
||||||
|
s.authFetch(SAMPLE_CONTAINER_URI, { |
||||||
|
method: "DELETE", |
||||||
|
}), |
||||||
|
]); |
||||||
|
}); |
||||||
|
|
||||||
|
return s; |
||||||
|
} |
@ -0,0 +1,52 @@ |
|||||||
|
{ |
||||||
|
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^6.0.0/components/context.jsonld", |
||||||
|
"import": [ |
||||||
|
"css:config/app/main/default.json", |
||||||
|
"css:config/app/init/initialize-prefilled-root.json", |
||||||
|
"css:config/app/setup/optional.json", |
||||||
|
"css:config/app/variables/default.json", |
||||||
|
"css:config/http/handler/default.json", |
||||||
|
"css:config/http/middleware/default.json", |
||||||
|
"css:config/http/notifications/all.json", |
||||||
|
"css:config/http/server-factory/http.json", |
||||||
|
"css:config/http/static/default.json", |
||||||
|
"css:config/identity/access/public.json", |
||||||
|
"css:config/identity/email/default.json", |
||||||
|
"css:config/identity/handler/default.json", |
||||||
|
"css:config/identity/ownership/token.json", |
||||||
|
"css:config/identity/pod/static.json", |
||||||
|
"css:config/identity/registration/enabled.json", |
||||||
|
"css:config/ldp/authentication/dpop-bearer.json", |
||||||
|
"css:config/ldp/authorization/webacl.json", |
||||||
|
"css:config/ldp/handler/default.json", |
||||||
|
"css:config/ldp/metadata-parser/default.json", |
||||||
|
"css:config/ldp/metadata-writer/default.json", |
||||||
|
"css:config/ldp/modes/default.json", |
||||||
|
"css:config/storage/backend/memory.json", |
||||||
|
"css:config/storage/key-value/resource-store.json", |
||||||
|
"css:config/storage/middleware/default.json", |
||||||
|
"css:config/util/auxiliary/acl.json", |
||||||
|
"css:config/util/identifiers/suffix.json", |
||||||
|
"css:config/util/index/default.json", |
||||||
|
"css:config/util/logging/winston.json", |
||||||
|
"css:config/util/representation-conversion/default.json", |
||||||
|
"css:config/util/resource-locker/memory.json", |
||||||
|
"css:config/util/variables/default.json" |
||||||
|
], |
||||||
|
"@graph": [ |
||||||
|
{ |
||||||
|
"comment": "A Solid server that stores its resources in memory and uses WAC for authorization." |
||||||
|
}, |
||||||
|
{ |
||||||
|
"comment": "The location of the new pod templates folder.", |
||||||
|
"@type": "Override", |
||||||
|
"overrideInstance": { |
||||||
|
"@id": "urn:solid-server:default:PodResourcesGenerator" |
||||||
|
}, |
||||||
|
"overrideParameters": { |
||||||
|
"@type": "StaticFolderGenerator", |
||||||
|
"templateFolder": "./test/test-server/configs/template" |
||||||
|
} |
||||||
|
} |
||||||
|
] |
||||||
|
} |
@ -0,0 +1,8 @@ |
|||||||
|
[ |
||||||
|
{ |
||||||
|
"podName": "example", |
||||||
|
"email": "hello@example.com", |
||||||
|
"password": "abc123", |
||||||
|
"template": "./template" |
||||||
|
} |
||||||
|
] |
@ -0,0 +1,13 @@ |
|||||||
|
@prefix : <#>. |
||||||
|
@prefix acl: <http://www.w3.org/ns/auth/acl#>. |
||||||
|
@prefix foaf: <http://xmlns.com/foaf/0.1/>. |
||||||
|
@prefix eve: <./>. |
||||||
|
@prefix c: <./profile/card#>. |
||||||
|
|
||||||
|
:ControlReadWrite |
||||||
|
a acl:Authorization; |
||||||
|
acl:accessTo eve:; |
||||||
|
acl:agent c:me, <mailto:info@o.team>; |
||||||
|
acl:agentClass foaf:Agent; |
||||||
|
acl:default eve:; |
||||||
|
acl:mode acl:Control, acl:Read, acl:Write. |
@ -0,0 +1,19 @@ |
|||||||
|
# ACL resource for the WebID profile document |
||||||
|
@prefix acl: <http://www.w3.org/ns/auth/acl#>. |
||||||
|
@prefix foaf: <http://xmlns.com/foaf/0.1/>. |
||||||
|
|
||||||
|
# The WebID profile is readable by the public. |
||||||
|
# This is required for discovery and verification, |
||||||
|
# e.g. when checking identity providers. |
||||||
|
<#public> |
||||||
|
a acl:Authorization; |
||||||
|
acl:agentClass foaf:Agent; |
||||||
|
acl:accessTo <./card>; |
||||||
|
acl:mode acl:Read. |
||||||
|
|
||||||
|
# The owner has full access to the profile |
||||||
|
<#owner> |
||||||
|
a acl:Authorization; |
||||||
|
acl:agent <{{webId}}>; |
||||||
|
acl:accessTo <./card>; |
||||||
|
acl:mode acl:Read, acl:Write, acl:Control. |
@ -0,0 +1,7 @@ |
|||||||
|
import { createApp } from "./solidServer.helper"; |
||||||
|
|
||||||
|
async function run() { |
||||||
|
const app = await createApp(); |
||||||
|
await app.start(); |
||||||
|
} |
||||||
|
run(); |
@ -0,0 +1,42 @@ |
|||||||
|
// Taken from https://github.com/comunica/comunica/blob/b237be4265c353a62a876187d9e21e3bc05123a3/engines/query-sparql/test/QuerySparql-solid-test.ts#L9
|
||||||
|
|
||||||
|
import * as path from "path"; |
||||||
|
import type { App } from "@solid/community-server"; |
||||||
|
import { AppRunner, resolveModulePath } from "@solid/community-server"; |
||||||
|
|
||||||
|
export async function createApp(): Promise<App> { |
||||||
|
if (process.env.SERVER) { |
||||||
|
return { |
||||||
|
start: () => {}, |
||||||
|
stop: () => {}, |
||||||
|
} as App; |
||||||
|
} |
||||||
|
const appRunner = new AppRunner(); |
||||||
|
return appRunner.create( |
||||||
|
{ |
||||||
|
mainModulePath: resolveModulePath(""), |
||||||
|
typeChecking: false, |
||||||
|
}, |
||||||
|
path.join( |
||||||
|
__dirname, |
||||||
|
"configs", |
||||||
|
"components-config", |
||||||
|
"unauthenticatedServer.json", |
||||||
|
), |
||||||
|
{}, |
||||||
|
{ |
||||||
|
port: 3_001, |
||||||
|
loggingLevel: "off", |
||||||
|
seededPodConfigJson: path.join( |
||||||
|
__dirname, |
||||||
|
"configs", |
||||||
|
"solid-css-seed.json", |
||||||
|
), |
||||||
|
}, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
export interface ISecretData { |
||||||
|
id: string; |
||||||
|
secret: string; |
||||||
|
} |
@ -1,5 +0,0 @@ |
|||||||
describe("Trivial", () => { |
|
||||||
it("Trivial", () => { |
|
||||||
expect(true).toBe(true); |
|
||||||
}); |
|
||||||
}); |
|
@ -1,195 +0,0 @@ |
|||||||
const resourceMethods: Record<string, string[]> = { |
|
||||||
RootContainerResource: [ |
|
||||||
"uri", |
|
||||||
"isLoading", |
|
||||||
"didInitialFetch", |
|
||||||
"ldoDataset", |
|
||||||
"getIsRootContainer", |
|
||||||
"createContainerIn", |
|
||||||
"createDataResourceIn", |
|
||||||
"uploadBinaryIn", |
|
||||||
"createOrOverwrite", |
|
||||||
"read", |
|
||||||
"reload", |
|
||||||
"load", |
|
||||||
"clear", |
|
||||||
"clearIfPresent", |
|
||||||
], |
|
||||||
ContainerResource: [ |
|
||||||
"uri", |
|
||||||
"isLoading", |
|
||||||
"didInitialFetch", |
|
||||||
"parentContainer", |
|
||||||
"getIsRootContainer", |
|
||||||
"getParentContainer", |
|
||||||
"childResources", |
|
||||||
"getRootContainer", |
|
||||||
"createContainerIn", |
|
||||||
"createDataResourceIn", |
|
||||||
"uploadBinaryIn", |
|
||||||
"ldoDataset", |
|
||||||
"createOrOverwrite", |
|
||||||
"read", |
|
||||||
"reload", |
|
||||||
"load", |
|
||||||
"delete", |
|
||||||
"deleteIfPresent", |
|
||||||
"clear", |
|
||||||
"clearIfPresent", |
|
||||||
], |
|
||||||
ChildDataResource: [ |
|
||||||
"uri", |
|
||||||
"isLoading", |
|
||||||
"didInitialFetch", |
|
||||||
"parentContainer", |
|
||||||
"getParentContainer", |
|
||||||
"hasData", |
|
||||||
"ldoDataset", |
|
||||||
"getRootContainer", |
|
||||||
"createOrOverwrite", |
|
||||||
"read", |
|
||||||
"reload", |
|
||||||
"load", |
|
||||||
"delete", |
|
||||||
"deleteIfPresent", |
|
||||||
], |
|
||||||
BinaryResource: [ |
|
||||||
"uri", |
|
||||||
"isLoading", |
|
||||||
"didInitialFetch", |
|
||||||
"mimeType", |
|
||||||
"fileExtension", |
|
||||||
"getRootContainer", |
|
||||||
"getParentContainer", |
|
||||||
"uploadOrOverwrite", |
|
||||||
"read", |
|
||||||
"reload", |
|
||||||
"load", |
|
||||||
"delete", |
|
||||||
"deleteIfPresent", |
|
||||||
], |
|
||||||
AbsentContainerResource: [ |
|
||||||
"uri", |
|
||||||
"isLoading", |
|
||||||
"didInitialFetch", |
|
||||||
"parentContainer", |
|
||||||
"getIsRootContainer", |
|
||||||
"getParentContainer", |
|
||||||
"getRootContainer", |
|
||||||
"createContainerIn", |
|
||||||
"createDataResourceIn", |
|
||||||
"uploadBinaryIn", |
|
||||||
"createOrOverwrite", |
|
||||||
"create", |
|
||||||
"createIfAbsent", |
|
||||||
"read", |
|
||||||
"reload", |
|
||||||
"load", |
|
||||||
"deleteIfPresent", |
|
||||||
"clearIfPresent", |
|
||||||
], |
|
||||||
AbsentChildDataResource: [ |
|
||||||
"uri", |
|
||||||
"isLoading", |
|
||||||
"didInitialFetch", |
|
||||||
"parentContainer", |
|
||||||
"getParentContainer", |
|
||||||
"getRootContainer", |
|
||||||
"createOrOverwrite", |
|
||||||
"create", |
|
||||||
"createIfAbsent", |
|
||||||
"read", |
|
||||||
"reload", |
|
||||||
"load", |
|
||||||
"deleteIfPresent", |
|
||||||
], |
|
||||||
AbsentBinaryResource: [ |
|
||||||
"uri", |
|
||||||
"isLoading", |
|
||||||
"didInitialFetch", |
|
||||||
"parentContainer", |
|
||||||
"getParentContainer", |
|
||||||
"getRootContainer", |
|
||||||
"uploadOrOverwrite", |
|
||||||
"upload", |
|
||||||
"uploadIfAbsent", |
|
||||||
"read", |
|
||||||
"reload", |
|
||||||
"load", |
|
||||||
"deleteIfPresent", |
|
||||||
], |
|
||||||
UnfetchedContainerResource: [ |
|
||||||
"uri", |
|
||||||
"isLoading", |
|
||||||
"didInitialFetch", |
|
||||||
"getIsRootContainer", |
|
||||||
"getParentContainer", |
|
||||||
"getRootContainer", |
|
||||||
"createContainerIn", |
|
||||||
"createDataResourceIn", |
|
||||||
"uploadBinaryIn", |
|
||||||
"createOrOverwrite", |
|
||||||
"createIfAbsent", |
|
||||||
"read", |
|
||||||
"reload", |
|
||||||
"load", |
|
||||||
"clearIfPresent", |
|
||||||
], |
|
||||||
UnfetchedChildDataResource: [ |
|
||||||
"parentContainer", |
|
||||||
"getParentContainer", |
|
||||||
"uri", |
|
||||||
"isLoading", |
|
||||||
"didInitialFetch", |
|
||||||
"getRootContainer", |
|
||||||
"createOrOverwrite", |
|
||||||
"createIfAbsent", |
|
||||||
"read", |
|
||||||
"reload", |
|
||||||
"load", |
|
||||||
"deleteIfPresent", |
|
||||||
], |
|
||||||
UnfetchedBinaryResource: [ |
|
||||||
"uri", |
|
||||||
"isLoading", |
|
||||||
"didInitialFetch", |
|
||||||
"parentContainer", |
|
||||||
"getParentContainer", |
|
||||||
"getRootContainer", |
|
||||||
"uploadOrOverwrite", |
|
||||||
"createOrOverwrite", |
|
||||||
"uploadIfAbsent", |
|
||||||
"read", |
|
||||||
"reload", |
|
||||||
"load", |
|
||||||
"deleteIfPresent", |
|
||||||
], |
|
||||||
}; |
|
||||||
|
|
||||||
function processTypes() { |
|
||||||
const usedKeys = new Set(); |
|
||||||
const interfaces = Object.keys(resourceMethods); |
|
||||||
const groupMap: Record<string, string[]> = {}; |
|
||||||
|
|
||||||
interfaces.forEach((interfaceName) => { |
|
||||||
resourceMethods[interfaceName].forEach((methodKey) => { |
|
||||||
if (!usedKeys.has(methodKey)) { |
|
||||||
usedKeys.add(methodKey); |
|
||||||
|
|
||||||
let groupName = ""; |
|
||||||
interfaces.forEach((interfaceName) => { |
|
||||||
if (resourceMethods[interfaceName].includes(methodKey)) { |
|
||||||
groupName += `${interfaceName}|`; |
|
||||||
} |
|
||||||
}); |
|
||||||
if (!groupMap[groupName]) { |
|
||||||
groupMap[groupName] = []; |
|
||||||
} |
|
||||||
groupMap[groupName].push(methodKey); |
|
||||||
} |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
console.log(groupMap); |
|
||||||
} |
|
||||||
processTypes(); |
|
@ -1,22 +0,0 @@ |
|||||||
import { Mixin } from "ts-mixer"; |
|
||||||
|
|
||||||
class Foo { |
|
||||||
protected makeFoo() { |
|
||||||
return "foo"; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
class Bar { |
|
||||||
protected makeFoo() { |
|
||||||
return "bar"; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
class FooBar extends Mixin(Foo, Bar) { |
|
||||||
public makeFooBar() { |
|
||||||
return this.makeFoo() + this.makeFoo(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
const fooBar = new FooBar(); |
|
||||||
console.log(fooBar.makeFooBar()); |
|
@ -1,16 +1,20 @@ |
|||||||
// import type TypedEmitter from "typed-emitter";
|
|
||||||
import type { ResourceStore } from "./ResourceStore"; |
import type { ResourceStore } from "./ResourceStore"; |
||||||
import type { SolidLdoDataset } from "./SolidLdoDataset"; |
import type { SolidLdoDataset } from "./SolidLdoDataset"; |
||||||
// import type { DocumentError } from "./document/errors/DocumentError";
|
|
||||||
|
|
||||||
// export type OnDocumentErrorCallback = (error: DocumentError) => void;
|
|
||||||
|
|
||||||
// export type DocumentEventEmitter = TypedEmitter<{
|
|
||||||
// documentError: OnDocumentErrorCallback;
|
|
||||||
// }>;
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Context to be shared between aspects of a SolidLdoDataset |
||||||
|
*/ |
||||||
export interface SolidLdoDatasetContext { |
export interface SolidLdoDatasetContext { |
||||||
|
/** |
||||||
|
* A pointer to the parent SolidLdoDataset |
||||||
|
*/ |
||||||
solidLdoDataset: SolidLdoDataset; |
solidLdoDataset: SolidLdoDataset; |
||||||
|
/** |
||||||
|
* The resource store of the SolidLdoDataset |
||||||
|
*/ |
||||||
resourceStore: ResourceStore; |
resourceStore: ResourceStore; |
||||||
|
/** |
||||||
|
* Http fetch function |
||||||
|
*/ |
||||||
fetch: typeof fetch; |
fetch: typeof fetch; |
||||||
} |
} |
||||||
|
@ -0,0 +1,90 @@ |
|||||||
|
import { |
||||||
|
startTransaction, |
||||||
|
type LdoBase, |
||||||
|
write, |
||||||
|
transactionChanges, |
||||||
|
getDataset, |
||||||
|
} from "@ldo/ldo"; |
||||||
|
import type { DatasetChanges } from "@ldo/rdf-utils"; |
||||||
|
import type { Resource } from "./resource/Resource"; |
||||||
|
import type { SolidLdoDataset } from "./SolidLdoDataset"; |
||||||
|
import type { Quad } from "@rdfjs/types"; |
||||||
|
import { _proxyContext, getProxyFromObject } from "@ldo/jsonld-dataset-proxy"; |
||||||
|
import type { SubscribableDataset } from "@ldo/subscribable-dataset"; |
||||||
|
|
||||||
|
/** |
||||||
|
* Begins tracking changes to eventually commit. |
||||||
|
* |
||||||
|
* @param input - A linked data object to track changes on |
||||||
|
* @param resource - A resource that all additions will eventually be committed to |
||||||
|
* @param additionalResources - Any additional resources that changes will eventually be committed to |
||||||
|
* |
||||||
|
* @returns A transactable Linked Data Object |
||||||
|
* |
||||||
|
* @example |
||||||
|
* ```typescript
|
||||||
|
* import { changeData } from "@ldo/solid"; |
||||||
|
* |
||||||
|
* // ...
|
||||||
|
* |
||||||
|
* const profile = solidLdoDataset |
||||||
|
* .using(ProfileShapeType) |
||||||
|
* .fromSubject("https://example.com/proifle#me"); |
||||||
|
* const resource = solidLdoDataset.getResource("https://example.com/profile"); |
||||||
|
* |
||||||
|
* const cProfile = changeData(profile, resource); |
||||||
|
* cProfile.name = "My New Name"; |
||||||
|
* await commitData(cProfile); |
||||||
|
* ``` |
||||||
|
*/ |
||||||
|
export function changeData<Type extends LdoBase>( |
||||||
|
input: Type, |
||||||
|
resource: Resource, |
||||||
|
...additionalResources: Resource[] |
||||||
|
): Type { |
||||||
|
const resources = [resource, ...additionalResources]; |
||||||
|
// Clone the input and set a graph
|
||||||
|
const [transactionLdo] = write(...resources.map((r) => r.uri)).usingCopy( |
||||||
|
input, |
||||||
|
); |
||||||
|
// Start a transaction with the input
|
||||||
|
startTransaction(transactionLdo); |
||||||
|
// Return
|
||||||
|
return transactionLdo; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Commits the transaction to the global dataset, syncing all subscribing |
||||||
|
* components and Solid Pods |
||||||
|
* |
||||||
|
* @param input - A transactable linked data object |
||||||
|
* |
||||||
|
* @example |
||||||
|
* ```typescript
|
||||||
|
* import { changeData } from "@ldo/solid"; |
||||||
|
* |
||||||
|
* // ...
|
||||||
|
* |
||||||
|
* const profile = solidLdoDataset |
||||||
|
* .using(ProfileShapeType) |
||||||
|
* .fromSubject("https://example.com/proifle#me"); |
||||||
|
* const resource = solidLdoDataset.getResource("https://example.com/profile"); |
||||||
|
* |
||||||
|
* const cProfile = changeData(profile, resource); |
||||||
|
* cProfile.name = "My New Name"; |
||||||
|
* await commitData(cProfile); |
||||||
|
* ``` |
||||||
|
*/ |
||||||
|
export function commitData( |
||||||
|
input: LdoBase, |
||||||
|
): ReturnType<SolidLdoDataset["commitChangesToPod"]> { |
||||||
|
const changes = transactionChanges(input); |
||||||
|
// Take the LdoProxy out of commit mode. This uses hidden methods of JSONLD-DATASET-PROXY
|
||||||
|
const proxy = getProxyFromObject(input); |
||||||
|
proxy[_proxyContext] = proxy[_proxyContext].duplicate({ |
||||||
|
dataset: proxy[_proxyContext].state |
||||||
|
.parentDataset as SubscribableDataset<Quad>, |
||||||
|
}); |
||||||
|
const dataset = getDataset(input) as SolidLdoDataset; |
||||||
|
return dataset.commitChangesToPod(changes as DatasetChanges<Quad>); |
||||||
|
} |
@ -1,9 +1,22 @@ |
|||||||
import type { Dataset, Quad } from "@rdfjs/types"; |
import type { BulkEditableDataset } from "@ldo/subscribable-dataset"; |
||||||
|
import type { Quad } from "@rdfjs/types"; |
||||||
|
|
||||||
|
/** |
||||||
|
* Request Options to be passed to request functions |
||||||
|
*/ |
||||||
export interface BasicRequestOptions { |
export interface BasicRequestOptions { |
||||||
|
/** |
||||||
|
* A fetch function usually imported from @inrupt/solid-client-authn-js |
||||||
|
*/ |
||||||
fetch?: typeof fetch; |
fetch?: typeof fetch; |
||||||
} |
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Request options with a dataset component |
||||||
|
*/ |
||||||
export interface DatasetRequestOptions extends BasicRequestOptions { |
export interface DatasetRequestOptions extends BasicRequestOptions { |
||||||
dataset?: Dataset<Quad>; |
/** |
||||||
|
* A dataset to be modified with any new information obtained from a request |
||||||
|
*/ |
||||||
|
dataset?: BulkEditableDataset<Quad>; |
||||||
} |
} |
||||||
|
@ -1,6 +1,13 @@ |
|||||||
import type { ResourceSuccess } from "./SuccessResult"; |
import type { ResourceSuccess } from "./SuccessResult"; |
||||||
|
|
||||||
|
/** |
||||||
|
* Indicates that the request to check if a resource is the root container was |
||||||
|
* a success. |
||||||
|
*/ |
||||||
export interface CheckRootContainerSuccess extends ResourceSuccess { |
export interface CheckRootContainerSuccess extends ResourceSuccess { |
||||||
type: "checkRootContainerSuccess"; |
type: "checkRootContainerSuccess"; |
||||||
|
/** |
||||||
|
* True if this resoure is the root container |
||||||
|
*/ |
||||||
isRootContainer: boolean; |
isRootContainer: boolean; |
||||||
} |
} |
||||||
|
@ -1,6 +1,13 @@ |
|||||||
import type { ResourceSuccess } from "./SuccessResult"; |
import type { ResourceSuccess } from "./SuccessResult"; |
||||||
|
|
||||||
|
/** |
||||||
|
* Indicates that the request to create the resource was a success. |
||||||
|
*/ |
||||||
export interface CreateSuccess extends ResourceSuccess { |
export interface CreateSuccess extends ResourceSuccess { |
||||||
type: "createSuccess"; |
type: "createSuccess"; |
||||||
|
/** |
||||||
|
* True if there was a resource that existed before at the given URI that was |
||||||
|
* overwritten |
||||||
|
*/ |
||||||
didOverwrite: boolean; |
didOverwrite: boolean; |
||||||
} |
} |
||||||
|
@ -1,6 +1,14 @@ |
|||||||
import type { ResourceSuccess } from "./SuccessResult"; |
import type { ResourceSuccess } from "./SuccessResult"; |
||||||
|
|
||||||
|
/** |
||||||
|
* Indicates that the request to delete a resource was a success. |
||||||
|
*/ |
||||||
export interface DeleteSuccess extends ResourceSuccess { |
export interface DeleteSuccess extends ResourceSuccess { |
||||||
type: "deleteSuccess"; |
type: "deleteSuccess"; |
||||||
|
|
||||||
|
/** |
||||||
|
* True if there was a resource at the provided URI that was deleted. False if |
||||||
|
* a resource didn't exist. |
||||||
|
*/ |
||||||
resourceExisted: boolean; |
resourceExisted: boolean; |
||||||
} |
} |
||||||
|
@ -1,15 +1,31 @@ |
|||||||
import type { RequesterResult } from "../RequesterResult"; |
import type { RequesterResult } from "../RequesterResult"; |
||||||
|
|
||||||
|
/** |
||||||
|
* Indicates that some action taken by LDO was a success |
||||||
|
*/ |
||||||
export interface SuccessResult extends RequesterResult { |
export interface SuccessResult extends RequesterResult { |
||||||
isError: false; |
isError: false; |
||||||
} |
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Indicates that a request to a resource was aa success |
||||||
|
*/ |
||||||
export interface ResourceSuccess extends SuccessResult { |
export interface ResourceSuccess extends SuccessResult { |
||||||
|
/** |
||||||
|
* The URI of the resource |
||||||
|
*/ |
||||||
uri: string; |
uri: string; |
||||||
} |
} |
||||||
|
|
||||||
|
/** |
||||||
|
* A grouping of multiple successes as a result of an action |
||||||
|
*/ |
||||||
export interface AggregateSuccess<SuccessType extends SuccessResult> |
export interface AggregateSuccess<SuccessType extends SuccessResult> |
||||||
extends SuccessResult { |
extends SuccessResult { |
||||||
type: "aggregateSuccess"; |
type: "aggregateSuccess"; |
||||||
|
|
||||||
|
/** |
||||||
|
* An array of all successesses |
||||||
|
*/ |
||||||
results: SuccessType[]; |
results: SuccessType[]; |
||||||
} |
} |
||||||
|
@ -1,5 +1,8 @@ |
|||||||
import type { ResourceSuccess } from "./SuccessResult"; |
import type { ResourceSuccess } from "./SuccessResult"; |
||||||
|
|
||||||
|
/** |
||||||
|
* Indicates that a specific resource is unfetched |
||||||
|
*/ |
||||||
export interface Unfetched extends ResourceSuccess { |
export interface Unfetched extends ResourceSuccess { |
||||||
type: "unfetched"; |
type: "unfetched"; |
||||||
} |
} |
||||||
|
@ -1,5 +1,16 @@ |
|||||||
import type { ResourceSuccess } from "./SuccessResult"; |
import type { ResourceSuccess } from "./SuccessResult"; |
||||||
|
|
||||||
|
/** |
||||||
|
* Indicates that an update request to a resource was successful |
||||||
|
*/ |
||||||
export interface UpdateSuccess extends ResourceSuccess { |
export interface UpdateSuccess extends ResourceSuccess { |
||||||
type: "updateSuccess"; |
type: "updateSuccess"; |
||||||
} |
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Indicates that an update request to the default graph was successful. This |
||||||
|
* data was not written to a Pod. It was only written locally. |
||||||
|
*/ |
||||||
|
export interface UpdateDefaultGraphSuccess extends ResourceSuccess { |
||||||
|
type: "updateDefaultGraphSuccess"; |
||||||
|
} |
||||||
|
@ -0,0 +1,26 @@ |
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */ |
||||||
|
import type { WaitingProcess } from "../../util/RequestBatcher"; |
||||||
|
|
||||||
|
/** |
||||||
|
* @internal |
||||||
|
* |
||||||
|
* A helper function for a common way to modify the batch queue. This merges |
||||||
|
* the incoming request with the currently executing request or the last request |
||||||
|
* in the queue if its keys are the same. |
||||||
|
* |
||||||
|
* @param key - the key of the incoming request |
||||||
|
* @returns a modifyQueue function |
||||||
|
*/ |
||||||
|
export function modifyQueueByMergingEventsWithTheSameKeys(key: string) { |
||||||
|
return ( |
||||||
|
queue: WaitingProcess<any[], any>[], |
||||||
|
currentlyLoading: WaitingProcess<any[], any> | undefined, |
||||||
|
) => { |
||||||
|
if (queue.length === 0 && currentlyLoading?.name === key) { |
||||||
|
return currentlyLoading; |
||||||
|
} else if (queue[queue.length - 1]?.name === key) { |
||||||
|
return queue[queue.length - 1]; |
||||||
|
} |
||||||
|
return undefined; |
||||||
|
}; |
||||||
|
} |
@ -1,5 +1,12 @@ |
|||||||
import crossFetch from "cross-fetch"; |
import crossFetch from "cross-fetch"; |
||||||
|
|
||||||
|
/** |
||||||
|
* @internal |
||||||
|
* Guantees that some kind of fetch is available |
||||||
|
* |
||||||
|
* @param fetchInput - A potential fetch object |
||||||
|
* @returns a proper fetch object. Cross-fetch is default |
||||||
|
*/ |
||||||
export function guaranteeFetch(fetchInput?: typeof fetch): typeof fetch { |
export function guaranteeFetch(fetchInput?: typeof fetch): typeof fetch { |
||||||
return fetchInput || crossFetch; |
return fetchInput || crossFetch; |
||||||
} |
} |
||||||
|
@ -0,0 +1,31 @@ |
|||||||
|
import { ContextDefinition } from "jsonld"; |
||||||
|
|
||||||
|
/** |
||||||
|
* ============================================================================= |
||||||
|
* postContext: JSONLD Context for post |
||||||
|
* ============================================================================= |
||||||
|
*/ |
||||||
|
export const postContext: ContextDefinition = { |
||||||
|
type: { |
||||||
|
"@id": "@type", |
||||||
|
}, |
||||||
|
SocialMediaPosting: "http://schema.org/SocialMediaPosting", |
||||||
|
CreativeWork: "http://schema.org/CreativeWork", |
||||||
|
Thing: "http://schema.org/Thing", |
||||||
|
articleBody: { |
||||||
|
"@id": "http://schema.org/articleBody", |
||||||
|
"@type": "http://www.w3.org/2001/XMLSchema#string", |
||||||
|
}, |
||||||
|
uploadDate: { |
||||||
|
"@id": "http://schema.org/uploadDate", |
||||||
|
"@type": "http://www.w3.org/2001/XMLSchema#date", |
||||||
|
}, |
||||||
|
image: { |
||||||
|
"@id": "http://schema.org/image", |
||||||
|
"@type": "@id", |
||||||
|
}, |
||||||
|
publisher: { |
||||||
|
"@id": "http://schema.org/publisher", |
||||||
|
"@type": "@id", |
||||||
|
}, |
||||||
|
}; |
@ -0,0 +1,155 @@ |
|||||||
|
import { Schema } from "shexj"; |
||||||
|
|
||||||
|
/** |
||||||
|
* ============================================================================= |
||||||
|
* postSchema: ShexJ Schema for post |
||||||
|
* ============================================================================= |
||||||
|
*/ |
||||||
|
export const postSchema: Schema = { |
||||||
|
type: "Schema", |
||||||
|
shapes: [ |
||||||
|
{ |
||||||
|
id: "https://example.com/PostSh", |
||||||
|
type: "ShapeDecl", |
||||||
|
shapeExpr: { |
||||||
|
type: "Shape", |
||||||
|
expression: { |
||||||
|
type: "EachOf", |
||||||
|
expressions: [ |
||||||
|
{ |
||||||
|
type: "TripleConstraint", |
||||||
|
predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", |
||||||
|
valueExpr: { |
||||||
|
type: "NodeConstraint", |
||||||
|
values: [ |
||||||
|
"http://schema.org/SocialMediaPosting", |
||||||
|
"http://schema.org/CreativeWork", |
||||||
|
"http://schema.org/Thing", |
||||||
|
], |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "TripleConstraint", |
||||||
|
predicate: "http://schema.org/articleBody", |
||||||
|
valueExpr: { |
||||||
|
type: "NodeConstraint", |
||||||
|
datatype: "http://www.w3.org/2001/XMLSchema#string", |
||||||
|
}, |
||||||
|
min: 0, |
||||||
|
max: 1, |
||||||
|
annotations: [ |
||||||
|
{ |
||||||
|
type: "Annotation", |
||||||
|
predicate: "http://www.w3.org/2000/01/rdf-schema#label", |
||||||
|
object: { |
||||||
|
value: "articleBody", |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "Annotation", |
||||||
|
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||||
|
object: { |
||||||
|
value: "The actual body of the article. ", |
||||||
|
}, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "TripleConstraint", |
||||||
|
predicate: "http://schema.org/uploadDate", |
||||||
|
valueExpr: { |
||||||
|
type: "NodeConstraint", |
||||||
|
datatype: "http://www.w3.org/2001/XMLSchema#date", |
||||||
|
}, |
||||||
|
annotations: [ |
||||||
|
{ |
||||||
|
type: "Annotation", |
||||||
|
predicate: "http://www.w3.org/2000/01/rdf-schema#label", |
||||||
|
object: { |
||||||
|
value: "uploadDate", |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "Annotation", |
||||||
|
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||||
|
object: { |
||||||
|
value: |
||||||
|
"Date when this media object was uploaded to this site.", |
||||||
|
}, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "TripleConstraint", |
||||||
|
predicate: "http://schema.org/image", |
||||||
|
valueExpr: { |
||||||
|
type: "NodeConstraint", |
||||||
|
nodeKind: "iri", |
||||||
|
}, |
||||||
|
min: 0, |
||||||
|
max: 1, |
||||||
|
annotations: [ |
||||||
|
{ |
||||||
|
type: "Annotation", |
||||||
|
predicate: "http://www.w3.org/2000/01/rdf-schema#label", |
||||||
|
object: { |
||||||
|
value: "image", |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "Annotation", |
||||||
|
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||||
|
object: { |
||||||
|
value: |
||||||
|
"A media object that encodes this CreativeWork. This property is a synonym for encoding.", |
||||||
|
}, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "TripleConstraint", |
||||||
|
predicate: "http://schema.org/publisher", |
||||||
|
valueExpr: { |
||||||
|
type: "NodeConstraint", |
||||||
|
nodeKind: "iri", |
||||||
|
}, |
||||||
|
annotations: [ |
||||||
|
{ |
||||||
|
type: "Annotation", |
||||||
|
predicate: "http://www.w3.org/2000/01/rdf-schema#label", |
||||||
|
object: { |
||||||
|
value: "publisher", |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "Annotation", |
||||||
|
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||||
|
object: { |
||||||
|
value: "The publisher of the creative work.", |
||||||
|
}, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
annotations: [ |
||||||
|
{ |
||||||
|
type: "Annotation", |
||||||
|
predicate: "http://www.w3.org/2000/01/rdf-schema#label", |
||||||
|
object: { |
||||||
|
value: "SocialMediaPost", |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "Annotation", |
||||||
|
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||||
|
object: { |
||||||
|
value: |
||||||
|
"A post to a social media platform, including blog posts, tweets, Facebook posts, etc.", |
||||||
|
}, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
}, |
||||||
|
], |
||||||
|
}; |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue