JS: Moves JS terms constuctors to the package root

pull/171/head
Tpt 3 years ago
parent 3ca149fa97
commit 5d3a909105
  1. 78
      js/README.md
  2. 7
      js/src/lib.rs
  3. 101
      js/src/model.rs
  4. 27
      js/src/store.rs
  5. 4
      js/test/model.js

@ -28,12 +28,11 @@ const oxigraph = require('oxigraph');
Insert the triple `<http://example/> <http://schema.org/name> "example"` and log the name of `<http://example/>` in SPARQL: Insert the triple `<http://example/> <http://schema.org/name> "example"` and log the name of `<http://example/>` in SPARQL:
```js ```js
const { MemoryStore } = require('oxigraph'); const oxigraph = require('oxigraph');
const store = new MemoryStore(); const store = new oxigraph.Store();
const dataFactory = store.dataFactory; const ex = oxigraph.namedNode("http://example/");
const ex = dataFactory.namedNode("http://example/"); const schemaName = oxigraph.namedNode("http://schema.org/name");
const schemaName = dataFactory.namedNode("http://schema.org/name"); store.add(oxigraph.triple(ex, schemaName, oxigraph.literal("example")));
store.add(dataFactory.triple(ex, schemaName, dataFactory.literal("example")));
for (binding of store.query("SELECT ?name WHERE { <http://example/> <http://schema.org/name> ?name }")) { for (binding of store.query("SELECT ?name WHERE { <http://example/> <http://schema.org/name> ?name }")) {
console.log(binding.get("name").value); console.log(binding.get("name").value);
} }
@ -42,38 +41,44 @@ for (binding of store.query("SELECT ?name WHERE { <http://example/> <http://sche
## API ## API
Oxigraph currently provides a simple JS API. Oxigraph currently provides a simple JS API.
It is centered around the `MemoryStore` class.
The `NamedNode`, `BlankNode`, `Literal`, `DefaultGraph`, `Variable`, `Quad` and `DataFactory` types ### RDF data model
are following the [RDF/JS datamodel specification](https://rdf.js.org/data-model-spec/).
Oxigraph implements the [RDF/JS datamodel specification](https://rdf.js.org/data-model-spec/).
To import `MemoryStore` using Node: For that, the `oxigraph` module implements the [RDF/JS `DataFactory` interface](http://rdf.js.org/data-model-spec/#datafactory-interface).
Example:
```js ```js
const { MemoryStore } = require('oxigraph'); const oxigraph = require('oxigraph');
const ex = oxigraph.namedNode("http://example.com");
const blank = oxigraph.blankNode();
const foo = oxigraph.literal("foo");
const quad = oxigraph.quad(blank, ex, foo);
``` ```
### `MemoryStore` ### `Store`
Oxigraph API is centered around the `Store` class.
A store contains an [RDF dataset](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-dataset) and allows to query and update them using SPARQL.
#### `Store(optional sequence<Quad>? quads)` (constructor)
Creates a new store.
#### `MemoryStore(optional sequence<Quad>? quads)` (constructor)
```js ```js
const store = new MemoryStore(); const oxigraph = require('oxigraph');
const store = new oxigraph.Store();
``` ```
If provided, the `MemoryStore` will be initialized with a sequence of quads. If provided, the `Store` will be initialized with a sequence of quads.
#### `MemoryStore.dataFactory`
Returns a `DataFactory` following [RDF/JS datamodel specification](https://rdf.js.org/data-model-spec/).
Example:
```js ```js
const store = new MemoryStore(); const oxigraph = require('oxigraph');
const ex = store.dataFactory.namedNode("http://example.com"); const store = new oxigraph.Store([oxigraph.quad(blank, ex, foo)]);
const blank = store.dataFactory.blankNode();
const foo = store.dataFactory.literal("foo");
const quad = store.dataFactory.quad(blank, ex, foo);
``` ```
#### `MemoryStore.prototype.add(Quad quad)` #### `Store.prototype.add(Quad quad)`
Inserts a quad in the store. Inserts a quad in the store.
Example: Example:
@ -81,7 +86,7 @@ Example:
store.add(quad); store.add(quad);
``` ```
#### `MemoryStore.prototype.delete(Quad quad)` #### `Store.prototype.delete(Quad quad)`
Removes a quad from the store. Removes a quad from the store.
Example: Example:
@ -89,7 +94,7 @@ Example:
store.delete(quad); store.delete(quad);
``` ```
#### `MemoryStore.prototype.has(Quad quad)` #### `Store.prototype.has(Quad quad)`
Returns a boolean stating if the store contains the quad. Returns a boolean stating if the store contains the quad.
Example: Example:
@ -97,12 +102,12 @@ Example:
store.has(quad); store.has(quad);
``` ```
#### `MemoryStore.prototype.match(optional Term? subject, optional Term? predicate, optional Term? object, optional Term? graph)` #### `Store.prototype.match(optional Term? subject, optional Term? predicate, optional Term? object, optional Term? graph)`
Returns an array with all the quads matching a given quad pattern. Returns an array with all the quads matching a given quad pattern.
Example to get all quads in the default graph with `ex` for subject: Example to get all quads in the default graph with `ex` for subject:
```js ```js
store.match(ex, null, null, store.dataFactory.defaultGraph()); store.match(ex, null, null, oxigraph.defaultGraph());
``` ```
Example to get all quads: Example to get all quads:
@ -110,7 +115,7 @@ Example to get all quads:
store.match(); store.match();
``` ```
#### `MemoryStore.prototype.query(String query)` #### `Store.prototype.query(String query)`
Executes a [SPARQL 1.1 Query](https://www.w3.org/TR/sparql11-query/). Executes a [SPARQL 1.1 Query](https://www.w3.org/TR/sparql11-query/).
For `SELECT` queries the return type is an array of `Map` which keys are the bound variables and values are the values the result is bound to. For `SELECT` queries the return type is an array of `Map` which keys are the bound variables and values are the values the result is bound to.
For `CONSTRUCT` and `ÐESCRIBE` queries the return type is an array of `Quad`. For `CONSTRUCT` and `ÐESCRIBE` queries the return type is an array of `Quad`.
@ -125,7 +130,7 @@ for (binding of store.query("SELECT DISTINCT ?s WHERE { ?s ?p ?o }")) {
Example of CONSTRUCT query: Example of CONSTRUCT query:
```js ```js
const filteredStore = new MemoryStore(store.query("CONSTRUCT { <http:/example.com/> ?p ?o } WHERE { <http:/example.com/> ?p ?o }")); const filteredStore = new oxigraph.Store(store.query("CONSTRUCT { <http:/example.com/> ?p ?o } WHERE { <http:/example.com/> ?p ?o }"));
``` ```
Example of ASK query: Example of ASK query:
@ -135,7 +140,7 @@ if (store.query("ASK { ?s ?s ?s }")) {
} }
``` ```
#### `MemoryStore.prototype.update(String query)` #### `Store.prototype.update(String query)`
Executes a [SPARQL 1.1 Update](https://www.w3.org/TR/sparql11-update/). Executes a [SPARQL 1.1 Update](https://www.w3.org/TR/sparql11-update/).
The [`LOAD` operation](https://www.w3.org/TR/sparql11-update/#load) is not supported yet. The [`LOAD` operation](https://www.w3.org/TR/sparql11-update/#load) is not supported yet.
@ -144,7 +149,7 @@ Example of update:
store.update("DELETE WHERE { <http://example.com/s> ?p ?o }") store.update("DELETE WHERE { <http://example.com/s> ?p ?o }")
``` ```
### `MemoryStore.prototype.load(String data, String mimeType, NamedNode|String? baseIRI, NamedNode|BlankNode|DefaultGraph? toNamedGraph)` #### `Store.prototype.load(String data, String mimeType, NamedNode|String? baseIRI, NamedNode|BlankNode|DefaultGraph? toNamedGraph)`
Loads serialized RDF triples or quad into the store. Loads serialized RDF triples or quad into the store.
The method arguments are: The method arguments are:
@ -162,11 +167,10 @@ The available formats are:
Example of loading a Turtle file into the named graph `<http://example.com/graph>` with the base IRI `http://example.com`: Example of loading a Turtle file into the named graph `<http://example.com/graph>` with the base IRI `http://example.com`:
```js ```js
store.load("<http://example.com> <http://example.com> <> .", "text/turtle", "http://example.com", store.dataFactory.namedNode("http://example.com/graph")); store.load("<http://example.com> <http://example.com> <> .", "text/turtle", "http://example.com", oxigraph.namedNode("http://example.com/graph"));
``` ```
#### `Store.prototype.dump(String mimeType, NamedNode|BlankNode|DefaultGraph? fromNamedGraph)`
### `MemoryStore.prototype.dump(String mimeType, NamedNode|BlankNode|DefaultGraph? fromNamedGraph)`
Returns serialized RDF triples or quad from the store. Returns serialized RDF triples or quad from the store.
The method arguments are: The method arguments are:
@ -182,7 +186,7 @@ The available formats are:
Example of building a Turtle file from the named graph `<http://example.com/graph>`: Example of building a Turtle file from the named graph `<http://example.com/graph>`:
```js ```js
store.dump("text/turtle", store.dataFactory.namedNode("http://example.com/graph")); store.dump("text/turtle", oxigraph.namedNode("http://example.com/graph"));
``` ```
## How to contribute ## How to contribute

@ -1,3 +1,10 @@
use wasm_bindgen::prelude::*;
mod model; mod model;
mod store; mod store;
mod utils; mod utils;
#[wasm_bindgen(start)]
pub fn main() {
console_error_panic_hook::set_once();
}

@ -7,37 +7,32 @@ use std::convert::TryFrom;
use std::sync::Arc; use std::sync::Arc;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
#[wasm_bindgen(js_name = DataFactory)] thread_local! {
#[derive(Default)] pub static FROM_JS: FromJsConverter = FromJsConverter::default();
pub struct JsDataFactory {
from_js: FromJsConverter,
} }
#[wasm_bindgen(js_class = DataFactory)] #[wasm_bindgen(js_name = namedNode)]
impl JsDataFactory { pub fn named_node(value: String) -> Result<JsNamedNode, JsValue> {
#[wasm_bindgen(js_name = namedNode)]
pub fn named_node(&self, value: String) -> Result<JsNamedNode, JsValue> {
NamedNode::new(value) NamedNode::new(value)
.map(|v| v.into()) .map(|v| v.into())
.map_err(|v| UriError::new(&v.to_string()).into()) .map_err(|v| UriError::new(&v.to_string()).into())
} }
#[wasm_bindgen(js_name = blankNode)] #[wasm_bindgen(js_name = blankNode)]
pub fn blank_node(&self, value: Option<String>) -> Result<JsBlankNode, JsValue> { pub fn blank_node(value: Option<String>) -> Result<JsBlankNode, JsValue> {
Ok(if let Some(value) = value { Ok(if let Some(value) = value {
BlankNode::new(value).map_err(to_err)? BlankNode::new(value).map_err(to_err)?
} else { } else {
BlankNode::default() BlankNode::default()
} }
.into()) .into())
} }
#[wasm_bindgen] #[wasm_bindgen]
pub fn literal( pub fn literal(
&self,
value: Option<String>, value: Option<String>,
language_or_datatype: &JsValue, language_or_datatype: &JsValue,
) -> Result<JsLiteral, JsValue> { ) -> Result<JsLiteral, JsValue> {
if language_or_datatype.is_null() || language_or_datatype.is_undefined() { if language_or_datatype.is_null() || language_or_datatype.is_undefined() {
Ok(Literal::new_simple_literal(value.unwrap_or_else(String::new)).into()) Ok(Literal::new_simple_literal(value.unwrap_or_else(String::new)).into())
} else if language_or_datatype.is_string() { } else if language_or_datatype.is_string() {
@ -47,64 +42,56 @@ impl JsDataFactory {
) )
.map_err(to_err)? .map_err(to_err)?
.into()) .into())
} else if let JsTerm::NamedNode(datatype) = self.from_js.to_term(language_or_datatype)? { } else if let JsTerm::NamedNode(datatype) = FROM_JS.with(|c| c.to_term(language_or_datatype))? {
Ok(Literal::new_typed_literal(value.unwrap_or_else(String::new), datatype).into()) Ok(Literal::new_typed_literal(value.unwrap_or_else(String::new), datatype).into())
} else { } else {
Err(format_err!("The literal datatype should be a NamedNode")) Err(format_err!("The literal datatype should be a NamedNode"))
} }
} }
#[wasm_bindgen(js_name = defaultGraph)] #[wasm_bindgen(js_name = defaultGraph)]
pub fn default_graph(&self) -> JsDefaultGraph { pub fn default_graph() -> JsDefaultGraph {
JsDefaultGraph {} JsDefaultGraph {}
} }
#[wasm_bindgen(js_name = variable)] #[wasm_bindgen(js_name = variable)]
pub fn variable(&self, value: String) -> Result<JsVariable, JsValue> { pub fn variable(value: String) -> Result<JsVariable, JsValue> {
Ok(Variable::new(value).map_err(to_err)?.into()) Ok(Variable::new(value).map_err(to_err)?.into())
} }
#[wasm_bindgen(js_name = triple)] #[wasm_bindgen(js_name = triple)]
pub fn triple( pub fn triple(subject: &JsValue, predicate: &JsValue, object: &JsValue) -> Result<JsQuad, JsValue> {
&self, quad(subject, predicate, object, &JsValue::UNDEFINED)
subject: &JsValue, }
predicate: &JsValue,
object: &JsValue,
) -> Result<JsQuad, JsValue> {
self.quad(subject, predicate, object, &JsValue::UNDEFINED)
}
#[wasm_bindgen(js_name = quad)] #[wasm_bindgen(js_name = quad)]
pub fn quad( pub fn quad(
&self,
subject: &JsValue, subject: &JsValue,
predicate: &JsValue, predicate: &JsValue,
object: &JsValue, object: &JsValue,
graph: &JsValue, graph: &JsValue,
) -> Result<JsQuad, JsValue> { ) -> Result<JsQuad, JsValue> {
Ok(self Ok(FROM_JS
.from_js .with(|c| c.to_quad_from_parts(subject, predicate, object, graph))?
.to_quad_from_parts(subject, predicate, object, graph)?
.into()) .into())
} }
#[wasm_bindgen(js_name = fromTerm)] #[wasm_bindgen(js_name = fromTerm)]
pub fn convert_term(&self, original: &JsValue) -> Result<JsValue, JsValue> { pub fn from_term(original: &JsValue) -> Result<JsValue, JsValue> {
Ok(if original.is_null() { Ok(if original.is_null() {
JsValue::NULL JsValue::NULL
} else { } else {
self.from_js.to_term(original)?.into() FROM_JS.with(|c| c.to_term(original))?.into()
}) })
} }
#[wasm_bindgen(js_name = fromQuad)] #[wasm_bindgen(js_name = fromQuad)]
pub fn convert_quad(&self, original: &JsValue) -> Result<JsValue, JsValue> { pub fn from_quad(original: &JsValue) -> Result<JsValue, JsValue> {
Ok(if original.is_null() { Ok(if original.is_null() {
JsValue::NULL JsValue::NULL
} else { } else {
JsQuad::from(self.from_js.to_quad(original)?).into() JsQuad::from(FROM_JS.with(|c| c.to_quad(original))?).into()
}) })
}
} }
#[wasm_bindgen(js_name = NamedNode)] #[wasm_bindgen(js_name = NamedNode)]
@ -340,7 +327,7 @@ impl JsVariable {
pub fn equals(&self, other: &JsValue) -> bool { pub fn equals(&self, other: &JsValue) -> bool {
if let Ok(Some(JsTerm::Variable(other))) = if let Ok(Some(JsTerm::Variable(other))) =
FromJsConverter::default().to_optional_term(&other) FromJsConverter::default().to_optional_term(other)
{ {
self == &other self == &other
} else { } else {
@ -400,7 +387,7 @@ impl JsQuad {
} }
pub fn equals(&self, other: &JsValue) -> bool { pub fn equals(&self, other: &JsValue) -> bool {
if let Ok(Some(JsTerm::Quad(other))) = FromJsConverter::default().to_optional_term(&other) { if let Ok(Some(JsTerm::Quad(other))) = FromJsConverter::default().to_optional_term(other) {
self == &other self == &other
} else { } else {
false false
@ -724,7 +711,7 @@ impl FromJsConverter {
} }
"DefaultGraph" => Ok(JsTerm::DefaultGraph(JsDefaultGraph {})), "DefaultGraph" => Ok(JsTerm::DefaultGraph(JsDefaultGraph {})),
"Variable" => Ok(Variable::new( "Variable" => Ok(Variable::new(
&Reflect::get(&value, &self.value)? &Reflect::get(value, &self.value)?
.as_string() .as_string()
.ok_or_else(|| format_err!("Variable should have a string value"))?, .ok_or_else(|| format_err!("Variable should have a string value"))?,
) )
@ -763,10 +750,10 @@ impl FromJsConverter {
pub fn to_quad(&self, value: &JsValue) -> Result<Quad, JsValue> { pub fn to_quad(&self, value: &JsValue) -> Result<Quad, JsValue> {
self.to_quad_from_parts( self.to_quad_from_parts(
&Reflect::get(&value, &self.subject)?, &Reflect::get(value, &self.subject)?,
&Reflect::get(&value, &self.predicate)?, &Reflect::get(value, &self.predicate)?,
&Reflect::get(&value, &self.object)?, &Reflect::get(value, &self.object)?,
&Reflect::get(&value, &self.graph)?, &Reflect::get(value, &self.graph)?,
) )
} }
@ -784,7 +771,7 @@ impl FromJsConverter {
graph_name: if graph_name.is_undefined() { graph_name: if graph_name.is_undefined() {
GraphName::DefaultGraph GraphName::DefaultGraph
} else { } else {
GraphName::try_from(self.to_term(&graph_name)?)? GraphName::try_from(self.to_term(graph_name)?)?
}, },
}) })
} }

@ -13,7 +13,6 @@ use wasm_bindgen::prelude::*;
#[wasm_bindgen(js_name = Store)] #[wasm_bindgen(js_name = Store)]
pub struct JsStore { pub struct JsStore {
store: Store, store: Store,
from_js: FromJsConverter,
} }
#[wasm_bindgen(js_class = Store)] #[wasm_bindgen(js_class = Store)]
@ -24,7 +23,6 @@ impl JsStore {
let store = Self { let store = Self {
store: Store::new().map_err(to_err)?, store: Store::new().map_err(to_err)?,
from_js: FromJsConverter::default(),
}; };
if let Some(quads) = quads { if let Some(quads) = quads {
for quad in quads.iter() { for quad in quads.iter() {
@ -34,28 +32,23 @@ impl JsStore {
Ok(store) Ok(store)
} }
#[wasm_bindgen(js_name = dataFactory, getter)]
pub fn data_factory(&self) -> JsDataFactory {
JsDataFactory::default()
}
pub fn add(&self, quad: &JsValue) -> Result<(), JsValue> { pub fn add(&self, quad: &JsValue) -> Result<(), JsValue> {
self.store self.store
.insert(&self.from_js.to_quad(quad)?.into()) .insert(&FROM_JS.with(|c| c.to_quad(quad))?)
.map_err(to_err)?; .map_err(to_err)?;
Ok(()) Ok(())
} }
pub fn delete(&self, quad: &JsValue) -> Result<(), JsValue> { pub fn delete(&self, quad: &JsValue) -> Result<(), JsValue> {
self.store self.store
.remove(&self.from_js.to_quad(quad)?) .remove(&FROM_JS.with(|c| c.to_quad(quad))?)
.map_err(to_err)?; .map_err(to_err)?;
Ok(()) Ok(())
} }
pub fn has(&self, quad: &JsValue) -> Result<bool, JsValue> { pub fn has(&self, quad: &JsValue) -> Result<bool, JsValue> {
self.store self.store
.contains(&self.from_js.to_quad(quad)?) .contains(&FROM_JS.with(|c| c.to_quad(quad))?)
.map_err(to_err) .map_err(to_err)
} }
@ -75,28 +68,28 @@ impl JsStore {
Ok(self Ok(self
.store .store
.quads_for_pattern( .quads_for_pattern(
if let Some(subject) = self.from_js.to_optional_term(subject)? { if let Some(subject) = FROM_JS.with(|c| c.to_optional_term(subject))? {
Some(subject.try_into()?) Some(subject.try_into()?)
} else { } else {
None None
} }
.as_ref() .as_ref()
.map(|t: &NamedOrBlankNode| t.into()), .map(|t: &NamedOrBlankNode| t.into()),
if let Some(predicate) = self.from_js.to_optional_term(predicate)? { if let Some(predicate) = FROM_JS.with(|c| c.to_optional_term(predicate))? {
Some(NamedNode::try_from(predicate)?) Some(NamedNode::try_from(predicate)?)
} else { } else {
None None
} }
.as_ref() .as_ref()
.map(|t: &NamedNode| t.into()), .map(|t: &NamedNode| t.into()),
if let Some(object) = self.from_js.to_optional_term(object)? { if let Some(object) = FROM_JS.with(|c| c.to_optional_term(object))? {
Some(object.try_into()?) Some(object.try_into()?)
} else { } else {
None None
} }
.as_ref() .as_ref()
.map(|t: &Term| t.into()), .map(|t: &Term| t.into()),
if let Some(graph_name) = self.from_js.to_optional_term(graph_name)? { if let Some(graph_name) = FROM_JS.with(|c| c.to_optional_term(graph_name))? {
Some(graph_name.try_into()?) Some(graph_name.try_into()?)
} else { } else {
None None
@ -158,7 +151,7 @@ impl JsStore {
None None
} else if base_iri.is_string() { } else if base_iri.is_string() {
base_iri.as_string() base_iri.as_string()
} else if let JsTerm::NamedNode(base_iri) = self.from_js.to_term(base_iri)? { } else if let JsTerm::NamedNode(base_iri) = FROM_JS.with(|c| c.to_term(base_iri))? {
Some(base_iri.value()) Some(base_iri.value())
} else { } else {
return Err(format_err!( return Err(format_err!(
@ -167,7 +160,7 @@ impl JsStore {
}; };
let to_graph_name = let to_graph_name =
if let Some(graph_name) = self.from_js.to_optional_term(to_graph_name)? { if let Some(graph_name) = FROM_JS.with(|c| c.to_optional_term(to_graph_name))? {
Some(graph_name.try_into()?) Some(graph_name.try_into()?)
} else { } else {
None None
@ -198,7 +191,7 @@ impl JsStore {
pub fn dump(&self, mime_type: &str, from_graph_name: &JsValue) -> Result<String, JsValue> { pub fn dump(&self, mime_type: &str, from_graph_name: &JsValue) -> Result<String, JsValue> {
let from_graph_name = let from_graph_name =
if let Some(graph_name) = self.from_js.to_optional_term(from_graph_name)? { if let Some(graph_name) = FROM_JS.with(|c| c.to_optional_term(from_graph_name))? {
Some(graph_name.try_into()?) Some(graph_name.try_into()?)
} else { } else {
None None

@ -1,2 +1,2 @@
const { Store } = require('../pkg/oxigraph.js') const oxigraph = require('../pkg/oxigraph.js')
require('../node_modules/@rdfjs/data-model/test/index.js')((new Store()).dataFactory) require('../node_modules/@rdfjs/data-model/test/index.js')(oxigraph)

Loading…
Cancel
Save