Skip to content

Commit

Permalink
expose main method
Browse files Browse the repository at this point in the history
  • Loading branch information
dherault committed Dec 2, 2016
1 parent 2514900 commit 1da3366
Show file tree
Hide file tree
Showing 4 changed files with 266 additions and 197 deletions.
48 changes: 48 additions & 0 deletions .eslintrc
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'] }],
}
}
211 changes: 14 additions & 197 deletions index.js
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;
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,10 @@
"dependencies": {
"n3": "^0.8.3",
"request": "^2.79.0"
},
"devDependencies": {
"eslint": "^3.11.1",
"eslint-config-airbnb-base": "^10.0.1",
"eslint-plugin-import": "^2.2.0"
}
}
Loading

0 comments on commit 1da3366

Please sign in to comment.