diff --git a/packages/solid-react/.eslintrc b/packages/react/.eslintrc similarity index 100% rename from packages/solid-react/.eslintrc rename to packages/react/.eslintrc diff --git a/packages/solid-react/.gitignore b/packages/react/.gitignore similarity index 100% rename from packages/solid-react/.gitignore rename to packages/react/.gitignore diff --git a/packages/solid-react/LICENSE.txt b/packages/react/LICENSE.txt similarity index 100% rename from packages/solid-react/LICENSE.txt rename to packages/react/LICENSE.txt diff --git a/packages/solid-react/README.md b/packages/react/README.md similarity index 73% rename from packages/solid-react/README.md rename to packages/react/README.md index 51c51f9..1eb1bbf 100644 --- a/packages/solid-react/README.md +++ b/packages/react/README.md @@ -1,6 +1,6 @@ -# @ldo/solid-react +# @ldo/react -`@ldo/solid-react` provides tool and hooks for easily building Solid applications using react. +`@ldo/react` provides tool and hooks for easily building Solid applications using react. ## Guide @@ -17,7 +17,7 @@ npx run @ldo/cli init Now install the @ldo/solid library ``` -npm i @ldo/solid @ldo/solid-react +npm i @ldo/solid @ldo/react ```
@@ -28,13 +28,13 @@ Manual Installation If you already have generated ShapeTypes, you may install the `@ldo/ldo` and `@ldo/solid` libraries independently. ``` -npm i @ldo/ldo @ldo/solid @ldo/solid-react +npm i @ldo/ldo @ldo/solid @ldo/react ```
## Simple Example -Below is a simple example of @ldo/solid-react in a real use-case. Assume that a ShapeType was previously generated and placed at `./.ldo/solidProfile.shapeTypess`. +Below is a simple example of @ldo/react in a real use-case. Assume that a ShapeType was previously generated and placed at `./.ldo/solidProfile.shapeTypess`. ```typescript @@ -45,7 +45,7 @@ import { useResource, useSolidAuth, useSubject, -} from "@ldo/solid-react"; +} from "@ldo/react"; import { SolidProfileShapeShapeType } from "./.ldo/solidProfile.shapeTypes"; import { changeData, commitData } from "@ldo/solid"; @@ -115,18 +115,18 @@ export default App; Providers - - [BrowserSolidLdoProvider](https://ldo.js.org/latest/api/solid-react/BrowserSolidLdoProvider/) - - [SolidLdoProvider](https://ldo.js.org/latest/api/solid-react/SolidLdoProvider/) + - [BrowserSolidLdoProvider](https://ldo.js.org/latest/api/react/BrowserSolidLdoProvider/) + - [SolidLdoProvider](https://ldo.js.org/latest/api/react/SolidLdoProvider/) Hooks - - [useLdo](https://ldo.js.org/latest/api/solid-react/useLdo/) - - [useResource](https://ldo.js.org/latest/api/solid-react/useResource/) - - [useRootContainer](https://ldo.js.org/latest/api/solid-react/useRootContainer/) - - [useSolidAuth](https://ldo.js.org/latest/api/solid-react/useSolidAuth/) - - [useSubject](https://ldo.js.org/latest/api/solid-react/useSubject/) - - [useMatchSubject](https://ldo.js.org/latest/api/solid-react/useMatchSubject/) - - [useMatchObject](https://ldo.js.org/latest/api/solid-react/useMatchSubject/) - - [useSubscribeToResource](https://ldo.js.org/latest/api/solid-react/useMatchSubject/) + - [useLdo](https://ldo.js.org/latest/api/react/useLdo/) + - [useResource](https://ldo.js.org/latest/api/react/useResource/) + - [useRootContainer](https://ldo.js.org/latest/api/react/useRootContainer/) + - [useSolidAuth](https://ldo.js.org/latest/api/react/useSolidAuth/) + - [useSubject](https://ldo.js.org/latest/api/react/useSubject/) + - [useMatchSubject](https://ldo.js.org/latest/api/react/useMatchSubject/) + - [useMatchObject](https://ldo.js.org/latest/api/react/useMatchSubject/) + - [useSubscribeToResource](https://ldo.js.org/latest/api/react/useMatchSubject/) ## 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/). diff --git a/packages/solid-react/jest.config.js b/packages/react/jest.config.js similarity index 100% rename from packages/solid-react/jest.config.js rename to packages/react/jest.config.js diff --git a/packages/solid-react/jest.setup.ts b/packages/react/jest.setup.ts similarity index 100% rename from packages/solid-react/jest.setup.ts rename to packages/react/jest.setup.ts diff --git a/packages/solid-react/package.json b/packages/react/package.json similarity index 96% rename from packages/solid-react/package.json rename to packages/react/package.json index 0f38c26..51dddff 100644 --- a/packages/solid-react/package.json +++ b/packages/react/package.json @@ -1,5 +1,5 @@ { - "name": "@ldo/solid-react", + "name": "@ldo/react", "version": "1.0.0-alpha.1", "description": "A React library for LDO and Solid", "main": "dist/index.js", @@ -40,7 +40,7 @@ "@ldo/dataset": "^1.0.0-alpha.1", "@ldo/jsonld-dataset-proxy": "^1.0.0-alpha.1", "@ldo/ldo": "^1.0.0-alpha.1", - "@ldo/solid": "^1.0.0-alpha.1", + "@ldo/connected": "^1.0.0-alpha.1", "@ldo/subscribable-dataset": "^1.0.0-alpha.1", "@rdfjs/data-model": "^1.2.0", "cross-fetch": "^3.1.6" diff --git a/packages/solid-react/src/BrowserSolidLdoProvider.tsx b/packages/react/src/BrowserSolidLdoProvider.tsx similarity index 100% rename from packages/solid-react/src/BrowserSolidLdoProvider.tsx rename to packages/react/src/BrowserSolidLdoProvider.tsx diff --git a/packages/solid-react/src/SolidAuthContext.ts b/packages/react/src/SolidAuthContext.ts similarity index 100% rename from packages/solid-react/src/SolidAuthContext.ts rename to packages/react/src/SolidAuthContext.ts diff --git a/packages/solid-react/src/SolidLdoProvider.tsx b/packages/react/src/SolidLdoProvider.tsx similarity index 100% rename from packages/solid-react/src/SolidLdoProvider.tsx rename to packages/react/src/SolidLdoProvider.tsx diff --git a/packages/solid-react/src/UnauthenticatedSolidLdoProvider.tsx b/packages/react/src/UnauthenticatedSolidLdoProvider.tsx similarity index 100% rename from packages/solid-react/src/UnauthenticatedSolidLdoProvider.tsx rename to packages/react/src/UnauthenticatedSolidLdoProvider.tsx diff --git a/packages/solid-react/src/index.ts b/packages/react/src/index.ts similarity index 100% rename from packages/solid-react/src/index.ts rename to packages/react/src/index.ts diff --git a/packages/solid-react/src/useLdoMethods.ts b/packages/react/src/useLdoMethods.ts similarity index 100% rename from packages/solid-react/src/useLdoMethods.ts rename to packages/react/src/useLdoMethods.ts diff --git a/packages/solid-react/src/useMatchObject.ts b/packages/react/src/useMatchObject.ts similarity index 100% rename from packages/solid-react/src/useMatchObject.ts rename to packages/react/src/useMatchObject.ts diff --git a/packages/solid-react/src/useMatchSubject.ts b/packages/react/src/useMatchSubject.ts similarity index 100% rename from packages/solid-react/src/useMatchSubject.ts rename to packages/react/src/useMatchSubject.ts diff --git a/packages/solid-react/src/useResource.ts b/packages/react/src/useResource.ts similarity index 100% rename from packages/solid-react/src/useResource.ts rename to packages/react/src/useResource.ts diff --git a/packages/solid-react/src/useRootContainer.ts b/packages/react/src/useRootContainer.ts similarity index 100% rename from packages/solid-react/src/useRootContainer.ts rename to packages/react/src/useRootContainer.ts diff --git a/packages/solid-react/src/useSubject.ts b/packages/react/src/useSubject.ts similarity index 100% rename from packages/solid-react/src/useSubject.ts rename to packages/react/src/useSubject.ts diff --git a/packages/solid-react/src/useSubscribeToResource.ts b/packages/react/src/useSubscribeToResource.ts similarity index 100% rename from packages/solid-react/src/useSubscribeToResource.ts rename to packages/react/src/useSubscribeToResource.ts diff --git a/packages/solid-react/src/util/TrackingProxyContext.ts b/packages/react/src/util/TrackingProxyContext.ts similarity index 100% rename from packages/solid-react/src/util/TrackingProxyContext.ts rename to packages/react/src/util/TrackingProxyContext.ts diff --git a/packages/solid-react/src/util/TrackingSetProxy.ts b/packages/react/src/util/TrackingSetProxy.ts similarity index 100% rename from packages/solid-react/src/util/TrackingSetProxy.ts rename to packages/react/src/util/TrackingSetProxy.ts diff --git a/packages/solid-react/src/util/TrackingSubjectProxy.ts b/packages/react/src/util/TrackingSubjectProxy.ts similarity index 100% rename from packages/solid-react/src/util/TrackingSubjectProxy.ts rename to packages/react/src/util/TrackingSubjectProxy.ts diff --git a/packages/solid-react/src/util/useTrackingProxy.ts b/packages/react/src/util/useTrackingProxy.ts similarity index 100% rename from packages/solid-react/src/util/useTrackingProxy.ts rename to packages/react/src/util/useTrackingProxy.ts diff --git a/packages/solid-react/test/.ldo/post.context.ts b/packages/react/test/.ldo/post.context.ts similarity index 100% rename from packages/solid-react/test/.ldo/post.context.ts rename to packages/react/test/.ldo/post.context.ts diff --git a/packages/solid-react/test/.ldo/post.schema.ts b/packages/react/test/.ldo/post.schema.ts similarity index 100% rename from packages/solid-react/test/.ldo/post.schema.ts rename to packages/react/test/.ldo/post.schema.ts diff --git a/packages/solid-react/test/.ldo/post.shapeTypes.ts b/packages/react/test/.ldo/post.shapeTypes.ts similarity index 100% rename from packages/solid-react/test/.ldo/post.shapeTypes.ts rename to packages/react/test/.ldo/post.shapeTypes.ts diff --git a/packages/solid-react/test/.ldo/post.typings.ts b/packages/react/test/.ldo/post.typings.ts similarity index 100% rename from packages/solid-react/test/.ldo/post.typings.ts rename to packages/react/test/.ldo/post.typings.ts diff --git a/packages/solid-react/test/Integration.test.tsx b/packages/react/test/Integration.test.tsx similarity index 100% rename from packages/solid-react/test/Integration.test.tsx rename to packages/react/test/Integration.test.tsx diff --git a/packages/solid-react/test/setUpServer.ts b/packages/react/test/setUpServer.ts similarity index 100% rename from packages/solid-react/test/setUpServer.ts rename to packages/react/test/setUpServer.ts diff --git a/packages/solid-react/test/test-server/configs/components-config/unauthenticatedServer.json b/packages/react/test/test-server/configs/components-config/unauthenticatedServer.json similarity index 100% rename from packages/solid-react/test/test-server/configs/components-config/unauthenticatedServer.json rename to packages/react/test/test-server/configs/components-config/unauthenticatedServer.json diff --git a/packages/solid-react/test/test-server/configs/solid-css-seed.json b/packages/react/test/test-server/configs/solid-css-seed.json similarity index 100% rename from packages/solid-react/test/test-server/configs/solid-css-seed.json rename to packages/react/test/test-server/configs/solid-css-seed.json diff --git a/packages/solid-react/test/test-server/configs/template/wac/.acl.hbs b/packages/react/test/test-server/configs/template/wac/.acl.hbs similarity index 100% rename from packages/solid-react/test/test-server/configs/template/wac/.acl.hbs rename to packages/react/test/test-server/configs/template/wac/.acl.hbs diff --git a/packages/solid-react/test/test-server/configs/template/wac/profile/card.acl.hbs b/packages/react/test/test-server/configs/template/wac/profile/card.acl.hbs similarity index 100% rename from packages/solid-react/test/test-server/configs/template/wac/profile/card.acl.hbs rename to packages/react/test/test-server/configs/template/wac/profile/card.acl.hbs diff --git a/packages/solid-react/test/test-server/runServer.ts b/packages/react/test/test-server/runServer.ts similarity index 100% rename from packages/solid-react/test/test-server/runServer.ts rename to packages/react/test/test-server/runServer.ts diff --git a/packages/solid-react/test/test-server/solidServer.helper.ts b/packages/react/test/test-server/solidServer.helper.ts similarity index 100% rename from packages/solid-react/test/test-server/solidServer.helper.ts rename to packages/react/test/test-server/solidServer.helper.ts diff --git a/packages/solid-react/tsconfig.build.json b/packages/react/tsconfig.build.json similarity index 100% rename from packages/solid-react/tsconfig.build.json rename to packages/react/tsconfig.build.json diff --git a/packages/solid/.eslintrc b/packages/solid/.eslintrc deleted file mode 100644 index 83c51a9..0000000 --- a/packages/solid/.eslintrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": ["../../.eslintrc"] -} \ No newline at end of file diff --git a/packages/solid/.gitignore b/packages/solid/.gitignore deleted file mode 100644 index 5ed9aae..0000000 --- a/packages/solid/.gitignore +++ /dev/null @@ -1 +0,0 @@ -test/data \ No newline at end of file diff --git a/packages/solid/LICENSE.txt b/packages/solid/LICENSE.txt deleted file mode 100644 index b87e67e..0000000 --- a/packages/solid/LICENSE.txt +++ /dev/null @@ -1,21 +0,0 @@ -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. \ No newline at end of file diff --git a/packages/solid/README.md b/packages/solid/README.md deleted file mode 100644 index 94c9ada..0000000 --- a/packages/solid/README.md +++ /dev/null @@ -1,238 +0,0 @@ -# @ldo/solid - -@ldo/solid is a client that implements the Solid specification with the use of Linked Data Objects. - -## Installation - -Navigate into your project's root folder and run the following command: -``` -cd my_project/ -npx run @ldo/cli init -``` - -Now install the @ldo/solid library - -``` -npm i @ldo/solid -``` - -
- -Manual Installation - - -If you already have generated ShapeTypes, you may install the `@ldo/ldo` and `@ldo/solid` libraries independently. - -``` -npm i @ldo/ldo @ldo/solid -``` -
- -## Simple Examples - -Below is a simple example of @ldo/solid. Assume that a ShapeType was previously generated and placed at `./.ldo/foafProfile.shapeTypes`. Also assume we have a shape type for social media at `./.ldo/socialMediaPost.shapeTypes` - -```typescript -import { changeData, commitData, createSolidLdoDataset } from "@ldo/solid"; -import { fetch, getDefaultSession } from "@inrupt/solid-client-authn-browser"; -import { FoafProfileShapeType } from "./.ldo/foafProfile.shapeTypes"; -import { SocialMediaPostShapeType } from "./.ldo/socialMediaPost.shapeTypes"; - -async function main() { - /** - * =========================================================================== - * READING DATA FROM A POD - * =========================================================================== - */ - - // Before we begin using @ldo/solid. Let's get the WebId of the current user - const webIdUri = getDefaultSession().info.webId; - if (!webIdUri) throw new Error("User is not logged in"); - - // Now let's proceed with @ldo/solid. Our first step is setting up a - // SolidLdoDataset. You can think of this dataset as a local store for all the - // information in the Solidverse. Don't forget to pass the authenticated fetch - // function to do your queries! - const solidLdoDataset = createSolidLdoDataset({ fetch }); - - // We'll start with getting a representation of our WebId's resource - const webIdResource = solidLdoDataset.getResource(webIdUri); - - // This resource is currently unfetched - console.log(webIdResource.isUnfetched()); // Logs true - - // So let's fetch it! Running the `read` command will make a request to get - // the WebId. - const readResult = await webIdResource.read(); - - // @ldo/solid will never throw an error. Instead, it will return errors. This - // design decision was made to force you to handle any errors. It may seem a - // bit annoying at first, but it will result in more resiliant code. You can - // easily follow intellisense tooltips to see what kinds of errors each action - // can throw. - if (readResult.isError) { - switch (readResult.type) { - case "serverError": - console.error("The solid server had an error:", readResult.message); - return; - case "noncompliantPodError": - console.error("The Pod responded in a way not compliant with the spec"); - return; - default: - console.error("Some other error was detected:", readResult.message); - } - } - - // When fetching a data resource, read triples will automatically be added to - // the solidLdoDataset. You can access them using Linked Data Objects. In - // the following example we're using a Profile Linked Data Object that was - // generated with the init step. - const profile = solidLdoDataset - .usingType(FoafProfileShapeType) - .fromSubject(webIdUri); - - // Now you can read "profile" like any JSON. - console.log(profile.name); - - /** - * =========================================================================== - * MODIFYING DATA - * =========================================================================== - */ - - // When we want to modify data the first step is to use the `changeData` - // function. We pass in an object that we want to change (in this case, - // "profile") as well an a list of any resources to which we want those - // changes to be applied (in this case, just the webIdResource). This gives - // us a new variable (conventionally named with a c for "changed") that we can - // write changes to. - const cProfile = changeData(profile, webIdResource); - - // We can make changes just like it's regular JSON - cProfile.name = "Captain Cool Dude"; - - // Committing data is as easy as running the "commitData" function. - const commitResult = await commitData(cProfile); - - // Remember to check for and handle errors! We'll keep it short this time. - if (commitResult.isError) throw commitResult; - - /** - * =========================================================================== - * CREATING NEW RESOURCES - * =========================================================================== - */ - - // Let's create some social media posts to be stored on the Solid Pod! - // Our first step is going to be finding where to place these posts. In the - // future, there will be advanced ways to determine the location of resources - // but for now, let's throw it in the root folder. - - // But, first, let's find out where the root folder is. We can take our WebId - // resource and call `getRootContainer`. Let's assume the root container has - // a URI "https://example.com/" - const rootContainer = await webIdResource.getRootContainer(); - if (rootContainer.isError) throw rootContainer; - - // Now, let's create a container for our posts - const createPostContainerResult = - await rootContainer.createChildIfAbsent("social-posts/"); - if (createPostContainerResult.isError) throw createPostContainerResult; - - // Most results store the affected resource in the "resource" field. This - // container has the URI "https://example.com/social-posts/" - const postContainer = createPostContainerResult.resource; - - // Now that we have our container, let's make a Post resource! This is a data - // resource, which means we can put raw Solid Data (RDF) into it. - const postResourceResult = - await postContainer.createChildAndOverwrite("post1.ttl"); - if (postResourceResult.isError) throw postResourceResult; - const postResource = postResourceResult.resource; - - // We can also create binary resources with things like images - const imageResourceResult = await postContainer.uploadChildAndOverwrite( - // name of the binary - "image1.svg", - // A blob for the binary - new Blob([``]), - // mime type of the binary - "image/svg+xml", - ); - if (imageResourceResult.isError) throw imageResourceResult; - const imageResource = imageResourceResult.resource; - - /** - * =========================================================================== - * CREATING NEW DATA - * =========================================================================== - */ - - // We create data in a similar way to the way we modify data. We can use the - // "createData" method. - const cPost = solidLdoDataset.createData( - // An LDO ShapeType saying that this is a social media psot - SocialMediaPostShapeType, - // The URI of the post (in this case we'll make it the same as the resource) - postResource.uri, - // The resource we should write it to - postResource, - ); - - // We can add new data - cPost.text = "Check out this bad svg:"; - cPost.image = { "@id": imageResource.uri }; - - // And now we commit data - const newDataResult = await commitData(cPost); - if (newDataResult.isError) throw newDataResult; - - /** - * =========================================================================== - * DELETING RESOURCES - * =========================================================================== - */ - - // Deleting resources can be done with a single method call. In this case, - // the container will be deleted along with all its contained resources - const deleteResult = await postContainer.delete(); - if (deleteResult.isError) throw deleteResult; -} -main(); -``` - -## API Details - -SolidLdoDataset - - - [createSolidLdoDataset](https://ldo.js.org/latest/api/solid/functions/createSolidLdoDataset/) - - [SolidLdoDataset](https://ldo.js.org/latest/api/solid/classes/SolidLdoDataset/) - -Resources (Manage batching requests) - - - [LeafUri](https://ldo.js.org/latest/api/solid/types/LeafUri/) - - [ContainerUri](https://ldo.js.org/latest/api/solid/types/ContainerUri/) - - [Leaf](https://ldo.js.org/latest/api/solid/classes/Leaf/) - - [Container](https://ldo.js.org/latest/api/solid/classes/Container/) - -Standalone Functions - - - [checkRootContainter](https://ldo.js.org/latest/api/solid/functions/checkRootContainer/) - - [createDataResource](https://ldo.js.org/latest/api/solid/functions/createDataResource/) - - [deleteResource](https://ldo.js.org/latest/api/solid/functions/deleteResource/) - - [readResource](https://ldo.js.org/latest/api/solid/functions/readResource/) - - [updateResource](https://ldo.js.org/latest/api/solid/functions/updateResource/) - - [uploadResource](https://ldo.js.org/latest/api/solid/functions/uploadResource/) - -Data Functions - - [changeData](https://ldo.js.org/latest/api/solid/functions/changeData/) - - [commitData](https://ldo.js.org/latest/api/solid/functions/commitData/) - -## 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/). - -[nlnet foundation logo](https://nlnet.nl/) -[NGI Zero Entrust Logo](https://nlnet.nl/) - -## Liscense -MIT diff --git a/packages/solid/babel.config.js b/packages/solid/babel.config.js deleted file mode 100644 index 721e8b8..0000000 --- a/packages/solid/babel.config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = { presets: ["@babel/preset-env"] }; diff --git a/packages/solid/jest.config.js b/packages/solid/jest.config.js deleted file mode 100644 index c55a5f7..0000000 --- a/packages/solid/jest.config.js +++ /dev/null @@ -1,11 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/no-var-requires -const sharedConfig = require("../../jest.config.js"); -module.exports = { - ...sharedConfig, - rootDir: "./", - setupFiles: ["/test/setup-tests.ts"], - transform: { - "^.+\\.(ts|tsx)?$": "ts-jest", - "^.+\\.(js|jsx)$": "babel-jest", - }, -}; diff --git a/packages/solid/package.json b/packages/solid/package.json deleted file mode 100644 index ce4a648..0000000 --- a/packages/solid/package.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "name": "@ldo/solid", - "version": "1.0.0-alpha.1", - "description": "A library for LDO and Solid", - "main": "dist/index.js", - "scripts": { - "example": "ts-node ./example/example.ts", - "build": "tsc --project tsconfig.build.json", - "watch": "tsc --watch", - "test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --coverage", - "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", - "docs": "typedoc --plugin typedoc-plugin-markdown" - }, - "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#readme", - "devDependencies": { - "@inrupt/solid-client-authn-core": "^2.2.6", - "@ldo/cli": "^1.0.0-alpha.1", - "@rdfjs/data-model": "^1.2.0", - "@rdfjs/types": "^1.0.1", - "@solid-notifications/types": "^0.1.2", - "@solid/community-server": "^7.1.3", - "@types/jest": "^27.0.3", - "cross-env": "^7.0.3", - "dotenv": "^16.3.1", - "jest-rdf": "^1.8.0", - "ts-jest": "^27.1.2", - "ts-node": "^10.9.1", - "typed-emitter": "^2.1.0", - "typedoc": "^0.25.4", - "typedoc-plugin-markdown": "^3.17.1" - }, - "dependencies": { - "@ldo/dataset": "^1.0.0-alpha.1", - "@ldo/ldo": "^1.0.0-alpha.1", - "@ldo/rdf-utils": "^1.0.0-alpha.1", - "@solid-notifications/subscription": "^0.1.2", - "cross-fetch": "^3.1.6", - "http-link-header": "^1.1.1", - "ws": "^8.18.0" - }, - "files": [ - "dist", - "src" - ], - "publishConfig": { - "access": "public" - }, - "gitHead": "0287cd6371f06630763568dec5e41653f7b8583e" -} diff --git a/packages/solid/src/.ldo/solid.context.ts b/packages/solid/src/.ldo/solid.context.ts deleted file mode 100644 index 7f50b27..0000000 --- a/packages/solid/src/.ldo/solid.context.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { LdoJsonldContext } from "@ldo/ldo"; - -/** - * ============================================================================= - * solidContext: JSONLD Context for solid - * ============================================================================= - */ -export const solidContext: LdoJsonldContext = { - type: { - "@id": "@type", - "@isCollection": true, - }, - Container: { - "@id": "http://www.w3.org/ns/ldp#Container", - "@context": { - type: { - "@id": "@type", - "@isCollection": true, - }, - modified: { - "@id": "http://purl.org/dc/terms/modified", - "@type": "http://www.w3.org/2001/XMLSchema#string", - }, - contains: { - "@id": "http://www.w3.org/ns/ldp#contains", - "@type": "@id", - "@isCollection": true, - }, - mtime: { - "@id": "http://www.w3.org/ns/posix/stat#mtime", - "@type": "http://www.w3.org/2001/XMLSchema#decimal", - }, - size: { - "@id": "http://www.w3.org/ns/posix/stat#size", - "@type": "http://www.w3.org/2001/XMLSchema#integer", - }, - }, - }, - Resource: { - "@id": "http://www.w3.org/ns/ldp#Resource", - "@context": { - type: { - "@id": "@type", - "@isCollection": true, - }, - modified: { - "@id": "http://purl.org/dc/terms/modified", - "@type": "http://www.w3.org/2001/XMLSchema#string", - }, - contains: { - "@id": "http://www.w3.org/ns/ldp#contains", - "@type": "@id", - "@isCollection": true, - }, - mtime: { - "@id": "http://www.w3.org/ns/posix/stat#mtime", - "@type": "http://www.w3.org/2001/XMLSchema#decimal", - }, - size: { - "@id": "http://www.w3.org/ns/posix/stat#size", - "@type": "http://www.w3.org/2001/XMLSchema#integer", - }, - }, - }, - modified: { - "@id": "http://purl.org/dc/terms/modified", - "@type": "http://www.w3.org/2001/XMLSchema#string", - }, - contains: { - "@id": "http://www.w3.org/ns/ldp#contains", - "@type": "@id", - "@isCollection": true, - }, - Resource2: { - "@id": "http://www.w3.org/ns/iana/media-types/text/turtle#Resource", - "@context": { - type: { - "@id": "@type", - "@isCollection": true, - }, - modified: { - "@id": "http://purl.org/dc/terms/modified", - "@type": "http://www.w3.org/2001/XMLSchema#string", - }, - mtime: { - "@id": "http://www.w3.org/ns/posix/stat#mtime", - "@type": "http://www.w3.org/2001/XMLSchema#decimal", - }, - size: { - "@id": "http://www.w3.org/ns/posix/stat#size", - "@type": "http://www.w3.org/2001/XMLSchema#integer", - }, - }, - }, - mtime: { - "@id": "http://www.w3.org/ns/posix/stat#mtime", - "@type": "http://www.w3.org/2001/XMLSchema#decimal", - }, - size: { - "@id": "http://www.w3.org/ns/posix/stat#size", - "@type": "http://www.w3.org/2001/XMLSchema#integer", - }, - storage: { - "@id": "http://www.w3.org/ns/pim/space#storage", - "@type": "@id", - "@isCollection": true, - }, -}; diff --git a/packages/solid/src/.ldo/solid.schema.ts b/packages/solid/src/.ldo/solid.schema.ts deleted file mode 100644 index 4d9adc0..0000000 --- a/packages/solid/src/.ldo/solid.schema.ts +++ /dev/null @@ -1,233 +0,0 @@ -import { Schema } from "shexj"; - -/** - * ============================================================================= - * solidSchema: ShexJ Schema for solid - * ============================================================================= - */ -export const solidSchema: Schema = { - type: "Schema", - shapes: [ - { - id: "http://www.w3.org/ns/lddps#Container", - type: "ShapeDecl", - shapeExpr: { - type: "Shape", - expression: { - id: "http://www.w3.org/ns/lddps#ContainerShape", - type: "EachOf", - expressions: [ - { - type: "TripleConstraint", - predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", - valueExpr: { - type: "NodeConstraint", - values: [ - "http://www.w3.org/ns/ldp#Container", - "http://www.w3.org/ns/ldp#Resource", - ], - }, - min: 0, - max: -1, - annotations: [ - { - type: "Annotation", - predicate: "http://www.w3.org/2000/01/rdf-schema#comment", - object: { - value: "A container on a Solid server", - }, - }, - ], - }, - { - type: "TripleConstraint", - predicate: "http://purl.org/dc/terms/modified", - valueExpr: { - type: "NodeConstraint", - datatype: "http://www.w3.org/2001/XMLSchema#string", - }, - min: 0, - max: 1, - annotations: [ - { - type: "Annotation", - predicate: "http://www.w3.org/2000/01/rdf-schema#comment", - object: { - value: "Date modified", - }, - }, - ], - }, - { - type: "TripleConstraint", - predicate: "http://www.w3.org/ns/ldp#contains", - valueExpr: "http://www.w3.org/ns/lddps#Resource", - min: 0, - max: -1, - annotations: [ - { - type: "Annotation", - predicate: "http://www.w3.org/2000/01/rdf-schema#comment", - object: { - value: "Defines a Solid Resource", - }, - }, - ], - }, - { - type: "TripleConstraint", - predicate: "http://www.w3.org/ns/posix/stat#mtime", - valueExpr: { - type: "NodeConstraint", - datatype: "http://www.w3.org/2001/XMLSchema#decimal", - }, - min: 0, - max: 1, - annotations: [ - { - type: "Annotation", - predicate: "http://www.w3.org/2000/01/rdf-schema#comment", - object: { - value: "?", - }, - }, - ], - }, - { - type: "TripleConstraint", - predicate: "http://www.w3.org/ns/posix/stat#size", - valueExpr: { - type: "NodeConstraint", - datatype: "http://www.w3.org/2001/XMLSchema#integer", - }, - min: 0, - max: 1, - annotations: [ - { - type: "Annotation", - predicate: "http://www.w3.org/2000/01/rdf-schema#comment", - object: { - value: "size of this container", - }, - }, - ], - }, - ], - }, - extra: ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"], - }, - }, - { - id: "http://www.w3.org/ns/lddps#Resource", - type: "ShapeDecl", - shapeExpr: { - type: "Shape", - expression: { - id: "http://www.w3.org/ns/lddps#ResourceShape", - type: "EachOf", - expressions: [ - { - type: "TripleConstraint", - predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", - valueExpr: { - type: "NodeConstraint", - values: [ - "http://www.w3.org/ns/ldp#Resource", - "http://www.w3.org/ns/iana/media-types/text/turtle#Resource", - ], - }, - min: 0, - max: -1, - annotations: [ - { - type: "Annotation", - predicate: "http://www.w3.org/2000/01/rdf-schema#comment", - object: { - value: "Any resource on a Solid server", - }, - }, - ], - }, - { - type: "TripleConstraint", - predicate: "http://purl.org/dc/terms/modified", - valueExpr: { - type: "NodeConstraint", - datatype: "http://www.w3.org/2001/XMLSchema#string", - }, - min: 0, - max: 1, - annotations: [ - { - type: "Annotation", - predicate: "http://www.w3.org/2000/01/rdf-schema#comment", - object: { - value: "Date modified", - }, - }, - ], - }, - { - type: "TripleConstraint", - predicate: "http://www.w3.org/ns/posix/stat#mtime", - valueExpr: { - type: "NodeConstraint", - datatype: "http://www.w3.org/2001/XMLSchema#decimal", - }, - min: 0, - max: 1, - annotations: [ - { - type: "Annotation", - predicate: "http://www.w3.org/2000/01/rdf-schema#comment", - object: { - value: "?", - }, - }, - ], - }, - { - type: "TripleConstraint", - predicate: "http://www.w3.org/ns/posix/stat#size", - valueExpr: { - type: "NodeConstraint", - datatype: "http://www.w3.org/2001/XMLSchema#integer", - }, - min: 0, - max: 1, - annotations: [ - { - type: "Annotation", - predicate: "http://www.w3.org/2000/01/rdf-schema#comment", - object: { - value: "size of this container", - }, - }, - ], - }, - ], - }, - extra: ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"], - }, - }, - { - 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"], - }, - }, - ], -}; diff --git a/packages/solid/src/.ldo/solid.shapeTypes.ts b/packages/solid/src/.ldo/solid.shapeTypes.ts deleted file mode 100644 index 69ddd90..0000000 --- a/packages/solid/src/.ldo/solid.shapeTypes.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { ShapeType } from "@ldo/ldo"; -import { solidSchema } from "./solid.schema"; -import { solidContext } from "./solid.context"; -import { Container, Resource, ProfileWithStorage } from "./solid.typings"; - -/** - * ============================================================================= - * LDO ShapeTypes solid - * ============================================================================= - */ - -/** - * Container ShapeType - */ -export const ContainerShapeType: ShapeType = { - schema: solidSchema, - shape: "http://www.w3.org/ns/lddps#Container", - context: solidContext, -}; - -/** - * Resource ShapeType - */ -export const ResourceShapeType: ShapeType = { - schema: solidSchema, - shape: "http://www.w3.org/ns/lddps#Resource", - context: solidContext, -}; - -/** - * ProfileWithStorage ShapeType - */ -export const ProfileWithStorageShapeType: ShapeType = { - schema: solidSchema, - shape: "http://www.w3.org/ns/lddps#ProfileWithStorage", - context: solidContext, -}; diff --git a/packages/solid/src/.ldo/solid.typings.ts b/packages/solid/src/.ldo/solid.typings.ts deleted file mode 100644 index 0405e75..0000000 --- a/packages/solid/src/.ldo/solid.typings.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { LdoJsonldContext, LdSet } from "@ldo/ldo"; - -/** - * ============================================================================= - * Typescript Typings for solid - * ============================================================================= - */ - -/** - * Container Type - */ -export interface Container { - "@id"?: string; - "@context"?: LdoJsonldContext; - /** - * A container on a Solid server - */ - type?: LdSet< - | { - "@id": "Container"; - } - | { - "@id": "Resource"; - } - >; - /** - * Date modified - */ - modified?: string; - /** - * Defines a Solid Resource - */ - contains?: LdSet; - /** - * ? - */ - mtime?: number; - /** - * size of this container - */ - size?: number; -} - -/** - * Resource Type - */ -export interface Resource { - "@id"?: string; - "@context"?: LdoJsonldContext; - /** - * Any resource on a Solid server - */ - type?: LdSet< - | { - "@id": "Resource"; - } - | { - "@id": "Resource2"; - } - >; - /** - * Date modified - */ - modified?: string; - /** - * ? - */ - mtime?: number; - /** - * size of this container - */ - size?: number; -} - -/** - * ProfileWithStorage Type - */ -export interface ProfileWithStorage { - "@id"?: string; - "@context"?: LdoJsonldContext; - storage?: LdSet<{ - "@id": string; - }>; -} diff --git a/packages/solid/src/.ldo/wac.context.ts b/packages/solid/src/.ldo/wac.context.ts deleted file mode 100644 index 6f6c9fd..0000000 --- a/packages/solid/src/.ldo/wac.context.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { LdoJsonldContext } from "@ldo/ldo"; - -/** - * ============================================================================= - * wacContext: JSONLD Context for wac - * ============================================================================= - */ -export const wacContext: LdoJsonldContext = { - type: { - "@id": "@type", - }, - Authorization: { - "@id": "http://www.w3.org/ns/auth/acl#Authorization", - "@context": { - type: { - "@id": "@type", - }, - accessTo: { - "@id": "http://www.w3.org/ns/auth/acl#accessTo", - "@type": "@id", - }, - default: { - "@id": "http://www.w3.org/ns/auth/acl#default", - "@type": "@id", - }, - agent: { - "@id": "http://www.w3.org/ns/auth/acl#agent", - "@type": "@id", - "@isCollection": true, - }, - agentGroup: { - "@id": "http://www.w3.org/ns/auth/acl#agentGroup", - "@type": "@id", - "@isCollection": true, - }, - agentClass: { - "@id": "http://www.w3.org/ns/auth/acl#agentClass", - "@isCollection": true, - }, - mode: { - "@id": "http://www.w3.org/ns/auth/acl#mode", - "@isCollection": true, - }, - }, - }, - accessTo: { - "@id": "http://www.w3.org/ns/auth/acl#accessTo", - "@type": "@id", - }, - default: { - "@id": "http://www.w3.org/ns/auth/acl#default", - "@type": "@id", - }, - agent: { - "@id": "http://www.w3.org/ns/auth/acl#agent", - "@type": "@id", - "@isCollection": true, - }, - agentGroup: { - "@id": "http://www.w3.org/ns/auth/acl#agentGroup", - "@type": "@id", - "@isCollection": true, - }, - agentClass: { - "@id": "http://www.w3.org/ns/auth/acl#agentClass", - "@isCollection": true, - }, - AuthenticatedAgent: "http://www.w3.org/ns/auth/acl#AuthenticatedAgent", - Agent: "http://xmlns.com/foaf/0.1/Agent", - mode: { - "@id": "http://www.w3.org/ns/auth/acl#mode", - "@isCollection": true, - }, - Read: "http://www.w3.org/ns/auth/acl#Read", - Write: "http://www.w3.org/ns/auth/acl#Write", - Append: "http://www.w3.org/ns/auth/acl#Append", - Control: "http://www.w3.org/ns/auth/acl#Control", -}; diff --git a/packages/solid/src/.ldo/wac.schema.ts b/packages/solid/src/.ldo/wac.schema.ts deleted file mode 100644 index 2d54115..0000000 --- a/packages/solid/src/.ldo/wac.schema.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { Schema } from "shexj"; - -/** - * ============================================================================= - * wacSchema: ShexJ Schema for wac - * ============================================================================= - */ -export const wacSchema: Schema = { - type: "Schema", - shapes: [ - { - id: "http://www.w3.org/ns/auth/acls#Authorization", - type: "ShapeDecl", - shapeExpr: { - type: "Shape", - expression: { - id: "http://www.w3.org/ns/auth/acls#AuthorizationShape", - 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/auth/acl#Authorization"], - }, - annotations: [ - { - type: "Annotation", - predicate: "http://www.w3.org/2000/01/rdf-schema#comment", - object: { - value: "Denotes this as an acl:Authorization", - }, - }, - ], - }, - { - type: "TripleConstraint", - predicate: "http://www.w3.org/ns/auth/acl#accessTo", - valueExpr: { - type: "NodeConstraint", - nodeKind: "iri", - }, - min: 0, - max: 1, - annotations: [ - { - type: "Annotation", - predicate: "http://www.w3.org/2000/01/rdf-schema#comment", - object: { - value: "The subject of this authorization", - }, - }, - ], - }, - { - type: "TripleConstraint", - predicate: "http://www.w3.org/ns/auth/acl#default", - valueExpr: { - type: "NodeConstraint", - nodeKind: "iri", - }, - min: 0, - max: 1, - annotations: [ - { - type: "Annotation", - predicate: "http://www.w3.org/2000/01/rdf-schema#comment", - object: { - value: "The container subject of this authorization", - }, - }, - ], - }, - { - type: "TripleConstraint", - predicate: "http://www.w3.org/ns/auth/acl#agent", - valueExpr: { - type: "NodeConstraint", - nodeKind: "iri", - }, - min: 0, - max: -1, - annotations: [ - { - type: "Annotation", - predicate: "http://www.w3.org/2000/01/rdf-schema#comment", - object: { - value: - "An agent is a person, social entity or software identified by a URI, e.g., a WebID denotes an agent", - }, - }, - ], - }, - { - type: "TripleConstraint", - predicate: "http://www.w3.org/ns/auth/acl#agentGroup", - valueExpr: { - type: "NodeConstraint", - nodeKind: "iri", - }, - min: 0, - max: -1, - annotations: [ - { - type: "Annotation", - predicate: "http://www.w3.org/2000/01/rdf-schema#comment", - object: { - value: - "Denotes a group of agents being given the access permission", - }, - }, - ], - }, - { - type: "TripleConstraint", - predicate: "http://www.w3.org/ns/auth/acl#agentClass", - valueExpr: { - type: "NodeConstraint", - values: [ - "http://www.w3.org/ns/auth/acl#AuthenticatedAgent", - "http://xmlns.com/foaf/0.1/Agent", - ], - }, - min: 0, - max: -1, - annotations: [ - { - type: "Annotation", - predicate: "http://www.w3.org/2000/01/rdf-schema#comment", - object: { - value: - "An agent class is a class of persons or entities identified by a URI.", - }, - }, - ], - }, - { - type: "TripleConstraint", - predicate: "http://www.w3.org/ns/auth/acl#mode", - valueExpr: { - type: "NodeConstraint", - values: [ - "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", - ], - }, - min: 0, - max: -1, - annotations: [ - { - type: "Annotation", - predicate: "http://www.w3.org/2000/01/rdf-schema#comment", - object: { - value: - "Denotes a class of operations that the agents can perform on a resource.", - }, - }, - ], - }, - ], - }, - extra: ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"], - }, - }, - ], -}; diff --git a/packages/solid/src/.ldo/wac.shapeTypes.ts b/packages/solid/src/.ldo/wac.shapeTypes.ts deleted file mode 100644 index 6689590..0000000 --- a/packages/solid/src/.ldo/wac.shapeTypes.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { ShapeType } from "@ldo/ldo"; -import { wacSchema } from "./wac.schema"; -import { wacContext } from "./wac.context"; -import { Authorization } from "./wac.typings"; - -/** - * ============================================================================= - * LDO ShapeTypes wac - * ============================================================================= - */ - -/** - * Authorization ShapeType - */ -export const AuthorizationShapeType: ShapeType = { - schema: wacSchema, - shape: "http://www.w3.org/ns/auth/acls#Authorization", - context: wacContext, -}; diff --git a/packages/solid/src/.ldo/wac.typings.ts b/packages/solid/src/.ldo/wac.typings.ts deleted file mode 100644 index 40860e0..0000000 --- a/packages/solid/src/.ldo/wac.typings.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { LdoJsonldContext, LdSet } from "@ldo/ldo"; - -/** - * ============================================================================= - * Typescript Typings for wac - * ============================================================================= - */ - -/** - * Authorization Type - */ -export interface Authorization { - "@id"?: string; - "@context"?: LdoJsonldContext; - /** - * Denotes this as an acl:Authorization - */ - type: { - "@id": "Authorization"; - }; - /** - * The subject of this authorization - */ - accessTo?: { - "@id": string; - }; - /** - * The container subject of this authorization - */ - default?: { - "@id": string; - }; - /** - * An agent is a person, social entity or software identified by a URI, e.g., a WebID denotes an agent - */ - agent?: LdSet<{ - "@id": string; - }>; - /** - * Denotes a group of agents being given the access permission - */ - agentGroup?: LdSet<{ - "@id": string; - }>; - /** - * An agent class is a class of persons or entities identified by a URI. - */ - agentClass?: LdSet< - | { - "@id": "AuthenticatedAgent"; - } - | { - "@id": "Agent"; - } - >; - /** - * Denotes a class of operations that the agents can perform on a resource. - */ - mode?: LdSet< - | { - "@id": "Read"; - } - | { - "@id": "Write"; - } - | { - "@id": "Append"; - } - | { - "@id": "Control"; - } - >; -} diff --git a/packages/solid/src/.shapes/solid.shex b/packages/solid/src/.shapes/solid.shex deleted file mode 100644 index f90f1b5..0000000 --- a/packages/solid/src/.shapes/solid.shex +++ /dev/null @@ -1,43 +0,0 @@ -PREFIX xsd: -PREFIX rdf: -PREFIX rdfs: -PREFIX ldp: -PREFIX ldps: -PREFIX dct: -PREFIX stat: -PREFIX tur: -PREFIX pim: - -ldps:Container EXTRA a { - $ldps:ContainerShape ( - a [ ldp:Container ldp:Resource ]* - // rdfs:comment "A container on a Solid server"; - dct:modified xsd:string? - // rdfs:comment "Date modified"; - ldp:contains @ldps:Resource* - // rdfs:comment "Defines a Solid Resource"; - stat:mtime xsd:decimal? - // rdfs:comment "?"; - stat:size xsd:integer? - // rdfs:comment "size of this container"; - ) -} - -ldps:Resource EXTRA a { - $ldps:ResourceShape ( - a [ ldp:Resource tur:Resource ]* - // rdfs:comment "Any resource on a Solid server"; - dct:modified xsd:string? - // rdfs:comment "Date modified"; - stat:mtime xsd:decimal? - // rdfs:comment "?"; - stat:size xsd:integer? - // rdfs:comment "size of this container"; - ) -} - -ldps:ProfileWithStorage EXTRA a { - $ldps:ProfileWithStorageShape ( - pim:storage IRI *; - ) -} diff --git a/packages/solid/src/.shapes/wac.shex b/packages/solid/src/.shapes/wac.shex deleted file mode 100644 index 5ff19cd..0000000 --- a/packages/solid/src/.shapes/wac.shex +++ /dev/null @@ -1,23 +0,0 @@ -PREFIX acl: -PREFIX acls: -PREFIX foaf: -PREFIX rdfs: - -acls:Authorization EXTRA a { - $acls:AuthorizationShape ( - a [ acl:Authorization ] - // rdfs:comment "Denotes this as an acl:Authorization"; - acl:accessTo IRI? - // rdfs:comment "The subject of this authorization"; - acl:default IRI? - // rdfs:comment "The container subject of this authorization"; - acl:agent IRI* - // rdfs:comment "An agent is a person, social entity or software identified by a URI, e.g., a WebID denotes an agent"; - acl:agentGroup IRI* - // rdfs:comment "Denotes a group of agents being given the access permission"; - acl:agentClass [ acl:AuthenticatedAgent foaf:Agent ]* - // rdfs:comment "An agent class is a class of persons or entities identified by a URI."; - acl:mode [ acl:Read acl:Write acl:Append acl:Control ]* - // rdfs:comment "Denotes a class of operations that the agents can perform on a resource."; - ) -} diff --git a/packages/solid/src/ResourceStore.ts b/packages/solid/src/ResourceStore.ts deleted file mode 100644 index 5ecbafe..0000000 --- a/packages/solid/src/ResourceStore.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { Container } from "./resource/Container"; -import { Leaf } from "./resource/Leaf"; -import type { SolidLdoDatasetContext } from "./SolidLdoDatasetContext"; -import type { ContainerUri, LeafUri } from "./util/uriTypes"; -import { isContainerUri } from "./util/uriTypes"; - -/** - * Options for getting a resource - */ -export interface ResourceGetterOptions { - /** - * If autoLoad is set to true and the resource is unfetched, `read` will be called. - * - * @default false - */ - autoLoad?: boolean; -} - -/** - * @internal - * A store of Solid resources - */ -export class ResourceStore { - /** - * @internal - * - * A mapping between a resource URI and a Solid resource - */ - protected resourceMap: Map; - /** - * @internal - * - * Context about the SolidLdoDataset - */ - protected context: SolidLdoDatasetContext; - - /** - * @param context - A SolidLdoDatasetContext of the parent SolidLdoDataset - */ - constructor(context: SolidLdoDatasetContext) { - this.resourceMap = new Map(); - this.context = context; - } - - /** - * Gets a resource representation - * - * @param uri - The URI of the resource - * @param options - ResourceGetterOptions - * - * @returns The resource representation - */ - get(uri: ContainerUri, options?: ResourceGetterOptions): Container; - get(uri: LeafUri, options?: ResourceGetterOptions): Leaf; - get(uri: string, options?: ResourceGetterOptions): Leaf | Container; - get(uri: string, options?: ResourceGetterOptions): Leaf | Container { - // Normalize URI by removing hash - const url = new URL(uri); - url.hash = ""; - const normalizedUri = url.toString(); - - // Get the document and return if exists - let resource = this.resourceMap.get(normalizedUri); - if (!resource) { - if (isContainerUri(normalizedUri)) { - resource = new Container(normalizedUri, this.context); - } else { - resource = new Leaf(normalizedUri as LeafUri, this.context); - } - this.resourceMap.set(normalizedUri, resource); - } - - if (options?.autoLoad) { - resource.read(); - } - - return resource; - } -} diff --git a/packages/solid/src/SolidLdoDataset.ts b/packages/solid/src/SolidLdoDataset.ts deleted file mode 100644 index dc16d09..0000000 --- a/packages/solid/src/SolidLdoDataset.ts +++ /dev/null @@ -1,169 +0,0 @@ -import type { LdoBase, ShapeType } from "@ldo/ldo"; -import { LdoDataset, startTransaction } from "@ldo/ldo"; -import type { Dataset, DatasetFactory, Quad } from "@rdfjs/types"; -import type { Container } from "./resource/Container"; -import type { Leaf } from "./resource/Leaf"; -import type { ResourceGetterOptions } from "./ResourceStore"; -import type { SolidLdoDatasetContext } from "./SolidLdoDatasetContext"; -import type { ContainerUri, LeafUri } from "./util/uriTypes"; -import { SolidLdoTransactionDataset } from "./SolidLdoTransactionDataset"; -import type { ITransactionDatasetFactory } from "@ldo/subscribable-dataset"; -import type { SubjectNode } from "@ldo/rdf-utils"; -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"; -import type { ISolidLdoDataset } from "./types"; - -/** - * A SolidLdoDataset has all the functionality of an LdoDataset with the added - * functionality of keeping track of fetched Solid Resources. - * - * It is recommended to use the { @link createSolidLdoDataset } to initialize - * this class - * - * @example - * ```typescript - * import { createSolidLdoDataset } from "@ldo/solid"; - * import { ProfileShapeType } from "./.ldo/profile.shapeTypes.ts" - * - * // ... - * - * const solidLdoDataset = createSolidLdoDataset(); - * - * const profileDocument = solidLdoDataset - * .getResource("https://example.com/profile"); - * await profileDocument.read(); - * - * const profile = solidLdoDataset - * .using(ProfileShapeType) - * .fromSubject("https://example.com/profile#me"); - * ``` - */ -export class SolidLdoDataset extends LdoDataset implements ISolidLdoDataset { - /** - * @internal - */ - public context: SolidLdoDatasetContext; - - /** - * @param context - SolidLdoDatasetContext - * @param datasetFactory - An optional dataset factory - * @param transactionDatasetFactory - A factory for creating transaction datasets - * @param initialDataset - A set of triples to initialize this dataset - */ - constructor( - context: SolidLdoDatasetContext, - datasetFactory: DatasetFactory, - transactionDatasetFactory: ITransactionDatasetFactory, - initialDataset?: Dataset, - ) { - super(datasetFactory, transactionDatasetFactory, initialDataset); - this.context = context; - } - - /** - * Retireves a representation (either a LeafResource or a ContainerResource) - * of a Solid Resource at the given URI. This resource represents the - * current state of the resource: whether it is currently fetched or in the - * process of fetching as well as some information about it. - * - * @param uri - the URI of the resource - * @param options - Special options for getting the resource - * - * @returns a Leaf or Container Resource - * - * @example - * ```typescript - * const profileDocument = solidLdoDataset - * .getResource("https://example.com/profile"); - * ``` - */ - getResource(uri: ContainerUri, options?: ResourceGetterOptions): Container; - getResource(uri: LeafUri, options?: ResourceGetterOptions): Leaf; - getResource(uri: string, options?: ResourceGetterOptions): Leaf | Container { - return this.context.resourceStore.get(uri, options); - } - - public startTransaction(): SolidLdoTransactionDataset { - return new SolidLdoTransactionDataset( - this, - this.context, - this.datasetFactory, - this.transactionDatasetFactory, - ); - } - - /** - * Shorthand for solidLdoDataset - * .usingType(shapeType) - * .write(...resources.map((r) => r.uri)) - * .fromSubject(subject); - * @param shapeType - The shapetype to represent the data - * @param subject - A subject URI - * @param resources - The resources changes to should written to - */ - createData( - shapeType: ShapeType, - subject: string | SubjectNode, - resource: Resource, - ...additionalResources: Resource[] - ): Type { - const resources = [resource, ...additionalResources]; - const linkedDataObject = this.usingType(shapeType) - .write(...resources.map((r) => r.uri)) - .fromSubject(subject); - startTransaction(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.size > 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; - } -} diff --git a/packages/solid/src/SolidLdoDatasetContext.ts b/packages/solid/src/SolidLdoDatasetContext.ts deleted file mode 100644 index c78b50b..0000000 --- a/packages/solid/src/SolidLdoDatasetContext.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { ResourceStore } from "./ResourceStore"; -import type { SolidLdoDataset } from "./SolidLdoDataset"; - -/** - * Context to be shared between aspects of a SolidLdoDataset - */ -export interface SolidLdoDatasetContext { - /** - * A pointer to the parent SolidLdoDataset - */ - solidLdoDataset: SolidLdoDataset; - /** - * The resource store of the SolidLdoDataset - */ - resourceStore: ResourceStore; - /** - * Http fetch function - */ - fetch: typeof fetch; -} diff --git a/packages/solid/src/SolidLdoTransactionDataset.ts b/packages/solid/src/SolidLdoTransactionDataset.ts deleted file mode 100644 index 4a2306e..0000000 --- a/packages/solid/src/SolidLdoTransactionDataset.ts +++ /dev/null @@ -1,186 +0,0 @@ -import { LdoTransactionDataset } from "@ldo/ldo"; -import type { ISolidLdoDataset } from "./types"; -import type { ResourceGetterOptions } from "./ResourceStore"; -import type { Container } from "./resource/Container"; -import type { Leaf } from "./resource/Leaf"; -import { - isContainerUri, - type ContainerUri, - type LeafUri, -} from "./util/uriTypes"; -import type { SolidLdoDatasetContext } from "./SolidLdoDatasetContext"; -import type { DatasetFactory, Quad } from "@rdfjs/types"; -import { - updateDatasetInBulk, - type ITransactionDatasetFactory, -} from "@ldo/subscribable-dataset"; -import type { AggregateSuccess } from "./requester/results/success/SuccessResult"; -import type { ResourceResult } from "./resource/resourceResult/ResourceResult"; -import type { - IgnoredInvalidUpdateSuccess, - UpdateDefaultGraphSuccess, - UpdateSuccess, -} from "./requester/results/success/UpdateSuccess"; -import { AggregateError } from "./requester/results/error/ErrorResult"; -import type { - UpdateResult, - UpdateResultError, -} from "./requester/requests/updateDataResource"; -import type { DatasetChanges, GraphNode } from "@ldo/rdf-utils"; -import { splitChangesByGraph } from "./util/splitChangesByGraph"; - -/** - * A SolidLdoTransactionDataset has all the functionality of a SolidLdoDataset - * and represents a transaction to the parent SolidLdoDataset. - * - * It is recommended to use the `startTransaction` method on a SolidLdoDataset - * to initialize this class - * - * @example - * ```typescript - * import { createSolidLdoDataset } from "@ldo/solid"; - * import { ProfileShapeType } from "./.ldo/profile.shapeTypes.ts" - * - * // ... - * - * const solidLdoDataset = createSolidLdoDataset(); - * - * const profileDocument = solidLdoDataset - * .getResource("https://example.com/profile"); - * await profileDocument.read(); - * - * const transaction = solidLdoDataset.startTransaction(); - * - * const profile = transaction - * .using(ProfileShapeType) - * .fromSubject("https://example.com/profile#me"); - * profile.name = "Some Name"; - * await transaction.commitToPod(); - * ``` - */ -export class SolidLdoTransactionDataset - extends LdoTransactionDataset - implements ISolidLdoDataset -{ - /** - * @internal - */ - public context: SolidLdoDatasetContext; - - /** - * @param context - SolidLdoDatasetContext - * @param datasetFactory - An optional dataset factory - * @param transactionDatasetFactory - A factory for creating transaction datasets - * @param initialDataset - A set of triples to initialize this dataset - */ - constructor( - parentDataset: ISolidLdoDataset, - context: SolidLdoDatasetContext, - datasetFactory: DatasetFactory, - transactionDatasetFactory: ITransactionDatasetFactory, - ) { - super(parentDataset, datasetFactory, transactionDatasetFactory); - this.context = context; - } - - /** - * Retireves a representation (either a LeafResource or a ContainerResource) - * of a Solid Resource at the given URI. This resource represents the - * current state of the resource: whether it is currently fetched or in the - * process of fetching as well as some information about it. - * - * @param uri - the URI of the resource - * @param options - Special options for getting the resource - * - * @returns a Leaf or Container Resource - * - * @example - * ```typescript - * const profileDocument = solidLdoDataset - * .getResource("https://example.com/profile"); - * ``` - */ - getResource(uri: ContainerUri, options?: ResourceGetterOptions): Container; - getResource(uri: LeafUri, options?: ResourceGetterOptions): Leaf; - getResource(uri: string, options?: ResourceGetterOptions): Leaf | Container; - getResource(uri: string, options?: ResourceGetterOptions): Leaf | Container { - return this.context.resourceStore.get(uri, options); - } - - public startTransaction(): SolidLdoTransactionDataset { - return new SolidLdoTransactionDataset( - this, - this.context, - this.datasetFactory, - this.transactionDatasetFactory, - ); - } - - async commitToPod(): Promise< - | AggregateSuccess< - ResourceResult - > - | AggregateError - > { - const changes = this.getChanges(); - const changesByGraph = splitChangesByGraph(changes); - - // Iterate through all changes by graph in - const results: [ - GraphNode, - DatasetChanges, - UpdateResult | IgnoredInvalidUpdateSuccess | UpdateDefaultGraphSuccess, - ][] = await Promise.all( - Array.from(changesByGraph.entries()).map( - async ([graph, datasetChanges]) => { - if (graph.termType === "DefaultGraph") { - // Undefined means that this is the default graph - updateDatasetInBulk(this.parentDataset, datasetChanges); - return [ - graph, - datasetChanges, - { - type: "updateDefaultGraphSuccess", - isError: false, - } as UpdateDefaultGraphSuccess, - ]; - } - if (isContainerUri(graph.value)) { - return [ - graph, - datasetChanges, - { - type: "ignoredInvalidUpdateSuccess", - isError: false, - } as IgnoredInvalidUpdateSuccess, - ]; - } - const resource = this.getResource(graph.value as LeafUri); - const updateResult = await resource.update(datasetChanges); - return [graph, datasetChanges, updateResult]; - }, - ), - ); - - // If one has errored, return error - const errors = results.filter((result) => result[2].isError); - - if (errors.length > 0) { - return new AggregateError( - errors.map((result) => result[2] as UpdateResultError), - ); - } - return { - isError: false, - type: "aggregateSuccess", - results: results - .map((result) => result[2]) - .filter( - (result): result is ResourceResult => - result.type === "updateSuccess" || - result.type === "updateDefaultGraphSuccess" || - result.type === "ignoredInvalidUpdateSuccess", - ), - }; - } -} diff --git a/packages/solid/src/createSolidLdoDataset.ts b/packages/solid/src/createSolidLdoDataset.ts deleted file mode 100644 index dfafe85..0000000 --- a/packages/solid/src/createSolidLdoDataset.ts +++ /dev/null @@ -1,68 +0,0 @@ -import type { Dataset, DatasetFactory } from "@rdfjs/types"; -import { SolidLdoDataset } from "./SolidLdoDataset"; - -import type { SolidLdoDatasetContext } from "./SolidLdoDatasetContext"; -import { createDataset, createDatasetFactory } from "@ldo/dataset"; -import { ResourceStore } from "./ResourceStore"; -import { guaranteeFetch } from "./util/guaranteeFetch"; -import { createTransactionDatasetFactory } from "@ldo/subscribable-dataset"; - -/** - * Options for createSolidDataset - */ -export interface CreateSolidLdoDatasetOptions { - /** - * A fetch function. Most often, this is the fetch function from @inrupt/solid-clieht-authn-js - */ - fetch?: typeof fetch; - /** - * An initial dataset - * @default A blank dataset - */ - dataset?: Dataset; - /** - * An RDFJS DatasetFactory - * @default An extended RDFJS DatasetFactory - */ - datasetFactory?: DatasetFactory; -} - -/** - * Creates a SolidLdoDataset - * - * @param options - CreateSolidLdoDatasetOptions - * @returns A SolidLdoDataset - * - * @example - * ```typescript - * import { createSolidLdoDataset } from "@ldo/solid"; - * import { fetch } from "@inrupt/solid-client-authn-browswer"; - * - * const solidLdoDataset = createSolidLdoDataset({ fetch }); - * ``` - */ -export function createSolidLdoDataset( - options?: CreateSolidLdoDatasetOptions, -): SolidLdoDataset { - const finalFetch = guaranteeFetch(options?.fetch); - const finalDatasetFactory = options?.datasetFactory || createDatasetFactory(); - const finalDataset = options?.dataset || createDataset(); - - // Ignoring because of circular dependency - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const context: SolidLdoDatasetContext = { - fetch: finalFetch, - }; - const solidLdoDataset = new SolidLdoDataset( - context, - finalDatasetFactory, - createTransactionDatasetFactory(), - finalDataset, - ); - const resourceStore = new ResourceStore(context); - context.solidLdoDataset = solidLdoDataset; - context.resourceStore = resourceStore; - - return solidLdoDataset; -} diff --git a/packages/solid/src/index.ts b/packages/solid/src/index.ts deleted file mode 100644 index 41ff71c..0000000 --- a/packages/solid/src/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -export * from "./createSolidLdoDataset"; -export * from "./SolidLdoDataset"; -export * from "./SolidLdoDatasetContext"; -export * from "./SolidLdoTransactionDataset"; - -export * from "./resource/Resource"; -export * from "./resource/Container"; -export * from "./resource/Leaf"; - -export * from "./util/uriTypes"; - -export * from "./methods"; - -export * from "./requester/requests/checkRootContainer"; -export * from "./requester/requests/createDataResource"; -export * from "./requester/requests/deleteResource"; -export * from "./requester/requests/readResource"; -export * from "./requester/requests/requestOptions"; -export * from "./requester/requests/updateDataResource"; -export * from "./requester/requests/uploadResource"; - -export * from "./resource/wac/WacRule"; -export * from "./resource/wac/getWacRule"; -export * from "./resource/wac/getWacUri"; -export * from "./resource/wac/setWacRule"; -export * from "./resource/wac/results/GetWacRuleSuccess"; -export * from "./resource/wac/results/GetWacUriSuccess"; -export * from "./resource/wac/results/SetWacRuleSuccess"; -export * from "./resource/wac/results/WacRuleAbsent"; - -export * from "./types"; diff --git a/packages/solid/src/methods.ts b/packages/solid/src/methods.ts deleted file mode 100644 index 5fb36a7..0000000 --- a/packages/solid/src/methods.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { startTransaction, type LdoBase, write, getDataset } from "@ldo/ldo"; -import type { Resource } from "./resource/Resource"; -import type { Quad } from "@rdfjs/types"; -import { _proxyContext, getProxyFromObject } from "@ldo/jsonld-dataset-proxy"; -import type { SubscribableDataset } from "@ldo/subscribable-dataset"; -import type { SolidLdoTransactionDataset } from "./SolidLdoTransactionDataset"; - -/** - * Begins tracking changes to eventually commit. - * - * @param input - A linked data object to track changes on - * @param resource - A resource that all additions will eventually be committed to - * @param additionalResources - Any additional resources that changes will eventually be committed to - * - * @returns A transactable Linked Data Object - * - * @example - * ```typescript - * import { changeData } from "@ldo/solid"; - * - * // ... - * - * const profile = solidLdoDataset - * .using(ProfileShapeType) - * .fromSubject("https://example.com/profile#me"); - * const resource = solidLdoDataset.getResource("https://example.com/profile"); - * - * const cProfile = changeData(profile, resource); - * cProfile.name = "My New Name"; - * const result = await commitData(cProfile); - * ``` - */ -export function changeData( - input: Type, - resource: Resource, - ...additionalResources: Resource[] -): Type { - const resources = [resource, ...additionalResources]; - // Clone the input and set a graph - const [transactionLdo] = write(...resources.map((r) => r.uri)).usingCopy( - input, - ); - // Start a transaction with the input - startTransaction(transactionLdo); - // Return - return transactionLdo; -} - -/** - * Commits the transaction to the global dataset, syncing all subscribing - * components and Solid Pods - * - * @param input - A transactable linked data object - * - * @example - * ```typescript - * import { changeData } from "@ldo/solid"; - * - * // ... - * - * const profile = solidLdoDataset - * .using(ProfileShapeType) - * .fromSubject("https://example.com/profile#me"); - * const resource = solidLdoDataset.getResource("https://example.com/profile"); - * - * const cProfile = changeData(profile, resource); - * cProfile.name = "My New Name"; - * const result = await commitData(cProfile); - * ``` - */ -export async function commitData( - input: LdoBase, -): ReturnType { - const transactionDataset = getDataset(input) as SolidLdoTransactionDataset; - const result = await transactionDataset.commitToPod(); - if (result.isError) return result; - // Take the LdoProxy out of commit mode. This uses hidden methods of JSONLD-DATASET-PROXY - const proxy = getProxyFromObject(input); - proxy[_proxyContext] = proxy[_proxyContext].duplicate({ - dataset: proxy[_proxyContext].state - .parentDataset as SubscribableDataset, - }); - return result; -} diff --git a/packages/solid/src/requester/BatchedRequester.ts b/packages/solid/src/requester/BatchedRequester.ts deleted file mode 100644 index 7c0b5a8..0000000 --- a/packages/solid/src/requester/BatchedRequester.ts +++ /dev/null @@ -1,190 +0,0 @@ -import { ANY_KEY, RequestBatcher } from "../util/RequestBatcher"; -import type { SolidLdoDatasetContext } from "../SolidLdoDatasetContext"; -import type { - ContainerCreateAndOverwriteResult, - ContainerCreateIfAbsentResult, - LeafCreateAndOverwriteResult, - LeafCreateIfAbsentResult, -} from "./requests/createDataResource"; -import { createDataResource } from "./requests/createDataResource"; -import type { - ReadContainerResult, - ReadLeafResult, -} from "./requests/readResource"; -import { readResource } from "./requests/readResource"; -import type { DeleteResult } from "./requests/deleteResource"; -import { deleteResource } from "./requests/deleteResource"; -import { modifyQueueByMergingEventsWithTheSameKeys } from "./util/modifyQueueFuntions"; - -const READ_KEY = "read"; -const CREATE_KEY = "createDataResource"; -const DELETE_KEY = "delete"; - -/** - * @internal - * - * A singleton for handling batched requests - */ -export abstract class BatchedRequester { - /** - * @internal - * A request batcher to maintain state for ongoing requests - */ - protected readonly requestBatcher = new RequestBatcher(); - - /** - * The uri of the resource - */ - abstract readonly uri: string; - - /** - * @internal - * SolidLdoDatasetContext for the parent SolidLdoDataset - */ - protected context: SolidLdoDatasetContext; - - /** - * @param context - SolidLdoDatasetContext for the parent SolidLdoDataset - */ - constructor(context: SolidLdoDatasetContext) { - this.context = context; - } - - /** - * Checks if the resource is currently making any request - * @returns true if the resource is making any requests - */ - isLoading(): boolean { - return this.requestBatcher.isLoading(ANY_KEY); - } - - /** - * Checks if the resource is currently executing a create request - * @returns true if the resource is currently executing a create request - */ - isCreating(): boolean { - return this.requestBatcher.isLoading(CREATE_KEY); - } - - /** - * Checks if the resource is currently executing a read request - * @returns true if the resource is currently executing a read request - */ - isReading(): boolean { - return this.requestBatcher.isLoading(READ_KEY); - } - - /** - * Checks if the resource is currently executing a delete request - * @returns true if the resource is currently executing a delete request - */ - isDeletinng(): boolean { - return this.requestBatcher.isLoading(DELETE_KEY); - } - - /** - * Read this resource. - * @returns A ReadLeafResult or a ReadContainerResult depending on the uri of - * this resource - */ - async read(): Promise { - const transaction = this.context.solidLdoDataset.startTransaction(); - const result = await this.requestBatcher.queueProcess({ - name: READ_KEY, - args: [this.uri, { dataset: transaction, fetch: this.context.fetch }], - perform: readResource, - modifyQueue: modifyQueueByMergingEventsWithTheSameKeys(READ_KEY), - after: (result) => { - if (!result.isError) { - transaction.commit(); - } - }, - }); - return result; - } - - /** - * Delete this resource - * @returns A DeleteResult - */ - async delete(): Promise { - const transaction = this.context.solidLdoDataset.startTransaction(); - const result = await this.requestBatcher.queueProcess({ - name: DELETE_KEY, - args: [this.uri, { dataset: transaction, fetch: this.context.fetch }], - perform: deleteResource, - modifyQueue: modifyQueueByMergingEventsWithTheSameKeys(DELETE_KEY), - after: (result) => { - if (!result.isError) { - transaction.commit(); - } - }, - }); - return result; - } - - /** - * Creates a Resource - * @param overwrite - If true, this will orverwrite the resource if it already - * exists - * @returns A ContainerCreateAndOverwriteResult or a - * LeafCreateAndOverwriteResult depending on this resource's URI - */ - createDataResource( - overwrite: true, - ): Promise; - createDataResource( - overwrite?: false, - ): Promise; - createDataResource( - overwrite?: boolean, - ): Promise< - | ContainerCreateAndOverwriteResult - | LeafCreateAndOverwriteResult - | ContainerCreateIfAbsentResult - | LeafCreateIfAbsentResult - >; - async createDataResource( - overwrite?: boolean, - ): Promise< - | ContainerCreateAndOverwriteResult - | LeafCreateAndOverwriteResult - | ContainerCreateIfAbsentResult - | LeafCreateIfAbsentResult - > { - const transaction = this.context.solidLdoDataset.startTransaction(); - const result = await this.requestBatcher.queueProcess({ - name: CREATE_KEY, - args: [ - this.uri, - overwrite, - { dataset: transaction, fetch: this.context.fetch }, - ], - perform: createDataResource, - modifyQueue: (queue, currentlyLoading, args) => { - const lastElementInQueue = queue[queue.length - 1]; - if ( - lastElementInQueue && - lastElementInQueue.name === CREATE_KEY && - !!lastElementInQueue.args[1] === !!args[1] - ) { - return lastElementInQueue; - } - if ( - currentlyLoading && - currentlyLoading.name === CREATE_KEY && - !!currentlyLoading.args[1] === !!args[1] - ) { - return currentlyLoading; - } - return undefined; - }, - after: (result) => { - if (!result.isError) { - transaction.commit(); - } - }, - }); - return result; - } -} diff --git a/packages/solid/src/requester/ContainerBatchedRequester.ts b/packages/solid/src/requester/ContainerBatchedRequester.ts deleted file mode 100644 index 42d744b..0000000 --- a/packages/solid/src/requester/ContainerBatchedRequester.ts +++ /dev/null @@ -1,79 +0,0 @@ -import type { SolidLdoDatasetContext } from "../SolidLdoDatasetContext"; -import type { ContainerUri } from "../util/uriTypes"; -import { BatchedRequester } from "./BatchedRequester"; -import type { CheckRootResult } from "./requests/checkRootContainer"; -import { checkRootContainer } from "./requests/checkRootContainer"; -import type { - ContainerCreateAndOverwriteResult, - ContainerCreateIfAbsentResult, -} from "./requests/createDataResource"; -import type { ReadContainerResult } from "./requests/readResource"; -import { modifyQueueByMergingEventsWithTheSameKeys } from "./util/modifyQueueFuntions"; - -export const IS_ROOT_CONTAINER_KEY = "isRootContainer"; - -/** - * @internal - * - * A singleton to handle batched requests for containers - */ -export class ContainerBatchedRequester extends BatchedRequester { - /** - * The URI of the container - */ - readonly uri: ContainerUri; - - /** - * @param uri - The URI of the container - * @param context - SolidLdoDatasetContext of the parent dataset - */ - constructor(uri: ContainerUri, context: SolidLdoDatasetContext) { - super(context); - this.uri = uri; - } - - /** - * Reads the container - * @returns A ReadContainerResult - */ - read(): Promise { - return super.read() as Promise; - } - - /** - * Creates the container - * @param overwrite - If true, this will orverwrite the resource if it already - * exists - */ - createDataResource( - overwrite: true, - ): Promise; - createDataResource(overwrite?: false): Promise; - createDataResource( - overwrite?: boolean, - ): Promise; - createDataResource( - overwrite?: boolean, - ): Promise< - ContainerCreateIfAbsentResult | ContainerCreateAndOverwriteResult - > { - return super.createDataResource(overwrite) as Promise< - ContainerCreateIfAbsentResult | ContainerCreateAndOverwriteResult - >; - } - - /** - * Checks to see if this container is a root container - * @returns A CheckRootResult - */ - async isRootContainer(): Promise { - return this.requestBatcher.queueProcess({ - name: IS_ROOT_CONTAINER_KEY, - args: [this.uri as ContainerUri, { fetch: this.context.fetch }], - perform: checkRootContainer, - modifyQueue: modifyQueueByMergingEventsWithTheSameKeys( - IS_ROOT_CONTAINER_KEY, - ), - }); - } -} diff --git a/packages/solid/src/requester/LeafBatchedRequester.ts b/packages/solid/src/requester/LeafBatchedRequester.ts deleted file mode 100644 index c40b63a..0000000 --- a/packages/solid/src/requester/LeafBatchedRequester.ts +++ /dev/null @@ -1,172 +0,0 @@ -import type { DatasetChanges } from "@ldo/rdf-utils"; -import { mergeDatasetChanges } from "@ldo/subscribable-dataset"; -import type { Quad } from "@rdfjs/types"; -import type { SolidLdoDatasetContext } from "../SolidLdoDatasetContext"; -import type { LeafUri } from "../util/uriTypes"; -import { BatchedRequester } from "./BatchedRequester"; -import type { - LeafCreateAndOverwriteResult, - LeafCreateIfAbsentResult, -} from "./requests/createDataResource"; -import type { ReadLeafResult } from "./requests/readResource"; -import type { UpdateResult } from "./requests/updateDataResource"; -import { updateDataResource } from "./requests/updateDataResource"; -import { uploadResource } from "./requests/uploadResource"; - -export const UPDATE_KEY = "update"; -export const UPLOAD_KEY = "upload"; - -/** - * @internal - * - * A singleton to handle batched requests for leafs - */ -export class LeafBatchedRequester extends BatchedRequester { - /** - * The URI of the leaf - */ - readonly uri: LeafUri; - - /** - * @param uri - the URI of the leaf - * @param context - SolidLdoDatasetContext of the parent dataset - */ - constructor(uri: LeafUri, context: SolidLdoDatasetContext) { - super(context); - this.uri = uri; - } - - /** - * Checks if the resource is currently executing an update request - * @returns true if the resource is currently executing an update request - */ - isUpdating(): boolean { - return this.requestBatcher.isLoading(UPDATE_KEY); - } - - /** - * Checks if the resource is currently executing an upload request - * @returns true if the resource is currently executing an upload request - */ - isUploading(): boolean { - return this.requestBatcher.isLoading(UPLOAD_KEY); - } - - /** - * Reads the leaf - * @returns A ReadLeafResult - */ - async read(): Promise { - return super.read() as Promise; - } - - /** - * Creates the leaf as a data resource - * @param overwrite - If true, this will orverwrite the resource if it already - * exists - */ - createDataResource(overwrite: true): Promise; - createDataResource(overwrite?: false): Promise; - createDataResource( - overwrite?: boolean, - ): Promise; - createDataResource( - overwrite?: boolean, - ): Promise { - return super.createDataResource(overwrite) as Promise< - LeafCreateIfAbsentResult | LeafCreateAndOverwriteResult - >; - } - - /** - * Update the data on this resource - * @param changes - DatasetChanges that should be applied to the Pod - */ - async updateDataResource( - changes: DatasetChanges, - ): Promise { - const result = await this.requestBatcher.queueProcess({ - name: UPDATE_KEY, - args: [ - this.uri, - changes, - { fetch: this.context.fetch, dataset: this.context.solidLdoDataset }, - ], - perform: updateDataResource, - modifyQueue: (queue, currentlyProcessing, [, changes]) => { - if (queue[queue.length - 1]?.name === UPDATE_KEY) { - // Merge Changes - const originalChanges = queue[queue.length - 1].args[1]; - mergeDatasetChanges(originalChanges, changes); - return queue[queue.length - 1]; - } - return undefined; - }, - }); - return result; - } - - /** - * Upload a binary at this resource's URI - * @param blob - A binary blob - * @param mimeType - the mime type of the blob - * @param overwrite: If true, will overwrite an existing file - */ - upload( - blob: Blob, - mimeType: string, - overwrite: true, - ): Promise; - upload( - blob: Blob, - mimeType: string, - overwrite?: false, - ): Promise; - upload( - blob: Blob, - mimeType: string, - overwrite?: boolean, - ): Promise; - async upload( - blob: Blob, - mimeType: string, - overwrite?: boolean, - ): Promise { - const transaction = this.context.solidLdoDataset.startTransaction(); - const result = await this.requestBatcher.queueProcess({ - name: UPLOAD_KEY, - args: [ - this.uri, - blob, - mimeType, - overwrite, - { dataset: transaction, fetch: this.context.fetch }, - ], - perform: uploadResource, - modifyQueue: (queue, currentlyLoading, args) => { - const lastElementInQueue = queue[queue.length - 1]; - if ( - lastElementInQueue && - lastElementInQueue.name === UPLOAD_KEY && - !!lastElementInQueue.args[3] === !!args[3] - ) { - return lastElementInQueue; - } - if ( - currentlyLoading && - currentlyLoading.name === UPLOAD_KEY && - !!currentlyLoading.args[3] === !!args[3] - ) { - return currentlyLoading; - } - return undefined; - }, - after: (result) => { - if (!result.isError) { - transaction.commit(); - } - }, - }); - return result; - } -} diff --git a/packages/solid/src/requester/requests/checkRootContainer.ts b/packages/solid/src/requester/requests/checkRootContainer.ts deleted file mode 100644 index 2c8df9f..0000000 --- a/packages/solid/src/requester/requests/checkRootContainer.ts +++ /dev/null @@ -1,99 +0,0 @@ -import type { BasicRequestOptions } from "./requestOptions"; -import { parse as parseLinkHeader } from "http-link-header"; -import type { CheckRootContainerSuccess } from "../results/success/CheckRootContainerSuccess"; -import type { - HttpErrorResultType, - UnexpectedHttpError, -} from "../results/error/HttpErrorResult"; -import { HttpErrorResult } from "../results/error/HttpErrorResult"; -import { UnexpectedResourceError } from "../results/error/ErrorResult"; -import { guaranteeFetch } from "../../util/guaranteeFetch"; -import type { ContainerUri } from "../../util/uriTypes"; - -/** - * checkRootContainer result - */ -export type CheckRootResult = CheckRootContainerSuccess | CheckRootResultError; - -/** - * All possible errors checkRootResult can return - */ -export type CheckRootResultError = - | HttpErrorResultType - | UnexpectedHttpError - | UnexpectedResourceError; - -/** - * @internal - * Checks provided headers to see if a given URI is a root container as defined - * in the [solid specification section 4.1](https://solidproject.org/TR/protocol#storage-resource) - * - * @param uri - the URI of the container resource - * @param headers - headers returned when making a GET request to the resource - * @returns CheckRootContainerSuccess if there is not error - */ -export function checkHeadersForRootContainer( - uri: ContainerUri, - headers: Headers, -): CheckRootContainerSuccess { - const linkHeader = headers.get("link"); - if (!linkHeader) { - return { - uri, - isRootContainer: false, - type: "checkRootContainerSuccess", - isError: false, - }; - } - const parsedLinkHeader = parseLinkHeader(linkHeader); - const types = parsedLinkHeader.get("rel", "type"); - const isRootContainer = types.some( - (type) => type.uri === "http://www.w3.org/ns/pim/space#Storage", - ); - return { - uri, - isRootContainer, - type: "checkRootContainerSuccess", - isError: false, - }; -} - -/** - * Performs a request to the Pod to check if the given URI is a root container - * as defined in the [solid specification section 4.1](https://solidproject.org/TR/protocol#storage-resource) - * - * @param uri - the URI of the container resource - * @param options - options variable to pass a fetch function - * @returns CheckResourceSuccess if there is no error - * - * @example - * ```typescript - * import { checkRootContainer } from "@ldo/solid"; - * import { fetch } from "@inrupt/solid-client-authn-browser"; - * - * const result = await checkRootContainer("https://example.com/", { fetch }); - * if (!result.isError) { - * // true if the container is a root container - * console.log(result.isRootContainer); - * } - * ``` - */ -export async function checkRootContainer( - uri: ContainerUri, - options?: BasicRequestOptions, -): Promise { - try { - const fetch = guaranteeFetch(options?.fetch); - // Fetch options to determine the document type - // 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); - if (httpErrorResult) return httpErrorResult; - - return checkHeadersForRootContainer(uri, response.headers); - } catch (err) { - return UnexpectedResourceError.fromThrown(uri, err); - } -} diff --git a/packages/solid/src/requester/requests/createDataResource.ts b/packages/solid/src/requester/requests/createDataResource.ts deleted file mode 100644 index 4c5bd5c..0000000 --- a/packages/solid/src/requester/requests/createDataResource.ts +++ /dev/null @@ -1,241 +0,0 @@ -import { guaranteeFetch } from "../../util/guaranteeFetch"; -import { - addResourceRdfToContainer, - getParentUri, - getSlug, -} from "../../util/rdfUtils"; -import type { ContainerUri, LeafUri } from "../../util/uriTypes"; -import { isContainerUri } from "../../util/uriTypes"; -import { UnexpectedResourceError } from "../results/error/ErrorResult"; -import type { HttpErrorResultType } from "../results/error/HttpErrorResult"; -import { HttpErrorResult } from "../results/error/HttpErrorResult"; -import type { CreateSuccess } from "../results/success/CreateSuccess"; -import type { AbsentReadSuccess } from "../results/success/ReadSuccess"; -import type { DeleteResultError } from "./deleteResource"; -import { deleteResource } from "./deleteResource"; -import type { - ReadContainerResult, - ReadLeafResult, - ReadResultError, -} from "./readResource"; -import { readResource } from "./readResource"; -import type { DatasetRequestOptions } from "./requestOptions"; - -/** - * All possible return values when creating and overwriting a container - */ -export type ContainerCreateAndOverwriteResult = - | CreateSuccess - | CreateAndOverwriteResultErrors; - -/** - * All possible return values when creating and overwriting a leaf - */ -export type LeafCreateAndOverwriteResult = - | CreateSuccess - | CreateAndOverwriteResultErrors; - -/** - * All possible return values when creating a container if absent - */ -export type ContainerCreateIfAbsentResult = - | CreateSuccess - | Exclude - | CreateIfAbsentResultErrors; - -/** - * All possible return values when creating a leaf if absent - */ -export type LeafCreateIfAbsentResult = - | CreateSuccess - | Exclude - | CreateIfAbsentResultErrors; - -/** - * All possible errors returned by creating and overwriting a resource - */ -export type CreateAndOverwriteResultErrors = DeleteResultError | CreateErrors; - -/** - * All possible errors returned by creating a resource if absent - */ -export type CreateIfAbsentResultErrors = ReadResultError | CreateErrors; - -/** - * All possible errors returned by creating a resource - */ -export type CreateErrors = HttpErrorResultType | UnexpectedResourceError; - -/** - * Creates a data resource (RDF resource) at the provided URI. This resource - * could also be a container. - * - * @param uri - The URI of the resource - * @param overwrite - If true, the request will overwrite any previous resource - * at this URI. - * @param options - Options to provide a fetch function and a local dataset to - * update. - * @returns One of many create results depending on the input - * - * @example - * `createDataResource` can be used to create containers. - * - * ```typescript - * import { createDataResource } from "@ldo/solid"; - * import { fetch } from "@inrupt/solid-client-autn-js"; - * - * const result = await createDataResource( - * "https://example.com/container/", - * true, - * { fetch }, - * ); - * if (!result.isError) { - * // Do something - * } - * ``` - * - * @example - * `createDataResource` can also create a blank data resource at the provided - * URI. - * - * ```typescript - * import { createDataResource } from "@ldo/solid"; - * import { fetch } from "@inrupt/solid-client-autn-js"; - * - * const result = await createDataResource( - * "https://example.com/container/someResource.ttl", - * true, - * { fetch }, - * ); - * if (!result.isError) { - * // Do something - * } - * ``` - * - * @example - * Any local RDFJS dataset passed to the `options` field will be updated with - * any new RDF data from the create process. - * - * ```typescript - * import { createDataResource } from "@ldo/solid"; - * import { createDataset } from "@ldo/dataset" - * import { fetch } from "@inrupt/solid-client-autn-js"; - * - * const localDataset = createDataset(); - * const result = await createDataResource( - * "https://example.com/container/someResource.ttl", - * true, - * { fetch, dataset: localDataset }, - * ); - * if (!result.isError) { - * // Do something - * } - * ``` - */ -export function createDataResource( - uri: ContainerUri, - overwrite: true, - options?: DatasetRequestOptions, -): Promise; -export function createDataResource( - uri: LeafUri, - overwrite: true, - options?: DatasetRequestOptions, -): Promise; -export function createDataResource( - uri: ContainerUri, - overwrite?: false, - options?: DatasetRequestOptions, -): Promise; -export function createDataResource( - uri: LeafUri, - overwrite?: false, - options?: DatasetRequestOptions, -): Promise; -export function createDataResource( - uri: ContainerUri, - overwrite?: boolean, - options?: DatasetRequestOptions, -): Promise; -export function createDataResource( - uri: LeafUri, - overwrite?: boolean, - options?: DatasetRequestOptions, -): Promise; -export function createDataResource( - uri: string, - overwrite: true, - options?: DatasetRequestOptions, -): Promise; -export function createDataResource( - uri: string, - overwrite?: false, - options?: DatasetRequestOptions, -): Promise; -export function createDataResource( - uri: string, - overwrite?: boolean, - options?: DatasetRequestOptions, -): Promise< - | ContainerCreateAndOverwriteResult - | LeafCreateAndOverwriteResult - | ContainerCreateIfAbsentResult - | LeafCreateIfAbsentResult ->; -export async function createDataResource( - uri: string, - overwrite?: boolean, - options?: DatasetRequestOptions, -): Promise< - | ContainerCreateAndOverwriteResult - | LeafCreateAndOverwriteResult - | ContainerCreateIfAbsentResult - | LeafCreateIfAbsentResult -> { - try { - const fetch = guaranteeFetch(options?.fetch); - let didOverwrite = false; - if (overwrite) { - const deleteResult = await deleteResource(uri, options); - // Return if it wasn't deleted - if (deleteResult.isError) return deleteResult; - didOverwrite = deleteResult.resourceExisted; - } else { - // Perform a read to check if it exists - const readResult = await readResource(uri, options); - - // If it does exist stop and return. - if (readResult.type !== "absentReadSuccess") { - return readResult; - } - } - // Create the document - const parentUri = getParentUri(uri)!; - const headers: HeadersInit = { - "content-type": "text/turtle", - slug: getSlug(uri), - }; - if (isContainerUri(uri)) { - headers.link = '; rel="type"'; - } - const response = await fetch(parentUri, { - method: "post", - headers, - }); - - const httpError = HttpErrorResult.checkResponse(uri, response); - if (httpError) return httpError; - - if (options?.dataset) { - addResourceRdfToContainer(uri, options.dataset); - } - return { - isError: false, - type: "createSuccess", - uri, - didOverwrite, - }; - } catch (err) { - return UnexpectedResourceError.fromThrown(uri, err); - } -} diff --git a/packages/solid/src/requester/requests/deleteResource.ts b/packages/solid/src/requester/requests/deleteResource.ts deleted file mode 100644 index cc8f084..0000000 --- a/packages/solid/src/requester/requests/deleteResource.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { namedNode } from "@rdfjs/data-model"; -import { guaranteeFetch } from "../../util/guaranteeFetch"; -import { deleteResourceRdfFromContainer } from "../../util/rdfUtils"; -import { UnexpectedResourceError } from "../results/error/ErrorResult"; -import type { HttpErrorResultType } from "../results/error/HttpErrorResult"; -import { UnexpectedHttpError } from "../results/error/HttpErrorResult"; -import { HttpErrorResult } from "../results/error/HttpErrorResult"; -import type { DeleteSuccess } from "../results/success/DeleteSuccess"; -import type { DatasetRequestOptions } from "./requestOptions"; -import type { IBulkEditableDataset } from "@ldo/subscribable-dataset"; -import type { Quad } from "@rdfjs/types"; - -/** - * All possible return values for deleteResource - */ -export type DeleteResult = DeleteSuccess | DeleteResultError; - -/** - * All possible errors that can be returned by deleteResource - */ -export type DeleteResultError = HttpErrorResultType | UnexpectedResourceError; - -/** - * Deletes a resource on a Pod at a given URL. - * - * @param uri - The URI for the resource that should be deleted - * @param options - Options to provide a fetch function and a local dataset to - * update. - * @returns a DeleteResult - * - * @example - * `deleteResource` will send a request to a Solid Pod using the provided fetch - * function. A local dataset can also be provided. It will be updated with any - * new information from the delete. - * - * ```typescript - * import { deleteResource } from "@ldo/solid"; - * import { createDataset } from "@ldo/dataset" - * import { fetch } from "@inrupt/solid-client-autn-js"; - * - * const localDataset = createDataset(); - * const result = await deleteResource( - * "https://example.com/container/someResource.ttl", - * { fetch, dataset: localDataset }, - * ); - * if (!result.isError) { - * // Do something - * } - * ``` - */ -export async function deleteResource( - uri: string, - options?: DatasetRequestOptions, -): Promise { - try { - const fetch = guaranteeFetch(options?.fetch); - const response = await fetch(uri, { - method: "delete", - }); - const errorResult = HttpErrorResult.checkResponse(uri, response); - if (errorResult) return errorResult; - - // Specifically check for a 205. Annoyingly, the server will return 200 even - // if it hasn't been deleted when you're unauthenticated. 404 happens when - // the document never existed - if (response.status === 205 || response.status === 404) { - if (options?.dataset) - updateDatasetOnSuccessfulDelete(uri, options.dataset); - return { - isError: false, - type: "deleteSuccess", - uri, - resourceExisted: response.status === 205, - }; - } - return new UnexpectedHttpError(uri, response); - } catch (err) { - return UnexpectedResourceError.fromThrown(uri, err); - } -} - -/** - * Assuming a successful delete has just been performed, this function updates - * datastores to reflect that. - * - * @param uri - The uri of the resouce that was removed - * @param dataset - The dataset that should be updated - */ -export function updateDatasetOnSuccessfulDelete( - uri: string, - dataset: IBulkEditableDataset, -): void { - dataset.deleteMatches(undefined, undefined, undefined, namedNode(uri)); - deleteResourceRdfFromContainer(uri, dataset); -} diff --git a/packages/solid/src/requester/requests/readResource.ts b/packages/solid/src/requester/requests/readResource.ts deleted file mode 100644 index 4b9a7bc..0000000 --- a/packages/solid/src/requester/requests/readResource.ts +++ /dev/null @@ -1,182 +0,0 @@ -import type { UnexpectedHttpError } from "../results/error/HttpErrorResult"; -import { - HttpErrorResult, - type HttpErrorResultType, -} from "../results/error/HttpErrorResult"; -import { - addRawTurtleToDataset, - addResourceRdfToContainer, -} from "../../util/rdfUtils"; -import type { DatasetRequestOptions } from "./requestOptions"; -import type { ContainerUri, LeafUri } from "../../util/uriTypes"; -import { isContainerUri } from "../../util/uriTypes"; -import type { BinaryReadSuccess } from "../results/success/ReadSuccess"; -import type { - ContainerReadSuccess, - DataReadSuccess, -} from "../results/success/ReadSuccess"; -import type { AbsentReadSuccess } from "../results/success/ReadSuccess"; -import { NoncompliantPodError } from "../results/error/NoncompliantPodError"; -import { guaranteeFetch } from "../../util/guaranteeFetch"; -import { UnexpectedResourceError } from "../results/error/ErrorResult"; -import { checkHeadersForRootContainer } from "./checkRootContainer"; -import { namedNode } from "@rdfjs/data-model"; - -/** - * All possible return values for reading a leaf - */ -export type ReadLeafResult = - | BinaryReadSuccess - | DataReadSuccess - | AbsentReadSuccess - | ReadResultError; - -/** - * All possible return values for reading a container - */ -export type ReadContainerResult = - | ContainerReadSuccess - | AbsentReadSuccess - | ReadResultError; - -/** - * All possible errors the readResource function can return - */ -export type ReadResultError = - | HttpErrorResultType - | NoncompliantPodError - | UnexpectedHttpError - | UnexpectedResourceError; - -/** - * Reads resource at a provided URI and returns the result - * - * @param uri - The URI of the resource - * @param options - Options to provide a fetch function and a local dataset to - * update. - * @returns ReadResult - * - * @example - * ```typescript - * import { deleteResource } from "@ldo/solid"; - * import { createDataset } from "@ldo/dataset" - * import { fetch } from "@inrupt/solid-client-autn-js"; - * - * const dataset = createDataset(); - * const result = await readResource( - * "https://example.com/container/someResource.ttl", - * { fetch, dataset }, - * ); - * if (!result.isError) { - * if (result.type === "absentReadSuccess") { - * // There was no problem reading the resource, but it doesn't exist - * } else if (result.type === "dataReadSuccess") { - * // The resource was read and it is an RDF resource. The dataset provided - * // dataset will also be loaded with the data from the resource - * } else if (result.type === "binaryReadSuccess") { - * // The resource is a binary - * console.log(result.blob); - * console.log(result.mimeType); - * } - * } - * ``` - */ -export async function readResource( - uri: LeafUri, - options?: DatasetRequestOptions, -): Promise; -export async function readResource( - uri: ContainerUri, - options?: DatasetRequestOptions, -): Promise; -export async function readResource( - uri: string, - options?: DatasetRequestOptions, -): Promise; -export async function readResource( - uri: string, - options?: DatasetRequestOptions, -): Promise { - try { - const fetch = guaranteeFetch(options?.fetch); - // Fetch options to determine the document type - const response = await fetch(uri, { - headers: { accept: "text/turtle, */*" }, - }); - if (response.status === 404) { - // Clear existing data if present - if (options?.dataset) { - options.dataset.deleteMatches( - undefined, - undefined, - undefined, - namedNode(uri), - ); - } - - return { - isError: false, - type: "absentReadSuccess", - uri, - recalledFromMemory: false, - }; - } - const httpErrorResult = HttpErrorResult.checkResponse(uri, response); - if (httpErrorResult) return httpErrorResult; - - // Add this resource to the container - if (options?.dataset) { - addResourceRdfToContainer(uri, options.dataset); - } - - const contentType = response.headers.get("content-type"); - if (!contentType) { - return new NoncompliantPodError( - uri, - "Resource requests must return a content-type header.", - ); - } - - if (contentType.startsWith("text/turtle")) { - // Parse Turtle - const rawTurtle = await response.text(); - if (options?.dataset) { - const result = await addRawTurtleToDataset( - rawTurtle, - options.dataset, - uri, - ); - if (result) return result; - } - if (isContainerUri(uri)) { - const result = checkHeadersForRootContainer(uri, response.headers); - return { - isError: false, - type: "containerReadSuccess", - uri, - recalledFromMemory: false, - isRootContainer: result.isRootContainer, - }; - } - return { - isError: false, - type: "dataReadSuccess", - uri, - recalledFromMemory: false, - }; - } else { - // Load Blob - const blob = await response.blob(); - return { - isError: false, - type: "binaryReadSuccess", - uri, - recalledFromMemory: false, - blob, - mimeType: contentType, - }; - } - } catch (err) { - return UnexpectedResourceError.fromThrown(uri, err); - } -} diff --git a/packages/solid/src/requester/requests/requestOptions.ts b/packages/solid/src/requester/requests/requestOptions.ts deleted file mode 100644 index 376c043..0000000 --- a/packages/solid/src/requester/requests/requestOptions.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { IBulkEditableDataset } from "@ldo/subscribable-dataset"; -import type { Quad } from "@rdfjs/types"; - -/** - * Request Options to be passed to request functions - */ -export interface BasicRequestOptions { - /** - * A fetch function usually imported from @inrupt/solid-client-authn-js - */ - fetch?: typeof fetch; -} - -/** - * Request options with a dataset component - */ -export interface DatasetRequestOptions extends BasicRequestOptions { - /** - * A dataset to be modified with any new information obtained from a request - */ - dataset?: IBulkEditableDataset; -} diff --git a/packages/solid/src/requester/requests/updateDataResource.ts b/packages/solid/src/requester/requests/updateDataResource.ts deleted file mode 100644 index 9b0237f..0000000 --- a/packages/solid/src/requester/requests/updateDataResource.ts +++ /dev/null @@ -1,101 +0,0 @@ -import type { DatasetChanges } from "@ldo/rdf-utils"; -import { changesToSparqlUpdate } from "@ldo/rdf-utils"; -import type { Quad } from "@rdfjs/types"; -import { guaranteeFetch } from "../../util/guaranteeFetch"; -import type { LeafUri } from "../../util/uriTypes"; -import { UnexpectedResourceError } from "../results/error/ErrorResult"; -import type { HttpErrorResultType } from "../results/error/HttpErrorResult"; -import { HttpErrorResult } from "../results/error/HttpErrorResult"; -import type { UpdateSuccess } from "../results/success/UpdateSuccess"; -import type { DatasetRequestOptions } from "./requestOptions"; - -/** - * All return values for updateDataResource - */ -export type UpdateResult = UpdateSuccess | UpdateResultError; - -/** - * All errors updateDataResource can return - */ -export type UpdateResultError = HttpErrorResultType | UnexpectedResourceError; - -/** - * Updates a specific data resource with the provided dataset changes - * - * @param uri - the URI of the data resource - * @param datasetChanges - A set of triples added and removed from this dataset - * @param options - Options to provide a fetch function and a local dataset to - * update. - * @returns An UpdateResult - * - * @example - * ```typescript - * import { - * updateDataResource, - * transactionChanges, - * changeData, - * createSolidLdoDataset, - * } from "@ldo/solid"; - * import { fetch } from "@inrupt/solid-client-authn-browser"; - * - * // Initialize an LDO dataset - * const solidLdoDataset = createSolidLdoDataset(); - * // Get a Linked Data Object - * const profile = solidLdoDataset - * .usingType(ProfileShapeType) - * .fromSubject("https://example.com/profile#me"); - * // Create a transaction to change data - * const cProfile = changeData( - * profile, - * solidLdoDataset.getResource("https://example.com/profile"), - * ); - * cProfile.name = "John Doe"; - * // Get data in "DatasetChanges" form - * const datasetChanges = transactionChanges(someLinkedDataObject); - * // Use "updateDataResource" to apply the changes - * const result = await updateDataResource( - * "https://example.com/profile", - * datasetChanges, - * { fetch, dataset: solidLdoDataset }, - * ); - * ``` - */ -export async function updateDataResource( - uri: LeafUri, - datasetChanges: DatasetChanges, - options?: DatasetRequestOptions, -): Promise { - try { - // Optimistically add data - options?.dataset?.bulk(datasetChanges); - const fetch = guaranteeFetch(options?.fetch); - - // Make request - const sparqlUpdate = await changesToSparqlUpdate(datasetChanges); - const response = await fetch(uri, { - method: "PATCH", - body: sparqlUpdate, - headers: { - "Content-Type": "application/sparql-update", - }, - }); - const httpError = HttpErrorResult.checkResponse(uri, response); - if (httpError) { - // Handle error rollback - if (options?.dataset) { - options.dataset.bulk({ - added: datasetChanges.removed, - removed: datasetChanges.added, - }); - } - return httpError; - } - return { - isError: false, - type: "updateSuccess", - uri, - }; - } catch (err) { - return UnexpectedResourceError.fromThrown(uri, err); - } -} diff --git a/packages/solid/src/requester/requests/uploadResource.ts b/packages/solid/src/requester/requests/uploadResource.ts deleted file mode 100644 index fcce355..0000000 --- a/packages/solid/src/requester/requests/uploadResource.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { guaranteeFetch } from "../../util/guaranteeFetch"; -import { - addResourceRdfToContainer, - getParentUri, - getSlug, -} from "../../util/rdfUtils"; -import type { LeafUri } from "../../util/uriTypes"; -import { UnexpectedResourceError } from "../results/error/ErrorResult"; -import { HttpErrorResult } from "../results/error/HttpErrorResult"; -import type { - LeafCreateAndOverwriteResult, - LeafCreateIfAbsentResult, -} from "./createDataResource"; -import { deleteResource } from "./deleteResource"; -import { readResource } from "./readResource"; -import type { DatasetRequestOptions } from "./requestOptions"; - -/** - * Uploads a binary resource at the provided URI - * - * @param uri - The URI of the resource - * @param overwrite - If true, the request will overwrite any previous resource - * at this URI. - * @param options - Options to provide a fetch function and a local dataset to - * update. - * @returns One of many create results depending on the input - * - * @example - * Any local RDFJS dataset passed to the `options` field will be updated with - * any new RDF data from the create process. - * - * ```typescript - * import { createDataResource } from "@ldo/solid"; - * import { createDataset } from "@ldo/dataset" - * import { fetch } from "@inrupt/solid-client-autn-js"; - * - * const localDataset = createDataset(); - * const result = await uploadResource( - * "https://example.com/container/someResource.txt", - * new Blob("some text."), - * "text/txt", - * true, - * { fetch, dataset: localDataset }, - * ); - * if (!result.isError) { - * // Do something - * } - * ``` - */ -export function uploadResource( - uri: LeafUri, - blob: Blob, - mimeType: string, - overwrite: true, - options?: DatasetRequestOptions, -): Promise; -export function uploadResource( - uri: LeafUri, - blob: Blob, - mimeType: string, - overwrite?: false, - options?: DatasetRequestOptions, -): Promise; -export function uploadResource( - uri: LeafUri, - blob: Blob, - mimeType: string, - overwrite?: boolean, - options?: DatasetRequestOptions, -): Promise; -export async function uploadResource( - uri: LeafUri, - blob: Blob, - mimeType: string, - overwrite?: boolean, - options?: DatasetRequestOptions, -): Promise { - try { - const fetch = guaranteeFetch(options?.fetch); - let didOverwrite = false; - if (overwrite) { - const deleteResult = await deleteResource(uri, options); - // Return if it wasn't deleted - if (deleteResult.isError) return deleteResult; - didOverwrite = deleteResult.resourceExisted; - } else { - // Perform a read to check if it exists - const readResult = await readResource(uri, options); - // If it does exist stop and return. - if (readResult.type !== "absentReadSuccess") { - return readResult; - } - } - // Create the document - const parentUri = getParentUri(uri)!; - const response = await fetch(parentUri, { - method: "post", - headers: { - "content-type": mimeType, - slug: getSlug(uri), - }, - body: blob, - }); - - const httpError = HttpErrorResult.checkResponse(uri, response); - if (httpError) return httpError; - - if (options?.dataset) { - addResourceRdfToContainer(uri, options.dataset); - } - return { - isError: false, - type: "createSuccess", - uri, - didOverwrite, - }; - } catch (err) { - const thing = UnexpectedResourceError.fromThrown(uri, err); - return thing; - } -} diff --git a/packages/solid/src/requester/results/RequesterResult.ts b/packages/solid/src/requester/results/RequesterResult.ts deleted file mode 100644 index 2c51cfb..0000000 --- a/packages/solid/src/requester/results/RequesterResult.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * A type returned by all request functions - */ -export interface RequesterResult { - type: string; - isError: boolean; -} diff --git a/packages/solid/src/requester/results/error/AccessControlError.ts b/packages/solid/src/requester/results/error/AccessControlError.ts deleted file mode 100644 index a4c5a18..0000000 --- a/packages/solid/src/requester/results/error/AccessControlError.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* istanbul ignore file */ -import { ResourceError } from "./ErrorResult"; - -/** - * An error: Could not fetch access rules - */ -export class AccessRuleFetchError extends ResourceError { - readonly type = "accessRuleFetchError" as const; - - /** - * @param uri - The uri of the resource for which access rules couldn't be - * fetched - * @param message - A custom message for the error - */ - constructor(uri: string, message?: string) { - super(uri, message || `${uri} had trouble fetching access rules.`); - } -} diff --git a/packages/solid/src/requester/results/error/ErrorResult.ts b/packages/solid/src/requester/results/error/ErrorResult.ts deleted file mode 100644 index 84a29ad..0000000 --- a/packages/solid/src/requester/results/error/ErrorResult.ts +++ /dev/null @@ -1,125 +0,0 @@ -import type { RequesterResult } from "../RequesterResult"; - -/** - * A result indicating that the request failed in some kind of way - */ -export abstract class ErrorResult extends Error implements RequesterResult { - /** - * Indicates the specific type of error - */ - abstract type: string; - - /** - * Always true - */ - readonly isError = true as const; - - /** - * @param message - a custom message for the error - */ - constructor(message?: string) { - super(message || "An unkown error was encountered."); - } -} - -/** - * An error for a specific resource - */ -export abstract class ResourceError extends ErrorResult { - /** - * The URI of the resource - */ - readonly uri: string; - - /** - * @param uri - The URI of the resource - * @param message - A custom message for the error - */ - constructor(uri: string, message?: string) { - super(message || `An unkown error for ${uri}`); - this.uri = uri; - } -} - -/** - * An error that aggregates many errors - */ -export class AggregateError extends ErrorResult { - readonly type = "aggregateError" as const; - - /** - * A list of all errors returned - */ - readonly errors: ErrorType[]; - - /** - * @param errors - List of all errors returned - * @param message - A custom message for the error - */ - constructor( - errors: (ErrorType | AggregateError)[], - message?: string, - ) { - const allErrors: ErrorType[] = []; - errors.forEach((error) => { - if (error instanceof AggregateError) { - error.errors.forEach((subError) => { - allErrors.push(subError); - }); - } else { - allErrors.push(error); - } - }); - super( - message || - `Encountered multiple errors:${allErrors.reduce( - (agg, cur) => `${agg}\n${cur}`, - "", - )}`, - ); - this.errors = allErrors; - } -} - -/** - * Represents some error that isn't handled under other errors. This is usually - * returned when something threw an error that LDO did not expect. - */ -export class UnexpectedResourceError extends ResourceError { - readonly type = "unexpectedResourceError" as const; - - /** - * The error that was thrown - */ - error: Error; - - /** - * @param uri - URI of the resource - * @param error - The error that was thrown - */ - constructor(uri: string, error: Error) { - super(uri, error.message); - this.error = error; - } - - /** - * @internal - * - * Creates an UnexpectedResourceError from a thrown error - * @param uri - The URI of the resource - * @param err - The thrown error - * @returns an UnexpectedResourceError - */ - static fromThrown(uri: string, err: unknown) { - if (err instanceof Error) { - return new UnexpectedResourceError(uri, err); - } else if (typeof err === "string") { - return new UnexpectedResourceError(uri, new Error(err)); - } else { - return new UnexpectedResourceError( - uri, - new Error(`Error of type ${typeof err} thrown: ${err}`), - ); - } - } -} diff --git a/packages/solid/src/requester/results/error/HttpErrorResult.ts b/packages/solid/src/requester/results/error/HttpErrorResult.ts deleted file mode 100644 index 69cc821..0000000 --- a/packages/solid/src/requester/results/error/HttpErrorResult.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { ResourceError } from "./ErrorResult"; - -/** - * A set of standard errors that can be returned as a result of an HTTP request - */ -export type HttpErrorResultType = - | ServerHttpError - | UnexpectedHttpError - | UnauthenticatedHttpError - | UnauthorizedHttpError; - -/** - * An error caused by an HTTP request - */ -export abstract class HttpErrorResult extends ResourceError { - /** - * The status of the HTTP request - */ - public readonly status: number; - - /** - * Headers returned by the HTTP request - */ - public readonly headers: Headers; - - /** - * Response returned by the HTTP request - */ - public readonly response: Response; - - /** - * @param uri - URI of the resource - * @param response - The response returned by the HTTP requests - * @param message - A custom message for the error - */ - constructor(uri: string, response: Response, message?: string) { - super( - uri, - message || - `Request for ${uri} returned ${response.status} (${response.statusText}).`, - ); - this.status = response.status; - this.headers = response.headers; - this.response = response; - } - - /** - * Checks to see if a given response does not constitute an HTTP Error - * @param response - The response of the request - * @returns true if the response does not constitute an HTTP Error - */ - static isnt(response: Response) { - return ( - !(response.status >= 200 && response.status < 300) && - response.status !== 404 && - response.status !== 304 - ); - } - - /** - * Checks a given response to see if it is a ServerHttpError, an - * UnauthenticatedHttpError or a some unexpected error. - * @param uri - The uri of the request - * @param response - The response of the request - * @returns An error if the response calls for it. Undefined if not. - */ - static checkResponse(uri: string, response: Response) { - if (ServerHttpError.is(response)) { - return new ServerHttpError(uri, response); - } - if (UnauthenticatedHttpError.is(response)) { - return new UnauthenticatedHttpError(uri, response); - } - if (UnauthorizedHttpError.is(response)) { - return new UnauthorizedHttpError(uri, response); - } - if (HttpErrorResult.isnt(response)) { - return new UnexpectedHttpError(uri, response); - } - return undefined; - } -} - -/** - * An unexpected error as a result of an HTTP request. This is usually returned - * when the HTTP request returns a status code LDO does not recognize. - */ -export class UnexpectedHttpError extends HttpErrorResult { - readonly type = "unexpectedHttpError" as const; -} - -/** - * An UnauthenticatedHttpError triggers when a Solid server returns a 401 status - * indicating that the request is not authenticated. - */ -export class UnauthenticatedHttpError extends HttpErrorResult { - readonly type = "unauthenticatedError" as const; - - /** - * Indicates if a specific response constitutes an UnauthenticatedHttpError - * @param response - The request response - * @returns true if this response constitutes an UnauthenticatedHttpError - */ - static is(response: Response) { - return response.status === 401; - } -} - -/** - * An UnauthenticatedHttpError triggers when a Solid server returns a 403 status - * indicating that the request is not authorized. - */ -export class UnauthorizedHttpError extends HttpErrorResult { - readonly type = "unauthorizedError" as const; - - /** - * Indicates if a specific response constitutes an UnauthenticatedHttpError - * @param response - The request response - * @returns true if this response constitutes an UnauthenticatedHttpError - */ - static is(response: Response) { - return response.status === 403; - } -} - -/** - * An NotFoundHttpError triggers when a Solid server returns a 404 status. This - * error is not returned in most cases as a "absent" resource is not considered - * an error, but it is thrown while trying for find a WAC rule for a resource - * that does not exist. - */ -export class NotFoundHttpError extends HttpErrorResult { - readonly type = "notFoundError" as const; - - /** - * Indicates if a specific response constitutes an NotFoundHttpError - * @param response - The request response - * @returns true if this response constitutes an NotFoundHttpError - */ - static is(response: Response) { - return response.status === 404; - } -} - -/** - * A ServerHttpError triggers when a Solid server returns a 5XX status, - * indicating that an error happened on the server. - */ -export class ServerHttpError extends HttpErrorResult { - readonly type = "serverError" as const; - - /** - * Indicates if a specific response constitutes a ServerHttpError - * @param response - The request response - * @returns true if this response constitutes a ServerHttpError - */ - static is(response: Response) { - return response.status >= 500 && response.status < 600; - } -} diff --git a/packages/solid/src/requester/results/error/InvalidUriError.ts b/packages/solid/src/requester/results/error/InvalidUriError.ts deleted file mode 100644 index e1c3201..0000000 --- a/packages/solid/src/requester/results/error/InvalidUriError.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ResourceError } from "./ErrorResult"; - -/** - * An InvalidUriError is returned when a URI was provided that is not a valid - * URI. - */ -export class InvalidUriError extends ResourceError { - readonly type = "invalidUriError" as const; - - constructor(uri: string, message?: string) { - super(uri, message || `${uri} is an invalid uri.`); - } -} diff --git a/packages/solid/src/requester/results/error/NoRootContainerError.ts b/packages/solid/src/requester/results/error/NoRootContainerError.ts deleted file mode 100644 index 8a17f2f..0000000 --- a/packages/solid/src/requester/results/error/NoRootContainerError.ts +++ /dev/null @@ -1,17 +0,0 @@ -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.`); - } -} diff --git a/packages/solid/src/requester/results/error/NoncompliantPodError.ts b/packages/solid/src/requester/results/error/NoncompliantPodError.ts deleted file mode 100644 index b981414..0000000 --- a/packages/solid/src/requester/results/error/NoncompliantPodError.ts +++ /dev/null @@ -1,20 +0,0 @@ -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 NoncompliantPodError extends ResourceError { - readonly type = "noncompliantPodError" as const; - - /** - * @param uri - the URI of the requested resource - * @param message - a custom message for the error - */ - constructor(uri: string, message?: string) { - super( - uri, - `Response from ${uri} is not compliant with the Solid Specification: ${message}`, - ); - } -} diff --git a/packages/solid/src/requester/results/success/CheckRootContainerSuccess.ts b/packages/solid/src/requester/results/success/CheckRootContainerSuccess.ts deleted file mode 100644 index 77a435f..0000000 --- a/packages/solid/src/requester/results/success/CheckRootContainerSuccess.ts +++ /dev/null @@ -1,19 +0,0 @@ -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 - * a success. - */ -export interface CheckRootContainerSuccess extends ResourceSuccess { - type: "checkRootContainerSuccess"; - /** - * True if this resoure is the root container - */ - isRootContainer: boolean; -} - -export interface GetStorageContainerFromWebIdSuccess extends SuccessResult { - type: "getStorageContainerFromWebIdSuccess"; - storageContainers: Container[]; -} diff --git a/packages/solid/src/requester/results/success/CreateSuccess.ts b/packages/solid/src/requester/results/success/CreateSuccess.ts deleted file mode 100644 index 3d83b9f..0000000 --- a/packages/solid/src/requester/results/success/CreateSuccess.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { ResourceSuccess } from "./SuccessResult"; - -/** - * Indicates that the request to create the resource was a success. - */ -export interface CreateSuccess extends ResourceSuccess { - type: "createSuccess"; - /** - * True if there was a resource that existed before at the given URI that was - * overwritten - */ - didOverwrite: boolean; -} diff --git a/packages/solid/src/requester/results/success/DeleteSuccess.ts b/packages/solid/src/requester/results/success/DeleteSuccess.ts deleted file mode 100644 index 0345a1c..0000000 --- a/packages/solid/src/requester/results/success/DeleteSuccess.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { ResourceSuccess } from "./SuccessResult"; - -/** - * Indicates that the request to delete a resource was a success. - */ -export interface DeleteSuccess extends ResourceSuccess { - type: "deleteSuccess"; - - /** - * True if there was a resource at the provided URI that was deleted. False if - * a resource didn't exist. - */ - resourceExisted: boolean; -} diff --git a/packages/solid/src/requester/results/success/ReadSuccess.ts b/packages/solid/src/requester/results/success/ReadSuccess.ts deleted file mode 100644 index 756642a..0000000 --- a/packages/solid/src/requester/results/success/ReadSuccess.ts +++ /dev/null @@ -1,71 +0,0 @@ -import type { ResourceSuccess, SuccessResult } from "./SuccessResult"; - -/** - * Indicates that the request to read a resource was a success - */ -export interface ReadSuccess extends ResourceSuccess { - /** - * True if the resource was recalled from local memory rather than a recent - * request - */ - recalledFromMemory: boolean; -} - -/** - * Indicates that the read request was successful and that the resource - * retrieved was a binary resource. - */ -export interface BinaryReadSuccess extends ReadSuccess { - type: "binaryReadSuccess"; - /** - * The raw data for the binary resource - */ - blob: Blob; - /** - * The mime type of the binary resource - */ - mimeType: string; -} - -/** - * Indicates that the read request was successful and that the resource - * retrieved was a data (RDF) resource. - */ -export interface DataReadSuccess extends ReadSuccess { - type: "dataReadSuccess"; -} - -/** - * Indicates that the read request was successful and that the resource - * retrieved was a container resource. - */ -export interface ContainerReadSuccess extends ReadSuccess { - type: "containerReadSuccess"; - /** - * True if this container is a root container - */ - isRootContainer: boolean; -} - -/** - * Indicates that the read request was successful, but no resource exists at - * the provided URI. - */ -export interface AbsentReadSuccess extends ReadSuccess { - type: "absentReadSuccess"; -} - -/** - * A helper function that checks to see if a result is a ReadSuccess result - * - * @param result - the result to check - * @returns true if the result is a ReadSuccessResult result - */ -export function isReadSuccess(result: SuccessResult): result is ReadSuccess { - return ( - result.type === "binaryReadSuccess" || - result.type === "dataReadSuccess" || - result.type === "absentReadSuccess" || - result.type === "containerReadSuccess" - ); -} diff --git a/packages/solid/src/requester/results/success/SuccessResult.ts b/packages/solid/src/requester/results/success/SuccessResult.ts deleted file mode 100644 index 35b891c..0000000 --- a/packages/solid/src/requester/results/success/SuccessResult.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { RequesterResult } from "../RequesterResult"; - -/** - * Indicates that some action taken by LDO was a success - */ -export interface SuccessResult extends RequesterResult { - isError: false; -} - -/** - * Indicates that a request to a resource was aa success - */ -export interface ResourceSuccess extends SuccessResult { - /** - * The URI of the resource - */ - uri: string; -} - -/** - * A grouping of multiple successes as a result of an action - */ -export interface AggregateSuccess - extends SuccessResult { - type: "aggregateSuccess"; - - /** - * An array of all successesses - */ - results: SuccessType[]; -} diff --git a/packages/solid/src/requester/results/success/Unfetched.ts b/packages/solid/src/requester/results/success/Unfetched.ts deleted file mode 100644 index 9d8db38..0000000 --- a/packages/solid/src/requester/results/success/Unfetched.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { ResourceSuccess } from "./SuccessResult"; - -/** - * Indicates that a specific resource is unfetched - */ -export interface Unfetched extends ResourceSuccess { - type: "unfetched"; -} diff --git a/packages/solid/src/requester/results/success/UpdateSuccess.ts b/packages/solid/src/requester/results/success/UpdateSuccess.ts deleted file mode 100644 index 5b740a0..0000000 --- a/packages/solid/src/requester/results/success/UpdateSuccess.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { ResourceSuccess } from "./SuccessResult"; - -/** - * Indicates that an update request to a resource was successful - */ -export interface UpdateSuccess extends ResourceSuccess { - type: "updateSuccess"; -} - -/** - * Indicates that an update request to the default graph was successful. This - * data was not written to a Pod. It was only written locally. - */ -export interface UpdateDefaultGraphSuccess extends ResourceSuccess { - type: "updateDefaultGraphSuccess"; -} - -/** - * Indicates that LDO ignored an invalid update (usually because a container - * attempted an update) - */ -export interface IgnoredInvalidUpdateSuccess extends ResourceSuccess { - type: "ignoredInvalidUpdateSuccess"; -} diff --git a/packages/solid/src/requester/util/modifyQueueFuntions.ts b/packages/solid/src/requester/util/modifyQueueFuntions.ts deleted file mode 100644 index 780b5d1..0000000 --- a/packages/solid/src/requester/util/modifyQueueFuntions.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import type { WaitingProcess } from "../../util/RequestBatcher"; - -/** - * @internal - * - * A helper function for a common way to modify the batch queue. This merges - * the incoming request with the currently executing request or the last request - * in the queue if its keys are the same. - * - * @param key - the key of the incoming request - * @returns a modifyQueue function - */ -export function modifyQueueByMergingEventsWithTheSameKeys(key: string) { - return ( - queue: WaitingProcess[], - currentlyLoading: WaitingProcess | undefined, - ) => { - if (queue.length === 0 && currentlyLoading?.name === key) { - return currentlyLoading; - } else if (queue[queue.length - 1]?.name === key) { - return queue[queue.length - 1]; - } - return undefined; - }; -} diff --git a/packages/solid/src/resource/Container.ts b/packages/solid/src/resource/Container.ts deleted file mode 100644 index 2670e28..0000000 --- a/packages/solid/src/resource/Container.ts +++ /dev/null @@ -1,599 +0,0 @@ -import { namedNode } from "@rdfjs/data-model"; -import { ContainerBatchedRequester } from "../requester/ContainerBatchedRequester"; -import type { - CheckRootResult, - CheckRootResultError, -} from "../requester/requests/checkRootContainer"; -import type { - ContainerCreateAndOverwriteResult, - ContainerCreateIfAbsentResult, - LeafCreateAndOverwriteResult, - LeafCreateIfAbsentResult, -} from "../requester/requests/createDataResource"; -import type { - DeleteResult, - DeleteResultError, -} from "../requester/requests/deleteResource"; -import type { - ReadContainerResult, - ReadResultError, -} from "../requester/requests/readResource"; -import { AggregateError } from "../requester/results/error/ErrorResult"; -import type { DeleteSuccess } from "../requester/results/success/DeleteSuccess"; -import type { AbsentReadSuccess } from "../requester/results/success/ReadSuccess"; -import type { ContainerReadSuccess } from "../requester/results/success/ReadSuccess"; -import type { AggregateSuccess } from "../requester/results/success/SuccessResult"; -import type { SolidLdoDatasetContext } from "../SolidLdoDatasetContext"; -import { getParentUri, ldpContains } from "../util/rdfUtils"; -import type { ContainerUri, LeafUri } from "../util/uriTypes"; -import type { Leaf } from "./Leaf"; -import type { SharedStatuses } from "./Resource"; -import { Resource } from "./Resource"; -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 - * LDO. - * - * @example - * ```typescript - * const container = solidLdoDataset - * .getResource("https://example.com/container/"); - * ``` - */ -export class Container extends Resource { - /** - * The URI of the container - */ - readonly uri: ContainerUri; - - /** - * @internal - * Batched Requester for the Container - */ - protected requester: ContainerBatchedRequester; - - /** - * @internal - * True if this is the root container, false if not, undefined if unknown - */ - protected rootContainer: boolean | undefined; - - /** - * Indicates that this resource is a container resource - */ - readonly type = "container" as const; - - /** - * Indicates that this resource is not an error - */ - readonly isError = false as const; - - /** - * The status of the last request made for this container - */ - status: - | SharedStatuses - | ReadContainerResult - | ContainerCreateAndOverwriteResult - | ContainerCreateIfAbsentResult - | CheckRootResult; - - /** - * @param uri - The uri of the container - * @param context - SolidLdoDatasetContext for the parent dataset - */ - constructor(uri: ContainerUri, context: SolidLdoDatasetContext) { - super(context); - this.uri = uri; - this.requester = new ContainerBatchedRequester(uri, context); - this.status = { isError: false, type: "unfetched", uri }; - } - - /** - * Checks if this container is a root container - * @returns true if this container is a root container, false if not, and - * undefined if this is unknown at the moment. - * - * @example - * ```typescript - * // Returns "undefined" when the container is unfetched - * console.log(container.isRootContainer()); - * const result = await container.read(); - * if (!result.isError) { - * // Returns true or false - * console.log(container.isRootContainer()); - * } - * ``` - */ - isRootContainer(): boolean | undefined { - return this.rootContainer; - } - - /** - * =========================================================================== - * READ METHODS - * =========================================================================== - */ - - /** - * @internal - * A helper method updates this container's internal state upon read success - * @param result - the result of the read success - */ - protected updateWithReadSuccess( - result: ContainerReadSuccess | AbsentReadSuccess, - ): void { - super.updateWithReadSuccess(result); - if (result.type === "containerReadSuccess") { - this.rootContainer = result.isRootContainer; - } - } - - /** - * Reads the container - * @returns A read result - * - * @example - * ```typescript - * const result = await container.read(); - * if (result.isError) { - * // Do something - * } - * ``` - */ - async read(): Promise> { - const result = (await this.handleRead()) as ReadContainerResult; - if (result.isError) return result; - return { ...result, resource: this }; - } - - /** - * @internal - * Converts the current state of this container to a readResult - * @returns a ReadContainerResult - */ - protected toReadResult(): ResourceResult { - if (this.isAbsent()) { - return { - isError: false, - type: "absentReadSuccess", - uri: this.uri, - recalledFromMemory: true, - resource: this, - }; - } else { - return { - isError: false, - type: "containerReadSuccess", - uri: this.uri, - recalledFromMemory: true, - isRootContainer: this.isRootContainer()!, - resource: this, - }; - } - } - - /** - * Makes a request to read this container if it hasn't been fetched yet. If it - * has, return the cached informtation - * @returns a ReadContainerResult - * - * @example - * ```typescript - * const result = await container.read(); - * if (!result.isError) { - * // Will execute without making a request - * const result2 = await container.readIfUnfetched(); - * } - * ``` - */ - async readIfUnfetched(): Promise< - ResourceResult - > { - return super.readIfUnfetched() as Promise< - ResourceResult - >; - } - - /** - * =========================================================================== - * PARENT CONTAINER METHODS - * =========================================================================== - */ - - /** - * @internal - * Checks if this container is a root container by making a request - * @returns CheckRootResult - */ - private async checkIfIsRootContainer(): Promise< - ResourceResult - > { - const rootContainerResult = await this.requester.isRootContainer(); - this.status = rootContainerResult; - if (rootContainerResult.isError) return rootContainerResult; - this.rootContainer = rootContainerResult.isRootContainer; - this.emit("update"); - return { ...rootContainerResult, resource: this }; - } - - /** - * Gets the root container of this container. If this container is the root - * container, this function returns itself. - * @returns The root container for this container or undefined if there is no - * root container. - * - * @example - * Suppose the root container is at `https://example.com/` - * - * ```typescript - * const container = ldoSolidDataset - * .getResource("https://example.com/container/"); - * const rootContainer = await container.getRootContainer(); - * if (!rootContainer.isError) { - * // logs "https://example.com/" - * console.log(rootContainer.uri); - * } - * ``` - */ - async getRootContainer(): Promise< - Container | CheckRootResultError | NoRootContainerError - > { - const parentContainerResult = await this.getParentContainer(); - if (parentContainerResult?.isError) return parentContainerResult; - if (!parentContainerResult) { - return this.isRootContainer() ? this : new NoRootContainerError(this.uri); - } - return parentContainerResult.getRootContainer(); - } - - /** - * Gets the parent container for this container by making a request - * @returns The parent container or undefined if there is no parent container - * because this container is the root container - * - * @example - * Suppose the root container is at `https://example.com/` - * - * ```typescript - * const root = solidLdoDataset.getResource("https://example.com/"); - * const container = solidLdoDataset - * .getResource("https://example.com/container"); - * const rootParent = await root.getParentContainer(); - * console.log(rootParent); // Logs "undefined" - * const containerParent = await container.getParentContainer(); - * if (!containerParent.isError) { - * // Logs "https://example.com/" - * console.log(containerParent.uri); - * } - * ``` - */ - async getParentContainer(): Promise< - Container | CheckRootResultError | undefined - > { - if (this.rootContainer === undefined) { - const checkResult = await this.checkIfIsRootContainer(); - if (checkResult.isError) return checkResult; - } - if (this.rootContainer) return undefined; - const parentUri = getParentUri(this.uri); - if (!parentUri) { - return undefined; - } - return this.context.resourceStore.get(parentUri); - } - - /** - * Lists the currently cached children of this container (no request is made) - * @returns An array of children - * - * ```typescript - * const result = await container.read(); - * if (!result.isError) { - * const children = container.children(); - * children.forEach((child) => { - * console.log(child.uri); - * }); - * } - * ``` - */ - children(): (Leaf | Container)[] { - const childQuads = this.context.solidLdoDataset.match( - namedNode(this.uri), - ldpContains, - null, - namedNode(this.uri), - ); - return childQuads.toArray().map((childQuad) => { - return this.context.resourceStore.get(childQuad.object.value); - }); - } - - /** - * Returns a child resource with a given name (slug) - * @param slug - the given name for that child resource - * @returns the child resource (either a Leaf or Container depending on the - * name) - * - * @example - * ```typescript - * const root = solidLdoDataset.getResource("https://example.com/"); - * const container = solidLdoDataset.child("container/"); - * // Logs "https://example.com/container/" - * console.log(container.uri); - * const resource = container.child("resource.ttl"); - * // Logs "https://example.com/container/resource.ttl" - * console.log(resource.uri); - * ``` - */ - child(slug: ContainerUri): Container; - child(slug: LeafUri): Leaf; - child(slug: string): Leaf | Container; - child(slug: string): Leaf | Container { - return this.context.resourceStore.get(`${this.uri}${slug}`); - } - - /** - * =========================================================================== - * CHILD CREATORS - * =========================================================================== - */ - - /** - * Creates a resource and overwrites any existing resource that existed at the - * URI - * - * @param slug - the name of the resource - * @return the result of creating that resource - * - * @example - * ```typescript - * const container = solidLdoDataset - * .getResource("https://example.com/container/"); - * cosnt result = await container.createChildAndOverwrite("resource.ttl"); - * if (!result.isError) { - * // Do something - * } - * ``` - */ - createChildAndOverwrite( - slug: ContainerUri, - ): Promise>; - createChildAndOverwrite( - slug: LeafUri, - ): Promise>; - createChildAndOverwrite( - slug: string, - ): Promise< - ResourceResult< - ContainerCreateAndOverwriteResult | LeafCreateAndOverwriteResult, - Leaf | Container - > - >; - createChildAndOverwrite( - slug: string, - ): Promise< - ResourceResult< - ContainerCreateAndOverwriteResult | LeafCreateAndOverwriteResult, - Leaf | Container - > - > { - return this.child(slug).createAndOverwrite(); - } - - /** - * Creates a resource only if that resource doesn't already exist on the Solid - * Pod - * - * @param slug - the name of the resource - * @return the result of creating that resource - * - * @example - * ```typescript - * const container = solidLdoDataset - * .getResource("https://example.com/container/"); - * cosnt result = await container.createChildIfAbsent("resource.ttl"); - * if (!result.isError) { - * // Do something - * } - * ``` - */ - createChildIfAbsent( - slug: ContainerUri, - ): Promise>; - createChildIfAbsent( - slug: LeafUri, - ): Promise>; - createChildIfAbsent( - slug: string, - ): Promise< - ResourceResult< - ContainerCreateIfAbsentResult | LeafCreateIfAbsentResult, - Leaf | Container - > - >; - createChildIfAbsent( - slug: string, - ): Promise< - ResourceResult< - ContainerCreateIfAbsentResult | LeafCreateIfAbsentResult, - Leaf | Container - > - > { - return this.child(slug).createIfAbsent(); - } - - /** - * Creates a new binary resource and overwrites any existing resource that - * existed at the URI - * - * @param slug - the name of the resource - * @return the result of creating that resource - * - * @example - * ```typescript - * const container = solidLdoDataset - * .getResource("https://example.com/container/"); - * cosnt result = await container.uploadChildAndOverwrite( - * "resource.txt", - * new Blob("some text."), - * "text/txt", - * ); - * if (!result.isError) { - * // Do something - * } - * ``` - */ - async uploadChildAndOverwrite( - slug: LeafUri, - blob: Blob, - mimeType: string, - ): Promise> { - return this.child(slug).uploadAndOverwrite(blob, mimeType); - } - - /** - * Creates a new binary resource and overwrites any existing resource that - * existed at the URI - * - * @param slug - the name of the resource - * @return the result of creating that resource - * - * @example - * ```typescript - * const container = solidLdoDataset - * .getResource("https://example.com/container/"); - * cosnt result = await container.uploadChildIfAbsent( - * "resource.txt", - * new Blob("some text."), - * "text/txt", - * ); - * if (!result.isError) { - * // Do something - * } - * ``` - */ - async uploadChildIfAbsent( - slug: LeafUri, - blob: Blob, - mimeType: string, - ): Promise> { - return this.child(slug).uploadIfAbsent(blob, mimeType); - } - - /** - * Deletes all contents in this container - * @returns An AggregateSuccess or Aggregate error corresponding with all the - * deleted resources - * - * @example - * ```typescript - * const result = container.clear(); - * if (!result.isError) { - * console.log("All deleted resources:"); - * result.results.forEach((result) => console.log(result.uri)); - * } - * ``` - */ - async clear(): Promise< - ResourceResult< - | AggregateSuccess> - | AggregateError, - Container - > - > { - const readResult = await this.read(); - if (readResult.isError) return new AggregateError([readResult]); - const results = ( - await Promise.all( - this.children().map(async (child) => { - return child.delete(); - }), - ) - ).flat(); - const errors = results.filter( - ( - value, - ): value is - | DeleteResultError - | AggregateError => value.isError, - ); - if (errors.length > 0) { - return new AggregateError(errors); - } - return { - isError: false, - type: "aggregateSuccess", - results: results as ResourceResult[], - resource: this, - }; - } - - /** - * Deletes this container and all its contents - * @returns A Delete result for this container - * - * ```typescript - * const result = await container.delete(); - * if (!result.isError) { - * // Do something - * } - * ``` - */ - async delete(): Promise< - ResourceResult< - DeleteResult | AggregateError, - Container - > - > { - const clearResult = await this.clear(); - if (clearResult.isError) return clearResult; - const deleteResult = await this.handleDelete(); - if (deleteResult.isError) return deleteResult; - return { ...deleteResult, resource: this }; - } - - /** - * Creates a container at this URI and overwrites any that already exists - * @returns ContainerCreateAndOverwriteResult - * - * @example - * ```typescript - * const result = await container.createAndOverwrite(); - * if (!result.isError) { - * // Do something - * } - * ``` - */ - async createAndOverwrite(): Promise< - ResourceResult - > { - const createResult = - (await this.handleCreateAndOverwrite()) as ContainerCreateAndOverwriteResult; - if (createResult.isError) return createResult; - return { ...createResult, resource: this }; - } - - /** - * Creates a container at this URI if the container doesn't already exist - * @returns ContainerCreateIfAbsentResult - * - * @example - * ```typescript - * const result = await container.createIfAbsent(); - * if (!result.isError) { - * // Do something - * } - * ``` - */ - async createIfAbsent(): Promise< - ResourceResult - > { - const createResult = - (await this.handleCreateIfAbsent()) as ContainerCreateIfAbsentResult; - if (createResult.isError) return createResult; - return { ...createResult, resource: this }; - } -} diff --git a/packages/solid/src/resource/Leaf.ts b/packages/solid/src/resource/Leaf.ts deleted file mode 100644 index 7272f15..0000000 --- a/packages/solid/src/resource/Leaf.ts +++ /dev/null @@ -1,559 +0,0 @@ -import type { DatasetChanges } from "@ldo/rdf-utils"; -import type { Quad } from "@rdfjs/types"; -import { LeafBatchedRequester } from "../requester/LeafBatchedRequester"; -import type { CheckRootResultError } from "../requester/requests/checkRootContainer"; -import type { - LeafCreateAndOverwriteResult, - LeafCreateIfAbsentResult, -} from "../requester/requests/createDataResource"; -import type { DeleteResult } from "../requester/requests/deleteResource"; -import type { ReadLeafResult } from "../requester/requests/readResource"; -import type { UpdateResult } from "../requester/requests/updateDataResource"; -import type { DeleteSuccess } from "../requester/results/success/DeleteSuccess"; -import type { AbsentReadSuccess } from "../requester/results/success/ReadSuccess"; -import type { - BinaryReadSuccess, - DataReadSuccess, -} from "../requester/results/success/ReadSuccess"; -import type { ResourceSuccess } from "../requester/results/success/SuccessResult"; -import type { SolidLdoDatasetContext } from "../SolidLdoDatasetContext"; -import { getParentUri } from "../util/rdfUtils"; -import type { LeafUri } from "../util/uriTypes"; -import type { Container } from "./Container"; -import type { SharedStatuses } from "./Resource"; -import { Resource } from "./Resource"; -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. - * - * @example - * ```typescript - * const leaf = solidLdoDataset - * .getResource("https://example.com/container/resource.ttl"); - * ``` - */ -export class Leaf extends Resource { - /** - * The URI of the leaf - */ - readonly uri: LeafUri; - - /** - * @internal - * Batched Requester for the Leaf - */ - protected requester: LeafBatchedRequester; - - /** - * Indicates that this resource is a leaf resource - */ - readonly type = "leaf" as const; - - /** - * Indicates that this resource is not an error - */ - readonly isError = false as const; - - /** - * The status of the last request made for this leaf - */ - status: - | SharedStatuses - | ReadLeafResult - | LeafCreateAndOverwriteResult - | LeafCreateIfAbsentResult - | UpdateResult; - - /** - * @internal - * The raw binary data if this leaf is a Binary resource - */ - protected binaryData: { blob: Blob; mimeType: string } | undefined; - - /** - * @param uri - The uri of the leaf - * @param context - SolidLdoDatasetContext for the parent dataset - */ - constructor(uri: LeafUri, context: SolidLdoDatasetContext) { - super(context); - const uriObject = new URL(uri); - uriObject.hash = ""; - this.uri = uriObject.toString() as LeafUri; - this.requester = new LeafBatchedRequester(uri, context); - this.status = { isError: false, type: "unfetched", uri }; - } - - /** - * =========================================================================== - * GETTERS - * =========================================================================== - */ - - /** - * Checks to see if the resource is currently uploading data - * @returns true if the current resource is uploading - * - * @example - * ```typescript - * leaf.uploadAndOverwrite(new Blob("some text"), "text/txt").then(() => { - * // Logs "false" - * console.log(leaf.isUploading()) - * }); - * // Logs "true" - * console.log(leaf.isUploading()); - * ``` - */ - isUploading(): boolean { - return this.requester.isUploading(); - } - - /** - * Checks to see if the resource is currently updating data - * @returns true if the current resource is updating - * - * @example - * ```typescript - * leaf.update(datasetChanges).then(() => { - * // Logs "false" - * console.log(leaf.isUpdating()) - * }); - * // Logs "true" - * console.log(leaf.isUpdating()); - * ``` - */ - isUpdating(): boolean { - return this.requester.isUpdating(); - } - - /** - * If this resource is a binary resource, returns the mime type - * @returns The mime type if this resource is a binary resource, undefined - * otherwise - * - * @example - * ```typescript - * // Logs "text/txt" - * console.log(leaf.getMimeType()); - * ``` - */ - getMimeType(): string | undefined { - return this.binaryData?.mimeType; - } - - /** - * If this resource is a binary resource, returns the Blob - * @returns The Blob if this resource is a binary resource, undefined - * otherwise - * - * @example - * ```typescript - * // Logs "some text." - * console.log(leaf.getBlob()?.toString()); - * ``` - */ - getBlob(): Blob | undefined { - return this.binaryData?.blob; - } - - /** - * Check if this resource is a binary resource - * @returns True if this resource is a binary resource, false if not, - * undefined if unknown - * - * @example - * ```typescript - * // Logs "undefined" - * console.log(leaf.isBinary()); - * const result = await leaf.read(); - * if (!result.isError) { - * // Logs "true" - * console.log(leaf.isBinary()); - * } - * ``` - */ - isBinary(): boolean | undefined { - if (!this.didInitialFetch) { - return undefined; - } - return !!this.binaryData; - } - - /** - * Check if this resource is a data (RDF) resource - * @returns True if this resource is a data resource, false if not, undefined - * if unknown - * - * @example - * ```typescript - * // Logs "undefined" - * console.log(leaf.isDataResource()); - * const result = await leaf.read(); - * if (!result.isError) { - * // Logs "true" - * console.log(leaf.isDataResource()); - * } - * ``` - */ - isDataResource(): boolean | undefined { - if (!this.didInitialFetch) { - return undefined; - } - return !this.binaryData; - } - - /** - * =========================================================================== - * READ METHODS - * =========================================================================== - */ - - /** - * @internal - * A helper method updates this leaf's internal state upon read success - * @param result - the result of the read success - */ - protected updateWithReadSuccess( - result: BinaryReadSuccess | DataReadSuccess | AbsentReadSuccess, - ): void { - super.updateWithReadSuccess(result); - if (result.type === "binaryReadSuccess") { - this.binaryData = { blob: result.blob, mimeType: result.mimeType }; - } else { - this.binaryData = undefined; - } - } - - /** - * Reads the leaf by making a request - * @returns A read result - * - * @example - * ```typescript - * const result = await leaf.read(); - * if (result.isError) { - * // Do something - * } - * ``` - */ - async read(): Promise> { - const result = (await this.handleRead()) as ReadLeafResult; - if (result.isError) return result; - return { ...result, resource: this }; - } - - /** - * @internal - * Converts the current state of this leaf to a readResult - * @returns a ReadLeafResult - */ - protected toReadResult(): ResourceResult { - if (this.isAbsent()) { - return { - isError: false, - type: "absentReadSuccess", - uri: this.uri, - recalledFromMemory: true, - resource: this, - }; - } else if (this.isBinary()) { - return { - isError: false, - type: "binaryReadSuccess", - uri: this.uri, - recalledFromMemory: true, - blob: this.binaryData!.blob, - mimeType: this.binaryData!.mimeType, - resource: this, - }; - } else { - return { - isError: false, - type: "dataReadSuccess", - uri: this.uri, - recalledFromMemory: true, - resource: this, - }; - } - } - - /** - * Makes a request to read this leaf if it hasn't been fetched yet. If it has, - * return the cached informtation - * @returns a ReadLeafResult - * - * @example - * ```typescript - * const result = await leaf.read(); - * if (!result.isError) { - * // Will execute without making a request - * const result2 = await leaf.readIfUnfetched(); - * } - * ``` - */ - async readIfUnfetched(): Promise> { - return super.readIfUnfetched() as Promise< - ResourceResult - >; - } - - /** - * =========================================================================== - * PARENT CONTAINER METHODS - * =========================================================================== - */ - - /** - * Gets the parent container for this leaf by making a request - * @returns The parent container - * - * @example - * ```typescript - * const leaf = solidLdoDataset - * .getResource("https://example.com/container/resource.ttl"); - * const leafParent = await leaf.getParentContainer(); - * if (!leafParent.isError) { - * // Logs "https://example.com/container/" - * console.log(leafParent.uri); - * } - * ``` - */ - async getParentContainer(): Promise { - const parentUri = getParentUri(this.uri)!; - return this.context.resourceStore.get(parentUri); - } - - /** - * Gets the root container for this leaf. - * @returns The root container for this leaf - * - * @example - * Suppose the root container is at `https://example.com/` - * - * ```typescript - * const leaf = ldoSolidDataset - * .getResource("https://example.com/container/resource.ttl"); - * const rootContainer = await leaf.getRootContainer(); - * if (!rootContainer.isError) { - * // logs "https://example.com/" - * console.log(rootContainer.uri); - * } - * ``` - */ - 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(); - return parent.getRootContainer(); - } - - /** - * =========================================================================== - * DELETE METHODS - * =========================================================================== - */ - - /** - * @internal - * A helper method updates this leaf's internal state upon delete success - * @param result - the result of the delete success - */ - public updateWithDeleteSuccess(result: DeleteSuccess) { - super.updateWithDeleteSuccess(result); - this.binaryData = undefined; - } - - /** - * Deletes this leaf and all its contents - * @returns A Delete result for this leaf - * - * ```typescript - * const result = await container.leaf(); - * if (!result.isError) { - * // Do something - * } - * ``` - */ - async delete(): Promise { - return this.handleDelete(); - } - - /** - * =========================================================================== - * CREATE METHODS - * =========================================================================== - */ - - /** - * A helper method updates this leaf's internal state upon create success - * @param _result - the result of the create success - */ - protected updateWithCreateSuccess(_result: ResourceSuccess): void { - this.binaryData = undefined; - } - - /** - * Creates a leaf at this URI and overwrites any that already exists - * @returns LeafCreateAndOverwriteResult - * - * @example - * ```typescript - * const result = await leaf.createAndOverwrite(); - * if (!result.isError) { - * // Do something - * } - * ``` - */ - async createAndOverwrite(): Promise< - ResourceResult - > { - const createResult = - (await this.handleCreateAndOverwrite()) as LeafCreateAndOverwriteResult; - if (createResult.isError) return createResult; - return { ...createResult, resource: this }; - } - - /** - * Creates a leaf at this URI if the leaf doesn't already exist - * @returns LeafCreateIfAbsentResult - * - * @example - * ```typescript - * const result = await leaf.createIfAbsent(); - * if (!result.isError) { - * // Do something - * } - * ``` - */ - async createIfAbsent(): Promise< - ResourceResult - > { - const createResult = - (await this.handleCreateIfAbsent()) as LeafCreateIfAbsentResult; - if (createResult.isError) return createResult; - return { ...createResult, resource: this }; - } - - /** - * =========================================================================== - * UPLOAD METHODS - * =========================================================================== - */ - - /** - * Uploads a binary resource to this URI. If there is already a resource - * present at this URI, it will be overwritten - * - * @param blob - the Blob of the binary - * @param mimeType - the MimeType of the binary - * @returns A LeafCreateAndOverwriteResult - * - * @example - * ```typescript - * const result = await leaf.uploadAndOverwrite( - * new Blob("some text."), - * "text/txt", - * ); - * if (!result.isError) { - * // Do something - * } - * ``` - */ - async uploadAndOverwrite( - blob: Blob, - mimeType: string, - ): Promise> { - const result = await this.requester.upload(blob, mimeType, true); - this.status = result; - if (result.isError) return result; - super.updateWithCreateSuccess(result); - this.binaryData = { blob, mimeType }; - this.emitThisAndParent(); - return { ...result, resource: this }; - } - - /** - * Uploads a binary resource to this URI tf there not is already a resource - * present at this URI. - * - * @param blob - the Blob of the binary - * @param mimeType - the MimeType of the binary - * @returns A LeafCreateIfAbsentResult - * - * @example - * ```typescript - * const result = await leaf.uploadIfAbsent( - * new Blob("some text."), - * "text/txt", - * ); - * if (!result.isError) { - * // Do something - * } - * ``` - */ - async uploadIfAbsent( - blob: Blob, - mimeType: string, - ): Promise> { - const result = await this.requester.upload(blob, mimeType); - this.status = result; - if (result.isError) return result; - super.updateWithCreateSuccess(result); - this.binaryData = { blob, mimeType }; - this.emitThisAndParent(); - return { ...result, resource: this }; - } - - /** - * =========================================================================== - * UPDATE METHODS - * =========================================================================== - */ - - /** - * Updates a data resource with the changes provided - * @param changes - Dataset changes that will be applied to the resoruce - * @returns An UpdateResult - * - * @example - * ```typescript - * import { - * updateDataResource, - * transactionChanges, - * changeData, - * createSolidLdoDataset, - * } from "@ldo/solid"; - * - * //... - * - * // Get a Linked Data Object - * const profile = solidLdoDataset - * .usingType(ProfileShapeType) - * .fromSubject("https://example.com/profile#me"); - * cosnt resource = solidLdoDataset - * .getResource("https://example.com/profile"); - * // Create a transaction to change data - * const cProfile = changeData(profile, resource); - * cProfile.name = "John Doe"; - * // Get data in "DatasetChanges" form - * const datasetChanges = transactionChanges(someLinkedDataObject); - * // Use "update" to apply the changes - * cosnt result = resource.update(datasetChanges); - * ``` - */ - async update( - changes: DatasetChanges, - ): Promise> { - const result = await this.requester.updateDataResource(changes); - this.status = result; - if (result.isError) return result; - this.binaryData = undefined; - this.absent = false; - this.emitThisAndParent(); - return { ...result, resource: this }; - } -} diff --git a/packages/solid/src/resource/Resource.ts b/packages/solid/src/resource/Resource.ts deleted file mode 100644 index 79c6e1c..0000000 --- a/packages/solid/src/resource/Resource.ts +++ /dev/null @@ -1,834 +0,0 @@ -import type { SolidLdoDatasetContext } from "../SolidLdoDatasetContext"; -import type { - ContainerCreateAndOverwriteResult, - ContainerCreateIfAbsentResult, - LeafCreateAndOverwriteResult, - LeafCreateIfAbsentResult, -} from "../requester/requests/createDataResource"; -import type { - ReadContainerResult, - ReadLeafResult, -} from "../requester/requests/readResource"; -import type { BatchedRequester } from "../requester/BatchedRequester"; -import type { CheckRootResultError } from "../requester/requests/checkRootContainer"; -import type TypedEmitter from "typed-emitter"; -import EventEmitter from "events"; -import { getParentUri } from "../util/rdfUtils"; -import type { RequesterResult } from "../requester/results/RequesterResult"; -import { - updateDatasetOnSuccessfulDelete, - type DeleteResult, -} from "../requester/requests/deleteResource"; -import type { ReadSuccess } from "../requester/results/success/ReadSuccess"; -import { isReadSuccess } from "../requester/results/success/ReadSuccess"; -import type { DeleteSuccess } from "../requester/results/success/DeleteSuccess"; -import type { ResourceSuccess } from "../requester/results/success/SuccessResult"; -import type { Unfetched } from "../requester/results/success/Unfetched"; -import type { CreateSuccess } from "../requester/results/success/CreateSuccess"; -import type { ResourceResult } from "./resourceResult/ResourceResult"; -import type { Container } from "./Container"; -import type { Leaf } from "./Leaf"; -import type { WacRule } from "./wac/WacRule"; -import type { GetWacUriError, GetWacUriResult } from "./wac/getWacUri"; -import { getWacUri } from "./wac/getWacUri"; -import { getWacRuleWithAclUri, type GetWacRuleResult } from "./wac/getWacRule"; -import { NoncompliantPodError } from "../requester/results/error/NoncompliantPodError"; -import { setWacRuleForAclUri, type SetWacRuleResult } from "./wac/setWacRule"; -import type { LeafUri } from "../util/uriTypes"; -import type { NoRootContainerError } from "../requester/results/error/NoRootContainerError"; -import type { - NotificationSubscription, - SubscriptionCallbacks, -} from "./notifications/NotificationSubscription"; -import { Websocket2023NotificationSubscription } from "./notifications/Websocket2023NotificationSubscription"; -import type { NotificationMessage } from "./notifications/NotificationMessage"; - -/** - * Statuses shared between both Leaf and Container - */ -export type SharedStatuses = Unfetched | DeleteResult | CreateSuccess; - -/** - * Represents the current status of a specific Resource on a Pod as known by LDO. - */ -export abstract class Resource extends (EventEmitter as new () => TypedEmitter<{ - update: () => void; - notification: () => void; -}>) { - /** - * @internal - * The SolidLdoDatasetContext from the Parent Dataset - */ - protected readonly context: SolidLdoDatasetContext; - - /** - * The uri of the resource - */ - abstract readonly uri: string; - - /** - * The type of resource (leaf or container) - */ - abstract readonly type: string; - - /** - * The status of the last request made for this resource - */ - abstract status: RequesterResult; - - /** - * @internal - * Batched Requester for the Resource - */ - protected abstract readonly requester: BatchedRequester; - - /** - * @internal - * True if this resource has been fetched at least once - */ - protected didInitialFetch: boolean = false; - - /** - * @internal - * True if this resource has been fetched but does not exist - */ - protected absent: boolean | undefined; - - /** - * @internal - * If a wac uri is fetched, it is cached here - */ - protected wacUri?: LeafUri; - - /** - * @internal - * If a wac rule was fetched, it is cached here - */ - protected wacRule?: WacRule; - - /** - * @internal - * Handles notification subscriptions - */ - protected notificationSubscription: NotificationSubscription; - - /** - * @param context - SolidLdoDatasetContext for the parent dataset - */ - constructor(context: SolidLdoDatasetContext) { - super(); - this.context = context; - this.notificationSubscription = new Websocket2023NotificationSubscription( - this, - this.onNotification.bind(this), - this.context, - ); - } - - /** - * =========================================================================== - * GETTERS - * =========================================================================== - */ - - /** - * Checks to see if this resource is loading in any way - * @returns true if the resource is currently loading - * - * @example - * ```typescript - * resource.read().then(() => { - * // Logs "false" - * console.log(resource.isLoading()) - * }); - * // Logs "true" - * console.log(resource.isLoading()); - * ``` - */ - isLoading(): boolean { - return this.requester.isLoading(); - } - - /** - * Checks to see if this resource is being created - * @returns true if the resource is currently being created - * - * @example - * ```typescript - * resource.read().then(() => { - * // Logs "false" - * console.log(resource.isCreating()) - * }); - * // Logs "true" - * console.log(resource.isCreating()); - * ``` - */ - isCreating(): boolean { - return this.requester.isCreating(); - } - - /** - * Checks to see if this resource is being read - * @returns true if the resource is currently being read - * - * @example - * ```typescript - * resource.read().then(() => { - * // Logs "false" - * console.log(resource.isReading()) - * }); - * // Logs "true" - * console.log(resource.isReading()); - * ``` - */ - isReading(): boolean { - return this.requester.isReading(); - } - - /** - * Checks to see if this resource is being deleted - * @returns true if the resource is currently being deleted - * - * @example - * ```typescript - * resource.read().then(() => { - * // Logs "false" - * console.log(resource.isDeleting()) - * }); - * // Logs "true" - * console.log(resource.isDeleting()); - * ``` - */ - isDeleting(): boolean { - return this.requester.isDeletinng(); - } - - /** - * Checks to see if this resource is being read for the first time - * @returns true if the resource is currently being read for the first time - * - * @example - * ```typescript - * resource.read().then(() => { - * // Logs "false" - * console.log(resource.isDoingInitialFetch()) - * }); - * // Logs "true" - * console.log(resource.isDoingInitialFetch()); - * ``` - */ - isDoingInitialFetch(): boolean { - return this.isReading() && !this.isFetched(); - } - - /** - * Checks to see if this resource is being read for a subsequent time - * @returns true if the resource is currently being read for a subsequent time - * - * @example - * ```typescript - * await resource.read(); - * resource.read().then(() => { - * // Logs "false" - * console.log(resource.isCreating()) - * }); - * // Logs "true" - * console.log(resource.isCreating()); - * ``` - */ - isReloading(): boolean { - return this.isReading() && this.isFetched(); - } - - /** - * =========================================================================== - * CHECKERS - * =========================================================================== - */ - - /** - * Check to see if this resource has been fetched - * @returns true if this resource has been fetched before - * - * @example - * ```typescript - * // Logs "false" - * console.log(resource.isFetched()); - * const result = await resource.read(); - * if (!result.isError) { - * // Logs "true" - * console.log(resource.isFetched()); - * } - * ``` - */ - isFetched(): boolean { - return this.didInitialFetch; - } - - /** - * Check to see if this resource is currently unfetched - * @returns true if the resource is currently unfetched - * - * @example - * ```typescript - * // Logs "true" - * console.log(resource.isUnetched()); - * const result = await resource.read(); - * if (!result.isError) { - * // Logs "false" - * console.log(resource.isUnfetched()); - * } - * ``` - */ - isUnfetched(): boolean { - return !this.didInitialFetch; - } - - /** - * Is this resource currently absent (it does not exist) - * @returns true if the resource is absent, false if not, undefined if unknown - * - * @example - * ```typescript - * // Logs "undefined" - * console.log(resource.isAbsent()); - * const result = await resource.read(); - * if (!result.isError) { - * // False if the resource exists, true if it does not - * console.log(resource.isAbsent()); - * } - * ``` - */ - isAbsent(): boolean | undefined { - return this.absent; - } - - /** - * Is this resource currently present on the Pod - * @returns false if the resource is absent, true if not, undefined if unknown - * - * @example - * ```typescript - * // Logs "undefined" - * console.log(resource.isPresent()); - * const result = await resource.read(); - * if (!result.isError) { - * // True if the resource exists, false if it does not - * console.log(resource.isPresent()); - * } - * ``` - */ - isPresent(): boolean | undefined { - return this.absent === undefined ? undefined : !this.absent; - } - - /** - * Is this resource currently listening to notifications from this document - * @returns true if the resource is subscribed to notifications, false if not - * - * @example - * ```typescript - * await resource.subscribeToNotifications(); - * // Logs "true" - * console.log(resource.isSubscribedToNotifications()); - * ``` - */ - isSubscribedToNotifications(): boolean { - return this.notificationSubscription.isSubscribedToNotifications(); - } - - /** - * =========================================================================== - * HELPER METHODS - * =========================================================================== - */ - - /** - * @internal - * Emits an update event for both this resource and the parent - */ - protected emitThisAndParent() { - this.emit("update"); - const parentUri = getParentUri(this.uri); - if (parentUri) { - const parentContainer = this.context.resourceStore.get(parentUri); - parentContainer.emit("update"); - } - } - - /** - * =========================================================================== - * READ METHODS - * =========================================================================== - */ - - /** - * @internal - * A helper method updates this resource's internal state upon read success - * @param result - the result of the read success - */ - protected updateWithReadSuccess(result: ReadSuccess) { - this.absent = result.type === "absentReadSuccess"; - this.didInitialFetch = true; - } - - /** - * @internal - * A helper method that handles the core functions for reading - * @returns ReadResult - */ - protected async handleRead(): Promise { - const result = await this.requester.read(); - this.status = result; - if (result.isError) return result; - this.updateWithReadSuccess(result); - this.emitThisAndParent(); - return result; - } - - /** - * @internal - * Converts the current state of this resource to a readResult - * @returns a ReadResult - */ - protected abstract toReadResult(): ResourceResult< - ReadLeafResult | ReadContainerResult, - Container | Leaf - >; - - /** - * Reads the resource - */ - abstract read(): Promise< - ResourceResult - >; - - /** - * Reads the resource if it isn't fetched yet - * @returns a ReadResult - */ - async readIfUnfetched(): Promise< - ResourceResult - > { - if (this.didInitialFetch) { - const readResult = this.toReadResult(); - this.status = readResult; - return readResult; - } - return this.read(); - } - - /** - * =========================================================================== - * DELETE METHODS - * =========================================================================== - */ - - /** - * @internal - * A helper method updates this resource's internal state upon delete success - * @param result - the result of the delete success - */ - public updateWithDeleteSuccess(_result: DeleteSuccess) { - this.absent = true; - this.didInitialFetch = true; - } - - /** - * @internal - * Helper method that handles the core functions for deleting a resource - * @returns DeleteResult - */ - protected async handleDelete(): Promise { - const result = await this.requester.delete(); - this.status = result; - if (result.isError) return result; - this.updateWithDeleteSuccess(result); - this.emitThisAndParent(); - return result; - } - - /** - * =========================================================================== - * CREATE METHODS - * =========================================================================== - */ - - /** - * A helper method updates this resource's internal state upon create success - * @param _result - the result of the create success - */ - protected updateWithCreateSuccess(result: ResourceSuccess) { - this.absent = false; - this.didInitialFetch = true; - if (isReadSuccess(result)) { - this.updateWithReadSuccess(result); - } - } - - /** - * Creates a resource at this URI and overwrites any that already exists - * @returns CreateAndOverwriteResult - * - * @example - * ```typescript - * const result = await resource.createAndOverwrite(); - * if (!result.isError) { - * // Do something - * } - * ``` - */ - abstract createAndOverwrite(): Promise< - ResourceResult< - ContainerCreateAndOverwriteResult | LeafCreateAndOverwriteResult, - Leaf | Container - > - >; - - /** - * @internal - * Helper method that handles the core functions for creating and overwriting - * a resource - * @returns DeleteResult - */ - protected async handleCreateAndOverwrite(): Promise< - ContainerCreateAndOverwriteResult | LeafCreateAndOverwriteResult - > { - const result = await this.requester.createDataResource(true); - this.status = result; - if (result.isError) return result; - this.updateWithCreateSuccess(result); - this.emitThisAndParent(); - return result; - } - - /** - * Creates a resource at this URI if the resource doesn't already exist - * @returns CreateIfAbsentResult - * - * @example - * ```typescript - * const result = await leaf.createIfAbsent(); - * if (!result.isError) { - * // Do something - * } - * ``` - */ - abstract createIfAbsent(): Promise< - ResourceResult< - ContainerCreateIfAbsentResult | LeafCreateIfAbsentResult, - Leaf | Container - > - >; - - /** - * @internal - * Helper method that handles the core functions for creating a resource if - * absent - * @returns DeleteResult - */ - protected async handleCreateIfAbsent(): Promise< - ContainerCreateIfAbsentResult | LeafCreateIfAbsentResult - > { - const result = await this.requester.createDataResource(); - this.status = result; - if (result.isError) return result; - this.updateWithCreateSuccess(result); - this.emitThisAndParent(); - return result; - } - - /** - * =========================================================================== - * PARENT CONTAINER METHODS - * =========================================================================== - */ - - /** - * Gets the root container for this resource. - * @returns The root container for this resource - * - * @example - * Suppose the root container is at `https://example.com/` - * - * ```typescript - * const resource = ldoSolidDataset - * .getResource("https://example.com/container/resource.ttl"); - * const rootContainer = await resource.getRootContainer(); - * if (!rootContainer.isError) { - * // logs "https://example.com/" - * console.log(rootContainer.uri); - * } - * ``` - */ - abstract getRootContainer(): Promise< - Container | CheckRootResultError | NoRootContainerError - >; - - abstract getParentContainer(): Promise< - Container | CheckRootResultError | undefined - >; - - /** - * =========================================================================== - * WEB ACCESS CONTROL METHODS - * =========================================================================== - */ - - /** - * Retrieves the URI for the web access control (WAC) rules for this resource - * @param options - set the "ignoreCache" field to true to ignore any cached - * information on WAC rules. - * @returns WAC Rules results - */ - protected async getWacUri(options?: { - ignoreCache: boolean; - }): Promise { - // Get the wacUri if not already present - if (!options?.ignoreCache && this.wacUri) { - return { - type: "getWacUriSuccess", - wacUri: this.wacUri, - isError: false, - uri: this.uri, - }; - } - - const wacUriResult = await getWacUri(this.uri, { - fetch: this.context.fetch, - }); - if (wacUriResult.isError) { - return wacUriResult; - } - this.wacUri = wacUriResult.wacUri; - return wacUriResult; - } - - /** - * Retrieves web access control (WAC) rules for this resource - * @param options - set the "ignoreCache" field to true to ignore any cached - * information on WAC rules. - * @returns WAC Rules results - * - * @example - * ```typescript - * const resource = ldoSolidDataset - * .getResource("https://example.com/container/resource.ttl"); - * const wacRulesResult = await resource.getWac(); - * if (!wacRulesResult.isError) { - * const wacRules = wacRulesResult.wacRule; - * // True if the resource is publicly readable - * console.log(wacRules.public.read); - * // True if authenticated agents can write to the resource - * console.log(wacRules.authenticated.write); - * // True if the given WebId has append access - * console.log( - * wacRules.agent[https://example.com/person1/profile/card#me].append - * ); - * // True if the given WebId has control access - * console.log( - * wacRules.agent[https://example.com/person1/profile/card#me].control - * ); - * } - * ``` - */ - async getWac(options?: { - ignoreCache: boolean; - }): Promise { - // Return the wac rule if it's already cached - if (!options?.ignoreCache && this.wacRule) { - return { - type: "getWacRuleSuccess", - uri: this.uri, - isError: false, - wacRule: this.wacRule, - }; - } - - // Get the wac uri - const wacUriResult = await this.getWacUri(options); - if (wacUriResult.isError) return wacUriResult; - - // Get the wac rule - const wacResult = await getWacRuleWithAclUri(wacUriResult.wacUri, { - fetch: this.context.fetch, - }); - if (wacResult.isError) return wacResult; - // If the wac rules was successfully found - if (wacResult.type === "getWacRuleSuccess") { - this.wacRule = wacResult.wacRule; - return wacResult; - } - - // If the WacRule is absent - const parentResource = await this.getParentContainer(); - if (parentResource?.isError) return parentResource; - if (!parentResource) { - return new NoncompliantPodError( - this.uri, - `Resource "${this.uri}" has no Effective ACL resource`, - ); - } - return parentResource.getWac(); - } - - /** - * Sets access rules for a specific resource - * @param wacRule - the access rules to set - * @returns SetWacRuleResult - * - * @example - * ```typescript - * const resource = ldoSolidDataset - * .getResource("https://example.com/container/resource.ttl"); - * const wacRulesResult = await resource.setWac({ - * public: { - * read: true, - * write: false, - * append: false, - * control: false - * }, - * authenticated: { - * read: true, - * write: false, - * append: true, - * control: false - * }, - * agent: { - * "https://example.com/person1/profile/card#me": { - * read: true, - * write: true, - * append: true, - * control: true - * } - * } - * }); - * ``` - */ - async setWac(wacRule: WacRule): Promise { - const wacUriResult = await this.getWacUri(); - if (wacUriResult.isError) return wacUriResult; - - const result = await setWacRuleForAclUri( - wacUriResult.wacUri, - wacRule, - this.uri, - { - fetch: this.context.fetch, - }, - ); - if (result.isError) return result; - this.wacRule = result.wacRule; - return result; - } - - /** - * =========================================================================== - * SUBSCRIPTION METHODS - * =========================================================================== - */ - - /** - * Activates Websocket subscriptions on this resource. Updates, deletions, - * and creations on this resource will be tracked and all changes will be - * relected in LDO's resources and graph. - * - * @param onNotificationError - A callback function if there is an error - * with notifications. - * @returns SubscriptionId: A string to use to unsubscribe - * - * @example - * ```typescript - * const resource = solidLdoDataset - * .getResource("https://example.com/spiderman"); - * // A listener for if anything about spiderman in the global dataset is - * // changed. Note that this will also listen for any local changes as well - * // as changes to remote resources to which you have notification - * // subscriptions enabled. - * solidLdoDataset.addListener( - * [namedNode("https://example.com/spiderman#spiderman"), null, null, null], - * () => { - * // Triggers when the file changes on the Pod or locally - * console.log("Something changed about SpiderMan"); - * }, - * ); - * - * // Subscribe - * const subscriptionId = await testContainer.subscribeToNotifications({ - * // These are optional callbacks. A subscription will automatically keep - * // the dataset in sync. Use these callbacks for additional functionality. - * onNotification: (message) => console.log(message), - * onNotificationError: (err) => console.log(err.message) - * }); - * // ... From there you can wait for a file to be changed on the Pod. - */ - async subscribeToNotifications( - callbacks?: SubscriptionCallbacks, - ): Promise { - return await this.notificationSubscription.subscribeToNotifications( - callbacks, - ); - } - - /** - * @internal - * Function that triggers whenever a notification is recieved. - */ - protected async onNotification(message: NotificationMessage): Promise { - const objectResource = this.context.solidLdoDataset.getResource( - message.object, - ); - switch (message.type) { - case "Update": - case "Add": - await objectResource.read(); - return; - case "Delete": - case "Remove": - // Delete the resource without have to make an additional read request - updateDatasetOnSuccessfulDelete( - message.object, - this.context.solidLdoDataset, - ); - objectResource.updateWithDeleteSuccess({ - type: "deleteSuccess", - isError: false, - uri: message.object, - resourceExisted: true, - }); - return; - } - } - - /** - * Unsubscribes from changes made to this resource on the Pod - * - * @returns UnsubscribeResult - * - * @example - * ```typescript - * const subscriptionId = await testContainer.subscribeToNotifications(); - * await testContainer.unsubscribeFromNotifications(subscriptionId); - * ``` - */ - async unsubscribeFromNotifications(subscriptionId: string): Promise { - return this.notificationSubscription.unsubscribeFromNotification( - subscriptionId, - ); - } - - /** - * Unsubscribes from all notifications on this resource - * - * @returns UnsubscribeResult[] - * - * @example - * ```typescript - * const subscriptionResult = await testContainer.subscribeToNotifications(); - * await testContainer.unsubscribeFromAllNotifications(); - * ``` - */ - async unsubscribeFromAllNotifications(): Promise { - return this.notificationSubscription.unsubscribeFromAllNotifications(); - } -} diff --git a/packages/solid/src/resource/notifications/NotificationMessage.ts b/packages/solid/src/resource/notifications/NotificationMessage.ts deleted file mode 100644 index 14e5c2d..0000000 --- a/packages/solid/src/resource/notifications/NotificationMessage.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * A message sent from the Pod as a notification - */ -export interface NotificationMessage { - "@context": string | string[]; - id: string; - type: "Update" | "Delete" | "Remove" | "Add"; - object: string; - published: string; -} diff --git a/packages/solid/src/resource/notifications/NotificationSubscription.ts b/packages/solid/src/resource/notifications/NotificationSubscription.ts deleted file mode 100644 index 4961075..0000000 --- a/packages/solid/src/resource/notifications/NotificationSubscription.ts +++ /dev/null @@ -1,144 +0,0 @@ -import type { SolidLdoDatasetContext } from "../../SolidLdoDatasetContext"; -import type { Resource } from "../Resource"; -import type { NotificationMessage } from "./NotificationMessage"; -import type { NotificationCallbackError } from "./results/NotificationErrors"; -import { v4 } from "uuid"; - -export interface SubscriptionCallbacks { - onNotification?: (message: NotificationMessage) => void; - // TODO: make notification errors more specific - onNotificationError?: (error: Error) => void; -} - -/** - * @internal - * Abstract class for notification subscription methods. - */ -export abstract class NotificationSubscription { - protected resource: Resource; - protected parentSubscription: (message: NotificationMessage) => void; - protected context: SolidLdoDatasetContext; - protected subscriptions: Record = {}; - private isOpen: boolean = false; - - constructor( - resource: Resource, - parentSubscription: (message: NotificationMessage) => void, - context: SolidLdoDatasetContext, - ) { - this.resource = resource; - this.parentSubscription = parentSubscription; - this.context = context; - } - - public isSubscribedToNotifications(): boolean { - return this.isOpen; - } - - /** - * =========================================================================== - * PUBLIC - * =========================================================================== - */ - - /** - * @internal - * subscribeToNotifications - */ - async subscribeToNotifications( - subscriptionCallbacks?: SubscriptionCallbacks, - ): Promise { - const subscriptionId = v4(); - this.subscriptions[subscriptionId] = subscriptionCallbacks ?? {}; - if (!this.isOpen) { - await this.open(); - this.setIsOpen(true); - } - return subscriptionId; - } - - /** - * @internal - * unsubscribeFromNotification - */ - async unsubscribeFromNotification(subscriptionId: string): Promise { - if ( - !!this.subscriptions[subscriptionId] && - Object.keys(this.subscriptions).length === 1 - ) { - await this.close(); - this.setIsOpen(false); - } - delete this.subscriptions[subscriptionId]; - } - - /** - * @internal - * unsubscribeFromAllNotifications - */ - async unsubscribeFromAllNotifications(): Promise { - await Promise.all( - Object.keys(this.subscriptions).map((id) => - this.unsubscribeFromNotification(id), - ), - ); - } - - /** - * =========================================================================== - * HELPERS - * =========================================================================== - */ - - /** - * @internal - * Opens the subscription - */ - protected abstract open(): Promise; - - /** - * @internal - * Closes the subscription - */ - protected abstract close(): Promise; - - /** - * =========================================================================== - * CALLBACKS - * =========================================================================== - */ - - /** - * @internal - * onNotification - */ - protected onNotification(message: NotificationMessage): void { - this.parentSubscription(message); - Object.values(this.subscriptions).forEach(({ onNotification }) => { - onNotification?.(message); - }); - } - - /** - * @internal - * onNotificationError - */ - protected onNotificationError(message: NotificationCallbackError): void { - Object.values(this.subscriptions).forEach(({ onNotificationError }) => { - onNotificationError?.(message); - }); - if (message.type === "disconnectedNotAttemptingReconnectError") { - this.setIsOpen(false); - } - } - - /** - * @internal - * setIsOpen - */ - protected setIsOpen(status: boolean) { - const shouldUpdate = status !== this.isOpen; - this.isOpen = status; - if (shouldUpdate) this.resource.emit("update"); - } -} diff --git a/packages/solid/src/resource/notifications/Websocket2023NotificationSubscription.ts b/packages/solid/src/resource/notifications/Websocket2023NotificationSubscription.ts deleted file mode 100644 index 80c51cd..0000000 --- a/packages/solid/src/resource/notifications/Websocket2023NotificationSubscription.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { UnexpectedResourceError } from "../../requester/results/error/ErrorResult"; -import { NotificationSubscription } from "./NotificationSubscription"; -import { SubscriptionClient } from "@solid-notifications/subscription"; -import { WebSocket } from "ws"; -import { - DisconnectedAttemptingReconnectError, - DisconnectedNotAttemptingReconnectError, - UnsupportedNotificationError, -} from "./results/NotificationErrors"; -import type { NotificationMessage } from "./NotificationMessage"; -import type { Resource } from "../Resource"; -import type { SolidLdoDatasetContext } from "../../SolidLdoDatasetContext"; -import type { - ChannelType, - NotificationChannel, -} from "@solid-notifications/types"; - -const CHANNEL_TYPE = - "http://www.w3.org/ns/solid/notifications#WebSocketChannel2023"; - -export class Websocket2023NotificationSubscription extends NotificationSubscription { - private socket: WebSocket | undefined; - private createWebsocket: (address: string) => WebSocket; - - // Reconnection data - // How often we should attempt a reconnection - private reconnectInterval = 5000; - // How many attempts have already been tried for a reconnection - private reconnectAttempts = 0; - // Whether or not the socket was manually closes - private isManualClose = false; - // Maximum number of attempts to reconnect - private maxReconnectAttempts = 6; - - constructor( - resource: Resource, - parentSubscription: (message: NotificationMessage) => void, - context: SolidLdoDatasetContext, - createWebsocket?: (address: string) => WebSocket, - ) { - super(resource, parentSubscription, context); - this.createWebsocket = createWebsocket ?? createWebsocketDefault; - } - - async open(): Promise { - try { - const notificationChannel = await this.discoverNotificationChannel(); - await this.subscribeToWebsocket(notificationChannel); - } catch (err) { - if ( - err instanceof Error && - err.message.startsWith("Discovery did not succeed") - ) { - this.onNotificationError( - new UnsupportedNotificationError(this.resource.uri, err.message), - ); - } else { - this.onNotificationError( - UnexpectedResourceError.fromThrown(this.resource.uri, err), - ); - } - this.onClose(); - } - } - - public async discoverNotificationChannel(): Promise { - const client = new SubscriptionClient(this.context.fetch); - return await client.subscribe( - this.resource.uri, - CHANNEL_TYPE as ChannelType, - ); - } - - public async subscribeToWebsocket( - notificationChannel: NotificationChannel, - ): Promise { - this.socket = this.createWebsocket( - notificationChannel.receiveFrom as string, - ); - - this.socket.onopen = () => { - this.reconnectAttempts = 0; // Reset attempts on successful connection - this.isManualClose = false; // Reset manual close flag - }; - - this.socket.onmessage = (message) => { - const messageData = message.data.toString(); - // TODO uncompliant Pod error on misformatted message - this.onNotification(JSON.parse(messageData) as NotificationMessage); - }; - - this.socket.onclose = this.onClose.bind(this); - - this.socket.onerror = (err) => { - this.onNotificationError( - new UnexpectedResourceError(this.resource.uri, err.error), - ); - }; - return; - } - - private onClose() { - if (!this.isManualClose) { - // Attempt to reconnect only if the disconnection was unintentional - if (this.reconnectAttempts < this.maxReconnectAttempts) { - this.reconnectAttempts++; - setTimeout(() => { - this.open(); - }, this.reconnectInterval); - this.onNotificationError( - new DisconnectedAttemptingReconnectError( - this.resource.uri, - `Attempting to reconnect to Websocket for ${this.resource.uri}.`, - ), - ); - } else { - this.onNotificationError( - new DisconnectedNotAttemptingReconnectError( - this.resource.uri, - `Lost connection to websocket for ${this.resource.uri}.`, - ), - ); - } - } - } - - protected async close(): Promise { - this.socket?.terminate(); - } -} - -function createWebsocketDefault(address: string) { - return new WebSocket(address); -} diff --git a/packages/solid/src/resource/notifications/results/NotificationErrors.ts b/packages/solid/src/resource/notifications/results/NotificationErrors.ts deleted file mode 100644 index f196c86..0000000 --- a/packages/solid/src/resource/notifications/results/NotificationErrors.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { UnexpectedResourceError } from "../../../requester/results/error/ErrorResult"; -import { ResourceError } from "../../../requester/results/error/ErrorResult"; - -export type NotificationCallbackError = - | DisconnectedAttemptingReconnectError - | DisconnectedNotAttemptingReconnectError - | UnsupportedNotificationError - | UnexpectedResourceError; - -/** - * Indicates that the requested method for receiving notifications is not - * supported by this Pod. - */ -export class UnsupportedNotificationError extends ResourceError { - readonly type = "unsupportedNotificationError" as const; -} - -/** - * Indicates that the socket has disconnected and is attempting to reconnect. - */ -export class DisconnectedAttemptingReconnectError extends ResourceError { - readonly type = "disconnectedAttemptingReconnectError" as const; -} - -/** - * Indicates that the socket has disconnected and is attempting to reconnect. - */ -export class DisconnectedNotAttemptingReconnectError extends ResourceError { - readonly type = "disconnectedNotAttemptingReconnectError" as const; -} diff --git a/packages/solid/src/resource/resourceResult/ResourceResult.ts b/packages/solid/src/resource/resourceResult/ResourceResult.ts deleted file mode 100644 index 3132fd1..0000000 --- a/packages/solid/src/resource/resourceResult/ResourceResult.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { RequesterResult } from "../../requester/results/RequesterResult"; -import type { Container } from "../Container"; -import type { Leaf } from "../Leaf"; - -/** - * Adds an additional field "resource" to SuccessResults. - */ -export type ResourceSuccess< - Result extends RequesterResult, - ResourceType extends Leaf | Container, -> = Result & { resource: ResourceType }; - -/** - * Adds an additional field "resource" to Results. - */ -export type ResourceResult< - Result extends RequesterResult, - ResourceType extends Leaf | Container, -> = Result extends Error ? Result : ResourceSuccess; diff --git a/packages/solid/src/resource/wac/WacRule.ts b/packages/solid/src/resource/wac/WacRule.ts deleted file mode 100644 index 150ed52..0000000 --- a/packages/solid/src/resource/wac/WacRule.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * A list of modes that a certain agent has access to - */ -export interface AccessModeList { - read: boolean; - append: boolean; - write: boolean; - control: boolean; -} - -/** - * A list of modes for each kind of agent - */ -export interface WacRule { - public: AccessModeList; - authenticated: AccessModeList; - agent: Record; -} diff --git a/packages/solid/src/resource/wac/getWacRule.ts b/packages/solid/src/resource/wac/getWacRule.ts deleted file mode 100644 index 3c2b68f..0000000 --- a/packages/solid/src/resource/wac/getWacRule.ts +++ /dev/null @@ -1,118 +0,0 @@ -import type { GetWacRuleSuccess } from "./results/GetWacRuleSuccess"; -import { guaranteeFetch } from "../../util/guaranteeFetch"; -import type { BasicRequestOptions } from "../../requester/requests/requestOptions"; -import type { HttpErrorResultType } from "../../requester/results/error/HttpErrorResult"; -import { HttpErrorResult } from "../../requester/results/error/HttpErrorResult"; -import type { NoncompliantPodError } from "../../requester/results/error/NoncompliantPodError"; -import type { UnexpectedResourceError } from "../../requester/results/error/ErrorResult"; -import { rawTurtleToDataset } from "../../util/rdfUtils"; -import { AuthorizationShapeType } from "../../.ldo/wac.shapeTypes"; -import type { AccessModeList, WacRule } from "./WacRule"; -import type { Authorization } from "../../.ldo/wac.typings"; -import type { WacRuleAbsent } from "./results/WacRuleAbsent"; - -export type GetWacRuleError = - | HttpErrorResultType - | NoncompliantPodError - | UnexpectedResourceError; -export type GetWacRuleResult = - | GetWacRuleSuccess - | GetWacRuleError - | WacRuleAbsent; - -/** - * Given the URI of an ACL document, return the Web Access Control (WAC) rules - * @param aclUri: The URI for the ACL document - * @param options: Options object to include an authenticated fetch function - * @returns GetWacRuleResult - */ -export async function getWacRuleWithAclUri( - aclUri: string, - options?: BasicRequestOptions, -): Promise { - const fetch = guaranteeFetch(options?.fetch); - const response = await fetch(aclUri); - const errorResult = HttpErrorResult.checkResponse(aclUri, response); - if (errorResult) return errorResult; - - if (response.status === 404) { - return { - type: "wacRuleAbsent", - uri: aclUri, - isError: false, - }; - } - - // Parse Turtle - const rawTurtle = await response.text(); - const rawTurtleResult = await rawTurtleToDataset(rawTurtle, aclUri); - if (rawTurtleResult.isError) return rawTurtleResult; - const dataset = rawTurtleResult.dataset; - const authorizations = dataset - .usingType(AuthorizationShapeType) - .matchSubject( - "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", - "http://www.w3.org/ns/auth/acl#Authorization", - ); - - const wacRule: WacRule = { - public: { - read: false, - write: false, - append: false, - control: false, - }, - authenticated: { - read: false, - write: false, - append: false, - control: false, - }, - agent: {}, - }; - - function applyAccessModesToList( - accessModeList: AccessModeList, - authorization: Authorization, - ): void { - authorization.mode?.forEach((mode) => { - accessModeList[mode["@id"].toLowerCase()] = true; - }); - } - - authorizations.forEach((authorization) => { - if ( - authorization.agentClass?.some( - (agentClass) => agentClass["@id"] === "Agent", - ) - ) { - applyAccessModesToList(wacRule.public, authorization); - applyAccessModesToList(wacRule.authenticated, authorization); - } - if ( - authorization.agentClass?.some( - (agentClass) => agentClass["@id"] === "AuthenticatedAgent", - ) - ) { - applyAccessModesToList(wacRule.authenticated, authorization); - } - authorization.agent?.forEach((agent) => { - if (!wacRule.agent[agent["@id"]]) { - wacRule.agent[agent["@id"]] = { - read: false, - write: false, - append: false, - control: false, - }; - } - applyAccessModesToList(wacRule.agent[agent["@id"]], authorization); - }); - }); - - return { - type: "getWacRuleSuccess", - uri: aclUri, - isError: false, - wacRule, - }; -} diff --git a/packages/solid/src/resource/wac/getWacUri.ts b/packages/solid/src/resource/wac/getWacUri.ts deleted file mode 100644 index d402c8b..0000000 --- a/packages/solid/src/resource/wac/getWacUri.ts +++ /dev/null @@ -1,70 +0,0 @@ -import type { GetWacUriSuccess } from "./results/GetWacUriSuccess"; -import type { HttpErrorResultType } from "../../requester/results/error/HttpErrorResult"; -import { - HttpErrorResult, - NotFoundHttpError, -} from "../../requester/results/error/HttpErrorResult"; -import { UnexpectedResourceError } from "../../requester/results/error/ErrorResult"; -import { guaranteeFetch } from "../../util/guaranteeFetch"; -import type { BasicRequestOptions } from "../../requester/requests/requestOptions"; -import { NoncompliantPodError } from "../../requester/results/error/NoncompliantPodError"; -import { parse as parseLinkHeader } from "http-link-header"; -import type { LeafUri } from "../../util/uriTypes"; - -export type GetWacUriError = - | HttpErrorResultType - | NotFoundHttpError - | NoncompliantPodError - | UnexpectedResourceError; -export type GetWacUriResult = GetWacUriSuccess | GetWacUriError; - -/** - * Get the URI for the WAC rules of a specific resource - * @param resourceUri: the URI of the resource - * @param options: Options object to include an authenticated fetch function - * @returns GetWacUriResult - */ -export async function getWacUri( - resourceUri: string, - options?: BasicRequestOptions, -): Promise { - try { - const fetch = guaranteeFetch(options?.fetch); - const response = await fetch(resourceUri, { - method: "head", - }); - const errorResult = HttpErrorResult.checkResponse(resourceUri, response); - if (errorResult) return errorResult; - if (NotFoundHttpError.is(response)) { - return new NotFoundHttpError( - resourceUri, - response, - "Could not get access control rules because the resource does not exist.", - ); - } - // Get the URI from the link header - const linkHeader = response.headers.get("link"); - if (!linkHeader) { - return new NoncompliantPodError( - resourceUri, - "No link header present in request.", - ); - } - const parsedLinkHeader = parseLinkHeader(linkHeader); - const aclUris = parsedLinkHeader.get("rel", "acl"); - if (aclUris.length !== 1) { - return new NoncompliantPodError( - resourceUri, - `There must be one link with a rel="acl"`, - ); - } - return { - type: "getWacUriSuccess", - isError: false, - uri: resourceUri, - wacUri: aclUris[0].uri as LeafUri, - }; - } catch (err: unknown) { - return UnexpectedResourceError.fromThrown(resourceUri, err); - } -} diff --git a/packages/solid/src/resource/wac/results/GetWacRuleSuccess.ts b/packages/solid/src/resource/wac/results/GetWacRuleSuccess.ts deleted file mode 100644 index 4dd17b6..0000000 --- a/packages/solid/src/resource/wac/results/GetWacRuleSuccess.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { ResourceSuccess } from "../../../requester/results/success/SuccessResult"; -import type { WacRule } from "../WacRule"; - -/** - * Returned when a WAC rule is successfully retrieved - */ -export interface GetWacRuleSuccess extends ResourceSuccess { - type: "getWacRuleSuccess"; - /** - * The rule that was retrieved - */ - wacRule: WacRule; -} diff --git a/packages/solid/src/resource/wac/results/GetWacUriSuccess.ts b/packages/solid/src/resource/wac/results/GetWacUriSuccess.ts deleted file mode 100644 index 3694d9f..0000000 --- a/packages/solid/src/resource/wac/results/GetWacUriSuccess.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { ResourceSuccess } from "../../../requester/results/success/SuccessResult"; -import type { LeafUri } from "../../../util/uriTypes"; - -/** - * Returned when the URI for a resources ACL document was successfully retried - */ -export interface GetWacUriSuccess extends ResourceSuccess { - type: "getWacUriSuccess"; - /** - * The URI of the ACL document - */ - wacUri: LeafUri; -} diff --git a/packages/solid/src/resource/wac/results/SetWacRuleSuccess.ts b/packages/solid/src/resource/wac/results/SetWacRuleSuccess.ts deleted file mode 100644 index a79b928..0000000 --- a/packages/solid/src/resource/wac/results/SetWacRuleSuccess.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { ResourceSuccess } from "../../../requester/results/success/SuccessResult"; -import type { WacRule } from "../WacRule"; - -/** - * Returned when rules were successfully written - */ -export interface SetWacRuleSuccess extends ResourceSuccess { - type: "setWacRuleSuccess"; - /** - * The written rule - */ - wacRule: WacRule; -} diff --git a/packages/solid/src/resource/wac/results/WacRuleAbsent.ts b/packages/solid/src/resource/wac/results/WacRuleAbsent.ts deleted file mode 100644 index 7bec46a..0000000 --- a/packages/solid/src/resource/wac/results/WacRuleAbsent.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { ResourceSuccess } from "../../../requester/results/success/SuccessResult"; - -/** - * Returned if no WAC rule was returned from the server - */ -export interface WacRuleAbsent extends ResourceSuccess { - type: "wacRuleAbsent"; -} diff --git a/packages/solid/src/resource/wac/setWacRule.ts b/packages/solid/src/resource/wac/setWacRule.ts deleted file mode 100644 index 1569cb7..0000000 --- a/packages/solid/src/resource/wac/setWacRule.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { createLdoDataset } from "@ldo/ldo"; -import type { BasicRequestOptions } from "../../requester/requests/requestOptions"; -import type { UnexpectedResourceError } from "../../requester/results/error/ErrorResult"; -import { - HttpErrorResult, - type HttpErrorResultType, -} from "../../requester/results/error/HttpErrorResult"; -import { isContainerUri, type LeafUri } from "../../util/uriTypes"; -import type { AccessModeList, WacRule } from "./WacRule"; -import type { SetWacRuleSuccess } from "./results/SetWacRuleSuccess"; -import type { Authorization } from "../../.ldo/wac.typings"; -import { AuthorizationShapeType } from "../../.ldo/wac.shapeTypes"; -import { v4 } from "uuid"; -import { guaranteeFetch } from "../../util/guaranteeFetch"; - -export type SetWacRuleError = HttpErrorResultType | UnexpectedResourceError; -export type SetWacRuleResult = SetWacRuleSuccess | SetWacRuleError; - -/** - * Given the URI of an ACL document and some WAC rules, set the WAC rules of - * that document - * @param aclUri: The URI for the ACL document - * @param newRule: A new WAC rule to set. This will overwrite old rules - * @param accessTo: The document this rule refers to - * @param options: Options object to include an authenticated fetch function - * @returns SetWacRuleResult - */ -export async function setWacRuleForAclUri( - aclUri: LeafUri, - newRule: WacRule, - accessTo: string, - options?: BasicRequestOptions, -): Promise { - const fetch = guaranteeFetch(options?.fetch); - // The rule map keeps track of all the rules that are currently being used - // so that similar rules can be grouped together - const ruleMap: Record = {}; - // The dataset that will eventually be sent to the Pod - const dataset = createLdoDataset(); - - // Helper function to add rules to the dataset by grouping them in the ruleMap - function addRuleToDataset( - type: "public" | "authenticated" | "agent", - accessModeList: AccessModeList, - agentId?: string, - ) { - const accessModeListHash = hashAccessModeList(accessModeList); - // No need to add if all access is false - if (accessModeListHash === "") return; - if (!ruleMap[accessModeListHash]) { - const authorization = dataset - .usingType(AuthorizationShapeType) - .fromSubject(`${aclUri}#${v4()}`); - authorization.type = { "@id": "Authorization" }; - if (accessModeList.read) authorization.mode?.add({ "@id": "Read" }); - if (accessModeList.write) authorization.mode?.add({ "@id": "Write" }); - if (accessModeList.append) authorization.mode?.add({ "@id": "Append" }); - if (accessModeList.control) authorization.mode?.add({ "@id": "Control" }); - authorization.accessTo = { "@id": accessTo }; - if (isContainerUri(accessTo)) { - authorization.default = { "@id": accessTo }; - } - ruleMap[accessModeListHash] = authorization; - } - const authorization = ruleMap[accessModeListHash]; - // Add agents to the rule - if (type === "public") { - authorization.agentClass?.add({ "@id": "Agent" }); - } else if (type === "authenticated") { - authorization.agentClass?.add({ "@id": "AuthenticatedAgent" }); - } else if (type === "agent" && agentId) { - authorization.agent?.add({ "@id": agentId }); - } - } - - // Add each rule to the dataset - addRuleToDataset("public", newRule.public); - addRuleToDataset("authenticated", newRule.authenticated); - Object.entries(newRule.agent).forEach(([agentUri, accessModeList]) => { - addRuleToDataset("agent", accessModeList, agentUri); - }); - - // Save to Pod - const response = await fetch(aclUri, { - method: "PUT", - headers: { - "content-type": "text/turtle", - }, - body: dataset.toString(), - }); - const errorResult = HttpErrorResult.checkResponse(aclUri, response); - if (errorResult) return errorResult; - - return { - type: "setWacRuleSuccess", - uri: aclUri, - isError: false, - wacRule: newRule, - }; -} - -// Hashes the access mode list for use in the rule map -function hashAccessModeList(list: AccessModeList): string { - return Object.entries(list).reduce( - (agg, [key, isPresent]) => (isPresent ? agg + key : agg), - "", - ); -} diff --git a/packages/solid/src/types.ts b/packages/solid/src/types.ts deleted file mode 100644 index 91141ea..0000000 --- a/packages/solid/src/types.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { ILdoDataset } from "@ldo/ldo"; -import type { ResourceGetterOptions } from "./ResourceStore"; -import type { Container } from "./resource/Container"; -import type { Leaf } from "./resource/Leaf"; -import type { ContainerUri, LeafUri } from "./util/uriTypes"; -import type { SolidLdoTransactionDataset } from "./SolidLdoTransactionDataset"; - -/** - * A SolidLdoDataset provides methods for getting Solid resources. - */ -export interface ISolidLdoDataset extends ILdoDataset { - startTransaction(): SolidLdoTransactionDataset; - - getResource(uri: ContainerUri, options?: ResourceGetterOptions): Container; - getResource(uri: LeafUri, options?: ResourceGetterOptions): Leaf; - getResource(uri: string, options?: ResourceGetterOptions): Leaf | Container; - getResource(uri: string, options?: ResourceGetterOptions): Leaf | Container; -} diff --git a/packages/solid/src/util/RequestBatcher.ts b/packages/solid/src/util/RequestBatcher.ts deleted file mode 100644 index 4528ec1..0000000 --- a/packages/solid/src/util/RequestBatcher.ts +++ /dev/null @@ -1,187 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -export interface WaitingProcess { - name: string; - args: Args; - perform: (...args: Args) => Promise; - awaitingResolutions: ((returnValue: Return) => void)[]; - awaitingRejections: ((err: any) => void)[]; - after?: (result: Return) => void; -} - -export const ANY_KEY = "any"; - -/** - * Options for processes that are waiting to execute - */ -export interface WaitingProcessOptions { - /** - * The name of the process like "read" or "delete" - */ - name: string; - /** - * The arguements supplied to the process - */ - args: Args; - /** - * A function that will be triggered when it's time to execute this process - * @param args - arguments supplied to the process - * @returns a return type - */ - perform: (...args: Args) => Promise; - /** - * A custom function to modify the queue based on the current state of the - * queue - * @param processQueue - The current process queue - * @param currentlyProcessing - The Process that is currently executing - * @param args - provided args - * @returns A WaitingProcess that this request should listen to, or undefined - * if it should create its own - */ - modifyQueue: ( - processQueue: WaitingProcess[], - currentlyProcessing: WaitingProcess | undefined, - args: Args, - ) => WaitingProcess | undefined; - after?: (result: Return) => void; -} - -/** - * @internal - * A utility for batching a request - */ -export class RequestBatcher { - /** - * A mapping between a process key and the last time in UTC a process of that - * key was executed. - */ - private lastRequestTimestampMap: Record = {}; - - /** - * A pointer to the current process the batcher is working on - */ - private currentlyProcessing: WaitingProcess | undefined = - undefined; - - /** - * A queue of upcoming processes - */ - private processQueue: WaitingProcess[] = []; - - /** - * The amount of time (in milliseconds) between requests of the same key - */ - public batchMillis: number; - - /** - * @param options - options, including the value for batchMillis - */ - constructor( - options?: Partial<{ - batchMillis: number; - }>, - ) { - this.batchMillis = options?.batchMillis || 1000; - } - - /** - * Check if the request batcher is currently working on a process - * @param key - the key of the process to check - * @returns true if the batcher is currently working on the provided process - */ - public isLoading(key: string): boolean { - if (key === ANY_KEY) return !!this.currentlyProcessing; - return this.currentlyProcessing?.name === key; - } - - /** - * Triggers the next process in the queue or triggers a timeout to wait to - * execute the next process in the queue if not enough time has passed since - * the last process was triggered. - */ - private triggerOrWaitProcess() { - if (!this.processQueue[0] || this.currentlyProcessing) { - return; - } - this.currentlyProcessing = this.processQueue.shift(); - const processName = this.currentlyProcessing!.name; - - // Set last request timestamp if not available - if (!this.lastRequestTimestampMap[processName]) { - this.lastRequestTimestampMap[processName] = Date.UTC(0, 0, 0, 0, 0, 0, 0); - } - - const lastRequestTimestamp = this.lastRequestTimestampMap[processName]; - const timeSinceLastTrigger = Date.now() - lastRequestTimestamp; - - const triggerProcess = async () => { - this.lastRequestTimestampMap[processName] = Date.now(); - this.lastRequestTimestampMap[ANY_KEY] = Date.now(); - // Remove the process from the queue - const processToTrigger = this.currentlyProcessing; - if (processToTrigger) { - this.currentlyProcessing = processToTrigger; - try { - const returnValue = await processToTrigger.perform( - ...processToTrigger.args, - ); - if (processToTrigger.after) { - processToTrigger.after(returnValue); - } - processToTrigger.awaitingResolutions.forEach((callback) => { - callback(returnValue); - }); - } catch (err) { - processToTrigger.awaitingRejections.forEach((callback) => { - callback(err); - }); - } - this.currentlyProcessing = undefined; - - this.triggerOrWaitProcess(); - } - }; - - if (timeSinceLastTrigger < this.batchMillis) { - setTimeout(triggerProcess, this.batchMillis - timeSinceLastTrigger); - } else { - triggerProcess(); - } - } - - /** - * Adds a process to the queue and waits for the process to be complete - * @param options - WaitingProcessOptions - * @returns A promise that resolves when the process resolves - */ - public async queueProcess( - options: WaitingProcessOptions, - ): Promise { - return new Promise((resolve, reject) => { - const shouldAwait = options.modifyQueue( - this.processQueue, - this.currentlyProcessing, - options.args, - ); - - if (shouldAwait) { - shouldAwait.awaitingResolutions.push(resolve); - shouldAwait.awaitingRejections.push(reject); - return; - } - - const waitingProcess: WaitingProcess = { - name: options.name, - args: options.args, - perform: options.perform, - awaitingResolutions: [resolve], - awaitingRejections: [reject], - after: options.after, - }; - // HACK: Ugly cast - this.processQueue.push( - waitingProcess as unknown as WaitingProcess, - ); - this.triggerOrWaitProcess(); - }); - } -} diff --git a/packages/solid/src/util/guaranteeFetch.ts b/packages/solid/src/util/guaranteeFetch.ts deleted file mode 100644 index 987b7b4..0000000 --- a/packages/solid/src/util/guaranteeFetch.ts +++ /dev/null @@ -1,12 +0,0 @@ -import crossFetch from "cross-fetch"; - -/** - * @internal - * Guantees that some kind of fetch is available - * - * @param fetchInput - A potential fetch object - * @returns a proper fetch object. Cross-fetch is default - */ -export function guaranteeFetch(fetchInput?: typeof fetch): typeof fetch { - return fetchInput || crossFetch; -} diff --git a/packages/solid/src/util/rdfUtils.ts b/packages/solid/src/util/rdfUtils.ts deleted file mode 100644 index d10a737..0000000 --- a/packages/solid/src/util/rdfUtils.ts +++ /dev/null @@ -1,149 +0,0 @@ -import type { LdoDataset } from "@ldo/ldo"; -import { parseRdf } from "@ldo/ldo"; -import { namedNode, quad as createQuad } from "@rdfjs/data-model"; -import type { Dataset } from "@rdfjs/types"; -import type { ContainerUri } from "./uriTypes"; -import { isContainerUri } from "./uriTypes"; -import { NoncompliantPodError } from "../requester/results/error/NoncompliantPodError"; -import { UnexpectedResourceError } from "../requester/results/error/ErrorResult"; - -export const ldpContains = namedNode("http://www.w3.org/ns/ldp#contains"); -export const rdfType = namedNode( - "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", -); -export const ldpResource = namedNode("http://www.w3.org/ns/ldp#Resource"); -export const ldpContainer = namedNode("http://www.w3.org/ns/ldp#Container"); -export const ldpBasicContainer = namedNode( - "http://www.w3.org/ns/ldp#BasicContainer", -); - -/** - * @internal - * Gets the URI of a parent according the the Solid Spec - * - * @param uri - the child URI - * @returns A parent URI or undefined if not possible - */ -export function getParentUri(uri: string): ContainerUri | undefined { - const urlObject = new URL(uri); - const pathItems = urlObject.pathname.split("/"); - if ( - pathItems.length < 2 || - (pathItems.length === 2 && pathItems[1].length === 0) - ) { - return undefined; - } - if (pathItems[pathItems.length - 1] === "") { - pathItems.pop(); - } - pathItems.pop(); - urlObject.pathname = `${pathItems.join("/")}/`; - return urlObject.toString() as ContainerUri; -} - -/** - * @internal - * Gets the slug (last part of the path) for a given URI - * - * @param uri - the full URI - * @returns the slug of the URI - */ -export function getSlug(uri: string): string { - const urlObject = new URL(uri); - const pathItems = urlObject.pathname.split("/"); - return pathItems[pathItems.length - 1] || pathItems[pathItems.length - 2]; -} - -/** - * @internal - * Deletes mention of a resource from the provided dataset - * - * @param resourceUri - the resource to delete - * @param dataset - dataset to modify - */ -export function deleteResourceRdfFromContainer( - resourceUri: string, - dataset: Dataset, -) { - const parentUri = getParentUri(resourceUri); - if (parentUri) { - const parentNode = namedNode(parentUri); - const resourceNode = namedNode(resourceUri); - dataset.delete( - createQuad(parentNode, ldpContains, resourceNode, parentNode), - ); - dataset.deleteMatches(resourceNode, undefined, undefined, parentNode); - } -} - -/** - * @internal - * Adds a resource to a container in an RDF dataset - * - * @param resourceUri - the resource to add - * @param dataset - the dataset to modify - */ -export function addResourceRdfToContainer( - resourceUri: string, - dataset: Dataset, -) { - const parentUri = getParentUri(resourceUri); - if (parentUri) { - const parentNode = namedNode(parentUri); - const resourceNode = namedNode(resourceUri); - dataset.add(createQuad(parentNode, ldpContains, resourceNode, parentNode)); - dataset.add(createQuad(resourceNode, rdfType, ldpResource, parentNode)); - if (isContainerUri(resourceUri)) { - dataset.add( - createQuad(resourceNode, rdfType, ldpBasicContainer, parentNode), - ); - dataset.add(createQuad(resourceNode, rdfType, ldpContainer, parentNode)); - } - addResourceRdfToContainer(parentUri, dataset); - } -} - -/** - * @internal - * Adds raw turtle to the provided dataset - * @param rawTurtle - String of raw turtle - * @param dataset - the dataset to modify - * @param baseUri - base URI to parsing turtle - * @returns Undefined if successful, noncompliantPodError if not - */ -export async function addRawTurtleToDataset( - rawTurtle: string, - dataset: Dataset, - baseUri: string, -): Promise { - const rawTurtleResult = await rawTurtleToDataset(rawTurtle, baseUri); - if (rawTurtleResult.isError) return rawTurtleResult; - const loadedDataset = rawTurtleResult.dataset; - const graphNode = namedNode(baseUri); - // Destroy all triples that were once a part of this resouce - dataset.deleteMatches(undefined, undefined, undefined, graphNode); - // Add the triples from the fetched item - dataset.addAll( - loadedDataset.map((quad) => - createQuad(quad.subject, quad.predicate, quad.object, graphNode), - ), - ); -} - -export async function rawTurtleToDataset( - rawTurtle: string, - baseUri: string, -): Promise<{ isError: false; dataset: LdoDataset } | NoncompliantPodError> { - try { - const loadedDataset = await parseRdf(rawTurtle, { - baseIRI: baseUri, - }); - return { isError: false, dataset: loadedDataset }; - } catch (err) { - const error = UnexpectedResourceError.fromThrown(baseUri, err); - return new NoncompliantPodError( - baseUri, - `Request returned noncompliant turtle: ${error.message}\n${rawTurtle}`, - ); - } -} diff --git a/packages/solid/src/util/splitChangesByGraph.ts b/packages/solid/src/util/splitChangesByGraph.ts deleted file mode 100644 index 007253f..0000000 --- a/packages/solid/src/util/splitChangesByGraph.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { createDataset } from "@ldo/dataset"; -import type { GraphNode, DatasetChanges } from "@ldo/rdf-utils"; -import type { Quad } from "@rdfjs/types"; -import { defaultGraph, namedNode, quad as createQuad } from "@rdfjs/data-model"; - -/** - * @internal - * Converts an RDFJS Graph Node to a string hash - * @param graphNode - the node to convert - * @returns a unique string corresponding to the node - */ -export function graphNodeToString(graphNode: GraphNode): string { - return graphNode.termType === "DefaultGraph" - ? "defaultGraph()" - : graphNode.value; -} - -/** - * @internal - * Converts a unique string to a GraphNode - * @param input - the unique string - * @returns A graph node - */ -export function stringToGraphNode(input: string): GraphNode { - return input === "defaultGraph()" ? defaultGraph() : namedNode(input); -} - -/** - * Splits all changes in a DatasetChanges into individual DatasetChanges grouped - * by the quad graph. - * @param changes - Changes to split - * @returns A map between the quad graph and the changes associated with that - * graph - */ -export function splitChangesByGraph( - changes: DatasetChanges, -): Map> { - const changesMap: Record> = {}; - changes.added?.forEach((quad) => { - const graphHash = graphNodeToString(quad.graph as GraphNode); - if (!changesMap[graphHash]) { - changesMap[graphHash] = {}; - } - if (!changesMap[graphHash].added) { - changesMap[graphHash].added = createDataset(); - } - changesMap[graphHash].added?.add( - createQuad(quad.subject, quad.predicate, quad.object, quad.graph), - ); - }); - - changes.removed?.forEach((quad) => { - const graphHash = graphNodeToString(quad.graph as GraphNode); - if (!changesMap[graphHash]) { - changesMap[graphHash] = {}; - } - if (!changesMap[graphHash].removed) { - changesMap[graphHash].removed = createDataset(); - } - changesMap[graphHash].removed?.add( - createQuad(quad.subject, quad.predicate, quad.object, quad.graph), - ); - }); - - const finalMap = new Map>(); - Object.entries(changesMap).forEach(([key, value]) => { - finalMap.set(stringToGraphNode(key), value); - }); - return finalMap; -} diff --git a/packages/solid/src/util/uriTypes.ts b/packages/solid/src/util/uriTypes.ts deleted file mode 100644 index aa15152..0000000 --- a/packages/solid/src/util/uriTypes.ts +++ /dev/null @@ -1,126 +0,0 @@ -/** - * A LeafUri is any URI that has a pahtname that ends in a "/". It represents a - * container. - */ -// The & {} allows for alias preservation -// eslint-disable-next-line @typescript-eslint/ban-types -export type ContainerUri = `${string}/${NonPathnameEnding}` & {}; - -/** - * A LeafUri is any URI that does not have a pahtname that ends in a "/". It - * represents a data resource or a binary resource. Not a container. - */ -export type LeafUri = - // The & {} allows for alias preservation - // eslint-disable-next-line @typescript-eslint/ban-types - `${string}${EveryLegalPathnameCharacterOtherThanSlash}${NonPathnameEnding}` & {}; - -/** - * Checks if a provided string is a Container URI - * @param uri - the string to check - * @returns true if the string is a container URI - */ -export function isContainerUri(uri: string): uri is ContainerUri { - const url = new URL(uri); - return url.pathname.endsWith("/"); -} - -/** - * Checks if a provided string is a leaf URI - * @param uri - the string to check - * @returns true if the string is a leaf URI - */ -export function isLeafUri(uri: string): uri is LeafUri { - return !isContainerUri(uri); -} - -/** - * @internal - */ -type NonPathnameEnding = "" | `?${string}` | `#${string}`; - -/** - * @internal - */ -type EveryLegalPathnameCharacterOtherThanSlash = - | "A" - | "B" - | "C" - | "D" - | "E" - | "F" - | "G" - | "H" - | "I" - | "J" - | "K" - | "L" - | "M" - | "N" - | "O" - | "P" - | "Q" - | "R" - | "S" - | "T" - | "U" - | "V" - | "W" - | "X" - | "Y" - | "Z" - | "a" - | "b" - | "c" - | "d" - | "e" - | "f" - | "g" - | "h" - | "i" - | "j" - | "k" - | "l" - | "m" - | "n" - | "o" - | "p" - | "q" - | "r" - | "s" - | "t" - | "u" - | "v" - | "w" - | "x" - | "y" - | "z" - | "1" - | "2" - | "3" - | "4" - | "5" - | "6" - | "7" - | "8" - | "9" - | "0" - | "-" - | "." - | "_" - | "~" - | ":" - | "[" - | "]" - | "@" - | "!" - | "$" - | "&" - | "'" - | "(" - | ")" - | "*" - | "+" - | "," - | ";" - | "="; diff --git a/packages/solid/test/.ldo/post.context.ts b/packages/solid/test/.ldo/post.context.ts deleted file mode 100644 index dafbe33..0000000 --- a/packages/solid/test/.ldo/post.context.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { ContextDefinition } from "jsonld"; - -/** - * ============================================================================= - * postContext: JSONLD Context for post - * ============================================================================= - */ -export const postContext: ContextDefinition = { - type: { - "@id": "@type", - }, - SocialMediaPosting: "http://schema.org/SocialMediaPosting", - CreativeWork: "http://schema.org/CreativeWork", - Thing: "http://schema.org/Thing", - articleBody: { - "@id": "http://schema.org/articleBody", - "@type": "http://www.w3.org/2001/XMLSchema#string", - }, - uploadDate: { - "@id": "http://schema.org/uploadDate", - "@type": "http://www.w3.org/2001/XMLSchema#date", - }, - image: { - "@id": "http://schema.org/image", - "@type": "@id", - }, - publisher: { - "@id": "http://schema.org/publisher", - "@type": "@id", - }, -}; diff --git a/packages/solid/test/.ldo/post.schema.ts b/packages/solid/test/.ldo/post.schema.ts deleted file mode 100644 index 39e8b63..0000000 --- a/packages/solid/test/.ldo/post.schema.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { Schema } from "shexj"; - -/** - * ============================================================================= - * postSchema: ShexJ Schema for post - * ============================================================================= - */ -export const postSchema: Schema = { - type: "Schema", - shapes: [ - { - id: "https://example.com/PostSh", - type: "ShapeDecl", - shapeExpr: { - type: "Shape", - expression: { - type: "EachOf", - expressions: [ - { - type: "TripleConstraint", - predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", - valueExpr: { - type: "NodeConstraint", - values: [ - "http://schema.org/SocialMediaPosting", - "http://schema.org/CreativeWork", - "http://schema.org/Thing", - ], - }, - }, - { - type: "TripleConstraint", - predicate: "http://schema.org/articleBody", - valueExpr: { - type: "NodeConstraint", - datatype: "http://www.w3.org/2001/XMLSchema#string", - }, - min: 0, - max: 1, - annotations: [ - { - type: "Annotation", - predicate: "http://www.w3.org/2000/01/rdf-schema#label", - object: { - value: "articleBody", - }, - }, - { - type: "Annotation", - predicate: "http://www.w3.org/2000/01/rdf-schema#comment", - object: { - value: "The actual body of the article. ", - }, - }, - ], - }, - { - type: "TripleConstraint", - predicate: "http://schema.org/uploadDate", - valueExpr: { - type: "NodeConstraint", - datatype: "http://www.w3.org/2001/XMLSchema#date", - }, - annotations: [ - { - type: "Annotation", - predicate: "http://www.w3.org/2000/01/rdf-schema#label", - object: { - value: "uploadDate", - }, - }, - { - type: "Annotation", - predicate: "http://www.w3.org/2000/01/rdf-schema#comment", - object: { - value: - "Date when this media object was uploaded to this site.", - }, - }, - ], - }, - { - type: "TripleConstraint", - predicate: "http://schema.org/image", - valueExpr: { - type: "NodeConstraint", - nodeKind: "iri", - }, - min: 0, - max: 1, - annotations: [ - { - type: "Annotation", - predicate: "http://www.w3.org/2000/01/rdf-schema#label", - object: { - value: "image", - }, - }, - { - type: "Annotation", - predicate: "http://www.w3.org/2000/01/rdf-schema#comment", - object: { - value: - "A media object that encodes this CreativeWork. This property is a synonym for encoding.", - }, - }, - ], - }, - { - type: "TripleConstraint", - predicate: "http://schema.org/publisher", - valueExpr: { - type: "NodeConstraint", - nodeKind: "iri", - }, - annotations: [ - { - type: "Annotation", - predicate: "http://www.w3.org/2000/01/rdf-schema#label", - object: { - value: "publisher", - }, - }, - { - type: "Annotation", - predicate: "http://www.w3.org/2000/01/rdf-schema#comment", - object: { - value: "The publisher of the creative work.", - }, - }, - ], - }, - ], - }, - annotations: [ - { - type: "Annotation", - predicate: "http://www.w3.org/2000/01/rdf-schema#label", - object: { - value: "SocialMediaPost", - }, - }, - { - type: "Annotation", - predicate: "http://www.w3.org/2000/01/rdf-schema#comment", - object: { - value: - "A post to a social media platform, including blog posts, tweets, Facebook posts, etc.", - }, - }, - ], - }, - }, - ], -}; diff --git a/packages/solid/test/.ldo/post.shapeTypes.ts b/packages/solid/test/.ldo/post.shapeTypes.ts deleted file mode 100644 index 4c50683..0000000 --- a/packages/solid/test/.ldo/post.shapeTypes.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { ShapeType } from "@ldo/ldo"; -import { postSchema } from "./post.schema"; -import { postContext } from "./post.context"; -import { PostSh } from "./post.typings"; - -/** - * ============================================================================= - * LDO ShapeTypes post - * ============================================================================= - */ - -/** - * PostSh ShapeType - */ -export const PostShShapeType: ShapeType = { - schema: postSchema, - shape: "https://example.com/PostSh", - context: postContext, -}; diff --git a/packages/solid/test/.ldo/post.typings.ts b/packages/solid/test/.ldo/post.typings.ts deleted file mode 100644 index 9ebaf71..0000000 --- a/packages/solid/test/.ldo/post.typings.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { ContextDefinition } from "jsonld"; - -/** - * ============================================================================= - * Typescript Typings for post - * ============================================================================= - */ - -/** - * PostSh Type - */ -export interface PostSh { - "@id"?: string; - "@context"?: ContextDefinition; - type: - | { - "@id": "SocialMediaPosting"; - } - | { - "@id": "CreativeWork"; - } - | { - "@id": "Thing"; - }; - /** - * The actual body of the article. - */ - articleBody?: string; - /** - * Date when this media object was uploaded to this site. - */ - uploadDate: string; - /** - * A media object that encodes this CreativeWork. This property is a synonym for encoding. - */ - image?: { - "@id": string; - }; - /** - * The publisher of the creative work. - */ - publisher: { - "@id": string; - }; -} diff --git a/packages/solid/test/ErrorResult.test.ts b/packages/solid/test/ErrorResult.test.ts deleted file mode 100644 index 50da070..0000000 --- a/packages/solid/test/ErrorResult.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { - AggregateError, - ErrorResult, - ResourceError, - UnexpectedResourceError, -} from "../src/requester/results/error/ErrorResult"; -import { InvalidUriError } from "../src/requester/results/error/InvalidUriError"; - -describe("ErrorResult", () => { - describe("fromThrown", () => { - it("returns an UnexpecteResourceError if a string is provided", () => { - expect( - UnexpectedResourceError.fromThrown("https://example.com/", "hello") - .message, - ).toBe("hello"); - }); - - it("returns an UnexpecteResourceError if an odd valud is provided", () => { - expect( - UnexpectedResourceError.fromThrown("https://example.com/", 5).message, - ).toBe("Error of type number thrown: 5"); - }); - }); - - describe("AggregateError", () => { - it("flattens aggregate errors provided to the constructor", () => { - const err1 = UnexpectedResourceError.fromThrown("https://abc.com", "1"); - const err2 = UnexpectedResourceError.fromThrown("https://abc.com", "2"); - const err3 = UnexpectedResourceError.fromThrown("https://abc.com", "3"); - const err4 = UnexpectedResourceError.fromThrown("https://abc.com", "4"); - const aggErr1 = new AggregateError([err1, err2]); - const aggErr2 = new AggregateError([err3, err4]); - const finalAgg = new AggregateError([aggErr1, aggErr2]); - expect(finalAgg.errors.length).toBe(4); - }); - }); - - describe("default messages", () => { - class ConcreteResourceError extends ResourceError { - readonly type = "concreteResourceError" as const; - } - class ConcreteErrorResult extends ErrorResult { - readonly type = "concreteErrorResult" as const; - } - - it("ResourceError fallsback to a default message if none is provided", () => { - expect(new ConcreteResourceError("https://example.com/").message).toBe( - "An unkown error for https://example.com/", - ); - }); - - it("ErrorResult fallsback to a default message if none is provided", () => { - expect(new ConcreteErrorResult().message).toBe( - "An unkown error was encountered.", - ); - }); - - it("InvalidUriError fallsback to a default message if none is provided", () => { - expect(new InvalidUriError("https://example.com/").message).toBe( - "https://example.com/ is an invalid uri.", - ); - }); - }); -}); diff --git a/packages/solid/test/Integration.test.ts b/packages/solid/test/Integration.test.ts deleted file mode 100644 index 4c63a19..0000000 --- a/packages/solid/test/Integration.test.ts +++ /dev/null @@ -1,2264 +0,0 @@ -import type { App } from "@solid/community-server"; -import type { - Container, - ContainerUri, - Leaf, - LeafUri, - SolidLdoDataset, - UpdateResultError, -} from "../src"; -import { - changeData, - commitData, - createSolidLdoDataset, - SolidLdoTransactionDataset, -} from "../src"; -import { ROOT_CONTAINER, WEB_ID, createApp } from "./solidServer.helper"; -import { - namedNode, - quad as createQuad, - literal, - defaultGraph, -} from "@rdfjs/data-model"; -import type { CreateSuccess } from "../src/requester/results/success/CreateSuccess"; -import type { AggregateSuccess } from "../src/requester/results/success/SuccessResult"; -import type { - IgnoredInvalidUpdateSuccess, - UpdateDefaultGraphSuccess, - UpdateSuccess, -} from "../src/requester/results/success/UpdateSuccess"; -import type { - ResourceResult, - ResourceSuccess, -} from "../src/resource/resourceResult/ResourceResult"; -import type { - AggregateError, - UnexpectedResourceError, -} from "../src/requester/results/error/ErrorResult"; -import type { InvalidUriError } from "../src/requester/results/error/InvalidUriError"; -import { Buffer } from "buffer"; -import { PostShShapeType } from "./.ldo/post.shapeTypes"; -import type { - ServerHttpError, - UnauthenticatedHttpError, - UnexpectedHttpError, -} from "../src/requester/results/error/HttpErrorResult"; -import type { NoncompliantPodError } from "../src/requester/results/error/NoncompliantPodError"; -import type { GetWacRuleSuccess } from "../src/resource/wac/results/GetWacRuleSuccess"; -import type { WacRule } from "../src/resource/wac/WacRule"; -import type { GetStorageContainerFromWebIdSuccess } from "../src/requester/results/success/CheckRootContainerSuccess"; -import { generateAuthFetch } from "./authFetch.helper"; -import { wait } from "./utils.helper"; -import fs from "fs/promises"; -import path from "path"; - -const TEST_CONTAINER_SLUG = "test_ldo/"; -const TEST_CONTAINER_URI = - `${ROOT_CONTAINER}${TEST_CONTAINER_SLUG}` as ContainerUri; -const SAMPLE_DATA_URI = `${TEST_CONTAINER_URI}sample.ttl` as LeafUri; -const SAMPLE2_DATA_SLUG = "sample2.ttl"; -const SAMPLE2_DATA_URI = `${TEST_CONTAINER_URI}${SAMPLE2_DATA_SLUG}` as LeafUri; -const SAMPLE_BINARY_URI = `${TEST_CONTAINER_URI}sample.txt` as LeafUri; -const SAMPLE2_BINARY_SLUG = `sample2.txt`; -const SAMPLE2_BINARY_URI = - `${TEST_CONTAINER_URI}${SAMPLE2_BINARY_SLUG}` as LeafUri; -const SAMPLE_CONTAINER_URI = - `${TEST_CONTAINER_URI}sample_container/` as ContainerUri; -const SAMPLE_PROFILE_URI = `${TEST_CONTAINER_URI}profile.ttl` as LeafUri; -const SPIDER_MAN_TTL = `@base . -@prefix rdf: . -@prefix rdfs: . -@prefix foaf: . -@prefix rel: . - -<#green-goblin> - rel:enemyOf <#spiderman> ; - a foaf:Person ; # in the context of the Marvel universe - foaf:name "Green Goblin" . - -<#spiderman> - rel:enemyOf <#green-goblin> ; - a foaf:Person ; - foaf:name "Spiderman", "Человек-паук"@ru .`; -const TEST_CONTAINER_TTL = `@prefix dc: . -@prefix ldp: . -@prefix posix: . -@prefix xsd: . - -<> "sample.txt"; - a ldp:Container, ldp:BasicContainer, ldp:Resource; - dc:modified "2023-10-20T13:57:14.000Z"^^xsd:dateTime. - a ldp:Resource, ; - dc:modified "2023-10-20T13:57:14.000Z"^^xsd:dateTime. - a ldp:Resource, ; - dc:modified "2023-10-20T13:57:14.000Z"^^xsd:dateTime. -<> posix:mtime 1697810234; - ldp:contains , . - posix:mtime 1697810234; - posix:size 522. - posix:mtime 1697810234; - posix:size 10.`; -const TEST_CONTAINER_ACL_URI = `${TEST_CONTAINER_URI}.acl`; -const TEST_CONTAINER_ACL = `<#b30e3fd1-b5a8-4763-ad9d-e95de9cf7933> a ; - <${TEST_CONTAINER_URI}>; - <${TEST_CONTAINER_URI}>; - , , , ; - <${WEB_ID}>; - , .`; -const SAMPLE_PROFILE_TTL = ` -@prefix pim: . - -<${SAMPLE_PROFILE_URI}> pim:storage , . -`; - -async function testRequestLoads( - request: () => Promise, - loadingResource: Leaf | Container, - loadingValues: Partial<{ - isLoading: boolean; - isCreating: boolean; - isReading: boolean; - isUploading: boolean; - isReloading: boolean; - isDeleting: boolean; - isUpdating: boolean; - isDoingInitialFetch: boolean; - }>, -): Promise { - const allLoadingValues = { - isLoading: false, - isCreating: false, - isReading: false, - isUploading: false, - isReloading: false, - isDeleting: false, - isUpdating: false, - isDoingInitialFetch: false, - ...loadingValues, - }; - const [returnVal] = await Promise.all([ - request(), - (async () => { - Object.entries(allLoadingValues).forEach(([key, value]) => { - if ( - loadingResource.type === "container" && - (key === "isUploading" || key === "isUpdating") - ) { - return; - } - expect(loadingResource[key]()).toBe(value); - }); - })(), - ]); - return returnVal; -} - -describe("Integration", () => { - let app: App; - let authFetch: typeof fetch; - let fetchMock: jest.Mock< - Promise, - [input: RequestInfo | URL, init?: RequestInit | undefined] - >; - let solidLdoDataset: SolidLdoDataset; - - let previousJestId: string | undefined; - let previousNodeEnv: string | undefined; - 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 - app = await createApp(); - await app.start(); - - authFetch = await generateAuthFetch(); - }); - - afterAll(async () => { - app.stop(); - process.env.JEST_WORKER_ID = previousJestId; - process.env.NODE_ENV = previousNodeEnv; - const testDataPath = path.join(__dirname, "./data"); - await fs.rm(testDataPath, { recursive: true, force: true }); - }); - - beforeEach(async () => { - fetchMock = jest.fn(authFetch); - solidLdoDataset = createSolidLdoDataset({ fetch: fetchMock }); - // Create a new document called sample.ttl - await authFetch(ROOT_CONTAINER, { - method: "POST", - headers: { - link: '; rel="type"', - slug: TEST_CONTAINER_SLUG, - }, - }); - await authFetch(TEST_CONTAINER_ACL_URI, { - method: "PUT", - headers: { - "content-type": "text/turtle", - }, - body: TEST_CONTAINER_ACL, - }); - await Promise.all([ - authFetch(TEST_CONTAINER_URI, { - method: "POST", - headers: { "content-type": "text/turtle", slug: "sample.ttl" }, - body: SPIDER_MAN_TTL, - }), - authFetch(TEST_CONTAINER_URI, { - method: "POST", - headers: { "content-type": "text/plain", slug: "sample.txt" }, - body: "some text.", - }), - authFetch(TEST_CONTAINER_URI, { - method: "POST", - headers: { "content-type": "text/turtle", slug: "profile.ttl" }, - body: SAMPLE_PROFILE_TTL, - }), - ]); - }); - - afterEach(async () => { - await Promise.all([ - authFetch(SAMPLE_DATA_URI, { - method: "DELETE", - }), - authFetch(SAMPLE2_DATA_URI, { - method: "DELETE", - }), - authFetch(SAMPLE_BINARY_URI, { - method: "DELETE", - }), - authFetch(SAMPLE2_BINARY_URI, { - method: "DELETE", - }), - authFetch(SAMPLE_PROFILE_URI, { - method: "DELETE", - }), - authFetch(SAMPLE_CONTAINER_URI, { - method: "DELETE", - }), - ]); - await authFetch(TEST_CONTAINER_URI, { - method: "DELETE", - }); - }); - - /** - * 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 - */ - describe("read", () => { - it("Reads a data leaf", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); - const result = await testRequestLoads(() => resource.read(), resource, { - isLoading: true, - isReading: true, - isDoingInitialFetch: true, - }); - expect(result.type).toBe("dataReadSuccess"); - expect( - solidLdoDataset.match( - namedNode("http://example.org/#spiderman"), - namedNode("http://www.perceive.net/schemas/relationship/enemyOf"), - namedNode("http://example.org/#green-goblin"), - ).size, - ).toBe(1); - expect(resource.isBinary()).toBe(false); - expect(resource.isDataResource()).toBe(true); - expect(resource.isPresent()).toBe(true); - }); - - it("Auto reads a resource", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI, { - autoLoad: true, - }); - // Wait until the resource is auto-loaded - await new Promise((resolve) => { - const interval = setInterval(() => { - if (!resource.isReading()) { - clearInterval(interval); - resolve(); - } - }, 250); - }); - expect( - solidLdoDataset.match( - namedNode("http://example.org/#spiderman"), - namedNode("http://www.perceive.net/schemas/relationship/enemyOf"), - namedNode("http://example.org/#green-goblin"), - ).size, - ).toBe(1); - }); - - it("Reads a container", async () => { - const resource = solidLdoDataset.getResource(TEST_CONTAINER_URI); - const result = await testRequestLoads(() => resource.read(), resource, { - isLoading: true, - isReading: true, - isDoingInitialFetch: true, - }); - expect(result.type).toBe("containerReadSuccess"); - expect(resource.children().length).toBe(3); - }); - - it("Reads a binary leaf", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_BINARY_URI); - const result = await testRequestLoads(() => resource.read(), resource, { - isLoading: true, - isReading: true, - isDoingInitialFetch: true, - }); - expect(result.type).toBe("binaryReadSuccess"); - expect(resource.isBinary()).toBe(true); - expect(await resource.getBlob()?.text()).toBe("some text."); - }); - - it("Returns an absent result if the document doesn't exist", async () => { - const resource = solidLdoDataset.getResource(SAMPLE2_DATA_URI); - const result = await testRequestLoads(() => resource.read(), resource, { - isLoading: true, - isReading: true, - isDoingInitialFetch: true, - }); - expect(result.type).toBe("absentReadSuccess"); - if (result.type !== "absentReadSuccess") return; - expect(result.resource.isAbsent()).toBe(true); - }); - - it("Returns an ServerError when an 500 error is returned", async () => { - fetchMock.mockResolvedValueOnce(new Response("Error", { status: 500 })); - const resource = solidLdoDataset.getResource(SAMPLE2_DATA_URI); - const result = await testRequestLoads(() => resource.read(), resource, { - isLoading: true, - isReading: true, - isDoingInitialFetch: true, - }); - expect(result.isError).toBe(true); - expect(result.type).toBe("serverError"); - }); - - it("Returns an Unauthorized error if a 403 error is returned", async () => { - fetchMock.mockResolvedValueOnce(new Response("Error", { status: 403 })); - const resource = solidLdoDataset.getResource(SAMPLE2_DATA_URI); - const result = await testRequestLoads(() => resource.read(), resource, { - isLoading: true, - isReading: true, - isDoingInitialFetch: true, - }); - expect(result.isError).toBe(true); - expect(result.type).toBe("unauthorizedError"); - }); - - it("Returns an UnauthenticatedError on an 401 error is returned", async () => { - fetchMock.mockResolvedValueOnce(new Response("Error", { status: 401 })); - const resource = solidLdoDataset.getResource(SAMPLE2_DATA_URI); - const result = await testRequestLoads(() => resource.read(), resource, { - isLoading: true, - isReading: true, - isDoingInitialFetch: true, - }); - expect(result.isError).toBe(true); - expect(result.type).toBe("unauthenticatedError"); - }); - - it("Returns an UnexpectedHttpError on a strange number error is returned", async () => { - fetchMock.mockResolvedValueOnce(new Response("Error", { status: 3942 })); - const resource = solidLdoDataset.getResource(SAMPLE2_DATA_URI); - const result = await testRequestLoads(() => resource.read(), resource, { - isLoading: true, - isReading: true, - isDoingInitialFetch: true, - }); - expect(result.isError).toBe(true); - expect(result.type).toBe("unexpectedHttpError"); - }); - - it("Returns a NoncompliantPod error when no content type is returned", async () => { - fetchMock.mockResolvedValueOnce( - new Response(undefined, { status: 200, headers: {} }), - ); - const resource = solidLdoDataset.getResource(SAMPLE2_DATA_URI); - const result = await testRequestLoads(() => resource.read(), resource, { - isLoading: true, - isReading: true, - isDoingInitialFetch: true, - }); - expect(result.isError).toBe(true); - if (!result.isError) return; - expect(result.type).toBe("noncompliantPodError"); - expect(result.message).toMatch( - /\Response from .* is not compliant with the Solid Specification: Resource requests must return a content-type header\./, - ); - }); - - it("Returns a NoncompliantPod error if invalid turtle is provided", async () => { - fetchMock.mockResolvedValueOnce( - new Response("Error", { - status: 200, - headers: new Headers({ "content-type": "text/turtle" }), - }), - ); - const resource = solidLdoDataset.getResource(SAMPLE2_DATA_URI); - const result = await testRequestLoads(() => resource.read(), resource, { - isLoading: true, - isReading: true, - isDoingInitialFetch: true, - }); - expect(result.isError).toBe(true); - if (!result.isError) return; - expect(result.type).toBe("noncompliantPodError"); - expect(result.message).toMatch( - /\Response from .* is not compliant with the Solid Specification: Request returned noncompliant turtle: Unexpected "Error" on line 1\./, - ); - }); - - 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 () => { - fetchMock.mockRejectedValueOnce(new Error("Something happened.")); - const resource = solidLdoDataset.getResource(SAMPLE2_DATA_URI); - const result = await testRequestLoads(() => resource.read(), resource, { - isLoading: true, - isReading: true, - isDoingInitialFetch: true, - }); - expect(result.isError).toBe(true); - if (!result.isError) return; - expect(result.type).toBe("unexpectedResourceError"); - expect(result.message).toBe("Something happened."); - }); - - it("Does not return an error if there is no link header for a container request", async () => { - fetchMock.mockResolvedValueOnce( - new Response(TEST_CONTAINER_TTL, { - status: 200, - headers: new Headers({ "content-type": "text/turtle" }), - }), - ); - const resource = solidLdoDataset.getResource(TEST_CONTAINER_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.resource.isRootContainer()).toBe(false); - }); - - it("knows nothing about a leaf resource if it is not fetched", () => { - const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); - expect(resource.isBinary()).toBe(undefined); - expect(resource.isDataResource()).toBe(undefined); - expect(resource.isUnfetched()).toBe(true); - expect(resource.isPresent()).toBe(undefined); - }); - - it("batches the read request when a read request is currently happening", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); - const [result, result1] = await Promise.all([ - resource.read(), - resource.read(), - ]); - - expect(fetchMock).toHaveBeenCalledTimes(1); - expect(result.type).toBe("dataReadSuccess"); - expect(result1.type).toBe("dataReadSuccess"); - }); - - it("batches the read request when a read request is in queue", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); - const [, result, result1] = await Promise.all([ - resource.createAndOverwrite(), - resource.read(), - resource.read(), - ]); - - expect(fetchMock).toHaveBeenCalledTimes(3); - expect(result.type).toBe("dataReadSuccess"); - expect(result1.type).toBe("dataReadSuccess"); - }); - }); - - /** - * readIfUnfetched - */ - describe("readIfUnfetched", () => { - it("reads an unfetched container", async () => { - const resource = solidLdoDataset.getResource(TEST_CONTAINER_URI); - const result = await testRequestLoads( - () => resource.readIfUnfetched(), - resource, - { - isLoading: true, - isReading: true, - isDoingInitialFetch: true, - }, - ); - expect(result.type).toBe("containerReadSuccess"); - expect(resource.children().length).toBe(3); - }); - - it("reads an unfetched leaf", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); - const result = await testRequestLoads( - () => resource.readIfUnfetched(), - resource, - { - isLoading: true, - isReading: true, - isDoingInitialFetch: true, - }, - ); - expect(result.type).toBe("dataReadSuccess"); - expect( - solidLdoDataset.match( - namedNode("http://example.org/#spiderman"), - namedNode("http://www.perceive.net/schemas/relationship/enemyOf"), - namedNode("http://example.org/#green-goblin"), - ).size, - ).toBe(1); - }); - - it("returns a cached existing container", async () => { - const resource = solidLdoDataset.getResource(TEST_CONTAINER_URI); - await resource.read(); - fetchMock.mockClear(); - const result = await resource.readIfUnfetched(); - expect(fetchMock).not.toHaveBeenCalled(); - expect(result.type).toBe("containerReadSuccess"); - expect(resource.children().length).toBe(3); - }); - - it("returns a cached existing data leaf", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); - await resource.read(); - fetchMock.mockClear(); - const result = await resource.readIfUnfetched(); - expect(result.type).toBe("dataReadSuccess"); - expect( - solidLdoDataset.match( - namedNode("http://example.org/#spiderman"), - namedNode("http://www.perceive.net/schemas/relationship/enemyOf"), - namedNode("http://example.org/#green-goblin"), - ).size, - ).toBe(1); - }); - - it("returns a cached existing binary leaf", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_BINARY_URI); - await resource.read(); - fetchMock.mockClear(); - const result = await resource.readIfUnfetched(); - expect(result.type).toBe("binaryReadSuccess"); - }); - - it("returns a cached absent container", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_CONTAINER_URI); - await resource.read(); - fetchMock.mockClear(); - const result = await resource.readIfUnfetched(); - expect(fetchMock).not.toHaveBeenCalled(); - expect(result.type).toBe("absentReadSuccess"); - }); - - it("returns a cached absent leaf", async () => { - const resource = solidLdoDataset.getResource(SAMPLE2_DATA_URI); - await resource.read(); - fetchMock.mockClear(); - const result = await resource.readIfUnfetched(); - expect(fetchMock).not.toHaveBeenCalled(); - expect(result.type).toBe("absentReadSuccess"); - }); - }); - - /** - * Get Root Container - */ - describe("rootContainer", () => { - it("Finds the root container", async () => { - const resource = solidLdoDataset.getResource(SAMPLE2_BINARY_URI); - const result = await resource.getRootContainer(); - expect(result.type).toBe("container"); - if (result.type !== "container") return; - expect(result.uri).toBe(ROOT_CONTAINER); - expect(result.isRootContainer()).toBe(true); - }); - - 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( - new Response(TEST_CONTAINER_TTL, { - status: 200, - headers: new Headers({ "content-type": "text/turtle" }), - }), - ); - const resource = solidLdoDataset.getResource(TEST_CONTAINER_URI); - const result = await resource.getRootContainer(); - expect(result.isError).toBe(true); - if (!result.isError) return; - expect(result.type).toBe("noRootContainerError"); - expect(result.message).toMatch(/\.* has not root container\./); - }); - - it("An error to be returned if a common http error is encountered", async () => { - fetchMock.mockResolvedValueOnce( - new Response(TEST_CONTAINER_TTL, { - status: 500, - }), - ); - const resource = solidLdoDataset.getResource(TEST_CONTAINER_URI); - const result = await resource.getRootContainer(); - expect(result.isError).toBe(true); - expect(result.type).toBe("serverError"); - }); - - it("Returns an UnexpectedResourceError if an unknown error is triggered", async () => { - fetchMock.mockRejectedValueOnce(new Error("Something happened.")); - const resource = solidLdoDataset.getResource(TEST_CONTAINER_URI); - const result = await resource.getRootContainer(); - expect(result.isError).toBe(true); - if (!result.isError) return; - expect(result.type).toBe("unexpectedResourceError"); - expect(result.message).toBe("Something happened."); - }); - - it("returns a NonCompliantPodError when there is no root", async () => { - fetchMock.mockResolvedValueOnce( - new Response(TEST_CONTAINER_TTL, { - status: 200, - headers: new Headers({ - "content-type": "text/turtle", - link: '; rel="type"', - }), - }), - ); - const resource = solidLdoDataset.getResource(ROOT_CONTAINER); - const result = await resource.getRootContainer(); - expect(result.isError).toBe(true); - 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); - }); - }); - - /** - * =========================================================================== - * Create - * =========================================================================== - */ - describe("createAndOverwrite", () => { - it("creates a document that doesn't exist", async () => { - const resource = solidLdoDataset.getResource(SAMPLE2_DATA_URI); - const container = solidLdoDataset.getResource(TEST_CONTAINER_URI); - const result = await testRequestLoads( - () => resource.createAndOverwrite(), - resource, - { - isLoading: true, - isCreating: true, - }, - ); - - expect(result.type).toBe("createSuccess"); - const createSuccess = result as CreateSuccess; - expect(createSuccess.didOverwrite).toBe(false); - expect( - solidLdoDataset.has( - createQuad( - namedNode(TEST_CONTAINER_URI), - namedNode("http://www.w3.org/ns/ldp#contains"), - namedNode(SAMPLE2_DATA_URI), - namedNode(TEST_CONTAINER_URI), - ), - ), - ).toBe(true); - expect( - container.children().some((child) => child.uri === SAMPLE2_DATA_URI), - ).toBe(true); - }); - - it("creates a data resource that doesn't exist while overwriting", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); - const container = solidLdoDataset.getResource(TEST_CONTAINER_URI); - const result = await testRequestLoads( - () => resource.createAndOverwrite(), - resource, - { - isLoading: true, - isCreating: true, - }, - ); - expect(result.type).toBe("createSuccess"); - const createSuccess = result as CreateSuccess; - expect(createSuccess.didOverwrite).toBe(true); - expect( - solidLdoDataset.has( - createQuad( - namedNode(TEST_CONTAINER_URI), - namedNode("http://www.w3.org/ns/ldp#contains"), - namedNode(SAMPLE_DATA_URI), - namedNode(TEST_CONTAINER_URI), - ), - ), - ).toBe(true); - expect( - container.children().some((child) => child.uri === SAMPLE_DATA_URI), - ).toBe(true); - }); - - it("creates a container", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_CONTAINER_URI); - const container = solidLdoDataset.getResource(TEST_CONTAINER_URI); - const result = await testRequestLoads( - () => resource.createAndOverwrite(), - resource, - { - isLoading: true, - isCreating: true, - }, - ); - expect(result.type).toBe("createSuccess"); - const createSuccess = result as CreateSuccess; - expect(createSuccess.didOverwrite).toBe(false); - expect( - solidLdoDataset.has( - createQuad( - namedNode(TEST_CONTAINER_URI), - namedNode("http://www.w3.org/ns/ldp#contains"), - namedNode(SAMPLE_CONTAINER_URI), - namedNode(TEST_CONTAINER_URI), - ), - ), - ).toBe(true); - expect( - container - .children() - .some((child) => child.uri === SAMPLE_CONTAINER_URI), - ).toBe(true); - }); - - it("returns and error if creating a container", async () => { - const resource = solidLdoDataset.getResource(TEST_CONTAINER_URI); - fetchMock.mockResolvedValueOnce( - new Response(TEST_CONTAINER_TTL, { - status: 500, - }), - ); - const result = await resource.createAndOverwrite(); - expect(result.isError).toBe(true); - expect(result.type).toBe("serverError"); - }); - - it("returns a delete error if delete failed", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); - fetchMock.mockResolvedValueOnce( - new Response(TEST_CONTAINER_TTL, { - status: 500, - }), - ); - const result = await resource.createAndOverwrite(); - expect(result.isError).toBe(true); - expect(result.type).toBe("serverError"); - }); - - it("returns an error if the create fetch fails", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); - fetchMock.mockImplementationOnce(async (...args) => { - return authFetch(...args); - }); - fetchMock.mockResolvedValueOnce( - new Response(TEST_CONTAINER_TTL, { - status: 500, - }), - ); - const result = await resource.createAndOverwrite(); - expect(result.isError).toBe(true); - expect(result.type).toBe("serverError"); - }); - - it("returns an unexpected error if some unknown error is triggered", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); - fetchMock.mockImplementationOnce(async (...args) => { - return authFetch(...args); - }); - fetchMock.mockImplementationOnce(async () => { - throw new Error("Some Unknown"); - }); - const result = await resource.createAndOverwrite(); - expect(result.isError).toBe(true); - expect(result.type).toBe("unexpectedResourceError"); - }); - - it("batches the create request while waiting on another request", async () => { - const resource = solidLdoDataset.getResource(SAMPLE2_DATA_URI); - const [, result1, result2] = await Promise.all([ - resource.read(), - resource.createAndOverwrite(), - resource.createAndOverwrite(), - ]); - - expect(result1.type).toBe("createSuccess"); - expect(result2.type).toBe("createSuccess"); - // 1 for read, 1 for delete in createAndOverwrite, 1 for create - expect(fetchMock).toHaveBeenCalledTimes(3); - }); - - it("batches the create request while waiting on a similar request", async () => { - const resource = solidLdoDataset.getResource(SAMPLE2_DATA_URI); - const [result1, result2] = await Promise.all([ - resource.createAndOverwrite(), - resource.createAndOverwrite(), - ]); - - expect(result1.type).toBe("createSuccess"); - expect(result2.type).toBe("createSuccess"); - // 1 for delete in createAndOverwrite, 1 for create - expect(fetchMock).toHaveBeenCalledTimes(2); - }); - }); - - describe("createIfAbsent", () => { - it("creates a data resource that doesn't exist", async () => { - const resource = solidLdoDataset.getResource(SAMPLE2_DATA_URI); - const container = solidLdoDataset.getResource(TEST_CONTAINER_URI); - const result = await testRequestLoads( - () => resource.createIfAbsent(), - resource, - { - isLoading: true, - isCreating: true, - }, - ); - - expect(result.type).toBe("createSuccess"); - const createSuccess = result as CreateSuccess; - expect(createSuccess.didOverwrite).toBe(false); - expect( - solidLdoDataset.has( - createQuad( - namedNode(TEST_CONTAINER_URI), - namedNode("http://www.w3.org/ns/ldp#contains"), - namedNode(SAMPLE2_DATA_URI), - namedNode(TEST_CONTAINER_URI), - ), - ), - ).toBe(true); - expect( - container.children().some((child) => child.uri === SAMPLE2_DATA_URI), - ).toBe(true); - }); - - it("doesn't overwrite a resources that does exist", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); - const container = solidLdoDataset.getResource(TEST_CONTAINER_URI); - const result = await testRequestLoads( - () => resource.createIfAbsent(), - resource, - { - isLoading: true, - isCreating: true, - }, - ); - - expect(result.type).toBe("dataReadSuccess"); - expect( - solidLdoDataset.has( - createQuad( - namedNode(TEST_CONTAINER_URI), - namedNode("http://www.w3.org/ns/ldp#contains"), - namedNode(SAMPLE_DATA_URI), - namedNode(TEST_CONTAINER_URI), - ), - ), - ).toBe(true); - expect( - container.children().some((child) => child.uri === SAMPLE_DATA_URI), - ).toBe(true); - }); - - it("creates a container that doesn't exist", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_CONTAINER_URI); - const container = solidLdoDataset.getResource(TEST_CONTAINER_URI); - const result = await testRequestLoads( - () => resource.createIfAbsent(), - resource, - { - isLoading: true, - isCreating: true, - }, - ); - - expect(result.type).toBe("createSuccess"); - const createSuccess = result as CreateSuccess; - expect(createSuccess.didOverwrite).toBe(false); - expect( - solidLdoDataset.has( - createQuad( - namedNode(TEST_CONTAINER_URI), - namedNode("http://www.w3.org/ns/ldp#contains"), - namedNode(SAMPLE_CONTAINER_URI), - namedNode(TEST_CONTAINER_URI), - ), - ), - ).toBe(true); - expect( - container - .children() - .some((child) => child.uri === SAMPLE_CONTAINER_URI), - ).toBe(true); - }); - - it("returns an error if creating a container", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_CONTAINER_URI); - fetchMock.mockResolvedValueOnce( - new Response(SAMPLE_CONTAINER_URI, { - status: 500, - }), - ); - const result = await resource.createIfAbsent(); - expect(result.isError).toBe(true); - expect(result.type).toBe("serverError"); - }); - - it("returns an error if creating a leaf", async () => { - const resource = solidLdoDataset.getResource(SAMPLE2_DATA_URI); - fetchMock.mockResolvedValueOnce( - new Response(SAMPLE2_DATA_URI, { - status: 500, - }), - ); - const result = await resource.createIfAbsent(); - expect(result.isError).toBe(true); - expect(result.type).toBe("serverError"); - }); - }); - - /** - * Delete - */ - describe("deleteResource", () => { - it("returns an unexpected http error if an unexpected value is returned", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); - fetchMock.mockResolvedValueOnce( - new Response(TEST_CONTAINER_TTL, { - status: 214, - }), - ); - const result = await resource.delete(); - expect(result.isError).toBe(true); - expect(result.type).toBe("unexpectedHttpError"); - }); - - it("returns an unexpected resource error if an unknown error is triggered", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); - fetchMock.mockImplementationOnce(async () => { - throw new Error("Some unknwon"); - }); - const result = await resource.delete(); - expect(result.isError).toBe(true); - expect(result.type).toBe("unexpectedResourceError"); - }); - - it("deletes a container", async () => { - const resource = solidLdoDataset.getResource(TEST_CONTAINER_URI); - const result = await resource.delete(); - expect(result.type === "deleteSuccess"); - }); - - it("returns an error on container read when deleting a container", async () => { - const resource = solidLdoDataset.getResource(TEST_CONTAINER_URI); - fetchMock.mockImplementation(async (input, init) => { - if ( - (init?.method === "get" || !init?.method) && - input === TEST_CONTAINER_URI - ) { - return new Response(SAMPLE_DATA_URI, { - status: 500, - }); - } - return authFetch(input, init); - }); - const result = await resource.delete(); - expect(result.isError).toBe(true); - expect(result.type).toBe("aggregateError"); - const aggregateError = result as AggregateError< - | ServerHttpError - | UnexpectedHttpError - | UnauthenticatedHttpError - | UnexpectedResourceError - | NoncompliantPodError - >; - expect(aggregateError.errors[0].type).toBe("serverError"); - }); - - it("returns an error on child delete when deleting a container", async () => { - const resource = solidLdoDataset.getResource(TEST_CONTAINER_URI); - fetchMock.mockImplementation(async (input, init) => { - if (init?.method === "delete" && input === SAMPLE_DATA_URI) { - return new Response(SAMPLE_DATA_URI, { - status: 500, - }); - } - return authFetch(input, init); - }); - const result = await resource.delete(); - expect(result.isError).toBe(true); - expect(result.type).toBe("aggregateError"); - const aggregateError = result as AggregateError< - | ServerHttpError - | UnexpectedHttpError - | UnauthenticatedHttpError - | UnexpectedResourceError - | NoncompliantPodError - >; - expect(aggregateError.errors[0].type).toBe("serverError"); - }); - - it("returns an error on container delete when deleting a container", async () => { - const resource = solidLdoDataset.getResource(TEST_CONTAINER_URI); - fetchMock.mockImplementation(async (input, init) => { - if (init?.method === "delete" && input === TEST_CONTAINER_URI) { - return new Response(SAMPLE_DATA_URI, { - status: 500, - }); - } - return authFetch(input, init); - }); - const result = await resource.delete(); - expect(result.isError).toBe(true); - expect(result.type).toBe("serverError"); - }); - }); - - /** - * Update - */ - describe("updateDataResource", () => { - const normanQuad = createQuad( - namedNode("http://example.org/#green-goblin"), - namedNode("http://xmlns.com/foaf/0.1/name"), - literal("Norman Osborn"), - namedNode(SAMPLE_DATA_URI), - ); - - const goblinQuad = createQuad( - namedNode("http://example.org/#green-goblin"), - namedNode("http://xmlns.com/foaf/0.1/name"), - literal("Green Goblin"), - namedNode(SAMPLE_DATA_URI), - ); - - it("applies changes to a Pod", async () => { - const result = await testRequestLoads( - () => { - const transaction = solidLdoDataset.startTransaction(); - transaction.add(normanQuad); - transaction.delete(goblinQuad); - return transaction.commitToPod(); - }, - solidLdoDataset.getResource(SAMPLE_DATA_URI), - { - isLoading: true, - isUpdating: true, - }, - ); - expect(result.type).toBe("aggregateSuccess"); - const aggregateSuccess = result as AggregateSuccess< - ResourceSuccess - >; - expect(aggregateSuccess.results.length).toBe(1); - expect(aggregateSuccess.results[0].type === "updateSuccess").toBe(true); - expect(solidLdoDataset.has(normanQuad)).toBe(true); - expect(solidLdoDataset.has(goblinQuad)).toBe(false); - }); - - it("applies only remove changes to the Pod", async () => { - const result = await testRequestLoads( - () => { - const transaction = solidLdoDataset.startTransaction(); - transaction.delete(goblinQuad); - return transaction.commitToPod(); - }, - solidLdoDataset.getResource(SAMPLE_DATA_URI), - { - isLoading: true, - isUpdating: true, - }, - ); - expect(result.type).toBe("aggregateSuccess"); - const aggregateSuccess = result as AggregateSuccess< - ResourceSuccess - >; - expect(aggregateSuccess.results.length).toBe(1); - expect(aggregateSuccess.results[0].type === "updateSuccess").toBe(true); - expect(solidLdoDataset.has(goblinQuad)).toBe(false); - }); - - it("handles an HTTP error", async () => { - fetchMock.mockResolvedValueOnce(new Response("Error", { status: 500 })); - - const transaction = solidLdoDataset.startTransaction(); - transaction.add(normanQuad); - transaction.delete(goblinQuad); - const result = await transaction.commitToPod(); - - expect(result.isError).toBe(true); - expect(result.type).toBe("aggregateError"); - const aggregateError = result as AggregateError< - UpdateResultError | InvalidUriError - >; - expect(aggregateError.errors.length).toBe(1); - expect(aggregateError.errors[0].type).toBe("serverError"); - }); - - it("handles an unknown request", async () => { - fetchMock.mockImplementationOnce(() => { - throw new Error("Some Error"); - }); - const transaction = solidLdoDataset.startTransaction(); - transaction.add(normanQuad); - transaction.delete(goblinQuad); - const result = await transaction.commitToPod(); - expect(result.isError).toBe(true); - expect(result.type).toBe("aggregateError"); - const aggregateError = result as AggregateError< - UpdateResultError | InvalidUriError - >; - expect(aggregateError.errors.length).toBe(1); - expect(aggregateError.errors[0].type).toBe("unexpectedResourceError"); - }); - - it("ignores update when trying to update a container", async () => { - const badContainerQuad = createQuad( - namedNode("http://example.org/#green-goblin"), - namedNode("http://xmlns.com/foaf/0.1/name"), - literal("Norman Osborn"), - namedNode(SAMPLE_CONTAINER_URI), - ); - const transaction = solidLdoDataset.startTransaction(); - transaction.add(badContainerQuad); - const result = await transaction.commitToPod(); - expect(result.isError).toBe(false); - expect(result.type).toBe("aggregateSuccess"); - const aggregateSuccess = result as AggregateSuccess< - UpdateSuccess | IgnoredInvalidUpdateSuccess - >; - expect(aggregateSuccess.results.length).toBe(1); - expect(aggregateSuccess.results[0].type).toBe( - "ignoredInvalidUpdateSuccess", - ); - }); - - it("writes to the default graph without fetching", async () => { - const defaultGraphQuad = createQuad( - namedNode("http://example.org/#green-goblin"), - namedNode("http://xmlns.com/foaf/0.1/name"), - literal("Norman Osborn"), - defaultGraph(), - ); - const transaction = solidLdoDataset.startTransaction(); - transaction.add(defaultGraphQuad); - const result = await transaction.commitToPod(); - expect(result.type).toBe("aggregateSuccess"); - const aggregateSuccess = result as AggregateSuccess< - ResourceSuccess - >; - expect(aggregateSuccess.results.length).toBe(1); - expect(aggregateSuccess.results[0].type).toBe( - "updateDefaultGraphSuccess", - ); - expect( - solidLdoDataset.has( - createQuad( - namedNode("http://example.org/#green-goblin"), - namedNode("http://xmlns.com/foaf/0.1/name"), - literal("Norman Osborn"), - defaultGraph(), - ), - ), - ).toBe(true); - }); - - it("batches data update changes", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); - - const transaction1 = solidLdoDataset.startTransaction(); - transaction1.delete(goblinQuad); - const transaction2 = solidLdoDataset.startTransaction(); - transaction2.add(normanQuad); - - const [, updateResult1, updateResult2] = await Promise.all([ - resource.read(), - transaction1.commitToPod(), - transaction2.commitToPod(), - ]); - expect(updateResult1.type).toBe("aggregateSuccess"); - expect(updateResult2.type).toBe("aggregateSuccess"); - expect(fetchMock).toHaveBeenCalledTimes(2); - expect( - solidLdoDataset.has( - createQuad( - namedNode("http://example.org/#green-goblin"), - namedNode("http://xmlns.com/foaf/0.1/name"), - literal("Norman Osborn"), - namedNode(SAMPLE_DATA_URI), - ), - ), - ).toBe(true); - expect( - solidLdoDataset.has( - createQuad( - namedNode("http://example.org/#green-goblin"), - namedNode("http://xmlns.com/foaf/0.1/name"), - literal("Green Goblin"), - namedNode(SAMPLE_DATA_URI), - ), - ), - ).toBe(false); - }); - }); - - it("allows a transaction on a transaction", () => { - const transaction = solidLdoDataset.startTransaction(); - const transaction2 = transaction.startTransaction(); - expect(transaction2).toBeInstanceOf(SolidLdoTransactionDataset); - }); - - /** - * =========================================================================== - * Upload - * =========================================================================== - */ - describe("uploadAndOverwrite", () => { - it("uploads a document that doesn't exist", async () => { - const resource = solidLdoDataset.getResource(SAMPLE2_BINARY_URI); - const container = solidLdoDataset.getResource(TEST_CONTAINER_URI); - const result = await testRequestLoads( - () => - resource.uploadAndOverwrite( - Buffer.from("some text.") as unknown as Blob, - "text/plain", - ), - resource, - { - isLoading: true, - isUploading: true, - }, - ); - - expect(result.type).toBe("createSuccess"); - const createSuccess = result as CreateSuccess; - expect(createSuccess.didOverwrite).toBe(false); - expect( - solidLdoDataset.has( - createQuad( - namedNode(TEST_CONTAINER_URI), - namedNode("http://www.w3.org/ns/ldp#contains"), - namedNode(SAMPLE2_BINARY_URI), - namedNode(TEST_CONTAINER_URI), - ), - ), - ).toBe(true); - expect( - container.children().some((child) => child.uri === SAMPLE2_BINARY_URI), - ).toBe(true); - expect(resource.getMimeType()).toBe("text/plain"); - expect(resource.isBinary()).toBe(true); - expect(resource.isDataResource()).toBe(false); - }); - - it("creates a binary resource that doesn't exist while overwriting", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_BINARY_URI); - const container = solidLdoDataset.getResource(TEST_CONTAINER_URI); - const result = await testRequestLoads( - () => - resource.uploadAndOverwrite( - Buffer.from("some text.") as unknown as Blob, - "text/plain", - ), - resource, - { - isLoading: true, - isUploading: true, - }, - ); - expect(result.type).toBe("createSuccess"); - const createSuccess = result as CreateSuccess; - expect(createSuccess.didOverwrite).toBe(true); - expect( - solidLdoDataset.has( - createQuad( - namedNode(TEST_CONTAINER_URI), - namedNode("http://www.w3.org/ns/ldp#contains"), - namedNode(SAMPLE_BINARY_URI), - namedNode(TEST_CONTAINER_URI), - ), - ), - ).toBe(true); - expect( - container.children().some((child) => child.uri === SAMPLE_BINARY_URI), - ).toBe(true); - }); - - it("returns a delete error if delete failed", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_BINARY_URI); - fetchMock.mockResolvedValueOnce( - new Response(TEST_CONTAINER_TTL, { - status: 500, - }), - ); - const result = await resource.uploadAndOverwrite( - Buffer.from("some text.") as unknown as Blob, - "text/plain", - ); - expect(result.isError).toBe(true); - expect(result.type).toBe("serverError"); - }); - - it("returns an error if the create fetch fails", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_BINARY_URI); - fetchMock.mockImplementationOnce(async (...args) => { - return authFetch(...args); - }); - fetchMock.mockResolvedValueOnce( - new Response(TEST_CONTAINER_TTL, { - status: 500, - }), - ); - const result = await resource.uploadAndOverwrite( - Buffer.from("some text.") as unknown as Blob, - "text/plain", - ); - expect(result.isError).toBe(true); - expect(result.type).toBe("serverError"); - }); - - it("returns an unexpected error if some unknown error is triggered", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_BINARY_URI); - fetchMock.mockImplementationOnce(async (...args) => { - return authFetch(...args); - }); - fetchMock.mockImplementationOnce(async () => { - throw new Error("Some Unknown"); - }); - const result = await resource.uploadAndOverwrite( - Buffer.from("some text.") as unknown as Blob, - "text/plain", - ); - expect(result.isError).toBe(true); - expect(result.type).toBe("unexpectedResourceError"); - }); - - it("batches the upload request while waiting on another request", async () => { - const resource = solidLdoDataset.getResource(SAMPLE2_DATA_URI); - const [, result1, result2] = await Promise.all([ - resource.read(), - resource.uploadAndOverwrite( - Buffer.from("some text.") as unknown as Blob, - "text/plain", - ), - resource.uploadAndOverwrite( - Buffer.from("some text 2.") as unknown as Blob, - "text/plain", - ), - ]); - - expect(result1.type).toBe("createSuccess"); - expect(result2.type).toBe("createSuccess"); - // 1 for read, 1 for delete in createAndOverwrite, 1 for create - expect(fetchMock).toHaveBeenCalledTimes(3); - expect(resource.getBlob()?.toString()).toBe("some text 2."); - }); - - it("batches the upload request while waiting on a similar request", async () => { - const resource = solidLdoDataset.getResource(SAMPLE2_DATA_URI); - const [result1, result2] = await Promise.all([ - resource.uploadAndOverwrite( - Buffer.from("some text.") as unknown as Blob, - "text/plain", - ), - resource.uploadAndOverwrite( - Buffer.from("some text 2.") as unknown as Blob, - "text/plain", - ), - ]); - - expect(result1.type).toBe("createSuccess"); - expect(result2.type).toBe("createSuccess"); - // 1 for delete in createAndOverwrite, 1 for create - expect(fetchMock).toHaveBeenCalledTimes(2); - expect(resource.getBlob()?.toString()).toBe("some text 2."); - }); - }); - - describe("uploadIfAbsent", () => { - it("creates a binary resource that doesn't exist", async () => { - const resource = solidLdoDataset.getResource(SAMPLE2_BINARY_URI); - const container = solidLdoDataset.getResource(TEST_CONTAINER_URI); - const result = await testRequestLoads( - () => - resource.uploadIfAbsent( - Buffer.from("some text.") as unknown as Blob, - "text/plain", - ), - resource, - { - isLoading: true, - isUploading: true, - }, - ); - - expect(result.type).toBe("createSuccess"); - const createSuccess = result as CreateSuccess; - expect(createSuccess.didOverwrite).toBe(false); - expect( - solidLdoDataset.has( - createQuad( - namedNode(TEST_CONTAINER_URI), - namedNode("http://www.w3.org/ns/ldp#contains"), - namedNode(SAMPLE2_BINARY_URI), - namedNode(TEST_CONTAINER_URI), - ), - ), - ).toBe(true); - expect( - container.children().some((child) => child.uri === SAMPLE2_BINARY_URI), - ).toBe(true); - }); - - it("doesn't overwrite a binary resource that does exist", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_BINARY_URI); - const container = solidLdoDataset.getResource(TEST_CONTAINER_URI); - const result = await testRequestLoads( - () => - resource.uploadIfAbsent( - Buffer.from("some text.") as unknown as Blob, - "text/plain", - ), - resource, - { - isLoading: true, - isUploading: true, - }, - ); - - expect(result.type).toBe("binaryReadSuccess"); - expect( - solidLdoDataset.has( - createQuad( - namedNode(TEST_CONTAINER_URI), - namedNode("http://www.w3.org/ns/ldp#contains"), - namedNode(SAMPLE_BINARY_URI), - namedNode(TEST_CONTAINER_URI), - ), - ), - ).toBe(true); - expect( - container.children().some((child) => child.uri === SAMPLE_BINARY_URI), - ).toBe(true); - }); - - it("returns an error if an error is encountered", async () => { - const resource = solidLdoDataset.getResource(SAMPLE2_BINARY_URI); - fetchMock.mockResolvedValueOnce( - new Response(SAMPLE2_BINARY_URI, { - status: 500, - }), - ); - const result = await resource.uploadIfAbsent( - Buffer.from("some text.") as unknown as Blob, - "text/plain", - ); - expect(result.isError).toBe(true); - expect(result.type).toBe("serverError"); - }); - }); - - /** - * =========================================================================== - * Methods - * =========================================================================== - */ - describe("methods", () => { - it("creates a data object for a specific subject", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); - const post = solidLdoDataset.createData( - PostShShapeType, - "https://example.com/subject", - resource, - ); - post.type = { "@id": "CreativeWork" }; - expect(post.type["@id"]).toBe("CreativeWork"); - const result = await commitData(post); - expect(result.type).toBe("aggregateSuccess"); - expect( - solidLdoDataset.has( - createQuad( - namedNode("https://example.com/subject"), - namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), - namedNode("http://schema.org/CreativeWork"), - namedNode(SAMPLE_DATA_URI), - ), - ), - ).toBe(true); - }); - - it("handles an error when committing data", async () => { - fetchMock.mockResolvedValueOnce( - new Response(SAMPLE_DATA_URI, { - status: 500, - }), - ); - const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); - const post = solidLdoDataset.createData( - PostShShapeType, - "https://example.com/subject", - resource, - ); - post.type = { "@id": "CreativeWork" }; - expect(post.type["@id"]).toBe("CreativeWork"); - const result = await commitData(post); - expect(result.isError).toBe(true); - }); - - it("uses changeData to start a transaction", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); - solidLdoDataset.add( - createQuad( - namedNode("https://example.com/subject"), - namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), - namedNode("http://schema.org/CreativeWork"), - namedNode(SAMPLE_DATA_URI), - ), - ); - const post = solidLdoDataset - .usingType(PostShShapeType) - .fromSubject("https://example.com/subject"); - const cPost = changeData(post, resource); - cPost.type = { "@id": "SocialMediaPosting" }; - expect(cPost.type["@id"]).toBe("SocialMediaPosting"); - const result = await commitData(cPost); - expect(result.isError).toBe(false); - expect( - solidLdoDataset.has( - createQuad( - namedNode("https://example.com/subject"), - namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), - namedNode("http://schema.org/SocialMediaPosting"), - namedNode(SAMPLE_DATA_URI), - ), - ), - ).toBe(true); - }); - }); - - /** - * =========================================================================== - * Container-Specific Methods - * =========================================================================== - */ - describe("container specific", () => { - it("returns the child with the child method", () => { - const container = solidLdoDataset.getResource(TEST_CONTAINER_URI); - const child = container.child(SAMPLE2_DATA_SLUG); - expect(child.uri).toBe(SAMPLE2_DATA_URI); - }); - - it("runs createAndOverwrite for a child via the createChildAndOverwrite method", async () => { - const resource = solidLdoDataset.getResource(TEST_CONTAINER_URI); - const result = await resource.createChildAndOverwrite(SAMPLE2_DATA_SLUG); - - expect(result.type).toBe("createSuccess"); - const createSuccess = result as ResourceResult; - expect(createSuccess.resource.uri).toBe(SAMPLE2_DATA_URI); - expect(createSuccess.didOverwrite).toBe(false); - expect( - solidLdoDataset.has( - createQuad( - namedNode(TEST_CONTAINER_URI), - namedNode("http://www.w3.org/ns/ldp#contains"), - namedNode(SAMPLE2_DATA_URI), - namedNode(TEST_CONTAINER_URI), - ), - ), - ).toBe(true); - expect( - resource.children().some((child) => child.uri === SAMPLE2_DATA_URI), - ).toBe(true); - }); - - it("runs createIfAbsent for a child via the createChildIfAbsent method", async () => { - const resource = solidLdoDataset.getResource(TEST_CONTAINER_URI); - const result = await resource.createChildIfAbsent(SAMPLE2_DATA_SLUG); - - expect(result.type).toBe("createSuccess"); - const createSuccess = result as ResourceResult; - expect(createSuccess.resource.uri).toBe(SAMPLE2_DATA_URI); - expect(createSuccess.didOverwrite).toBe(false); - expect( - solidLdoDataset.has( - createQuad( - namedNode(TEST_CONTAINER_URI), - namedNode("http://www.w3.org/ns/ldp#contains"), - namedNode(SAMPLE2_DATA_URI), - namedNode(TEST_CONTAINER_URI), - ), - ), - ).toBe(true); - expect( - resource.children().some((child) => child.uri === SAMPLE2_DATA_URI), - ).toBe(true); - }); - - it("runs uploadAndOverwrite for a child via the uploadChildAndOverwrite method", async () => { - const resource = solidLdoDataset.getResource(TEST_CONTAINER_URI); - const result = await resource.uploadChildAndOverwrite( - SAMPLE2_BINARY_SLUG, - Buffer.from("some text.") as unknown as Blob, - "text/plain", - ); - - expect(result.type).toBe("createSuccess"); - const createSuccess = result as ResourceResult; - expect(createSuccess.resource.uri).toBe(SAMPLE2_BINARY_URI); - expect(createSuccess.didOverwrite).toBe(false); - expect( - solidLdoDataset.has( - createQuad( - namedNode(TEST_CONTAINER_URI), - namedNode("http://www.w3.org/ns/ldp#contains"), - namedNode(SAMPLE2_BINARY_URI), - namedNode(TEST_CONTAINER_URI), - ), - ), - ).toBe(true); - expect( - resource.children().some((child) => child.uri === SAMPLE2_BINARY_URI), - ).toBe(true); - }); - - it("runs uploadIfAbsent for a child via the uploadChildIfAbsent method", async () => { - const resource = solidLdoDataset.getResource(TEST_CONTAINER_URI); - const result = await resource.uploadChildIfAbsent( - SAMPLE2_BINARY_SLUG, - Buffer.from("some text.") as unknown as Blob, - "text/plain", - ); - - expect(result.type).toBe("createSuccess"); - const createSuccess = result as ResourceResult; - expect(createSuccess.resource.uri).toBe(SAMPLE2_BINARY_URI); - expect(createSuccess.didOverwrite).toBe(false); - expect( - solidLdoDataset.has( - createQuad( - namedNode(TEST_CONTAINER_URI), - namedNode("http://www.w3.org/ns/ldp#contains"), - namedNode(SAMPLE2_BINARY_URI), - namedNode(TEST_CONTAINER_URI), - ), - ), - ).toBe(true); - expect( - resource.children().some((child) => child.uri === SAMPLE2_BINARY_URI), - ).toBe(true); - }); - }); - - /** - * =========================================================================== - * ACCESS CONTROL - * =========================================================================== - */ - describe("getWacRule", () => { - it("Fetches a wac rules for a container that has a corresponding acl", async () => { - const container = solidLdoDataset.getResource(TEST_CONTAINER_URI); - const wacResult = await container.getWac(); - expect(wacResult.isError).toBe(false); - const wacSuccess = wacResult as GetWacRuleSuccess; - expect(wacSuccess.wacRule.public).toEqual({ - read: true, - write: true, - append: true, - control: true, - }); - expect(wacSuccess.wacRule.authenticated).toEqual({ - read: true, - write: true, - append: true, - control: true, - }); - expect(wacSuccess.wacRule.agent[WEB_ID]).toEqual({ - read: true, - write: true, - append: true, - control: true, - }); - }); - - it("Gets wac rules of a parent resource for a resource that does not have a corresponding acl", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); - const wacResult = await resource.getWac(); - expect(wacResult.isError).toBe(false); - const wacSuccess = wacResult as GetWacRuleSuccess; - expect(wacSuccess.wacRule.public).toEqual({ - read: true, - write: true, - append: true, - control: true, - }); - expect(wacSuccess.wacRule.authenticated).toEqual({ - read: true, - write: true, - append: true, - control: true, - }); - expect(wacSuccess.wacRule.agent[WEB_ID]).toEqual({ - read: true, - write: true, - append: true, - control: true, - }); - }); - - it("uses cached values for a retrieved resource", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); - await resource.getWac(); - const wacResult = await resource.getWac(); - expect(wacResult.isError).toBe(false); - const wacSuccess = wacResult as GetWacRuleSuccess; - expect(wacSuccess.wacRule.public).toEqual({ - read: true, - write: true, - append: true, - control: true, - }); - expect(wacSuccess.wacRule.authenticated).toEqual({ - read: true, - write: true, - append: true, - control: true, - }); - expect(wacSuccess.wacRule.agent[WEB_ID]).toEqual({ - read: true, - write: true, - append: true, - control: true, - }); - }); - - it("returns an error when an error is encountered fetching the aclUri", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); - fetchMock.mockResolvedValueOnce(new Response("Error", { status: 500 })); - const wacResult = await resource.getWac(); - expect(wacResult.isError).toBe(true); - expect(wacResult.type).toBe("serverError"); - }); - - it("returns an error when a document is not found", async () => { - const resource = solidLdoDataset.getResource(SAMPLE2_DATA_URI); - const wacResult = await resource.getWac(); - expect(wacResult.isError).toBe(true); - expect(wacResult.type).toBe("notFoundError"); - }); - - it("returns a non-compliant error if a response is returned without a link header", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); - fetchMock.mockResolvedValueOnce( - new Response("Error", { - status: 200, - }), - ); - const wacResult = await resource.getWac(); - expect(wacResult.isError).toBe(true); - expect(wacResult.type).toBe("noncompliantPodError"); - expect((wacResult as NoncompliantPodError).message).toBe( - `Response from ${SAMPLE_DATA_URI} is not compliant with the Solid Specification: No link header present in request.`, - ); - }); - - it("returns a non-compliant error if a response is returned without an ACL link", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); - fetchMock.mockResolvedValueOnce( - new Response("Error", { - status: 200, - headers: { link: `; rel="describedBy"` }, - }), - ); - const wacResult = await resource.getWac(); - expect(wacResult.isError).toBe(true); - expect(wacResult.type).toBe("noncompliantPodError"); - expect((wacResult as NoncompliantPodError).message).toBe( - `Response from ${SAMPLE_DATA_URI} is not compliant with the Solid Specification: There must be one link with a rel="acl"`, - ); - }); - - it("Returns an UnexpectedResourceError if an unknown error is triggered while getting the wac URI", async () => { - fetchMock.mockRejectedValueOnce(new Error("Something happened.")); - const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); - const result = await resource.getWac(); - expect(result.isError).toBe(true); - if (!result.isError) return; - expect(result.type).toBe("unexpectedResourceError"); - expect(result.message).toBe("Something happened."); - }); - - it("Returns an error if the request to get the ACL fails", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); - fetchMock.mockResolvedValueOnce( - new Response("", { - status: 200, - headers: { link: `; rel="acl"` }, - }), - ); - fetchMock.mockResolvedValueOnce(new Response("Error", { status: 500 })); - const wacResult = await resource.getWac(); - expect(wacResult.isError).toBe(true); - expect(wacResult.type).toBe("serverError"); - }); - - it("Returns a non-compliant error if the root uri has no ACL", () => {}); - - it("Returns an error if the request to the ACL resource returns invalid turtle", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); - fetchMock.mockResolvedValueOnce( - new Response("", { - status: 200, - headers: { link: `; rel="acl"` }, - }), - ); - fetchMock.mockResolvedValueOnce( - new Response("BAD TURTLE", { status: 200 }), - ); - const wacResult = await resource.getWac(); - expect(wacResult.isError).toBe(true); - expect(wacResult.type).toBe("noncompliantPodError"); - expect((wacResult as NoncompliantPodError).message).toBe( - `Response from card.acl is not compliant with the Solid Specification: Request returned noncompliant turtle: Unexpected "BAD" on line 1.\nBAD TURTLE`, - ); - }); - - it("Returns an error if there was a problem getting the parent resource", async () => { - const resource = solidLdoDataset.getResource(TEST_CONTAINER_URI); - fetchMock.mockResolvedValueOnce( - new Response("", { - status: 200, - headers: { link: `; rel="acl"` }, - }), - ); - fetchMock.mockResolvedValueOnce(new Response("", { status: 404 })); - fetchMock.mockResolvedValueOnce(new Response("", { status: 500 })); - const wacResult = await resource.getWac(); - expect(wacResult.isError).toBe(true); - expect(wacResult.type).toBe("serverError"); - }); - - it("returns a NonCompliantPodError when this is the root resource and it doesn't have an ACL", async () => { - const resource = solidLdoDataset.getResource(ROOT_CONTAINER); - fetchMock.mockResolvedValueOnce( - new Response("", { - status: 200, - headers: { link: `; rel="acl"` }, - }), - ); - fetchMock.mockResolvedValueOnce(new Response("", { status: 404 })); - const wacResult = await resource.getWac(); - expect(wacResult.isError).toBe(true); - expect(wacResult.type).toBe("noncompliantPodError"); - expect((wacResult as NoncompliantPodError).message).toBe( - `Response from ${ROOT_CONTAINER} is not compliant with the Solid Specification: Resource "${ROOT_CONTAINER}" has no Effective ACL resource`, - ); - }); - }); - - describe("setWacRule", () => { - const newRules: WacRule = { - public: { read: true, write: false, append: false, control: false }, - authenticated: { - read: true, - write: false, - append: true, - control: false, - }, - agent: { - [WEB_ID]: { read: true, write: true, append: true, control: true }, - }, - }; - - it("sets wac rules for a resource that didn't have one before", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); - const result = await resource.setWac(newRules); - expect(result.isError).toBe(false); - expect(result.type).toBe("setWacRuleSuccess"); - const readResult = await resource.getWac({ ignoreCache: true }); - expect(readResult.isError).toBe(false); - expect(readResult.type).toBe("getWacRuleSuccess"); - const rules = (readResult as GetWacRuleSuccess).wacRule; - expect(rules).toEqual(newRules); - }); - - it("overwrites an existing access control rule", async () => { - const resource = solidLdoDataset.getResource(TEST_CONTAINER_URI); - const result = await resource.setWac(newRules); - expect(result.isError).toBe(false); - expect(result.type).toBe("setWacRuleSuccess"); - const readResult = await resource.getWac({ ignoreCache: true }); - expect(readResult.isError).toBe(false); - expect(readResult.type).toBe("getWacRuleSuccess"); - const rules = (readResult as GetWacRuleSuccess).wacRule; - expect(rules).toEqual(newRules); - }); - - it("Does not write a rule when access is not granted to an agent", async () => { - const moreRules = { - ...newRules, - public: { read: false, write: false, append: false, control: false }, - }; - const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); - const result = await resource.setWac(moreRules); - expect(result.isError).toBe(false); - expect(result.type).toBe("setWacRuleSuccess"); - const readResult = await resource.getWac({ ignoreCache: true }); - expect(readResult.isError).toBe(false); - expect(readResult.type).toBe("getWacRuleSuccess"); - const rules = (readResult as GetWacRuleSuccess).wacRule; - expect(rules).toEqual(moreRules); - }); - - it("returns an error when an error is encountered fetching the aclUri", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); - fetchMock.mockResolvedValueOnce(new Response("Error", { status: 500 })); - const wacResult = await resource.setWac(newRules); - expect(wacResult.isError).toBe(true); - expect(wacResult.type).toBe("serverError"); - }); - - it("Returns an error when the request to write the access rules throws an error", async () => { - const resource = solidLdoDataset.getResource(TEST_CONTAINER_URI); - fetchMock.mockResolvedValueOnce( - new Response("", { - status: 200, - headers: { link: `; rel="acl"` }, - }), - ); - fetchMock.mockResolvedValueOnce(new Response("", { status: 500 })); - const wacResult = await resource.setWac(newRules); - expect(wacResult.isError).toBe(true); - expect(wacResult.type).toBe("serverError"); - }); - }); - - /** - * =========================================================================== - * NOTIFICATION SUBSCRIPTIONS - * =========================================================================== - */ - describe("Notification Subscriptions", () => { - const spidermanNode = namedNode("http://example.org/#spiderman"); - const foafNameNode = namedNode("http://xmlns.com/foaf/0.1/name"); - - it("handles notification when a resource is updated", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); - await resource.read(); - - const spidermanCallback = jest.fn(); - solidLdoDataset.addListener( - [spidermanNode, null, null, null], - spidermanCallback, - ); - - const subscriptionId = await resource.subscribeToNotifications(); - - expect(resource.isSubscribedToNotifications()).toBe(true); - - await authFetch(SAMPLE_DATA_URI, { - method: "PATCH", - body: 'INSERT DATA { "Peter Parker" . }', - headers: { - "Content-Type": "application/sparql-update", - }, - }); - await wait(1000); - - expect( - solidLdoDataset.match( - spidermanNode, - foafNameNode, - literal("Peter Parker"), - ).size, - ).toBe(1); - expect(spidermanCallback).toHaveBeenCalledTimes(1); - - // Notification is not propogated after unsubscribe - spidermanCallback.mockClear(); - await resource.unsubscribeFromNotifications(subscriptionId); - expect(resource.isSubscribedToNotifications()).toBe(false); - await authFetch(SAMPLE_DATA_URI, { - method: "PATCH", - body: 'INSERT DATA { "Miles Morales" . }', - headers: { - "Content-Type": "application/sparql-update", - }, - }); - await wait(50); - - expect(spidermanCallback).not.toHaveBeenCalled(); - expect( - solidLdoDataset.match( - spidermanNode, - foafNameNode, - literal("Miles Morales"), - ).size, - ).toBe(0); - }); - - it("handles notification when subscribed to a child that is deleted", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); - const testContainer = solidLdoDataset.getResource(TEST_CONTAINER_URI); - await resource.read(); - - const spidermanCallback = jest.fn(); - solidLdoDataset.addListener( - [spidermanNode, null, null, null], - spidermanCallback, - ); - - const containerCallback = jest.fn(); - solidLdoDataset.addListener( - [namedNode(TEST_CONTAINER_URI), null, null, null], - containerCallback, - ); - - await resource.subscribeToNotifications(); - - await authFetch(SAMPLE_DATA_URI, { - method: "DELETE", - }); - await wait(1000); - - expect(solidLdoDataset.match(spidermanNode, null, null).size).toBe(0); - expect( - testContainer.children().some((child) => child.uri === SAMPLE_DATA_URI), - ).toBe(false); - expect(spidermanCallback).toHaveBeenCalledTimes(1); - expect(containerCallback).toHaveBeenCalledTimes(1); - - await resource.unsubscribeFromAllNotifications(); - }); - - it("handles notification when subscribed to a parent with a deleted child", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); - const testContainer = solidLdoDataset.getResource(TEST_CONTAINER_URI); - await resource.read(); - - const spidermanCallback = jest.fn(); - solidLdoDataset.addListener( - [spidermanNode, null, null, null], - spidermanCallback, - ); - - const containerCallback = jest.fn(); - solidLdoDataset.addListener( - [namedNode(TEST_CONTAINER_URI), null, null, null], - containerCallback, - ); - - await testContainer.subscribeToNotifications(); - - await authFetch(SAMPLE_DATA_URI, { - method: "DELETE", - }); - await wait(1000); - - expect(solidLdoDataset.match(spidermanNode, null, null).size).toBe(0); - expect( - testContainer.children().some((child) => child.uri === SAMPLE_DATA_URI), - ).toBe(false); - expect(spidermanCallback).toHaveBeenCalledTimes(1); - expect(containerCallback).toHaveBeenCalledTimes(1); - - await testContainer.unsubscribeFromAllNotifications(); - }); - - it("handles notification when subscribed to a parent with an added child", async () => { - const resource = solidLdoDataset.getResource(SAMPLE2_DATA_URI); - const testContainer = solidLdoDataset.getResource(TEST_CONTAINER_URI); - await resource.read(); - - const spidermanCallback = jest.fn(); - solidLdoDataset.addListener( - [spidermanNode, null, null, null], - spidermanCallback, - ); - - const containerCallback = jest.fn(); - solidLdoDataset.addListener( - [namedNode(TEST_CONTAINER_URI), null, null, null], - containerCallback, - ); - - await testContainer.subscribeToNotifications(); - - await authFetch(TEST_CONTAINER_URI, { - method: "POST", - headers: { "content-type": "text/turtle", slug: "sample2.ttl" }, - body: SPIDER_MAN_TTL, - }); - await wait(1000); - - expect(solidLdoDataset.match(spidermanNode, null, null).size).toBe(4); - expect( - testContainer - .children() - .some((child) => child.uri === SAMPLE2_DATA_URI), - ).toBe(true); - expect(spidermanCallback).toHaveBeenCalledTimes(1); - expect(containerCallback).toHaveBeenCalledTimes(1); - - await testContainer.unsubscribeFromAllNotifications(); - }); - - it("returns an error when it cannot subscribe to a notification", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); - const onError = jest.fn(); - - await app.stop(); - await resource.subscribeToNotifications({ onNotificationError: onError }); - expect(onError).toHaveBeenCalledTimes(2); - await app.start(); - }); - - it("returns an error when the server doesnt support websockets", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); - const onError = jest.fn(); - - await app.stop(); - const disabledWebsocketsApp = await createApp( - path.join(__dirname, "./configs/server-config-without-websocket.json"), - ); - await disabledWebsocketsApp.start(); - - await resource.subscribeToNotifications({ onNotificationError: onError }); - expect(onError).toHaveBeenCalledTimes(2); - - await disabledWebsocketsApp.stop(); - await app.start(); - }); - - it("attempts to reconnect multiple times before giving up.", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); - const onError = jest.fn(); - - await app.stop(); - const disabledWebsocketsApp = await createApp( - path.join(__dirname, "./configs/server-config-without-websocket.json"), - ); - await disabledWebsocketsApp.start(); - - await resource.subscribeToNotifications({ onNotificationError: onError }); - - // TODO: This is a bad test because of the wait. Instead inject better - // numbers into the websocket class. - await wait(35000); - - expect(onError).toHaveBeenCalledTimes(14); - expect(onError.mock.calls[1][0].type).toBe( - "disconnectedAttemptingReconnectError", - ); - expect(onError.mock.calls[13][0].type).toBe( - "disconnectedNotAttemptingReconnectError", - ); - - await disabledWebsocketsApp.stop(); - await app.start(); - }); - - it("causes no problems when unsubscribing when not subscribed", async () => { - const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); - await resource.unsubscribeFromAllNotifications(); - expect(resource.isSubscribedToNotifications()).toBe(false); - }); - }); -}); diff --git a/packages/solid/test/LeafRequester.test.ts b/packages/solid/test/LeafRequester.test.ts deleted file mode 100644 index 72dd179..0000000 --- a/packages/solid/test/LeafRequester.test.ts +++ /dev/null @@ -1,256 +0,0 @@ -// import type { App } from "@solid/community-server"; -// import { getAuthenticatedFetch, ROOT_COONTAINER } from "./solidServer.helper"; -// import type { SolidLdoDataset } from "../src/SolidLdoDataset"; -// import { createSolidLdoDataset } from "../src/createSolidLdoDataset"; -// import { LeafRequester } from "../src/requester/LeafRequester"; -// import { namedNode, quad as createQuad } from "@rdfjs/data-model"; - -describe("Leaf Requester", () => { - it("trivial", () => { - expect(true).toBe(true); - }); -}); - -// describe.skip("Leaf Requester", () => { -// let _app: App; -// let authFetch: typeof fetch; -// let fetchMock: typeof fetch; -// let solidLdoDataset: SolidLdoDataset; - -// beforeAll(async () => { -// // Start up the server -// // app = await createApp(); -// // await app.start(); - -// authFetch = await getAuthenticatedFetch(); -// }); - -// beforeEach(async () => { -// fetchMock = jest.fn(authFetch); -// solidLdoDataset = createSolidLdoDataset({ fetch: fetchMock }); -// // Create a new document called sample.ttl -// await Promise.all([ -// authFetch(`${ROOT_COONTAINER}test_leaf/`, { -// method: "POST", -// headers: { "content-type": "text/turtle", slug: "sample.ttl" }, -// body: `@base . -// @prefix rdf: . -// @prefix rdfs: . -// @prefix foaf: . -// @prefix rel: . - -// <#green-goblin> -// rel:enemyOf <#spiderman> ; -// a foaf:Person ; # in the context of the Marvel universe -// foaf:name "Green Goblin" . - -// <#spiderman> -// rel:enemyOf <#green-goblin> ; -// a foaf:Person ; -// foaf:name "Spiderman", "Человек-паук"@ru .`, -// }), -// authFetch(`${ROOT_COONTAINER}test_leaf/`, { -// method: "PUT", -// headers: { "content-type": "text/plain", slug: "sample.txt" }, -// body: `some text.`, -// }), -// ]); -// }); - -// afterEach(async () => { -// await Promise.all([ -// authFetch(`${ROOT_COONTAINER}test_leaf/sample.ttl`, { -// method: "DELETE", -// }), -// authFetch(`${ROOT_COONTAINER}test_leaf/sample2.ttl`, { -// method: "DELETE", -// }), -// authFetch(`${ROOT_COONTAINER}test_leaf/sample.txt`, { -// method: "DELETE", -// }), -// authFetch(`${ROOT_COONTAINER}test_leaf/sample2.txt`, { -// method: "DELETE", -// }), -// ]); -// }); - -// /** -// * =========================================================================== -// * Read -// * =========================================================================== -// */ -// it("reads data", async () => { -// const leafRequester = new LeafRequester( -// `${ROOT_COONTAINER}test_leaf/sample.ttl`, -// solidLdoDataset.context, -// ); -// const result = await leafRequester.read(); -// expect(result.type).toBe("data"); -// expect( -// solidLdoDataset.match( -// null, -// null, -// null, -// namedNode(`${ROOT_COONTAINER}test_leaf/sample.ttl`), -// ).size, -// ).toBe(7); -// }); - -// it("reads data that doesn't exist", async () => { -// const leafRequester = new LeafRequester( -// `${ROOT_COONTAINER}test_leaf/doesnotexist.ttl`, -// solidLdoDataset.context, -// ); -// const result = await leafRequester.read(); -// expect(result.type).toBe("absent"); -// }); - -// /** -// * =========================================================================== -// * Create -// * =========================================================================== -// */ -// it("creates a data resource that doesn't exist while not overwriting", async () => { -// const leafRequester = new LeafRequester( -// `${ROOT_COONTAINER}test_leaf/sample2.ttl`, -// solidLdoDataset.context, -// ); -// const result = await leafRequester.createDataResource(); -// expect(result.type).toBe("data"); -// expect( -// solidLdoDataset.has( -// createQuad( -// namedNode(`${ROOT_COONTAINER}test_leaf/`), -// namedNode("http://www.w3.org/ns/ldp#contains"), -// namedNode(`${ROOT_COONTAINER}test_leaf/sample2.ttl`), -// namedNode(`${ROOT_COONTAINER}test_leaf/`), -// ), -// ), -// ).toBe(true); -// }); - -// it("creates a data resource that doesn't exist while overwriting", async () => { -// const leafRequester = new LeafRequester( -// `${ROOT_COONTAINER}test_leaf/sample2.ttl`, -// solidLdoDataset.context, -// ); -// const result = await leafRequester.createDataResource(true); -// expect(result.type).toBe("data"); -// expect( -// solidLdoDataset.has( -// createQuad( -// namedNode(`${ROOT_COONTAINER}test_leaf/`), -// namedNode("http://www.w3.org/ns/ldp#contains"), -// namedNode(`${ROOT_COONTAINER}test_leaf/sample2.ttl`), -// namedNode(`${ROOT_COONTAINER}test_leaf/`), -// ), -// ), -// ).toBe(true); -// }); - -// it("creates a data resource that does exist while not overwriting", async () => { -// const leafRequester = new LeafRequester( -// `${ROOT_COONTAINER}test_leaf/sample.ttl`, -// solidLdoDataset.context, -// ); -// const result = await leafRequester.createDataResource(); -// expect(result.type).toBe("data"); -// expect( -// solidLdoDataset.has( -// createQuad( -// namedNode("http://example.org/#spiderman"), -// namedNode("http://www.perceive.net/schemas/relationship/enemyOf"), -// namedNode("http://example.org/#green-goblin"), -// namedNode(`${ROOT_COONTAINER}test_leaf/sample.ttl`), -// ), -// ), -// ).toBe(true); -// expect( -// solidLdoDataset.has( -// createQuad( -// namedNode(`${ROOT_COONTAINER}test_leaf/`), -// namedNode("http://www.w3.org/ns/ldp#contains"), -// namedNode(`${ROOT_COONTAINER}test_leaf/sample.ttl`), -// namedNode(`${ROOT_COONTAINER}test_leaf/`), -// ), -// ), -// ).toBe(true); -// }); - -// it("creates a data resource that does exist while overwriting", async () => { -// const leafRequester = new LeafRequester( -// `${ROOT_COONTAINER}test_leaf/sample.ttl`, -// solidLdoDataset.context, -// ); -// const result = await leafRequester.createDataResource(true); -// expect(result.type).toBe("data"); -// expect( -// solidLdoDataset.has( -// createQuad( -// namedNode("http://example.org/#spiderman"), -// namedNode("http://www.perceive.net/schemas/relationship/enemyOf"), -// namedNode("http://example.org/#green-goblin"), -// namedNode(`${ROOT_COONTAINER}test_leaf/sample.ttl`), -// ), -// ), -// ).toBe(false); -// expect( -// solidLdoDataset.has( -// createQuad( -// namedNode(`${ROOT_COONTAINER}test_leaf/`), -// namedNode("http://www.w3.org/ns/ldp#contains"), -// namedNode(`${ROOT_COONTAINER}test_leaf/sample.ttl`), -// namedNode(`${ROOT_COONTAINER}test_leaf/`), -// ), -// ), -// ).toBe(true); -// }); - -// /** -// * =========================================================================== -// * Delete -// * =========================================================================== -// */ -// it("deletes data", async () => { -// solidLdoDataset.add( -// createQuad( -// namedNode("a"), -// namedNode("b"), -// namedNode("c"), -// namedNode(`${ROOT_COONTAINER}/test_leaf/sample.ttl`), -// ), -// ); -// solidLdoDataset.add( -// createQuad( -// namedNode(`${ROOT_COONTAINER}/test_leaf/`), -// namedNode("http://www.w3.org/ns/ldp#contains"), -// namedNode(`${ROOT_COONTAINER}/test_leaf/sample.ttl`), -// namedNode(`${ROOT_COONTAINER}/test_leaf/`), -// ), -// ); -// const leafRequester = new LeafRequester( -// `${ROOT_COONTAINER}/test_leaf/sample.ttl`, -// solidLdoDataset.context, -// ); -// const result = await leafRequester.delete(); -// expect(result.type).toBe("absent"); -// expect( -// solidLdoDataset.match( -// null, -// null, -// null, -// namedNode(`${ROOT_COONTAINER}/test_leaf/sample.ttl`), -// ).size, -// ).toBe(0); -// expect( -// solidLdoDataset.has( -// createQuad( -// namedNode(`${ROOT_COONTAINER}/test_leaf/`), -// namedNode("http://www.w3.org/ns/ldp#contains"), -// namedNode(`${ROOT_COONTAINER}/test_leaf/sample.ttl`), -// namedNode(`${ROOT_COONTAINER}/test_leaf/`), -// ), -// ), -// ).toBe(false); -// }); -// }); diff --git a/packages/solid/test/RequestBatcher.test.ts b/packages/solid/test/RequestBatcher.test.ts deleted file mode 100644 index 38ff5a2..0000000 --- a/packages/solid/test/RequestBatcher.test.ts +++ /dev/null @@ -1,112 +0,0 @@ -import type { WaitingProcess } from "../src/util/RequestBatcher"; -import { RequestBatcher } from "../src/util/RequestBatcher"; - -describe("RequestBatcher", () => { - type ReadWaitingProcess = WaitingProcess<[string], string>; - - it("Batches a request", async () => { - const requestBatcher = new RequestBatcher({ batchMillis: 500 }); - const perform = async (input: string): Promise => { - await wait(100); - return `Hello ${input}`; - }; - const perform1 = jest.fn(perform); - const perform2 = jest.fn(perform); - const perform3 = jest.fn((input: string): Promise => { - expect(requestBatcher.isLoading("read")).toBe(true); - return perform(input); - }); - const perform4 = jest.fn(perform); - - const modifyQueue = (queue, currentlyProcessing, input: [string]) => { - const last = queue[queue.length - 1]; - if (last?.name === "read") { - (last as ReadWaitingProcess).args[0] += input; - return last; - } - return undefined; - }; - - let return1: string = ""; - let return2: string = ""; - let return3: string = ""; - let return4: string = ""; - - expect(requestBatcher.isLoading("read")).toBe(false); - - await Promise.all([ - requestBatcher - .queueProcess<[string], string>({ - name: "read", - args: ["a"], - perform: perform1, - modifyQueue, - }) - .then((val) => (return1 = val)), - requestBatcher - .queueProcess<[string], string>({ - name: "read", - args: ["b"], - perform: perform2, - modifyQueue, - }) - .then((val) => (return2 = val)), - , - requestBatcher - .queueProcess<[string], string>({ - name: "read", - args: ["c"], - perform: perform3, - modifyQueue, - }) - .then((val) => (return3 = val)), - , - requestBatcher - .queueProcess<[string], string>({ - name: "read", - args: ["d"], - perform: perform4, - modifyQueue, - }) - .then((val) => (return4 = val)), - , - ]); - - expect(return1).toBe("Hello a"); - expect(return2).toBe("Hello bcd"); - expect(return3).toBe("Hello bcd"); - expect(return4).toBe("Hello bcd"); - - expect(perform1).toHaveBeenCalledTimes(1); - expect(perform1).toHaveBeenCalledWith("a"); - expect(perform2).toHaveBeenCalledTimes(1); - expect(perform2).toHaveBeenCalledWith("bcd"); - expect(perform3).toHaveBeenCalledTimes(0); - expect(perform4).toHaveBeenCalledTimes(0); - }); - - it("sets a default batch millis", () => { - const requestBatcher = new RequestBatcher(); - expect(requestBatcher.batchMillis).toBe(1000); - }); - - it("handles an error being thrown in the process", () => { - const requestBatcher = new RequestBatcher({ batchMillis: 500 }); - const perform = async (_input: string): Promise => { - throw new Error("Test Error"); - }; - const perform1 = jest.fn(perform); - expect(() => - requestBatcher.queueProcess<[string], string>({ - name: "read", - args: ["a"], - perform: perform1, - modifyQueue: () => undefined, - }), - ).rejects.toThrowError("Test Error"); - }); -}); - -function wait(millis: number): Promise { - return new Promise((resolve) => setTimeout(resolve, millis)); -} diff --git a/packages/solid/test/Websocket2023NotificationSubscription.test.ts b/packages/solid/test/Websocket2023NotificationSubscription.test.ts deleted file mode 100644 index 1f97b0d..0000000 --- a/packages/solid/test/Websocket2023NotificationSubscription.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { WebSocket, Event, ErrorEvent } from "ws"; -import { Websocket2023NotificationSubscription } from "../src/resource/notifications/Websocket2023NotificationSubscription"; -import type { SolidLdoDatasetContext } from "../src"; -import { Leaf } from "../src"; -import type { NotificationChannel } from "@solid-notifications/types"; - -describe("Websocket2023NotificationSubscription", () => { - it("returns an error when websockets have an error", async () => { - const WebSocketMock: WebSocket = {} as WebSocket; - - const subscription = new Websocket2023NotificationSubscription( - new Leaf("https://example.com", { - fetch, - } as unknown as SolidLdoDatasetContext), - () => {}, - {} as unknown as SolidLdoDatasetContext, - () => WebSocketMock, - ); - - const subPromise = subscription.subscribeToWebsocket({ - receiveFrom: "http://example.com", - } as unknown as NotificationChannel); - WebSocketMock.onopen?.({} as Event); - - await subPromise; - - WebSocketMock.onerror?.({ error: new Error("Test Error") } as ErrorEvent); - }); - - it("returns an error when websockets have an error at the beginning", async () => { - const WebSocketMock: WebSocket = {} as WebSocket; - - const subscription = new Websocket2023NotificationSubscription( - new Leaf("https://example.com", { - fetch, - } as unknown as SolidLdoDatasetContext), - () => {}, - {} as unknown as SolidLdoDatasetContext, - () => WebSocketMock, - ); - - const subPromise = subscription.subscribeToWebsocket({ - receiveFrom: "http://example.com", - } as unknown as NotificationChannel); - WebSocketMock.onerror?.({ error: new Error("Test Error") } as ErrorEvent); - await subPromise; - }); -}); diff --git a/packages/solid/test/authFetch.helper.ts b/packages/solid/test/authFetch.helper.ts deleted file mode 100644 index fffee6a..0000000 --- a/packages/solid/test/authFetch.helper.ts +++ /dev/null @@ -1,112 +0,0 @@ -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 { - // 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 }); -} diff --git a/packages/solid/test/configs/server-config-without-websocket.json b/packages/solid/test/configs/server-config-without-websocket.json deleted file mode 100644 index 626d082..0000000 --- a/packages/solid/test/configs/server-config-without-websocket.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^7.0.0/components/context.jsonld", - "import": [ - "css:config/app/init/initialize-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/webhooks.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/no-accounts.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/file.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/file.json", - "css:config/util/variables/default.json" - ], - "@graph": [ - { - "comment": [ - "A Solid server that stores its resources on disk and uses WAC for authorization.", - "No registration and the root container is initialized to allow full access for everyone so make sure to change this." - ] - } - ] -} \ No newline at end of file diff --git a/packages/solid/test/configs/server-config.json b/packages/solid/test/configs/server-config.json deleted file mode 100644 index 5e96784..0000000 --- a/packages/solid/test/configs/server-config.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "@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." - } - ] -} \ No newline at end of file diff --git a/packages/solid/test/configs/solid-css-seed.json b/packages/solid/test/configs/solid-css-seed.json deleted file mode 100644 index 5894d0d..0000000 --- a/packages/solid/test/configs/solid-css-seed.json +++ /dev/null @@ -1,9 +0,0 @@ -[ - { - "email": "hello@example.com", - "password": "abc123", - "pods": [ - { "name": "example" } - ] - } -] \ No newline at end of file diff --git a/packages/solid/test/guaranteeFetch.test.ts b/packages/solid/test/guaranteeFetch.test.ts deleted file mode 100644 index 92cf6c4..0000000 --- a/packages/solid/test/guaranteeFetch.test.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { guaranteeFetch } from "../src/util/guaranteeFetch"; -import crossFetch from "cross-fetch"; - -describe("guaranteeFetch", () => { - it("returns crossfetch when no fetch is provided", () => { - expect(guaranteeFetch()).toBe(crossFetch); - }); -}); diff --git a/packages/solid/test/setup-tests.ts b/packages/solid/test/setup-tests.ts deleted file mode 100644 index f4378ae..0000000 --- a/packages/solid/test/setup-tests.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { config } from "dotenv"; - -config(); diff --git a/packages/solid/test/solidServer.helper.ts b/packages/solid/test/solidServer.helper.ts deleted file mode 100644 index 38069d5..0000000 --- a/packages/solid/test/solidServer.helper.ts +++ /dev/null @@ -1,40 +0,0 @@ -// 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"; -import "jest-rdf"; - -export const SERVER_DOMAIN = process.env.SERVER || "http://localhost:3001/"; -export const ROOT_ROUTE = process.env.ROOT_CONTAINER || ""; -export const ROOT_CONTAINER = `${SERVER_DOMAIN}${ROOT_ROUTE}`; -export const WEB_ID = - process.env.WEB_ID || `${SERVER_DOMAIN}example/profile/card#me`; - -// Use an increased timeout, since the CSS server takes too much setup time. -jest.setTimeout(40_000); - -export async function createApp(customConfigPath?: string): Promise { - if (process.env.SERVER) { - return { - start: () => {}, - stop: () => {}, - } as App; - } - const appRunner = new AppRunner(); - - return appRunner.create({ - loaderProperties: { - mainModulePath: resolveModulePath(""), - typeChecking: false, - }, - config: customConfigPath ?? resolveModulePath("config/file-root.json"), - variableBindings: {}, - shorthand: { - port: 3_001, - loggingLevel: "off", - seedConfig: path.join(__dirname, "configs", "solid-css-seed.json"), - rootFilePath: path.join(__dirname, "./data"), - }, - }); -} diff --git a/packages/solid/test/uriTypes.test.ts b/packages/solid/test/uriTypes.test.ts deleted file mode 100644 index 589e874..0000000 --- a/packages/solid/test/uriTypes.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { isLeafUri } from "../src"; - -describe("isLeafUri", () => { - it("returns true if the given value is a leaf URI", () => { - expect(isLeafUri("https://example.com/index.ttl")).toBe(true); - }); -}); diff --git a/packages/solid/test/utils.helper.ts b/packages/solid/test/utils.helper.ts deleted file mode 100644 index 109fa51..0000000 --- a/packages/solid/test/utils.helper.ts +++ /dev/null @@ -1,3 +0,0 @@ -export async function wait(millis: number) { - return new Promise((resolve) => setTimeout(resolve, millis)); -} diff --git a/packages/solid/tsconfig.build.json b/packages/solid/tsconfig.build.json deleted file mode 100644 index 4bd5a5e..0000000 --- a/packages/solid/tsconfig.build.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "outDir": "./dist", - }, - "include": ["./src"] -} \ No newline at end of file diff --git a/packages/solid/typedoc.json b/packages/solid/typedoc.json deleted file mode 100644 index c0e7b5d..0000000 --- a/packages/solid/typedoc.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "entryPoints": ["src/index.ts"], - "out": "docs", - "allReflectionsHaveOwnDocument": true, - "hideInPageTOC": true, - "hideBreadcrumbs": true, -} \ No newline at end of file