@housekit/orm 0.1.47 → 0.1.48

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 (79) hide show
  1. package/README.md +34 -0
  2. package/dist/builders/delete.js +112 -0
  3. package/dist/builders/insert.d.ts +0 -91
  4. package/dist/builders/insert.js +393 -0
  5. package/dist/builders/prepared.d.ts +1 -2
  6. package/dist/builders/prepared.js +30 -0
  7. package/dist/builders/select.d.ts +0 -161
  8. package/dist/builders/select.js +562 -0
  9. package/dist/builders/select.types.js +1 -0
  10. package/dist/builders/update.js +136 -0
  11. package/dist/client.d.ts +0 -6
  12. package/dist/client.js +140 -0
  13. package/dist/codegen/zod.js +107 -0
  14. package/dist/column.d.ts +1 -25
  15. package/dist/column.js +133 -0
  16. package/dist/compiler.d.ts +0 -7
  17. package/dist/compiler.js +513 -0
  18. package/dist/core.js +6 -0
  19. package/dist/data-types.d.ts +0 -61
  20. package/dist/data-types.js +127 -0
  21. package/dist/dictionary.d.ts +0 -149
  22. package/dist/dictionary.js +158 -0
  23. package/dist/engines.d.ts +0 -385
  24. package/dist/engines.js +292 -0
  25. package/dist/expressions.d.ts +0 -10
  26. package/dist/expressions.js +268 -0
  27. package/dist/external.d.ts +0 -112
  28. package/dist/external.js +224 -0
  29. package/dist/index.d.ts +0 -51
  30. package/dist/index.js +139 -6853
  31. package/dist/logger.js +36 -0
  32. package/dist/materialized-views.d.ts +0 -188
  33. package/dist/materialized-views.js +380 -0
  34. package/dist/metadata.js +59 -0
  35. package/dist/modules/aggregates.d.ts +0 -164
  36. package/dist/modules/aggregates.js +121 -0
  37. package/dist/modules/array.d.ts +0 -98
  38. package/dist/modules/array.js +71 -0
  39. package/dist/modules/conditional.d.ts +0 -84
  40. package/dist/modules/conditional.js +138 -0
  41. package/dist/modules/conversion.d.ts +0 -147
  42. package/dist/modules/conversion.js +109 -0
  43. package/dist/modules/geo.d.ts +0 -164
  44. package/dist/modules/geo.js +112 -0
  45. package/dist/modules/hash.js +4 -0
  46. package/dist/modules/index.js +12 -0
  47. package/dist/modules/json.d.ts +0 -106
  48. package/dist/modules/json.js +76 -0
  49. package/dist/modules/math.d.ts +0 -16
  50. package/dist/modules/math.js +16 -0
  51. package/dist/modules/string.d.ts +0 -136
  52. package/dist/modules/string.js +89 -0
  53. package/dist/modules/time.d.ts +0 -123
  54. package/dist/modules/time.js +91 -0
  55. package/dist/modules/types.d.ts +0 -133
  56. package/dist/modules/types.js +114 -0
  57. package/dist/modules/window.js +140 -0
  58. package/dist/relational.d.ts +0 -82
  59. package/dist/relational.js +290 -0
  60. package/dist/relations.js +21 -0
  61. package/dist/schema-builder.d.ts +0 -90
  62. package/dist/schema-builder.js +140 -0
  63. package/dist/table.d.ts +0 -42
  64. package/dist/table.js +406 -0
  65. package/dist/utils/background-batcher.js +75 -0
  66. package/dist/utils/batch-transform.js +51 -0
  67. package/dist/utils/binary-reader.d.ts +0 -6
  68. package/dist/utils/binary-reader.js +334 -0
  69. package/dist/utils/binary-serializer.d.ts +0 -125
  70. package/dist/utils/binary-serializer.js +637 -0
  71. package/dist/utils/binary-worker-code.js +1 -0
  72. package/dist/utils/binary-worker-pool.d.ts +0 -34
  73. package/dist/utils/binary-worker-pool.js +206 -0
  74. package/dist/utils/binary-worker.d.ts +0 -11
  75. package/dist/utils/binary-worker.js +63 -0
  76. package/dist/utils/insert-processing.d.ts +0 -2
  77. package/dist/utils/insert-processing.js +163 -0
  78. package/dist/utils/lru-cache.js +30 -0
  79. package/package.json +68 -3
