Evaluation of signal-libraries and their integration in frontend-frameworks with nested objects using js proxies.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

125 lines
3.6 KiB

/**
* 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;
}