UNPKG

eyereasoner

Version:

Distributing the [EYE](https://github.com/eyereasoner/eye) reasoner for browser and node using WebAssembly.

316 lines (227 loc) 12.1 kB
# EYE JS Distributing the [EYE](https://github.com/eyereasoner/eye) reasoner for browser and node using WebAssembly. [![GitHub license](https://img.shields.io/github/license/eyereasoner/eye-js.svg)](https://github.com/eyereasoner/eye-js/blob/master/LICENSE) [![npm version](https://img.shields.io/npm/v/eyereasoner.svg)](https://www.npmjs.com/package/eyereasoner) [![build](https://img.shields.io/github/actions/workflow/status/eyereasoner/eye-js/nodejs.yml?branch=main)](https://github.com/eyereasoner/eye-js/tree/main/) [![Dependabot](https://badgen.net/badge/Dependabot/enabled/green?icon=dependabot)](https://dependabot.com/) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) [![bundlephobia](https://img.shields.io/bundlephobia/min/eyereasoner.svg)](https://www.npmjs.com/package/eyereasoner) [![DOI](https://zenodo.org/badge/581706557.svg)](https://zenodo.org/doi/10.5281/zenodo.12211023) ## Usage The simplest way to use this package is to use the `n3reasoner` to execute a query over a dataset and get the results. The input `data` should include the data and any inference rules that you wish to apply to the dataset; the optional `query` should match the pattern of data you wish the engine to return; if left undefined, all new inferred facts will be returned. For example: ```ts import { n3reasoner } from 'eyereasoner'; export const queryString = ` @prefix : <http://example.org/socrates#>. {:Socrates a ?WHAT} => {:Socrates a ?WHAT}. `; export const dataString = ` @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>. @prefix : <http://example.org/socrates#>. :Socrates a :Human. :Human rdfs:subClassOf :Mortal. {?A rdfs:subClassOf ?B. ?S a ?A} => {?S a ?B}. `; // The result of the query (as a string) const resultString = await n3reasoner(dataString, queryString); // All inferred data const resultString = await n3reasoner(dataString); ``` *Note:* One can also supply an array of `dataString`s rather than a single `dataString` if one has multiple input data files. The `n3reasoner` accepts both `string`s (formatted in Notation3 syntax) and `quad`s as input. The output will be of the same type as the input `data`. This means that we can use `n3reasoner` with RDF/JS quads as follows: ```ts import { Parser } from 'n3'; const parser = new Parser({ format: 'text/n3' }); export const queryQuads = parser.parse(queryString); export const dataQuads = parser.parse(dataString); // The result of the query (as an array of quads) const resultQuads = await n3reasoner(dataQuads, queryQuads); ``` ### Options The `n3reasoner` function allows one to optionally pass along a set of options ```ts import { n3reasoner } from 'eyereasoner'; const data = ` @prefix : <urn:example.org:> . :Alice a :Person . { ?S a :Person } => { ?S a :Human } . `; const result = await n3reasoner(data, undefined, { output: 'derivations', outputType: 'string' }); ``` The `options` parameter can be used to configure the reasoning process. The following options are available: - `output`: What to output with implicit queries. - `derivations`: output only new derived triples, a.k.a `--pass-only-new` (default) - `deductive_closure`: output deductive closure, a.k.a `--pass` - `deductive_closure_plus_rules`: output deductive closure plus rules, a.k.a `--pass-all` - `grounded_deductive_closure_plus_rules`: ground the rules and output deductive closure plus rules, a.k.a `--pass-all-ground` - `none`: provides no `-pass-*` arguments to eye, often used when doing RDF Surface reasoning - `outputType`: The type of output (if different from the input) - `string`: output as string - `quads`: output as array of RDF/JS Quads ## Advanced usage To have more granular control one can also use this module as follows ```ts import { SwiplEye, queryOnce } from 'eyereasoner'; const query = ` @prefix : <http://example.org/socrates#>. {:Socrates a ?WHAT} => {:Socrates a ?WHAT}. ` const data = ` @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>. @prefix : <http://example.org/socrates#>. :Socrates a :Human. :Human rdfs:subClassOf :Mortal. {?A rdfs:subClassOf ?B. ?S a ?A} => {?S a ?B}. ` async function main() { // Instantiate a new SWIPL module and log any results it produces to the console const Module = await SwiplEye({ print: (str: string) => { console.log(str) }, arguments: ['-q'] }); // Load the the strings data and query as files data.n3 and query.n3 into the module Module.FS.writeFile('data.n3', data); Module.FS.writeFile('query.n3', query); // Execute main(['--nope', '--quiet', './data.n3', '--query', './query.n3']). queryOnce(Module, 'main', ['--nope', '--quiet', './data.n3', '--query', './query.n3']); } main(); ``` ## Selecting the `SWIPL` module The `SWIPL` module exported from this library is a build that inlines WebAssembly and data strings in order to be isomorphic across browser and node without requiring any bundlers. Some users may wish to have more fine-grained control over their SWIPL module; for instance in order to load the `.wasm` file separately for performance. In these cases see the `SWIPL` modules exported by [npm swipl wasm](https://github.com/rla/npm-swipl-wasm/). An example usage of the node-specific swipl-wasm build is as follows: ```ts import { loadEyeImage, queryOnce } from 'eyereasoner'; import SWIPL from 'swipl-wasm/dist/swipl-node'; async function main() { const SwiplEye = loadEyeImage(SWIPL); // Instantiate a new SWIPL module and log any results it produces to the console const Module = await SwiplEye({ print: (str: string) => { console.log(str) }, arguments: ['-q'] }); // Load the the strings data and query as files data.n3 and query.n3 into the module Module.FS.writeFile('data.n3', data); Module.FS.writeFile('query.n3', query); // Execute main(['--nope', '--quiet', './data.n3', '--query', './query.n3']). queryOnce(Module, 'main', ['--nope', '--quiet', './data.n3', '--query', './query.n3']); } main(); ``` ## CLI Usage This package also exposes a CLI interface for using the reasoner. It can be used via `npx` ```bash # Run the command using the latest version of eyereasoner on npm npx eyereasoner --nope --quiet ./socrates.n3 --query ./socrates-query.n3 ``` or by globally installing `eyereasoner` ```bash # Gloablly install eyereasoner npm i -g eyereasoner # Run a command with eyereasoner eyereasoner --nope --quiet ./socrates.n3 --query ./socrates-query.n3 ``` ## Browser Builds For convenience we provide deploy bundled versions of the eyereasoner on github pages which can be directly used in an HTML document as shown in [this example](https://github.com/eyereasoner/eye-js/tree/main/examples/prebuilt/index.html) which is also [deployed on github pages](https://eyereasoner.github.io/eye-js/example/index.html). There is a bundled version for each release - which can be found at the url: <p align=center> https://eyereasoner.github.io/eye-js/vMajor/vMinor/vPatch/index.js for instance v2.3.14 has the url https://eyereasoner.github.io/eye-js/2/3/14/index.js. We also have shortcuts for: - the latest version https://eyereasoner.github.io/eye-js/latest/index.js, - the latest of each major version https://eyereasoner.github.io/eye-js/vMajor/latest/index.js, and - the latest of each minor version https://eyereasoner.github.io/eye-js/vMajor/vMinor/latest/index.js Available versions can be browsed at https://github.com/eyereasoner/eye-js/tree/pages. Github also serves these files with a `gzip` content encoding which compresses the script to ~1.4MB when being served. ![](./github-transfer.png) ### Serving Files When self-hosting the bundled files, ensure your server includes `charset=utf-8` in the `Content-Type` header for JavaScript files: ``` Content-Type: text/javascript; charset=utf-8 ``` Without the charset, WASM streaming instantiation may fail in headless browsers with errors like: - Firefox: `CompileError: wasm validation error: at offset 642: byte size mismatch in type section` - Chromium: `CompileError: WebAssembly.instantiate(): section was shorter than expected size` Most static file servers (e.g., `express.static()`) set this automatically, but custom streaming handlers using `createReadStream().pipe(res)` may not. ### Dynamic imports We also distribute bundles that can be dynamically imported on github pages; for example ```ts const { eyereasoner } = await import('https://eyereasoner.github.io/eye-js/2/latest/dynamic-import.js'); // Instantiate a new SWIPL module and log any results it produces to the console const Module = await eyereasoner.SwiplEye({ print: (str) => { console.log(str) }, arguments: ['-q'] }); // Load the the strings data and query as files data.n3 and query.n3 into the module Module.FS.writeFile('data.n3', data); Module.FS.writeFile('query.n3', query); // Execute main(['--nope', '--quiet', './data.n3', '--query', './query.n3']). eyereasoner.queryOnce(Module, 'main', ['--nope', '--quiet', './data.n3', '--query', './query.n3']); ``` ## Using with Webpack When bundling `eyereasoner` with Webpack for the browser, the underlying Emscripten-generated code may import Node.js built-in modules using the `node:` scheme (e.g. `node:fs`, `node:crypto`). Webpack does not handle the `node:` scheme by default and will produce errors like: ``` Module build failed: UnhandledSchemeError: Reading from "node:fs" is not handled by plugins (Unhandled scheme). ``` To fix this, add the following to your `webpack.config.js`: ```js const { NormalModuleReplacementPlugin } = require('webpack'); module.exports = { // ...your existing config resolve: { fallback: { path: false, fs: false, crypto: false, perf_hooks: false, }, }, plugins: [ new NormalModuleReplacementPlugin(/^node:/, (resource) => { resource.request = resource.request.replace(/^node:/, ''); }), ], }; ``` The `NormalModuleReplacementPlugin` strips the `node:` prefix so that the imports are resolved by the `resolve.fallback` entries, which map them to `false` (i.e. empty modules) since they are not needed in the browser. ## Examples We provide some examples of using `eyereasoner`: - Using as an npm package and bundling using webpack ([`./examples/rollup`](https://github.com/eyereasoner/eye-js/tree/main/examples/rollup)). - Using a prebuilt version of `eyereasoner` ([`./examples/prebuilt`](https://github.com/eyereasoner/eye-js/tree/main/examples/prebuilt)) - this example is [deployed on github pages](https://eyereasoner.github.io/eye-js/example/index.html). ## Performance We use [benchmark.js](https://benchmarkjs.com/) to collect the performance results of some basic operations. Those results are published [here](https://eyereasoner.github.io/eye-js/dev/bench/). ## Experimental `linguareasoner` We have experimental support for RDF Lingua using the `linguareasoner`; similarly to `n3reasoner` it can be used with both string and quad input/output. For instance: ```ts import { linguareasoner } from 'eyereasoner'; const result = await linguareasoner(` # ------------------ # Socrates Inference # ------------------ # # Infer that Socrates is mortal. @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>. @prefix log: <http://www.w3.org/2000/10/swap/log#>. @prefix var: <http://www.w3.org/2000/10/swap/var#>. @prefix : <http://example.org/socrates#>. # facts :Socrates a :Human. :Human rdfs:subClassOf :Mortal. # rdfs subclass _:ng1 log:implies _:ng2. _:ng1 { var:A rdfs:subClassOf var:B. var:S a var:A. } _:ng2 { var:S a var:B. } # query _:ng3 log:query _:ng3. _:ng3 { var:S a :Mortal. }`) ``` ## Cite If you are using or extending eye-js as part of a scientific publication, we would appreciate a citation of our [zenodo artefact](https://zenodo.org/doi/10.5281/zenodo.12211023). ## License ©2022–present [Jesse Wright](https://github.com/jeswr), [Jos De Roo](https://github.com/josd/), [MIT License](https://github.com/eyereasoner/eye-js/blob/master/LICENSE).