main
Jackson Morgan 9 months ago
commit 6047c3f1e4
  1. 2
      lerna.json
  2. 1865
      package-lock.json
  3. 9
      packages/cli/package.json
  4. 16
      packages/cli/src/build.ts
  5. 19
      packages/cli/test/.shapes/foafProfile.shex
  6. 2
      packages/dataset/example/loadDataExample.ts
  7. 4
      packages/dataset/package.json
  8. 4
      packages/dataset/test/createExtendedDatasetFromSerializedInput.test.ts
  9. 8
      packages/demo-react/package.json
  10. 6
      packages/jsonld-dataset-proxy/package.json
  11. 1
      packages/jsonld-dataset-proxy/src/util/nodeToJsonldRepresentation.ts
  12. 12
      packages/ldo/package.json
  13. 4
      packages/ldo/src/parseRdf.ts
  14. 2
      packages/rdf-utils/package.json
  15. 2
      packages/rdf-utils/src/serializedToQuads.ts
  16. 4
      packages/schema-converter-shex/package.json
  17. 16
      packages/solid-react/package.json
  18. 26
      packages/solid-react/src/BrowserSolidLdoProvider.tsx
  19. 8
      packages/solid-react/test/test-server/configs/components-config/unauthenticatedServer.json
  20. 5
      packages/solid-react/test/test-server/configs/solid-css-seed.json
  21. 19
      packages/solid-react/test/test-server/solidServer.helper.ts
  22. 1
      packages/solid/babel.config.js
  23. 4
      packages/solid/jest.config.js
  24. 19
      packages/solid/package.json
  25. 8
      packages/solid/src/.ldo/solid.context.ts
  26. 21
      packages/solid/src/.ldo/solid.schema.ts
  27. 13
      packages/solid/src/.ldo/solid.shapeTypes.ts
  28. 16
      packages/solid/src/.ldo/solid.typings.ts
  29. 7
      packages/solid/src/.shapes/solid.shex
  30. 53
      packages/solid/src/SolidLdoDataset.ts
  31. 4
      packages/solid/src/methods.ts
  32. 16
      packages/solid/src/requester/requests/checkRootContainer.ts
  33. 7
      packages/solid/src/requester/requests/readResource.ts
  34. 17
      packages/solid/src/requester/results/error/NoRootContainerError.ts
  35. 8
      packages/solid/src/requester/results/success/CheckRootContainerSuccess.ts
  36. 16
      packages/solid/src/resource/Container.ts
  37. 12
      packages/solid/src/resource/Leaf.ts
  38. 5
      packages/solid/src/resource/Resource.ts
  39. 143
      packages/solid/test/Integration.test.ts
  40. 112
      packages/solid/test/authFetch.helper.ts
  41. 43
      packages/solid/test/configs/server-config.json
  42. 6
      packages/solid/test/configs/solid-css-seed.json
  43. 99
      packages/solid/test/solidServer.helper.ts
  44. 2
      packages/subscribable-dataset/Readme.md
  45. 6
      packages/subscribable-dataset/package.json
  46. 2
      packages/subscribable-dataset/test/createSubscribableDatasetFromSerializedInput.test.ts
  47. 4
      packages/traverser-shexj/package.json
  48. 2
      packages/type-traverser/package.json

@ -1,4 +1,4 @@
{ {
"$schema": "node_modules/lerna/schemas/lerna-schema.json", "$schema": "node_modules/lerna/schemas/lerna-schema.json",
"version": "0.0.1-alpha.23" "version": "0.0.1-alpha.28"
} }

