@housekit/orm 0.1.46 → 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.
- package/README.md +47 -6
- package/dist/builders/delete.js +112 -0
- package/dist/builders/insert.d.ts +0 -91
- package/dist/builders/insert.js +393 -0
- package/dist/builders/prepared.d.ts +1 -2
- package/dist/builders/prepared.js +30 -0
- package/dist/builders/select.d.ts +0 -161
- package/dist/builders/select.js +562 -0
- package/dist/builders/select.types.js +1 -0
- package/dist/builders/update.js +136 -0
- package/dist/client.d.ts +0 -6
- package/dist/client.js +140 -0
- package/dist/codegen/zod.js +107 -0
- package/dist/column.d.ts +1 -25
- package/dist/column.js +133 -0
- package/dist/compiler.d.ts +0 -7
- package/dist/compiler.js +513 -0
- package/dist/core.js +6 -0
- package/dist/data-types.d.ts +0 -61
- package/dist/data-types.js +127 -0
- package/dist/dictionary.d.ts +0 -149
- package/dist/dictionary.js +158 -0
- package/dist/engines.d.ts +0 -385
- package/dist/engines.js +292 -0
- package/dist/expressions.d.ts +0 -10
- package/dist/expressions.js +268 -0
- package/dist/external.d.ts +0 -112
- package/dist/external.js +224 -0
- package/dist/index.d.ts +0 -51
- package/dist/index.js +139 -6853
- package/dist/logger.js +36 -0
- package/dist/materialized-views.d.ts +0 -188
- package/dist/materialized-views.js +380 -0
- package/dist/metadata.js +59 -0
- package/dist/modules/aggregates.d.ts +0 -164
- package/dist/modules/aggregates.js +121 -0
- package/dist/modules/array.d.ts +0 -98
- package/dist/modules/array.js +71 -0
- package/dist/modules/conditional.d.ts +0 -84
- package/dist/modules/conditional.js +138 -0
- package/dist/modules/conversion.d.ts +0 -147
- package/dist/modules/conversion.js +109 -0
- package/dist/modules/geo.d.ts +0 -164
- package/dist/modules/geo.js +112 -0
- package/dist/modules/hash.js +4 -0
- package/dist/modules/index.js +12 -0
- package/dist/modules/json.d.ts +0 -106
- package/dist/modules/json.js +76 -0
- package/dist/modules/math.d.ts +0 -16
- package/dist/modules/math.js +16 -0
- package/dist/modules/string.d.ts +0 -136
- package/dist/modules/string.js +89 -0
- package/dist/modules/time.d.ts +0 -123
- package/dist/modules/time.js +91 -0
- package/dist/modules/types.d.ts +0 -133
- package/dist/modules/types.js +114 -0
- package/dist/modules/window.js +140 -0
- package/dist/relational.d.ts +0 -82
- package/dist/relational.js +290 -0
- package/dist/relations.js +21 -0
- package/dist/schema-builder.d.ts +0 -90
- package/dist/schema-builder.js +140 -0
- package/dist/table.d.ts +0 -42
- package/dist/table.js +406 -0
- package/dist/utils/background-batcher.js +75 -0
- package/dist/utils/batch-transform.js +51 -0
- package/dist/utils/binary-reader.d.ts +0 -6
- package/dist/utils/binary-reader.js +334 -0
- package/dist/utils/binary-serializer.d.ts +0 -125
- package/dist/utils/binary-serializer.js +637 -0
- package/dist/utils/binary-worker-code.js +1 -0
- package/dist/utils/binary-worker-pool.d.ts +0 -34
- package/dist/utils/binary-worker-pool.js +206 -0
- package/dist/utils/binary-worker.d.ts +0 -11
- package/dist/utils/binary-worker.js +63 -0
- package/dist/utils/insert-processing.d.ts +0 -2
- package/dist/utils/insert-processing.js +163 -0
- package/dist/utils/lru-cache.js +30 -0
- package/package.json +68 -3
package/README.md
CHANGED
|
@@ -4,14 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
> ⚠️ **Public Beta**: This package is currently in public beta. Feedback is highly appreciated as we polish the API for v1.0.
|
|
6
6
|
|
|
7
|
-
> [
|
|
8
|
-
> **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 the entire codebase and documentation for free (Updated instantly).
|
|
9
8
|
|
|
10
|
-
> [
|
|
11
|
-
> **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 the codebase (Updated weekly).
|
|
12
10
|
|
|
13
|
-
> [
|
|
14
|
-
> **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 the Wiki](https://deepwiki.com/pablofdezr/housekit) for AI-powered assistance (Updated weekly).
|
|
15
12
|
|
|
16
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.
|
|
17
14
|
|
|
@@ -100,6 +97,16 @@ id: t.uuid('id').primaryKey().default('generateUUIDv7()')
|
|
|
100
97
|
|
|
101
98
|
**Note:** Don't combine both - it's redundant. Choose one based on whether you need `.returning()` support.
|
|
102
99
|
|
|
100
|
+
**Custom IDs:** You can always provide your own ID when inserting - autoGenerate only kicks in when the field is `undefined`:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
// Auto-generated UUID
|
|
104
|
+
await db.insert(users).values({ email: 'a@b.com' });
|
|
105
|
+
|
|
106
|
+
// Custom ID provided by user
|
|
107
|
+
await db.insert(users).values({ id: 'my-custom-uuid', email: 'a@b.com' });
|
|
108
|
+
```
|
|
109
|
+
|
|
103
110
|
### 2. Connect and Query
|
|
104
111
|
|
|
105
112
|
```typescript
|
|
@@ -319,6 +326,40 @@ await db.select()
|
|
|
319
326
|
|
|
320
327
|
---
|
|
321
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
|
+
|
|
322
363
|
## 🛠 SQL Utilities
|
|
323
364
|
|
|
324
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;
|