Socket
Socket
Sign inDemoInstall

@clickhouse/client

Package Overview
Dependencies
Maintainers
4
Versions
44
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@clickhouse/client - npm Package Compare versions

Comparing version 1.0.1 to 1.0.2

10

dist/config.js

@@ -64,7 +64,9 @@ "use strict";

values_encoder: new utils_1.NodeValuesEncoder(),
make_result_set: ((stream, format, query_id) => new result_set_1.ResultSet(stream, format, query_id)),
close_stream: async (stream) => {
stream.destroy();
},
make_result_set: ((stream, format, query_id, log_error) => result_set_1.ResultSet.instance({
stream,
format,
query_id,
log_error,
})),
};
//# sourceMappingURL=config.js.map

12

dist/connection/node_base_connection.d.ts
/// <reference types="node" />
/// <reference types="node" />
/// <reference types="node" />
import type { ConnBaseQueryParams, Connection, ConnectionParams, ConnExecResult, ConnInsertParams, ConnInsertResult, ConnPingResult, ConnQueryResult } from '@clickhouse/client-common';
import type { ConnBaseQueryParams, ConnCommandResult, Connection, ConnectionParams, ConnExecResult, ConnInsertParams, ConnInsertResult, ConnPingResult, ConnQueryResult } from '@clickhouse/client-common';
import type Http from 'http';

@@ -40,10 +40,10 @@ import Stream from 'stream';

protected constructor(params: NodeConnectionParams, agent: Http.Agent);
protected buildDefaultHeaders(username: string, password: string, additional_http_headers?: Record<string, string>): Http.OutgoingHttpHeaders;
protected abstract createClientRequest(params: RequestParams): Http.ClientRequest;
private request;
ping(): Promise<ConnPingResult>;
query(params: ConnBaseQueryParams): Promise<ConnQueryResult<Stream.Readable>>;
insert(params: ConnInsertParams<Stream.Readable>): Promise<ConnInsertResult>;
exec(params: ConnBaseQueryParams): Promise<ConnExecResult<Stream.Readable>>;
insert(params: ConnInsertParams<Stream.Readable>): Promise<ConnInsertResult>;
command(params: ConnBaseQueryParams): Promise<ConnCommandResult>;
close(): Promise<void>;
protected buildDefaultHeaders(username: string, password: string, additional_http_headers?: Record<string, string>): Http.OutgoingHttpHeaders;
protected abstract createClientRequest(params: RequestParams): Http.ClientRequest;
private getQueryId;

@@ -55,2 +55,4 @@ private getAbortController;

private parseSummary;
private runExec;
private request;
}

