This document defines a JavaScript-based extension mechanism for the Shapes Constraint Language (SHACL). This early draft outlines a proposed syntax for declaring constraints and constraint components (i.e. high-level terms such as sh:minCount) in JavaScript.

Note that reading this draft only makes sense after you have read the main SHACL spec, and the SHACL-SPARQL extension mechanism in particular. The JavaScript mechanism is very similar to that, just without SPARQL.

Document Outline

Some examples in this document use Turtle [[!turtle]]. The reader should be familiar with basic RDF concepts [[!rdf11-concepts]] such as triples and, for the advanced concepts of SHACL, with SPARQL [[!sparql11-overview]].

Introduction

Document Conventions

Within this document, the following namespace prefix bindings are used:

Prefix Namespace
rdf: http://www.w3.org/1999/02/22-rdf-syntax-ns#
rdfs: http://www.w3.org/2000/01/rdf-schema#
sh: http://www.w3.org/ns/shacl#
xsd: http://www.w3.org/2001/XMLSchema#
ex: http://example.com/ns#

The Turtle serialization of the SHACL-JS vocabulary will be uploaded to the web URL of the graph that it represents.

Throughout the document, color-coded boxes containing RDF graphs in Turtle will appear. These fragments of Turtle documents use the prefix bindings given above.

# This box represents an input shapes graph

# Triples that can be omitted are marked as grey e.g.
<s> <p> <o> .
# This box represents an input data graph.
# When highlighting is used in the examples:

# Elements highlighted in blue are focus nodes
ex:Bob a ex:Person .

# Elements highlighted in red are focus nodes that fail validation
ex:Alice a ex:Person .
# This box represents an output results graph

SHACL Definitions appear in blue boxes:

TEXTUAL DEFINITIONS
# This box contains textual definitions. 

Grey boxes such as this include syntax rules that apply to the shapes graph.

true denotes the RDF term "true"^^xsd:boolean. false denotes the RDF term "false"^^xsd:boolean.

JavaScript API for RDF

This section defines an API that JavaScript-based SHACL features can use to operate on RDF terms (IRIs, blank nodes and literals) and to query graphs.

Note that the API expects these JavaScript objects to be immutable, i.e. neither the values of an RDF term nor triples nor graphs can be modified during SHACL validation.

RDF Terms

All RDF terms (IRIs, blank nodes and literals) are represented using JavaScript objects that have attributes as described in the following three subsections. In addition to these, each of these objects must have a function equals that takes another term object as its only parameter and returns true exactly if the underlying RDF terms are equal.

This API is compatible with a minimal subset of the Interface Specification by the RDFJS Representation Task Force.

IRIs / Named Nodes

IRIs are represented by a JavaScript NamedNode object that has the string value "NamedNode" for the attribute termType and the IRI string itself as its value for the attribute value.

{
	termType : "NamedNode",
	value : "http://example.org/ns#myIRI",
	equals : function(other) { ... }
}

Blank Nodes

Blank nodes are represented by a JavaScript BlankNode object that has the string value "BlankNode" for the attribute termType and a string as its value for the attribute value. That string represents an identifier for the blank node, which is consistent for the duration of a SHACL validation.

{
	termType : "NamedNode",
	value : "1234-56-7890",
	equals : function(other) { ... }
}

Literals

Literals are represented by a JavaScript Literal object that has the string value "Literal" for the attribute termType and a string containing the lexical form of the literal as its value for the attribute value. The attribute language is a lowercase BCP-47 string (for example, en, en-gb), or an empty string if the literal has no language. The attribute datatype is a NamedNode representation of the datatype IRI of the literal.

{
	termType : "Literal",
	value : "Haus",
	language : "de",
	datatype : {
		termType : "NamedNode",
		value : "http://www.w3.org/1999/02/22-rdf-syntax-ns#langString"
	},
	equals : function(other) { ... }
}

The TermFactory

During the execution of JavaScript code, the SHACL engine MUST provide an object accessible via the variable TermFactory that can be used to create JavaScript objects for RDF terms. The TermFactory provides the following three functions:

  • namedNode(value) returns a NamedNode with the IRI provided as a string via value.
  • blankNode(value) returns a BlankNode with an optional identifier provided as a string via value. If the value is omitted then the system generates a unique identifier.
  • literal(value, languageOrDatatype) returns a Literal with the lexical form provided as a string via value. The second argument languageOrDatatype must be either a string, in which case the literal will have that value as its language tag, or be a JavaScript NamedNode object representing a datatype.