package/README.md CHANGED
@@ -326,6 +326,40 @@ await db.select()
326
326
 
327
327
  ---
328
328
 
329
+ ## 📦 Bundle Size & Performance
330
+
331
+ HouseKit is optimized for minimal bundle impact in your applications:
332
+
333
+ | Metric | Value |
334
+ |--------|-------|
335
+ | **Tarball Size** | 96KB |
336
+ | **Unpacked Size** | 644KB |
337
+ | **Tree Shaking** | ✅ Enabled |
338
+ | **Granular Exports** | 17 paths for precise imports |
339
+
340
+ ### Optimizations
341
+
342
+ - **Modular Build**: 46 separate JS files vs 1 monolithic bundle
343
+ - **Tree-Shakable**: Consumers can eliminate unused code automatically
344
+ - **Granular Exports**: Import only what you need
345
+ - **No Runtime Overhead**: Zero runtime dependency overhead
346
+
347
+ ### Import Examples
348
+
349
+ ```typescript
350
+ // Import everything (full bundle)
351
+ import { housekit, Engine, t } from '@housekit/orm';
352
+
353
+ // Import specific modules only (recommended for tree-shaking)
354
+ import { Engine } from '@housekit/orm/engines';
355
+ import { defineTable, t } from '@housekit/orm/schema-builder';
356
+ import { ClickHouseColumn } from '@housekit/orm/column';
357
+ ```
358
+
359
+ **Note**: While HouseKit includes advanced features like binary serialization, engines, and relations (96KB), the modular structure ensures your bundle only includes what you actually use.
360
+
361
+ ---
362
+
329
363
  ## 🛠 SQL Utilities
330
364
 
331
365
  ### Dynamic Queries
