-
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
4 changed files
with
266 additions
and
197 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 |
---|---|---|
@@ -0,0 +1,48 @@ | ||
{ | ||
"extends": "airbnb-base", | ||
"rules": { | ||
"arrow-parens": [2, "as-needed"], | ||
"brace-style": [2, "stroustrup", { "allowSingleLine": true }], | ||
"comma-dangle": [1, { | ||
"arrays": "always-multiline", | ||
"objects": "always-multiline", | ||
"imports": "always-multiline", | ||
"exports": "always-multiline", | ||
"functions": "ignore", | ||
}], | ||
"consistent-return": 0, | ||
"default-case": 0, | ||
"eol-last": 0, # Belongs to editorconfig | ||
"global-require": 0, | ||
"guard-for-in": 0, | ||
"max-len": 0, | ||
"newline-before-return": 2, | ||
"no-confusing-arrow": 0, | ||
"no-console": 0, | ||
"no-mixed-operators": 0, | ||
"no-multi-spaces": 1, | ||
"no-nested-ternary": 0, | ||
"no-new": 0, | ||
"no-param-reassign": 0, | ||
"no-plusplus": 0, | ||
"no-restricted-syntax": 0, | ||
"no-return-assign": 0, | ||
"no-self-compare": 0, # For NaN check | ||
"no-shadow": 0, | ||
"no-trailing-spaces": 0, # Belongs to editorconfig | ||
"no-underscore-dangle": 0, | ||
"no-unused-expressions": 0, | ||
"no-unused-vars": 1, | ||
"no-use-before-define": [2, { "functions": false, "classes": false }], | ||
"object-curly-spacing": 1, | ||
"one-var-declaration-per-line": 0, | ||
"one-var": [2, { "uninitialized": "always", "initialized": "never" }], | ||
"padded-blocks": 0, | ||
"semi": 1, | ||
"space-in-parens": 1, | ||
"spaced-comment": 0, | ||
"import/no-dynamic-require": 0, | ||
"import/no-extraneous-dependencies": [2, { "devDependencies": true }], | ||
"import/no-unresolved": [2, { "ignore": ['graphql'] }], | ||
} | ||
} |
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,204 +1,21 @@ | ||
/* eslint-disable no-shadow */ | ||
const request = require('request'); | ||
const createRdfParser = require('n3').Parser; | ||
const { host, port, namespace } = require('../config'); | ||
const methods = require('./src'); | ||
|
||
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 defaultOptions = { | ||
host: 'localhost', | ||
port: 9999, | ||
namespace: 'kb', | ||
}; | ||
|
||
const params = encodeQuad(input); | ||
function getClient(options = defaultOptions) { | ||
const { host, port, namespace } = options; | ||
const blazegraphUrl = `http://${host}:${port}/blazegraph/namespace/${namespace}/sparql`; | ||
const passUrl = fn => (...args) => fn(blazegraphUrl, ...args); | ||
|
||
if (!params) throw new Error('You almost deleted the whole database!'); | ||
const bindedMethods = {}; | ||
|
||
return makeRequest({ | ||
url: `${blazegraphUrl}?${params}`, | ||
method: 'DELETE', | ||
}); | ||
} | ||
for (const methodName in methods) bindedMethods[methodName] = passUrl(methods[methodName]); | ||
|
||
// 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', | ||
}); | ||
return bindedMethods; | ||
} | ||
|
||
module.exports = { | ||
readQuads, | ||
createQuads, | ||
updateQuad, | ||
updateSparql, | ||
deleteQuads, | ||
deleteSparql, | ||
}; | ||
module.exports = getClient; |
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
Oops, something went wrong.