@@ -56,169 +56,2 @@ "use strict";

}
buildDefaultHeaders(username, password, additional_http_headers) {
return {
// KeepAlive agent for some reason does not set this on its own
Connection: this.params.keep_alive.enabled ? 'keep-alive' : 'close',
Authorization: `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`,
'User-Agent': (0, utils_1.getUserAgent)(this.params.application_id),
...additional_http_headers,
};
}
async request(params, op) {
return new Promise((resolve, reject) => {
const start = Date.now();
const request = this.createClientRequest(params);
function onError(err) {
removeRequestListeners();
reject(err);
}
const onResponse = async (_response) => {
this.logResponse(op, request, params, _response, start);
const decompressionResult = (0, compression_1.decompressResponse)(_response);
if ((0, compression_1.isDecompressionError)(decompressionResult)) {
return reject(decompressionResult.error);
}
if ((0, client_common_1.isSuccessfulResponse)(_response.statusCode)) {
return resolve({
stream: decompressionResult.response,
summary: params.parse_summary
? this.parseSummary(op, _response)
: undefined,
});
}
else {
reject((0, client_common_1.parseError)(await (0, utils_1.getAsText)(decompressionResult.response)));
}
};
function onAbort() {
// Prefer 'abort' event since it always triggered unlike 'error' and 'close'
// see the full sequence of events https://nodejs.org/api/http.html#httprequesturl-options-callback
removeRequestListeners();
request.once('error', function () {
/**
* catch "Error: ECONNRESET" error which shouldn't be reported to users.
* see the full sequence of events https://nodejs.org/api/http.html#httprequesturl-options-callback
* */
});
reject(new Error('The user aborted a request.'));
}
function onClose() {
// Adapter uses 'close' event to clean up listeners after the successful response.
// It's necessary in order to handle 'abort' and 'timeout' events while response is streamed.
// It's always the last event, according to https://nodejs.org/docs/latest-v14.x/api/http.html#http_http_request_url_options_callback
removeRequestListeners();
}
function pipeStream() {
// if request.end() was called due to no data to send
if (request.writableEnded) {
return;
}
const bodyStream = (0, utils_1.isStream)(params.body)
? params.body
: stream_1.default.Readable.from([params.body]);
const callback = (err) => {
if (err) {
removeRequestListeners();
reject(err);
}
};
if (params.compress_request) {
stream_1.default.pipeline(bodyStream, zlib_1.default.createGzip(), request, callback);
}
else {
stream_1.default.pipeline(bodyStream, request, callback);
}
}
const onSocket = (socket) => {
if (this.params.keep_alive.enabled) {
const socketInfo = this.knownSockets.get(socket);
// It is the first time we encounter this socket,
// so it doesn't have the idle timeout handler attached to it
if (socketInfo === undefined) {
const socketId = crypto_1.default.randomUUID();
this.logger.trace({
message: `Using a fresh socket ${socketId}, setting up a new 'free' listener`,
});
this.knownSockets.set(socket, {
id: socketId,
idle_timeout_handle: undefined,
});
// When the request is complete and the socket is released,
// make sure that the socket is removed after `idleSocketTTL`.
socket.on('free', () => {
this.logger.trace({
message: `Socket ${socketId} was released`,
});
// Avoiding the built-in socket.timeout() method usage here,
// as we don't want to clash with the actual request timeout.
const idleTimeoutHandle = setTimeout(() => {
this.logger.trace({
message: `Removing socket ${socketId} after ${this.idleSocketTTL} ms of idle`,
});
this.knownSockets.delete(socket);
socket.destroy();
}, this.idleSocketTTL).unref();
this.knownSockets.set(socket, {
id: socketId,
idle_timeout_handle: idleTimeoutHandle,
});
});
const cleanup = () => {
const maybeSocketInfo = this.knownSockets.get(socket);
// clean up a possibly dangling idle timeout handle (preventing leaks)
if (maybeSocketInfo?.idle_timeout_handle) {
clearTimeout(maybeSocketInfo.idle_timeout_handle);
}
this.logger.trace({
message: `Socket ${socketId} was closed or ended, 'free' listener removed`,
});
};
socket.once('end', cleanup);
socket.once('close', cleanup);
}
else {
clearTimeout(socketInfo.idle_timeout_handle);
this.logger.trace({
message: `Reusing socket ${socketInfo.id}`,
});
this.knownSockets.set(socket, {
...socketInfo,
idle_timeout_handle: undefined,
});
}
}
// Socket is "prepared" with idle handlers, continue with our request
pipeStream();
// This is for request timeout only. Surprisingly, it is not always enough to set in the HTTP request.
// The socket won't be actually destroyed, and it will be returned to the pool.
socket.setTimeout(this.params.request_timeout, onTimeout);
};
function onTimeout() {
removeRequestListeners();
request.destroy();
reject(new Error('Timeout error.'));
}
function removeRequestListeners() {
if (request.socket !== null) {
request.socket.setTimeout(0); // reset previously set timeout
request.socket.removeListener('timeout', onTimeout);
}
request.removeListener('socket', onSocket);
request.removeListener('response', onResponse);
request.removeListener('error', onError);
request.removeListener('close', onClose);
if (params.abort_signal !== undefined) {
request.removeListener('abort', onAbort);
}
}
request.on('socket', onSocket);
request.on('response', onResponse);
request.on('error', onError);
request.on('close', onClose);
if (params.abort_signal !== undefined) {
params.abort_signal.addEventListener('abort', onAbort, { once: true });
}
if (!params.body)
return request.end();
});
}
async ping() {

@@ -294,44 +127,2 @@ const abortController = new AbortController();

}
async exec(params) {
const query_id = this.getQueryId(params.query_id);
const searchParams = (0, client_common_1.toSearchParams)({
database: this.params.database,
clickhouse_settings: params.clickhouse_settings,
query_params: params.query_params,
session_id: params.session_id,
query_id,
});
const { controller, controllerCleanup } = this.getAbortController(params);
try {
const { stream, summary } = await this.request({
method: 'POST',
url: (0, client_common_1.transformUrl)({ url: this.params.url, searchParams }),
body: params.query,
abort_signal: controller.signal,
parse_summary: true,
}, 'Exec');
return {
stream,
query_id,
summary,
};
}
catch (err) {
controller.abort('Exec HTTP request failed');
this.logRequestError({
op: 'Exec',
query_id: query_id,
query_params: params,
search_params: searchParams,
err: err,
extra_args: {
clickhouse_settings: params.clickhouse_settings ?? {},
},
});
throw err; // should be propagated to the user
}
finally {
controllerCleanup();
}
}
async insert(params) {

@@ -378,2 +169,17 @@ const query_id = this.getQueryId(params.query_id);

}
async exec(params) {
return this.runExec({
...params,
op: 'Exec',
});
}
async command(params) {
const { stream, query_id, summary } = await this.runExec({
...params,
op: 'Command',
});
// ignore the response stream and release the socket immediately
await (0, stream_2.drainStream)(stream);
return { query_id, summary };
}
async close() {

@@ -384,2 +190,11 @@ if (this.agent !== undefined && this.agent.destroy !== undefined) {

}
buildDefaultHeaders(username, password, additional_http_headers) {
return {
// KeepAlive agent for some reason does not set this on its own
Connection: this.params.keep_alive.enabled ? 'keep-alive' : 'close',
Authorization: `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`,
'User-Agent': (0, utils_1.getUserAgent)(this.params.application_id),
...additional_http_headers,
};
}
getQueryId(query_id) {

@@ -454,4 +269,204 @@ return query_id || crypto_1.default.randomUUID();

}
async runExec(params) {
const query_id = this.getQueryId(params.query_id);
const searchParams = (0, client_common_1.toSearchParams)({
database: this.params.database,
clickhouse_settings: params.clickhouse_settings,
query_params: params.query_params,
session_id: params.session_id,
query_id,
});
const { controller, controllerCleanup } = this.getAbortController(params);
try {
const { stream, summary } = await this.request({
method: 'POST',
url: (0, client_common_1.transformUrl)({ url: this.params.url, searchParams }),
body: params.query,
abort_signal: controller.signal,
parse_summary: true,
}, params.op);
return {
stream,
query_id,
summary,
};
}
catch (err) {
controller.abort(`${params.op} HTTP request failed`);
this.logRequestError({
op: params.op,
query_id: query_id,
query_params: params,
search_params: searchParams,
err: err,
extra_args: {
clickhouse_settings: params.clickhouse_settings ?? {},
},
});
throw err; // should be propagated to the user
}
finally {
controllerCleanup();
}
}
async request(params, op) {
return new Promise((resolve, reject) => {
const start = Date.now();
const request = this.createClientRequest(params);
function onError(err) {
removeRequestListeners();
reject(err);
}
const onResponse = async (_response) => {
this.logResponse(op, request, params, _response, start);
const decompressionResult = (0, compression_1.decompressResponse)(_response);
if ((0, compression_1.isDecompressionError)(decompressionResult)) {
return reject(decompressionResult.error);
}
if ((0, client_common_1.isSuccessfulResponse)(_response.statusCode)) {
return resolve({
stream: decompressionResult.response,
summary: params.parse_summary
? this.parseSummary(op, _response)
: undefined,
});
}
else {
reject((0, client_common_1.parseError)(await (0, utils_1.getAsText)(decompressionResult.response)));
}
};
function onAbort() {
// Prefer 'abort' event since it always triggered unlike 'error' and 'close'
// see the full sequence of events https://nodejs.org/api/http.html#httprequesturl-options-callback
removeRequestListeners();
request.once('error', function () {
/**
* catch "Error: ECONNRESET" error which shouldn't be reported to users.
* see the full sequence of events https://nodejs.org/api/http.html#httprequesturl-options-callback
* */
});
reject(new Error('The user aborted a request.'));
}
function onClose() {
// Adapter uses 'close' event to clean up listeners after the successful response.
// It's necessary in order to handle 'abort' and 'timeout' events while response is streamed.
// It's always the last event, according to https://nodejs.org/docs/latest-v14.x/api/http.html#http_http_request_url_options_callback
removeRequestListeners();
}
function pipeStream() {
// if request.end() was called due to no data to send
if (request.writableEnded) {
return;
}
const bodyStream = (0, utils_1.isStream)(params.body)
? params.body
: stream_1.default.Readable.from([params.body]);
const callback = (err) => {
if (err) {
removeRequestListeners();
reject(err);
}
};
if (params.compress_request) {
stream_1.default.pipeline(bodyStream, zlib_1.default.createGzip(), request, callback);
}
else {
stream_1.default.pipeline(bodyStream, request, callback);
}
}
const onSocket = (socket) => {
if (this.params.keep_alive.enabled) {
const socketInfo = this.knownSockets.get(socket);
// It is the first time we encounter this socket,
// so it doesn't have the idle timeout handler attached to it
if (socketInfo === undefined) {
const socketId = crypto_1.default.randomUUID();
this.logger.trace({
message: `Using a fresh socket ${socketId}, setting up a new 'free' listener`,
});
this.knownSockets.set(socket, {
id: socketId,
idle_timeout_handle: undefined,
});
// When the request is complete and the socket is released,
// make sure that the socket is removed after `idleSocketTTL`.
socket.on('free', () => {
this.logger.trace({
message: `Socket ${socketId} was released`,
});
// Avoiding the built-in socket.timeout() method usage here,
// as we don't want to clash with the actual request timeout.
const idleTimeoutHandle = setTimeout(() => {
this.logger.trace({
message: `Removing socket ${socketId} after ${this.idleSocketTTL} ms of idle`,
});
this.knownSockets.delete(socket);
socket.destroy();
}, this.idleSocketTTL).unref();
this.knownSockets.set(socket, {
id: socketId,
idle_timeout_handle: idleTimeoutHandle,
});
});
const cleanup = () => {
const maybeSocketInfo = this.knownSockets.get(socket);
// clean up a possibly dangling idle timeout handle (preventing leaks)
if (maybeSocketInfo?.idle_timeout_handle) {
clearTimeout(maybeSocketInfo.idle_timeout_handle);
}
this.logger.trace({
message: `Socket ${socketId} was closed or ended, 'free' listener removed`,
});
};
socket.once('end', cleanup);
socket.once('close', cleanup);
}
else {
clearTimeout(socketInfo.idle_timeout_handle);
this.logger.trace({
message: `Reusing socket ${socketInfo.id}`,
});
this.knownSockets.set(socket, {
...socketInfo,
idle_timeout_handle: undefined,
});
}
}
// Socket is "prepared" with idle handlers, continue with our request
pipeStream();
// This is for request timeout only. Surprisingly, it is not always enough to set in the HTTP request.
// The socket won't be actually destroyed, and it will be returned to the pool.
socket.setTimeout(this.params.request_timeout, onTimeout);
};
function onTimeout() {
removeRequestListeners();
request.destroy();
reject(new Error('Timeout error.'));
}
function removeRequestListeners() {
if (request.socket !== null) {
request.socket.setTimeout(0); // reset previously set timeout
request.socket.removeListener('timeout', onTimeout);
}
request.removeListener('socket', onSocket);
request.removeListener('response', onResponse);
request.removeListener('error', onError);
request.removeListener('close', onClose);
if (params.abort_signal !== undefined) {
request.removeListener('abort', onAbort);
}
}
request.on('socket', onSocket);
request.on('response', onResponse);
request.on('error', onError);
request.on('close', onClose);
if (params.abort_signal !== undefined) {
params.abort_signal.addEventListener('abort', onAbort, { once: true });
}
if (!params.body)
return request.end();
});
}
}
exports.NodeBaseConnection = NodeBaseConnection;
//# sourceMappingURL=node_base_connection.js.map

