diff --git a/builder.ts b/builder.ts index 5fd4189..aa01d64 100644 --- a/builder.ts +++ b/builder.ts @@ -24,16 +24,19 @@ */ import { - raw, + rawPattern, validatePrefixName, validateIRI, - toRawString, isSparqlValue, + toRawString, toVarToken, toVarOrIriRef, toGraphRef, type SparqlValue, type VariableName, + type PatternValue, + type SparqlTerm, + SparqlExpr, } from './sparql.ts' import { createExecutor, type BindingMap, type ExecutionConfig, type QueryResult } from './executor.ts' import { bind, filter, optional } from './utils.ts' @@ -45,7 +48,7 @@ import { bind, filter, optional } from './utils.ts' /** * Pattern-like input for WHERE/OPTIONAL/UNION clauses. */ -export type PatternLike = string | SparqlValue +export type PatternLike = string | SparqlExpr | SparqlTerm /** * Variables to select in query results. @@ -180,7 +183,7 @@ export class QueryBuilder { /** * Start a DESCRIBE query. */ - static describe(resources: (string | SparqlValue)[]): QueryBuilder { + static describe(resources: PatternLike[]): QueryBuilder { return new QueryBuilder({ ...initialState, type: 'DESCRIBE', @@ -415,7 +418,7 @@ export class QueryBuilder { * @param varName - Variable name (validated) * @param vals - Values to match against (should be SparqlValue objects) */ - values(varName: VariableName, vals: SparqlValue[]): QueryBuilder { + values(varName: VariableName, vals: SparqlTerm[]): QueryBuilder { const name = toVarToken(varName) const valuesMap = new Map(this.state.values || []) valuesMap.set(name, vals) @@ -429,8 +432,8 @@ export class QueryBuilder { /** * Wrap this query as a subquery for nesting. */ - asSubquery(): SparqlValue { - return raw(`{ ${this.build().value} }`) + asSubquery(): PatternValue { + return rawPattern(`{ ${this.build().value} }`) } // -------------------------------------------------------------------------- @@ -440,7 +443,7 @@ export class QueryBuilder { /** * Build the final SPARQL query string. */ - build(): SparqlValue { + build(): PatternValue { const parts: string[] = [] // PREFIX declarations @@ -581,7 +584,7 @@ export class QueryBuilder { parts.push(`OFFSET ${this.state.offset}`) } - return raw(parts.join('\n')) + return rawPattern(parts.join('\n')) } /** diff --git a/examples/basic.ts b/examples/basic.ts index 524c3f6..9bcfa25 100644 --- a/examples/basic.ts +++ b/examples/basic.ts @@ -6,8 +6,6 @@ import { node, select, variable, - regex, - v, type ExecutionConfig, } from '../mod.ts' diff --git a/patterns/cypher.ts b/patterns/cypher.ts index 7682873..892dc4e 100644 --- a/patterns/cypher.ts +++ b/patterns/cypher.ts @@ -15,7 +15,7 @@ * @module */ -import { raw, toPredicateName, type SparqlValue } from '../sparql.ts' +import { rawPattern, toPredicateName, type SparqlTerm, type PatternValue, } from '../sparql.ts' import { Node } from './objects.ts' /** @@ -77,8 +77,8 @@ import { Node } from './objects.ts' */ export function cypher( strings: TemplateStringsArray, - ...values: Array -): SparqlValue { + ...values: Array +): PatternValue { let result = strings[0] const nodes: Node[] = [] @@ -118,5 +118,5 @@ export function cypher( triples.push(`${fromVar} ${predicate} ${toVar} .`) } - return raw(`${triples.join('\n')}`) + return rawPattern(`${triples.join('\n')}`) } \ No newline at end of file diff --git a/patterns/objects.ts b/patterns/objects.ts index 2fb1431..b3ffa69 100644 --- a/patterns/objects.ts +++ b/patterns/objects.ts @@ -23,7 +23,12 @@ import { raw, variable, SPARQL_VALUE_BRAND, + SPARQL_PATTERN_BRAND, type SparqlValue, + type SparqlTerm, + type PatternValue, + rawTerm, + rawPattern, } from '../sparql.ts' import { exprTermString } from '../utils.ts' @@ -185,9 +190,11 @@ export interface NodePropertyMap { * ) * ``` */ -export class Node implements SparqlValue { - readonly [SPARQL_VALUE_BRAND] = true - readonly subjectTerm: SparqlValue +export class Node implements PatternValue { + readonly [SPARQL_VALUE_BRAND] = true as const + readonly [SPARQL_PATTERN_BRAND] = true as const + + readonly subjectTerm: SparqlTerm private readonly varName: string private readonly typesTerm: TriplePredicate[] = [] private readonly properties: NodePropertyMap = {} @@ -230,7 +237,7 @@ export class Node implements SparqlValue { * Use this when you need to reference the node as an object in another triple. * For example, when connecting two nodes with a relationship. */ - term(): SparqlValue { + term(): SparqlTerm { return this.subjectTerm } @@ -295,7 +302,7 @@ export class Node implements SparqlValue { * node('product').prop('schema:publisher', node('publisher', 'schema:Organization')) * ``` */ - prop(predicate: string | SparqlValue, value: TripleObject | TripleObject[]): this { + prop(predicate: TriplePredicate, value: PropertyValue): this { const key = typeof predicate === 'string' ? predicate : predicate.value const existing = this.properties[key] @@ -332,7 +339,7 @@ export class Node implements SparqlValue { * }) * ``` */ - props(map: Record): this { + props(map: NodePropertyMap): this { for (const [key, value] of Object.entries(map)) { this.prop(key, value) } @@ -358,7 +365,7 @@ export class Node implements SparqlValue { // Add rdf:type triples if (this.typesTerm.length > 0) { const typeObjs: TripleObject[] = this.typesTerm.map((t) => - typeof t === 'string' ? raw(t) : t.value, + typeof t === 'string' ? rawTerm(t) : t.value, ) const existing = @@ -428,10 +435,10 @@ export class Node implements SparqlValue { * * Call this to get the complete pattern including all nested nodes. */ - pattern(): SparqlValue { + pattern(): PatternValue { const visited = new Set() const text = this.buildPatternInternal(visited) - return raw(text) + return rawPattern(text) } /** @@ -578,8 +585,10 @@ export interface RelationshipPropertyMap { * .where(friend2Rel) * ``` */ -export class Relationship implements SparqlValue { - readonly [SPARQL_VALUE_BRAND] = true +export class Relationship implements PatternValue { + readonly [SPARQL_VALUE_BRAND] = true as const + readonly [SPARQL_PATTERN_BRAND] = true as const + private readonly fromNode?: Node private readonly toNode?: Node private readonly fromTerm: TripleSubject @@ -595,7 +604,7 @@ export class Relationship implements SparqlValue { constructor( from: Node | TripleSubject, predicate: TriplePredicate, - to: Node | TripleSubject, + to: Node | string | SparqlTerm, ) { if (from instanceof Node) { this.fromNode = from @@ -634,7 +643,7 @@ export class Relationship implements SparqlValue { * rel('person', 'knows', 'friend').prop('timestamp', dateTime(new Date())) * ``` */ - prop(predicate: string | SparqlValue, value: TripleObject | TripleObject[]): this { + prop(predicate: TriplePredicate, value: PropertyValue): this { const key = typeof predicate === 'string' ? predicate : predicate.value const existing = this.properties[key] @@ -652,6 +661,19 @@ export class Relationship implements SparqlValue { return this } + /** + * Add multiple properties at once. + * + * Convenient when you have several properties to set. Just pass an object + * where keys are predicates and values are objects. + */ + props(map: RelationshipPropertyMap): this { + for (const [key, value] of Object.entries(map)) { + this.prop(key, value) + } + return this + } + /** * Generate deterministic ID for reified edge. * @@ -679,20 +701,20 @@ export class Relationship implements SparqlValue { // Reify with properties const edgeId = this.getEdgeId() - const poMap: RelationshipPropertyMap = { + const poMap: PredicateObjectMap = { // `a` = `rdf:type` - 'a': raw('rdf:Statement'), + 'a': rawTerm('rdf:Statement'), 'rdf:subject': this.fromTerm, 'rdf:predicate': typeof this.predicate === 'string' - ? raw(this.predicate) + ? rawTerm(this.predicate) : this.predicate, 'rdf:object': this.toTerm, ...this.properties, } - const edgeTriples = triples(raw(edgeId), poMap) + const edgeTriples = triples(rawTerm(edgeId), poMap) - return raw(`${base.value}\n ${edgeTriples.value}`) + return rawPattern(`${base.value}\n ${edgeTriples.value}`) } get value(): string { diff --git a/patterns/triples.ts b/patterns/triples.ts index a6ec053..1f5e862 100644 --- a/patterns/triples.ts +++ b/patterns/triples.ts @@ -11,9 +11,9 @@ * @module */ -import { isSparqlValue, toVarToken } from '../sparql.ts' -import { raw, type VariableName, type SparqlValue, toPredicateName, toRawString } from '../sparql.ts' -import { exprTermString, termString, type ExpressionPrimitive } from '../utils.ts' +import { isSparqlValue, toVarToken, type SparqlExpr, type SparqlTerm } from '../sparql.ts' +import { raw, toPredicateName, toRawString, type SparqlValue, } from '../sparql.ts' +import { termString, type ExpressionPrimitive } from '../utils.ts' // ============================================================================ // Triple Component Types @@ -25,7 +25,7 @@ import { exprTermString, termString, type ExpressionPrimitive } from '../utils.t * Can be a variable (?person), an IRI (), or a blank node. * Most often you'll use variables to match multiple resources. */ -export type TripleSubject = VariableName | SparqlValue +export type TripleSubject = string | SparqlTerm /** * Predicate of a triple pattern. @@ -33,7 +33,7 @@ export type TripleSubject = VariableName | SparqlValue * Can be a prefixed name (foaf:name), full IRI, or variable. Predicates * describe relationships or properties. */ -export type TriplePredicate = string | SparqlValue +export type TriplePredicate = string | SparqlTerm /** * Values that are allowed in the object position of a triple, per SPARQL. @@ -48,8 +48,7 @@ export type TriplePredicate = string | SparqlValue * lists `[ ... ]` via additional SparqlValue kinds.) */ export type TripleObject = - | VariableName - | SparqlValue // but only certain `kind`s, enforced at runtime + | SparqlTerm // but only certain `kind`s, enforced at runtime | ExpressionPrimitive /** @@ -110,7 +109,7 @@ export function triple( subject: TripleSubject, predicate: TriplePredicate, object: TripleObject, -): SparqlValue { +): SparqlExpr { const s = tripleSubjectString(subject) const p = toPredicateName(toRawString(predicate)) const o = tripleObjectString(object) @@ -184,7 +183,7 @@ export type PredicateObjectMap = Record< export function triples( subject: TripleSubject, predicateObjects: PredicateObjectList | PredicateObjectMap, -): SparqlValue { +): SparqlExpr { const subjectTerm = tripleSubjectString(subject) // 4 spaces; 2 (block) + 2 (extra) @@ -274,10 +273,10 @@ export function triples( * ``` */ export function quotedTriple( - subject: string | SparqlValue, - predicate: string | SparqlValue, - object: SparqlValue | ExpressionPrimitive -): SparqlValue { + subject: TripleSubject, + predicate: TriplePredicate, + object: TripleObject, +): SparqlExpr { const s = tripleSubjectString(subject) const p = toPredicateName(toRawString(predicate)) const o = tripleObjectString(object) diff --git a/sparql.ts b/sparql.ts index 755af24..5b9f6a9 100644 --- a/sparql.ts +++ b/sparql.ts @@ -48,19 +48,34 @@ import { outdent } from "outdent" * Internal brand used to distinguish SPARQL values from plain strings. */ export const SPARQL_VALUE_BRAND = Symbol('SparqlValueBrand') +export const SPARQL_TERM_BRAND = Symbol('SparqlTermBrand') +export const SPARQL_EXPR_BRAND = Symbol('SparqlExprBrand') +export const SPARQL_PATTERN_BRAND = Symbol('SparqlPatternBrand') + +export interface SparqlTerm { + readonly [SPARQL_VALUE_BRAND]: true + readonly [SPARQL_TERM_BRAND]: true + readonly value: string +} + +export interface SparqlExpr { + readonly [SPARQL_VALUE_BRAND]: true + readonly [SPARQL_EXPR_BRAND]: true + readonly value: string +} /** - * A SPARQL snippet that is already syntactically valid and should be used - * verbatim in the final query. - * - * This is the "wrapped" representation returned by helpers like `strlit`, - * `num`, `boolean`, `dateTime`, `bnode`, `valuesList`, etc. + * A graph pattern snippet – e.g. a block of triples to drop into WHERE {}. + * Node, Relationship, and other pattern builders should be this. */ -export interface SparqlValue { +export interface PatternValue { readonly [SPARQL_VALUE_BRAND]: true + readonly [SPARQL_PATTERN_BRAND]: true readonly value: string } +export type SparqlValue = SparqlTerm | SparqlExpr | PatternValue + /** * Values that can be safely interpolated into the `sparql` tag *as a single * RDF term*. This deliberately does NOT include arrays or plain objects. @@ -84,7 +99,7 @@ export type SparqlInterpolatable = * In SPARQL, variables can be written as ?name or $name. We normalize these * internally to just store the name part, then add the ? when generating queries. */ -export type VariableName = string | `?${string}` | SparqlValue +export type VariableName = string | `?${string}` | SparqlTerm /** * Namespace prefix for abbreviated IRIs (e.g., "foaf" in foaf:name). @@ -115,6 +130,19 @@ export function isSparqlValue(value: unknown): value is SparqlValue { (value as SparqlValue)[SPARQL_VALUE_BRAND] === true ) } + +export function isSparqlTerm(v: SparqlValue): v is SparqlTerm { + return (v as SparqlTerm)[SPARQL_TERM_BRAND] === true +} + +export function isSparqlExpr(v: SparqlValue): v is SparqlExpr { + return (v as SparqlExpr)[SPARQL_EXPR_BRAND] === true +} + +export function isPatternValue(v: SparqlValue): v is PatternValue { + return (v as PatternValue)[SPARQL_PATTERN_BRAND] === true +} + /** * Extract the raw string from a SparqlValue or return the string as-is. * @@ -383,6 +411,30 @@ export function parseVarOrIriRef( } } +export function rawTerm(value: string): SparqlTerm { + return { + [SPARQL_VALUE_BRAND]: true, + [SPARQL_TERM_BRAND]: true, + value, + } as const +} + +export function rawExpr(value: string): SparqlExpr { + return { + [SPARQL_VALUE_BRAND]: true, + [SPARQL_EXPR_BRAND]: true, + value, + } as const +} + +export function rawPattern(text: string): PatternValue { + return { + [SPARQL_VALUE_BRAND]: true, + [SPARQL_PATTERN_BRAND]: true, + value: text, + } as const +} + /** * Wrap a raw SPARQL snippet as a `SparqlValue`. * @@ -403,11 +455,8 @@ export function parseVarOrIriRef( * raw('BNODE()') // Built-in function * raw('ex:customFunc(?x, ?y)') // Custom function */ -export function raw(value: string): SparqlValue { - return { - [SPARQL_VALUE_BRAND]: true, - value, - } +export function raw(value: string): SparqlExpr { + return rawExpr(value) } // ============================================================================ @@ -729,10 +778,10 @@ export function normalizeVariableName(name: VariableName): string { * variable('name') // → ?name * variable('?name') // → ?name */ -export function variable(name: VariableName): SparqlValue { +export function variable(name: VariableName): SparqlTerm { const n = normalizeVariableName(name) validateVariableName(n) - return raw(`?${n}`) + return rawTerm(`?${n}`) } /** @@ -746,9 +795,9 @@ export function variable(name: VariableName): SparqlValue { * uri('urn:isbn:0451450523') // → */ -export function uri(iri: string): SparqlValue { +export function uri(iri: string): SparqlTerm { validateIRI(iri) - return raw(`<${iri}>`) + return rawTerm(`<${iri}>`) } /** @@ -759,7 +808,7 @@ export function uri(iri: string): SparqlValue { * @example * prefixed('foaf', 'name') // → foaf:name */ -export function prefixed(prefix: PrefixName, localName: string): SparqlValue { +export function prefixed(prefix: PrefixName, localName: string): SparqlExpr { validatePrefixName(prefix) // Local names have complex rules; block obvious injection if (INJECTION_CHARS.test(localName)) { @@ -771,7 +820,7 @@ export function prefixed(prefix: PrefixName, localName: string): SparqlValue { /** * Alias for {@link prefixed} with more explicit naming. */ -export function prefix(namespace: PrefixName, local: string): SparqlValue { +export function prefix(namespace: PrefixName, local: string): SparqlExpr { return prefixed(namespace, local) } @@ -788,14 +837,14 @@ export function prefix(namespace: PrefixName, local: string): SparqlValue { * strlit('Hello') // → "Hello" * strlit('Line 1\nLine 2') // → """Line 1\nLine 2""" */ -export function strlit(value: string): SparqlValue { +export function strlit(value: string): SparqlTerm { const escaped = escapeString(value) if (needsLongQuotes(value)) { - return raw(`"""${escaped}"""`) + return rawTerm(`"""${escaped}"""`) } - return raw(`"${escaped}"`) + return rawTerm(`"${escaped}"`) } /** @@ -805,15 +854,15 @@ export function strlit(value: string): SparqlValue { * typed('42', 'http://www.w3.org/2001/XMLSchema#integer') * // → "42"^^ */ -export function typed(value: string, datatype: DatatypeIRI): SparqlValue { +export function typed(value: string, datatype: DatatypeIRI): SparqlTerm { validateIRI(datatype) const escaped = escapeString(value) if (needsLongQuotes(value)) { - return raw(`"""${escaped}"""^^<${datatype}>`) + return rawTerm(`"""${escaped}"""^^<${datatype}>`) } - return raw(`"${escaped}"^^<${datatype}>`) + return rawTerm(`"${escaped}"^^<${datatype}>`) } /** @@ -825,15 +874,15 @@ export function typed(value: string, datatype: DatatypeIRI): SparqlValue { * @example lang('Hello', 'en') → "Hello"@en * @example lang('Bonjour', 'fr') → "Bonjour"@fr */ -export function lang(value: string, tag: LanguageTag): SparqlValue { +export function lang(value: string, tag: LanguageTag): SparqlTerm { validateLanguageTag(tag) const escaped = escapeString(value) if (needsLongQuotes(value)) { - return raw(`"""${escaped}"""@${tag.toLowerCase()}`) + return rawTerm(`"""${escaped}"""@${tag.toLowerCase()}`) } - return raw(`"${escaped}"@${tag.toLowerCase()}`) + return rawTerm(`"${escaped}"@${tag.toLowerCase()}`) } /** @@ -846,13 +895,13 @@ export function lang(value: string, tag: LanguageTag): SparqlValue { * * @throws {Error} If value is not an integer */ -export function integer(value: number): SparqlValue { +export function integer(value: number): SparqlTerm { if (!Number.isInteger(value)) { throw new Error(`Expected integer, got: ${value}`) } // Use SPARQL native integer syntax - return raw(String(value)) + return rawTerm(String(value)) } /** @@ -866,7 +915,7 @@ export function integer(value: number): SparqlValue { * * @throws {Error} If value is not finite (NaN or Infinity) */ -export function decimal(value: number): SparqlValue { +export function decimal(value: number): SparqlTerm { if (!Number.isFinite(value)) { throw new Error(`Expected finite number, got: ${value}`) } @@ -874,9 +923,9 @@ export function decimal(value: number): SparqlValue { const str = String(value) // Ensure decimal point for unambiguous decimal type if (!str.includes('.') && !str.includes('e') && !str.includes('E')) { - return raw(str + '.0') + return rawTerm(str + '.0') } - return raw(str) + return rawTerm(str) } /** @@ -890,12 +939,12 @@ export function decimal(value: number): SparqlValue { * * @throws {Error} If value is not an double */ -export function double(value: number): SparqlValue { +export function double(value: number): SparqlTerm { if (!Number.isFinite(value)) { throw new Error(`Expected finite number, got: ${value}`) } // Scientific notation triggers xsd:double - return raw(value.toExponential()) + return rawTerm(value.toExponential()) } /** @@ -908,7 +957,7 @@ export function double(value: number): SparqlValue { * num(42) // → 42 * num(3.14) // → 3.14 */ -export function num(value: number): SparqlValue { +export function num(value: number): SparqlTerm { if (Number.isInteger(value)) { return integer(value) } @@ -921,14 +970,14 @@ export function num(value: number): SparqlValue { * * Boolean values in SPARQL are written as bare keywords, not quoted strings. */ -export function boolean(value: boolean): SparqlValue { - return raw(value ? 'true' : 'false') +export function boolean(value: boolean): SparqlTerm { + return rawTerm(value ? 'true' : 'false') } /** * Short alias for {@link boolean}. */ -export function bool(value: boolean): SparqlValue { +export function bool(value: boolean): SparqlTerm { return boolean(value) } @@ -938,12 +987,12 @@ export function bool(value: boolean): SparqlValue { * @example * date(new Date('2024-01-15')) // → "2024-01-15"^^ */ -export function date(value: Date | string): SparqlValue { +export function date(value: Date | string): SparqlTerm { const dateObj = value instanceof Date ? value : new Date(value) const yyyy = dateObj.getFullYear() const mm = String(dateObj.getMonth() + 1).padStart(2, '0') const dd = String(dateObj.getDate()).padStart(2, '0') - return raw(`"${yyyy}-${mm}-${dd}"^^`) + return rawTerm(`"${yyyy}-${mm}-${dd}"^^`) } /** @@ -952,9 +1001,9 @@ export function date(value: Date | string): SparqlValue { * @example * dateTime(new Date()) // → "2024-01-15T10:30:00.000Z"^^ */ -export function dateTime(value: Date | string): SparqlValue { +export function dateTime(value: Date | string): SparqlTerm { const dateObj = value instanceof Date ? value : new Date(value) - return raw(`"${dateObj.toISOString()}"^^`) + return rawTerm(`"${dateObj.toISOString()}"^^`) } // ============================================================================ @@ -1176,7 +1225,7 @@ export function rdfList( throw new Error('Cannot create RDF list from an empty iterable') } - return raw(`( ${parts.join(' ')} )`) + return rawTerm(`( ${parts.join(' ')} )`) } // ============================================================================ @@ -1214,11 +1263,11 @@ export function rdfList( * // Fresh blank nodes for each result row. * ``` */ -export function bnode(id?: string): SparqlValue { +export function bnode(id?: string): SparqlTerm { if (id) { - return raw(`_:${id}`) + return rawTerm(`_:${id}`) } - return raw('BNODE()') + return rawTerm('[]') } /** @@ -1341,7 +1390,7 @@ export function toPredicateName(key: string): string { * // ] * ``` */ -export function bnodePattern(props: BnodeProps): SparqlValue { +export function bnodePattern(props: BnodeProps): SparqlTerm { const entries = Object.entries(props) if (entries.length === 0) { @@ -1394,7 +1443,7 @@ export function bnodePattern(props: BnodeProps): SparqlValue { ) } - return raw(`[ ${propertyFragments.join(' ; ')} ]`) + return rawTerm(`[ ${propertyFragments.join(' ; ')} ]`) } // ============================================================================ diff --git a/update.ts b/update.ts index ab37bb6..fab3300 100644 --- a/update.ts +++ b/update.ts @@ -34,7 +34,7 @@ * @module */ -import { raw, sparql, SPARQL_VALUE_BRAND, toGraphRef, toGraphRefAll, toVarOrIriRef, type SparqlValue } from './sparql.ts' +import { raw, sparql, SPARQL_EXPR_BRAND, SPARQL_VALUE_BRAND, toGraphRef, toGraphRefAll, toVarOrIriRef, type SparqlValue } from './sparql.ts' import { createExecutor, type BindingMap, type ExecutionConfig, type QueryResult } from './executor.ts' // ============================================================================ @@ -289,7 +289,7 @@ export class UpdateBuilder { return new UpdateBuilder({ operations: [ ...this.state.operations, - { type: 'LOAD', data: { [SPARQL_VALUE_BRAND]: true, value: toGraphRef(url) }, graph: graph ? toVarOrIriRef(graph) : graph, silent } + { type: 'LOAD', data: { [SPARQL_VALUE_BRAND]: true, [SPARQL_EXPR_BRAND]: true, value: toGraphRef(url) }, graph: graph ? toVarOrIriRef(graph) : graph, silent } ] }) } diff --git a/utils.ts b/utils.ts index 18528ac..fe438da 100644 --- a/utils.ts +++ b/utils.ts @@ -36,9 +36,14 @@ import { validateIRI, isIRIRefToken, SPARQL_VALUE_BRAND, + SPARQL_EXPR_BRAND, + SPARQL_TERM_BRAND, + SPARQL_PATTERN_BRAND, type VariableName, type SparqlValue, type SparqlInterpolatable, + type SparqlExpr, + type SparqlTerm, } from './sparql.ts' // ============================================================================ @@ -250,43 +255,93 @@ export type TermPosition = 'subject' | 'object' | 'graph' * - variable (?x, $x) * - IRI (<...>) * - prefixed name (prefix:local) - * - blank node label (_:b1) + * - blank node label (_:b1, _:foo-123, etc.) + * - blank node property list ([] or [ ... ]) * - literal ("...", 42, true, "..."@en, "..."^^<...>) * - RDF* quoted triple (<< ... >>) * * Anything that looks like a function call or complex expression * (STR(...), CONCAT(...), BNODE(), etc.) is rejected. + * + * NOTE: This is intentionally conservative. It’s not a full SPARQL parser, + * just a guardrail to keep obvious expressions out of triple term positions. */ export function isGraphNodeLexical(lex: string): boolean { const t = lex.trim() if (!t) return false - // Variable ?x or $x + // --------------------------------------------------------------------------- + // Variables: ?x or $x (VAR1 / VAR2) + // --------------------------------------------------------------------------- if (/^[?$][A-Za-z_][\w-]*$/.test(t)) return true + // --------------------------------------------------------------------------- // IRI reference: + // (roughly the IRIREF production; we still rely on higher-level validation) + // --------------------------------------------------------------------------- if (/^<[^<>"{}|^`\\\s]+>$/.test(t)) return true - // Blank node label _:b1 - if (/^_:[A-Za-z][A-Za-z0-9_]*$/.test(t)) return true - - // RDF* quoted triple << ... >> + // --------------------------------------------------------------------------- + // Blank node label: _:label + // + // SPARQL uses the same BNODE label rules as Turtle: + // BLANK_NODE_LABEL ::= '_:' PN_LOCAL + // + // PN_LOCAL is quite involved; we approximate a common subset: + // - starts with letter or underscore + // - then letters/digits/underscore/dot/hyphen allowed + // + // This covers typical labels like _:b1, _:foo-123, _:my.node + // without trying to implement the full PN_LOCAL grammar. + // --------------------------------------------------------------------------- + if (/^_:[A-Za-z_][A-Za-z0-9._-]*$/.test(t)) return true + + // --------------------------------------------------------------------------- + // Blank node property list: [] or [ ... ] + // + // In SPARQL/Turtle this is a TriplesNode (GraphNode), e.g.: + // ?x :p [ :q "v" ] . + // :x :p [] . + // + // We don't attempt to validate the internal predicate-object list here, + // we just recognize the outer [ ... ] form as "some kind of bnode term". + // --------------------------------------------------------------------------- + if (/^\[\s*[\s\S]*\s*\]$/.test(t)) return true + + // --------------------------------------------------------------------------- + // RDF* quoted triple: << ... >> + // --------------------------------------------------------------------------- if (/^<<[\s\S]*>>$/.test(t)) return true + // --------------------------------------------------------------------------- // Boolean literal + // --------------------------------------------------------------------------- if (/^(true|false)$/i.test(t)) return true + // --------------------------------------------------------------------------- // Numeric literal (very simple integer/decimal/double checks) + // --------------------------------------------------------------------------- if (/^[+-]?[0-9]+$/.test(t)) return true // integer if (/^[+-]?[0-9]*\.[0-9]+([eE][+-]?[0-9]+)?$/.test(t)) return true // decimal if (/^[+-]?[0-9]+(\.[0-9]+)?[eE][+-]?[0-9]+$/.test(t)) return true // double - // String / language-tagged / typed literals: start with " or ' + // --------------------------------------------------------------------------- + // String / language-tagged / typed literals: + // We don't fully parse them; we just require starting with " or '. + // The actual escaping and datatype/lang handling is done in convertValue(). + // --------------------------------------------------------------------------- if (/^["']/.test(t)) return true + // --------------------------------------------------------------------------- // Prefixed name prefix:local (approximate but safe enough) + // + // This matches ex:Foo, foaf:name, xsd:int, etc. + // It's intentionally a bit loose; full PN_PREFIX/PN_LOCAL is gnarly. + // --------------------------------------------------------------------------- if (/^[A-Za-z_][\w.-]*:[\w.-]+$/.test(t)) return true + // Anything else (including function calls like BNODE(), STR(...), CONCAT(...)) + // is treated as an expression, not a term/GraphNode. return false } @@ -303,7 +358,7 @@ export function isGraphNodeLexical(lex: string): boolean { * a valid SPARQL term (e.g. STR(...), CONCAT(...), BNODE()). */ export function termString( - value: SparqlValue | ExpressionPrimitive, + value: SparqlTerm | ExpressionPrimitive, position: TermPosition = 'object', ): string { const lex = exprTermString(value) @@ -339,7 +394,7 @@ export function termString( */ export function concat( ...args: Array -): FluentValue { +): FluentExpr { if (args.length === 0) { return fluent(strlit('')) } @@ -354,7 +409,7 @@ export function concat( * Forces conversion to string representation. Useful when you need to ensure * a value is treated as a string for comparison or manipulation. */ -export function str(value: SparqlValue | ExpressionPrimitive): FluentValue { +export function str(value: SparqlValue | ExpressionPrimitive): FluentExpr { return fluent(raw(`STR(${exprTermString(value)})`)) } @@ -365,21 +420,21 @@ export function str(value: SparqlValue | ExpressionPrimitive): FluentValue { */ export function strlen( value: SparqlValue | ExpressionPrimitive, -): FluentValue { +): FluentExpr { return fluent(raw(`STRLEN(${exprTermString(value)})`)) } /** * Convert string to uppercase. */ -export function ucase(value: SparqlValue | ExpressionPrimitive): FluentValue { +export function ucase(value: SparqlValue | ExpressionPrimitive): FluentExpr { return fluent(raw(`UCASE(${exprTermString(value)})`)) } /** * Convert string to lowercase. */ -export function lcase(value: SparqlValue | ExpressionPrimitive): FluentValue { +export function lcase(value: SparqlValue | ExpressionPrimitive): FluentExpr { return fluent(raw(`LCASE(${exprTermString(value)})`)) } @@ -398,7 +453,7 @@ export function lcase(value: SparqlValue | ExpressionPrimitive): FluentValue { export function contains( text: SparqlValue | ExpressionPrimitive, pattern: SparqlValue | ExpressionPrimitive, -): SparqlValue { +): SparqlExpr { return raw( `CONTAINS(${exprTermString(text)}, ${exprTermString(pattern)})`, ) @@ -412,7 +467,7 @@ export function contains( export function startsWith( text: SparqlValue | ExpressionPrimitive, pattern: SparqlValue | ExpressionPrimitive, -): SparqlValue { +): SparqlExpr { return raw( `STRSTARTS(${exprTermString(text)}, ${exprTermString(pattern)})`, ) @@ -422,7 +477,7 @@ export function startsWith( export function strstarts( text: SparqlValue | ExpressionPrimitive, pattern: SparqlValue | ExpressionPrimitive, -): SparqlValue { +): SparqlExpr { return startsWith(text, pattern) } @@ -434,7 +489,7 @@ export function strstarts( export function endsWith( text: SparqlValue | ExpressionPrimitive, pattern: SparqlValue | ExpressionPrimitive, -): SparqlValue { +): SparqlExpr { return raw( `STRENDS(${exprTermString(text)}, ${exprTermString(pattern)})`, ) @@ -444,7 +499,7 @@ export function endsWith( export function strends( text: SparqlValue | ExpressionPrimitive, pattern: SparqlValue | ExpressionPrimitive, -): SparqlValue { +): SparqlExpr { return endsWith(text, pattern) } @@ -469,7 +524,7 @@ export function regex( text: SparqlValue | ExpressionPrimitive, pattern: string, flags?: string, -): SparqlValue { +): SparqlExpr { const textStr = exprTermString(text) const patternStr = exprTermString(pattern) @@ -503,7 +558,7 @@ export function substr( text: SparqlValue | ExpressionPrimitive, start: SparqlValue | ExpressionPrimitive, length?: SparqlValue | ExpressionPrimitive, -): FluentValue { +): FluentExpr { const textStr = exprTermString(text) const startStr = exprTermString(start) @@ -538,7 +593,7 @@ export function replaceStr( pattern: SparqlValue | ExpressionPrimitive, replacement: SparqlValue | ExpressionPrimitive, flags?: string, -): FluentValue { +): FluentExpr { const textStr = exprTermString(text) const patternStr = exprTermString(pattern) const replacementStr = exprTermString(replacement) @@ -572,7 +627,7 @@ export function replaceStr( export function strBefore( text: SparqlValue | ExpressionPrimitive, match: SparqlValue | ExpressionPrimitive, -): FluentValue { +): FluentExpr { const textTerm = exprTermString(text) const matchTerm = exprTermString(match) return fluent(raw(`STRBEFORE(${textTerm}, ${matchTerm})`)) @@ -599,7 +654,7 @@ export function strBefore( export function strAfter( text: SparqlValue | ExpressionPrimitive, match: SparqlValue | ExpressionPrimitive, -): FluentValue { +): FluentExpr { const textTerm = exprTermString(text) const matchTerm = exprTermString(match) return fluent(raw(`STRAFTER(${textTerm}, ${matchTerm})`)) @@ -621,7 +676,7 @@ export function ifElse( condition: SparqlValue, whenTrue: SparqlValue | ExpressionPrimitive, whenFalse: SparqlValue | ExpressionPrimitive, -): FluentValue { +): FluentExpr { const trueTerm = exprTermString(whenTrue); const falseTerm = exprTermString(whenFalse) return fluent(raw( @@ -637,7 +692,7 @@ export function ifElse( export function add( left: SparqlValue | ExpressionPrimitive, right: SparqlValue | ExpressionPrimitive, -): FluentValue { +): FluentExpr { return fluent(raw(`${exprTermString(left)} + ${exprTermString(right)}`)) } @@ -645,7 +700,7 @@ export function add( export function sub( left: SparqlValue | ExpressionPrimitive, right: SparqlValue | ExpressionPrimitive, -): FluentValue { +): FluentExpr { return fluent(raw(`${exprTermString(left)} - ${exprTermString(right)}`)) } @@ -653,7 +708,7 @@ export function sub( export function mul( left: SparqlValue | ExpressionPrimitive, right: SparqlValue | ExpressionPrimitive, -): FluentValue { +): FluentExpr { return fluent(raw(`${exprTermString(left)} * ${exprTermString(right)}`)) } @@ -661,7 +716,7 @@ export function mul( export function div( left: SparqlValue | ExpressionPrimitive, right: SparqlValue | ExpressionPrimitive, -): FluentValue { +): FluentExpr { return fluent(raw(`${exprTermString(left)} / ${exprTermString(right)}`)) } @@ -669,35 +724,35 @@ export function div( export function mod( left: SparqlValue | ExpressionPrimitive, right: SparqlValue | ExpressionPrimitive, -): FluentValue { +): FluentExpr { return fluent(raw(`(${exprTermString(left)} % ${exprTermString(right)})`)) } /** Absolute value. */ export function abs( value: SparqlValue | ExpressionPrimitive, -): FluentValue { +): FluentExpr { return fluent(raw(`ABS(${exprTermString(value)})`)) } /** Round to nearest integer. */ export function round( value: SparqlValue | ExpressionPrimitive, -): FluentValue { +): FluentExpr { return fluent(raw(`ROUND(${exprTermString(value)})`)) } /** Round up to next integer. */ export function ceil( value: SparqlValue | ExpressionPrimitive, -): FluentValue { +): FluentExpr { return fluent(raw(`CEIL(${exprTermString(value)})`)) } /** Round down to previous integer. */ export function floor( value: SparqlValue | ExpressionPrimitive, -): FluentValue { +): FluentExpr { return fluent(raw(`FLOOR(${exprTermString(value)})`)) } @@ -709,7 +764,7 @@ export function floor( export function eq( left: SparqlValue | ExpressionPrimitive, right: SparqlValue | ExpressionPrimitive, -): SparqlValue { +): SparqlExpr { return raw(`${exprTermString(left)} = ${exprTermString(right)}`) } @@ -717,7 +772,7 @@ export function eq( export function neq( left: SparqlValue | ExpressionPrimitive, right: SparqlValue | ExpressionPrimitive, -): SparqlValue { +): SparqlExpr { return raw(`${exprTermString(left)} != ${exprTermString(right)}`) } @@ -725,7 +780,7 @@ export function neq( export function gt( left: SparqlValue | ExpressionPrimitive, right: SparqlValue | ExpressionPrimitive, -): SparqlValue { +): SparqlExpr { return raw(`${exprTermString(left)} > ${exprTermString(right)}`) } @@ -733,7 +788,7 @@ export function gt( export function gte( left: SparqlValue | ExpressionPrimitive, right: SparqlValue | ExpressionPrimitive, -): SparqlValue { +): SparqlExpr { return raw(`${exprTermString(left)} >= ${exprTermString(right)}`) } @@ -741,7 +796,7 @@ export function gte( export function lt( left: SparqlValue | ExpressionPrimitive, right: SparqlValue | ExpressionPrimitive, -): SparqlValue { +): SparqlExpr { return raw(`${exprTermString(left)} < ${exprTermString(right)}`) } @@ -749,7 +804,7 @@ export function lt( export function lte( left: SparqlValue | ExpressionPrimitive, right: SparqlValue | ExpressionPrimitive, -): SparqlValue { +): SparqlExpr { return raw(`${exprTermString(left)} <= ${exprTermString(right)}`) } @@ -771,7 +826,7 @@ export function lte( */ export function isNull( value: SparqlValue, -): SparqlValue { +): SparqlExpr { return raw(`!BOUND(${value.value})`) } @@ -782,35 +837,35 @@ export function isNull( */ export function isNotNull( value: SparqlValue, -): SparqlValue { +): SparqlExpr { return raw(`BOUND(${value.value})`) } /** Check if a variable is bound. Basically the same thing as {@link isNotNull} */ export function bound( variable: SparqlValue, -): SparqlValue { +): SparqlExpr { return raw(`BOUND(${variable.value})`) } /** Check if a term is an IRI. */ export function isIri( term: SparqlValue, -): SparqlValue { +): SparqlExpr { return raw(`isIRI(${term.value})`) } /** Check if a term is a blank node. */ export function isBlank( term: SparqlValue, -): SparqlValue { +): SparqlExpr { return raw(`isBlank(${term.value})`) } /** Check if a term is a literal. */ export function isLiteral( term: SparqlValue, -): SparqlValue { +): SparqlExpr { return raw(`isLiteral(${term.value})`) } @@ -836,11 +891,11 @@ export function isLiteral( */ export function and( ...conditions: SparqlValue[] -): SparqlValue { +): SparqlExpr { const filtered = conditions.filter(Boolean) if (filtered.length === 0) throw new Error('and() requires at least one condition') - if (filtered.length === 1) return filtered[0] + if (filtered.length === 1) return filtered[0] as SparqlExpr return raw(filtered.map((c) => `(${c.value})`).join(' && ')) } @@ -862,11 +917,11 @@ export function and( */ export function or( ...conditions: SparqlValue[] -): SparqlValue { +): SparqlExpr { const filtered = conditions.filter(Boolean) if (filtered.length === 0) throw new Error('or() requires at least one condition') - if (filtered.length === 1) return filtered[0] + if (filtered.length === 1) return filtered[0] as SparqlExpr return raw(conditions.map((c) => `(${c.value})`).join(' || ')) } @@ -876,7 +931,7 @@ export function or( * * Flips true to false and false to true. */ -export function not(condition: SparqlValue): SparqlValue { +export function not(condition: SparqlValue): SparqlExpr { return raw(`!(${condition.value})`) } @@ -898,7 +953,7 @@ export function not(condition: SparqlValue): SparqlValue { export function inList( expr: SparqlValue | ExpressionPrimitive, values: Array, -): SparqlValue { +): SparqlExpr { if (values.length === 0) { return raw('false') } @@ -914,7 +969,7 @@ export function inList( export function notInList( expr: SparqlValue | ExpressionPrimitive, values: Array, -): SparqlValue { +): SparqlExpr { if (values.length === 0) { return raw('true') } @@ -937,7 +992,7 @@ export function between( expr: SparqlValue | ExpressionPrimitive, low: SparqlValue | ExpressionPrimitive, high: SparqlValue | ExpressionPrimitive, -): SparqlValue { +): SparqlExpr { const exprTerm = exprTermString(expr) const lowTerm = exprTermString(low) const highTerm = exprTermString(high) @@ -958,7 +1013,7 @@ export function between( */ export function coalesce( ...values: Array -): FluentValue { +): FluentExpr { if (values.length === 0) { return fluent(strlit('')) } @@ -966,6 +1021,16 @@ export function coalesce( return fluent(raw(`COALESCE(${inner})`)) } +/** + * Create a blank node *term*. + * + * - Represents the SPARQL `BNODE()` function, which creates + * a fresh blank node per evaluation. + */ +export function bnodeFn(): SparqlExpr { + return raw('BNODE()') +} + // ============================================================================ // Fluent Value Interface // ============================================================================ @@ -1001,57 +1066,57 @@ export function coalesce( * ) * ``` */ -export interface FluentValue extends SparqlValue { +export interface FluentExpr extends SparqlExpr { // Comparison operators - eq(other: SparqlValue | ExpressionPrimitive): SparqlValue - neq(other: SparqlValue | ExpressionPrimitive): SparqlValue - lt(other: SparqlValue | ExpressionPrimitive): SparqlValue - lte(other: SparqlValue | ExpressionPrimitive): SparqlValue - gt(other: SparqlValue | ExpressionPrimitive): SparqlValue - gte(other: SparqlValue | ExpressionPrimitive): SparqlValue + eq(other: SparqlValue | ExpressionPrimitive): SparqlExpr + neq(other: SparqlValue | ExpressionPrimitive): SparqlExpr + lt(other: SparqlValue | ExpressionPrimitive): SparqlExpr + lte(other: SparqlValue | ExpressionPrimitive): SparqlExpr + gt(other: SparqlValue | ExpressionPrimitive): SparqlExpr + gte(other: SparqlValue | ExpressionPrimitive): SparqlExpr // Arithmetic operators - add(other: SparqlValue | ExpressionPrimitive): FluentValue - sub(other: SparqlValue | ExpressionPrimitive): FluentValue - mul(other: SparqlValue | ExpressionPrimitive): FluentValue - div(other: SparqlValue | ExpressionPrimitive): FluentValue - mod(other: SparqlValue | ExpressionPrimitive): FluentValue + add(other: SparqlValue | ExpressionPrimitive): FluentExpr + sub(other: SparqlValue | ExpressionPrimitive): FluentExpr + mul(other: SparqlValue | ExpressionPrimitive): FluentExpr + div(other: SparqlValue | ExpressionPrimitive): FluentExpr + mod(other: SparqlValue | ExpressionPrimitive): FluentExpr // String functions - concat(...others: Array): FluentValue - contains(substring: SparqlValue | ExpressionPrimitive): SparqlValue - startsWith(prefix: SparqlValue | ExpressionPrimitive): SparqlValue - endsWith(suffix: SparqlValue | ExpressionPrimitive): SparqlValue - regex(pattern: string, flags?: string): SparqlValue - strlen(): FluentValue - ucase(): FluentValue - lcase(): FluentValue - substr(start: SparqlValue | ExpressionPrimitive, length?: SparqlValue | ExpressionPrimitive): FluentValue - replace(pattern: SparqlValue | ExpressionPrimitive, replacement: SparqlValue | ExpressionPrimitive, flags?: string): FluentValue - strBefore(match: SparqlValue | ExpressionPrimitive): FluentValue - strAfter(match: SparqlValue | ExpressionPrimitive): FluentValue + concat(...others: Array): FluentExpr + contains(substring: SparqlValue | ExpressionPrimitive): SparqlExpr + startsWith(prefix: SparqlValue | ExpressionPrimitive): SparqlExpr + endsWith(suffix: SparqlValue | ExpressionPrimitive): SparqlExpr + regex(pattern: string, flags?: string): SparqlExpr + strlen(): FluentExpr + ucase(): FluentExpr + lcase(): FluentExpr + substr(start: SparqlValue | ExpressionPrimitive, length?: SparqlValue | ExpressionPrimitive): FluentExpr + replace(pattern: SparqlValue | ExpressionPrimitive, replacement: SparqlValue | ExpressionPrimitive, flags?: string): FluentExpr + strBefore(match: SparqlValue | ExpressionPrimitive): FluentExpr + strAfter(match: SparqlValue | ExpressionPrimitive): FluentExpr // Type checking - isNull(): SparqlValue - isNotNull(): SparqlValue - isIri(): SparqlValue - isBlank(): SparqlValue - isLiteral(): SparqlValue - bound(): SparqlValue + isNull(): SparqlExpr + isNotNull(): SparqlExpr + isIri(): SparqlExpr + isBlank(): SparqlExpr + isLiteral(): SparqlExpr + bound(): SparqlExpr // Logical operators - and(other: SparqlValue): SparqlValue - or(other: SparqlValue): SparqlValue - not(): SparqlValue + and(other: SparqlValue): SparqlExpr + or(other: SparqlValue): SparqlExpr + not(): SparqlExpr // Math functions - abs(): FluentValue - round(): FluentValue - ceil(): FluentValue - floor(): FluentValue + abs(): FluentExpr + round(): FluentExpr + ceil(): FluentExpr + floor(): FluentExpr // Utility - as(variable: VariableName): SparqlValue + as(variable: VariableName): SparqlExpr } /** @@ -1074,9 +1139,14 @@ export interface FluentValue extends SparqlValue { * fluent(v('price')).mul(1.1).add(5) * ``` */ -export function fluent(value: SparqlValue): FluentValue { - const result: FluentValue = { - ...value, +export function fluent(value: SparqlTerm | SparqlExpr): FluentExpr { + if (SPARQL_PATTERN_BRAND in value && value[SPARQL_PATTERN_BRAND]) + throw new Error(`Cannot convert pattern value "${value}" to a fluent expression`); + + const result: FluentExpr = { + ...Object.assign(value, { [SPARQL_TERM_BRAND]: false }), + + [SPARQL_EXPR_BRAND]: true, // Comparison operators eq: (other) => eq(result, other), @@ -1175,21 +1245,21 @@ export function fluent(value: SparqlValue): FluentValue { * ) * ``` */ -export function v(name: string): FluentValue { +export function v(name: string): FluentExpr { return fluent(variable(name)) } /** Get the language tag of a literal. */ export function getlang( literal: SparqlValue | ExpressionPrimitive, -): FluentValue { +): FluentExpr { return fluent(raw(`LANG(${exprTermString(literal)})`)) } /** Get the datatype IRI of a literal. */ export function datatype( literal: SparqlValue | ExpressionPrimitive, -): FluentValue { +): FluentExpr { return fluent(raw(`DATATYPE(${exprTermString(literal)})`)) } @@ -1204,8 +1274,8 @@ export function datatype( * used with GROUP BY clauses. The `.as()` method lets you assign the result * to a variable. */ -export interface AggregationExpression extends SparqlValue { - as(variable: string): SparqlValue +export interface AggregationExpression extends SparqlExpr { + as(variable: string): SparqlExpr } /** @@ -1217,8 +1287,9 @@ function createAggregation(sparqlFunc: string, expr?: SparqlValue | ExpressionPr const result: AggregationExpression = { [SPARQL_VALUE_BRAND]: true, + [SPARQL_EXPR_BRAND]: true, value: baseValue, - as(variable: string): SparqlValue { + as(variable: string): SparqlExpr { const varName = toVarToken(variable) // SPARQL 1.1 requires (Expression AS ?var) in SELECT return raw(`(${baseValue} AS ${varName})`) @@ -1803,7 +1874,7 @@ export function definePrefix(prefix: PrefixName, iri: string): SparqlValue { * // BIND(MD5(CONCAT(?firstName, ?lastName, ?birthDate)) AS ?personKey) * ``` */ -export function md5(value: SparqlValue | ExpressionPrimitive): FluentValue { +export function md5(value: SparqlValue | ExpressionPrimitive): FluentExpr { return fluent(raw(`MD5(${exprTermString(value)})`)) } @@ -1826,7 +1897,7 @@ export function md5(value: SparqlValue | ExpressionPrimitive): FluentValue { * // BIND(SHA1(?documentText) AS ?contentHash) * ``` */ -export function sha1(value: SparqlValue | ExpressionPrimitive): FluentValue { +export function sha1(value: SparqlValue | ExpressionPrimitive): FluentExpr { return fluent(raw(`SHA1(${exprTermString(value)})`)) } @@ -1852,7 +1923,7 @@ export function sha1(value: SparqlValue | ExpressionPrimitive): FluentValue { * // WHERE { ?user ex:password ?password } * ``` */ -export function sha256(value: SparqlValue | ExpressionPrimitive): FluentValue { +export function sha256(value: SparqlValue | ExpressionPrimitive): FluentExpr { return fluent(raw(`SHA256(${exprTermString(value)})`)) } @@ -1874,7 +1945,7 @@ export function sha256(value: SparqlValue | ExpressionPrimitive): FluentValue { * // SHA384(?data) * ``` */ -export function sha384(value: SparqlValue | ExpressionPrimitive): FluentValue { +export function sha384(value: SparqlValue | ExpressionPrimitive): FluentExpr { return fluent(raw(`SHA384(${exprTermString(value)})`)) } @@ -1898,7 +1969,7 @@ export function sha384(value: SparqlValue | ExpressionPrimitive): FluentValue { * // BIND(SHA512(?sensitiveData) AS ?secureHash) * ``` */ -export function sha512(value: SparqlValue | ExpressionPrimitive): FluentValue { +export function sha512(value: SparqlValue | ExpressionPrimitive): FluentExpr { return fluent(raw(`SHA512(${exprTermString(value)})`)) } @@ -2013,7 +2084,7 @@ export function uuid(): SparqlValue { * // WHERE { ?user ex:loginTime NOW() } * ``` */ -export function struuid(): FluentValue { +export function struuid(): FluentExpr { return fluent(raw('STRUUID()')) } @@ -2051,7 +2122,7 @@ export function struuid(): FluentValue { * // ORDER BY (RAND() AS ?random) * ``` */ -export function rand(): FluentValue { +export function rand(): FluentExpr { return fluent(raw('RAND()')) } @@ -2110,7 +2181,7 @@ export function sameTerm(a: SparqlValue, b: SparqlValue): SparqlValue { * // BIND(IRI(CONCAT("http://example.org/person/", ENCODE_FOR_URI(?name))) AS ?personIri) * ``` */ -export function encodeForUri(value: SparqlValue | ExpressionPrimitive): FluentValue { +export function encodeForUri(value: SparqlValue | ExpressionPrimitive): FluentExpr { return fluent(raw(`ENCODE_FOR_URI(${exprTermString(value)})`)) }