Skip to content

Commit 7a37e89

Browse files
authored
Merge pull request #240 from ClickHouse/1.0.0
1.0.0 * Add support for URL parameters parsing (#232) * Infer ResultSet type hints based on DataFormat (#238) * Add SharedMergeTree Cloud tests, remove Node 16 from the CI, and add Node 21 (#234) * Add pathname config option, revert read-only switch/default settings (#251) * Improved performance when decoding the entire set of rows with streamable JSON formats (#253) * Bump dev dependencies, update internal module resolution (#248)
2 parents 1cba687 + 392bd82 commit 7a37e89

File tree

139 files changed

+4627
-1157
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

139 files changed

+4627
-1157
lines changed

.eslintrc.json

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,19 @@
88
"env": {
99
"node": true
1010
},
11-
"plugins": ["@typescript-eslint", "prettier"],
11+
"plugins": ["@typescript-eslint", "prettier", "eslint-plugin-expect-type"],
1212
"extends": [
1313
"eslint:recommended",
1414
"plugin:@typescript-eslint/eslint-recommended",
1515
"plugin:@typescript-eslint/recommended",
16+
"plugin:expect-type/recommended",
1617
"prettier"
1718
],
1819
"rules": {
1920
"prettier/prettier": "error",
2021
"@typescript-eslint/no-floating-promises": "error",
2122
"eqeqeq": "error",
23+
"no-console": "error",
2224
// Disable some inherited recommended checks until they all are fixed in targeted PRs.
2325
"@typescript-eslint/no-explicit-any": "off",
2426
"@typescript-eslint/consistent-type-imports": "warn"
@@ -30,7 +32,14 @@
3032
"@typescript-eslint/no-explicit-any": "off",
3133
"@typescript-eslint/no-non-null-assertion": "off",
3234
"@typescript-eslint/ban-ts-comment": "off",
33-
"no-constant-condition": "off"
35+
"no-constant-condition": "off",
36+
"no-console": "off"
37+
}
38+
},
39+
{
40+
"files": ["./**/examples/**/*.ts", "./**/benchmarks/**/*.ts"],
41+
"rules": {
42+
"no-console": "off"
3443
}
3544
}
3645
]

.github/workflows/tests.yml

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ jobs:
3939
run: |
4040
npm install
4141
42+
- name: Install dependencies (examples)
43+
working-directory: examples
44+
run: |
45+
npm install
46+
4247
- name: Run linting
4348
run: |
4449
npm run lint
@@ -214,6 +219,33 @@ jobs:
214219
run: |
215220
npm run test:node:integration:cloud
216221
222+
node-integration-tests-cloud-smt:
223+
needs: node-unit-tests
224+
runs-on: ubuntu-latest
225+
strategy:
226+
fail-fast: true
227+
matrix:
228+
node: [18, 20, 21]
229+
230+
steps:
231+
- uses: actions/checkout@main
232+
233+
- name: Setup NodeJS ${{ matrix.node }}
234+
uses: actions/setup-node@v4
235+
with:
236+
node-version: ${{ matrix.node }}
237+
238+
- name: Install dependencies
239+
run: |
240+
npm install
241+
242+
- name: Run integration tests
243+
env:
244+
CLICKHOUSE_CLOUD_HOST: ${{ secrets.INTEGRATIONS_TEAM_TESTS_CLOUD_HOST_SMT }}
245+
CLICKHOUSE_CLOUD_PASSWORD: ${{ secrets.INTEGRATIONS_TEAM_TESTS_CLOUD_PASSWORD_SMT }}
246+
run: |
247+
npm run test:node:integration:cloud_smt
248+
217249
web-integration-tests-cloud:
218250
needs: node-unit-tests
219251
runs-on: ubuntu-latest
@@ -236,3 +268,26 @@ jobs:
236268
CLICKHOUSE_CLOUD_PASSWORD: ${{ secrets.INTEGRATIONS_TEAM_TESTS_CLOUD_PASSWORD }}
237269
run: |
238270
npm run test:web:integration:cloud
271+
272+
web-integration-tests-cloud-smt:
273+
needs: node-unit-tests
274+
runs-on: ubuntu-latest
275+
permissions: write-all
276+
steps:
277+
- uses: actions/checkout@main
278+
279+
- name: Setup NodeJS
280+
uses: actions/setup-node@v4
281+
with:
282+
node-version: 20
283+
284+
- name: Install dependencies
285+
run: |
286+
npm install
287+
288+
- name: Run integration tests
289+
env:
290+
CLICKHOUSE_CLOUD_HOST: ${{ secrets.INTEGRATIONS_TEAM_TESTS_CLOUD_HOST_SMT }}
291+
CLICKHOUSE_CLOUD_PASSWORD: ${{ secrets.INTEGRATIONS_TEAM_TESTS_CLOUD_PASSWORD_SMT }}
292+
run: |
293+
npm run test:web:integration:cloud_smt