1865
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,6 +1,6 @@
{ {
"name": "@ldo/cli", "name": "@ldo/cli",
"version": "0.0.1-alpha.23", "version": "0.0.1-alpha.28",
"description": "A Command Line Interface for Linked Data Objects", "description": "A Command Line Interface for Linked Data Objects",
"main": "./dist/index.js", "main": "./dist/index.js",
"bin": { "bin": {
@ -19,7 +19,8 @@
"test": "jest --coverage", "test": "jest --coverage",
"test:watch": "jest --watch", "test:watch": "jest --watch",
"prepublishOnly": "npm run test && npm run build", "prepublishOnly": "npm run test && npm run build",
"lint": "eslint src/** --fix --no-error-on-unmatched-pattern" "lint": "eslint src/** --fix --no-error-on-unmatched-pattern",
"build:ldo": "./dist/index.js build --input test/.shapes --output test/.ldo"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -43,7 +44,7 @@
"ts-jest": "^27.0.7" "ts-jest": "^27.0.7"
}, },
"dependencies": { "dependencies": {
"@ldo/schema-converter-shex": "^0.0.1-alpha.23", "@ldo/schema-converter-shex": "^0.0.1-alpha.24",
"@shexjs/parser": "^1.0.0-alpha.24", "@shexjs/parser": "^1.0.0-alpha.24",
"child-process-promise": "^2.2.1", "child-process-promise": "^2.2.1",
"commander": "^9.3.0", "commander": "^9.3.0",
@ -59,5 +60,5 @@
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },
"gitHead": "d2364cd2f8da5f0b673b1202d29df5b7c071a17c" "gitHead": "c63f055aab22155b60a5fdee4172979b9c287dfa"
} }

@ -38,9 +38,19 @@ export async function build(options: BuildOptions) {
"utf8", "utf8",
); );
// Convert to ShexJ // Convert to ShexJ
const schema: Schema = parser let schema: Schema;
.construct("https://ldo.js.org/") try {
.parse(shexC); schema = parser.construct("https://ldo.js.org/").parse(shexC);
} catch (err) {
const errMessage =
err instanceof Error
? err.message
: typeof err === "string"
? err
: "Unknown Error";
console.error(`Error processing ${file.name}: ${errMessage}`);
return;
}
// Convert the content to types // Convert the content to types
const [typings, context] = await schemaConverterShex(schema); const [typings, context] = await schemaConverterShex(schema);
await Promise.all( await Promise.all(

@ -0,0 +1,19 @@
# This shape is provided by default as an example
# You can create your own shape to fit your needs using ShEx (https://shex.io)
# Also check out https://shaperepo.com for examples of more shapes.
PREFIX ex: <https://example.com/>
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
ex:FoafProfile EXTRA a {
a [ foaf:Person ]
// rdfs:comment "Defines the node as a Person (from foaf)" ;
foaf:name xsd:string ?
// rdfs:comment "Define a person's name." ;
foaf:img xsd:string ?
// rdfs:comment "Photo link but in string form"
foaf:knows @ex:FoafProfile *
// rdfs:comment "A list of WebIds for all the people this user knows." ;
}

@ -34,7 +34,7 @@ async function run(): Promise<void> {
const jsonLdDataset = await serializedToDataset(JSON.stringify(jsonLdData), { const jsonLdDataset = await serializedToDataset(JSON.stringify(jsonLdData), {
baseIRI: baseIRI:
"https://jackson.solidcommunity.net/IndividualChats/jackson.solidcommunity.net/index.ttl#", "https://jackson.solidcommunity.net/IndividualChats/jackson.solidcommunity.net/index.ttl#",
format: "application/json-ld", format: "application/ld+json",
}); });
// Returns true because the input data describes the same triple. // Returns true because the input data describes the same triple.
console.log(turtleDataset.equals(jsonLdDataset)); console.log(turtleDataset.equals(jsonLdDataset));

@ -1,6 +1,6 @@
{ {
"name": "@ldo/dataset", "name": "@ldo/dataset",
"version": "0.0.1-alpha.23", "version": "0.0.1-alpha.24",
"description": "An RDFJS dataset implementation", "description": "An RDFJS dataset implementation",
"main": "dist/index.js", "main": "dist/index.js",
"scripts": { "scripts": {
@ -34,7 +34,7 @@
"ts-node": "^9.1.1" "ts-node": "^9.1.1"
}, },
"dependencies": { "dependencies": {
"@ldo/rdf-utils": "^0.0.1-alpha.23", "@ldo/rdf-utils": "^0.0.1-alpha.24",
"@rdfjs/dataset": "^1.1.0", "@rdfjs/dataset": "^1.1.0",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"readable-stream": "^4.2.0" "readable-stream": "^4.2.0"

@ -12,7 +12,7 @@ describe("createExtendedDatasetFromSerializedInput", () => {
it.skip("creates a dataset with json-ld", async () => { it.skip("creates a dataset with json-ld", async () => {
const dataset = await serializedToDataset(JSON.stringify(jsonLdData), { const dataset = await serializedToDataset(JSON.stringify(jsonLdData), {
format: "application/json-ld", format: "application/ld+json",
}); });
expect(dataset.size).toBe(9); expect(dataset.size).toBe(9);
}); });
@ -26,7 +26,7 @@ describe("createExtendedDatasetFromSerializedInput", () => {
it.skip("Should error when given invalid JSON", async () => { it.skip("Should error when given invalid JSON", async () => {
await expect( await expect(
serializedToDataset('{ bad" json', { format: "application/json-ld" }), serializedToDataset('{ bad" json', { format: "application/ld+json" }),
).rejects.toThrow("Unexpected token b in JSON at position 2"); ).rejects.toThrow("Unexpected token b in JSON at position 2");
}); });
}); });

@ -1,9 +1,9 @@
{ {
"name": "@ldo/demo-react", "name": "@ldo/demo-react",
"version": "0.0.1-alpha.19", "version": "0.0.1-alpha.28",
"dependencies": { "dependencies": {
"@inrupt/solid-client-authn-browser": "^2.0.0", "@inrupt/solid-client-authn-browser": "^2.0.0",
"@ldo/solid-react": "^0.0.1-alpha.19", "@ldo/solid-react": "^0.0.1-alpha.28",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-router-dom": "^6.15.0", "react-router-dom": "^6.15.0",
@ -37,13 +37,13 @@
}, },
"devDependencies": { "devDependencies": {
"@craco/craco": "^7.1.0", "@craco/craco": "^7.1.0",
"@ldo/cli": "^0.0.1-alpha.19", "@ldo/cli": "^0.0.1-alpha.28",
"@types/jsonld": "^1.5.9", "@types/jsonld": "^1.5.9",
"@types/react": "^18.2.21", "@types/react": "^18.2.21",
"@types/shexj": "^2.1.4", "@types/shexj": "^2.1.4",
"tsconfig-paths-webpack-plugin": "^4.1.0" "tsconfig-paths-webpack-plugin": "^4.1.0"
}, },
"gitHead": "4548985c0de9b0ec83cf5ee93f2d7c1ca2c1b8d8", "gitHead": "c63f055aab22155b60a5fdee4172979b9c287dfa",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
} }

@ -1,6 +1,6 @@
{ {
"name": "@ldo/jsonld-dataset-proxy", "name": "@ldo/jsonld-dataset-proxy",
"version": "0.0.1-alpha.23", "version": "0.0.1-alpha.24",
"description": "", "description": "",
"main": "dist/index.js", "main": "dist/index.js",
"scripts": { "scripts": {
@ -40,8 +40,8 @@
"src" "src"
], ],
"dependencies": { "dependencies": {
"@ldo/rdf-utils": "^0.0.1-alpha.23", "@ldo/rdf-utils": "^0.0.1-alpha.24",
"@ldo/subscribable-dataset": "^0.0.1-alpha.23", "@ldo/subscribable-dataset": "^0.0.1-alpha.24",
"@rdfjs/data-model": "^1.2.0", "@rdfjs/data-model": "^1.2.0",
"@rdfjs/dataset": "^1.1.0", "@rdfjs/dataset": "^1.1.0",
"jsonld2graphobject": "^0.0.4" "jsonld2graphobject": "^0.0.4"

@ -34,6 +34,7 @@ export function literalToJsonldRepresentation(literal: Literal) {
case "http://www.w3.org/2001/XMLSchema#integer": case "http://www.w3.org/2001/XMLSchema#integer":
case "http://www.w3.org/2001/XMLSchema#byte": case "http://www.w3.org/2001/XMLSchema#byte":
case "http://www.w3.org/2001/XMLSchema#decimal": case "http://www.w3.org/2001/XMLSchema#decimal":
case "http://www.w3.org/2001/XMLSchema#double":
case "http://www.w3.org/2001/XMLSchema#int": case "http://www.w3.org/2001/XMLSchema#int":
case "http://www.w3.org/2001/XMLSchema#long": case "http://www.w3.org/2001/XMLSchema#long":
case "http://www.w3.org/2001/XMLSchema#negativeInteger": case "http://www.w3.org/2001/XMLSchema#negativeInteger":

@ -1,6 +1,6 @@
{ {
"name": "@ldo/ldo", "name": "@ldo/ldo",
"version": "0.0.1-alpha.23", "version": "0.0.1-alpha.28",
"description": "", "description": "",
"main": "dist/index.js", "main": "dist/index.js",
"scripts": { "scripts": {
@ -23,7 +23,7 @@
}, },
"homepage": "https://github.com/o-development/ldobjects/tree/main/packages/ldo#readme", "homepage": "https://github.com/o-development/ldobjects/tree/main/packages/ldo#readme",
"devDependencies": { "devDependencies": {
"@ldo/rdf-utils": "^0.0.1-alpha.23", "@ldo/rdf-utils": "^0.0.1-alpha.24",
"@rdfjs/types": "^1.0.1", "@rdfjs/types": "^1.0.1",
"@types/jest": "^27.0.3", "@types/jest": "^27.0.3",
"@types/jsonld": "^1.5.6", "@types/jsonld": "^1.5.6",
@ -38,9 +38,9 @@
"typedoc-plugin-markdown": "^3.17.1" "typedoc-plugin-markdown": "^3.17.1"
}, },
"dependencies": { "dependencies": {
"@ldo/dataset": "^0.0.1-alpha.23", "@ldo/dataset": "^0.0.1-alpha.24",
"@ldo/jsonld-dataset-proxy": "^0.0.1-alpha.23", "@ldo/jsonld-dataset-proxy": "^0.0.1-alpha.24",
"@ldo/subscribable-dataset": "^0.0.1-alpha.23", "@ldo/subscribable-dataset": "^0.0.1-alpha.24",
"@rdfjs/data-model": "^1.2.0", "@rdfjs/data-model": "^1.2.0",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"readable-stream": "^4.3.0" "readable-stream": "^4.3.0"
@ -55,5 +55,5 @@
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },
"gitHead": "d2364cd2f8da5f0b673b1202d29df5b7c071a17c" "gitHead": "c63f055aab22155b60a5fdee4172979b9c287dfa"
} }

@ -24,7 +24,7 @@ import type { LdoDataset } from "./LdoDataset";
* import { parseRdf } from "ldo"; * import { parseRdf } from "ldo";
* *
* const rawTurtle = "..."; * const rawTurtle = "...";
* const ldoDataset = parseRdf(rawTurtle, { baseIRI: "https://example.com/" }); * const ldoDataset = await parseRdf(rawTurtle, { baseIRI: "https://example.com/" });
* ``` * ```
*/ */
export async function parseRdf( export async function parseRdf(
@ -47,7 +47,7 @@ export async function parseRdf(
ldoDatasetFactory, ldoDatasetFactory,
JSON.stringify(data), JSON.stringify(data),
{ {
format: "application/json-ld", format: "application/ld+json",
}, },
); );
} }

@ -1,6 +1,6 @@
{ {
"name": "@ldo/rdf-utils", "name": "@ldo/rdf-utils",
"version": "0.0.1-alpha.23", "version": "0.0.1-alpha.24",
"description": "Some RDF Utilities to support LDO librariers", "description": "Some RDF Utilities to support LDO librariers",
"main": "dist/index.js", "main": "dist/index.js",
"scripts": { "scripts": {

@ -9,7 +9,7 @@ export async function serializedToQuads(
options?: ParserOptions, options?: ParserOptions,
): Promise<Quad[]> { ): Promise<Quad[]> {
// JSON-LD Parsing // JSON-LD Parsing
if (options && options.format === "application/json-ld") { if (options && options.format === "application/ld+json") {
throw new Error("Not Implemented"); throw new Error("Not Implemented");
// return new Promise((resolve, reject) => { // return new Promise((resolve, reject) => {
// JSON.parse(data); // JSON.parse(data);

@ -1,6 +1,6 @@
{ {
"name": "@ldo/schema-converter-shex", "name": "@ldo/schema-converter-shex",
"version": "0.0.1-alpha.23", "version": "0.0.1-alpha.24",
"description": "", "description": "",
"main": "dist/index.js", "main": "dist/index.js",
"scripts": { "scripts": {
@ -33,7 +33,7 @@
"dist" "dist"
], ],
"dependencies": { "dependencies": {
"@ldo/traverser-shexj": "^0.0.1-alpha.23", "@ldo/traverser-shexj": "^0.0.1-alpha.24",
"dts-dom": "^3.6.0", "dts-dom": "^3.6.0",
"jsonld2graphobject": "^0.0.5" "jsonld2graphobject": "^0.0.5"
}, },

@ -1,6 +1,6 @@
{ {
"name": "@ldo/solid-react", "name": "@ldo/solid-react",
"version": "0.0.1-alpha.23", "version": "0.0.1-alpha.28",
"description": "A React library for LDO and Solid", "description": "A React library for LDO and Solid",
"main": "dist/index.js", "main": "dist/index.js",
"scripts": { "scripts": {
@ -26,7 +26,7 @@
}, },
"homepage": "https://github.com/o-development/ldobjects/tree/main/packages/solid-react#readme", "homepage": "https://github.com/o-development/ldobjects/tree/main/packages/solid-react#readme",
"devDependencies": { "devDependencies": {
"@ldo/rdf-utils": "^0.0.1-alpha.23", "@ldo/rdf-utils": "^0.0.1-alpha.24",
"@rdfjs/types": "^1.0.1", "@rdfjs/types": "^1.0.1",
"@testing-library/react": "^14.1.2", "@testing-library/react": "^14.1.2",
"@types/jest": "^27.0.3", "@types/jest": "^27.0.3",
@ -37,11 +37,11 @@
}, },
"dependencies": { "dependencies": {
"@inrupt/solid-client-authn-browser": "^2.0.0", "@inrupt/solid-client-authn-browser": "^2.0.0",
"@ldo/dataset": "^0.0.1-alpha.23", "@ldo/dataset": "^0.0.1-alpha.24",
"@ldo/jsonld-dataset-proxy": "^0.0.1-alpha.23", "@ldo/jsonld-dataset-proxy": "^0.0.1-alpha.24",
"@ldo/ldo": "^0.0.1-alpha.23", "@ldo/ldo": "^0.0.1-alpha.28",
"@ldo/solid": "^0.0.1-alpha.23", "@ldo/solid": "^0.0.1-alpha.28",
"@ldo/subscribable-dataset": "^0.0.1-alpha.23", "@ldo/subscribable-dataset": "^0.0.1-alpha.24",
"@rdfjs/data-model": "^1.2.0", "@rdfjs/data-model": "^1.2.0",
"cross-fetch": "^3.1.6" "cross-fetch": "^3.1.6"
}, },
@ -52,5 +52,5 @@
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },
"gitHead": "d2364cd2f8da5f0b673b1202d29df5b7c071a17c" "gitHead": "c63f055aab22155b60a5fdee4172979b9c287dfa"
} }

@ -27,25 +27,31 @@ export const BrowserSolidLdoProvider: FunctionComponent<PropsWithChildren> = ({
await handleIncomingRedirect({ await handleIncomingRedirect({
restorePreviousSession: true, restorePreviousSession: true,
}); });
setSession({ ...getDefaultSession().info }); // Set timout to ensure this happens after the redirect
window.history.replaceState( setTimeout(() => {
{}, setSession({ ...getDefaultSession().info });
"", window.history.replaceState(
window.localStorage.getItem(PRE_REDIRECT_URI), {},
); "",
window.localStorage.removeItem(PRE_REDIRECT_URI); window.localStorage.getItem(PRE_REDIRECT_URI),
);
window.localStorage.removeItem(PRE_REDIRECT_URI);
setRanInitialAuthCheck(true); setRanInitialAuthCheck(true);
}, 0);
}, []); }, []);
const login = useCallback(async (issuer: string, options?: LoginOptions) => { const login = useCallback(async (issuer: string, options?: LoginOptions) => {
const cleanUrl = new URL(window.location.href);
cleanUrl.hash = "";
cleanUrl.search = "";
const fullOptions = { const fullOptions = {
redirectUrl: window.location.href, redirectUrl: cleanUrl.toString(),
clientName: "Solid App", clientName: "Solid App",
oidcIssuer: issuer, oidcIssuer: issuer,
...options, ...options,
}; };
window.localStorage.setItem(PRE_REDIRECT_URI, fullOptions.redirectUrl); window.localStorage.setItem(PRE_REDIRECT_URI, window.location.href);
await libraryLogin(fullOptions); await libraryLogin(fullOptions);
setSession({ ...getDefaultSession().info }); setSession({ ...getDefaultSession().info });
}, []); }, []);

@ -1,9 +1,8 @@
{ {
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^6.0.0/components/context.jsonld", "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^7.0.0/components/context.jsonld",
"import": [ "import": [
"css:config/app/init/initialize-intro.json",
"css:config/app/main/default.json", "css:config/app/main/default.json",
"css:config/app/init/initialize-prefilled-root.json",
"css:config/app/setup/optional.json",
"css:config/app/variables/default.json", "css:config/app/variables/default.json",
"css:config/http/handler/default.json", "css:config/http/handler/default.json",
"css:config/http/middleware/default.json", "css:config/http/middleware/default.json",
@ -13,9 +12,9 @@
"css:config/identity/access/public.json", "css:config/identity/access/public.json",
"css:config/identity/email/default.json", "css:config/identity/email/default.json",
"css:config/identity/handler/default.json", "css:config/identity/handler/default.json",
"css:config/identity/oidc/default.json",
"css:config/identity/ownership/token.json", "css:config/identity/ownership/token.json",
"css:config/identity/pod/static.json", "css:config/identity/pod/static.json",
"css:config/identity/registration/enabled.json",
"css:config/ldp/authentication/dpop-bearer.json", "css:config/ldp/authentication/dpop-bearer.json",
"css:config/ldp/authorization/webacl.json", "css:config/ldp/authorization/webacl.json",
"css:config/ldp/handler/default.json", "css:config/ldp/handler/default.json",
@ -24,6 +23,7 @@
"css:config/ldp/modes/default.json", "css:config/ldp/modes/default.json",
"css:config/storage/backend/memory.json", "css:config/storage/backend/memory.json",
"css:config/storage/key-value/resource-store.json", "css:config/storage/key-value/resource-store.json",
"css:config/storage/location/root.json",
"css:config/storage/middleware/default.json", "css:config/storage/middleware/default.json",
"css:config/util/auxiliary/acl.json", "css:config/util/auxiliary/acl.json",
"css:config/util/identifiers/suffix.json", "css:config/util/identifiers/suffix.json",

@ -1,8 +1,9 @@
[ [
{ {
"podName": "example",
"email": "hello@example.com", "email": "hello@example.com",
"password": "abc123", "password": "abc123",
"template": "./template" "pods": [
{ "name": "example" }
]
} }
] ]

@ -12,28 +12,25 @@ export async function createApp(): Promise<App> {
} as App; } as App;
} }
const appRunner = new AppRunner(); const appRunner = new AppRunner();
return appRunner.create(
{ return appRunner.create({
loaderProperties: {
mainModulePath: resolveModulePath(""), mainModulePath: resolveModulePath(""),
typeChecking: false, typeChecking: false,
}, },
path.join( config: path.join(
__dirname, __dirname,
"configs", "configs",
"components-config", "components-config",
"unauthenticatedServer.json", "unauthenticatedServer.json",
), ),
{}, variableBindings: {},
{ shorthand: {
port: 3_001, port: 3_001,
loggingLevel: "off", loggingLevel: "off",
seededPodConfigJson: path.join( seedConfig: path.join(__dirname, "configs", "solid-css-seed.json"),
__dirname,
"configs",
"solid-css-seed.json",
),
}, },
); });
} }
export interface ISecretData { export interface ISecretData {

@ -0,0 +1 @@
module.exports = { presets: ["@babel/preset-env"] };

@ -4,4 +4,8 @@ module.exports = {
...sharedConfig, ...sharedConfig,
rootDir: "./", rootDir: "./",
setupFiles: ["<rootDir>/test/setup-tests.ts"], setupFiles: ["<rootDir>/test/setup-tests.ts"],
transform: {
"^.+\\.(ts|tsx)?$": "ts-jest",
"^.+\\.(js|jsx)$": "babel-jest",
},
}; };

@ -1,13 +1,13 @@
{ {
"name": "@ldo/solid", "name": "@ldo/solid",
"version": "0.0.1-alpha.23", "version": "0.0.1-alpha.28",
"description": "A library for LDO and Solid", "description": "A library for LDO and Solid",
"main": "dist/index.js", "main": "dist/index.js",
"scripts": { "scripts": {
"example": "ts-node ./example/example.ts", "example": "ts-node ./example/example.ts",
"build": "tsc --project tsconfig.build.json", "build": "tsc --project tsconfig.build.json",
"watch": "tsc --watch", "watch": "tsc --watch",
"test": "jest --coverage", "test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --coverage",
"test:watch": "jest --watch", "test:watch": "jest --watch",
"prepublishOnly": "npm run test && npm run build", "prepublishOnly": "npm run test && npm run build",
"build:ldo": "ldo build --input src/.shapes --output src/.ldo", "build:ldo": "ldo build --input src/.shapes --output src/.ldo",
@ -25,12 +25,13 @@
}, },
"homepage": "https://github.com/o-development/ldobjects/tree/main/packages/solid#readme", "homepage": "https://github.com/o-development/ldobjects/tree/main/packages/solid#readme",
"devDependencies": { "devDependencies": {
"@inrupt/solid-client-authn-core": "^1.17.1", "@inrupt/solid-client-authn-core": "^2.2.6",
"@ldo/cli": "^0.0.1-alpha.23", "@ldo/cli": "^0.0.1-alpha.28",
"@rdfjs/data-model": "^1.2.0", "@rdfjs/data-model": "^1.2.0",
"@rdfjs/types": "^1.0.1", "@rdfjs/types": "^1.0.1",
"@solid/community-server": "^6.0.2", "@solid/community-server": "^7.1.3",
"@types/jest": "^27.0.3", "@types/jest": "^27.0.3",
"cross-env": "^7.0.3",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"jest-rdf": "^1.8.0", "jest-rdf": "^1.8.0",
"ts-jest": "^27.1.2", "ts-jest": "^27.1.2",
@ -40,9 +41,9 @@
"typedoc-plugin-markdown": "^3.17.1" "typedoc-plugin-markdown": "^3.17.1"
}, },
"dependencies": { "dependencies": {
"@ldo/dataset": "^0.0.1-alpha.23", "@ldo/dataset": "^0.0.1-alpha.24",
"@ldo/ldo": "^0.0.1-alpha.23", "@ldo/ldo": "^0.0.1-alpha.28",
"@ldo/rdf-utils": "^0.0.1-alpha.23", "@ldo/rdf-utils": "^0.0.1-alpha.24",
"cross-fetch": "^3.1.6", "cross-fetch": "^3.1.6",
"http-link-header": "^1.1.1" "http-link-header": "^1.1.1"
}, },
@ -53,5 +54,5 @@
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },
"gitHead": "d2364cd2f8da5f0b673b1202d29df5b7c071a17c" "gitHead": "c63f055aab22155b60a5fdee4172979b9c287dfa"
} }

@ -1,4 +1,4 @@
import type { ContextDefinition } from "jsonld"; import { ContextDefinition } from "jsonld";
/** /**
* ============================================================================= * =============================================================================
@ -15,7 +15,6 @@ export const solidContext: ContextDefinition = {
modified: { modified: {
"@id": "http://purl.org/dc/terms/modified", "@id": "http://purl.org/dc/terms/modified",
"@type": "http://www.w3.org/2001/XMLSchema#string", "@type": "http://www.w3.org/2001/XMLSchema#string",
"@container": "@set",
}, },
contains: { contains: {
"@id": "http://www.w3.org/ns/ldp#contains", "@id": "http://www.w3.org/ns/ldp#contains",
@ -26,11 +25,14 @@ export const solidContext: ContextDefinition = {
mtime: { mtime: {
"@id": "http://www.w3.org/ns/posix/stat#mtime", "@id": "http://www.w3.org/ns/posix/stat#mtime",
"@type": "http://www.w3.org/2001/XMLSchema#decimal", "@type": "http://www.w3.org/2001/XMLSchema#decimal",
"@container": "@set",
}, },
size: { size: {
"@id": "http://www.w3.org/ns/posix/stat#size", "@id": "http://www.w3.org/ns/posix/stat#size",
"@type": "http://www.w3.org/2001/XMLSchema#integer", "@type": "http://www.w3.org/2001/XMLSchema#integer",
},
storage: {
"@id": "http://www.w3.org/ns/pim/space#storage",
"@type": "@id",
"@container": "@set", "@container": "@set",
}, },
}; };

@ -1,4 +1,4 @@
import type { Schema } from "shexj"; import { Schema } from "shexj";
/** /**
* ============================================================================= * =============================================================================
@ -210,5 +210,24 @@ export const solidSchema: Schema = {
extra: ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"], extra: ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"],
}, },
}, },
{
id: "http://www.w3.org/ns/lddps#ProfileWithStorage",
type: "ShapeDecl",
shapeExpr: {
type: "Shape",
expression: {
id: "http://www.w3.org/ns/lddps#ProfileWithStorageShape",
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/pim/space#storage",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
min: 0,
max: -1,
},
extra: ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"],
},
},
], ],
}; };

@ -1,7 +1,7 @@
import type { ShapeType } from "@ldo/ldo"; import { ShapeType } from "@ldo/ldo";
import { solidSchema } from "./solid.schema"; import { solidSchema } from "./solid.schema";
import { solidContext } from "./solid.context"; import { solidContext } from "./solid.context";
import type { Container, Resource } from "./solid.typings"; import { Container, Resource, ProfileWithStorage } from "./solid.typings";
/** /**
* ============================================================================= * =============================================================================
@ -26,3 +26,12 @@ export const ResourceShapeType: ShapeType<Resource> = {
shape: "http://www.w3.org/ns/lddps#Resource", shape: "http://www.w3.org/ns/lddps#Resource",
context: solidContext, context: solidContext,
}; };
/**
* ProfileWithStorage ShapeType
*/
export const ProfileWithStorageShapeType: ShapeType<ProfileWithStorage> = {
schema: solidSchema,
shape: "http://www.w3.org/ns/lddps#ProfileWithStorage",
context: solidContext,
};

@ -1,4 +1,4 @@
import type { ContextDefinition } from "jsonld"; import { ContextDefinition } from "jsonld";
/** /**
* ============================================================================= * =============================================================================
@ -57,9 +57,6 @@ export interface Resource {
| { | {
"@id": "Resource2"; "@id": "Resource2";
} }
| {
"@id": "Container";
}
)[]; )[];
/** /**
* Date modified * Date modified
@ -74,3 +71,14 @@ export interface Resource {
*/ */
size?: number; size?: number;
} }
/**
* ProfileWithStorage Type
*/
export interface ProfileWithStorage {
"@id"?: string;
"@context"?: ContextDefinition;
storage?: {
"@id": string;
}[];
}

@ -6,6 +6,7 @@ PREFIX ldps: <http://www.w3.org/ns/lddps#>
PREFIX dct: <http://purl.org/dc/terms/> PREFIX dct: <http://purl.org/dc/terms/>
PREFIX stat: <http://www.w3.org/ns/posix/stat#> PREFIX stat: <http://www.w3.org/ns/posix/stat#>
PREFIX tur: <http://www.w3.org/ns/iana/media-types/text/turtle#> PREFIX tur: <http://www.w3.org/ns/iana/media-types/text/turtle#>
PREFIX pim: <http://www.w3.org/ns/pim/space#>
ldps:Container EXTRA a { ldps:Container EXTRA a {
$ldps:ContainerShape ( $ldps:ContainerShape (
@ -34,3 +35,9 @@ ldps:Resource EXTRA a {
// rdfs:comment "size of this container"; // rdfs:comment "size of this container";
) )
} }
ldps:ProfileWithStorage EXTRA a {
$ldps:ProfileWithStorageShape (
pim:storage IRI *;
)
}

@ -10,6 +10,11 @@ import { SolidLdoTransactionDataset } from "./SolidLdoTransactionDataset";
import type { ITransactionDatasetFactory } from "@ldo/subscribable-dataset"; import type { ITransactionDatasetFactory } from "@ldo/subscribable-dataset";
import type { SubjectNode } from "@ldo/rdf-utils"; import type { SubjectNode } from "@ldo/rdf-utils";
import type { Resource } from "./resource/Resource"; import type { Resource } from "./resource/Resource";
import type { CheckRootResultError } from "./requester/requests/checkRootContainer";
import type { NoRootContainerError } from "./requester/results/error/NoRootContainerError";
import type { ReadResultError } from "./requester/requests/readResource";
import { ProfileWithStorageShapeType } from "./.ldo/solid.shapeTypes";
import type { GetStorageContainerFromWebIdSuccess } from "./requester/results/success/CheckRootContainerSuccess";
/** /**
* A SolidLdoDataset has all the functionality of an LdoDataset with the added * A SolidLdoDataset has all the functionality of an LdoDataset with the added
@ -113,4 +118,52 @@ export class SolidLdoDataset extends LdoDataset {
startTransaction(linkedDataObject); startTransaction(linkedDataObject);
return linkedDataObject; return linkedDataObject;
} }
/**
* Gets a list of root storage containers for a user given their WebId
* @param webId: The webId for the user
* @returns A list of storages if successful, an error if not
* @example
* ```typescript
* const result = await solidLdoDataset
* .getStorageFromWebId("https://example.com/profile/card#me");
* if (result.isError) {
* // Do something
* }
* console.log(result.storageContainer[0].uri);
* ```
*/
async getStorageFromWebId(
webId: LeafUri,
): Promise<
| GetStorageContainerFromWebIdSuccess
| CheckRootResultError
| ReadResultError
| NoRootContainerError
> {
const webIdResource = this.getResource(webId);
const readResult = await webIdResource.readIfUnfetched();
if (readResult.isError) return readResult;
const profile = this.usingType(ProfileWithStorageShapeType).fromSubject(
webId,
);
if (profile.storage && profile.storage.length > 0) {
const containers = profile.storage.map((storageNode) =>
this.getResource(storageNode["@id"] as ContainerUri),
);
return {
type: "getStorageContainerFromWebIdSuccess",
isError: false,
storageContainers: containers,
};
}
const getContainerResult = await webIdResource.getRootContainer();
if (getContainerResult.type === "container")
return {
type: "getStorageContainerFromWebIdSuccess",
isError: false,
storageContainers: [getContainerResult],
};
return getContainerResult;
}
} }

@ -22,7 +22,7 @@ import type { SolidLdoTransactionDataset } from "./SolidLdoTransactionDataset";
* *
* const profile = solidLdoDataset * const profile = solidLdoDataset
* .using(ProfileShapeType) * .using(ProfileShapeType)
* .fromSubject("https://example.com/proifle#me"); * .fromSubject("https://example.com/profile#me");
* const resource = solidLdoDataset.getResource("https://example.com/profile"); * const resource = solidLdoDataset.getResource("https://example.com/profile");
* *
* const cProfile = changeData(profile, resource); * const cProfile = changeData(profile, resource);
@ -60,7 +60,7 @@ export function changeData<Type extends LdoBase>(
* *
* const profile = solidLdoDataset * const profile = solidLdoDataset
* .using(ProfileShapeType) * .using(ProfileShapeType)
* .fromSubject("https://example.com/proifle#me"); * .fromSubject("https://example.com/profile#me");
* const resource = solidLdoDataset.getResource("https://example.com/profile"); * const resource = solidLdoDataset.getResource("https://example.com/profile");
* *
* const cProfile = changeData(profile, resource); * const cProfile = changeData(profile, resource);

@ -1,6 +1,5 @@
import type { BasicRequestOptions } from "./requestOptions"; import type { BasicRequestOptions } from "./requestOptions";
import { parse as parseLinkHeader } from "http-link-header"; import { parse as parseLinkHeader } from "http-link-header";
import { NoncompliantPodError } from "../results/error/NoncompliantPodError";
import type { CheckRootContainerSuccess } from "../results/success/CheckRootContainerSuccess"; import type { CheckRootContainerSuccess } from "../results/success/CheckRootContainerSuccess";
import type { import type {
HttpErrorResultType, HttpErrorResultType,
@ -21,7 +20,6 @@ export type CheckRootResult = CheckRootContainerSuccess | CheckRootResultError;
*/ */
export type CheckRootResultError = export type CheckRootResultError =
| HttpErrorResultType | HttpErrorResultType
| NoncompliantPodError
| UnexpectedHttpError | UnexpectedHttpError
| UnexpectedResourceError; | UnexpectedResourceError;
@ -37,10 +35,15 @@ export type CheckRootResultError =
export function checkHeadersForRootContainer( export function checkHeadersForRootContainer(
uri: ContainerUri, uri: ContainerUri,
headers: Headers, headers: Headers,
): CheckRootContainerSuccess | NoncompliantPodError { ): CheckRootContainerSuccess {
const linkHeader = headers.get("link"); const linkHeader = headers.get("link");
if (!linkHeader) { if (!linkHeader) {
return new NoncompliantPodError(uri, "No link header present in request."); return {
uri,
isRootContainer: false,
type: "checkRootContainerSuccess",
isError: false,
};
} }
const parsedLinkHeader = parseLinkHeader(linkHeader); const parsedLinkHeader = parseLinkHeader(linkHeader);
const types = parsedLinkHeader.get("rel", "type"); const types = parsedLinkHeader.get("rel", "type");
@ -82,7 +85,10 @@ export async function checkRootContainer(
try { try {
const fetch = guaranteeFetch(options?.fetch); const fetch = guaranteeFetch(options?.fetch);
// Fetch options to determine the document type // Fetch options to determine the document type
const response = await fetch(uri, { method: "HEAD" }); // Note cache: "no-store": we don't want to depend on cached results because
// web browsers do not cache link headers
// https://github.com/CommunitySolidServer/CommunitySolidServer/issues/1959
const response = await fetch(uri, { method: "HEAD", cache: "no-store" });
const httpErrorResult = HttpErrorResult.checkResponse(uri, response); const httpErrorResult = HttpErrorResult.checkResponse(uri, response);
if (httpErrorResult) return httpErrorResult; if (httpErrorResult) return httpErrorResult;

@ -99,7 +99,9 @@ export async function readResource(
try { try {
const fetch = guaranteeFetch(options?.fetch); const fetch = guaranteeFetch(options?.fetch);
// Fetch options to determine the document type // Fetch options to determine the document type
const response = await fetch(uri); const response = await fetch(uri, {
headers: { accept: "text/turtle, */*" },
});
if (response.status === 404) { if (response.status === 404) {
return { return {
isError: false, isError: false,
@ -124,7 +126,7 @@ export async function readResource(
); );
} }
if (contentType === "text/turtle") { if (contentType.startsWith("text/turtle")) {
// Parse Turtle // Parse Turtle
const rawTurtle = await response.text(); const rawTurtle = await response.text();
if (options?.dataset) { if (options?.dataset) {
@ -137,7 +139,6 @@ export async function readResource(
} }
if (isContainerUri(uri)) { if (isContainerUri(uri)) {
const result = checkHeadersForRootContainer(uri, response.headers); const result = checkHeadersForRootContainer(uri, response.headers);
if (result.isError) return result;
return { return {
isError: false, isError: false,
type: "containerReadSuccess", type: "containerReadSuccess",

@ -0,0 +1,17 @@
import { ResourceError } from "./ErrorResult";
/**
* A NoncompliantPodError is returned when the server responded in a way that is
* not compliant with the Solid specification.
*/
export class NoRootContainerError extends ResourceError {
readonly type = "noRootContainerError" as const;
/**
* @param uri - the URI of the requested resource
* @param message - a custom message for the error
*/
constructor(uri: string) {
super(uri, `${uri} has not root container.`);
}
}

@ -1,4 +1,5 @@
import type { ResourceSuccess } from "./SuccessResult"; import type { Container } from "../../../resource/Container";
import type { ResourceSuccess, SuccessResult } from "./SuccessResult";
/** /**
* Indicates that the request to check if a resource is the root container was * Indicates that the request to check if a resource is the root container was
@ -11,3 +12,8 @@ export interface CheckRootContainerSuccess extends ResourceSuccess {
*/ */
isRootContainer: boolean; isRootContainer: boolean;
} }
export interface GetStorageContainerFromWebIdSuccess extends SuccessResult {
type: "getStorageContainerFromWebIdSuccess";
storageContainers: Container[];
}

@ -19,7 +19,6 @@ import type {
ReadResultError, ReadResultError,
} from "../requester/requests/readResource"; } from "../requester/requests/readResource";
import { AggregateError } from "../requester/results/error/ErrorResult"; import { AggregateError } from "../requester/results/error/ErrorResult";
import { NoncompliantPodError } from "../requester/results/error/NoncompliantPodError";
import type { DeleteSuccess } from "../requester/results/success/DeleteSuccess"; import type { DeleteSuccess } from "../requester/results/success/DeleteSuccess";
import type { AbsentReadSuccess } from "../requester/results/success/ReadSuccess"; import type { AbsentReadSuccess } from "../requester/results/success/ReadSuccess";
import type { ContainerReadSuccess } from "../requester/results/success/ReadSuccess"; import type { ContainerReadSuccess } from "../requester/results/success/ReadSuccess";
@ -31,6 +30,7 @@ import type { Leaf } from "./Leaf";
import type { SharedStatuses } from "./Resource"; import type { SharedStatuses } from "./Resource";
import { Resource } from "./Resource"; import { Resource } from "./Resource";
import type { ResourceResult } from "./resourceResult/ResourceResult"; import type { ResourceResult } from "./resourceResult/ResourceResult";
import { NoRootContainerError } from "../requester/results/error/NoRootContainerError";
/** /**
* Represents the current status of a specific container on a Pod as known by * Represents the current status of a specific container on a Pod as known by
@ -222,7 +222,8 @@ export class Container extends Resource {
/** /**
* Gets the root container of this container. If this container is the root * Gets the root container of this container. If this container is the root
* container, this function returns itself. * container, this function returns itself.
* @returns The root container for this container * @returns The root container for this container or undefined if there is no
* root container.
* *
* @example * @example
* Suppose the root container is at `https://example.com/` * Suppose the root container is at `https://example.com/`
@ -237,11 +238,13 @@ export class Container extends Resource {
* } * }
* ``` * ```
*/ */
async getRootContainer(): Promise<Container | CheckRootResultError> { async getRootContainer(): Promise<
Container | CheckRootResultError | NoRootContainerError
> {
const parentContainerResult = await this.getParentContainer(); const parentContainerResult = await this.getParentContainer();
if (parentContainerResult?.isError) return parentContainerResult; if (parentContainerResult?.isError) return parentContainerResult;
if (!parentContainerResult) { if (!parentContainerResult) {
return this; return this.isRootContainer() ? this : new NoRootContainerError(this.uri);
} }
return parentContainerResult.getRootContainer(); return parentContainerResult.getRootContainer();
} }
@ -277,10 +280,7 @@ export class Container extends Resource {
if (this.rootContainer) return undefined; if (this.rootContainer) return undefined;
const parentUri = getParentUri(this.uri); const parentUri = getParentUri(this.uri);
if (!parentUri) { if (!parentUri) {
return new NoncompliantPodError( return undefined;
this.uri,
`${this.uri} is not root does not have a parent container`,
);
} }
return this.context.resourceStore.get(parentUri); return this.context.resourceStore.get(parentUri);
} }

@ -23,6 +23,7 @@ import type { Container } from "./Container";
import type { SharedStatuses } from "./Resource"; import type { SharedStatuses } from "./Resource";
import { Resource } from "./Resource"; import { Resource } from "./Resource";
import type { ResourceResult } from "./resourceResult/ResourceResult"; import type { ResourceResult } from "./resourceResult/ResourceResult";
import type { NoRootContainerError } from "../requester/results/error/NoRootContainerError";
/** /**
* Represents the current status of a specific Leaf on a Pod as known by LDO. * Represents the current status of a specific Leaf on a Pod as known by LDO.
@ -77,7 +78,9 @@ export class Leaf extends Resource {
*/ */
constructor(uri: LeafUri, context: SolidLdoDatasetContext) { constructor(uri: LeafUri, context: SolidLdoDatasetContext) {
super(context); super(context);
this.uri = uri; const uriObject = new URL(uri);
uriObject.hash = "";
this.uri = uriObject.toString() as LeafUri;
this.requester = new LeafBatchedRequester(uri, context); this.requester = new LeafBatchedRequester(uri, context);
this.status = { isError: false, type: "unfetched", uri }; this.status = { isError: false, type: "unfetched", uri };
} }
@ -338,7 +341,12 @@ export class Leaf extends Resource {
* } * }
* ``` * ```
*/ */
async getRootContainer(): Promise<Container | CheckRootResultError> { async getRootContainer(): Promise<
Container | CheckRootResultError | NoRootContainerError
> {
// Check to see if this document has a pim:storage if so, use that
// If not, traverse the tree
const parent = await this.getParentContainer(); const parent = await this.getParentContainer();
return parent.getRootContainer(); return parent.getRootContainer();
} }

@ -32,6 +32,7 @@ import { getWacRuleWithAclUri, type GetWacRuleResult } from "./wac/getWacRule";
import { NoncompliantPodError } from "../requester/results/error/NoncompliantPodError"; import { NoncompliantPodError } from "../requester/results/error/NoncompliantPodError";
import { setWacRuleForAclUri, type SetWacRuleResult } from "./wac/setWacRule"; import { setWacRuleForAclUri, type SetWacRuleResult } from "./wac/setWacRule";
import type { LeafUri } from "../util/uriTypes"; import type { LeafUri } from "../util/uriTypes";
import type { NoRootContainerError } from "../requester/results/error/NoRootContainerError";
/** /**
* Statuses shared between both Leaf and Container * Statuses shared between both Leaf and Container
@ -524,7 +525,9 @@ export abstract class Resource extends (EventEmitter as new () => TypedEmitter<{
* } * }
* ``` * ```
*/ */
abstract getRootContainer(): Promise<Container | CheckRootResultError>; abstract getRootContainer(): Promise<
Container | CheckRootResultError | NoRootContainerError
>;
abstract getParentContainer(): Promise< abstract getParentContainer(): Promise<
Container | CheckRootResultError | undefined Container | CheckRootResultError | undefined

@ -8,12 +8,7 @@ import type {
UpdateResultError, UpdateResultError,
} from "../src"; } from "../src";
import { changeData, commitData, createSolidLdoDataset } from "../src"; import { changeData, commitData, createSolidLdoDataset } from "../src";
import { import { ROOT_CONTAINER, WEB_ID, createApp } from "./solidServer.helper";
ROOT_CONTAINER,
WEB_ID,
createApp,
getAuthenticatedFetch,
} from "./solidServer.helper";
import { import {
namedNode, namedNode,
quad as createQuad, quad as createQuad,
@ -45,6 +40,8 @@ import type {
import type { NoncompliantPodError } from "../src/requester/results/error/NoncompliantPodError"; import type { NoncompliantPodError } from "../src/requester/results/error/NoncompliantPodError";
import type { GetWacRuleSuccess } from "../src/resource/wac/results/GetWacRuleSuccess"; import type { GetWacRuleSuccess } from "../src/resource/wac/results/GetWacRuleSuccess";
import type { WacRule } from "../src/resource/wac/WacRule"; import type { WacRule } from "../src/resource/wac/WacRule";
import type { GetStorageContainerFromWebIdSuccess } from "../src/requester/results/success/CheckRootContainerSuccess";
import { generateAuthFetch } from "./authFetch.helper";
const TEST_CONTAINER_SLUG = "test_ldo/"; const TEST_CONTAINER_SLUG = "test_ldo/";
const TEST_CONTAINER_URI = const TEST_CONTAINER_URI =
@ -58,6 +55,7 @@ const SAMPLE2_BINARY_URI =
`${TEST_CONTAINER_URI}${SAMPLE2_BINARY_SLUG}` as LeafUri; `${TEST_CONTAINER_URI}${SAMPLE2_BINARY_SLUG}` as LeafUri;
const SAMPLE_CONTAINER_URI = const SAMPLE_CONTAINER_URI =
`${TEST_CONTAINER_URI}sample_container/` as ContainerUri; `${TEST_CONTAINER_URI}sample_container/` as ContainerUri;
const SAMPLE_PROFILE_URI = `${TEST_CONTAINER_URI}profile.ttl` as LeafUri;
const SPIDER_MAN_TTL = `@base <http://example.org/> . const SPIDER_MAN_TTL = `@base <http://example.org/> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@ -98,6 +96,11 @@ const TEST_CONTAINER_ACL = `<#b30e3fd1-b5a8-4763-ad9d-e95de9cf7933> a <http://ww
<http://www.w3.org/ns/auth/acl#mode> <http://www.w3.org/ns/auth/acl#Read>, <http://www.w3.org/ns/auth/acl#Write>, <http://www.w3.org/ns/auth/acl#Append>, <http://www.w3.org/ns/auth/acl#Control>; <http://www.w3.org/ns/auth/acl#mode> <http://www.w3.org/ns/auth/acl#Read>, <http://www.w3.org/ns/auth/acl#Write>, <http://www.w3.org/ns/auth/acl#Append>, <http://www.w3.org/ns/auth/acl#Control>;
<http://www.w3.org/ns/auth/acl#agent> <${WEB_ID}>; <http://www.w3.org/ns/auth/acl#agent> <${WEB_ID}>;
<http://www.w3.org/ns/auth/acl#agentClass> <http://xmlns.com/foaf/0.1/Agent>, <http://www.w3.org/ns/auth/acl#AuthenticatedAgent>.`; <http://www.w3.org/ns/auth/acl#agentClass> <http://xmlns.com/foaf/0.1/Agent>, <http://www.w3.org/ns/auth/acl#AuthenticatedAgent>.`;
const SAMPLE_PROFILE_TTL = `
@prefix pim: <http://www.w3.org/ns/pim/space#> .
<${SAMPLE_PROFILE_URI}> pim:storage <https://example.com/A/>, <https://example.com/B/> .
`;
async function testRequestLoads<ReturnVal>( async function testRequestLoads<ReturnVal>(
request: () => Promise<ReturnVal>, request: () => Promise<ReturnVal>,
@ -150,16 +153,25 @@ describe("Integration", () => {
>; >;
let solidLdoDataset: SolidLdoDataset; let solidLdoDataset: SolidLdoDataset;
let previousJestId: string | undefined;
let previousNodeEnv: string | undefined;
beforeAll(async () => { beforeAll(async () => {
// Remove Jest ID so that community solid server doesn't use the Jest Import
previousJestId = process.env.JEST_WORKER_ID;
previousNodeEnv = process.env.NODE_ENV;
delete process.env.JEST_WORKER_ID;
process.env.NODE_ENV = "other_test";
// Start up the server // Start up the server
app = await createApp(); app = await createApp();
await app.start(); await app.start();
authFetch = await getAuthenticatedFetch(); authFetch = await generateAuthFetch();
}); });
afterAll(async () => { afterAll(async () => {
app.stop(); app.stop();
process.env.JEST_WORKER_ID = previousJestId;
process.env.NODE_ENV = previousNodeEnv;
}); });
beforeEach(async () => { beforeEach(async () => {
@ -191,6 +203,11 @@ describe("Integration", () => {
headers: { "content-type": "text/plain", slug: "sample.txt" }, headers: { "content-type": "text/plain", slug: "sample.txt" },
body: "some text.", body: "some text.",
}), }),
authFetch(TEST_CONTAINER_URI, {
method: "POST",
headers: { "content-type": "text/turtle", slug: "profile.ttl" },
body: SAMPLE_PROFILE_TTL,
}),
]); ]);
}); });
@ -208,6 +225,9 @@ describe("Integration", () => {
authFetch(SAMPLE2_BINARY_URI, { authFetch(SAMPLE2_BINARY_URI, {
method: "DELETE", method: "DELETE",
}), }),
authFetch(SAMPLE_PROFILE_URI, {
method: "DELETE",
}),
authFetch(SAMPLE_CONTAINER_URI, { authFetch(SAMPLE_CONTAINER_URI, {
method: "DELETE", method: "DELETE",
}), }),
@ -217,6 +237,18 @@ describe("Integration", () => {
}); });
}); });
/**
* General
*/
describe("General", () => {
it("Does not include the hash when creating a resource", () => {
const resource = solidLdoDataset.getResource(
"https://example.com/thing#hash",
);
expect(resource.uri).toBe("https://example.com/thing");
});
});
/** /**
* Read * Read
*/ */
@ -271,7 +303,7 @@ describe("Integration", () => {
isDoingInitialFetch: true, isDoingInitialFetch: true,
}); });
expect(result.type).toBe("containerReadSuccess"); expect(result.type).toBe("containerReadSuccess");
expect(resource.children().length).toBe(2); expect(resource.children().length).toBe(3);
}); });
it("Reads a binary leaf", async () => { it("Reads a binary leaf", async () => {
@ -385,6 +417,24 @@ describe("Integration", () => {
); );
}); });
it("Parses Turtle even when the content type contains parameters", async () => {
fetchMock.mockResolvedValueOnce(
new Response(SPIDER_MAN_TTL, {
status: 200,
headers: new Headers({ "content-type": "text/turtle;charset=utf-8" }),
}),
);
const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI);
const result = await testRequestLoads(() => resource.read(), resource, {
isLoading: true,
isReading: true,
isDoingInitialFetch: true,
});
expect(result.isError).toBe(false);
if (result.isError) return;
expect(result.type).toBe("dataReadSuccess");
});
it("Returns an UnexpectedResourceError if an unknown error is triggered", async () => { it("Returns an UnexpectedResourceError if an unknown error is triggered", async () => {
fetchMock.mockRejectedValueOnce(new Error("Something happened.")); fetchMock.mockRejectedValueOnce(new Error("Something happened."));
const resource = solidLdoDataset.getResource(SAMPLE2_DATA_URI); const resource = solidLdoDataset.getResource(SAMPLE2_DATA_URI);
@ -399,7 +449,7 @@ describe("Integration", () => {
expect(result.message).toBe("Something happened."); expect(result.message).toBe("Something happened.");
}); });
it("Returns an error if there is no link header for a container request", async () => { it("Does not return an error if there is no link header for a container request", async () => {
fetchMock.mockResolvedValueOnce( fetchMock.mockResolvedValueOnce(
new Response(TEST_CONTAINER_TTL, { new Response(TEST_CONTAINER_TTL, {
status: 200, status: 200,
@ -412,12 +462,9 @@ describe("Integration", () => {
isReading: true, isReading: true,
isDoingInitialFetch: true, isDoingInitialFetch: true,
}); });
expect(result.isError).toBe(true); expect(result.isError).toBe(false);
if (!result.isError) return; if (result.isError) return;
expect(result.type).toBe("noncompliantPodError"); expect(result.resource.isRootContainer()).toBe(false);
expect(result.message).toMatch(
/\Response from .* is not compliant with the Solid Specification: No link header present in request\./,
);
}); });
it("knows nothing about a leaf resource if it is not fetched", () => { it("knows nothing about a leaf resource if it is not fetched", () => {
@ -470,7 +517,7 @@ describe("Integration", () => {
}, },
); );
expect(result.type).toBe("containerReadSuccess"); expect(result.type).toBe("containerReadSuccess");
expect(resource.children().length).toBe(2); expect(resource.children().length).toBe(3);
}); });
it("reads an unfetched leaf", async () => { it("reads an unfetched leaf", async () => {
@ -501,7 +548,7 @@ describe("Integration", () => {
const result = await resource.readIfUnfetched(); const result = await resource.readIfUnfetched();
expect(fetchMock).not.toHaveBeenCalled(); expect(fetchMock).not.toHaveBeenCalled();
expect(result.type).toBe("containerReadSuccess"); expect(result.type).toBe("containerReadSuccess");
expect(resource.children().length).toBe(2); expect(resource.children().length).toBe(3);
}); });
it("returns a cached existing data leaf", async () => { it("returns a cached existing data leaf", async () => {
@ -559,7 +606,19 @@ describe("Integration", () => {
expect(result.isRootContainer()).toBe(true); expect(result.isRootContainer()).toBe(true);
}); });
it("Returns an error if there is no link header for a container request", async () => { it("Returns an error if there is no root container", async () => {
fetchMock.mockResolvedValueOnce(
new Response(TEST_CONTAINER_TTL, {
status: 200,
headers: new Headers({ "content-type": "text/turtle" }),
}),
);
fetchMock.mockResolvedValueOnce(
new Response(TEST_CONTAINER_TTL, {
status: 200,
headers: new Headers({ "content-type": "text/turtle" }),
}),
);
fetchMock.mockResolvedValueOnce( fetchMock.mockResolvedValueOnce(
new Response(TEST_CONTAINER_TTL, { new Response(TEST_CONTAINER_TTL, {
status: 200, status: 200,
@ -570,10 +629,8 @@ describe("Integration", () => {
const result = await resource.getRootContainer(); const result = await resource.getRootContainer();
expect(result.isError).toBe(true); expect(result.isError).toBe(true);
if (!result.isError) return; if (!result.isError) return;
expect(result.type).toBe("noncompliantPodError"); expect(result.type).toBe("noRootContainerError");
expect(result.message).toMatch( expect(result.message).toMatch(/\.* has not root container\./);
/\Response from .* is not compliant with the Solid Specification: No link header present in request\./,
);
}); });
it("An error to be returned if a common http error is encountered", async () => { it("An error to be returned if a common http error is encountered", async () => {
@ -611,7 +668,47 @@ describe("Integration", () => {
const resource = solidLdoDataset.getResource(ROOT_CONTAINER); const resource = solidLdoDataset.getResource(ROOT_CONTAINER);
const result = await resource.getRootContainer(); const result = await resource.getRootContainer();
expect(result.isError).toBe(true); expect(result.isError).toBe(true);
expect(result.type).toBe("noncompliantPodError"); expect(result.type).toBe("noRootContainerError");
});
});
/**
* Get Storage From WebId
*/
describe("getStorageFromWebId", () => {
it("Gets storage when a pim:storage field isn't present", async () => {
const result = await solidLdoDataset.getStorageFromWebId(SAMPLE_DATA_URI);
expect(result.type).toBe("getStorageContainerFromWebIdSuccess");
const realResult = result as GetStorageContainerFromWebIdSuccess;
expect(realResult.storageContainers.length).toBe(1);
expect(realResult.storageContainers[0].uri).toBe(ROOT_CONTAINER);
});
it("Gets storage when a pim:storage field is present", async () => {
const result =
await solidLdoDataset.getStorageFromWebId(SAMPLE_PROFILE_URI);
expect(result.type).toBe("getStorageContainerFromWebIdSuccess");
const realResult = result as GetStorageContainerFromWebIdSuccess;
expect(realResult.storageContainers.length).toBe(2);
expect(realResult.storageContainers[0].uri).toBe(
"https://example.com/A/",
);
expect(realResult.storageContainers[1].uri).toBe(
"https://example.com/B/",
);
});
it("Passes any errors returned from the read method", async () => {
fetchMock.mockRejectedValueOnce(new Error("Something happened."));
const result = await solidLdoDataset.getStorageFromWebId(SAMPLE_DATA_URI);
expect(result.isError).toBe(true);
});
it("Passes any errors returned from the getRootContainer method", async () => {
fetchMock.mockResolvedValueOnce(new Response(""));
fetchMock.mockRejectedValueOnce(new Error("Something happened."));
const result = await solidLdoDataset.getStorageFromWebId(SAMPLE_DATA_URI);
expect(result.isError).toBe(true);
}); });
}); });

@ -0,0 +1,112 @@
import type { KeyPair } from "@inrupt/solid-client-authn-core";
import {
buildAuthenticatedFetch,
createDpopHeader,
generateDpopKeyPair,
} from "@inrupt/solid-client-authn-core";
import fetch from "cross-fetch";
const config = {
podName: process.env.USER_NAME || "example",
email: process.env.EMAIL || "hello@example.com",
password: process.env.PASSWORD || "abc123",
};
async function getAuthorization(): Promise<string> {
// First we request the account API controls to find out where we can log in
const indexResponse = await fetch("http://localhost:3001/.account/");
const { controls } = await indexResponse.json();
// And then we log in to the account API
const response = await fetch(controls.password.login, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
email: config.email,
password: config.password,
}),
});
// This authorization value will be used to authenticate in the next step
const result = await response.json();
return result.authorization;
}
async function getSecret(
authorization: string,
): Promise<{ id: string; secret: string; resource: string }> {
// Now that we are logged in, we need to request the updated controls from the server.
// These will now have more values than in the previous example.
const indexResponse = await fetch("http://localhost:3001/.account/", {
headers: { authorization: `CSS-Account-Token ${authorization}` },
});
const { controls } = await indexResponse.json();
// Here we request the server to generate a token on our account
const response = await fetch(controls.account.clientCredentials, {
method: "POST",
headers: {
authorization: `CSS-Account-Token ${authorization}`,
"content-type": "application/json",
},
// The name field will be used when generating the ID of your token.
// The WebID field determines which WebID you will identify as when using the token.
// Only WebIDs linked to your account can be used.
body: JSON.stringify({
name: "my-token",
webId: `http://localhost:3001/${config.podName}/profile/card#me`,
}),
});
// These are the identifier and secret of your token.
// Store the secret somewhere safe as there is no way to request it again from the server!
// The `resource` value can be used to delete the token at a later point in time.
const response2 = await response.json();
return response2;
}
async function getAccessToken(
id: string,
secret: string,
): Promise<{ accessToken: string; dpopKey: KeyPair }> {
try {
// A key pair is needed for encryption.
// This function from `solid-client-authn` generates such a pair for you.
const dpopKey = await generateDpopKeyPair();
// These are the ID and secret generated in the previous step.
// Both the ID and the secret need to be form-encoded.
const authString = `${encodeURIComponent(id)}:${encodeURIComponent(
secret,
)}`;
// This URL can be found by looking at the "token_endpoint" field at
// http://localhost:3001/.well-known/openid-configuration
// if your server is hosted at http://localhost:3000/.
const tokenUrl = "http://localhost:3001/.oidc/token";
const response = await fetch(tokenUrl, {
method: "POST",
headers: {
// The header needs to be in base64 encoding.
authorization: `Basic ${Buffer.from(authString).toString("base64")}`,
"content-type": "application/x-www-form-urlencoded",
dpop: await createDpopHeader(tokenUrl, "POST", dpopKey),
},
body: "grant_type=client_credentials&scope=webid",
});
// This is the Access token that will be used to do an authenticated request to the server.
// The JSON also contains an "expires_in" field in seconds,
// which you can use to know when you need request a new Access token.
const response2 = await response.json();
return { accessToken: response2.access_token, dpopKey };
} catch (err) {
console.error(err);
throw err;
}
}
export async function generateAuthFetch() {
const authorization = await getAuthorization();
const { id, secret } = await getSecret(authorization);
const { accessToken, dpopKey } = await getAccessToken(id, secret);
return await buildAuthenticatedFetch(accessToken, { dpopKey });
}

@ -0,0 +1,43 @@
{
"@context": [
"https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^7.0.0/components/context.jsonld"
],
"import": [
"css:config/app/init/static-root.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/pod.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 on disk and uses WAC for authorization."
}
]
}

@ -1,7 +1,9 @@
[ [
{ {
"podName": "example",
"email": "hello@example.com", "email": "hello@example.com",
"password": "abc123" "password": "abc123",
"pods": [
{ "name": "example" }
]
} }
] ]

@ -1,24 +1,9 @@
// Taken from https://github.com/comunica/comunica/blob/b237be4265c353a62a876187d9e21e3bc05123a3/engines/query-sparql/test/QuerySparql-solid-test.ts#L9 // Taken from https://github.com/comunica/comunica/blob/b237be4265c353a62a876187d9e21e3bc05123a3/engines/query-sparql/test/QuerySparql-solid-test.ts#L9
import * as path from "path"; import * as path from "path";
import type { KeyPair } from "@inrupt/solid-client-authn-core";
import {
buildAuthenticatedFetch,
createDpopHeader,
generateDpopKeyPair,
} from "@inrupt/solid-client-authn-core";
import type { App } from "@solid/community-server"; import type { App } from "@solid/community-server";
import { AppRunner, resolveModulePath } from "@solid/community-server"; import { AppRunner, resolveModulePath } from "@solid/community-server";
import "jest-rdf"; import "jest-rdf";
import fetch from "cross-fetch";
const config = [
{
podName: process.env.USER_NAME || "example",
email: process.env.EMAIL || "hello@example.com",
password: process.env.PASSWORD || "abc123",
},
];
export const SERVER_DOMAIN = process.env.SERVER || "http://localhost:3001/"; export const SERVER_DOMAIN = process.env.SERVER || "http://localhost:3001/";
export const ROOT_ROUTE = process.env.ROOT_CONTAINER || ""; export const ROOT_ROUTE = process.env.ROOT_CONTAINER || "";
@ -37,88 +22,18 @@ export async function createApp(): Promise<App> {
} as App; } as App;
} }
const appRunner = new AppRunner(); const appRunner = new AppRunner();
return appRunner.create(
{ return appRunner.create({
loaderProperties: {
mainModulePath: resolveModulePath(""), mainModulePath: resolveModulePath(""),
typeChecking: false, typeChecking: false,
}, },
resolveModulePath("config/default.json"), config: resolveModulePath("config/default.json"),
{}, variableBindings: {},
{ shorthand: {
port: 3_001, port: 3_001,
loggingLevel: "off", loggingLevel: "off",
seededPodConfigJson: path.join( seedConfig: path.join(__dirname, "configs", "solid-css-seed.json"),
__dirname,
"configs",
"solid-css-seed.json",
),
}, },
);
}
export interface ISecretData {
id: string;
secret: string;
}
// From https://communitysolidserver.github.io/CommunitySolidServer/5.x/usage/client-credentials/
export async function getSecret(): Promise<ISecretData> {
const result = await fetch(`${SERVER_DOMAIN}idp/credentials/`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
email: config[0].email,
password: config[0].password,
name: config[0].podName,
}),
});
const json = await result.json();
return json;
}
export interface ITokenData {
accessToken: string;
dpopKey: KeyPair;
}
// From https://communitysolidserver.github.io/CommunitySolidServer/5.x/usage/client-credentials/
export async function refreshToken({
id,
secret,
}: ISecretData): Promise<ITokenData> {
const dpopKey = await generateDpopKeyPair();
const authString = `${encodeURIComponent(id)}:${encodeURIComponent(secret)}`;
const tokenUrl = `${SERVER_DOMAIN}.oidc/token`;
const accessToken = await fetch(tokenUrl, {
method: "POST",
headers: {
// The header needs to be in base64 encoding.
authorization: `Basic ${Buffer.from(authString).toString("base64")}`,
"content-type": "application/x-www-form-urlencoded",
dpop: await createDpopHeader(tokenUrl, "POST", dpopKey),
},
body: "grant_type=client_credentials&scope=webid",
})
.then((res) => res.json())
.then((res) => res.access_token);
return { accessToken, dpopKey };
}
export async function getAuthenticatedFetch() {
// Generate secret
const secret = await getSecret();
if (!secret) throw new Error("No Secret");
// Get token
const token = await refreshToken(secret);
if (!token) throw new Error("No Token");
// Build authenticated fetch
const authFetch = await buildAuthenticatedFetch(fetch, token.accessToken, {
dpopKey: token.dpopKey,
}); });
return authFetch;
} }

@ -86,7 +86,7 @@ async function run(): Promise<void> {
{ {
baseIRI: baseIRI:
"https://jackson.solidcommunity.net/IndividualChats/jackson.solidcommunity.net/index.ttl#", "https://jackson.solidcommunity.net/IndividualChats/jackson.solidcommunity.net/index.ttl#",
format: "application/json-ld", format: "application/ld+json",
} }
); );
// Returns true because the input data describes the same triple. // Returns true because the input data describes the same triple.

@ -1,6 +1,6 @@
{ {
"name": "@ldo/subscribable-dataset", "name": "@ldo/subscribable-dataset",
"version": "0.0.1-alpha.23", "version": "0.0.1-alpha.24",
"description": "An RDFJS dataset implementation that can be subscribed to for updates", "description": "An RDFJS dataset implementation that can be subscribed to for updates",
"main": "dist/index.js", "main": "dist/index.js",
"scripts": { "scripts": {
@ -33,8 +33,8 @@
"ts-node": "^9.1.1" "ts-node": "^9.1.1"
}, },
"dependencies": { "dependencies": {
"@ldo/dataset": "^0.0.1-alpha.23", "@ldo/dataset": "^0.0.1-alpha.24",
"@ldo/rdf-utils": "^0.0.1-alpha.23" "@ldo/rdf-utils": "^0.0.1-alpha.24"
}, },
"files": [ "files": [
"dist", "dist",

@ -11,7 +11,7 @@ describe("createExtendedDatasetFromSerializedInput", () => {
const dataset = await serializedToSubscribableDataset( const dataset = await serializedToSubscribableDataset(
JSON.stringify(jsonLdData), JSON.stringify(jsonLdData),
{ {
format: "application/json-ld", format: "application/ld+json",
}, },
); );
expect(dataset.size).toBe(9); expect(dataset.size).toBe(9);

@ -1,6 +1,6 @@
{ {
"name": "@ldo/traverser-shexj", "name": "@ldo/traverser-shexj",
"version": "0.0.1-alpha.23", "version": "0.0.1-alpha.24",
"description": "A type-traverser for ShexJ", "description": "A type-traverser for ShexJ",
"main": "dist/index.js", "main": "dist/index.js",
"scripts": { "scripts": {
@ -30,7 +30,7 @@
"src" "src"
], ],
"dependencies": { "dependencies": {
"@ldo/type-traverser": "^0.0.1-alpha.23" "@ldo/type-traverser": "^0.0.1-alpha.24"
}, },
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"

@ -1,6 +1,6 @@
{ {
"name": "@ldo/type-traverser", "name": "@ldo/type-traverser",
"version": "0.0.1-alpha.23", "version": "0.0.1-alpha.24",
"description": "An organized way to traverse over objects using typescript", "description": "An organized way to traverse over objects using typescript",
"main": "dist/index.js", "main": "dist/index.js",
"scripts": { "scripts": {

Loading…
Cancel
Save