slicerjs
Version:
Official JavaScript client for SlicingDice, Data Warehouse and Analytics Database as a Service.
763 lines (692 loc) • 25.5 kB
JavaScript
(function(){
'use strict'
// add http module to make requests
let https = require('https');
const url = require('url');
// add api mapped errors to client
let mappedErrors = require('./mappedErrors');
// add all errors
let errors = require('./errors');
// RequesterBrowser make http requests from the browser
class RequesterBrowser {
run(token, url, reqType, data = null, sql) {
let content_type;
if (sql) {
content_type = 'application/sql';
} else {
content_type = 'application/json';
}
return new Promise(function(resolve, reject) {
let req = new XMLHttpRequest();
req.open(reqType, url, true);
req.setRequestHeader("Authorization", token);
req.setRequestHeader('Content-Type', content_type);
req.setRequestHeader('Accept', "application/json");
req.onload = function() {
if (req.status == 200) {
// Resolve the promise with the response text
resolve(req.response);
}
else {
reject(Error(req.statusText));
}
};
// Handle network errors
req.onerror = function() {
reject(Error("Network Error"));
};
req.send(sql ? data : JSON.stringify(data));
});
}
}
// RequesterNode make http requests from the node.js (with we're not running in a web-browser)
class RequesterNode {
run(token, urlReq, reqType, data = null, sql) {
return new Promise((resolve, reject) => {
let port;
let jsonToSend;
let urlData = url.parse(urlReq);
if (urlData.port === null){
port = 443;
} else{
port = urlData.port;
}
let content_type;
if (sql) {
content_type = 'application/sql';
} else {
content_type = 'application/json';
}
let headers = {
'Content-type': content_type,
'Authorization': token
}
if (data !== null && !sql) {
jsonToSend = JSON.stringify(data);
headers['Content-Length'] = Buffer.byteLength(jsonToSend);
} else {
jsonToSend = data;
}
let options = {
hostname: urlData.hostname,
port: port,
path: urlData.path,
method: reqType,
headers: headers,
rejectUnauthorized: false,
};
let req = https.request(options, function(response){
// temporary data holder
const body = [];
// on every content chunk, push it to the data array
response.on('data', (data) => body.push(data));
// we are done, resolve promise with those joined chunks
response.on('end', () => {
resolve(body.join(''));
});
});
if (reqType == "POST" || reqType == "PUT"){
req.write(jsonToSend);
}
req.on('error', (err) => reject(err));
req.end();
});
}
}
// VALIDATORS
class SDBaseQueryValidator {
constructor(query) {
this.query = query;
}
}
// Validator for saved queries
class SavedQueryValidator extends SDBaseQueryValidator {
constructor(query) {
super(query)
this.listQueryTypes = [
"count/entity", "count/event", "count/entity/total",
"aggregation", "top_values"];
}
// Check if saved query has valid type
_has_valid_type(){
let typeQuery = this.query.type;
if (!this.listQueryTypes.includes(typeQuery)) {
throw new errors.InvalidQueryTypeError("The saved query has an invalid type(" + typeQuery + ")");
}
return true;
}
// If saved query is valid this returns true
validator() {
return this._has_valid_type();
}
}
// Validator for count queries
class QueryCountValidator extends SDBaseQueryValidator {
constructor(query){
super(query)
}
// If count query is valid this returns true
validator() {
if (Object.keys(this.query).length > 10) {
throw new errors.MaxLimitError("The query count entity has a limit of 10 queries per request.");
}
return true;
}
}
// Validator for top values queries
class QueryTopValuesValidator extends SDBaseQueryValidator{
constructor(query) {
super(query)
}
// Check query limit
_exceedsQueriesLimit() {
if (Object.keys(this.query).length > 5) {
return true;
}
return false;
}
// Check columns limit
_exceedsColumnsLimit() {
for(let key in this.query) {
let column = this.query[key];
if (Object.keys(column).length > 5){
throw new errors.MaxLimitError("The query " + column + " exceeds the limit of columns per query in request");
}
}
}
// Check contains limit
_exceedsValuesContainsLimit() {
for (let key in this.query){
let query = this.query[key];
if (query.hasOwnProperty("contains") && query["contains"].length > 5) {
throw new errors.MaxLimitError("The query " + query + " exceeds the limit of contains per query in request");
}
}
}
// if top values query is valid this returns true, otherwise false
validator() {
this._exceedsColumnsLimit();
this._exceedsValuesContainsLimit();
if (!this._exceedsQueriesLimit()){
return true
}
return false;
}
}
// Validator for score or result queries
class QueryDataExtractionValidator extends SDBaseQueryValidator{
constructor(query) {
super(query)
}
// Check if data extraction query is valid
validKeys() {
for(let key in this.query) {
let value = this.query[key];
// Check columns property, columns should have a maximum of 10 itens
if (key == "columns") {
if (value.constructor != Array) {
if (value != 'all') {
throw new errors.InvalidQueryException("The key 'columns' in query has a invalid value.");
}
}
else {
if (value.length > 10) {
throw new errors.InvalidQueryException("The key 'columns' in data extraction result must have up to 10 columns.");
}
}
}
// Check limit property, limit should be less or equal than 100
if (key == "limit") {
if (value.constructor != Number){
throw new errors.InvalidQueryError("The key 'limit' in query has a invalid value.");
}
}
}
return true;
}
// If data extraction query is valid this returns true
validator() {
return this.validKeys();
}
}
// Validator for column
class ColumnValidator extends SDBaseQueryValidator{
constructor(query) {
super(query)
}
// Check column name
validateName(query) {
if (!query.hasOwnProperty("name")) {
throw new errors.InvalidColumnDescriptionError("The column's name can't be empty/None.");
}
else {
let name = query["name"];
if (name.length > 80) {
throw new errors.InvalidColumnDescriptionError("The column's name have a very big content. (Max: 80 chars)");
}
}
}
// Check column description
validateDescription(query) {
let description = query.description;
if (description.length > 80){
throw new errors.InvalidColumnDescriptionError("The column's description have a very big content. (Max: 300chars)");
}
}
// Check column type
validateColumnType(query) {
// The column should have a type property
if (!query.hasOwnProperty("type")){
throw new errors.InvalidColumnError("The column should have a type.");
}
}
// If column is decimal check if it has decimal or decimal-time-series type
validateDecimalType(query) {
let decimal_types = ["decimal", "decimal-time-series"];
if (!decimal_types.includes(query["decimal-place"])) {
throw new errors.InvalidColumnError("This column is only accepted on type 'decimal' or 'decimal-time-series'");
}
}
// Check if string column is valid
checkStrTypeIntegrity(query) {
if (!query.hasOwnProperty("cardinality")){
throw new errors.InvalidColumnError("The column with type string should have 'cardinality' key.");
}
}
// Check if enumerated column is valid
validateEnumeratedType(query) {
if (!query.hasOwnProperty("range")){
throw new errors.InvalidColumnError("The 'enumerated' type needs of the 'range' parameter.");
}
}
// If column is valid this returns true
validator() {
if (this.query instanceof Array) {
for (let i = 0; i < this.query.length; i++) {
this.validateColumn(this.query[i]);
}
} else {
this.validateColumn(this.query);
}
return true;
}
validateColumn(query) {
this.validateName(query);
this.validateColumnType(query);
if (query["type"] === "string") {
this.checkStrTypeIntegrity(query);
}
if (query["type"] === "enumerated") {
this.validateEnumeratedType(query);
}
if (query.hasOwnProperty("description")) {
this.validateDescription(query);
}
if (query.hasOwnProperty("decimal-place")) {
this.validateDecimalType(query);
}
}
}
// Class to handle response from Slicing Dice API
class SlicerResponse {
constructor(jsonResponse) {
this.jsonResponse = JSON.parse(jsonResponse);
}
_raiseErrors(error) {
let codeError = error['code'];
if (mappedErrors[codeError] === undefined){
throw new errors.SlicingDiceClientError(error);
} else {
throw new Error(error);
}
}
requestSuccessful(){
if (this.jsonResponse["errors"] !== undefined){
this._raiseErrors(this.jsonResponse["errors"][0]);
}
return true;
}
}
class SlicingDice{
constructor(apiKeys) {
this._key = apiKeys;
this._checkKey(apiKeys);
this._sdRoutes = {
column: '/column/',
insert: '/insert/',
countEntity: '/query/count/entity/',
countEntityTotal: '/query/count/entity/total/',
countEvent: '/query/count/event/',
aggregation: '/query/aggregation/',
topValues: '/query/top_values/',
existsEntity: '/query/exists/entity/',
result: '/data_extraction/result/',
score: '/data_extraction/score/',
saved: '/query/saved/',
database: '/database/',
sql: '/sql/',
delete: '/delete/',
update: '/update/'
};
this._setUpRequest();
}
get sdAddress() {
return this.BASE_URL;
}
set sdAddress(value){
this.BASE_URL = value;
}
_checkKey(key) {
if (!key.hasOwnProperty("writeKey") && !key.hasOwnProperty("readKey") && !key.hasOwnProperty("masterKey") && !key.hasOwnProperty("customKey")) {
throw new errors.InvalidKeyError("The keys aren't valid.");
}
}
_setUpRequest() {
// Check if this script is running on a web-browser
if (typeof window === 'undefined') {
this.requester = new RequesterNode();
// Get the base URL on an enviroment variable
this.BASE_URL = this._getEnviromentSDAddress();
}
else{
this.requester = new RequesterBrowser();
this.BASE_URL = "https://api.slicingdice.com/v1";
}
}
_getEnviromentSDAddress() {
let sdAddress = process.env.SD_API_ADDRESS;
if (sdAddress === undefined){
return "https://api.slicingdice.com/v1";
}
else {
return sdAddress;
}
}
_getCurrentKey(){
if (this._key.hasOwnProperty("masterKey"))
return [this._key["masterKey"], 2];
else if(this._key.hasOwnProperty("customKey"))
return [this._key["customKey"], 2];
else if(this._key.hasOwnProperty("writeKey"))
return [this._key["writeKey"], 1];
else
return [this._key["readKey"], 0];
}
_getAPIKey(levelKey){
let currentLevelKey = this._getCurrentKey();
if (currentLevelKey[1] == 2){
return currentLevelKey[0];
}
else if (currentLevelKey[1] != levelKey){
throw new errors.InvalidKeyError("This key is not allowed to perform this operation.")
}
return currentLevelKey[0];
}
/* Make request to Slicing Dice API
*/
makeRequest(objRequest, sql = false) {
let token = this._getAPIKey(objRequest.levelKey);
let urlReq = this.BASE_URL + objRequest.path;
let requestMethods = ["POST", "PUT", "GET", "DELETE", "PATCH"];
if (requestMethods.indexOf(objRequest.reqType) === -1){
throw new errors.InvalidMethodRequestError('This method request is invalid.');
}
let req = this.requester.run(
token,
urlReq,
objRequest.reqType,
objRequest.data,
sql);
return req.then((resp) => {
let slicerResponse = new SlicerResponse(resp);
slicerResponse.requestSuccessful();
return JSON.parse(resp);
}, (err) => { return err;});
}
/* Get information about current database */
getDatabase(){
let path = this._sdRoutes.database;
return this.makeRequest({
path: path,
reqType: "GET",
levelKey: 2
});
}
/* Get all columns */
getColumns(){
let path = this._sdRoutes.column;
return this.makeRequest({
path: path,
reqType: "GET",
levelKey: 2
});
}
/* Get all saved queries */
getSavedQueries() {
let path = this._sdRoutes.saved;
return this.makeRequest({
path: path,
reqType: "GET",
levelKey: 2
});
}
/* Delete a saved query
*
* @param (string) name - the name of the saved query that will be deleted
*/
deleteSavedQuery(name) {
let path = this._sdRoutes.saved + name;
return this.makeRequest({
path: path,
reqType: "DELETE",
levelKey: 2
});
}
/* Get saved query by name
*
* @param (string) name - the name of the saved query that will be retrieved
*/
getSavedQuery(name) {
let path = this._sdRoutes.saved + name;
return this.makeRequest({
path: path,
reqType: "GET",
levelKey: 0
});
}
/* Send a insert command to the Slicing Dice API
*
* @param (array) query - the query to send to Slicing Dice API
*/
insert(query){
let path = this._sdRoutes.insert;
return this.makeRequest({
path: path,
reqType: "POST",
data: query,
levelKey: 1
});
}
/* Create a column on Slicing Dice API
*
* @param (array) query - the query to send to Slicing Dice API
*/
createColumn(query){
let path = this._sdRoutes.column;
let sdValidator = new ColumnValidator(query);
if (sdValidator.validator()){
return this.makeRequest({
path: path,
reqType: "POST",
data: query,
levelKey: 1
});
}
}
/* Makes a count query on Slicing Dice API
*
* @param (array) query - the query to send to Slicing Dice API
* @param (string) path - the path to send the query (count entity or count event path)
*/
countQueryWrapper(query, path){
let sdValidator = new QueryCountValidator(query);
if (sdValidator.validator()){
return this.makeRequest({
path: path,
reqType: "POST",
data: query,
levelKey: 0
});
}
}
/* Makes a count entity query on Slicing Dice API
*
* @param (array) query - the query to send to Slicing Dice API
*/
countEntity(query) {
let path = this._sdRoutes.countEntity;
let sdValidator = new QueryCountValidator(query);
return this.countQueryWrapper(query, path);
}
/* Makes a total query on Slicing Dice API
*
* @param (array) dimensions - the dimensions in which the total query will be performed
*/
countEntityTotal(dimensions = []) {
let query = {
"dimensions": dimensions
};
let path = this._sdRoutes.countEntityTotal;
return this.makeRequest({
path: path,
reqType: "POST",
data: query,
levelKey: 0
})
}
/* Makes a count event query on Slicing Dice API
*
* @param (array) query - the query to send to Slicing Dice API
*/
countEvent(query) {
let path = this._sdRoutes.countEvent;
return this.countQueryWrapper(query, path);
}
/* Makes a exists query on Slicing Dice API
*
* @param (array) ids - the array of IDs to check
* @param (string) dimension - the dimension to check for IDs
*/
existsEntity(ids, dimension = null) {
if (ids.constructor != Array) {
throw new errors.WrongTypeError("This method should receive an array as parameter");
}
if (ids.length > 100) {
throw new errors.MaxLimitError("The query exists entity must have up to 100 ids.");
}
let path = this._sdRoutes.existsEntity;
let query = {
"ids": ids
}
if (dimension) {
query["dimension"] = dimension
}
return this.makeRequest({
path: path,
reqType: "POST",
data: query,
levelKey: 0
});
}
/* Makes an aggregation query on Slicing Dice API
*
* @param (array) query - the query to send to Slicing Dice API
*/
aggregation(query){
let path = this._sdRoutes.aggregation;
return this.makeRequest({
path: path,
reqType: "POST",
data: query,
levelKey: 0
});
}
/* Makes a top values query on Slicing Dice API
*
* @param (array) query - the query to send to Slicing Dice API
*/
topValues(query) {
let path = this._sdRoutes.topValues;
let sdValidator = new QueryTopValuesValidator(query);
if (sdValidator.validator()){
return this.makeRequest({
path: path,
reqType: "POST",
data: query,
levelKey: 0
});
}
}
/* Create a saved query on Slicing Dice API
*
* @param (array) query - the query to send to Slicing Dice API
*/
createSavedQuery(query) {
let path = this._sdRoutes.saved;
let sdValidator = new SavedQueryValidator(query);
if (sdValidator.validator()){
return this.makeRequest({
path: path,
reqType: "POST",
data: query,
levelKey: 2
});
}
}
/* Update a previous saved query on Slicing Dice API
*
* @param (string) name - the name of the saved query to update
* @param (array) query - the query to send to Slicing Dice API
*/
updateSavedQuery(name, query) {
let path = this._sdRoutes.saved + name;
return this.makeRequest({
path: path,
reqType: "PUT",
data: query,
levelKey: 2
});
}
/* Makes a data extraction query (result or score) on Slicing Dice API
*
* @param (array) query - the query to send to Slicing Dice API
* @param (string) path - the path to send the query (result or score path)
*/
dataExtractionWrapper(query, path) {
let sdValidator = new QueryDataExtractionValidator(query);
if (sdValidator.validator()){
return this.makeRequest({
path: path,
reqType: "POST",
data: query,
levelKey: 0
});
}
}
/* Makes a result query on Slicing Dice API
*
* @param (array) query - the query to send to Slicing Dice API
*/
result(query) {
let path = this._sdRoutes.result;
return this.dataExtractionWrapper(query, path);
}
/* Makes a score query on Slicing Dice API
*
* @param (array) query - the query to send to Slicing Dice API
*/
score(query) {
let path = this._sdRoutes.score;
return this.dataExtractionWrapper(query, path);
}
sql(query) {
let path = this._sdRoutes.sql;
return this.makeRequest({
path: path,
reqType: "POST",
data: query,
levelKey: 0
}, true);
}
/* Make a delete request
*
* @param (array) query - The query that represents the data to be deleted
*/
delete(query) {
let path = this._sdRoutes.delete;
return this.makeRequest({
path: path,
reqType: "POST",
data: query,
levelKey: 2
});
}
/* Make a update request
*
* @param (array) query - The query that represents the data to be updated
*/
update(query) {
let path = this._sdRoutes.update;
return this.makeRequest({
path: path,
reqType: "POST",
data: query,
levelKey: 2
});
}
}
module.exports = SlicingDice;
if (typeof window !== 'undefined'){
window.SlicingDice = SlicingDice;
}
}());