Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
a226c2a
Fix ResultSet.json() race condition on JSONEachRow streams
Onyx2406 Mar 8, 2026
45c8925
Fix: don't check readableEnded in markAsConsumed
Onyx2406 Mar 8, 2026
fa44941
Fix stream() marking consumed before format validation, improve test
Onyx2406 Mar 8, 2026
8034819
Move markAsConsumed into consumption branches in json()
Onyx2406 Mar 19, 2026
a26ec2e
ci: skip cloud tests for PRs from forks
Copilot May 27, 2026
f5ebc5f
chore: bump version to 1.20.0
github-actions[bot] May 28, 2026
8e42e7f
Merge pull request #761 from ClickHouse/release-1.20.0
peter-leonov-ch May 28, 2026
9a2b991
Merge pull request #765 from ClickHouse/copilot/change-gh-test-workflow
peter-leonov-ch May 29, 2026
50fcfb5
Merge branch 'main' into fix/result-set-stream-consumed-race
peter-leonov-ch May 29, 2026
daccd30
Merge pull request #603 from Onyx2406/fix/result-set-stream-consumed-…
peter-leonov-ch May 29, 2026
457b5a4
unwrap to avoid indirection in test results
peter-leonov-ch May 29, 2026
b6e1397
revert for testing
peter-leonov-ch May 29, 2026
01c6cf4
construct the error
peter-leonov-ch May 29, 2026
f260259
use proper matchers
peter-leonov-ch May 29, 2026
0452839
extra
peter-leonov-ch May 29, 2026
8fa0c9d
explain expectations
peter-leonov-ch May 29, 2026
5c28df8
shield the stream though consume() method
peter-leonov-ch May 29, 2026
cbe0d13
adapt the tests
peter-leonov-ch May 29, 2026
fc4b8d8
ci: add copilot-setup-steps using medium-runner-ubuntu-x64
Copilot May 29, 2026
23255c5
for the future
peter-leonov-ch May 29, 2026
97181db
Update packages/client-node/src/result_set.ts
peter-leonov-ch May 29, 2026
a0c8526
unwrap
peter-leonov-ch May 29, 2026
24e4a05
wrap :)
peter-leonov-ch May 29, 2026
40c1d60
Update .github/workflows/copilot-setup-steps.yml
peter-leonov-ch May 29, 2026
f54a80b
Merge pull request #769 from ClickHouse/copilot/use-medium-runner-ubu…
peter-leonov-ch May 29, 2026
84aaf6d
Merge pull request #767 from ClickHouse/fix_ci_3
peter-leonov-ch May 29, 2026
0d06d19
ci(publish): gate npm publish jobs on npm-publish environment (#771)
Copilot May 29, 2026
ecccb2f
Fix splitQueries to skip SQL comments in upstream test runner (#762)
Copilot May 29, 2026
297045c
docs: add 1.20.0 changelog entry (#773)
Copilot May 29, 2026
2762d0f
Pin Playwright to ^1.60.0 to fix CI install hang (#774)
Copilot May 29, 2026
89a4872
Refactoring test client to eliminate ClickHouse dependency (#775)
Copilot May 29, 2026
373d195
Move ResponseHeaders re-export note from 1.20.0 to 1.19.0 changelog (…
Copilot May 29, 2026
f4c4ec1
upstream-sql-tests: disable 5 allowlist entries failing on released C…
Copilot May 31, 2026
7d0627f
Soft-deprecate runtime value re-exports from @clickhouse/client-commo…
Copilot May 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/workflows/copilot-setup-steps.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: 'Copilot Setup Steps'
permissions: {}

# Automatically run the setup steps when they are changed to allow for easy validation,
# and allow manual testing through the repository's "Actions" tab.
on:
workflow_dispatch:
push:
paths:
- .github/workflows/copilot-setup-steps.yml
pull_request:
paths:
- .github/workflows/copilot-setup-steps.yml

jobs:
# The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot.
copilot-setup-steps:
runs-on: medium-runner-ubuntu-x64
permissions: {}
Comment thread
peter-leonov-ch marked this conversation as resolved.
2 changes: 2 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ jobs:
head:
if: github.ref == 'refs/heads/release' && github.event_name == 'push'
runs-on: ubuntu-latest
environment: npm-publish
outputs:
version: ${{ steps.version.outputs.version }}
steps:
Expand Down Expand Up @@ -81,6 +82,7 @@ jobs:
latest:
if: github.ref == 'refs/heads/release' && github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
environment: npm-publish
permissions:
contents: write # Required to push the release git tag
id-token: write # Required for npm OIDC authentication and provenance
Expand Down
10 changes: 10 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,8 @@ jobs:
node .scripts/export-coverage-metrics.mjs

node-integration-tests-cloud:
# Cloud secrets are not available for PRs from forks; skip the job in that case.
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
timeout-minutes: 5
runs-on: ubuntu-latest
strategy:
Expand Down Expand Up @@ -371,6 +373,8 @@ jobs:
node .scripts/export-coverage-metrics.mjs

web-integration-tests-cloud:
# Cloud secrets are not available for PRs from forks; skip the job in that case.
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
timeout-minutes: 5
runs-on: ubuntu-latest
strategy:
Expand Down Expand Up @@ -506,7 +510,13 @@ jobs:
'web-integration-tests-cloud',
'web-codecov-upload',
]
# Run even if some needed jobs were skipped (e.g. cloud jobs on PRs from forks),
# but still fail if any of them failed or were cancelled.
if: ${{ always() }}
runs-on: ubuntu-latest
steps:
- name: Fail if any needed job failed or was cancelled
if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}
run: exit 1
- name: All tests passed
run: echo "All tests passed! 🎉"
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# 1.20.0

## Bug Fixes

- (Node.js only) Fixed a race condition in `ResultSet.json()` and `ResultSet.stream()` on `JSONEachRow` (and other streamable) result sets where calling `json()` on a fast/small response could throw `Stream has been already consumed` if the underlying stream ended between internal `readableEnded` checks. The consumption guard has been hardened: the stream is now shielded through a single `consume()` path that marks the result set as consumed in the appropriate branches, after format validation, so a successful `json()` call no longer races against the stream finishing. ([#603])

[#603]: https://github.com/ClickHouse/clickhouse-js/pull/603

# 1.19.0

## Improvements

- Re-exported the `ResponseHeaders` type from `@clickhouse/client` and `@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-common` an internal-only package so downstream consumers can depend solely on `@clickhouse/client` or `@clickhouse/client-web`. ([#758])
Comment thread
peter-leonov-ch marked this conversation as resolved.

[#758]: https://github.com/ClickHouse/clickhouse-js/pull/758

## Breaking Changes

- **Enum type parsing now correctly unescapes backslash escape sequences in enum names.** Previously, `parseEnumType` returned enum names with raw escape sequences (e.g., `f\'` instead of `f'`). 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.
Expand Down
18 changes: 8 additions & 10 deletions examples/web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions examples/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-expect-type": "^0.6.2",
"eslint-plugin-prettier": "^5.5.4",
"playwright": "^1.60.0",
"tsx": "^4.21.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.46.4",
Expand Down
30 changes: 14 additions & 16 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"jsonwebtoken": "^9.0.3",
"lint-staged": "^16.4.0",
"parquet-wasm": "0.7.1",
"playwright": "^1.60.0",
"prettier": "3.8.1",
"split2": "^4.2.0",
"typescript": "^5.9.3",
Expand Down
13 changes: 13 additions & 0 deletions packages/client-common/__tests__/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,16 @@

This folder contains unit and integration test scenarios that we expect to be compatible to every connection,
as well as the shared utilities for effective tests writing.

#### Test client utilities

For integration tests that need a running ClickHouse instance, use `createTestClient()` (or the platform
wrappers `createNodeTestClient()` / `createWebTestClient()`). These connect to the configured test environment
(local single node, local cluster, or cloud), apply environment-specific settings, and rely on the shared
`beforeAll` initializer registered in `utils/client.ts`.

For unit tests that must be runnable **without** a reachable ClickHouse instance, use `createSimpleTestClient()`
from `utils/simple_client.ts` (or the platform wrappers `createSimpleNodeTestClient()` /
`createSimpleWebTestClient()`). This factory lives in a side-effect-free module: importing it never registers the
shared `beforeAll` test-environment initializer and it does not read any connection details from the environment,
so no ClickHouse server is required as long as the test does not issue an actual request.
9 changes: 9 additions & 0 deletions packages/client-common/__tests__/unit/client.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { vi, describe, it, expect } from 'vitest'
import { sleep } from '../utils/sleep'
import { ClickHouseClient } from '../../src/client'
import { createSimpleTestClient } from '../utils/simple_client'

function isAwaitUsingStatementSupported(): boolean {
try {
Expand All @@ -27,6 +28,14 @@ function mockImpl(): any {
}

describe('client', () => {
it('createSimpleTestClient creates a client without requiring ClickHouse', async () => {
// Imported from the side-effect-free `simple_client` module, so it does not
// register the shared `beforeAll` test-env init and needs no ClickHouse.
const client = createSimpleTestClient()
expect(client).toBeDefined()
await client.close()
})

it.skipIf(!isAwaitUsingStatementSupported())(
'closes the client when used with using statement',
async () => {
Expand Down
63 changes: 30 additions & 33 deletions packages/client-common/__tests__/utils/client.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,55 @@
/* eslint @typescript-eslint/no-var-requires: 0 */
import { beforeAll } from 'vitest'
import {
ClickHouseLogLevel,
type BaseClickHouseClientConfigOptions,
type ClickHouseClient,
type ClickHouseSettings,
} from '@clickhouse/client-common'
import { EnvKeys, getFromEnv } from './env'
import { guid } from './guid'
import { createSimpleTestClient, getTestLogConfig } from './simple_client'
import {
getClickHouseTestEnvironment,
isCloudTestEnv,
PRINT_DDL,
SKIP_INIT,
TestEnv,
} from './test_env'
import { TestLogger } from './test_logger'

let databaseName: string
beforeAll(async () => {
if (SKIP_INIT) {
// it will be skipped for unit tests that don't require DB setup
console.log('\nSkipping test environment initialization')
return
}
export { createSimpleTestClient }

console.log(
`\nTest environment: ${getClickHouseTestEnvironment()}, database: ${
databaseName ?? 'default'
}`,
)
const initClient = createTestClient({
request_timeout: 10_000,
let databaseName: string
// Only register the shared test-environment initializer when it is actually
// needed. Skipping the registration entirely (instead of returning early from
// the hook) ensures that importing this module never couples a test suite to a
// reachable ClickHouse instance when init is skipped.
if (!SKIP_INIT) {
beforeAll(async () => {
console.log(
`\nTest environment: ${getClickHouseTestEnvironment()}, database: ${
databaseName ?? 'default'
}`,
)
const initClient = createTestClient({
request_timeout: 10_000,
})
if (isCloudTestEnv() && databaseName === undefined) {
await wakeUpPing(initClient)
databaseName = await createRandomDatabase(initClient)
}
await initClient.close()
})
if (isCloudTestEnv() && databaseName === undefined) {
await wakeUpPing(initClient)
databaseName = await createRandomDatabase(initClient)
}
await initClient.close()
})
}

export function createTestClient<Stream = unknown>(
config: BaseClickHouseClientConfigOptions = {},
): ClickHouseClient<Stream> {
// When the shared test-environment init is skipped, there is no ClickHouse
// instance to talk to; fall back to a client that requires no server.
if (SKIP_INIT) {
return createSimpleTestClient<Stream>(config)
}

const env = getClickHouseTestEnvironment()
const clickHouseSettings: ClickHouseSettings = {
// (U)Int64 are not quoted by default since 25.8
Expand All @@ -55,17 +62,7 @@ export function createTestClient<Stream = unknown>(
}
// Allow to override `insert_quorum` if necessary
Object.assign(clickHouseSettings, config?.clickhouse_settings || {})
const level =
config.log?.level ??
(!process.env.LOG_LEVEL || process.env.LOG_LEVEL === 'undefined'
? undefined
: ClickHouseLogLevel[
process.env.LOG_LEVEL as keyof typeof ClickHouseLogLevel
])
const log: BaseClickHouseClientConfigOptions['log'] = {
LoggerClass: TestLogger,
level,
}
const log = getTestLogConfig(config)

if (isCloudTestEnv()) {
return (globalThis as any).environmentSpecificCreateClient({
Expand Down
1 change: 1 addition & 0 deletions packages/client-common/__tests__/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export { TestLogger } from './test_logger'
export {
createTestClient,
createSimpleTestClient,
createRandomDatabase,
createTable,
getTestDatabaseName,
Expand Down
Loading
Loading