@transitive-sdk/clickhouse 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/index.js +98 -0
  2. package/package.json +30 -0
package/index.js ADDED
@@ -0,0 +1,98 @@
1
+ const { createClient } = require('@clickhouse/client');
2
+
3
+ /** Singleton ClickHouse client wrapper with multi-tenant table support */
4
+ class ClickHouse {
5
+ init({url, dbName, user, password} = {}) {
6
+ const _url = url || process.env.CLICKHOUSE_URL || 'http://localhost:8123';
7
+ const _dbName = dbName || process.env.CLICKHOUSE_DB || 'default';
8
+ const _user = user || process.env.CLICKHOUSE_USER || 'default';
9
+ const _password = password || process.env.CLICKHOUSE_PASSWORD || '';
10
+ console.debug(`Creating ClickHouse client for URL: ${_url}, DB: ${_dbName}, User: ${_user}`);
11
+ this._client = createClient({
12
+ url: _url,
13
+ max_open_connections: 10,
14
+ database: _dbName,
15
+ username: _user,
16
+ password: _password,
17
+ clickhouse_settings: {
18
+ // https://clickhouse.com/docs/en/operations/settings/settings#async-insert
19
+ async_insert: 1,
20
+ // https://clickhouse.com/docs/en/operations/settings/settings#wait-for-async-insert
21
+ wait_for_async_insert: 1,
22
+ // https://clickhouse.com/docs/en/operations/settings/settings#async-insert-max-data-size
23
+ async_insert_max_data_size: '1000000',
24
+ // https://clickhouse.com/docs/en/operations/settings/settings#async-insert-busy-timeout-ms
25
+ async_insert_busy_timeout_ms: 1000,
26
+ // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
27
+ date_time_input_format: 'best_effort',
28
+ },
29
+ });
30
+ }
31
+
32
+ get client() {
33
+ if (this._client == undefined) {
34
+ console.warn('Cannot access ClickHouse client before init() is called');
35
+ throw new Error('ClickHouse client not initialized');
36
+ }
37
+ return this._client;
38
+ }
39
+
40
+ /** Create a table if it does not already exist
41
+ * adding OrgId and DeviceId columns to the schema
42
+ * for multi-tenancy support.
43
+ * @param {string} tableName - name of the table to create
44
+ * @param {Array<string>} columns - array of column definitions and indexes, e.g. ['Timestamp DateTime CODEC(ZSTD(1))', 'Value Float32 CODEC(ZSTD(1))']
45
+ * @param {Array<string>} settings - array of table settings, e.g. ['ENGINE = MergeTree()', 'ORDER BY (Timestamp)']
46
+ */
47
+ async createTable(tableName, columns, settings = []) {
48
+ const fullSchema = [
49
+ ...columns,
50
+ 'OrgId String CODEC(ZSTD(1))',
51
+ 'DeviceId String CODEC(ZSTD(1))',
52
+ 'INDEX idx_orgid (OrgId) TYPE bloom_filter(0.01) GRANULARITY 1',
53
+ 'INDEX idx_deviceid (DeviceId) TYPE bloom_filter(0.01) GRANULARITY 1'
54
+ ];
55
+ const query = `CREATE TABLE IF NOT EXISTS ${tableName} (${fullSchema.join(', ')}) ${settings.join(' ')}`;
56
+
57
+ try {
58
+ return await this.client.exec({
59
+ query,
60
+ clickhouse_settings: {
61
+ wait_end_of_query: 1,
62
+ }
63
+ });
64
+ } catch (error) {
65
+ console.error('Error executing query:', error.message);
66
+ console.debug('Query was:', query);
67
+ throw error;
68
+ }
69
+ }
70
+
71
+ /** Insert rows into a multi-tenant table, adding OrgId and DeviceId to each row
72
+ * @param {string} tableName - name of the table to insert into
73
+ * @param {Array<Object>} rows - array of row objects to insert (JSON each item)
74
+ * @param {string} orgId - organization ID to add to each row
75
+ * @param {string} deviceId - device ID to add to each row
76
+ */
77
+ async insert(tableName, rows, orgId, deviceId) {
78
+ // assert that orgId and deviceId are provided
79
+ if (!orgId || !deviceId) {
80
+ throw new Error('Both orgId and deviceId must be provided for multi-tenant insert');
81
+ }
82
+
83
+ // Augment each row with OrgId and DeviceId
84
+ const rowsWithIds = rows.map(row => ({
85
+ ...row,
86
+ OrgId: orgId,
87
+ DeviceId: deviceId
88
+ }));
89
+ return await this.client.insert({
90
+ table: tableName,
91
+ values: rowsWithIds,
92
+ format: 'JSONEachRow'
93
+ });
94
+ }
95
+ }
96
+
97
+ const instance = new ClickHouse();
98
+ module.exports = instance;
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@transitive-sdk/clickhouse",
3
+ "version": "0.1.0",
4
+ "description": "A tiny ClickHouse utility class for use in the Transitive framework.",
5
+ "homepage": "https://transitiverobotics.com",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/transitiverobotics/transitive-utils.git",
9
+ "directory": "clickhouse"
10
+ },
11
+ "author": {
12
+ "name": "Christian Fritz",
13
+ "email": "christian@transitiverobotics.com"
14
+ },
15
+ "keywords": [
16
+ "robotics"
17
+ ],
18
+ "license": "Apache-2.0",
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "main": "index.js",
23
+ "scripts": {
24
+ },
25
+ "dependencies": {
26
+ "@clickhouse/client": "^1.12.1"
27
+ },
28
+ "devDependencies": {
29
+ }
30
+ }