From c3c9b5671ddaa9e1a6e8e6396253dc57de7eb081 Mon Sep 17 00:00:00 2001 From: Gary Arora Date: Tue, 14 Aug 2018 22:01:03 -0400 Subject: [PATCH] initial --- .DS_Store | Bin 0 -> 6148 bytes .gitignore | 61 ++++++++ .travis.yml | 12 ++ LICENSE | 21 +++ README.md | 259 +++++++++++++++++++++++++++++++ athena-client.js | 21 +++ lib/athenaExpress.js | 180 ++++++++++++++++++++++ lib/index.js | 8 + package-lock.json | 357 +++++++++++++++++++++++++++++++++++++++++++ package.json | 37 +++++ test/index.js | 15 ++ test/module.test.js | 24 +++ 12 files changed, 995 insertions(+) create mode 100644 .DS_Store create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 athena-client.js create mode 100644 lib/athenaExpress.js create mode 100644 lib/index.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 test/index.js create mode 100644 test/module.test.js diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..0076166fce2964b7588d3ab012c7fe238b21ae09 GIT binary patch literal 6148 zcmeHK%}T>S5Z-O8ZYyFBf<5lVTMsQ!dlEve2X8_|4=Qa!iw(r2G^s&rB(I@w8Djz(B1dJ3;P6sc$pj;EoFkriLF9q; z=1q+6uLFL2nFY*eo|k_8{=kouR_mRY%9XYC4IyfxwpF`v#!>1dZZeCz?(_<+GdCG~ zMn8|D;8SpRC$n7Dp8nyO@fID(MtOFtY&X*ZgM!&uGZXonD0QSH|o^SNam zG^J&=vZkDOI<2PMw+^$cDt7nUN2i0khsXI-_WYvxEzn!~iis3=jh=#eg{m#O6v>O~n!e#K2D&!2Ll&L$nR%8r9YT9bTW&UqM6x9p4g& z(x7cH*9aaEu2TVZDmPCIuG3*$nmF5Fu2H8mu2zP5%*w^%g{#$JT&i%!ZH?3u1H`~0 z17&U2@%%r5UuNkee=&tDVt^RpiV*6EfnQ+Y17%WAvH$=8 literal 0 HcmV?d00001 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e33bd7e --- /dev/null +++ b/.gitignore @@ -0,0 +1,61 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +example.js + diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..a382c78 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,12 @@ +trlanguage: "node_js" +node_js: + - "10" + - "9" + - "8" + - "7" + +before_install: + - "npm install" + +script: + - npm start \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7cc0dc0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Gary Arora + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a8c04b7 --- /dev/null +++ b/README.md @@ -0,0 +1,259 @@ + +# Athena-Express: Simplify SQL queries on Amazon Athena + +## Synopsis + +Athena-Express makes it easier to execute SQL queries on Amazon Athena and returns the response in a neatly formatted JSON array. + +It's lightweight (~4KB uncompressed) and has zero dependencies. + +**Example:** +```javascript +athenaExpress.executeQuery("SELECT * FROM movies LIMIT 3"); +``` +**Response:** +```javascript +[{name: "Mission: Impossible Fallout", year: "2018"}, + {name: "Captain America: Civil War", year: "2016"}, + {name: "Star Wars: The Last Jedi", year: "2017"}] +``` + + +## Motivation + +[Amazon Athena](https://aws.amazon.com/athena/), launched at AWS re:Invent 2016, made it easier to analyze data in Amazon S3 using standard SQL. Under the covers, it uses [Presto](https://prestodb.io/), which is an opensource SQL engine developed by Facebook in 2012 to query their 300 Petabyte data warehouse. It's incredibly powerful! + +**Good News** is that Amazon Athena combines the colossal strength of Presto with serverless & self-managed capabilities of AWS + +**Not So Good News** is that using Amazon Athena via the AWS' SDK requires too many moving parts to setup including the manual error handling. + +**Enter Athena-Express!** + +Athena-Express essentially bundles following steps as listed on the official [AWS Documentation](https://docs.aws.amazon.com/athena/latest/APIReference/Welcome.html): +1. Start a query execution +2. Keep checking until the said query has finished executing +3. Fetch the results of said query execution from Amazon S3 + +And as an added bonus +4. Format the results into a clean, friendly JSON array +5. Handle common Athena errors + + + +## Prerequisites + +- You will need an `IAM Role` (if executing from `AWS Lambda`) **OR** an `IAM User` with `accessKeyId` and `secretAccessKey` +- This IAM role/user must have at least `AmazonAthenaFullAccess` and `AmazonS3FullAccess` policies attached to its permissions + * As an alternative to granting `AmazonS3FullAccess` you could granularize the policy to a specific bucket that you must specify during Athena-Express initialization + + +## Configuration & Initialization options +#### Zero config mode: +In zero config mode, AthenaExpress creates a new `S3 bucket` in your AWS account for Amazon Athena to store the query results in. +```javascript +const AthenaExpress = require("athena-express"), + aws = require("aws-sdk"), + awsCredentials = { /* required */ + region: "STRING_VALUE", + accessKeyId: "STRING_VALUE", + secretAccessKey: "STRING_VALUE", + }; + +aws.config.update(awsCredentials); + +//Initializing AthenaExpress with zero configuration +const athenaExpress = new AthenaExpress({ aws }); +``` + + +#### Minimal config mode: (recommended) +In minimal config mode, you specify an `S3 bucket` in your AWS account for Amazon Athena to store the query results in. +```javascript +const AthenaExpress = require("athena-express"), + aws = require("aws-sdk"), + awsCredentials = { /* required */ + region: "STRING_VALUE", + accessKeyId: "STRING_VALUE", + secretAccessKey: "STRING_VALUE" + }; + +aws.config.update(awsCredentials); + +//AthenaExpress config object +const athenaExpressConfig = { + aws, /* required */ + s3: "STRING_VALUE" +}; + +//Initializing AthenaExpress with minimal configuration +const athenaExpress = new AthenaExpress(athenaExpressConfig); +``` +Minimum Config Parameters: +- `s3` - (String) S3 bucket name/prefix you want created in your AWS account. e.g. `s3://my-bucket-us-east-1` + + +#### Advance config mode: +All config options +```javascript +//AthenaExpress config object +const athenaExpressConfig = { + aws, /* required */ + s3: "STRING_VALUE", + formatJson: BOOLEAN, + retry: Integer, + db: "STRING_VALUE" +}; + +//Initializing AthenaExpress with all configuration options +const athenaExpress = new AthenaExpress(athenaExpressConfig); +``` +Advance Config Parameters: +- `s3` - (String) S3 bucket name/prefix you want created in your AWS account +- `formatJson` - (Boolean) default value is true. Override as false if you rather get the raw unformatted JSON from Athena +- `retry` - (Integer) default value is 200 (milliseconds) of interval to keep checking if the specific Athena query has finished executing +- `db` - (String) Set the Athena database name here to execute athena queries without needing to specify a `db` everytime during execution. + ```javascript + //So you can execute Athena queries simply by passing the SQL statement + athenaExpress.executeQuery("SELECT * FROM movies LIMIT 3"); + + //Instead of SQL & DB object + athenaExpress.executeQuery({ + sql: "SELECT * FROM movies LIMIT 3", + db: "moviedb" + }); + ``` + + +## Usage +###### Using Promises: +```javascript +let query = { + sql: "SELECT elb_name, request_port, request_ip FROM elb_logs LIMIT 3", /* required */ + db: "sampledb" /* assumes 'default' database if not specified here */ +}; + +athenaExpress + .executeQuery(query) + .then(results => { console.log(results); }) + .catch(error => { console.log(error); }); +``` + +###### Using Async/Await: +```javascript +(async () => { + let query = { + sql: "SELECT elb_name, request_port, request_ip FROM elb_logs LIMIT 3", /* required */ + db: "sampledb" /* assumes 'default' database if not specified here */ + }; + + try { + let results = await athenaExpress.executeQuery(query); + console.log(results); + } catch (error) { + console.log(error); + } +})(); +``` + +## Full Example +###### Using standard NodeJS application +```javascript +"use strict"; + +const AthenaExpress = require("athena-express"), + aws = require("aws-sdk"), + awsCredentials = { + region: "us-east-1", + accessKeyId: "AKIAIHV5B6DGMEXVCXGA", + secretAccessKey: "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" + }; + +aws.config.update(awsCredentials); + +const athenaExpressConfig = { + aws, + s3: "s3://my-bucket-for-storing-athena-results-us-east-1" +}; + +//Initializing AthenaExpress with minimal configuration +const athenaExpress = new AthenaExpress(athenaExpressConfig); + +//Invoking a query on Amazon Athena +(async () => { + let query = { + sql: "SELECT elb_name, request_port, request_ip FROM elb_logs LIMIT 3", + db: "sampledb" + }; + + try { + let results = await athenaExpress.executeQuery(query); + console.log(results); + } catch (error) { + console.log(error); + } +})(); +``` + +###### Using AWS Lambda +```javascript +"use strict"; + +const AthenaExpress = require("athena-express"), + aws = require("aws-sdk"); + + /* AWS Credentials are not required here + /* but the IAM Role assumed by this Lambda + /* must have the necessary permission to execute Athena queries + /* and store the result in Amazon S3 bucket */ + +const athenaExpressConfig = { + aws, + s3: "s3://my-bucket-for-storing-athena-results-us-east-1" +}; + +//Initializing AthenaExpress with minimal configuration +const athenaExpress = new AthenaExpress(athenaExpressConfig); + +exports.handler = async (event, context, callback) => { + let query = { + sql: "SELECT elb_name, request_port, request_ip FROM elb_logs LIMIT 3", + db: "sampledb" + }; + + try { + let results = await athenaExpress.executeQuery(query); + callback(null, results); + } catch (error) { + callback(error, null); + } +}; +``` + +###### Results: +```javascript + [{ + elb_name: "elb_demo_005", + request_port: "8222", + request_ip: "245.85.197.169" + }, + { + elb_name: "elb_demo_003", + request_port: "24615", + request_ip: "251.165.102.100" + }, + { + elb_name: "elb_demo_007", + request_port: "24251", + request_ip: "250.120.176.53" + }] +``` + + + +## Contributors + +[Gary Arora](https://twitter.com/AroraGary) + +## License + +MIT \ No newline at end of file diff --git a/athena-client.js b/athena-client.js new file mode 100644 index 0000000..1319364 --- /dev/null +++ b/athena-client.js @@ -0,0 +1,21 @@ +var clientConfig = { + bucketUri: "s3://aws-athena-query-results-919476868523-us-east-1" +}; + +var awsConfig = { + region: "us-east-1", + accessKeyId: "AKIAJZX5UJVWZ6DI7BFQ", + secretAccessKey: "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" +}; + +var athena = require("athena-client"); +var client = athena.createClient(clientConfig, awsConfig); + +console.time("Athena"); +client.execute("SHOW DATABASES", function(err, data) { + if (err) { + return console.error(err); + } + console.timeEnd("Athena"); + console.log(data); +}); diff --git a/lib/athenaExpress.js b/lib/athenaExpress.js new file mode 100644 index 0000000..c3f9d61 --- /dev/null +++ b/lib/athenaExpress.js @@ -0,0 +1,180 @@ +"use strict"; + +module.exports = class AthenaExpress { + constructor(init) { + validateConstructor(init); + this.config = { + athena: new init.aws.Athena({ apiVersion: "2017-05-18" }), + s3: + init.s3 || + `s3://athena-express-${init.aws.config.credentials.accessKeyId + .substring(0, 10) + .toLowerCase()}-${new Date().getFullYear()}`, + db: init.db || "default", + retry: Number(init.retry) || 200, + formatJson: init.formatJson === false ? false : true + }; + } + + async executeQuery(query) { + if (!this.config) + throw new TypeError("Config object not present in the constructor"); + + if (!query) throw new TypeError("SQL query is missing"); + + try { + const queryExecutionId = await startQueryExecution( + query, + this.config + ); + await checkIfExecutionCompleted(queryExecutionId, this.config); + const results = await getQueryResults( + queryExecutionId, + this.config + ); + return this.config.formatJson ? cleanUpResults(results) : results; + } catch (error) { + throw new Error(error); + } + } +}; + +async function startQueryExecution(query, config) { + const params = { + QueryString: query.sql || query, + ResultConfiguration: { + OutputLocation: config.s3 + }, + QueryExecutionContext: { + Database: query.db || config.db + } + }; + + let retry = 0; + + return new Promise(function(resolve, reject) { + let startQueryExecutionRecursively = async function() { + try { + let data = await config.athena + .startQueryExecution(params) + .promise(); + resolve(data.QueryExecutionId); + } catch (err) { + if ( + err.code === "TooManyRequestsException" || + err.code === "ThrottlingException" || + err.code === "NetworkingError" + ) { + setTimeout(() => { + startQueryExecutionRecursively(); + }, 2000); + } else { + reject(err); + } + } + }; + startQueryExecutionRecursively(); + }); +} + +async function checkIfExecutionCompleted(QueryExecutionId, config) { + let retry = config.retry; + + return new Promise(function(resolve, reject) { + let keepCheckingRecursively = async function() { + try { + let data = await config.athena + .getQueryExecution({ QueryExecutionId }) + .promise(); + if (data.QueryExecution.Status.State === "SUCCEEDED") { + retry = config.retry; + resolve(); + } else if (data.QueryExecution.Status.State === "FAILED") { + reject(data.QueryExecution.Status.StateChangeReason); + } else { + setTimeout(() => { + keepCheckingRecursively(); + }, retry); + } + } catch (err) { + if ( + err.code === "ThrottlingException" || + err.code === "NetworkingError" + ) { + retry = 2000; + setTimeout(() => { + keepCheckingRecursively(); + }, retry); + } else { + reject(err); + } + } + }; + keepCheckingRecursively(); + }); +} + +async function getQueryResults(QueryExecutionId, config) { + return new Promise(function(resolve, reject) { + let gettingQueryResultsRecursively = async function() { + try { + let queryResults = await config.athena + .getQueryResults({ QueryExecutionId }) + .promise(); + resolve(queryResults.ResultSet.Rows); + } catch (err) { + if ( + err.code === "ThrottlingException" || + err.code === "NetworkingError" + ) { + setTimeout(() => { + gettingQueryResultsRecursively(); + }, 2000); + } else { + reject(err); + } + } + }; + gettingQueryResultsRecursively(); + }); +} + +function cleanUpResults(results) { + if (!results.length) return results; + + let rowIterator = 1, + columnIterator = 0, + cleanedUpObject = {}, + cleanedUpResults = []; + + const rowCount = results.length, + fieldNames = results[0].Data, + columnCount = fieldNames.length; + + for (; rowIterator < rowCount; rowIterator++) { + for (; columnIterator < columnCount; columnIterator++) { + cleanedUpObject[ + Object.values(fieldNames[columnIterator])[0] + ] = Object.values(results[rowIterator].Data[columnIterator])[0]; + } + cleanedUpResults.push(cleanedUpObject); + cleanedUpObject = {}; + columnIterator = 0; + } + + return cleanedUpResults; +} + +function validateConstructor(init) { + if (!init) + throw new TypeError("Config object not present in the constructor"); + + try { + let aws = init.s3 ? init.s3 : init.aws.config.credentials.accessKeyId; + let athena = new init.aws.Athena({ apiVersion: "2017-05-18" }); + } catch { + throw new TypeError( + "AWS object not present or incorrect in the constructor" + ); + } +} diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..5a99272 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,8 @@ +// Load modules. +var AthenaExpress = require("./athenaExpress"); + +// Expose Strategy. +exports = module.exports = AthenaExpress; + +// Exports. +exports.AthenaExpress = AthenaExpress; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..be83917 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,357 @@ +{ + "name": "athena-express", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "aws-sdk": { + "version": "2.291.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.291.0.tgz", + "integrity": "sha512-al/oJuKN2mokw4nafloptGrmdzm4UZ8AeLuM2bFtPmb0nvwajOr/vSa/XhQTw+T07Vm9T8iJyWyqygq74E/YMQ==", + "dev": true, + "requires": { + "buffer": "4.9.1", + "events": "1.1.1", + "ieee754": "1.1.8", + "jmespath": "0.15.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "uuid": "3.1.0", + "xml2js": "0.4.19" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base64-js": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "buffer": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "dev": true, + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "chai": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", + "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", + "dev": true, + "requires": { + "assertion-error": "^1.0.1", + "check-error": "^1.0.1", + "deep-eql": "^3.0.0", + "get-func-name": "^2.0.0", + "pathval": "^1.0.0", + "type-detect": "^4.0.0" + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "ieee754": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", + "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "jmespath": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", + "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "dev": true, + "requires": { + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "supports-color": "5.4.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "dev": true + }, + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=", + "dev": true + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "uuid": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", + "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "dev": true, + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + } + }, + "xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..4a49e5c --- /dev/null +++ b/package.json @@ -0,0 +1,37 @@ +{ + "name": "athena-express", + "version": "0.0.1", + "description": + "Athena-Express makes it easier to execute SQL queries on Amazon Athena by consolidating & astracting several methods in the AWS SDK", + "main": "./lib/index.js", + "scripts": { + "test": "node ./node_modules/mocha/bin/mocha", + "start": "npm test" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/ghdna/athena-express.git" + }, + "keywords": [ + "aws", + "athena", + "amazon", + "amazon", + "web", + "services", + "sql", + "database" + ], + "author": "Gary Arora (https://twitter.com/AroraGary)", + "license": "MIT", + "bugs": { + "url": "https://github.com/ghdna/athena-express/issues" + }, + "homepage": "https://github.com/ghdna/athena-express#readme", + "devDependencies": { + "aws-sdk": "^2.291.0", + "chai": "^4.1.2", + "mocha": "^5.2.0" + }, + "dependencies": {} +} diff --git a/test/index.js b/test/index.js new file mode 100644 index 0000000..ae32035 --- /dev/null +++ b/test/index.js @@ -0,0 +1,15 @@ +"use strict"; + +const expect = require("chai").expect, + athenaExpress = require(".."); + +describe("Repository Structure", function() { + it("should export AthenaExpress constructor directly from package", function() { + expect(athenaExpress).to.be.a("function"); + expect(athenaExpress).to.equal(athenaExpress.AthenaExpress); + }); + + it("should export AthenaExpress constructor", function() { + expect(athenaExpress.AthenaExpress).to.be.a("function"); + }); +}); diff --git a/test/module.test.js b/test/module.test.js new file mode 100644 index 0000000..13817eb --- /dev/null +++ b/test/module.test.js @@ -0,0 +1,24 @@ +"use strict"; + +const chai = require("chai"), + expect = chai.expect, + AthenaExpress = require(".."), + aws = require("aws-sdk"); + +chai.should(); + +describe("Negative Scenarios", () => { + it("should not have config object undefined", function() { + expect(function() { + new AthenaExpress(); + }).to.throw(TypeError, "Config object not present in the constructor"); + }); + it("should not have aws object undefined", function() { + expect(function() { + const athenaExpress = new AthenaExpress({}); + }).to.throw( + TypeError, + "AWS object not present or incorrect in the constructor" + ); + }); +});