-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
242 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,15 @@ | ||
# blazegraph-js | ||
Blazegraph JavaScript API | ||
# Blazegraph-js | ||
|
||
*[Blazegraph](https://www.blazegraph.com/) JavaScript API* | ||
|
||
# Note | ||
|
||
Unstable, do not use in production! | ||
|
||
The repo won't be public until the first production-ready release. | ||
|
||
# LICENSE | ||
|
||
Blazegraph-js is released under the MIT license. | ||
|
||
[Blazegraph](https://www.blazegraph.com/) is freely available under the GPLv2 open-source license. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
/* eslint-disable no-shadow */ | ||
const request = require('request'); | ||
const createRdfParser = require('n3').Parser; | ||
const { host, port, namespace } = require('../config'); | ||
|
||
const blazegraphUrl = `http://${host}:${port}/blazegraph/namespace/${namespace}/sparql`; | ||
const nquadsMimeType = 'text/x-nquads'; | ||
|
||
// const headers = { 'Content-Type': 'text/x-nquads' }; | ||
|
||
const parser = createRdfParser({ format: 'N-Quads' }); | ||
|
||
/* ---------- | ||
UTILS | ||
---------- */ | ||
|
||
// Simple promise wrapper around the 'request' library | ||
function makeRequest(options) { | ||
return new Promise((resolve, reject) => { | ||
request(options, (error, response, body) => (error || response.statusCode !== 200 ? reject : resolve)(body || error)); | ||
}); | ||
} | ||
|
||
// Validation functions | ||
function validateInput(input) { | ||
if (!(input && typeof input === 'object')) throw new Error('Malformed input'); | ||
|
||
return input; | ||
} | ||
|
||
function isNonEmptyString(value) { | ||
return value && typeof value === 'string'; | ||
} | ||
|
||
// URI encodes a SPARQL query | ||
function encodeQuery(query) { | ||
return encodeURIComponent(query.replace('\n', ' ').replace('\t', '')); | ||
} | ||
|
||
// URI encodes a quad (complete or not) | ||
function encodeQuad({ subject, predicate, object, graph }) { | ||
let url = ''; | ||
|
||
if (isNonEmptyString(subject)) url += `&s=${encodeURIComponent(subject)}`; | ||
if (isNonEmptyString(predicate)) url += `&p=${encodeURIComponent(predicate)}`; | ||
if (isNonEmptyString(object)) url += `&o=${encodeURIComponent(object)}`; | ||
if (isNonEmptyString(graph)) url += `&c=${encodeURIComponent(graph)}`; | ||
|
||
return url.slice(1); | ||
} | ||
|
||
// Serializes a quad into the nquad format (has to be complete) | ||
// NOTE: this middleware enforces the usage of named graph on every triple | ||
function serializeQuad({ subject, predicate, object, graph }) { | ||
|
||
if (!(isNonEmptyString(subject) && isNonEmptyString(predicate) && isNonEmptyString(object) && isNonEmptyString(graph))) { | ||
throw new Error(`Malformed input: ${JSON.stringify({ subject, predicate, object, graph }, null, 2)}`); | ||
} | ||
|
||
return `${subject} ${predicate} ${object} ${graph} .`; | ||
} | ||
|
||
/* --------------- | ||
MIDDLEWARE | ||
--------------- */ | ||
|
||
/* | ||
Read all quads matching a pattern | ||
{ | ||
subject?: <IRI> | ||
predicate?: <IRI> | ||
object?: <IRI> or "Literal" | ||
graph?: <IRI> | ||
graphs?: [<IRI>] | ||
} | ||
*/ | ||
function readQuads(input) { | ||
validateInput(input); | ||
|
||
let fullUrl = `${blazegraphUrl}?GETSTMTS&includeInferred=false&${encodeQuad(input)}`; | ||
|
||
if (Array.isArray(input.graphs)) input.graphs.forEach(g => fullUrl += `&c=${g}`); | ||
|
||
return makeRequest(fullUrl) | ||
.then(nquads => new Promise((resolve, reject) => { | ||
const quads = []; | ||
|
||
parser.parse(nquads, (error, triple) => { | ||
if (error) return reject(error); | ||
if (triple) return quads.push(triple); | ||
|
||
resolve(quads); | ||
}); | ||
})); | ||
} | ||
|
||
/* | ||
Create one or more quads | ||
{ | ||
subject: <IRI> | ||
predicate: <IRI> | ||
object: <IRI> or "Literal" | ||
graph: <IRI> | ||
} | ||
Input can also be an array of quads | ||
*/ | ||
function createQuads(input) { | ||
return makeRequest({ | ||
url: blazegraphUrl, | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': nquadsMimeType, | ||
}, | ||
body: (Array.isArray(input) ? input : [input]) | ||
.map(quad => serializeQuad(validateInput(quad))) | ||
.join('\n'), | ||
}); | ||
} | ||
|
||
/* | ||
Update a quad knowing its old statement | ||
{ | ||
subject: <IRI> | ||
predicate: <IRI> | ||
object: <IRI> or "Literal", from the old statement | ||
newObject: <IRI> or "Literal", defines the new statement | ||
graph: <IRI> | ||
} | ||
*/ | ||
function updateQuad(input) { | ||
validateInput(input); | ||
|
||
const oldQuad = serializeQuad(input); | ||
const newQuad = serializeQuad(Object.assign({}, input, { object: input.newObject })); | ||
const options = { contentType: nquadsMimeType }; | ||
|
||
return makeRequest({ | ||
url: `${blazegraphUrl}?updatePost`, | ||
method: 'POST', | ||
formData: { | ||
remove: { | ||
options, | ||
value: oldQuad, | ||
}, | ||
add: { | ||
options, | ||
value: newQuad, | ||
}, | ||
}, | ||
}); | ||
} | ||
|
||
// Perform a SPARQL update query | ||
// NOTE: this does not allow to perform any SPARQL query | ||
function updateSparql(query) { | ||
if (!isNonEmptyString(query)) throw new Error('Query must be a non-empty string'); | ||
|
||
return makeRequest({ | ||
url: `${blazegraphUrl}?update=${encodeQuery(query)}`, | ||
method: 'POST', | ||
}); | ||
} | ||
|
||
/* | ||
Delete all quads matching a pattern | ||
{ | ||
subject?: <IRI> | ||
predicate?: <IRI> | ||
object?: <IRI> or "Literal" | ||
graph?: <IRI> | ||
} | ||
*/ | ||
function deleteQuads(input) { | ||
validateInput(input); | ||
|
||
const params = encodeQuad(input); | ||
|
||
if (!params) throw new Error('You almost deleted the whole database!'); | ||
|
||
return makeRequest({ | ||
url: `${blazegraphUrl}?${params}`, | ||
method: 'DELETE', | ||
}); | ||
} | ||
|
||
// Delete statements using a SPARQL CONSTRUCT or DESCRIBE query | ||
// NOTE: this does not allow to perform any SPARQL query | ||
function deleteSparql(query) { | ||
if (!isNonEmptyString(query)) throw new Error('Query must be a non-empty string'); | ||
|
||
return makeRequest({ | ||
url: `${blazegraphUrl}?query=${encodeQuery(query)}`, | ||
method: 'DELETE', | ||
}); | ||
} | ||
|
||
module.exports = { | ||
readQuads, | ||
createQuads, | ||
updateQuad, | ||
updateSparql, | ||
deleteQuads, | ||
deleteSparql, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
{ | ||
"name": "blazegraph", | ||
"version": "0.0.0", | ||
"description": "Blazegraph JavaScript API", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "echo \"Error: no test specified\" && exit 1" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/nelson-ai/blazegraph-js.git" | ||
}, | ||
"author": "Nelson a.i.", | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/nelson-ai/blazegraph-js/issues" | ||
}, | ||
"homepage": "https://github.com/nelson-ai/blazegraph-js#readme", | ||
"dependencies": { | ||
"n3": "^0.8.3", | ||
"request": "^2.79.0" | ||
} | ||
} |