diff --git a/Readme.md b/Readme.md index d084623..c0135cf 100644 --- a/Readme.md +++ b/Readme.md @@ -15,6 +15,7 @@ The LDO monorepo contains the following - [@ldo/schema-converter-shex](./packages/schema-converter-shex/) - [@ldo/solid](./packages/solid/) - [@ldo/solid-react](./packages/solid-react/) + - [@ldo/solid-type-index](./packages/solid-type-index/) - [@ldo/subscribable-dataset](./packages/subscribable-dataset/) - [@ldo/traverser-shexj](./packages/traverser-shexj/) - [@ldo/type-traverser](./packages/type-traverser/) diff --git a/packages/connected-nextgraph/.eslintrc b/packages/connected-nextgraph/.eslintrc new file mode 100644 index 0000000..83c51a9 --- /dev/null +++ b/packages/connected-nextgraph/.eslintrc @@ -0,0 +1,3 @@ +{ + "extends": ["../../.eslintrc"] +} \ No newline at end of file diff --git a/packages/connected-nextgraph/.gitignore b/packages/connected-nextgraph/.gitignore new file mode 100644 index 0000000..869539a --- /dev/null +++ b/packages/connected-nextgraph/.gitignore @@ -0,0 +1,2 @@ +test/data +node_modules \ No newline at end of file diff --git a/packages/connected-nextgraph/LICENSE.txt b/packages/connected-nextgraph/LICENSE.txt new file mode 100644 index 0000000..b87e67e --- /dev/null +++ b/packages/connected-nextgraph/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Jackson Morgan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/connected-nextgraph/README.md b/packages/connected-nextgraph/README.md new file mode 100644 index 0000000..94c9ada --- /dev/null +++ b/packages/connected-nextgraph/README.md @@ -0,0 +1,238 @@ +# @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/connected-nextgraph/babel.config.js b/packages/connected-nextgraph/babel.config.js new file mode 100644 index 0000000..721e8b8 --- /dev/null +++ b/packages/connected-nextgraph/babel.config.js @@ -0,0 +1 @@ +module.exports = { presets: ["@babel/preset-env"] }; diff --git a/packages/connected-nextgraph/jest.config.js b/packages/connected-nextgraph/jest.config.js new file mode 100644 index 0000000..c55a5f7 --- /dev/null +++ b/packages/connected-nextgraph/jest.config.js @@ -0,0 +1,11 @@ +// 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/connected-nextgraph/package.json b/packages/connected-nextgraph/package.json new file mode 100644 index 0000000..122ca09 --- /dev/null +++ b/packages/connected-nextgraph/package.json @@ -0,0 +1,58 @@ +{ + "name": "@ldo/connected-solid", + "version": "1.0.0-alpha.1", + "description": "A plugin for @ldo/connected to work with the Solid ecosystem.", + "main": "dist/index.js", + "scripts": { + "build": "tsc --project tsconfig.build.json", + "watch": "tsc --watch", + "test": "jest --coverage", + "test:watch": "jest --watch", + "prepublishOnly": "npm run test && npm run build", + "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" + } +} diff --git a/packages/connected-nextgraph/tsconfig.build.json b/packages/connected-nextgraph/tsconfig.build.json new file mode 100644 index 0000000..4bd5a5e --- /dev/null +++ b/packages/connected-nextgraph/tsconfig.build.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + }, + "include": ["./src"] +} \ No newline at end of file diff --git a/packages/connected-nextgraph/typedoc.json b/packages/connected-nextgraph/typedoc.json new file mode 100644 index 0000000..c0e7b5d --- /dev/null +++ b/packages/connected-nextgraph/typedoc.json @@ -0,0 +1,7 @@ +{ + "entryPoints": ["src/index.ts"], + "out": "docs", + "allReflectionsHaveOwnDocument": true, + "hideInPageTOC": true, + "hideBreadcrumbs": true, +} \ No newline at end of file diff --git a/packages/connected-solid/.eslintrc b/packages/connected-solid/.eslintrc new file mode 100644 index 0000000..83c51a9 --- /dev/null +++ b/packages/connected-solid/.eslintrc @@ -0,0 +1,3 @@ +{ + "extends": ["../../.eslintrc"] +} \ No newline at end of file diff --git a/packages/connected-solid/.gitignore b/packages/connected-solid/.gitignore new file mode 100644 index 0000000..869539a --- /dev/null +++ b/packages/connected-solid/.gitignore @@ -0,0 +1,2 @@ +test/data +node_modules \ No newline at end of file diff --git a/packages/connected-solid/LICENSE.txt b/packages/connected-solid/LICENSE.txt new file mode 100644 index 0000000..b87e67e --- /dev/null +++ b/packages/connected-solid/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Jackson Morgan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/connected-solid/README.md b/packages/connected-solid/README.md new file mode 100644 index 0000000..94c9ada --- /dev/null +++ b/packages/connected-solid/README.md @@ -0,0 +1,238 @@ +# @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/connected-solid/babel.config.js b/packages/connected-solid/babel.config.js new file mode 100644 index 0000000..721e8b8 --- /dev/null +++ b/packages/connected-solid/babel.config.js @@ -0,0 +1 @@ +module.exports = { presets: ["@babel/preset-env"] }; diff --git a/packages/connected-solid/jest.config.js b/packages/connected-solid/jest.config.js new file mode 100644 index 0000000..c55a5f7 --- /dev/null +++ b/packages/connected-solid/jest.config.js @@ -0,0 +1,11 @@ +// 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/connected-solid/package.json b/packages/connected-solid/package.json new file mode 100644 index 0000000..122ca09 --- /dev/null +++ b/packages/connected-solid/package.json @@ -0,0 +1,58 @@ +{ + "name": "@ldo/connected-solid", + "version": "1.0.0-alpha.1", + "description": "A plugin for @ldo/connected to work with the Solid ecosystem.", + "main": "dist/index.js", + "scripts": { + "build": "tsc --project tsconfig.build.json", + "watch": "tsc --watch", + "test": "jest --coverage", + "test:watch": "jest --watch", + "prepublishOnly": "npm run test && npm run build", + "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" + } +} diff --git a/packages/connected-solid/tsconfig.build.json b/packages/connected-solid/tsconfig.build.json new file mode 100644 index 0000000..4bd5a5e --- /dev/null +++ b/packages/connected-solid/tsconfig.build.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + }, + "include": ["./src"] +} \ No newline at end of file diff --git a/packages/connected-solid/typedoc.json b/packages/connected-solid/typedoc.json new file mode 100644 index 0000000..c0e7b5d --- /dev/null +++ b/packages/connected-solid/typedoc.json @@ -0,0 +1,7 @@ +{ + "entryPoints": ["src/index.ts"], + "out": "docs", + "allReflectionsHaveOwnDocument": true, + "hideInPageTOC": true, + "hideBreadcrumbs": true, +} \ No newline at end of file diff --git a/packages/connected/.eslintrc b/packages/connected/.eslintrc new file mode 100644 index 0000000..83c51a9 --- /dev/null +++ b/packages/connected/.eslintrc @@ -0,0 +1,3 @@ +{ + "extends": ["../../.eslintrc"] +} \ No newline at end of file diff --git a/packages/connected/.gitignore b/packages/connected/.gitignore new file mode 100644 index 0000000..869539a --- /dev/null +++ b/packages/connected/.gitignore @@ -0,0 +1,2 @@ +test/data +node_modules \ No newline at end of file diff --git a/packages/connected/LICENSE.txt b/packages/connected/LICENSE.txt new file mode 100644 index 0000000..b87e67e --- /dev/null +++ b/packages/connected/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Jackson Morgan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/connected/README.md b/packages/connected/README.md new file mode 100644 index 0000000..94c9ada --- /dev/null +++ b/packages/connected/README.md @@ -0,0 +1,238 @@ +# @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/connected/babel.config.js b/packages/connected/babel.config.js new file mode 100644 index 0000000..721e8b8 --- /dev/null +++ b/packages/connected/babel.config.js @@ -0,0 +1 @@ +module.exports = { presets: ["@babel/preset-env"] }; diff --git a/packages/connected/jest.config.js b/packages/connected/jest.config.js new file mode 100644 index 0000000..c55a5f7 --- /dev/null +++ b/packages/connected/jest.config.js @@ -0,0 +1,11 @@ +// 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/connected/package.json b/packages/connected/package.json new file mode 100644 index 0000000..fa47235 --- /dev/null +++ b/packages/connected/package.json @@ -0,0 +1,58 @@ +{ + "name": "@ldo/connected", + "version": "1.0.0-alpha.1", + "description": "A library for connecting LDO to resources outside the LDO environment", + "main": "dist/index.js", + "scripts": { + "build": "tsc --project tsconfig.build.json", + "watch": "tsc --watch", + "test": "jest --coverage", + "test:watch": "jest --watch", + "prepublishOnly": "npm run test && npm run build", + "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" + } +} diff --git a/packages/connected/src/ConnectedLdoDataset.ts b/packages/connected/src/ConnectedLdoDataset.ts new file mode 100644 index 0000000..c2a5148 --- /dev/null +++ b/packages/connected/src/ConnectedLdoDataset.ts @@ -0,0 +1,33 @@ +import { LdoDataset } from "@ldo/ldo"; +import type { ConnectedLdoPlugin } from "./ConnectedLdoPlugin"; +import type { Dataset, DatasetFactory, Quad } from "@rdfjs/types"; +import type { ITransactionDatasetFactory } from "@ldo/subscribable-dataset"; + +type ReturnTypeFromArgs = T extends (arg: Arg) => infer R ? R : never; + +export class ConnectedLdoDataset< + Plugins extends ConnectedLdoPlugin[], +> extends LdoDataset { + private plugins: Plugins; + + constructor( + plugins: Plugins, + datasetFactory: DatasetFactory, + transactionDatasetFactory: ITransactionDatasetFactory, + initialDataset?: Dataset, + ) { + super(datasetFactory, transactionDatasetFactory, initialDataset); + this.plugins = plugins; + } + + getResource< + Name extends Plugins[number]["name"], + Plugin extends Extract, + UriType extends Parameters[0], + >( + _uri: UriType, + _pluginName?: Name, + ): ReturnTypeFromArgs { + return "eh?" as ReturnTypeFromArgs; + } +} diff --git a/packages/connected/src/ConnectedLdoPlugin.ts b/packages/connected/src/ConnectedLdoPlugin.ts new file mode 100644 index 0000000..2b9ae2e --- /dev/null +++ b/packages/connected/src/ConnectedLdoPlugin.ts @@ -0,0 +1,6 @@ +import type { Resource } from "./Resource"; + +export interface ConnectedLdoPlugin { + name: string; + getResource(uri: string): Resource; +} diff --git a/packages/connected/src/Resource.ts b/packages/connected/src/Resource.ts new file mode 100644 index 0000000..106a26b --- /dev/null +++ b/packages/connected/src/Resource.ts @@ -0,0 +1,7 @@ +export interface Resource {} + +export class SolidResource implements Resource {} +export class SolidContainer extends SolidResource {} +export class SolidLeaf extends SolidResource {} + +export class NextGraphResource implements Resource {} diff --git a/packages/connected/src/test.ts b/packages/connected/src/test.ts new file mode 100644 index 0000000..8f7eebc --- /dev/null +++ b/packages/connected/src/test.ts @@ -0,0 +1,55 @@ +import { createDatasetFactory } from "@ldo/dataset"; +import { ConnectedLdoDataset } from "./ConnectedLdoDataset"; +import { createTransactionDatasetFactory } from "@ldo/subscribable-dataset"; +import type { ContainerUri, LeafUri } from "../../solid/src/index"; +import { SolidContainer, SolidLeaf } from "./Resource"; + +interface SolidPlugin { + name: "solid"; + getResource(uri: ContainerUri): SolidContainer; + getResource(uri: LeafUri): SolidLeaf; + getResource(uri: string): SolidLeaf | SolidContainer; +} + +const solidPlugin: SolidPlugin = { + name: "solid", + // eslint-disable-next-line @typescript-eslint/no-explicit-any + getResource(_uri: string): any { + throw new Error(); + }, +}; + +interface NextgraphPlugin { + name: "nextgraph"; + getResource(uri: ContainerUri); + getResource(uri: string): "nextgraphResource"; +} + +const nextgraphPlugin: NextgraphPlugin = { + name: "nextgraph", + getResource(_uri: string): "nextgraphResource" { + return "nextgraphResource"; + }, +}; + +async function main() { + const dataset = new ConnectedLdoDataset( + [solidPlugin, nextgraphPlugin], + createDatasetFactory(), + createTransactionDatasetFactory(), + ); + + const solidContainerResource = dataset.getResource("https://example.com/"); + const solidLeafResource = dataset.getResource( + "https://example.com/resource.ttl", + ); + const stringUri: string = "https://example.com/"; + const allResources = dataset.getResource(stringUri); + const solidResource = dataset.getResource(stringUri, "solid"); + const nextgraphResource = dataset.getResource(stringUri, "nextgraph"); + + const nextgraphResource2 = dataset.getResource( + "did:ng:o:OGNxCWfTXMfYIJi8HCEfL6_uExLtCHrK0JGT4fU5pH4A:v:R2y5iENVwuaaoW86TvMbfZfCIrNXaNIFA3BF6fx9svQA", + ); +} +main(); diff --git a/packages/connected/tsconfig.build.json b/packages/connected/tsconfig.build.json new file mode 100644 index 0000000..4bd5a5e --- /dev/null +++ b/packages/connected/tsconfig.build.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + }, + "include": ["./src"] +} \ No newline at end of file diff --git a/packages/connected/typedoc.json b/packages/connected/typedoc.json new file mode 100644 index 0000000..c0e7b5d --- /dev/null +++ b/packages/connected/typedoc.json @@ -0,0 +1,7 @@ +{ + "entryPoints": ["src/index.ts"], + "out": "docs", + "allReflectionsHaveOwnDocument": true, + "hideInPageTOC": true, + "hideBreadcrumbs": true, +} \ No newline at end of file diff --git a/packages/solid/src/SolidLdoDataset.ts b/packages/solid/src/SolidLdoDataset.ts index cfd46f4..dc16d09 100644 --- a/packages/solid/src/SolidLdoDataset.ts +++ b/packages/solid/src/SolidLdoDataset.ts @@ -83,7 +83,6 @@ export class SolidLdoDataset extends LdoDataset implements ISolidLdoDataset { */ 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); }