@@ -0,0 +1,112 @@
1
+ import { eq } from '../expressions';
2
+ import { and } from '../modules/conditional';
3
+ export class ClickHouseDeleteBuilder {
4
+ client;
5
+ table;
6
+ _where = null;
7
+ _lastMutationId = null;
8
+ constructor(client, table) {
9
+ this.client = client;
10
+ this.table = table;
11
+ }
12
+ where(expression) {
13
+ if (!expression)
14
+ return this;
15
+ if (typeof expression === 'object' && 'toSQL' in expression) {
16
+ this._where = expression;
17
+ return this;
18
+ }
19
+ if (typeof expression === 'object') {
20
+ const chunks = [];
21
+ for (const [key, value] of Object.entries(expression)) {
22
+ const column = this.table.$columns[key];
23
+ if (column && value !== undefined) {
24
+ chunks.push(eq(column, value));
25
+ }
26
+ }
27
+ if (chunks.length > 0) {
28
+ const combined = chunks.length === 1 ? chunks[0] : and(...chunks);
29
+ if (combined)
30
+ this._where = combined;
31
+ }
32
+ }
33
+ return this;
34
+ }
35
+ toSQL() {
36
+ if (this.table.$options.appendOnly !== false) {
37
+ throw new Error(`DELETE is blocked for append-only table ${this.table.$table}. Set appendOnly: false to allow.`);
38
+ }
39
+ if (!this._where) {
40
+ throw new Error("❌ DELETE requires a WHERE clause in ClickHouse (safety first!)");
41
+ }
42
+ const { sql, params } = this._where.toSQL({ ignoreTablePrefix: true });
43
+ const tableName = this.table.$table;
44
+ return {
45
+ query: `ALTER TABLE \`${tableName}\` DELETE WHERE ${sql}`,
46
+ params
47
+ };
48
+ }
49
+ async execute() {
50
+ const { query, params } = this.toSQL();
51
+ await this.client.command({
52
+ query,
53
+ query_params: params,
54
+ });
55
+ this._lastMutationId = await fetchLatestMutationId(this.client, this.table.$table);
56
+ }
57
+ async wait(options) {
58
+ if (!this._lastMutationId) {
59
+ await this.execute();
60
+ }
61
+ if (!this._lastMutationId)
62
+ return;
63
+ await waitForMutationCompletion(this.client, this.table.$table, this._lastMutationId, options);
64
+ }
65
+ async then(onfulfilled, onrejected) {
66
+ try {
67
+ await this.execute();
68
+ if (onfulfilled) {
69
+ return Promise.resolve(onfulfilled());
70
+ }
71
+ return Promise.resolve();
72
+ }
73
+ catch (error) {
74
+ if (onrejected) {
75
+ return Promise.resolve(onrejected(error));
76
+ }
77
+ return Promise.reject(error);
78
+ }
79
+ }
80
+ }
81
+ async function fetchLatestMutationId(client, tableName) {
82
+ const result = await client.query({
83
+ query: `SELECT mutation_id FROM system.mutations WHERE database = currentDatabase() AND table = {table:String} ORDER BY create_time DESC LIMIT 1 FORMAT JSONEachRow`,
84
+ query_params: { table: tableName }
85
+ });
86
+ const rows = await result.json();
87
+ return rows[0]?.mutation_id ?? null;
88
+ }
89
+ async function waitForMutationCompletion(client, tableName, mutationId, options) {
90
+ const pollInterval = options?.pollIntervalMs ?? 500;
91
+ const timeout = options?.timeoutMs ?? 60_000;
92
+ const start = Date.now();
93
+ while (true) {
94
+ const result = await client.query({
95
+ query: `SELECT is_done, latest_failed_part, latest_fail_reason FROM system.mutations WHERE database = currentDatabase() AND table = {table:String} AND mutation_id = {mid:String} FORMAT JSONEachRow`,
96
+ query_params: { table: tableName, mid: mutationId }
97
+ });
98
+ const rows = await result.json();
99
+ const row = rows[0];
100
+ if (!row)
101
+ return;
102
+ if (row.latest_fail_reason) {
103
+ throw new Error(`Mutation ${mutationId} failed: ${row.latest_fail_reason}`);
104
+ }
105
+ if (row.is_done === 1)
106
+ return;
107
+ if (Date.now() - start > timeout) {
108
+ throw new Error(`Mutation ${mutationId} not completed after ${timeout}ms`);
109
+ }
110
+ await new Promise(res => setTimeout(res, pollInterval));
111
+ }
112
+ }
@@ -8,7 +8,6 @@ export interface BinaryInsertConfig {
8
8
  username: string;
9
9
  password: string;
10
10
  database: string;
11
- /** Skip validation for maximum performance */
12
11
  skipValidation?: boolean;
13
12
  }
