commit
c30aa1b94d
@ -1,8 +1,8 @@ |
||||
import { ContextDefinition } from "jsonld"; |
||||
import { LdoJsonldContext } from "@ldo/jsonld-dataset-proxy"; |
||||
|
||||
/** |
||||
* ============================================================================= |
||||
* <%- fileName %>Context: JSONLD Context for <%- fileName %> |
||||
* ============================================================================= |
||||
*/ |
||||
export const <%- fileName %>Context: ContextDefinition = <%- context %>; |
||||
export const <%- fileName %>Context: LdoJsonldContext = <%- context %>; |
||||
|
@ -0,0 +1,52 @@ |
||||
import { useLdo } from "./SolidLdoProvider"; |
||||
import { useEffect, useRef } from "react"; |
||||
|
||||
export function useSubscribeToResource(...uris: string[]): void { |
||||
const { dataset } = useLdo(); |
||||
const currentlySubscribed = useRef<Record<string, string>>({}); |
||||
useEffect(() => { |
||||
const resources = uris.map((uri) => dataset.getResource(uri)); |
||||
const previousSubscriptions = { ...currentlySubscribed.current }; |
||||
Promise.all<void>( |
||||
resources.map(async (resource) => { |
||||
if (!previousSubscriptions[resource.uri]) { |
||||
// Prevent multiple triggers from created subscriptions while waiting
|
||||
// for connection
|
||||
currentlySubscribed.current[resource.uri] = "AWAITING"; |
||||
// Read and subscribe
|
||||
await resource.readIfUnfetched(); |
||||
currentlySubscribed.current[resource.uri] = |
||||
await resource.subscribeToNotifications(); |
||||
} else { |
||||
delete previousSubscriptions[resource.uri]; |
||||
} |
||||
}), |
||||
).then(async () => { |
||||
// Unsubscribe from all remaining previous subscriptions
|
||||
await Promise.all( |
||||
Object.entries(previousSubscriptions).map( |
||||
async ([resourceUri, subscriptionId]) => { |
||||
// Unsubscribe
|
||||
delete currentlySubscribed.current[resourceUri]; |
||||
const resource = dataset.getResource(resourceUri); |
||||
await resource.unsubscribeFromNotifications(subscriptionId); |
||||
}, |
||||
), |
||||
); |
||||
}); |
||||
}, [uris]); |
||||
|
||||
// Cleanup Subscriptions
|
||||
useEffect(() => { |
||||
return () => { |
||||
Promise.all( |
||||
Object.entries(currentlySubscribed.current).map( |
||||
async ([resourceUri, subscriptionId]) => { |
||||
const resource = dataset.getResource(resourceUri); |
||||
await resource.unsubscribeFromNotifications(subscriptionId); |
||||
}, |
||||
), |
||||
); |
||||
}; |
||||
}, []); |
||||
} |
@ -0,0 +1,3 @@ |
||||
{ |
||||
"extends": ["../../.eslintrc"] |
||||
} |
@ -0,0 +1 @@ |
||||
test/test-server/data |
@ -0,0 +1,21 @@ |
||||
MIT License |
||||
|
||||
Copyright (c) 2023 Jackson Morgan |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
of this software and associated documentation files (the "Software"), to deal |
||||
in the Software without restriction, including without limitation the rights |
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
copies of the Software, and to permit persons to whom the Software is |
||||
furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in all |
||||
copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
SOFTWARE. |
@ -0,0 +1,49 @@ |
||||
# @ldo/solid-type-index |
||||
|
||||
A library to handle [type indexes](https://solid.github.io/type-indexes/index.html) with [LDO](https://ldo.js.org). |
||||
|
||||
## Installation |
||||
|
||||
``` |
||||
npm i @ldo/solid-type-index @ldo/solid |
||||
``` |
||||
|
||||
## Usage |
||||
|
||||
|
||||
```typescript |
||||
import { initTypeIndex } from "@ldo/solid-type-index"; |
||||
import { createSolidLdoDataset } from "@ldo/solid"; |
||||
|
||||
async function main() { |
||||
const myWebId = "https://example.com/profile/card#me"; |
||||
const solidLdoDataset = createSolidLodDataset(); |
||||
|
||||
// Initialize a type index for a webId in case it isn't initialized |
||||
await initTypeIndex(myWebId, { solidLdoDataset }); |
||||
|
||||
// Get Type Registrations |
||||
const typeRegistrations = await getTypeRegistrations(WEB_ID, { |
||||
solidLdoDataset, |
||||
}); |
||||
|
||||
// Get Instance Uris (the URIs for resources that contain an instance of a |
||||
// class) |
||||
const bookmarkUris: string[] = await getInstanceUris( |
||||
"http://www.w3.org/2002/01/bookmark#Bookmark", |
||||
typeRegistrations, |
||||
{ solidLdoDataset } |
||||
); |
||||
|
||||
} |
||||
main(); |
||||
``` |
||||
|
||||
## Sponsorship |
||||
This project was made possible by a grant from NGI Zero Entrust via nlnet. Learn more on the [NLnet project page](https://nlnet.nl/project/SolidUsableApps/). |
||||
|
||||
[<img src="https://nlnet.nl/logo/banner.png" alt="nlnet foundation logo" width="300" />](https://nlnet.nl/) |
||||
[<img src="https://nlnet.nl/image/logos/NGI0Entrust_tag.svg" alt="NGI Zero Entrust Logo" width="300" />](https://nlnet.nl/) |
||||
|
||||
## Liscense |
||||
MIT |
@ -0,0 +1,6 @@ |
||||
const sharedConfig = require("../../jest.config.js"); |
||||
module.exports = { |
||||
...sharedConfig, |
||||
rootDir: "./", |
||||
testEnvironment: "jsdom", |
||||
}; |
@ -0,0 +1,2 @@ |
||||
import "@inrupt/jest-jsdom-polyfills"; |
||||
globalThis.fetch = async () => new Response(); |
@ -0,0 +1,52 @@ |
||||
{ |
||||
"name": "@ldo/solid-type-index", |
||||
"version": "0.0.1-alpha.28", |
||||
"description": "Solid Type Index support for LDO", |
||||
"main": "dist/index.js", |
||||
"scripts": { |
||||
"build": "tsc --project tsconfig.build.json", |
||||
"watch": "tsc --watch", |
||||
"test": "npm run test:integration", |
||||
"test:watch": "jest --watch", |
||||
"prepublishOnly": "npm run test && npm run build", |
||||
"build:ldo": "ldo build --input src/.shapes --output src/.ldo", |
||||
"lint": "eslint src/** --fix --no-error-on-unmatched-pattern", |
||||
"test:integration": "start-server-and-test start-test-server http://localhost:3003 start-integration-test", |
||||
"start-test-server": "ts-node ./test/test-server/runServer.ts", |
||||
"start-integration-test": "jest --coverage" |
||||
}, |
||||
"repository": { |
||||
"type": "git", |
||||
"url": "git+https://github.com/o-development/ldobjects.git" |
||||
}, |
||||
"author": "Jackson Morgan", |
||||
"license": "MIT", |
||||
"bugs": { |
||||
"url": "https://github.com/o-development/ldobjects/issues" |
||||
}, |
||||
"homepage": "https://github.com/o-development/ldobjects/tree/main/packages/solid-react#readme", |
||||
"devDependencies": { |
||||
"@ldo/rdf-utils": "^0.0.1-alpha.24", |
||||
"@rdfjs/types": "^1.0.1", |
||||
"@testing-library/react": "^14.1.2", |
||||
"@types/jest": "^27.0.3", |
||||
"@types/uuid": "^10.0.0", |
||||
"jest-environment-jsdom": "^27.0.0", |
||||
"start-server-and-test": "^2.0.3", |
||||
"ts-jest": "^27.1.2", |
||||
"ts-node": "^10.9.2" |
||||
}, |
||||
"dependencies": { |
||||
"@ldo/solid": "^0.0.1-alpha.28", |
||||
"@ldo/solid-react": "^0.0.1-alpha.28", |
||||
"uuid": "^11.0.5" |
||||
}, |
||||
"files": [ |
||||
"dist", |
||||
"src" |
||||
], |
||||
"publishConfig": { |
||||
"access": "public" |
||||
}, |
||||
"gitHead": "c63f055aab22155b60a5fdee4172979b9c287dfa" |
||||
} |
@ -0,0 +1,19 @@ |
||||
import { LdoJsonldContext } from "@ldo/jsonld-dataset-proxy"; |
||||
|
||||
/** |
||||
* ============================================================================= |
||||
* profileContext: JSONLD Context for profile |
||||
* ============================================================================= |
||||
*/ |
||||
export const profileContext: LdoJsonldContext = { |
||||
privateTypeIndex: { |
||||
"@id": "http://www.w3.org/ns/solid/terms#privateTypeIndex", |
||||
"@type": "@id", |
||||
"@isCollection": true, |
||||
}, |
||||
publicTypeIndex: { |
||||
"@id": "http://www.w3.org/ns/solid/terms#publicTypeIndex", |
||||
"@type": "@id", |
||||
"@isCollection": true, |
||||
}, |
||||
}; |
@ -0,0 +1,64 @@ |
||||
import { Schema } from "shexj"; |
||||
|
||||
/** |
||||
* ============================================================================= |
||||
* profileSchema: ShexJ Schema for profile |
||||
* ============================================================================= |
||||
*/ |
||||
export const profileSchema: Schema = { |
||||
type: "Schema", |
||||
shapes: [ |
||||
{ |
||||
id: "https://shaperepo.com/schemas/solidProfile#TypeIndexProfile", |
||||
type: "ShapeDecl", |
||||
shapeExpr: { |
||||
type: "Shape", |
||||
expression: { |
||||
type: "EachOf", |
||||
expressions: [ |
||||
{ |
||||
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)", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
}, |
||||
}, |
||||
}, |
||||
], |
||||
}; |
@ -0,0 +1,19 @@ |
||||
import { ShapeType } from "@ldo/ldo"; |
||||
import { profileSchema } from "./profile.schema"; |
||||
import { profileContext } from "./profile.context"; |
||||
import { TypeIndexProfile } from "./profile.typings"; |
||||
|
||||
/** |
||||
* ============================================================================= |
||||
* LDO ShapeTypes profile |
||||
* ============================================================================= |
||||
*/ |
||||
|
||||
/** |
||||
* TypeIndexProfile ShapeType |
||||
*/ |
||||
export const TypeIndexProfileShapeType: ShapeType<TypeIndexProfile> = { |
||||
schema: profileSchema, |
||||
shape: "https://shaperepo.com/schemas/solidProfile#TypeIndexProfile", |
||||
context: profileContext, |
||||
}; |
@ -0,0 +1,27 @@ |
||||
import { ContextDefinition } from "jsonld"; |
||||
|
||||
/** |
||||
* ============================================================================= |
||||
* Typescript Typings for profile |
||||
* ============================================================================= |
||||
*/ |
||||
|
||||
/** |
||||
* TypeIndexProfile Type |
||||
*/ |
||||
export interface TypeIndexProfile { |
||||
"@id"?: string; |
||||
"@context"?: ContextDefinition; |
||||
/** |
||||
* 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; |
||||
}[]; |
||||
} |
@ -0,0 +1,50 @@ |
||||
import { LdoJsonldContext } from "@ldo/jsonld-dataset-proxy"; |
||||
|
||||
/** |
||||
* ============================================================================= |
||||
* typeIndexContext: JSONLD Context for typeIndex |
||||
* ============================================================================= |
||||
*/ |
||||
export const typeIndexContext: LdoJsonldContext = { |
||||
type: { |
||||
"@id": "@type" |
||||
}, |
||||
TypeIndex: { |
||||
"@id": "http://www.w3.org/ns/solid/terms#TypeIndex", |
||||
"@context": { |
||||
type: { |
||||
"@id": "@type", |
||||
}, |
||||
}, |
||||
}, |
||||
ListedDocument: { |
||||
"@id": "http://www.w3.org/ns/solid/terms#ListedDocument", |
||||
"@context": { |
||||
type: { |
||||
"@id": "@type", |
||||
}, |
||||
}, |
||||
}, |
||||
TypeRegistration: { |
||||
"@id": "http://www.w3.org/ns/solid/terms#TypeRegistration", |
||||
"@context": { |
||||
type: { |
||||
"@id": "@type", |
||||
}, |
||||
forClass: { |
||||
"@id": "http://www.w3.org/ns/solid/terms#forClass", |
||||
"@type": "@id", |
||||
}, |
||||
instance: { |
||||
"@id": "http://www.w3.org/ns/solid/terms#instance", |
||||
"@type": "@id", |
||||
"@isCollection": true, |
||||
}, |
||||
instanceContainer: { |
||||
"@id": "http://www.w3.org/ns/solid/terms#instanceContainer", |
||||
"@type": "@id", |
||||
"@isCollection": true, |
||||
}, |
||||
}, |
||||
}, |
||||
}; |
@ -0,0 +1,144 @@ |
||||
import { Schema } from "shexj"; |
||||
|
||||
/** |
||||
* ============================================================================= |
||||
* typeIndexSchema: ShexJ Schema for typeIndex |
||||
* ============================================================================= |
||||
*/ |
||||
export const typeIndexSchema: Schema = { |
||||
type: "Schema", |
||||
shapes: [ |
||||
{ |
||||
id: "https://shaperepo.com/schemas/solidProfile#TypeIndexDocument", |
||||
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/ns/solid/terms#TypeIndex"], |
||||
}, |
||||
annotations: [ |
||||
{ |
||||
type: "Annotation", |
||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||
object: { |
||||
value: "Defines the node as a TypeIndex", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", |
||||
valueExpr: { |
||||
type: "NodeConstraint", |
||||
values: ["http://www.w3.org/ns/solid/terms#ListedDocument"], |
||||
}, |
||||
annotations: [ |
||||
{ |
||||
type: "Annotation", |
||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||
object: { |
||||
value: "Defines the node as a Listed Document", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
}, |
||||
extra: ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"], |
||||
}, |
||||
}, |
||||
{ |
||||
id: "https://shaperepo.com/schemas/solidProfile#TypeRegistration", |
||||
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/ns/solid/terms#TypeRegistration"], |
||||
}, |
||||
annotations: [ |
||||
{ |
||||
type: "Annotation", |
||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||
object: { |
||||
value: "Defines this node as a Type Registration", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://www.w3.org/ns/solid/terms#forClass", |
||||
valueExpr: { |
||||
type: "NodeConstraint", |
||||
nodeKind: "iri", |
||||
}, |
||||
annotations: [ |
||||
{ |
||||
type: "Annotation", |
||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||
object: { |
||||
value: "The class of object at this type.", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://www.w3.org/ns/solid/terms#instance", |
||||
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 specific resource that contains the class.", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://www.w3.org/ns/solid/terms#instanceContainer", |
||||
valueExpr: { |
||||
type: "NodeConstraint", |
||||
nodeKind: "iri", |
||||
}, |
||||
min: 0, |
||||
max: -1, |
||||
annotations: [ |
||||
{ |
||||
type: "Annotation", |
||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
||||
object: { |
||||
value: "Containers that contain resources with the class.", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
}, |
||||
extra: ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"], |
||||
}, |
||||
}, |
||||
], |
||||
}; |
@ -0,0 +1,28 @@ |
||||
import { ShapeType } from "@ldo/ldo"; |
||||
import { typeIndexSchema } from "./typeIndex.schema"; |
||||
import { typeIndexContext } from "./typeIndex.context"; |
||||
import { TypeIndexDocument, TypeRegistration } from "./typeIndex.typings"; |
||||
|
||||
/** |
||||
* ============================================================================= |
||||
* LDO ShapeTypes typeIndex |
||||
* ============================================================================= |
||||
*/ |
||||
|
||||
/** |
||||
* TypeIndexDocument ShapeType |
||||
*/ |
||||
export const TypeIndexDocumentShapeType: ShapeType<TypeIndexDocument> = { |
||||
schema: typeIndexSchema, |
||||
shape: "https://shaperepo.com/schemas/solidProfile#TypeIndexDocument", |
||||
context: typeIndexContext, |
||||
}; |
||||
|
||||
/** |
||||
* TypeRegistration ShapeType |
||||
*/ |
||||
export const TypeRegistrationShapeType: ShapeType<TypeRegistration> = { |
||||
schema: typeIndexSchema, |
||||
shape: "https://shaperepo.com/schemas/solidProfile#TypeRegistration", |
||||
context: typeIndexContext, |
||||
}; |
@ -0,0 +1,58 @@ |
||||
import { ContextDefinition } from "jsonld"; |
||||
|
||||
/** |
||||
* ============================================================================= |
||||
* Typescript Typings for typeIndex |
||||
* ============================================================================= |
||||
*/ |
||||
|
||||
/** |
||||
* TypeIndexDocument Type |
||||
*/ |
||||
export interface TypeIndexDocument { |
||||
"@id"?: string; |
||||
"@context"?: ContextDefinition; |
||||
/** |
||||
* Defines the node as a TypeIndex | Defines the node as a Listed Document |
||||
*/ |
||||
type: ( |
||||
| { |
||||
"@id": "TypeIndex"; |
||||
} |
||||
| { |
||||
"@id": "ListedDocument"; |
||||
} |
||||
)[]; |
||||
} |
||||
|
||||
/** |
||||
* TypeRegistration Type |
||||
*/ |
||||
export interface TypeRegistration { |
||||
"@id"?: string; |
||||
"@context"?: ContextDefinition; |
||||
/** |
||||
* Defines this node as a Type Registration |
||||
*/ |
||||
type: { |
||||
"@id": "TypeRegistration"; |
||||
}; |
||||
/** |
||||
* The class of object at this type. |
||||
*/ |
||||
forClass: { |
||||
"@id": string; |
||||
}; |
||||
/** |
||||
* A specific resource that contains the class. |
||||
*/ |
||||
instance?: { |
||||
"@id": string; |
||||
}[]; |
||||
/** |
||||
* Containers that contain resources with the class. |
||||
*/ |
||||
instanceContainer?: { |
||||
"@id": string; |
||||
}[]; |
||||
} |
@ -0,0 +1,10 @@ |
||||
PREFIX srs: <https://shaperepo.com/schemas/solidProfile#> |
||||
PREFIX solid: <http://www.w3.org/ns/solid/terms#> |
||||
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> |
||||
|
||||
srs:TypeIndexProfile { |
||||
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)" ; |
||||
} |
@ -0,0 +1,23 @@ |
||||
PREFIX srs: <https://shaperepo.com/schemas/solidProfile#> |
||||
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> |
||||
PREFIX solid: <http://www.w3.org/ns/solid/terms#> |
||||
PREFIX vcard: <http://www.w3.org/2006/vcard/ns#> |
||||
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> |
||||
|
||||
srs:TypeIndexDocument EXTRA a { |
||||
a [ solid:TypeIndex ] |
||||
// rdfs:comment "Defines the node as a TypeIndex" ; |
||||
a [ solid:ListedDocument ] |
||||
// rdfs:comment "Defines the node as a Listed Document" ; |
||||
} |
||||
|
||||
srs:TypeRegistration EXTRA a { |
||||
a [ solid:TypeRegistration ] |
||||
// rdfs:comment "Defines this node as a Type Registration" ; |
||||
solid:forClass IRI |
||||
// rdfs:comment "The class of object at this type." ; |
||||
solid:instance IRI * |
||||
// rdfs:comment "A specific resource that contains the class." ; |
||||
solid:instanceContainer IRI * |
||||
// rdfs:comment "Containers that contain resources with the class." ; |
||||
} |
@ -0,0 +1,7 @@ |
||||
export const RDF_TYPE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"; |
||||
export const TYPE_REGISTRATION = |
||||
"http://www.w3.org/ns/solid/terms#TypeRegistration"; |
||||
export const FOR_CLASS = "http://www.w3.org/ns/solid/terms#forClass"; |
||||
export const INSTANCE = "http://www.w3.org/ns/solid/terms#instance"; |
||||
export const INSTANCE_CONTAINER = |
||||
"http://www.w3.org/ns/solid/terms#instanceContainer"; |
@ -0,0 +1,92 @@ |
||||
import type { ContainerUri, LeafUri } from "@ldo/solid"; |
||||
import type { TypeRegistration } from "./.ldo/typeIndex.typings"; |
||||
import type { TypeIndexProfile } from "./.ldo/profile.typings"; |
||||
import { TypeIndexProfileShapeType } from "./.ldo/profile.shapeTypes"; |
||||
import { TypeRegistrationShapeType } from "./.ldo/typeIndex.shapeTypes"; |
||||
import { RDF_TYPE, TYPE_REGISTRATION } from "./constants"; |
||||
import type { Options } from "./util/Options"; |
||||
import { guaranteeOptions } from "./util/Options"; |
||||
|
||||
export async function getTypeRegistrations( |
||||
webId: string, |
||||
options?: Options, |
||||
): Promise<TypeRegistration[]> { |
||||
const { dataset } = guaranteeOptions(options); |
||||
|
||||
// Get Profile
|
||||
const profile = await getProfile(webId, options); |
||||
|
||||
// Get Type Indexes
|
||||
const typeIndexUris = getTypeIndexesUrisFromProfile(profile); |
||||
|
||||
// Fetch the type Indexes
|
||||
await Promise.all( |
||||
typeIndexUris.map(async (typeIndexUri) => { |
||||
const typeIndexResource = dataset.getResource(typeIndexUri); |
||||
const readResult = await typeIndexResource.readIfUnfetched(); |
||||
if (readResult.isError) throw readResult; |
||||
}), |
||||
); |
||||
|
||||
// Get Type Registrations
|
||||
return dataset |
||||
.usingType(TypeRegistrationShapeType) |
||||
.matchSubject(RDF_TYPE, TYPE_REGISTRATION); |
||||
} |
||||
|
||||
export async function getProfile( |
||||
webId: string, |
||||
options?: Options, |
||||
): Promise<TypeIndexProfile> { |
||||
const { dataset } = guaranteeOptions(options); |
||||
const profileResource = dataset.getResource(webId); |
||||
const readResult = await profileResource.readIfUnfetched(); |
||||
if (readResult.isError) throw readResult; |
||||
return dataset.usingType(TypeIndexProfileShapeType).fromSubject(webId); |
||||
} |
||||
|
||||
export function getTypeIndexesUrisFromProfile( |
||||
profile: TypeIndexProfile, |
||||
): LeafUri[] { |
||||
const uris: LeafUri[] = []; |
||||
profile.privateTypeIndex?.forEach((indexNode) => { |
||||
uris.push(indexNode["@id"] as LeafUri); |
||||
}); |
||||
profile.publicTypeIndex?.forEach((indexNode) => { |
||||
uris.push(indexNode["@id"] as LeafUri); |
||||
}); |
||||
return uris; |
||||
} |
||||
|
||||
export async function getInstanceUris( |
||||
classUri: string, |
||||
typeRegistrations: TypeRegistration[], |
||||
options?: Options, |
||||
): Promise<LeafUri[]> { |
||||
const { dataset } = guaranteeOptions(options); |
||||
|
||||
const leafUris = new Set<LeafUri>(); |
||||
await Promise.all( |
||||
typeRegistrations.map(async (registration) => { |
||||
if (registration.forClass["@id"] === classUri) { |
||||
// Individual registrations
|
||||
registration.instance?.forEach((instance) => |
||||
leafUris.add(instance["@id"] as LeafUri), |
||||
); |
||||
// Container registrations
|
||||
await Promise.all( |
||||
registration.instanceContainer?.map(async (instanceContainer) => { |
||||
const containerResource = dataset.getResource( |
||||
instanceContainer["@id"] as ContainerUri, |
||||
); |
||||
await containerResource.readIfUnfetched(); |
||||
containerResource.children().forEach((child) => { |
||||
if (child.type === "leaf") leafUris.add(child.uri); |
||||
}); |
||||
}) ?? [], |
||||
); |
||||
} |
||||
}), |
||||
); |
||||
return Array.from(leafUris); |
||||
} |
@ -0,0 +1,4 @@ |
||||
export * from "./getTypeIndex"; |
||||
export * from "./setTypeIndex"; |
||||
export * from "./react/useInstanceUris"; |
||||
export * from "./react/useTypeIndexProfile"; |
@ -0,0 +1,45 @@ |
||||
import type { LeafUri } from "@ldo/solid"; |
||||
import { useTypeIndexProfile } from "./useTypeIndexProfile"; |
||||
import { useEffect, useMemo, useState } from "react"; |
||||
import { useSubscribeToUris } from "./util/useSubscribeToUris"; |
||||
import { useLdo, useMatchSubject } from "@ldo/solid-react"; |
||||
import { TypeRegistrationShapeType } from "../.ldo/typeIndex.shapeTypes"; |
||||
import { RDF_TYPE, TYPE_REGISTRATION } from "../constants"; |
||||
import { |
||||
getInstanceUris, |
||||
getTypeIndexesUrisFromProfile, |
||||
} from "../getTypeIndex"; |
||||
|
||||
/** |
||||
* Provides the LeafUris of everything in a type node for a specific class uri |
||||
* |
||||
* @param classUri - the class uri |
||||
* @returns - URIs of all resources registered with this node |
||||
*/ |
||||
export function useInstanceUris(classUri: string): LeafUri[] { |
||||
const { dataset } = useLdo(); |
||||
const profile = useTypeIndexProfile(); |
||||
|
||||
const typeIndexUris: string[] = useMemo( |
||||
() => (profile ? getTypeIndexesUrisFromProfile(profile) : []), |
||||
[profile], |
||||
); |
||||
|
||||
useSubscribeToUris(typeIndexUris); |
||||
|
||||
const [leafUris, setLeafUris] = useState<LeafUri[]>([]); |
||||
|
||||
const typeRegistrations = useMatchSubject( |
||||
TypeRegistrationShapeType, |
||||
RDF_TYPE, |
||||
TYPE_REGISTRATION, |
||||
); |
||||
|
||||
useEffect(() => { |
||||
getInstanceUris(classUri, typeRegistrations, { |
||||
solidLdoDataset: dataset, |
||||
}).then(setLeafUris); |
||||
}, [typeRegistrations]); |
||||
|
||||
return leafUris; |
||||
} |
@ -0,0 +1,10 @@ |
||||
import { useResource, useSolidAuth, useSubject } from "@ldo/solid-react"; |
||||
import type { TypeIndexProfile } from "../.ldo/profile.typings"; |
||||
import { TypeIndexProfileShapeType } from "../.ldo/profile.shapeTypes"; |
||||
|
||||
export function useTypeIndexProfile(): TypeIndexProfile | undefined { |
||||
const { session } = useSolidAuth(); |
||||
useResource(session.webId, { subscribe: true }); |
||||
const profile = useSubject(TypeIndexProfileShapeType, session.webId); |
||||
return profile; |
||||
} |
@ -0,0 +1,35 @@ |
||||
import { useLdo } from "@ldo/solid-react"; |
||||
import { useEffect, useRef } from "react"; |
||||
|
||||
export function useSubscribeToUris(uris: string[]) { |
||||
const { dataset } = useLdo(); |
||||
const currentlySubscribed = useRef<Record<string, string>>({}); |
||||
useEffect(() => { |
||||
const resources = uris.map((uri) => dataset.getResource(uri)); |
||||
const previousSubscriptions = { ...currentlySubscribed.current }; |
||||
Promise.all<void>( |
||||
resources.map(async (resource) => { |
||||
if (!previousSubscriptions[resource.uri]) { |
||||
// Read and subscribe
|
||||
await resource.readIfUnfetched(); |
||||
currentlySubscribed.current[resource.uri] = |
||||
await resource.subscribeToNotifications(); |
||||
} else { |
||||
delete previousSubscriptions[resource.uri]; |
||||
} |
||||
}), |
||||
).then(async () => { |
||||
// Unsubscribe from all remaining previous subscriptions
|
||||
await Promise.all( |
||||
Object.entries(previousSubscriptions).map( |
||||
async ([resourceUri, subscriptionId]) => { |
||||
// Unsubscribe
|
||||
delete currentlySubscribed.current[resourceUri]; |
||||
const resource = dataset.getResource(resourceUri); |
||||
await resource.unsubscribeFromNotifications(subscriptionId); |
||||
}, |
||||
), |
||||
); |
||||
}); |
||||
}, [uris]); |
||||
} |
@ -0,0 +1,207 @@ |
||||
import { v4 } from "uuid"; |
||||
import { |
||||
TypeIndexDocumentShapeType, |
||||
TypeRegistrationShapeType, |
||||
} from "./.ldo/typeIndex.shapeTypes"; |
||||
import { FOR_CLASS, RDF_TYPE, TYPE_REGISTRATION } from "./constants"; |
||||
import { guaranteeOptions, type Options } from "./util/Options"; |
||||
import { namedNode, quad } from "@rdfjs/data-model"; |
||||
import type { TypeRegistration } from "./.ldo/typeIndex.typings"; |
||||
import { getProfile } from "./getTypeIndex"; |
||||
import { TypeIndexProfileShapeType } from "./.ldo/profile.shapeTypes"; |
||||
import type { Container } from "@ldo/solid"; |
||||
import type { ISolidLdoDataset } from "@ldo/solid"; |
||||
import type { NamedNode } from "@rdfjs/types"; |
||||
|
||||
/** |
||||
* ============================================================================= |
||||
* INITIALIZERS |
||||
* ============================================================================= |
||||
*/ |
||||
export async function initTypeIndex( |
||||
webId: string, |
||||
options?: Options, |
||||
): Promise<void> { |
||||
const { dataset } = guaranteeOptions(options); |
||||
const profile = await getProfile(webId, options); |
||||
if (!profile.privateTypeIndex?.length || !profile.publicTypeIndex?.length) { |
||||
const profileFolder = await dataset.getResource(webId).getParentContainer(); |
||||
if (profileFolder?.isError) throw profileFolder; |
||||
if (!profileFolder) |
||||
throw new Error("No folder to save the type indexes to."); |
||||
if (!profile.privateTypeIndex?.length) { |
||||
await createIndex(webId, profileFolder, dataset, true); |
||||
} |
||||
if (!profile.publicTypeIndex?.length) { |
||||
await createIndex(webId, profileFolder, dataset, false); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @internal |
||||
* @param webId |
||||
* @param profileFolder |
||||
* @param dataset |
||||
*/ |
||||
export async function createIndex( |
||||
webId, |
||||
profileFolder: Container, |
||||
dataset: ISolidLdoDataset, |
||||
isPrivate: boolean, |
||||
) { |
||||
// Create a private type index
|
||||
const createResult = await profileFolder.createChildAndOverwrite( |
||||
`${isPrivate ? "private" : "public"}_index_${v4()}`, |
||||
); |
||||
if (createResult.isError) throw createResult; |
||||
const indexResource = createResult.resource; |
||||
const wacResult = await indexResource.setWac({ |
||||
agent: { |
||||
[webId]: { read: true, write: true, append: true, control: true }, |
||||
}, |
||||
public: { |
||||
read: isPrivate ? false : true, |
||||
write: true, |
||||
append: true, |
||||
control: true, |
||||
}, |
||||
authenticated: { |
||||
read: isPrivate ? false : true, |
||||
write: true, |
||||
append: true, |
||||
control: true, |
||||
}, |
||||
}); |
||||
if (wacResult.isError) throw wacResult; |
||||
const transaction = dataset.startTransaction(); |
||||
const cProfile = transaction |
||||
.usingType(TypeIndexProfileShapeType) |
||||
.write(dataset.getResource(webId).uri) |
||||
.fromSubject(webId); |
||||
if (isPrivate) { |
||||
cProfile.privateTypeIndex?.push({ "@id": indexResource.uri }); |
||||
} else { |
||||
cProfile.publicTypeIndex?.push({ "@id": indexResource.uri }); |
||||
} |
||||
const cTypeIndex = transaction |
||||
.usingType(TypeIndexDocumentShapeType) |
||||
.write(indexResource.uri) |
||||
.fromSubject(indexResource.uri); |
||||
|
||||
cTypeIndex.type = [{ "@id": "ListedDocument" }, { "@id": "TypeIndex" }]; |
||||
const commitResult = await transaction.commitToPod(); |
||||
if (commitResult.isError) throw commitResult; |
||||
} |
||||
|
||||
/** |
||||
* ============================================================================= |
||||
* DATASET MODIFIERS |
||||
* ============================================================================= |
||||
*/ |
||||
interface Instances { |
||||
instance?: string[]; |
||||
instanceContainer?: string[]; |
||||
} |
||||
|
||||
export function addRegistration( |
||||
indexUri: string, |
||||
classUri: string, |
||||
instances: Instances, |
||||
options?: Options, |
||||
): void { |
||||
// Check to see if its already in the index
|
||||
const typeRegistration = findAppropriateTypeRegistration( |
||||
indexUri, |
||||
classUri, |
||||
options, |
||||
); |
||||
|
||||
// Add instances to type registration
|
||||
instances.instance?.forEach((instance) => { |
||||
typeRegistration.instance?.push({ "@id": instance }); |
||||
}); |
||||
instances.instanceContainer?.forEach((instanceContainer) => { |
||||
typeRegistration.instanceContainer?.push({ "@id": instanceContainer }); |
||||
}); |
||||
} |
||||
|
||||
export async function removeRegistration( |
||||
indexUri: string, |
||||
classUri: string, |
||||
instances: Instances, |
||||
options?: Options, |
||||
) { |
||||
// Check to see if its already in the index
|
||||
const typeRegistration = findAppropriateTypeRegistration( |
||||
indexUri, |
||||
classUri, |
||||
options, |
||||
); |
||||
|
||||
console.log(typeRegistration["@id"]); |
||||
|
||||
// Add instances to type registration
|
||||
instances.instance?.forEach((instance) => { |
||||
typeRegistration.instance?.splice( |
||||
typeRegistration.instance.findIndex((val) => val["@id"] === instance), |
||||
1, |
||||
); |
||||
}); |
||||
instances.instanceContainer?.forEach((instanceContainer) => { |
||||
console.log("Splicing instanceContainers", instanceContainer); |
||||
typeRegistration.instanceContainer?.splice( |
||||
typeRegistration.instanceContainer.findIndex( |
||||
(val) => val["@id"] === instanceContainer, |
||||
), |
||||
1, |
||||
); |
||||
}); |
||||
} |
||||
|
||||
export function findAppropriateTypeRegistration( |
||||
indexUri: string, |
||||
classUri: string, |
||||
options?: Options, |
||||
) { |
||||
const { dataset } = guaranteeOptions(options); |
||||
// Check to see if its already in the index
|
||||
const existingRegistrationsUris: NamedNode[] = dataset |
||||
.match( |
||||
null, |
||||
namedNode(RDF_TYPE), |
||||
namedNode(TYPE_REGISTRATION), |
||||
namedNode(indexUri), |
||||
) |
||||
.toArray() |
||||
.map((quad) => quad.subject) as NamedNode[]; |
||||
|
||||
const existingRegistrationForClassUri = existingRegistrationsUris.find( |
||||
(registrationUri) => { |
||||
return dataset.has( |
||||
quad( |
||||
registrationUri, |
||||
namedNode(FOR_CLASS), |
||||
namedNode(classUri), |
||||
namedNode(indexUri), |
||||
), |
||||
); |
||||
}, |
||||
)?.value; |
||||
|
||||
let typeRegistration: TypeRegistration; |
||||
if (existingRegistrationForClassUri) { |
||||
typeRegistration = dataset |
||||
.usingType(TypeRegistrationShapeType) |
||||
.write(indexUri) |
||||
.fromSubject(existingRegistrationForClassUri); |
||||
} else { |
||||
typeRegistration = dataset |
||||
.usingType(TypeRegistrationShapeType) |
||||
.write(indexUri) |
||||
.fromSubject(`${indexUri}#${v4()}`); |
||||
typeRegistration.type = { "@id": "TypeRegistration" }; |
||||
typeRegistration.forClass = { "@id": classUri }; |
||||
} |
||||
return typeRegistration; |
||||
} |
@ -0,0 +1,14 @@ |
||||
import { createSolidLdoDataset } from "@ldo/solid"; |
||||
import type { ISolidLdoDataset } from "@ldo/solid"; |
||||
import { guaranteeFetch } from "@ldo/solid/dist/util/guaranteeFetch"; |
||||
|
||||
export interface Options { |
||||
solidLdoDataset?: ISolidLdoDataset; |
||||
fetch?: typeof fetch; |
||||
} |
||||
|
||||
export function guaranteeOptions(options?: Options) { |
||||
const fetch = guaranteeFetch(options?.fetch); |
||||
const dataset = options?.solidLdoDataset ?? createSolidLdoDataset({ fetch }); |
||||
return { fetch, dataset }; |
||||
} |
@ -0,0 +1,138 @@ |
||||
import { createSolidLdoDataset } from "@ldo/solid"; |
||||
import { |
||||
MY_BOOKMARKS_1_URI, |
||||
MY_BOOKMARKS_2_URI, |
||||
PRIVATE_TYPE_INDEX_URI, |
||||
PUBLIC_TYPE_INDEX_URI, |
||||
ROOT_CONTAINER, |
||||
setupEmptyTypeIndex, |
||||
setupFullTypeIndex, |
||||
setUpServer, |
||||
WEB_ID, |
||||
} from "./setUpServer"; |
||||
import { getInstanceUris, getTypeRegistrations } from "../src/getTypeIndex"; |
||||
import { |
||||
addRegistration, |
||||
initTypeIndex, |
||||
removeRegistration, |
||||
} from "../src/setTypeIndex"; |
||||
import { TypeIndexProfileShapeType } from "../src/.ldo/profile.shapeTypes"; |
||||
import { namedNode } from "@rdfjs/dataset"; |
||||
import { INSTANCE } from "../src/constants"; |
||||
|
||||
// Use an increased timeout, since the CSS server takes too much setup time.
|
||||
jest.setTimeout(40_000); |
||||
|
||||
const ADDRESS_BOOK = "http://www.w3.org/2006/vcard/ns#AddressBook"; |
||||
const BOOKMARK = "http://www.w3.org/2002/01/bookmark#Bookmark"; |
||||
const EXAMPLE_THING = "https://example.com/ExampleThing"; |
||||
|
||||
describe("General Tests", () => { |
||||
const s = setUpServer(); |
||||
|
||||
it("gets the current typeindex", async () => { |
||||
await setupFullTypeIndex(s); |
||||
|
||||
const solidLdoDataset = createSolidLdoDataset(); |
||||
const typeRegistrations = await getTypeRegistrations(WEB_ID, { |
||||
solidLdoDataset, |
||||
}); |
||||
const addressBookUris = await getInstanceUris( |
||||
ADDRESS_BOOK, |
||||
typeRegistrations, |
||||
{ solidLdoDataset }, |
||||
); |
||||
expect(addressBookUris).toEqual( |
||||
expect.arrayContaining([ |
||||
"https://example.com/myPrivateAddressBook.ttl", |
||||
"https://example.com/myPublicAddressBook.ttl", |
||||
]), |
||||
); |
||||
const bookmarkUris = await getInstanceUris(BOOKMARK, typeRegistrations, { |
||||
solidLdoDataset, |
||||
}); |
||||
expect(bookmarkUris).toEqual( |
||||
expect.arrayContaining([MY_BOOKMARKS_1_URI, MY_BOOKMARKS_2_URI]), |
||||
); |
||||
}); |
||||
|
||||
it("initializes the type index", async () => { |
||||
await setupEmptyTypeIndex(s); |
||||
|
||||
const solidLdoDataset = createSolidLdoDataset(); |
||||
|
||||
await initTypeIndex(WEB_ID, { solidLdoDataset }); |
||||
|
||||
const profile = solidLdoDataset |
||||
.usingType(TypeIndexProfileShapeType) |
||||
.fromSubject(WEB_ID); |
||||
|
||||
expect(profile.privateTypeIndex?.[0]?.["@id"]).toBeDefined(); |
||||
expect(profile.publicTypeIndex?.[0]?.["@id"]).toBeDefined(); |
||||
}); |
||||
|
||||
it("Adds to the typeIndex", async () => { |
||||
await setupFullTypeIndex(s); |
||||
|
||||
const solidLdoDataset = createSolidLdoDataset(); |
||||
|
||||
await getTypeRegistrations(WEB_ID, { solidLdoDataset }); |
||||
|
||||
const transaction = solidLdoDataset.startTransaction(); |
||||
addRegistration( |
||||
PUBLIC_TYPE_INDEX_URI, |
||||
ADDRESS_BOOK, |
||||
{ instance: ["https://example.com/AdressBook3"] }, |
||||
{ solidLdoDataset: transaction }, |
||||
); |
||||
addRegistration( |
||||
PRIVATE_TYPE_INDEX_URI, |
||||
EXAMPLE_THING, |
||||
{ instanceContainer: ["https://example.com/ExampleInstance"] }, |
||||
{ solidLdoDataset: transaction }, |
||||
); |
||||
const { added, removed } = transaction.getChanges(); |
||||
|
||||
const existingRegistration = namedNode( |
||||
"http://localhost:3003/example/profile/publicTypeIndex.ttl#ab09fd", |
||||
); |
||||
|
||||
expect(removed).not.toBeDefined(); |
||||
expect(added?.size).toBe(4); |
||||
expect(added?.match(existingRegistration).size).toBe(1); |
||||
expect( |
||||
added?.match( |
||||
existingRegistration, |
||||
namedNode(INSTANCE), |
||||
namedNode("https://example.com/AdressBook3"), |
||||
namedNode("http://localhost:3003/example/profile/publicTypeIndex.ttl"), |
||||
).size, |
||||
).toBe(1); |
||||
}); |
||||
|
||||
it("Removes from the typeIndex", async () => { |
||||
await setupFullTypeIndex(s); |
||||
|
||||
const solidLdoDataset = createSolidLdoDataset(); |
||||
|
||||
await getTypeRegistrations(WEB_ID, { solidLdoDataset }); |
||||
|
||||
const transaction = solidLdoDataset.startTransaction(); |
||||
removeRegistration( |
||||
PUBLIC_TYPE_INDEX_URI, |
||||
ADDRESS_BOOK, |
||||
{ instance: ["https://example.com/myPublicAddressBook.ttl"] }, |
||||
{ solidLdoDataset: transaction }, |
||||
); |
||||
removeRegistration( |
||||
PRIVATE_TYPE_INDEX_URI, |
||||
BOOKMARK, |
||||
{ instanceContainer: [`${ROOT_CONTAINER}myBookmarks/`] }, |
||||
{ solidLdoDataset: transaction }, |
||||
); |
||||
const { added, removed } = transaction.getChanges(); |
||||
|
||||
expect(added).not.toBeDefined(); |
||||
expect(removed?.size).toBe(2); |
||||
}); |
||||
}); |
@ -0,0 +1,153 @@ |
||||
import fetch from "cross-fetch"; |
||||
|
||||
export const SERVER_DOMAIN = process.env.SERVER || "http://localhost:3003/"; |
||||
export const ROOT_ROUTE = process.env.ROOT_CONTAINER || "example/"; |
||||
export const ROOT_CONTAINER = `${SERVER_DOMAIN}${ROOT_ROUTE}`; |
||||
export const PROFILE_CONTAINER = `${ROOT_CONTAINER}profile/`; |
||||
export const WEB_ID = `${PROFILE_CONTAINER}card.ttl#me`; |
||||
export const PUBLIC_TYPE_INDEX_URI = `${PROFILE_CONTAINER}publicTypeIndex.ttl`; |
||||
export const PRIVATE_TYPE_INDEX_URI = `${PROFILE_CONTAINER}privateTypeIndex.ttl`; |
||||
export const MY_BOOKMARKS_CONTAINER = `${ROOT_CONTAINER}myBookmarks/`; |
||||
export const MY_BOOKMARKS_1_URI = `${ROOT_CONTAINER}myBookmarks/bookmark1.ttl`; |
||||
export const MY_BOOKMARKS_2_URI = `${ROOT_CONTAINER}myBookmarks/bookmark2.ttl`; |
||||
|
||||
export const PROFILE_TTL = ` |
||||
<#me> <http://www.w3.org/ns/solid/terms#publicTypeIndex> <${PROFILE_CONTAINER}publicTypeIndex.ttl> ; |
||||
<http://www.w3.org/ns/solid/terms#privateTypeIndex> <${PROFILE_CONTAINER}privateTypeIndex.ttl> .`;
|
||||
export const PUBLIC_TYPE_INDEX_TTL = `@prefix solid: <http://www.w3.org/ns/solid/terms#>.
|
||||
@prefix vcard: <http://www.w3.org/2006/vcard/ns#>. |
||||
@prefix bk: <http://www.w3.org/2002/01/bookmark#>. |
||||
|
||||
<> |
||||
a solid:TypeIndex ; |
||||
a solid:ListedDocument. |
||||
|
||||
<#ab09fd> a solid:TypeRegistration; |
||||
solid:forClass vcard:AddressBook; |
||||
solid:instance <https://example.com/myPublicAddressBook.ttl>. |
||||
|
||||
<#bq1r5e> a solid:TypeRegistration; |
||||
solid:forClass bk:Bookmark; |
||||
solid:instanceContainer <${ROOT_CONTAINER}myBookmarks/>.`;
|
||||
export const PRIVATE_TYPE_INDEX_TTL = `@prefix solid: <http://www.w3.org/ns/solid/terms#>.
|
||||
@prefix vcard: <http://www.w3.org/2006/vcard/ns#>. |
||||
@prefix bk: <http://www.w3.org/2002/01/bookmark#>. |
||||
|
||||
<> |
||||
a solid:TypeIndex ; |
||||
a solid:UnlistedDocument. |
||||
|
||||
<#ab09fd> a solid:TypeRegistration; |
||||
solid:forClass vcard:AddressBook; |
||||
solid:instance <https://example.com/myPrivateAddressBook.ttl>. |
||||
|
||||
<#bq1r5e> a solid:TypeRegistration; |
||||
solid:forClass bk:Bookmark; |
||||
solid:instanceContainer <${ROOT_CONTAINER}myBookmarks/>.`;
|
||||
|
||||
export interface SetUpServerReturn { |
||||
authFetch: typeof fetch; |
||||
fetchMock: jest.Mock< |
||||
Promise<Response>, |
||||
[input: RequestInfo | URL, init?: RequestInit | undefined] |
||||
>; |
||||
} |
||||
|
||||
export async function setupFullTypeIndex(s: SetUpServerReturn) { |
||||
// Create a new document called sample.ttl
|
||||
await s.authFetch(WEB_ID, { method: "DELETE" }); |
||||
await s.authFetch(ROOT_CONTAINER, { |
||||
method: "POST", |
||||
headers: { |
||||
link: '<http://www.w3.org/ns/ldp#Container>; rel="type"', |
||||
slug: "myBookmarks/", |
||||
}, |
||||
}); |
||||
await Promise.all([ |
||||
s.authFetch(PROFILE_CONTAINER, { |
||||
method: "POST", |
||||
headers: { "content-type": "text/turtle", slug: "card.ttl" }, |
||||
body: PROFILE_TTL, |
||||
}), |
||||
s.authFetch(PROFILE_CONTAINER, { |
||||
method: "POST", |
||||
headers: { "content-type": "text/turtle", slug: "publicTypeIndex.ttl" }, |
||||
body: PUBLIC_TYPE_INDEX_TTL, |
||||
}), |
||||
s.authFetch(PROFILE_CONTAINER, { |
||||
method: "POST", |
||||
headers: { |
||||
"content-type": "text/turtle", |
||||
slug: "privateTypeIndex.ttl", |
||||
}, |
||||
body: PRIVATE_TYPE_INDEX_TTL, |
||||
}), |
||||
s.authFetch(MY_BOOKMARKS_CONTAINER, { |
||||
method: "POST", |
||||
headers: { "content-type": "text/turtle", slug: "bookmark1.ttl" }, |
||||
body: "", |
||||
}), |
||||
s.authFetch(MY_BOOKMARKS_CONTAINER, { |
||||
method: "POST", |
||||
headers: { "content-type": "text/turtle", slug: "bookmark2.ttl" }, |
||||
body: "", |
||||
}), |
||||
]); |
||||
} |
||||
|
||||
export async function setupEmptyTypeIndex(s: SetUpServerReturn) { |
||||
// Create a new document called sample.ttl
|
||||
await s.authFetch(WEB_ID, { method: "DELETE" }); |
||||
await s.authFetch(ROOT_CONTAINER, { |
||||
method: "POST", |
||||
headers: { |
||||
link: '<http://www.w3.org/ns/ldp#Container>; rel="type"', |
||||
slug: "myBookmarks/", |
||||
}, |
||||
}); |
||||
await Promise.all([ |
||||
s.authFetch(PROFILE_CONTAINER, { |
||||
method: "POST", |
||||
headers: { "content-type": "text/turtle", slug: "card.ttl" }, |
||||
body: "", |
||||
}), |
||||
s.authFetch(MY_BOOKMARKS_CONTAINER, { |
||||
method: "POST", |
||||
headers: { "content-type": "text/turtle", slug: "bookmark1.ttl" }, |
||||
body: "", |
||||
}), |
||||
s.authFetch(MY_BOOKMARKS_CONTAINER, { |
||||
method: "POST", |
||||
headers: { "content-type": "text/turtle", slug: "bookmark2.ttl" }, |
||||
body: "", |
||||
}), |
||||
]); |
||||
} |
||||
|
||||
export function setUpServer(): SetUpServerReturn { |
||||
// Ignore to build s
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const s: SetUpServerReturn = {}; |
||||
|
||||
beforeAll(async () => { |
||||
// s.authFetch = await getAuthenticatedFetch();
|
||||
s.authFetch = fetch; |
||||
}); |
||||
|
||||
beforeEach(async () => { |
||||
s.fetchMock = jest.fn(s.authFetch); |
||||
}); |
||||
|
||||
afterEach(async () => { |
||||
await Promise.all([ |
||||
await s.authFetch(WEB_ID, { method: "DELETE" }), |
||||
await s.authFetch(PUBLIC_TYPE_INDEX_URI, { method: "DELETE" }), |
||||
await s.authFetch(PRIVATE_TYPE_INDEX_URI, { method: "DELETE" }), |
||||
await s.authFetch(MY_BOOKMARKS_1_URI, { method: "DELETE" }), |
||||
await s.authFetch(MY_BOOKMARKS_2_URI, { method: "DELETE" }), |
||||
]); |
||||
}); |
||||
|
||||
return s; |
||||
} |
@ -0,0 +1,52 @@ |
||||
{ |
||||
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^7.0.0/components/context.jsonld", |
||||
"import": [ |
||||
"css:config/app/init/initialize-intro.json", |
||||
"css:config/app/main/default.json", |
||||
"css:config/app/variables/default.json", |
||||
"css:config/http/handler/default.json", |
||||
"css:config/http/middleware/default.json", |
||||
"css:config/http/notifications/all.json", |
||||
"css:config/http/server-factory/http.json", |
||||
"css:config/http/static/default.json", |
||||
"css:config/identity/access/public.json", |
||||
"css:config/identity/email/default.json", |
||||
"css:config/identity/handler/default.json", |
||||
"css:config/identity/oidc/default.json", |
||||
"css:config/identity/ownership/token.json", |
||||
"css:config/identity/pod/static.json", |
||||
"css:config/ldp/authentication/dpop-bearer.json", |
||||
"css:config/ldp/authorization/webacl.json", |
||||
"css:config/ldp/handler/default.json", |
||||
"css:config/ldp/metadata-parser/default.json", |
||||
"css:config/ldp/metadata-writer/default.json", |
||||
"css:config/ldp/modes/default.json", |
||||
"css:config/storage/backend/memory.json", |
||||
"css:config/storage/key-value/resource-store.json", |
||||
"css:config/storage/location/root.json", |
||||
"css:config/storage/middleware/default.json", |
||||
"css:config/util/auxiliary/acl.json", |
||||
"css:config/util/identifiers/suffix.json", |
||||
"css:config/util/index/default.json", |
||||
"css:config/util/logging/winston.json", |
||||
"css:config/util/representation-conversion/default.json", |
||||
"css:config/util/resource-locker/memory.json", |
||||
"css:config/util/variables/default.json" |
||||
], |
||||
"@graph": [ |
||||
{ |
||||
"comment": "A Solid server that stores its resources in memory and uses WAC for authorization." |
||||
}, |
||||
{ |
||||
"comment": "The location of the new pod templates folder.", |
||||
"@type": "Override", |
||||
"overrideInstance": { |
||||
"@id": "urn:solid-server:default:PodResourcesGenerator" |
||||
}, |
||||
"overrideParameters": { |
||||
"@type": "StaticFolderGenerator", |
||||
"templateFolder": "./test/test-server/configs/template" |
||||
} |
||||
} |
||||
] |
||||
} |
@ -0,0 +1,9 @@ |
||||
[ |
||||
{ |
||||
"email": "hello@example.com", |
||||
"password": "abc123", |
||||
"pods": [ |
||||
{ "name": "example" } |
||||
] |
||||
} |
||||
] |
@ -0,0 +1,13 @@ |
||||
@prefix : <#>. |
||||
@prefix acl: <http://www.w3.org/ns/auth/acl#>. |
||||
@prefix foaf: <http://xmlns.com/foaf/0.1/>. |
||||
@prefix eve: <./>. |
||||
@prefix c: <./profile/card#>. |
||||
|
||||
:ControlReadWrite |
||||
a acl:Authorization; |
||||
acl:accessTo eve:; |
||||
acl:agent c:me, <mailto:info@o.team>; |
||||
acl:agentClass foaf:Agent; |
||||
acl:default eve:; |
||||
acl:mode acl:Control, acl:Read, acl:Write. |
@ -0,0 +1,19 @@ |
||||
# ACL resource for the WebID profile document |
||||
@prefix acl: <http://www.w3.org/ns/auth/acl#>. |
||||
@prefix foaf: <http://xmlns.com/foaf/0.1/>. |
||||
|
||||
# The WebID profile is readable by the public. |
||||
# This is required for discovery and verification, |
||||
# e.g. when checking identity providers. |
||||
<#public> |
||||
a acl:Authorization; |
||||
acl:agentClass foaf:Agent; |
||||
acl:accessTo <./card>; |
||||
acl:mode acl:Read. |
||||
|
||||
# The owner has full access to the profile |
||||
<#owner> |
||||
a acl:Authorization; |
||||
acl:agent <{{webId}}>; |
||||
acl:accessTo <./card>; |
||||
acl:mode acl:Read, acl:Write, acl:Control. |
@ -0,0 +1,7 @@ |
||||
import { createApp } from "./solidServer.helper"; |
||||
|
||||
async function run() { |
||||
const app = await createApp(); |
||||
await app.start(); |
||||
} |
||||
run(); |
@ -0,0 +1,39 @@ |
||||
// Taken from https://github.com/comunica/comunica/blob/b237be4265c353a62a876187d9e21e3bc05123a3/engines/query-sparql/test/QuerySparql-solid-test.ts#L9
|
||||
|
||||
import * as path from "path"; |
||||
import type { App } from "@solid/community-server"; |
||||
import { AppRunner, resolveModulePath } from "@solid/community-server"; |
||||
|
||||
export async function createApp(): Promise<App> { |
||||
if (process.env.SERVER) { |
||||
return { |
||||
start: () => {}, |
||||
stop: () => {}, |
||||
} as App; |
||||
} |
||||
const appRunner = new AppRunner(); |
||||
|
||||
return appRunner.create({ |
||||
loaderProperties: { |
||||
mainModulePath: resolveModulePath(""), |
||||
typeChecking: false, |
||||
}, |
||||
config: path.join( |
||||
__dirname, |
||||
"configs", |
||||
"components-config", |
||||
"unauthenticatedServer.json", |
||||
), |
||||
variableBindings: {}, |
||||
shorthand: { |
||||
port: 3_003, |
||||
loggingLevel: "off", |
||||
seedConfig: path.join(__dirname, "configs", "solid-css-seed.json"), |
||||
}, |
||||
}); |
||||
} |
||||
|
||||
export interface ISecretData { |
||||
id: string; |
||||
secret: string; |
||||
} |
@ -0,0 +1,8 @@ |
||||
{ |
||||
"extends": "../../tsconfig.base.json", |
||||
"compilerOptions": { |
||||
"outDir": "./dist", |
||||
"lib": ["dom"] |
||||
}, |
||||
"include": ["./src"] |
||||
} |
@ -1,8 +0,0 @@ |
||||
import type { ResourceSuccess } from "../../../requester/results/success/SuccessResult"; |
||||
|
||||
/** |
||||
* Returned when a notification has been successfully subscribed to for a resource |
||||
*/ |
||||
export interface SubscribeToNotificationSuccess extends ResourceSuccess { |
||||
type: "subscribeToNotificationSuccess"; |
||||
} |
@ -1,8 +0,0 @@ |
||||
import type { ResourceSuccess } from "../../../requester/results/success/SuccessResult"; |
||||
|
||||
/** |
||||
* Returned when a notification has been successfully unsubscribed from for a resource |
||||
*/ |
||||
export interface UnsubscribeToNotificationSuccess extends ResourceSuccess { |
||||
type: "unsubscribeFromNotificationSuccess"; |
||||
} |
Loading…
Reference in new issue