diff --git a/.gitignore b/.gitignore index 23b8d3d..50288e9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ node_modules/ build/ +libs/ npm-debug.log +libpg_query/**/*.a +libpg_query/**/*.h diff --git a/.travis.yml b/.travis.yml index e2641fa..7463a2f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,6 @@ language: node_js node_js: - - "0.12" - - "4" - - "5" + - "8" addons: apt: sources: diff --git a/CHANGELOG.md b/CHANGELOG.md index 506e7d7..89d3605 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +# 1.2.1 +* Rename package + +# 1.2.0 +* Add TS typings + +# 1.1.2 (07/23/19) +* Fix build script + +# 1.1.0 (Republish, 07/23/19) +* Rewrite to use Node-Addons-API (C++ wrapper for the stable N-API) +* Support PL/PGSql Parsing +* Support async parsing + ## 0.0.5 (November 19, 2015) * Update libpg_query to include latest A_CONST fixes diff --git a/README.md b/README.md index 04e3559..03faed1 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,42 @@ -# pg-query-native [![Build Status](https://travis-ci.org/zhm/node-pg-query-native.svg?branch=master)](https://travis-ci.org/zhm/node-pg-query-native) +# @sqlutils/parse -The real PostgreSQL parser for nodejs. +The real PostgreSQL parser, exposed for nodejs. This is based on the output of [libpg_query](https://github.com/lfittl/libpg_query). This wraps the static library output and links it into a node module for use in js. All credit for the hard problems goes to [Lukas Fittl](https://github.com/lfittl). +## Requirements + +Install node-gyp globally + +```sh +npm install node-gyp -g +``` + ## Installation ```sh -npm install pg-query-native-latest +npm install @sqlutils/parse ``` ### Documentation -### `query.parse(query)` +### `query.parseQuery(sql)`/`parseQuerySync` -Parses the query and returns the parse tree. +Parses the sql and returns a Promise for the parse tree (or returns the parse tree directly in the sync version). May reject with/throw a parse error. -### Parameters +The return value is an array, as multiple queries may be provided in a single string (semicolon-delimited, as Postgres expects). -| parameter | type | description | -| -------------------- | ------------------ | --------------------------------------------------------- | -| `query` | String | SQL query | +### `query.parsePlPgSQL(funcsSql)`/`query.parsePlPgSQLSync(funcsSql)` -Returns an object in the format: - -``` -{ query: , - error: { message: , - fileName: , - lineNumber: , - cursorPosition: } -``` +Parses the contents of a PL/PGSql function, from a `CREATE FUNCTION` declaration, and returns a Promise for the parse tree (or returns the parse tree directly in the sync version). May reject with/throw a parse error. ## Example ```js -var parse = require('pg-query-native').parse; - -console.log(parse('select 1').query); +const parser = require('@sqlutils/parse'); +parser.parseQuery('select 1').then(console.log); ``` ## Related diff --git a/binding.gyp b/binding.gyp index 9e1798a..e9116df 100644 --- a/binding.gyp +++ b/binding.gyp @@ -1,21 +1,40 @@ { "targets": [ { - "target_name": "pg-query", - "sources": [ "pg-query.cc", "functions.cc" ], - "include_dirs" : [ - " hash = Nan::New(); - - if (result.error) { - v8::Local error = Nan::New(); - - v8::Local message = Nan::New(result.error->message).ToLocalChecked(); - v8::Local fileName = Nan::New(result.error->filename).ToLocalChecked(); - v8::Local functionName = Nan::New(result.error->funcname).ToLocalChecked(); - v8::Local lineNumber = Nan::New(result.error->lineno); - v8::Local cursorPosition = Nan::New(result.error->cursorpos); - - Nan::Set(error, Nan::New("message").ToLocalChecked(), message); - Nan::Set(error, Nan::New("fileName").ToLocalChecked(), fileName); - Nan::Set(error, Nan::New("functionName").ToLocalChecked(), functionName); - Nan::Set(error, Nan::New("lineNumber").ToLocalChecked(), lineNumber); - Nan::Set(error, Nan::New("cursorPosition").ToLocalChecked(), cursorPosition); - - if (result.error->context) { - Nan::Set(error, Nan::New("context").ToLocalChecked(), Nan::New(result.error->context).ToLocalChecked()); - } - else { - Nan::Set(error, Nan::New("context").ToLocalChecked(), Nan::Null()); - } - - Nan::Set(hash, Nan::New("error").ToLocalChecked(), error); - } - - if (result.parse_tree) { - Nan::Set(hash, Nan::New("query").ToLocalChecked(), - Nan::New(result.parse_tree).ToLocalChecked()); - } - - if (result.stderr_buffer) { - Nan::Set(hash, Nan::New("stderr").ToLocalChecked(), - Nan::New(result.stderr_buffer).ToLocalChecked()); - } - - pg_query_free_parse_result(result); - - info.GetReturnValue().Set(hash); -} diff --git a/functions.h b/functions.h deleted file mode 100644 index f9eb2d8..0000000 --- a/functions.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef __PG_QUERY_FUNCTIONS_H__ -#define __PG_QUERY_FUNCTIONS_H__ - -#include -#include - -NAN_METHOD(parse); - -#endif diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..f55d65f --- /dev/null +++ b/index.d.ts @@ -0,0 +1,4 @@ +export function parseQuery(sql: string): Promise; +export function parsePlPgSQL(funcsSql: string): Promise; +export function parseQuerySync(sql: string): any; +export function parsePlPgSQLSync(funcsSql: string): any; diff --git a/index.js b/index.js index 2efd98c..078e737 100644 --- a/index.js +++ b/index.js @@ -1,25 +1,27 @@ -var PgQuery = require('bindings')('pg-query'); +const PgQuery = require('./build/Release/queryparser'); module.exports = { - parse: function(query) { - var result = PgQuery.parse(query); + parseQuery(query) { + return new Promise((resolve, reject) => { + PgQuery.parseQueryAsync(query, (err, result) => { + err ? reject(err) : resolve(JSON.parse(result)); + }); + }); + }, - if (result.query) { - result.query = JSON.parse(result.query); - } + parsePlPgSQL(query) { + return new Promise((resolve, reject) => { + PgQuery.parsePlPgSQLAsync(query, (err, result) => { + err ? reject(err) : resolve(JSON.parse(result)); + }); + }); + }, - if (result.error) { - var err = new Error(result.error.message); + parseQuerySync(query) { + return JSON.parse(PgQuery.parseQuerySync(query)); + }, - err.fileName = result.error.fileName; - err.lineNumber = result.error.lineNumber; - err.cursorPosition = result.error.cursorPosition; - err.functionName = result.error.functionName; - err.context = result.error.context; - - result.error = err; - } - - return result; + parsePlPgSQLSync(query) { + return JSON.parse(PgQuery.parsePlPgSQLSync(query)); } }; diff --git a/libpg_query/include/.gitkeep b/libpg_query/include/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/libpg_query/include/pg_query.h b/libpg_query/include/pg_query.h deleted file mode 100644 index 6745cb8..0000000 --- a/libpg_query/include/pg_query.h +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef PG_QUERY_H -#define PG_QUERY_H - -typedef struct { - char* message; // exception message - char* funcname; // source function of exception (e.g. SearchSysCache) - char* filename; // source of exception (e.g. parse.l) - int lineno; // source of exception (e.g. 104) - int cursorpos; // char in query at which exception occurred - char* context; // additional context (optional, can be NULL) -} PgQueryError; - -typedef struct { - char* parse_tree; - char* stderr_buffer; - PgQueryError* error; -} PgQueryParseResult; - -typedef struct { - char* plpgsql_funcs; - PgQueryError* error; -} PgQueryPlpgsqlParseResult; - -typedef struct { - char* hexdigest; - char* stderr_buffer; - PgQueryError* error; -} PgQueryFingerprintResult; - -typedef struct { - char* normalized_query; - PgQueryError* error; -} PgQueryNormalizeResult; - -#ifdef __cplusplus -extern "C" { -#endif - -PgQueryNormalizeResult pg_query_normalize(const char* input); -PgQueryParseResult pg_query_parse(const char* input); -PgQueryPlpgsqlParseResult pg_query_parse_plpgsql(const char* input); - -PgQueryFingerprintResult pg_query_fingerprint(const char* input); - -void pg_query_free_normalize_result(PgQueryNormalizeResult result); -void pg_query_free_parse_result(PgQueryParseResult result); -void pg_query_free_plpgsql_parse_result(PgQueryPlpgsqlParseResult result); -void pg_query_free_fingerprint_result(PgQueryFingerprintResult result); - -// Postgres version information -#define PG_VERSION "10.0" -#define PG_MAJORVERSION "10" -#define PG_VERSION_NUM 100000 - -// Deprecated APIs below - -void pg_query_init(void); // Deprecated as of 9.5-1.4.1, this is now run automatically as needed - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/libpg_query/linux/.gitkeep b/libpg_query/linux/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/libpg_query/linux/libpg_query.a b/libpg_query/linux/libpg_query.a deleted file mode 100644 index 182ae70..0000000 Binary files a/libpg_query/linux/libpg_query.a and /dev/null differ diff --git a/libpg_query/osx/.gitkeep b/libpg_query/osx/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/libpg_query/osx/libpg_query.a b/libpg_query/osx/libpg_query.a deleted file mode 100644 index 7067b42..0000000 Binary files a/libpg_query/osx/libpg_query.a and /dev/null differ diff --git a/libpg_query/windows/.gitkeep b/libpg_query/windows/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..962e087 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,855 @@ +{ + "name": "@sqlutils/parse", + "version": "1.2.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "ajv": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "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 + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "requires": { + "inherits": "~2.0.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "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 + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "chai": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", + "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=", + "dev": true, + "requires": { + "assertion-error": "^1.0.1", + "deep-eql": "^0.1.3", + "type-detect": "^1.0.0" + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "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=" + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "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": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", + "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", + "dev": true, + "requires": { + "type-detect": "0.1.1" + }, + "dependencies": { + "type-detect": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", + "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", + "dev": true + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "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 + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "requires": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + } + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "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" + } + }, + "graceful-fs": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.0.tgz", + "integrity": "sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg==" + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "requires": { + "mime-db": "1.40.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "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=" + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "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" + }, + "dependencies": { + "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" + } + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node-addon-api": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.6.3.tgz", + "integrity": "sha512-FXWH6mqjWgU8ewuahp4spec8LkroFZK2NicOv6bNwZC3kcwZUI8LeZdG80UzTSLLhK4T7MsgNwlYDVRlDdfTDg==" + }, + "node-gyp": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz", + "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==", + "requires": { + "fstream": "^1.0.0", + "glob": "^7.0.3", + "graceful-fs": "^4.1.2", + "mkdirp": "^0.5.0", + "nopt": "2 || 3", + "npmlog": "0 || 1 || 2 || 3 || 4", + "osenv": "0", + "request": "^2.87.0", + "rimraf": "2", + "semver": "~5.3.0", + "tar": "^2.0.0", + "which": "1" + } + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "requires": { + "abbrev": "1" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "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=" + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "psl": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.2.0.tgz", + "integrity": "sha512-GEn74ZffufCmkDDLNcl3uuyF/aSD6exEyh1v/ZSdAomB82t6G9hzJVRx0jBmLDW+VfZqks3aScmMw9DszwUalA==" + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=" + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "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" + } + }, + "tar": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz", + "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==", + "requires": { + "block-stream": "*", + "fstream": "^1.0.12", + "inherits": "2" + } + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + } + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "type-detect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz", + "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + } + } +} diff --git a/package.json b/package.json index 30beb35..d7fa312 100644 --- a/package.json +++ b/package.json @@ -1,27 +1,35 @@ { - "name": "pg-query-native", - "version": "1.1.0", + "name": "@sqlutils/parse", + "version": "1.2.1", "description": "The real PostgreSQL query parser", - "homepage": "http://github.com/zhm/node-pg-query-native", + "homepage": "http://github.com/ethanresnick/node-pg-query-native", "main": "index.js", + "typings": "index.d.ts", + "publishConfig": { + "access": "public" + }, + "scripts": { - "configure": "./node_modules/node-gyp/bin/node-gyp.js configure", - "build": "./node_modules/node-gyp/bin/node-gyp.js configure build", - "rebuild": "./node_modules/node-gyp/bin/node-gyp.js configure rebuild", - "test": "mocha" + "configure": "node-gyp configure", + "build": "node-gyp configure build", + "rebuild": "node-gyp configure rebuild", + "test": "mocha --timeout 5000", + "preinstall": "script/buildAddon.sh", + "postinstall": "node-gyp rebuild" }, - "author": "Zac McCormick (http://github.com/zhm)", + "author": "Ethan Resnick (http://github.com/ethanresnick)", "license": "BSD", "repository": { "type": "git", - "url": "git://github.com/zhm/node-pg-query-native.git" + "url": "git://github.com/ethanresnick/node-pg-query-native.git" }, "devDependencies": { - "mocha": "^2.5.3" + "chai": "^3.5.0", + "lodash": "^4.17.15", + "mocha": "^5.2.0" }, "dependencies": { - "bindings": "^1.2.1", - "nan": "^2.3.5", + "node-addon-api": "^1.6.3", "node-gyp": "^3.3.1" }, "keywords": [ @@ -30,6 +38,7 @@ "postgresql", "pg", "query", + "plpgsql", "database" ] } diff --git a/pg-query.cc b/pg-query.cc deleted file mode 100644 index fcb3026..0000000 --- a/pg-query.cc +++ /dev/null @@ -1,10 +0,0 @@ -#include "functions.h" - -using v8::FunctionTemplate; - -NAN_MODULE_INIT(PgQuery) { - Nan::Set(target, Nan::New("parse").ToLocalChecked(), - Nan::GetFunction(Nan::New(parse)).ToLocalChecked()); -} - -NODE_MODULE(PgQuery, PgQuery) diff --git a/queryparser.cc b/queryparser.cc new file mode 100644 index 0000000..c383c50 --- /dev/null +++ b/queryparser.cc @@ -0,0 +1,235 @@ +#include +#include +#include "pg_query.h" +#include + +using namespace v8; + +Local CreateError(Isolate* isolate, const PgQueryError& err) +{ + v8::EscapableHandleScope handleScope(isolate); + Local error = Object::New(isolate); + + error->Set(String::NewFromUtf8(isolate, "message"), String::NewFromUtf8(isolate, err.message)); + error->Set(String::NewFromUtf8(isolate, "fileName"), String::NewFromUtf8(isolate, err.filename)); + error->Set(String::NewFromUtf8(isolate, "functionName"), String::NewFromUtf8(isolate, err.funcname)); + error->Set(String::NewFromUtf8(isolate, "lineNumber"), Integer::New(isolate, err.lineno)); + error->Set(String::NewFromUtf8(isolate, "cursorPosition"), Integer::New(isolate, err.cursorpos)); + + if (err.context) { + error->Set(String::NewFromUtf8(isolate, "context"), String::NewFromUtf8(isolate, err.context)); + } + else { + error->Set(String::NewFromUtf8(isolate, "context"), Null(isolate)); + } + + return handleScope.Escape(error); +} + +Local QueryParseResponse(Isolate* isolate, const PgQueryParseResult& result) +{ + v8::EscapableHandleScope handleScope(isolate); + Local obj = Object::New(isolate); + + if (result.error) { + obj->Set( + String::NewFromUtf8(isolate, "error"), + CreateError(isolate, *result.error) + ); + } + + if (result.parse_tree) { + Local parseResult; + bool parseSucceeded = JSON::Parse( + isolate, + String::NewFromUtf8(isolate, result.parse_tree) + ).ToLocal(&parseResult); + + if(parseSucceeded == true) + obj->Set(String::NewFromUtf8(isolate, "query"),parseResult); + } + + if (result.stderr_buffer) { + obj->Set( + String::NewFromUtf8(isolate, "stderr"), + String::NewFromUtf8(isolate, result.stderr_buffer) + ); + } + + pg_query_free_parse_result(result); + return handleScope.Escape(obj); +} + +Local PlPgSQLParseResponse(Isolate* isolate, const PgQueryPlpgsqlParseResult& result) +{ + v8::EscapableHandleScope handleScope(isolate); + Local obj = Object::New(isolate); + + if (result.error) { + obj->Set( + String::NewFromUtf8(isolate, "error"), + CreateError(isolate, *result.error) + ); + } + + if (result.plpgsql_funcs) { + Local parseResult; + bool parseSucceeded = JSON::Parse( + isolate, + String::NewFromUtf8(isolate, result.plpgsql_funcs) + ).ToLocal(&parseResult); + + if(parseSucceeded == true) + obj->Set(String::NewFromUtf8(isolate, "functions"),parseResult); + } + + pg_query_free_plpgsql_parse_result(result); + return handleScope.Escape(obj); +} + +void Method(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + String::Utf8Value query(args[0]->ToString()); + PgQueryParseResult result = pg_query_parse(*query); + args.GetReturnValue().Set(QueryParseResponse(isolate, result)); +} + +void MethodPlPgSQL(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + String::Utf8Value query(args[0]->ToString()); + PgQueryPlpgsqlParseResult result = pg_query_parse_plpgsql(*query); + args.GetReturnValue().Set(PlPgSQLParseResponse(isolate, result)); +} + +struct Work { + uv_work_t request; + Persistent callback; + std::string ss; + PgQueryParseResult result; +}; + +struct WorkPlPgSQL { + uv_work_t request; + Persistent callback; + std::string ss; + PgQueryPlpgsqlParseResult result; +}; + +// called by libuv worker in separate thread +static void WorkAsync(uv_work_t* req) +{ + Work* work = static_cast(req->data); + work->result = pg_query_parse(work->ss.data()); +} + +static void WorkPlPgSQLAsync(uv_work_t* req) +{ + WorkPlPgSQL* work = static_cast(req->data); + work->result = pg_query_parse_plpgsql(work->ss.data()); +} + +// called by libuv in event loop when async function completes +static void WorkAsyncComplete(uv_work_t *req, int status) +{ + Isolate* isolate = Isolate::GetCurrent(); + Work* work = static_cast(req->data); + + // Set up return arguments. + // Rather than calling back to node with an error if the parse failed, + // as would be more correct, we just call back object that may have an + // error property; caller can deal with it. + v8::HandleScope handleScope(isolate); + Handle argv[1] = { + QueryParseResponse(isolate, work->result) + }; + + // execute the callback + // https://stackoverflow.com/questions/13826803/calling-javascript-function-from-a-c-callback-in-v8/28554065#28554065 + Local::New(isolate, work->callback)->Call( + isolate->GetEnteredOrMicrotaskContext()->Global(), + 1, + argv + ); + + // Free up the persistent function callback + work->callback.Reset(); + delete work; +} + +static void WorkPlPgSQLAsyncComplete(uv_work_t *req, int status) +{ + Isolate* isolate = Isolate::GetCurrent(); + WorkPlPgSQL* work = static_cast(req->data); + + // Set up return arguments. + // Rather than calling back to node with an error if the parse failed, + // as would be more correct, we just call back object that may have an + // error property; caller can deal with it. + v8::HandleScope handleScope(isolate); + Handle argv[1] = { + PlPgSQLParseResponse(isolate, work->result) + }; + + // execute the callback + Local::New(isolate, work->callback)->Call( + isolate->GetEnteredOrMicrotaskContext()->Global(), + 1, + argv + ); + + // Free up the persistent function callback + work->callback.Reset(); + delete work; +} + +void MethodAsync(const v8::FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + v8::EscapableHandleScope handleScope(isolate); + + // Set up the work object on the heap so that we can do our calculations + // in WorkAsync, and put our callback there too for use in WorkAsynComplete. + Work* work = new Work(); + work->request.data = work; + + String::Utf8Value query(args[0]->ToString()); + work->ss.assign(*query); + + // TODO: should check that this is actually a function. + Local callback = Local::Cast(args[1]); + work->callback.Reset(isolate, callback); + + // kick of the worker thread + uv_queue_work(uv_default_loop(),&work->request,WorkAsync,WorkAsyncComplete); + + args.GetReturnValue().Set(handleScope.Escape(Undefined(isolate))); +} + +void MethodPlPgSQLAsync(const v8::FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + + WorkPlPgSQL* work = new WorkPlPgSQL(); + work->request.data = work; + + String::Utf8Value query(args[0]->ToString()); + work->ss.assign(*query); + + // store the callback from JS in the work package so we can invoke it later + // TODO: should check that this is actually a function. + Local callback = Local::Cast(args[1]); + work->callback.Reset(isolate, callback); + + // kick of the worker thread + uv_queue_work(uv_default_loop(),&work->request,WorkPlPgSQLAsync,WorkPlPgSQLAsyncComplete); + + args.GetReturnValue().Set(Undefined(isolate)); +} + + +void init(Handle exports, Handle module) { + NODE_SET_METHOD(exports, "parseQuery", Method); + NODE_SET_METHOD(exports, "parseQueryAsync", MethodAsync); + NODE_SET_METHOD(exports, "parsePlPgSQL", MethodPlPgSQL); + NODE_SET_METHOD(exports, "parsePlPgSQLAsync", MethodPlPgSQLAsync); +} + +NODE_MODULE(addon, init) diff --git a/script/buildAddon.sh b/script/buildAddon.sh new file mode 100755 index 0000000..dd0f134 --- /dev/null +++ b/script/buildAddon.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +commit=2eab0008556a7e46289a9907d03e9b2534376c56 + +rDIR=$(pwd) +tmpDir=/tmp + +cd $tmpDir + +git clone -b 10-latest --single-branch https://github.com/ethanresnick/libpg_query +cd libpg_query + +# echo "git checkout to $commit" +git checkout $commit + + +if [ "$(uname)" == "Darwin" ]; then + make CFLAGS='-mmacosx-version-min=10.7' PG_CFLAGS='-mmacosx-version-min=10.7' +elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then + make CFLAGS='' PG_CFLAGS='' +fi + +if [ $? -ne 0 ]; then + echo "ERROR: 'make' command failed"; + exit 1; +fi + +wDIR=$(pwd) + +file=$(ls | grep 'libpg_query.a') + +if [ ! $file ]; then + echo "ERROR: libpg_query.a not found"; + exit 1; +fi + +file=$(ls | grep 'pg_query.h') + +if [ ! $file ]; then + echo "ERROR: pg_query.h not found"; + exit 1; +fi + +#copy queryparser.cc, binding.gyp to current directory +# +# + +if [ "$(uname)" == "Darwin" ]; then + cp $(pwd)/libpg_query.a $rDIR/libpg_query/osx/ +elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then + cp $(pwd)/libpg_query.a $rDIR/libpg_query/linux/ +fi + +cp $(pwd)/pg_query.h $rDIR/libpg_query/include/ + +cd $rDIR && rm -rf $wDIR \ No newline at end of file diff --git a/src/addon.cc b/src/addon.cc new file mode 100644 index 0000000..549cb16 --- /dev/null +++ b/src/addon.cc @@ -0,0 +1,30 @@ +#include +#include "sync.h" // NOLINT(build/include) +#include "async.h" // NOLINT(build/include) + +// Expose synchronous and asynchronous access to our parsing functions +Napi::Object Init(Napi::Env env, Napi::Object exports) { + exports.Set( + Napi::String::New(env, "parseQuerySync"), + Napi::Function::New(env, ParseQuerySync) + ); + + exports.Set( + Napi::String::New(env, "parseQueryAsync"), + Napi::Function::New(env, ParseQueryAsync) + ); + + exports.Set( + Napi::String::New(env, "parsePlPgSQLSync"), + Napi::Function::New(env, ParsePlPgSQLSync) + ); + + exports.Set( + Napi::String::New(env, "parsePlPgSQLAsync"), + Napi::Function::New(env, ParsePlPgSQLAsync) + ); + + return exports; +} + +NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/src/async.cc b/src/async.cc new file mode 100644 index 0000000..4de7177 --- /dev/null +++ b/src/async.cc @@ -0,0 +1,82 @@ +#include "helpers.h" // NOLINT(build/include) +#include "async.h" // NOLINT(build/include) +#include "pg_query.h" +#include + +class QueryWorker : public Napi::AsyncWorker { + public: + QueryWorker(Napi::Function& callback, const std::string& query) + : Napi::AsyncWorker(callback), query(query) {} + ~QueryWorker() {} + + // Executed inside the worker-thread. + // It is not safe to access JS engine data structure + // here, so everything we need for input and output + // should go on `this`. + void Execute () { + result = pg_query_parse(query.c_str()); + } + + // Executed when the async work is complete + // this function will be run inside the main event loop + // so it is safe to use JS engine data again + void OnOK() { + Napi::HandleScope scope(Env()); + try { + Callback().Call({Env().Undefined(), QueryParseResult(Env(), result) }); + } catch (const Napi::Error& e) { + Callback().Call({ e.Value(), Env().Undefined() }); + } + } + + private: + std::string query; + PgQueryParseResult result; +}; + +class PgPlQSLWorker : public Napi::AsyncWorker { + public: + PgPlQSLWorker(Napi::Function& callback, const std::string& query) + : Napi::AsyncWorker(callback), query(query) {} + ~PgPlQSLWorker() {} + + // Executed inside the worker-thread. + // It is not safe to access JS engine data structure + // here, so everything we need for input and output + // should go on `this`. + void Execute () { + result = pg_query_parse_plpgsql(query.c_str()); + } + + // Executed when the async work is complete + // this function will be run inside the main event loop + // so it is safe to use JS engine data again + void OnOK() { + Napi::HandleScope scope(Env()); + try { + Callback().Call({Env().Undefined(), PlPgSQLParseResult(Env(), result) }); + } catch (const Napi::Error& e) { + Callback().Call({ e.Value(), Env().Undefined() }); + } + } + + private: + std::string query; + PgQueryPlpgsqlParseResult result; +}; + +Napi::Value ParseQueryAsync(const Napi::CallbackInfo& info) { + std::string query = info[0].As(); + Napi::Function callback = info[1].As(); + QueryWorker* worker = new QueryWorker(callback, query); + worker->Queue(); + return info.Env().Undefined(); +} + +Napi::Value ParsePlPgSQLAsync(const Napi::CallbackInfo& info) { + std::string query = info[0].As(); + Napi::Function callback = info[1].As(); + PgPlQSLWorker* worker = new PgPlQSLWorker(callback, query); + worker->Queue(); + return info.Env().Undefined(); +} diff --git a/src/async.h b/src/async.h new file mode 100644 index 0000000..9fdfb76 --- /dev/null +++ b/src/async.h @@ -0,0 +1,4 @@ +#include + +Napi::Value ParseQueryAsync(const Napi::CallbackInfo& info); +Napi::Value ParsePlPgSQLAsync(const Napi::CallbackInfo& info); diff --git a/src/helpers.cc b/src/helpers.cc new file mode 100644 index 0000000..7c1f41f --- /dev/null +++ b/src/helpers.cc @@ -0,0 +1,41 @@ +#include "helpers.h" // NOLINT(build/include) +#include "pg_query.h" +#include "helpers.h" +#include + +Napi::Error CreateError(Napi::Env env, const PgQueryError& err) +{ + auto error = Napi::Error::New(env, err.message); + error.Set("fileName", err.filename); + error.Set("functionName", err.funcname); + error.Set("lineNumber", Napi::Value::From(env, err.lineno)); + error.Set("cursorPosition", Napi::Value::From(env, err.cursorpos)); + error.Set("context", err.context ? Napi::Value::From(env, err.context) : env.Null()); + return error; +} + +Napi::String QueryParseResult(Napi::Env env, const PgQueryParseResult& result) +{ + if (result.error) { + auto throwVal = CreateError(env, *result.error); + pg_query_free_parse_result(result); + throw throwVal; + } + + auto returnVal = Napi::String::New(env, result.parse_tree); + pg_query_free_parse_result(result); + return returnVal; +} + +Napi::String PlPgSQLParseResult(Napi::Env env, const PgQueryPlpgsqlParseResult& result) +{ + if (result.error) { + auto throwVal = CreateError(env, *result.error); + pg_query_free_plpgsql_parse_result(result); + throw throwVal; + } + + auto returnVal = Napi::String::New(env, result.plpgsql_funcs); + pg_query_free_plpgsql_parse_result(result); + return returnVal; +} diff --git a/src/helpers.h b/src/helpers.h new file mode 100644 index 0000000..6063700 --- /dev/null +++ b/src/helpers.h @@ -0,0 +1,6 @@ +#include "pg_query.h" +#include + +Napi::Error CreateError(Napi::Env env, const PgQueryError& err); +Napi::String QueryParseResult(Napi::Env env, const PgQueryParseResult& result); +Napi::String PlPgSQLParseResult(Napi::Env env, const PgQueryPlpgsqlParseResult& result); diff --git a/src/sync.cc b/src/sync.cc new file mode 100644 index 0000000..07d2d7f --- /dev/null +++ b/src/sync.cc @@ -0,0 +1,19 @@ +#include +#include +#include "sync.h" // NOLINT(build/include) +#include "helpers.h" // NOLINT(build/include) + +Napi::String ParseQuerySync(const Napi::CallbackInfo& info) { + std::string query = info[0].As(); + PgQueryParseResult result = pg_query_parse(query.c_str()); + + return QueryParseResult(info.Env(), result); +} + +Napi::String ParsePlPgSQLSync(const Napi::CallbackInfo& info) { + std::string query = info[0].As(); + PgQueryPlpgsqlParseResult result = pg_query_parse_plpgsql(query.c_str()); + + return PlPgSQLParseResult(info.Env(), result); +} + diff --git a/src/sync.h b/src/sync.h new file mode 100644 index 0000000..eb7173f --- /dev/null +++ b/src/sync.h @@ -0,0 +1,4 @@ +#include + +Napi::String ParseQuerySync(const Napi::CallbackInfo& info); +Napi::String ParsePlPgSQLSync(const Napi::CallbackInfo& info); diff --git a/test/index.js b/test/index.js index e0602fb..8c5f7b2 100644 --- a/test/index.js +++ b/test/index.js @@ -1,20 +1,94 @@ -var query = require('../'); -var assert = require('assert'); +const query = require('../'); +const { expect } = require('chai'); +const { omit, cloneDeepWith } = require("lodash"); -describe('pg-query', function() { - it('should parse a query', function() { - assert.equal(typeof query.parse('select 1').query[0].RawStmt.stmt.SelectStmt, 'object'); - }); +describe('Queries', () => { + describe("Sync Parsing", () => { + it("should return a single-item parse result for common queries", () => { + const queries = ["select 1", "select null", "select ''", "select a, b"]; + const results = queries.map(query.parseQuerySync); - it('should parse a null', function() { - assert(query.parse("select null").query[0].RawStmt.stmt.SelectStmt.targetList[0].ResTarget.val.A_Const.val.Null); - }); + results.forEach(res => { + expect(res).to.have.lengthOf(1); + }); + + // Do some rough asserting on the shape of the result. + // These tests aren't really meant to test the parsing functionality + // itself, but doing a bit for sanity doesn't hurt. + const selectedDatas = results.map(it => it[0].RawStmt.stmt.SelectStmt.targetList); + + expect(selectedDatas[0][0].ResTarget.val.A_Const.val.Integer.ival).to.eq(1); + expect(selectedDatas[1][0].ResTarget.val.A_Const.val).to.have.property("Null"); + expect(selectedDatas[2][0].ResTarget.val.A_Const.val.String.str).to.eq(''); + expect(selectedDatas[3]).to.have.lengthOf(2); + }); + + it("should support parsing multiple queries", () => { + const res = query.parseQuerySync("select 1; select null;"); + const removeChangedProps = (it) => omit(it, changedProps); + const changedProps = [ + "RawStmt.stmt_len", + "RawStmt.stmt_location", + "RawStmt.stmt.SelectStmt.targetList[0].ResTarget.location", + "RawStmt.stmt.SelectStmt.targetList[0].ResTarget.val.A_Const.location" + ]; + + expect(res.map(removeChangedProps)).to.deep.eq([ + ...(query.parseQuerySync("select 1;").map(removeChangedProps)), + ...(query.parseQuerySync("select null;").map(removeChangedProps)) + ]); + }); - it('should parse an empty string', function() { - assert(query.parse("select ''").query[0].RawStmt.stmt.SelectStmt.targetList[0].ResTarget.val.A_Const.val.String.str === ''); + it('should not parse a bogus query', () => { + expect(() => query.parseQuerySync('NOT A QUERY')).to.throw(Error); + }); }); - it('should not parse a bogus query', function() { - assert.ok(query.parse('NOT A QUERY').error instanceof Error); + describe("Async parsing", () => { + it("should return a promise resolving to same result", async () => { + const testQuery = 'select * from john;'; + const resPromise = query.parseQuery(testQuery); + const res = await resPromise; + + expect(resPromise).to.be.instanceof(Promise); + expect(res).to.deep.eq(query.parseQuerySync(testQuery)); + }); + + it('should reject on bogus queries', async () => { + return query.parseQuery("NOT A QUERY").then(() => { + throw new Error("should have rejected"); + }, (e) => { + expect(e).instanceof(Error); + expect(e.message).to.match(/NOT/); + }); + }); + }) +}); + +describe('PlPgSQL (async)', () => { + it('should parse a function', async () => { + const testFunction = ` + CREATE FUNCTION t() RETURNS trigger AS + $BODY$ + DECLARE + resultVal integer; + finalVal integer; + BEGIN + resultVal = 0; + IF (resultVal >= 5) + THEN finalVal = 'Yes'; + ELSE finalVal = 'No'; + END IF; + RETURN finalVal; + END; + $BODY$ + LANGUAGE plpgsql; + `; + + const resPromise = query.parsePlPgSQL(testFunction); + const res = await resPromise; + + expect(resPromise).to.be.instanceof(Promise); + expect(res).to.deep.have.property("0.PLpgSQL_function"); }); });