@housekit/orm 0.1.47 → 0.1.49

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 +120 -5
  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
@@ -2,16 +2,17 @@
2
2
 
3
3
  **The high-performance, type-safe ClickHouse ORM for Node.js and Bun.**
4
4
 
5
- > ⚠️ **Public Beta**: This package is currently in public beta. Feedback is highly appreciated as we polish the API for v1.0.
5
+ > ⚠️ **Public Beta**: This package is currently in public beta. Feedback is highly appreciated as we polish API for v1.0.
6
6
 
7
- > 💡 **Interactive Docs**: Use [RepoGrep](https://app.ami.dev/repogrep?repo=https://github.com/pablofdezr/housekit) to search and query the entire codebase and documentation for free (Updated instantly).
7
+ > 💡 **Interactive Docs**: Use [RepoGrep](https://app.ami.dev/repogrep?repo=https://github.com/pablofdezr/housekit) to search and query entire codebase and documentation for free (Updated instantly).
8
8
 
9
- > 💡 **Ask ZRead**: Need deep insights? [Ask ZRead](https://zread.ai/pablofdezr/housekit) for AI-powered understanding of the codebase (Updated weekly).
9
+ > 💡 **Ask ZRead**: Need deep insights? [Ask ZRead](https://zread.ai/pablofdezr/housekit) for AI-powered understanding of codebase (Updated weekly).
10
10
 
11
- > 💡 **Ask Devin AI**: Have questions about integrating HouseKit? [Ask the Wiki](https://deepwiki.com/pablofdezr/housekit) for AI-powered assistance (Updated weekly).
11
+ > 💡 **Ask Devin AI**: Have questions about integrating HouseKit? [Ask Wiki](https://deepwiki.com/pablofdezr/housekit) for AI-powered assistance (Updated weekly).
12
12
 
13
- HouseKit ORM is a modern database toolkit designed specifically for ClickHouse. It bridges the gap between ergonomic developer experiences and the extreme performance requirements of high-volume OLAP workloads.
13
+ HouseKit ORM is a modern database toolkit designed specifically for ClickHouse. It bridges gap between ergonomic developer experiences and extreme performance requirements of high-volume OLAP workloads.
14
14
 
15
+ [![npm](https://nodei.co/npm/@housekit/orm.png)](https://www.npmjs.com/package/@housekit/orm)
15
16
  [![npm version](https://img.shields.io/npm/v/@housekit/orm.svg)](https://www.npmjs.com/package/@housekit/orm)
16
17
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
17
18
  [![Documentation](https://img.shields.io/badge/Docs-RepoGrep-teal?style=flat-square)](https://app.ami.dev/repogrep?repo=https://github.com/pablofdezr/housekit)
@@ -127,6 +128,86 @@ const [user] = await db
127
128
 
128
129
  ---
129
130
 
131
+ ## 🎯 The `housekit()` Client
132
+
133
+ The `housekit()` function creates a fully-featured ClickHouse client with query builders for all operations.
134
+
135
+ ### Client Methods
136
+
137
+ | Method | Description |
138
+ |--------|-------------|
139
+ | **`db.select()`** | Creates a SELECT query builder |
140
+ | **`db.insert(table)`** | Inserts data into a table |
141
+ | **`db.insertMany(table, data, opts)`** | Bulk inserts with configuration |
142
+ | **`db.update(table)`** | Updates rows in a table |
143
+ | **`db.delete(table)`** | Deletes rows from a table |
144
+ | **`db.raw(sql, params)`** | Executes raw SQL queries |
145
+ | **`db.command({query, query_params})`** | Executes ClickHouse commands |
146
+ | **`db.close()`** | Closes the connection |
147
+
148
+ ### Client Properties
149
+
150
+ | Property | Description |
151
+ |----------|-------------|
152
+ | **`db.rawClient`** | Raw `@clickhouse/client` instance (direct access) |
153
+ | **`db.query`** ⭐ | **Relational API** - only available if `{ schema }` is passed |
154
+ | **`db.schema`** | Your defined table schema |
155
+
156
+ ### ⭐ The Relational API (`db.query`)
157
+
158
+ **Only available when you pass a schema:**
159
+
160
+ ```typescript
161
+ const db = housekit({ url: 'http://localhost:8123' }, {
162
+ schema: { users, events }
163
+ });
164
+ ```
165
+
166
+ Then you can query using ORM-style methods:
167
+
168
+ ```typescript
169
+ // Find by ID
170
+ db.query.users.findById('uuid-here');
171
+
172
+ // Find many with conditions
173
+ db.query.users.findMany({ where: { role: 'admin' } });
174
+
175
+ // Find first with columns
176
+ db.query.users.findFirst({ columns: { id: true, email: true } });
177
+
178
+ // Find with relations (automatic JOIN)
179
+ db.query.users.findMany({
180
+ with: { posts: true }
181
+ });
182
+ ```
183
+
184
+ ### Complete Example
185
+
186
+ ```typescript
187
+ const db = housekit({ url: 'http://localhost:8123' }, { schema });
188
+
189
+ // 1. Insert using builder
190
+ await db.insert(schema.users).values({ email: 'a@b.com', role: 'admin' });
191
+
192
+ // 2. Regular SELECT
193
+ const result = await db.select().from(schema.users).where(eq(schema.users.role, 'admin'));
194
+
195
+ // 3. Relational query (automatic JOIN)
196
+ const user = await db.query.users.findById('uuid-here', {
197
+ with: { posts: true }
198
+ });
199
+
200
+ // 4. Raw SQL
201
+ const data = await db.raw('SELECT * FROM users LIMIT 10');
202
+
203
+ // 5. Close connection
204
+ await db.close();
205
+ ```
206
+
207
+ ---
208
+
209
+ ---
210
+
130
211
  ## 🔍 Relational Query API
131
212
 
132
213
  ### findMany / findFirst
@@ -326,6 +407,40 @@ await db.select()
326
407
 
327
408
  ---
328
409
 
410
+ ## 📦 Bundle Size & Performance
411
+
412
+ HouseKit is optimized for minimal bundle impact in your applications:
413
+
414
+ | Metric | Value |
415
+ |--------|-------|
416
+ | **Tarball Size** | 96KB |
417
+ | **Unpacked Size** | 644KB |
418
+ | **Tree Shaking** | ✅ Enabled |
419
+ | **Granular Exports** | 17 paths for precise imports |
420
+
421
+ ### Optimizations
422
+
423
+ - **Modular Build**: 46 separate JS files vs 1 monolithic bundle
424
+ - **Tree-Shakable**: Consumers can eliminate unused code automatically
425
+ - **Granular Exports**: Import only what you need
426
+ - **No Runtime Overhead**: Zero runtime dependency overhead
427
+
428
+ ### Import Examples
429
+
430
+ ```typescript
431
+ // Import everything (full bundle)
432
+ import { housekit, Engine, t } from '@housekit/orm';
433
+
434
+ // Import specific modules only (recommended for tree-shaking)
435
+ import { Engine } from '@housekit/orm/engines';
436
+ import { defineTable, t } from '@housekit/orm/schema-builder';
437
+ import { ClickHouseColumn } from '@housekit/orm/column';
438
+ ```
439
+
440
+ **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.
441
+
442
+ ---
443
+
329
444
  ## 🛠 SQL Utilities
330
445
 
331
446
  ### 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;