From 1da336619e191334773fe6c7dde6b4dc63b832bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=A9rault?= Date: Fri, 2 Dec 2016 16:05:42 +0100 Subject: [PATCH] expose main method --- .eslintrc | 48 ++++++++++++ index.js | 211 ++++----------------------------------------------- package.json | 5 ++ src/index.js | 199 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 266 insertions(+), 197 deletions(-) create mode 100644 .eslintrc create mode 100644 src/index.js diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..9a4c65e --- /dev/null +++ b/.eslintrc @@ -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'] }], + } +} diff --git a/index.js b/index.js index d6736a1..1ceb86e 100644 --- a/index.js +++ b/index.js @@ -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?: - predicate?: - object?: or "Literal" - graph?: - graphs?: [] -} -*/ -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: - predicate: - object: or "Literal" - graph: -} -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: - predicate: - object: or "Literal", from the old statement - newObject: or "Literal", defines the new statement - graph: -} -*/ -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?: - predicate?: - object?: or "Literal" - graph?: -} -*/ -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; diff --git a/package.json b/package.json index bdac255..b5e6830 100644 --- a/package.json +++ b/package.json @@ -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" } } diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..a81026d --- /dev/null +++ b/src/index.js @@ -0,0 +1,199 @@ +/* eslint-disable no-shadow */ +const request = require('request'); +const createRdfParser = require('n3').Parser; + +const nquadsMimeType = '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?: + predicate?: + object?: or "Literal" + graph?: + graphs?: [] +} +*/ +function readQuads(blazegraphUrl, 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: + predicate: + object: or "Literal" + graph: +} +Input can also be an array of quads +*/ +function createQuads(blazegraphUrl, 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: + predicate: + object: or "Literal", from the old statement + newObject: or "Literal", defines the new statement + graph: +} +*/ +function updateQuad(blazegraphUrl, 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(blazegraphUrl, 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?: + predicate?: + object?: or "Literal" + graph?: +} +*/ +function deleteQuads(blazegraphUrl, 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(blazegraphUrl, 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, +};