parent
bd472135e7
commit
cc225bcde7
File diff suppressed because it is too large
Load Diff
@ -1,99 +0,0 @@ |
|||||||
import * as shapeManager from "./shapeManager"; |
|
||||||
import type { WasmConnection, Diff, Scope } from "./types"; |
|
||||||
import type { CompactShapeType } from "@ldo/ldo"; |
|
||||||
import type { LdoCompactBase } from "@ldo/ldo"; |
|
||||||
import type { Person } from "src/shapes/ldo/personShape.typings"; |
|
||||||
import type { Cat } from "src/shapes/ldo/catShape.typings"; |
|
||||||
import type { TestObject } from "src/shapes/ldo/testShape.typings"; |
|
||||||
|
|
||||||
export const mockTestObject = { |
|
||||||
id: "ex:mock-id-1", |
|
||||||
type: "TestObject", |
|
||||||
stringValue: "string", |
|
||||||
numValue: 42, |
|
||||||
boolValue: true, |
|
||||||
arrayValue: new Set([1, 2, 3]), |
|
||||||
objectValue: { |
|
||||||
nestedString: "nested", |
|
||||||
nestedNum: 7, |
|
||||||
nestedArray: new Set([10, 12]), |
|
||||||
}, |
|
||||||
anotherObject: { |
|
||||||
"id:1": { |
|
||||||
id: "id:1", |
|
||||||
prop1: "prop1 value", |
|
||||||
prop2: 100, |
|
||||||
}, |
|
||||||
"id:2": { |
|
||||||
id: "id:1", |
|
||||||
prop1: "prop2 value", |
|
||||||
prop2: 200, |
|
||||||
}, |
|
||||||
}, |
|
||||||
} satisfies TestObject; |
|
||||||
|
|
||||||
const mockShapeObject1 = { |
|
||||||
id: "ex:person-1", |
|
||||||
type: "Person", |
|
||||||
name: "Bob", |
|
||||||
address: { |
|
||||||
street: "First street", |
|
||||||
houseNumber: "15", |
|
||||||
}, |
|
||||||
hasChildren: true, |
|
||||||
numberOfHouses: 0, |
|
||||||
} satisfies Person; |
|
||||||
const mockShapeObject2 = { |
|
||||||
id: "ex:cat-1", |
|
||||||
type: "Cat", |
|
||||||
name: "Niko's cat", |
|
||||||
age: 12, |
|
||||||
numberOfHomes: 3, |
|
||||||
address: { |
|
||||||
street: "Niko's street", |
|
||||||
houseNumber: "15", |
|
||||||
floor: 0, |
|
||||||
}, |
|
||||||
} satisfies Cat; |
|
||||||
|
|
||||||
let connectionIdCounter = 1; |
|
||||||
|
|
||||||
export default async function requestShape<T extends LdoCompactBase>( |
|
||||||
shape: CompactShapeType<T>, |
|
||||||
scope: Scope | undefined, |
|
||||||
callback: (diff: Diff, connectionId: WasmConnection["id"]) => void |
|
||||||
): Promise<{ |
|
||||||
connectionId: string; |
|
||||||
shapeObject: T; |
|
||||||
}> { |
|
||||||
const connectionId = `connection-${connectionIdCounter++}-${ |
|
||||||
shape.schema.shapes?.[0].id |
|
||||||
}`;
|
|
||||||
|
|
||||||
let shapeObject: T; |
|
||||||
if (shape.schema.shapes?.[0].id.includes("TestObject")) { |
|
||||||
shapeObject = mockTestObject as T; |
|
||||||
} else if (shape.schema.shapes?.[0].id.includes("Person")) { |
|
||||||
shapeObject = mockShapeObject1 as T; |
|
||||||
} else if (shape.schema.shapes?.[0].id.includes("Cat")) { |
|
||||||
shapeObject = mockShapeObject2 as T; |
|
||||||
} else { |
|
||||||
console.warn( |
|
||||||
"BACKEND: requestShape for unknown shape, returning empty object.", |
|
||||||
shape.schema.shapes?.[0].id |
|
||||||
); |
|
||||||
shapeObject = {}; |
|
||||||
} |
|
||||||
|
|
||||||
shapeManager.connections.set(connectionId, { |
|
||||||
id: connectionId, |
|
||||||
shape, |
|
||||||
state: shapeObject, |
|
||||||
callback, |
|
||||||
}); |
|
||||||
|
|
||||||
return { |
|
||||||
connectionId, |
|
||||||
shapeObject, |
|
||||||
}; |
|
||||||
} |
|
@ -0,0 +1,255 @@ |
|||||||
|
import * as shapeManager from "./shapeManager"; |
||||||
|
import type { WasmConnection, Diff, Scope } from "./types"; |
||||||
|
import type { CompactShapeType, LdoCompactBase } from "@ldo/ldo"; |
||||||
|
import type { Person } from "src/shapes/ldo/personShape.typings"; |
||||||
|
import type { Cat } from "src/shapes/ldo/catShape.typings"; |
||||||
|
import type { TestObject } from "src/shapes/ldo/testShape.typings"; |
||||||
|
import updateShape from "./updateShape"; |
||||||
|
|
||||||
|
// Messages exchanged over the BroadcastChannel("shape-manager")
|
||||||
|
interface WasmMessage { |
||||||
|
type: |
||||||
|
| "Request" |
||||||
|
| "InitialResponse" |
||||||
|
| "FrontendUpdate" |
||||||
|
| "BackendUpdate" |
||||||
|
| "Stop"; |
||||||
|
connectionId: string; |
||||||
|
diff?: Diff; |
||||||
|
schema?: CompactShapeType<any>["schema"]; |
||||||
|
initialData?: LdoCompactBase; |
||||||
|
} |
||||||
|
|
||||||
|
export const mockTestObject = { |
||||||
|
id: "ex:mock-id-1", |
||||||
|
type: "TestObject", |
||||||
|
stringValue: "string", |
||||||
|
numValue: 42, |
||||||
|
boolValue: true, |
||||||
|
arrayValue: [1, 2, 3], |
||||||
|
objectValue: { |
||||||
|
id: "urn:obj-1", |
||||||
|
nestedString: "nested", |
||||||
|
nestedNum: 7, |
||||||
|
nestedArray: [10, 12], |
||||||
|
}, |
||||||
|
anotherObject: { |
||||||
|
"id:1": { |
||||||
|
id: "id:1", |
||||||
|
prop1: "prop1 value", |
||||||
|
prop2: 100, |
||||||
|
}, |
||||||
|
"id:2": { |
||||||
|
id: "id:1", |
||||||
|
prop1: "prop2 value", |
||||||
|
prop2: 200, |
||||||
|
}, |
||||||
|
}, |
||||||
|
} satisfies TestObject; |
||||||
|
|
||||||
|
const mockShapeObject1 = { |
||||||
|
id: "ex:person-1", |
||||||
|
type: "Person", |
||||||
|
name: "Bob", |
||||||
|
address: { |
||||||
|
id: "urn:person-home-1", |
||||||
|
street: "First street", |
||||||
|
houseNumber: "15", |
||||||
|
}, |
||||||
|
hasChildren: true, |
||||||
|
numberOfHouses: 0, |
||||||
|
} satisfies Person; |
||||||
|
|
||||||
|
const mockShapeObject2 = { |
||||||
|
id: "ex:cat-1", |
||||||
|
type: "Cat", |
||||||
|
name: "Niko's cat", |
||||||
|
age: 12, |
||||||
|
numberOfHomes: 3, |
||||||
|
address: { |
||||||
|
id: "Nikos-cat-home", |
||||||
|
street: "Niko's street", |
||||||
|
houseNumber: "15", |
||||||
|
floor: 0, |
||||||
|
}, |
||||||
|
} satisfies Cat; |
||||||
|
|
||||||
|
// Single BroadcastChannel for wasm-land side
|
||||||
|
const communicationChannel = new BroadcastChannel("shape-manager"); |
||||||
|
|
||||||
|
function getInitialObjectByShapeId<T extends LdoCompactBase>( |
||||||
|
shapeId?: string, |
||||||
|
): T { |
||||||
|
if (shapeId?.includes("TestObject")) return mockTestObject as unknown as T; |
||||||
|
if (shapeId?.includes("Person")) return mockShapeObject1 as unknown as T; |
||||||
|
if (shapeId?.includes("Cat")) return mockShapeObject2 as unknown as T; |
||||||
|
console.warn( |
||||||
|
"BACKEND: requestShape for unknown shape, returning empty object.", |
||||||
|
shapeId, |
||||||
|
); |
||||||
|
return {} as T; |
||||||
|
} |
||||||
|
|
||||||
|
// Register handler for messages coming from js-land
|
||||||
|
communicationChannel.addEventListener( |
||||||
|
"message", |
||||||
|
(event: MessageEvent<WasmMessage>) => { |
||||||
|
console.log("BACKEND: Received message", event.data); |
||||||
|
const { type, connectionId, schema } = event.data; |
||||||
|
|
||||||
|
if (type === "Request") { |
||||||
|
const shapeId = schema?.shapes?.[0]?.id; |
||||||
|
const initialData = getInitialObjectByShapeId(shapeId); |
||||||
|
|
||||||
|
// Store connection. We store the shapeId string to allow equality across connections.
|
||||||
|
shapeManager.connections.set(connectionId, { |
||||||
|
id: connectionId, |
||||||
|
// Cast to any to satisfy WasmConnection type, comparison in updateShape uses ==
|
||||||
|
shape: (shapeId ?? "__unknown__") as any, |
||||||
|
state: initialData, |
||||||
|
callback: (diff: Diff, conId: WasmConnection["id"]) => { |
||||||
|
// Notify js-land about backend updates
|
||||||
|
const msg: WasmMessage = { |
||||||
|
type: "BackendUpdate", |
||||||
|
connectionId: conId, |
||||||
|
diff, |
||||||
|
}; |
||||||
|
communicationChannel.postMessage(msg); |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
const msg: WasmMessage = { |
||||||
|
type: "InitialResponse", |
||||||
|
connectionId, |
||||||
|
initialData, |
||||||
|
}; |
||||||
|
communicationChannel.postMessage(msg); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (type === "Stop") { |
||||||
|
shapeManager.connections.delete(connectionId); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (type === "FrontendUpdate" && event.data.diff) { |
||||||
|
updateShape(connectionId, event.data.diff); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
console.warn("BACKEND: Unknown message type or missing diff", event.data); |
||||||
|
}, |
||||||
|
); |
||||||
|
|
||||||
|
// Keep the original function for compatibility with any direct callers.
|
||||||
|
let connectionIdCounter = 1; |
||||||
|
export default async function requestShape<T extends LdoCompactBase>( |
||||||
|
shape: CompactShapeType<T>, |
||||||
|
_scope: Scope | undefined, |
||||||
|
callback: (diff: Diff, connectionId: WasmConnection["id"]) => void, |
||||||
|
): Promise<{ connectionId: string; shapeObject: T }> { |
||||||
|
const connectionId = `connection-${connectionIdCounter++}-${shape.schema.shapes?.[0]?.id}`; |
||||||
|
const shapeId = shape.schema.shapes?.[0]?.id; |
||||||
|
const shapeObject = getInitialObjectByShapeId<T>(shapeId); |
||||||
|
|
||||||
|
shapeManager.connections.set(connectionId, { |
||||||
|
id: connectionId, |
||||||
|
shape: (shapeId ?? "__unknown__") as any, |
||||||
|
state: shapeObject, |
||||||
|
callback, |
||||||
|
}); |
||||||
|
|
||||||
|
return { connectionId, shapeObject }; |
||||||
|
} |
||||||
|
|
||||||
|
const getObjectsForShapeType = <T extends LdoCompactBase>( |
||||||
|
shape: CompactShapeType<T>, |
||||||
|
scope: string = "", |
||||||
|
): T[] => { |
||||||
|
// Procedure
|
||||||
|
// - Get all triples for the scope
|
||||||
|
// - Parse the schema (all shapes and anonymous shapes required for the shape type).
|
||||||
|
|
||||||
|
// - Group triples by subject
|
||||||
|
// - For the shapeType in the schema, match all required predicates
|
||||||
|
// - For predicates pointing to nested objects
|
||||||
|
// - recurse
|
||||||
|
|
||||||
|
// Repeat procedure for all matched subjects with optional predicates
|
||||||
|
|
||||||
|
const quads: [ |
||||||
|
string, |
||||||
|
string, |
||||||
|
number | string | boolean, |
||||||
|
string | undefined, |
||||||
|
][] = []; |
||||||
|
|
||||||
|
// The URI of the shape to find matches for.
|
||||||
|
const schemaId = shape.shape; |
||||||
|
// ShexJ shape object
|
||||||
|
const rootShapeDecl = shape.schema.shapes?.find( |
||||||
|
(shape) => shape.id === schemaId, |
||||||
|
); |
||||||
|
if (!rootShapeDecl) |
||||||
|
throw new Error(`Could not find shape id ${schemaId} in shape schema`); |
||||||
|
|
||||||
|
if (rootShapeDecl.shapeExpr.type !== "Shape") |
||||||
|
throw new Error("Expected shapeExpr.type to be Shape"); |
||||||
|
|
||||||
|
const shapeExpression = rootShapeDecl.shapeExpr.expression; |
||||||
|
// If shape is a reference...
|
||||||
|
if (typeof shapeExpression === "string") { |
||||||
|
// TODO: Recurse
|
||||||
|
return []; |
||||||
|
} |
||||||
|
|
||||||
|
const requiredPredicates = []; |
||||||
|
const optionalPredicates = []; |
||||||
|
|
||||||
|
if (shapeExpression?.type === "EachOf") { |
||||||
|
const predicates = shapeExpression.expressions.map((constraint) => { |
||||||
|
if (typeof constraint === "string") { |
||||||
|
// Cannot parse constraint refs
|
||||||
|
return; |
||||||
|
} else if (constraint.type === "TripleConstraint") { |
||||||
|
requiredPredicates.push({ |
||||||
|
predicate: constraint.predicate, |
||||||
|
}); |
||||||
|
} else { |
||||||
|
// EachOf or OneOf possible?
|
||||||
|
} |
||||||
|
}); |
||||||
|
} else if (shapeExpression?.type === "OneOf") { |
||||||
|
// Does not occur AFAIK.
|
||||||
|
} else if (shapeExpression?.type === "TripleConstraint") { |
||||||
|
// Does not occur AFAIK.
|
||||||
|
} |
||||||
|
|
||||||
|
return []; |
||||||
|
}; |
||||||
|
|
||||||
|
interface ShapeConstraintTracked { |
||||||
|
subject: string; |
||||||
|
childOf?: ShapeConstraintTracked; |
||||||
|
predicates: [ |
||||||
|
{ |
||||||
|
displayName: string; |
||||||
|
uri: string; |
||||||
|
type: "number" | "string" | "boolean" | "nested" | "literal"; |
||||||
|
literalValue?: number | string | boolean | number[] | string[]; |
||||||
|
nested?: ShapeConstraintTracked; |
||||||
|
min: number; |
||||||
|
max: number; |
||||||
|
currentCount: number; |
||||||
|
}, |
||||||
|
]; |
||||||
|
} |
||||||
|
|
||||||
|
// Group by subject, check predicates of root level
|
||||||
|
// For all subjects of root level,
|
||||||
|
// - recurse
|
||||||
|
|
||||||
|
// Construct matching subjects
|
||||||
|
// for each optional and non-optional predicate
|
||||||
|
// - fill objects and record
|
||||||
|
// - build tracked object (keeping reference counts to check if the object is still valid)
|
@ -0,0 +1,14 @@ |
|||||||
|
# SPARQL builders |
||||||
|
|
||||||
|
Utilities to build SPARQL SELECT and CONSTRUCT queries from a ShapeConstraint structure. |
||||||
|
|
||||||
|
Exports: |
||||||
|
|
||||||
|
- buildSelectQuery(shape, options) |
||||||
|
- buildConstructQuery(shape, options) |
||||||
|
|
||||||
|
Options: |
||||||
|
|
||||||
|
- prefixes: Record<prefix, IRI> |
||||||
|
- graph: named graph IRI or CURIE |
||||||
|
- includeOptionalForMinZero: wrap min=0 predicates in OPTIONAL (default true) |
@ -0,0 +1,149 @@ |
|||||||
|
import type { |
||||||
|
BuildContext, |
||||||
|
PredicateConstraint, |
||||||
|
ShapeConstraint, |
||||||
|
SparqlBuildOptions, |
||||||
|
} from "./common"; |
||||||
|
import { |
||||||
|
predicateToSparql, |
||||||
|
prefixesToText, |
||||||
|
toIriOrCurie, |
||||||
|
uniqueVar, |
||||||
|
valuesBlock, |
||||||
|
varToken, |
||||||
|
} from "./common"; |
||||||
|
|
||||||
|
/** |
||||||
|
* Build a SPARQL CONSTRUCT query from a ShapeConstraint definition. |
||||||
|
* The WHERE mirrors the graph template. Optional predicates (min=0) are wrapped in OPTIONAL in WHERE |
||||||
|
* but still appear in the CONSTRUCT template so that matched triples are constructed. |
||||||
|
*/ |
||||||
|
export function buildConstructQuery( |
||||||
|
shape: ShapeConstraint, |
||||||
|
options?: SparqlBuildOptions, |
||||||
|
): string { |
||||||
|
const ctx: BuildContext = { usedVars: new Set<string>() }; |
||||||
|
const prefixes = prefixesToText(options?.prefixes); |
||||||
|
const subject = toIriOrCurie(shape.subject); |
||||||
|
|
||||||
|
const templateLines: string[] = []; |
||||||
|
const whereLines: string[] = []; |
||||||
|
const postFilters: string[] = []; |
||||||
|
const valuesBlocks: string[] = []; |
||||||
|
|
||||||
|
const rootVar = |
||||||
|
subject.startsWith("?") || subject.startsWith("$") |
||||||
|
? subject |
||||||
|
: uniqueVar(ctx, "s"); |
||||||
|
if (!subject.startsWith("?") && !subject.startsWith("$")) { |
||||||
|
valuesBlocks.push(valuesBlock(rootVar, [subject] as any)); |
||||||
|
} |
||||||
|
|
||||||
|
const predicates = Array.isArray(shape.predicates) |
||||||
|
? shape.predicates |
||||||
|
: [...shape.predicates]; |
||||||
|
for (const pred of predicates) { |
||||||
|
addConstructPattern( |
||||||
|
ctx, |
||||||
|
pred, |
||||||
|
rootVar, |
||||||
|
templateLines, |
||||||
|
whereLines, |
||||||
|
postFilters, |
||||||
|
valuesBlocks, |
||||||
|
options, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
const graphWrap = (body: string) => |
||||||
|
options?.graph |
||||||
|
? `GRAPH ${toIriOrCurie(options.graph)} {\n${body}\n}` |
||||||
|
: body; |
||||||
|
|
||||||
|
const where = [ |
||||||
|
...valuesBlocks, |
||||||
|
graphWrap(whereLines.join("\n")), |
||||||
|
...postFilters, |
||||||
|
] |
||||||
|
.filter(Boolean) |
||||||
|
.join("\n"); |
||||||
|
|
||||||
|
const template = templateLines.join("\n"); |
||||||
|
|
||||||
|
return [prefixes, `CONSTRUCT {`, template, `} WHERE {`, where, `}`].join( |
||||||
|
"\n", |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
function addConstructPattern( |
||||||
|
ctx: BuildContext, |
||||||
|
pred: PredicateConstraint, |
||||||
|
subjectVar: string, |
||||||
|
template: string[], |
||||||
|
where: string[], |
||||||
|
postFilters: string[], |
||||||
|
valuesBlocks: string[], |
||||||
|
options?: SparqlBuildOptions, |
||||||
|
) { |
||||||
|
const p = predicateToSparql(pred.uri); |
||||||
|
const objVar = uniqueVar(ctx, pred.displayName || "o"); |
||||||
|
const objTerm = |
||||||
|
pred.type === "nested" && |
||||||
|
pred.nested?.subject && |
||||||
|
!pred.nested.subject.match(/^\?|^\$/) |
||||||
|
? toIriOrCurie(pred.nested.subject) |
||||||
|
: objVar; |
||||||
|
|
||||||
|
const triple = `${subjectVar} ${p} ${objTerm} .`; |
||||||
|
|
||||||
|
const isOptional = |
||||||
|
(pred.min ?? 0) === 0 && (options?.includeOptionalForMinZero ?? true); |
||||||
|
|
||||||
|
if (pred.type === "nested" && pred.nested) { |
||||||
|
template.push(triple); |
||||||
|
const nestedBody: string[] = [triple]; |
||||||
|
const nestedPreds = Array.isArray(pred.nested.predicates) |
||||||
|
? pred.nested.predicates |
||||||
|
: [...pred.nested.predicates]; |
||||||
|
for (const n of nestedPreds) { |
||||||
|
addConstructPattern( |
||||||
|
ctx, |
||||||
|
n, |
||||||
|
objTerm, |
||||||
|
template, |
||||||
|
nestedBody, |
||||||
|
postFilters, |
||||||
|
valuesBlocks, |
||||||
|
options, |
||||||
|
); |
||||||
|
} |
||||||
|
const block = nestedBody.join("\n"); |
||||||
|
where.push(isOptional ? `OPTIONAL {\n${block}\n}` : block); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// Non-nested
|
||||||
|
template.push(triple); |
||||||
|
const blockLines: string[] = [triple]; |
||||||
|
|
||||||
|
if (pred.type === "literal" && pred.literalValue !== undefined) { |
||||||
|
if (Array.isArray(pred.literalValue)) { |
||||||
|
valuesBlocks.push(valuesBlock(objVar, pred.literalValue as any[])); |
||||||
|
} else { |
||||||
|
const lit = |
||||||
|
typeof pred.literalValue === "string" || |
||||||
|
typeof pred.literalValue === "number" || |
||||||
|
typeof pred.literalValue === "boolean" |
||||||
|
? pred.literalValue |
||||||
|
: String(pred.literalValue); |
||||||
|
postFilters.push( |
||||||
|
`FILTER(${objVar} = ${typeof lit === "string" ? `"${String(lit).replace(/"/g, '\\"')}"` : lit})`, |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const block = blockLines.join("\n"); |
||||||
|
where.push(isOptional ? `OPTIONAL {\n${block}\n}` : block); |
||||||
|
} |
||||||
|
|
||||||
|
export default buildConstructQuery; |
@ -0,0 +1,152 @@ |
|||||||
|
import type { |
||||||
|
BuildContext, |
||||||
|
PredicateConstraint, |
||||||
|
ShapeConstraint, |
||||||
|
SparqlBuildOptions, |
||||||
|
} from "./common"; |
||||||
|
import { |
||||||
|
predicateToSparql, |
||||||
|
prefixesToText, |
||||||
|
toIriOrCurie, |
||||||
|
uniqueVar, |
||||||
|
valuesBlock, |
||||||
|
varToken, |
||||||
|
} from "./common"; |
||||||
|
|
||||||
|
/** |
||||||
|
* Build a SPARQL SELECT query from a ShapeConstraint definition. |
||||||
|
* The query matches the shape subject and constraints; optional predicates (min=0) are wrapped in OPTIONAL. |
||||||
|
*/ |
||||||
|
export function buildSelectQuery( |
||||||
|
shape: ShapeConstraint, |
||||||
|
options?: SparqlBuildOptions, |
||||||
|
): string { |
||||||
|
const ctx: BuildContext = { usedVars: new Set<string>() }; |
||||||
|
const prefixes = prefixesToText(options?.prefixes); |
||||||
|
const subject = toIriOrCurie(shape.subject); |
||||||
|
|
||||||
|
const selectVars: string[] = []; |
||||||
|
const whereLines: string[] = []; |
||||||
|
const postFilters: string[] = []; |
||||||
|
const valuesBlocks: string[] = []; |
||||||
|
|
||||||
|
// ensure a consistent root variable when subject is a variable
|
||||||
|
const rootVar = |
||||||
|
subject.startsWith("?") || subject.startsWith("$") |
||||||
|
? subject |
||||||
|
: uniqueVar(ctx, "s"); |
||||||
|
if (!subject.startsWith("?") && !subject.startsWith("$")) { |
||||||
|
// bind fixed subject via VALUES for portability
|
||||||
|
valuesBlocks.push(valuesBlock(rootVar, [subject] as any)); |
||||||
|
} |
||||||
|
|
||||||
|
const predicates = Array.isArray(shape.predicates) |
||||||
|
? shape.predicates |
||||||
|
: [...shape.predicates]; |
||||||
|
|
||||||
|
for (const pred of predicates) { |
||||||
|
addPredicatePattern( |
||||||
|
ctx, |
||||||
|
pred, |
||||||
|
rootVar, |
||||||
|
whereLines, |
||||||
|
selectVars, |
||||||
|
postFilters, |
||||||
|
valuesBlocks, |
||||||
|
options, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
const graphWrap = (body: string) => |
||||||
|
options?.graph |
||||||
|
? `GRAPH ${toIriOrCurie(options.graph)} {\n${body}\n}` |
||||||
|
: body; |
||||||
|
|
||||||
|
const where = [ |
||||||
|
...valuesBlocks, |
||||||
|
graphWrap(whereLines.join("\n")), |
||||||
|
...postFilters, |
||||||
|
] |
||||||
|
.filter(Boolean) |
||||||
|
.join("\n"); |
||||||
|
|
||||||
|
const select = selectVars.length ? selectVars.join(" ") : "*"; |
||||||
|
|
||||||
|
return [prefixes, `SELECT ${select} WHERE {`, where, `}`].join("\n"); |
||||||
|
} |
||||||
|
|
||||||
|
function addPredicatePattern( |
||||||
|
ctx: BuildContext, |
||||||
|
pred: PredicateConstraint, |
||||||
|
subjectVar: string, |
||||||
|
where: string[], |
||||||
|
selectVars: string[], |
||||||
|
postFilters: string[], |
||||||
|
valuesBlocks: string[], |
||||||
|
options?: SparqlBuildOptions, |
||||||
|
) { |
||||||
|
const p = predicateToSparql(pred.uri); |
||||||
|
const objVar = uniqueVar(ctx, pred.displayName || "o"); |
||||||
|
const objTerm = |
||||||
|
pred.type === "nested" && |
||||||
|
pred.nested?.subject && |
||||||
|
!pred.nested.subject.match(/^\?|^\$/) |
||||||
|
? toIriOrCurie(pred.nested.subject) |
||||||
|
: objVar; |
||||||
|
|
||||||
|
const triple = `${subjectVar} ${p} ${objTerm} .`; |
||||||
|
|
||||||
|
const isOptional = |
||||||
|
(pred.min ?? 0) === 0 && (options?.includeOptionalForMinZero ?? true); |
||||||
|
|
||||||
|
if (pred.type === "nested" && pred.nested) { |
||||||
|
// For nested, we select the nested object var and then recurse
|
||||||
|
if (objTerm === objVar) selectVars.push(objVar); |
||||||
|
const nestedBody: string[] = [triple]; |
||||||
|
const nestedPreds = Array.isArray(pred.nested.predicates) |
||||||
|
? pred.nested.predicates |
||||||
|
: [...pred.nested.predicates]; |
||||||
|
for (const n of nestedPreds) { |
||||||
|
addPredicatePattern( |
||||||
|
ctx, |
||||||
|
n, |
||||||
|
objTerm, |
||||||
|
nestedBody, |
||||||
|
selectVars, |
||||||
|
postFilters, |
||||||
|
valuesBlocks, |
||||||
|
options, |
||||||
|
); |
||||||
|
} |
||||||
|
const block = nestedBody.join("\n"); |
||||||
|
where.push(isOptional ? `OPTIONAL {\n${block}\n}` : block); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// Non-nested: literals or IRIs
|
||||||
|
selectVars.push(objVar); |
||||||
|
const blockLines: string[] = [triple]; |
||||||
|
|
||||||
|
if (pred.type === "literal" && pred.literalValue !== undefined) { |
||||||
|
if (Array.isArray(pred.literalValue)) { |
||||||
|
// VALUES block for IN-like matching
|
||||||
|
valuesBlocks.push(valuesBlock(objVar, pred.literalValue as any[])); |
||||||
|
} else { |
||||||
|
// simple equality filter
|
||||||
|
const lit = |
||||||
|
typeof pred.literalValue === "string" || |
||||||
|
typeof pred.literalValue === "number" || |
||||||
|
typeof pred.literalValue === "boolean" |
||||||
|
? pred.literalValue |
||||||
|
: String(pred.literalValue); |
||||||
|
postFilters.push( |
||||||
|
`FILTER(${objVar} = ${typeof lit === "string" ? `"${String(lit).replace(/"/g, '\\"')}"` : lit})`, |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const block = blockLines.join("\n"); |
||||||
|
where.push(isOptional ? `OPTIONAL {\n${block}\n}` : block); |
||||||
|
} |
||||||
|
|
||||||
|
export default buildSelectQuery; |
@ -0,0 +1,125 @@ |
|||||||
|
/** |
||||||
|
* Shared helpers and types to build SPARQL queries from ShapeConstraint |
||||||
|
*/ |
||||||
|
|
||||||
|
export type LiteralKind = |
||||||
|
| "number" |
||||||
|
| "string" |
||||||
|
| "boolean" |
||||||
|
| "nested" |
||||||
|
| "literal"; |
||||||
|
|
||||||
|
export interface PredicateConstraint { |
||||||
|
displayName: string; |
||||||
|
uri: string; |
||||||
|
type: LiteralKind; |
||||||
|
literalValue?: number | string | boolean | number[] | string[]; |
||||||
|
nested?: ShapeConstraint; |
||||||
|
min: number; |
||||||
|
max: number; |
||||||
|
currentCount: number; |
||||||
|
} |
||||||
|
|
||||||
|
export interface ShapeConstraint { |
||||||
|
subject: string; |
||||||
|
// In upstream code this is typed as a 1-length tuple; we normalize to an array here
|
||||||
|
predicates: PredicateConstraint[] | [PredicateConstraint]; |
||||||
|
} |
||||||
|
|
||||||
|
export interface SparqlBuildOptions { |
||||||
|
prefixes?: Record<string, string>; |
||||||
|
graph?: string; // IRI of the named graph to query, if any
|
||||||
|
includeOptionalForMinZero?: boolean; // default true
|
||||||
|
} |
||||||
|
|
||||||
|
export const defaultPrefixes: Record<string, string> = { |
||||||
|
xsd: "http://www.w3.org/2001/XMLSchema#", |
||||||
|
rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#", |
||||||
|
rdfs: "http://www.w3.org/2000/01/rdf-schema#", |
||||||
|
}; |
||||||
|
|
||||||
|
export function prefixesToText(prefixes?: Record<string, string>): string { |
||||||
|
const all = { ...defaultPrefixes, ...(prefixes ?? {}) }; |
||||||
|
return Object.entries(all) |
||||||
|
.map(([p, iri]) => `PREFIX ${p}: <${iri}>`) |
||||||
|
.join("\n"); |
||||||
|
} |
||||||
|
|
||||||
|
export function toIriOrCurie(term: string): string { |
||||||
|
// variable
|
||||||
|
if (term.startsWith("?") || term.startsWith("$")) return term; |
||||||
|
// blank node
|
||||||
|
if (term.startsWith("_:")) return term; |
||||||
|
// full IRI
|
||||||
|
if (term.includes("://")) return `<${term}>`; |
||||||
|
// fallback: assume CURIE or already-angled
|
||||||
|
if (term.startsWith("<") && term.endsWith(">")) return term; |
||||||
|
return term; // CURIE, caller must ensure prefix provided
|
||||||
|
} |
||||||
|
|
||||||
|
export function predicateToSparql(uri: string): string { |
||||||
|
// Allow CURIEs or IRIs
|
||||||
|
return toIriOrCurie(uri); |
||||||
|
} |
||||||
|
|
||||||
|
export function safeVarName(name: string): string { |
||||||
|
const base = name |
||||||
|
.replace(/[^a-zA-Z0-9_]/g, "_") |
||||||
|
.replace(/^([0-9])/, "_$1") |
||||||
|
.slice(0, 60); |
||||||
|
return base || "v"; |
||||||
|
} |
||||||
|
|
||||||
|
export function varToken(name: string): string { |
||||||
|
const n = name.startsWith("?") || name.startsWith("$") ? name.slice(1) : name; |
||||||
|
return `?${safeVarName(n)}`; |
||||||
|
} |
||||||
|
|
||||||
|
export function formatLiteral(value: string | number | boolean): string { |
||||||
|
if (typeof value === "number") return String(value); |
||||||
|
if (typeof value === "boolean") return value ? "true" : "false"; |
||||||
|
// default string literal
|
||||||
|
const escaped = value.replace(/"/g, '\\"'); |
||||||
|
return `"${escaped}"`; |
||||||
|
} |
||||||
|
|
||||||
|
export function formatTermForValues(value: string | number | boolean): string { |
||||||
|
if (typeof value === "number" || typeof value === "boolean") |
||||||
|
return formatLiteral(value); |
||||||
|
// strings: detect IRI or CURIE and keep raw; otherwise quote
|
||||||
|
const v = value.trim(); |
||||||
|
const looksLikeIri = v.startsWith("<") && v.endsWith(">"); |
||||||
|
const looksLikeHttp = v.includes("://"); |
||||||
|
const looksLikeCurie = |
||||||
|
/^[A-Za-z_][A-Za-z0-9_-]*:.+$/u.test(v) && !looksLikeHttp; |
||||||
|
if (looksLikeIri || looksLikeHttp || looksLikeCurie) { |
||||||
|
return looksLikeHttp ? `<${v}>` : v; |
||||||
|
} |
||||||
|
return formatLiteral(v); |
||||||
|
} |
||||||
|
|
||||||
|
export function valuesBlock( |
||||||
|
varName: string, |
||||||
|
values: Array<string | number | boolean>, |
||||||
|
): string { |
||||||
|
const rendered = values.map(formatTermForValues).join(" "); |
||||||
|
return `VALUES ${varName} { ${rendered} }`; |
||||||
|
} |
||||||
|
|
||||||
|
export interface BuildContext { |
||||||
|
// Tracks used variable names to avoid collisions
|
||||||
|
usedVars: Set<string>; |
||||||
|
} |
||||||
|
|
||||||
|
export function uniqueVar(ctx: BuildContext, base: string): string { |
||||||
|
let candidate = varToken(base); |
||||||
|
if (!ctx.usedVars.has(candidate)) { |
||||||
|
ctx.usedVars.add(candidate); |
||||||
|
return candidate; |
||||||
|
} |
||||||
|
let i = 2; |
||||||
|
while (ctx.usedVars.has(`${candidate}_${i}`)) i++; |
||||||
|
const unique = `${candidate}_${i}`; |
||||||
|
ctx.usedVars.add(unique); |
||||||
|
return unique; |
||||||
|
} |
@ -0,0 +1,77 @@ |
|||||||
|
import type { CompactSchema } from "@ldo/ldo"; |
||||||
|
|
||||||
|
/** |
||||||
|
* ============================================================================= |
||||||
|
* catShapeSchema: Compact Schema for catShape |
||||||
|
* ============================================================================= |
||||||
|
*/ |
||||||
|
export const catShapeSchema: CompactSchema = { |
||||||
|
"http://example.org/Cat": { |
||||||
|
schemaUri: "http://example.org/Cat", |
||||||
|
predicates: [ |
||||||
|
{ |
||||||
|
type: "literal", |
||||||
|
literalValue: ["Cat"], |
||||||
|
maxCardinality: 1, |
||||||
|
minCardinality: 1, |
||||||
|
predicateUri: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", |
||||||
|
readablePredicate: "type", |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "string", |
||||||
|
maxCardinality: 1, |
||||||
|
minCardinality: 1, |
||||||
|
predicateUri: "http://example.org/name", |
||||||
|
readablePredicate: "name", |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "number", |
||||||
|
maxCardinality: 1, |
||||||
|
minCardinality: 1, |
||||||
|
predicateUri: "http://example.org/age", |
||||||
|
readablePredicate: "age", |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "number", |
||||||
|
maxCardinality: 1, |
||||||
|
minCardinality: 1, |
||||||
|
predicateUri: "http://example.org/numberOfHomes", |
||||||
|
readablePredicate: "numberOfHomes", |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "nested", |
||||||
|
nestedSchema: "http://example.org/Cat::http://example.org/address", |
||||||
|
maxCardinality: 1, |
||||||
|
minCardinality: 1, |
||||||
|
predicateUri: "http://example.org/address", |
||||||
|
readablePredicate: "address", |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
"http://example.org/Cat::http://example.org/address": { |
||||||
|
schemaUri: "http://example.org/Cat::http://example.org/address", |
||||||
|
predicates: [ |
||||||
|
{ |
||||||
|
type: "string", |
||||||
|
maxCardinality: 1, |
||||||
|
minCardinality: 1, |
||||||
|
predicateUri: "http://example.org/street", |
||||||
|
readablePredicate: "street", |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "string", |
||||||
|
maxCardinality: 1, |
||||||
|
minCardinality: 1, |
||||||
|
predicateUri: "http://example.org/houseNumber", |
||||||
|
readablePredicate: "houseNumber", |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "number", |
||||||
|
maxCardinality: 1, |
||||||
|
minCardinality: 1, |
||||||
|
predicateUri: "http://example.org/floor", |
||||||
|
readablePredicate: "floor", |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
}; |
@ -1,104 +0,0 @@ |
|||||||
import type { Schema } from "shexj"; |
|
||||||
|
|
||||||
/** |
|
||||||
* ============================================================================= |
|
||||||
* catShapeSchema: ShexJ Schema for catShape |
|
||||||
* ============================================================================= |
|
||||||
*/ |
|
||||||
export const catShapeSchema: Schema = { |
|
||||||
type: "Schema", |
|
||||||
shapes: [ |
|
||||||
{ |
|
||||||
id: "http://example.org/Cat", |
|
||||||
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: [ |
|
||||||
{ |
|
||||||
value: "Cat", |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
readablePredicate: "type", |
|
||||||
}, |
|
||||||
{ |
|
||||||
type: "TripleConstraint", |
|
||||||
predicate: "http://example.org/name", |
|
||||||
valueExpr: { |
|
||||||
type: "NodeConstraint", |
|
||||||
datatype: "http://www.w3.org/2001/XMLSchema#string", |
|
||||||
}, |
|
||||||
readablePredicate: "name", |
|
||||||
}, |
|
||||||
{ |
|
||||||
type: "TripleConstraint", |
|
||||||
predicate: "http://example.org/age", |
|
||||||
valueExpr: { |
|
||||||
type: "NodeConstraint", |
|
||||||
datatype: "http://www.w3.org/2001/XMLSchema#integer", |
|
||||||
}, |
|
||||||
readablePredicate: "age", |
|
||||||
}, |
|
||||||
{ |
|
||||||
type: "TripleConstraint", |
|
||||||
predicate: "http://example.org/numberOfHomes", |
|
||||||
valueExpr: { |
|
||||||
type: "NodeConstraint", |
|
||||||
datatype: "http://www.w3.org/2001/XMLSchema#integer", |
|
||||||
}, |
|
||||||
readablePredicate: "numberOfHomes", |
|
||||||
}, |
|
||||||
{ |
|
||||||
type: "TripleConstraint", |
|
||||||
predicate: "http://example.org/address", |
|
||||||
valueExpr: { |
|
||||||
type: "Shape", |
|
||||||
expression: { |
|
||||||
type: "EachOf", |
|
||||||
expressions: [ |
|
||||||
{ |
|
||||||
type: "TripleConstraint", |
|
||||||
predicate: "http://example.org/street", |
|
||||||
valueExpr: { |
|
||||||
type: "NodeConstraint", |
|
||||||
datatype: "http://www.w3.org/2001/XMLSchema#string", |
|
||||||
}, |
|
||||||
readablePredicate: "street", |
|
||||||
}, |
|
||||||
{ |
|
||||||
type: "TripleConstraint", |
|
||||||
predicate: "http://example.org/houseNumber", |
|
||||||
valueExpr: { |
|
||||||
type: "NodeConstraint", |
|
||||||
datatype: "http://www.w3.org/2001/XMLSchema#string", |
|
||||||
}, |
|
||||||
readablePredicate: "houseNumber", |
|
||||||
}, |
|
||||||
{ |
|
||||||
type: "TripleConstraint", |
|
||||||
predicate: "http://example.org/floor", |
|
||||||
valueExpr: { |
|
||||||
type: "NodeConstraint", |
|
||||||
datatype: "http://www.w3.org/2001/XMLSchema#integer", |
|
||||||
}, |
|
||||||
readablePredicate: "floor", |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
}, |
|
||||||
readablePredicate: "address", |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
}, |
|
||||||
}, |
|
||||||
], |
|
||||||
}; |
|
@ -0,0 +1,70 @@ |
|||||||
|
import type { CompactSchema } from "@ldo/ldo"; |
||||||
|
|
||||||
|
/** |
||||||
|
* ============================================================================= |
||||||
|
* personShapeSchema: Compact Schema for personShape |
||||||
|
* ============================================================================= |
||||||
|
*/ |
||||||
|
export const personShapeSchema: CompactSchema = { |
||||||
|
"http://example.org/Person": { |
||||||
|
schemaUri: "http://example.org/Person", |
||||||
|
predicates: [ |
||||||
|
{ |
||||||
|
type: "literal", |
||||||
|
literalValue: ["Person"], |
||||||
|
maxCardinality: 1, |
||||||
|
minCardinality: 1, |
||||||
|
predicateUri: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", |
||||||
|
readablePredicate: "type", |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "string", |
||||||
|
maxCardinality: 1, |
||||||
|
minCardinality: 1, |
||||||
|
predicateUri: "http://example.org/name", |
||||||
|
readablePredicate: "name", |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "nested", |
||||||
|
nestedSchema: "http://example.org/Person::http://example.org/address", |
||||||
|
maxCardinality: 1, |
||||||
|
minCardinality: 1, |
||||||
|
predicateUri: "http://example.org/address", |
||||||
|
readablePredicate: "address", |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "boolean", |
||||||
|
maxCardinality: 1, |
||||||
|
minCardinality: 1, |
||||||
|
predicateUri: "http://example.org/hasChildren", |
||||||
|
readablePredicate: "hasChildren", |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "number", |
||||||
|
maxCardinality: 1, |
||||||
|
minCardinality: 1, |
||||||
|
predicateUri: "http://example.org/numberOfHouses", |
||||||
|
readablePredicate: "numberOfHouses", |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
"http://example.org/Person::http://example.org/address": { |
||||||
|
schemaUri: "http://example.org/Person::http://example.org/address", |
||||||
|
predicates: [ |
||||||
|
{ |
||||||
|
type: "string", |
||||||
|
maxCardinality: 1, |
||||||
|
minCardinality: 1, |
||||||
|
predicateUri: "http://example.org/street", |
||||||
|
readablePredicate: "street", |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "string", |
||||||
|
maxCardinality: 1, |
||||||
|
minCardinality: 1, |
||||||
|
predicateUri: "http://example.org/houseNumber", |
||||||
|
readablePredicate: "houseNumber", |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
}; |
@ -1,95 +0,0 @@ |
|||||||
import type { Schema } from "shexj"; |
|
||||||
|
|
||||||
/** |
|
||||||
* ============================================================================= |
|
||||||
* personShapeSchema: ShexJ Schema for personShape |
|
||||||
* ============================================================================= |
|
||||||
*/ |
|
||||||
export const personShapeSchema: Schema = { |
|
||||||
type: "Schema", |
|
||||||
shapes: [ |
|
||||||
{ |
|
||||||
id: "http://example.org/Person", |
|
||||||
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: [ |
|
||||||
{ |
|
||||||
value: "Person", |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
readablePredicate: "type", |
|
||||||
}, |
|
||||||
{ |
|
||||||
type: "TripleConstraint", |
|
||||||
predicate: "http://example.org/name", |
|
||||||
valueExpr: { |
|
||||||
type: "NodeConstraint", |
|
||||||
datatype: "http://www.w3.org/2001/XMLSchema#string", |
|
||||||
}, |
|
||||||
readablePredicate: "name", |
|
||||||
}, |
|
||||||
{ |
|
||||||
type: "TripleConstraint", |
|
||||||
predicate: "http://example.org/address", |
|
||||||
valueExpr: { |
|
||||||
type: "Shape", |
|
||||||
expression: { |
|
||||||
type: "EachOf", |
|
||||||
expressions: [ |
|
||||||
{ |
|
||||||
type: "TripleConstraint", |
|
||||||
predicate: "http://example.org/street", |
|
||||||
valueExpr: { |
|
||||||
type: "NodeConstraint", |
|
||||||
datatype: "http://www.w3.org/2001/XMLSchema#string", |
|
||||||
}, |
|
||||||
readablePredicate: "street", |
|
||||||
}, |
|
||||||
{ |
|
||||||
type: "TripleConstraint", |
|
||||||
predicate: "http://example.org/houseNumber", |
|
||||||
valueExpr: { |
|
||||||
type: "NodeConstraint", |
|
||||||
datatype: "http://www.w3.org/2001/XMLSchema#string", |
|
||||||
}, |
|
||||||
readablePredicate: "houseNumber", |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
}, |
|
||||||
readablePredicate: "address", |
|
||||||
}, |
|
||||||
{ |
|
||||||
type: "TripleConstraint", |
|
||||||
predicate: "http://example.org/hasChildren", |
|
||||||
valueExpr: { |
|
||||||
type: "NodeConstraint", |
|
||||||
datatype: "http://www.w3.org/2001/XMLSchema#boolean", |
|
||||||
}, |
|
||||||
readablePredicate: "hasChildren", |
|
||||||
}, |
|
||||||
{ |
|
||||||
type: "TripleConstraint", |
|
||||||
predicate: "http://example.org/numberOfHouses", |
|
||||||
valueExpr: { |
|
||||||
type: "NodeConstraint", |
|
||||||
datatype: "http://www.w3.org/2001/XMLSchema#integer", |
|
||||||
}, |
|
||||||
readablePredicate: "numberOfHouses", |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
}, |
|
||||||
}, |
|
||||||
], |
|
||||||
}; |
|
@ -0,0 +1,114 @@ |
|||||||
|
import type { CompactSchema } from "@ldo/ldo"; |
||||||
|
|
||||||
|
/** |
||||||
|
* ============================================================================= |
||||||
|
* testShapeSchema: Compact Schema for testShape |
||||||
|
* ============================================================================= |
||||||
|
*/ |
||||||
|
export const testShapeSchema: CompactSchema = { |
||||||
|
"http://example.org/TestObject": { |
||||||
|
schemaUri: "http://example.org/TestObject", |
||||||
|
predicates: [ |
||||||
|
{ |
||||||
|
type: "literal", |
||||||
|
literalValue: ["TestObject"], |
||||||
|
maxCardinality: 1, |
||||||
|
minCardinality: 1, |
||||||
|
predicateUri: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", |
||||||
|
readablePredicate: "type", |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "string", |
||||||
|
maxCardinality: 1, |
||||||
|
minCardinality: 1, |
||||||
|
predicateUri: "http://example.org/stringValue", |
||||||
|
readablePredicate: "stringValue", |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "number", |
||||||
|
maxCardinality: 1, |
||||||
|
minCardinality: 1, |
||||||
|
predicateUri: "http://example.org/numValue", |
||||||
|
readablePredicate: "numValue", |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "boolean", |
||||||
|
maxCardinality: 1, |
||||||
|
minCardinality: 1, |
||||||
|
predicateUri: "http://example.org/boolValue", |
||||||
|
readablePredicate: "boolValue", |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "number", |
||||||
|
maxCardinality: -1, |
||||||
|
minCardinality: 0, |
||||||
|
predicateUri: "http://example.org/arrayValue", |
||||||
|
readablePredicate: "arrayValue", |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "nested", |
||||||
|
nestedSchema: |
||||||
|
"http://example.org/TestObject::http://example.org/objectValue", |
||||||
|
maxCardinality: 1, |
||||||
|
minCardinality: 1, |
||||||
|
predicateUri: "http://example.org/objectValue", |
||||||
|
readablePredicate: "objectValue", |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "nested", |
||||||
|
nestedSchema: |
||||||
|
"http://example.org/TestObject::http://example.org/anotherObject", |
||||||
|
maxCardinality: -1, |
||||||
|
minCardinality: 0, |
||||||
|
predicateUri: "http://example.org/anotherObject", |
||||||
|
readablePredicate: "anotherObject", |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
"http://example.org/TestObject::http://example.org/objectValue": { |
||||||
|
schemaUri: "http://example.org/TestObject::http://example.org/objectValue", |
||||||
|
predicates: [ |
||||||
|
{ |
||||||
|
type: "string", |
||||||
|
maxCardinality: 1, |
||||||
|
minCardinality: 1, |
||||||
|
predicateUri: "http://example.org/nestedString", |
||||||
|
readablePredicate: "nestedString", |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "number", |
||||||
|
maxCardinality: 1, |
||||||
|
minCardinality: 1, |
||||||
|
predicateUri: "http://example.org/nestedNum", |
||||||
|
readablePredicate: "nestedNum", |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "number", |
||||||
|
maxCardinality: -1, |
||||||
|
minCardinality: 0, |
||||||
|
predicateUri: "http://example.org/nestedArray", |
||||||
|
readablePredicate: "nestedArray", |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
"http://example.org/TestObject::http://example.org/anotherObject": { |
||||||
|
schemaUri: |
||||||
|
"http://example.org/TestObject::http://example.org/anotherObject", |
||||||
|
predicates: [ |
||||||
|
{ |
||||||
|
type: "string", |
||||||
|
maxCardinality: 1, |
||||||
|
minCardinality: 1, |
||||||
|
predicateUri: "http://example.org/prop1", |
||||||
|
readablePredicate: "prop1", |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "number", |
||||||
|
maxCardinality: 1, |
||||||
|
minCardinality: 1, |
||||||
|
predicateUri: "http://example.org/prop2", |
||||||
|
readablePredicate: "prop2", |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
}; |
@ -1,150 +0,0 @@ |
|||||||
import type { Schema } from "shexj"; |
|
||||||
|
|
||||||
/** |
|
||||||
* ============================================================================= |
|
||||||
* testShapeSchema: ShexJ Schema for testShape |
|
||||||
* ============================================================================= |
|
||||||
*/ |
|
||||||
export const testShapeSchema: Schema = { |
|
||||||
type: "Schema", |
|
||||||
shapes: [ |
|
||||||
{ |
|
||||||
id: "http://example.org/TestObject", |
|
||||||
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: [ |
|
||||||
{ |
|
||||||
value: "TestObject", |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
readablePredicate: "type", |
|
||||||
}, |
|
||||||
{ |
|
||||||
type: "TripleConstraint", |
|
||||||
predicate: "http://example.org/stringValue", |
|
||||||
valueExpr: { |
|
||||||
type: "NodeConstraint", |
|
||||||
datatype: "http://www.w3.org/2001/XMLSchema#string", |
|
||||||
}, |
|
||||||
readablePredicate: "stringValue", |
|
||||||
}, |
|
||||||
{ |
|
||||||
type: "TripleConstraint", |
|
||||||
predicate: "http://example.org/numValue", |
|
||||||
valueExpr: { |
|
||||||
type: "NodeConstraint", |
|
||||||
datatype: "http://www.w3.org/2001/XMLSchema#integer", |
|
||||||
}, |
|
||||||
readablePredicate: "numValue", |
|
||||||
}, |
|
||||||
{ |
|
||||||
type: "TripleConstraint", |
|
||||||
predicate: "http://example.org/boolValue", |
|
||||||
valueExpr: { |
|
||||||
type: "NodeConstraint", |
|
||||||
datatype: "http://www.w3.org/2001/XMLSchema#boolean", |
|
||||||
}, |
|
||||||
readablePredicate: "boolValue", |
|
||||||
}, |
|
||||||
{ |
|
||||||
type: "TripleConstraint", |
|
||||||
predicate: "http://example.org/arrayValue", |
|
||||||
valueExpr: { |
|
||||||
type: "NodeConstraint", |
|
||||||
datatype: "http://www.w3.org/2001/XMLSchema#integer", |
|
||||||
}, |
|
||||||
min: 0, |
|
||||||
max: -1, |
|
||||||
readablePredicate: "arrayValue", |
|
||||||
}, |
|
||||||
{ |
|
||||||
type: "TripleConstraint", |
|
||||||
predicate: "http://example.org/objectValue", |
|
||||||
valueExpr: { |
|
||||||
type: "Shape", |
|
||||||
expression: { |
|
||||||
type: "EachOf", |
|
||||||
expressions: [ |
|
||||||
{ |
|
||||||
type: "TripleConstraint", |
|
||||||
predicate: "http://example.org/nestedString", |
|
||||||
valueExpr: { |
|
||||||
type: "NodeConstraint", |
|
||||||
datatype: "http://www.w3.org/2001/XMLSchema#string", |
|
||||||
}, |
|
||||||
readablePredicate: "nestedString", |
|
||||||
}, |
|
||||||
{ |
|
||||||
type: "TripleConstraint", |
|
||||||
predicate: "http://example.org/nestedNum", |
|
||||||
valueExpr: { |
|
||||||
type: "NodeConstraint", |
|
||||||
datatype: "http://www.w3.org/2001/XMLSchema#integer", |
|
||||||
}, |
|
||||||
readablePredicate: "nestedNum", |
|
||||||
}, |
|
||||||
{ |
|
||||||
type: "TripleConstraint", |
|
||||||
predicate: "http://example.org/nestedArray", |
|
||||||
valueExpr: { |
|
||||||
type: "NodeConstraint", |
|
||||||
datatype: "http://www.w3.org/2001/XMLSchema#integer", |
|
||||||
}, |
|
||||||
min: 0, |
|
||||||
max: -1, |
|
||||||
readablePredicate: "nestedArray", |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
}, |
|
||||||
readablePredicate: "objectValue", |
|
||||||
}, |
|
||||||
{ |
|
||||||
type: "TripleConstraint", |
|
||||||
predicate: "http://example.org/anotherObject", |
|
||||||
valueExpr: { |
|
||||||
type: "Shape", |
|
||||||
expression: { |
|
||||||
type: "EachOf", |
|
||||||
expressions: [ |
|
||||||
{ |
|
||||||
type: "TripleConstraint", |
|
||||||
predicate: "http://example.org/prop1", |
|
||||||
valueExpr: { |
|
||||||
type: "NodeConstraint", |
|
||||||
datatype: "http://www.w3.org/2001/XMLSchema#string", |
|
||||||
}, |
|
||||||
readablePredicate: "prop1", |
|
||||||
}, |
|
||||||
{ |
|
||||||
type: "TripleConstraint", |
|
||||||
predicate: "http://example.org/prop2", |
|
||||||
valueExpr: { |
|
||||||
type: "NodeConstraint", |
|
||||||
datatype: "http://www.w3.org/2001/XMLSchema#integer", |
|
||||||
}, |
|
||||||
readablePredicate: "prop2", |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
}, |
|
||||||
min: 0, |
|
||||||
max: -1, |
|
||||||
readablePredicate: "anotherObject", |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
}, |
|
||||||
}, |
|
||||||
], |
|
||||||
}; |
|
Loading…
Reference in new issue