Triples

RDF triples are represented as JavaScript Triple objects with three attributes subject, predicate and object, each of which with exactly one RDF term as its value. Furthermore, triples must provide an equals function that takes an object as its only parameter and returns true exactly if that object has equal values for its subject, predicate and object attributes, and false otherwise.

Graphs

RDF graphs are represented as JavaScript Graph objects that have a function find which takes three parameters of type object, all of which are optional. The result of the find function is an Iterator object with two functions:

The following JavaScript snippet assumes that the current data graph is represented by the variable $data and gets the first label of a given resource where the label is an english literal.

function getEnglishLabel(resource) {
	var labelProperty = TermFactory.namedNode("http://www.w3.org/2000/01/rdf-schema#label");
	var labels = $data.find(resource, labelProperty, null);
	for(;;) {
		var labelTriple = labels.next();
		if(!labelTriple) {
			return null;
		}
		var label = labelTriple.object;
		if(label.termType === "Literal" && label.language.startsWith("en")) {
			labels.close();
			return label.value;
		}
	}
}

Accessing the Data Graph via $data

During a validation process, the variable $data points at the JavaScript graph object containing the SHACL data graph.

Accessing the Shapes Graph via $shapes

During a validation process, the variable $shapes points at the JavaScript graph object containing the SHACL shapes graph. This may be identical to the data graph.

Validating Nodes via JavaScript

SHACL-JS processors must provide a JavaScript function with the following signature:

SHACL.nodeConformsToShape(node, shape)
  1. node: The RDF term object of the node to validate
  2. shape: The RDF term object of the shape to validate

The result of this function is true if and only if the validation of the given node against the given shape returns with no validation results.

This function is needed because the implementation of some of the constraint components such as sh:NotConstraintComponent requires the ability to spawn off a new SHACL validation process.

Representing JavaScript in RDF

The SHACL-JS vocabulary includes the class sh:JSExecutable. Instances of this class are called JavaScript executables and may have values for the properties sh:jsFunctionName and sh:jsLibrary.

sh:jsFunctionName

Every SHACL instance of sh:JSExecutable must have exactly one value for the property sh:jsFunctionName. The values of sh:jsFunctionName are literals with datatype xsd:string.

The semantics of how arguments are passed into the provided function depend on the specific type of executable, as described in later sections.

sh:jsLibrary

The values of the property sh:jsLibrary are IRIs or blank nodes. that are well-formed SHACL instances of the class sh:JSLibrary.

sh:JSLibrary

The class sh:JSLibrary can be used to declare JavaScript libraries. A library can be understood as a pointer to zero or more JavaScript files that need to be executed before a JSExecutable can be evaluated. Libraries may depend on each other by declaring further sh:jsLibrary triples.

The values of the property sh:jsLibraryURL are literals with datatype xsd:anyURI.

Executing JavaScript

Execution of sh:script
The execution of a script consists of evaluating the string of the script by the JavaScript engine.
Execution of a JavaScript executable
The execution of a JavaScript executable consists of executing any JavaScript libraries that are values of sh:jsLibrary (including the libraries referenced by those libraries), followed by the execution of the function that has the same name as the value of sh:jsFunctionName. A failure MUST be reported if the SHACL processor encounters a cyclic dependency between libraries.
Execution of a JavaScript library
Within the same JavaScript engine, the same JavaScript library MUST NOT be executed more than once. When a JavaScript library is executed, all its values for sh:jsLibraryURL must be resolved into JavaScript code. By default this resolution mechanism MUST be performing an HTTP GET request against the given script URL.

In practice, some SHACL implementations may redirect the resolution of URLs to local files or cached copies.

JavaScript-based Constraints

SHACL-JS supports a constraint component that can be used to express restrictions based on JavaScript. In a nutshell, whenever a SHACL validation engine encounters a shape with a sh:js constraint, it will execute the provided JavaScript snippet and use the returned result to create validation results.

Constraint Component IRI: sh:JSConstraintComponent

Parameters:
Property Summary
sh:js A JavaScript-based constraint declaring the JavaScript to evaluate.

The syntax rules and validation process for JavaScript-based constraints are defined in the rest of this section.

An Example JavaScript-based Constraint

The following example illustrates the syntax of a JavaScript-based constraint.

ex:ValidCountry a ex:Country ;
	ex:germanLabel "Spanien"@de .
  
ex:InvalidCountry a ex:Country ;
	ex:germanLabel "Spain"@en .
