├── .gitignore ├── LICENSE ├── README.md ├── index.d.ts ├── index.js ├── package-lock.json ├── package.json └── test ├── cert ├── server.crt └── server.key └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea/* 3 | .vscode/ 4 | test_config.json 5 | *.code-workspace -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # clickhouse 2 | NodeJS client for [ClickHouse](https://clickhouse.yandex/). 3 | Send query over HTTP interface. 4 | 5 | Install: 6 | 7 | ```bash 8 | npm i clickhouse 9 | ``` 10 | 11 | Example: 12 | 13 | ```javascript 14 | const { ClickHouse } = require('clickhouse'); 15 | 16 | const clickhouse = new ClickHouse(); 17 | ``` 18 | or with all options: 19 | 20 | ```javascript 21 | const clickhouse = new ClickHouse({ 22 | url: 'http://localhost', 23 | port: 8123, 24 | debug: false, 25 | basicAuth: null, 26 | isUseGzip: false, 27 | trimQuery: false, 28 | usePost: false, 29 | format: "json", // "json" || "csv" || "tsv" 30 | raw: false, 31 | config: { 32 | session_id : 'session_id if neeed', 33 | session_timeout : 60, 34 | output_format_json_quote_64bit_integers : 0, 35 | enable_http_compression : 0, 36 | database : 'my_database_name', 37 | }, 38 | 39 | // This object merge with request params (see request lib docs) 40 | reqParams: { 41 | ... 42 | } 43 | }); 44 | ``` 45 | 46 | or change 47 | 48 | basicAuth: null 49 | to 50 | 51 | basicAuth: { 52 | username: 'default', 53 | password: '', 54 | }, 55 | 56 | 57 | *** 58 | 59 | Exec query: 60 | ```javascript 61 | const queries = [ 62 | 'DROP TABLE IF EXISTS session_temp', 63 | 64 | `CREATE TABLE session_temp ( 65 | date Date, 66 | time DateTime, 67 | mark String, 68 | ips Array(UInt32), 69 | queries Nested ( 70 | act String, 71 | id UInt32 72 | ) 73 | ) 74 | ENGINE=MergeTree(date, (mark, time), 8192)`, 75 | 76 | 'OPTIMIZE TABLE ukit.loadstat PARTITION 201807 FINAL' 77 | ]; 78 | 79 | for(const query of queries) { 80 | const r = await clickhouse.query(query).toPromise(); 81 | 82 | console.log(query, r); 83 | } 84 | ```` 85 | 86 | *** 87 | 88 | Exec by callback way: 89 | ```javascript 90 | clickhouse.query(query).exec(function (err, rows) { 91 | ... 92 | }); 93 | ```` 94 | 95 | *** 96 | 97 | Stream: 98 | ```javascript 99 | clickhouse.query(`SELECT number FROM system.numbers LIMIT 10`).stream() 100 | .on('data', function() { 101 | const stream = this; 102 | 103 | stream.pause(); 104 | 105 | setTimeout(() => { 106 | stream.resume(); 107 | }, 1000); 108 | }) 109 | .on('error', err => { 110 | ... 111 | }) 112 | .on('end', () => { 113 | ... 114 | }); 115 | ``` 116 | 117 | or **async** stream: 118 | ```javascript 119 | // async iteration 120 | for await (const row of clickhouse.query(sql).stream()) { 121 | console.log(row); 122 | } 123 | ``` 124 | 125 | *** 126 | 127 | As promise: 128 | ```javascript 129 | const rows = await clickhouse.query(query).toPromise(); 130 | 131 | // use query with external data 132 | const rows = await clickhouse.query('SELECT * AS count FROM temp_table', { 133 | external: [ 134 | { 135 | name: 'temp_table', 136 | data: e._.range(0, rowCount).map(i => `str${i}`) 137 | }, 138 | ] 139 | }).toPromise(); 140 | ``` 141 | 142 | *** 143 | 144 | Set session: 145 | ```javascript 146 | clickhouse.sessionId = '...'; 147 | const r = await clickhouse.query( 148 | `CREATE TEMPORARY TABLE test_table 149 | (_id String, str String) 150 | ENGINE=Memory` 151 | ).toPromise(); 152 | ```` 153 | 154 | In case your application requires specific sessions to manage specific data then you can send `session_id` with each query. 155 | 156 | ```javascript 157 | let mySessionId = 'some_randome_string'; 158 | const r = await clickhouse.query( 159 | `CREATE TEMPORARY TABLE test_table 160 | (_id String, str String) 161 | ENGINE=Memory`, {}, {sessionId: mySessionId} 162 | ).toPromise(); 163 | ``` 164 | 165 | Insert stream: 166 | ```javascript 167 | const ws = clickhouse.insert('INSERT INTO session_temp').stream(); 168 | for(let i = 0; i <= 1000; i++) { 169 | await ws.writeRow( 170 | [ 171 | e._.range(0, 50).map( 172 | j => `${i}:${i * 2}:${j}` 173 | ).join('-') 174 | ] 175 | ); 176 | } 177 | 178 | //wait stream finish 179 | const result = await ws.exec(); 180 | ``` 181 | 182 | *** 183 | 184 | Pipe readable stream to writable stream (across transform): 185 | ```javascript 186 | const rs = clickhouse.query(query).stream(); 187 | 188 | const tf = new stream.Transform({ 189 | objectMode : true, 190 | transform : function (chunk, enc, cb) { 191 | cb(null, JSON.stringify(chunk) + '\n'); 192 | } 193 | }); 194 | 195 | clickhouse.sessionId = Date.now(); 196 | const ws = clickhouse.insert('INSERT INTO session_temp2').stream(); 197 | 198 | const result = await rs.pipe(tf).pipe(ws).exec(); 199 | ``` 200 | *** 201 | 202 | insert array of objects: 203 | ```javascript 204 | 205 | /* 206 | CREATE TABLE IF NOT EXISTS test_array ( 207 | date Date, 208 | str String, 209 | arr Array(String), 210 | arr2 Array(Date), 211 | arr3 Array(UInt8), 212 | id1 UUID 213 | ) ENGINE=MergeTree(date, date, 8192) 214 | */ 215 | const rows = [ 216 | { 217 | date: '2018-01-01', 218 | str: 'Something1...', 219 | arr: [], 220 | arr2: ['1985-01-02', '1985-01-03'], 221 | arr3: [1,2,3,4,5], 222 | id1: '102a05cb-8aaf-4f11-a442-20c3558e4384' 223 | }, 224 | 225 | { 226 | date: '2018-02-01', 227 | str: 'Something2...', 228 | arr: ['5670000000', 'Something3...'], 229 | arr2: ['1985-02-02'], 230 | arr3: [], 231 | id1: 'c2103985-9a1e-4f4a-b288-b292b5209de1' 232 | } 233 | ]; 234 | 235 | await clickhouse.insert( 236 | `insert into test_array 237 | (date, str, arr, arr2, 238 | arr3, id1)`, 239 | rows 240 | ).toPromise(); 241 | ``` 242 | *** 243 | 244 | Parameterized Values: 245 | ```javascript 246 | const rows = await clickhouse.query( 247 | 'SELECT * AS count FROM temp_table WHERE version = {ver:UInt16}', 248 | { 249 | params: { 250 | ver: 1 251 | }, 252 | } 253 | ).toPromise(); 254 | ``` 255 | For more information on encoding in the query, see [this section](https://clickhouse.com/docs/en/interfaces/http/#cli-queries-with-parameters) of the ClickHouse documentation. 256 | 257 | *** 258 | 259 | **Run Tests**: 260 | 261 | ``` 262 | npm install 263 | npm run test 264 | # or 265 | # node_modules/.bin/mocha --timeout 60000 --slow 5000 -f "SPECIFIC TEST NAME" 266 | ``` 267 | 268 | *** 269 | 270 | **Changelogs**: 271 | * 2020-08-26 (v2.6.0) 272 | - A lot of PRs from community 273 | * 2020-11-02 (v2.4.1) 274 | - Merge list of PR 275 | - fix test with Base Auth check 276 | * 2020-11-02 (v2.2.0) ___Backward Incompatible Change___ 277 | - port from url more important than port from config 278 | * 2020-04-17 (v2.1.0) 279 | - Fix query with totals. For json formats work perfect, but for another - doesn't 280 | * 2019-02-13 281 | - Add compatibility with user and username options 282 | * 2019-02-07 283 | - Add TLS/SSL Protocol support 284 | - Add async iteration over SELECT 285 | 286 | 287 | 288 | **Links** 289 | * request lib doc https://github.com/request/request#requestoptions-callback 290 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module 'clickhouse' { 4 | import {Stream} from 'stream'; 5 | 6 | type callbackExec = (error: Error, rows?: Object[]) => void; 7 | 8 | export class ClickHouse { 9 | constructor(opts: Object); 10 | query(query: String, reqParams?: object): QueryCursor; 11 | insert(query: String, data?: object): QueryCursor; 12 | sessionId: string; 13 | } 14 | 15 | export class WriteStream extends Stream.Transform { 16 | writeRow(data: Array | string): Promise; 17 | exec(): Promise<{}>; 18 | } 19 | 20 | class QueryCursor { 21 | toPromise(): Promise; 22 | exec(callback: callbackExec): void; 23 | stream(): Stream & WriteStream; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const zlib = require('zlib'); 4 | const _ = require('lodash'); 5 | const request = require('request'); 6 | const { Transform, Readable, } = require('stream'); 7 | const JSONStream = require('JSONStream'); 8 | const through = require('through'); 9 | const stream2asynciter = require('stream2asynciter'); 10 | const { URL } = require('url'); 11 | const tsv = require('tsv'); 12 | const uuidv4 = require('uuid/v4'); 13 | const INSERT_FIELDS_MASK = /^INSERT\sINTO\s(.+?)\s*\(((\n|.)+?)\)/i; 14 | 15 | 16 | /** 17 | * Content-Encoding: gzip 18 | * Accept-Encoding: gzip 19 | * и включить настройку ClickHouse enable_http_compression. 20 | * 21 | * session_id 22 | * 23 | * session_timeout 24 | */ 25 | 26 | const SEPARATORS = { 27 | TSV: "\t", 28 | CSV: ",", 29 | Values: "," 30 | }; 31 | 32 | const ALIASES = { 33 | TabSeparated: "TSV" 34 | }; 35 | 36 | var ESCAPE_STRING = { 37 | /** 38 | * @return {string} 39 | */ 40 | TSV: function (value) { 41 | return value 42 | .replace(/\\/g, '\\\\') 43 | .replace(/\'/g, '\\\'') 44 | .replace(/\t/g, '\\t') 45 | .replace(/\n/g, '\\n'); 46 | }, 47 | 48 | CSV: function (value) { 49 | return value.replace (/\"/g, '""'); 50 | }, 51 | }; 52 | 53 | var ESCAPE_NULL = { 54 | TSV: "\\N", 55 | CSV: "\\N", 56 | Values: "\\N", 57 | JSONEachRow: "\\N", 58 | }; 59 | 60 | const R_ERROR = new RegExp('(Code|Error): ([0-9]{2})[,.] .*Exception: (.+?)$', 'm'); 61 | 62 | const URI = 'localhost'; 63 | 64 | const PORT = 8123; 65 | 66 | const DATABASE = 'default'; 67 | 68 | const FORMAT_NAMES = { 69 | JSON: 'json', 70 | TSV: 'tsv', 71 | CSV: 'csv' 72 | } 73 | 74 | const FORMATS = { 75 | [FORMAT_NAMES.JSON]: 'JSON', 76 | [FORMAT_NAMES.TSV]: 'TabSeparatedWithNames', 77 | [FORMAT_NAMES.CSV]: 'CSVWithNames', 78 | }; 79 | 80 | const REVERSE_FORMATS = Object.keys(FORMATS).reduce( 81 | function(obj, format) { 82 | obj[FORMATS[format]] = format; 83 | return obj; 84 | }, 85 | {} 86 | ); 87 | 88 | const R_FORMAT_PARSER = new RegExp( 89 | `FORMAT (${Object.keys(FORMATS).map(k => FORMATS[k]).join('|')})`, 90 | 'mi' 91 | ); 92 | 93 | function parseCSV(body, options = { header: true }) { 94 | const data = new tsv.Parser(SEPARATORS.CSV, options).parse(body); 95 | data.splice(data.length - 1, 1); 96 | return data; 97 | } 98 | 99 | function parseTSV(body, options = { header: true }) { 100 | const data = new tsv.Parser(SEPARATORS.TSV, options).parse(body); 101 | data.splice(data.length - 1, 1); 102 | return data; 103 | } 104 | 105 | function parseCSVStream(s = new Set()) { 106 | let isFirst = true; 107 | let ref = { 108 | fields: [] 109 | }; 110 | 111 | return through(function (chunk) { 112 | let str = chunk.toString(); 113 | let parsed = parseCSV(str, {header: isFirst}); 114 | let strarr = str.split("\n"); 115 | let plen = (isFirst && strarr.length - 1 || strarr.length) - parsed.length; 116 | 117 | if (!isFirst) { 118 | chunk = Buffer.concat([Buffer.from([...s].join("\n")), chunk]).toString(); 119 | parsed = parseCSV(str, {header: isFirst}); 120 | s = new Set(); 121 | } 122 | strarr.splice(strarr.length - plen).forEach((value => s.add(value))); 123 | chunkBuilder.call(this, isFirst, ref, str, parsed); 124 | isFirst = false; 125 | }) 126 | } 127 | 128 | function parseJSONStream() { 129 | return JSONStream.parse(['data', true]); 130 | } 131 | 132 | function parseTSVStream(s = new Set()) { 133 | let isFirst = true; 134 | let ref = { 135 | fields: [] 136 | }; 137 | 138 | return through(function (chunk) { 139 | let str = chunk.toString(); 140 | let parsed = parseTSV(str, {header: isFirst}); 141 | let strarr = str.split("\n"); 142 | let plen = (isFirst && strarr.length - 1 || strarr.length) - parsed.length; 143 | 144 | if (!isFirst) { 145 | chunk = Buffer.concat([Buffer.from([...s].join("\n")), chunk]).toString(); 146 | parsed = parseTSV(str, {header: isFirst}); 147 | s = new Set(); 148 | } 149 | strarr.splice(strarr.length - plen).forEach((value => s.add(value))); 150 | chunkBuilder.call(this, isFirst, ref, str, parsed); 151 | isFirst = false; 152 | }); 153 | } 154 | 155 | function chunkBuilder(isFirst, ref, chunk, parsed) { 156 | if (isFirst) { 157 | ref.fields = Object.keys(parsed[0]); 158 | parsed.forEach((value) => { 159 | this.queue(value); 160 | }); 161 | } else { 162 | parsed.forEach((value) => { 163 | let result = {}; 164 | ref.fields.forEach((field, index) => (result[field] = value[index])); 165 | this.queue(result); 166 | result = null; 167 | }); 168 | } 169 | } 170 | 171 | function encodeValue(quote, v, _format, isArray) { 172 | const format = ALIASES[_format] || _format; 173 | 174 | switch (typeof v) { 175 | case 'string': 176 | if (isArray) { 177 | return `'${ESCAPE_STRING[format] ? ESCAPE_STRING[format](v, quote) : v}'`; 178 | } 179 | 180 | return ESCAPE_STRING[format] ? ESCAPE_STRING[format](v, quote) : v; 181 | case 'number': 182 | if (isNaN(v)) { 183 | return 'nan'; 184 | } 185 | 186 | if (v === +Infinity) { 187 | return '+inf'; 188 | } 189 | 190 | if (v === -Infinity) { 191 | return '-inf'; 192 | } 193 | 194 | if (v === Infinity) { 195 | return 'inf'; 196 | } 197 | 198 | return v; 199 | case 'object': 200 | 201 | // clickhouse allows to use unix timestamp in seconds 202 | if (v instanceof Date) { 203 | return Math.round(v.getTime() / 1000); 204 | } 205 | 206 | // you can add array items 207 | if (v instanceof Array) { 208 | return '[' + v.map(function (i) { 209 | return encodeValue(true, i, format, true); 210 | }).join(',') + ']'; 211 | } 212 | 213 | // TODO: tuples support 214 | if (!format) { 215 | console.trace(); 216 | } 217 | 218 | if (v === null) { 219 | return format in ESCAPE_NULL ? ESCAPE_NULL[format] : v; 220 | } 221 | 222 | return format in ESCAPE_NULL ? ESCAPE_NULL[format] : v; 223 | case 'boolean': 224 | return v === true ? 1 : 0; 225 | default: 226 | return v; 227 | } 228 | } 229 | 230 | function getErrorObj(res) { 231 | const err = new Error(`${res.statusCode}: ${res.body || res.statusMessage}`); 232 | 233 | if (res.body) { 234 | const m = res.body.match(R_ERROR); 235 | if (m) { 236 | if (m[2] && isNaN(parseInt(m[2])) === false) { 237 | err.code = parseInt(m[2]); 238 | } 239 | 240 | if (m[3]) { 241 | err.message = m[3]; 242 | } 243 | } 244 | } 245 | 246 | return err; 247 | } 248 | 249 | 250 | function isObject(obj) { 251 | return Object.prototype.toString.call(obj) === '[object Object]'; 252 | } 253 | 254 | 255 | class Rs extends Transform { 256 | constructor(reqParams) { 257 | super(); 258 | 259 | const me = this; 260 | 261 | me.ws = request.post(reqParams); 262 | 263 | me.isPiped = false; 264 | 265 | // Без этого обработчика и вызова read Transform не отрабатывает до конца 266 | // https://nodejs.org/api/stream.html#stream_implementing_a_transform_stream 267 | // Writing data while the stream is not draining is particularly problematic for a Transform, 268 | // because the Transform streams are paused by default until they are piped or 269 | // an 'data' or 'readable' event handler is added. 270 | me.on('readable', function () { 271 | let data = me.read(); 272 | }); 273 | 274 | me.pipe(me.ws); 275 | 276 | me.on('pipe', function () { 277 | me.isPiped = true; 278 | }); 279 | } 280 | 281 | _transform(chunk, encoding, cb) { 282 | cb(null, chunk); 283 | } 284 | 285 | writeRow(data) { 286 | let row = ''; 287 | 288 | if (typeof data === 'string') { 289 | row = data; 290 | } else if (Array.isArray(data)) { 291 | row = ClickHouse.mapRowAsArray(data); 292 | } else if (isObject(data)) { 293 | throw new Error('Error: Inserted data must be an array, not an object.'); 294 | } 295 | 296 | let isOk = this.write( 297 | row + '\n' 298 | ); 299 | 300 | this.rowCount++; 301 | 302 | if (isOk) { 303 | return Promise.resolve(); 304 | } else { 305 | return new Promise((resolve, reject) => { 306 | const fn = err => reject(err); 307 | this.ws.once('error', fn); 308 | this.ws.once('drain', err => { 309 | this.ws.removeListener('error', fn); 310 | if (err) { 311 | reject(err); 312 | } else { 313 | resolve(); 314 | } 315 | }); 316 | }); 317 | } 318 | } 319 | 320 | 321 | exec() { 322 | let me = this; 323 | 324 | return new Promise((resolve, reject) => { 325 | me.ws 326 | .on('error', function(err) { 327 | reject(err); 328 | }) 329 | .on('response', function (res) { 330 | if (res.statusCode === 200) { 331 | return resolve({ r: 1 }); 332 | } 333 | 334 | let body = ''; 335 | 336 | res 337 | .on('data', data => body += data) 338 | .on('end', () => { 339 | res.body = body; 340 | 341 | return reject( 342 | getErrorObj(res) 343 | ); 344 | }); 345 | }); 346 | 347 | if ( ! me.isPiped) { 348 | me.end(); 349 | } 350 | }); 351 | } 352 | } 353 | 354 | 355 | class QueryCursor { 356 | constructor(connection, query, data, opts = {}) { 357 | this.connection = connection; 358 | 359 | this.query = query; 360 | this.data = data; 361 | 362 | this.opts = _.merge({}, opts, { 363 | format: this.connection.opts.format, 364 | raw: this.connection.opts.raw 365 | }); 366 | 367 | // Sometime needs to override format by query 368 | const formatFromQuery = ClickHouse.getFormatFromQuery(this.query); 369 | if (formatFromQuery && formatFromQuery !== this.format) { 370 | this.opts.format = formatFromQuery; 371 | } 372 | 373 | this.useTotals = false; 374 | this._request = null; 375 | this.queryId = opts.queryId || uuidv4(); 376 | 377 | if (this.isDebug) { 378 | console.log('QueryCursor', {query: this.query, data: this.data, opts: this.opts}); 379 | } 380 | } 381 | 382 | get isInsert() { 383 | return !!this.query.match(/^insert/i); 384 | } 385 | 386 | get isDebug() { 387 | return this.connection.opts.debug; 388 | } 389 | 390 | get format() { 391 | return this.opts.format; 392 | } 393 | 394 | // TODO Add check for white list of formats 395 | set format(newFormat) { 396 | this.opts.format = newFormat; 397 | } 398 | 399 | _getBodyForInsert() { 400 | const me = this; 401 | 402 | let query = me.query; 403 | let data = me.data; 404 | 405 | let values = [], 406 | fieldList = [], 407 | isFirstElObject = false; 408 | 409 | if(Array.isArray(data) && data.every(d => typeof d === 'string')) { 410 | values = data; 411 | } else if (Array.isArray(data) && Array.isArray(data[0])) { 412 | values = data; 413 | } else if (Array.isArray(data) && isObject(data[0])) { 414 | values = data; 415 | isFirstElObject = true; 416 | } else if (isObject(data)) { 417 | values = [data]; 418 | isFirstElObject = true; 419 | } else { 420 | throw new Error('ClickHouse._getBodyForInsert: data is invalid format'); 421 | } 422 | 423 | if (isFirstElObject) { 424 | let m = query.match(INSERT_FIELDS_MASK); 425 | if (m) { 426 | fieldList = m[2].split(',').map(s => s.trim()); 427 | } else { 428 | throw new Error('insert query wasnt parsed field list after TABLE_NAME'); 429 | } 430 | } 431 | 432 | return values.map(row => { 433 | if (typeof row === 'string') { 434 | return row; 435 | } 436 | if (isFirstElObject) { 437 | return ClickHouse.mapRowAsObject(fieldList, row); 438 | } else { 439 | return ClickHouse.mapRowAsArray(row); 440 | } 441 | }).join('\n'); 442 | } 443 | 444 | 445 | _getReqParams() { 446 | const me = this; 447 | 448 | const { 449 | reqParams, 450 | config, 451 | username, 452 | password, 453 | database, 454 | } = me.connection.opts; 455 | 456 | const params = _.merge({ 457 | headers: { 458 | 'Content-Type': 'text/plain' 459 | }, 460 | }, reqParams); 461 | 462 | const configQS = _.merge({}, config, { 463 | query_id: me.queryId, 464 | }); 465 | 466 | if (me.connection.opts.isSessionPerQuery) { 467 | configQS.session_id = uuidv4(); 468 | } 469 | 470 | if (database) { 471 | configQS.database = database; 472 | } 473 | 474 | const url = new URL(me.connection.url); 475 | 476 | if (username) { 477 | url.searchParams.append('user', username); 478 | } 479 | 480 | if (password) { 481 | url.searchParams.append('password', password); 482 | } 483 | 484 | Object.keys(configQS).forEach(k => { 485 | url.searchParams.append(k, configQS[k]); 486 | }); 487 | 488 | 489 | let data = me.data; 490 | let query = me.query; 491 | 492 | // check for any query params passed for interpolation 493 | // https://clickhouse.com/docs/en/interfaces/http/#cli-queries-with-parameters 494 | if (data && data.params) { 495 | 496 | // each variable used in the query is expected to be prefixed with `param_` 497 | // when passed in the request. 498 | Object.keys(data.params).forEach(k => { 499 | 500 | let value = encodeValue(false, data.params[k], 'TabSeparated'); 501 | 502 | url.searchParams.append( 503 | `param_${k}`, value 504 | ); 505 | }); 506 | 507 | } 508 | 509 | if (typeof query === 'string') { 510 | if (/with totals/i.test(query)) { 511 | me.useTotals = true; 512 | } 513 | 514 | // Hack for Sequelize ORM 515 | query = query.trim().trimEnd().replace(/;$/gm, ''); 516 | if (me.connection.trimQuery) { 517 | // Remove comments from the SQL 518 | // replace multiple white spaces with one white space 519 | query = query.replace(/(--[^\n]*)/g, '').replace(/\s+/g, ' ') 520 | } 521 | 522 | if (query.match(/^(with|select|show|exists|create|drop)/i)) { 523 | if ( ! R_FORMAT_PARSER.test(query)) { 524 | query += ` FORMAT ${ClickHouse.getFullFormatName(me.format)}`; 525 | } 526 | 527 | query += ';'; 528 | 529 | if (data && data.external) { 530 | params['formData'] = data.external.reduce( 531 | function(formData, external) { 532 | url.searchParams.append( 533 | `${external.name}_structure`, 534 | external.structure || 'str String' 535 | ); 536 | 537 | formData[external.name] = { 538 | value: external.data.join('\n'), 539 | options: { 540 | filename: external.name, 541 | contentType: 'text/plain' 542 | } 543 | }; 544 | 545 | return formData; 546 | }, 547 | {} 548 | ); 549 | } 550 | } else if (me.isInsert) { 551 | if (query.match(/values/i)) { 552 | if (data && Array.isArray(data) && data.every(d => typeof d === 'string')) { 553 | params['body'] = me._getBodyForInsert(); 554 | } 555 | } else { 556 | query += ' FORMAT TabSeparated'; 557 | 558 | if (data) { 559 | params['body'] = me._getBodyForInsert(); 560 | } 561 | } 562 | } 563 | } 564 | 565 | if (me.opts.sessionId !== undefined && typeof me.opts.sessionId === 'string') { 566 | url.searchParams.append('session_id', me.opts.sessionId); 567 | } 568 | 569 | if (me.connection.usePost) { 570 | // use formData transfer query body for long sql 571 | if (typeof params['formData'] === 'undefined') { 572 | params['formData'] = {} 573 | } 574 | params['formData']['query'] = query; 575 | } else { 576 | url.searchParams.append('query', query); 577 | } 578 | 579 | if (me.connection.isUseGzip) { 580 | params.headers['Accept-Encoding'] = 'gzip'; 581 | } 582 | 583 | params['url'] = url.toString(); 584 | 585 | if (me.isDebug) { 586 | console.log('QueryCursor._getReqParams: params', me.query, params); 587 | } 588 | 589 | return params; 590 | } 591 | 592 | exec(cb) { 593 | const me = this; 594 | const reqParams = me._getReqParams(); 595 | 596 | me._request = request.post(reqParams, (err, res) => { 597 | if (me.isDebug) { 598 | console.log('QueryCursor.exec: result', me.query, err, _.pick(res, [ 599 | 'statusCode', 600 | 'body', 601 | 'statusMessage', 602 | 'headers' 603 | ])); 604 | } 605 | 606 | if (err) { 607 | return cb(err); 608 | } else if (res.statusCode !== 200) { 609 | return cb( 610 | getErrorObj(res) 611 | ); 612 | } 613 | 614 | if ( ! res.body) { 615 | return cb(null, {r: 1}); 616 | } 617 | 618 | try { 619 | const data = this.opts.raw ? res.body : me.getBodyParser()(res.body); 620 | 621 | if (me.format === FORMAT_NAMES.JSON) { 622 | if (me.useTotals) { 623 | return cb(null, data); 624 | } 625 | 626 | return this.opts.raw ? cb(null, data) : cb(null, data.data); 627 | } 628 | 629 | if (me.useTotals) { 630 | return cb(null, { 631 | meta: {}, 632 | data, 633 | totals: {}, 634 | rows: {}, 635 | statistics: {}, 636 | }); 637 | } 638 | 639 | return cb(null, data); 640 | } catch (err) { 641 | cb(err); 642 | } 643 | }); 644 | } 645 | 646 | getBodyParser() { 647 | if (this.format === FORMAT_NAMES.JSON) { 648 | return JSON.parse; 649 | } 650 | 651 | if (this.format === FORMAT_NAMES.TSV) { 652 | return parseTSV; 653 | } 654 | 655 | if (this.format === FORMAT_NAMES.CSV) { 656 | return parseCSV; 657 | } 658 | 659 | throw new Error(`CursorQuery.getBodyParser: unknown format "${this.format}"`); 660 | }; 661 | 662 | getStreamParser() { 663 | if (this.format === FORMAT_NAMES.JSON) { 664 | return parseJSONStream; 665 | } 666 | 667 | if (this.format === FORMAT_NAMES.TSV) { 668 | return parseTSVStream; 669 | } 670 | 671 | if (this.format === FORMAT_NAMES.CSV) { 672 | return parseCSVStream; 673 | } 674 | 675 | throw new Error(`CursorQuery.getStreamParser: unknown format "${this.format}"`); 676 | } 677 | 678 | withTotals() { 679 | this.useTotals = true; 680 | 681 | return this; 682 | } 683 | 684 | toPromise() { 685 | let me = this; 686 | 687 | return new Promise((resolve, reject) => { 688 | me.exec(function (err, data) { 689 | if (err) return reject(err); 690 | 691 | resolve(data); 692 | }) 693 | }); 694 | } 695 | 696 | stream() { 697 | const me = this; 698 | 699 | const reqParams = me._getReqParams(); 700 | 701 | if (me.isInsert) { 702 | const rs = new Rs(reqParams); 703 | rs.query = me.query; 704 | 705 | me._request = rs; 706 | 707 | return rs; 708 | } else { 709 | const streamParser = this.getStreamParser()(); 710 | 711 | const rs = new Readable({ objectMode: true }); 712 | rs._read = () => {}; 713 | rs.query = me.query; 714 | 715 | const tf = new Transform({ objectMode: true }); 716 | let isFirstChunk = true; 717 | tf._transform = function (chunk, encoding, cb) { 718 | 719 | // В независимости от формата, в случае ошибки, в теле ответа будет текс, 720 | // подпадающий под регулярку R_ERROR. 721 | if (isFirstChunk) { 722 | isFirstChunk = false; 723 | 724 | if (R_ERROR.test(chunk.toString())) { 725 | streamParser.emit('error', new Error(chunk.toString())); 726 | rs.emit('close'); 727 | 728 | return cb(); 729 | } 730 | } 731 | 732 | cb(null, chunk); 733 | }; 734 | 735 | let metaData = {}; 736 | 737 | const requestStream = request.post(reqParams); 738 | 739 | // handle network socket errors to avoid uncaught error 740 | requestStream.on('error', function (err) { 741 | rs.emit('error', err); 742 | }); 743 | 744 | // Не делаем .pipe(rs) потому что rs - Readable, 745 | // а для pipe нужен Writable 746 | let s; 747 | if (me.connection.isUseGzip) { 748 | const z = zlib.createGunzip(); 749 | s = requestStream.pipe(z).pipe(tf).pipe(streamParser) 750 | } else { 751 | s = requestStream.pipe(tf).pipe(streamParser) 752 | } 753 | 754 | s 755 | .on('error', function (err) { 756 | rs.emit('error', err); 757 | }) 758 | .on('header', header => { 759 | metaData = _.merge({}, header); 760 | }) 761 | .on('footer', footer => { 762 | rs.emit('meta', _.merge(metaData, footer)); 763 | }) 764 | .on('data', function (data) { 765 | rs.emit('data', data); 766 | }) 767 | .on('close', function () { 768 | rs.emit('close'); 769 | }) 770 | .on('end', function () { 771 | rs.emit('end'); 772 | }); 773 | 774 | rs.__pause = rs.pause; 775 | rs.pause = () => { 776 | rs.__pause(); 777 | requestStream.pause(); 778 | streamParser.pause(); 779 | }; 780 | 781 | rs.__resume = rs.resume; 782 | rs.resume = () => { 783 | rs.__resume(); 784 | requestStream.resume(); 785 | streamParser.resume(); 786 | }; 787 | 788 | me._request = rs; 789 | 790 | return stream2asynciter(rs); 791 | } 792 | } 793 | 794 | 795 | destroy() { 796 | const me = this; 797 | 798 | let isCallDestroy = false; 799 | 800 | if (me._request instanceof Readable) { 801 | isCallDestroy = true; 802 | me._request.destroy(); 803 | } else if (me._request) { 804 | isCallDestroy = true; 805 | me._request.abort(); 806 | } 807 | 808 | // To trying to kill query by query id 809 | if (me.queryId) { 810 | 811 | // Because this realesation work with session witout any ideas, 812 | // we need use this hack 813 | me.connection.query( 814 | `KILL QUERY WHERE query_id = '${me.queryId}' SYNC`, {}, { 815 | sessionId: uuidv4(), 816 | } 817 | ).exec(() => {}); 818 | } 819 | 820 | if (isCallDestroy) { 821 | return ; 822 | } 823 | 824 | throw new Error('QueryCursor.destroy error: private field _request is invalid'); 825 | } 826 | } 827 | 828 | 829 | class ClickHouse { 830 | constructor(opts = {}) { 831 | this.opts = _.merge( 832 | { 833 | debug: false, 834 | database: DATABASE, 835 | password: '', 836 | basicAuth: null, 837 | isUseGzip: false, 838 | config: { 839 | session_timeout : 60, 840 | output_format_json_quote_64bit_integers : 0, 841 | enable_http_compression : 0 842 | }, 843 | format: FORMAT_NAMES.JSON, 844 | raw: false, 845 | isSessionPerQuery: false, 846 | trimQuery: false, 847 | usePost: false, 848 | }, 849 | opts 850 | ); 851 | 852 | 853 | let url = opts.url || opts.host || URI, 854 | port = opts.port || PORT; 855 | 856 | if ( ! url.match(/^https?/)) { 857 | url = 'http://' + url; 858 | } 859 | 860 | const u = new URL(url); 861 | 862 | if (u.protocol === 'https:' && (port === 443 || !opts.port)) { 863 | u.port = ''; 864 | } else if (! u.port && port) { 865 | u.port = port; 866 | } 867 | 868 | this.opts.url = u.toString(); 869 | 870 | if (this.opts.user || this.opts.username) { 871 | this.opts.username = this.opts.user || this.opts.username; 872 | } 873 | 874 | 875 | if (this.opts.config) { 876 | const { database } = this.opts.config; 877 | 878 | if (database && database !== this.opts.database) { 879 | this.opts.database = database; 880 | } 881 | } 882 | } 883 | 884 | get sessionId() { 885 | return this.opts.config.session_id; 886 | } 887 | 888 | set sessionId(sessionId) { 889 | this.opts.config.session_id = '' + sessionId; 890 | return this; 891 | } 892 | 893 | noSession() { 894 | delete this.opts.config.session_id; 895 | 896 | return this; 897 | } 898 | 899 | get sessionPerQuery() { 900 | return this.opts.isSessionPerQuery; 901 | } 902 | 903 | setSessionPerQuery(value) { 904 | this.opts.isSessionPerQuery = !!value; 905 | 906 | return this; 907 | } 908 | 909 | get sessionTimeout() { 910 | return this.opts.config.session_timeout; 911 | } 912 | 913 | set sessionTimeout(timeout) { 914 | this.opts.config.session_timeout = timeout; 915 | return this; 916 | } 917 | 918 | get url() { 919 | if (this.opts.basicAuth) { 920 | const u = new URL(this.opts.url); 921 | 922 | u.username = this.opts.basicAuth.username || ''; 923 | u.password = this.opts.basicAuth.password || ''; 924 | 925 | return u.toString(); 926 | } 927 | 928 | return this.opts.url; 929 | } 930 | 931 | set url(url) { 932 | this.opts.url = url; 933 | return this; 934 | } 935 | 936 | get port() { 937 | return this.opts.port; 938 | } 939 | 940 | set port(port) { 941 | this.opts.port = port; 942 | return this; 943 | } 944 | 945 | get isUseGzip() { 946 | return this.opts.isUseGzip; 947 | } 948 | 949 | set isUseGzip(val) { 950 | this.opts.isUseGzip = !!val; 951 | 952 | this.opts.config.enable_http_compression = this.opts.isUseGzip ? 1 : 0; 953 | } 954 | 955 | get bodyParser() { 956 | if (this.opts.format === FORMAT_NAMES.CSV) { 957 | return parseCSV; 958 | } else if (this.opts.format === FORMAT_NAMES.TSV) { 959 | return parseTSV; 960 | } else { 961 | return JSON.parse; 962 | } 963 | } 964 | 965 | get trimQuery() { 966 | return this.opts.trimQuery; 967 | } 968 | 969 | set trimQuery(val) { 970 | this.opts.trimQuery = !!val; 971 | return this; 972 | } 973 | 974 | get usePost() { 975 | return this.opts.usePost; 976 | } 977 | 978 | set usePost(val) { 979 | this.opts.usePost = !!val; 980 | return this; 981 | } 982 | 983 | static mapRowAsArray(row) { 984 | return row 985 | .map(value => encodeValue(false, value, 'TabSeparated')) 986 | .join('\t'); 987 | } 988 | 989 | static mapRowAsObject(fieldList, row) { 990 | return fieldList 991 | .map(f => { 992 | return encodeValue(false, row[f] != null ? row[f] : '', 'TabSeparated'); 993 | }) 994 | .join('\t'); 995 | } 996 | 997 | static getFullFormatName(format = '') { 998 | if ( ! FORMATS[format]) { 999 | throw new Error(`Clickhouse.getFullFormatName: unknown format "${format}`); 1000 | } 1001 | 1002 | return FORMATS[format]; 1003 | } 1004 | 1005 | static getFormatFromQuery(query = '') { 1006 | if ( ! query) { 1007 | throw new Error(`Clickhouse.getFormatFromQuery: query is empty!`); 1008 | } 1009 | 1010 | // We use regexp with "g" flag then match doen't return first group. 1011 | // So, use exec. 1012 | const m = R_FORMAT_PARSER.exec(query); 1013 | if (m) { 1014 | const format = m[1]; 1015 | if ( ! REVERSE_FORMATS[format]) { 1016 | throw new Error(`Clickhouse.getFormatFromQuery: unknown format "${format}"!`); 1017 | } 1018 | 1019 | return REVERSE_FORMATS[format]; 1020 | } 1021 | 1022 | return ''; 1023 | } 1024 | 1025 | static getFormats() { 1026 | return Object.keys(FORMATS).map(k => ({ format: k, fullFormatExpr: FORMATS[k], })); 1027 | } 1028 | 1029 | query(...args) { 1030 | if (typeof args[args.length - 1] === 'function') { 1031 | const newArgs = args.slice(0, args.length); 1032 | const cb = args[args.length - 1]; 1033 | 1034 | return new QueryCursor(this, ...newArgs).exec(cb); 1035 | } 1036 | 1037 | return new QueryCursor(this, ...args); 1038 | } 1039 | 1040 | insert(query, data) { 1041 | return new QueryCursor(this, query, data); 1042 | } 1043 | } 1044 | 1045 | module.exports = { 1046 | ClickHouse, 1047 | }; 1048 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clickhouse", 3 | "version": "2.5.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@ungap/promise-all-settled": { 8 | "version": "1.1.2", 9 | "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", 10 | "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", 11 | "dev": true 12 | }, 13 | "JSONStream": { 14 | "version": "1.3.4", 15 | "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.4.tgz", 16 | "integrity": "sha512-Y7vfi3I5oMOYIr+WxV8NZxDSwcbNgzdKYsTNInmycOq9bUYwGg9ryu57Wg5NLmCjqdFPNUmpMBo3kSJN9tCbXg==", 17 | "requires": { 18 | "jsonparse": "^1.2.0", 19 | "through": ">=2.2.7 <3" 20 | } 21 | }, 22 | "ajv": { 23 | "version": "6.12.6", 24 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 25 | "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 26 | "requires": { 27 | "fast-deep-equal": "^3.1.1", 28 | "fast-json-stable-stringify": "^2.0.0", 29 | "json-schema-traverse": "^0.4.1", 30 | "uri-js": "^4.2.2" 31 | } 32 | }, 33 | "ansi-colors": { 34 | "version": "4.1.1", 35 | "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", 36 | "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", 37 | "dev": true 38 | }, 39 | "ansi-regex": { 40 | "version": "3.0.1", 41 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", 42 | "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", 43 | "dev": true 44 | }, 45 | "ansi-styles": { 46 | "version": "4.3.0", 47 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 48 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 49 | "dev": true, 50 | "requires": { 51 | "color-convert": "^2.0.1" 52 | } 53 | }, 54 | "anymatch": { 55 | "version": "3.1.2", 56 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", 57 | "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", 58 | "dev": true, 59 | "requires": { 60 | "normalize-path": "^3.0.0", 61 | "picomatch": "^2.0.4" 62 | } 63 | }, 64 | "argparse": { 65 | "version": "1.0.10", 66 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 67 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 68 | "dev": true, 69 | "requires": { 70 | "sprintf-js": "~1.0.2" 71 | } 72 | }, 73 | "asn1": { 74 | "version": "0.2.6", 75 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", 76 | "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", 77 | "requires": { 78 | "safer-buffer": "~2.1.0" 79 | } 80 | }, 81 | "assert-plus": { 82 | "version": "1.0.0", 83 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 84 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 85 | }, 86 | "asynckit": { 87 | "version": "0.4.0", 88 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 89 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 90 | }, 91 | "aws-sign2": { 92 | "version": "0.7.0", 93 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", 94 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" 95 | }, 96 | "aws4": { 97 | "version": "1.11.0", 98 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", 99 | "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" 100 | }, 101 | "balanced-match": { 102 | "version": "1.0.2", 103 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 104 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 105 | "dev": true 106 | }, 107 | "bcrypt-pbkdf": { 108 | "version": "1.0.2", 109 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", 110 | "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", 111 | "requires": { 112 | "tweetnacl": "^0.14.3" 113 | } 114 | }, 115 | "binary-extensions": { 116 | "version": "2.2.0", 117 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", 118 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", 119 | "dev": true 120 | }, 121 | "brace-expansion": { 122 | "version": "1.1.11", 123 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 124 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 125 | "dev": true, 126 | "requires": { 127 | "balanced-match": "^1.0.0", 128 | "concat-map": "0.0.1" 129 | } 130 | }, 131 | "braces": { 132 | "version": "3.0.2", 133 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 134 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 135 | "dev": true, 136 | "requires": { 137 | "fill-range": "^7.0.1" 138 | } 139 | }, 140 | "browser-stdout": { 141 | "version": "1.3.1", 142 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", 143 | "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", 144 | "dev": true 145 | }, 146 | "camelcase": { 147 | "version": "5.3.1", 148 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", 149 | "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", 150 | "dev": true 151 | }, 152 | "caseless": { 153 | "version": "0.12.0", 154 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 155 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" 156 | }, 157 | "chalk": { 158 | "version": "4.1.2", 159 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 160 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 161 | "dev": true, 162 | "requires": { 163 | "ansi-styles": "^4.1.0", 164 | "supports-color": "^7.1.0" 165 | } 166 | }, 167 | "chokidar": { 168 | "version": "3.4.3", 169 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", 170 | "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", 171 | "dev": true, 172 | "requires": { 173 | "anymatch": "~3.1.1", 174 | "braces": "~3.0.2", 175 | "fsevents": "~2.1.2", 176 | "glob-parent": "~5.1.0", 177 | "is-binary-path": "~2.1.0", 178 | "is-glob": "~4.0.1", 179 | "normalize-path": "~3.0.0", 180 | "readdirp": "~3.5.0" 181 | } 182 | }, 183 | "cliui": { 184 | "version": "5.0.0", 185 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", 186 | "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", 187 | "dev": true, 188 | "requires": { 189 | "string-width": "^3.1.0", 190 | "strip-ansi": "^5.2.0", 191 | "wrap-ansi": "^5.1.0" 192 | }, 193 | "dependencies": { 194 | "ansi-regex": { 195 | "version": "4.1.1", 196 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", 197 | "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", 198 | "dev": true 199 | }, 200 | "string-width": { 201 | "version": "3.1.0", 202 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", 203 | "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", 204 | "dev": true, 205 | "requires": { 206 | "emoji-regex": "^7.0.1", 207 | "is-fullwidth-code-point": "^2.0.0", 208 | "strip-ansi": "^5.1.0" 209 | } 210 | }, 211 | "strip-ansi": { 212 | "version": "5.2.0", 213 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", 214 | "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", 215 | "dev": true, 216 | "requires": { 217 | "ansi-regex": "^4.1.0" 218 | } 219 | } 220 | } 221 | }, 222 | "color-convert": { 223 | "version": "2.0.1", 224 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 225 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 226 | "dev": true, 227 | "requires": { 228 | "color-name": "~1.1.4" 229 | } 230 | }, 231 | "color-name": { 232 | "version": "1.1.4", 233 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 234 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 235 | "dev": true 236 | }, 237 | "combined-stream": { 238 | "version": "1.0.8", 239 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 240 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 241 | "requires": { 242 | "delayed-stream": "~1.0.0" 243 | } 244 | }, 245 | "concat-map": { 246 | "version": "0.0.1", 247 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 248 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 249 | "dev": true 250 | }, 251 | "core-util-is": { 252 | "version": "1.0.2", 253 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 254 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 255 | }, 256 | "dashdash": { 257 | "version": "1.14.1", 258 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 259 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 260 | "requires": { 261 | "assert-plus": "^1.0.0" 262 | } 263 | }, 264 | "debug": { 265 | "version": "4.2.0", 266 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", 267 | "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", 268 | "dev": true, 269 | "requires": { 270 | "ms": "2.1.2" 271 | } 272 | }, 273 | "decamelize": { 274 | "version": "1.2.0", 275 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", 276 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", 277 | "dev": true 278 | }, 279 | "delayed-stream": { 280 | "version": "1.0.0", 281 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 282 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 283 | }, 284 | "diff": { 285 | "version": "4.0.2", 286 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 287 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 288 | "dev": true 289 | }, 290 | "ecc-jsbn": { 291 | "version": "0.1.2", 292 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", 293 | "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", 294 | "requires": { 295 | "jsbn": "~0.1.0", 296 | "safer-buffer": "^2.1.0" 297 | } 298 | }, 299 | "emoji-regex": { 300 | "version": "7.0.3", 301 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", 302 | "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", 303 | "dev": true 304 | }, 305 | "escape-string-regexp": { 306 | "version": "4.0.0", 307 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 308 | "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 309 | "dev": true 310 | }, 311 | "esprima": { 312 | "version": "4.0.1", 313 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 314 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", 315 | "dev": true 316 | }, 317 | "expect.js": { 318 | "version": "0.3.1", 319 | "resolved": "https://registry.npmjs.org/expect.js/-/expect.js-0.3.1.tgz", 320 | "integrity": "sha1-sKWaDS7/VDdUTr8M6qYBWEHQm1s=", 321 | "dev": true 322 | }, 323 | "extend": { 324 | "version": "3.0.2", 325 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", 326 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" 327 | }, 328 | "extsprintf": { 329 | "version": "1.3.0", 330 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", 331 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" 332 | }, 333 | "fast-deep-equal": { 334 | "version": "3.1.3", 335 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 336 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" 337 | }, 338 | "fast-json-stable-stringify": { 339 | "version": "2.1.0", 340 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 341 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" 342 | }, 343 | "fill-range": { 344 | "version": "7.0.1", 345 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 346 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 347 | "dev": true, 348 | "requires": { 349 | "to-regex-range": "^5.0.1" 350 | } 351 | }, 352 | "find-up": { 353 | "version": "5.0.0", 354 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", 355 | "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", 356 | "dev": true, 357 | "requires": { 358 | "locate-path": "^6.0.0", 359 | "path-exists": "^4.0.0" 360 | } 361 | }, 362 | "flat": { 363 | "version": "5.0.2", 364 | "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", 365 | "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", 366 | "dev": true 367 | }, 368 | "forever-agent": { 369 | "version": "0.6.1", 370 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 371 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" 372 | }, 373 | "form-data": { 374 | "version": "2.3.3", 375 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", 376 | "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", 377 | "requires": { 378 | "asynckit": "^0.4.0", 379 | "combined-stream": "^1.0.6", 380 | "mime-types": "^2.1.12" 381 | } 382 | }, 383 | "fs.realpath": { 384 | "version": "1.0.0", 385 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 386 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 387 | "dev": true 388 | }, 389 | "fsevents": { 390 | "version": "2.1.3", 391 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", 392 | "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", 393 | "dev": true, 394 | "optional": true 395 | }, 396 | "get-caller-file": { 397 | "version": "2.0.5", 398 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 399 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", 400 | "dev": true 401 | }, 402 | "getpass": { 403 | "version": "0.1.7", 404 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 405 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", 406 | "requires": { 407 | "assert-plus": "^1.0.0" 408 | } 409 | }, 410 | "glob": { 411 | "version": "7.1.6", 412 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 413 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 414 | "dev": true, 415 | "requires": { 416 | "fs.realpath": "^1.0.0", 417 | "inflight": "^1.0.4", 418 | "inherits": "2", 419 | "minimatch": "^3.0.4", 420 | "once": "^1.3.0", 421 | "path-is-absolute": "^1.0.0" 422 | } 423 | }, 424 | "glob-parent": { 425 | "version": "5.1.2", 426 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 427 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 428 | "dev": true, 429 | "requires": { 430 | "is-glob": "^4.0.1" 431 | } 432 | }, 433 | "growl": { 434 | "version": "1.10.5", 435 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", 436 | "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", 437 | "dev": true 438 | }, 439 | "har-schema": { 440 | "version": "2.0.0", 441 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", 442 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" 443 | }, 444 | "har-validator": { 445 | "version": "5.1.5", 446 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", 447 | "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", 448 | "requires": { 449 | "ajv": "^6.12.3", 450 | "har-schema": "^2.0.0" 451 | } 452 | }, 453 | "has-flag": { 454 | "version": "4.0.0", 455 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 456 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 457 | "dev": true 458 | }, 459 | "he": { 460 | "version": "1.2.0", 461 | "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", 462 | "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", 463 | "dev": true 464 | }, 465 | "http-signature": { 466 | "version": "1.2.0", 467 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", 468 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", 469 | "requires": { 470 | "assert-plus": "^1.0.0", 471 | "jsprim": "^1.2.2", 472 | "sshpk": "^1.7.0" 473 | } 474 | }, 475 | "inflight": { 476 | "version": "1.0.6", 477 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 478 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 479 | "dev": true, 480 | "requires": { 481 | "once": "^1.3.0", 482 | "wrappy": "1" 483 | } 484 | }, 485 | "inherits": { 486 | "version": "2.0.4", 487 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 488 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 489 | "dev": true 490 | }, 491 | "is-binary-path": { 492 | "version": "2.1.0", 493 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 494 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 495 | "dev": true, 496 | "requires": { 497 | "binary-extensions": "^2.0.0" 498 | } 499 | }, 500 | "is-extglob": { 501 | "version": "2.1.1", 502 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 503 | "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", 504 | "dev": true 505 | }, 506 | "is-fullwidth-code-point": { 507 | "version": "2.0.0", 508 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", 509 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", 510 | "dev": true 511 | }, 512 | "is-glob": { 513 | "version": "4.0.3", 514 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 515 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 516 | "dev": true, 517 | "requires": { 518 | "is-extglob": "^2.1.1" 519 | } 520 | }, 521 | "is-number": { 522 | "version": "7.0.0", 523 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 524 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 525 | "dev": true 526 | }, 527 | "is-plain-obj": { 528 | "version": "2.1.0", 529 | "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", 530 | "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", 531 | "dev": true 532 | }, 533 | "is-typedarray": { 534 | "version": "1.0.0", 535 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 536 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" 537 | }, 538 | "isexe": { 539 | "version": "2.0.0", 540 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 541 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", 542 | "dev": true 543 | }, 544 | "isstream": { 545 | "version": "0.1.2", 546 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 547 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" 548 | }, 549 | "js-yaml": { 550 | "version": "3.14.0", 551 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", 552 | "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", 553 | "dev": true, 554 | "requires": { 555 | "argparse": "^1.0.7", 556 | "esprima": "^4.0.0" 557 | } 558 | }, 559 | "jsbn": { 560 | "version": "0.1.1", 561 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 562 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" 563 | }, 564 | "json-schema": { 565 | "version": "0.4.0", 566 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", 567 | "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" 568 | }, 569 | "json-schema-traverse": { 570 | "version": "0.4.1", 571 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 572 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" 573 | }, 574 | "json-stringify-safe": { 575 | "version": "5.0.1", 576 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 577 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" 578 | }, 579 | "jsonparse": { 580 | "version": "1.3.1", 581 | "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", 582 | "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" 583 | }, 584 | "jsprim": { 585 | "version": "1.4.2", 586 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", 587 | "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", 588 | "requires": { 589 | "assert-plus": "1.0.0", 590 | "extsprintf": "1.3.0", 591 | "json-schema": "0.4.0", 592 | "verror": "1.10.0" 593 | } 594 | }, 595 | "locate-path": { 596 | "version": "6.0.0", 597 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", 598 | "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", 599 | "dev": true, 600 | "requires": { 601 | "p-locate": "^5.0.0" 602 | } 603 | }, 604 | "lodash": { 605 | "version": "4.17.21", 606 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 607 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" 608 | }, 609 | "log-symbols": { 610 | "version": "4.0.0", 611 | "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", 612 | "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", 613 | "dev": true, 614 | "requires": { 615 | "chalk": "^4.0.0" 616 | } 617 | }, 618 | "mime-db": { 619 | "version": "1.52.0", 620 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 621 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" 622 | }, 623 | "mime-types": { 624 | "version": "2.1.35", 625 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 626 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 627 | "requires": { 628 | "mime-db": "1.52.0" 629 | } 630 | }, 631 | "minimatch": { 632 | "version": "3.0.4", 633 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 634 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 635 | "dev": true, 636 | "requires": { 637 | "brace-expansion": "^1.1.7" 638 | } 639 | }, 640 | "mocha": { 641 | "version": "8.2.0", 642 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.2.0.tgz", 643 | "integrity": "sha512-lEWEMq2LMfNJMKeuEwb5UELi+OgFDollXaytR5ggQcHpzG3NP/R7rvixAvF+9/lLsTWhWG+4yD2M70GsM06nxw==", 644 | "dev": true, 645 | "requires": { 646 | "@ungap/promise-all-settled": "1.1.2", 647 | "ansi-colors": "4.1.1", 648 | "browser-stdout": "1.3.1", 649 | "chokidar": "3.4.3", 650 | "debug": "4.2.0", 651 | "diff": "4.0.2", 652 | "escape-string-regexp": "4.0.0", 653 | "find-up": "5.0.0", 654 | "glob": "7.1.6", 655 | "growl": "1.10.5", 656 | "he": "1.2.0", 657 | "js-yaml": "3.14.0", 658 | "log-symbols": "4.0.0", 659 | "minimatch": "3.0.4", 660 | "ms": "2.1.2", 661 | "nanoid": "3.1.12", 662 | "serialize-javascript": "5.0.1", 663 | "strip-json-comments": "3.1.1", 664 | "supports-color": "7.2.0", 665 | "which": "2.0.2", 666 | "wide-align": "1.1.3", 667 | "workerpool": "6.0.2", 668 | "yargs": "13.3.2", 669 | "yargs-parser": "13.1.2", 670 | "yargs-unparser": "2.0.0" 671 | } 672 | }, 673 | "ms": { 674 | "version": "2.1.2", 675 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 676 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 677 | "dev": true 678 | }, 679 | "nanoid": { 680 | "version": "3.1.12", 681 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.12.tgz", 682 | "integrity": "sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==", 683 | "dev": true 684 | }, 685 | "normalize-path": { 686 | "version": "3.0.0", 687 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 688 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 689 | "dev": true 690 | }, 691 | "oauth-sign": { 692 | "version": "0.9.0", 693 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", 694 | "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" 695 | }, 696 | "once": { 697 | "version": "1.4.0", 698 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 699 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 700 | "dev": true, 701 | "requires": { 702 | "wrappy": "1" 703 | } 704 | }, 705 | "p-limit": { 706 | "version": "3.1.0", 707 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", 708 | "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", 709 | "dev": true, 710 | "requires": { 711 | "yocto-queue": "^0.1.0" 712 | } 713 | }, 714 | "p-locate": { 715 | "version": "5.0.0", 716 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", 717 | "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", 718 | "dev": true, 719 | "requires": { 720 | "p-limit": "^3.0.2" 721 | } 722 | }, 723 | "p-try": { 724 | "version": "2.2.0", 725 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", 726 | "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", 727 | "dev": true 728 | }, 729 | "path-exists": { 730 | "version": "4.0.0", 731 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 732 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 733 | "dev": true 734 | }, 735 | "path-is-absolute": { 736 | "version": "1.0.1", 737 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 738 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 739 | "dev": true 740 | }, 741 | "performance-now": { 742 | "version": "2.1.0", 743 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 744 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" 745 | }, 746 | "picomatch": { 747 | "version": "2.3.1", 748 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 749 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 750 | "dev": true 751 | }, 752 | "psl": { 753 | "version": "1.8.0", 754 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", 755 | "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" 756 | }, 757 | "punycode": { 758 | "version": "2.1.1", 759 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 760 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" 761 | }, 762 | "qs": { 763 | "version": "6.5.3", 764 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", 765 | "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==" 766 | }, 767 | "querystring": { 768 | "version": "0.2.0", 769 | "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", 770 | "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" 771 | }, 772 | "randombytes": { 773 | "version": "2.1.0", 774 | "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", 775 | "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", 776 | "dev": true, 777 | "requires": { 778 | "safe-buffer": "^5.1.0" 779 | } 780 | }, 781 | "readdirp": { 782 | "version": "3.5.0", 783 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", 784 | "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", 785 | "dev": true, 786 | "requires": { 787 | "picomatch": "^2.2.1" 788 | } 789 | }, 790 | "request": { 791 | "version": "2.88.0", 792 | "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", 793 | "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", 794 | "requires": { 795 | "aws-sign2": "~0.7.0", 796 | "aws4": "^1.8.0", 797 | "caseless": "~0.12.0", 798 | "combined-stream": "~1.0.6", 799 | "extend": "~3.0.2", 800 | "forever-agent": "~0.6.1", 801 | "form-data": "~2.3.2", 802 | "har-validator": "~5.1.0", 803 | "http-signature": "~1.2.0", 804 | "is-typedarray": "~1.0.0", 805 | "isstream": "~0.1.2", 806 | "json-stringify-safe": "~5.0.1", 807 | "mime-types": "~2.1.19", 808 | "oauth-sign": "~0.9.0", 809 | "performance-now": "^2.1.0", 810 | "qs": "~6.5.2", 811 | "safe-buffer": "^5.1.2", 812 | "tough-cookie": "~2.4.3", 813 | "tunnel-agent": "^0.6.0", 814 | "uuid": "^3.3.2" 815 | } 816 | }, 817 | "require-directory": { 818 | "version": "2.1.1", 819 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 820 | "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", 821 | "dev": true 822 | }, 823 | "require-main-filename": { 824 | "version": "2.0.0", 825 | "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", 826 | "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", 827 | "dev": true 828 | }, 829 | "safe-buffer": { 830 | "version": "5.2.1", 831 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 832 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 833 | }, 834 | "safer-buffer": { 835 | "version": "2.1.2", 836 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 837 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 838 | }, 839 | "serialize-javascript": { 840 | "version": "5.0.1", 841 | "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", 842 | "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", 843 | "dev": true, 844 | "requires": { 845 | "randombytes": "^2.1.0" 846 | } 847 | }, 848 | "set-blocking": { 849 | "version": "2.0.0", 850 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 851 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", 852 | "dev": true 853 | }, 854 | "sprintf-js": { 855 | "version": "1.0.3", 856 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 857 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", 858 | "dev": true 859 | }, 860 | "sshpk": { 861 | "version": "1.17.0", 862 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", 863 | "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", 864 | "requires": { 865 | "asn1": "~0.2.3", 866 | "assert-plus": "^1.0.0", 867 | "bcrypt-pbkdf": "^1.0.0", 868 | "dashdash": "^1.12.0", 869 | "ecc-jsbn": "~0.1.1", 870 | "getpass": "^0.1.1", 871 | "jsbn": "~0.1.0", 872 | "safer-buffer": "^2.0.2", 873 | "tweetnacl": "~0.14.0" 874 | } 875 | }, 876 | "stream2asynciter": { 877 | "version": "1.0.3", 878 | "resolved": "https://registry.npmjs.org/stream2asynciter/-/stream2asynciter-1.0.3.tgz", 879 | "integrity": "sha512-9/dEZW+LQjuW6ub5hmWi4n9Pn8W8qA8k7NAE1isecesA164e73xTdy1CJ3S9o9YS+O21HuiK7T+4uS7FgKDy4w==" 880 | }, 881 | "string-width": { 882 | "version": "2.1.1", 883 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", 884 | "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", 885 | "dev": true, 886 | "requires": { 887 | "is-fullwidth-code-point": "^2.0.0", 888 | "strip-ansi": "^4.0.0" 889 | } 890 | }, 891 | "strip-ansi": { 892 | "version": "4.0.0", 893 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", 894 | "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", 895 | "dev": true, 896 | "requires": { 897 | "ansi-regex": "^3.0.0" 898 | } 899 | }, 900 | "strip-json-comments": { 901 | "version": "3.1.1", 902 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 903 | "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", 904 | "dev": true 905 | }, 906 | "supports-color": { 907 | "version": "7.2.0", 908 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 909 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 910 | "dev": true, 911 | "requires": { 912 | "has-flag": "^4.0.0" 913 | } 914 | }, 915 | "through": { 916 | "version": "2.3.8", 917 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 918 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" 919 | }, 920 | "to-regex-range": { 921 | "version": "5.0.1", 922 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 923 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 924 | "dev": true, 925 | "requires": { 926 | "is-number": "^7.0.0" 927 | } 928 | }, 929 | "tough-cookie": { 930 | "version": "2.4.3", 931 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", 932 | "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", 933 | "requires": { 934 | "psl": "^1.1.24", 935 | "punycode": "^1.4.1" 936 | }, 937 | "dependencies": { 938 | "punycode": { 939 | "version": "1.4.1", 940 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", 941 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" 942 | } 943 | } 944 | }, 945 | "tsv": { 946 | "version": "0.2.0", 947 | "resolved": "https://registry.npmjs.org/tsv/-/tsv-0.2.0.tgz", 948 | "integrity": "sha1-koaaPLX1AzLz3JD8qCvmZ9tvctY=" 949 | }, 950 | "tunnel-agent": { 951 | "version": "0.6.0", 952 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 953 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", 954 | "requires": { 955 | "safe-buffer": "^5.0.1" 956 | } 957 | }, 958 | "tweetnacl": { 959 | "version": "0.14.5", 960 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 961 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" 962 | }, 963 | "uri-js": { 964 | "version": "4.4.1", 965 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 966 | "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 967 | "requires": { 968 | "punycode": "^2.1.0" 969 | } 970 | }, 971 | "uuid": { 972 | "version": "3.4.0", 973 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", 974 | "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" 975 | }, 976 | "verror": { 977 | "version": "1.10.0", 978 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 979 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", 980 | "requires": { 981 | "assert-plus": "^1.0.0", 982 | "core-util-is": "1.0.2", 983 | "extsprintf": "^1.2.0" 984 | } 985 | }, 986 | "which": { 987 | "version": "2.0.2", 988 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 989 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 990 | "dev": true, 991 | "requires": { 992 | "isexe": "^2.0.0" 993 | } 994 | }, 995 | "which-module": { 996 | "version": "2.0.0", 997 | "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", 998 | "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", 999 | "dev": true 1000 | }, 1001 | "wide-align": { 1002 | "version": "1.1.3", 1003 | "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", 1004 | "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", 1005 | "dev": true, 1006 | "requires": { 1007 | "string-width": "^1.0.2 || 2" 1008 | } 1009 | }, 1010 | "workerpool": { 1011 | "version": "6.0.2", 1012 | "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.2.tgz", 1013 | "integrity": "sha512-DSNyvOpFKrNusaaUwk+ej6cBj1bmhLcBfj80elGk+ZIo5JSkq+unB1dLKEOcNfJDZgjGICfhQ0Q5TbP0PvF4+Q==", 1014 | "dev": true 1015 | }, 1016 | "wrap-ansi": { 1017 | "version": "5.1.0", 1018 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", 1019 | "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", 1020 | "dev": true, 1021 | "requires": { 1022 | "ansi-styles": "^3.2.0", 1023 | "string-width": "^3.0.0", 1024 | "strip-ansi": "^5.0.0" 1025 | }, 1026 | "dependencies": { 1027 | "ansi-regex": { 1028 | "version": "4.1.1", 1029 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", 1030 | "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", 1031 | "dev": true 1032 | }, 1033 | "ansi-styles": { 1034 | "version": "3.2.1", 1035 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 1036 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 1037 | "dev": true, 1038 | "requires": { 1039 | "color-convert": "^1.9.0" 1040 | } 1041 | }, 1042 | "color-convert": { 1043 | "version": "1.9.3", 1044 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 1045 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 1046 | "dev": true, 1047 | "requires": { 1048 | "color-name": "1.1.3" 1049 | } 1050 | }, 1051 | "color-name": { 1052 | "version": "1.1.3", 1053 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 1054 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", 1055 | "dev": true 1056 | }, 1057 | "string-width": { 1058 | "version": "3.1.0", 1059 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", 1060 | "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", 1061 | "dev": true, 1062 | "requires": { 1063 | "emoji-regex": "^7.0.1", 1064 | "is-fullwidth-code-point": "^2.0.0", 1065 | "strip-ansi": "^5.1.0" 1066 | } 1067 | }, 1068 | "strip-ansi": { 1069 | "version": "5.2.0", 1070 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", 1071 | "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", 1072 | "dev": true, 1073 | "requires": { 1074 | "ansi-regex": "^4.1.0" 1075 | } 1076 | } 1077 | } 1078 | }, 1079 | "wrappy": { 1080 | "version": "1.0.2", 1081 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1082 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 1083 | "dev": true 1084 | }, 1085 | "y18n": { 1086 | "version": "4.0.3", 1087 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", 1088 | "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", 1089 | "dev": true 1090 | }, 1091 | "yargs": { 1092 | "version": "13.3.2", 1093 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", 1094 | "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", 1095 | "dev": true, 1096 | "requires": { 1097 | "cliui": "^5.0.0", 1098 | "find-up": "^3.0.0", 1099 | "get-caller-file": "^2.0.1", 1100 | "require-directory": "^2.1.1", 1101 | "require-main-filename": "^2.0.0", 1102 | "set-blocking": "^2.0.0", 1103 | "string-width": "^3.0.0", 1104 | "which-module": "^2.0.0", 1105 | "y18n": "^4.0.0", 1106 | "yargs-parser": "^13.1.2" 1107 | }, 1108 | "dependencies": { 1109 | "ansi-regex": { 1110 | "version": "4.1.1", 1111 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", 1112 | "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", 1113 | "dev": true 1114 | }, 1115 | "find-up": { 1116 | "version": "3.0.0", 1117 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", 1118 | "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", 1119 | "dev": true, 1120 | "requires": { 1121 | "locate-path": "^3.0.0" 1122 | } 1123 | }, 1124 | "locate-path": { 1125 | "version": "3.0.0", 1126 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", 1127 | "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", 1128 | "dev": true, 1129 | "requires": { 1130 | "p-locate": "^3.0.0", 1131 | "path-exists": "^3.0.0" 1132 | } 1133 | }, 1134 | "p-limit": { 1135 | "version": "2.3.0", 1136 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", 1137 | "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", 1138 | "dev": true, 1139 | "requires": { 1140 | "p-try": "^2.0.0" 1141 | } 1142 | }, 1143 | "p-locate": { 1144 | "version": "3.0.0", 1145 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", 1146 | "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", 1147 | "dev": true, 1148 | "requires": { 1149 | "p-limit": "^2.0.0" 1150 | } 1151 | }, 1152 | "path-exists": { 1153 | "version": "3.0.0", 1154 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", 1155 | "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", 1156 | "dev": true 1157 | }, 1158 | "string-width": { 1159 | "version": "3.1.0", 1160 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", 1161 | "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", 1162 | "dev": true, 1163 | "requires": { 1164 | "emoji-regex": "^7.0.1", 1165 | "is-fullwidth-code-point": "^2.0.0", 1166 | "strip-ansi": "^5.1.0" 1167 | } 1168 | }, 1169 | "strip-ansi": { 1170 | "version": "5.2.0", 1171 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", 1172 | "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", 1173 | "dev": true, 1174 | "requires": { 1175 | "ansi-regex": "^4.1.0" 1176 | } 1177 | } 1178 | } 1179 | }, 1180 | "yargs-parser": { 1181 | "version": "13.1.2", 1182 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", 1183 | "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", 1184 | "dev": true, 1185 | "requires": { 1186 | "camelcase": "^5.0.0", 1187 | "decamelize": "^1.2.0" 1188 | } 1189 | }, 1190 | "yargs-unparser": { 1191 | "version": "2.0.0", 1192 | "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", 1193 | "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", 1194 | "dev": true, 1195 | "requires": { 1196 | "camelcase": "^6.0.0", 1197 | "decamelize": "^4.0.0", 1198 | "flat": "^5.0.2", 1199 | "is-plain-obj": "^2.1.0" 1200 | }, 1201 | "dependencies": { 1202 | "camelcase": { 1203 | "version": "6.3.0", 1204 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", 1205 | "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", 1206 | "dev": true 1207 | }, 1208 | "decamelize": { 1209 | "version": "4.0.0", 1210 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", 1211 | "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", 1212 | "dev": true 1213 | } 1214 | } 1215 | }, 1216 | "yocto-queue": { 1217 | "version": "0.1.0", 1218 | "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", 1219 | "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", 1220 | "dev": true 1221 | } 1222 | } 1223 | } 1224 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "_from": "clickhouse@^1.2.15", 3 | "_inBundle": false, 4 | "_integrity": "sha512-uBB8BnlOJm09620hhvsrSJv9RmrgQ/AHJsQE1fwr7nD0Aof2kPjCw6r+ag3YE/f0Pgurk5cYzoPxyR2Io6VHwA==", 5 | "_location": "/clickhouse", 6 | "_phantomChildren": { 7 | "aws-sign2": "0.7.0", 8 | "aws4": "1.8.0", 9 | "caseless": "0.12.0", 10 | "combined-stream": "1.0.7", 11 | "extend": "3.0.2", 12 | "forever-agent": "0.6.1", 13 | "form-data": "2.3.3", 14 | "har-validator": "5.1.3", 15 | "http-signature": "1.2.0", 16 | "is-typedarray": "1.0.0", 17 | "isstream": "0.1.2", 18 | "json-stringify-safe": "5.0.1", 19 | "jsonparse": "1.3.1", 20 | "mime-types": "2.1.22", 21 | "oauth-sign": "0.9.0", 22 | "performance-now": "2.1.0", 23 | "safe-buffer": "5.1.2", 24 | "through": "2.3.8", 25 | "tough-cookie": "2.4.3", 26 | "tunnel-agent": "0.6.0" 27 | }, 28 | "_requested": { 29 | "type": "range", 30 | "registry": true, 31 | "raw": "clickhouse@^1.2.15", 32 | "name": "clickhouse", 33 | "escapedName": "clickhouse", 34 | "rawSpec": "^1.2.15", 35 | "saveSpec": null, 36 | "fetchSpec": "^1.2.15" 37 | }, 38 | "_requiredBy": [ 39 | "/" 40 | ], 41 | "_resolved": "https://registry.npmjs.org/clickhouse/-/clickhouse-1.2.17.tgz", 42 | "_shasum": "4922b86acdf32f0233d94704d8cfb57b9578f68d", 43 | "_spec": "clickhouse@^1.2.15", 44 | "_where": "/home/www/ukit_dev", 45 | "author": { 46 | "name": "Dmitry Berezhnov" 47 | }, 48 | "bugs": { 49 | "url": "https://github.com/TimonKK/clickhouse/issues" 50 | }, 51 | "bundleDependencies": [], 52 | "dependencies": { 53 | "JSONStream": "1.3.4", 54 | "lodash": "4.17.21", 55 | "querystring": "0.2.0", 56 | "request": "2.88.0", 57 | "stream2asynciter": "1.0.3", 58 | "through": "2.3.8", 59 | "tsv": "0.2.0", 60 | "uuid": "3.4.0" 61 | }, 62 | "deprecated": false, 63 | "description": "Client for ClickHouse", 64 | "devDependencies": { 65 | "expect.js": "0.3.1", 66 | "mocha": "8.2.0" 67 | }, 68 | "directories": {}, 69 | "dist": { 70 | "shasum": "cf85ab5364055c479b1ee71f12ba206a3ecb80b2", 71 | "tarball": "https://registry.npmjs.org/clickhouse/-/clickhouse-1.0.0.tgz" 72 | }, 73 | "homepage": "https://github.com/TimonKK/clickhouse#readme", 74 | "keywords": [ 75 | "clickhouse" 76 | ], 77 | "license": "ISC", 78 | "main": "index.js", 79 | "maintainers": [ 80 | { 81 | "name": "dmitry.berezhnov", 82 | "email": "dim901@yandex.ru" 83 | } 84 | ], 85 | "name": "clickhouse", 86 | "repository": { 87 | "type": "git", 88 | "url": "git+https://github.com/TimonKK/clickhouse.git" 89 | }, 90 | "scripts": { 91 | "test": "mocha --bail --timeout 60000 --slow 5000" 92 | }, 93 | "types": "index.d.ts", 94 | "version": "2.6.0" 95 | } 96 | -------------------------------------------------------------------------------- /test/cert/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEvjCCAqYCCQDI8x8ZQg/1izANBgkqhkiG9w0BAQsFADAhMQswCQYDVQQGEwJS 3 | VTESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIxMDYxNzE0Mjg0OVoXDTI0MDQwNjE0 4 | Mjg0OVowITELMAkGA1UEBhMCUlUxEjAQBgNVBAMMCWxvY2FsaG9zdDCCAiIwDQYJ 5 | KoZIhvcNAQEBBQADggIPADCCAgoCggIBAKW1lwkw3/mW2fyu43OAOyeAt3B/59fg 6 | OpooilKAWl6Vr05jh36rSLmZzZ2pSfT6hgxwVe0uAKqFxEOHfEQifgo3tBjC0C3X 7 | 1aAEJHsqLQV/TvN8OTufIagwus/xYG0JvvmIVU9H70M+FxM+u5s8oPu4LyexI5rM 8 | enGYEfdZyipQz1Z5QGEs4Hid5ye0G2hxrY3+M6t42XAzUGbRk/8uJMXJ0B5JhpDj 9 | 39CDnfQj+YPTsphSji+lcDx1webUmNbMi3UJsi51wMXd8KdTOoFOVgLW+2jMNkPH 10 | 3hBHY4rbY8pHX85oEXzE/+UdAAN6N5Et5t8UlF2VmJa5ZCusNqNHs5GnfpNPCW5d 11 | dLvlsCsFCok78QstVYE1yeo/e7XjKqXFrWkLM+pDfbPt2962t4pwaas/skkAxsuJ 12 | 41hQSGjXFbboNV3Hns7EoRoB7fh3pTQeVGNKkvsl1lwNWCnWpFTbwOOwKwaAQdzT 13 | b3PpyUktKhWUmRwEq894wdaLwzp7EN7bfPkujqTVdFTwD5Z46k6A24KsZM7khnhl 14 | O7Feuf5PY0L6WYgTOw/OTVGJUmEaNx72eF+B/HlaC4BixABu8g9K08xDmNlv2gyW 15 | XVPfrje9eZ30G7G6w99EB1cPSYAKAZmkpZTEGNeBAI/+YQ6eL2IT+Q6z1d2pWsiG 16 | +13sorO77KCzAgMBAAEwDQYJKoZIhvcNAQELBQADggIBAJPTHk3rKzbW0LcTGZhx 17 | DmNnKycPMxS117jIoiUfAfeu2GaHObZcszlXVgiP1vnjCaJ75Z5KxuYTYClnxWKh 18 | bjJH5dwvrKKTs4vFCLmXKiQV8SXD2jZ3FzOCgV9BlpGb7tMFTA1MbS4dQ1xoPP6i 19 | vosD8oYDyC8qi4HmXKb/hJAvwK1osI4nHPvKXGtcSFDXFD2fe5vSIMKvaQh2nUF2 20 | 5nGq4AWxVPZgohW2mE34rv7HlzSkc9faVJYrUhNPWdsIQmtlE9hSlY+ioMGYPEeD 21 | 4jdkx6LBCkQXYPBkKsRYVmwoRJE3R4xmfWCQVWLLR2K9gvC24y5Bw6wl8IJe/wck 22 | d0hTCSbzMcVxR40hjaTzWdbTGlj6HINjepIoJCgfbyBAHTCVu02bbxFqCEC+WVRi 23 | LDFGnLZQDQR9Qs0KkYvLc6bwZBYEU3ICFKM872GJOliRk8SJEPqAmZ1jKQHI6PAU 24 | q/AchvCW9ycKqYHWI8PPL18cUBJtrFLd9fLAMj5ssmIH76fYVz9OPA8yUEQfirxC 25 | cuOKJkKtJ1VNIs7dnnS8z1Zv8+3E4TBeXaVaaPlLcHUCRhX4ELPBVoyWiFwfA2gg 26 | 957FExkLNPWC8svzX7E0a+kbAU0C9yMDyxIESPWKbnS/Cs+U+b6BM/DunZV+fODT 27 | UJ59O1z0kbQW6d7n+dWYxPjD 28 | -----END CERTIFICATE----- 29 | -------------------------------------------------------------------------------- /test/cert/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQCltZcJMN/5ltn8 3 | ruNzgDsngLdwf+fX4DqaKIpSgFpela9OY4d+q0i5mc2dqUn0+oYMcFXtLgCqhcRD 4 | h3xEIn4KN7QYwtAt19WgBCR7Ki0Ff07zfDk7nyGoMLrP8WBtCb75iFVPR+9DPhcT 5 | PrubPKD7uC8nsSOazHpxmBH3WcoqUM9WeUBhLOB4necntBtoca2N/jOreNlwM1Bm 6 | 0ZP/LiTFydAeSYaQ49/Qg530I/mD07KYUo4vpXA8dcHm1JjWzIt1CbIudcDF3fCn 7 | UzqBTlYC1vtozDZDx94QR2OK22PKR1/OaBF8xP/lHQADejeRLebfFJRdlZiWuWQr 8 | rDajR7ORp36TTwluXXS75bArBQqJO/ELLVWBNcnqP3u14yqlxa1pCzPqQ32z7dve 9 | treKcGmrP7JJAMbLieNYUEho1xW26DVdx57OxKEaAe34d6U0HlRjSpL7JdZcDVgp 10 | 1qRU28DjsCsGgEHc029z6clJLSoVlJkcBKvPeMHWi8M6exDe23z5Lo6k1XRU8A+W 11 | eOpOgNuCrGTO5IZ4ZTuxXrn+T2NC+lmIEzsPzk1RiVJhGjce9nhfgfx5WguAYsQA 12 | bvIPStPMQ5jZb9oMll1T3643vXmd9BuxusPfRAdXD0mACgGZpKWUxBjXgQCP/mEO 13 | ni9iE/kOs9XdqVrIhvtd7KKzu+ygswIDAQABAoICAHQZEcSitYlMo1VhwoG9qbQ3 14 | fk4Xjvu0Ydp35NGAk9AI11X4bYnVv5Ipq+F2Jha5Hwm/+rO9s0HFjogxeGLUN7I4 15 | 7LkcZHZ57XW68URJfFmJpGr68a5QA2ivG3NtDb3XQjHZzstI/t0Bt3EIVrCgfble 16 | hcstVGC0OmUzwgKmqfmTvHA9a1mp82sOVlF+4mT+oDYHj3Pnq4/so7wlwabbUXAx 17 | wNo3cUw6GykfzZ6rRbmSrGIEFRJUupnhdwSelqaHkH1jWa8rWPBIWr8glLDbjW9X 18 | 57S4vpwmf/Kv0ZILu5KRBxK53uanrxF6i/PjwOK6YjKRXJFkqg+PvBrNAUrMhYW6 19 | SIR7CkguEvJUmLD83S0lRALruYNYgdxkGfY2PC+6FdSrsHOheeiky7FLUgnGaSgk 20 | /SaAU8/NwgFN/TA8yWMh/EyKMfac/ucDSHrjZJHrRa8ynx84tqjhwMcMsd0ESqqz 21 | V1BalCmaycmSkEh5SRXMBZjN8QIAcpwjq0gpoXR3tca4zKY13M6le8kGHnJHcw7v 22 | kl4Ye2/FIFnJVe36689JTmjvviPgAuFfkA6Thg5S8ww8oj6XI7qK3BKBXW35l/Ty 23 | ZOHssYQVyUldr3ccbmE3OUccxPCphNFsndh5uG11IZjvfeZ8d49uAKUoG3TfhS1R 24 | km9Lwpj8YC9zMYfmH04BAoIBAQDRs2r95QAu/XFiDlHWV35/JbymgOmhGysbRQtq 25 | 9mmUPSHdlVPOFejvDg+HTDuOsNmUvFLaHqkfPIRUS3QThld0qRzeTCVR50rf2Xo0 26 | BIhWhbtcOaXA+gsq3QcwvWw982si4oUkWLpxvmhH/JqKOfJzIQ7tWZFTxzUMQmM7 27 | mgm3ZWbGZm/elFgfp3cmL2WKBZCuvfpJ86Vk/Hik9VzZy49s46xToZ/i0vS8iVDS 28 | G0PjtMclmHcFrKydHxak//5oOlTGOK90IqPxrALyM+GliIkFcAULUBcEUcwbJgpF 29 | vi9fcfNSTRTY2eWcJzknUPYB0GDrqsHTtiwzL2Y9bfvXh/5BAoIBAQDKS7X09FSI 30 | XlsKzsFENVbFtVawxv5KaVyDS18FrAdCPsw4leqME9w4KWmhGBhDOwBgsHNG6g6m 31 | DZOk7lvDksNdsgpqKuTGtIYJ4rnr8NaC4A9MF4iZb/GQvITmkk8lqmsdeV6rZAPb 32 | SIQPBbwqMEJp6GychMGU+WanXAWV5Wob8IIcwkWWW+/dy+K9zZMA13iFjOJqNxL1 33 | c8rhgthFnKPYlcJsk1+Pjsh1Wu0otGNrbXLZ9QS1R7xXSAT9IMqeR2ev5p5YHXTe 34 | ValK0+Ui802ApJeIM4BElb8ho0ljjv/zLoMWAlJEEocdvL+3USiMIx795PFtTjt/ 35 | 4WZ7cD8QZgnzAoIBAENiFtg89MEkuYFbvppUhRZQRv1t4STeuQxLNG8LfUOgHTi5 36 | mjNXwQDIEN9V9LFWTKRJm4nxXkDmTlNT55m6inG3QcqAx2E5JEsn8+exKSo8UMdC 37 | 0uteraX5Xi0gQR7rEBsmlMfoJwkTuSigl/wgwLKUBdro9BTqpzYoQwDM76BZ2huY 38 | oQvxuRt60YOYqVUfkq1D6KBIxXXc1tuS6GIn0g+YXTCGd/0H8pJ+wVSyZC9QUMnY 39 | 4y4UnWbjrYAZrmNJkyIa5u43j1zB/DRdzX1GkVkI9OX8t1iw/BghMh3VDh3qXNiu 40 | YiuIYQq7Y9cpYdOVZP91YFEqcauE6KKGq+1HCsECggEAWPwMwf9n38SBj7N62CIO 41 | VVF7HK+r09ec1jQlTD1rw/wToSBw18U0rY+p4y7SHWCqvd1gF+WFJA380CP5QYIh 42 | ZemXIzRTBgUKoPaWie6arV8Z24LOl4/iVqRWYYYiQpNAIursdb+84ox3apQ/PN+V 43 | lzih7geHj5xVR3D3FASiNEALO9NJAObYEFxLFOqEq24cmcJrJgWr1URmDeScdaJ9 44 | gBwANfptopxQGS8x+yG+SdcPGkjpFUTE2C/Z4e62sgD+3yU2qdPrQJG1sjCbLlCP 45 | 2vKLpWHiHkkwlf9ZnO2eML5Y8yl6Pm9X+AC65ExS5bHdRBmdCVPMYGgpyEqLsw7v 46 | eQKCAQBxFebya+Rf/FiAmE+z877VRFV8ezo+1LxMMj6K2L2k3jUDTv5dsFsB+59i 47 | ZKBhslaDuHZHu+MyisU19u32I+lq2veYp42mQIi8RxJ6TuolWEMMaAMHsfa4XZdN 48 | yk81LLdMMPSLVxh0cyejvP+1CalV4PZoxYMUWyb3HGu8OpCd7s0RzBnwZc1R0LR4 49 | TmimHhoBdXcU+TRyo82WvxughdFgOUta/8uUxhnWdwUU4Rmlyw/YfgLLDhgE8L07 50 | 5+eBuOGTXCXuBv4c8XFawkwuZOh0JC24Gl7EwhEWQo6RxZpyNAySbNrXQfdqaJ5o 51 | mHDbdt3ZRNxhHGSiih0iX9YZT7yg 52 | -----END PRIVATE KEY----- 53 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stream = require('stream'); 4 | const expect = require('expect.js'); 5 | const _ = require('lodash'); 6 | const https = require('https'); 7 | const fs = require('fs'); 8 | const path = require('path'); 9 | 10 | const { ClickHouse } = require('../.'); 11 | 12 | const database = 'test_' + _.random(1000, 100000); 13 | 14 | const 15 | configFilepath = process.env.CLICKHOUSE_TEST_CONF_FILE || './test_config.json', 16 | configPath = path.resolve(process.cwd(), configFilepath), 17 | extConfig = fs.existsSync(configPath) ? JSON.parse(fs.readFileSync(configPath, { encoding: 'utf-8' })) : undefined, 18 | config = { 19 | ...extConfig, 20 | debug: false, 21 | }, 22 | 23 | clickhouse = new ClickHouse({ 24 | ...config, 25 | database : database, 26 | }), 27 | minRnd = 50 * 1024, 28 | rowCount = _.random(minRnd, 128 * 1024), 29 | sql = `SELECT 30 | number, 31 | toString(number * 2) AS str, 32 | toDate(number + 1) AS date 33 | FROM system.numbers 34 | LIMIT ${rowCount}`; 35 | 36 | before(async () => { 37 | const temp = new ClickHouse(config); 38 | 39 | await temp.query(`DROP DATABASE IF EXISTS ${database}`).toPromise(); 40 | await temp.query(`CREATE DATABASE ${database}`).toPromise(); 41 | }); 42 | 43 | describe.skip('On cluster', () => { 44 | // Note: this test only works with ClickHouse setup as Cluster named test_cluster 45 | it('should be able to create and drop a table', async () => { 46 | const createTableQuery = ` 47 | CREATE TABLE ${database}.test_on_cluster ON CLUSTER test_cluster ( 48 | test String 49 | ) 50 | ENGINE=MergeTree ORDER BY test;`; 51 | const createTableQueryResult = await clickhouse.query(createTableQuery).toPromise(); 52 | expect(createTableQueryResult).to.be.ok(); 53 | 54 | const dropTableQuery = ` 55 | DROP TABLE ${database}.test_on_cluster ON CLUSTER test_cluster;`; 56 | const dropTableQueryResult = await clickhouse.query(dropTableQuery).toPromise(); 57 | expect(dropTableQueryResult).to.be.ok(); 58 | }); 59 | }); 60 | 61 | describe('Exec', () => { 62 | it('should return not null object', async () => { 63 | const sqlList = [ 64 | 'DROP TABLE IF EXISTS session_temp', 65 | 66 | `CREATE TABLE session_temp ( 67 | date Date, 68 | time DateTime, 69 | mark String, 70 | ips Array(UInt32), 71 | queries Nested ( 72 | act String, 73 | id UInt32 74 | ) 75 | ) 76 | ENGINE=MergeTree(date, (mark, time), 8192)`, 77 | 78 | 'OPTIMIZE TABLE session_temp PARTITION 201807 FINAL', 79 | 80 | ` 81 | SELECT 82 | * 83 | FROM session_temp 84 | LIMIT 10 85 | `, 86 | ]; 87 | 88 | for(const query of sqlList) { 89 | const r = await clickhouse.query(query).toPromise(); 90 | 91 | expect(r).to.be.ok(); 92 | } 93 | }); 94 | }); 95 | 96 | describe('Select', () => { 97 | it('callback #1', callback => { 98 | clickhouse.query(sql).exec((err, rows) => { 99 | expect(err).to.not.be.ok(); 100 | 101 | expect(rows).to.have.length(rowCount); 102 | expect(rows[0]).to.eql({ number: 0, str: '0', date: '1970-01-02' }); 103 | 104 | callback(); 105 | }); 106 | }); 107 | 108 | it('callback #2', callback => { 109 | clickhouse.query(sql, (err, rows) => { 110 | expect(err).to.not.be.ok(); 111 | 112 | expect(rows).to.have.length(rowCount); 113 | expect(rows[0]).to.eql({ number: 0, str: '0', date: '1970-01-02' }); 114 | 115 | callback(); 116 | }); 117 | }); 118 | 119 | it('promise and await/async', async () => { 120 | const rows = await clickhouse.query(sql).toPromise(); 121 | 122 | expect(rows).to.have.length(rowCount); 123 | expect(rows[0]).to.eql({ number: 0, str: '0', date: '1970-01-02' }); 124 | }); 125 | 126 | it('stream', function(callback) { 127 | let i = 0; 128 | let error = null; 129 | 130 | clickhouse.query(sql).stream() 131 | .on('data', () => ++i) 132 | .on('error', err => error = err) 133 | .on('end', () => { 134 | expect(error).to.not.be.ok(); 135 | expect(i).to.be(rowCount); 136 | 137 | callback(); 138 | }); 139 | }); 140 | 141 | it('stream with pause/resume', function(callback) { 142 | const 143 | count = 10, 144 | pause = 1000, 145 | ts = Date.now(); 146 | 147 | this.timeout(count * pause * 2); 148 | 149 | let i = 0, 150 | error = null; 151 | 152 | const stream = clickhouse.query(`SELECT number FROM system.numbers LIMIT ${count}`).stream(); 153 | 154 | stream 155 | .on('data', () => { 156 | ++i; 157 | 158 | stream.pause(); 159 | 160 | setTimeout(() => stream.resume(), pause); 161 | }) 162 | .on('error', err => error = err) 163 | .on('end', () => { 164 | expect(error).to.not.be.ok(); 165 | expect(i).to.be(count); 166 | expect(Date.now() - ts).to.be.greaterThan(count * pause); 167 | 168 | callback(); 169 | }) 170 | }); 171 | 172 | it('streams should handle network error', function(callback) { 173 | let i = 0; 174 | const host = 'non-existing-clickhouse-server'; // to simulate a dns failure 175 | 176 | new ClickHouse({ 177 | ...config, 178 | host 179 | }).query('SELECT number FROM system.numbers LIMIT 10').stream() 180 | .on('data', () => ++i) 181 | .on('error', error => { 182 | expect(error.code).to.be.equal('ENOTFOUND'); 183 | expect(error.syscall).to.be.equal('getaddrinfo'); 184 | expect(error.hostname).to.be.equal(host); 185 | expect(i).to.be(0); 186 | callback(); 187 | }); 188 | }); 189 | 190 | const nodeVersion = process.version.split('.')[0].substr(1); 191 | if (parseInt(nodeVersion, 10) >= 10) { 192 | it('async for', async function() { 193 | let i = 0; 194 | 195 | for await (const row of clickhouse.query(sql).stream()) { 196 | ++i; 197 | expect(row).to.have.key('number'); 198 | expect(row).to.have.key('str'); 199 | expect(row).to.have.key('date'); 200 | } 201 | 202 | expect(i).to.be(rowCount); 203 | }); 204 | } 205 | 206 | it('select with external', async () => { 207 | const result = await clickhouse.query( 208 | 'SELECT count(*) AS count FROM temp_table', 209 | { 210 | external: [ 211 | { 212 | name: 'temp_table', 213 | data: _.range(0, rowCount).map(i => `str${i}`) 214 | }, 215 | ] 216 | } 217 | ).toPromise(); 218 | 219 | expect(result).to.be.ok(); 220 | expect(result).to.have.length(1); 221 | expect(result[0]).to.have.key('count'); 222 | expect(result[0].count).to.be(rowCount); 223 | }); 224 | 225 | it('select with external && join', async () => { 226 | const result = await clickhouse.query( 227 | ` 228 | SELECT 229 | * 230 | FROM system.numbers AS i 231 | LEFT JOIN ( 232 | SELECT 233 | number 234 | FROM system.numbers 235 | WHERE number IN temp_table 236 | LIMIT 10 237 | ) AS n ON(n.number = i.number) 238 | LIMIT 100 239 | `, 240 | { 241 | external: [ 242 | { 243 | name: 'temp_table', 244 | data: _.range(0, 10), 245 | structure: 'i UInt64' 246 | }, 247 | ] 248 | } 249 | ).toPromise(); 250 | 251 | expect(result).to.be.ok(); 252 | expect(result).to.have.length(100); 253 | }); 254 | 255 | it('catch error', async () => { 256 | try { 257 | await clickhouse.query(sql + '1').toPromise(); 258 | } catch (err) { 259 | expect(err).to.be.ok(); 260 | } 261 | }); 262 | 263 | [ 264 | { 265 | format: 'fake_name', 266 | fullFormatExpr: 'FakeName', 267 | }, 268 | ...ClickHouse.getFormats(), 269 | ].forEach(({ format, fullFormatExpr }) => { 270 | 271 | // The string "foRmat" is used because different forms of writing are found in real code. 272 | const fullSql = sql + (format === 'fake_name' ? '' : ` foRmat ${fullFormatExpr}`); 273 | 274 | it(`with "${fullFormatExpr}" into sql`, async () => { 275 | const rows = await clickhouse.query(fullSql).toPromise(); 276 | 277 | expect(rows).to.have.length(rowCount); 278 | expect(rows[0]).to.eql({ number: 0, str: '0', date: '1970-01-02' }); 279 | }); 280 | 281 | if (format !== 'fake_name') { 282 | it(`with "${fullFormatExpr}" in options`, async () => { 283 | const rows = await clickhouse.query(sql, {}, { format }).toPromise(); 284 | 285 | expect(rows).to.have.length(rowCount); 286 | expect(rows[0]).to.eql({ number: 0, str: '0', date: '1970-01-02' }); 287 | }); 288 | } 289 | 290 | it(`with promise "${fullFormatExpr}"`, async () => { 291 | try { 292 | await clickhouse.query(`SELECT * FROM random_table_name ${fullFormatExpr}`).toPromise(); 293 | } catch (err) { 294 | expect(err).to.be.ok(); 295 | } 296 | }); 297 | 298 | it(`with stream ${fullFormatExpr}"`, cb => { 299 | let i = 0, 300 | error = null; 301 | 302 | const stream = clickhouse.query(`SELECT * FROM random_table_name ${fullFormatExpr}`).stream(); 303 | 304 | stream 305 | .on('data', () => ++i) 306 | .on('error', err => error = err) 307 | .on('close', () => { 308 | expect(error).to.be.ok(); 309 | expect(error.toString()).to.match(new RegExp(`Table ${database}.random_table_name doesn\'t exist`)); 310 | 311 | expect(i).to.eql(0); 312 | 313 | cb(); 314 | }) 315 | .on('end', () => { 316 | cb(new Error('no way #2')); 317 | }); 318 | }); 319 | }); 320 | 321 | it('parameterized ', callback => { 322 | 323 | let params_sql = `SELECT 324 | number, 325 | toString(number * 2) AS str, 326 | toDate(number + 1) AS date 327 | FROM numbers(10) 328 | WHERE number = {num:UInt64} 329 | OR number IN {nums:Array(UInt64)}`; 330 | 331 | let params_data = { 332 | params: { 333 | num: 0, 334 | nums: [1,2] 335 | } 336 | } 337 | 338 | clickhouse.query(params_sql, params_data).exec((err, rows) => { 339 | expect(err).to.not.be.ok(); 340 | 341 | expect(rows).to.have.length(3); 342 | expect(rows[0]).to.eql({ number: 0, str: '0', date: '1970-01-02' }); 343 | 344 | callback(); 345 | }); 346 | }); 347 | 348 | }); 349 | 350 | (extConfig? describe.skip : describe)('session', () => { 351 | it('use session', async () => { 352 | const sessionId = clickhouse.sessionId; 353 | clickhouse.sessionId = Date.now(); 354 | 355 | const result = await clickhouse.query( 356 | `CREATE TEMPORARY TABLE test_table 357 | (_id String, str String) 358 | ENGINE=Memory` 359 | ).toPromise(); 360 | expect(result).to.be.ok(); 361 | 362 | clickhouse.sessionId = Date.now(); 363 | const result2 = await clickhouse.query( 364 | `CREATE TEMPORARY TABLE test_table 365 | (_id String, str String) 366 | ENGINE=Memory` 367 | ).toPromise(); 368 | expect(result2).to.be.ok(); 369 | 370 | clickhouse.sessionId = sessionId; 371 | }); 372 | 373 | it('uses session per query', async () => { 374 | let tempSessionId = `${Date.now()}`; 375 | 376 | const result = await clickhouse.query( 377 | `CREATE TEMPORARY TABLE test_table 378 | (_id String, str String) 379 | ENGINE=Memory`, {}, {sessionId: tempSessionId} 380 | ).toPromise(); 381 | expect(result).to.be.ok(); 382 | 383 | const result2 = await clickhouse.query( 384 | `SELECT * FROM test_table LIMIT 10`, {}, {sessionId: tempSessionId} 385 | ).toPromise(); 386 | expect(result2).to.be.ok(); 387 | try { 388 | await clickhouse.query( 389 | `SELECT * FROM test_table LIMIT 10`, {}, {sessionId: `${tempSessionId}_bad`} 390 | ).toPromise(); 391 | } catch (error) { 392 | expect(error.code).to.be(60); 393 | } 394 | 395 | const result3 = await clickhouse.query( 396 | `DROP TEMPORARY TABLE test_table`, {}, {sessionId: tempSessionId} 397 | ).toPromise(); 398 | expect(result3).to.be.ok(); 399 | }); 400 | 401 | it('use setSessionPerQuery #1', async () => { 402 | const sessionPerQuery = clickhouse.sessionPerQuery; 403 | clickhouse.setSessionPerQuery(false); 404 | 405 | const result = await clickhouse.query( 406 | `CREATE TEMPORARY TABLE test_table 407 | (_id String, str String) 408 | ENGINE=Memory` 409 | ).toPromise(); 410 | expect(result).to.be.ok(); 411 | 412 | try { 413 | await clickhouse.query( 414 | `CREATE TEMPORARY TABLE test_table 415 | (_id String, str String) 416 | ENGINE=Memory` 417 | ).toPromise(); 418 | 419 | expect(1).to.be(0); 420 | } catch (err) { 421 | expect(err).to.be.ok(); 422 | expect(err.code).to.be(57); 423 | } 424 | 425 | clickhouse.setSessionPerQuery(sessionPerQuery); 426 | }); 427 | 428 | it('use setSessionPerQuery #2', async () => { 429 | const sessionPerQuery = clickhouse.sessionPerQuery; 430 | clickhouse.setSessionPerQuery(true); 431 | 432 | const result = await clickhouse.query( 433 | `CREATE TEMPORARY TABLE test_table 434 | (_id String, str String) 435 | ENGINE=Memory` 436 | ).toPromise(); 437 | expect(result).to.be.ok(); 438 | 439 | await clickhouse.query( 440 | `CREATE TEMPORARY TABLE test_table 441 | (_id String, str String) 442 | ENGINE=Memory` 443 | ).toPromise(); 444 | 445 | clickhouse.setSessionPerQuery(sessionPerQuery); 446 | }); 447 | }); 448 | 449 | // You can use all settings from request library (https://github.com/request/request#tlsssl-protocol) 450 | // Generate ssl file with: 451 | // sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout test/cert/server.key -out test/cert/server.crt 452 | 453 | (extConfig? describe.skip : describe)('TLS/SSL Protocol', () => { 454 | it('use TLS/SSL Protocol', async () => { 455 | let server = null; 456 | 457 | try { 458 | server = https.createServer( 459 | { 460 | key : fs.readFileSync('test/cert/server.key'), 461 | cert : fs.readFileSync('test/cert/server.crt'), 462 | }, 463 | (req, res) => { 464 | res.writeHead(200); 465 | res.end('{\n\t"meta":\n\t[\n\t\t{\n\t\t\t"name": "plus(1, 1)",\n\t\t\t"type": "UInt16"\n\t\t}\n\t],\n\n\t"data":\n\t[\n\t\t{\n\t\t\t"plus(1, 1)": 2\n\t\t}\n\t],\n\n\t"rows": 1,\n\n\t"statistics":\n\t{\n\t\t"elapsed": 0.000037755,\n\t\t"rows_read": 1,\n\t\t"bytes_read": 1\n\t}\n}\n'); 466 | }) 467 | .listen(8000); 468 | 469 | const temp = new ClickHouse({ 470 | ...config, 471 | url : 'https://localhost:8000', 472 | port : 8000, 473 | reqParams : { 474 | agentOptions: { 475 | ca: fs.readFileSync('test/cert/server.crt'), 476 | cert: fs.readFileSync('test/cert/server.crt'), 477 | key: fs.readFileSync('test/cert/server.key'), 478 | } 479 | }, 480 | debug : false, 481 | }); 482 | 483 | const r = await temp.query('SELECT 1 + 1 Format JSON').toPromise(); 484 | expect(r).to.be.ok(); 485 | expect(r[0]).to.have.key('plus(1, 1)'); 486 | expect(r[0]['plus(1, 1)']).to.be(2); 487 | 488 | if (server) { 489 | server.close(); 490 | } 491 | } catch(err) { 492 | if (server) { 493 | server.close(); 494 | } 495 | 496 | throw err; 497 | } 498 | }); 499 | it('default HTTPS port is 433', async () => { 500 | const clickhouse = new ClickHouse({ 501 | ...config, 502 | url : 'https://localhost' 503 | }); 504 | expect(clickhouse.opts.url).to.match(/localhost\//); 505 | }); 506 | }); 507 | 508 | describe('queries', () => { 509 | it('insert field as array', async () => { 510 | clickhouse.sessionId = Date.now(); 511 | 512 | const r = await clickhouse.query(` 513 | CREATE TABLE IF NOT EXISTS test_array ( 514 | date Date, 515 | str String, 516 | arr Array(String), 517 | arr2 Array(Date), 518 | arr3 Array(UInt8), 519 | id1 UUID 520 | ) ENGINE=MergeTree(date, date, 8192) 521 | `).toPromise(); 522 | expect(r).to.be.ok(); 523 | 524 | const rows = [ 525 | { 526 | date: '2018-01-01', 527 | str: 'Вам, проживающим за оргией оргию,', 528 | arr: [], 529 | arr2: ['1985-01-02', '1985-01-03'], 530 | arr3: [1,2,3,4,5], 531 | id1: '102a05cb-8aaf-4f11-a442-20c3558e4384' 532 | }, 533 | 534 | { 535 | date: '2018-02-01', 536 | str: 'It\'s apostrophe test.', 537 | arr: ['5670000000', 'asdas dasf. It\'s apostrophe test.'], 538 | arr2: ['1985-02-02'], 539 | arr3: [], 540 | id1: 'c2103985-9a1e-4f4a-b288-b292b5209de1' 541 | } 542 | ]; 543 | 544 | const r2 = await clickhouse.insert( 545 | `insert into test_array 546 | (date, str, arr, arr2, 547 | arr3, id1)`, 548 | rows 549 | ).toPromise(); 550 | expect(r2).to.be.ok(); 551 | const r3 = await clickhouse.query('SELECT * FROM test_array ORDER BY date').toPromise(); 552 | expect(r3).to.eql(rows); 553 | }); 554 | 555 | it('insert field as raw string', async () => { 556 | clickhouse.sessionId = Date.now(); 557 | 558 | const r = await clickhouse.query(` 559 | CREATE TABLE IF NOT EXISTS test_raw_string ( 560 | date Date, 561 | str String, 562 | arr Array(String), 563 | arr2 Array(Date), 564 | arr3 Array(UInt8), 565 | fixedStr String 566 | ) ENGINE=MergeTree(date, date, 8192) 567 | `).toPromise(); 568 | expect(r).to.be.ok(); 569 | 570 | const rows = [ 571 | '(\'2018-01-01 10:00:00\',\'Вам, проживающим за оргией оргию,\',[],[\'1915-01-02 10:00:00\',\'1915-01-03 10:00:00\'],[1,2,3,4,5],unhex(\'60ed56e75bb93bd353267faa\'))', 572 | '(\'2018-02-01 10:00:00\',\'имеющим ванную и теплый клозет! It\'\'s apostrophe test.\',[\'5670000000\',\'asdas dasf\'],[\'1915-02-02 10:00:00\'],[],unhex(\'60ed56f4a88cd5dcb249d959\'))' 573 | ]; 574 | 575 | const r2 = await clickhouse.insert( 576 | 'INSERT INTO test_raw_string (date, str, arr, arr2, arr3, fixedStr) VALUES', 577 | rows 578 | ).toPromise(); 579 | expect(r2).to.be.ok(); 580 | }); 581 | 582 | it('insert stream accept raw string', async () => { 583 | clickhouse.sessionId = Date.now(); 584 | 585 | const r = await clickhouse.query(` 586 | CREATE TABLE IF NOT EXISTS test_insert_stream_raw_string ( 587 | date Date, 588 | str String, 589 | arr Array(String), 590 | arr2 Array(Date), 591 | arr3 Array(UInt8), 592 | fixedStr FixedString(12) 593 | ) ENGINE=MergeTree(date, date, 8192) 594 | `).toPromise(); 595 | expect(r).to.be.ok(); 596 | 597 | const rows = [ 598 | '(\'2018-01-01 10:00:00\',\'Вам, проживающим за оргией оргию,\',[],[\'1915-01-02 10:00:00\',\'1915-01-03 10:00:00\'],[1,2,3,4,5],unhex(\'60ed56e75bb93bd353267faa\'))', 599 | '(\'2018-02-01 10:00:00\',\'имеющим ванную и теплый клозет!\',[\'5670000000\',\'asdas dasf\'],[\'1915-02-02 10:00:00\'],[],unhex(\'60ed56f4a88cd5dcb249d959\'))' 600 | ]; 601 | 602 | const stream = await clickhouse.insert( 603 | 'INSERT INTO test_insert_stream_raw_string (date, str, arr, arr2, arr3, fixedStr) VALUES', 604 | ).stream(); 605 | stream.writeRow(rows[0]); 606 | stream.writeRow(rows[1]); 607 | const r2 = await stream.exec(); 608 | expect(r2).to.be.ok(); 609 | }); 610 | 611 | it('select, insert and two pipes', async () => { 612 | const result = await clickhouse.query('DROP TABLE IF EXISTS session_temp').toPromise(); 613 | expect(result).to.be.ok(); 614 | 615 | const result2 = await clickhouse.query('DROP TABLE IF EXISTS session_temp2').toPromise(); 616 | expect(result2).to.be.ok(); 617 | 618 | const result3 = await clickhouse.query('CREATE TABLE session_temp (str String) ENGINE=MergeTree PARTITION BY tuple() ORDER BY tuple()').toPromise(); 619 | expect(result3).to.be.ok(); 620 | 621 | const result4 = await clickhouse.query('CREATE TABLE session_temp2 (str String) ENGINE=MergeTree PARTITION BY tuple() ORDER BY tuple()').toPromise(); 622 | expect(result4).to.be.ok(); 623 | 624 | const data = _.range(0, rowCount).map(r => [r]); 625 | const result5 = await clickhouse.insert( 626 | 'INSERT INTO session_temp', data 627 | ).toPromise(); 628 | expect(result5).to.be.ok(); 629 | 630 | const rows = await clickhouse.query('SELECT COUNT(*) AS count FROM session_temp').toPromise(); 631 | expect(rows).to.be.ok(); 632 | expect(rows).to.have.length(1); 633 | expect(rows[0]).to.have.key('count'); 634 | expect(rows[0].count).to.be(data.length); 635 | expect(rows[0].count).to.be(rowCount); 636 | 637 | const result6 = await clickhouse.query('TRUNCATE TABLE session_temp').toPromise(); 638 | expect(result6).to.be.ok(); 639 | 640 | const ws = clickhouse.insert('INSERT INTO session_temp').stream(); 641 | let count = 0; 642 | 643 | for(let i = 1; i <= rowCount; i++) { 644 | await ws.writeRow( 645 | [ 646 | _.range(0, 50).map( 647 | j => `${i}:${i * 2}:${j}` 648 | ).join('-') 649 | ] 650 | ); 651 | ++count; 652 | } 653 | const result7 = await ws.exec(); 654 | expect(result7).to.be.ok(); 655 | expect(count).to.be(rowCount); 656 | 657 | clickhouse.isUseGzip = true; 658 | const rs = clickhouse.query(sql).stream(); 659 | 660 | const tf = new stream.Transform({ 661 | objectMode : true, 662 | transform : function (chunk, enc, cb) { 663 | cb(null, JSON.stringify(chunk) + '\n'); 664 | } 665 | }); 666 | 667 | clickhouse.sessionId = Date.now(); 668 | const ws2 = clickhouse.insert('INSERT INTO session_temp2').stream(); 669 | 670 | const result8 = await rs.pipe(tf).pipe(ws2).exec(); 671 | expect(result8).to.be.ok(); 672 | clickhouse.isUseGzip = false; 673 | const result9 = await clickhouse.query('SELECT count(*) AS count FROM session_temp').toPromise(); 674 | const result10 = await clickhouse.query('SELECT count(*) AS count FROM session_temp2').toPromise(); 675 | expect(result9).to.eql(result10); 676 | }); 677 | 678 | it('insert && errors', async () => { 679 | try { 680 | const stream = clickhouse.insert('INSERT INTO BAD_TABLE').stream(); 681 | 682 | for (let i = 0; i < 10; i++) { 683 | stream.writeRow([i, `test: ${i}`]); 684 | } 685 | 686 | await stream.exec(); 687 | 688 | // no way! 689 | expect(1).to.be.equal(0); 690 | } catch (err) { 691 | expect(err).to.be.ok(); 692 | expect(err.code).to.be(60); 693 | } 694 | }); 695 | 696 | it('select number as number', async () => { 697 | const result = await clickhouse.query('DROP TABLE IF EXISTS test_int_temp').toPromise(); 698 | expect(result).to.be.ok(); 699 | 700 | const result1 = await clickhouse.query('CREATE TABLE test_int_temp (int_value Int8 ) ENGINE=Memory').toPromise(); 701 | expect(result1).to.be.ok(); 702 | 703 | const int_value_data = [{int_value: 0}]; 704 | const result3 = await clickhouse.insert('INSERT INTO test_int_temp (int_value)', int_value_data).toPromise(); 705 | expect(result3).to.be.ok(); 706 | 707 | const result4 = await clickhouse.query('SELECT int_value FROM test_int_temp').toPromise(); 708 | expect(result4).to.eql(int_value_data); 709 | }); 710 | 711 | it('insert with params', async () => { 712 | const result = await clickhouse.query('DROP TABLE IF EXISTS test_par_temp').toPromise(); 713 | expect(result).to.be.ok(); 714 | 715 | const result1 = await clickhouse.query(`CREATE TABLE test_par_temp ( 716 | int_value UInt32, 717 | str_value1 String, 718 | str_value2 String, 719 | date_value Date, 720 | date_time_value DateTime, 721 | decimal_value Decimal(10,4), 722 | arr Array(String), 723 | arr2 Array(Date), 724 | arr3 Array(UInt32) 725 | ) ENGINE=Memory`).toPromise(); 726 | expect(result1).to.be.ok(); 727 | 728 | const row = { 729 | int_value: 12345, 730 | str_value1: 'Test for "masked" characters. It workes, isn\'t it?', 731 | str_value2: JSON.stringify({name:'It is "something".'}), 732 | date_value: '2022-08-18', 733 | date_time_value: '2022-08-18 19:07:00', 734 | decimal_value: 1234.678, 735 | arr: ['asdfasdf', 'It\'s apostrophe test'], 736 | arr2: ['2022-01-01', '2022-10-10'], 737 | arr3: [12345, 54321], 738 | }; 739 | const result2 = await clickhouse.insert(`INSERT INTO test_par_temp (int_value, str_value1, str_value2, date_value, date_time_value, decimal_value, 740 | arr, arr2, arr3) 741 | VALUES ({int_value:UInt32}, {str_value1:String}, {str_value2:String}, {date_value:Date}, {date_time_value:DateTime}, {decimal_value: Decimal(10,4)}, 742 | {arr:Array(String)},{arr2:Array(Date)},{arr3:Array(UInt32)})`, 743 | {params: { 744 | ...row, 745 | decimal_value: row.decimal_value.toFixed(4) 746 | } 747 | }).toPromise(); 748 | expect(result2).to.be.ok(); 749 | 750 | const result3 = await clickhouse.query('SELECT * FROM test_par_temp').toPromise(); 751 | expect(result3).to.eql([row]); 752 | }); 753 | 754 | it('insert select', async () => { 755 | const result = await clickhouse.query('DROP TABLE IF EXISTS test_par_temp').toPromise(); 756 | expect(result).to.be.ok(); 757 | 758 | const result1 = await clickhouse.query(`CREATE TABLE test_par_temp ( 759 | int_value UInt32, 760 | str_value1 String, 761 | str_value2 String, 762 | date_value Date, 763 | date_time_value DateTime, 764 | decimal_value Decimal(10,4), 765 | arr Array(String), 766 | arr2 Array(Date), 767 | arr3 Array(UInt32) 768 | ) ENGINE=Memory`).toPromise(); 769 | expect(result1).to.be.ok(); 770 | 771 | const row = { 772 | int_value: 12345, 773 | str_value1: 'Test for "masked" characters. It workes, isn\'t it?', 774 | str_value2: JSON.stringify({name:'It is "something".'}), 775 | date_value: '2022-08-18', 776 | date_time_value: '2022-08-18 19:07:00', 777 | decimal_value: 1234.678, 778 | arr: ['asdfasdf', 'It\'s apostrophe test'], 779 | arr2: ['2022-01-01', '2022-10-10'], 780 | arr3: [12345, 54321], 781 | }; 782 | const result2 = await clickhouse.insert(`INSERT INTO test_par_temp (int_value, str_value1, str_value2, date_value, date_time_value, decimal_value, 783 | arr, arr2, arr3) 784 | select {int_value:UInt32}, {str_value1:String}, {str_value2:String}, {date_value:Date}, {date_time_value:DateTime}, {decimal_value: Decimal(10,4)}, 785 | {arr:Array(String)},{arr2:Array(Date)},{arr3:Array(UInt32)}`, 786 | {params: { 787 | ...row, 788 | decimal_value: row.decimal_value.toFixed(4) 789 | } 790 | }).toPromise(); 791 | expect(result2).to.be.ok(); 792 | 793 | const result3 = await clickhouse.query('SELECT * FROM test_par_temp').toPromise(); 794 | expect(result3).to.eql([row]); 795 | 796 | const result4 = await clickhouse.insert(`INSERT INTO test_par_temp (int_value, str_value1, str_value2, date_value, date_time_value, decimal_value, 797 | arr, arr2, arr3) 798 | select 123456, 'awerqwerqwer', 'rweerwrrewr', '2022-08-25', '2022-08-25 02:00:01', '123.1234', 799 | ['aaa','bbb'],['2022-08-22','2022-08-23'],[1,2,3,4]` 800 | ).toPromise(); 801 | expect(result2).to.be.ok(); 802 | 803 | 804 | }); 805 | 806 | }); 807 | 808 | describe('response codes', () => { 809 | it('table is not exists', async () => { 810 | try { 811 | const result = await clickhouse.query('DROP TABLE session_temp').toPromise(); 812 | expect(result).to.be.ok(); 813 | 814 | await clickhouse.query('SELECT COUNT(*) AS count FROM session_temp').toPromise(); 815 | expect().fail('You should not be here'); 816 | } catch (err) { 817 | expect(err).to.be.ok(); 818 | expect(err).to.have.key('code'); 819 | expect(err.code).to.be(60); 820 | } 821 | 822 | try { 823 | let result = await clickhouse.query('DROP TABLE session_temp2').toPromise(); 824 | expect(result).to.be.ok(); 825 | 826 | await clickhouse.query('SELECT COUNT(*) AS count FROM session_temp2').toPromise(); 827 | expect().fail('You should not be here2'); 828 | } catch (err) { 829 | expect(err).to.be.ok(); 830 | expect(err).to.have.key('code'); 831 | expect(err.code).to.be(60); 832 | } 833 | }); 834 | }); 835 | 836 | 837 | 838 | describe('set database', () => { 839 | it('create instance with non-default database', async () => { 840 | const noDefaultDb = 'default_' + _.random(1000, 10000); 841 | const r = await clickhouse.query(`CREATE DATABASE ${noDefaultDb}`).toPromise(); 842 | expect(r).to.be.ok(); 843 | 844 | const temp = new ClickHouse({ 845 | ...config, 846 | database: noDefaultDb, 847 | }); 848 | 849 | 850 | const result3 = await temp.query('CREATE TABLE session_temp (str String) ENGINE=MergeTree PARTITION BY tuple() ORDER BY tuple()').toPromise(); 851 | expect(result3).to.be.ok(); 852 | 853 | const r2 = await temp.query(`DROP DATABASE ${noDefaultDb}`).toPromise(); 854 | expect(r2).to.be.ok(); 855 | }); 856 | }); 857 | 858 | 859 | describe('compatibility with Sequelize ORM', () => { 860 | it('select with ;', async () => { 861 | const sqls = [ 862 | 'SELECT 1 + 1', 863 | 'SELECT 1 + 1;', 864 | 'SELECT 1 + 1 ;', 865 | 'SELECT 1 + 1 ; ' 866 | ]; 867 | 868 | for(const sql of sqls) { 869 | const r = await clickhouse.query(sql).toPromise(); 870 | expect(r).to.be.ok(); 871 | } 872 | }); 873 | }); 874 | 875 | 876 | 877 | (extConfig? describe.skip : describe)('Constructor options', () => { 878 | const addConfigs = [ 879 | { 880 | url: 'localhost', 881 | }, 882 | 883 | { 884 | url: 'http://localhost', 885 | }, 886 | 887 | { 888 | url: 'http://localhost:8123', 889 | port: 8123, 890 | }, 891 | 892 | { 893 | host: 'localhost', 894 | }, 895 | 896 | { 897 | host: 'http://localhost', 898 | }, 899 | 900 | { 901 | host: 'http://localhost:8123', 902 | port: 8124, 903 | }, 904 | 905 | { 906 | host: 'http://localhost:8123', 907 | port: 8124, 908 | format: "json", 909 | }, 910 | 911 | { 912 | host: 'http://localhost:8123', 913 | port: 8124, 914 | format: "tsv", 915 | }, 916 | 917 | { 918 | host: 'http://localhost:8123', 919 | port: 8124, 920 | format: "csv", 921 | }, 922 | ]; 923 | 924 | for(const addConfig of addConfigs) { 925 | it(`url and host (${JSON.stringify(addConfig)})`, async () => { 926 | const clickhouse = new ClickHouse({ 927 | ...config, 928 | ...addConfig, 929 | }); 930 | 931 | const r = await clickhouse.query('SELECT 1 + 1').toPromise(); 932 | expect(r).to.be.ok(); 933 | }); 934 | } 935 | 936 | 937 | it('user && password ok', async () => { 938 | const clickhouses = [ 939 | new ClickHouse({ 940 | ...config, 941 | user: 'default', 942 | password: '' 943 | }), 944 | 945 | new ClickHouse({ 946 | ...config, 947 | username: 'default', 948 | password: '' 949 | }), 950 | 951 | new ClickHouse({ 952 | ...config, 953 | basicAuth: { 954 | username: 'default', 955 | password: '' 956 | } 957 | }), 958 | ]; 959 | 960 | for(const clickhouse of clickhouses) { 961 | const r = await clickhouse.query('SELECT 1 + 1').toPromise(); 962 | expect(r).to.be.ok(); 963 | } 964 | }); 965 | 966 | 967 | it('user && password fail', async () => { 968 | const clickhouses = [ 969 | new ClickHouse({ 970 | ...config, 971 | user: 'default1', 972 | password: '' 973 | }), 974 | 975 | new ClickHouse({ 976 | ...config, 977 | username: 'default1', 978 | password: '' 979 | }), 980 | ]; 981 | 982 | for(const clickhouse of clickhouses) { 983 | try { 984 | await clickhouse.query('SELECT 1 + 1').toPromise(); 985 | } catch (err) { 986 | expect(err).to.be.ok(); 987 | } 988 | } 989 | }); 990 | 991 | 992 | it('database', async () => { 993 | const clickhouses = [ 994 | new ClickHouse({ 995 | ...config, 996 | ...{ 997 | config: { 998 | database: 'system', 999 | } 1000 | }, 1001 | }), 1002 | 1003 | new ClickHouse({ 1004 | ...config, 1005 | database: 'system', 1006 | }), 1007 | ]; 1008 | 1009 | for(const clickhouse of clickhouses) { 1010 | const [{ count }] = await clickhouse.query( 1011 | 'SELECT count(*) AS count FROM (SELECT number FROM numbers LIMIT 1024)' 1012 | ).toPromise(); 1013 | 1014 | expect(count).to.be(1024); 1015 | } 1016 | }); 1017 | 1018 | }); 1019 | 1020 | 1021 | describe('Exec system queries', () => { 1022 | it('select with ;', async () => { 1023 | const sqls = [ 1024 | 'EXISTS TABLE test_db.myTable' 1025 | ]; 1026 | 1027 | for(const sql of sqls) { 1028 | const [ row ] = await clickhouse.query(sql).toPromise(); 1029 | expect(row).to.be.ok(); 1030 | expect(row).to.have.key('result'); 1031 | expect(row.result).to.be(0); 1032 | } 1033 | }); 1034 | }); 1035 | 1036 | describe('Select and WITH TOTALS statement', () => { 1037 | [false, true].forEach(withTotals => { 1038 | it(`is ${withTotals}`, async () => { 1039 | const query = clickhouse.query(` 1040 | SELECT 1041 | number % 3 AS i, 1042 | groupArray(number) as kList 1043 | FROM ( 1044 | SELECT number FROM system.numbers LIMIT 14 1045 | ) 1046 | GROUP BY i ${withTotals ? '' : 'WITH TOTALS'} 1047 | FORMAT TabSeparatedWithNames 1048 | `); 1049 | 1050 | if (withTotals) { 1051 | query.withTotals(); 1052 | } 1053 | 1054 | const result = await query.toPromise(); 1055 | 1056 | expect(result).to.have.key('meta'); 1057 | expect(result).to.have.key('data'); 1058 | expect(result).to.have.key('totals'); 1059 | expect(result).to.have.key('rows'); 1060 | expect(result).to.have.key('statistics'); 1061 | }); 1062 | }); 1063 | 1064 | it('WITH TOTALS #2', async () => { 1065 | const LIMIT_COUNT = 10; 1066 | 1067 | const result = await clickhouse.query(` 1068 | SELECT 1069 | rowNumberInAllBlocks() AS i, 1070 | SUM(number) 1071 | FROM ( 1072 | SELECT 1073 | number 1074 | FROM 1075 | system.numbers 1076 | LIMIT 1000 1077 | ) 1078 | GROUP BY i WITH TOTALS 1079 | LIMIT ${LIMIT_COUNT} 1080 | `).toPromise(); 1081 | 1082 | expect(result).to.have.key('meta'); 1083 | expect(result).to.have.key('data'); 1084 | expect(result).to.have.key('totals'); 1085 | expect(result).to.have.key('rows'); 1086 | expect(result.rows).to.be(LIMIT_COUNT); 1087 | expect(result).to.have.key('statistics'); 1088 | }); 1089 | 1090 | it('WITH TOTALS #3 (JSON)', async () => { 1091 | const LIMIT_COUNT = 10; 1092 | 1093 | const result = await clickhouse.query(` 1094 | SELECT 1095 | rowNumberInAllBlocks() AS i, 1096 | number 1097 | FROM ( 1098 | SELECT 1099 | number 1100 | FROM 1101 | system.numbers 1102 | LIMIT 1000 1103 | ) 1104 | LIMIT ${LIMIT_COUNT} 1105 | FORMAT JSON 1106 | `).withTotals().toPromise(); 1107 | 1108 | expect(result).to.have.key('meta'); 1109 | expect(result).to.have.key('data'); 1110 | expect(result).to.have.key('rows'); 1111 | expect(result.rows).to.be(LIMIT_COUNT); 1112 | expect(result).to.have.key('statistics'); 1113 | }); 1114 | 1115 | it('start with WITH', async() => { 1116 | const r = await clickhouse.query(` 1117 | WITH x as (SELECT 1) SELECT * FROM x 1118 | `).toPromise(); 1119 | 1120 | expect(r).to.be.ok(); 1121 | expect(r[0]).to.be.eql({1: 1}); 1122 | }); 1123 | }); 1124 | 1125 | describe('Abort query', () => { 1126 | it('exec & abort', cb => { 1127 | const $q = clickhouse.query(`SELECT number FROM system.numbers LIMIT ${rowCount}`); 1128 | 1129 | let i = 0, 1130 | error = null; 1131 | 1132 | const stream = $q.stream() 1133 | .on('data', () => { 1134 | ++i; 1135 | 1136 | if (i > minRnd) { 1137 | stream.pause(); 1138 | } 1139 | }) 1140 | .on('error', err => error = err) 1141 | .on('close', () => { 1142 | expect(error).to.not.be.ok(); 1143 | expect(i).to.be.below(rowCount); 1144 | 1145 | // Take some time for waiting of cancel query 1146 | setTimeout(() => cb(), 4 * 1000); 1147 | }) 1148 | .on('end', () => { 1149 | cb(new Error('no way! May be stream very quick!')); 1150 | }); 1151 | 1152 | setTimeout(() => $q.destroy(), 10 * 1000); 1153 | }); 1154 | }); 1155 | 1156 | describe('Raw response', () => { 1157 | it('"raw" parameter should return response as a raw CSV/TSV/JSON string', async () => { 1158 | const tableName = 'test_raw_response'; 1159 | const insertValues = [ 1160 | {id: 'fm', name: 'Freddie Mercury', age: 45}, 1161 | {id: 'js', name: 'John Lennon', age: 40}, 1162 | {id: 'ep', name: 'Elvis Presley', age: 42}, 1163 | ]; 1164 | 1165 | const createResponse = await clickhouse.query(` 1166 | CREATE TABLE IF NOT EXISTS ${tableName} (id String, name String, age UInt8) ENGINE = MergeTree() ORDER BY id; 1167 | `).toPromise(); 1168 | expect(createResponse).to.be.ok(); 1169 | 1170 | const insertResponse = await clickhouse.insert(`INSERT INTO ${tableName} (id, name, age)`, insertValues).toPromise(); 1171 | expect(insertResponse).to.be.ok(); 1172 | 1173 | for (const format of ['csv', 'tsv', 'json']) { 1174 | const rawClient = new ClickHouse({...config, database, raw: true, format}); 1175 | const result = await rawClient.query(`SELECT * FROM ${tableName}`).toPromise(); 1176 | expect(typeof result).to.be.equal('string'); 1177 | 1178 | let data = rawClient.bodyParser(result); 1179 | format === 'json' && ({data} = data); 1180 | expect(data.length).to.be.equal(insertValues.length); 1181 | 1182 | for (const insertValue of insertValues) { 1183 | const resultValue = data.find(el => el.id === insertValue.id); 1184 | expect(resultValue).not.to.be.empty(); 1185 | expect(resultValue).to.be.eql(insertValue); 1186 | } 1187 | } 1188 | 1189 | }) 1190 | }) 1191 | 1192 | after(async () => { 1193 | await clickhouse.query(`DROP DATABASE IF EXISTS ${database}`).toPromise(); 1194 | }); 1195 | --------------------------------------------------------------------------------