parent
3961705934
commit
02de7a1757
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,367 @@ |
|||||||
|
# LDO (Linked Data Objects) |
||||||
|
|
||||||
|
LDO (Linked Data Objects) is a library that lets you easily manipulate RDF as if it were a standard TypeScript object that follows a [ShEx](https://shex.io) shape you define. |
||||||
|
|
||||||
|
For a full tutorial of using LDO to build React Solid applications, see [this tutorial](https://medium.com/@JacksonMorgan/building-solid-apps-with-ldo-6127a5a1979c). |
||||||
|
|
||||||
|
## Setup |
||||||
|
|
||||||
|
### Automatic Setup |
||||||
|
To setup LDO, `cd` into your typescript project and run `npx ldo-cli init`. |
||||||
|
|
||||||
|
```bash |
||||||
|
cd my-typescript-project |
||||||
|
npx ldo-cli init |
||||||
|
``` |
||||||
|
|
||||||
|
### Manual Setup |
||||||
|
The following is handled by the __automatic setup__: |
||||||
|
|
||||||
|
Install the LDO dependencies. |
||||||
|
```bash |
||||||
|
npm install ldo |
||||||
|
npm install ldo-cli --save-dev |
||||||
|
``` |
||||||
|
|
||||||
|
Create a folder to store your ShEx shapes: |
||||||
|
```bash |
||||||
|
mkdir shapes |
||||||
|
``` |
||||||
|
|
||||||
|
Create a script to build ShEx shapes and convert them into Linked Data Objects. You can put this script in `package.json` |
||||||
|
```json |
||||||
|
{ |
||||||
|
... |
||||||
|
scripts: { |
||||||
|
... |
||||||
|
"build:ldo": "ldo build --input ./shapes --output ./ldo" |
||||||
|
... |
||||||
|
} |
||||||
|
... |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
## Creating ShEx Schemas |
||||||
|
LDO uses [ShEx](https://shex.io) as a schema for the RDF data in your project. To add a ShEx schema to your project, simply create a file ending in `.shex` to the `shapes` folder. |
||||||
|
|
||||||
|
For more information on writing ShEx schemas see the [ShEx Primer](http://shex.io/shex-primer/index.html). |
||||||
|
|
||||||
|
|
||||||
|
`./shapes/foafProfile.shex`: |
||||||
|
```shex |
||||||
|
PREFIX ex: <https://example.com/> |
||||||
|
PREFIX foaf: <http://xmlns.com/foaf/0.1/> |
||||||
|
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> |
||||||
|
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> |
||||||
|
|
||||||
|
ex:FoafProfile EXTRA a { |
||||||
|
a [ foaf:Person ] |
||||||
|
// rdfs:comment "Defines the node as a Person (from foaf)" ; |
||||||
|
foaf:name xsd:string ? |
||||||
|
// rdfs:comment "Define a person's name." ; |
||||||
|
foaf:img xsd:string ? |
||||||
|
// rdfs:comment "Photo link but in string form" ; |
||||||
|
foaf:knows @ex:FoafProfile * |
||||||
|
// rdfs:comment "A list of WebIds for all the people this user knows." ; |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
To build the shape, run: |
||||||
|
```bash |
||||||
|
npm run build:ldo |
||||||
|
``` |
||||||
|
|
||||||
|
This will generate five files: |
||||||
|
- `./ldo/foafProfile.shapeTypes.ts` <-- This is the important file |
||||||
|
- `./ldo/foafProfile.typings.ts` |
||||||
|
- `./ldo/foafProfile.schema.ts` |
||||||
|
- `./ldo/foafProfile.context.ts` |
||||||
|
|
||||||
|
## Simple Example |
||||||
|
|
||||||
|
Below is a simple example of LDO in a real use-case (changing the name on a Solid Pod) |
||||||
|
|
||||||
|
```typescript |
||||||
|
import { parseRdf, startTransaction, toSparqlUpdate, toTurtle } from "ldo"; |
||||||
|
import { FoafProfileShapeType } from "./ldo/foafProfile.shapeTypes"; |
||||||
|
|
||||||
|
async function run() { |
||||||
|
const rawTurtle = ` |
||||||
|
<#me> a <http://xmlns.com/foaf/0.1/Person>; |
||||||
|
<http://xmlns.com/foaf/0.1/name> "Jane Doe". |
||||||
|
`; |
||||||
|
|
||||||
|
/** |
||||||
|
* Step 1: Convert Raw RDF into a Linked Data Object |
||||||
|
*/ |
||||||
|
const ldoDataset = await parseRdf(rawTurtle, { |
||||||
|
baseIRI: "https://solidweb.me/jane_doe/profile/card", |
||||||
|
}); |
||||||
|
// Create a linked data object by telling the dataset the type and subject of |
||||||
|
// the object |
||||||
|
const janeProfile = ldoDataset |
||||||
|
// Tells the LDO dataset that we're looking for a FoafProfile |
||||||
|
.usingType(FoafProfileShapeType) |
||||||
|
// Says the subject of the FoafProfile |
||||||
|
.fromSubject("https://solidweb.me/jane_doe/profile/card#me"); |
||||||
|
|
||||||
|
/** |
||||||
|
* Step 2: Manipulate the Linked Data Object |
||||||
|
*/ |
||||||
|
// Logs "Jane Doe" |
||||||
|
console.log(janeProfile.name); |
||||||
|
// Logs "Person" |
||||||
|
console.log(janeProfile.type); |
||||||
|
// Logs 0 |
||||||
|
console.log(janeProfile.knows?.length); |
||||||
|
|
||||||
|
// Begins a transaction that tracks your changes |
||||||
|
startTransaction(janeProfile); |
||||||
|
janeProfile.name = "Jane Smith"; |
||||||
|
janeProfile.knows?.push({ |
||||||
|
"@id": "https://solidweb.me/john_smith/profile/card#me", |
||||||
|
type: { |
||||||
|
"@id": "Person", |
||||||
|
}, |
||||||
|
name: "John Smith", |
||||||
|
knows: [janeProfile], |
||||||
|
}); |
||||||
|
|
||||||
|
// Logs "Jane Smith" |
||||||
|
console.log(janeProfile.name); |
||||||
|
// Logs "John Smith" |
||||||
|
console.log(janeProfile.knows?.[0].name); |
||||||
|
// Logs "Jane Smith" |
||||||
|
console.log(janeProfile.knows?.[0].knows?.[0].name); |
||||||
|
|
||||||
|
/** |
||||||
|
* Step 3: Convert it back to RDF |
||||||
|
*/ |
||||||
|
// Logs: |
||||||
|
// <https://solidweb.me/jane_doe/profile/card#me> a <http://xmlns.com/foaf/0.1/Person>; |
||||||
|
// <http://xmlns.com/foaf/0.1/name> "Jane Smith"; |
||||||
|
// <http://xmlns.com/foaf/0.1/knows> <https://solidweb.me/john_smith/profile/card#me>. |
||||||
|
// <https://solidweb.me/john_smith/profile/card#me> a <http://xmlns.com/foaf/0.1/Person>; |
||||||
|
// <http://xmlns.com/foaf/0.1/name> "John Smith"; |
||||||
|
// <http://xmlns.com/foaf/0.1/knows> <https://solidweb.me/jane_doe/profile/card#me>. |
||||||
|
console.log(await toTurtle(janeProfile)); |
||||||
|
// Logs: |
||||||
|
// DELETE DATA { |
||||||
|
// <https://solidweb.me/jane_doe/profile/card#me> <http://xmlns.com/foaf/0.1/name> "Jane Doe" . |
||||||
|
// }; |
||||||
|
// INSERT DATA { |
||||||
|
// <https://solidweb.me/jane_doe/profile/card#me> <http://xmlns.com/foaf/0.1/name> "Jane Smith" . |
||||||
|
// <https://solidweb.me/jane_doe/profile/card#me> <http://xmlns.com/foaf/0.1/knows> <https://solidweb.me/john_smith/profile/card#me> . |
||||||
|
// <https://solidweb.me/john_smith/profile/card#me> <http://xmlns.com/foaf/0.1/name> "John Smith" . |
||||||
|
// <https://solidweb.me/john_smith/profile/card#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://xmlns.com/foaf/0.1/Person> . |
||||||
|
// <https://solidweb.me/john_smith/profile/card#me> <http://xmlns.com/foaf/0.1/knows> <https://solidweb.me/jane_doe/profile/card#me> . |
||||||
|
// } |
||||||
|
console.log(await toSparqlUpdate(janeProfile)); |
||||||
|
} |
||||||
|
run(); |
||||||
|
``` |
||||||
|
|
||||||
|
## Getting an LDO Dataset |
||||||
|
|
||||||
|
An LDO Dataset is a kind of [RDF JS Dataset](https://rdf.js.org/dataset-spec/) that can create linked data objects. |
||||||
|
|
||||||
|
LDO datasets can be created in two ways: |
||||||
|
|
||||||
|
`createLdoDataset(initialDataset?: Dataset<Quad, Quad> | Quad[])` |
||||||
|
```typescript |
||||||
|
import { createLdoDataset } from "ldo"; |
||||||
|
|
||||||
|
const ldoDataset = createLdoDataset(); |
||||||
|
``` |
||||||
|
|
||||||
|
- `initialDataset`: An optional dataset or array of quads for the new dataset. |
||||||
|
|
||||||
|
`parseRdf(data: string, parserOptions?: ParserOptions)` |
||||||
|
```typescript |
||||||
|
import { parseRdf } from "ldo"; |
||||||
|
|
||||||
|
const rawTurtle = "..."; |
||||||
|
const ldoDataset = parseRdf(rawTurtle, { baseIRI: "https://example.com/" }); |
||||||
|
``` |
||||||
|
|
||||||
|
- `data`: The raw data to parse as a `string`. |
||||||
|
- `options` (optional): Parse options containing the following keys: |
||||||
|
- `format` (optional): The format the data is in. The following are acceptable formats: `Turtle`, `TriG`, `N-Triples`, `N-Quads`, `N3`, `Notation3`. |
||||||
|
- `baseIRI` (optional): If this data is hosted at a specific location, you can provide the baseIRI of that location. |
||||||
|
- `blankNodePrefix` (optional): If blank nodes should have a prefix, that should be provided here. |
||||||
|
- `factory` (optional): a RDF Data Factory from [`@rdfjs/data-model`](https://www.npmjs.com/package/@rdfjs/data-model). |
||||||
|
|
||||||
|
## Getting a Linked Data Object |
||||||
|
Once you have an LdoDataset we can get a Linked Data Object. A linked data object feels just like a JavaScript object literal, but when you make modifications to it, it will affect the underlying LdoDataset. |
||||||
|
|
||||||
|
Thie first step is defining which Shape Type you want to retrieve from the dataset. We can use the generated shape types and the `usingType()` method for this. |
||||||
|
|
||||||
|
```typescript |
||||||
|
import { FoafProfileShapeType } from "./ldo/foafProfile.shapeTypes.ts"; |
||||||
|
|
||||||
|
// ... Get the LdoDataset |
||||||
|
|
||||||
|
ldoDataset.usingType(FoafProfileShapeType); |
||||||
|
``` |
||||||
|
|
||||||
|
Next, we want to identify exactly what part of the dataset we want to extract. We can do this in a few ways: |
||||||
|
|
||||||
|
### `.fromSubject(entryNode)` |
||||||
|
`fromSubject` lets you define a an `entryNode`, the place of entry for the graph. The object returned by `jsonldDatasetProxy` will represent the given node. This parameter accepts both `namedNode`s and `blankNode`s. `fromSubject` takes a generic type representing the typescript type of the given subject. |
||||||
|
|
||||||
|
```typescript |
||||||
|
const profile = ldoDataset |
||||||
|
.usingType(FoafProfileShapeType) |
||||||
|
.fromSubject("http://example.com/Person1"); |
||||||
|
``` |
||||||
|
|
||||||
|
### `.matchSubject(predicate?, object?, graph?)` |
||||||
|
`matchSubject` returns a Jsonld Dataset Proxy representing all subjects in the dataset matching the given predicate, object, and graph. |
||||||
|
|
||||||
|
```typescript |
||||||
|
const profiles = ldoDataset |
||||||
|
.usingType(FoafProfileShapeType) |
||||||
|
.matchSubject( |
||||||
|
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), |
||||||
|
namedNode("http://xmlns.com/foaf/0.1/Person") |
||||||
|
); |
||||||
|
profiles.forEach((person) => { |
||||||
|
console.log(person.fn); |
||||||
|
}); |
||||||
|
``` |
||||||
|
|
||||||
|
### `.matchObject(subject?, predicate?, object?)` |
||||||
|
`matchObject` returns a Jsonld Dataset Proxy representing all objects in the dataset matching the given subject, predicate, and graph. |
||||||
|
|
||||||
|
```typescript |
||||||
|
const friendsOfPerson1 = ldoDataset |
||||||
|
.usingType(FoafProfileShapeType) |
||||||
|
.matchSubject( |
||||||
|
namedNode("http://example.com/Person1"), |
||||||
|
namedNode("http://xmlns.com/foaf/0.1/knows") |
||||||
|
); |
||||||
|
friendsOfPerson1.forEach((person) => { |
||||||
|
console.log(person.fn); |
||||||
|
}); |
||||||
|
``` |
||||||
|
|
||||||
|
### `.fromJson(inputData)` |
||||||
|
`fromJson` will take any regular Json, add the information to the dataset, and return a Jsonld Dataset Proxy representing the given data. |
||||||
|
|
||||||
|
```typescript |
||||||
|
const person2 = ldoDataset |
||||||
|
.usingType(FoafProfileShapeType) |
||||||
|
.fromJson({ |
||||||
|
"@id": "http://example.com/Person2", |
||||||
|
fn: ["Jane Doe"], |
||||||
|
}); |
||||||
|
``` |
||||||
|
|
||||||
|
## Getting and Setting Data on a Linked Data Object |
||||||
|
Once you've created a Linked Data Object, you can get and set data as if it were a normal TypeScript Object. For specific details, see the documentation at [JSONLD Dataset Proxy](https://github.com/o-development/jsonld-dataset-proxy/blob/master/Readme.md). |
||||||
|
|
||||||
|
```typescript |
||||||
|
import { LinkedDataObject } from "ldo"; |
||||||
|
import { FoafProfileFactory } from "./ldo/foafProfile.ldoFactory.ts"; |
||||||
|
import { FoafProfile } from "./ldo/foafProfile.typings"; |
||||||
|
|
||||||
|
aysnc function start() { |
||||||
|
const profile: FoafProfile = // Create LDO |
||||||
|
// Logs "Aang" |
||||||
|
console.log(profile.name); |
||||||
|
// Logs "Person" |
||||||
|
console.log(profile.type); |
||||||
|
// Logs 1 |
||||||
|
console.log(profile.knows?.length); |
||||||
|
// Logs "Katara" |
||||||
|
console.log(profile.knows?.[0].name); |
||||||
|
profile.name = "Bonzu Pippinpaddleopsicopolis III" |
||||||
|
// Logs "Bonzu Pippinpaddleopsicopolis III" |
||||||
|
console.log(profile.name); |
||||||
|
profile.knows?.push({ |
||||||
|
type: "Person", |
||||||
|
name: "Sokka" |
||||||
|
}); |
||||||
|
// Logs 2 |
||||||
|
console.log(profile.knows?.length); |
||||||
|
// Logs "Katara" and "Sokka" |
||||||
|
profile.knows?.forEach((person) => console.log(person.name)); |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
## Converting a Linked Data Object back to RDF |
||||||
|
A linked data object can be converted into RDF in multiple ways: |
||||||
|
|
||||||
|
### `toTurtle(linkedDataObject)` |
||||||
|
```typescript |
||||||
|
import { toTurtle } from "ldo" |
||||||
|
// ... |
||||||
|
const rawTurtle: string = await toTurtle(profile); |
||||||
|
``` |
||||||
|
|
||||||
|
### `toNTiples(linkedDataObject)` |
||||||
|
```typescript |
||||||
|
import { toNTriples } from "ldo" |
||||||
|
// ... |
||||||
|
const rawNTriples: string = await toNTriples(profile); |
||||||
|
``` |
||||||
|
|
||||||
|
### `serialize(linkedDataObject, options)` |
||||||
|
```typescript |
||||||
|
const rawTurtle: string = await profile.$serialize({ |
||||||
|
format: "Turtle", |
||||||
|
prefixes: { |
||||||
|
ex: "https://example.com/", |
||||||
|
foaf: "http://xmlns.com/foaf/0.1/", |
||||||
|
} |
||||||
|
}); |
||||||
|
``` |
||||||
|
`serialize(linkedDataObject, options)` provides general serialization based on provided options: |
||||||
|
- `foramt` (optional): The format to serialize to. The following are acceptable formats: `Turtle`, `TriG`, `N-Triples`, `N-Quads`, `N3`, `Notation3`. |
||||||
|
- `prefixes`: The prefixes for those serializations that use prefixes. |
||||||
|
|
||||||
|
## Transactions |
||||||
|
|
||||||
|
Sometimes, you want to keep track of changes you make for the object. This is where transactions come in handy. |
||||||
|
|
||||||
|
To start a transaction, use the `startTransaction(linkedDataObject)` function. From then on, all transactions will be tracked, but not added to the original ldoDataset. You can view the changes using the `transactionChanges(linkedDataObject)` or `toSparqlUpdate(linkedDataObject)` methods. When you're done with the transaction, you can run the `commitTransaction(linkedDataObject)` method to add the changes to the original ldoDataset. |
||||||
|
|
||||||
|
```typescript |
||||||
|
import { |
||||||
|
startTransaction, |
||||||
|
transactionChanges, |
||||||
|
toSparqlUpdate, |
||||||
|
commitTransaction, |
||||||
|
} from "ldo"; |
||||||
|
|
||||||
|
// ... Get the profile linked data object |
||||||
|
|
||||||
|
startTransaction(profile); |
||||||
|
profile.name = "Kuzon" |
||||||
|
const changes = transactionChanges(profile)); |
||||||
|
// Logs: <https://example.com/aang> <http://xmlns.com/foaf/0.1/name> "Kuzon" |
||||||
|
console.log(changes.added?.toString()) |
||||||
|
// Logs: <https://example.com/aang> <http://xmlns.com/foaf/0.1/name> "Aang" |
||||||
|
console.log(changes.removed?.toString()) |
||||||
|
console.log(await toSparqlUpdate(profile)); |
||||||
|
commitTransaction(profile); |
||||||
|
``` |
||||||
|
|
||||||
|
## Other LDO Helper Functions |
||||||
|
|
||||||
|
### `getDataset(linkedDataObject)` |
||||||
|
Returns the Linked Data Object's underlying RDFJS dataset. Modifying this dataset will change the Linked Data Object as well. |
||||||
|
```typescript |
||||||
|
import { Dataset } from "@rdfjs/types"; |
||||||
|
import { getDataset } from "ldo" |
||||||
|
const dataset: Dataset = dataset(profile); |
||||||
|
``` |
||||||
|
|
||||||
|
## Sponsorship |
||||||
|
This project was made possible by a grant from NGI Zero Entrust via nlnet. Learn more on the [NLnet project page](https://nlnet.nl/project/SolidUsableApps/). |
||||||
|
|
||||||
|
[<img src="https://nlnet.nl/logo/banner.png" alt="nlnet foundation logo" width="300" />](https://nlnet.nl/) |
||||||
|
[<img src="https://nlnet.nl/image/logos/NGI0Entrust_tag.svg" alt="NGI Zero Entrust Logo" width="300" />](https://nlnet.nl/) |
||||||
|
|
||||||
|
## Liscense |
||||||
|
MIT |
@ -0,0 +1,168 @@ |
|||||||
|
import { literal, namedNode, quad } from "@rdfjs/data-model"; |
||||||
|
import { createDataset } from "o-dataset-pack"; |
||||||
|
import type { SolidProfileShape } from "./profileData"; |
||||||
|
import { ProfileShapeType } from "./profileData"; |
||||||
|
import type { LdoBuilder, LdoDataset } from "../src"; |
||||||
|
import { createLdoDataset, graphOf, parseRdf, toTurtle } from "../src"; |
||||||
|
import { sampleJsonld, sampleTurtle } from "./sampleData"; |
||||||
|
import type { SubjectProxy } from "jsonld-dataset-proxy"; |
||||||
|
import { _proxyContext } from "jsonld-dataset-proxy"; |
||||||
|
|
||||||
|
describe("LdoDataset", () => { |
||||||
|
let ldoDataset: LdoDataset; |
||||||
|
let profileBuilder: LdoBuilder<SolidProfileShape>; |
||||||
|
|
||||||
|
beforeEach(async () => { |
||||||
|
ldoDataset = await parseRdf(sampleTurtle, { |
||||||
|
baseIRI: "https://solidweb.org/jackson/profile/card", |
||||||
|
}); |
||||||
|
profileBuilder = ldoDataset.usingType(ProfileShapeType); |
||||||
|
}); |
||||||
|
|
||||||
|
it("Creates a blank profile", async () => { |
||||||
|
ldoDataset = createLdoDataset(); |
||||||
|
profileBuilder = ldoDataset.usingType(ProfileShapeType); |
||||||
|
const profile = profileBuilder.fromSubject( |
||||||
|
"https://example.com/person1#me", |
||||||
|
); |
||||||
|
profile.fn = "Diplo"; |
||||||
|
expect(await toTurtle(profile)).toBe( |
||||||
|
'<https://example.com/person1#me> <http://www.w3.org/2006/vcard/ns#fn> "Diplo".\n', |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
it("initializes a profile using the fromJson method", () => { |
||||||
|
const profile = profileBuilder.fromJson({ |
||||||
|
type: [{ "@id": "Person" }, { "@id": "Person2" }], |
||||||
|
inbox: { "@id": "https://inbox.com" }, |
||||||
|
fn: "Diplo", |
||||||
|
}); |
||||||
|
expect(profile.inbox).toEqual({ "@id": "https://inbox.com" }); |
||||||
|
expect(profile.fn).toBe("Diplo"); |
||||||
|
expect(profile["@id"]).toBe(undefined); |
||||||
|
}); |
||||||
|
|
||||||
|
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" }], |
||||||
|
inbox: { "@id": "https://inbox.com" }, |
||||||
|
fn: "Diplo", |
||||||
|
}); |
||||||
|
expect(profile.inbox).toEqual({ "@id": "https://inbox.com" }); |
||||||
|
expect(profile.fn).toBe("Diplo"); |
||||||
|
expect(profile["@id"]).toBe("https://example.com/person1"); |
||||||
|
}); |
||||||
|
|
||||||
|
it("retrieves a subject with a named node", async () => { |
||||||
|
const profile = await profileBuilder.fromSubject( |
||||||
|
namedNode("https://solidweb.org/jackson/profile/card#me"), |
||||||
|
); |
||||||
|
expect(profile.fn).toBe("Jackson Morgan"); |
||||||
|
}); |
||||||
|
|
||||||
|
it("retrieves a subject with a string id", async () => { |
||||||
|
const profile = profileBuilder.fromSubject( |
||||||
|
"https://solidweb.org/jackson/profile/card#me", |
||||||
|
); |
||||||
|
expect(profile.fn).toBe("Jackson Morgan"); |
||||||
|
}); |
||||||
|
|
||||||
|
it("uses an existing dataset as the basis for the ldo", async () => { |
||||||
|
const dataset = createDataset(); |
||||||
|
dataset.add( |
||||||
|
quad( |
||||||
|
namedNode("https://example.com/person1"), |
||||||
|
namedNode("http://xmlns.com/foaf/0.1/name"), |
||||||
|
literal("Captain cool"), |
||||||
|
), |
||||||
|
); |
||||||
|
const profile = createLdoDataset(dataset) |
||||||
|
.usingType(ProfileShapeType) |
||||||
|
.fromSubject("https://example.com/person1"); |
||||||
|
expect(profile.name).toBe("Captain cool"); |
||||||
|
}); |
||||||
|
|
||||||
|
it("uses an existing array of quads as the basis for the ldo", async () => { |
||||||
|
const quads = [ |
||||||
|
quad( |
||||||
|
namedNode("https://example.com/person1"), |
||||||
|
namedNode("http://xmlns.com/foaf/0.1/name"), |
||||||
|
literal("Captain cool"), |
||||||
|
), |
||||||
|
]; |
||||||
|
const profile = createLdoDataset(quads) |
||||||
|
.usingType(ProfileShapeType) |
||||||
|
.fromSubject("https://example.com/person1"); |
||||||
|
expect(profile.name).toBe("Captain cool"); |
||||||
|
}); |
||||||
|
|
||||||
|
it("parses JsonLD", async () => { |
||||||
|
await expect(async () => parseRdf(sampleJsonld)).rejects.toThrow( |
||||||
|
"Not Implemented", |
||||||
|
); |
||||||
|
// ldoDataset = await parseRdf(sampleJsonld);
|
||||||
|
// const profile = ldoDataset
|
||||||
|
// .usingType(ProfileShapeType)
|
||||||
|
// .fromSubject("https://example.com/item");
|
||||||
|
// expect(profile.name).toBe("Captain of Coolness");
|
||||||
|
}); |
||||||
|
|
||||||
|
it("parses an existing dataset", async () => { |
||||||
|
const ldoDataset = await parseRdf(createDataset()); |
||||||
|
expect(typeof ldoDataset.usingType).toBe("function"); |
||||||
|
}); |
||||||
|
|
||||||
|
it("Sets the proper write graphs", () => { |
||||||
|
const profile = profileBuilder |
||||||
|
.write("https://example.com/exampleGraph") |
||||||
|
.fromSubject("https://example.com/person1"); |
||||||
|
profile.name = "Jackson"; |
||||||
|
expect(graphOf(profile, "name")[0].value).toBe( |
||||||
|
"https://example.com/exampleGraph", |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
it("Lets a match query retrieve subjects", () => { |
||||||
|
const profiles = profileBuilder.matchSubject( |
||||||
|
"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"); |
||||||
|
}); |
||||||
|
|
||||||
|
it("Handles alternate optionality for subject match", () => { |
||||||
|
const profiles = profileBuilder.matchSubject( |
||||||
|
undefined, |
||||||
|
undefined, |
||||||
|
"https://someGraph.com", |
||||||
|
); |
||||||
|
expect(profiles.length).toBe(0); |
||||||
|
}); |
||||||
|
|
||||||
|
it("Lets a match query retrieve objects", () => { |
||||||
|
const profiles = profileBuilder.matchObject( |
||||||
|
null, |
||||||
|
"http://xmlns.com/foaf/0.1/primaryTopic", |
||||||
|
); |
||||||
|
expect(profiles[0].fn).toBe("Jackson Morgan"); |
||||||
|
}); |
||||||
|
|
||||||
|
it("Handles alternate optionality for object match", () => { |
||||||
|
const profiles = profileBuilder.matchObject( |
||||||
|
"https://someSubject", |
||||||
|
undefined, |
||||||
|
"https://someGraph.com", |
||||||
|
); |
||||||
|
expect(profiles.length).toBe(0); |
||||||
|
}); |
||||||
|
|
||||||
|
it("Sets language preferences", () => { |
||||||
|
const profile = profileBuilder |
||||||
|
.setLanguagePreferences("@none", "en") |
||||||
|
.fromSubject("https://solidweb.org/jackson/profile/card#me"); |
||||||
|
expect( |
||||||
|
(profile as unknown as SubjectProxy)[_proxyContext].languageOrdering, |
||||||
|
).toEqual(["@none", "en"]); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,151 @@ |
|||||||
|
import { namedNode } from "@rdfjs/data-model"; |
||||||
|
import type { SubjectProxy } from "jsonld-dataset-proxy"; |
||||||
|
import { |
||||||
|
getProxyFromObject, |
||||||
|
graphOf, |
||||||
|
_getUnderlyingDataset, |
||||||
|
_proxyContext, |
||||||
|
} from "jsonld-dataset-proxy"; |
||||||
|
import { createDataset } from "o-dataset-pack"; |
||||||
|
import type { SolidProfileShape } from "./profileData"; |
||||||
|
import { ProfileShapeType } from "./profileData"; |
||||||
|
import type { LdoDataset } from "../src"; |
||||||
|
import { |
||||||
|
commitTransaction, |
||||||
|
createLdoDataset, |
||||||
|
getDataset, |
||||||
|
serialize, |
||||||
|
startTransaction, |
||||||
|
toJsonLd, |
||||||
|
toNTriples, |
||||||
|
toSparqlUpdate, |
||||||
|
toTurtle, |
||||||
|
transactionChanges, |
||||||
|
write, |
||||||
|
setLanguagePreferences, |
||||||
|
languagesOf, |
||||||
|
} from "../src"; |
||||||
|
|
||||||
|
describe("methods", () => { |
||||||
|
let dataset: LdoDataset; |
||||||
|
let profile: SolidProfileShape; |
||||||
|
beforeEach(() => { |
||||||
|
dataset = createLdoDataset(); |
||||||
|
profile = dataset |
||||||
|
.usingType(ProfileShapeType) |
||||||
|
.fromSubject(namedNode("https://example.com/item")); |
||||||
|
}); |
||||||
|
|
||||||
|
it("Records changes in a transaction", () => { |
||||||
|
startTransaction(profile); |
||||||
|
profile.name = "Beeboo"; |
||||||
|
const changes = transactionChanges(profile); |
||||||
|
expect(changes.added?.size).toBe(1); |
||||||
|
expect(changes.removed).toBe(undefined); |
||||||
|
}); |
||||||
|
|
||||||
|
it("throws when called with startTransaction if an underlying dataset is not a subscribable dataset", () => { |
||||||
|
const proxy = getProxyFromObject(profile); |
||||||
|
proxy[_proxyContext] = proxy[_proxyContext].duplicate({ |
||||||
|
dataset: createDataset(), |
||||||
|
}); |
||||||
|
expect(() => startTransaction(profile)).toThrow( |
||||||
|
"Object is not transactable.", |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
it("Commits changes", () => { |
||||||
|
startTransaction(profile); |
||||||
|
profile.name = "Joey"; |
||||||
|
expect(dataset.size).toBe(0); |
||||||
|
commitTransaction(profile); |
||||||
|
expect(dataset.size).toBe(1); |
||||||
|
expect(profile.name).toBe("Joey"); |
||||||
|
}); |
||||||
|
|
||||||
|
it("throws an error if transaction dependent functions are called without a transaction", async () => { |
||||||
|
expect(() => transactionChanges(profile)).toThrow( |
||||||
|
"Object is not currently in a transaction", |
||||||
|
); |
||||||
|
expect(() => commitTransaction(profile)).toThrow( |
||||||
|
"Object is not currently in a transaction", |
||||||
|
); |
||||||
|
await expect(async () => toSparqlUpdate(profile)).rejects.toThrow( |
||||||
|
"Object is not currently in a transaction", |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
it("provides the correct sparql update", async () => { |
||||||
|
profile.name = "Mr. Cool Dude"; |
||||||
|
startTransaction(profile); |
||||||
|
profile.name = "Captain of Coolness"; |
||||||
|
expect(await toSparqlUpdate(profile)).toBe( |
||||||
|
`DELETE DATA { <https://example.com/item> <http://xmlns.com/foaf/0.1/name> "Mr. Cool Dude" . }; INSERT DATA { <https://example.com/item> <http://xmlns.com/foaf/0.1/name> "Captain of Coolness" . }`, |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
it("provides a sparql update when nothing has been changed", async () => { |
||||||
|
startTransaction(profile); |
||||||
|
expect(await toSparqlUpdate(profile)).toBe(""); |
||||||
|
}); |
||||||
|
|
||||||
|
it("translates into turtle", async () => { |
||||||
|
profile.name = "Captain of Coolness"; |
||||||
|
expect(await toTurtle(profile)).toBe( |
||||||
|
'<https://example.com/item> <http://xmlns.com/foaf/0.1/name> "Captain of Coolness".\n', |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
it("translates into n-triples", async () => { |
||||||
|
profile.name = "Captain of Coolness"; |
||||||
|
expect(await toNTriples(profile)).toBe( |
||||||
|
'<https://example.com/item> <http://xmlns.com/foaf/0.1/name> "Captain of Coolness" .\n', |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
it("uses the serialize method", async () => { |
||||||
|
profile.name = "Captain of Coolness"; |
||||||
|
expect(await serialize(profile, { format: "Turtle" })).toBe( |
||||||
|
'<https://example.com/item> <http://xmlns.com/foaf/0.1/name> "Captain of Coolness".\n', |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
it.skip("translates into jsonld", async () => { |
||||||
|
profile.name = "Captain of Coolness"; |
||||||
|
expect(await toJsonLd(profile)).toEqual([ |
||||||
|
{ |
||||||
|
"@id": "https://example.com/item", |
||||||
|
"http://xmlns.com/foaf/0.1/name": "Captain of Coolness", |
||||||
|
}, |
||||||
|
]); |
||||||
|
}); |
||||||
|
|
||||||
|
it("errors when asked to convert to JsonLd", async () => { |
||||||
|
await expect(async () => toJsonLd(profile)).rejects.toThrow( |
||||||
|
"Not Implemented", |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
it("returns the underlying dataset", () => { |
||||||
|
const underlyingDataset = getDataset(profile); |
||||||
|
expect(typeof underlyingDataset.add).toBe("function"); |
||||||
|
}); |
||||||
|
|
||||||
|
it("sets a write graph", () => { |
||||||
|
write("https://graphname.com").using(profile); |
||||||
|
profile.name = "Jackson"; |
||||||
|
expect(graphOf(profile, "name")[0].value).toBe("https://graphname.com"); |
||||||
|
}); |
||||||
|
|
||||||
|
it("sets the language preferences", () => { |
||||||
|
setLanguagePreferences("@none", "en").using(profile); |
||||||
|
expect( |
||||||
|
(profile as unknown as SubjectProxy)[_proxyContext].languageOrdering, |
||||||
|
).toEqual(["@none", "en"]); |
||||||
|
}); |
||||||
|
|
||||||
|
it("uses languagesOf", () => { |
||||||
|
const result = languagesOf(profile, "name"); |
||||||
|
expect(result).toEqual({}); |
||||||
|
}); |
||||||
|
}); |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,16 @@ |
|||||||
|
export const sampleTurtle = ` |
||||||
|
<https://solidweb.me/jackson/profile/card> a <http://xmlns.com/foaf/0.1/PersonalProfileDocument>; |
||||||
|
<http://xmlns.com/foaf/0.1/maker> <#me>; |
||||||
|
<http://xmlns.com/foaf/0.1/primaryTopic> <#me>. |
||||||
|
<#me> a <http://xmlns.com/foaf/0.1/Person>; |
||||||
|
<http://www.w3.org/ns/solid/terms#oidcIssuer> <https://solidweb.me/>; |
||||||
|
<http://www.w3.org/2006/vcard/ns#fn> "Jackson Morgan"; |
||||||
|
<http://www.w3.org/2006/vcard/ns#hasEmail> <#id1651115504716>. |
||||||
|
`;
|
||||||
|
|
||||||
|
export const sampleJsonld = [ |
||||||
|
{ |
||||||
|
"@id": "https://example.com/item", |
||||||
|
"http://xmlns.com/foaf/0.1/name": [{ "@value": "Captain of Coolness" }], |
||||||
|
}, |
||||||
|
]; |
@ -0,0 +1,3 @@ |
|||||||
|
{ |
||||||
|
"extends": ["../../.eslintrc"] |
||||||
|
} |
@ -0,0 +1,120 @@ |
|||||||
|
# ShexJ 2 Type and Context |
||||||
|
|
||||||
|
Turn ShexJ into typescript typings and JSON-LD context. |
||||||
|
|
||||||
|
## Installation |
||||||
|
```bash |
||||||
|
npm i @ldo/schema-converter-shex |
||||||
|
``` |
||||||
|
|
||||||
|
## API |
||||||
|
See the [full API docs](docs/modules.md). |
||||||
|
|
||||||
|
## Usage |
||||||
|
|
||||||
|
```typescript |
||||||
|
import { Schema } from "shexj"; |
||||||
|
import shexjToTypeAndContext from "@ldo/schema-converter-shex"; |
||||||
|
|
||||||
|
async function run() { |
||||||
|
/** |
||||||
|
* Sample ShexJ. Equivalent to: |
||||||
|
* |
||||||
|
* <EmployeeShape> { # An <EmployeeShape> has: |
||||||
|
* foaf:givenName xsd:string+, # at least one givenName. |
||||||
|
* foaf:familyName xsd:string, # one familyName. |
||||||
|
* foaf:phone IRI*, # any number of phone numbers. |
||||||
|
* foaf:mbox IRI # one FOAF mbox. |
||||||
|
* } |
||||||
|
*/ |
||||||
|
const sampleShexj: Schema = { |
||||||
|
type: "Schema", |
||||||
|
shapes: [ |
||||||
|
{ |
||||||
|
type: "Shape", |
||||||
|
id: "http://shex.io/webapps/shex.js/doc/EmployeeShape", |
||||||
|
expression: { |
||||||
|
type: "EachOf", |
||||||
|
expressions: [ |
||||||
|
{ |
||||||
|
type: "TripleConstraint", |
||||||
|
predicate: "http://xmlns.com/foaf/0.1/givenName", |
||||||
|
valueExpr: { |
||||||
|
type: "NodeConstraint", |
||||||
|
datatype: "http://www.w3.org/2001/XMLSchema#string", |
||||||
|
}, |
||||||
|
min: 1, |
||||||
|
max: -1, |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "TripleConstraint", |
||||||
|
predicate: "http://xmlns.com/foaf/0.1/familyName", |
||||||
|
valueExpr: { |
||||||
|
type: "NodeConstraint", |
||||||
|
datatype: "http://www.w3.org/2001/XMLSchema#string", |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "TripleConstraint", |
||||||
|
predicate: "http://xmlns.com/foaf/0.1/phone", |
||||||
|
valueExpr: { |
||||||
|
type: "NodeConstraint", |
||||||
|
nodeKind: "iri", |
||||||
|
}, |
||||||
|
min: 0, |
||||||
|
max: -1, |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "TripleConstraint", |
||||||
|
predicate: "http://xmlns.com/foaf/0.1/mbox", |
||||||
|
valueExpr: { |
||||||
|
type: "NodeConstraint", |
||||||
|
nodeKind: "iri", |
||||||
|
}, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
}, |
||||||
|
], |
||||||
|
"@context": "http://www.w3.org/ns/shex.jsonld", |
||||||
|
}; |
||||||
|
|
||||||
|
const [typings, context] = await shexjToTypeAndContext(sampleShexj); |
||||||
|
|
||||||
|
/* |
||||||
|
Logs: |
||||||
|
declare namespace { |
||||||
|
interface EmployeeShape { |
||||||
|
givenName: string[]; |
||||||
|
familyName: string; |
||||||
|
phone?: string[]; |
||||||
|
mbox: string; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
*/ |
||||||
|
console.log(typings.typingsString); |
||||||
|
/* |
||||||
|
Logs: |
||||||
|
{ |
||||||
|
givenName: { |
||||||
|
'@id': 'http://xmlns.com/foaf/0.1/givenName', |
||||||
|
'@type': 'http://www.w3.org/2001/XMLSchema#string', |
||||||
|
'@container': '@set' |
||||||
|
}, |
||||||
|
familyName: { |
||||||
|
'@id': 'http://xmlns.com/foaf/0.1/familyName', |
||||||
|
'@type': 'http://www.w3.org/2001/XMLSchema#string' |
||||||
|
}, |
||||||
|
phone: { '@id': 'http://xmlns.com/foaf/0.1/phone', '@container': '@set' }, |
||||||
|
mbox: { '@id': 'http://xmlns.com/foaf/0.1/mbox' } |
||||||
|
} |
||||||
|
*/ |
||||||
|
console.log(context); |
||||||
|
} |
||||||
|
run(); |
||||||
|
``` |
||||||
|
|
||||||
|
|
||||||
|
## Liscense |
||||||
|
MIT |
@ -0,0 +1,5 @@ |
|||||||
|
const sharedConfig = require('../../jest.config.js'); |
||||||
|
module.exports = { |
||||||
|
...sharedConfig, |
||||||
|
'rootDir': './', |
||||||
|
} |
@ -0,0 +1,40 @@ |
|||||||
|
{ |
||||||
|
"name": "@ldo/schema-converter-shex", |
||||||
|
"version": "0.0.0", |
||||||
|
"description": "", |
||||||
|
"main": "dist/index.js", |
||||||
|
"scripts": { |
||||||
|
"build": "tsc --project tsconfig.build.json", |
||||||
|
"test": "jest --coverage", |
||||||
|
"prepublishOnly": "npm run test && npm run build" |
||||||
|
}, |
||||||
|
"repository": { |
||||||
|
"type": "git", |
||||||
|
"url": "git+https://github.com/o-development/shexj2typeandcontext.git" |
||||||
|
}, |
||||||
|
"author": "", |
||||||
|
"license": "MIT", |
||||||
|
"bugs": { |
||||||
|
"url": "https://github.com/o-development/shexj2typeandcontext/issues" |
||||||
|
}, |
||||||
|
"homepage": "https://github.com/o-development/shexj2typeandcontextr#readme", |
||||||
|
"devDependencies": { |
||||||
|
"@shexjs/parser": "^1.0.0-alpha.24", |
||||||
|
"@types/jest": "^27.0.3", |
||||||
|
"@types/jsonld": "^1.5.6", |
||||||
|
"@types/shexj": "^2.1.3", |
||||||
|
"jest": "^27.4.5", |
||||||
|
"jsonld": "^5.2.0", |
||||||
|
"o-dataset-pack": "^0.2.1", |
||||||
|
"shex-test": "^2.1.0", |
||||||
|
"ts-jest": "^27.1.2" |
||||||
|
}, |
||||||
|
"files": [ |
||||||
|
"dist" |
||||||
|
], |
||||||
|
"dependencies": { |
||||||
|
"dts-dom": "^3.6.0", |
||||||
|
"jsonld2graphobject": "^0.0.5", |
||||||
|
"shexj-traverser": "^2.0.1" |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,182 @@ |
|||||||
|
import { Annotation } from "shexj"; |
||||||
|
import { ContextDefinition, ExpandedTermDefinition } from "jsonld"; |
||||||
|
|
||||||
|
/** |
||||||
|
* Name functions |
||||||
|
*/ |
||||||
|
export function iriToName(iri: string): string { |
||||||
|
try { |
||||||
|
const url = new URL(iri); |
||||||
|
if (url.hash) { |
||||||
|
return url.hash.slice(1); |
||||||
|
} else { |
||||||
|
const splitPathname = url.pathname.split("/"); |
||||||
|
return splitPathname[splitPathname.length - 1]; |
||||||
|
} |
||||||
|
} catch (err) { |
||||||
|
return iri; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export function nameFromObject(obj: { |
||||||
|
id?: string; |
||||||
|
annotations?: Annotation[]; |
||||||
|
}): string | undefined { |
||||||
|
const labelAnnotationObject = obj.annotations?.find( |
||||||
|
(annotation) => |
||||||
|
annotation.predicate === "http://www.w3.org/2000/01/rdf-schema#label" |
||||||
|
)?.object; |
||||||
|
if (labelAnnotationObject && typeof labelAnnotationObject === "string") { |
||||||
|
return toCamelCase(iriToName(labelAnnotationObject)); |
||||||
|
} else if ( |
||||||
|
labelAnnotationObject && |
||||||
|
typeof labelAnnotationObject !== "string" |
||||||
|
) { |
||||||
|
return toCamelCase(labelAnnotationObject.value); |
||||||
|
} else if (obj.id) { |
||||||
|
return toCamelCase(iriToName(obj.id)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export function toCamelCase(text: string) { |
||||||
|
return text |
||||||
|
.replace(/([-_ ]){1,}/g, " ") |
||||||
|
.split(/[-_ ]/) |
||||||
|
.reduce((cur, acc) => { |
||||||
|
return cur + acc[0].toUpperCase() + acc.substring(1); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* JsonLd Context Builder |
||||||
|
*/ |
||||||
|
export class JsonLdContextBuilder { |
||||||
|
private iriAnnotations: Record<string, Annotation[]> = {}; |
||||||
|
private iriTypes: Record<string, ExpandedTermDefinition> = {}; |
||||||
|
private generatedNames: Record<string, string> | undefined; |
||||||
|
|
||||||
|
addSubject(iri: string, annotations?: Annotation[]) { |
||||||
|
if (!this.iriAnnotations[iri]) { |
||||||
|
this.iriAnnotations[iri] = []; |
||||||
|
} |
||||||
|
if (annotations && annotations.length > 0) { |
||||||
|
this.iriAnnotations[iri].push(...annotations); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
addPredicate( |
||||||
|
iri: string, |
||||||
|
expandedTermDefinition: ExpandedTermDefinition, |
||||||
|
isContainer: boolean, |
||||||
|
annotations?: Annotation[] |
||||||
|
) { |
||||||
|
this.addSubject(iri, annotations); |
||||||
|
if (!this.iriTypes[iri]) { |
||||||
|
this.iriTypes[iri] = expandedTermDefinition; |
||||||
|
if (isContainer) { |
||||||
|
this.iriTypes[iri]["@container"] = "@set"; |
||||||
|
} |
||||||
|
} else { |
||||||
|
const curDef = this.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["@container"] = "@set"; |
||||||
|
} |
||||||
|
// 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"]]; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
generateNames(): Record<string, string> { |
||||||
|
const generatedNames: Record<string, string> = {}; |
||||||
|
const claimedNames: Set<string> = new Set(); |
||||||
|
Object.entries(this.iriAnnotations).forEach(([iri, annotations]) => { |
||||||
|
let potentialName: string | undefined; |
||||||
|
if (annotations.length > 0) { |
||||||
|
const labelAnnotationObject = annotations.find( |
||||||
|
(annotation) => |
||||||
|
annotation.predicate === |
||||||
|
"http://www.w3.org/2000/01/rdf-schema#label" |
||||||
|
)?.object; |
||||||
|
if ( |
||||||
|
labelAnnotationObject && |
||||||
|
typeof labelAnnotationObject === "string" |
||||||
|
) { |
||||||
|
potentialName = toCamelCase(iriToName(labelAnnotationObject)); |
||||||
|
} else if ( |
||||||
|
labelAnnotationObject && |
||||||
|
typeof labelAnnotationObject !== "string" |
||||||
|
) { |
||||||
|
potentialName = toCamelCase(labelAnnotationObject.value); |
||||||
|
} |
||||||
|
} |
||||||
|
if (!potentialName) { |
||||||
|
potentialName = toCamelCase(iriToName(iri)); |
||||||
|
} |
||||||
|
if (claimedNames.has(potentialName)) { |
||||||
|
let i = 2; |
||||||
|
let newName: string | undefined; |
||||||
|
do { |
||||||
|
if (!claimedNames.has(`${potentialName}${i}`)) { |
||||||
|
newName = `${potentialName}${i}`; |
||||||
|
} |
||||||
|
i++; |
||||||
|
} while (!newName); |
||||||
|
potentialName = newName; |
||||||
|
} |
||||||
|
claimedNames.add(potentialName); |
||||||
|
generatedNames[iri] = potentialName; |
||||||
|
}); |
||||||
|
return generatedNames; |
||||||
|
} |
||||||
|
|
||||||
|
getNameFromIri(iri: string) { |
||||||
|
if (!this.generatedNames) { |
||||||
|
this.generatedNames = this.generateNames(); |
||||||
|
} |
||||||
|
if (this.generatedNames[iri]) { |
||||||
|
return this.generatedNames[iri]; |
||||||
|
} else { |
||||||
|
return iri; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
generateJsonldContext(): ContextDefinition { |
||||||
|
const contextDefnition: ContextDefinition = {}; |
||||||
|
const namesMap = this.generateNames(); |
||||||
|
Object.entries(namesMap).forEach(([iri, name]) => { |
||||||
|
if (this.iriTypes[iri]) { |
||||||
|
contextDefnition[name] = { |
||||||
|
"@id": |
||||||
|
iri === "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" |
||||||
|
? "@type" |
||||||
|
: iri, |
||||||
|
...this.iriTypes[iri], |
||||||
|
}; |
||||||
|
} else { |
||||||
|
contextDefnition[name] = iri; |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
return contextDefnition; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,83 @@ |
|||||||
|
import ShexJTraverser from "shexj-traverser"; |
||||||
|
import { JsonLdContextBuilder } from "./JsonLdContextBuilder"; |
||||||
|
|
||||||
|
/** |
||||||
|
* Visitor |
||||||
|
*/ |
||||||
|
export const ShexJNameVisitor = |
||||||
|
ShexJTraverser.createVisitor<JsonLdContextBuilder>({ |
||||||
|
Shape: { |
||||||
|
visitor: async (shape, context) => { |
||||||
|
|
||||||
|
} |
||||||
|
}, |
||||||
|
TripleConstraint: { |
||||||
|
visitor: async (tripleConstraint, context) => { |
||||||
|
if (tripleConstraint.valueExpr) { |
||||||
|
const isContainer = |
||||||
|
tripleConstraint.max !== undefined && tripleConstraint.max !== 1; |
||||||
|
if (typeof tripleConstraint.valueExpr === "string") { |
||||||
|
// TOOD handle string value expr
|
||||||
|
} else if (tripleConstraint.valueExpr.type === "NodeConstraint") { |
||||||
|
if (tripleConstraint.valueExpr.datatype) { |
||||||
|
context.addPredicate( |
||||||
|
tripleConstraint.predicate, |
||||||
|
{ |
||||||
|
"@type": tripleConstraint.valueExpr.datatype, |
||||||
|
}, |
||||||
|
isContainer, |
||||||
|
tripleConstraint.annotations |
||||||
|
); |
||||||
|
} else if ( |
||||||
|
tripleConstraint.valueExpr.nodeKind && |
||||||
|
tripleConstraint.valueExpr.nodeKind !== "literal" |
||||||
|
) { |
||||||
|
context.addPredicate( |
||||||
|
tripleConstraint.predicate, |
||||||
|
{ "@type": "@id" }, |
||||||
|
isContainer, |
||||||
|
tripleConstraint.annotations |
||||||
|
); |
||||||
|
} else { |
||||||
|
context.addPredicate( |
||||||
|
tripleConstraint.predicate, |
||||||
|
{}, |
||||||
|
isContainer, |
||||||
|
tripleConstraint.annotations |
||||||
|
); |
||||||
|
} |
||||||
|
} else { |
||||||
|
context.addPredicate( |
||||||
|
tripleConstraint.predicate, |
||||||
|
{ |
||||||
|
"@type": "@id", |
||||||
|
}, |
||||||
|
isContainer, |
||||||
|
tripleConstraint.annotations |
||||||
|
); |
||||||
|
} |
||||||
|
} else { |
||||||
|
context.addSubject( |
||||||
|
tripleConstraint.predicate, |
||||||
|
tripleConstraint.annotations |
||||||
|
); |
||||||
|
} |
||||||
|
}, |
||||||
|
}, |
||||||
|
NodeConstraint: { |
||||||
|
visitor: async (nodeConstraint, context) => { |
||||||
|
if (nodeConstraint.values) { |
||||||
|
nodeConstraint.values.forEach((value) => { |
||||||
|
if (typeof value === "string") { |
||||||
|
context.addSubject(value); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
}, |
||||||
|
}, |
||||||
|
IriStem: { |
||||||
|
visitor: async (iriStem, context) => { |
||||||
|
context.addSubject(iriStem.stem); |
||||||
|
}, |
||||||
|
}, |
||||||
|
}); |
@ -0,0 +1,21 @@ |
|||||||
|
import { ContextDefinition } from "jsonld"; |
||||||
|
import { Schema } from "shexj"; |
||||||
|
import { JsonLdContextBuilder } from "./JsonLdContextBuilder"; |
||||||
|
import { ShexJNameVisitor } from "./ShexJContextVisitor"; |
||||||
|
import { jsonld2graphobject } from "jsonld2graphobject"; |
||||||
|
|
||||||
|
export async function shexjToContext( |
||||||
|
shexj: Schema |
||||||
|
): Promise<ContextDefinition> { |
||||||
|
const processedShexj: Schema = (await jsonld2graphobject( |
||||||
|
{ |
||||||
|
...shexj, |
||||||
|
"@id": "SCHEMA", |
||||||
|
"@context": "http://www.w3.org/ns/shex.jsonld", |
||||||
|
}, |
||||||
|
"SCHEMA" |
||||||
|
)) as unknown as Schema; |
||||||
|
const jsonLdContextBuilder = new JsonLdContextBuilder(); |
||||||
|
await ShexJNameVisitor.visit(processedShexj, "Schema", jsonLdContextBuilder); |
||||||
|
return jsonLdContextBuilder.generateJsonldContext(); |
||||||
|
} |
@ -0,0 +1,3 @@ |
|||||||
|
import { shexjToTyping } from "./typing/shexjToTyping"; |
||||||
|
|
||||||
|
export default shexjToTyping; |
@ -0,0 +1,5 @@ |
|||||||
|
import { InterfaceDeclaration } from "dts-dom"; |
||||||
|
|
||||||
|
export interface ShapeInterfaceDeclaration extends InterfaceDeclaration { |
||||||
|
shapeId?: string; |
||||||
|
} |
@ -0,0 +1,355 @@ |
|||||||
|
import ShexJTraverser from "shexj-traverser"; |
||||||
|
import * as dom from "dts-dom"; |
||||||
|
import { Annotation } from "shexj"; |
||||||
|
import { nameFromObject } from "../context/JsonLdContextBuilder"; |
||||||
|
import { ShapeInterfaceDeclaration } from "./ShapeInterfaceDeclaration"; |
||||||
|
|
||||||
|
export interface ShexJTypeTransformerContext { |
||||||
|
getNameFromIri: (iri: string) => string; |
||||||
|
} |
||||||
|
|
||||||
|
export function commentFromAnnotations( |
||||||
|
annotations?: Annotation[] |
||||||
|
): string | undefined { |
||||||
|
const commentAnnotationObject = annotations?.find( |
||||||
|
(annotation) => |
||||||
|
annotation.predicate === "http://www.w3.org/2000/01/rdf-schema#comment" |
||||||
|
)?.object; |
||||||
|
if (typeof commentAnnotationObject === "string") { |
||||||
|
// It's an IRI
|
||||||
|
return commentAnnotationObject; |
||||||
|
} else { |
||||||
|
return commentAnnotationObject?.value; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export const ShexJTypingTransformer = ShexJTraverser.createTransformer< |
||||||
|
{ |
||||||
|
Schema: { |
||||||
|
return: dom.TopLevelDeclaration[]; |
||||||
|
}; |
||||||
|
ShapeDecl: { |
||||||
|
return: dom.InterfaceDeclaration; |
||||||
|
}; |
||||||
|
Shape: { |
||||||
|
return: dom.InterfaceDeclaration; |
||||||
|
}; |
||||||
|
EachOf: { |
||||||
|
return: dom.ObjectType | dom.InterfaceDeclaration; |
||||||
|
}; |
||||||
|
TripleConstraint: { |
||||||
|
return: dom.PropertyDeclaration; |
||||||
|
}; |
||||||
|
NodeConstraint: { |
||||||
|
return: dom.Type; |
||||||
|
}; |
||||||
|
}, |
||||||
|
ShexJTypeTransformerContext |
||||||
|
>({ |
||||||
|
Schema: { |
||||||
|
transformer: async ( |
||||||
|
_schema, |
||||||
|
getTransformedChildren |
||||||
|
): Promise<dom.TopLevelDeclaration[]> => { |
||||||
|
const transformedChildren = await getTransformedChildren(); |
||||||
|
const interfaces: dom.TopLevelDeclaration[] = []; |
||||||
|
transformedChildren.shapes?.forEach((shape) => { |
||||||
|
if ( |
||||||
|
typeof shape !== "string" && |
||||||
|
(shape as dom.InterfaceDeclaration).kind === "interface" |
||||||
|
) { |
||||||
|
interfaces.push(shape as dom.InterfaceDeclaration); |
||||||
|
} |
||||||
|
}); |
||||||
|
return interfaces; |
||||||
|
}, |
||||||
|
}, |
||||||
|
ShapeDecl: { |
||||||
|
transformer: async ( |
||||||
|
shapeDecl, |
||||||
|
getTransformedChildren |
||||||
|
): Promise<dom.InterfaceDeclaration> => { |
||||||
|
const shapeName = nameFromObject(shapeDecl) || "Shape"; |
||||||
|
const { shapeExpr } = await getTransformedChildren(); |
||||||
|
if ((shapeExpr as dom.InterfaceDeclaration).kind === "interface") { |
||||||
|
const shapeInterface = shapeExpr as ShapeInterfaceDeclaration; |
||||||
|
shapeInterface.name = shapeName; |
||||||
|
// This exists so the LDO-CLI can understand which type corresponds to the shape
|
||||||
|
shapeInterface.shapeId = shapeDecl.id; |
||||||
|
return shapeInterface; |
||||||
|
} else { |
||||||
|
// TODO: Handle other items
|
||||||
|
throw new Error( |
||||||
|
"Cannot handle ShapeOr, ShapeAnd, ShapeNot, ShapeExternal, or NodeConstraint" |
||||||
|
); |
||||||
|
} |
||||||
|
}, |
||||||
|
}, |
||||||
|
Shape: { |
||||||
|
transformer: async (shape, getTransformedChildren, setReturnPointer) => { |
||||||
|
const newInterface: ShapeInterfaceDeclaration = dom.create.interface(""); |
||||||
|
setReturnPointer(newInterface); |
||||||
|
const transformedChildren = await getTransformedChildren(); |
||||||
|
// Add @id and @context
|
||||||
|
newInterface.members.push( |
||||||
|
dom.create.property( |
||||||
|
"@id", |
||||||
|
dom.type.string, |
||||||
|
dom.DeclarationFlags.Optional |
||||||
|
) |
||||||
|
); |
||||||
|
newInterface.members.push( |
||||||
|
dom.create.property( |
||||||
|
"@context", |
||||||
|
dom.create.namedTypeReference("ContextDefinition"), |
||||||
|
dom.DeclarationFlags.Optional |
||||||
|
) |
||||||
|
); |
||||||
|
if (typeof transformedChildren.expression === "string") { |
||||||
|
// TODO: handle string
|
||||||
|
} else if ( |
||||||
|
(transformedChildren.expression as dom.ObjectType).kind === "object" || |
||||||
|
(transformedChildren.expression as dom.InterfaceDeclaration).kind === |
||||||
|
"interface" |
||||||
|
) { |
||||||
|
newInterface.members.push( |
||||||
|
...(transformedChildren.expression as dom.ObjectType).members |
||||||
|
); |
||||||
|
} else if ( |
||||||
|
(transformedChildren.expression as dom.PropertyDeclaration).kind === |
||||||
|
"property" |
||||||
|
) { |
||||||
|
newInterface.members.push( |
||||||
|
transformedChildren.expression as dom.PropertyDeclaration |
||||||
|
); |
||||||
|
} |
||||||
|
// Use EXTENDS
|
||||||
|
if (transformedChildren.extends) { |
||||||
|
newInterface.baseTypes = []; |
||||||
|
transformedChildren.extends.forEach((extendsItem) => { |
||||||
|
if ((extendsItem as dom.InterfaceDeclaration).kind === "interface") { |
||||||
|
newInterface.baseTypes?.push( |
||||||
|
extendsItem as dom.InterfaceDeclaration |
||||||
|
); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
return newInterface; |
||||||
|
}, |
||||||
|
}, |
||||||
|
EachOf: { |
||||||
|
transformer: async (eachOf, getTransformedChildren, setReturnPointer) => { |
||||||
|
const transformedChildren = await getTransformedChildren(); |
||||||
|
const name = nameFromObject(eachOf); |
||||||
|
const objectType = name |
||||||
|
? dom.create.interface(name) |
||||||
|
: dom.create.objectType([]); |
||||||
|
const eachOfComment = commentFromAnnotations(eachOf.annotations); |
||||||
|
setReturnPointer(objectType); |
||||||
|
// Get Input property expressions
|
||||||
|
const inputPropertyExpressions: dom.PropertyDeclaration[] = []; |
||||||
|
transformedChildren.expressions |
||||||
|
.filter( |
||||||
|
( |
||||||
|
expression |
||||||
|
): expression is dom.ObjectType | dom.PropertyDeclaration => { |
||||||
|
return ( |
||||||
|
(expression as dom.PropertyDeclaration).kind === "property" || |
||||||
|
(expression as dom.ObjectType).kind === "object" || |
||||||
|
(expression as dom.InterfaceDeclaration).kind === "interface" |
||||||
|
); |
||||||
|
} |
||||||
|
) |
||||||
|
.forEach( |
||||||
|
( |
||||||
|
expression: |
||||||
|
| dom.ObjectType |
||||||
|
| dom.InterfaceDeclaration |
||||||
|
| dom.PropertyDeclaration |
||||||
|
) => { |
||||||
|
if (expression.kind === "property") { |
||||||
|
inputPropertyExpressions.push(expression); |
||||||
|
} else { |
||||||
|
expression.members.forEach((objectMember) => { |
||||||
|
if (objectMember.kind === "property") { |
||||||
|
inputPropertyExpressions.push(objectMember); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
); |
||||||
|
|
||||||
|
// Merge property expressions
|
||||||
|
const properties: Record<string, dom.PropertyDeclaration> = {}; |
||||||
|
inputPropertyExpressions.forEach((expression) => { |
||||||
|
const propertyDeclaration = expression as dom.PropertyDeclaration; |
||||||
|
// 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 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])), |
||||||
|
isOptional |
||||||
|
? dom.DeclarationFlags.Optional |
||||||
|
: dom.DeclarationFlags.None |
||||||
|
); |
||||||
|
// Set JS Comment
|
||||||
|
properties[propertyDeclaration.name].jsDocComment = |
||||||
|
oldPropertyDeclaration.jsDocComment && |
||||||
|
propertyDeclaration.jsDocComment |
||||||
|
? `${oldPropertyDeclaration.jsDocComment} | ${propertyDeclaration.jsDocComment}` |
||||||
|
: oldPropertyDeclaration.jsDocComment || |
||||||
|
propertyDeclaration.jsDocComment || |
||||||
|
eachOfComment; |
||||||
|
} else { |
||||||
|
properties[propertyDeclaration.name] = propertyDeclaration; |
||||||
|
} |
||||||
|
}); |
||||||
|
objectType.members.push(...Object.values(properties)); |
||||||
|
return objectType; |
||||||
|
}, |
||||||
|
}, |
||||||
|
TripleConstraint: { |
||||||
|
transformer: async ( |
||||||
|
tripleConstraint, |
||||||
|
getTransformedChildren, |
||||||
|
setReturnPointer, |
||||||
|
context |
||||||
|
) => { |
||||||
|
const transformedChildren = await getTransformedChildren(); |
||||||
|
const propertyName = context.getNameFromIri(tripleConstraint.predicate); |
||||||
|
const isArray = |
||||||
|
tripleConstraint.max !== undefined && tripleConstraint.max !== 1; |
||||||
|
const isOptional = tripleConstraint.min === 0; |
||||||
|
let type: dom.Type = dom.type.any; |
||||||
|
if (transformedChildren.valueExpr) { |
||||||
|
type = transformedChildren.valueExpr as dom.Type; |
||||||
|
} |
||||||
|
|
||||||
|
const propertyDeclaration = dom.create.property( |
||||||
|
propertyName, |
||||||
|
isArray ? dom.type.array(type) : type, |
||||||
|
isOptional ? dom.DeclarationFlags.Optional : dom.DeclarationFlags.None |
||||||
|
); |
||||||
|
|
||||||
|
propertyDeclaration.jsDocComment = commentFromAnnotations( |
||||||
|
tripleConstraint.annotations |
||||||
|
); |
||||||
|
return propertyDeclaration; |
||||||
|
}, |
||||||
|
}, |
||||||
|
NodeConstraint: { |
||||||
|
transformer: async ( |
||||||
|
nodeConstraint, |
||||||
|
_getTransformedChildren, |
||||||
|
setReturnPointer, |
||||||
|
context |
||||||
|
) => { |
||||||
|
if (nodeConstraint.datatype) { |
||||||
|
switch (nodeConstraint.datatype) { |
||||||
|
case "http://www.w3.org/2001/XMLSchema#string": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#ENTITIES": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#ENTITY": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#ID": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#IDREF": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#IDREFS": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#language": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#Name": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#NCName": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#NMTOKEN": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#NMTOKENS": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#normalizedString": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#QName": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#token": |
||||||
|
return dom.type.string; |
||||||
|
case "http://www.w3.org/2001/XMLSchema#date": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#dateTime": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#duration": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#gDay": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#gMonth": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#gMonthDay": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#gYear": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#gYearMonth": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#time": |
||||||
|
return dom.type.string; |
||||||
|
case "http://www.w3.org/2001/XMLSchema#byte": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#decimal": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#int": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#integer": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#long": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#negativeInteger": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#nonNegativeInteger": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#nonPositiveInteger": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#positiveInteger": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#short": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#unsignedLong": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#unsignedInt": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#unsignedShort": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#unsignedByte": |
||||||
|
return dom.type.number; |
||||||
|
case "http://www.w3.org/2001/XMLSchema#boolean": |
||||||
|
return dom.type.boolean; |
||||||
|
case "http://www.w3.org/2001/XMLSchema#hexBinary": |
||||||
|
return dom.type.string; |
||||||
|
case "http://www.w3.org/2001/XMLSchema#anyURI": |
||||||
|
return dom.type.string; |
||||||
|
default: |
||||||
|
return dom.type.string; |
||||||
|
} |
||||||
|
} |
||||||
|
if (nodeConstraint.nodeKind) { |
||||||
|
switch (nodeConstraint.nodeKind) { |
||||||
|
case "iri": |
||||||
|
return dom.create.objectType([ |
||||||
|
dom.create.property("@id", dom.type.string), |
||||||
|
]); |
||||||
|
case "bnode": |
||||||
|
return dom.create.objectType([]); |
||||||
|
case "nonliteral": |
||||||
|
return dom.create.objectType([ |
||||||
|
dom.create.property( |
||||||
|
"@id", |
||||||
|
dom.type.string, |
||||||
|
dom.DeclarationFlags.Optional |
||||||
|
), |
||||||
|
]); |
||||||
|
case "literal": |
||||||
|
default: |
||||||
|
return dom.type.string; |
||||||
|
} |
||||||
|
} |
||||||
|
if (nodeConstraint.values) { |
||||||
|
const valuesUnion = dom.create.union([]); |
||||||
|
nodeConstraint.values.forEach((value) => { |
||||||
|
if (typeof value === "string") { |
||||||
|
valuesUnion.members.push( |
||||||
|
dom.create.objectType([ |
||||||
|
dom.create.property( |
||||||
|
"@id", |
||||||
|
dom.type.stringLiteral(context.getNameFromIri(value)) |
||||||
|
), |
||||||
|
]) |
||||||
|
); |
||||||
|
} |
||||||
|
}); |
||||||
|
return valuesUnion; |
||||||
|
} |
||||||
|
return dom.type.undefined; |
||||||
|
}, |
||||||
|
}, |
||||||
|
}); |
@ -0,0 +1,59 @@ |
|||||||
|
import { ContextDefinition } from "jsonld"; |
||||||
|
import { Schema } from "shexj"; |
||||||
|
import { JsonLdContextBuilder } from "../context/JsonLdContextBuilder"; |
||||||
|
import { ShexJNameVisitor } from "../context/ShexJContextVisitor"; |
||||||
|
import { jsonld2graphobject } from "jsonld2graphobject"; |
||||||
|
import { ShexJTypingTransformer } from "./ShexJTypingTransformer"; |
||||||
|
import * as dom from "dts-dom"; |
||||||
|
|
||||||
|
export interface TypeingReturn { |
||||||
|
typingsString: string; |
||||||
|
typings: { |
||||||
|
typingString: string; |
||||||
|
dts: dom.TopLevelDeclaration; |
||||||
|
}[]; |
||||||
|
} |
||||||
|
|
||||||
|
export async function shexjToTyping( |
||||||
|
shexj: Schema |
||||||
|
): Promise<[TypeingReturn, ContextDefinition]> { |
||||||
|
const processedShexj: Schema = (await jsonld2graphobject( |
||||||
|
{ |
||||||
|
...shexj, |
||||||
|
"@id": "SCHEMA", |
||||||
|
"@context": "http://www.w3.org/ns/shex.jsonld", |
||||||
|
}, |
||||||
|
"SCHEMA" |
||||||
|
)) as unknown as Schema; |
||||||
|
const jsonLdContextBuilder = new JsonLdContextBuilder(); |
||||||
|
await ShexJNameVisitor.visit(processedShexj, "Schema", jsonLdContextBuilder); |
||||||
|
|
||||||
|
const declarations = await ShexJTypingTransformer.transform( |
||||||
|
processedShexj, |
||||||
|
"Schema", |
||||||
|
{ |
||||||
|
getNameFromIri: |
||||||
|
jsonLdContextBuilder.getNameFromIri.bind(jsonLdContextBuilder), |
||||||
|
} |
||||||
|
); |
||||||
|
const typings = declarations.map((declaration) => { |
||||||
|
return { |
||||||
|
typingString: dom |
||||||
|
.emit(declaration, { |
||||||
|
rootFlags: dom.ContextFlags.InAmbientNamespace, |
||||||
|
}) |
||||||
|
.replace("\r\n", "\n"), |
||||||
|
dts: declaration, |
||||||
|
}; |
||||||
|
}); |
||||||
|
const typingsString = |
||||||
|
`import {ContextDefinition} from "jsonld"\n\n` + |
||||||
|
typings.map((typing) => `export ${typing.typingString}`).join(""); |
||||||
|
|
||||||
|
const typeingReturn: TypeingReturn = { |
||||||
|
typingsString, |
||||||
|
typings, |
||||||
|
}; |
||||||
|
|
||||||
|
return [typeingReturn, jsonLdContextBuilder.generateJsonldContext()]; |
||||||
|
} |
@ -0,0 +1,16 @@ |
|||||||
|
import { testData } from "./testData/testData"; |
||||||
|
import { shexjToContext } from "../src/context/shexjToContext"; |
||||||
|
import parser from "@shexjs/parser"; |
||||||
|
import type { Schema } from "shexj"; |
||||||
|
|
||||||
|
describe("context", () => { |
||||||
|
testData.forEach(({ name, shexc, successfulContext }) => { |
||||||
|
it(`Creates a context for ${name}`, async () => { |
||||||
|
const schema: Schema = parser |
||||||
|
.construct("https://ldo.js.org/") |
||||||
|
.parse(shexc); |
||||||
|
const context = await shexjToContext(schema); |
||||||
|
expect(context).toEqual(successfulContext); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
File diff suppressed because one or more lines are too long
@ -0,0 +1,42 @@ |
|||||||
|
import type { TestData } from "./testData"; |
||||||
|
|
||||||
|
/** |
||||||
|
* Circular |
||||||
|
*/ |
||||||
|
export const circular: TestData = { |
||||||
|
name: "circular", |
||||||
|
shexc: ` |
||||||
|
PREFIX example: <http://example.com/>
|
||||||
|
|
||||||
|
example:ParentShape { |
||||||
|
a [ example:Parent ]? ; |
||||||
|
example:hasChild @example:ChildShape ; |
||||||
|
} |
||||||
|
|
||||||
|
example:ChildShape { |
||||||
|
a [ example:Child ]? ; |
||||||
|
example:hasParent @example:ParentShape ; |
||||||
|
} |
||||||
|
`,
|
||||||
|
sampleTurtle: ` |
||||||
|
@prefix example: <http://example.com/> . |
||||||
|
|
||||||
|
example:SampleParent |
||||||
|
a example:Parent ; |
||||||
|
example:hasChild example:SampleChild . |
||||||
|
|
||||||
|
example:SampleChild |
||||||
|
a example:Child ; |
||||||
|
example:hasParent example:SampleParent . |
||||||
|
`,
|
||||||
|
baseNode: "http://example.com/SampleParent", |
||||||
|
successfulContext: { |
||||||
|
type: { "@id": "@type" }, |
||||||
|
Parent: "http://example.com/Parent", |
||||||
|
hasChild: { "@id": "http://example.com/hasChild", "@type": "@id" }, |
||||||
|
Child: "http://example.com/Child", |
||||||
|
hasParent: { "@id": "http://example.com/hasParent", "@type": "@id" }, |
||||||
|
}, |
||||||
|
successfulTypings: |
||||||
|
'import {ContextDefinition} from "jsonld"\n\nexport interface ParentShape {\n "@id"?: string;\r\n "@context"?: ContextDefinition;\r\n type?: {\r\n "@id": "Parent";\r\n };\r\n hasChild: ChildShape;\r\n}\r\n\r\nexport interface ChildShape {\n "@id"?: string;\r\n "@context"?: ContextDefinition;\r\n type?: {\r\n "@id": "Child";\r\n };\r\n hasParent: ParentShape;\r\n}\r\n\r\n', |
||||||
|
}; |
@ -0,0 +1,43 @@ |
|||||||
|
import type { TestData } from "./testData"; |
||||||
|
|
||||||
|
/** |
||||||
|
* Circular |
||||||
|
*/ |
||||||
|
export const extendsSimple: TestData = { |
||||||
|
name: "extends simple", |
||||||
|
shexc: ` |
||||||
|
PREFIX ex: <https://example.com/> |
||||||
|
PREFIX foaf: <http://xmlns.com/foaf/0.1/> |
||||||
|
|
||||||
|
ex:EntityShape { |
||||||
|
ex:entityId . |
||||||
|
} |
||||||
|
|
||||||
|
ex:PersonShape EXTENDS @ex:EntityShape { |
||||||
|
foaf:name . |
||||||
|
} |
||||||
|
|
||||||
|
ex:EmployeeShape EXTENDS @ex:PersonShape { |
||||||
|
ex:employeeNumber . |
||||||
|
} |
||||||
|
`,
|
||||||
|
sampleTurtle: ` |
||||||
|
@prefix example: <http://example.com/> . |
||||||
|
|
||||||
|
example:SampleParent |
||||||
|
a example:Parent ; |
||||||
|
example:hasChild example:SampleChild . |
||||||
|
|
||||||
|
example:SampleChild |
||||||
|
a example:Child ; |
||||||
|
example:hasParent example:SampleParent . |
||||||
|
`,
|
||||||
|
baseNode: "http://example.com/SampleParent", |
||||||
|
successfulContext: { |
||||||
|
entityId: "https://example.com/entityId", |
||||||
|
name: "http://xmlns.com/foaf/0.1/name", |
||||||
|
employeeNumber: "https://example.com/employeeNumber", |
||||||
|
}, |
||||||
|
successfulTypings: |
||||||
|
'import {ContextDefinition} from "jsonld"\n\nexport interface EntityShape {\n "@id"?: string;\r\n "@context"?: ContextDefinition;\r\n entityId: any;\r\n}\r\n\r\nexport interface PersonShapeextends EntityShape {\n "@id"?: string;\r\n "@context"?: ContextDefinition;\r\n name: any;\r\n}\r\n\r\nexport interface EmployeeShapeextends PersonShape {\n "@id"?: string;\r\n "@context"?: ContextDefinition;\r\n employeeNumber: any;\r\n}\r\n\r\n', |
||||||
|
}; |
@ -0,0 +1,48 @@ |
|||||||
|
import type { TestData } from "./testData"; |
||||||
|
|
||||||
|
/** |
||||||
|
* Circular |
||||||
|
*/ |
||||||
|
export const oldExtends: TestData = { |
||||||
|
name: "old extends", |
||||||
|
shexc: ` |
||||||
|
PREFIX ex: <https://example.com/> |
||||||
|
PREFIX foaf: <http://xmlns.com/foaf/0.1/> |
||||||
|
|
||||||
|
ex:EntityShape { |
||||||
|
$ex:EntityRef ( |
||||||
|
ex:entityId . |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
ex:PersonShape { |
||||||
|
$ex:PersonRef ( |
||||||
|
&ex:EntityRef ; |
||||||
|
foaf:name . |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
ex:EmployeeShape EXTENDS @ex:PersonShape { |
||||||
|
&ex:PersonRef ; |
||||||
|
ex:employeeNumber . |
||||||
|
} |
||||||
|
`,
|
||||||
|
sampleTurtle: ` |
||||||
|
@prefix ex: <http://example.com/> . |
||||||
|
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . |
||||||
|
@prefix foaf: <http://xmlns.com/foaf/0.1/> . |
||||||
|
|
||||||
|
ex:SampleEmployee |
||||||
|
ex:entityId "123"^^xsd:integer ; |
||||||
|
foaf:name "Jacko" ; |
||||||
|
ex:employeeNumber "456"^^xsd:integer ; |
||||||
|
`,
|
||||||
|
baseNode: "http://example.com/SampleParent", |
||||||
|
successfulContext: { |
||||||
|
entityId: "https://example.com/entityId", |
||||||
|
name: "http://xmlns.com/foaf/0.1/name", |
||||||
|
employeeNumber: "https://example.com/employeeNumber", |
||||||
|
}, |
||||||
|
successfulTypings: |
||||||
|
'import {ContextDefinition} from "jsonld"\n\nexport interface EntityShape {\n "@id"?: string;\r\n "@context"?: ContextDefinition;\r\n entityId: any;\r\n}\r\n\r\nexport interface PersonShape {\n "@id"?: string;\r\n "@context"?: ContextDefinition;\r\n entityId: any;\r\n name: any;\r\n}\r\n\r\nexport interface EmployeeShape {\n "@id"?: string;\r\n "@context"?: ContextDefinition;\r\n entityId: any;\r\n name: any;\r\n employeeNumber: any;\r\n}\r\n\r\n', |
||||||
|
}; |
File diff suppressed because one or more lines are too long
@ -0,0 +1,81 @@ |
|||||||
|
import type { TestData } from "./testData"; |
||||||
|
|
||||||
|
/** |
||||||
|
* Reduced Profile |
||||||
|
*/ |
||||||
|
export const reducedProfile: TestData = { |
||||||
|
name: "reduced profile", |
||||||
|
shexc: ` |
||||||
|
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" ;
|
||||||
|
a [ foaf:Person ] |
||||||
|
// rdfs:comment "Defines the node as a Person" ;
|
||||||
|
vcard:hasEmail @srs:EmailShape * |
||||||
|
// rdfs:comment "The person's email." ;
|
||||||
|
foaf:name xsd:string ? |
||||||
|
// rdfs:comment "An alternate way to define a person's name" ;
|
||||||
|
} |
||||||
|
|
||||||
|
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>)" ;
|
||||||
|
} |
||||||
|
`,
|
||||||
|
sampleTurtle: ``, |
||||||
|
baseNode: "", |
||||||
|
successfulContext: { |
||||||
|
type: { "@id": "@type" }, |
||||||
|
Person: "http://schema.org/Person", |
||||||
|
Person2: "http://xmlns.com/foaf/0.1/Person", |
||||||
|
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" }, |
||||||
|
name: { |
||||||
|
"@id": "http://xmlns.com/foaf/0.1/name", |
||||||
|
"@type": "http://www.w3.org/2001/XMLSchema#string", |
||||||
|
}, |
||||||
|
}, |
||||||
|
successfulTypings: |
||||||
|
'import {ContextDefinition} from "jsonld"\n\nexport interface SolidProfileShape {\n "@id"?: string;\r\n "@context"?: ContextDefinition;\r\n /**\r\n * Defines the node as a Person | Defines the node as a Person\r\n */\r\n type: ({\r\n "@id": "Person";\r\n } | {\r\n "@id": "Person2";\r\n })[];\r\n /**\r\n * The person\'s email.\r\n */\r\n hasEmail?: (EmailShape)[];\r\n /**\r\n * An alternate way to define a person\'s name\r\n */\r\n name?: string;\r\n}\r\n\r\nexport interface EmailShape {\n "@id"?: string;\r\n "@context"?: ContextDefinition;\r\n /**\r\n * The type of email.\r\n */\r\n type?: {\r\n "@id": "Dom";\r\n } | {\r\n "@id": "Home";\r\n } | {\r\n "@id": "ISDN";\r\n } | {\r\n "@id": "Internet";\r\n } | {\r\n "@id": "Intl";\r\n } | {\r\n "@id": "Label";\r\n } | {\r\n "@id": "Parcel";\r\n } | {\r\n "@id": "Postal";\r\n } | {\r\n "@id": "Pref";\r\n } | {\r\n "@id": "Work";\r\n } | {\r\n "@id": "X400";\r\n };\r\n /**\r\n * The value of an email as a mailto link (Example <mailto:jane@example.com>)\r\n */\r\n value: {\r\n "@id": string;\r\n };\r\n}\r\n\r\n', |
||||||
|
}; |
@ -0,0 +1,60 @@ |
|||||||
|
import type { TestData } from "./testData"; |
||||||
|
|
||||||
|
/** |
||||||
|
* Reused Predicate |
||||||
|
*/ |
||||||
|
export const reusedPredicates: TestData = { |
||||||
|
name: "reused predicates", |
||||||
|
shexc: ` |
||||||
|
PREFIX app: <https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#> |
||||||
|
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> |
||||||
|
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> |
||||||
|
|
||||||
|
app:DocumentShape { |
||||||
|
rdf:type [ app:Document ] ; |
||||||
|
app:vocabulary @app:VocabularyShape* ; |
||||||
|
app:law @app:LawShape ; |
||||||
|
} |
||||||
|
|
||||||
|
app:LawShape { |
||||||
|
rdf:type [ app:Law ] ; |
||||||
|
app:name xsd:string ; |
||||||
|
app:path IRI ; |
||||||
|
} |
||||||
|
|
||||||
|
app:VocabularyShape { |
||||||
|
rdf:type [ app:Vocabulary ] ; |
||||||
|
app:name xsd:string ; |
||||||
|
app:path IRI ; |
||||||
|
} |
||||||
|
`,
|
||||||
|
sampleTurtle: ``, |
||||||
|
baseNode: "http://example.com/SampleParent", |
||||||
|
successfulContext: { |
||||||
|
type: { "@id": "@type" }, |
||||||
|
Document: "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#Document", |
||||||
|
vocabulary: { |
||||||
|
"@id": |
||||||
|
"https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#vocabulary", |
||||||
|
"@type": "@id", |
||||||
|
"@container": "@set", |
||||||
|
}, |
||||||
|
Vocabulary: |
||||||
|
"https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#Vocabulary", |
||||||
|
name: { |
||||||
|
"@id": "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#name", |
||||||
|
"@type": "http://www.w3.org/2001/XMLSchema#string", |
||||||
|
}, |
||||||
|
path: { |
||||||
|
"@id": "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#path", |
||||||
|
"@type": "@id", |
||||||
|
}, |
||||||
|
law: { |
||||||
|
"@id": "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#law", |
||||||
|
"@type": "@id", |
||||||
|
}, |
||||||
|
Law: "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#Law", |
||||||
|
}, |
||||||
|
successfulTypings: |
||||||
|
'import {ContextDefinition} from "jsonld"\n\nexport interface DocumentShape {\n "@id"?: string;\r\n "@context"?: ContextDefinition;\r\n type: {\r\n "@id": "Document";\r\n };\r\n vocabulary?: (VocabularyShape)[];\r\n law: LawShape;\r\n}\r\n\r\nexport interface LawShape {\n "@id"?: string;\r\n "@context"?: ContextDefinition;\r\n type: {\r\n "@id": "Law";\r\n };\r\n name: string;\r\n path: {\r\n "@id": string;\r\n };\r\n}\r\n\r\nexport interface VocabularyShape {\n "@id"?: string;\r\n "@context"?: ContextDefinition;\r\n type: {\r\n "@id": "Vocabulary";\r\n };\r\n name: string;\r\n path: {\r\n "@id": string;\r\n };\r\n}\r\n\r\n', |
||||||
|
}; |
@ -0,0 +1,50 @@ |
|||||||
|
import type { TestData } from "./testData"; |
||||||
|
|
||||||
|
/** |
||||||
|
* SIMPLE |
||||||
|
*/ |
||||||
|
export const simple: TestData = { |
||||||
|
name: "simple", |
||||||
|
shexc: ` |
||||||
|
PREFIX foaf: <http://xmlns.com/foaf/0.1/> |
||||||
|
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> |
||||||
|
PREFIX example: <https://example.com/> |
||||||
|
|
||||||
|
example:EmployeeShape { # An <EmployeeShape> has: |
||||||
|
foaf:givenName xsd:string+, # at least one givenName. |
||||||
|
foaf:familyName xsd:string, # one familyName. |
||||||
|
foaf:phone IRI*, # any number of phone numbers. |
||||||
|
foaf:mbox IRI # one FOAF mbox. |
||||||
|
} |
||||||
|
`,
|
||||||
|
sampleTurtle: ` |
||||||
|
@prefix foaf: <http://xmlns.com/foaf/0.1/> . |
||||||
|
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . |
||||||
|
<http://a.example/Employee7> |
||||||
|
foaf:givenName "Robert"^^xsd:string, "Taylor"^^xsd:string ; |
||||||
|
foaf:familyName "Johnson"^^xsd:string ; |
||||||
|
# no phone number needed |
||||||
|
foaf:mbox <mailto:rtj@example.com> |
||||||
|
. |
||||||
|
`,
|
||||||
|
baseNode: "http://a.example/Employee7", |
||||||
|
successfulContext: { |
||||||
|
givenName: { |
||||||
|
"@id": "http://xmlns.com/foaf/0.1/givenName", |
||||||
|
"@type": "http://www.w3.org/2001/XMLSchema#string", |
||||||
|
"@container": "@set", |
||||||
|
}, |
||||||
|
familyName: { |
||||||
|
"@id": "http://xmlns.com/foaf/0.1/familyName", |
||||||
|
"@type": "http://www.w3.org/2001/XMLSchema#string", |
||||||
|
}, |
||||||
|
phone: { |
||||||
|
"@id": "http://xmlns.com/foaf/0.1/phone", |
||||||
|
"@type": "@id", |
||||||
|
"@container": "@set", |
||||||
|
}, |
||||||
|
mbox: { "@id": "http://xmlns.com/foaf/0.1/mbox", "@type": "@id" }, |
||||||
|
}, |
||||||
|
successfulTypings: |
||||||
|
'import {ContextDefinition} from "jsonld"\n\nexport interface EmployeeShape {\n "@id"?: string;\r\n "@context"?: ContextDefinition;\r\n givenName: string[];\r\n familyName: string;\r\n phone?: {\r\n "@id": string;\r\n }[];\r\n mbox: {\r\n "@id": string;\r\n };\r\n}\r\n\r\n', |
||||||
|
}; |
@ -0,0 +1,29 @@ |
|||||||
|
import type { ContextDefinition } from "jsonld"; |
||||||
|
import { activityPub } from "./activityPub"; |
||||||
|
import { circular } from "./circular"; |
||||||
|
import { profile } from "./profile"; |
||||||
|
import { reducedProfile } from "./reducedProfile"; |
||||||
|
import { simple } from "./simple"; |
||||||
|
import { extendsSimple } from "./extendsSimple"; |
||||||
|
import { reusedPredicates } from "./reusedPredicates"; |
||||||
|
// import { oldExtends } from "./oldExtends";
|
||||||
|
|
||||||
|
export interface TestData { |
||||||
|
name: string; |
||||||
|
shexc: string; |
||||||
|
sampleTurtle: string; |
||||||
|
baseNode: string; |
||||||
|
successfulContext: ContextDefinition; |
||||||
|
successfulTypings: string; |
||||||
|
} |
||||||
|
|
||||||
|
export const testData: TestData[] = [ |
||||||
|
simple, |
||||||
|
circular, |
||||||
|
profile, |
||||||
|
reducedProfile, |
||||||
|
activityPub, |
||||||
|
extendsSimple, |
||||||
|
// oldExtends,
|
||||||
|
reusedPredicates, |
||||||
|
]; |
@ -0,0 +1,16 @@ |
|||||||
|
import parser from "@shexjs/parser"; |
||||||
|
import { testData } from "./testData/testData"; |
||||||
|
import { shexjToTyping } from "../src/typing/shexjToTyping"; |
||||||
|
import type { Schema } from "shexj"; |
||||||
|
|
||||||
|
describe("typing", () => { |
||||||
|
testData.forEach(({ name, shexc, successfulTypings }) => { |
||||||
|
it(`Creates a typings for ${name}`, async () => { |
||||||
|
const schema: Schema = parser |
||||||
|
.construct("https://ldo.js.org/") |
||||||
|
.parse(shexc); |
||||||
|
const [typings] = await shexjToTyping(schema); |
||||||
|
expect(typings.typingsString).toBe(successfulTypings); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,7 @@ |
|||||||
|
{ |
||||||
|
"extends": "../../tsconfig.base.json", |
||||||
|
"compilerOptions": { |
||||||
|
"outDir": "./dist" |
||||||
|
}, |
||||||
|
"include": ["./src"] |
||||||
|
} |
Loading…
Reference in new issue