Releases: ClickHouse/clickhouse-js
1.20.0
Bug Fixes
- (Node.js only) Fixed a race condition in
ResultSet.json()andResultSet.stream()onJSONEachRow(and other streamable) result sets where callingjson()on a fast/small response could throwStream has been already consumedif the underlying stream ended between internalreadableEndedchecks. The consumption guard has been hardened: the stream is now shielded through a singleconsume()path that marks the result set as consumed in the appropriate branches, after format validation, so a successfuljson()call no longer races against the stream finishing. (#603) kudos to @lord007tn and @Onyx2406
1.19.0
Improvements
- Re-exported the
ResponseHeaderstype from@clickhouse/clientand@clickhouse/client-web. Previously this type was only available from@clickhouse/client-common; it is now part of the public re-export surface of both flavored packages, alongside the other commonly used types. This is part of an ongoing effort to make@clickhouse/client-commonan internal-only package so downstream consumers can depend solely on@clickhouse/clientor@clickhouse/client-web. (#758)
Bug Fixes
- Enum type parsing now correctly unescapes backslash escape sequences in enum names. Previously,
parseEnumTypereturned enum names with raw escape sequences (e.g.,f\'instead off'). Now it properly decodes escape sequences including\'(single quote),\\(backslash),\n(newline),\t(tab), and\r(carriage return). This matches the behavior of ClickHouse string literals and ensures consistency with how the client encodes strings when sending data to the server. If you were relying on the previous incorrect behavior where backslash escape sequences were preserved in enum names, you will need to update your code to handle properly unescaped values.
Example:
// Before (incorrect):
parseEnumType({
columnType: "Enum8('f\\'' = 1)",
sourceType: "Enum8('f\\'' = 1)",
})
// returned: { values: { 1: "f\\'" } } // with backslash
// After (correct):
parseEnumType({
columnType: "Enum8('f\\'' = 1)",
sourceType: "Enum8('f\\'' = 1)",
})
// returns: { values: { 1: "f'" } } // unescaped1.18.5
Improvements
- (Node.js only) Added
max_response_headers_sizeclient option that forwards themaxHeaderSizeoption to the underlyinghttp(s).requestcall. This raises the per-request limit on the total size of HTTP response headers received from the server (Node.js default is ~16 KB). It is most useful when running long-running queries withsend_progress_in_http_headersenabled — theX-ClickHouse-Progressheaders accumulate over the lifetime of the request and can exceed the default limit, causing the request to fail withHPE_HEADER_OVERFLOW. Setting this option avoids the need to use the global--max-http-header-sizeNode.js CLI flag or theNODE_OPTIONSenvironment variable. Has no effect for the Web client (which usesfetch) and no effect when a customhttp_agentis configured with a request implementation that does not honor the option.
const client = createClient({
request_timeout: 400_000,
max_response_headers_size: 1024 * 1024, // accept up to 1 MiB of response headers
clickhouse_settings: {
send_progress_in_http_headers: 1,
http_headers_progress_interval_ms: '110000',
},
})- The
@clickhouse/clientnpm package now ships embedded AI-agent skills,clickhouse-js-node-codingandclickhouse-js-node-troubleshooting, undernode_modules/@clickhouse/client/skills/. These skills are also declared in theagents.skillsfield of the package manifest for discovery tools that scannode_modules. This allows agentic coding tools to load focused, Node-client-specific coding and troubleshooting guidance without any additional setup. (#682)
1.18.4
A release-infrastructure-only version bump (no user-facing changes). See 1.18.5 for the next release with user-facing improvements.
1.18.3
Improvements
- Added
keep_alive.eagerly_destroy_stale_socketsoption (Node.js only, default:false). When enabled, sockets that have been idle for longer thanidle_socket_ttlare destroyed immediately before each request, rather than waiting for the idle timeout to fire. This helps reclaim stale sockets during event loop delays, where the timeout callback may not run on time.
const client = createClient({
keep_alive: {
enabled: true,
idle_socket_ttl: 2500,
eagerly_destroy_stale_sockets: true,
},
})- Added auto-detection and warning when
request_timeoutis high (> 60 seconds) but progress headers are not configured. Long-running queries may fail with socket hang-up errors if they exceed the load balancer idle timeout. The client now warns users to enablesend_progress_in_http_headersandhttp_headers_progress_interval_mssettings to prevent such issues.
// This will now trigger a warning
const client = createClient({
request_timeout: 120_000, // 120 seconds
// send_progress_in_http_headers is not configured
})
// ✓ Properly configured to avoid load balancer timeouts
const client = createClient({
request_timeout: 400_000,
clickhouse_settings: {
send_progress_in_http_headers: 1,
http_headers_progress_interval_ms: '110000', // ~10s below LB timeout
},
})1.18.2
Improvements
- Added a helping
WARNlevel log message with a suggestion to check thekeep_aliveconfiguration if the client receives anECONNRESETerror from the server, which can happen when the server closes idle connections after a certain timeout, and the client tries to reuse such a connection from the pool. This can be especially helpful for new users who might not be aware of this aspect of HTTP connection management. The log message is only emitted if thekeep_aliveoption is enabled in the client configuration, and it includes the server's keep-alive timeout value (if available) to assist with troubleshooting. (#597)
How to reproduce the issue that triggers the log message:
const client = createClient({
// ...
keep_alive: {
enabled: true,
// ❌ DON'T SET THIS VALUE SO HIGH IN PRODUCTION
idle_socket_ttl: 1_000_000,
},
log: {
level: ClickHouseLogLevel.WARN, // to see the warning logs
},
})
for (let i = 0; i < 1000; i++) {
await client.ping({
// To use a regular query instead of the /ping endpoint
// which might be configured differently on the server side
// and have different timeout settings.
select: true,
})
// Wait long enough to let the server close the idle connection,
// but not too long to let the client remove it from the pool,
// in other words try to hit the scenario when the race condition
// happens between the server closing the connection and the client
// trying to reuse it.
await sleep(SERVER_KEEP_ALIVE_TIMEOUT_MS - 100)
}Example log message:
{
"message": "Ping: idle socket TTL is greater than server keep-alive timeout, try setting idle socket TTL to a value lower than the server keep-alive timeout to prevent unexpected connection resets, see https://c.house/js_keep_alive_econnreset for more details.",
"args": {
"operation": "Ping",
"connection_id": "8dc1c9bd-7895-49b1-8a95-276470151c65",
"query_id": "beee95af-2e83-4dcb-8e1e-045bd61f4985",
"request_id": "8dc1c9bd-7895-49b1-8a95-276470151c65:2",
"socket_id": "8dc1c9bd-7895-49b1-8a95-276470151c65:1",
"server_keep_alive_timeout_ms": 10000,
"idle_socket_ttl": 15000
},
"module": "HTTP Adapter"
}1.18.1
Improvements
- Setting
log.leveldefault value toClickHouseLogLevel.WARNinstead ofClickHouseLogLevel.OFFto provide better visibility into potential issues without overwhelming users with too much information by default.
const client = createClient({
// ...
log: {
level: ClickHouseLogLevel.WARN, // default is now ClickHouseLogLevel.WARN instead of ClickHouseLogLevel.OFF
},
})- Logging is now lazy, which means that the log messages will only be constructed if the log level is appropriate for the message. This can improve performance in cases where constructing the log message is expensive, and the log level is set to ignore such messages. See
ClickHouseLogLevelenum for the complete list of log levels. (#520)
const client = createClient({
// ...
log: {
level: ClickHouseLogLevel.TRACE, // to log everything available down to the network level events
},
})- Enhanced the logging of the HTTP request / socket lifecycle with additional trace messages and context such as Connection ID (UUID) and Request ID and Socket ID that embed the connection ID for ease of tracing the logs of a particular request across the connection lifecycle. To enable such logs, set the
log.levelconfig option toClickHouseLogLevel.TRACE. (#567)
[2026-02-25T09:19:13.511Z][TRACE][@clickhouse/client][Connection] Insert: received 'close' event, 'free' listener removed
Arguments: {
operation: 'Insert',
connection_id: 'da3c9796-5dc5-46ef-83b0-ed1f4422094c',
query_id: '9dfda627-39a2-41a6-9fc9-8f8716574826',
request_id: 'da3c9796-5dc5-46ef-83b0-ed1f4422094c:3',
socket_id: 'da3c9796-5dc5-46ef-83b0-ed1f4422094c:2',
event: 'close'
}
[2026-02-25T09:19:13.502Z][TRACE][@clickhouse/client][Connection] Query: reusing socket
Arguments: {
operation: 'Query',
connection_id: 'da3c9796-5dc5-46ef-83b0-ed1f4422094c',
query_id: 'ad0127e8-b1c7-4ed6-9681-c0162f7a0ea9',
request_id: 'da3c9796-5dc5-46ef-83b0-ed1f4422094c:4',
socket_id: 'da3c9796-5dc5-46ef-83b0-ed1f4422094c:2',
usage_count: 1
}- A step towards structured logging: the client now passes rich context to the logger
argsparameter (e.g.connection_id,query_id,request_id,socket_id). (#576)
Deprecated API
-
The
drainStreamutility function is now deprecated, as the client will handle draining the stream internally when needed. Useclient.command()instead, which will handle draining the stream internally when needed. (#578) -
The
sleeputility function is now deprecated, as it is not intended to be used outside of the client implementation. UsesetTimeoutdirectly or a more full-featured utility library if you need additional features like cancellation or timers management. (#578)
1.17.0
1.16.0
New features
- Added support for the new Disposable API (a.k.a the
usingkeyword) (#500)
async function main() {
await using client = await client.query(…);
// some code that can throw
// but thanks to `using` the client will still get closed
// client is also automatically closed here by calling [Symbol.disaposeAsync]
}Without the new using keyword it is required to wrap the code that might leak expensive resources like sockets and big buffers in try / finally
async function main() {
let client
try {
client = await createClient(…);
// some code that can throw
} finally {
if (client) {
await client.close()
}
}
}