parent
b7397f8f6d
commit
1db769103f
@ -0,0 +1,5 @@ |
||||
module.exports = { |
||||
preset: "ts-jest", |
||||
testEnvironment: "node", |
||||
coveragePathIgnorePatterns: ["/test/"], |
||||
}; |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,28 @@ |
||||
// this file overrides the default CRA configurations (webpack, eslint, babel, etc)
|
||||
// Ingnore because config scripts can't use the import variable
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin"); |
||||
|
||||
module.exports = { |
||||
webpack: { |
||||
configure: (config) => { |
||||
// Remove ModuleScopePlugin which throws when we try to import something
|
||||
// outside of src/.
|
||||
config.resolve.plugins.pop(); |
||||
|
||||
// Resolve the path aliases.
|
||||
config.resolve.plugins.push(new TsconfigPathsPlugin()); |
||||
|
||||
// Let Babel compile outside of src/.
|
||||
const oneOfRule = config.module.rules.find((rule) => rule.oneOf); |
||||
const tsRule = oneOfRule.oneOf.find((rule) => |
||||
rule.test.toString().includes("ts|tsx"), |
||||
); |
||||
|
||||
tsRule.include = undefined; |
||||
tsRule.exclude = /node_modules/; |
||||
|
||||
return config; |
||||
}, |
||||
}, |
||||
}; |
@ -1,23 +1,18 @@ |
||||
import React, { FunctionComponent } from "react"; |
||||
import Profile from "./Profile"; |
||||
import { SolidAuthProvider, LdoProvider } from "@ldo/solid-react"; |
||||
import { fetch } from "solid-authn-react-native"; |
||||
import Layout from "./Layout"; |
||||
|
||||
const App: FunctionComponent = () => { |
||||
const ProfileApp: FunctionComponent = () => { |
||||
return ( |
||||
<div className="App"> |
||||
<header className="App-header"> |
||||
<p> |
||||
Edit <code>src/App.js</code> and save to reload. |
||||
</p> |
||||
<a |
||||
className="App-link" |
||||
href="https://reactjs.org" |
||||
target="_blank" |
||||
rel="noopener noreferrer" |
||||
> |
||||
Learn React |
||||
</a> |
||||
</header> |
||||
</div> |
||||
<SolidAuthProvider> |
||||
<LdoProvider fetch={fetch}> |
||||
<Layout> |
||||
<Profile /> |
||||
</Layout> |
||||
</LdoProvider> |
||||
</SolidAuthProvider> |
||||
); |
||||
}; |
||||
|
||||
export default App; |
||||
export default ProfileApp; |
||||
|
@ -0,0 +1,34 @@ |
||||
import React, { FunctionComponent, useState } from "react"; |
||||
|
||||
interface BlurTextInputProps { |
||||
value: string; |
||||
onBlurText: (text: string) => void; |
||||
} |
||||
|
||||
const BlurTextInput: FunctionComponent<BlurTextInputProps> = ({ |
||||
value, |
||||
onBlurText, |
||||
}) => { |
||||
const [isFocused, setIsFocused] = useState(false); |
||||
const [text, setText] = useState(value); |
||||
|
||||
return ( |
||||
<input |
||||
type="text" |
||||
value={isFocused ? text : value} |
||||
onChange={(e) => setText(e.target.value)} |
||||
onFocus={() => { |
||||
setIsFocused(true); |
||||
setText(value); |
||||
}} |
||||
onBlur={(e) => { |
||||
setIsFocused(false); |
||||
if (e.target.value !== value) { |
||||
onBlurText(e.target.value); |
||||
} |
||||
}} |
||||
/> |
||||
); |
||||
}; |
||||
|
||||
export default BlurTextInput; |
@ -0,0 +1,40 @@ |
||||
import React, { |
||||
FunctionComponent, |
||||
PropsWithChildren, |
||||
useCallback, |
||||
} from "react"; |
||||
import { useSolidAuth } from "@ldo/solid-react"; |
||||
|
||||
const Layout: FunctionComponent<PropsWithChildren> = ({ children }) => { |
||||
const { login, session, logout } = useSolidAuth(); |
||||
|
||||
const loginCb = useCallback(async () => { |
||||
const issuer = prompt( |
||||
"Enter your Pod Provider", |
||||
"https://solidcommunity.net", |
||||
); |
||||
if (issuer) { |
||||
await login(issuer); |
||||
} |
||||
}, []); |
||||
|
||||
return ( |
||||
<div> |
||||
<h1>LDO Solid React Test</h1> |
||||
{session.isLoggedIn ? ( |
||||
<div> |
||||
<p> |
||||
Logged in as {session.webId}{" "} |
||||
<button onClick={logout}>Log Out</button> |
||||
</p> |
||||
<hr /> |
||||
{children} |
||||
</div> |
||||
) : ( |
||||
<button onClick={loginCb}>Log In</button> |
||||
)} |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
export default Layout; |
@ -0,0 +1,39 @@ |
||||
import React, { FunctionComponent } from "react"; |
||||
import { SolidProfileShapeShapeType } from "./ldo/solidProfile.shapeTypes"; |
||||
import BlurTextInput from "./BlurTextInput"; |
||||
import { |
||||
useSolidAuth, |
||||
useLdo, |
||||
useDataResource, |
||||
useSubject, |
||||
} from "@ldo/solid-react"; |
||||
|
||||
const Profile: FunctionComponent = () => { |
||||
const { changeData, commitData } = useLdo(); |
||||
const { session } = useSolidAuth(); |
||||
const webId = session.webId!; |
||||
const webIdResource = useDataResource(webId); |
||||
const [profile, profileError] = useSubject(SolidProfileShapeShapeType, webId); |
||||
|
||||
if (webIdResource.isLoading) { |
||||
return <p>Loading</p>; |
||||
} else if (profileError) { |
||||
return <p>profileError.message</p>; |
||||
} else { |
||||
return ( |
||||
<div> |
||||
<label>Name:</label> |
||||
<BlurTextInput |
||||
value={profile.name || ""} |
||||
onBlurText={async (text) => { |
||||
const cProfile = changeData(profile, webIdResource); |
||||
cProfile.name = text; |
||||
await commitData(cProfile); |
||||
}} |
||||
/> |
||||
</div> |
||||
); |
||||
} |
||||
}; |
||||
|
||||
export default Profile; |
@ -0,0 +1,156 @@ |
||||
import { ContextDefinition } from "jsonld"; |
||||
|
||||
/** |
||||
* ============================================================================= |
||||
* solidProfileContext: JSONLD Context for solidProfile |
||||
* ============================================================================= |
||||
*/ |
||||
export const solidProfileContext: ContextDefinition = { |
||||
type: { |
||||
"@id": "@type", |
||||
"@container": "@set", |
||||
}, |
||||
Person: "http://schema.org/Person", |
||||
Person2: "http://xmlns.com/foaf/0.1/Person", |
||||
fn: { |
||||
"@id": "http://www.w3.org/2006/vcard/ns#fn", |
||||
"@type": "http://www.w3.org/2001/XMLSchema#string", |
||||
}, |
||||
name: { |
||||
"@id": "http://xmlns.com/foaf/0.1/name", |
||||
"@type": "http://www.w3.org/2001/XMLSchema#string", |
||||
}, |
||||
hasAddress: { |
||||
"@id": "http://www.w3.org/2006/vcard/ns#hasAddress", |
||||
"@type": "@id", |
||||
"@container": "@set", |
||||
}, |
||||
countryName: { |
||||
"@id": "http://www.w3.org/2006/vcard/ns#country-name", |
||||
"@type": "http://www.w3.org/2001/XMLSchema#string", |
||||
}, |
||||
locality: { |
||||
"@id": "http://www.w3.org/2006/vcard/ns#locality", |
||||
"@type": "http://www.w3.org/2001/XMLSchema#string", |
||||
}, |
||||
postalCode: { |
||||
"@id": "http://www.w3.org/2006/vcard/ns#postal-code", |
||||
"@type": "http://www.w3.org/2001/XMLSchema#string", |
||||
}, |
||||
region: { |
||||
"@id": "http://www.w3.org/2006/vcard/ns#region", |
||||
"@type": "http://www.w3.org/2001/XMLSchema#string", |
||||
}, |
||||
streetAddress: { |
||||
"@id": "http://www.w3.org/2006/vcard/ns#street-address", |
||||
"@type": "http://www.w3.org/2001/XMLSchema#string", |
||||
}, |
||||
hasEmail: { |
||||
"@id": "http://www.w3.org/2006/vcard/ns#hasEmail", |
||||
"@type": "@id", |
||||
"@container": "@set", |
||||
}, |
||||
Dom: "http://www.w3.org/2006/vcard/ns#Dom", |
||||
Home: "http://www.w3.org/2006/vcard/ns#Home", |
||||
ISDN: "http://www.w3.org/2006/vcard/ns#ISDN", |
||||
Internet: "http://www.w3.org/2006/vcard/ns#Internet", |
||||
Intl: "http://www.w3.org/2006/vcard/ns#Intl", |
||||
Label: "http://www.w3.org/2006/vcard/ns#Label", |
||||
Parcel: "http://www.w3.org/2006/vcard/ns#Parcel", |
||||
Postal: "http://www.w3.org/2006/vcard/ns#Postal", |
||||
Pref: "http://www.w3.org/2006/vcard/ns#Pref", |
||||
Work: "http://www.w3.org/2006/vcard/ns#Work", |
||||
X400: "http://www.w3.org/2006/vcard/ns#X400", |
||||
value: { |
||||
"@id": "http://www.w3.org/2006/vcard/ns#value", |
||||
"@type": "@id", |
||||
"@container": "@set", |
||||
}, |
||||
hasPhoto: { |
||||
"@id": "http://www.w3.org/2006/vcard/ns#hasPhoto", |
||||
"@type": "@id", |
||||
}, |
||||
img: { |
||||
"@id": "http://xmlns.com/foaf/0.1/img", |
||||
"@type": "http://www.w3.org/2001/XMLSchema#string", |
||||
}, |
||||
hasTelephone: { |
||||
"@id": "http://www.w3.org/2006/vcard/ns#hasTelephone", |
||||
"@type": "@id", |
||||
"@container": "@set", |
||||
}, |
||||
phone: { |
||||
"@id": "http://www.w3.org/2006/vcard/ns#phone", |
||||
"@type": "http://www.w3.org/2001/XMLSchema#string", |
||||
}, |
||||
organizationName: { |
||||
"@id": "http://www.w3.org/2006/vcard/ns#organization-name", |
||||
"@type": "http://www.w3.org/2001/XMLSchema#string", |
||||
}, |
||||
role: { |
||||
"@id": "http://www.w3.org/2006/vcard/ns#role", |
||||
"@type": "http://www.w3.org/2001/XMLSchema#string", |
||||
}, |
||||
trustedApp: { |
||||
"@id": "http://www.w3.org/ns/auth/acl#trustedApp", |
||||
"@type": "@id", |
||||
"@container": "@set", |
||||
}, |
||||
mode: { |
||||
"@id": "http://www.w3.org/ns/auth/acl#mode", |
||||
"@container": "@set", |
||||
}, |
||||
Append: "http://www.w3.org/ns/auth/acl#Append", |
||||
Control: "http://www.w3.org/ns/auth/acl#Control", |
||||
Read: "http://www.w3.org/ns/auth/acl#Read", |
||||
Write: "http://www.w3.org/ns/auth/acl#Write", |
||||
origin: { |
||||
"@id": "http://www.w3.org/ns/auth/acl#origin", |
||||
"@type": "@id", |
||||
}, |
||||
key: { |
||||
"@id": "http://www.w3.org/ns/auth/cert#key", |
||||
"@type": "@id", |
||||
"@container": "@set", |
||||
}, |
||||
modulus: { |
||||
"@id": "http://www.w3.org/ns/auth/cert#modulus", |
||||
"@type": "http://www.w3.org/2001/XMLSchema#string", |
||||
}, |
||||
exponent: { |
||||
"@id": "http://www.w3.org/ns/auth/cert#exponent", |
||||
"@type": "http://www.w3.org/2001/XMLSchema#integer", |
||||
}, |
||||
inbox: { |
||||
"@id": "http://www.w3.org/ns/ldp#inbox", |
||||
"@type": "@id", |
||||
}, |
||||
preferencesFile: { |
||||
"@id": "http://www.w3.org/ns/pim/space#preferencesFile", |
||||
"@type": "@id", |
||||
}, |
||||
storage: { |
||||
"@id": "http://www.w3.org/ns/pim/space#storage", |
||||
"@type": "@id", |
||||
"@container": "@set", |
||||
}, |
||||
account: { |
||||
"@id": "http://www.w3.org/ns/solid/terms#account", |
||||
"@type": "@id", |
||||
}, |
||||
privateTypeIndex: { |
||||
"@id": "http://www.w3.org/ns/solid/terms#privateTypeIndex", |
||||
"@type": "@id", |
||||
"@container": "@set", |
||||
}, |
||||
publicTypeIndex: { |
||||
"@id": "http://www.w3.org/ns/solid/terms#publicTypeIndex", |
||||
"@type": "@id", |
||||
"@container": "@set", |
||||
}, |
||||
knows: { |
||||
"@id": "http://xmlns.com/foaf/0.1/knows", |
||||
"@type": "@id", |
||||
"@container": "@set", |
||||
}, |
||||
}; |
@ -0,0 +1,749 @@ |
||||
import { Schema } from "shexj"; |
||||
|
||||
/** |
||||
* ============================================================================= |
||||
* solidProfileSchema: ShexJ Schema for solidProfile |
||||
* ============================================================================= |
||||
*/ |
||||
export const solidProfileSchema: Schema = { |
||||
type: "Schema", |
||||
shapes: [ |
||||
{ |
||||
id: "https://shaperepo.com/schemas/solidProfile#SolidProfileShape", |
||||
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/Person"], |
||||
}, |
||||
annotations: [ |
||||
{ |
||||
type: "Annotation", |
||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||
object: { |
||||
value: "Defines the node as a Person (from Schema.org)", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
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://www.w3.org/2006/vcard/ns#fn", |
||||
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: |
||||
"The formatted name of a person. Example: John Smith", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
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: "An alternate way to define a person's name.", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://www.w3.org/2006/vcard/ns#hasAddress", |
||||
valueExpr: |
||||
"https://shaperepo.com/schemas/solidProfile#AddressShape", |
||||
min: 0, |
||||
max: -1, |
||||
annotations: [ |
||||
{ |
||||
type: "Annotation", |
||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||
object: { |
||||
value: "The person's street address.", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://www.w3.org/2006/vcard/ns#hasEmail", |
||||
valueExpr: |
||||
"https://shaperepo.com/schemas/solidProfile#EmailShape", |
||||
min: 0, |
||||
max: -1, |
||||
annotations: [ |
||||
{ |
||||
type: "Annotation", |
||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||
object: { |
||||
value: "The person's email.", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://www.w3.org/2006/vcard/ns#hasPhoto", |
||||
valueExpr: { |
||||
type: "NodeConstraint", |
||||
nodeKind: "iri", |
||||
}, |
||||
min: 0, |
||||
max: 1, |
||||
annotations: [ |
||||
{ |
||||
type: "Annotation", |
||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||
object: { |
||||
value: "A link to the person's photo", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
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://www.w3.org/2006/vcard/ns#hasTelephone", |
||||
valueExpr: |
||||
"https://shaperepo.com/schemas/solidProfile#PhoneNumberShape", |
||||
min: 0, |
||||
max: -1, |
||||
annotations: [ |
||||
{ |
||||
type: "Annotation", |
||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||
object: { |
||||
value: "Person's telephone number", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://www.w3.org/2006/vcard/ns#phone", |
||||
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: |
||||
"An alternative way to define a person's telephone number using a string", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://www.w3.org/2006/vcard/ns#organization-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: |
||||
"The name of the organization with which the person is affiliated", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://www.w3.org/2006/vcard/ns#role", |
||||
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: |
||||
"The name of the person's role in their organization", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://www.w3.org/ns/auth/acl#trustedApp", |
||||
valueExpr: |
||||
"https://shaperepo.com/schemas/solidProfile#TrustedAppShape", |
||||
min: 0, |
||||
max: -1, |
||||
annotations: [ |
||||
{ |
||||
type: "Annotation", |
||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||
object: { |
||||
value: |
||||
"A list of app origins that are trusted by this user", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://www.w3.org/ns/auth/cert#key", |
||||
valueExpr: |
||||
"https://shaperepo.com/schemas/solidProfile#RSAPublicKeyShape", |
||||
min: 0, |
||||
max: -1, |
||||
annotations: [ |
||||
{ |
||||
type: "Annotation", |
||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||
object: { |
||||
value: |
||||
"A list of RSA public keys that are associated with private keys the user holds.", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://www.w3.org/ns/ldp#inbox", |
||||
valueExpr: { |
||||
type: "NodeConstraint", |
||||
nodeKind: "iri", |
||||
}, |
||||
annotations: [ |
||||
{ |
||||
type: "Annotation", |
||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||
object: { |
||||
value: |
||||
"The user's LDP inbox to which apps can post notifications", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://www.w3.org/ns/pim/space#preferencesFile", |
||||
valueExpr: { |
||||
type: "NodeConstraint", |
||||
nodeKind: "iri", |
||||
}, |
||||
min: 0, |
||||
max: 1, |
||||
annotations: [ |
||||
{ |
||||
type: "Annotation", |
||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||
object: { |
||||
value: "The user's preferences", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://www.w3.org/ns/pim/space#storage", |
||||
valueExpr: { |
||||
type: "NodeConstraint", |
||||
nodeKind: "iri", |
||||
}, |
||||
min: 0, |
||||
max: -1, |
||||
annotations: [ |
||||
{ |
||||
type: "Annotation", |
||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||
object: { |
||||
value: |
||||
"The location of a Solid storage server related to this WebId", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://www.w3.org/ns/solid/terms#account", |
||||
valueExpr: { |
||||
type: "NodeConstraint", |
||||
nodeKind: "iri", |
||||
}, |
||||
min: 0, |
||||
max: 1, |
||||
annotations: [ |
||||
{ |
||||
type: "Annotation", |
||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||
object: { |
||||
value: "The user's account", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://www.w3.org/ns/solid/terms#privateTypeIndex", |
||||
valueExpr: { |
||||
type: "NodeConstraint", |
||||
nodeKind: "iri", |
||||
}, |
||||
min: 0, |
||||
max: -1, |
||||
annotations: [ |
||||
{ |
||||
type: "Annotation", |
||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||
object: { |
||||
value: |
||||
"A registry of all types used on the user's Pod (for private access only)", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://www.w3.org/ns/solid/terms#publicTypeIndex", |
||||
valueExpr: { |
||||
type: "NodeConstraint", |
||||
nodeKind: "iri", |
||||
}, |
||||
min: 0, |
||||
max: -1, |
||||
annotations: [ |
||||
{ |
||||
type: "Annotation", |
||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||
object: { |
||||
value: |
||||
"A registry of all types used on the user's Pod (for public access)", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://xmlns.com/foaf/0.1/knows", |
||||
valueExpr: |
||||
"https://shaperepo.com/schemas/solidProfile#SolidProfileShape", |
||||
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"], |
||||
}, |
||||
}, |
||||
{ |
||||
id: "https://shaperepo.com/schemas/solidProfile#AddressShape", |
||||
type: "ShapeDecl", |
||||
shapeExpr: { |
||||
type: "Shape", |
||||
expression: { |
||||
type: "EachOf", |
||||
expressions: [ |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://www.w3.org/2006/vcard/ns#country-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: "The name of the user's country of residence", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://www.w3.org/2006/vcard/ns#locality", |
||||
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: |
||||
"The name of the user's locality (City, Town etc.) of residence", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://www.w3.org/2006/vcard/ns#postal-code", |
||||
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: "The user's postal code", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://www.w3.org/2006/vcard/ns#region", |
||||
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: |
||||
"The name of the user's region (State, Province etc.) of residence", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://www.w3.org/2006/vcard/ns#street-address", |
||||
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: "The user's street address", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
}, |
||||
}, |
||||
}, |
||||
{ |
||||
id: "https://shaperepo.com/schemas/solidProfile#EmailShape", |
||||
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://www.w3.org/2006/vcard/ns#Dom", |
||||
"http://www.w3.org/2006/vcard/ns#Home", |
||||
"http://www.w3.org/2006/vcard/ns#ISDN", |
||||
"http://www.w3.org/2006/vcard/ns#Internet", |
||||
"http://www.w3.org/2006/vcard/ns#Intl", |
||||
"http://www.w3.org/2006/vcard/ns#Label", |
||||
"http://www.w3.org/2006/vcard/ns#Parcel", |
||||
"http://www.w3.org/2006/vcard/ns#Postal", |
||||
"http://www.w3.org/2006/vcard/ns#Pref", |
||||
"http://www.w3.org/2006/vcard/ns#Work", |
||||
"http://www.w3.org/2006/vcard/ns#X400", |
||||
], |
||||
}, |
||||
min: 0, |
||||
max: 1, |
||||
annotations: [ |
||||
{ |
||||
type: "Annotation", |
||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||
object: { |
||||
value: "The type of email.", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://www.w3.org/2006/vcard/ns#value", |
||||
valueExpr: { |
||||
type: "NodeConstraint", |
||||
nodeKind: "iri", |
||||
}, |
||||
annotations: [ |
||||
{ |
||||
type: "Annotation", |
||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||
object: { |
||||
value: |
||||
"The value of an email as a mailto link (Example <mailto:jane@example.com>)", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
}, |
||||
extra: ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"], |
||||
}, |
||||
}, |
||||
{ |
||||
id: "https://shaperepo.com/schemas/solidProfile#PhoneNumberShape", |
||||
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://www.w3.org/2006/vcard/ns#Dom", |
||||
"http://www.w3.org/2006/vcard/ns#Home", |
||||
"http://www.w3.org/2006/vcard/ns#ISDN", |
||||
"http://www.w3.org/2006/vcard/ns#Internet", |
||||
"http://www.w3.org/2006/vcard/ns#Intl", |
||||
"http://www.w3.org/2006/vcard/ns#Label", |
||||
"http://www.w3.org/2006/vcard/ns#Parcel", |
||||
"http://www.w3.org/2006/vcard/ns#Postal", |
||||
"http://www.w3.org/2006/vcard/ns#Pref", |
||||
"http://www.w3.org/2006/vcard/ns#Work", |
||||
"http://www.w3.org/2006/vcard/ns#X400", |
||||
], |
||||
}, |
||||
min: 0, |
||||
max: 1, |
||||
annotations: [ |
||||
{ |
||||
type: "Annotation", |
||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||
object: { |
||||
value: "They type of Phone Number", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://www.w3.org/2006/vcard/ns#value", |
||||
valueExpr: { |
||||
type: "NodeConstraint", |
||||
nodeKind: "iri", |
||||
}, |
||||
annotations: [ |
||||
{ |
||||
type: "Annotation", |
||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||
object: { |
||||
value: |
||||
"The value of a phone number as a tel link (Example <tel:555-555-5555>)", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
}, |
||||
extra: ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"], |
||||
}, |
||||
}, |
||||
{ |
||||
id: "https://shaperepo.com/schemas/solidProfile#TrustedAppShape", |
||||
type: "ShapeDecl", |
||||
shapeExpr: { |
||||
type: "Shape", |
||||
expression: { |
||||
type: "EachOf", |
||||
expressions: [ |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://www.w3.org/ns/auth/acl#mode", |
||||
valueExpr: { |
||||
type: "NodeConstraint", |
||||
values: [ |
||||
"http://www.w3.org/ns/auth/acl#Append", |
||||
"http://www.w3.org/ns/auth/acl#Control", |
||||
"http://www.w3.org/ns/auth/acl#Read", |
||||
"http://www.w3.org/ns/auth/acl#Write", |
||||
], |
||||
}, |
||||
min: 1, |
||||
max: -1, |
||||
annotations: [ |
||||
{ |
||||
type: "Annotation", |
||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||
object: { |
||||
value: "The level of access provided to this origin", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://www.w3.org/ns/auth/acl#origin", |
||||
valueExpr: { |
||||
type: "NodeConstraint", |
||||
nodeKind: "iri", |
||||
}, |
||||
annotations: [ |
||||
{ |
||||
type: "Annotation", |
||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||
object: { |
||||
value: "The app origin the user trusts", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
}, |
||||
}, |
||||
}, |
||||
{ |
||||
id: "https://shaperepo.com/schemas/solidProfile#RSAPublicKeyShape", |
||||
type: "ShapeDecl", |
||||
shapeExpr: { |
||||
type: "Shape", |
||||
expression: { |
||||
type: "EachOf", |
||||
expressions: [ |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://www.w3.org/ns/auth/cert#modulus", |
||||
valueExpr: { |
||||
type: "NodeConstraint", |
||||
datatype: "http://www.w3.org/2001/XMLSchema#string", |
||||
}, |
||||
annotations: [ |
||||
{ |
||||
type: "Annotation", |
||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||
object: { |
||||
value: "RSA Modulus", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://www.w3.org/ns/auth/cert#exponent", |
||||
valueExpr: { |
||||
type: "NodeConstraint", |
||||
datatype: "http://www.w3.org/2001/XMLSchema#integer", |
||||
}, |
||||
annotations: [ |
||||
{ |
||||
type: "Annotation", |
||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||
object: { |
||||
value: "RSA Exponent", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
}, |
||||
}, |
||||
}, |
||||
], |
||||
}; |
@ -0,0 +1,71 @@ |
||||
import { ShapeType } from "ldo"; |
||||
import { solidProfileSchema } from "./solidProfile.schema"; |
||||
import { solidProfileContext } from "./solidProfile.context"; |
||||
import { |
||||
SolidProfileShape, |
||||
AddressShape, |
||||
EmailShape, |
||||
PhoneNumberShape, |
||||
TrustedAppShape, |
||||
RSAPublicKeyShape, |
||||
} from "./solidProfile.typings"; |
||||
|
||||
/** |
||||
* ============================================================================= |
||||
* LDO ShapeTypes solidProfile |
||||
* ============================================================================= |
||||
*/ |
||||
|
||||
/** |
||||
* SolidProfileShape ShapeType |
||||
*/ |
||||
export const SolidProfileShapeShapeType: ShapeType<SolidProfileShape> = { |
||||
schema: solidProfileSchema, |
||||
shape: "https://shaperepo.com/schemas/solidProfile#SolidProfileShape", |
||||
context: solidProfileContext, |
||||
}; |
||||
|
||||
/** |
||||
* AddressShape ShapeType |
||||
*/ |
||||
export const AddressShapeShapeType: ShapeType<AddressShape> = { |
||||
schema: solidProfileSchema, |
||||
shape: "https://shaperepo.com/schemas/solidProfile#AddressShape", |
||||
context: solidProfileContext, |
||||
}; |
||||
|
||||
/** |
||||
* EmailShape ShapeType |
||||
*/ |
||||
export const EmailShapeShapeType: ShapeType<EmailShape> = { |
||||
schema: solidProfileSchema, |
||||
shape: "https://shaperepo.com/schemas/solidProfile#EmailShape", |
||||
context: solidProfileContext, |
||||
}; |
||||
|
||||
/** |
||||
* PhoneNumberShape ShapeType |
||||
*/ |
||||
export const PhoneNumberShapeShapeType: ShapeType<PhoneNumberShape> = { |
||||
schema: solidProfileSchema, |
||||
shape: "https://shaperepo.com/schemas/solidProfile#PhoneNumberShape", |
||||
context: solidProfileContext, |
||||
}; |
||||
|
||||
/** |
||||
* TrustedAppShape ShapeType |
||||
*/ |
||||
export const TrustedAppShapeShapeType: ShapeType<TrustedAppShape> = { |
||||
schema: solidProfileSchema, |
||||
shape: "https://shaperepo.com/schemas/solidProfile#TrustedAppShape", |
||||
context: solidProfileContext, |
||||
}; |
||||
|
||||
/** |
||||
* RSAPublicKeyShape ShapeType |
||||
*/ |
||||
export const RSAPublicKeyShapeShapeType: ShapeType<RSAPublicKeyShape> = { |
||||
schema: solidProfileSchema, |
||||
shape: "https://shaperepo.com/schemas/solidProfile#RSAPublicKeyShape", |
||||
context: solidProfileContext, |
||||
}; |
@ -0,0 +1,293 @@ |
||||
import { ContextDefinition } from "jsonld"; |
||||
|
||||
/** |
||||
* ============================================================================= |
||||
* Typescript Typings for solidProfile |
||||
* ============================================================================= |
||||
*/ |
||||
|
||||
/** |
||||
* SolidProfileShape Type |
||||
*/ |
||||
export interface SolidProfileShape { |
||||
"@id"?: string; |
||||
"@context"?: ContextDefinition; |
||||
/** |
||||
* Defines the node as a Person (from Schema.org) | Defines the node as a Person (from foaf) |
||||
*/ |
||||
type: ( |
||||
| { |
||||
"@id": "Person"; |
||||
} |
||||
| { |
||||
"@id": "Person2"; |
||||
} |
||||
)[]; |
||||
/** |
||||
* The formatted name of a person. Example: John Smith |
||||
*/ |
||||
fn?: string; |
||||
/** |
||||
* An alternate way to define a person's name. |
||||
*/ |
||||
name?: string; |
||||
/** |
||||
* The person's street address. |
||||
*/ |
||||
hasAddress?: AddressShape[]; |
||||
/** |
||||
* The person's email. |
||||
*/ |
||||
hasEmail?: EmailShape[]; |
||||
/** |
||||
* A link to the person's photo |
||||
*/ |
||||
hasPhoto?: { |
||||
"@id": string; |
||||
}; |
||||
/** |
||||
* Photo link but in string form |
||||
*/ |
||||
img?: string; |
||||
/** |
||||
* Person's telephone number |
||||
*/ |
||||
hasTelephone?: PhoneNumberShape[]; |
||||
/** |
||||
* An alternative way to define a person's telephone number using a string |
||||
*/ |
||||
phone?: string; |
||||
/** |
||||
* The name of the organization with which the person is affiliated |
||||
*/ |
||||
organizationName?: string; |
||||
/** |
||||
* The name of the person's role in their organization |
||||
*/ |
||||
role?: string; |
||||
/** |
||||
* A list of app origins that are trusted by this user |
||||
*/ |
||||
trustedApp?: TrustedAppShape[]; |
||||
/** |
||||
* A list of RSA public keys that are associated with private keys the user holds. |
||||
*/ |
||||
key?: RSAPublicKeyShape[]; |
||||
/** |
||||
* The user's LDP inbox to which apps can post notifications |
||||
*/ |
||||
inbox: { |
||||
"@id": string; |
||||
}; |
||||
/** |
||||
* The user's preferences |
||||
*/ |
||||
preferencesFile?: { |
||||
"@id": string; |
||||
}; |
||||
/** |
||||
* The location of a Solid storage server related to this WebId |
||||
*/ |
||||
storage?: { |
||||
"@id": string; |
||||
}[]; |
||||
/** |
||||
* The user's account |
||||
*/ |
||||
account?: { |
||||
"@id": string; |
||||
}; |
||||
/** |
||||
* A registry of all types used on the user's Pod (for private access only) |
||||
*/ |
||||
privateTypeIndex?: { |
||||
"@id": string; |
||||
}[]; |
||||
/** |
||||
* A registry of all types used on the user's Pod (for public access) |
||||
*/ |
||||
publicTypeIndex?: { |
||||
"@id": string; |
||||
}[]; |
||||
/** |
||||
* A list of WebIds for all the people this user knows. |
||||
*/ |
||||
knows?: SolidProfileShape[]; |
||||
} |
||||
|
||||
/** |
||||
* AddressShape Type |
||||
*/ |
||||
export interface AddressShape { |
||||
"@id"?: string; |
||||
"@context"?: ContextDefinition; |
||||
/** |
||||
* The name of the user's country of residence |
||||
*/ |
||||
countryName?: string; |
||||
/** |
||||
* The name of the user's locality (City, Town etc.) of residence |
||||
*/ |
||||
locality?: string; |
||||
/** |
||||
* The user's postal code |
||||
*/ |
||||
postalCode?: string; |
||||
/** |
||||
* The name of the user's region (State, Province etc.) of residence |
||||
*/ |
||||
region?: string; |
||||
/** |
||||
* The user's street address |
||||
*/ |
||||
streetAddress?: string; |
||||
} |
||||
|
||||
/** |
||||
* EmailShape Type |
||||
*/ |
||||
export interface EmailShape { |
||||
"@id"?: string; |
||||
"@context"?: ContextDefinition; |
||||
/** |
||||
* The type of email. |
||||
*/ |
||||
type?: |
||||
| { |
||||
"@id": "Dom"; |
||||
} |
||||
| { |
||||
"@id": "Home"; |
||||
} |
||||
| { |
||||
"@id": "ISDN"; |
||||
} |
||||
| { |
||||
"@id": "Internet"; |
||||
} |
||||
| { |
||||
"@id": "Intl"; |
||||
} |
||||
| { |
||||
"@id": "Label"; |
||||
} |
||||
| { |
||||
"@id": "Parcel"; |
||||
} |
||||
| { |
||||
"@id": "Postal"; |
||||
} |
||||
| { |
||||
"@id": "Pref"; |
||||
} |
||||
| { |
||||
"@id": "Work"; |
||||
} |
||||
| { |
||||
"@id": "X400"; |
||||
}; |
||||
/** |
||||
* The value of an email as a mailto link (Example <mailto:jane@example.com>) |
||||
*/ |
||||
value: { |
||||
"@id": string; |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* PhoneNumberShape Type |
||||
*/ |
||||
export interface PhoneNumberShape { |
||||
"@id"?: string; |
||||
"@context"?: ContextDefinition; |
||||
/** |
||||
* They type of Phone Number |
||||
*/ |
||||
type?: |
||||
| { |
||||
"@id": "Dom"; |
||||
} |
||||
| { |
||||
"@id": "Home"; |
||||
} |
||||
| { |
||||
"@id": "ISDN"; |
||||
} |
||||
| { |
||||
"@id": "Internet"; |
||||
} |
||||
| { |
||||
"@id": "Intl"; |
||||
} |
||||
| { |
||||
"@id": "Label"; |
||||
} |
||||
| { |
||||
"@id": "Parcel"; |
||||
} |
||||
| { |
||||
"@id": "Postal"; |
||||
} |
||||
| { |
||||
"@id": "Pref"; |
||||
} |
||||
| { |
||||
"@id": "Work"; |
||||
} |
||||
| { |
||||
"@id": "X400"; |
||||
}; |
||||
/** |
||||
* The value of a phone number as a tel link (Example <tel:555-555-5555>) |
||||
*/ |
||||
value: { |
||||
"@id": string; |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* TrustedAppShape Type |
||||
*/ |
||||
export interface TrustedAppShape { |
||||
"@id"?: string; |
||||
"@context"?: ContextDefinition; |
||||
/** |
||||
* The level of access provided to this origin |
||||
*/ |
||||
mode: ( |
||||
| { |
||||
"@id": "Append"; |
||||
} |
||||
| { |
||||
"@id": "Control"; |
||||
} |
||||
| { |
||||
"@id": "Read"; |
||||
} |
||||
| { |
||||
"@id": "Write"; |
||||
} |
||||
)[]; |
||||
/** |
||||
* The app origin the user trusts |
||||
*/ |
||||
origin: { |
||||
"@id": string; |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* RSAPublicKeyShape Type |
||||
*/ |
||||
export interface RSAPublicKeyShape { |
||||
"@id"?: string; |
||||
"@context"?: ContextDefinition; |
||||
/** |
||||
* RSA Modulus |
||||
*/ |
||||
modulus: string; |
||||
/** |
||||
* RSA Exponent |
||||
*/ |
||||
exponent: number; |
||||
} |
@ -0,0 +1,121 @@ |
||||
PREFIX srs: <https://shaperepo.com/schemas/solidProfile#> |
||||
PREFIX foaf: <http://xmlns.com/foaf/0.1/> |
||||
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> |
||||
PREFIX schem: <http://schema.org/> |
||||
PREFIX vcard: <http://www.w3.org/2006/vcard/ns#> |
||||
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> |
||||
PREFIX acl: <http://www.w3.org/ns/auth/acl#> |
||||
PREFIX cert: <http://www.w3.org/ns/auth/cert#> |
||||
PREFIX ldp: <http://www.w3.org/ns/ldp#> |
||||
PREFIX sp: <http://www.w3.org/ns/pim/space#> |
||||
PREFIX solid: <http://www.w3.org/ns/solid/terms#> |
||||
|
||||
srs:SolidProfileShape EXTRA a { |
||||
a [ schem:Person ] |
||||
// rdfs:comment "Defines the node as a Person (from Schema.org)" ; |
||||
a [ foaf:Person ] |
||||
// rdfs:comment "Defines the node as a Person (from foaf)" ; |
||||
vcard:fn xsd:string ? |
||||
// rdfs:comment "The formatted name of a person. Example: John Smith" ; |
||||
foaf:name xsd:string ? |
||||
// rdfs:comment "An alternate way to define a person's name." ; |
||||
vcard:hasAddress @srs:AddressShape * |
||||
// rdfs:comment "The person's street address." ; |
||||
vcard:hasEmail @srs:EmailShape * |
||||
// rdfs:comment "The person's email." ; |
||||
vcard:hasPhoto IRI ? |
||||
// rdfs:comment "A link to the person's photo" ; |
||||
foaf:img xsd:string ? |
||||
// rdfs:comment "Photo link but in string form" ; |
||||
vcard:hasTelephone @srs:PhoneNumberShape * |
||||
// rdfs:comment "Person's telephone number" ; |
||||
vcard:phone xsd:string ? |
||||
// rdfs:comment "An alternative way to define a person's telephone number using a string" ; |
||||
vcard:organization-name xsd:string ? |
||||
// rdfs:comment "The name of the organization with which the person is affiliated" ; |
||||
vcard:role xsd:string ? |
||||
// rdfs:comment "The name of the person's role in their organization" ; |
||||
acl:trustedApp @srs:TrustedAppShape * |
||||
// rdfs:comment "A list of app origins that are trusted by this user" ; |
||||
cert:key @srs:RSAPublicKeyShape * |
||||
// rdfs:comment "A list of RSA public keys that are associated with private keys the user holds." ; |
||||
ldp:inbox IRI |
||||
// rdfs:comment "The user's LDP inbox to which apps can post notifications" ; |
||||
sp:preferencesFile IRI ? |
||||
// rdfs:comment "The user's preferences" ; |
||||
sp:storage IRI * |
||||
// rdfs:comment "The location of a Solid storage server related to this WebId" ; |
||||
solid:account IRI ? |
||||
// rdfs:comment "The user's account" ; |
||||
solid:privateTypeIndex IRI * |
||||
// rdfs:comment "A registry of all types used on the user's Pod (for private access only)" ; |
||||
solid:publicTypeIndex IRI * |
||||
// rdfs:comment "A registry of all types used on the user's Pod (for public access)" ; |
||||
foaf:knows @srs:SolidProfileShape * |
||||
// rdfs:comment "A list of WebIds for all the people this user knows." ; |
||||
} |
||||
|
||||
srs:AddressShape { |
||||
vcard:country-name xsd:string ? |
||||
// rdfs:comment "The name of the user's country of residence" ; |
||||
vcard:locality xsd:string ? |
||||
// rdfs:comment "The name of the user's locality (City, Town etc.) of residence" ; |
||||
vcard:postal-code xsd:string ? |
||||
// rdfs:comment "The user's postal code" ; |
||||
vcard:region xsd:string ? |
||||
// rdfs:comment "The name of the user's region (State, Province etc.) of residence" ; |
||||
vcard:street-address xsd:string ? |
||||
// rdfs:comment "The user's street address" ; |
||||
} |
||||
|
||||
srs:EmailShape EXTRA a { |
||||
a [ |
||||
vcard:Dom |
||||
vcard:Home |
||||
vcard:ISDN |
||||
vcard:Internet |
||||
vcard:Intl |
||||
vcard:Label |
||||
vcard:Parcel |
||||
vcard:Postal |
||||
vcard:Pref |
||||
vcard:Work |
||||
vcard:X400 |
||||
] ? |
||||
// rdfs:comment "The type of email." ; |
||||
vcard:value IRI |
||||
// rdfs:comment "The value of an email as a mailto link (Example <mailto:jane@example.com>)" ; |
||||
} |
||||
|
||||
srs:PhoneNumberShape EXTRA a { |
||||
a [ |
||||
vcard:Dom |
||||
vcard:Home |
||||
vcard:ISDN |
||||
vcard:Internet |
||||
vcard:Intl |
||||
vcard:Label |
||||
vcard:Parcel |
||||
vcard:Postal |
||||
vcard:Pref |
||||
vcard:Work |
||||
vcard:X400 |
||||
] ? |
||||
// rdfs:comment "They type of Phone Number" ; |
||||
vcard:value IRI |
||||
// rdfs:comment "The value of a phone number as a tel link (Example <tel:555-555-5555>)" ; |
||||
} |
||||
|
||||
srs:TrustedAppShape { |
||||
acl:mode [acl:Append acl:Control acl:Read acl:Write] + |
||||
// rdfs:comment "The level of access provided to this origin" ; |
||||
acl:origin IRI |
||||
// rdfs:comment "The app origin the user trusts" |
||||
} |
||||
|
||||
srs:RSAPublicKeyShape { |
||||
cert:modulus xsd:string |
||||
// rdfs:comment "RSA Modulus" ; |
||||
cert:exponent xsd:integer |
||||
// rdfs:comment "RSA Exponent" ; |
||||
} |
@ -0,0 +1,16 @@ |
||||
{ |
||||
"sourceType": "unambiguous", |
||||
"presets": [ |
||||
[ |
||||
"@babel/preset-env", |
||||
{ |
||||
"targets": { |
||||
"chrome": 100 |
||||
} |
||||
} |
||||
], |
||||
"@babel/preset-typescript", |
||||
"@babel/preset-react" |
||||
], |
||||
"plugins": [] |
||||
} |
@ -0,0 +1,3 @@ |
||||
{ |
||||
"extends": ["../../eslintrc"] |
||||
} |
@ -0,0 +1,13 @@ |
||||
# Solid-Dataset |
||||
|
||||
Pre-alpha |
||||
|
||||
## Notes: |
||||
- Any quads in the default graph will not be synced to Solid Pods, but will be added to the dataset without any syncing |
||||
|
||||
|
||||
## TODO: |
||||
- Create an event for each thing that happens. Like "Loaded" |
||||
- You should be able to initialize classes with loading = true so that there isn't a flash on initial load |
||||
- Access rule stuff just doesn't work. I might need to program a custom implementation for that |
||||
- There should probably be separate libraries for React native and Web React |
@ -0,0 +1,5 @@ |
||||
const sharedConfig = require('../../jest.config.js'); |
||||
module.exports = { |
||||
...sharedConfig, |
||||
'rootDir': './', |
||||
} |
@ -0,0 +1,50 @@ |
||||
{ |
||||
"name": "@ldo/solid-react", |
||||
"version": "0.0.0", |
||||
"description": "A React library for LDO and Solid", |
||||
"main": "dist/index.js", |
||||
"scripts": { |
||||
"build": "tsc --project tsconfig.build.json", |
||||
"watch": "tsc --watch", |
||||
"test": "jest --coverage", |
||||
"test:watch": "jest --watch", |
||||
"docs": "typedoc", |
||||
"prepublishOnly": "npm run test && npm run build && npm run docs", |
||||
"storybook": "storybook dev -p 6006", |
||||
"build-storybook": "storybook build", |
||||
"storybook:ldo": "ldo build --input stories/shapes --output stories/ldo", |
||||
"build:ldo": "ldo build --input lib/shapes --output lib/ldo" |
||||
}, |
||||
"repository": { |
||||
"type": "git", |
||||
"url": "git+https://github.com/o-development/devtool-boilerplate.git" |
||||
}, |
||||
"author": "Jackson Morgan", |
||||
"license": "MIT", |
||||
"bugs": { |
||||
"url": "https://github.com/o-development/devtool-boilerplate/issues" |
||||
}, |
||||
"homepage": "https://github.com/o-development/devtool-boilerplate#readme", |
||||
"devDependencies": { |
||||
"@babel/preset-env": "^7.22.10", |
||||
"@babel/preset-react": "^7.22.5", |
||||
"@babel/preset-typescript": "^7.22.11", |
||||
"@rdfjs/types": "^1.1.0", |
||||
"@types/jest": "^29.0.3", |
||||
"@types/jsonld": "^1.5.8", |
||||
"@types/n3": "^1.10.4", |
||||
"@types/shexj": "^2.1.4", |
||||
"ldo-cli": "^3.0.1", |
||||
"prop-types": "^15.8.1", |
||||
"ts-jest": "^29.0.2" |
||||
}, |
||||
"dependencies": { |
||||
"@inrupt/solid-client": "^1.29.0", |
||||
"cross-fetch": "^3.1.6", |
||||
"jsonld-dataset-proxy": "^1.2.3", |
||||
"ldo": "^1.0.3", |
||||
"o-dataset-pack": "^0.2.14", |
||||
"solid-authn-react-native": "^2.0.3", |
||||
"stream": "^0.0.2" |
||||
} |
||||
} |
@ -0,0 +1,19 @@ |
||||
import { createContext, useContext } from "react"; |
||||
import { BinaryResourceStoreDependencies } from "./document/resource/binaryResource/BinaryResourceStore"; |
||||
import { DataResourceStoreDependencies } from "./document/resource/dataResource/DataResourceStore"; |
||||
import { AccessRulesStoreDependencies } from "./document/accessRules/AccessRulesStore"; |
||||
import { ContainerResourceStoreDependencies } from "./document/resource/dataResource/containerResource/ContainerResourceStore"; |
||||
|
||||
export interface LdoContextData |
||||
extends BinaryResourceStoreDependencies, |
||||
DataResourceStoreDependencies, |
||||
AccessRulesStoreDependencies, |
||||
ContainerResourceStoreDependencies {} |
||||
|
||||
// No default parameter is required as it will be set in the provider
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const LdoContext = createContext<LdoContextData>({}); |
||||
|
||||
export const LdoContextProvider = LdoContext.Provider; |
||||
export const useLdoContext = () => useContext(LdoContext); |
@ -0,0 +1,66 @@ |
||||
import React, { |
||||
FunctionComponent, |
||||
PropsWithChildren, |
||||
useEffect, |
||||
useMemo, |
||||
} from "react"; |
||||
import crossFetch from "cross-fetch"; |
||||
import { createLdoDataset } from "ldo"; |
||||
import { LdoContextData, LdoContextProvider } from "./LdoContext"; |
||||
import { UpdateManager } from "./ldoHooks/helpers/UpdateManager"; |
||||
import { BinaryResourceStore } from "./document/resource/binaryResource/BinaryResourceStore"; |
||||
import { DataResourceStore } from "./document/resource/dataResource/DataResourceStore"; |
||||
import { ContainerResourceStore } from "./document/resource/dataResource/containerResource/ContainerResourceStore"; |
||||
import { AccessRulesStore } from "./document/accessRules/AccessRulesStore"; |
||||
import { Dataset } from "@rdfjs/types"; |
||||
|
||||
export interface LdoProviderProps extends PropsWithChildren { |
||||
fetch?: typeof fetch; |
||||
dataset?: Dataset; |
||||
onDocumentError?: LdoContextData["onDocumentError"]; |
||||
} |
||||
|
||||
/** |
||||
* Main Ldo Provider |
||||
*/ |
||||
export const LdoProvider: FunctionComponent< |
||||
PropsWithChildren<LdoProviderProps> |
||||
> = ({ dataset, fetch, onDocumentError, children }) => { |
||||
const finalFetch = useMemo(() => fetch || crossFetch, [fetch]); |
||||
const ldoDataset = useMemo(() => createLdoDataset(dataset), [dataset]); |
||||
|
||||
// Initialize storeDependencies before render
|
||||
const storeDependencies = useMemo(() => { |
||||
// Ingnoring this because we're setting up circular dependencies
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const dependencies: LdoContextData = { |
||||
onDocumentError, |
||||
fetch: finalFetch, |
||||
dataset: ldoDataset, |
||||
updateManager: new UpdateManager(), |
||||
}; |
||||
const binaryResourceStore = new BinaryResourceStore(dependencies); |
||||
const dataResourceStore = new DataResourceStore(dependencies); |
||||
const containerResourceStore = new ContainerResourceStore(dependencies); |
||||
const accessRulesStore = new AccessRulesStore(dependencies); |
||||
dependencies.binaryResourceStore = binaryResourceStore; |
||||
dependencies.dataResourceStore = dataResourceStore; |
||||
dependencies.containerResourceStore = containerResourceStore; |
||||
dependencies.accessRulesStore = accessRulesStore; |
||||
return dependencies; |
||||
}, []); |
||||
|
||||
// Update the resource manager in case fetch or ldo dataset changes
|
||||
useEffect(() => { |
||||
storeDependencies.fetch = finalFetch; |
||||
storeDependencies.dataset = ldoDataset; |
||||
storeDependencies.onDocumentError = onDocumentError; |
||||
}, [finalFetch, ldoDataset, onDocumentError]); |
||||
|
||||
return ( |
||||
<LdoContextProvider value={storeDependencies}> |
||||
{children} |
||||
</LdoContextProvider> |
||||
); |
||||
}; |
@ -0,0 +1,98 @@ |
||||
import { useCallback, useEffect, useMemo, useState } from "react"; |
||||
import { |
||||
ISessionInfo, |
||||
handleIncomingRedirect, |
||||
login as libraryLogin, |
||||
getDefaultSession, |
||||
logout as libraryLogout, |
||||
fetch as libraryFetch, |
||||
} from "solid-authn-react-native"; |
||||
|
||||
import { createGlobalHook } from "./util/createGlobalHook"; |
||||
|
||||
const PRE_REDIRECT_URI = "PRE_REDIRECT_URI"; |
||||
|
||||
interface AuthGlobalHookReturn { |
||||
runInitialAuthCheck: () => Promise<void>; |
||||
login: (issuer: string) => Promise<void>; |
||||
logout: () => Promise<void>; |
||||
signUp: (issuer: string) => Promise<void>; |
||||
fetch: typeof fetch; |
||||
session: ISessionInfo; |
||||
ranInitialAuthCheck: boolean; |
||||
} |
||||
|
||||
function useAuthGlobalHookFunc(): AuthGlobalHookReturn { |
||||
const [session, setSession] = useState<ISessionInfo>( |
||||
getDefaultSession().info |
||||
); |
||||
const [ranInitialAuthCheck, setRanInitialAuthCheck] = useState(false); |
||||
|
||||
const runInitialAuthCheck = useCallback(async () => { |
||||
// TODO: Change this to dependency injection so it works in React Native
|
||||
if (!window.localStorage.getItem(PRE_REDIRECT_URI)) { |
||||
window.localStorage.setItem(PRE_REDIRECT_URI, window.location.href); |
||||
} |
||||
|
||||
await handleIncomingRedirect({ |
||||
restorePreviousSession: true, |
||||
}); |
||||
setSession({ ...getDefaultSession().info }); |
||||
|
||||
window.history.replaceState( |
||||
{}, |
||||
"", |
||||
window.localStorage.getItem(PRE_REDIRECT_URI) |
||||
); |
||||
window.localStorage.removeItem(PRE_REDIRECT_URI); |
||||
|
||||
setRanInitialAuthCheck(true); |
||||
}, []); |
||||
|
||||
const login = useCallback( |
||||
async (issuer: string, clientName = "Solid App") => { |
||||
window.localStorage.setItem(PRE_REDIRECT_URI, window.location.href); |
||||
await libraryLogin({ |
||||
oidcIssuer: issuer, |
||||
// TODO: this ties this to in-browser use
|
||||
redirectUrl: window.location.href, |
||||
clientName, |
||||
}); |
||||
setSession({ ...getDefaultSession().info }); |
||||
}, |
||||
[] |
||||
); |
||||
|
||||
const logout = useCallback(async () => { |
||||
await libraryLogout(); |
||||
setSession({ ...getDefaultSession().info }); |
||||
}, []); |
||||
|
||||
const signUp = useCallback(async (issuer: string) => { |
||||
/* Do nothing for now */ |
||||
console.log(`Signup Pressed with issuer ${issuer}`); |
||||
}, []); |
||||
|
||||
useEffect(() => { |
||||
runInitialAuthCheck(); |
||||
}, []); |
||||
|
||||
return useMemo( |
||||
() => ({ |
||||
runInitialAuthCheck, |
||||
login, |
||||
logout, |
||||
signUp, |
||||
session, |
||||
ranInitialAuthCheck, |
||||
fetch: libraryFetch, |
||||
}), |
||||
[login, logout, ranInitialAuthCheck, runInitialAuthCheck, session, signUp] |
||||
); |
||||
} |
||||
|
||||
const authGlobalHook = createGlobalHook(useAuthGlobalHookFunc); |
||||
|
||||
export const SolidAuthContext = authGlobalHook.Context; |
||||
export const SolidAuthProvider = authGlobalHook.Provider; |
||||
export const useSolidAuth = authGlobalHook.useGlobal; |
@ -0,0 +1,36 @@ |
||||
import { FetchableDocument } from "./FetchableDocument"; |
||||
|
||||
// This may eventually have fields
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface DocumentStoreDependencies {} |
||||
|
||||
export abstract class DocumentStore< |
||||
DocumentType extends FetchableDocument, |
||||
Initializer, |
||||
Dependencies extends DocumentStoreDependencies |
||||
> { |
||||
protected documentMap: Map<Initializer, DocumentType>; |
||||
protected dependencies: Dependencies; |
||||
|
||||
constructor(dependencies: Dependencies) { |
||||
this.documentMap = new Map(); |
||||
this.dependencies = dependencies; |
||||
} |
||||
|
||||
get(initializerInput: Initializer): DocumentType { |
||||
const initializer = this.normalizeInitializer(initializerInput); |
||||
const document = this.documentMap.get(initializer); |
||||
if (document) { |
||||
return document; |
||||
} |
||||
const newDocument = this.create(initializer); |
||||
this.documentMap.set(initializer, newDocument); |
||||
return newDocument; |
||||
} |
||||
|
||||
protected abstract create(initializer: Initializer): DocumentType; |
||||
|
||||
protected normalizeInitializer(initializer: Initializer): Initializer { |
||||
return initializer; |
||||
} |
||||
} |
@ -0,0 +1,107 @@ |
||||
import EventEmitter from "events"; |
||||
import { DocumentError } from "./errors/DocumentError"; |
||||
|
||||
export interface FetchableDocumentDependencies { |
||||
onDocumentError?: (error: DocumentError) => void; |
||||
} |
||||
|
||||
const STATE_UPDATE = "stateUpdate"; |
||||
|
||||
export abstract class FetchableDocument extends EventEmitter { |
||||
protected _isLoading: boolean; |
||||
protected _isWriting: boolean; |
||||
protected _didInitialFetch: boolean; |
||||
protected _error?: DocumentError; |
||||
private dependencies; |
||||
|
||||
constructor(dependencies: FetchableDocumentDependencies) { |
||||
super(); |
||||
this._isLoading = false; |
||||
this._isWriting = false; |
||||
this._didInitialFetch = false; |
||||
this.dependencies = dependencies; |
||||
} |
||||
/** |
||||
* =========================================================================== |
||||
* Getters |
||||
* =========================================================================== |
||||
*/ |
||||
get isLoading() { |
||||
return this._isLoading; |
||||
} |
||||
|
||||
get didInitialFetch() { |
||||
return this._didInitialFetch; |
||||
} |
||||
|
||||
get error() { |
||||
return this._error; |
||||
} |
||||
|
||||
get isWriting() { |
||||
return this._isWriting; |
||||
} |
||||
|
||||
protected get onDocumentError() { |
||||
return this.dependencies.onDocumentError; |
||||
} |
||||
|
||||
/** |
||||
* =========================================================================== |
||||
* Methods |
||||
* =========================================================================== |
||||
*/ |
||||
async read() { |
||||
this._isLoading = true; |
||||
this.emitStateUpdate(); |
||||
const documentError = await this.fetchDocument(); |
||||
this._isLoading = false; |
||||
this._didInitialFetch = true; |
||||
if (documentError) { |
||||
this.setError(documentError); |
||||
} |
||||
this.emitStateUpdate(); |
||||
} |
||||
|
||||
async reload() { |
||||
return this.read(); |
||||
} |
||||
|
||||
protected abstract fetchDocument(): Promise<DocumentError | undefined>; |
||||
|
||||
protected beginWrite() { |
||||
this._isWriting = true; |
||||
this.emitStateUpdate(); |
||||
} |
||||
|
||||
protected endWrite(error?: DocumentError) { |
||||
if (error) { |
||||
this.setError(error); |
||||
} |
||||
this._isWriting = false; |
||||
this.emitStateUpdate(); |
||||
} |
||||
|
||||
setError(error: DocumentError) { |
||||
this._error = error; |
||||
this.emitStateUpdate(); |
||||
if (this.onDocumentError) { |
||||
this.onDocumentError(error); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Emitter Information |
||||
*/ |
||||
protected emitStateUpdate() { |
||||
this.emit(STATE_UPDATE); |
||||
} |
||||
|
||||
onStateUpdate(callback: () => void) { |
||||
this.on(STATE_UPDATE, callback); |
||||
} |
||||
|
||||
offStateUpdate(callback: () => void) { |
||||
this.off(STATE_UPDATE, callback); |
||||
} |
||||
} |
@ -0,0 +1,80 @@ |
||||
import { |
||||
universalAccess, |
||||
AccessModes as IAccessModes, |
||||
} from "@inrupt/solid-client"; |
||||
import { |
||||
FetchableDocument, |
||||
FetchableDocumentDependencies, |
||||
} from "../FetchableDocument"; |
||||
import { Resource } from "../resource/Resource"; |
||||
import { DocumentError } from "../errors/DocumentError"; |
||||
|
||||
export type AccessModes = IAccessModes; |
||||
|
||||
export interface AccessRulesDependencies extends FetchableDocumentDependencies { |
||||
fetch: typeof fetch; |
||||
} |
||||
|
||||
export class AccessRules extends FetchableDocument { |
||||
readonly resource: Resource; |
||||
private _publicAccess: IAccessModes | null; |
||||
private _agentAccess: Record<string, IAccessModes> | null; |
||||
private dependencies0; |
||||
|
||||
constructor(resource: Resource, dependencies: AccessRulesDependencies) { |
||||
super(dependencies); |
||||
this._publicAccess = null; |
||||
this._agentAccess = null; |
||||
this.dependencies0 = dependencies; |
||||
this.resource = resource; |
||||
} |
||||
|
||||
/** |
||||
* =========================================================================== |
||||
* Getters |
||||
* =========================================================================== |
||||
*/ |
||||
get publicAccess() { |
||||
return this._publicAccess; |
||||
} |
||||
|
||||
get agentAccess() { |
||||
return this._agentAccess; |
||||
} |
||||
|
||||
protected get fetch() { |
||||
return this.dependencies0.fetch; |
||||
} |
||||
|
||||
/** |
||||
* =========================================================================== |
||||
* Methods |
||||
* =========================================================================== |
||||
*/ |
||||
protected async fetchDocument() { |
||||
try { |
||||
const [publicAccess, agentAccess] = await Promise.all([ |
||||
universalAccess.getPublicAccess(this.resource.uri, { |
||||
fetch: this.fetch, |
||||
}), |
||||
universalAccess.getAgentAccessAll(this.resource.uri, { |
||||
fetch: this.fetch, |
||||
}), |
||||
]); |
||||
this._publicAccess = publicAccess || { |
||||
read: false, |
||||
write: false, |
||||
append: false, |
||||
controlRead: false, |
||||
controlWrite: false, |
||||
}; |
||||
this._agentAccess = agentAccess || {}; |
||||
return undefined; |
||||
} catch (err: unknown) { |
||||
if (typeof err === "object" && (err as Error).message) { |
||||
this.setError(new DocumentError(this, (err as Error).message)); |
||||
} |
||||
this.setError(new DocumentError(this, "Error Fetching Access Rules")); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,17 @@ |
||||
import { DocumentStore, DocumentStoreDependencies } from "../DocumentStore"; |
||||
import { Resource } from "../resource/Resource"; |
||||
import { AccessRules, AccessRulesDependencies } from "./AccessRules"; |
||||
|
||||
export interface AccessRulesStoreDependencies |
||||
extends DocumentStoreDependencies, |
||||
AccessRulesDependencies {} |
||||
|
||||
export class AccessRulesStore extends DocumentStore< |
||||
AccessRules, |
||||
Resource, |
||||
AccessRulesStoreDependencies |
||||
> { |
||||
create(initializer: Resource) { |
||||
return new AccessRules(initializer, this.dependencies); |
||||
} |
||||
} |
@ -0,0 +1,10 @@ |
||||
import { FetchableDocument } from "../FetchableDocument"; |
||||
|
||||
export class DocumentError extends Error { |
||||
public readonly document: FetchableDocument; |
||||
|
||||
constructor(document: FetchableDocument, message: string) { |
||||
super(message); |
||||
this.document = document; |
||||
} |
||||
} |
@ -0,0 +1,11 @@ |
||||
import { FetchableDocument } from "../FetchableDocument"; |
||||
import { DocumentError } from "./DocumentError"; |
||||
|
||||
export class DocumentFetchError extends DocumentError { |
||||
public readonly status: number; |
||||
|
||||
constructor(document: FetchableDocument, status: number, message: string) { |
||||
super(document, message); |
||||
this.status = status; |
||||
} |
||||
} |
@ -0,0 +1,91 @@ |
||||
import { |
||||
FetchableDocument, |
||||
FetchableDocumentDependencies, |
||||
} from "../FetchableDocument"; |
||||
import { AccessRulesStore } from "../accessRules/AccessRulesStore"; |
||||
import { DocumentFetchError } from "../errors/DocumentFetchError"; |
||||
import { ContainerResource } from "./dataResource/containerResource/ContainerResource"; |
||||
import { ContainerResourceStore } from "./dataResource/containerResource/ContainerResourceStore"; |
||||
|
||||
export interface ResourceDependencies extends FetchableDocumentDependencies { |
||||
fetch: typeof fetch; |
||||
accessRulesStore: AccessRulesStore; |
||||
containerResourceStore: ContainerResourceStore; |
||||
} |
||||
|
||||
export abstract class Resource extends FetchableDocument { |
||||
public readonly uri: string; |
||||
private dependencies1; |
||||
|
||||
constructor(uri: string, dependencies: ResourceDependencies) { |
||||
super(dependencies); |
||||
this.uri = uri; |
||||
this.dependencies1 = dependencies; |
||||
} |
||||
|
||||
/** |
||||
* =========================================================================== |
||||
* Getters |
||||
* =========================================================================== |
||||
*/ |
||||
get accessRules() { |
||||
return this.accessRulesStore.get(this); |
||||
} |
||||
|
||||
get parentContainer(): ContainerResource | undefined { |
||||
return this.containerResourceStore.getContainerForResouce(this); |
||||
} |
||||
|
||||
get ["@id"]() { |
||||
return this.uri; |
||||
} |
||||
|
||||
protected get fetch() { |
||||
return this.dependencies1.fetch; |
||||
} |
||||
|
||||
protected get accessRulesStore() { |
||||
return this.dependencies1.accessRulesStore; |
||||
} |
||||
|
||||
protected get containerResourceStore() { |
||||
return this.dependencies1.containerResourceStore; |
||||
} |
||||
|
||||
/** |
||||
* =========================================================================== |
||||
* Methods |
||||
* =========================================================================== |
||||
*/ |
||||
async delete() { |
||||
this.beginWrite(); |
||||
const response = await this.fetch(this.uri, { |
||||
method: "DELETE", |
||||
}); |
||||
if (response.status >= 200 && response.status < 300) { |
||||
this.endWrite(); |
||||
this.parentContainer?.removeContainedResources(this); |
||||
return; |
||||
} |
||||
this.endWrite( |
||||
new DocumentFetchError( |
||||
this, |
||||
response.status, |
||||
`Could not delete ${this.uri}` |
||||
) |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* =========================================================================== |
||||
* Static Methods |
||||
* =========================================================================== |
||||
*/ |
||||
/** |
||||
* Takes in a URL and will normalize it to the document it's fetching |
||||
*/ |
||||
static normalizeUri(uri: string): string { |
||||
const [strippedHashUri] = uri.split("#"); |
||||
return strippedHashUri; |
||||
} |
||||
} |
@ -0,0 +1,10 @@ |
||||
import { DocumentError } from "../../errors/DocumentError"; |
||||
import { Resource, ResourceDependencies } from "../Resource"; |
||||
|
||||
export declare type BinaryResourceDependencies = ResourceDependencies; |
||||
|
||||
export class BinaryResource extends Resource { |
||||
fetchDocument(): Promise<DocumentError | undefined> { |
||||
throw new Error("Method not implemented."); |
||||
} |
||||
} |
@ -0,0 +1,20 @@ |
||||
import { DocumentStore, DocumentStoreDependencies } from "../../DocumentStore"; |
||||
import { BinaryResource, BinaryResourceDependencies } from "./BinaryResource"; |
||||
|
||||
export interface BinaryResourceStoreDependencies |
||||
extends DocumentStoreDependencies, |
||||
BinaryResourceDependencies {} |
||||
|
||||
export class BinaryResourceStore extends DocumentStore< |
||||
BinaryResource, |
||||
string, |
||||
BinaryResourceStoreDependencies |
||||
> { |
||||
create(initializer: string) { |
||||
return new BinaryResource(initializer, this.dependencies); |
||||
} |
||||
|
||||
protected normalizeInitializer(initializer: string): string { |
||||
return BinaryResource.normalizeUri(initializer); |
||||
} |
||||
} |
@ -0,0 +1,127 @@ |
||||
import { LdoDataset, parseRdf } from "ldo"; |
||||
import { Resource, ResourceDependencies } from "../Resource"; |
||||
import { DocumentFetchError } from "../../errors/DocumentFetchError"; |
||||
import { DocumentError } from "../../errors/DocumentError"; |
||||
import { namedNode, quad as createQuad } from "@rdfjs/data-model"; |
||||
import { DatasetChanges } from "o-dataset-pack"; |
||||
import { Quad } from "@rdfjs/types"; |
||||
import { changesToSparqlUpdate } from "../../../util/changesToSparqlUpdate"; |
||||
import { UpdateManager } from "../../../ldoHooks/helpers/UpdateManager"; |
||||
|
||||
export interface DataResourceDependencies extends ResourceDependencies { |
||||
dataset: LdoDataset; |
||||
updateManager: UpdateManager; |
||||
} |
||||
|
||||
export class DataResource extends Resource { |
||||
private dependencies2; |
||||
|
||||
constructor(uri: string, dependencies: DataResourceDependencies) { |
||||
super(uri, dependencies); |
||||
this.dependencies2 = dependencies; |
||||
} |
||||
|
||||
/** |
||||
* =========================================================================== |
||||
* Getters |
||||
* =========================================================================== |
||||
*/ |
||||
protected get dataset() { |
||||
return this.dependencies2.dataset; |
||||
} |
||||
|
||||
protected get updateManager() { |
||||
return this.dependencies2.updateManager; |
||||
} |
||||
|
||||
/** |
||||
* =========================================================================== |
||||
* Methods |
||||
* =========================================================================== |
||||
*/ |
||||
async create() { |
||||
// TODO
|
||||
} |
||||
|
||||
protected async fetchDocument(): Promise<DocumentError | undefined> { |
||||
// Fetch the document using auth fetch
|
||||
const response = await this.fetch(this.uri, { |
||||
headers: { |
||||
accept: "text/turtle", |
||||
}, |
||||
}); |
||||
// Handle Error
|
||||
if (response.status !== 200) { |
||||
// TODO: Handle edge cases
|
||||
return new DocumentFetchError( |
||||
this, |
||||
response.status, |
||||
`Error fetching resource ${this.uri}` |
||||
); |
||||
} |
||||
// Parse the incoming turtle into a dataset
|
||||
const rawTurtle = await response.text(); |
||||
let loadedDataset; |
||||
try { |
||||
loadedDataset = await parseRdf(rawTurtle, { |
||||
baseIRI: this.uri, |
||||
}); |
||||
} catch (err) { |
||||
if (typeof err === "object" && (err as Error).message) { |
||||
return new DocumentError(this, (err as Error).message); |
||||
} |
||||
return new DocumentError(this, "Server returned poorly formatted Turtle"); |
||||
} |
||||
// Start transaction
|
||||
const transactionalDataset = this.dataset.startTransaction(); |
||||
const graphNode = namedNode(this.uri); |
||||
// Destroy all triples that were once a part of this resouce
|
||||
loadedDataset.deleteMatches(undefined, undefined, undefined, graphNode); |
||||
// Add the triples from the fetched item
|
||||
loadedDataset.forEach((quad) => { |
||||
transactionalDataset.add( |
||||
createQuad(quad.subject, quad.predicate, quad.object, graphNode) |
||||
); |
||||
}); |
||||
const changes = transactionalDataset.getChanges(); |
||||
this.updateManager.notifyListenersOfChanges(changes); |
||||
transactionalDataset.commit(); |
||||
return undefined; |
||||
} |
||||
|
||||
async update( |
||||
changes: DatasetChanges<Quad> |
||||
): Promise<DocumentError | undefined> { |
||||
this.beginWrite(); |
||||
// Convert changes to transactional Dataset
|
||||
const transactionalDataset = this.dataset.startTransaction(); |
||||
changes.added?.forEach((quad) => transactionalDataset.add(quad)); |
||||
changes.removed?.forEach((quad) => transactionalDataset.delete(quad)); |
||||
// Commit data optimistically
|
||||
transactionalDataset.commit(); |
||||
this.updateManager.notifyListenersOfChanges(changes); |
||||
// Make request
|
||||
const sparqlUpdate = await changesToSparqlUpdate(changes); |
||||
const response = await this.fetch(this.uri, { |
||||
method: "PATCH", |
||||
body: sparqlUpdate, |
||||
headers: { |
||||
"Content-Type": "application/sparql-update", |
||||
}, |
||||
}); |
||||
if (response.status < 200 || response.status > 299) { |
||||
// Handle Error by rollback
|
||||
transactionalDataset.rollback(); |
||||
this.updateManager.notifyListenersOfChanges(changes); |
||||
this.endWrite( |
||||
new DocumentFetchError( |
||||
this, |
||||
response.status, |
||||
`Problem writing to ${this.uri}` |
||||
) |
||||
); |
||||
return; |
||||
} |
||||
this.endWrite(); |
||||
} |
||||
} |
@ -0,0 +1,20 @@ |
||||
import { DocumentStore, DocumentStoreDependencies } from "../../DocumentStore"; |
||||
import { DataResource, DataResourceDependencies } from "./DataResource"; |
||||
|
||||
export interface DataResourceStoreDependencies |
||||
extends DocumentStoreDependencies, |
||||
DataResourceDependencies {} |
||||
|
||||
export class DataResourceStore extends DocumentStore< |
||||
DataResource, |
||||
string, |
||||
DataResourceStoreDependencies |
||||
> { |
||||
protected create(initializer: string) { |
||||
return new DataResource(initializer, this.dependencies); |
||||
} |
||||
|
||||
protected normalizeInitializer(initializer: string): string { |
||||
return DataResource.normalizeUri(initializer); |
||||
} |
||||
} |
@ -0,0 +1,102 @@ |
||||
import { ContainerShapeType } from "../../../../ldo/solid.shapeTypes"; |
||||
import { Resource } from "../../Resource"; |
||||
import { BinaryResourceStore } from "../../binaryResource/BinaryResourceStore"; |
||||
import { DataResource, DataResourceDependencies } from "../DataResource"; |
||||
import { DataResourceStore } from "../DataResourceStore"; |
||||
|
||||
export interface ContainerResourceDependencies |
||||
extends DataResourceDependencies { |
||||
dataResourceStore: DataResourceStore; |
||||
binaryResourceStore: BinaryResourceStore; |
||||
} |
||||
|
||||
export class ContainerResource extends DataResource { |
||||
private _contains: Set<Resource>; |
||||
private dependencies3: ContainerResourceDependencies; |
||||
|
||||
constructor(uri: string, dependencies: ContainerResourceDependencies) { |
||||
super(uri, dependencies); |
||||
this._contains = new Set(); |
||||
this.dependencies3 = dependencies; |
||||
} |
||||
|
||||
/** |
||||
* =========================================================================== |
||||
* Getters |
||||
* =========================================================================== |
||||
*/ |
||||
get contains() { |
||||
return Array.from(this._contains); |
||||
} |
||||
|
||||
protected get binaryResourceStore() { |
||||
return this.dependencies3.binaryResourceStore; |
||||
} |
||||
|
||||
protected get dataResourceStore() { |
||||
return this.dependencies3.dataResourceStore; |
||||
} |
||||
/** |
||||
* =========================================================================== |
||||
* Methods |
||||
* =========================================================================== |
||||
*/ |
||||
protected async fetchDocument() { |
||||
const error = await super.fetchDocument(); |
||||
if (error) { |
||||
return error; |
||||
} |
||||
// Update the contains
|
||||
const container = this.dataset |
||||
.usingType(ContainerShapeType) |
||||
.fromSubject(this.uri); |
||||
const resourcesToAdd: Resource[] = []; |
||||
container.contains?.forEach((resourceData) => { |
||||
if (resourceData["@id"]) { |
||||
if (resourceData.type?.some((type) => type["@id"] === "Container")) { |
||||
resourcesToAdd.push( |
||||
this.containerResourceStore.get(resourceData["@id"]) |
||||
); |
||||
} else { |
||||
if (resourceData["@id"].endsWith(".ttl")) { |
||||
resourcesToAdd.push( |
||||
this.dataResourceStore.get(resourceData["@id"]) |
||||
); |
||||
} else { |
||||
resourcesToAdd.push( |
||||
this.binaryResourceStore.get(resourceData["@id"]) |
||||
); |
||||
} |
||||
} |
||||
} |
||||
}); |
||||
this.addContainedResources(...resourcesToAdd); |
||||
} |
||||
|
||||
public addContainedResources(...resources: Resource[]) { |
||||
let someResourceUpdated = false; |
||||
resources.forEach((resource) => { |
||||
if (!this._contains.has(resource)) { |
||||
someResourceUpdated = true; |
||||
this._contains.add(resource); |
||||
this.parentContainer?.addContainedResources(this); |
||||
} |
||||
}); |
||||
if (someResourceUpdated) { |
||||
this.emitStateUpdate(); |
||||
} |
||||
} |
||||
|
||||
public removeContainedResources(...resources: Resource[]) { |
||||
let someResourceUpdated = false; |
||||
resources.forEach((resource) => { |
||||
if (this._contains.has(resource)) { |
||||
someResourceUpdated = true; |
||||
this._contains.delete(resource); |
||||
} |
||||
}); |
||||
if (someResourceUpdated) { |
||||
this.emitStateUpdate(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,52 @@ |
||||
import { |
||||
DocumentStore, |
||||
DocumentStoreDependencies, |
||||
} from "../../../DocumentStore"; |
||||
import { Resource } from "../../Resource"; |
||||
import { |
||||
ContainerResource, |
||||
ContainerResourceDependencies, |
||||
} from "./ContainerResource"; |
||||
|
||||
export interface ContainerResourceStoreDependencies |
||||
extends ContainerResourceDependencies, |
||||
DocumentStoreDependencies {} |
||||
|
||||
export class ContainerResourceStore extends DocumentStore< |
||||
ContainerResource, |
||||
string, |
||||
ContainerResourceStoreDependencies |
||||
> { |
||||
protected create(initializer: string) { |
||||
return new ContainerResource(initializer, this.dependencies); |
||||
} |
||||
|
||||
protected normalizeInitializer(initializer: string) { |
||||
return ContainerResource.normalizeUri(initializer); |
||||
} |
||||
|
||||
getContainerForResouce(resource: Resource) { |
||||
const parentUri = ContainerResourceStore.getParentUri(resource.uri); |
||||
return parentUri ? this.get(parentUri) : undefined; |
||||
} |
||||
|
||||
/** |
||||
* Returns the parent container URI |
||||
*/ |
||||
static getParentUri(uri: string) { |
||||
const urlObject = new URL(uri); |
||||
const pathItems = urlObject.pathname.split("/"); |
||||
if ( |
||||
pathItems.length < 2 || |
||||
(pathItems.length === 2 && pathItems[1].length === 0) |
||||
) { |
||||
return undefined; |
||||
} |
||||
if (pathItems[pathItems.length - 1] === "") { |
||||
pathItems.pop(); |
||||
} |
||||
pathItems.pop(); |
||||
urlObject.pathname = `${pathItems.join("/")}/`; |
||||
return urlObject.toString(); |
||||
} |
||||
} |
@ -0,0 +1,17 @@ |
||||
import { useMemo } from "react"; |
||||
import { useLdoContext } from "../LdoContext"; |
||||
import { UseDocumentOptions, useDocument } from "./useDocument"; |
||||
import { Resource } from "../document/resource/Resource"; |
||||
|
||||
export function useAccessRules( |
||||
resource: string | Resource, |
||||
options?: UseDocumentOptions |
||||
) { |
||||
const { dataResourceStore, accessRulesStore } = useLdoContext(); |
||||
const realResource = useMemo(() => { |
||||
return typeof resource === "string" |
||||
? dataResourceStore.get(resource) |
||||
: resource; |
||||
}, [resource]); |
||||
return useDocument(realResource, accessRulesStore, options); |
||||
} |
@ -0,0 +1,7 @@ |
||||
import { useLdoContext } from "../LdoContext"; |
||||
import { UseDocumentOptions, useDocument } from "./useDocument"; |
||||
|
||||
export function useBinaryResource(uri: string, options?: UseDocumentOptions) { |
||||
const { binaryResourceStore } = useLdoContext(); |
||||
return useDocument(uri, binaryResourceStore, options); |
||||
} |
@ -0,0 +1,10 @@ |
||||
import { useLdoContext } from "../LdoContext"; |
||||
import { UseDocumentOptions, useDocument } from "./useDocument"; |
||||
|
||||
export function useContainerResource( |
||||
uri: string, |
||||
options?: UseDocumentOptions |
||||
) { |
||||
const { containerResourceStore } = useLdoContext(); |
||||
return useDocument(uri, containerResourceStore, options); |
||||
} |
@ -0,0 +1,7 @@ |
||||
import { useLdoContext } from "../LdoContext"; |
||||
import { UseDocumentOptions, useDocument } from "./useDocument"; |
||||
|
||||
export function useDataResource(uri: string, options?: UseDocumentOptions) { |
||||
const { dataResourceStore } = useLdoContext(); |
||||
return useDocument(uri, dataResourceStore, options); |
||||
} |
@ -0,0 +1,45 @@ |
||||
import { useEffect, useMemo } from "react"; |
||||
import { |
||||
DocumentStore, |
||||
DocumentStoreDependencies, |
||||
} from "../document/DocumentStore"; |
||||
import { FetchableDocument } from "../document/FetchableDocument"; |
||||
import { useForceUpdate } from "../util/useForceReload"; |
||||
|
||||
export interface UseDocumentOptions { |
||||
suppressLoadOnMount: boolean; |
||||
} |
||||
|
||||
export function useDocument< |
||||
DocumentType extends FetchableDocument, |
||||
Initializer |
||||
>( |
||||
initializer: Initializer, |
||||
documentStore: DocumentStore< |
||||
DocumentType, |
||||
Initializer, |
||||
DocumentStoreDependencies |
||||
>, |
||||
options?: UseDocumentOptions |
||||
) { |
||||
const document = useMemo(() => { |
||||
return documentStore.get(initializer); |
||||
}, [initializer, documentStore]); |
||||
|
||||
const forceUpdate = useForceUpdate(); |
||||
|
||||
useEffect(() => { |
||||
// Set up the listener for state update
|
||||
function onStateUpdateCallback() { |
||||
forceUpdate(); |
||||
} |
||||
document.onStateUpdate(onStateUpdateCallback); |
||||
// Load the resource if load on mount is true
|
||||
if (!options?.suppressLoadOnMount) { |
||||
document.read(); |
||||
} |
||||
return () => document.offStateUpdate(onStateUpdateCallback); |
||||
}, []); |
||||
|
||||
return document; |
||||
} |
@ -0,0 +1,41 @@ |
||||
// document
|
||||
export * from "./document/FetchableDocument"; |
||||
export * from "./document/DocumentStore"; |
||||
|
||||
// document/errors
|
||||
export * from "./document/errors/DocumentError"; |
||||
export * from "./document/errors/DocumentFetchError"; |
||||
|
||||
// document/accessRules
|
||||
export * from "./document/accessRules/AccessRules"; |
||||
export * from "./document/accessRules/AccessRulesStore"; |
||||
|
||||
// document/resource
|
||||
export * from "./document/resource/Resource"; |
||||
|
||||
// document/resource/binaryResource
|
||||
export * from "./document/resource/binaryResource/BinaryResource"; |
||||
export * from "./document/resource/binaryResource/BinaryResourceStore"; |
||||
|
||||
// document/resource/dataResource
|
||||
export * from "./document/resource/dataResource/DataResource"; |
||||
export * from "./document/resource/dataResource/DataResourceStore"; |
||||
|
||||
// document/resource/containerResource
|
||||
export * from "./document/resource/dataResource/containerResource/ContainerResource"; |
||||
export * from "./document/resource/dataResource/containerResource/ContainerResourceStore"; |
||||
|
||||
// documentHooks
|
||||
export * from "./documentHooks/useAccessRules"; |
||||
export * from "./documentHooks/useBinaryResource"; |
||||
export * from "./documentHooks/useContainerResource"; |
||||
export * from "./documentHooks/useDataResource"; |
||||
export * from "./documentHooks/useDocument"; |
||||
|
||||
// ldoHooks
|
||||
export * from "./ldoHooks/useSubject"; |
||||
|
||||
// export
|
||||
export * from "./useLdo"; |
||||
export * from "./LdoProvider"; |
||||
export * from "./SolidAuthProvider"; |
@ -0,0 +1,36 @@ |
||||
import { ContextDefinition } from "jsonld"; |
||||
|
||||
/** |
||||
* ============================================================================= |
||||
* solidContext: JSONLD Context for solid |
||||
* ============================================================================= |
||||
*/ |
||||
export const solidContext: ContextDefinition = { |
||||
type: { |
||||
"@id": "@type", |
||||
"@container": "@set", |
||||
}, |
||||
Container: "http://www.w3.org/ns/ldp#Container", |
||||
Resource: "http://www.w3.org/ns/ldp#Resource", |
||||
modified: { |
||||
"@id": "http://purl.org/dc/terms/modified", |
||||
"@type": "http://www.w3.org/2001/XMLSchema#string", |
||||
"@container": "@set", |
||||
}, |
||||
contains: { |
||||
"@id": "http://www.w3.org/ns/ldp#contains", |
||||
"@type": "@id", |
||||
"@container": "@set", |
||||
}, |
||||
Resource2: "http://www.w3.org/ns/iana/media-types/text/turtle#Resource", |
||||
mtime: { |
||||
"@id": "http://www.w3.org/ns/posix/stat#mtime", |
||||
"@type": "http://www.w3.org/2001/XMLSchema#decimal", |
||||
"@container": "@set", |
||||
}, |
||||
size: { |
||||
"@id": "http://www.w3.org/ns/posix/stat#size", |
||||
"@type": "http://www.w3.org/2001/XMLSchema#integer", |
||||
"@container": "@set", |
||||
}, |
||||
}; |
@ -0,0 +1,214 @@ |
||||
import { Schema } from "shexj"; |
||||
|
||||
/** |
||||
* ============================================================================= |
||||
* solidSchema: ShexJ Schema for solid |
||||
* ============================================================================= |
||||
*/ |
||||
export const solidSchema: Schema = { |
||||
type: "Schema", |
||||
shapes: [ |
||||
{ |
||||
id: "http://www.w3.org/ns/lddps#Container", |
||||
type: "ShapeDecl", |
||||
shapeExpr: { |
||||
type: "Shape", |
||||
expression: { |
||||
id: "http://www.w3.org/ns/lddps#ContainerShape", |
||||
type: "EachOf", |
||||
expressions: [ |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", |
||||
valueExpr: { |
||||
type: "NodeConstraint", |
||||
values: [ |
||||
"http://www.w3.org/ns/ldp#Container", |
||||
"http://www.w3.org/ns/ldp#Resource", |
||||
], |
||||
}, |
||||
min: 0, |
||||
max: -1, |
||||
annotations: [ |
||||
{ |
||||
type: "Annotation", |
||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||
object: { |
||||
value: "A container on a Solid server", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://purl.org/dc/terms/modified", |
||||
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: "Date modified", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://www.w3.org/ns/ldp#contains", |
||||
valueExpr: "http://www.w3.org/ns/lddps#Resource", |
||||
min: 0, |
||||
max: -1, |
||||
annotations: [ |
||||
{ |
||||
type: "Annotation", |
||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||
object: { |
||||
value: "Defines a Solid Resource", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://www.w3.org/ns/posix/stat#mtime", |
||||
valueExpr: { |
||||
type: "NodeConstraint", |
||||
datatype: "http://www.w3.org/2001/XMLSchema#decimal", |
||||
}, |
||||
min: 0, |
||||
max: 1, |
||||
annotations: [ |
||||
{ |
||||
type: "Annotation", |
||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||
object: { |
||||
value: "?", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://www.w3.org/ns/posix/stat#size", |
||||
valueExpr: { |
||||
type: "NodeConstraint", |
||||
datatype: "http://www.w3.org/2001/XMLSchema#integer", |
||||
}, |
||||
min: 0, |
||||
max: 1, |
||||
annotations: [ |
||||
{ |
||||
type: "Annotation", |
||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||
object: { |
||||
value: "size of this container", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
}, |
||||
extra: ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"], |
||||
}, |
||||
}, |
||||
{ |
||||
id: "http://www.w3.org/ns/lddps#Resource", |
||||
type: "ShapeDecl", |
||||
shapeExpr: { |
||||
type: "Shape", |
||||
expression: { |
||||
id: "http://www.w3.org/ns/lddps#ResourceShape", |
||||
type: "EachOf", |
||||
expressions: [ |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", |
||||
valueExpr: { |
||||
type: "NodeConstraint", |
||||
values: [ |
||||
"http://www.w3.org/ns/ldp#Resource", |
||||
"http://www.w3.org/ns/iana/media-types/text/turtle#Resource", |
||||
], |
||||
}, |
||||
min: 0, |
||||
max: -1, |
||||
annotations: [ |
||||
{ |
||||
type: "Annotation", |
||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||
object: { |
||||
value: "Any resource on a Solid server", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://purl.org/dc/terms/modified", |
||||
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: "Date modified", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://www.w3.org/ns/posix/stat#mtime", |
||||
valueExpr: { |
||||
type: "NodeConstraint", |
||||
datatype: "http://www.w3.org/2001/XMLSchema#decimal", |
||||
}, |
||||
min: 0, |
||||
max: 1, |
||||
annotations: [ |
||||
{ |
||||
type: "Annotation", |
||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||
object: { |
||||
value: "?", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://www.w3.org/ns/posix/stat#size", |
||||
valueExpr: { |
||||
type: "NodeConstraint", |
||||
datatype: "http://www.w3.org/2001/XMLSchema#integer", |
||||
}, |
||||
min: 0, |
||||
max: 1, |
||||
annotations: [ |
||||
{ |
||||
type: "Annotation", |
||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||
object: { |
||||
value: "size of this container", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
}, |
||||
extra: ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"], |
||||
}, |
||||
}, |
||||
], |
||||
}; |
@ -0,0 +1,28 @@ |
||||
import { ShapeType } from "ldo"; |
||||
import { solidSchema } from "./solid.schema"; |
||||
import { solidContext } from "./solid.context"; |
||||
import { Container, Resource } from "./solid.typings"; |
||||
|
||||
/** |
||||
* ============================================================================= |
||||
* LDO ShapeTypes solid |
||||
* ============================================================================= |
||||
*/ |
||||
|
||||
/** |
||||
* Container ShapeType |
||||
*/ |
||||
export const ContainerShapeType: ShapeType<Container> = { |
||||
schema: solidSchema, |
||||
shape: "http://www.w3.org/ns/lddps#Container", |
||||
context: solidContext, |
||||
}; |
||||
|
||||
/** |
||||
* Resource ShapeType |
||||
*/ |
||||
export const ResourceShapeType: ShapeType<Resource> = { |
||||
schema: solidSchema, |
||||
shape: "http://www.w3.org/ns/lddps#Resource", |
||||
context: solidContext, |
||||
}; |
@ -0,0 +1,73 @@ |
||||
import { ContextDefinition } from "jsonld"; |
||||
|
||||
/** |
||||
* ============================================================================= |
||||
* Typescript Typings for solid |
||||
* ============================================================================= |
||||
*/ |
||||
|
||||
/** |
||||
* Container Type |
||||
*/ |
||||
export interface Container { |
||||
"@id"?: string; |
||||
"@context"?: ContextDefinition; |
||||
/** |
||||
* A container on a Solid server |
||||
*/ |
||||
type?: ( |
||||
| { |
||||
"@id": "Container"; |
||||
} |
||||
| { |
||||
"@id": "Resource"; |
||||
} |
||||
)[]; |
||||
/** |
||||
* Date modified |
||||
*/ |
||||
modified?: string; |
||||
/** |
||||
* Defines a Solid Resource |
||||
*/ |
||||
contains?: (Resource | Container)[]; |
||||
/** |
||||
* ? |
||||
*/ |
||||
mtime?: number; |
||||
/** |
||||
* size of this container |
||||
*/ |
||||
size?: number; |
||||
} |
||||
|
||||
/** |
||||
* Resource Type |
||||
*/ |
||||
export interface Resource { |
||||
"@id"?: string; |
||||
"@context"?: ContextDefinition; |
||||
/** |
||||
* Any resource on a Solid server |
||||
*/ |
||||
type?: ( |
||||
| { |
||||
"@id": "Resource"; |
||||
} |
||||
| { |
||||
"@id": "Resource2"; |
||||
} |
||||
)[]; |
||||
/** |
||||
* Date modified |
||||
*/ |
||||
modified?: string; |
||||
/** |
||||
* ? |
||||
*/ |
||||
mtime?: number; |
||||
/** |
||||
* size of this container |
||||
*/ |
||||
size?: number; |
||||
} |
@ -0,0 +1,98 @@ |
||||
import { |
||||
ArrayProxyTarget, |
||||
ProxyContext, |
||||
SubjectProxyTarget, |
||||
ProxyContextOptions, |
||||
} from "jsonld-dataset-proxy"; |
||||
import { UpdateManager } from "./UpdateManager"; |
||||
import { namedNode } from "@rdfjs/data-model"; |
||||
|
||||
export class TrackingProxyContext extends ProxyContext { |
||||
private updateManager: UpdateManager; |
||||
private listener: () => void; |
||||
|
||||
constructor( |
||||
options: ProxyContextOptions, |
||||
updateManager: UpdateManager, |
||||
listener: () => void |
||||
) { |
||||
super(options); |
||||
this.updateManager = updateManager; |
||||
this.listener = listener; |
||||
} |
||||
|
||||
protected createSubjectHandler(): ProxyHandler<SubjectProxyTarget> { |
||||
const baseHandler = super.createSubjectHandler(); |
||||
const oldGetFunction = baseHandler.get; |
||||
const newGetFunction: ProxyHandler<SubjectProxyTarget>["get"] = ( |
||||
target: SubjectProxyTarget, |
||||
key: string | symbol, |
||||
receiver |
||||
) => { |
||||
const subject = target["@id"]; |
||||
if (typeof key === "symbol") { |
||||
// Do Nothing
|
||||
} else if (key === "@id") { |
||||
this.updateManager.registerListener( |
||||
[subject, null, null], |
||||
this.listener |
||||
); |
||||
} else if (!this.contextUtil.isArray(key)) { |
||||
const predicate = namedNode(this.contextUtil.keyToIri(key)); |
||||
this.updateManager.registerListener( |
||||
[subject, predicate, null], |
||||
this.listener |
||||
); |
||||
} |
||||
return oldGetFunction && oldGetFunction(target, key, receiver); |
||||
}; |
||||
baseHandler.get = newGetFunction; |
||||
baseHandler.set = () => { |
||||
console.warn( |
||||
"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." |
||||
); |
||||
return true; |
||||
}; |
||||
return baseHandler; |
||||
} |
||||
|
||||
protected createArrayHandler(): ProxyHandler<ArrayProxyTarget> { |
||||
const baseHandler = super.createArrayHandler(); |
||||
const oldGetFunction = baseHandler.get; |
||||
const newGetFunction: ProxyHandler<ArrayProxyTarget>["get"] = ( |
||||
target: ArrayProxyTarget, |
||||
key: string | symbol, |
||||
receiver |
||||
) => { |
||||
if (qualifiedArrayMethods.has(key)) { |
||||
this.updateManager.registerListener( |
||||
[target[0][0], target[0][1], target[0][2]], |
||||
this.listener |
||||
); |
||||
} |
||||
return oldGetFunction && oldGetFunction(target, key, receiver); |
||||
}; |
||||
baseHandler.get = newGetFunction; |
||||
return baseHandler; |
||||
} |
||||
} |
||||
|
||||
const qualifiedArrayMethods = new Set([ |
||||
"forEach", |
||||
"map", |
||||
"reduce", |
||||
Symbol.iterator, |
||||
"entries", |
||||
"every", |
||||
"filter", |
||||
"find", |
||||
"findIndex", |
||||
"findLast", |
||||
"findLastIndex", |
||||
"includes, indexOf", |
||||
"keys", |
||||
"lastIndexOf", |
||||
"reduceRight", |
||||
"some", |
||||
"values", |
||||
]); |
@ -0,0 +1,83 @@ |
||||
import { DatasetChanges, createDataset } from "o-dataset-pack"; |
||||
import { |
||||
QuadMatch, |
||||
SubjectType, |
||||
PredicateType, |
||||
ObjectType, |
||||
nodeToString, |
||||
} from "jsonld-dataset-proxy"; |
||||
import { Quad } from "@rdfjs/types"; |
||||
|
||||
export type TripleMatch = [QuadMatch[0], QuadMatch[1], QuadMatch[2]]; |
||||
|
||||
export class UpdateManager { |
||||
private tripleMatchListenerMap: Record<string, Set<() => void>> = {}; |
||||
private listenerHashMap: Map<() => void, Set<string>> = new Map(); |
||||
|
||||
private tripleMatchToHash(tripleMatch: TripleMatch): string { |
||||
return `${nodeToString(tripleMatch[0])}${nodeToString( |
||||
tripleMatch[1] |
||||
)}${nodeToString(tripleMatch[2])}`;
|
||||
} |
||||
|
||||
registerListener(tripleMatch: TripleMatch, callback: () => void): void { |
||||
const hash = this.tripleMatchToHash(tripleMatch); |
||||
if (!this.tripleMatchListenerMap[hash]) { |
||||
this.tripleMatchListenerMap[hash] = new Set(); |
||||
} |
||||
if (!this.listenerHashMap.has(callback)) { |
||||
this.listenerHashMap.set(callback, new Set()); |
||||
} |
||||
this.tripleMatchListenerMap[hash].add(callback); |
||||
this.listenerHashMap.get(callback)?.add(hash); |
||||
} |
||||
|
||||
removeListener(callback: () => void) { |
||||
const hashSet = this.listenerHashMap.get(callback); |
||||
if (hashSet) { |
||||
hashSet.forEach((hash) => { |
||||
this.tripleMatchListenerMap[hash]?.delete(callback); |
||||
}); |
||||
} |
||||
} |
||||
|
||||
notifyListenersOfChanges(changes: DatasetChanges<Quad>): void { |
||||
const listenersToNotify = new Set<() => void>(); |
||||
|
||||
const allQuads = createDataset(); |
||||
allQuads.addAll(changes.added || []); |
||||
allQuads.addAll(changes.removed || []); |
||||
|
||||
// Iterate through all quads looking for any dataset match they effect
|
||||
allQuads.forEach((tempQuad) => { |
||||
// Cast the input because RDFJS types assume RDF 1.2 where a Subject can
|
||||
// be a Quad
|
||||
const quad = tempQuad as { |
||||
subject: SubjectType; |
||||
predicate: PredicateType; |
||||
object: ObjectType; |
||||
}; |
||||
const tripleMatches: TripleMatch[] = [ |
||||
[null, null, null], |
||||
[quad.subject, null, null], |
||||
[quad.subject, quad.predicate, null], |
||||
[quad.subject, null, quad.object], |
||||
[null, quad.predicate, null], |
||||
[null, quad.predicate, quad.object], |
||||
[null, null, quad.object], |
||||
[quad.subject, quad.predicate, quad.object], |
||||
]; |
||||
|
||||
tripleMatches.forEach((tripleMatch) => { |
||||
const hash = this.tripleMatchToHash(tripleMatch); |
||||
this.tripleMatchListenerMap[hash]?.forEach((callback) => { |
||||
listenersToNotify.add(callback); |
||||
}); |
||||
delete this.tripleMatchListenerMap[hash]; |
||||
}); |
||||
}); |
||||
listenersToNotify.forEach((listener) => { |
||||
listener(); |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,59 @@ |
||||
import { |
||||
ContextUtil, |
||||
JsonldDatasetProxyBuilder, |
||||
SubjectType, |
||||
} from "jsonld-dataset-proxy"; |
||||
import { LdoBuilder, ShapeType } from "ldo"; |
||||
import { LdoBase } from "ldo/dist/util"; |
||||
import { useLdoContext } from "../LdoContext"; |
||||
import { useCallback, useEffect, useMemo, useState } from "react"; |
||||
import { TrackingProxyContext } from "./helpers/TrackingProxyContext"; |
||||
import { defaultGraph } from "@rdfjs/data-model"; |
||||
|
||||
export function useSubject<Type extends LdoBase>( |
||||
shapeType: ShapeType<Type>, |
||||
subject: string | SubjectType |
||||
): [Type, undefined] | [undefined, Error] { |
||||
const { dataset, updateManager } = useLdoContext(); |
||||
|
||||
const [forceUpdateCounter, setForceUpdateCounter] = useState(0); |
||||
const forceUpdate = useCallback( |
||||
() => setForceUpdateCounter((val) => val + 1), |
||||
[setForceUpdateCounter] |
||||
); |
||||
|
||||
// The main linked data object
|
||||
const linkedDataObject = useMemo(() => { |
||||
// Rebuild the LdoBuilder from scratch to inject TrackingProxyContext
|
||||
const contextUtil = new ContextUtil(shapeType.context); |
||||
const proxyContext = new TrackingProxyContext( |
||||
{ |
||||
dataset, |
||||
contextUtil, |
||||
writeGraphs: [defaultGraph()], |
||||
languageOrdering: ["none", "en", "other"], |
||||
}, |
||||
updateManager, |
||||
forceUpdate |
||||
); |
||||
const builder = new LdoBuilder( |
||||
new JsonldDatasetProxyBuilder(proxyContext), |
||||
shapeType |
||||
); |
||||
return builder.fromSubject(subject); |
||||
}, [ |
||||
shapeType, |
||||
subject, |
||||
dataset, |
||||
updateManager, |
||||
forceUpdateCounter, |
||||
forceUpdate, |
||||
]); |
||||
|
||||
useEffect(() => { |
||||
// Unregister force update listener upon unmount
|
||||
return () => updateManager.removeListener(forceUpdate); |
||||
}, [shapeType, subject]); |
||||
|
||||
return [linkedDataObject, undefined]; |
||||
} |
@ -0,0 +1,36 @@ |
||||
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> |
||||
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> |
||||
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> |
||||
PREFIX ldp: <http://www.w3.org/ns/ldp#> |
||||
PREFIX ldps: <http://www.w3.org/ns/lddps#> |
||||
PREFIX dct: <http://purl.org/dc/terms/> |
||||
PREFIX stat: <http://www.w3.org/ns/posix/stat#> |
||||
PREFIX tur: <http://www.w3.org/ns/iana/media-types/text/turtle#> |
||||
|
||||
ldps:Container EXTRA a { |
||||
$ldps:ContainerShape ( |
||||
a [ ldp:Container ldp:Resource ]* |
||||
// rdfs:comment "A container on a Solid server"; |
||||
dct:modified xsd:string? |
||||
// rdfs:comment "Date modified"; |
||||
ldp:contains @ldps:Resource* |
||||
// rdfs:comment "Defines a Solid Resource"; |
||||
stat:mtime xsd:decimal? |
||||
// rdfs:comment "?"; |
||||
stat:size xsd:integer? |
||||
// rdfs:comment "size of this container"; |
||||
) |
||||
} |
||||
|
||||
ldps:Resource EXTRA a { |
||||
$ldps:ResourceShape ( |
||||
a [ ldp:Resource tur:Resource ]* |
||||
// rdfs:comment "Any resource on a Solid server"; |
||||
dct:modified xsd:string? |
||||
// rdfs:comment "Date modified"; |
||||
stat:mtime xsd:decimal? |
||||
// rdfs:comment "?"; |
||||
stat:size xsd:integer? |
||||
// rdfs:comment "size of this container"; |
||||
) |
||||
} |
@ -0,0 +1,117 @@ |
||||
import { useCallback, useMemo } from "react"; |
||||
import { useLdoContext } from "./LdoContext"; |
||||
import { |
||||
LdoDataset, |
||||
ShapeType, |
||||
startTransaction, |
||||
transactionChanges, |
||||
write, |
||||
} from "ldo"; |
||||
import { splitChangesByGraph } from "./util/splitChangesByGraph"; |
||||
import { LdoBase } from "ldo/dist/util"; |
||||
import { Resource } from "./document/resource/Resource"; |
||||
import { DataResource } from "./document/resource/dataResource/DataResource"; |
||||
import { BinaryResource } from "./document/resource/binaryResource/BinaryResource"; |
||||
import { ContainerResource } from "./document/resource/dataResource/containerResource/ContainerResource"; |
||||
import { AccessRules } from "./document/accessRules/AccessRules"; |
||||
import { SubjectType } from "jsonld-dataset-proxy"; |
||||
import { DatasetChanges } from "o-dataset-pack"; |
||||
import { Quad } from "@rdfjs/types"; |
||||
|
||||
export interface UseLdoReturn { |
||||
changeData<Type extends LdoBase>(input: Type, ...resources: Resource[]): Type; |
||||
commitData(input: LdoBase): Promise<void>; |
||||
createData<Type extends LdoBase>( |
||||
shapeType: ShapeType<Type>, |
||||
subject: string | SubjectType, |
||||
...resources: Resource[] |
||||
): Type; |
||||
dataset: LdoDataset; |
||||
getDataResource: (uri: string) => DataResource; |
||||
getBinaryResource: (uri: string) => BinaryResource; |
||||
getContainerResource: (uri: string) => ContainerResource; |
||||
getAccessRules: (resource: Resource) => AccessRules; |
||||
} |
||||
|
||||
export function useLdo(): UseLdoReturn { |
||||
const { |
||||
dataResourceStore, |
||||
containerResourceStore, |
||||
binaryResourceStore, |
||||
accessRulesStore, |
||||
dataset, |
||||
} = useLdoContext(); |
||||
/** |
||||
* Begins tracking changes to eventually commit |
||||
*/ |
||||
const changeData = useCallback( |
||||
<Type extends LdoBase>(input: Type, ...resources: Resource[]) => { |
||||
// 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; |
||||
}, |
||||
[dataset] |
||||
); |
||||
/** |
||||
* Begins tracking changes to eventually commit for a new subject |
||||
*/ |
||||
const createData = useCallback( |
||||
<Type extends LdoBase>( |
||||
shapeType: ShapeType<Type>, |
||||
subject: string | SubjectType, |
||||
...resources: Resource[] |
||||
) => { |
||||
const linkedDataObject = dataset |
||||
.usingType(shapeType) |
||||
.write(...resources.map((r) => r.uri)) |
||||
.fromSubject(subject); |
||||
startTransaction(linkedDataObject); |
||||
return linkedDataObject; |
||||
}, |
||||
[] |
||||
); |
||||
/** |
||||
* Commits the transaction to the global dataset, syncing all subscribing |
||||
* components and Solid Pods |
||||
*/ |
||||
const commitData = useCallback( |
||||
async (input: LdoBase) => { |
||||
const changes = transactionChanges(input); |
||||
const changesByGraph = splitChangesByGraph( |
||||
changes as DatasetChanges<Quad> |
||||
); |
||||
// Make queries
|
||||
await Promise.all( |
||||
Array.from(changesByGraph.entries()).map( |
||||
async ([graph, datasetChanges]) => { |
||||
if (graph.termType === "DefaultGraph") { |
||||
return; |
||||
} |
||||
const resource = dataResourceStore.get(graph.value); |
||||
await resource.update(datasetChanges); |
||||
} |
||||
) |
||||
); |
||||
}, |
||||
[dataset, fetch] |
||||
); |
||||
// Returns the values
|
||||
return useMemo( |
||||
() => ({ |
||||
dataset, |
||||
changeData, |
||||
createData, |
||||
commitData, |
||||
getDataResource: (uri) => dataResourceStore.get(uri), |
||||
getBinaryResource: (uri) => binaryResourceStore.get(uri), |
||||
getContainerResource: (uri) => containerResourceStore.get(uri), |
||||
getAccessRules: (resource) => accessRulesStore.get(resource), |
||||
}), |
||||
[dataset, changeData, commitData] |
||||
); |
||||
} |
@ -0,0 +1,30 @@ |
||||
import { DatasetChanges } from "o-dataset-pack"; |
||||
import { datasetToString } from "ldo/dist/datasetConverters"; |
||||
import { Quad } from "@rdfjs/types"; |
||||
import { quad as createQuad } from "@rdfjs/data-model"; |
||||
|
||||
// TODO: This file is a clone from the one in the base ldo library. This resused
|
||||
// code should be put into a helper library once everything becomes a monorepo.
|
||||
export async function changesToSparqlUpdate(changes: DatasetChanges<Quad>) { |
||||
let output = ""; |
||||
if (changes.removed) { |
||||
const removedTriples = changes.removed.map((quad) => |
||||
createQuad(quad.subject, quad.predicate, quad.object) |
||||
); |
||||
output += `DELETE DATA { ${await datasetToString(removedTriples, { |
||||
format: "N-Triples", |
||||
})} }`;
|
||||
} |
||||
if (changes.added && changes.removed) { |
||||
output += "; "; |
||||
} |
||||
if (changes.added) { |
||||
const addedTriples = changes.added.map((quad) => |
||||
createQuad(quad.subject, quad.predicate, quad.object) |
||||
); |
||||
output += `INSERT DATA { ${await datasetToString(addedTriples, { |
||||
format: "N-Triples", |
||||
})} }`;
|
||||
} |
||||
return output.replaceAll("\n", " "); |
||||
} |
@ -0,0 +1,33 @@ |
||||
import React, { |
||||
createContext, |
||||
FunctionComponent, |
||||
useContext, |
||||
Context, |
||||
PropsWithChildren, |
||||
} from "react"; |
||||
|
||||
export function createGlobalHook<ReturnValues>(useHook: () => ReturnValues): { |
||||
Provider: FunctionComponent<PropsWithChildren>; |
||||
useGlobal: () => ReturnValues; |
||||
Context: Context<ReturnValues>; |
||||
} { |
||||
// The initial value will be set immediately
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const CreatedContext = createContext<ReturnValues>(undefined); |
||||
|
||||
const Provider: FunctionComponent<PropsWithChildren> = ({ children }) => { |
||||
const returnValues = useHook(); |
||||
return ( |
||||
<CreatedContext.Provider value={returnValues}> |
||||
{children} |
||||
</CreatedContext.Provider> |
||||
); |
||||
}; |
||||
|
||||
const useGlobal = () => { |
||||
return useContext(CreatedContext); |
||||
}; |
||||
|
||||
return { Provider, useGlobal, Context: CreatedContext }; |
||||
} |
@ -0,0 +1,51 @@ |
||||
import { createDataset, DatasetChanges } from "o-dataset-pack"; |
||||
import { GraphType } from "jsonld-dataset-proxy"; |
||||
import { Quad } from "@rdfjs/types"; |
||||
import { defaultGraph, namedNode, quad as createQuad } from "@rdfjs/data-model"; |
||||
|
||||
export function graphNodeToString(graphNode: GraphType): string { |
||||
return graphNode.termType === "DefaultGraph" |
||||
? "defaultGraph()" |
||||
: graphNode.value; |
||||
} |
||||
|
||||
export function stringToGraphNode(input: string): GraphType { |
||||
return input === "defaultGraph()" ? defaultGraph() : namedNode(input); |
||||
} |
||||
|
||||
export function splitChangesByGraph( |
||||
changes: DatasetChanges<Quad> |
||||
): Map<GraphType, DatasetChanges<Quad>> { |
||||
const changesMap: Record<string, DatasetChanges<Quad>> = {}; |
||||
changes.added?.forEach((quad) => { |
||||
const graphHash = graphNodeToString(quad.graph as GraphType); |
||||
if (!changesMap[graphHash]) { |
||||
changesMap[graphHash] = {}; |
||||
} |
||||
if (!changesMap[graphHash].added) { |
||||
changesMap[graphHash].added = createDataset(); |
||||
} |
||||
changesMap[graphHash].added?.add( |
||||
createQuad(quad.subject, quad.predicate, quad.object, quad.graph) |
||||
); |
||||
}); |
||||
|
||||
changes.removed?.forEach((quad) => { |
||||
const graphHash = graphNodeToString(quad.graph as GraphType); |
||||
if (!changesMap[graphHash]) { |
||||
changesMap[graphHash] = {}; |
||||
} |
||||
if (!changesMap[graphHash].removed) { |
||||
changesMap[graphHash].removed = createDataset(); |
||||
} |
||||
changesMap[graphHash].removed?.add( |
||||
createQuad(quad.subject, quad.predicate, quad.object, quad.graph) |
||||
); |
||||
}); |
||||
|
||||
const finalMap = new Map<GraphType, DatasetChanges<Quad>>(); |
||||
Object.entries(changesMap).forEach(([key, value]) => { |
||||
finalMap.set(stringToGraphNode(key), value); |
||||
}); |
||||
return finalMap; |
||||
} |
@ -0,0 +1,6 @@ |
||||
import { useState } from "react"; |
||||
|
||||
export function useForceUpdate() { |
||||
const [, setValue] = useState(0); |
||||
return () => setValue((value) => value + 1); |
||||
} |
@ -0,0 +1,5 @@ |
||||
describe("Trivial", () => { |
||||
it("Trivial", () => { |
||||
expect(true).toBe(true); |
||||
}); |
||||
}); |
@ -0,0 +1,8 @@ |
||||
{ |
||||
"extends": "../../tsconfig.base.json", |
||||
"compilerOptions": { |
||||
"outDir": "./dist", |
||||
"jsx": "react-jsx" |
||||
}, |
||||
"include": ["./src"] |
||||
} |
@ -0,0 +1,21 @@ |
||||
{ |
||||
"compilerOptions": { |
||||
"module": "commonjs", |
||||
"baseUrl": ".", |
||||
"strict": true, |
||||
"declaration": true, |
||||
"esModuleInterop": true, |
||||
"noImplicitAny": false, |
||||
"removeComments": true, |
||||
"noLib": false, |
||||
"emitDecoratorMetadata": true, |
||||
"experimentalDecorators": true, |
||||
"target": "ES2021", |
||||
"sourceMap": true, |
||||
"jsx": "react-jsx", |
||||
}, |
||||
"exclude": [ |
||||
"node_modules", |
||||
"**/*.spec.ts" |
||||
] |
||||
} |
@ -1,22 +1,8 @@ |
||||
{ |
||||
"extends": "./tsconfig.base.json", |
||||
"compilerOptions": { |
||||
"module": "commonjs", |
||||
"strict": true, |
||||
"declaration": true, |
||||
"esModuleInterop": true, |
||||
"noImplicitAny": false, |
||||
"removeComments": true, |
||||
"noLib": false, |
||||
"emitDecoratorMetadata": true, |
||||
"experimentalDecorators": true, |
||||
"target": "es6", |
||||
"sourceMap": true, |
||||
"lib": [ |
||||
"es6" |
||||
] |
||||
}, |
||||
"exclude": [ |
||||
"node_modules", |
||||
"**/*.spec.ts" |
||||
] |
||||
"paths": { |
||||
"@ldo/*": ["packages/*/src"] |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue