Merge pull request #73 from o-development/feat/set-refactor

Feat/set refactor
main
jaxoncreed 6 months ago committed by GitHub
commit 59a10acda1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      lerna.json
  2. 29381
      package-lock.json
  3. 14
      packages/cli/README.md
  4. 8
      packages/cli/package.json
  5. 19
      packages/cli/src/create.ts
  6. 20
      packages/cli/src/generateReadme.ts
  7. 9
      packages/cli/src/index.ts
  8. 2
      packages/cli/src/templates/context.ejs
  9. 2
      packages/cli/src/templates/typings.ejs
  10. 6
      packages/dataset/Readme.md
  11. 4
      packages/dataset/package.json
  12. 3
      packages/demo-react/.eslintrc
  13. 23
      packages/demo-react/.gitignore
  14. 21
      packages/demo-react/LICENSE.txt
  15. 12
      packages/demo-react/README.md
  16. 28
      packages/demo-react/craco.config.js
  17. 50
      packages/demo-react/package.json
  18. 41
      packages/demo-react/public/index.html
  19. 25
      packages/demo-react/public/manifest.json
  20. 3
      packages/demo-react/public/robots.txt
  21. 31
      packages/demo-react/src/.ldo/post.context.ts
  22. 155
      packages/demo-react/src/.ldo/post.schema.ts
  23. 19
      packages/demo-react/src/.ldo/post.shapeTypes.ts
  24. 45
      packages/demo-react/src/.ldo/post.typings.ts
  25. 154
      packages/demo-react/src/.ldo/solidProfile.context.ts
  26. 749
      packages/demo-react/src/.ldo/solidProfile.schema.ts
  27. 71
      packages/demo-react/src/.ldo/solidProfile.shapeTypes.ts
  28. 293
      packages/demo-react/src/.ldo/solidProfile.typings.ts
  29. 23
      packages/demo-react/src/.shapes/post.shex
  30. 121
      packages/demo-react/src/.shapes/solidProfile.shex
  31. 13
      packages/demo-react/src/App-old.tsx
  32. 65
      packages/demo-react/src/App.tsx
  33. 54
      packages/demo-react/src/Header.tsx
  34. 50
      packages/demo-react/src/Layout.tsx
  35. 73
      packages/demo-react/src/MainContainerProvider.tsx
  36. 30
      packages/demo-react/src/blog/Blog.tsx
  37. 92
      packages/demo-react/src/blog/MakePost.tsx
  38. 8
      packages/demo-react/src/index.tsx
  39. 42
      packages/demo-react/src/post/Post.tsx
  40. 16
      packages/demo-react/src/post/PostPage.tsx
  41. 16
      packages/demo-react/src/post/PostedBy.tsx
  42. 34
      packages/demo-react/src/profile/Profile.tsx
  43. 20
      packages/demo-react/tsconfig.json
  44. 157
      packages/jsonld-dataset-proxy/README.md
  45. 8
      packages/jsonld-dataset-proxy/package.json
  46. BIN
      packages/jsonld-dataset-proxy/readme-images/Intellisense.png
  47. 2
      packages/jsonld-dataset-proxy/src/ContextUtil.ts
  48. 27
      packages/jsonld-dataset-proxy/src/JsonldDatasetProxyBuilder.ts
  49. 73
      packages/jsonld-dataset-proxy/src/ProxyContext.ts
  50. 20
      packages/jsonld-dataset-proxy/src/arrayProxy/ArrayProxy.ts
  51. 219
      packages/jsonld-dataset-proxy/src/arrayProxy/arrayMethods.ts
  52. 177
      packages/jsonld-dataset-proxy/src/arrayProxy/createArrayHandler.ts
  53. 23
      packages/jsonld-dataset-proxy/src/arrayProxy/isArrayProxy.ts
  54. 141
      packages/jsonld-dataset-proxy/src/arrayProxy/modifyArray.ts
  55. 38
      packages/jsonld-dataset-proxy/src/graphOf.ts
  56. 11
      packages/jsonld-dataset-proxy/src/index.ts
  57. 5
      packages/jsonld-dataset-proxy/src/language/languagesOf.ts
  58. 66
      packages/jsonld-dataset-proxy/src/setProxy/ObjectSetProxy.ts
  59. 141
      packages/jsonld-dataset-proxy/src/setProxy/SetProxy.ts
  60. 85
      packages/jsonld-dataset-proxy/src/setProxy/SubjectSetProxy.ts
  61. 89
      packages/jsonld-dataset-proxy/src/setProxy/WildcardObjectSetProxy.ts
  62. 58
      packages/jsonld-dataset-proxy/src/setProxy/WildcardSubjectSetProxy.ts
  63. 47
      packages/jsonld-dataset-proxy/src/setProxy/createNewSetProxy.ts
  64. 7
      packages/jsonld-dataset-proxy/src/setProxy/isSetProxy.ts
  65. 266
      packages/jsonld-dataset-proxy/src/setProxy/ldSet/BasicLdSet.ts
  66. 189
      packages/jsonld-dataset-proxy/src/setProxy/ldSet/LdSet.ts
  67. 14
      packages/jsonld-dataset-proxy/src/setProxy/set.ts
  68. 2
      packages/jsonld-dataset-proxy/src/subjectProxy/SubjectProxy.ts
  69. 14
      packages/jsonld-dataset-proxy/src/subjectProxy/createSubjectHandler.ts
  70. 37
      packages/jsonld-dataset-proxy/src/subjectProxy/deleteFromDataset.ts
  71. 13
      packages/jsonld-dataset-proxy/src/subjectProxy/getValueForKey.ts
  72. 4
      packages/jsonld-dataset-proxy/src/types.ts
  73. 12
      packages/jsonld-dataset-proxy/src/util/NodeSet.ts
  74. 12
      packages/jsonld-dataset-proxy/src/util/RawObject.ts
  75. 12
      packages/jsonld-dataset-proxy/src/util/addObjectToDataset.ts
  76. 17
      packages/jsonld-dataset-proxy/src/util/getNodeFromRaw.ts
  77. 13
      packages/jsonld-dataset-proxy/src/util/isProxy.ts
  78. 5
      packages/jsonld-dataset-proxy/src/util/nodeToJsonldRepresentation.ts
  79. 325
      packages/jsonld-dataset-proxy/test/BasicLdSet.test.ts
  80. 6
      packages/jsonld-dataset-proxy/test/ContextUtil.test.ts
  81. 8
      packages/jsonld-dataset-proxy/test/isProxy.test.ts
  82. 896
      packages/jsonld-dataset-proxy/test/jsonldDatasetProxy.test.ts
  83. 7
      packages/jsonld-dataset-proxy/test/patientExampleData.ts
  84. 2
      packages/jsonld-dataset-proxy/tsconfig.build.json
  85. 62
      packages/ldo/README.md
  86. 10
      packages/ldo/package.json
  87. 5
      packages/ldo/src/LdoBuilder.ts
  88. 1
      packages/ldo/src/index.ts
  89. 4
      packages/ldo/src/util.ts
  90. 14
      packages/ldo/test/LdoDataset.test.ts
  91. 26
      packages/ldo/test/profileData.ts
  92. 2
      packages/rdf-utils/package.json
  93. 18
      packages/schema-converter-shex/README.md
  94. 6
      packages/schema-converter-shex/package.json
  95. 76
      packages/schema-converter-shex/src/context/JsonLdContextBuilder.ts
  96. 12
      packages/schema-converter-shex/src/typing/ShexJTypingTransformer.ts
  97. 2
      packages/schema-converter-shex/src/typing/shexjToTyping.ts
  98. 33
      packages/schema-converter-shex/src/typing/util/dedupeObjectTypeMembers.ts
  99. 2
      packages/schema-converter-shex/test/context.test.ts
  100. 1342
      packages/schema-converter-shex/test/testData/activityPub.ts
  101. Some files were not shown because too many files have changed in this diff Show More

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

29381
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -12,7 +12,10 @@ cd my-typescript-project
npx @ldo/cli init
```
### Manual Setup
<details>
<summary>
Manual Setup
</summary>
The following is handled by the __automatic setup__:
Install the LDO dependencies.
@ -38,6 +41,7 @@ Create a script to build ShEx shapes and convert them into Linked Data Objects.
...
}
```
</details>
## Generating a ShapeType
@ -64,10 +68,10 @@ npx @ldo/cli create ./my-project
This script will generate a project with a place to put your shapes. Running `npm publish` will build the shapes and push to project to NPM for you.
## API Details
- [`init` command](https://ldo.js.org/api/cli/init/)
- [`build` command](https://ldo.js.org/api/cli/build/)
- [`create` command](https://ldo.js.org/api/cli/create/)
```
- [`init` command](https://ldo.js.org/latest/api/cli/init/)
- [`build` command](https://ldo.js.org/latest/api/cli/build/)
- [`create` command](https://ldo.js.org/latest/api/cli/create/)
## 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/).

@ -1,6 +1,6 @@
{
"name": "@ldo/cli",
"version": "0.0.1-alpha.29",
"version": "1.0.0-alpha.1",
"description": "A Command Line Interface for Linked Data Objects",
"main": "./dist/index.js",
"bin": {
@ -43,8 +43,8 @@
"ts-jest": "^27.0.7"
},
"dependencies": {
"@ldo/ldo": "^0.0.1-alpha.29",
"@ldo/schema-converter-shex": "^0.0.1-alpha.29",
"@ldo/ldo": "^1.0.0-alpha.1",
"@ldo/schema-converter-shex": "^1.0.0-alpha.1",
"@shexjs/parser": "^1.0.0-alpha.24",
"child-process-promise": "^2.2.1",
"commander": "^9.3.0",
@ -63,5 +63,5 @@
"publishConfig": {
"access": "public"
},
"gitHead": "c63f055aab22155b60a5fdee4172979b9c287dfa"
"gitHead": "0287cd6371f06630763568dec5e41653f7b8583e"
}

@ -88,24 +88,13 @@ export async function create(directory: string) {
await modifyPackageJson(directory, async (packageJson) => {
if (!packageJson.scripts) packageJson.scripts = {};
packageJson.scripts.prepublish =
"npm run build:ldo & npm run generate-readme";
"npm run build:ldo && npm run generate-readme";
packageJson.scripts[
"genenerate-readme"
"generate-readme"
] = `ldo generate-readme --project ./ --shapes ./.shapes --ldo ./.ldo`;
return packageJson;
});
// Create index.js
load.text = "Generating index.js";
const ldoDir = await fs.readdir(path.join(directory, "./.ldo"), {
withFileTypes: true,
});
const indexText = await renderFile(
path.join(__dirname, "./templates/readme/projectIndex.ejs"),
{ fileNames: ldoDir.map((file) => file.name) },
);
await fs.writeFile(path.join(directory, "index.js"), indexText);
// Generate ReadMe
load.text = "Generating README";
await generateReadme({
@ -114,5 +103,9 @@ export async function create(directory: string) {
ldo: path.join(directory, ".ldo"),
});
// Create .gitignore
load.text = "Create .gitignore";
await fs.writeFile(path.join(directory, ".gitignore"), "node_modules");
load.stop();
}

@ -66,6 +66,8 @@ export async function generateReadme(options: GenerateReadmeOptions) {
);
// Save readme to document
await fs.writeFile(path.join(options.project, "README.md"), finalContent);
await generateIndex({ project: options.project });
}
/**
@ -79,3 +81,21 @@ function listInterfaces(filePath: string): string[] {
const interfaces = sourceFile.getInterfaces().map((iface) => iface.getName());
return interfaces;
}
/**
* Generate Index
*/
interface GenerateIndexOptions {
project: string;
}
export async function generateIndex(options: GenerateIndexOptions) {
const ldoDir = await fs.readdir(path.join(options.project, "./.ldo"), {
withFileTypes: true,
});
const indexText = await renderFile(
path.join(__dirname, "./templates/readme/projectIndex.ejs"),
{ fileNames: ldoDir.map((file) => file.name) },
);
await fs.writeFile(path.join(options.project, "index.js"), indexText);
}

@ -4,6 +4,7 @@ import { program } from "commander";
import { build } from "./build";
import { init } from "./init";
import { create } from "./create";
import { generateReadme } from "./generateReadme";
program
.name("LDO-CLI")
@ -33,9 +34,9 @@ program
.command("generate-readme")
.description("Create a ReadMe from the shapes and generated code.")
.requiredOption(
"-r, --readme <readmePath>",
"Provide the path to the readme",
"./.shapes",
"-p, --project <projectPath>",
"Provide the path to the root project",
"./",
)
.requiredOption(
"-s, --shapes <shapesPath>",
@ -47,6 +48,6 @@ program
"Provide the path to the ldo folder",
"./.ldo",
)
.action(build);
.action(generateReadme);
program.parse();

@ -1,4 +1,4 @@
import { LdoJsonldContext } from "@ldo/jsonld-dataset-proxy";
import { LdoJsonldContext } from "@ldo/ldo";
/**
* =============================================================================

@ -1,4 +1,4 @@
import { ContextDefinition } from "jsonld";
import { LdoJsonldContext, LdSet } from "@ldo/ldo";
/**
* =============================================================================

@ -11,7 +11,7 @@ npm i @ldo/dataset
## Simple Example
```typescript
import { createDataset } from "o-dataset-pack";
import { createDataset } from "@ldo/dataset";
import { quad, namedNode } from "@rdfjs/data-model";
const dataset = createDataset();
dataset.add(
@ -38,8 +38,8 @@ import { createDataset } from "@ldo/dataset";
import { quad, namedNode, literal } from "@rdfjs/data-model";
// Required for advanced features:
import { dataset as initializeDatasetCore } from "@rdfjs/dataset";
import { ExtendedDatasetFactory } from "o-dataset-pack";
import { Dataset, Quad, DatasetCoreFactory, DatasetCore } from "rdf-js";
import { ExtendedDatasetFactory } from "@ldo/dataset";
import { Dataset, Quad, DatasetCoreFactory, DatasetCore } from "@rdfjs/types";
/**
* Create a dataset with default settings

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

@ -1,3 +0,0 @@
{
"extends": ["../../.eslintrc"]
}

@ -1,23 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

@ -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.

@ -1,12 +0,0 @@
# LDO Demo-React
A demo app to show off the use of LDO.
## Sponsorship
This project was made possible by a grant from NGI Zero Entrust via nlnet. Learn more on the [NLnet project page](https://nlnet.nl/project/SolidUsableApps/).
[<img src="https://nlnet.nl/logo/banner.png" alt="nlnet foundation logo" width="300" />](https://nlnet.nl/)
[<img src="https://nlnet.nl/image/logos/NGI0Entrust_tag.svg" alt="NGI Zero Entrust Logo" width="300" />](https://nlnet.nl/)
## Liscense
MIT

@ -1,28 +0,0 @@
// this file overrides the default CRA configurations (webpack, eslint, babel, etc)
// Ingnore because config scripts can't use the import variable
// eslint-disable-next-line @typescript-eslint/no-var-requires
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
module.exports = {
webpack: {
configure: (config) => {
// Remove ModuleScopePlugin which throws when we try to import something
// outside of src/.
config.resolve.plugins.pop();
// Resolve the path aliases.
config.resolve.plugins.push(new TsconfigPathsPlugin());
// Let Babel compile outside of src/.
const oneOfRule = config.module.rules.find((rule) => rule.oneOf);
const tsRule = oneOfRule.oneOf.find((rule) =>
rule.test.toString().includes("ts|tsx"),
);
tsRule.include = undefined;
tsRule.exclude = /node_modules/;
return config;
},
},
};

@ -1,50 +0,0 @@
{
"name": "@ldo/demo-react",
"version": "0.0.1-alpha.29",
"dependencies": {
"@inrupt/solid-client-authn-browser": "^2.0.0",
"@ldo/solid-react": "^0.0.1-alpha.29",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.15.0",
"react-scripts": "5.0.1",
"uuid": "^9.0.1"
},
"scripts": {
"start": "craco start",
"build": "craco build",
"eject": "react-scripts eject",
"lint": "eslint src/** --fix --no-error-on-unmatched-pattern",
"build:ldo": "ldo build --input src/.shapes --output src/.ldo"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 firefox version",
"last 1 chrome version",
"last 1 safari version"
]
},
"devDependencies": {
"@craco/craco": "^7.1.0",
"@ldo/cli": "^0.0.1-alpha.29",
"@types/jsonld": "^1.5.9",
"@types/react": "^18.2.21",
"@types/shexj": "^2.1.4",
"tsconfig-paths-webpack-plugin": "^4.1.0"
},
"gitHead": "c63f055aab22155b60a5fdee4172979b9c287dfa",
"publishConfig": {
"access": "public"
}
}

@ -1,41 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

@ -1,25 +0,0 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

@ -1,3 +0,0 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

@ -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",
},
};

@ -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.",
},
},
],
},
},
],
};

@ -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<PostSh> = {
schema: postSchema,
shape: "https://example.com/PostSh",
context: postContext,
};

@ -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;
};
}

@ -1,154 +0,0 @@
import { ContextDefinition } from "jsonld";
/**
* =============================================================================
* solidProfileContext: JSONLD Context for solidProfile
* =============================================================================
*/
export const solidProfileContext: ContextDefinition = {
type: {
"@id": "@type",
},
Person: "http://schema.org/Person",
Person2: "http://xmlns.com/foaf/0.1/Person",
fn: {
"@id": "http://www.w3.org/2006/vcard/ns#fn",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
name: {
"@id": "http://xmlns.com/foaf/0.1/name",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
hasAddress: {
"@id": "http://www.w3.org/2006/vcard/ns#hasAddress",
"@type": "@id",
"@container": "@set",
},
countryName: {
"@id": "http://www.w3.org/2006/vcard/ns#country-name",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
locality: {
"@id": "http://www.w3.org/2006/vcard/ns#locality",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
postalCode: {
"@id": "http://www.w3.org/2006/vcard/ns#postal-code",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
region: {
"@id": "http://www.w3.org/2006/vcard/ns#region",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
streetAddress: {
"@id": "http://www.w3.org/2006/vcard/ns#street-address",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
hasEmail: {
"@id": "http://www.w3.org/2006/vcard/ns#hasEmail",
"@type": "@id",
"@container": "@set",
},
Dom: "http://www.w3.org/2006/vcard/ns#Dom",
Home: "http://www.w3.org/2006/vcard/ns#Home",
ISDN: "http://www.w3.org/2006/vcard/ns#ISDN",
Internet: "http://www.w3.org/2006/vcard/ns#Internet",
Intl: "http://www.w3.org/2006/vcard/ns#Intl",
Label: "http://www.w3.org/2006/vcard/ns#Label",
Parcel: "http://www.w3.org/2006/vcard/ns#Parcel",
Postal: "http://www.w3.org/2006/vcard/ns#Postal",
Pref: "http://www.w3.org/2006/vcard/ns#Pref",
Work: "http://www.w3.org/2006/vcard/ns#Work",
X400: "http://www.w3.org/2006/vcard/ns#X400",
value: {
"@id": "http://www.w3.org/2006/vcard/ns#value",
"@type": "@id",
},
hasPhoto: {
"@id": "http://www.w3.org/2006/vcard/ns#hasPhoto",
"@type": "@id",
},
img: {
"@id": "http://xmlns.com/foaf/0.1/img",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
hasTelephone: {
"@id": "http://www.w3.org/2006/vcard/ns#hasTelephone",
"@type": "@id",
"@container": "@set",
},
phone: {
"@id": "http://www.w3.org/2006/vcard/ns#phone",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
organizationName: {
"@id": "http://www.w3.org/2006/vcard/ns#organization-name",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
role: {
"@id": "http://www.w3.org/2006/vcard/ns#role",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
trustedApp: {
"@id": "http://www.w3.org/ns/auth/acl#trustedApp",
"@type": "@id",
"@container": "@set",
},
mode: {
"@id": "http://www.w3.org/ns/auth/acl#mode",
"@container": "@set",
},
Append: "http://www.w3.org/ns/auth/acl#Append",
Control: "http://www.w3.org/ns/auth/acl#Control",
Read: "http://www.w3.org/ns/auth/acl#Read",
Write: "http://www.w3.org/ns/auth/acl#Write",
origin: {
"@id": "http://www.w3.org/ns/auth/acl#origin",
"@type": "@id",
},
key: {
"@id": "http://www.w3.org/ns/auth/cert#key",
"@type": "@id",
"@container": "@set",
},
modulus: {
"@id": "http://www.w3.org/ns/auth/cert#modulus",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
exponent: {
"@id": "http://www.w3.org/ns/auth/cert#exponent",
"@type": "http://www.w3.org/2001/XMLSchema#integer",
},
inbox: {
"@id": "http://www.w3.org/ns/ldp#inbox",
"@type": "@id",
},
preferencesFile: {
"@id": "http://www.w3.org/ns/pim/space#preferencesFile",
"@type": "@id",
},
storage: {
"@id": "http://www.w3.org/ns/pim/space#storage",
"@type": "@id",
"@container": "@set",
},
account: {
"@id": "http://www.w3.org/ns/solid/terms#account",
"@type": "@id",
},
privateTypeIndex: {
"@id": "http://www.w3.org/ns/solid/terms#privateTypeIndex",
"@type": "@id",
"@container": "@set",
},
publicTypeIndex: {
"@id": "http://www.w3.org/ns/solid/terms#publicTypeIndex",
"@type": "@id",
"@container": "@set",
},
knows: {
"@id": "http://xmlns.com/foaf/0.1/knows",
"@type": "@id",
"@container": "@set",
},
};

@ -1,749 +0,0 @@
import { Schema } from "shexj";
/**
* =============================================================================
* solidProfileSchema: ShexJ Schema for solidProfile
* =============================================================================
*/
export const solidProfileSchema: Schema = {
type: "Schema",
shapes: [
{
id: "https://shaperepo.com/schemas/solidProfile#SolidProfileShape",
type: "ShapeDecl",
shapeExpr: {
type: "Shape",
expression: {
type: "EachOf",
expressions: [
{
type: "TripleConstraint",
predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
valueExpr: {
type: "NodeConstraint",
values: ["http://schema.org/Person"],
},
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "Defines the node as a Person (from Schema.org)",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
valueExpr: {
type: "NodeConstraint",
values: ["http://xmlns.com/foaf/0.1/Person"],
},
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "Defines the node as a Person (from foaf)",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/2006/vcard/ns#fn",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#string",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"The formatted name of a person. Example: John Smith",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://xmlns.com/foaf/0.1/name",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#string",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "An alternate way to define a person's name.",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/2006/vcard/ns#hasAddress",
valueExpr:
"https://shaperepo.com/schemas/solidProfile#AddressShape",
min: 0,
max: -1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "The person's street address.",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/2006/vcard/ns#hasEmail",
valueExpr:
"https://shaperepo.com/schemas/solidProfile#EmailShape",
min: 0,
max: -1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "The person's email.",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/2006/vcard/ns#hasPhoto",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "A link to the person's photo",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://xmlns.com/foaf/0.1/img",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#string",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "Photo link but in string form",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/2006/vcard/ns#hasTelephone",
valueExpr:
"https://shaperepo.com/schemas/solidProfile#PhoneNumberShape",
min: 0,
max: -1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "Person's telephone number",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/2006/vcard/ns#phone",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#string",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"An alternative way to define a person's telephone number using a string",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/2006/vcard/ns#organization-name",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#string",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"The name of the organization with which the person is affiliated",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/2006/vcard/ns#role",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#string",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"The name of the person's role in their organization",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/auth/acl#trustedApp",
valueExpr:
"https://shaperepo.com/schemas/solidProfile#TrustedAppShape",
min: 0,
max: -1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"A list of app origins that are trusted by this user",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/auth/cert#key",
valueExpr:
"https://shaperepo.com/schemas/solidProfile#RSAPublicKeyShape",
min: 0,
max: -1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"A list of RSA public keys that are associated with private keys the user holds.",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/ldp#inbox",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"The user's LDP inbox to which apps can post notifications",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/pim/space#preferencesFile",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "The user's preferences",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/pim/space#storage",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
min: 0,
max: -1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"The location of a Solid storage server related to this WebId",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/solid/terms#account",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "The user's account",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/solid/terms#privateTypeIndex",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
min: 0,
max: -1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"A registry of all types used on the user's Pod (for private access only)",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/solid/terms#publicTypeIndex",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
min: 0,
max: -1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"A registry of all types used on the user's Pod (for public access)",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://xmlns.com/foaf/0.1/knows",
valueExpr:
"https://shaperepo.com/schemas/solidProfile#SolidProfileShape",
min: 0,
max: -1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"A list of WebIds for all the people this user knows.",
},
},
],
},
],
},
extra: ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"],
},
},
{
id: "https://shaperepo.com/schemas/solidProfile#AddressShape",
type: "ShapeDecl",
shapeExpr: {
type: "Shape",
expression: {
type: "EachOf",
expressions: [
{
type: "TripleConstraint",
predicate: "http://www.w3.org/2006/vcard/ns#country-name",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#string",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "The name of the user's country of residence",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/2006/vcard/ns#locality",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#string",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"The name of the user's locality (City, Town etc.) of residence",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/2006/vcard/ns#postal-code",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#string",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "The user's postal code",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/2006/vcard/ns#region",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#string",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"The name of the user's region (State, Province etc.) of residence",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/2006/vcard/ns#street-address",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#string",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "The user's street address",
},
},
],
},
],
},
},
},
{
id: "https://shaperepo.com/schemas/solidProfile#EmailShape",
type: "ShapeDecl",
shapeExpr: {
type: "Shape",
expression: {
type: "EachOf",
expressions: [
{
type: "TripleConstraint",
predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
valueExpr: {
type: "NodeConstraint",
values: [
"http://www.w3.org/2006/vcard/ns#Dom",
"http://www.w3.org/2006/vcard/ns#Home",
"http://www.w3.org/2006/vcard/ns#ISDN",
"http://www.w3.org/2006/vcard/ns#Internet",
"http://www.w3.org/2006/vcard/ns#Intl",
"http://www.w3.org/2006/vcard/ns#Label",
"http://www.w3.org/2006/vcard/ns#Parcel",
"http://www.w3.org/2006/vcard/ns#Postal",
"http://www.w3.org/2006/vcard/ns#Pref",
"http://www.w3.org/2006/vcard/ns#Work",
"http://www.w3.org/2006/vcard/ns#X400",
],
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "The type of email.",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/2006/vcard/ns#value",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"The value of an email as a mailto link (Example <mailto:jane@example.com>)",
},
},
],
},
],
},
extra: ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"],
},
},
{
id: "https://shaperepo.com/schemas/solidProfile#PhoneNumberShape",
type: "ShapeDecl",
shapeExpr: {
type: "Shape",
expression: {
type: "EachOf",
expressions: [
{
type: "TripleConstraint",
predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
valueExpr: {
type: "NodeConstraint",
values: [
"http://www.w3.org/2006/vcard/ns#Dom",
"http://www.w3.org/2006/vcard/ns#Home",
"http://www.w3.org/2006/vcard/ns#ISDN",
"http://www.w3.org/2006/vcard/ns#Internet",
"http://www.w3.org/2006/vcard/ns#Intl",
"http://www.w3.org/2006/vcard/ns#Label",
"http://www.w3.org/2006/vcard/ns#Parcel",
"http://www.w3.org/2006/vcard/ns#Postal",
"http://www.w3.org/2006/vcard/ns#Pref",
"http://www.w3.org/2006/vcard/ns#Work",
"http://www.w3.org/2006/vcard/ns#X400",
],
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "They type of Phone Number",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/2006/vcard/ns#value",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"The value of a phone number as a tel link (Example <tel:555-555-5555>)",
},
},
],
},
],
},
extra: ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"],
},
},
{
id: "https://shaperepo.com/schemas/solidProfile#TrustedAppShape",
type: "ShapeDecl",
shapeExpr: {
type: "Shape",
expression: {
type: "EachOf",
expressions: [
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/auth/acl#mode",
valueExpr: {
type: "NodeConstraint",
values: [
"http://www.w3.org/ns/auth/acl#Append",
"http://www.w3.org/ns/auth/acl#Control",
"http://www.w3.org/ns/auth/acl#Read",
"http://www.w3.org/ns/auth/acl#Write",
],
},
min: 1,
max: -1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "The level of access provided to this origin",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/auth/acl#origin",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "The app origin the user trusts",
},
},
],
},
],
},
},
},
{
id: "https://shaperepo.com/schemas/solidProfile#RSAPublicKeyShape",
type: "ShapeDecl",
shapeExpr: {
type: "Shape",
expression: {
type: "EachOf",
expressions: [
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/auth/cert#modulus",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#string",
},
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "RSA Modulus",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/auth/cert#exponent",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#integer",
},
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "RSA Exponent",
},
},
],
},
],
},
},
},
],
};

@ -1,71 +0,0 @@
import { ShapeType } from "@ldo/ldo";
import { solidProfileSchema } from "./solidProfile.schema";
import { solidProfileContext } from "./solidProfile.context";
import {
SolidProfileShape,
AddressShape,
EmailShape,
PhoneNumberShape,
TrustedAppShape,
RSAPublicKeyShape,
} from "./solidProfile.typings";
/**
* =============================================================================
* LDO ShapeTypes solidProfile
* =============================================================================
*/
/**
* SolidProfileShape ShapeType
*/
export const SolidProfileShapeShapeType: ShapeType<SolidProfileShape> = {
schema: solidProfileSchema,
shape: "https://shaperepo.com/schemas/solidProfile#SolidProfileShape",
context: solidProfileContext,
};
/**
* AddressShape ShapeType
*/
export const AddressShapeShapeType: ShapeType<AddressShape> = {
schema: solidProfileSchema,
shape: "https://shaperepo.com/schemas/solidProfile#AddressShape",
context: solidProfileContext,
};
/**
* EmailShape ShapeType
*/
export const EmailShapeShapeType: ShapeType<EmailShape> = {
schema: solidProfileSchema,
shape: "https://shaperepo.com/schemas/solidProfile#EmailShape",
context: solidProfileContext,
};
/**
* PhoneNumberShape ShapeType
*/
export const PhoneNumberShapeShapeType: ShapeType<PhoneNumberShape> = {
schema: solidProfileSchema,
shape: "https://shaperepo.com/schemas/solidProfile#PhoneNumberShape",
context: solidProfileContext,
};
/**
* TrustedAppShape ShapeType
*/
export const TrustedAppShapeShapeType: ShapeType<TrustedAppShape> = {
schema: solidProfileSchema,
shape: "https://shaperepo.com/schemas/solidProfile#TrustedAppShape",
context: solidProfileContext,
};
/**
* RSAPublicKeyShape ShapeType
*/
export const RSAPublicKeyShapeShapeType: ShapeType<RSAPublicKeyShape> = {
schema: solidProfileSchema,
shape: "https://shaperepo.com/schemas/solidProfile#RSAPublicKeyShape",
context: solidProfileContext,
};

@ -1,293 +0,0 @@
import { ContextDefinition } from "jsonld";
/**
* =============================================================================
* Typescript Typings for solidProfile
* =============================================================================
*/
/**
* SolidProfileShape Type
*/
export interface SolidProfileShape {
"@id"?: string;
"@context"?: ContextDefinition;
/**
* Defines the node as a Person (from Schema.org) | Defines the node as a Person (from foaf)
*/
type: (
| {
"@id": "Person";
}
| {
"@id": "Person2";
}
)[];
/**
* The formatted name of a person. Example: John Smith
*/
fn?: string;
/**
* An alternate way to define a person's name.
*/
name?: string;
/**
* The person's street address.
*/
hasAddress?: AddressShape[];
/**
* The person's email.
*/
hasEmail?: EmailShape[];
/**
* A link to the person's photo
*/
hasPhoto?: {
"@id": string;
};
/**
* Photo link but in string form
*/
img?: string;
/**
* Person's telephone number
*/
hasTelephone?: PhoneNumberShape[];
/**
* An alternative way to define a person's telephone number using a string
*/
phone?: string;
/**
* The name of the organization with which the person is affiliated
*/
organizationName?: string;
/**
* The name of the person's role in their organization
*/
role?: string;
/**
* A list of app origins that are trusted by this user
*/
trustedApp?: TrustedAppShape[];
/**
* A list of RSA public keys that are associated with private keys the user holds.
*/
key?: RSAPublicKeyShape[];
/**
* The user's LDP inbox to which apps can post notifications
*/
inbox: {
"@id": string;
};
/**
* The user's preferences
*/
preferencesFile?: {
"@id": string;
};
/**
* The location of a Solid storage server related to this WebId
*/
storage?: {
"@id": string;
}[];
/**
* The user's account
*/
account?: {
"@id": string;
};
/**
* A registry of all types used on the user's Pod (for private access only)
*/
privateTypeIndex?: {
"@id": string;
}[];
/**
* A registry of all types used on the user's Pod (for public access)
*/
publicTypeIndex?: {
"@id": string;
}[];
/**
* A list of WebIds for all the people this user knows.
*/
knows?: SolidProfileShape[];
}
/**
* AddressShape Type
*/
export interface AddressShape {
"@id"?: string;
"@context"?: ContextDefinition;
/**
* The name of the user's country of residence
*/
countryName?: string;
/**
* The name of the user's locality (City, Town etc.) of residence
*/
locality?: string;
/**
* The user's postal code
*/
postalCode?: string;
/**
* The name of the user's region (State, Province etc.) of residence
*/
region?: string;
/**
* The user's street address
*/
streetAddress?: string;
}
/**
* EmailShape Type
*/
export interface EmailShape {
"@id"?: string;
"@context"?: ContextDefinition;
/**
* The type of email.
*/
type?:
| {
"@id": "Dom";
}
| {
"@id": "Home";
}
| {
"@id": "ISDN";
}
| {
"@id": "Internet";
}
| {
"@id": "Intl";
}
| {
"@id": "Label";
}
| {
"@id": "Parcel";
}
| {
"@id": "Postal";
}
| {
"@id": "Pref";
}
| {
"@id": "Work";
}
| {
"@id": "X400";
};
/**
* The value of an email as a mailto link (Example <mailto:jane@example.com>)
*/
value: {
"@id": string;
};
}
/**
* PhoneNumberShape Type
*/
export interface PhoneNumberShape {
"@id"?: string;
"@context"?: ContextDefinition;
/**
* They type of Phone Number
*/
type?:
| {
"@id": "Dom";
}
| {
"@id": "Home";
}
| {
"@id": "ISDN";
}
| {
"@id": "Internet";
}
| {
"@id": "Intl";
}
| {
"@id": "Label";
}
| {
"@id": "Parcel";
}
| {
"@id": "Postal";
}
| {
"@id": "Pref";
}
| {
"@id": "Work";
}
| {
"@id": "X400";
};
/**
* The value of a phone number as a tel link (Example <tel:555-555-5555>)
*/
value: {
"@id": string;
};
}
/**
* TrustedAppShape Type
*/
export interface TrustedAppShape {
"@id"?: string;
"@context"?: ContextDefinition;
/**
* The level of access provided to this origin
*/
mode: (
| {
"@id": "Append";
}
| {
"@id": "Control";
}
| {
"@id": "Read";
}
| {
"@id": "Write";
}
)[];
/**
* The app origin the user trusts
*/
origin: {
"@id": string;
};
}
/**
* RSAPublicKeyShape Type
*/
export interface RSAPublicKeyShape {
"@id"?: string;
"@context"?: ContextDefinition;
/**
* RSA Modulus
*/
modulus: string;
/**
* RSA Exponent
*/
exponent: number;
}

@ -1,23 +0,0 @@
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX ex: <https://example.com/>
BASE <http://schema.org/>
ex:PostSh {
a [<SocialMediaPosting> <CreativeWork> <Thing>] ;
<articleBody> xsd:string?
// rdfs:label '''articleBody'''
// rdfs:comment '''The actual body of the article. ''' ;
<uploadDate> xsd:date
// rdfs:label '''uploadDate'''
// rdfs:comment '''Date when this media object was uploaded to this site.''' ;
<image> IRI ?
// rdfs:label '''image'''
// rdfs:comment '''A media object that encodes this CreativeWork. This property is a synonym for encoding.''' ;
<publisher> IRI
// rdfs:label '''publisher'''
// rdfs:comment '''The publisher of the creative work.''' ;
}
// rdfs:label '''SocialMediaPost'''
// rdfs:comment '''A post to a social media platform, including blog posts, tweets, Facebook posts, etc.'''

@ -1,121 +0,0 @@
PREFIX srs: <https://shaperepo.com/schemas/solidProfile#>
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX schem: <http://schema.org/>
PREFIX vcard: <http://www.w3.org/2006/vcard/ns#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX acl: <http://www.w3.org/ns/auth/acl#>
PREFIX cert: <http://www.w3.org/ns/auth/cert#>
PREFIX ldp: <http://www.w3.org/ns/ldp#>
PREFIX sp: <http://www.w3.org/ns/pim/space#>
PREFIX solid: <http://www.w3.org/ns/solid/terms#>
srs:SolidProfileShape EXTRA a {
a [ schem:Person ]
// rdfs:comment "Defines the node as a Person (from Schema.org)" ;
a [ foaf:Person ]
// rdfs:comment "Defines the node as a Person (from foaf)" ;
vcard:fn xsd:string ?
// rdfs:comment "The formatted name of a person. Example: John Smith" ;
foaf:name xsd:string ?
// rdfs:comment "An alternate way to define a person's name." ;
vcard:hasAddress @srs:AddressShape *
// rdfs:comment "The person's street address." ;
vcard:hasEmail @srs:EmailShape *
// rdfs:comment "The person's email." ;
vcard:hasPhoto IRI ?
// rdfs:comment "A link to the person's photo" ;
foaf:img xsd:string ?
// rdfs:comment "Photo link but in string form" ;
vcard:hasTelephone @srs:PhoneNumberShape *
// rdfs:comment "Person's telephone number" ;
vcard:phone xsd:string ?
// rdfs:comment "An alternative way to define a person's telephone number using a string" ;
vcard:organization-name xsd:string ?
// rdfs:comment "The name of the organization with which the person is affiliated" ;
vcard:role xsd:string ?
// rdfs:comment "The name of the person's role in their organization" ;
acl:trustedApp @srs:TrustedAppShape *
// rdfs:comment "A list of app origins that are trusted by this user" ;
cert:key @srs:RSAPublicKeyShape *
// rdfs:comment "A list of RSA public keys that are associated with private keys the user holds." ;
ldp:inbox IRI
// rdfs:comment "The user's LDP inbox to which apps can post notifications" ;
sp:preferencesFile IRI ?
// rdfs:comment "The user's preferences" ;
sp:storage IRI *
// rdfs:comment "The location of a Solid storage server related to this WebId" ;
solid:account IRI ?
// rdfs:comment "The user's account" ;
solid:privateTypeIndex IRI *
// rdfs:comment "A registry of all types used on the user's Pod (for private access only)" ;
solid:publicTypeIndex IRI *
// rdfs:comment "A registry of all types used on the user's Pod (for public access)" ;
foaf:knows @srs:SolidProfileShape *
// rdfs:comment "A list of WebIds for all the people this user knows." ;
}
srs:AddressShape {
vcard:country-name xsd:string ?
// rdfs:comment "The name of the user's country of residence" ;
vcard:locality xsd:string ?
// rdfs:comment "The name of the user's locality (City, Town etc.) of residence" ;
vcard:postal-code xsd:string ?
// rdfs:comment "The user's postal code" ;
vcard:region xsd:string ?
// rdfs:comment "The name of the user's region (State, Province etc.) of residence" ;
vcard:street-address xsd:string ?
// rdfs:comment "The user's street address" ;
}
srs:EmailShape EXTRA a {
a [
vcard:Dom
vcard:Home
vcard:ISDN
vcard:Internet
vcard:Intl
vcard:Label
vcard:Parcel
vcard:Postal
vcard:Pref
vcard:Work
vcard:X400
] ?
// rdfs:comment "The type of email." ;
vcard:value IRI
// rdfs:comment "The value of an email as a mailto link (Example <mailto:jane@example.com>)" ;
}
srs:PhoneNumberShape EXTRA a {
a [
vcard:Dom
vcard:Home
vcard:ISDN
vcard:Internet
vcard:Intl
vcard:Label
vcard:Parcel
vcard:Postal
vcard:Pref
vcard:Work
vcard:X400
] ?
// rdfs:comment "They type of Phone Number" ;
vcard:value IRI
// rdfs:comment "The value of a phone number as a tel link (Example <tel:555-555-5555>)" ;
}
srs:TrustedAppShape {
acl:mode [acl:Append acl:Control acl:Read acl:Write] +
// rdfs:comment "The level of access provided to this origin" ;
acl:origin IRI
// rdfs:comment "The app origin the user trusts"
}
srs:RSAPublicKeyShape {
cert:modulus xsd:string
// rdfs:comment "RSA Modulus" ;
cert:exponent xsd:integer
// rdfs:comment "RSA Exponent" ;
}

@ -1,13 +0,0 @@
import type { FunctionComponent } from "react";
import React from "react";
import { Router } from "./Layout";
import { BrowserSolidLdoProvider } from "@ldo/solid-react";
const ProfileApp: FunctionComponent = () => {
return (
<BrowserSolidLdoProvider>
<Router />
</BrowserSolidLdoProvider>
);
};
export default ProfileApp;

@ -1,65 +0,0 @@
import type { FunctionComponent } from "react";
import React, { useCallback } from "react";
import {
BrowserSolidLdoProvider,
useResource,
useSolidAuth,
useSubject,
} from "@ldo/solid-react";
import { SolidProfileShapeShapeType } from "./.ldo/solidProfile.shapeTypes";
import { changeData, commitData } from "@ldo/solid";
// The base component for the app
const App: FunctionComponent = () => {
return (
/* The application should be surrounded with the BrowserSolidLdoProvider
this will set up all the underlying infrastructure for the application */
<BrowserSolidLdoProvider>
<Login />
</BrowserSolidLdoProvider>
);
};
// A component that handles login
const Login: FunctionComponent = () => {
// Get login information using the "useSolidAuth" hook
const { login, logout, session } = useSolidAuth();
const onLogin = useCallback(() => {
const issuer = prompt("What is your Solid IDP?");
// Call the "login" function to initiate login
if (issuer) login(issuer);
}, []);
// You can use session.isLoggedIn to check if the user is logged in
if (session.isLoggedIn) {
return (
<div>
{/* Get the user's webId from session.webId */}
<p>Logged in as {session.webId}</p>
{/* Use the logout function to log out */}
<button onClick={logout}>Log Out</button>
<Profile />
</div>
);
}
return <button onClick={onLogin}>Log In</button>;
};
const Profile: FunctionComponent = () => {
const { session } = useSolidAuth();
const resource = useResource(session.webId);
const profile = useSubject(SolidProfileShapeShapeType, session.webId);
const onNameChange = useCallback(async (e) => {
// Ensure that the
if (!profile || !resource) return;
const cProfile = changeData(profile, resource);
cProfile.name = e.target.value;
await commitData(cProfile);
}, []);
return <input type="text" value={profile?.name} onChange={onNameChange} />;
};
export default App;

@ -1,54 +0,0 @@
import { useState } from "react";
import type { FunctionComponent } from "react";
import React from "react";
import { useResource, useSolidAuth, useSubject } from "@ldo/solid-react";
import { SolidProfileShapeShapeType } from "./.ldo/solidProfile.shapeTypes";
import { Link } from "react-router-dom";
const DEFAULT_ISSUER = "https://solidweb.me";
export const LoggedInHeader: FunctionComponent<{ webId: string }> = ({
webId,
}) => {
const webIdResource = useResource(webId);
const profile = useSubject(SolidProfileShapeShapeType, webId);
const { logout } = useSolidAuth();
return (
<>
<span>
Logged in as {webId}. Welcome{" "}
{webIdResource.isReading() ? "LOADING NAME" : profile.fn}
</span>
<button onClick={logout}>Log Out</button>
</>
);
};
export const Header: FunctionComponent = () => {
const [issuer, setIssuer] = useState(DEFAULT_ISSUER);
const { login, signUp, session } = useSolidAuth();
return (
<header>
<div style={{ display: "flex" }}>
{session.isLoggedIn ? (
<LoggedInHeader webId={session.webId!} />
) : (
<>
<input
type="text"
value={issuer}
onChange={(e) => setIssuer(e.target.value)}
/>
<button onClick={() => login(issuer)}>Log In</button>
<button onClick={() => signUp(issuer)}>Sign Up</button>
</>
)}
</div>
<p>
<Link to="/">Blog</Link>
{" "}
<Link to="/profile">Profile</Link>
</p>
</header>
);
};

@ -1,50 +0,0 @@
import { useSolidAuth } from "@ldo/solid-react";
import React, { Fragment } from "react";
import type { FunctionComponent } from "react";
import { createBrowserRouter, Outlet, RouterProvider } from "react-router-dom";
import { Blog } from "./blog/Blog";
import { PostPage } from "./post/PostPage";
import { Header } from "./Header";
import { MainContainerProvider } from "./MainContainerProvider";
import { Profile } from "./profile/Profile";
export const Layout: FunctionComponent = () => {
const { session } = useSolidAuth();
return (
<div>
<Header />
<hr />
<MainContainerProvider>
{session.isLoggedIn ? <Outlet /> : <Fragment />}
</MainContainerProvider>
</div>
);
};
const router = createBrowserRouter([
{
element: <Layout />,
children: [
{
path: "/",
element: <Blog />,
},
{
path: "/media/:uri",
element: <PostPage />,
},
{
path: "/profile",
element: <Profile />,
},
],
},
]);
export const Router: FunctionComponent = () => {
const { ranInitialAuthCheck } = useSolidAuth();
if (!ranInitialAuthCheck) {
return <p>Loading</p>;
}
return <RouterProvider router={router} />;
};

@ -1,73 +0,0 @@
import React, { useState, useEffect, createContext } from "react";
import type { FunctionComponent, PropsWithChildren } from "react";
import type { Container, LeafUri } from "@ldo/solid";
import { useSolidAuth, useLdo, useResource } from "@ldo/solid-react";
export const MainContainerContext = createContext<Container | undefined>(
undefined,
);
const MainContainerSubProvider: FunctionComponent<
PropsWithChildren<{ uri?: string }>
> = ({ uri, children }) => {
const mainContainer = useResource(uri);
return (
<MainContainerContext.Provider value={mainContainer as Container}>
{children}
</MainContainerContext.Provider>
);
};
export const MainContainerProvider: FunctionComponent<PropsWithChildren> = ({
children,
}) => {
const [mainContainer, setMainContainer] = useState<Container | undefined>();
const { session } = useSolidAuth();
const { getResource } = useLdo();
useEffect(() => {
if (session.webId) {
const webIdResource = getResource(session.webId as LeafUri);
webIdResource.getRootContainer().then(async (rootContainer) => {
if (rootContainer.isError) {
alert(rootContainer.message);
return;
}
const mainContainer = getResource(`${rootContainer.uri}demo-react/`);
setMainContainer(mainContainer);
const createResult = await mainContainer.createIfAbsent();
// Only set the access rules if the create was a success.
if (createResult.type === "createSuccess") {
await mainContainer.setWac({
public: {
read: true,
write: false,
append: false,
control: false,
},
authenticated: {
read: true,
write: false,
append: false,
control: false,
},
agent: {
[session.webId!]: {
read: true,
write: true,
append: true,
control: true,
},
},
});
}
});
}
}, [session.webId]);
return (
<MainContainerSubProvider uri={mainContainer?.uri}>
{children}
</MainContainerSubProvider>
);
};

@ -1,30 +0,0 @@
import React, { Fragment, useContext } from "react";
import type { FunctionComponent } from "react";
import { MainContainerContext } from "../MainContainerProvider";
import { Post } from "../post/Post";
import { MakePost } from "./MakePost";
export const Blog: FunctionComponent = () => {
const mainContainer = useContext(MainContainerContext);
if (mainContainer === undefined) {
return <p>Loading...</p>;
}
if (mainContainer.isDoingInitialFetch()) {
return <p>Loading Blob</p>;
}
return (
<div>
<div>
<MakePost mainContainer={mainContainer} />
</div>
<hr />
{mainContainer.children().map((child) => (
<Fragment key={child.uri}>
<Post uri={child.uri} />
<hr />
</Fragment>
))}
</div>
);
};

@ -1,92 +0,0 @@
import React, { useCallback, useState, useRef } from "react";
import type { FunctionComponent, FormEvent } from "react";
import type { Container, Leaf, LeafUri } from "@ldo/solid";
import { v4 } from "uuid";
import { useLdo, useSolidAuth } from "@ldo/solid-react";
import { PostShShapeType } from "../.ldo/post.shapeTypes";
export const MakePost: FunctionComponent<{ mainContainer: Container }> = ({
mainContainer,
}) => {
const [message, setMessage] = useState("");
const [selectedFile, setSelectedFile] = useState<File | undefined>();
const fileInputRef = useRef<HTMLInputElement | null>(null);
const { createData, commitData } = useLdo();
const { session } = useSolidAuth();
const onSubmit = useCallback(
async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
// Create the container file
const mediaContainerResult = await mainContainer.createChildAndOverwrite(
`${v4()}/`,
);
if (mediaContainerResult.isError) {
alert(mediaContainerResult.message);
return;
}
const mediaContainer = mediaContainerResult.resource;
// Upload Image
let uploadedImage: Leaf | undefined;
if (selectedFile) {
const result = await mediaContainer.uploadChildAndOverwrite(
selectedFile.name as LeafUri,
selectedFile,
selectedFile.type,
);
if (result.isError) {
alert(result.message);
await mediaContainer.delete();
return;
}
uploadedImage = result.resource;
}
// Create Post
const indexResource = mediaContainer.child("index.ttl");
const post = createData(
PostShShapeType,
indexResource.uri,
indexResource,
);
post.articleBody = message;
if (uploadedImage) {
post.image = { "@id": uploadedImage.uri };
}
if (session.webId) {
post.publisher = { "@id": session.webId };
}
post.type = { "@id": "SocialMediaPosting" };
post.uploadDate = new Date().toISOString();
const result = await commitData(post);
if (result.isError) {
alert(result.message);
}
// Clear the UI after Upload
setMessage("");
setSelectedFile(undefined);
if (fileInputRef.current) fileInputRef.current.value = "";
},
[message, selectedFile, session.webId],
);
return (
<form onSubmit={onSubmit}>
<input
type="text"
placeholder="Make a Post"
value={message}
onChange={(e) => setMessage(e.target.value)}
/>
<input
type="file"
accept="image/*"
ref={fileInputRef}
onChange={(e) => setSelectedFile(e.target.files?.[0])}
/>
<input type="submit" value="Post" />
</form>
);
};

@ -1,8 +0,0 @@
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement,
);
root.render(<App />);

@ -1,42 +0,0 @@
import React, { useCallback } from "react";
import type { FunctionComponent } from "react";
import { useLdo, useResource, useSubject } from "@ldo/solid-react";
import { PostShShapeType } from "../.ldo/post.shapeTypes";
import { useNavigate } from "react-router-dom";
import { PostedBy } from "./PostedBy";
export const Post: FunctionComponent<{ uri: string }> = ({ uri }) => {
const navigate = useNavigate();
const mediaResource = useResource(`${uri}index.ttl`);
const post = useSubject(PostShShapeType, mediaResource.uri);
const { getResource } = useLdo();
const deletePost = useCallback(async () => {
const postContainer = getResource(uri);
const result = await postContainer.delete();
if (result.isError) {
alert(result.message);
}
}, [uri]);
if (mediaResource.isReading()) {
return <p>Loading Post...</p>;
} else if (mediaResource.status.isError) {
return <p>Error: {mediaResource.status.message}</p>;
} else if (mediaResource.isAbsent()) {
return <p>Post does not exist.</p>;
}
return (
<div>
{post.publisher?.["@id"] && <PostedBy webId={post.publisher["@id"]} />}
<div
onClick={() => navigate(`/media/${encodeURIComponent(uri)}`)}
style={{ cursor: "pointer" }}
>
{post.articleBody && <p>{post.articleBody}</p>}
{post.image && <img src={post.image["@id"]} style={{ height: 300 }} />}
</div>
<button onClick={deletePost}>Delete Post</button>
</div>
);
};

@ -1,16 +0,0 @@
import React from "react";
import type { FunctionComponent } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { Post } from "./Post";
export const PostPage: FunctionComponent = () => {
const navigate = useNavigate();
const { uri } = useParams();
return (
<div>
<button onClick={() => navigate("/")}>Back to Feed</button>
{uri ? <Post uri={uri} /> : <p>No URI Present</p>}
</div>
);
};

@ -1,16 +0,0 @@
import type { FunctionComponent } from "react";
import React from "react";
import { useResource, useSubject } from "@ldo/solid-react";
import { SolidProfileShapeShapeType } from "../.ldo/solidProfile.shapeTypes";
export const PostedBy: FunctionComponent<{ webId: string }> = ({ webId }) => {
const webIdResource = useResource(webId);
const profile = useSubject(SolidProfileShapeShapeType, webId);
if (webIdResource.isReading()) {
return <p>Loading Profile...</p>;
} else if (webIdResource.status.isError) {
return <p>Error: {webIdResource.status.message}</p>;
}
return <p>Posted By: {profile.fn}</p>;
};

@ -1,34 +0,0 @@
import {
useLdo,
useResource,
useSolidAuth,
useSubject,
} from "@ldo/solid-react";
import type { ChangeEvent } from "react";
import React, { useCallback, type FunctionComponent } from "react";
import { SolidProfileShapeShapeType } from "../.ldo/solidProfile.shapeTypes";
export const Profile: FunctionComponent = () => {
const { session } = useSolidAuth();
const profile = useSubject(SolidProfileShapeShapeType, session.webId);
const webIdResource = useResource(session.webId);
const { changeData, commitData } = useLdo();
const onNameChange = useCallback(
async (e: ChangeEvent<HTMLInputElement>) => {
if (profile && webIdResource) {
const cProfile = changeData(profile, webIdResource);
cProfile.fn = e.target.value;
await commitData(cProfile);
}
},
[profile, webIdResource],
);
return (
<>
<label>Name</label>
<input type="text" value={profile?.fn || ""} onChange={onNameChange} />
</>
);
};

@ -1,20 +0,0 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"./src"
]
}