function germanLabel(value) {
	var results = [];
	var p = TermFactory.namedNode("http://example.org/ns#germanLabel");
	var s = $data.find(value, p, null);
	for(var t = s.next(); t; t = s.next()) {
		var object = t.object;
		if(object.termType != "Literal" || !object.language.startsWith("de")) {
			results.push({
				value : object
			});
		}
	}
	return results;
}
ex:LanguageExampleShape
	a sh:NodeShape ;
	sh:targetClass ex:Country ;
	sh:js [
		a sh:JSConstraint ;   # This triple is optional
		sh:message "Values are literals with German language tag." ;
		sh:jsLibrary [ sh:jsLibraryURL "http://example/org/js/germanLabel.js" ] ;
		sh:jsFunctionName "germanLabel" ;
	] .

The target of the shape above includes all SHACL instances of ex:Country. For those nodes (represented by the variable value), the JavaScript code walks through the values of ex:germanLabel and verifies that they are literals with a German language code. The validation results for the aforementioned data graph is shown below:

[	a sh:ValidationReport ;
	sh:conforms false ;
	sh:result [
		a sh:ValidationResult ;
		sh:resultSeverity sh:Violation ;
		sh:focusNode ex:InvalidCountry ;
		sh:resultPath ex:germanLabel ;
		sh:value "Spain"@en ;
		sh:sourceConstraintComponent sh:SPARQLConstraintComponent ;
		sh:sourceShape ex:LanguageExampleShape ;
		# ...
	]
] .

TODO!

JavaScript-based Constraint Components

SHACL is based on constraint components, which can be used to express constraints in a declarative, high-level vocabulary. For example, the constraint component sh:MinCountConstraintComponent defines a parameter sh:minCount. When a shape uses one of these constraint parameters, it finds a suitable validator. SHACL-SPARQL defines how SPARQL queries can be used as validators. SHACL-JS defines a class sh:JSValidator which can be used to declare validators using JavaScript.

An Example JavaScript-based Constraint Component

The following example demonstrates how JavaScript can be used to specify new constraint components using the SHACL-JS language. The example implements sh:maxLength using a JavaScript Validator to validate that the string of each value node has at most a given number of characters. Note that this is only an example implementation and should not be considered normative.

function hasMaxLength(value, maxLength) {
	return (value.value.length <= maxLength.value)
}
sh:MaxLengthConstraintComponent
	a sh:ConstraintComponent ;
	sh:parameter [
		sh:path sh:maxLength ;
	] ;
	sh:validator shimpl:hasMaxLength .

shimpl:hasMaxLength
	a sh:JSValidator ;
	sh:message "Value has more than {$maxLength} characters" ;
	rdfs:comment """
		Note that $value and $maxLength are RDF nodes expressed in JavaScript objects.
		Their lexical value is accessed via the .value attribute.
		The function returns true if no violation has been found.
		""" ;
	sh:jsLibrary [ sh:jsLibraryURL "http://example.org/ns/hasMaxLength.js"^^xsd:anyURI ] ;
	sh:jsFunctionName "hasMaxLength" .

TODO!

JavaScript-based Functions

SHACL includes a generic vocabulary for declare new functions. In particular this is used to declare new SPARQL functions, using the class sh:SPARQLFunction. SHACL-JS includes a very similar mechanism, allowing users to declare new functions in an RDF vocabulary so that certain engines can use them. Functions declared using the SHACL-JS vocabulary can, among others, be used by function calls in SPARQL FILTER or BIND clauses.

An Example JavaScript-based Function

The following example demonstrates how JavaScript can be used to specify new SHACL function. The function can be used, for example in SPARQL using BIND (ex:square(4) AS ?s).

function square(number) {
	return number.value * number.value;
}
ex:square
	a sh:JSFunction ;
	sh:parameter [
		sh:path ex:number ;
		sh:datatype xsd:integer ;
	] ;
	sh:returnType xsd:integer ;
	sh:jsLibrary [ sh:jsLibraryURL "http://example.org/js/square.js" ] ;
	sh:jsFunctionName "square" .

TODO!

Appendix

Summary of SHACL-JS Syntax Rules

This section enumerates all normative syntax rules of SHACL-JS. This section is automatically generated from other parts of this spec and hyperlinks are provided back into the prose if the context of the rule in unclear. Nodes that violate these rules in a shapes graph are ill-formed.

Syntax Rule Id Syntax Rule Text