commit
1def1750f4
@ -1,4 +1,4 @@ |
||||
{ |
||||
"$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 React from "react"; |
||||
import { Router } from "./Layout"; |
||||
import { BrowserSolidLdoProvider } from "@ldo/solid-react"; |
||||
import React, { useCallback } from "react"; |
||||
import { |
||||
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 ( |
||||
/* The application should be surrounded with the BrowserSolidLdoProvider |
||||
this will set up all the underlying infrastructure for the application */ |
||||
<BrowserSolidLdoProvider> |
||||
<Router /> |
||||
<Login /> |
||||
</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 = { |
||||
...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 { 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 { |
||||
/** |
||||
* A pointer to the parent SolidLdoDataset |
||||
*/ |
||||
solidLdoDataset: SolidLdoDataset; |
||||
/** |
||||
* The resource store of the SolidLdoDataset |
||||
*/ |
||||
resourceStore: ResourceStore; |
||||
/** |
||||
* Http fetch function |
||||
*/ |
||||
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 { |
||||
/** |
||||
* A fetch function usually imported from @inrupt/solid-client-authn-js |
||||
*/ |
||||
fetch?: typeof fetch; |
||||
} |
||||
|
||||
/** |
||||
* Request options with a dataset component |
||||
*/ |
||||
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"; |
||||
|
||||
/** |
||||
* Indicates that the request to check if a resource is the root container was |
||||
* a success. |
||||
*/ |
||||
export interface CheckRootContainerSuccess extends ResourceSuccess { |
||||
type: "checkRootContainerSuccess"; |
||||
/** |
||||
* True if this resoure is the root container |
||||
*/ |
||||
isRootContainer: boolean; |
||||
} |
||||
|
@ -1,6 +1,13 @@ |
||||
import type { ResourceSuccess } from "./SuccessResult"; |
||||
|
||||
/** |
||||
* Indicates that the request to create the resource was a success. |
||||
*/ |
||||
export interface CreateSuccess extends ResourceSuccess { |
||||
type: "createSuccess"; |
||||
/** |
||||
* True if there was a resource that existed before at the given URI that was |
||||
* overwritten |
||||
*/ |
||||
didOverwrite: boolean; |
||||
} |
||||
|
@ -1,6 +1,14 @@ |
||||
import type { ResourceSuccess } from "./SuccessResult"; |
||||
|
||||
/** |
||||
* Indicates that the request to delete a resource was a success. |
||||
*/ |
||||
export interface DeleteSuccess extends ResourceSuccess { |
||||
type: "deleteSuccess"; |
||||
|
||||
/** |
||||
* True if there was a resource at the provided URI that was deleted. False if |
||||
* a resource didn't exist. |
||||
*/ |
||||
resourceExisted: boolean; |
||||
} |
||||
|
@ -1,15 +1,31 @@ |
||||
import type { RequesterResult } from "../RequesterResult"; |
||||
|
||||
/** |
||||
* Indicates that some action taken by LDO was a success |
||||
*/ |
||||
export interface SuccessResult extends RequesterResult { |
||||
isError: false; |
||||
} |
||||
|
||||
/** |
||||
* Indicates that a request to a resource was aa success |
||||
*/ |
||||
export interface ResourceSuccess extends SuccessResult { |
||||
/** |
||||
* The URI of the resource |
||||
*/ |
||||
uri: string; |
||||
} |
||||
|
||||
/** |
||||
* A grouping of multiple successes as a result of an action |
||||
*/ |
||||
export interface AggregateSuccess<SuccessType extends SuccessResult> |
||||
extends SuccessResult { |
||||
type: "aggregateSuccess"; |
||||
|
||||
/** |
||||
* An array of all successesses |
||||
*/ |
||||
results: SuccessType[]; |
||||
} |
||||
|
@ -1,5 +1,8 @@ |
||||
import type { ResourceSuccess } from "./SuccessResult"; |
||||
|
||||
/** |
||||
* Indicates that a specific resource is unfetched |
||||
*/ |
||||
export interface Unfetched extends ResourceSuccess { |
||||
type: "unfetched"; |
||||
} |
||||
|
@ -1,5 +1,16 @@ |
||||
import type { ResourceSuccess } from "./SuccessResult"; |
||||
|
||||
/** |
||||
* Indicates that an update request to a resource was successful |
||||
*/ |
||||
export interface UpdateSuccess extends ResourceSuccess { |
||||
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"; |
||||
|
||||
/** |
||||
* @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 { |
||||
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