@cleverbrush/knex-clickhouse 1.1.11 → 3.0.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.
package/README.md CHANGED
@@ -1,3 +1,129 @@
1
- # Knex client for ClickHouse.
1
+ # @cleverbrush/knex-clickhouse
2
+ <!-- coverage-badge-start -->
3
+ ![Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen)
4
+ <!-- coverage-badge-end -->
2
5
 
3
- This is a library to use [knex](https://knexjs.org/) query builder against ClickHouse. It uses `@clickhouse/client` library to connect to ClickHouse.
6
+ A [Knex](https://knexjs.org/) dialect for [ClickHouse](https://clickhouse.com/). Uses the official `@clickhouse/client` library to connect to ClickHouse and lets you build queries with Knex's familiar API.
7
+
8
+ ## Installation
9
+
10
+ ```bash
11
+ npm install @cleverbrush/knex-clickhouse
12
+ ```
13
+
14
+ **Peer dependency:** `knex >= 3.1.0`
15
+
16
+ ## Quick Start
17
+
18
+ ```typescript
19
+ import { getClickhouseConnection } from '@cleverbrush/knex-clickhouse';
20
+
21
+ const db = getClickhouseConnection({
22
+ connection: {
23
+ url: 'http://localhost:8123',
24
+ user: 'default',
25
+ password: '',
26
+ database: 'my_database'
27
+ }
28
+ });
29
+
30
+ // Select
31
+ const [rows] = await db('my_table').select('*').where('id', 1);
32
+
33
+ // Insert
34
+ await db('my_table').insert({ id: 1, name: 'Alice' });
35
+
36
+ // Raw queries
37
+ const [rows] = await db.raw('SELECT * FROM my_table WHERE id = ?', [1]);
38
+ ```
39
+
40
+ ## API
41
+
42
+ ### `getClickhouseConnection(config?, clickHouseSettings?)`
43
+
44
+ Creates and returns a Knex instance configured with the ClickHouse dialect.
45
+
46
+ **Parameters:**
47
+
48
+ | Name | Type | Description |
49
+ | --- | --- | --- |
50
+ | `config` | `Knex.Config & AdditionalClientOptions` | Knex configuration with optional retry settings |
51
+ | `clickHouseSettings` | `ClickHouseSettings` | Optional ClickHouse-specific settings |
52
+
53
+ ### `ClickhouseKnexClient`
54
+
55
+ The Knex client class for ClickHouse. Can be used directly if you need more control over Knex initialization:
56
+
57
+ ```typescript
58
+ import Knex from 'knex';
59
+ import { ClickhouseKnexClient } from '@cleverbrush/knex-clickhouse';
60
+
61
+ const db = Knex({
62
+ client: ClickhouseKnexClient,
63
+ connection: {
64
+ url: 'http://localhost:8123',
65
+ user: 'default',
66
+ password: '',
67
+ database: 'my_database'
68
+ }
69
+ });
70
+ ```
71
+
72
+ ## Query Methods
73
+
74
+ Standard Knex query methods are supported:
75
+
76
+ - `select()` / `first()` / `pluck()` — read queries
77
+ - `insert()` — standard inserts
78
+ - `update()` — update records
79
+ - `del()` — delete records
80
+ - `raw()` — raw SQL queries (auto-detected as SELECT when the query starts with `SELECT` or `WITH`)
81
+
82
+ ### `insertToClickhouse(rows)`
83
+
84
+ An extended method for inserting rows with ClickHouse-specific data types like arrays and tuples:
85
+
86
+ ```typescript
87
+ await db('my_table').insertToClickhouse([
88
+ [1, 'Alice', [10, 20, 30]],
89
+ [2, 'Bob', [40, 50]]
90
+ ]);
91
+ ```
92
+
93
+ ## Retry Configuration
94
+
95
+ By default, queries are retried up to 10 times on `ECONNRESET` errors with exponential backoff. You can customize or disable this:
96
+
97
+ ```typescript
98
+ const db = getClickhouseConnection({
99
+ connection: { /* ... */ },
100
+ retry: {
101
+ maxRetries: 5,
102
+ minDelay: 200,
103
+ delayFactor: 2,
104
+ delayRandomizationPercent: 0.1,
105
+ shouldRetry: (error) => error.code === 'ECONNRESET'
106
+ }
107
+ });
108
+
109
+ // Disable retries
110
+ const db = getClickhouseConnection({
111
+ connection: { /* ... */ },
112
+ retry: null
113
+ });
114
+ ```
115
+
116
+ ## Type Parsing
117
+
118
+ The client automatically parses ClickHouse types to JavaScript types:
119
+
120
+ | ClickHouse Type | JavaScript Type |
121
+ | --- | --- |
122
+ | `Date`, `DateTime`, `DateTime('UTC')` | `Date` |
123
+ | `Nullable(Date)`, `Nullable(DateTime)` | `Date` |
124
+ | `LowCardinality(Date)`, `LowCardinality(DateTime)` | `Date` |
125
+ | `UInt64`, `UInt32` | `number` |
126
+
127
+ ## License
128
+
129
+ BSD-3-Clause
package/dist/index.d.ts CHANGED
@@ -1,7 +1,8 @@
1
- import Knex from 'knex';
2
- import ClickhouseDriver, { ClickHouseClient, type ClickHouseSettings } from '@clickhouse/client';
3
1
  import { retry } from '@cleverbrush/async';
4
- export { type ClickHouseClient };
2
+ import ClickhouseDriver, { type ClickHouseClient, type ClickHouseSettings } from '@clickhouse/client';
3
+ import knex, { type Knex } from 'knex';
4
+ export type { ClickHouseClient };
5
+ declare const KnexClient: typeof Knex.Client;
5
6
  type AdditionalClientOptions = {
6
7
  /**
7
8
  * Options for retrying the query,
@@ -13,10 +14,10 @@ type AdditionalClientOptions = {
13
14
  */
14
15
  retry?: Parameters<typeof retry>[1];
15
16
  };
16
- export declare class ClickhouseKnexClient extends Knex.Client {
17
+ export declare class ClickhouseKnexClient extends KnexClient {
17
18
  #private;
18
19
  _driver(): typeof ClickhouseDriver;
19
- constructor(config?: Knex.Knex.Config<any> & AdditionalClientOptions);
20
+ constructor(config?: Knex.Config<any> & AdditionalClientOptions);
20
21
  _escapeBinding(value: any): any;
21
22
  /**
22
23
  *
@@ -38,4 +39,16 @@ export declare class ClickhouseKnexClient extends Knex.Client {
38
39
  *
39
40
  * @returns {import('knex').Knex<any, unknown[]>}
40
41
  */
41
- export declare const getClickhouseConnection: (config?: Knex.Knex.Config<any> & AdditionalClientOptions, clickHouseSettings?: ClickHouseSettings, preQueryCallback?: (connection: ClickHouseClient) => Promise<void>) => Knex.Knex<any, unknown[]>;
42
+ export declare const getClickhouseConnection: (config?: Knex.Config<any> & AdditionalClientOptions, clickHouseSettings?: ClickHouseSettings,
43
+ /**
44
+ * If defined, this function will be called before each query
45
+ * and awaited if it returns a promise.
46
+ *
47
+ * Could be useful in some cases. For example you want first to check
48
+ * if the server is not in idle state (for example Clickhouse Cloud have idling options)
49
+ * and if it is, you want to wake it up before sending the query.
50
+ *
51
+ * if the function throws an error this error will not be caught and will propagate to the caller.
52
+ * Therefore the query will not be executed.
53
+ */
54
+ preQueryCallback?: (connection: ClickHouseClient) => Promise<void>) => knex.Knex<any, unknown[]>;
package/dist/index.js CHANGED
@@ -1,298 +1,2 @@
1
- import Knex from 'knex';
2
- import ClickhouseDriver from '@clickhouse/client';
3
- import { retry } from '@cleverbrush/async';
4
- import { deepExtend } from '@cleverbrush/deep';
5
- import { makeEscape } from './makeEscape.js';
6
- const parseDate = (value) => new Date(Date.parse(value));
7
- // this function is taken from knex npm package
8
- function arrayString(arr, esc) {
9
- let result = '';
10
- for (let i = 0; i < arr.length; i++) {
11
- if (i > 0)
12
- result += ',';
13
- const val = arr[i];
14
- if (val === null || typeof val === 'undefined') {
15
- result += 'NULL';
16
- }
17
- else if (Array.isArray(val)) {
18
- result += arrayString(val, esc);
19
- }
20
- else if (typeof val === 'number') {
21
- result += val;
22
- }
23
- else {
24
- result += esc(val);
25
- }
26
- }
27
- return `[${result}]`;
28
- }
29
- export class ClickhouseKnexClient extends Knex.Client {
30
- _driver() {
31
- return ClickhouseDriver;
32
- }
33
- #retryOptions;
34
- constructor(config = {}) {
35
- const { retry: retryOptions } = config;
36
- super(config);
37
- this.#retryOptions = retryOptions
38
- ? deepExtend({
39
- maxRetries: 10,
40
- delayFactor: 2,
41
- minDelay: 100,
42
- delayRandomizationPercent: 0.1,
43
- shouldRetry: (error) => error.code === 'ECONNRESET'
44
- }, retryOptions)
45
- : null;
46
- }
47
- #typeParsers = {
48
- Date: parseDate,
49
- DateTime: parseDate,
50
- 'LowCardinality(Date)': parseDate,
51
- 'LowCardinality(DateTime)': parseDate,
52
- 'Nullable(Date)': parseDate,
53
- 'Nullable(DateTime)': parseDate,
54
- "DateTime('UTC')": parseDate,
55
- "Nullable(DateTime('UTC'))": parseDate,
56
- 'LowCardinality(Nullable(Date))': parseDate,
57
- 'LowCardinality(Nullable(DateTime))': parseDate,
58
- UInt64: (value) => Number(value),
59
- UInt32: (value) => Number(value)
60
- };
61
- #selectQueryRegex = /^(\s+)?(select|with)/i;
62
- _escapeBinding(value) {
63
- const escapeBinding = makeEscape({
64
- escapeArray(val, esc) {
65
- return arrayString(val, esc);
66
- },
67
- escapeString(str) {
68
- let escaped = "'";
69
- for (let i = 0; i < str.length; i++) {
70
- const c = str[i];
71
- if (c === "'") {
72
- escaped += c + c;
73
- }
74
- else if (c === '\\') {
75
- escaped += c + c;
76
- }
77
- else {
78
- escaped += c;
79
- }
80
- }
81
- escaped += "'";
82
- return escaped;
83
- },
84
- escapeObject(val, prepareValue, timezone, seen = []) {
85
- if (val && typeof val.toPostgres === 'function') {
86
- seen = seen || [];
87
- if (seen.indexOf(val) !== -1) {
88
- throw new Error(`circular reference detected while preparing "${val}" for query`);
89
- }
90
- seen.push(val);
91
- return prepareValue(val.toPostgres(prepareValue), seen);
92
- }
93
- return JSON.stringify(val);
94
- }
95
- });
96
- const result = escapeBinding(value);
97
- if (typeof result === 'string') {
98
- return result.replace(/\\/g, '\\\\');
99
- }
100
- return result;
101
- }
102
- /**
103
- *
104
- * @param {import('@clickhouse/client').QueryParams} obj
105
- * @returns
106
- */
107
- async _query(connection, obj) {
108
- if (!obj || typeof obj === 'string') {
109
- obj = { sql: obj };
110
- }
111
- else if (!obj.sql) {
112
- throw new Error('The query is empty');
113
- }
114
- let { method } = obj;
115
- if (method === 'raw' && this.#selectQueryRegex.test(obj.sql)) {
116
- method = 'select';
117
- }
118
- const query = Array.isArray(obj.bindings) && obj.bindings.length > 0
119
- ? obj.sql
120
- .split('?')
121
- .map((x, i) => `${x}${obj.bindings.length > i
122
- ? this._escapeBinding(obj.bindings[i])
123
- : ''}`)
124
- .join('')
125
- : obj.sql;
126
- let response;
127
- const queryParams = {
128
- clickhouse_settings: {
129
- exact_rows_before_limit: true
130
- },
131
- query,
132
- format: 'JSONCompact'
133
- };
134
- obj.finalQuery = query;
135
- const { preQueryCallback } = this.config;
136
- if (typeof preQueryCallback === 'function') {
137
- await Promise.resolve(preQueryCallback(connection));
138
- }
139
- switch (method) {
140
- case 'select':
141
- case 'first':
142
- case 'pluck':
143
- {
144
- const p = (await (this.#retryOptions
145
- ? retry(() => connection.query(queryParams), this.#retryOptions)
146
- : connection.query(queryParams)));
147
- response = await p.json();
148
- const { data, meta, ...rest } = response;
149
- obj.response = [data, meta, { ...rest }];
150
- }
151
- break;
152
- default:
153
- response = await (this.#retryOptions
154
- ? retry(() => connection.exec(queryParams), this.#retryOptions)
155
- : connection.exec(queryParams));
156
- }
157
- return obj;
158
- }
159
- /**
160
- *
161
- * @param {import('@clickhouse/client').ResultSet} obj
162
- * @param {*} runner
163
- * @returns
164
- */
165
- processResponse(obj, runner) {
166
- if (obj == null)
167
- return null;
168
- const { response } = obj;
169
- const [rows, fields, meta] = response || [[]];
170
- const parsers = Array.isArray(fields)
171
- ? fields.map((field) => typeof this.#typeParsers[field.type] === 'function'
172
- ? this.#typeParsers[field.type]
173
- : (x) => x)
174
- : fields;
175
- const rowToObj = (row) => fields.reduce((acc, field, index) => ({
176
- ...acc,
177
- [field.name]: parsers[index](row[index])
178
- }), {});
179
- if (obj.output)
180
- return obj.output.call(runner, rows, fields);
181
- let { method } = obj;
182
- if (method === 'raw' && this.#selectQueryRegex.test(obj.sql)) {
183
- method = 'select';
184
- }
185
- switch (method) {
186
- case 'select':
187
- return [rows.map((r) => rowToObj(r)), fields, meta];
188
- case 'first':
189
- return [rowToObj(rows[0]), fields, meta];
190
- case 'pluck': {
191
- const pluckIndex = fields.findIndex((val) => val.name === obj.pluck);
192
- if (pluckIndex === -1)
193
- return rows;
194
- return [rows.map((row) => row[pluckIndex]), obj.pluck, meta];
195
- }
196
- case 'insert':
197
- case 'del':
198
- case 'update':
199
- case 'counter':
200
- return response;
201
- default:
202
- return response;
203
- }
204
- }
205
- /**
206
- * @type {import('@clickhouse/client').ClickHouseClient}
207
- */
208
- #connection;
209
- // Get a raw connection, called by the `pool` whenever a new
210
- // connection needs to be added to the pool.
211
- async acquireRawConnection() {
212
- if (!this.#connection) {
213
- const connection = ClickhouseDriver.createClient({
214
- // @ts-ignore
215
- url: this.config.connection.url,
216
- // @ts-ignore
217
- username: this.config.connection.user,
218
- // @ts-ignore
219
- password: this.config.connection.password,
220
- // @ts-ignore
221
- database: this.config.connection.database,
222
- compression: { request: true, response: true },
223
- // @ts-ignore
224
- clickhouse_settings: this.config.clickHouseSettings
225
- });
226
- // @ts-ignore
227
- connection.connected = true;
228
- this.#connection = connection;
229
- }
230
- return this.#connection;
231
- }
232
- async destroyRawConnection(connection) {
233
- connection.close();
234
- }
235
- }
236
- Object.assign(ClickhouseKnexClient.prototype, {
237
- dialect: 'clikhouse',
238
- driverName: 'clikhouse',
239
- canCancelQuery: true
240
- });
241
- const registerKnexClickhouseExtensions = (knex) => {
242
- Knex.QueryBuilder.extend('insertToClickhouse', function (rows) {
243
- const getTuple = (r, p = ['(', ')']) => p[0] +
244
- r
245
- .map((c) => {
246
- if (c === null) {
247
- return 'NULL';
248
- }
249
- if (typeof c === 'string') {
250
- return knex.raw('?', [c]).toQuery();
251
- }
252
- if (Array.isArray(c)) {
253
- return getTuple(c, ['[', ']']);
254
- }
255
- if (typeof c === 'object') {
256
- const keys = Object.keys(c);
257
- return `[${keys
258
- .map((k) => `(${knex.raw('?', [k]).toQuery()}, ${knex
259
- .raw('?', [c[k]])
260
- .toQuery()})`)
261
- .join(', ')}]`;
262
- }
263
- return c;
264
- })
265
- .join(', ') +
266
- p[1];
267
- const values = rows.map((r) => getTuple(r));
268
- // @ts-ignore
269
- return knex.raw(`INSERT INTO ${this._single.table} VALUES ${values}`);
270
- });
271
- };
272
- /**
273
- *
274
- * @returns {import('knex').Knex<any, unknown[]>}
275
- */
276
- export const getClickhouseConnection = (config = {}, clickHouseSettings,
277
- /**
278
- * If defined, this function will be called before each query
279
- * and awaited if it returns a promise.
280
- *
281
- * Could be useful in some cases. For example you want first to check
282
- * if the server is not in idle state (for example Clickhouse Cloud have idling options)
283
- * and if it is, you want to wake it up before sending the query.
284
- *
285
- * if the function throws an error this error will not be caught and will propagate to the caller.
286
- * Therefore the query will not be executed.
287
- */
288
- preQueryCallback) => {
289
- const connection = Knex({
290
- ...config,
291
- client: ClickhouseKnexClient,
292
- // @ts-ignore
293
- clickHouseSettings,
294
- preQueryCallback
295
- });
296
- registerKnexClickhouseExtensions(connection);
297
- return connection;
298
- };
1
+ import{retry as k}from"@cleverbrush/async";import{deepExtend as q}from"@cleverbrush/deep";import x from"@clickhouse/client";import S from"knex";var m=/[\0\b\t\n\r\x1a"'\\]/g,D={"\0":"\\0","\b":"\\b"," ":"\\t","\n":"\\n","\r":"\\r","":"\\Z",'"':'\\"',"'":"\\'","\\":"\\\\"},A=r=>function e(n,s={}){return r(n,e,s)},E=(r,e,n)=>r&&typeof r.toSQL=="function"?r.toSQL(n):JSON.stringify(r),C=r=>{m.lastIndex=0;let e=0,n="",s=null;for(;s=m.exec(r);)n+=r.slice(e,s.index)+D[s[0]],e=m.lastIndex;return e===0?`'${r}'`:e<r.length?`'${n}${r.slice(e)}'`:`'${n}'`},N=r=>`X${C(r.toString("hex"))}`,w=(r,e,n)=>{let s="";for(let t=0;t<r.length;t++){let a=r[t];Array.isArray(a)?s+=(t===0?"":", ")+"("+w(a,e,n)+")":s+=(t===0?"":", ")+e(a,n)}return s},f=(r,e)=>{let n=r.toString();for(;n.length<e;)n=`0${n}`;return n},O=r=>{if(r==="Z")return 0;let e=r.match(/([+\-\s])(\d\d):?(\d\d)?/);return e?(e[1]==="-"?-1:1)*(parseInt(e[2],10)+(e[3]?parseInt(e[3],10):0)/60)*60:!1},b=(r,e,n={})=>{let s=n.timeZone||"local",t=new Date(r),a,i,c,o,y,u,l;if(s==="local")a=t.getFullYear(),i=t.getMonth()+1,c=t.getDate(),o=t.getHours(),y=t.getMinutes(),u=t.getSeconds(),l=t.getMilliseconds();else{let d=O(s);d!==!1&&d!==0&&t.setTime(t.getTime()+d*6e4),a=t.getUTCFullYear(),i=t.getUTCMonth()+1,c=t.getUTCDate(),o=t.getUTCHours(),y=t.getUTCMinutes(),u=t.getUTCSeconds(),l=t.getUTCMilliseconds()}return`${f(a,4)}-${f(i,2)}-${f(c,2)} ${f(o,2)}:${f(y,2)}:${f(u,2)}.${f(l,3)}`},T=(r={})=>{let e=r.escapeDate||b,n=r.escapeArray||w,s=r.escapeBuffer||N,t=r.escapeString||C,a=r.escapeObject||E,i=r.wrap||A;function c(o,y,u){if(o==null)return"NULL";switch(typeof o){case"boolean":return o?"true":"false";case"number":return o+"";case"object":if(o instanceof Date)o=e(o,y,u);else return Array.isArray(o)?n(o,y,u):Buffer.isBuffer(o)?s(o,y,u):a(o,y,u)}return t(o,y,u)}return i?i(c):c};var p=r=>new Date(Date.parse(r)),U=S.Client;function $(r,e){let n="";for(let s=0;s<r.length;s++){s>0&&(n+=",");let t=r[s];t===null||typeof t>"u"?n+="NULL":Array.isArray(t)?n+=$(t,e):typeof t=="number"?n+=t:n+=e(t)}return`[${n}]`}var g=class extends U{_driver(){return x}#e;constructor(e={}){let{retry:n}=e;super(e),this.#e=n?q({maxRetries:10,delayFactor:2,minDelay:100,delayRandomizationPercent:.1,shouldRetry:s=>s.code==="ECONNRESET"},n):void 0}#n={Date:p,DateTime:p,"LowCardinality(Date)":p,"LowCardinality(DateTime)":p,"Nullable(Date)":p,"Nullable(DateTime)":p,"DateTime('UTC')":p,"Nullable(DateTime('UTC'))":p,"LowCardinality(Nullable(Date))":p,"LowCardinality(Nullable(DateTime))":p,UInt64:e=>Number(e),UInt32:e=>Number(e)};#r=/^(\s+)?(select|with)/i;_escapeBinding(e){let s=T({escapeArray(t,a){return $(t,a)},escapeString(t){let a="'";for(let i=0;i<t.length;i++){let c=t[i];c==="'"||c==="\\"?a+=c+c:a+=c}return a+="'",a},escapeObject(t,a,i,c=[]){if(t&&typeof t.toPostgres=="function"){if(c=c||[],c.indexOf(t)!==-1)throw new Error(`circular reference detected while preparing "${t}" for query`);return c.push(t),a(t.toPostgres(a),c)}return JSON.stringify(t)}})(e);return typeof s=="string"?s.replace(/\\/g,"\\\\"):s}async _query(e,n){if(!n||typeof n=="string")n={sql:n};else if(!n.sql)throw new Error("The query is empty");let{method:s}=n;s==="raw"&&this.#r.test(n.sql)&&(s="select");let t=Array.isArray(n.bindings)&&n.bindings.length>0?n.sql.split("?").map((o,y)=>`${o}${n.bindings.length>y?this._escapeBinding(n.bindings[y]):""}`).join(""):n.sql,a,i={clickhouse_settings:{exact_rows_before_limit:!0},query:t,format:"JSONCompact"};n.finalQuery=t;let{preQueryCallback:c}=this.config;switch(typeof c=="function"&&await Promise.resolve(c(e)),s){case"select":case"first":case"pluck":{a=await(await(this.#e?k(()=>e.query(i),this.#e):e.query(i))).json();let{data:y,meta:u,...l}=a;n.response=[y,u,{...l}]}break;default:a=await(this.#e?k(()=>e.exec(i),this.#e):e.exec(i))}return n}processResponse(e,n){if(e==null)return null;let{response:s}=e,[t,a,i]=s||[[]],c=Array.isArray(a)?a.map(u=>typeof this.#n[u.type]=="function"?this.#n[u.type]:l=>l):a,o=u=>{let l={};return a.forEach((d,h)=>{l[d.name]=c[h](u[h])}),l};if(e.output)return e.output.call(n,t,a);let{method:y}=e;switch(y==="raw"&&this.#r.test(e.sql)&&(y="select"),y){case"select":return[t.map(u=>o(u)),a,i];case"first":return[o(t[0]),a,i];case"pluck":{let u=a.findIndex(l=>l.name===e.pluck);return u===-1?t:[t.map(l=>l[u]),e.pluck,i]}case"insert":case"del":case"update":case"counter":return s;default:return s}}#t;async acquireRawConnection(){if(!this.#t){let e=x.createClient({url:this.config.connection.url,username:this.config.connection.user,password:this.config.connection.password,database:this.config.connection.database,compression:{request:!0,response:!0},clickhouse_settings:this.config.clickHouseSettings});e.connected=!0,this.#t=e}return this.#t}async destroyRawConnection(e){e.close()}};Object.assign(g.prototype,{dialect:"clikhouse",driverName:"clikhouse",canCancelQuery:!0});var L=r=>{r.QueryBuilder.extend("insertToClickhouse",function(e){let n=(t,a=["(",")"])=>a[0]+t.map(i=>i===null?"NULL":typeof i=="string"?r.raw("?",[i]).toQuery():Array.isArray(i)?n(i,["[","]"]):typeof i=="object"?`[${Object.keys(i).map(o=>`(${r.raw("?",[o]).toQuery()}, ${r.raw("?",[i[o]]).toQuery()})`).join(", ")}]`:i).join(", ")+a[1],s=e.map(t=>n(t));return r.raw(`INSERT INTO ${this._single.table} VALUES ${s}`)})},H=(r={},e,n)=>{let s=S({...r,client:g,clickHouseSettings:e,preQueryCallback:n});return L(s),s};export{g as ClickhouseKnexClient,H as getClickhouseConnection};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/makeEscape.ts"],"sourcesContent":["import { retry } from '@cleverbrush/async';\nimport { deepExtend } from '@cleverbrush/deep';\nimport ClickhouseDriver, {\n type ClickHouseClient,\n type ClickHouseSettings\n} from '@clickhouse/client';\nimport knex, { type Knex } from 'knex';\nimport { makeEscape } from './makeEscape.js';\n\nexport type { ClickHouseClient };\n\nconst parseDate = (value: any) => new Date(Date.parse(value));\n\n// Access the Client class from the knex runtime object (CJS compat)\nconst KnexClient: typeof Knex.Client = (knex as any).Client;\n\n// this function is taken from knex npm package\nfunction arrayString(arr: any[], esc: (val: any) => string) {\n let result = '';\n for (let i = 0; i < arr.length; i++) {\n if (i > 0) result += ',';\n const val = arr[i];\n if (val === null || typeof val === 'undefined') {\n result += 'NULL';\n } else if (Array.isArray(val)) {\n result += arrayString(val, esc);\n } else if (typeof val === 'number') {\n result += val;\n } else {\n result += esc(val);\n }\n }\n return `[${result}]`;\n}\n\ntype AdditionalClientOptions = {\n /**\n * Options for retrying the query,\n * by default it will retry 10 times with a delay factor of 2 and a minimum delay of\n * 100ms and delay randomization of 10%.\n * and will retry only if the error code is 'ECONNRESET',\n * you can override this behavior by passing your own options.\n * If this option is set to `null` then the query will not be retried.\n */\n retry?: Parameters<typeof retry>[1];\n};\n\nexport class ClickhouseKnexClient extends KnexClient {\n _driver() {\n return ClickhouseDriver;\n }\n\n #retryOptions: Parameters<typeof retry>[1];\n\n constructor(config: Knex.Config<any> & AdditionalClientOptions = {}) {\n const { retry: retryOptions } = config;\n\n super(config);\n\n this.#retryOptions = retryOptions\n ? deepExtend(\n {\n maxRetries: 10,\n delayFactor: 2,\n minDelay: 100,\n delayRandomizationPercent: 0.1,\n shouldRetry: (error: any) => error.code === 'ECONNRESET'\n },\n retryOptions\n )\n : undefined;\n }\n\n #typeParsers = {\n Date: parseDate,\n DateTime: parseDate,\n 'LowCardinality(Date)': parseDate,\n 'LowCardinality(DateTime)': parseDate,\n 'Nullable(Date)': parseDate,\n 'Nullable(DateTime)': parseDate,\n \"DateTime('UTC')\": parseDate,\n \"Nullable(DateTime('UTC'))\": parseDate,\n 'LowCardinality(Nullable(Date))': parseDate,\n 'LowCardinality(Nullable(DateTime))': parseDate,\n UInt64: (value: any) => Number(value),\n UInt32: (value: any) => Number(value)\n };\n\n #selectQueryRegex = /^(\\s+)?(select|with)/i;\n\n _escapeBinding(value: any) {\n const escapeBinding = makeEscape({\n escapeArray(val: any, esc: any) {\n return arrayString(val, esc);\n },\n escapeString(str: string) {\n let escaped = \"'\";\n for (let i = 0; i < str.length; i++) {\n const c = str[i];\n if (c === \"'\") {\n escaped += c + c;\n } else if (c === '\\\\') {\n escaped += c + c;\n } else {\n escaped += c;\n }\n }\n escaped += \"'\";\n return escaped;\n },\n escapeObject(\n val: any,\n prepareValue: any,\n _timezone: any,\n seen: any[] = []\n ) {\n if (val && typeof val.toPostgres === 'function') {\n seen = seen || [];\n if (seen.indexOf(val) !== -1) {\n throw new Error(\n `circular reference detected while preparing \"${val}\" for query`\n );\n }\n seen.push(val);\n return prepareValue(val.toPostgres(prepareValue), seen);\n }\n return JSON.stringify(val);\n }\n });\n\n const result = escapeBinding(value);\n\n if (typeof result === 'string') {\n return result.replace(/\\\\/g, '\\\\\\\\');\n }\n\n return result;\n }\n\n /**\n *\n * @param {import('@clickhouse/client').QueryParams} obj\n * @returns\n */\n async _query(connection: ClickHouseClient, obj: any) {\n if (!obj || typeof obj === 'string') {\n obj = { sql: obj };\n } else if (!obj.sql) {\n throw new Error('The query is empty');\n }\n\n let { method } = obj;\n\n if (method === 'raw' && this.#selectQueryRegex.test(obj.sql)) {\n method = 'select';\n }\n\n const query =\n Array.isArray(obj.bindings) && obj.bindings.length > 0\n ? obj.sql\n .split('?')\n .map(\n (x: any, i: number) =>\n `${x}${\n obj.bindings.length > i\n ? this._escapeBinding(obj.bindings[i])\n : ''\n }`\n )\n .join('')\n : obj.sql;\n\n let response;\n const queryParams = {\n clickhouse_settings: {\n exact_rows_before_limit: true\n },\n query,\n format: 'JSONCompact'\n };\n obj.finalQuery = query;\n\n const { preQueryCallback } = this.config as any;\n\n if (typeof preQueryCallback === 'function') {\n await Promise.resolve(preQueryCallback(connection));\n }\n\n switch (method) {\n case 'select':\n case 'first':\n case 'pluck':\n {\n const p = (await (this.#retryOptions\n ? retry(\n () => connection.query(queryParams as any),\n this.#retryOptions\n )\n : connection.query(queryParams as any))) as any;\n\n response = await p.json();\n\n const { data, meta, ...rest } = response;\n\n obj.response = [data, meta, { ...rest }];\n }\n break;\n default:\n response = await (this.#retryOptions\n ? retry(\n () => connection.exec(queryParams as any),\n this.#retryOptions\n )\n : connection.exec(queryParams as any));\n }\n\n return obj;\n }\n\n /**\n *\n * @param {import('@clickhouse/client').ResultSet} obj\n * @param {*} runner\n * @returns\n */\n processResponse(obj: any, runner: any) {\n if (obj == null) return null;\n const { response } = obj;\n const [rows, fields, meta] = response || [[]];\n\n const parsers = Array.isArray(fields)\n ? fields.map((field: any) =>\n typeof (this.#typeParsers as Record<string, (v: any) => any>)[\n field.type\n ] === 'function'\n ? (this.#typeParsers as Record<string, (v: any) => any>)[\n field.type\n ]\n : (x: any) => x\n )\n : fields;\n\n const rowToObj = (row: any) => {\n const result: Record<string, unknown> = {};\n\n fields.forEach((field: any, index: number) => {\n result[field.name] = parsers[index](row[index]);\n });\n\n return result;\n };\n\n if (obj.output) return obj.output.call(runner, rows, fields);\n\n let { method } = obj;\n\n if (method === 'raw' && this.#selectQueryRegex.test(obj.sql)) {\n method = 'select';\n }\n\n switch (method) {\n case 'select':\n return [rows.map((r: any) => rowToObj(r)), fields, meta];\n case 'first':\n return [rowToObj(rows[0]), fields, meta];\n case 'pluck': {\n const pluckIndex = fields.findIndex(\n (val: any) => val.name === obj.pluck\n );\n if (pluckIndex === -1) return rows;\n return [\n rows.map((row: any) => row[pluckIndex]),\n obj.pluck,\n meta\n ];\n }\n case 'insert':\n case 'del':\n case 'update':\n case 'counter':\n return response;\n default:\n return response;\n }\n }\n\n /**\n * @type {import('@clickhouse/client').ClickHouseClient}\n */\n #connection: any;\n\n // Get a raw connection, called by the `pool` whenever a new\n // connection needs to be added to the pool.\n async acquireRawConnection() {\n if (!this.#connection) {\n const connection = ClickhouseDriver.createClient({\n url: (this.config as any).connection.url,\n username: (this.config as any).connection.user,\n password: (this.config as any).connection.password,\n database: (this.config as any).connection.database,\n compression: { request: true, response: true },\n clickhouse_settings: (this.config as any).clickHouseSettings\n });\n\n // @ts-expect-error\n connection.connected = true;\n\n this.#connection = connection;\n }\n\n return this.#connection;\n }\n\n async destroyRawConnection(connection: any) {\n connection.close();\n }\n}\n\nObject.assign(ClickhouseKnexClient.prototype, {\n dialect: 'clikhouse',\n driverName: 'clikhouse',\n canCancelQuery: true\n});\n\nconst registerKnexClickhouseExtensions = (knex: any) => {\n (knex as any).QueryBuilder.extend(\n 'insertToClickhouse',\n function (this: any, rows: any) {\n const getTuple = (r: any, p = ['(', ')']) =>\n p[0] +\n r\n .map((c: any) => {\n if (c === null) {\n return 'NULL';\n }\n if (typeof c === 'string') {\n return knex.raw('?', [c]).toQuery();\n }\n\n if (Array.isArray(c)) {\n return getTuple(c, ['[', ']']);\n }\n\n if (typeof c === 'object') {\n const keys = Object.keys(c);\n return `[${keys\n .map(\n k =>\n `(${knex.raw('?', [k]).toQuery()}, ${knex\n .raw('?', [c[k]])\n .toQuery()})`\n )\n .join(', ')}]`;\n }\n\n return c;\n })\n .join(', ') +\n p[1];\n const values = rows.map((r: any) => getTuple(r));\n return knex.raw(\n `INSERT INTO ${(this as any)._single.table} VALUES ${values}`\n );\n }\n );\n};\n\n/**\n *\n * @returns {import('knex').Knex<any, unknown[]>}\n */\nexport const getClickhouseConnection = (\n config: Knex.Config<any> & AdditionalClientOptions = {},\n clickHouseSettings?: ClickHouseSettings,\n /**\n * If defined, this function will be called before each query\n * and awaited if it returns a promise.\n *\n * Could be useful in some cases. For example you want first to check\n * if the server is not in idle state (for example Clickhouse Cloud have idling options)\n * and if it is, you want to wake it up before sending the query.\n *\n * if the function throws an error this error will not be caught and will propagate to the caller.\n * Therefore the query will not be executed.\n */\n preQueryCallback?: (connection: ClickHouseClient) => Promise<void>\n) => {\n const connection = knex({\n ...config,\n client: ClickhouseKnexClient as any,\n clickHouseSettings,\n preQueryCallback\n } as any);\n\n registerKnexClickhouseExtensions(connection);\n\n return connection;\n};\n","/*\n * NOTE: functions below are taken from knex npm package,\n * unfortunately they are not exported, so we have to copy them here\n * because they are useful when it comes to write your own knex dialect\n */\n\n// biome-ignore lint/suspicious/noControlCharactersInRegex: intentional regex for escaping control characters in strings\nconst charsRegex = /[\\0\\b\\t\\n\\r\\x1a\"'\\\\]/g;\nconst charsMap = {\n '\\0': '\\\\0',\n '\\b': '\\\\b',\n '\\t': '\\\\t',\n '\\n': '\\\\n',\n '\\r': '\\\\r',\n '\\x1a': '\\\\Z',\n '\"': '\\\\\"',\n \"'\": \"\\\\'\",\n '\\\\': '\\\\\\\\'\n};\n\nconst wrapEscape = (escapeFn: (val: any, finalEscape: any, ctx: any) => any) =>\n function finalEscape(val: any, ctx: any = {}) {\n return escapeFn(val, finalEscape, ctx);\n };\n\nconst escapeObject = (val: any, _finalEscape: any, ctx: any) => {\n if (val && typeof val.toSQL === 'function') {\n return val.toSQL(ctx);\n }\n return JSON.stringify(val);\n};\n\nconst escapeString = (val: any) => {\n charsRegex.lastIndex = 0;\n let chunkIndex = 0;\n let escapedVal = '';\n let match: RegExpExecArray | null = null;\n\n // biome-ignore lint/suspicious/noAssignInExpressions: intentional assignment in while condition to find matches in the string\n while ((match = charsRegex.exec(val))) {\n escapedVal +=\n val.slice(chunkIndex, match.index) + (charsMap as any)[match[0]];\n chunkIndex = charsRegex.lastIndex;\n }\n\n if (chunkIndex === 0) {\n // Nothing was escaped\n return `'${val}'`;\n }\n\n if (chunkIndex < val.length) {\n return `'${escapedVal}${val.slice(chunkIndex)}'`;\n }\n\n return `'${escapedVal}'`;\n};\n\nconst bufferToString = (buffer: Buffer) =>\n `X${escapeString(buffer.toString('hex'))}`;\n\nconst arrayToList = (array: any[], finalEscape: any, ctx: any) => {\n let sql = '';\n for (let i = 0; i < array.length; i++) {\n const val = array[i];\n if (Array.isArray(val)) {\n sql +=\n (i === 0 ? '' : ', ') +\n '(' +\n arrayToList(val, finalEscape, ctx) +\n ')';\n } else {\n sql += (i === 0 ? '' : ', ') + finalEscape(val, ctx);\n }\n }\n return sql;\n};\n\nconst zeroPad = (number: number, length: number) => {\n let numberStr = number.toString();\n while (numberStr.length < length) {\n numberStr = `0${numberStr}`;\n }\n return numberStr;\n};\n\nconst convertTimezone = (tz: any) => {\n if (tz === 'Z') {\n return 0;\n }\n const m = tz.match(/([+\\-\\s])(\\d\\d):?(\\d\\d)?/);\n if (m) {\n return (\n (m[1] === '-' ? -1 : 1) *\n (parseInt(m[2], 10) + (m[3] ? parseInt(m[3], 10) : 0) / 60) *\n 60\n );\n }\n return false;\n};\n\nconst dateToString = (date: any, _finalEscape: any, ctx: any = {}) => {\n const timeZone = ctx.timeZone || 'local';\n\n const dt = new Date(date);\n let year;\n let month;\n let day;\n let hour;\n let minute;\n let second;\n let millisecond;\n\n if (timeZone === 'local') {\n year = dt.getFullYear();\n month = dt.getMonth() + 1;\n day = dt.getDate();\n hour = dt.getHours();\n minute = dt.getMinutes();\n second = dt.getSeconds();\n millisecond = dt.getMilliseconds();\n } else {\n const tz = convertTimezone(timeZone);\n\n if (tz !== false && tz !== 0) {\n dt.setTime(dt.getTime() + tz * 60000);\n }\n\n year = dt.getUTCFullYear();\n month = dt.getUTCMonth() + 1;\n day = dt.getUTCDate();\n hour = dt.getUTCHours();\n minute = dt.getUTCMinutes();\n second = dt.getUTCSeconds();\n millisecond = dt.getUTCMilliseconds();\n }\n\n // YYYY-MM-DD HH:mm:ss.mmm\n return `${zeroPad(year, 4)}-${zeroPad(month, 2)}-${zeroPad(\n day,\n 2\n )} ${zeroPad(hour, 2)}:${zeroPad(minute, 2)}:${zeroPad(\n second,\n 2\n )}.${zeroPad(millisecond, 3)}`;\n};\n\nexport const makeEscape = (config: any = {}) => {\n const finalEscapeDate = config.escapeDate || dateToString;\n const finalEscapeArray = config.escapeArray || arrayToList;\n const finalEscapeBuffer = config.escapeBuffer || bufferToString;\n const finalEscapeString = config.escapeString || escapeString;\n const finalEscapeObject = config.escapeObject || escapeObject;\n const finalWrap = config.wrap || wrapEscape;\n\n function escapeFn(val: any, finalEscape: any, ctx: any) {\n if (val === undefined || val === null) {\n return 'NULL';\n }\n switch (typeof val) {\n case 'boolean':\n return val ? 'true' : 'false';\n case 'number':\n return val + '';\n case 'object':\n if (val instanceof Date) {\n val = finalEscapeDate(val, finalEscape, ctx);\n } else if (Array.isArray(val)) {\n return finalEscapeArray(val, finalEscape, ctx);\n } else if (Buffer.isBuffer(val)) {\n return finalEscapeBuffer(val, finalEscape, ctx);\n } else {\n return finalEscapeObject(val, finalEscape, ctx);\n }\n }\n return finalEscapeString(val, finalEscape, ctx);\n }\n\n return finalWrap ? finalWrap(escapeFn) : escapeFn;\n};\n"],"mappings":"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","names":["retry","deepExtend","ClickhouseDriver","knex","charsRegex","charsMap","wrapEscape","escapeFn","finalEscape","val","ctx","escapeObject","_finalEscape","escapeString","chunkIndex","escapedVal","match","bufferToString","buffer","arrayToList","array","sql","i","zeroPad","number","length","numberStr","convertTimezone","tz","m","dateToString","date","timeZone","dt","year","month","day","hour","minute","second","millisecond","makeEscape","config","finalEscapeDate","finalEscapeArray","finalEscapeBuffer","finalEscapeString","finalEscapeObject","finalWrap","parseDate","value","KnexClient","knex","arrayString","arr","esc","result","i","val","ClickhouseKnexClient","ClickhouseDriver","#retryOptions","config","retryOptions","deepExtend","error","#typeParsers","#selectQueryRegex","makeEscape","str","escaped","prepareValue","_timezone","seen","connection","obj","method","query","x","response","queryParams","preQueryCallback","retry","data","meta","rest","runner","rows","fields","parsers","field","rowToObj","row","index","r","pluckIndex","#connection","registerKnexClickhouseExtensions","getTuple","p","c","k","values","getClickhouseConnection","clickHouseSettings"]}
package/package.json CHANGED
@@ -5,9 +5,9 @@
5
5
  "email": "andrew_zol@cleverbrush.com"
6
6
  },
7
7
  "dependencies": {
8
- "@cleverbrush/async": "^1.1.11",
9
- "@cleverbrush/deep": "^1.1.11",
10
- "@clickhouse/client": "^1.7.0"
8
+ "@cleverbrush/async": "^3.0.0",
9
+ "@cleverbrush/deep": "^3.0.0",
10
+ "@clickhouse/client": "^1.18.2"
11
11
  },
12
12
  "peerDependencies": {
13
13
  "knex": ">=3.1.0"
@@ -16,7 +16,7 @@
16
16
  "files": [
17
17
  "dist"
18
18
  ],
19
- "homepage": "CHANGE ME",
19
+ "homepage": "https://docs.cleverbrush.com/knex-schema",
20
20
  "keywords": [
21
21
  "knex",
22
22
  "query builder",
@@ -25,6 +25,13 @@
25
25
  ],
26
26
  "license": "BSD 3-Clause",
27
27
  "main": "./dist/index.js",
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.ts",
31
+ "import": "./dist/index.js"
32
+ }
33
+ },
34
+ "sideEffects": false,
28
35
  "name": "@cleverbrush/knex-clickhouse",
29
36
  "readme": "https://github.com/cleverbrush/framework/tree/master/libs/knex-clickhouse#readme",
30
37
  "repository": {
@@ -32,10 +39,11 @@
32
39
  "url": "github:cleverbrush/framework"
33
40
  },
34
41
  "scripts": {
35
- "watch": "tsc --watch",
36
- "build": "tsc"
42
+ "watch": "tsc --build --watch",
43
+ "build": "tsup && rm -f tsconfig.build.tsbuildinfo && tsc --project tsconfig.build.json --emitDeclarationOnly",
44
+ "clean": "rm -rf dist tsconfig.tsbuildinfo"
37
45
  },
38
46
  "type": "module",
39
47
  "types": "./dist/index.d.ts",
40
- "version": "1.1.11"
48
+ "version": "3.0.0"
41
49
  }
@@ -1,158 +0,0 @@
1
- /*
2
- * NOTE: functions below are taken from knex npm package,
3
- * unfortunately they are not exported, so we have to copy them here
4
- * because they are useful when it comes to write your own knex dialect
5
- */
6
- const charsRegex = /[\0\b\t\n\r\x1a"'\\]/g; // eslint-disable-line no-control-regex
7
- const charsMap = {
8
- '\0': '\\0',
9
- '\b': '\\b',
10
- '\t': '\\t',
11
- '\n': '\\n',
12
- '\r': '\\r',
13
- '\x1a': '\\Z',
14
- '"': '\\"',
15
- "'": "\\'",
16
- '\\': '\\\\'
17
- };
18
- const wrapEscape = (escapeFn) => function finalEscape(val, ctx = {}) {
19
- return escapeFn(val, finalEscape, ctx);
20
- };
21
- const escapeObject = (val, finalEscape, ctx) => {
22
- if (val && typeof val.toSQL === 'function') {
23
- return val.toSQL(ctx);
24
- }
25
- return JSON.stringify(val);
26
- };
27
- // eslint-disable-next-line
28
- const escapeString = (val) => {
29
- // eslint-disable-next-line
30
- let chunkIndex = (charsRegex.lastIndex = 0);
31
- let escapedVal = '';
32
- let match;
33
- // eslint-disable-next-line
34
- while ((match = charsRegex.exec(val))) {
35
- escapedVal += val.slice(chunkIndex, match.index) + charsMap[match[0]];
36
- chunkIndex = charsRegex.lastIndex;
37
- }
38
- if (chunkIndex === 0) {
39
- // Nothing was escaped
40
- return `'${val}'`;
41
- }
42
- if (chunkIndex < val.length) {
43
- return `'${escapedVal}${val.slice(chunkIndex)}'`;
44
- }
45
- return `'${escapedVal}'`;
46
- };
47
- const bufferToString = (buffer) => `X${escapeString(buffer.toString('hex'))}`;
48
- const arrayToList = (array, finalEscape, ctx) => {
49
- let sql = '';
50
- for (let i = 0; i < array.length; i++) {
51
- const val = array[i];
52
- if (Array.isArray(val)) {
53
- sql +=
54
- // eslint-disable-next-line
55
- (i === 0 ? '' : ', ') +
56
- '(' +
57
- arrayToList(val, finalEscape, ctx) +
58
- ')';
59
- }
60
- else {
61
- sql += (i === 0 ? '' : ', ') + finalEscape(val, ctx);
62
- }
63
- }
64
- return sql;
65
- };
66
- const zeroPad = (number, length) => {
67
- number = number.toString();
68
- while (number.length < length) {
69
- number = `0${number}`;
70
- }
71
- return number;
72
- };
73
- const convertTimezone = (tz) => {
74
- if (tz === 'Z') {
75
- return 0;
76
- }
77
- const m = tz.match(/([+\-\s])(\d\d):?(\d\d)?/);
78
- if (m) {
79
- return (
80
- // eslint-disable-next-line
81
- (m[1] == '-' ? -1 : 1) *
82
- (parseInt(m[2], 10) + (m[3] ? parseInt(m[3], 10) : 0) / 60) *
83
- 60);
84
- }
85
- return false;
86
- };
87
- const dateToString = (date, finalEscape, ctx = {}) => {
88
- // @ts-ignore
89
- const timeZone = ctx.timeZone || 'local';
90
- const dt = new Date(date);
91
- let year;
92
- let month;
93
- let day;
94
- let hour;
95
- let minute;
96
- let second;
97
- let millisecond;
98
- if (timeZone === 'local') {
99
- year = dt.getFullYear();
100
- month = dt.getMonth() + 1;
101
- day = dt.getDate();
102
- hour = dt.getHours();
103
- minute = dt.getMinutes();
104
- second = dt.getSeconds();
105
- millisecond = dt.getMilliseconds();
106
- }
107
- else {
108
- const tz = convertTimezone(timeZone);
109
- if (tz !== false && tz !== 0) {
110
- dt.setTime(dt.getTime() + tz * 60000);
111
- }
112
- year = dt.getUTCFullYear();
113
- month = dt.getUTCMonth() + 1;
114
- day = dt.getUTCDate();
115
- hour = dt.getUTCHours();
116
- minute = dt.getUTCMinutes();
117
- second = dt.getUTCSeconds();
118
- millisecond = dt.getUTCMilliseconds();
119
- }
120
- // YYYY-MM-DD HH:mm:ss.mmm
121
- return `${zeroPad(year, 4)}-${zeroPad(month, 2)}-${zeroPad(day, 2)} ${zeroPad(hour, 2)}:${zeroPad(minute, 2)}:${zeroPad(second, 2)}.${zeroPad(millisecond, 3)}`;
122
- };
123
- export const makeEscape = (config = {}) => {
124
- const finalEscapeDate = config.escapeDate || dateToString;
125
- const finalEscapeArray = config.escapeArray || arrayToList;
126
- const finalEscapeBuffer = config.escapeBuffer || bufferToString;
127
- const finalEscapeString = config.escapeString || escapeString;
128
- const finalEscapeObject = config.escapeObject || escapeObject;
129
- const finalWrap = config.wrap || wrapEscape;
130
- function escapeFn(val, finalEscape, ctx) {
131
- if (val === undefined || val === null) {
132
- return 'NULL';
133
- }
134
- // eslint-disable-next-line
135
- switch (typeof val) {
136
- case 'boolean':
137
- return val ? 'true' : 'false';
138
- case 'number':
139
- // eslint-disable-next-line
140
- return val + '';
141
- case 'object':
142
- if (val instanceof Date) {
143
- val = finalEscapeDate(val, finalEscape, ctx);
144
- }
145
- else if (Array.isArray(val)) {
146
- return finalEscapeArray(val, finalEscape, ctx);
147
- }
148
- else if (Buffer.isBuffer(val)) {
149
- return finalEscapeBuffer(val, finalEscape, ctx);
150
- }
151
- else {
152
- return finalEscapeObject(val, finalEscape, ctx);
153
- }
154
- }
155
- return finalEscapeString(val, finalEscape, ctx);
156
- }
157
- return finalWrap ? finalWrap(escapeFn) : escapeFn;
158
- };