.scripts/jasmine.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
#!/bin/bash
2-
ts-node -r tsconfig-paths/register --project=tsconfig.dev.json node_modules/jasmine/bin/jasmine --config=$1
2+
ts-node -r tsconfig-paths/register --transpileOnly --project=tsconfig.dev.json node_modules/jasmine/bin/jasmine --config=$1

CHANGELOG.md

Lines changed: 222 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,224 @@
1+
# 1.0.0 (Common, Node.js, Web)
2+
3+
Formal stable release milestone with a lot of improvements and some [breaking changes](#breaking-changes-in-100).
4+
5+
Major new features overview:
6+
7+
- [Advanced TypeScript support for `query` + `ResultSet`](#advanced-typescript-support-for-query--resultset)
8+
- [URL configuration](#url-configuration)
9+
10+
From now on, the client will follow the [official semantic versioning](https://docs.npmjs.com/about-semantic-versioning) guidelines.
11+
12+
## Deprecated API
13+
14+
The following configuration parameters are marked as deprecated:
15+
16+
- `host` configuration parameter is deprecated; use `url` instead.
17+
- `additional_headers` configuration parameter is deprecated; use `http_headers` instead.
18+
19+
The client will log a warning if any of these parameters are used. However, it is still allowed to use `host` instead of `url` and `additional_headers` instead of `http_headers` for now; this deprecation is not supposed to break the existing code.
20+
21+
These parameters will be removed in the next major release (2.0.0).
22+
23+
See "New features" section for more details.
24+
25+
## Breaking changes in 1.0.0
26+
27+
- `compression.response` is now disabled by default in the client configuration options, as it cannot be used with readonly=1 users, and it was not clear from the ClickHouse error message what exact client option was causing the failing query in this case. If you'd like to continue using response compression, you should explicitly enable it in the client configuration.
28+
- As the client now supports parsing [URL configuration](#url-configuration), you should specify `pathname` as a separate configuration option (as it would be considered as the `database` otherwise).
29+
- (TypeScript only) `ResultSet` and `Row` are now more strictly typed, according to the format used during the `query` call. See [this section](#advanced-typescript-support-for-query--resultset) for more details.
30+
- (TypeScript only) Both Node.js and Web versions now uniformly export correct `ClickHouseClient` and `ClickHouseClientConfigOptions` types, specific to each implementation. Exported `ClickHouseClient` now does not have a `Stream` type parameter, as it was unintended to expose it there. NB: you should still use `createClient` factory function provided in the package.
31+
32+
## New features in 1.0.0
33+
34+
### Advanced TypeScript support for `query` + `ResultSet`
35+
36+
Client will now try its best to figure out the shape of the data based on the DataFormat literal specified to the `query` call, as well as which methods are allowed to be called on the `ResultSet`.
37+
38+
Live demo (see the full description below):
39+
40+
[Screencast](https://github.com/ClickHouse/clickhouse-js/assets/3175289/b66afcb2-3a10-4411-af59-51d2754c417e)
41+
42+
Complete reference:
43+
44+
| Format | `ResultSet.json<T>()` | `ResultSet.stream<T>()` | Stream data | `Row.json<T>()` |
45+
| ------------------------------- | --------------------- | --------------------------- | ----------------- | --------------- |
46+
| JSON | ResponseJSON\<T\> | never | never | never |
47+
| JSONObjectEachRow | Record\<string, T\> | never | never | never |
48+
| All other JSON\*EachRow | Array\<T\> | Stream\<Array\<Row\<T\>\>\> | Array\<Row\<T\>\> | T |
49+
| CSV/TSV/CustomSeparated/Parquet | never | Stream\<Array\<Row\<T\>\>\> | Array\<Row\<T\>\> | never |
50+
51+
By default, `T` (which represents `JSONType`) is still `unknown`. However, considering `JSONObjectsEachRow` example: prior to 1.0.0, you had to specify the entire type hint, including the shape of the data, manually:
52+
53+
```ts
54+
type Data = { foo: string }
55+
56+
const resultSet = await client.query({
57+
query: 'SELECT * FROM my_table',
58+
format: 'JSONObjectsEachRow',
59+
})
60+
61+
// pre-1.0.0, `resultOld` has type Record<string, Data>
62+
const resultOld = resultSet.json<Record<string, Data>>()
63+
// const resultOld = resultSet.json<Data>() // incorrect! The type hint should've been `Record<string, Data>` here.
64+
65+
// 1.0.0, `resultNew` also has type Record<string, Data>; client inferred that it has to be a Record from the format literal.
66+
const resultNew = resultSet.json<Data>()
67+
```
68+
69+
This is even more handy in case of streaming on the Node.js platform:
70+
71+
```ts
72+
const resultSet = await client.query({
73+
query: 'SELECT * FROM my_table',
74+
format: 'JSONEachRow',
75+
})
76+
77+
// pre-1.0.0
78+
// `streamOld` was just a regular Node.js Stream.Readable
79+
const streamOld = resultSet.stream()
80+
// `rows` were `any`, needed an explicit type hint
81+
streamNew.on('data', (rows: Row[]) => {
82+
rows.forEach((row) => {
83+
// without an explicit type hint to `rows`, calling `forEach` and other array methods resulted in TS compiler errors
84+
const t = row.text
85+
const j = row.json<Data>() // `j` needed a type hint here, otherwise, it's `unknown`
86+
})
87+
})
88+
89+
// 1.0.0
90+
// `streamNew` is now StreamReadable<T> (Node.js Stream.Readable with a bit more type hints);
91+
// type hint for the further `json` calls can be added here (and removed from the `json` calls)
92+
const streamNew = resultSet.stream<Data>()
93+
// `rows` are inferred as an Array<Row<Data, "JSONEachRow">> instead of `any`
94+
streamNew.on('data', (rows) => {
95+
// `row` is inferred as Row<Data, "JSONEachRow">
96+
rows.forEach((row) => {
97+
// no explicit type hints required, you can use `forEach` straight away and TS compiler will be happy
98+
const t = row.text
99+
const j = row.json() // `j` will be of type Data
100+
})
101+
})
102+
103+
// async iterator now also has type hints
104+
// similarly to the `on(data)` example above, `rows` are inferred as Array<Row<Data, "JSONEachRow">>
105+
for await (const rows of streamNew) {
106+
// `row` is inferred as Row<Data, "JSONEachRow">
107+
rows.forEach((row) => {
108+
const t = row.text
109+
const j = row.json() // `j` will be of type Data
110+
})
111+
}
112+
```
113+
114+
Calling `ResultSet.stream` is not allowed for certain data formats, such as `JSON` and `JSONObjectsEachRow` (unlike `JSONEachRow` and the rest of `JSON*EachRow`, these formats return a single object). In these cases, the client throws an error. However, it was previously not reflected on the type level; now, calling `stream` on these formats will result in a TS compiler error. For example:
115+
116+
```ts
117+
const resultSet = await client.query('SELECT * FROM table', {
118+
format: 'JSON',
119+
})
120+
const stream = resultSet.stream() // `stream` is `never`
121+
```
122+
123+
Calling `ResultSet.json` also does not make sense on `CSV` and similar "raw" formats, and the client throws. Again, now, it is typed properly:
124+
125+
```ts
126+
const resultSet = await client.query('SELECT * FROM table', {
127+
format: 'CSV',
128+
})
129+
// `json` is `never`; same if you stream CSV, and call `Row.json` - it will be `never`, too.
130+
const json = resultSet.json()
131+
```
132+
133+
Currently, there is one known limitation: as the general shape of the data and the methods allowed for calling are inferred from the format literal, there might be situations where it will fail to do so, for example:
134+
135+
```ts
136+
// assuming that `queryParams` has `JSONObjectsEachRow` format inside
137+
async function runQuery(
138+
queryParams: QueryParams,
139+
): Promise<Record<string, Data>> {
140+
const resultSet = await client.query(queryParams)
141+
// type hint here will provide a union of all known shapes instead of a specific one
142+
// inferred shapes: Data[] | ResponseJSON<Data> | Record<string, Data>
143+
return resultSet.json<Data>()
144+
}
145+
```
146+
147+
In this case, as it is _likely_ that you already know the desired format in advance (otherwise, returning a specific shape like `Record<string, Data>` would've been incorrect), consider helping the client a bit:
148+
149+
```ts
150+
async function runQuery(
151+
queryParams: QueryParams,
152+
): Promise<Record<string, Data>> {
153+
const resultSet = await client.query({
154+
...queryParams,
155+
format: 'JSONObjectsEachRow',
156+
})
157+
// TS understands that it is a Record<string, Data> now
158+
return resultSet.json<Data>()
159+
}
160+
```
161+
162+
If you are interested in more details, see the [related test](./packages/client-node/__tests__/integration/node_query_format_types.test.ts) (featuring a great ESLint plugin [expect-types](https://github.com/JoshuaKGoldberg/eslint-plugin-expect-type)) in the client package.
163+
164+
### URL configuration
165+
166+
- Added `url` configuration parameter. It is intended to replace the deprecated `host`, which was already supposed to be passed as a valid URL.
167+
- It is now possible to configure most of the client instance parameters with a URL. The URL format is `http[s]://[username:password@]hostname:port[/database][?param1=value1&param2=value2]`. In almost every case, the name of a particular parameter reflects its path in the config options interface, with a few exceptions. The following parameters are supported:
168+
169+
| Parameter | Type |
170+
| ------------------------------------------- | ----------------------------------------------------------------- |
171+
| `pathname` | an arbitrary string. |
172+
| `application_id` | an arbitrary string. |
173+
| `session_id` | an arbitrary string. |
174+
| `request_timeout` | non-negative number. |
175+
| `max_open_connections` | non-negative number, greater than zero. |
176+
| `compression_request` | boolean. See below [1]. |
177+
| `compression_response` | boolean. |
178+
| `log_level` | allowed values: `OFF`, `TRACE`, `DEBUG`, `INFO`, `WARN`, `ERROR`. |
179+
| `keep_alive_enabled` | boolean. |
180+
| `clickhouse_setting_*` or `ch_*` | see below [2]. |
181+
| `http_header_*` | see below [3]. |
182+
| (Node.js only) `keep_alive_idle_socket_ttl` | non-negative number. |
183+
184+
[1] For booleans, valid values will be `true`/`1` and `false`/`0`.
185+
186+
[2] Any parameter prefixed with `clickhouse_setting_` or `ch_` will have this prefix removed and the rest added to client's `clickhouse_settings`. For example, `?ch_async_insert=1&ch_wait_for_async_insert=1` will be the same as:
187+
188+
```ts
189+
createClient({
190+
clickhouse_settings: {
191+
async_insert: 1,
192+
wait_for_async_insert: 1,
193+
},
194+
})
195+
```
196+
197+
Note: boolean values for `clickhouse_settings` should be passed as `1`/`0` in the URL.
198+
199+
[3] Similar to [2], but for `http_header` configuration. For example, `?http_header_x-clickhouse-auth=foobar` will be an equivalent of:
200+
201+
```ts
202+
createClient({
203+
http_headers: {
204+
'x-clickhouse-auth': 'foobar',
205+
},
206+
})
207+
```
208+
209+
**Important: URL will _always_ overwrite the hardcoded values and a warning will be logged in this case.**
210+
211+
Currently not supported via URL:
212+
213+
- `log.LoggerClass`
214+
- (Node.js only) `tls_ca_cert`, `tls_cert`, `tls_key`.
215+
216+
See also: [URL configuration example](./examples/url_configuration.ts).
217+
218+
### Miscellaneous
219+
220+
- Added `http_headers` configuration parameter as a direct replacement for `additional_headers`. Functionally, it is the same, and the change is purely cosmetic, as we'd like to leave an option to implement TCP connection in the future open.
221+
1222
## 0.3.1 (Common, Node.js, Web)
2223

3224
### Bug fixes
@@ -250,7 +471,7 @@ await client.exec('CREATE TABLE foo (id String) ENGINE Memory')
250471

251472
// correct: stream does not contain any information and just destroyed
252473
const { stream } = await client.exec(
253-
'CREATE TABLE foo (id String) ENGINE Memory'
474+
'CREATE TABLE foo (id String) ENGINE Memory',
254475
)
255476
stream.destroy()
256477

benchmarks/common/handlers.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export function attachExceptionHandlers() {
2+
process.on('uncaughtException', (err) => logAndQuit(err))
3+
process.on('unhandledRejection', (err) => logAndQuit(err))
4+
5+
function logAndQuit(err: unknown) {
6+
console.error(err)
7+
process.exit(1)
8+
}
9+
}

benchmarks/common/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './handlers'

0 commit comments

Comments
 (0)