@@ -22,2 +22,8 @@ /// <reference types="node" />

};
export interface ResultSetOptions<Format extends DataFormat> {
stream: Stream.Readable;
format: Format;
query_id: string;
log_error: (error: Error) => void;
}
export declare class ResultSet<Format extends DataFormat | unknown> implements BaseResultSet<Stream.Readable, Format> {

@@ -27,3 +33,4 @@ private _stream;

readonly query_id: string;
constructor(_stream: Stream.Readable, format: Format, query_id: string);
private readonly log_error;
constructor(_stream: Stream.Readable, format: Format, query_id: string, log_error?: (error: Error) => void);
/** See {@link BaseResultSet.text}. */

@@ -36,2 +43,3 @@ text(): Promise<string>;

close(): void;
static instance<Format extends DataFormat>({ stream, format, query_id, log_error, }: ResultSetOptions<Format>): ResultSet<Format>;
}

@@ -33,3 +33,3 @@ "use strict";

class ResultSet {
constructor(_stream, format, query_id) {
constructor(_stream, format, query_id, log_error) {
Object.defineProperty(this, "_stream", {

@@ -53,2 +53,10 @@ enumerable: true,

});
Object.defineProperty(this, "log_error", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
// eslint-disable-next-line no-console
this.log_error = log_error ?? ((err) => console.error(err));
}

@@ -100,2 +108,3 @@ /** See {@link BaseResultSet.text}. */

let incompleteChunks = [];
const logError = this.log_error;
const toRows = new stream_1.Transform({

@@ -154,6 +163,6 @@ transform(chunk, _encoding, callback) {

const pipeline = stream_1.default.pipeline(this._stream, toRows, function pipelineCb(err) {
if (err) {
// FIXME: use logger instead
// eslint-disable-next-line no-console
console.error(err);
if (err &&
err.name !== 'AbortError' &&
err.message !== resultSetClosedMessage) {
logError(err);
}

@@ -164,7 +173,11 @@ });

close() {
this._stream.destroy();
this._stream.destroy(new Error(resultSetClosedMessage));
}
static instance({ stream, format, query_id, log_error, }) {
return new ResultSet(stream, format, query_id, log_error);
}
}
exports.ResultSet = ResultSet;
const streamAlreadyConsumedMessage = 'Stream has been already consumed';
const resultSetClosedMessage = 'ResultSet has been closed';
//# sourceMappingURL=result_set.js.map

@@ -1,2 +0,2 @@

declare const _default: "1.0.1";
declare const _default: "1.0.2";
export default _default;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = '1.0.1';
exports.default = '1.0.2';
//# sourceMappingURL=version.js.map

@@ -5,3 +5,3 @@ {

"homepage": "https://clickhouse.com",
"version": "1.0.1",
"version": "1.0.2",
"license": "Apache-2.0",

@@ -27,4 +27,4 @@ "keywords": [

"dependencies": {
"@clickhouse/client-common": "1.0.1"
"@clickhouse/client-common": "1.0.2"
}
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc