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