14
13
  export declare class ClickHouseInsertBuilder<TTable extends TableRuntime<any, any>, TReturn = any> {
@@ -26,116 +25,26 @@ export declare class ClickHouseInsertBuilder<TTable extends TableRuntime<any, an
26
25
  private _isSingle;
27
26
  private _skipValidation;
28
27
  constructor(client: ClickHouseClient, table: TTable, connectionConfig?: BinaryInsertConfig | undefined);
29
- /**
30
- * Skip enum validation for maximum performance.
31
- * Use in production when you trust your data source.
32
- */
33
28
  skipValidation(): this;
34
29
  values(value: CleanInsert<TTable> | Array<CleanInsert<TTable>> | Iterable<CleanInsert<TTable>> | AsyncIterable<CleanInsert<TTable>> | Readable): ClickHouseInsertBuilder<TTable, TReturn>;
35
- /** @template [T = CleanInsert<TTable>] */
36
30
  insert(data: CleanInsert<TTable> | CleanInsert<TTable>[]): Promise<TReturn>;
37
- /**
38
- * Return inserted data as an array.
39
- * Use when inserting multiple rows.
40
- */
41
31
  returning(): ClickHouseInsertBuilder<TTable, CleanSelect<TTable>[]>;
42
- /**
43
- * Return the single inserted row directly (not wrapped in array).
44
- * Use when inserting a single value for cleaner syntax.
45
- *
46
- * @example
47
- * const user = await db.insert(users).values({ email: 'a@b.com' }).returningOne();
48
- */
49
32
  returningOne(): ClickHouseInsertBuilder<TTable, CleanSelect<TTable>>;
50
- /**
51
- * Disable the default returning() behavior.
52
- * Useful when you don't need the inserted data back and want to avoid the overhead.
53
- */
54
33
  noReturning(): ClickHouseInsertBuilder<TTable, void>;
55
- /**
56
- * Force synchronous insert (disables async_insert).
57
- * This is already the default in HouseKit for best performance.
58
- */
59
34
  syncInsert(): this;
60
- /**
61
- * Enable asynchronous inserts on the server (not the default).
62
- * ClickHouse will batch multiple small inserts into a single disk operation.
63
- *
64
- * Note: Sync insert is faster for most use cases. Use async only when
65
- * you need server-side batching for very high-frequency writes.
66
- */
67
35
  asyncInsert(waitForCompletion?: boolean): this;
68
- /**
69
- * Activate Background Batching (Client-side buffering).
70
- *
71
- * Instead of sending request immediately, rows are buffered in memory
72
- * and sent when limit is reached or interval passes.
73
- *
74
- * @param options Batch configuration
75
- */
76
36
  batch(options?: Partial<BatchConfig>): this;
77
- /** Configure batch processing options */
78
37
  batchOptions(options: BatchTransformOptions): this;
79
- /**
80
- * Set the batch size for streaming inserts.
81
- * Larger batches = better throughput, higher memory usage.
82
- * Default: 1000
83
- */
84
38
  batchSize(size: number): this;
85
- /**
86
- * Add a row to the background batcher.
87
- *
88
- * If batching is not yet configured, it will use default settings
89
- * (10,000 rows or 5 seconds).
90
- *
91
- * Note: This method is "fire-and-forget" and does not wait for
92
- * the database to acknowledge the insert.
93
- */
94
39
  append(row: CleanInsert<TTable>): Promise<void>;
95
- /**
96
- * Use JSON format explicitly (this is already the default).
97
- *
98
- * @example
99
- * ```typescript
100
- * await db.insert(events)
101
- * .values(rows)
102
- * .useJsonFormat()
103
- * .execute();
104
- * ```
105
- */
106
40
  useJsonFormat(): this;
107
- /**
108
- * Use JSONCompactEachRow format (smaller payload than JSON).
109
- */
110
41
  useCompactFormat(): this;
111
- /**
112
- * Force RowBinary format.
113
- * Uses direct HTTP request (bypasses official client).
114
- */
115
42
  useBinaryFormat(): this;
116
- /**
117
- * Alias for useBinaryFormat().
118
- * @deprecated Binary format is not faster than JSON with the official client.
119
- */
120
43
  turbo(): this;
121
44
  execute(): Promise<TReturn>;
122
- /**
123
- * Resolve the actual format to use based on settings.
124
- * Default is JSON (most reliable and well-optimized by the official client).
125
- */
126
45
  private resolveFormat;
127
- /**
128
- * Execute insert using JSON format
129
- */
130
46
  private executeJsonInsert;
131
- /**
132
- * Execute insert using RowBinary format (fastest)
133
- * Uses direct HTTP request since the official client doesn't support RowBinary yet
134
- */
135
47
  private executeBinaryInsert;
136
- /**
137
- * Process rows and yield them with column names mapped and defaults applied
138
- */
139
48
  private processRows;
140
49
  private collectReturningRows;
141
50
  private assertReturningRow;
@@ -0,0 +1,393 @@
1
+ import { buildInsertPlan, processRowWithPlan } from '../utils/insert-processing';
2
+ import { createBatchTransformStream } from '../utils/batch-transform';
3
+ import { SyncBinarySerializer } from '../utils/binary-worker-pool';
4
+ import { Readable } from 'stream';
5
+ import { globalBatcher } from '../utils/background-batcher';
6
+ import http from 'http';
7
+ import https from 'https';
8
+ let uuidv4Fn = null;
9
+ let uuidv7Fn = null;
10
+ let uuidv1 = null;
11
+ let uuidv6 = null;
12
+ const hasNativeUUID = typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function';
13
+ function getUUIDv4() {
14
+ if (hasNativeUUID) {
15
+ return crypto.randomUUID();
16
+ }
17
+ if (uuidv4Fn)
18
+ return uuidv4Fn();
19
+ const uuid = require('uuid');
20
+ uuidv4Fn = uuid.v4;
21
+ return uuidv4Fn();
22
+ }
23
+ function getUUIDv7() {
24
+ if (uuidv7Fn)
25
+ return uuidv7Fn();
26
+ const uuid = require('uuid');
27
+ uuidv7Fn = uuid.v7;
28
+ return uuidv7Fn();
29
+ }
30
+ export class ClickHouseInsertBuilder {
31
+ client;
32
+ table;
33
+ connectionConfig;
34
+ _values = null;
35
+ _async = false;
36
+ _waitForAsync = true;
37
+ _batchOptions = {};
38
+ _format = 'auto';
39
+ _batchConfig = null;
40
+ _forceJson = false;
41
+ _returning = false;
42
+ _isSingle = false;
43
+ _skipValidation = false;
44
+ constructor(client, table, connectionConfig) {
45
+ this.client = client;
46
+ this.table = table;
47
+ this.connectionConfig = connectionConfig;
48
+ if (table.$options?.asyncInsert !== undefined) {
49
+ this._async = table.$options.asyncInsert;
50
+ }
51
+ if (connectionConfig?.skipValidation) {
52
+ this._skipValidation = true;
53
+ }
54
+ }
55
+ skipValidation() {
56
+ this._skipValidation = true;
57
+ return this;
58
+ }
59
+ values(value) {
60
+ this._values = value;
61
+ this._isSingle = !Array.isArray(value) && !isIterable(value) && !isAsyncIterable(value) && !(value instanceof Readable);
62
+ return this;
63
+ }
64
+ async insert(data) {
65
+ return this.values(data).execute();
66
+ }
67
+ returning() {
68
+ this._returning = true;
69
+ return this;
70
+ }
71
+ returningOne() {
72
+ this._returning = true;
73
+ this._isSingle = true;
74
+ return this;
75
+ }
76
+ noReturning() {
77
+ this._returning = false;
78
+ return this;
79
+ }
80
+ syncInsert() {
81
+ this._async = false;
82
+ return this;
83
+ }
84
+ asyncInsert(waitForCompletion = true) {
85
+ this._async = true;
86
+ this._waitForAsync = waitForCompletion;
87
+ return this;
88
+ }
89
+ batch(options = {}) {
90
+ this._batchConfig = {
91
+ maxRows: options.maxRows ?? 10000,
92
+ flushIntervalMs: options.flushIntervalMs ?? 5000,
93
+ };
94
+ return this;
95
+ }
96
+ batchOptions(options) {
97
+ this._batchOptions = options;
98
+ return this;
99
+ }
100
+ batchSize(size) {
101
+ this._batchOptions.batchSize = size;
102
+ return this;
103
+ }
104
+ async append(row) {
105
+ if (!this._batchConfig) {
106
+ this.batch();
107
+ }
108
+ const plan = buildInsertPlan(this.table, { skipValidation: this._skipValidation });
109
+ const batcher = globalBatcher(this.client);
110
+ const oldValues = this._values;
111
+ this._values = [row];
112
+ try {
113
+ const rowIterator = this.processRows(plan);
114
+ for await (const processedRow of rowIterator) {
115
+ batcher.add(this.table, processedRow, this._batchConfig);
116
+ }
117
+ }
118
+ finally {
119
+ this._values = oldValues;
120
+ }
121
+ }
122
+ useJsonFormat() {
123
+ this._format = 'json';
124
+ this._forceJson = true;
125
+ return this;
126
+ }
127
+ useCompactFormat() {
128
+ this._format = 'compact';
129
+ return this;
130
+ }
131
+ useBinaryFormat() {
132
+ this._format = 'binary';
133
+ return this;
134
+ }
135
+ turbo() {
136
+ return this.useBinaryFormat();
137
+ }
138
+ async execute() {
139
+ if (!this._values) {
140
+ throw new Error("❌ No values to insert");
141
+ }
142
+ if (Array.isArray(this._values) && this._values.length === 0) {
143
+ throw new Error("❌ No values to insert");
144
+ }
145
+ const plan = buildInsertPlan(this.table, { skipValidation: this._skipValidation });
146
+ if (this._returning) {
147
+ if (this._batchConfig) {
148
+ throw new Error('❌ returning() cannot be used with background batching');
149
+ }
150
+ const { processedRows, resultRows } = await this.collectReturningRows(plan);
151
+ const stream = Readable.from(processedRows, { objectMode: true });
152
+ await this.client.insert({
153
+ table: this.table.$table,
154
+ values: stream,
155
+ format: 'JSONEachRow',
156
+ clickhouse_settings: {
157
+ async_insert: this._async ? 1 : 0,
158
+ wait_for_async_insert: this._waitForAsync ? 1 : 0,
159
+ },
160
+ });
161
+ if (this._isSingle && resultRows.length > 0) {
162
+ return resultRows[0];
163
+ }
164
+ return resultRows;
165
+ }
166
+ if (this._batchConfig && !this._forceJson) {
167
+ const batcher = globalBatcher(this.client);
168
+ const rowIterator = this.processRows(plan);
169
+ for await (const row of rowIterator) {
170
+ batcher.add(this.table, row, this._batchConfig);
171
+ }
172
+ return undefined;
173
+ }
174
+ const tableName = this.table.$table;
175
+ const format = this.resolveFormat(plan);
176
+ if (format === 'binary') {
177
+ await this.executeBinaryInsert(plan, tableName);
178
+ }
179
+ else {
180
+ await this.executeJsonInsert(plan, tableName, format);
181
+ }
182
+ return undefined;
183
+ }
184
+ resolveFormat(plan) {
185
+ if (this._format !== 'auto') {
186
+ return this._format;
187
+ }
188
+ return plan.useCompact ? 'compact' : 'json';
189
+ }
190
+ async executeJsonInsert(plan, tableName, format) {
191
+ const mode = format === 'compact' ? 'compact' : 'json';
192
+ let stream;
193
+ if (this._values instanceof Readable) {
194
+ stream = this._values;
195
+ }
196
+ else {
197
+ const iterable = (Array.isArray(this._values) || isIterable(this._values) || isAsyncIterable(this._values))
198
+ ? this._values
199
+ : [this._values];
200
+ const sourceStream = Readable.from(iterable, { objectMode: true });
201
+ const batchTransform = createBatchTransformStream(plan, mode, this._batchOptions);
202
+ stream = sourceStream.pipe(batchTransform);
203
+ }
204
+ await this.client.insert({
205
+ table: tableName,
206
+ values: stream,
207
+ format: mode === 'compact' ? 'JSONCompactEachRow' : 'JSONEachRow',
208
+ columns: mode === 'compact' && plan.columnNames.length > 0 ? plan.columnNames : undefined,
209
+ clickhouse_settings: {
210
+ async_insert: this._async ? 1 : 0,
211
+ wait_for_async_insert: this._waitForAsync ? 1 : 0,
212
+ },
213
+ });
214
+ }
215
+ async executeBinaryInsert(plan, tableName) {
216
+ if (!this.connectionConfig) {
217
+ throw new Error('❌ Binary format requires connection configuration. This is an internal error - please report it.');
218
+ }
219
+ const columns = plan.columns.map((col) => ({
220
+ name: col.columnName,
221
+ type: col.column.type,
222
+ isNullable: col.column.isNull,
223
+ propKey: col.propKey,
224
+ }));
225
+ const serializer = new SyncBinarySerializer(columns);
226
+ const allRows = [];
227
+ if (this._values instanceof Readable) {
228
+ for await (const chunk of this._values) {
229
+ allRows.push(chunk);
230
+ }
231
+ }
232
+ else {
233
+ for await (const row of this.processRows(plan, true)) {
234
+ allRows.push(row);
235
+ }
236
+ }
237
+ if (allRows.length === 0) {
238
+ return;
239
+ }
240
+ const binaryData = serializer.serialize(allRows);
241
+ const { url: baseUrl, username, password, database } = this.connectionConfig;
242
+ const url = new URL(baseUrl);
243
+ const queryParams = new URLSearchParams({
244
+ query: `INSERT INTO ${tableName} FORMAT RowBinary`,
245
+ database: database,
246
+ });
247
+ if (this._async) {
248
+ queryParams.set('async_insert', '1');
249
+ queryParams.set('wait_for_async_insert', this._waitForAsync ? '1' : '0');
250
+ }
251
+ url.search = queryParams.toString();
252
+ const isHttps = url.protocol === 'https:';
253
+ const httpModule = isHttps ? https : http;
254
+ const authHeader = Buffer.from(`${username}:${password}`).toString('base64');
255
+ return new Promise((resolve, reject) => {
256
+ const req = httpModule.request(url, {
257
+ method: 'POST',
258
+ headers: {
259
+ 'Content-Type': 'application/octet-stream',
260
+ 'Authorization': `Basic ${authHeader}`,
261
+ },
262
+ }, (res) => {
263
+ let body = '';
264
+ res.on('data', (chunk) => { body += chunk; });
265
+ res.on('end', () => {
266
+ if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
267
+ resolve();
268
+ }
269
+ else {
270
+ reject(new Error(`ClickHouse error (${res.statusCode}): ${body}`));
271
+ }
272
+ });
273
+ });
274
+ req.on('error', reject);
275
+ req.write(binaryData);
276
+ req.end();
277
+ });
278
+ }
279
+ async *processRows(plan, skipDateTransform = false) {
280
+ const values = this._values;
281
+ const iterable = Array.isArray(values) ? values :
282
+ isIterable(values) ? values :
283
+ isAsyncIterable(values) ? values :
284
+ [values];
285
+ for await (const row of iterable) {
286
+ const processedRow = {};
287
+ for (const col of plan.columns) {
288
+ let value = row[col.propKey] !== undefined ? row[col.propKey] : row[col.columnName];
289
+ if (value === undefined) {
290
+ if (col.defaultFn) {
291
+ value = col.defaultFn(row);
292
+ }
293
+ else if (col.autoUUIDVersion !== null && !col.useServerUUID) {
294
+ value = col.autoUUIDVersion === 7 ? getUUIDv7() : getUUIDv4();
295
+ }
296
+ else if (col.hasDefault) {
297
+ value = col.defaultValue;
298
+ }
299
+ }
300
+ if (value !== undefined && !skipDateTransform) {
301
+ value = col.transform(value);
302
+ }
303
+ processedRow[col.columnName] = value;
304
+ }
305
+ yield processedRow;
306
+ }
307
+ }
308
+ async collectReturningRows(plan) {
309
+ const processedRows = [];
310
+ const resultRows = [];
311
+ const values = this._values;
312
+ const iterable = values instanceof Readable ? values :
313
+ Array.isArray(values) ? values :
314
+ isIterable(values) ? values :
315
+ isAsyncIterable(values) ? values :
316
+ [values];
317
+ for await (const row of iterable) {
318
+ const processed = processRowWithPlan(row, plan, 'json');
319
+ for (const col of plan.columns) {
320
+ if (processed[col.columnName] !== undefined)
321
+ continue;
322
+ const expr = col.column.meta?.defaultExpr;
323
+ if (!expr)
324
+ continue;
325
+ const resolved = this.resolveClientDefaultExpr(expr);
326
+ if (resolved !== undefined) {
327
+ processed[col.columnName] = col.transform(resolved);
328
+ }
329
+ }
330
+ this.assertReturningRow(row, processed, plan);
331
+ processedRows.push(processed);
332
+ const resultRow = {};
333
+ for (const col of plan.columns) {
334
+ const value = processed[col.columnName];
335
+ resultRow[col.propKey] = value;
336
+ }
337
+ resultRows.push(resultRow);
338
+ }
339
+ return { processedRows, resultRows };
340
+ }
341
+ assertReturningRow(rawRow, processed, plan) {
342
+ for (const col of plan.columns) {
343
+ const hasValue = rawRow[col.propKey] !== undefined || rawRow[col.columnName] !== undefined;
344
+ if (hasValue)
345
+ continue;
346
+ if (processed[col.columnName] !== undefined) {
347
+ continue;
348
+ }
349
+ if (col.defaultFn || (col.autoUUIDVersion !== null && !col.useServerUUID) || col.hasDefault) {
350
+ continue;
351
+ }
352
+ if (col.useServerUUID || col.column.meta?.defaultExpr) {
353
+ throw new Error(`❌ returning() cannot infer column '${col.columnName}' because it uses a server-side default. Provide a value or remove the default expression.`);
354
+ }
355
+ }
356
+ }
357
+ resolveClientDefaultExpr(expr) {
358
+ const normalized = expr.replace(/\s+/g, '').toLowerCase();
359
+ if (normalized === 'now()' || normalized === 'now64()' || normalized.startsWith('now64(')) {
360
+ return new Date();
361
+ }
362
+ if (normalized === 'generateuuidv4()')
363
+ return getUUIDv4();
364
+ if (normalized === 'generateuuidv7()')
365
+ return getUUIDv7();
366
+ if (normalized === 'generateuuidv1()')
367
+ return uuidv1?.() ?? getUUIDv4();
368
+ if (normalized === 'generateuuidv6()')
369
+ return uuidv6?.() ?? getUUIDv7();
370
+ return undefined;
371
+ }
372
+ async then(onfulfilled, onrejected) {
373
+ try {
374
+ const result = await this.execute();
375
+ if (onfulfilled) {
376
+ return Promise.resolve(onfulfilled(result));
377
+ }
378
+ return Promise.resolve(result);
379
+ }
380
+ catch (error) {
381
+ if (onrejected) {
382
+ return Promise.resolve(onrejected(error));
383
+ }
384
+ return Promise.reject(error);
385
+ }
386
+ }
387
+ }
388
+ function isIterable(obj) {
389
+ return obj && typeof obj[Symbol.iterator] === 'function';
390
+ }
391
+ function isAsyncIterable(obj) {
392
+ return obj && typeof obj[Symbol.asyncIterator] === 'function';
393
+ }
@@ -5,7 +5,6 @@ export declare class PreparedQuery<TResult> {
5
5
  private querySuggestions;
6
6
  private columnNames;
7
7
  private columnTypes;
8
- constructor(client: any, sql: string, paramKeys: string[], // The order of parameters (p_1, p_2...)
9
- querySuggestions: string[], columnNames?: string[], columnTypes?: string[]);
8
+ constructor(client: any, sql: string, paramKeys: string[], querySuggestions: string[], columnNames?: string[], columnTypes?: string[]);
10
9
  execute(values: any[]): Promise<TResult[]>;
11
10
  }