@ -10,7 +10,7 @@ const person = jsonldDatasetProxy(
PersonContext
).fromSubject<IPerson>(namedNode("http://example.com/Person1"));
person.age = 23;
person.name.push("John");
person.name.add("John");
```
are equivalent to:
@ -47,9 +47,9 @@ npm install @ldo/jsonld-dataset-proxy
## Simple Example
```typescript
import jsonldDatasetProxy, { write } from "jsonld-dataset-proxy";
import jsonldDatasetProxy, { write, LdSet } from "jsonld-dataset-proxy";
import { ContextDefinition } from "jsonld";
import { serializedToDataset } from "o-dataset-pack";
import { serializedToDataset } from "@ldo/dataset";
import { namedNode } from "@rdfjs/data-model";
async function start() {
@ -72,9 +72,9 @@ async function start() {
).fromSubject<IPerson>(namedNode("http://example.com/Person1"));
// Make Modifications
person.age = 23;
person.name.push("John");
person.name.add("John");
write(namedNode("http://example.com/otherGraph")).using(person);
person.name.push("Smith");
person.name.add("Smith");
console.log(dataset.toString());
// Logs:
@ -86,7 +86,7 @@ async function start() {
// Person Typescript Typing
interface IPerson {
name: string[];
name: LdSet<string>;
age: number;
}
@ -117,7 +117,7 @@ start();
- [Getting Field Values and Traversing](#getting-field-values-and-traversing)
- [Setting a Primitive](#setting-a-primitive)
- [Setting an Object](#setting-an-object)
- [Array Methods](#array-methods)
- [Set Methods](#set-methods)
- [Overwriting an Object](#overwriting-an-object)
- [Changing an Object's Id](#changing-an-objects-id)
- [Removing an Object Connection](#removing-an-object-connection)
@ -129,23 +129,23 @@ start();
- [`write(...graphs).usingCopy(...jsonldDatasetProxies)`](#writegraphsusingcopyjsonlddatasetproxies)
- [Detecting a the graph of specific information](#detecting-a-the-graph-of-specific-information)
For the most part, a JSONLD Dataset Proxy has parity with JavaScript Object Literals. However, there are a few differences to highlight. This section details how you would do different tasks.
For the most part, you can think of a JSONLD Dataset Proxy as a JavaScript Object Literal with "Sets" instead of arrays.
### Defining a Context and Type
The first step to getting a JSONLD Dataset Proxy is defining the JSONLD Context and TypeScript Typings. This can either be done through a [generator](https://github.com/o-development/shexj2typeandcontext) or defining them manually.
The first step to getting a JSONLD Dataset Proxy is defining the JSONLD Context and TypeScript Typings. This can either be done through a [generator](https://ldo.js.org/latest/api/cli/build/) or defining them manually.
In this example typescript typing `IPerson` is an interface that represents a person. Notice the `@id` and `@context` fields. Be sure to include them in your interfaces if you wish to use those properties.
```typescript
import { ContextDefinition } from "jsonld";
import { LdoJsonldContext, LdSet } from "@ldo/jsonld-dataset-proxy";
interface IPerson {
"@id"?: string;
"@context"?: ContextDefinition;
name?: string[];
"@context"?: LdoJsonldContext;
name?: LdSet<string>;
age?: number;
bestFriend?: IPerson;
knows?: IPerson[];
knows?: LdSet<IPerson>;
}
```
@ -250,12 +250,14 @@ friendsOfPerson1.forEach((person) => {
`fromJson` will take any regular Json, add the information to the dataset, and return a Jsonld Dataset Proxy representing the given data.
```typescript
import { jsonldDatasetProxy, set } from "@ldo/jsonld-dataset-proxy";
const person2 = jsonldDatasetProxy(
dataset,
PersonContext
).fromJson<IPerson>({
"@id": "http://example.com/Person2",
name: ["Jane", "Doe"],
name: set("Jane", "Doe"),
birthdate: "1990/11/03",
age: 33,
});
@ -300,7 +302,7 @@ console.log(person.name?.reduce((agg, cur) => agg + cur, "")); // JonathanJohn
// But this isn't recommened. The library will do its best to maintain the
// ordering in the array, but as datasets have no concept of order, this is
// not always accurate.
console.log(person.name?.[1]); // John
console.log(person.name?.toArray()[0]); // John
// Get the id of the object
// (If the node is a blankNode the @id will be undefined)
console.log(person.bestFriend?.["@id"]); // "http://example.com/Person2"
@ -328,6 +330,8 @@ console.log(dataset.toString());
Setting a field to a JavaScript object literal will recursively add all parts of the object literal to the dataset.
```typescript
import { set } from "@ldo/jsonld-dataset-pack";
const dataset = createDataset();
const person = jsonldDatasetProxy(
dataset,
@ -335,10 +339,10 @@ const person = jsonldDatasetProxy(
).fromSubject<IPerson>(namedNode("http://example.com/Person1"));
person.bestFriend = {
"@id": "http://example.com/Person2",
name: ["Alice"],
name: set("Alice"),
bestFriend: {
"@id": "http://example.com/Person3",
name: ["Bob"],
name: set("Bob"),
},
};
console.log(dataset.toString());
@ -348,8 +352,9 @@ console.log(dataset.toString());
// <http://example.com/Person3> <http://xmlns.com/foaf/0.1/name> "Bob" .
```
### Array Methods
Any methods that modify arrays work as expected.
### Set Methods
Any methods that on a [JavaScript Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) are available.
```typescript
const dataset = await serializedToDataset(`
@ -364,13 +369,48 @@ const person = jsonldDatasetProxy(
dataset,
PersonContext
).fromSubject<IPerson>(namedNode("http://example.com/Person1"));
person.name?.push("Ferguson");
person.name?.add("Ferguson");
console.log(dataset.toString());
// <http://example.com/Person1> <http://xmlns.com/foaf/0.1/name> "Garrett" .
// <http://example.com/Person1> <http://xmlns.com/foaf/0.1/name> "Bobby" .
// <http://example.com/Person1> <http://xmlns.com/foaf/0.1/name> "Ferguson" .
```
For convenience, some methods commonly used on arrays have been added. The methods available on Sets are defined as follows:
```typescript
LdSet<T> {
add(value: T): this;
clear(): void;
delete(value: T): boolean;
has(value: T): boolean;
readonly size: number;
[Symbol.iterator](): IterableIterator<T>;
entries(): IterableIterator<[T, T]>;
keys(): IterableIterator<T>;
values(): IterableIterator<T>;
every<S extends T>(predicate: (value: T, set: LdSet<T>) => value is S, thisArg?: any): this is LdSet<S>;
every(predicate: (value: T, set: LdSet<T>) => unknown, thisArg?: any): boolean;
some(predicate: (value: T, set: LdSet<T>) => unknown, thisArg?: any): boolean;
forEach(callbackfn: (value: T, value2: T, set: LdSet<T>) => void, thisArg?: any): void;
map<U>(callbackfn: (value: T, set: LdSet<T>) => U, thisArg?: any): U[];
filter<S extends T>(predicate: (value: T, set: LdSet<T>) => value is S, thisArg?: any): LdSet<S>;
filter(predicate: (value: T, set: LdSet<T>) => unknown, thisArg?: any): LdSet<T>;
reduce(callbackfn: (previousValue: T, currentValue: T, set: LdSet<T>) => T): T;
reduce(callbackfn: (previousValue: T, currentValue: T, set: LdSet<T>) => T, initialValue: T): T;
reduce<U>(callbackfn: (previousValue: U, currentValue: T, array: LdSet<T>) => U, initialValue: U): U;
toArray(): T[];
toJSON(): T[];
difference(other: Set<T>): LdSet<T>;
intersection(other: Set<T>): LdSet<T>;
isDisjointFrom(other: Set<T>): boolean;
isSubsetOf(other: Set<T>): boolean;
isSupersetOf(other: Set<T>): boolean;
symmetricDifference(other: Set<T>): LdSet<T>;
union(other: Set<T>): LdSet<T>;
}
```
### Overwriting an Object
If an object literal is set and the id is equivalent to an existing id, that node will be overwritten. All triples from the previous object are removed and replaced with triples from the new object.
@ -390,7 +430,7 @@ const person = jsonldDatasetProxy(
).fromSubject<IPerson>(namedNode("http://example.com/Person1"));
person.bestFriend = {
"@id": "http://example.com/Person2",
name: ["Jane"],
name: set("Jane"),
};
console.log(dataset.toString());
// <http://example.com/Person2> <http://xmlns.com/foaf/0.1/name> "Jane" .
@ -398,7 +438,7 @@ console.log(dataset.toString());
```
### Changing an Object's Id
You can rename an object by setting its `@id` field. This will update all triples that reference the id to the new id.
You can rename an object by setting its `@id` field. This will update all triples that reference the id to the new id. Setting the `@id` field to `undefined` will turn it into a blank node.
```typescript
const dataset = await serializedToDataset(`
@ -425,7 +465,7 @@ console.log(dataset.toString());
```
### Removing an Object Connection
Removing one triple can be done by setting a property to `undefined`;
Removing a connection between nodes can be done by setting a field to `undefined` or using the `delete` operator. Values can be removed from a set by using the `delete` method or the `clear` method to remove all connections.
```typescript
const dataset = await serializedToDataset(`
@ -445,41 +485,16 @@ const person = jsonldDatasetProxy(
dataset,
PersonContext
).fromSubject<IPerson>(namedNode("http://example.com/Person1"));
perons.bestFriend.name.delete("Bob");
person.bestFriend = undefined;
console.log(dataset.toString());
// <http://example.com/Person1> <http://xmlns.com/foaf/0.1/name> "Alice" .
// <http://example.com/Person2> <http://xmlns.com/foaf/0.1/name> "Bob" .
// <http://example.com/Person2> <http://xmlns.com/foaf/0.1/bestFriend> <http://example.com/Person1> .
```
### Deleting an Entire Object
If you want to delete all triples represented by an object, there are two ways using the `delete` operator.
First, you can call `delete` on a specific property:
```typescript
const dataset = await serializedToDataset(`
@prefix example: <http://example.com/> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
example:Person1
foaf:name "Alice"^^xsd:string;
foaf:bestFriend example:Person2.
example:Person2
foaf:name "Bob"^^xsd:string;
foaf:bestFriend example:Person1.
`);
const person = jsonldDatasetProxy(
dataset,
PersonContext
).fromSubject<IPerson>(namedNode("http://example.com/Person1"));
delete person.bestFriend;
console.log(dataset.toString());
// <http://example.com/Person1> <http://xmlns.com/foaf/0.1/name> "Alice" .
```
If you want to delete all triples represented by an object you can call `delete` on the `@id` property.
And secondly, you can call `delete` on the `@id` property.
```typescript
const dataset = await serializedToDataset(`
@prefix example: <http://example.com/> .
@ -512,7 +527,7 @@ const person = jsonldDatasetProxy(
PersonContext
).fromSubject<IPerson>(namedNode("http://example.com/Person1"));
person.bestFriend = {
name: ["Charlie"],
name: set("Charlie"),
};
console.log(dataset.toString());
// <http://example.com/Person1> <http://xmlns.com/foaf/0.1/bestFriend> _:b1 .
@ -536,7 +551,7 @@ const person = jsonldDatasetProxy(
PersonContext,
).fromSubject<IPerson>(namedNode("http://example.com/Person1"));
const alice = person.knows?.[0];
const alice = person.knows?.toArray()[0];
person.bestFriend = alice;
console.log(dataset.toString());
// _:n3-0 <http://xmlns.com/foaf/0.1/name> "Alice" .
@ -556,7 +571,7 @@ The write graph can be set upon creating a jsonld dataset proxy by using the `wr
const person1 = jsonldDatasetProxy(dataset, PersonContext)
.write(namedNode("http://example.com/ExampleGraph"))
.fromSubject<IPerson>(namedNode("http://example.com/Person1"));
person1.name.push("Jack");
person1.name.add("Jack");
console.log(dataset.toString());
// Logs:
// <http://example.com/Person1> <http://xmlns.com/foaf/0.1/name> "Jack" <http://example.com/ExampleGraph> .
@ -574,10 +589,10 @@ const person1 = jsonldDatasetProxy(
).fromSubject<IPerson>(namedNode("http://example.com/Person1"));
// Now all additions with person1 will be on ExampleGraph1
write(namedNode("http://example.com/ExampleGraph1")).using(person1);
person1.name.push("Jack");
person1.name.add("Jack");
// Now all additions with person1 will be on ExampleGraph2
write(namedNode("http://example.com/ExampleGraph2")).using(person1);
person1.name.push("Spicer");
person1.name.add("Spicer");
console.log(dataset.toString());
// Logs:
@ -592,19 +607,19 @@ const person1 = jsonldDatasetProxy(
dataset,
PersonContext
).fromSubject<IPerson>(namedNode("http://example.com/Person1"));
person1.name.push("default");
person1.name.add("default");
const end1 = write(namedNode("http://example.com/Graph1")).using(person1);
person1.name.push("1");
person1.name.add("1");
const end2 = write(namedNode("http://example.com/Graph2")).using(person1);
person1.name.push("2");
person1.name.add("2");
const end3 = write(namedNode("http://example.com/Graph3")).using(person1);
person1.name.push("3");
person1.name.add("3");
end3();
person1.name.push("2 again");
person1.name.add("2 again");
end2();
person1.name.push("1 again");
person1.name.add("1 again");
end1();
person1.name.push("default again");
person1.name.add("default again");
console.log(dataset.toString());
// Logs:
// <http://example.com/Person1> <http://xmlns.com/foaf/0.1/name> "default" .
@ -627,8 +642,8 @@ const person1 = jsonldDatasetProxy(
const [person1WritingToNewGraph] = write(
namedNode("http://example.com/NewGraph")
).usingCopy(person1);
person1WritingToNewGraph.name.push("Brandon");
person1.name.push("Sanderson");
person1WritingToNewGraph.name.add("Brandon");
person1.name.add("Sanderson");
console.log(dataset.toString());
// Logs:
// <http://example.com/Person1> <http://xmlns.com/foaf/0.1/name> "Brandon" <http://example.com/NewGraph> .
@ -641,10 +656,10 @@ The graph of specific information can be detected using the `graphOf(subject, pr
- `subject`: A Jsonld Dataset Proxy that represents the subject of a quad.
- `predicate`: A string key
- `object?`: An optional parameter that represents the direct object of a statement. This could be a Jsonld Dataset Proxy or a number to indicate the location in an array. This argument can be left blank if the given field is not an array.
- `object?`: A representation of the direct object of the triple in question. This could be a jsonld-dataset-proxy or simply and object with an `@id` field.
```typescript
graphOf(person, "name", 0); // returns defaultGraph()
graphOf(person, "name", "Bob"); // returns defaultGraph()
graphOf(person, "age"); // returns defaultGraph()
```
@ -723,17 +738,17 @@ const hospitalInfo = jsonldDatasetProxy(dataset, PersonContext)
.fromSubject<IThing>(namedNode("http://example.com/Hospital"));
console.log(hospitalInfo.label); // Logs "병원"
console.log(hospitalInfo.description.length); // Logs "2" for the 2 korean entries
console.log(hospitalInfo.description[0]); // Logs "환자를 치료하다"
console.log(hospitalInfo.description[1]); // Logs "의사 있음"
console.log(hospitalInfo.description.size); // Logs "2" for the 2 korean entries
console.log(hospitalInfo.description.toArray()[0]); // Logs "환자를 치료하다"
console.log(hospitalInfo.description.toArray()[1]); // Logs "의사 있음"
// Adds a string to the description in spanish, because spanish if the first
// language in the language preference
hospitalInfo.description.push("Cura a las pacientes");
hospitalInfo.description.add("Cura a las pacientes");
// Now that a spanish entry exists, JSON-LD dataset proxy focuses on that
console.log(hospitalInfo.description.length); // Logs "1" for the 1 spanish entry
console.log(hospitalInfo.description[0]); // Logs "Cura a las pacientes"
console.log(hospitalInfo.description.toArray()[0]); // Logs "Cura a las pacientes"
```
### `setLanguagePreferences(...languagePreferences).using(...jsonldDatasetProxies)`

@ -1,12 +1,12 @@
{
"name": "@ldo/jsonld-dataset-proxy",
"version": "0.0.1-alpha.29",
"version": "1.0.0-alpha.1",
"description": "",
"main": "dist/index.js",
"scripts": {
"build": "tsc --project tsconfig.build.json",
"build:watch": "tsc-watch",
"test": "jest --coverage",
"test": "NODE_NO_WARNINGS=1 jest --coverage",
"prepublishOnly": "npm run test && npm run build",
"start": "ts-node ./example/example.ts",
"start:lang": "ts-node ./example/languageExample.ts",
@ -40,8 +40,8 @@
"src"
],
"dependencies": {
"@ldo/rdf-utils": "^0.0.1-alpha.24",
"@ldo/subscribable-dataset": "^0.0.1-alpha.24",
"@ldo/rdf-utils": "^1.0.0-alpha.1",
"@ldo/subscribable-dataset": "^1.0.0-alpha.1",
"@rdfjs/data-model": "^1.2.0",
"@rdfjs/dataset": "^1.1.0",
"jsonld2graphobject": "^0.0.4"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 269 KiB

After

Width:  |  Height:  |  Size: 229 KiB

@ -144,7 +144,7 @@ export class ContextUtil {
/**
* Returns true if the object is a collection
*/
public isArray(key: string, typeName: NamedNode[]): boolean {
public isSet(key: string, typeName: NamedNode[]): boolean {
const relevantContext = this.getRelevantContext(key, typeName);
return !!(
relevantContext[key] &&

@ -3,7 +3,8 @@ import type { BlankNode, NamedNode } from "@rdfjs/types";
import type { GraphNode, QuadMatch } from "@ldo/rdf-utils";
import type { LanguageOrdering } from "./language/languageTypes";
import type { ProxyContext } from "./ProxyContext";
import type { ObjectLike } from "./types";
import type { LiteralLike, ObjectLike } from "./types";
import type { LdSet } from "./setProxy/ldSet/LdSet";
/**
* Helps build JSON LD Dataset Proxies for a specific dataset and context
@ -56,14 +57,14 @@ export class JsonldDatasetProxyBuilder {
* @param graph The graph to match
*/
matchSubject<T extends ObjectLike>(
predicate?: QuadMatch[1],
object?: QuadMatch[2],
graph?: QuadMatch[3],
): T[] {
return this.proxyContext.createArrayProxy(
predicate?: QuadMatch[1] | undefined | null,
object?: QuadMatch[2] | undefined | null,
graph?: QuadMatch[3] | undefined | null,
): LdSet<T> {
return this.proxyContext.createSetProxy(
[null, predicate, object, graph],
true,
) as unknown as T[];
) as unknown as LdSet<T>;
}
/**
@ -73,17 +74,17 @@ export class JsonldDatasetProxyBuilder {
* @param predicate The predicate to match
* @param graph The graph to match
*/
matchObject<T extends ObjectLike>(
subject?: QuadMatch[0],
matchObject<T extends ObjectLike | LiteralLike>(
subject?: QuadMatch[0] | undefined | null,
predicate?: QuadMatch[1],
graph?: QuadMatch[3],
): T[] {
return this.proxyContext.createArrayProxy([
graph?: QuadMatch[3] | undefined | null,
): LdSet<T> {
return this.proxyContext.createSetProxy([
subject,
predicate,
null,
graph,
]) as unknown as T[];
]) as unknown as LdSet<T>;
}
/**

@ -1,21 +1,19 @@
import type { GraphNode, QuadMatch, SubjectNode } from "@ldo/rdf-utils";
import type { BlankNode, Dataset, NamedNode } from "@rdfjs/types";
import type { ArrayProxyTarget } from "./arrayProxy/createArrayHandler";
import { createArrayHandler } from "./arrayProxy/createArrayHandler";
import { createSubjectHandler } from "./subjectProxy/createSubjectHandler";
import type { SubjectProxy } from "./subjectProxy/SubjectProxy";
import type { ArrayProxy } from "./arrayProxy/ArrayProxy";
import { _getUnderlyingArrayTarget } from "./types";
import type { SetProxy } from "./setProxy/SetProxy";
import type { ContextUtil } from "./ContextUtil";
import type { LanguageOrdering } from "./language/languageTypes";
import { namedNode } from "@rdfjs/data-model";
import type { RawValue } from "./util/RawObject";
import { createNewSetProxy } from "./setProxy/createNewSetProxy";
export interface ProxyContextOptions {
dataset: Dataset;
contextUtil: ContextUtil;
writeGraphs: GraphNode[];
languageOrdering: LanguageOrdering;
prefilledArrayTargets?: ArrayProxyTarget[];
state?: Record<string, unknown>;
}
@ -28,7 +26,7 @@ const rdfType = namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
*/
export class ProxyContext {
private subjectMap: Map<string, SubjectProxy> = new Map();
private arrayMap: Map<string, ArrayProxy> = new Map();
private setMap: Map<string, SetProxy<NonNullable<RawValue>>> = new Map();
readonly dataset: Dataset;
readonly contextUtil: ContextUtil;
@ -42,29 +40,24 @@ export class ProxyContext {
this.writeGraphs = options.writeGraphs;
this.languageOrdering = options.languageOrdering;
this.state = options.state || {};
if (options.prefilledArrayTargets) {
options.prefilledArrayTargets.forEach((target) => {
this.createArrayProxy(target[0], target[2], target);
});
}
}
public createSubjectProxy(node: NamedNode | BlankNode): SubjectProxy {
if (!this.subjectMap.has(node.value)) {
const proxy = new Proxy(
{ "@id": node },
this.createSubjectHandler(),
) as unknown as SubjectProxy;
const proxy = this.createNewSubjectProxy(node);
this.subjectMap.set(node.value, proxy);
}
return this.subjectMap.get(node.value) as SubjectProxy;
}
protected createSubjectHandler() {
return createSubjectHandler(this);
protected createNewSubjectProxy(node: NamedNode | BlankNode): SubjectProxy {
return new Proxy(
{ "@id": node },
createSubjectHandler(this),
) as unknown as SubjectProxy;
}
private getArrayKey(...quadMatch: QuadMatch) {
private getSetKey(...quadMatch: QuadMatch) {
return `${quadMatch[0]?.value || "undefined"}|${
quadMatch[1]?.value || "undefined"
}|${quadMatch[2]?.value || "undefined"}|${
@ -72,39 +65,43 @@ export class ProxyContext {
}`;
}
public createArrayProxy(
public createSetProxy(
quadMatch: QuadMatch,
isSubjectOriented = false,
initialTarget?: ArrayProxyTarget,
isLangStringArray?: boolean,
): ArrayProxy {
const key = this.getArrayKey(...quadMatch);
if (!this.arrayMap.has(key)) {
const proxy = new Proxy(
initialTarget || [quadMatch, [], isSubjectOriented, isLangStringArray],
this.createArrayHandler(),
) as unknown as ArrayProxy;
this.arrayMap.set(key, proxy);
isSubjectOriented?: boolean,
isLangStringSet?: boolean,
): SetProxy {
const key = this.getSetKey(...quadMatch);
if (!this.setMap.has(key)) {
const proxy = this.createNewSetProxy(
quadMatch,
isSubjectOriented,
isLangStringSet,
);
this.setMap.set(key, proxy);
}
return this.arrayMap.get(key) as ArrayProxy;
return this.setMap.get(key)!;
}
protected createArrayHandler() {
return createArrayHandler(this);
protected createNewSetProxy(
quadMatch: QuadMatch,
isSubjectOriented?: boolean,
isLangStringSet?: boolean,
) {
return createNewSetProxy(
quadMatch,
isSubjectOriented ?? false,
this,
isLangStringSet,
);
}
public duplicate(alternativeOptions: Partial<ProxyContextOptions>) {
const prefilledArrayTargets: ArrayProxyTarget[] = [];
this.arrayMap.forEach((value) => {
prefilledArrayTargets.push(value[_getUnderlyingArrayTarget]);
});
const fullOptions: ProxyContextOptions = {
...{
dataset: this.dataset,
contextUtil: this.contextUtil,
writeGraphs: this.writeGraphs,
languageOrdering: this.languageOrdering,
prefilledArrayTargets,
},
...alternativeOptions,
};

@ -1,20 +0,0 @@
import type { Dataset } from "@rdfjs/types";
import type { ObjectNode } from "@ldo/rdf-utils";
import type { ArrayProxyTarget } from "./createArrayHandler";
import type {
_getNodeAtIndex,
_getUnderlyingArrayTarget,
_getUnderlyingDataset,
_getUnderlyingMatch,
_proxyContext,
} from "../types";
import { _getUnderlyingNode } from "../types";
import type { ProxyContext } from "../ProxyContext";
export type ArrayProxy = Array<unknown> & {
readonly [_getUnderlyingDataset]: Dataset;
readonly [_getUnderlyingMatch]: ArrayProxyTarget[0];
readonly [_getNodeAtIndex]: (index: number) => ObjectNode | undefined;
readonly [_getUnderlyingArrayTarget]: ArrayProxyTarget;
[_proxyContext]: ProxyContext;
};

@ -1,219 +0,0 @@
import type { ArrayProxyTarget } from "./createArrayHandler";
import type { ObjectJsonRepresentation } from "../util/nodeToJsonldRepresentation";
import { nodeToJsonldRepresentation } from "../util/nodeToJsonldRepresentation";
import { modifyArray } from "./modifyArray";
import type { ProxyContext } from "../ProxyContext";
export type methodBuilder<Return> = (
target: ArrayProxyTarget,
key: string,
proxyContext: ProxyContext,
) => Return;
export interface ArrayMethodBuildersType {
copyWithin: methodBuilder<Array<ObjectJsonRepresentation>["copyWithin"]>;
fill: methodBuilder<Array<ObjectJsonRepresentation>["fill"]>;
pop: methodBuilder<Array<ObjectJsonRepresentation>["pop"]>;
push: methodBuilder<Array<ObjectJsonRepresentation>["push"]>;
reverse: methodBuilder<Array<ObjectJsonRepresentation>["reverse"]>;
shift: methodBuilder<Array<ObjectJsonRepresentation>["shift"]>;
sort: methodBuilder<Array<ObjectJsonRepresentation>["sort"]>;
splice: methodBuilder<Array<ObjectJsonRepresentation>["splice"]>;
unshift: methodBuilder<Array<ObjectJsonRepresentation>["unshift"]>;
}
export const methodNames: Set<keyof ArrayMethodBuildersType> = new Set([
"copyWithin",
"fill",
"pop",
"push",
"reverse",
"shift",
"sort",
"splice",
"unshift",
]);
export const arrayMethodsBuilders: ArrayMethodBuildersType = {
copyWithin: (target, key, proxyContext) => {
return (targetIndex, start, end) => {
return modifyArray(
{
target,
key,
quadsToDelete: (quads) => {
const oldQuads = [...quads];
const newQuadSet = new Set(
quads.copyWithin(targetIndex, start, end),
);
return oldQuads.filter((oldQuad) => !newQuadSet.has(oldQuad));
},
modifyCoreArray: (coreArray) => {
coreArray.copyWithin(targetIndex, start, end);
return proxyContext.createArrayProxy(
target[0],
target[2],
) as ObjectJsonRepresentation[];
},
},
proxyContext,
);
};
},
fill: (target, key, proxyContext) => {
return (value, start, end) => {
return modifyArray(
{
target,
key,
toAdd: [value],
quadsToDelete: (quads) => {
return quads.slice(start, end);
},
modifyCoreArray: (coreArray, addedValues) => {
coreArray.fill(addedValues[0], start, end);
return proxyContext.createArrayProxy(
target[0],
target[2],
) as ObjectJsonRepresentation[];
},
},
proxyContext,
);
};
},
pop: (target, key, proxyContext) => {
return () => {
return modifyArray(
{
target,
key,
quadsToDelete: (quads) => {
return quads[quads.length - 1] ? [quads[quads.length - 1]] : [];
},
modifyCoreArray: (coreArray) => {
const popped = coreArray.pop();
return popped
? nodeToJsonldRepresentation(popped, proxyContext)
: undefined;
},
},
proxyContext,
);
};
},
push: (target, key, proxyContext) => {
return (...args) => {
return modifyArray(
{
target,
key,
toAdd: args,
modifyCoreArray: (coreArray, addedValues) => {
coreArray.push(...addedValues);
return proxyContext.createArrayProxy(target[0], target[2]).length;
},
},
proxyContext,
);
};
},
reverse: (target, _key, proxyContext) => {
return () => {
target[1].reverse();
return proxyContext.createArrayProxy(
target[0],
target[2],
) as ObjectJsonRepresentation[];
};
},
shift: (target, key, proxyContext) => {
return () => {
return modifyArray(
{
target,
key,
quadsToDelete: (quads) => {
return quads[0] ? [quads[0]] : [];
},
modifyCoreArray: (coreArray) => {
const shifted = coreArray.shift();
return shifted
? nodeToJsonldRepresentation(shifted, proxyContext)
: undefined;
},
},
proxyContext,
);
};
},
sort: (target, _key, proxyContext) => {
return (compareFunction) => {
if (compareFunction) {
target[1].sort((a, b) => {
return compareFunction(
nodeToJsonldRepresentation(a, proxyContext),
nodeToJsonldRepresentation(b, proxyContext),
);
});
} else if (target) {
target[1].sort((a, b) => {
const aReal = nodeToJsonldRepresentation(a, proxyContext);
const bReal = nodeToJsonldRepresentation(b, proxyContext);
if (aReal > bReal) {
return 1;
} else if (bReal > aReal) {
return -1;
} else {
return 0;
}
});
}
return proxyContext.createArrayProxy(
target[0],
target[2],
) as ObjectJsonRepresentation[];
};
},
splice: (target, key, proxyContext) => {
return (start, deleteCount, ...items: ObjectJsonRepresentation[]) => {
return modifyArray(
{
target,
key,
toAdd: items,
quadsToDelete: (quads) => {
return quads.splice(start, deleteCount);
},
modifyCoreArray: (coreArray, addedValues) => {
const spliced = coreArray.splice(
start,
deleteCount || 0,
...addedValues,
);
return spliced.map((node) => {
return nodeToJsonldRepresentation(node, proxyContext);
});
},
},
proxyContext,
);
};
},
unshift: (target, key, proxyContext) => {
return (...args) => {
return modifyArray(
{
target,
key,
toAdd: args,
modifyCoreArray: (coreArray, addedValues) => {
coreArray.unshift(...addedValues);
return proxyContext.createArrayProxy(target[0], target[2]).length;
},
},
proxyContext,
);
};
},
};

@ -1,177 +0,0 @@
import { quad } from "@rdfjs/data-model";
import type { NamedNode } from "@rdfjs/types";
import type { ObjectNode, QuadMatch, SubjectNode } from "@ldo/rdf-utils";
import type { ObjectJsonRepresentation } from "../util/nodeToJsonldRepresentation";
import { nodeToJsonldRepresentation } from "../util/nodeToJsonldRepresentation";
import type { ArrayMethodBuildersType } from "./arrayMethods";
import { arrayMethodsBuilders, methodNames } from "./arrayMethods";
import {
_getNodeAtIndex,
_getUnderlyingArrayTarget,
_getUnderlyingDataset,
_getUnderlyingMatch,
_isSubjectOriented,
_proxyContext,
} from "../types";
import { modifyArray } from "./modifyArray";
import type { ProxyContext } from "../ProxyContext";
import { NodeSet } from "../util/NodeSet";
import { filterQuadsByLanguageOrdering } from "../language/languageUtils";
export type ArrayProxyTarget = [
quadMatch: QuadMatch,
curArray: ObjectNode[],
isSubjectOriented?: boolean,
isLangStringArray?: boolean,
];
function updateArrayOrder(
target: ArrayProxyTarget,
proxyContext: ProxyContext,
): void {
let quads = proxyContext.dataset.match(...target[0]);
if (target[3]) {
// Is lang string array
quads = filterQuadsByLanguageOrdering(quads, proxyContext.languageOrdering);
}
const datasetObjects = new NodeSet();
quads.toArray().forEach((quad) => {
// If this this a subject-oriented document
if (target[2]) {
datasetObjects.add(quad.subject as SubjectNode);
} else {
datasetObjects.add(quad.object as ObjectNode);
}
});
const processedObjects: ObjectNode[] = [];
target[1].forEach((arrItem) => {
if (datasetObjects.has(arrItem)) {
processedObjects.push(arrItem);
datasetObjects.delete(arrItem);
}
});
datasetObjects.toArray().forEach((datasetObject) => {
processedObjects.push(datasetObject);
});
target[1] = processedObjects;
}
function getProcessedArray(
target: ArrayProxyTarget,
proxyContext: ProxyContext,
): ObjectJsonRepresentation[] {
return target[1].map((node) => {
return nodeToJsonldRepresentation(node, proxyContext);
});
}
export function createArrayHandler(
proxyContext: ProxyContext,
): ProxyHandler<ArrayProxyTarget> {
return {
get(target, key, ...rest) {
switch (key) {
case _getUnderlyingDataset:
return proxyContext.dataset;
case _getUnderlyingMatch:
return target[0];
case _isSubjectOriented:
return target[2];
case _getUnderlyingArrayTarget:
return target;
case _proxyContext:
return proxyContext;
case _getNodeAtIndex:
return (index: number): ObjectNode | undefined => {
updateArrayOrder(target, proxyContext);
return target[1][index];
};
}
// TODO: Because of this, every get operation is O(n). Consider changing
// this
updateArrayOrder(target, proxyContext);
const processedArray = getProcessedArray(target, proxyContext);
if (methodNames.has(key as keyof ArrayMethodBuildersType)) {
return arrayMethodsBuilders[key as keyof ArrayMethodBuildersType](
target,
key as string,
proxyContext,
);
}
return Reflect.get(processedArray, key, ...rest);
},
getOwnPropertyDescriptor(target, key, ...rest) {
updateArrayOrder(target, proxyContext);
const processedArray = getProcessedArray(target, proxyContext);
return Reflect.getOwnPropertyDescriptor(processedArray, key, ...rest);
},
ownKeys(target, ...rest) {
updateArrayOrder(target, proxyContext);
const processedArray = getProcessedArray(target, proxyContext);
return Reflect.ownKeys(processedArray, ...rest);
},
getPrototypeOf(target, ...rest) {
updateArrayOrder(target, proxyContext);
const processedObjects = getProcessedArray(target, proxyContext);
return Reflect.getPrototypeOf(processedObjects, ...rest);
},
has(target, ...rest) {
updateArrayOrder(target, proxyContext);
const processedObjects = getProcessedArray(target, proxyContext);
return Reflect.has(processedObjects, ...rest);
},
set(target, key, value, ...rest) {
if (key === _proxyContext) {
proxyContext = value;
return true;
}
updateArrayOrder(target, proxyContext);
if (typeof key !== "symbol" && !isNaN(parseInt(key as string))) {
const index = parseInt(key);
return modifyArray(
{
target,
key,
toAdd: [value],
quadsToDelete(allQuads) {
return allQuads[index] ? [allQuads[index]] : [];
},
modifyCoreArray(coreArray, addedValues) {
coreArray[index] = addedValues[0];
return true;
},
},
proxyContext,
);
}
return Reflect.set(target[1], key, ...rest);
},
deleteProperty(target, key) {
const { dataset } = proxyContext;
if (typeof key !== "symbol" && !isNaN(parseInt(key as string))) {
const objectQuad = dataset.match(...target[0]).toArray()[parseInt(key)];
if (!objectQuad) {
return true;
}
const term = target[2] ? objectQuad.subject : objectQuad.object;
if (term.termType === "Literal") {
const subject = target[0][0] as NamedNode;
const predicate = target[0][1] as NamedNode;
if (subject && predicate) {
dataset.delete(quad(subject, predicate, term));
}
return true;
} else if (
term.termType === "NamedNode" ||
term.termType === "BlankNode"
) {
dataset.deleteMatches(term, undefined, undefined);
dataset.deleteMatches(undefined, undefined, term);
return true;
}
}
return true;
},
};
}

@ -1,23 +0,0 @@
import {
_getNodeAtIndex,
_getUnderlyingArrayTarget,
_getUnderlyingDataset,
_getUnderlyingMatch,
_getUnderlyingNode,
_proxyContext,
_writeGraphs,
} from "../types";
import type { ArrayProxy } from "./ArrayProxy";
export function isArrayProxy(someObject?: unknown): someObject is ArrayProxy {
if (!someObject) return false;
if (typeof someObject !== "object") return false;
const potentialArrayProxy = someObject as ArrayProxy;
return !(
typeof potentialArrayProxy[_getUnderlyingDataset] !== "object" ||
typeof potentialArrayProxy[_getUnderlyingMatch] !== "object" ||
typeof potentialArrayProxy[_getNodeAtIndex] !== "function" ||
typeof potentialArrayProxy[_getUnderlyingArrayTarget] !== "object"
);
}

@ -1,141 +0,0 @@
import { defaultGraph } from "@rdfjs/data-model";
import type { Quad } from "@rdfjs/types";
import type { ObjectNode } from "@ldo/rdf-utils";
import {
TransactionDataset,
createTransactionDatasetFactory,
} from "@ldo/subscribable-dataset";
import { createDatasetFactory } from "@ldo/dataset";
import type { ProxyContext } from "../ProxyContext";
import { addObjectToDataset } from "../util/addObjectToDataset";
import {
getNodeFromRawObject,
getNodeFromRawValue,
} from "../util/getNodeFromRaw";
import { nodeToString } from "../util/NodeSet";
import type { ObjectJsonRepresentation } from "../util/nodeToJsonldRepresentation";
import type { RawObject, RawValue } from "../util/RawObject";
import type { ArrayProxyTarget } from "./createArrayHandler";
export function checkArrayModification(
target: ArrayProxyTarget,
objectsToAdd: RawValue[],
proxyContext: ProxyContext,
) {
if (target[2]) {
for (const objectToAdd of objectsToAdd) {
// Undefined is fine no matter what
if (objectToAdd === undefined) {
return;
}
if (typeof objectToAdd !== "object") {
throw new Error(
`Cannot add a literal "${objectToAdd}"(${typeof objectToAdd}) to a subject-oriented collection.`,
);
}
// Create a test dataset to see if the inputted data is valid
const testDataset = new TransactionDataset(
proxyContext.dataset,
createDatasetFactory(),
createTransactionDatasetFactory(),
);
addObjectToDataset(
objectToAdd as RawObject,
false,
proxyContext.duplicate({
writeGraphs: [defaultGraph()],
}),
);
const isValidAddition =
testDataset.match(
getNodeFromRawObject(objectToAdd, proxyContext.contextUtil),
target[0][1],
target[0][2],
).size !== 0;
if (!isValidAddition) {
throw new Error(
`Cannot add value to collection. This must contain a quad that matches (${nodeToString(
target[0][0],
)}, ${nodeToString(target[0][1])}, ${nodeToString(
target[0][2],
)}, ${nodeToString(target[0][3])})`,
);
}
}
} else if (!target[0][0] || !target[0][1]) {
throw new Error(
"A collection that does not specify a match for both a subject or predicate cannot be modified directly.",
);
}
}
export function modifyArray<ReturnType>(
config: {
target: ArrayProxyTarget;
key: string;
toAdd?: RawValue[];
quadsToDelete?: (quads: Quad[]) => Quad[];
modifyCoreArray: (
coreArray: ArrayProxyTarget[1],
addedValues: ArrayProxyTarget[1],
) => ReturnType;
},
proxyContext: ProxyContext,
): ReturnType {
const { target, toAdd, quadsToDelete, modifyCoreArray, key } = config;
const { dataset, contextUtil } = proxyContext;
checkArrayModification(target, toAdd || [], proxyContext);
// Remove appropriate Quads
if (quadsToDelete) {
const quadArr = dataset.match(...target[0]).toArray();
const deleteQuadArr = quadsToDelete(quadArr);
// Filter out overlapping items
deleteQuadArr.forEach((delQuad) => {
if (target[2]) {
dataset.deleteMatches(delQuad.subject, undefined, undefined);
} else {
dataset.delete(delQuad);
}
});
}
// Add new items to the dataset
const added = toAdd
?.map((item) => {
return typeof item === "object"
? addObjectToDataset(item, false, proxyContext)
: item;
})
.filter(
(val) => val != undefined,
) as NonNullable<ObjectJsonRepresentation>[];
if (!target[2] && target[0][0] && target[0][1] && added) {
addObjectToDataset(
{
"@id": target[0][0],
[contextUtil.iriToKey(
target[0][1].value,
proxyContext.getRdfType(target[0][0]),
)]: added,
} as RawObject,
false,
proxyContext,
);
}
const addedNodes = added
? (added
.map((addedValue) => {
return getNodeFromRawValue(
key,
addedValue,
target[0][0] ? proxyContext.getRdfType(target[0][0]) : [],
proxyContext,
);
})
.filter((val) => val != undefined) as ObjectNode[])
: [];
// Allow the base array to be modified
return modifyCoreArray(target[1], addedNodes);
}

@ -1,31 +1,28 @@
import type { ObjectNode, GraphNode } from "@ldo/rdf-utils";
import { namedNode } from "@rdfjs/data-model";
import {
getSubjectProxyFromObject,
isSubjectProxy,
} from "./subjectProxy/isSubjectProxy";
import { getSubjectProxyFromObject } from "./subjectProxy/isSubjectProxy";
import type { ObjectLike } from "./types";
import {
_getNodeAtIndex,
_getUnderlyingDataset,
_getUnderlyingMatch,
_getUnderlyingNode,
_proxyContext,
} from "./types";
import type { LdSet } from "./setProxy/ldSet/LdSet";
import { getNodeFromRawValue } from "./util/getNodeFromRaw";
/**
* Returns the graph for which a defined triple is a member
* @param subject A JsonldDatasetProxy that represents the subject
* @param predicate The key on the JsonldDatasetProxy
* @param object The direct object. This can be a JsonldDatasetProxy or the index
* @param object The direct object. This can be a JsonldDatasetProxy. This field
* is optional.
* @returns a list of graphs for which the triples are members
*/
export function graphOf<Subject extends ObjectLike, Key extends keyof Subject>(
subject: Subject,
predicate: Key,
object?: NonNullable<Subject[Key]> extends Array<unknown>
? number | ObjectLike
: ObjectLike,
object?: NonNullable<Subject[Key]> extends LdSet<infer T> ? T : Subject[Key],
): GraphNode[] {
const subjectProxy = getSubjectProxyFromObject(subject);
const proxyContext = subjectProxy[_proxyContext];
@ -36,26 +33,15 @@ export function graphOf<Subject extends ObjectLike, Key extends keyof Subject>(
proxyContext.getRdfType(subjectNode),
),
);
let objectNode: ObjectNode | null;
let objectNode: ObjectNode | undefined | null;
if (object == null) {
objectNode = null;
} else if (typeof object === "number") {
const proxyArray = subject[predicate];
if (!proxyArray[_getUnderlyingMatch]) {
throw new Error(
`Key "${String(predicate)}" of ${subject} is not an array.`,
);
}
if (!proxyArray[object]) {
throw new Error(`Index ${object} does not exist.`);
}
if (isSubjectProxy(proxyArray[object])) {
objectNode = proxyArray[object][1];
}
objectNode = proxyArray[_getNodeAtIndex](object);
} else {
const objectProxy = getSubjectProxyFromObject(object);
objectNode = objectProxy[_getUnderlyingNode];
const datatype = proxyContext.contextUtil.getDataType(
predicate as string,
proxyContext.getRdfType(subjectNode),
);
objectNode = getNodeFromRawValue(object, proxyContext, datatype);
}
const quads = subjectProxy[_getUnderlyingDataset].match(
subjectNode,

@ -17,11 +17,12 @@ export * from "./language/languageSet";
export * from "./language/languageTypes";
export * from "./language/languageUtils";
export * from "./arrayProxy/createArrayHandler";
export * from "./arrayProxy/arrayMethods";
export * from "./arrayProxy/ArrayProxy";
export * from "./arrayProxy/modifyArray";
export * from "./arrayProxy/isArrayProxy";
export * from "./setProxy/createNewSetProxy";
export * from "./setProxy/isSetProxy";
export * from "./setProxy/SetProxy";
export * from "./setProxy/set";
export * from "./setProxy/ldSet/LdSet";
export * from "./setProxy/ldSet/BasicLdSet";
export * from "./subjectProxy/createSubjectHandler";
export * from "./subjectProxy/SubjectProxy";

@ -3,6 +3,7 @@ import { getSubjectProxyFromObject } from "../subjectProxy/isSubjectProxy";
import type { ObjectLike } from "../types";
import { _getUnderlyingNode, _proxyContext } from "../types";
import { createLanguageMapProxy } from "./languageMapProxy";
import type { LdSet } from "../setProxy/ldSet/LdSet";
/**
* -----------------------------------------------------------------------------
@ -25,7 +26,7 @@ export type LanguageSet = Set<string>;
export type LanguageOfConditionalReturn<
SubjectObject extends ObjectLike,
Key extends keyof SubjectObject,
> = NonNullable<SubjectObject[Key]> extends Array<unknown>
> = NonNullable<SubjectObject[Key]> extends LdSet<unknown>
? LanguageSetMap
: LanguageMap;
@ -59,6 +60,6 @@ export function languagesOf<
subject,
predicate,
proxyContext,
proxyContext.contextUtil.isArray(key as string, rdfTypes),
proxyContext.contextUtil.isSet(key as string, rdfTypes),
) as LanguageOfConditionalReturn<SubjectObject, Key>;
}

@ -0,0 +1,66 @@
import type { GraphNode, PredicateNode, SubjectNode } from "@ldo/rdf-utils";
import type { RawObject, RawValue } from "../util/RawObject";
import { WildcardObjectSetProxy } from "./WildcardObjectSetProxy";
import { addObjectToDataset } from "../util/addObjectToDataset";
import type { ProxyContext } from "../ProxyContext";
export type ObjectSetProxyQuadMatch = [
SubjectNode,
PredicateNode,
undefined | null,
GraphNode | undefined | null,
];
export class ObjectSetProxy<
T extends NonNullable<RawValue>,
> extends WildcardObjectSetProxy<T> {
protected quadMatch: ObjectSetProxyQuadMatch;
constructor(
context: ProxyContext,
quadMatch: ObjectSetProxyQuadMatch,
isLangSet?: boolean,
) {
super(context, quadMatch, isLangSet);
this.quadMatch = quadMatch;
}
/**
* Appends a new element with a specified value to the end of the Set.
*/
add(value: T): this {
addObjectToDataset(
{
"@id": this.quadMatch[0],
[this.context.contextUtil.iriToKey(
this.quadMatch[1].value,
this.context.getRdfType(this.quadMatch[0]),
)]: value,
} as RawObject,
false,
this.context,
);
return this;
}
/**
* Clears the set of all values
*/
clear(): void {
for (const value of this) {
this.delete(value);
}
}
/**
* Deletes an item for the set
* @param value the item to delete
* @returns true if the item was present before deletion
*/
delete(value: T): boolean {
const { dataset } = this.context;
const quads = this.getQuads(value);
quads.forEach((quad) => dataset.delete(quad));
return quads.size > 0;
}
}

@ -0,0 +1,141 @@
/**
* This file handles the underlying functionality of a set, including hidden
* helper methods
*/
import type { Dataset, Quad } from "@rdfjs/types";
import type {
GraphNode,
ObjectNode,
QuadMatch,
SubjectNode,
} from "@ldo/rdf-utils";
import {
_isSubjectOriented,
_getUnderlyingDataset,
_proxyContext,
_isLangString,
_getUnderlyingMatch,
_getUnderlyingNode,
_writeGraphs,
} from "../types";
import type { ProxyContext } from "../ProxyContext";
import type { RawValue } from "../util/RawObject";
import { nodeToJsonldRepresentation } from "../util/nodeToJsonldRepresentation";
import { BasicLdSet } from "./ldSet/BasicLdSet";
/**
* A Set Proxy represents a set of items in a dataset and is a proxy for
* accessing those items in the dataset.
*/
export abstract class SetProxy<
T extends NonNullable<RawValue> = NonNullable<RawValue>,
> extends BasicLdSet<T> {
protected quadMatch: QuadMatch;
protected context: ProxyContext;
constructor(context: ProxyContext, quadMatch: QuadMatch) {
super();
this.context = context;
this.quadMatch = quadMatch;
}
/**
* Gets the subject, predicate and object for this set
*/
protected abstract getQuads(value?: T): Dataset<Quad, Quad>;
protected abstract getNodeOfFocus(quad: Quad): SubjectNode | ObjectNode;
/**
* The add method on a wildcard set does nothing.
* @deprecated You cannot add data to a wildcard set as it is simply a proxy to an underlying dataset
*/
add(_value: T) {
console.warn(
'You\'ve attempted to call "add" on a wildcard set. You cannot add data to a wildcard set as it is simply a proxy to an underlying dataset',
);
return this;
}
/**
* The clear method on an abstract set does nothing.
* @deprecated You cannot clear data from an abstract set as it is simply a proxy to an underlying dataset
*/
clear(): void {
console.warn(
'You\'ve attempted to call "clear" on an abstract set. You cannot clear data from an abstract set as it is simply a proxy to an underlying dataset',
);
return;
}
/**
* The delete method on an abstract set does nothing.
* @deprecated You cannot delete data from an abstract set as it is simply a proxy to an underlying dataset
*/
delete(_value: T): boolean {
console.warn(
'You\'ve attempted to call "clear" on an abstract set. You cannot delete data from an abstract set as it is simply a proxy to an underlying dataset',
);
return false;
}
has(value: T): boolean {
return this.getQuads(value).size > 0;
}
get size() {
return this.getQuads().size;
}
entries(): IterableIterator<[T, T]> {
const iteratorSet = new Set<[T, T]>();
for (const value of this) {
iteratorSet.add([value, value]);
}
return iteratorSet[Symbol.iterator]();
}
keys(): IterableIterator<T> {
return this.values();
}
values(): IterableIterator<T> {
return this[Symbol.iterator]();
}
[Symbol.iterator](): IterableIterator<T> {
const quads = this.getQuads();
const collection: T[] = quads.toArray().map((quad) => {
const quadSubject = this.getNodeOfFocus(quad);
return nodeToJsonldRepresentation(quadSubject, this.context) as T;
});
return new Set(collection)[Symbol.iterator]();
}
get [Symbol.toStringTag]() {
// TODO: Change this to be human readable.
return "LdSet";
}
get [_getUnderlyingDataset](): Dataset {
return this.context.dataset;
}
get [_getUnderlyingMatch](): QuadMatch {
return this.quadMatch;
}
get [_proxyContext](): ProxyContext {
return this.context;
}
set [_proxyContext](newContext: ProxyContext) {
this.context = newContext;
}
get [_writeGraphs](): GraphNode[] {
return this.context.writeGraphs;
}
abstract get [_isSubjectOriented](): boolean;
}

@ -0,0 +1,85 @@
import {
type GraphNode,
type ObjectNode,
type PredicateNode,
} from "@ldo/rdf-utils";
import type { RawObject } from "../util/RawObject";
import { addObjectToDataset } from "../util/addObjectToDataset";
import type { ProxyContext } from "../ProxyContext";
import { WildcardSubjectSetProxy } from "./WildcardSubjectSetProxy";
import { _getUnderlyingNode } from "../types";
import { defaultGraph, quad } from "@rdfjs/data-model";
import {
createTransactionDatasetFactory,
TransactionDataset,
} from "@ldo/subscribable-dataset";
import { createDatasetFactory } from "@ldo/dataset";
import { getNodeFromRawObject } from "../util/getNodeFromRaw";
import { nodeToString } from "../util/NodeSet";
export type SubjectSetProxyQuadMatch = [
undefined | null,
PredicateNode,
ObjectNode,
GraphNode | undefined | null,
];
export class SubjectSetProxy<
T extends RawObject,
> extends WildcardSubjectSetProxy<T> {
protected quadMatch: SubjectSetProxyQuadMatch;
constructor(context: ProxyContext, quadMatch: SubjectSetProxyQuadMatch) {
super(context, quadMatch);
this.quadMatch = quadMatch;
}
/**
* Appends a new element with a specified value to the end of the Set.
*/
add(value: T): this {
if (typeof value !== "object") {
throw new Error(
`Cannot add a literal "${value}"(${typeof value}) to a subject-oriented collection.`,
);
}
// Create a test dataset to see if the inputted data is valid
const testDataset = new TransactionDataset(
this.context.dataset,
createDatasetFactory(),
createTransactionDatasetFactory(),
);
addObjectToDataset(
value,
false,
this.context.duplicate({
writeGraphs: [defaultGraph()],
}),
);
const isValidAddition =
testDataset.match(
getNodeFromRawObject(value, this.context.contextUtil),
this.quadMatch[1],
this.quadMatch[2],
).size !== 0;
if (!isValidAddition) {
throw new Error(
`Cannot add value to collection. This must contain a quad that matches (${nodeToString(
this.quadMatch[0],
)}, ${nodeToString(this.quadMatch[1])}, ${nodeToString(
this.quadMatch[2],
)}, ${nodeToString(this.quadMatch[3])})`,
);
}
// Add the object if everything's okay
const added = addObjectToDataset(value as RawObject, false, this.context);
const addedNode = added[_getUnderlyingNode];
this.context.writeGraphs.forEach((graph) => {
this.context.dataset.add(
quad(addedNode, this.quadMatch[1], this.quadMatch[2], graph),
);
});
return this;
}
}

@ -0,0 +1,89 @@
import type {
SubjectNode,
PredicateNode,
ObjectNode,
GraphNode,
} from "@ldo/rdf-utils";
import type { Dataset, Quad } from "@rdfjs/types";
import type { RawValue } from "../util/RawObject";
import { SetProxy } from "./SetProxy";
import type { ProxyContext } from "../ProxyContext";
import { getNodeFromRawValue } from "../util/getNodeFromRaw";
import { _isSubjectOriented } from "../types";
import { filterQuadsByLanguageOrdering } from "../language/languageUtils";
export type WildcardObjectSetProxyQuadMatch = [
SubjectNode | undefined | null,
PredicateNode | undefined | null,
undefined | null,
GraphNode | undefined | null,
];
/**
* A WildcardObjectProxy represents a set of nodes in a dataset that are all the
* object of a given subject and predicate. Because this is a wildcard, the
* subject and predicate don't necissarily need to be defined.
*/
export class WildcardObjectSetProxy<
T extends NonNullable<RawValue>,
> extends SetProxy<T> {
protected quadMatch: WildcardObjectSetProxyQuadMatch;
protected isLangStringSet: boolean;
constructor(
context: ProxyContext,
quadMatch: WildcardObjectSetProxyQuadMatch,
isLangStringSet?: boolean,
) {
super(context, quadMatch);
this.quadMatch = quadMatch;
this.isLangStringSet = isLangStringSet ?? false;
}
protected getQuads(value?: T | undefined): Dataset<Quad, Quad> {
const { dataset } = this.context;
let quads: Dataset<Quad, Quad>;
// Get the RDF Node that represents the value, skip if no value
const subject = this.quadMatch[0] ?? undefined;
const predicate = this.quadMatch[1] ?? undefined;
const graph = this.quadMatch[3] ?? undefined;
if (value) {
// Get datatype if applicable
let datatype: string | undefined = undefined;
if (this.quadMatch[0] && predicate) {
const rdfType = this.context.getRdfType(this.quadMatch[0]);
const key = this.context.contextUtil.iriToKey(predicate.value, rdfType);
datatype = this.context.contextUtil.getDataType(key, rdfType);
}
const valueNode = getNodeFromRawValue(value, this.context, datatype);
quads = dataset.match(subject, predicate, valueNode, graph);
// If there is no valueNode, we must filter by value manually as we
// weren't able to deduce the datatype.
if (!valueNode) {
quads = quads.filter(
(quad) =>
quad.object.termType === "Literal" && quad.object.value === value,
);
}
} else {
// SPO for no value
quads = dataset.match(subject, predicate, undefined, graph);
}
// If this is a langStringSet, filter by language preferences
if (this.isLangStringSet) {
return filterQuadsByLanguageOrdering(
quads,
this.context.languageOrdering,
);
}
return quads;
}
protected getNodeOfFocus(quad: Quad): ObjectNode {
return quad.object as ObjectNode;
}
get [_isSubjectOriented](): false {
return false;
}
}

@ -0,0 +1,58 @@
import type {
SubjectNode,
PredicateNode,
ObjectNode,
GraphNode,
} from "@ldo/rdf-utils";
import type { Dataset, Quad } from "@rdfjs/types";
import type { RawObject } from "../util/RawObject";
import { SetProxy } from "./SetProxy";
import type { ProxyContext } from "../ProxyContext";
import { getNodeFromRawObject } from "../util/getNodeFromRaw";
import { _isSubjectOriented } from "../types";
export type WildcardSubjectSetProxyQuadMatch = [
undefined | null,
PredicateNode | undefined | null,
ObjectNode | undefined | null,
GraphNode | undefined | null,
];
/**
* A WildcardObjectProxy represents a set of nodes in a dataset that are all the
* object of a given subject and predicate. Because this is a wildcard, the
* subject and predicate don't necissarily need to be defined.
*/
export class WildcardSubjectSetProxy<T extends RawObject> extends SetProxy<T> {
protected quadMatch: WildcardSubjectSetProxyQuadMatch;
constructor(
context: ProxyContext,
quadMatch: WildcardSubjectSetProxyQuadMatch,
) {
super(context, quadMatch);
this.quadMatch = quadMatch;
}
protected getQuads(value?: T | undefined): Dataset<Quad, Quad> {
const { dataset } = this.context;
// Get the RDF Node that represents the value, skip is no value
const predicate = this.quadMatch[1];
const object = this.quadMatch[2];
const graph = this.quadMatch[3];
if (value) {
const valueNode = getNodeFromRawObject(value, this.context.contextUtil);
return dataset.match(valueNode, predicate, object, graph);
}
// SPO for no value
return dataset.match(undefined, predicate, object, graph);
}
protected getNodeOfFocus(quad: Quad): SubjectNode {
return quad.subject as SubjectNode;
}
get [_isSubjectOriented](): true {
return true;
}
}

@ -0,0 +1,47 @@
import type { QuadMatch } from "@ldo/rdf-utils";
import type { ProxyContext } from "../ProxyContext";
import type { RawObject, RawValue } from "../util/RawObject";
import type { ObjectSetProxyQuadMatch } from "./ObjectSetProxy";
import { ObjectSetProxy } from "./ObjectSetProxy";
import type { SubjectSetProxyQuadMatch } from "./SubjectSetProxy";
import { SubjectSetProxy } from "./SubjectSetProxy";
import type { WildcardObjectSetProxyQuadMatch } from "./WildcardObjectSetProxy";
import { WildcardObjectSetProxy } from "./WildcardObjectSetProxy";
import type { WildcardSubjectSetProxyQuadMatch } from "./WildcardSubjectSetProxy";
import { WildcardSubjectSetProxy } from "./WildcardSubjectSetProxy";
import type { SetProxy } from "./SetProxy";
export function createNewSetProxy<T extends NonNullable<RawValue>>(
quadMatch: QuadMatch,
isSubjectOriented: boolean,
proxyContext: ProxyContext,
isLangSet?: boolean,
): SetProxy<T> {
if (!isSubjectOriented) {
if (quadMatch[0] && quadMatch[1]) {
return new ObjectSetProxy<T>(
proxyContext,
quadMatch as ObjectSetProxyQuadMatch,
isLangSet,
);
} else {
return new WildcardObjectSetProxy<T>(
proxyContext,
quadMatch as WildcardObjectSetProxyQuadMatch,
isLangSet,
);
}
} else {
if (quadMatch[1] && quadMatch[2]) {
return new SubjectSetProxy<T & RawObject>(
proxyContext,
quadMatch as SubjectSetProxyQuadMatch,
);
} else {
return new WildcardSubjectSetProxy<T & RawObject>(
proxyContext,
quadMatch as WildcardSubjectSetProxyQuadMatch,
);
}
}
}

@ -0,0 +1,7 @@
import { SetProxy } from "./SetProxy";
export function isSetProxy(someObject?: unknown): someObject is SetProxy {
if (!someObject) return false;
if (typeof someObject !== "object") return false;
return someObject instanceof SetProxy;
}

@ -0,0 +1,266 @@
import type { BlankNode, NamedNode } from "@rdfjs/types";
import { _getUnderlyingNode } from "../../types";
import type { RawValue } from "../../util/RawObject";
import type { LdSet } from "./LdSet";
import { blankNode } from "@rdfjs/data-model";
/* eslint-disable @typescript-eslint/no-explicit-any */
export class BasicLdSet<T extends NonNullable<RawValue> = NonNullable<RawValue>>
implements LdSet<T>
{
private hashMap: Map<string, T>;
constructor(values?: Iterable<T> | null) {
this.hashMap = new Map();
if (values) {
for (const value of values) {
this.add(value);
}
}
}
private hashFn(value: T): string {
if (typeof value !== "object") return value.toString();
if (value[_getUnderlyingNode]) {
return (value[_getUnderlyingNode] as NamedNode | BlankNode).value;
} else if (!value["@id"]) {
return blankNode().value;
} else if (typeof value["@id"] === "string") {
return value["@id"];
} else {
return value["@id"].value;
}
}
/**
* ===========================================================================
* Base Set Functions
* ===========================================================================
*/
add(value: T): this {
const key = this.hashFn(value);
if (!this.hashMap.has(key)) {
this.hashMap.set(key, value);
}
return this;
}
clear(): void {
this.hashMap.clear();
}
delete(value: T): boolean {
const key = this.hashFn(value);
return this.hashMap.delete(key);
}
has(value: T): boolean {
const key = this.hashFn(value);
return this.hashMap.has(key);
}
get size(): number {
return this.hashMap.size;
}
*entries(): IterableIterator<[T, T]> {
for (const [, value] of this.hashMap.entries()) {
yield [value, value];
}
}
keys(): IterableIterator<T> {
return this.hashMap.values();
}
values(): IterableIterator<T> {
return this.hashMap.values();
}
[Symbol.iterator](): IterableIterator<T> {
return this.hashMap.values();
}
get [Symbol.toStringTag]() {
// TODO: Change this to be human readable.
return "BasicLdSet";
}
/**
* ===========================================================================
* Array Functions
* ===========================================================================
*/
every<S extends T>(
predicate: (value: T, set: LdSet<T>) => value is S,
thisArg?: any,
): this is LdSet<S>;
every(
predicate: (value: T, set: LdSet<T>) => unknown,
thisArg?: any,
): boolean;
every(predicate: (value: T, set: LdSet<T>) => any, thisArg?: any): boolean {
for (const value of this) {
if (!predicate.call(thisArg, value, this)) return false;
}
return true;
}
some(
predicate: (value: T, set: LdSet<T>) => unknown,
thisArg?: any,
): boolean {
for (const value of this) {
if (predicate.call(thisArg, value, this)) return true;
}
return false;
}
forEach(
callbackfn: (value: T, value2: T, set: LdSet<T>) => void,
thisArg?: any,
): void {
for (const value of this) {
callbackfn.call(thisArg, value, value, this);
}
}
map<U>(callbackfn: (value: T, set: LdSet<T>) => U, thisArg?: any): U[] {
const returnValues: U[] = [];
for (const value of this) {
returnValues.push(callbackfn.call(thisArg, value, this));
}
return returnValues;
}
filter<S extends T>(
predicate: (value: T, set: LdSet<T>) => value is S,
thisArg?: any,
): LdSet<S>;
filter(
predicate: (value: T, set: LdSet<T>) => unknown,
thisArg?: any,
): LdSet<T>;
filter(
predicate: (value: T, set: LdSet<T>) => any,
thisArg?: unknown,
): LdSet<T> {
const newSet = new BasicLdSet<T>();
for (const value of this) {
if (predicate.call(thisArg, value, this)) newSet.add(value);
}
return newSet;
}
reduce(
callbackfn: (previousValue: T, currentValue: T, set: LdSet<T>) => T,
): T;
reduce(
callbackfn: (previousValue: T, currentValue: T, set: LdSet<T>) => T,
initialValue: T,
): T;
reduce<U>(
callbackfn: (previousValue: U, currentValue: T, set: LdSet<T>) => U,
initialValue: U,
): U;
reduce(callbackfn: any, initialValue?: any): any {
const iterator = this[Symbol.iterator]();
let accumulator;
if (initialValue === undefined) {
const first = iterator.next();
if (first.done) {
throw new TypeError("Reduce of empty collection with no initial value");
}
accumulator = first.value;
} else {
accumulator = initialValue;
}
let result = iterator.next();
while (!result.done) {
accumulator = callbackfn(accumulator, result.value, this);
result = iterator.next();
}
return accumulator;
}
toArray(): T[] {
const arr: T[] = [];
this.forEach((value) => arr.push(value));
return arr;
}
toJSON(): T[] {
return this.toArray();
}
/**
* ===========================================================================
* Set Methods
* ===========================================================================
*/
difference(other: Set<T>): LdSet<T> {
return this.filter((value) => !other.has(value));
}
intersection(other: Set<T>): LdSet<T> {
const newSet = new BasicLdSet<T>();
const iteratingSet = this.size < other.size ? this : other;
const comparingSet = this.size < other.size ? other : this;
for (const value of iteratingSet) {
if (comparingSet.has(value)) {
newSet.add(value);
}
}
return newSet;
}
isDisjointFrom(other: Set<T>): boolean {
const iteratingSet = this.size < other.size ? this : other;
const comparingSet = this.size < other.size ? other : this;
for (const value of iteratingSet) {
if (comparingSet.has(value)) return false;
}
return true;
}
isSubsetOf(other: Set<T>): boolean {
if (this.size > other.size) return false;
for (const value of this) {
if (!other.has(value)) return false;
}
return true;
}
isSupersetOf(other: Set<T>): boolean {
if (this.size < other.size) return false;
for (const value of other) {
if (!this.has(value)) return false;
}
return true;
}
symmetricDifference(other: Set<T>): LdSet<T> {
const newSet = new BasicLdSet<T>();
this.forEach((value) => newSet.add(value));
other.forEach((value) => {
if (newSet.has(value)) {
newSet.delete(value);
} else {
newSet.add(value);
}
});
return newSet;
}
union(other: Set<T>): LdSet<T> {
const newSet = new BasicLdSet<T>();
this.forEach((value) => newSet.add(value));
other.forEach((value) => newSet.add(value));
return newSet;
}
}

@ -0,0 +1,189 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/**
* An abract representation for a set of Linked Data Objects
*/
export interface LdSet<T> extends Set<T> {
/**
* ===========================================================================
* BASE METHODS
* ===========================================================================
*/
/**
* Appends a new element with a specified value to the end of the Set.
*/
add(value: T): this;
/**
* Clears this set of all values, but keeps the values in the datastore
*/
clear(): void;
/**
* Removes a specified value from the Set.
* @returns Returns true if an element in the Set existed and has been removed, or false if the element does not exist.
*/
delete(value: T): boolean;
/**
* @returns a boolean indicating whether an element with the specified value exists in the Set or not.
*/
has(value: T): boolean;
/**
* @returns the number of (unique) elements in Set.
*/
readonly size: number;
/** Iterates over values in the set. */
[Symbol.iterator](): IterableIterator<T>;
/**
* Returns an iterable of [v,v] pairs for every value `v` in the set.
*/
entries(): IterableIterator<[T, T]>;
/**
* Despite its name, returns an iterable of the values in the set.
*/
keys(): IterableIterator<T>;
/**
* Returns an iterable of values in the set.
*/
values(): IterableIterator<T>;
/**
* ===========================================================================
* ITERATOR METHODS
* These methods mimic array methods
* ===========================================================================
*/
/**
* Determines whether all the members of an set satisfy the specified test.
* @param predicate A function that accepts up to two arguments. The every method calls
* the predicate function for each element in the set until the predicate returns a value
* which is coercible to the Boolean value false, or until the end of the set.
* @param thisArg An object to which the this keyword can refer in the predicate function.
* If thisArg is omitted, undefined is used as the this value.
*/
every<S extends T>(
predicate: (value: T, set: LdSet<T>) => value is S,
thisArg?: any,
): this is LdSet<S>;
/**
* Determines whether all the members of an set satisfy the specified test.
* @param predicate A function that accepts up to two arguments. The every method calls
* the predicate function for each element in the set until the predicate returns a value
* which is coercible to the Boolean value false, or until the end of the set.
* @param thisArg An object to which the this keyword can refer in the predicate function.
* If thisArg is omitted, undefined is used as the this value.
*/
every(
predicate: (value: T, set: LdSet<T>) => unknown,
thisArg?: any,
): boolean;
/**
* Determines whether the specified callback function returns true for any element of a set.
* @param predicate A function that accepts up to two arguments. The some method calls
* the predicate function for each element in the set until the predicate returns a value
* which is coercible to the Boolean value true, or until the end of the set.
* @param thisArg An object to which the this keyword can refer in the predicate function.
* If thisArg is omitted, undefined is used as the this value.
*/
some(predicate: (value: T, set: LdSet<T>) => unknown, thisArg?: any): boolean;
/**
* Performs the specified action for each element in an set.
* @param callbackfn A function that accepts up to three arguments. forEach calls the callbackfn function one time for each element in the set. A "value2" is provided for parity with the base set.
* @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
*/
forEach(
callbackfn: (value: T, value2: T, set: LdSet<T>) => void,
thisArg?: any,
): void;
/**
* Calls a defined callback function on each element of an set, and returns a set that contains the results.
* @param callbackfn A function that accepts up to two arguments. The map method calls the callbackfn function one time for each element in the set.
* @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
*/
map<U>(callbackfn: (value: T, set: LdSet<T>) => U, thisArg?: any): U[];
/**
* Returns the elements of a set that meet the condition specified in a callback function.
* @param predicate A function that accepts up to two arguments. The filter method calls the predicate function one time for each element in the set.
* @param thisArg An object to which the this keyword can refer in the predicate function. If thisArg is omitted, undefined is used as the this value.
*/
filter<S extends T>(
predicate: (value: T, set: LdSet<T>) => value is S,
thisArg?: any,
): LdSet<S>;
/**
* Returns the elements of a set that meet the condition specified in a callback function.
* @param predicate A function that accepts up to two arguments. The filter method calls the predicate function one time for each element in the set.
* @param thisArg An object to which the this keyword can refer in the predicate function. If thisArg is omitted, undefined is used as the this value.
*/
filter(
predicate: (value: T, set: LdSet<T>) => unknown,
thisArg?: any,
): LdSet<T>;
/**
* Calls the specified callback function for all the elements in a set. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
* @param callbackfn A function that accepts up to three arguments. The reduce method calls the callbackfn function one time for each element in the set.
* @param initialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.
*/
reduce(
callbackfn: (previousValue: T, currentValue: T, set: LdSet<T>) => T,
): T;
/**
* Calls the specified callback function for all the elements in a set. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
* @param callbackfn A function that accepts up to three arguments. The reduce method calls the callbackfn function one time for each element in the set.
* @param initialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.
*/
reduce(
callbackfn: (previousValue: T, currentValue: T, set: LdSet<T>) => T,
initialValue: T,
): T;
/**
* Calls the specified callback function for all the elements in a set. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
* @param callbackfn A function that accepts up to three arguments. The reduce method calls the callbackfn function one time for each element in the set.
* @param initialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.
*/
reduce<U>(
callbackfn: (previousValue: U, currentValue: T, array: LdSet<T>) => U,
initialValue: U,
): U;
/**
* Converts the current LdSet to an array.
*/
toArray(): T[];
/**
* Converts to JSON
*/
toJSON(): T[];
/**
* ===========================================================================
* EXTENDED SET METHODS
* ===========================================================================
*/
/**
* Returns a new set containing elements in this set but not in the given set.
*/
difference(other: Set<T>): LdSet<T>;
/**
* returns a new set containing elements in both this set and the given set.
*/
intersection(other: Set<T>): LdSet<T>;
/**
* Returns a boolean indicating if this set has no elements in common with the given set.
*/
isDisjointFrom(other: Set<T>): boolean;
/**
* Returns a boolean indicating if all elements of this set are in the given set.
*/
isSubsetOf(other: Set<T>): boolean;
/**
* Returns a boolean indicating if all elements of the given set are in this set.
*/
isSupersetOf(other: Set<T>): boolean;
/**
* Returns a new set containing elements which are in either this set or the given set, but not in both.
*/
symmetricDifference(other: Set<T>): LdSet<T>;
/**
* Returns a new set containing elements which are in either or both of this set and the given set.
*/
union(other: Set<T>): LdSet<T>;
}

@ -0,0 +1,14 @@
import type { RawValue } from "../util/RawObject";
import { BasicLdSet } from "./ldSet/BasicLdSet";
import type { LdSet } from "./ldSet/LdSet";
/**
* Creates an LdSet used by LDO as a list of items.
* @param values The list of items in the set
* @returns An LdSet
*/
export function set<T>(...values: T[]): LdSet<T> {
return new BasicLdSet(
values as Iterable<NonNullable<RawValue>>,
) as unknown as LdSet<T>;
}

@ -12,7 +12,7 @@ import type {
export type SubjectProxy = {
"@id"?: string;
"@context": ContextDefinition;
readonly [key: string | number | symbol]: unknown;
[key: string | number | symbol]: unknown;
readonly [_getUnderlyingDataset]: Dataset;
readonly [_getUnderlyingNode]: NamedNode | BlankNode;
[_proxyContext]: ProxyContext;

@ -1,4 +1,4 @@
import { namedNode, quad } from "@rdfjs/data-model";
import { blankNode, namedNode, quad } from "@rdfjs/data-model";
import type { BlankNode, NamedNode } from "@rdfjs/types";
import { addObjectToDataset } from "../util/addObjectToDataset";
import { deleteValueFromDataset } from "./deleteFromDataset";
@ -62,14 +62,18 @@ export function createSubjectHandler(
proxyContext = value;
return true;
}
if (key === "@id" && typeof value === "string") {
if (
key === "@id" &&
(typeof value === "string" || typeof value == "undefined")
) {
const newSubjectNode = value ? namedNode(value) : blankNode();
// Replace Subject Quads
const currentSubjectQuads = proxyContext.dataset
.match(target["@id"])
.toArray();
const newSubjectQuads = currentSubjectQuads.map((curQuad) =>
quad(
namedNode(value),
newSubjectNode,
curQuad.predicate,
curQuad.object,
curQuad.graph,
@ -87,7 +91,7 @@ export function createSubjectHandler(
quad(
curQuad.subject,
curQuad.predicate,
namedNode(value),
newSubjectNode,
curQuad.graph,
),
);
@ -95,7 +99,7 @@ export function createSubjectHandler(
proxyContext.dataset.delete(curQuad),
);
proxyContext.dataset.addAll(newObjectQuads);
target["@id"] = namedNode(value);
target["@id"] = newSubjectNode;
}
addObjectToDataset(
{ "@id": target["@id"], [key]: value },

@ -1,14 +1,12 @@
import { namedNode, quad } from "@rdfjs/data-model";
import type { Term } from "@rdfjs/types";
import type { SubjectProxyTarget } from "./createSubjectHandler";
import type { ProxyContext } from "../ProxyContext";
import { addObjectToDataset } from "../util/addObjectToDataset";
export function deleteValueFromDataset(
target: SubjectProxyTarget,
key: string | symbol,
proxyContext: ProxyContext,
) {
const nodesToRemove: Term[] = [];
if (key === "@context") {
return true;
}
@ -18,29 +16,18 @@ export function deleteValueFromDataset(
if (typeof key === "symbol") {
return true;
}
const subject = target["@id"];
const predicate = namedNode(
proxyContext.contextUtil.keyToIri(key, proxyContext.getRdfType(subject)),
);
// Remove this node completely if delete on ID
if (key === "@id") {
nodesToRemove.push(target["@id"]);
} else {
const objectDataset = proxyContext.dataset.match(subject, predicate);
if (objectDataset.size === 0) {
return true;
} else {
nodesToRemove.push(...objectDataset.toArray().map((quad) => quad.object));
}
const thisNode = target["@id"];
proxyContext.dataset.deleteMatches(thisNode, undefined, undefined);
proxyContext.dataset.deleteMatches(undefined, undefined, thisNode);
return true;
}
nodesToRemove.forEach((term) => {
if (term.termType === "Literal") {
proxyContext.dataset.delete(quad(subject, predicate, term));
return true;
} else if (term.termType === "NamedNode") {
proxyContext.dataset.deleteMatches(term, undefined, undefined);
proxyContext.dataset.deleteMatches(undefined, undefined, term);
return true;
}
});
// Otherwise, this is essentially treated like setting a key to undefined.
addObjectToDataset(
{ "@id": target["@id"], [key]: undefined },
true,
proxyContext,
);
return true;
}

@ -2,7 +2,7 @@ import type { SubjectProxyTarget } from "./createSubjectHandler";
import { namedNode } from "@rdfjs/data-model";
import { nodeToJsonldRepresentation } from "../util/nodeToJsonldRepresentation";
import type { SubjectProxy } from "./SubjectProxy";
import type { ArrayProxy } from "../arrayProxy/ArrayProxy";
import type { SetProxy } from "../setProxy/SetProxy";
import type { ProxyContext } from "../ProxyContext";
import { filterQuadsByLanguageOrdering } from "../language/languageUtils";
@ -13,7 +13,7 @@ export function getValueForKey(
target: SubjectProxyTarget,
key: string | symbol,
proxyContext: ProxyContext,
): SubjectProxy | ArrayProxy | string | number | boolean | undefined {
): SubjectProxy | SetProxy | string | number | boolean | undefined {
const { contextUtil, dataset } = proxyContext;
if (key === "@id") {
if (target["@id"].termType === "BlankNode") {
@ -35,11 +35,10 @@ export function getValueForKey(
const subject = target["@id"];
const rdfType = proxyContext.getRdfType(subject);
const predicate = namedNode(contextUtil.keyToIri(key, rdfType));
if (contextUtil.isArray(key, rdfType)) {
const arrayProxy = proxyContext.createArrayProxy(
if (contextUtil.isSet(key, rdfType)) {
const arrayProxy = proxyContext.createSetProxy(
[subject, predicate, null, null],
false,
undefined,
contextUtil.isLangString(key, rdfType),
);
return arrayProxy;
@ -53,13 +52,11 @@ export function getValueForKey(
}
if (objectDataset.size === 0) {
return undefined;
} else if (objectDataset.size === 1) {
} else {
const thing = nodeToJsonldRepresentation(
objectDataset.toArray()[0].object,
proxyContext,
);
return thing;
} else {
return proxyContext.createArrayProxy([subject, predicate, null, null]);
}
}

@ -1,11 +1,11 @@
export const _getUnderlyingNode = Symbol("_getUnderlyingNode");
export const _getUnderlyingMatch = Symbol("_getUnderlyingMatch");
export const _isSubjectOriented = Symbol("_isSubjectOriented");
export const _getNodeAtIndex = Symbol("_getNodeAtIndex");
export const _isLangString = Symbol("_isLangString");
export const _getUnderlyingDataset = Symbol("_getUnderlyingDataset");
export const _getUnderlyingArrayTarget = Symbol("_getUnderlyingArrayTarget");
export const _proxyContext = Symbol("_proxyContext");
export const _writeGraphs = Symbol("_writeGraphs");
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type ObjectLike = Record<string | number | symbol, any>;
export type LiteralLike = string | number | boolean;

@ -32,16 +32,4 @@ export class NodeSet {
has(node: ObjectNode): boolean {
return this.set.has(nodeToString(node));
}
delete(node: ObjectNode) {
const key = nodeToString(node);
delete this.map[key];
return this.set.delete(nodeToString(node));
}
toArray() {
return Array.from(this.set).map((stringVal) => {
return this.map[stringVal];
});
}
}

@ -1,13 +1,21 @@
import type { BlankNode, NamedNode } from "@rdfjs/types";
import { _getUnderlyingNode } from "../types";
import type { LdSet } from "../setProxy/ldSet/LdSet";
import type { SubjectProxy } from "../subjectProxy/SubjectProxy";
export type RawObject =
| ({
"@id"?: string | NamedNode | BlankNode;
} & {
[key: string | symbol | number]: RawValue | RawValue[];
[key: string | symbol | number]: RawValue | LdSet<RawValue>;
})
| SubjectProxy;
export type RawValue = string | boolean | number | RawObject | undefined;
export type RawValue =
| string
| boolean
| number
| RawObject
| NamedNode
| BlankNode
| undefined;

@ -12,6 +12,7 @@ import {
languageDeleteMatch,
languageKeyToLiteralLanguage,
} from "../language/languageUtils";
import { BasicLdSet } from "../setProxy/ldSet/BasicLdSet";
export function addRawValueToDatasetRecursive(
subject: NamedNode | BlankNode,
@ -25,7 +26,11 @@ export function addRawValueToDatasetRecursive(
const rdfType = proxyContext.getRdfType(subject);
const predicate = namedNode(contextUtil.keyToIri(key, rdfType));
// Get the Object Node
const object = getNodeFromRawValue(key, value, rdfType, proxyContext);
const object = getNodeFromRawValue(
value,
proxyContext,
contextUtil.getDataType(key, rdfType),
);
if (object == undefined) {
dataset.deleteMatches(subject, predicate);
} else if (object.termType === "Literal") {
@ -77,9 +82,6 @@ export function addRawObjectToDatasetRecursive(
shouldDeleteOldTriples: boolean,
proxyContext: ProxyContext,
): SubjectProxy {
if (isSubjectProxy(item)) {
return item as SubjectProxy;
}
const { dataset } = proxyContext;
const subject = getNodeFromRawObject(item, proxyContext.contextUtil);
const rdfType = proxyContext.getRdfType(subject);
@ -106,7 +108,7 @@ export function addRawObjectToDatasetRecursive(
dataset.deleteMatches(subject, predicate);
}
}
if (Array.isArray(value)) {
if (value instanceof BasicLdSet) {
value.forEach((valueItem) => {
addRawValueToDatasetRecursive(
subject,

@ -23,10 +23,10 @@ export function getNodeFromRawObject(
}
export function getNodeFromRawValue(
key: string,
value: RawValue,
rdfTypes: NamedNode[],
proxyContext: ProxyContext,
// To get this run proxyContext.contextUtil.getDataType(key, proxyContext.getRdfType(subjectNode))
datatype?: string,
): BlankNode | NamedNode | Literal | undefined {
// Get the Object Node
if (value == undefined) {
@ -36,14 +36,19 @@ export function getNodeFromRawValue(
typeof value === "boolean" ||
typeof value === "number"
) {
// PICKUP: figure out how to handle looking for the RDF Types of a raw value
const datatype = proxyContext.contextUtil.getDataType(key, rdfTypes);
if (datatype === "@id") {
if (!datatype) {
return undefined;
} else if (datatype === "@id") {
return namedNode(value.toString());
} else {
return literal(value.toString(), datatype);
}
} else if (
typeof value.termType === "string" &&
(value.termType === "NamedNode" || value.termType === "BlankNode")
) {
return value as NamedNode | BlankNode;
} else {
return getNodeFromRawObject(value, proxyContext.contextUtil);
return getNodeFromRawObject(value as RawObject, proxyContext.contextUtil);
}
}

@ -1,18 +1,19 @@
import type { ArrayProxy } from "../arrayProxy/ArrayProxy";
import { isArrayProxy } from "../arrayProxy/isArrayProxy";
import { isSetProxy } from "../setProxy/isSetProxy";
import type { LdSet } from "../setProxy/ldSet/LdSet";
import type { SetProxy } from "../setProxy/SetProxy";
import { isSubjectProxy } from "../subjectProxy/isSubjectProxy";
import type { SubjectProxy } from "../subjectProxy/SubjectProxy";
import type { ObjectLike } from "../types";
export function isProxy(
someObject?: unknown,
): someObject is ArrayProxy | SubjectProxy {
return isSubjectProxy(someObject) || isArrayProxy(someObject);
): someObject is SetProxy | SubjectProxy {
return isSubjectProxy(someObject) || isSetProxy(someObject);
}
export function getProxyFromObject(
object: ObjectLike | ObjectLike[],
): SubjectProxy | ArrayProxy {
object: ObjectLike | LdSet<ObjectLike>,
): SubjectProxy | SetProxy {
if (!isProxy(object)) {
throw new Error(`${object} is not a Jsonld Dataset Proxy`);
}

@ -1,8 +1,9 @@
import type { Literal, Quad_Object } from "@rdfjs/types";
import type { ProxyContext } from "../ProxyContext";
import type { SubjectProxy } from "../subjectProxy/SubjectProxy";
import type { LiteralLike } from "../types";
export type ObjectJsonRepresentation = string | number | boolean | SubjectProxy;
export type ObjectJsonRepresentation = LiteralLike | SubjectProxy;
export function literalToJsonldRepresentation(literal: Literal) {
switch (literal.datatype.value) {
@ -66,7 +67,7 @@ export function literalToJsonldRepresentation(literal: Literal) {
export function nodeToJsonldRepresentation(
node: Quad_Object,
proxyContext: ProxyContext,
): string | number | boolean | SubjectProxy {
): ObjectJsonRepresentation {
if (node.termType === "Literal") {
return literalToJsonldRepresentation(node);
} else if (node.termType === "NamedNode" || node.termType === "BlankNode") {

@ -0,0 +1,325 @@
import { namedNode } from "@rdfjs/data-model";
import jsonldDatasetProxy, { BasicLdSet, _getUnderlyingNode } from "../src";
import { createDataset } from "@ldo/dataset";
describe("BasicLdSet", () => {
describe("constructor and add", () => {
test("should add primitive values correctly", () => {
const set = new BasicLdSet<number>();
expect(set.size).toBe(0);
set.add(1);
expect(set.size).toBe(1);
expect(set.has(1)).toBe(true);
// Duplicate primitives should not increase size.
set.add(1);
expect(set.size).toBe(1);
});
test('should add objects with "@id" as string correctly', () => {
const obj1 = { "@id": "testId" };
const set = new BasicLdSet();
set.add(obj1);
expect(set.has(obj1)).toBe(true);
expect(set.size).toBe(1);
// A different object with the same "@id" should be considered a duplicate.
const obj2 = { "@id": "testId" };
set.add(obj2);
expect(set.size).toBe(1);
});
test('should add objects with "@id" as an object correctly', () => {
// In this case the object’s "@id" is a string already.
const obj1 = { "@id": "testIdObj" };
const set = new BasicLdSet();
set.add(obj1);
expect(set.has(obj1)).toBe(true);
expect(set.size).toBe(1);
// A different object with an equivalent "@id" should not increase the size.
const obj2 = { "@id": "testIdObj" };
set.add(obj2);
expect(set.size).toBe(1);
});
test("should add LinkedDataObject", () => {
// In this case the object’s "@id" is a string already.
const obj1 = jsonldDatasetProxy(createDataset(), {}).fromSubject(
namedNode("testIdObj"),
);
const set = new BasicLdSet();
set.add(obj1);
expect(set.has(obj1)).toBe(true);
expect(set.size).toBe(1);
// A different object with an equivalent "@id" should not increase the size.
const obj2 = { "@id": "testIdObj" };
set.add(obj2);
expect(set.size).toBe(1);
});
test("should add objects with underlying nodes correctly", () => {
// Here we simulate a case where the object has a NamedNode stored as its "@id"
// which in turn yields its .value.
const obj1 = { "@id": namedNode("testIdObj") };
const set = new BasicLdSet();
set.add(obj1);
expect(set.has(obj1)).toBe(true);
expect(set.size).toBe(1);
// A different object with an equivalent "@id".value should not increase the size.
const obj2 = { "@id": "testIdObj" };
set.add(obj2);
expect(set.size).toBe(1);
});
test('should treat objects with no "@id" as unique even if same reference', () => {
// When an object does not have "@id" (or _getUnderlyingNode),
// the hashFn falls back to generating a new blank node each time.
const obj = {};
const set = new BasicLdSet();
set.add(obj);
// Adding the same object twice produces two different hash keys.
set.add(obj);
expect(set.size).toBe(2);
});
test("should initialize with iterable values", () => {
const set = new BasicLdSet<number>([1, 2, 3, 3]);
expect(set.size).toBe(3);
expect([...set]).toEqual([1, 2, 3]);
});
});
describe("clear", () => {
test("should clear all elements", () => {
const set = new BasicLdSet<number>([1, 2, 3]);
expect(set.size).toBe(3);
set.clear();
expect(set.size).toBe(0);
expect([...set]).toEqual([]);
});
});
describe("delete", () => {
test("should delete an existing element and return true", () => {
const set = new BasicLdSet<number>([1, 2, 3]);
expect(set.delete(2)).toBe(true);
expect(set.has(2)).toBe(false);
expect(set.size).toBe(2);
});
test("should return false when deleting a non-existent element", () => {
const set = new BasicLdSet<number>([1, 2, 3]);
expect(set.delete(4)).toBe(false);
expect(set.size).toBe(3);
});
});
describe("has", () => {
test("should correctly identify the presence of elements", () => {
const set = new BasicLdSet<number>([1, 2, 3]);
expect(set.has(1)).toBe(true);
expect(set.has(4)).toBe(false);
});
});
describe("iteration functions", () => {
test("every should return true if all elements satisfy the predicate", () => {
const set = new BasicLdSet<number>([2, 4, 6]);
const result = set.every((num) => num % 2 === 0);
expect(result).toBe(true);
});
test("every should return false if any element fails the predicate", () => {
const set = new BasicLdSet<number>([2, 3, 6]);
const result = set.every((num) => num % 2 === 0);
expect(result).toBe(false);
});
test("some should return true if any element satisfies the predicate", () => {
const set = new BasicLdSet<number>([1, 3, 4]);
const result = set.some((num) => num % 2 === 0);
expect(result).toBe(true);
});
test("some should return false if no element satisfies the predicate", () => {
const set = new BasicLdSet<number>([1, 3, 5]);
const result = set.some((num) => num % 2 === 0);
expect(result).toBe(false);
});
test("forEach should call the callback for each element", () => {
const set = new BasicLdSet<number>([1, 2, 3]);
const mockFn = jest.fn();
set.forEach((value, value2, collection) => {
expect(collection).toBe(set);
expect(value).toBe(value2);
mockFn(value);
});
expect(mockFn).toHaveBeenCalledTimes(3);
expect(mockFn).toHaveBeenCalledWith(1);
expect(mockFn).toHaveBeenCalledWith(2);
expect(mockFn).toHaveBeenCalledWith(3);
});
test("map should return an array with mapped values", () => {
const set = new BasicLdSet<number>([1, 2, 3]);
const result = set.map((num) => num * 2);
expect(result).toEqual([2, 4, 6]);
});
test("filter should return a new set with filtered elements", () => {
const set = new BasicLdSet<number>([1, 2, 3, 4]);
const filtered = set.filter((num) => num % 2 === 0);
expect(filtered.size).toBe(2);
expect(filtered.has(2)).toBe(true);
expect(filtered.has(4)).toBe(true);
});
test("reduce should work without an initial value", () => {
const set = new BasicLdSet<number>([1, 2, 3, 4]);
const result = set.reduce((acc, curr) => acc + curr);
expect(result).toBe(10);
});
test("reduce should work with an initial value", () => {
const set = new BasicLdSet<number>([1, 2, 3, 4]);
const result = set.reduce((acc, curr) => acc + curr, 10);
expect(result).toBe(20);
});
test("reduce should throw an error for an empty set without an initial value", () => {
const set = new BasicLdSet<number>();
expect(() => {
set.reduce((acc, curr) => acc + curr);
}).toThrow("Reduce of empty collection with no initial value");
});
test("toArray and toJSON should return an array of elements", () => {
const elements = [1, 2, 3];
const set = new BasicLdSet<number>(elements);
expect(set.toArray()).toEqual(elements);
expect(set.toJSON()).toEqual(elements);
});
});
describe("set operations", () => {
test("difference should return elements in the first set not present in the second", () => {
const set1 = new BasicLdSet<number>([1, 2, 3, 4]);
const set2 = new Set<number>([3, 4, 5]);
const diff = set1.difference(set2);
expect(diff.size).toBe(2);
expect(diff.has(1)).toBe(true);
expect(diff.has(2)).toBe(true);
});
test("intersection should return only the common elements", () => {
const set1 = new BasicLdSet<number>([1, 2, 3, 4]);
const set2 = new BasicLdSet<number>([3, 4, 5]);
const inter = set1.intersection(set2);
expect(inter.size).toBe(2);
expect(inter.has(3)).toBe(true);
expect(inter.has(4)).toBe(true);
const inter2 = set2.intersection(set1);
expect(inter2.size).toBe(2);
expect(inter2.has(3)).toBe(true);
expect(inter2.has(4)).toBe(true);
});
test("isDisjointFrom should return true if the sets have no common elements", () => {
const set1 = new BasicLdSet<number>([1, 2]);
const set2 = new BasicLdSet<number>([3, 4, 5]);
expect(set1.isDisjointFrom(set2)).toBe(true);
expect(set2.isDisjointFrom(set1)).toBe(true);
});
test("isDisjointFrom should return false if the sets share elements", () => {
const set1 = new BasicLdSet<number>([1, 2]);
const set2 = new Set<number>([2, 3]);
expect(set1.isDisjointFrom(set2)).toBe(false);
});
test("isSubsetOf should return true when the set is a subset of another", () => {
const set1 = new BasicLdSet<number>([1, 2]);
const set2 = new BasicLdSet<number>([1, 2, 3]);
expect(set1.isSubsetOf(set2)).toBe(true);
expect(set2.isSubsetOf(set1)).toBe(false);
});
test("isSubsetOf should return false when the set is not a subset of another", () => {
const set1 = new BasicLdSet<number>([1, 2, 4]);
const set2 = new Set<number>([1, 2, 3]);
expect(set1.isSubsetOf(set2)).toBe(false);
});
test("isSupersetOf should return true when the set is a superset of another", () => {
const set1 = new BasicLdSet<number>([1, 2, 3]);
const set2 = new Set<number>([1, 2]);
expect(set1.isSupersetOf(set2)).toBe(true);
});
test("isSupersetOf should return false when the set is larger", () => {
const set1 = new BasicLdSet<number>([1, 2]);
const set2 = new BasicLdSet<number>([1, 2, 3]);
expect(set1.isSupersetOf(set2)).toBe(false);
});
test("isSupersetOf should return false when the set is not a superset of another", () => {
const set1 = new BasicLdSet<number>([1, 2, 5]);
const set2 = new BasicLdSet<number>([1, 2, 3]);
expect(set1.isSupersetOf(set2)).toBe(false);
});
test("symmetricDifference should return the symmetric difference of two sets", () => {
const set1 = new BasicLdSet<number>([1, 2, 3]);
const set2 = new Set<number>([2, 3, 4]);
const symDiff = set1.symmetricDifference(set2);
expect(symDiff.size).toBe(2);
expect(symDiff.has(1)).toBe(true);
expect(symDiff.has(4)).toBe(true);
});
test("union should return the union of two sets", () => {
const set1 = new BasicLdSet<number>([1, 2]);
const set2 = new Set<number>([2, 3]);
const union = set1.union(set2);
expect(union.size).toBe(3);
expect(union.has(1)).toBe(true);
expect(union.has(2)).toBe(true);
expect(union.has(3)).toBe(true);
});
});
describe("iterator methods", () => {
test("entries returns pairs [value, value]", () => {
const set = new BasicLdSet<number>([1, 2, 3]);
const entries = Array.from(set.entries());
expect(entries).toEqual([
[1, 1],
[2, 2],
[3, 3],
]);
});
test("keys returns all values", () => {
const set = new BasicLdSet<number>([1, 2, 3]);
const keys = Array.from(set.keys());
expect(keys).toEqual([1, 2, 3]);
});
test("values returns all values", () => {
const set = new BasicLdSet<number>([1, 2, 3]);
const values = Array.from(set.values());
expect(values).toEqual([1, 2, 3]);
});
test("iterator returns all values", () => {
const set = new BasicLdSet<number>([1, 2, 3]);
const iterated = [...set];
expect(iterated).toEqual([1, 2, 3]);
});
test("toStringTag returns 'BasicLdSet'", () => {
const set = new BasicLdSet<number>();
expect(Object.prototype.toString.call(set)).toBe("[object BasicLdSet]");
expect(set[Symbol.toStringTag]).toBe("BasicLdSet");
});
});
});

@ -53,13 +53,11 @@ describe("ContextUtil", () => {
});
});
describe("isArray", () => {
describe("isSet", () => {
it("indicates that the special @isCollection field means array", () => {
const contextUtil = new ContextUtil(scopedContext);
expect(
contextUtil.isArray("element", [
namedNode("http://example.com/Avatar"),
]),
contextUtil.isSet("element", [namedNode("http://example.com/Avatar")]),
).toBe(true);
});
});

@ -1,7 +1,7 @@
import {
getProxyFromObject,
getSubjectProxyFromObject,
isArrayProxy,
isSetProxy,
isSubjectProxy,
} from "../src";
@ -25,12 +25,12 @@ describe("isProxy", () => {
});
});
describe("isArrayProxy", () => {
describe("isSetProxy", () => {
it("returns false if undefined is passed as a parameter", () => {
expect(isArrayProxy(undefined)).toBe(false);
expect(isSetProxy(undefined)).toBe(false);
});
it("returns false if string is passed as a parameter", () => {
expect(isArrayProxy("hello")).toBe(false);
expect(isSetProxy("hello")).toBe(false);
});
});

@ -1,6 +1,7 @@
import type { ContextDefinition } from "jsonld";
import type { Schema } from "shexj";
import type { LdoJsonldContext } from "../src/LdoJsonldContext";
import type { LdSet } from "../src";
export interface ObservationShape {
"@id"?: string;
@ -15,12 +16,12 @@ export type PatientShape = {
"@id"?: string;
"@context"?: ContextDefinition;
type: { "@id": "Patient" };
name?: string[];
langName?: string[];
name?: LdSet<string>;
langName?: LdSet<string>;
birthdate?: string;
age?: number;
isHappy?: boolean;
roommate?: PatientShape[];
roommate?: LdSet<PatientShape>;
};
// No need to fully define the schema because this library doesn't use it

@ -1,7 +1,7 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist"
"outDir": "./dist",
},
"include": ["./src"]
}

@ -4,7 +4,7 @@
## Guide
A full walkthrough for using the `@ldo/ldo` library can be found in the [For RDF Usage Guide](https://ldo.js.org/raw_rdf/).
A full walkthrough for using the `@ldo/ldo` library can be found in the [For RDF Usage Guide](https://ldo.js.org/latest/raw_rdf/).
## Installation
@ -16,20 +16,30 @@ cd my_project/
npx run @ldo/cli init
```
### Manual Installation
<details>
<summary>
Manual Installation
</summary>
If you already have generated ShapeTypes, you may install the `@ldo/ldo` library independently.
```
npm i @ldo/ldo
```
</details>
## Simple Example
Below is a simple example of LDO in a real use-case (changing the name on a Solid Pod). Assume that a ShapeType was previously generated and placed at `./.ldo/foafProfile.shapeTypes`.
```typescript
import { parseRdf, startTransaction, toSparqlUpdate, toTurtle } from "@ldo/ldo";
import {
parseRdf,
startTransaction,
toSparqlUpdate,
toTurtle,
set,
} from "@ldo/ldo";
import { FoafProfileShapeType } from "./.ldo/foafProfile.shapeTypes";
async function run() {
@ -60,26 +70,26 @@ async function run() {
// Logs "Person"
console.log(janeProfile.type);
// Logs 0
console.log(janeProfile.knows?.length);
console.log(janeProfile.knows?.size);
// Begins a transaction that tracks your changes
startTransaction(janeProfile);
janeProfile.name = "Jane Smith";
janeProfile.knows?.push({
janeProfile.knows?.add({
"@id": "https://solidweb.me/john_smith/profile/card#me",
type: {
"@id": "Person",
},
name: "John Smith",
knows: [janeProfile],
knows: set(janeProfile),
});
// Logs "Jane Smith"
console.log(janeProfile.name);
// Logs "John Smith"
console.log(janeProfile.knows?.[0].name);
console.log(janeProfile.knows?.toArray()[0].name);
// Logs "Jane Smith"
console.log(janeProfile.knows?.[0].knows?.[0].name);
console.log(janeProfile.knows?.toArray()[0].knows?.toArray()[0].name);
/**
* Step 3: Convert it back to RDF
@ -112,45 +122,45 @@ run();
Types
- [`LdoBase`](https://ldo.js.org/api/ldo/LdoBase/)
- [`ShapeType`](https://ldo.js.org/api/ldo/ShapeType/)
- [`LdoBase`](https://ldo.js.org/latest/api/ldo/LdoBase/)
- [`ShapeType`](https://ldo.js.org/latest/api/ldo/ShapeType/)
Getting an LdoDataset
- [`parseRdf`](https://ldo.js.org/api/ldo/parseRdf/)
- [`createLdoDatasetFactory`](https://ldo.js.org/api/ldo/createLdoDatasetFactory/)
- [`LdoDatasetFactory`](https://ldo.js.org/api/ldo/LdoDatasetFactory/)
- [`createLdoDataset`](https://ldo.js.org/api/ldo/createLdoDataset/)
- [`LdoDataset`](https://ldo.js.org/api/ldo/LdoDataset/)
- [`parseRdf`](https://ldo.js.org/latest/api/ldo/parseRdf/)
- [`createLdoDatasetFactory`](https://ldo.js.org/latest/api/ldo/createLdoDatasetFactory/)
- [`LdoDatasetFactory`](https://ldo.js.org/latest/api/ldo/LdoDatasetFactory/)
- [`createLdoDataset`](https://ldo.js.org/latest/api/ldo/createLdoDataset/)
- [`LdoDataset`](https://ldo.js.org/latest/api/ldo/LdoDataset/)
Getting a Linked Data Object
- [`LdoBuilder`](https://ldo.js.org/api/ldo/LdoBuilder/)
- [`LdoBuilder`](https://ldo.js.org/latest/api/ldo/LdoBuilder/)
Converting a Linked Data Object to Raw RDF
- [`toTurtle`](https://ldo.js.org/api/ldo/toTurtle/)
- [`toNTriples`](https://ldo.js.org/api/ldo/toNTriples/)
- [`serialize`](https://ldo.js.org/api/ldo/serialize/)
- [`toTurtle`](https://ldo.js.org/latest/api/ldo/toTurtle/)
- [`toNTriples`](https://ldo.js.org/latest/api/ldo/toNTriples/)
- [`serialize`](https://ldo.js.org/latest/api/ldo/serialize/)
Transactions
- [transactions](https://ldo.js.org/api/ldo/transactions/)
- [`toSparqlUpdate`](https://ldo.js.org/api/ldo/toSparqlUpdate/)
- [transactions](https://ldo.js.org/latest/api/ldo/transactions/)
- [`toSparqlUpdate`](https://ldo.js.org/latest/api/ldo/toSparqlUpdate/)
Language Tag Support
- [`languageOf`](https://ldo.js.org/api/ldo/languageOf/)
- [`setLanguagePreferences`](https://ldo.js.org/api/ldo/setLanguagePreferences/)
- [`languageOf`](https://ldo.js.org/latest/api/ldo/languageOf/)
- [`setLanguagePreferences`](https://ldo.js.org/latest/api/ldo/setLanguagePreferences/)
Graph Support
- [`graphOf`](https://ldo.js.org/api/ldo/graphOf/)
- [`write`](https://ldo.js.org/api/ldo/write/)
- [`graphOf`](https://ldo.js.org/latest/api/ldo/graphOf/)
- [`write`](https://ldo.js.org/latest/api/ldo/write/)
Other Helper Functions
- [`getDataset`](https://ldo.js.org/api/ldo/getDataset/)
- [`getDataset`](https://ldo.js.org/latest/api/ldo/getDataset/)
## 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/).

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

@ -2,6 +2,7 @@ import type { GraphNode, QuadMatch, SubjectNode } from "@ldo/rdf-utils";
import type {
LanguageOrdering,
JsonldDatasetProxyBuilder,
LdSet,
} from "@ldo/jsonld-dataset-proxy";
import type { ShapeType } from "./ShapeType";
import type { LdoBase } from "./util";
@ -93,7 +94,7 @@ export class LdoBuilder<Type extends LdoBase> {
predicate: QuadMatch[1] | string,
object?: QuadMatch[2] | string,
graph?: QuadMatch[3] | string,
): Type[] {
): LdSet<Type> {
return this.jsonldDatasetProxyBuilder.matchSubject<Type>(
predicate != undefined ? normalizeNodeName(predicate) : undefined,
object != undefined ? normalizeNodeName(object) : undefined,
@ -123,7 +124,7 @@ export class LdoBuilder<Type extends LdoBase> {
subject?: QuadMatch[0] | string,
predicate?: QuadMatch[1] | string,
graph?: QuadMatch[3] | string,
): Type[] {
): LdSet<Type> {
return this.jsonldDatasetProxyBuilder.matchObject<Type>(
subject != undefined ? normalizeNodeName(subject) : undefined,
predicate != undefined ? normalizeNodeName(predicate) : undefined,

@ -8,3 +8,4 @@ export * from "./createLdoDataset";
import type { LdoBase as LdoBaseImport } from "./util";
export type LdoBase = LdoBaseImport;
export * from "./types";
export { LdSet, LdoJsonldContext, set } from "@ldo/jsonld-dataset-proxy";

@ -1,6 +1,6 @@
import { namedNode } from "@rdfjs/data-model";
import type { Dataset, Quad } from "@rdfjs/types";
import type { ArrayProxy, SubjectProxy } from "@ldo/jsonld-dataset-proxy";
import type { SetProxy, SubjectProxy } from "@ldo/jsonld-dataset-proxy";
import {
getProxyFromObject,
_getUnderlyingDataset,
@ -57,7 +57,7 @@ export function isTransactionalDataset(
export function getTransactionalDatasetFromLdo(
ldo: LdoBase,
): [ITransactionDataset<Quad>, SubjectProxy | ArrayProxy] {
): [ITransactionDataset<Quad>, SubjectProxy | SetProxy] {
const proxy = getProxyFromObject(ldo);
const dataset = proxy[_getUnderlyingDataset];
if (

@ -3,7 +3,7 @@ import { createDataset } from "@ldo/dataset";
import type { SolidProfileShape } from "./profileData";
import { ProfileShapeType } from "./profileData";
import type { LdoBuilder, LdoDataset } from "../src";
import { createLdoDataset, graphOf, parseRdf, toTurtle } from "../src";
import { createLdoDataset, graphOf, parseRdf, toTurtle, set } from "../src";
import { sampleJsonld, sampleTurtle } from "./sampleData";
import type { SubjectProxy } from "@ldo/jsonld-dataset-proxy";
import { _proxyContext } from "@ldo/jsonld-dataset-proxy";
@ -33,7 +33,7 @@ describe("LdoDataset", () => {
it("initializes a profile using the fromJson method", () => {
const profile = profileBuilder.fromJson({
type: [{ "@id": "Person" }, { "@id": "Person2" }],
type: set({ "@id": "Person" }, { "@id": "Person2" }),
inbox: { "@id": "https://inbox.com" },
fn: "Diplo",
});
@ -45,7 +45,7 @@ describe("LdoDataset", () => {
it("initializes a profile with an id using the fromJson method", () => {
const profile = profileBuilder.fromJson({
"@id": "https://example.com/person1",
type: [{ "@id": "Person" }, { "@id": "Person2" }],
type: set({ "@id": "Person" }, { "@id": "Person2" }),
inbox: { "@id": "https://inbox.com" },
fn: "Diplo",
});
@ -128,7 +128,7 @@ describe("LdoDataset", () => {
"http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
"http://xmlns.com/foaf/0.1/Person",
);
expect(profiles[0].fn).toBe("Jackson Morgan");
expect(profiles.toArray()[0].fn).toBe("Jackson Morgan");
});
it("Handles alternate optionality for subject match", () => {
@ -137,7 +137,7 @@ describe("LdoDataset", () => {
undefined,
"https://someGraph.com",
);
expect(profiles.length).toBe(0);
expect(profiles.size).toBe(0);
});
it("Lets a match query retrieve objects", () => {
@ -145,7 +145,7 @@ describe("LdoDataset", () => {
null,
"http://xmlns.com/foaf/0.1/primaryTopic",
);
expect(profiles[0].fn).toBe("Jackson Morgan");
expect(profiles.toArray()[0].fn).toBe("Jackson Morgan");
});
it("Handles alternate optionality for object match", () => {
@ -154,7 +154,7 @@ describe("LdoDataset", () => {
undefined,
"https://someGraph.com",
);
expect(profiles.length).toBe(0);
expect(profiles.size).toBe(0);
});
it("Sets language preferences", () => {

@ -1,6 +1,6 @@
import type { Schema } from "shexj";
import type { ContextDefinition } from "jsonld";
import type { ShapeType } from "../src";
import type { LdSet, ShapeType } from "../src";
export const profileShex: Schema = {
type: "Schema",
@ -978,7 +978,7 @@ export interface SolidProfileShape {
/**
* Defines the node as a Person (from Schema.org) | Defines the node as a Person (from foaf)
*/
type: ({ "@id": "Person" } | { "@id": "Person2" })[];
type: LdSet<{ "@id": "Person" } | { "@id": "Person2" }>;
/**
* The formatted name of a person. Example: John Smith
*/
@ -990,11 +990,11 @@ export interface SolidProfileShape {
/**
* The person's street address.
*/
hasAddress?: AddressShape[];
hasAddress?: LdSet<AddressShape>;
/**
* The person's email.
*/
hasEmail?: EmailShape[];
hasEmail?: LdSet<EmailShape>;
/**
* A link to the person's photo
*/
@ -1006,7 +1006,7 @@ export interface SolidProfileShape {
/**
* Person's telephone number
*/
hasTelephone?: PhoneNumberShape[];
hasTelephone?: LdSet<PhoneNumberShape>;
/**
* An alternative way to define a person's telephone number using a string
*/
@ -1022,11 +1022,11 @@ export interface SolidProfileShape {
/**
* A list of app origins that are trusted by this user
*/
trustedApp?: TrustedAppShape[];
trustedApp?: LdSet<TrustedAppShape>;
/**
* A list of RSA public keys that are associated with private keys the user holds.
*/
key?: RSAPublicKeyShape[];
key?: LdSet<RSAPublicKeyShape>;
/**
* The user's LDP inbox to which apps can post notifications
*/
@ -1038,7 +1038,7 @@ export interface SolidProfileShape {
/**
* The location of a Solid storage server related to this WebId
*/
storage?: string[];
storage?: LdSet<string>;
/**
* The user's account
*/
@ -1046,15 +1046,15 @@ export interface SolidProfileShape {
/**
* A registry of all types used on the user's Pod (for private access only)
*/
privateTypeIndex?: string[];
privateTypeIndex?: LdSet<string>;
/**
* A registry of all types used on the user's Pod (for public access)
*/
publicTypeIndex?: string[];
publicTypeIndex?: LdSet<string>;
/**
* A list of WebIds for all the people this user knows.
*/
knows?: SolidProfileShape[];
knows?: LdSet<SolidProfileShape>;
}
export interface TrustedAppShape {
@ -1063,12 +1063,12 @@ export interface TrustedAppShape {
/**
* The level of access provided to this origin
*/
mode: (
mode: LdSet<
| { "@id": "Append" }
| { "@id": "Control" }
| { "@id": "Read" }
| { "@id": "Write" }
)[];
>;
/**
* The app origin the user trusts
*/

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

@ -7,9 +7,6 @@ Turn ShexJ into typescript typings and JSON-LD context.
npm i @ldo/schema-converter-shex
```
## API
See the [full API docs](docs/modules.md).
## Usage
```typescript
@ -83,14 +80,15 @@ async function run() {
/*
Logs:
declare namespace {
interface EmployeeShape {
givenName: string[];
familyName: string;
phone?: string[];
mbox: string;
}
import { LdoJsonldContext, LdSet } from "@ldo/ldo";
interface EmployeeShape {
"@id"?: string;
"@context"?: LdoJsonldContext;
givenName: LdSet<string>;
familyName: string;
phone?: LdSet<string>;
mbox: string;
}
*/
console.log(typings.typingsString);

@ -1,6 +1,6 @@
{
"name": "@ldo/schema-converter-shex",
"version": "0.0.1-alpha.29",
"version": "1.0.0-alpha.1",
"description": "",
"main": "dist/index.js",
"scripts": {
@ -20,7 +20,7 @@
},
"homepage": "https://github.com/o-development/ldobjects/tree/main/packages/schema-converter-shex#readme",
"devDependencies": {
"@ldo/jsonld-dataset-proxy": "^0.0.1-alpha.29",
"@ldo/jsonld-dataset-proxy": "^1.0.0-alpha.1",
"@shexjs/parser": "^1.0.0-alpha.24",
"@types/jest": "^27.0.3",
"@types/jsonld": "^1.5.6",
@ -34,7 +34,7 @@
"dist"
],
"dependencies": {
"@ldo/traverser-shexj": "^0.0.1-alpha.29",
"@ldo/traverser-shexj": "^1.0.0-alpha.1",
"dts-dom": "~3.6.0",
"jsonld2graphobject": "^0.0.5"
},

@ -76,14 +76,21 @@ export class JsonLdContextBuilder {
return this.iriTypes[rdfType] as JsonLdContextBuilder;
}
addSubject(iri: string, rdfType?: string, annotations?: Annotation[]) {
private getRelevantBuilders(rdfType?: string): JsonLdContextBuilder[] {
const relevantBuilder = this.getRelevantBuilder(rdfType);
if (!relevantBuilder.iriAnnotations[iri]) {
relevantBuilder.iriAnnotations[iri] = [];
}
if (annotations && annotations.length > 0) {
relevantBuilder.iriAnnotations[iri].push(...annotations);
}
return relevantBuilder === this ? [this] : [this, relevantBuilder];
}
addSubject(iri: string, rdfType?: string, annotations?: Annotation[]) {
const relevantBuilders = this.getRelevantBuilders(rdfType);
relevantBuilders.forEach((relevantBuilder) => {
if (!relevantBuilder.iriAnnotations[iri]) {
relevantBuilder.iriAnnotations[iri] = [];
}
if (annotations && annotations.length > 0) {
relevantBuilder.iriAnnotations[iri].push(...annotations);
}
});
}
addPredicate(
@ -93,41 +100,30 @@ export class JsonLdContextBuilder {
rdfType?: string,
annotations?: Annotation[],
) {
const relevantBuilder = this.getRelevantBuilder(rdfType);
relevantBuilder.addSubject(iri, undefined, annotations);
if (!relevantBuilder.iriTypes[iri]) {
relevantBuilder.iriTypes[iri] = expandedTermDefinition;
if (isContainer) {
relevantBuilder.iriTypes[iri]["@isCollection"] = true;
}
} else {
const curDef = relevantBuilder.iriTypes[iri];
const newDef = expandedTermDefinition;
// TODO: if you reuse the same predicate with a different cardinality,
// it will overwrite the past cardinality. Perhapse we might want to
// split contexts in the various shapes.
if (isContainer) {
curDef["@isCollection"] = true;
}
// If the old and new versions both have types
if (curDef["@type"] && newDef["@type"]) {
if (
Array.isArray(curDef["@type"]) &&
!(curDef["@type"] as string[]).includes(newDef["@type"])
) {
curDef["@type"].push(newDef["@type"]);
} else if (
typeof curDef["@type"] === "string" &&
curDef["@type"] !== newDef["@type"]
) {
// The typings are incorrect. String arrays are allowed on @type
// see https://w3c.github.io/json-ld-syntax/#example-specifying-multiple-types-for-a-node
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
curDef["@type"] = [curDef["@type"], newDef["@type"]];
const relevantBuilders = this.getRelevantBuilders(rdfType);
relevantBuilders.forEach((relevantBuilder) => {
relevantBuilder.addSubject(iri, undefined, annotations);
if (!relevantBuilder.iriTypes[iri]) {
relevantBuilder.iriTypes[iri] = expandedTermDefinition;
if (isContainer) {
relevantBuilder.iriTypes[iri]["@isCollection"] = true;
}
} else {
const curDef = relevantBuilder.iriTypes[iri];
const newDef = expandedTermDefinition;
if (isContainer) {
curDef["@isCollection"] = true;
}
// If the old and new versions both have types
if (curDef["@type"] && newDef["@type"]) {
if (curDef["@type"] !== newDef["@type"]) {
console.warn(
`You've specified that a specific field "${iri}" can have an object of multiple literal types (${curDef["@type"]} or ${newDef["@type"]}). This is not expressable in JSON-LD context, and we will randomly select one type to use.`,
);
}
}
}
}
});
}
generateNames(): Record<string, string> {

@ -115,7 +115,7 @@ export const ShexJTypingTransformer = ShexJTraverser.createTransformer<
newInterface.members.push(
dom.create.property(
"@context",
dom.create.namedTypeReference("ContextDefinition"),
dom.create.namedTypeReference("LdoJsonldContext"),
dom.DeclarationFlags.Optional,
),
);
@ -222,7 +222,7 @@ export const ShexJTypingTransformer = ShexJTraverser.createTransformer<
tripleConstraint.predicate,
rdfTypes[0],
);
const isArray =
const isSet =
tripleConstraint.max !== undefined && tripleConstraint.max !== 1;
const isOptional = tripleConstraint.min === 0;
let type: dom.Type = dom.type.any;
@ -232,7 +232,13 @@ export const ShexJTypingTransformer = ShexJTraverser.createTransformer<
const propertyDeclaration = dom.create.property(
propertyName,
isArray ? dom.type.array(type) : type,
isSet
? {
kind: "name",
name: "LdSet",
typeArguments: [type],
}
: type,
isOptional ? dom.DeclarationFlags.Optional : dom.DeclarationFlags.None,
);

@ -47,7 +47,7 @@ export async function shexjToTyping(
};
});
const typingsString =
`import {ContextDefinition} from "jsonld"\n\n` +
`import { LdSet, LdoJsonldContext } from "@ldo/ldo"\n\n` +
typings.map((typing) => `export ${typing.typingString}`).join("");
const typeingReturn: TypeingReturn = {

@ -10,24 +10,22 @@ export function dedupeObjectTypeMembers(
// Combine properties if they're duplicates
if (properties[propertyDeclaration.name]) {
const oldPropertyDeclaration = properties[propertyDeclaration.name];
const oldPropertyTypeAsArray =
oldPropertyDeclaration.type as dom.ArrayTypeReference;
const oldProeprtyType =
oldPropertyTypeAsArray.kind === "array"
? oldPropertyTypeAsArray.type
: oldPropertyDeclaration.type;
const propertyTypeAsArray =
propertyDeclaration.type as dom.ArrayTypeReference;
const propertyType =
propertyTypeAsArray.kind === "array"
? propertyTypeAsArray.type
: propertyDeclaration.type;
const oldPropertyType = isLdSetType(oldPropertyDeclaration.type)
? oldPropertyDeclaration.type.typeArguments[0]
: oldPropertyDeclaration.type;
const propertyType = isLdSetType(propertyDeclaration.type)
? propertyDeclaration.type.typeArguments[0]
: propertyDeclaration.type;
const isOptional =
propertyDeclaration.flags === dom.DeclarationFlags.Optional ||
oldPropertyDeclaration.flags === dom.DeclarationFlags.Optional;
properties[propertyDeclaration.name] = dom.create.property(
propertyDeclaration.name,
dom.type.array(dom.create.union([oldProeprtyType, propertyType])),
{
kind: "name",
name: "LdSet",
typeArguments: [dom.create.union([oldPropertyType, propertyType])],
},
isOptional ? dom.DeclarationFlags.Optional : dom.DeclarationFlags.None,
);
// Set JS Comment
@ -42,3 +40,12 @@ export function dedupeObjectTypeMembers(
});
return Object.values(properties);
}
function isLdSetType(
potentialLdSet: dom.Type,
): potentialLdSet is dom.NamedTypeReference {
return (
(potentialLdSet as dom.NamedTypeReference).kind === "name" &&
(potentialLdSet as dom.NamedTypeReference).name === "LdSet"
);
}

@ -3,6 +3,8 @@ import { shexjToContext } from "../src/context/shexjToContext";
import parser from "@shexjs/parser";
import type { Schema } from "shexj";
console.warn = () => {};
describe("context", () => {
testData.forEach(({ name, shexc, successfulContext }) => {
it(`Creates a context for ${name}`, async () => {

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save