A compact Notation3 (N3) reasoner in JavaScript.
eyeling.js), no external runtime dependencies=>) and backward (<=) chaining over Horn-style rulesreason() output is mode-dependent by default: it prints newly derived forward facts in normal mode, or (when top-level { ... } log:query { ... }. directives are present) the unique instantiated conclusion triples of those queries, optionally with compact proof commentsEyeling is regularly checked against the community Notation3 test suite. If you want implementation details (parser, unifier, proof search, skolemization, scoped closure, builtins), start with the handbook.
npm i eyeling
Run on a file:
npx eyeling examples/socrates.n3
Show all options:
npx eyeling --help
Useful flags include --proof-comments, --stream, and --enforce-https. If the final closure contains any log:outputString triples, Eyeling now renders those strings automatically instead of printing N3 output.
Without top-level log:query directives, Eyeling prints newly derived forward facts by default.
log:query mode (output selection)If the input contains one or more top-level directives of the form:
{ ?x a :Human. } log:query { ?x a :Mortal. }.
Eyeling still computes the saturated forward closure, but it prints only the unique instantiated conclusion triples of those log:query directives (instead of all newly derived forward facts).
reason()CommonJS:
const { reason } = require('eyeling');
const input = `
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.
@prefix : <http://example.org/socrates#>.
:Socrates a :Human.
:Human rdfs:subClassOf :Mortal.
{ ?s a ?A. ?A rdfs:subClassOf ?B. } => { ?s a ?B. }.
`;
console.log(reason({ proofComments: false }, input));
ESM:
import eyeling from 'eyeling';
console.log(eyeling.reason({ proofComments: false }, input));
Notes:
reason() returns the same textual output you would get from the CLI for the same input/options.proofComments: false).eyeling.js CLI for simplicity and robustness.The JavaScript APIs now accept three input styles:
quads, facts, or dataset)eyeling --ast)If you want to use N3 source text, pass the whole input as a plain N3 string.
For RDF/JS facts, the graph must be the default graph. Named-graph quads are rejected.
For structured inputs, rules is supplied as current Eyeling rule objects:
const { reason, rdfjs } = require('eyeling');
const ex = 'http://example.org/';
const rule = {
_type: 'Rule',
premise: [
{
_type: 'Triple',
s: { _type: 'Var', name: 'x' },
p: { _type: 'Iri', value: ex + 'parent' },
o: { _type: 'Var', name: 'y' },
},
],
conclusion: [
{
_type: 'Triple',
s: { _type: 'Var', name: 'x' },
p: { _type: 'Iri', value: ex + 'ancestor' },
o: { _type: 'Var', name: 'y' },
},
],
isForward: true,
isFuse: false,
headBlankLabels: [],
};
const out = reason(
{ proofComments: false },
{
quads: [rdfjs.quad(rdfjs.namedNode(ex + 'alice'), rdfjs.namedNode(ex + 'parent'), rdfjs.namedNode(ex + 'bob'))],
rules: [rule],
},
);
You can also pass a full AST bundle directly:
const ast = [prefixes, triples, forwardRules, backwardRules];
const out2 = reason({ proofComments: false }, ast);
reasonStream()For in-process reasoning (browser, worker, or direct use of eyeling.js):
const result = eyeling.reasonStream(input, {
proof: false,
onDerived: ({ triple }) => console.log(triple),
// includeInputFactsInClosure: false,
});
console.log(result.closureN3);
eyeling.js now also exposes eyeling.rdfjs and eyeling.reasonRdfJs(...) for RDF/JS workflows.
reasonStream() output behaviorclosureN3 is also mode-dependent:
closureN3 is the closure (input facts + derived facts)log:query mode: closureN3 is the query-selected triplesTo exclude input facts from the normal-mode closure, pass:
includeInputFactsInClosure: false;
When rdfjs: true is passed, onDerived also receives a quad field and the result may include closureQuads / queryQuads.
The returned object also includes queryMode, queryTriples, and queryDerived (and in normal mode, onDerived fires for newly derived facts; in log:query mode it fires for the query-selected derived triples).
reasonRdfJs()reasonRdfJs(input, opts) exposes derived results as an async iterable of RDF/JS quads as they are produced:
const { reasonRdfJs, rdfjs } = require('eyeling');
for await (const quad of reasonRdfJs({
quads: [rdfjs.quad(rdfjs.namedNode(ex + 'alice'), rdfjs.namedNode(ex + 'parent'), rdfjs.namedNode(ex + 'bob'))],
rules: [rule],
})) {
console.log(quad.subject.value, quad.predicate.value, quad.object.value);
}
Builtins are defined in eyeling-builtins.ttl and described in the Handbook (Chapter 11).
npm test
You can also inspect the examples/ directory for many small and large N3 programs.
MIT — see LICENSE.md.