Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LinkedQL #1

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 8 additions & 17 deletions cayley.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ import NamedNode = require("@rdfjs/data-model/lib/named-node");
import BlankNode = require("@rdfjs/data-model/lib/blank-node");
import Literal = require("@rdfjs/data-model/lib/literal");
import * as N3 from "./n3";
import { Graph } from "./query-builder";
import Path, { Identifier } from "./path";
import "isomorphic-fetch";

export { NamedNode, BlankNode, Literal, Graph };

type Identifier = NamedNode | BlankNode;
export { NamedNode, BlankNode, Literal, Path, Identifier };

export enum Language {
LinkedQL = "linkedql",
Gizmo = "gizmo",
GraphQL = "graphql",
MQL = "mql"
Expand All @@ -27,17 +26,9 @@ export enum QueryResultFormat {
}

export default class Client {
/** This is the only special object in the environment, generates the query objects.
* Under the hood, they're simple objects that get compiled to a Go iterator tree when executed. */
graph: Graph;
/** Alias of graph. This is the only special object in the environment, generates the query objects.
Under the hood, they're simple objects that get compiled to a Go iterator tree when executed. */
g: Graph;
url: string;
constructor(url: string = "http://localhost:64210") {
this.url = url;
this.graph = new Graph(this);
this.g = this.graph;
}

/**
Expand Down Expand Up @@ -124,10 +115,10 @@ export default class Client {
* @param limit Globally limit the number of results
*/
query(
query: string,
language: Language = Language.Gizmo,
format: QueryResultFormat = QueryResultFormat.JsonLD,
limit: number = 100
query: string | Path,
limit: number = 100,
language: Language = Language.LinkedQL,
format: QueryResultFormat = QueryResultFormat.JsonLD
): Promise<Response> {
return fetch(
`${this.url}/api/v2/query?${new URLSearchParams({
Expand All @@ -139,7 +130,7 @@ export default class Client {
headers: {
Accept: format
},
body: query
body: String(query)
}
);
}
Expand Down
222 changes: 222 additions & 0 deletions generate/generate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
import path = require("path");
import fs = require("fs");
import ts = require("typescript");
import prettier = require("prettier");
import * as schema from "./schema.json";

const headerFile = path.join(path.dirname(__filename), "header.ts");
const pathFile = "path.ts";
const code = fs.readFileSync(headerFile, "utf-8");

const sourceFile = ts.createSourceFile(
pathFile,
code,
ts.ScriptTarget.Latest,
true
);

const pathClass = sourceFile.statements[sourceFile.statements.length - 1];
if (
!(ts.isClassDeclaration(pathClass) && pathClass.name.escapedText === "Path")
) {
throw new Error(`Unexpected statement: ${pathClass}`);
}

type SchemaObject = {
"@id": string;
};

type Restriction = SchemaObject & {
"@type": "owl:Restriction";
"owl:cardinality"?: number;
"owl:maxCardinality"?: number;
"owl:onProperty"?: SchemaObject;
};

type BaseStep = SchemaObject & {
"@type": "rdfs:Class";
"rdfs:subClassOf": Array<SchemaObject | Restriction>;
"rdfs:comment": string;
};

type BaseProperty = SchemaObject & {
"@type": "owl:ObjectProperty" | "owl:DatatypeProperty";
/** @todo this is invalid domain should receive { @id: string } */
"rdfs:domain":
| string
| {
"@id": string;
"@type": "owl:Class";
"owl:unionOf": {
"@id": string;
"@list": BaseStep[];
};
};
/** @todo this is invalid range should receive { @id: string } */
"rdfs:range": string;
};

const rangeToType = (range: string) => {
const rangeID = range;
if (rangeID === "linkedql:PathStep") {
return ts.createTypeReferenceNode("Path", []);
}
if (rangeID == "xsd:string") {
return ts.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
}
if (rangeID == "xsd:int" || rangeID == "xsd:float") {
return ts.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
}
if (rangeID == "xsd:boolean") {
return ts.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
}
if (rangeID == "linkedql:Operator") {
return ts.createTypeReferenceNode("Operator", []);
}
if (rangeID == "rdfs:Resource") {
return ts.createTypeReferenceNode("Identifier", []);
}
throw Error(`Unexpected range: ${range}`);
};

function createMethodFromStep(
step: BaseStep,
pathClassName: ts.Identifier,
properties: BaseProperty[]
): ts.MethodDeclaration {
const stepTypeID = step["@id"];
const stepTypeName = stepTypeID.replace("linkedql:", "");
const stepName = stepTypeName[0].toLowerCase() + stepTypeName.slice(1);
const restrictions: Restriction[] = step["rdfs:subClassOf"].filter(
(subClass): subClass is Restriction =>
subClass["@type"] === "owl:Restriction"
);
const stepProperties = properties.filter(property => {
if (property["@id"] === "linkedql:from") {
return false;
}
const domain = property["rdfs:domain"];
if (typeof domain === "string") {
return domain === stepTypeID;
}
return domain["owl:unionOf"]["@list"].some(
step => step["@id"] === stepTypeID
);
});
const parameterNames = stepProperties.map(property =>
property["@id"].replace("linkedql:", "")
);
const parameters = parameterNames.map((name, i) => {
const property = stepProperties[i];
const propertyRestrictions = restrictions.filter(restriction => {
return restriction["owl:onProperty"]["@id"] === property["@id"];
});
const cardinality =
propertyRestrictions[0] && propertyRestrictions[0]["owl:cardinality"];
const maxCardinality =
propertyRestrictions[0] && propertyRestrictions[0]["owl:maxCardinality"];
const baseType = rangeToType(property["rdfs:range"]);
let type: ts.TypeNode = baseType;
if (maxCardinality === 1) {
type = ts.createUnionTypeNode([
type,
ts.createKeywordTypeNode(ts.SyntaxKind.NullKeyword)
]);
} else if (cardinality !== 1) {
type = ts.createArrayTypeNode(type);
}
return ts.createParameter(
[],
[],
undefined,
name,
undefined,
type,
undefined
);
});
const stepPropertyAssignments = [
ts.createPropertyAssignment(
ts.createStringLiteral("@type"),
ts.createStringLiteral(stepTypeID)
),
...stepProperties.map((property, i) => {
const propertyName = parameterNames[i];
return ts.createPropertyAssignment(
ts.createStringLiteral(property["@id"]),
ts.createIdentifier(propertyName)
);
})
];
return ts.createMethod(
[],
[],
undefined,
stepName,
undefined,
[],
parameters,
ts.createTypeReferenceNode(pathClassName, []),
ts.createBlock([
ts.createStatement(
ts.createCall(
ts.createPropertyAccess(ts.createThis(), "addStep"),
[],
[ts.createObjectLiteral(stepPropertyAssignments)]
)
),
ts.createReturn(ts.createThis())
])
);
}

// @ts-ignore
const steps: BaseStep[] = schema.filter(
object => object["@type"] === "rdfs:Class"
);

// @ts-ignore
const properties: BaseProperty[] = schema.filter(
object =>
object["@type"] === "owl:ObjectProperty" ||
object["@type"] === "owl:DatatypeProperty"
);

const newMembers = [
...pathClass.members,
...steps.map((step: BaseStep) =>
createMethodFromStep(step, pathClass.name, properties)
)
];
const newPathClass = ts.createClassDeclaration(
pathClass.decorators,
pathClass.modifiers,
pathClass.name,
pathClass.typeParameters,
pathClass.heritageClauses,
newMembers
);

const newSourceFile = ts.createSourceFile(
sourceFile.fileName,
sourceFile.text,
ts.ScriptTarget.Latest,
/*setParentNodes*/ false,
ts.ScriptKind.TS
);
const printer = ts.createPrinter({
newLine: ts.NewLineKind.LineFeed
});

const result = printer.printList(
ts.ListFormat.SourceFileStatements,
ts.createNodeArray(
[
...sourceFile.statements.slice(0, sourceFile.statements.length - 1),
newPathClass
],
false
),
newSourceFile
);
fs.writeFileSync(pathFile, prettier.format(result, { parser: "typescript" }));
24 changes: 24 additions & 0 deletions generate/header.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import NamedNode = require("@rdfjs/data-model/lib/named-node");
import BlankNode = require("@rdfjs/data-model/lib/blank-node");
// @ts-ignore
import { QueryLanguage, QueryContentType } from "./client";

class Operator {}

type Step = Object;

export type Identifier = NamedNode | BlankNode;

export default class Path {
private cursor: Step | null;
private addStep(step: Step): void {
const previousCursor = this.cursor;
this.cursor = step;
if (previousCursor) {
this.cursor = { ...this.cursor, "linkedql:from": previousCursor };
}
}
toString(): string {
return JSON.stringify(this.cursor);
}
}
Loading