@housekit/orm 0.1.25 → 0.1.27
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 +256 -134
- package/dist/builders/insert.d.ts +37 -22
- package/dist/builders/select.d.ts +3 -1
- package/dist/client.d.ts +15 -0
- package/dist/column.d.ts +2 -2
- package/dist/index.d.ts +45 -6
- package/dist/index.js +450 -150
- package/dist/relational.d.ts +92 -20
- package/dist/schema-builder.d.ts +20 -4
- package/dist/table.d.ts +11 -1
- package/dist/utils/batch-transform.d.ts +0 -2
- package/dist/utils/binary-serializer.d.ts +57 -0
- package/dist/utils/binary-worker-code.d.ts +1 -1
- package/dist/utils/binary-worker-pool.d.ts +2 -2
- package/dist/utils/insert-processing.d.ts +7 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,10 +14,10 @@ HouseKit ORM is a modern database toolkit designed specifically for ClickHouse.
|
|
|
14
14
|
## 🚀 Key Features
|
|
15
15
|
|
|
16
16
|
- **🛡️ First-Class TypeScript**: Full type inference for every query. Schema definition acts as the single source of truth.
|
|
17
|
-
- **🏎️
|
|
17
|
+
- **🏎️ Binary Inserts by Default**: Native `RowBinary` serialization is used automatically. **5-10x faster** than JSON.
|
|
18
18
|
- **🏗️ ClickHouse Native Engines**: Fluent DSL for `MergeTree`, `ReplacingMergeTree`, `SummingMergeTree`, `Distributed`, `Buffer`, and more.
|
|
19
19
|
- **🔍 Advanced Analytics**: Specialized support for `ASOF JOIN`, `ARRAY JOIN`, `PREWHERE`, and complex Window Functions.
|
|
20
|
-
- **🤝 Smart Relational API**: Query relations using `groupArray` internally, preventing row duplication
|
|
20
|
+
- **🤝 Smart Relational API**: Query relations using `groupArray` internally, preventing row duplication.
|
|
21
21
|
- **📦 Background Batching**: Built-in buffering to collect small inserts into high-performance batches automatically.
|
|
22
22
|
|
|
23
23
|
---
|
|
@@ -25,9 +25,6 @@ HouseKit ORM is a modern database toolkit designed specifically for ClickHouse.
|
|
|
25
25
|
## 📦 Installation
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
# HouseKit requires the official ClickHouse client as a peer dependency
|
|
29
|
-
npm install @housekit/orm @clickhouse/client
|
|
30
|
-
# or
|
|
31
28
|
bun add @housekit/orm @clickhouse/client
|
|
32
29
|
```
|
|
33
30
|
|
|
@@ -35,68 +32,199 @@ bun add @housekit/orm @clickhouse/client
|
|
|
35
32
|
|
|
36
33
|
## ⚡️ Quick Start
|
|
37
34
|
|
|
38
|
-
### 1. Define your Table
|
|
39
|
-
Use the fluent `defineTable` API. You can export the inferred types directly from the schema definition thanks to **Phantom Types**.
|
|
35
|
+
### 1. Define your Table
|
|
40
36
|
|
|
41
37
|
```typescript
|
|
42
38
|
// schema.ts
|
|
43
|
-
import { defineTable, t, Engine } from '@housekit/orm';
|
|
44
|
-
|
|
45
|
-
export const
|
|
46
|
-
id: t.uuid('id').primaryKey(),
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
tags: t.array(t.string('tag')),
|
|
51
|
-
metadata: t.json('metadata'),
|
|
52
|
-
at: t.datetime('at').default('now()'),
|
|
39
|
+
import { defineTable, t, Engine, relations } from '@housekit/orm';
|
|
40
|
+
|
|
41
|
+
export const users = defineTable('users', {
|
|
42
|
+
id: t.uuid('id').autoGenerate({ version: 7 }).primaryKey().default('generateUUIDv7()'),
|
|
43
|
+
email: t.string('email'),
|
|
44
|
+
role: t.enum('role', ['admin', 'user']),
|
|
45
|
+
...t.timestamps(),
|
|
53
46
|
}, {
|
|
54
47
|
engine: Engine.MergeTree(),
|
|
55
|
-
orderBy: '
|
|
56
|
-
partitionBy: 'toYYYYMM(at)',
|
|
57
|
-
ttl: 'at + INTERVAL 1 MONTH'
|
|
48
|
+
orderBy: 'id'
|
|
58
49
|
});
|
|
59
50
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
51
|
+
export const posts = defineTable('posts', {
|
|
52
|
+
id: t.uuid('id').autoGenerate({ version: 7 }).primaryKey().default('generateUUIDv7()'),
|
|
53
|
+
userId: t.uuid('user_id'),
|
|
54
|
+
title: t.string('title'),
|
|
55
|
+
createdAt: t.timestamp('created_at').default('now()'),
|
|
56
|
+
}, {
|
|
57
|
+
engine: Engine.MergeTree(),
|
|
58
|
+
orderBy: 'createdAt'
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
relations(users, ({ many }) => ({
|
|
62
|
+
posts: many(posts, { fields: [users.id], references: [posts.userId] })
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
export type User = typeof users.$type;
|
|
66
|
+
export type NewUser = typeof users.$insert;
|
|
63
67
|
```
|
|
64
68
|
|
|
65
69
|
### 2. Connect and Query
|
|
66
|
-
HouseKit automatically picks up configuration from your environment or `housekit.config.ts`.
|
|
67
70
|
|
|
68
71
|
```typescript
|
|
69
|
-
import {
|
|
70
|
-
import
|
|
71
|
-
|
|
72
|
-
const db =
|
|
73
|
-
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
})
|
|
81
|
-
.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
72
|
+
import { housekit } from '@housekit/orm';
|
|
73
|
+
import * as schema from './schema';
|
|
74
|
+
|
|
75
|
+
const db = housekit({ url: 'http://localhost:8123' }, { schema });
|
|
76
|
+
|
|
77
|
+
// Binary insert (default, fastest)
|
|
78
|
+
await db.insert(schema.users).values({ email: 'a@b.com', role: 'admin' });
|
|
79
|
+
|
|
80
|
+
// JSON insert with returning data
|
|
81
|
+
const [user] = await db
|
|
82
|
+
.insert(schema.users)
|
|
83
|
+
.values({ email: 'a@b.com', role: 'admin' })
|
|
84
|
+
.returning();
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 🔍 Relational Query API
|
|
90
|
+
|
|
91
|
+
### findMany / findFirst
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
const users = await db.query.users.findMany({
|
|
95
|
+
where: { role: 'admin', active: true },
|
|
96
|
+
columns: { id: true, email: true },
|
|
97
|
+
orderBy: (cols, { desc }) => desc(cols.createdAt),
|
|
98
|
+
limit: 10,
|
|
99
|
+
with: {
|
|
100
|
+
posts: { limit: 5 }
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### findById
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
// Simple lookup
|
|
109
|
+
const user = await db.query.users.findById('uuid-here');
|
|
110
|
+
|
|
111
|
+
// With relations
|
|
112
|
+
const user = await db.query.users.findById('uuid-here', {
|
|
113
|
+
with: { posts: true }
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### where syntax
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
// Object syntax (simplest)
|
|
121
|
+
where: { email: 'a@b.com' }
|
|
122
|
+
where: { role: 'admin', active: true } // AND implícito
|
|
123
|
+
|
|
124
|
+
// Direct expression
|
|
125
|
+
where: eq(users.role, 'admin')
|
|
126
|
+
|
|
127
|
+
// Callback for complex filters
|
|
128
|
+
where: (cols, { and, gt, inArray }) => and(
|
|
129
|
+
gt(cols.age, 18),
|
|
130
|
+
inArray(cols.role, ['admin', 'moderator'])
|
|
131
|
+
)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Available operators: `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `inArray`, `notInArray`, `between`, `notBetween`, `has`, `hasAll`, `hasAny`, `and`, `or`, `not`, `isNull`, `isNotNull`
|
|
135
|
+
|
|
136
|
+
### columns selection
|
|
137
|
+
|
|
138
|
+
Select specific columns:
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
const users = await db.query.users.findMany({
|
|
142
|
+
columns: { id: true, email: true }
|
|
143
|
+
});
|
|
144
|
+
// Returns: [{ id: '...', email: '...' }]
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### orderBy
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
// Callback (recommended)
|
|
151
|
+
orderBy: (cols, { desc }) => desc(cols.createdAt)
|
|
152
|
+
|
|
153
|
+
// Multiple columns
|
|
154
|
+
orderBy: (cols, { desc, asc }) => [desc(cols.createdAt), asc(cols.name)]
|
|
155
|
+
|
|
156
|
+
// Direct value
|
|
157
|
+
orderBy: desc(users.createdAt)
|
|
158
|
+
|
|
159
|
+
// Array
|
|
160
|
+
orderBy: [desc(users.createdAt), asc(users.name)]
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## 🚀 High-Performance Inserts
|
|
166
|
+
|
|
167
|
+
### Binary Insert (Default)
|
|
168
|
+
|
|
169
|
+
Binary format is used by default for maximum performance:
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
// Binary insert (no data returned)
|
|
173
|
+
await db.insert(events).values([
|
|
174
|
+
{ type: 'click', userId: '...' },
|
|
175
|
+
{ type: 'view', userId: '...' },
|
|
176
|
+
]);
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### JSON Insert with Returning
|
|
180
|
+
|
|
181
|
+
Use `.returningOne()` for single inserts or `.returning()` for multiple:
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
// Single insert
|
|
185
|
+
const user = await db
|
|
186
|
+
.insert(users)
|
|
187
|
+
.values({ email: 'a@b.com', role: 'admin' })
|
|
188
|
+
.returningOne();
|
|
189
|
+
|
|
190
|
+
console.log(user.id); // Generated UUID
|
|
191
|
+
|
|
192
|
+
// Multiple inserts
|
|
193
|
+
const [user1, user2] = await db
|
|
194
|
+
.insert(users)
|
|
195
|
+
.values([{ email: 'a@b.com' }, { email: 'b@c.com' }])
|
|
196
|
+
.returning();
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Force JSON Format
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
await db.insert(events).values(data).useJsonFormat();
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Background Batching
|
|
206
|
+
|
|
207
|
+
Collect small writes into efficient batches:
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
const builder = db.insert(events).batch({
|
|
211
|
+
maxRows: 10000,
|
|
212
|
+
flushIntervalMs: 5000
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Fire-and-forget
|
|
216
|
+
await builder.append(event1);
|
|
217
|
+
await builder.append(event2);
|
|
89
218
|
```
|
|
90
219
|
|
|
91
220
|
---
|
|
92
221
|
|
|
93
|
-
## 🧠 Advanced Schema
|
|
222
|
+
## 🧠 Advanced Schema
|
|
94
223
|
|
|
95
224
|
### Complex Engines
|
|
96
|
-
HouseKit supports specialized ClickHouse engines with strict type checking for their parameters.
|
|
97
225
|
|
|
98
226
|
```typescript
|
|
99
|
-
// SummingMergeTree
|
|
227
|
+
// SummingMergeTree
|
|
100
228
|
export const dailyRevenue = defineTable('daily_revenue', {
|
|
101
229
|
day: t.date('day'),
|
|
102
230
|
revenue: t.float64('revenue'),
|
|
@@ -105,23 +233,19 @@ export const dailyRevenue = defineTable('daily_revenue', {
|
|
|
105
233
|
orderBy: 'day'
|
|
106
234
|
});
|
|
107
235
|
|
|
108
|
-
// ReplacingMergeTree
|
|
236
|
+
// ReplacingMergeTree
|
|
109
237
|
export const users = defineTable('users', {
|
|
110
238
|
id: t.uint64('id'),
|
|
111
239
|
email: t.string('email'),
|
|
112
240
|
version: t.uint64('version'),
|
|
113
241
|
}, {
|
|
114
242
|
engine: Engine.ReplacingMergeTree('version'),
|
|
115
|
-
|
|
116
|
-
// Portability: '{cluster}' references the server-side macro.
|
|
117
|
-
onCluster: '{cluster}',
|
|
118
|
-
|
|
243
|
+
onCluster: '{cluster}',
|
|
119
244
|
orderBy: 'id'
|
|
120
245
|
});
|
|
121
246
|
```
|
|
122
247
|
|
|
123
248
|
### Dictionaries
|
|
124
|
-
Map external data or internal tables to fast in-memory dictionaries for ultra-low latency lookups.
|
|
125
249
|
|
|
126
250
|
```typescript
|
|
127
251
|
import { defineDictionary } from '@housekit/orm';
|
|
@@ -138,130 +262,128 @@ export const userCache = defineDictionary('user_dict', {
|
|
|
138
262
|
|
|
139
263
|
---
|
|
140
264
|
|
|
141
|
-
##
|
|
265
|
+
## 🔍 Specialized Joins
|
|
142
266
|
|
|
143
|
-
###
|
|
144
|
-
When you call `db.insert()`, HouseKit analyzes your schema. If types are compatible, it automatically switches to **Turbo Mode**, using native binary serialization instead of JSON.
|
|
267
|
+
### ASOF JOIN
|
|
145
268
|
|
|
146
269
|
```typescript
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
]);
|
|
152
|
-
// Logic: Object -> Buffer (Binary) -> ClickHouse Stream (Zero-copy)
|
|
270
|
+
const matched = await db.select()
|
|
271
|
+
.from(trades)
|
|
272
|
+
.asofJoin(quotes, sql`${trades.symbol} = ${quotes.symbol} AND ${trades.at} >= ${quotes.at}`)
|
|
273
|
+
.limit(100);
|
|
153
274
|
```
|
|
154
275
|
|
|
155
|
-
###
|
|
156
|
-
Collect small, frequent writes into large batches to prevent the "too many parts" error in ClickHouse.
|
|
276
|
+
### GLOBAL JOIN
|
|
157
277
|
|
|
158
278
|
```typescript
|
|
159
|
-
|
|
160
|
-
.
|
|
161
|
-
|
|
162
|
-
flushIntervalMs: 5000
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
// Add rows to the background queue.
|
|
166
|
-
// Processing and flushing happen automatically.
|
|
167
|
-
// Returns immediately (Fire-and-forget).
|
|
168
|
-
await builder.append(row1);
|
|
169
|
-
await builder.append(row2);
|
|
279
|
+
await db.select()
|
|
280
|
+
.from(distributedTable)
|
|
281
|
+
.globalJoin(rightTable, condition);
|
|
170
282
|
```
|
|
171
283
|
|
|
172
284
|
---
|
|
173
285
|
|
|
174
|
-
##
|
|
175
|
-
|
|
176
|
-
Because we use **Phantom Types**, you don't need to import generic helpers like `Infer<T>` in your application code. You can use `typeof table.$infer...` or the types you exported from your schema file.
|
|
286
|
+
## 🛠 SQL Utilities
|
|
177
287
|
|
|
178
|
-
###
|
|
288
|
+
### Dynamic Queries
|
|
179
289
|
|
|
180
290
|
```typescript
|
|
181
|
-
|
|
291
|
+
const conditions = [
|
|
292
|
+
eq(users.active, true),
|
|
293
|
+
gte(users.age, 18)
|
|
294
|
+
];
|
|
182
295
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
296
|
+
const query = await db.select()
|
|
297
|
+
.from(users)
|
|
298
|
+
.where(sql.join(conditions, sql` AND `));
|
|
187
299
|
```
|
|
188
300
|
|
|
189
301
|
---
|
|
190
302
|
|
|
191
|
-
##
|
|
303
|
+
## ⚡ Performance Optimizations
|
|
304
|
+
|
|
305
|
+
HouseKit includes several optimizations for maximum throughput in production environments.
|
|
192
306
|
|
|
193
|
-
|
|
307
|
+
### Connection Pooling
|
|
308
|
+
|
|
309
|
+
Reuse HTTP connections across requests:
|
|
194
310
|
|
|
195
311
|
```typescript
|
|
196
|
-
|
|
197
|
-
|
|
312
|
+
const db = housekit({
|
|
313
|
+
url: 'http://localhost:8123',
|
|
314
|
+
pool: {
|
|
315
|
+
maxSockets: 200, // Max concurrent connections
|
|
316
|
+
keepAlive: true, // Reuse connections
|
|
317
|
+
timeout: 30000 // Socket timeout (ms)
|
|
318
|
+
}
|
|
319
|
+
}, { schema });
|
|
320
|
+
```
|
|
198
321
|
|
|
199
|
-
|
|
200
|
-
posts: many(posts, { fields: [users.id], references: [posts.userId] })
|
|
201
|
-
}));
|
|
322
|
+
### Skip Validation
|
|
202
323
|
|
|
203
|
-
|
|
204
|
-
const usersWithData = await db.query.users.findMany({
|
|
205
|
-
where: { country: 'US' }, // Object syntax
|
|
206
|
-
with: {
|
|
207
|
-
posts: {
|
|
208
|
-
limit: 5,
|
|
209
|
-
orderBy: { col: posts.createdAt, dir: 'DESC' }
|
|
210
|
-
}
|
|
211
|
-
},
|
|
212
|
-
limit: 10
|
|
213
|
-
});
|
|
324
|
+
Bypass enum validation in production when you trust your data source:
|
|
214
325
|
|
|
215
|
-
|
|
216
|
-
//
|
|
326
|
+
```typescript
|
|
327
|
+
// Global (all inserts)
|
|
328
|
+
const db = housekit({
|
|
329
|
+
url: 'http://localhost:8123',
|
|
330
|
+
skipValidation: true
|
|
331
|
+
}, { schema });
|
|
332
|
+
|
|
333
|
+
// Per-insert
|
|
334
|
+
await db.insert(events).values(data).skipValidation();
|
|
217
335
|
```
|
|
218
336
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
## 🛠 SQL Utilities
|
|
337
|
+
### Object Pooling
|
|
222
338
|
|
|
223
|
-
|
|
224
|
-
Easily build complex queries by joining SQL fragments with separators.
|
|
339
|
+
HouseKit automatically pools `BinaryWriter` instances to reduce GC pressure. For advanced use cases:
|
|
225
340
|
|
|
226
341
|
```typescript
|
|
227
|
-
|
|
228
|
-
eq(users.active, true),
|
|
229
|
-
gte(users.age, 18)
|
|
230
|
-
];
|
|
342
|
+
import { acquireWriter, releaseWriter } from '@housekit/orm';
|
|
231
343
|
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
344
|
+
const writer = acquireWriter();
|
|
345
|
+
try {
|
|
346
|
+
// Use writer for custom serialization
|
|
347
|
+
} finally {
|
|
348
|
+
releaseWriter(writer);
|
|
349
|
+
}
|
|
235
350
|
```
|
|
236
351
|
|
|
237
|
-
|
|
352
|
+
### Optimized Serialization
|
|
238
353
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
### ASOF JOIN
|
|
242
|
-
The industry standard for time-series matches (e.g., matching a trade with the closest price quote).
|
|
354
|
+
For maximum performance with large batches:
|
|
243
355
|
|
|
244
356
|
```typescript
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
357
|
+
import {
|
|
358
|
+
buildOptimizedBinaryConfig,
|
|
359
|
+
serializeRowsOptimized
|
|
360
|
+
} from '@housekit/orm';
|
|
361
|
+
|
|
362
|
+
// Pre-compile accessors once
|
|
363
|
+
const config = buildOptimizedBinaryConfig(columns, {
|
|
364
|
+
skipValidation: true
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
// Serialize batches with pooled writer + pre-compiled accessors
|
|
368
|
+
const buffer = serializeRowsOptimized(rows, config);
|
|
249
369
|
```
|
|
250
370
|
|
|
251
|
-
###
|
|
252
|
-
|
|
371
|
+
### TypedArray for Numeric Data
|
|
372
|
+
|
|
373
|
+
For schemas with mostly numeric columns:
|
|
253
374
|
|
|
254
375
|
```typescript
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
376
|
+
import { isNumericHeavySchema, serializeNumericBatch } from '@housekit/orm';
|
|
377
|
+
|
|
378
|
+
if (isNumericHeavySchema(columns)) {
|
|
379
|
+
// Uses Float64Array for better memory layout
|
|
380
|
+
const { numericBuffer, otherData } = serializeNumericBatch(rows, config, numericIndices);
|
|
381
|
+
}
|
|
258
382
|
```
|
|
259
383
|
|
|
260
384
|
---
|
|
261
385
|
|
|
262
|
-
## 🛠 Observability
|
|
263
|
-
|
|
264
|
-
Inject a custom logger to monitor query performance, throughput, and error rates.
|
|
386
|
+
## 🛠 Observability
|
|
265
387
|
|
|
266
388
|
```typescript
|
|
267
389
|
const db = await createClient({
|
|
@@ -278,4 +400,4 @@ const db = await createClient({
|
|
|
278
400
|
|
|
279
401
|
## License
|
|
280
402
|
|
|
281
|
-
MIT © [Pablo Fernandez Ruiz](https://github.com/pablofdezr)
|
|
403
|
+
MIT © [Pablo Fernandez Ruiz](https://github.com/pablofdezr)
|
|
@@ -3,40 +3,55 @@ import { type TableRuntime, type CleanInsert, type CleanSelect } from '../core';
|
|
|
3
3
|
import { type BatchTransformOptions } from '../utils/batch-transform';
|
|
4
4
|
import { Readable } from 'stream';
|
|
5
5
|
import { type BatchConfig } from '../utils/background-batcher';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
type InsertFormat = 'auto' | 'binary' | 'json' | 'compact';
|
|
14
|
-
export interface InsertOptions {
|
|
15
|
-
/**
|
|
16
|
-
* Format strategy for serialization.
|
|
17
|
-
* Default: 'auto' (uses RowBinary when possible, falls back to JSON)
|
|
18
|
-
*/
|
|
19
|
-
format?: InsertFormat;
|
|
20
|
-
/** Rows per batch when streaming */
|
|
21
|
-
batchSize?: number;
|
|
6
|
+
export interface BinaryInsertConfig {
|
|
7
|
+
url: string;
|
|
8
|
+
username: string;
|
|
9
|
+
password: string;
|
|
10
|
+
database: string;
|
|
11
|
+
/** Skip validation for maximum performance */
|
|
12
|
+
skipValidation?: boolean;
|
|
22
13
|
}
|
|
23
|
-
export declare class ClickHouseInsertBuilder<TTable extends TableRuntime<any, any>, TReturn =
|
|
14
|
+
export declare class ClickHouseInsertBuilder<TTable extends TableRuntime<any, any>, TReturn = any> {
|
|
24
15
|
private client;
|
|
25
16
|
private table;
|
|
17
|
+
private connectionConfig?;
|
|
26
18
|
private _values;
|
|
27
19
|
private _async;
|
|
28
20
|
private _waitForAsync;
|
|
29
21
|
private _batchOptions;
|
|
30
22
|
private _format;
|
|
31
|
-
private _batchSize;
|
|
32
23
|
private _batchConfig;
|
|
33
24
|
private _forceJson;
|
|
34
25
|
private _returning;
|
|
35
|
-
|
|
36
|
-
|
|
26
|
+
private _isSingle;
|
|
27
|
+
private _skipValidation;
|
|
28
|
+
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
|
+
skipValidation(): this;
|
|
34
|
+
values(value: CleanInsert<TTable> | Array<CleanInsert<TTable>> | Iterable<CleanInsert<TTable>> | AsyncIterable<CleanInsert<TTable>> | Readable): ClickHouseInsertBuilder<TTable, TReturn>;
|
|
37
35
|
/** @template [T = CleanInsert<TTable>] */
|
|
38
|
-
insert(data: CleanInsert<TTable> | CleanInsert<TTable>[]): Promise<
|
|
36
|
+
insert(data: CleanInsert<TTable> | CleanInsert<TTable>[]): Promise<TReturn>;
|
|
37
|
+
/**
|
|
38
|
+
* Return inserted data as an array.
|
|
39
|
+
* Use when inserting multiple rows.
|
|
40
|
+
*/
|
|
39
41
|
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
|
+
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
|
+
noReturning(): ClickHouseInsertBuilder<TTable, void>;
|
|
40
55
|
/**
|
|
41
56
|
* Force synchronous insert (disables async_insert).
|
|
42
57
|
* Use when you need immediate durability guarantee.
|
|
@@ -131,6 +146,7 @@ export declare class ClickHouseInsertBuilder<TTable extends TableRuntime<any, an
|
|
|
131
146
|
private executeJsonInsert;
|
|
132
147
|
/**
|
|
133
148
|
* Execute insert using RowBinary format (fastest)
|
|
149
|
+
* Uses direct HTTP request since the official client doesn't support RowBinary yet
|
|
134
150
|
*/
|
|
135
151
|
private executeBinaryInsert;
|
|
136
152
|
/**
|
|
@@ -142,4 +158,3 @@ export declare class ClickHouseInsertBuilder<TTable extends TableRuntime<any, an
|
|
|
142
158
|
private resolveClientDefaultExpr;
|
|
143
159
|
then<TResult1 = TReturn, TResult2 = never>(onfulfilled?: ((value: TReturn) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
|
|
144
160
|
}
|
|
145
|
-
export {};
|
|
@@ -221,7 +221,9 @@ export declare class ClickHouseQueryBuilder<TTable extends TableDefinition<any>
|
|
|
221
221
|
}>;
|
|
222
222
|
readonly $inferSelect: { [K_2 in keyof (TSelection extends SelectionShape ? { [K in keyof (TSelection extends infer T_2 ? T_2 extends TSelection ? T_2 extends SelectionShape ? SelectResult<T_2> : Record<string, any> : never : never)]: ClickHouseColumn<(TSelection extends infer T_3 ? T_3 extends TSelection ? T_3 extends SelectionShape ? SelectResult<T_3> : Record<string, any> : never : never)[K], true, false>; } : Record<string, ClickHouseColumn<any, true, false>>)]: (TSelection extends SelectionShape ? { [K in keyof (TSelection extends infer T_3 ? T_3 extends TSelection ? T_3 extends SelectionShape ? SelectResult<T_3> : Record<string, any> : never : never)]: ClickHouseColumn<(TSelection extends infer T_4 ? T_4 extends TSelection ? T_4 extends SelectionShape ? SelectResult<T_4> : Record<string, any> : never : never)[K], true, false>; } : Record<string, ClickHouseColumn<any, true, false>>)[K_2] extends infer T_4 ? T_4 extends (TSelection extends SelectionShape ? { [K in keyof (TSelection extends infer T_5 ? T_5 extends TSelection ? T_5 extends SelectionShape ? SelectResult<T_5> : Record<string, any> : never : never)]: ClickHouseColumn<(TSelection extends infer T_6 ? T_6 extends TSelection ? T_6 extends SelectionShape ? SelectResult<T_6> : Record<string, any> : never : never)[K], true, false>; } : Record<string, ClickHouseColumn<any, true, false>>)[K_2] ? T_4 extends ClickHouseColumn<infer Type, infer IsNotNull extends boolean, any> ? IsNotNull extends true ? Type : Type | null : never : never : never; } extends infer T_1 ? { [K_1 in keyof T_1]: T_1[K_1]; } : never;
|
|
223
223
|
readonly $inferInsert: import("..").TableInsert<TSelection extends SelectionShape ? { [K in keyof (TSelection extends infer T_4 ? T_4 extends TSelection ? T_4 extends SelectionShape ? SelectResult<T_4> : Record<string, any> : never : never)]: ClickHouseColumn<(TSelection extends infer T_5 ? T_5 extends TSelection ? T_5 extends SelectionShape ? SelectResult<T_5> : Record<string, any> : never : never)[K], true, false>; } : Record<string, ClickHouseColumn<any, true, false>>> extends infer T_3 ? { [K_3 in keyof T_3]: T_3[K_3]; } : never;
|
|
224
|
-
|
|
224
|
+
readonly $type: { [K_5 in keyof (TSelection extends SelectionShape ? { [K in keyof (TSelection extends infer T_6 ? T_6 extends TSelection ? T_6 extends SelectionShape ? SelectResult<T_6> : Record<string, any> : never : never)]: ClickHouseColumn<(TSelection extends infer T_7 ? T_7 extends TSelection ? T_7 extends SelectionShape ? SelectResult<T_7> : Record<string, any> : never : never)[K], true, false>; } : Record<string, ClickHouseColumn<any, true, false>>)]: (TSelection extends SelectionShape ? { [K in keyof (TSelection extends infer T_7 ? T_7 extends TSelection ? T_7 extends SelectionShape ? SelectResult<T_7> : Record<string, any> : never : never)]: ClickHouseColumn<(TSelection extends infer T_8 ? T_8 extends TSelection ? T_8 extends SelectionShape ? SelectResult<T_8> : Record<string, any> : never : never)[K], true, false>; } : Record<string, ClickHouseColumn<any, true, false>>)[K_5] extends infer T_8 ? T_8 extends (TSelection extends SelectionShape ? { [K in keyof (TSelection extends infer T_9 ? T_9 extends TSelection ? T_9 extends SelectionShape ? SelectResult<T_9> : Record<string, any> : never : never)]: ClickHouseColumn<(TSelection extends infer T_10 ? T_10 extends TSelection ? T_10 extends SelectionShape ? SelectResult<T_10> : Record<string, any> : never : never)[K], true, false>; } : Record<string, ClickHouseColumn<any, true, false>>)[K_5] ? T_8 extends ClickHouseColumn<infer Type, infer IsNotNull extends boolean, any> ? IsNotNull extends true ? Type : Type | null : never : never : never; } extends infer T_5 ? { [K_4 in keyof T_5]: T_5[K_4]; } : never;
|
|
225
|
+
readonly $insert: import("..").TableInsert<TSelection extends SelectionShape ? { [K in keyof (TSelection extends infer T_8 ? T_8 extends TSelection ? T_8 extends SelectionShape ? SelectResult<T_8> : Record<string, any> : never : never)]: ClickHouseColumn<(TSelection extends infer T_9 ? T_9 extends TSelection ? T_9 extends SelectionShape ? SelectResult<T_9> : Record<string, any> : never : never)[K], true, false>; } : Record<string, ClickHouseColumn<any, true, false>>> extends infer T_7 ? { [K_3 in keyof T_7]: T_7[K_3]; } : never;
|
|
226
|
+
} & (TSelection extends SelectionShape ? { [K in keyof (TSelection extends infer T_9 ? T_9 extends TSelection ? T_9 extends SelectionShape ? SelectResult<T_9> : Record<string, any> : never : never)]: ClickHouseColumn<(TSelection extends infer T_10 ? T_10 extends TSelection ? T_10 extends SelectionShape ? SelectResult<T_10> : Record<string, any> : never : never)[K], true, false>; } : Record<string, ClickHouseColumn<any, true, false>>);
|
|
225
227
|
register: (mainQuery: ClickHouseQueryBuilder<any, any, any>) => ClickHouseQueryBuilder<any, any, any>;
|
|
226
228
|
};
|
|
227
229
|
final(): this;
|
package/dist/client.d.ts
CHANGED
|
@@ -6,9 +6,23 @@ import { ClickHouseUpdateBuilder } from './builders/update';
|
|
|
6
6
|
import { type TableDefinition, type TableRuntime, type CleanInsert } from './core';
|
|
7
7
|
import { type HousekitLogger } from './logger';
|
|
8
8
|
import { type RelationalAPI } from './relational';
|
|
9
|
+
interface ConnectionPoolConfig {
|
|
10
|
+
/** Maximum concurrent sockets (default: 100) */
|
|
11
|
+
maxSockets?: number;
|
|
12
|
+
/** Keep connections alive (default: true) */
|
|
13
|
+
keepAlive?: boolean;
|
|
14
|
+
/** Keep-alive initial delay in ms (default: 1000) */
|
|
15
|
+
keepAliveInitialDelay?: number;
|
|
16
|
+
/** Socket timeout in ms (default: 30000) */
|
|
17
|
+
timeout?: number;
|
|
18
|
+
}
|
|
9
19
|
export type HousekitClientConfig = ClickHouseClientConfigOptions & {
|
|
10
20
|
schema?: Record<string, TableDefinition<any>>;
|
|
11
21
|
logger?: HousekitLogger;
|
|
22
|
+
/** Connection pool configuration */
|
|
23
|
+
pool?: ConnectionPoolConfig;
|
|
24
|
+
/** Skip validation for maximum insert performance */
|
|
25
|
+
skipValidation?: boolean;
|
|
12
26
|
};
|
|
13
27
|
export type ClientConfigWithSchema = HousekitClientConfig;
|
|
14
28
|
export interface HousekitClient<TSchema extends Record<string, TableDefinition<any>> | undefined = any> {
|
|
@@ -45,3 +59,4 @@ export declare function createHousekitClient<TSchema extends Record<string, Tabl
|
|
|
45
59
|
schema?: TSchema;
|
|
46
60
|
}): HousekitClient<TSchema>;
|
|
47
61
|
export declare const createClientFromConfigObject: typeof createHousekitClient;
|
|
62
|
+
export {};
|
package/dist/column.d.ts
CHANGED
|
@@ -44,7 +44,7 @@ export declare class ClickHouseColumn<TType = any, TNotNull extends boolean = tr
|
|
|
44
44
|
* Define a default value for the column.
|
|
45
45
|
* Can be a static value or a SQL expression like 'now()'.
|
|
46
46
|
*/
|
|
47
|
-
default(value: any): ClickHouseColumn<TType, TNotNull,
|
|
47
|
+
default(value: any): ClickHouseColumn<TType, TNotNull, true>;
|
|
48
48
|
references(getColumn: () => ClickHouseColumn, options?: {
|
|
49
49
|
onDelete?: 'cascade' | 'set null' | 'restrict' | 'no action';
|
|
50
50
|
onUpdate?: 'cascade' | 'set null' | 'restrict' | 'no action';
|
|
@@ -71,6 +71,6 @@ export declare class ClickHouseColumn<TType = any, TNotNull extends boolean = tr
|
|
|
71
71
|
* Calculates the column value on the client side before insertion.
|
|
72
72
|
* Useful for UUIDs, hashes, or computations based on other fields.
|
|
73
73
|
*/
|
|
74
|
-
$defaultFn(fn: (row: Record<string, any>) => any): ClickHouseColumn<TType, TNotNull,
|
|
74
|
+
$defaultFn(fn: (row: Record<string, any>) => any): ClickHouseColumn<TType, TNotNull, true>;
|
|
75
75
|
toSQL(): string;
|
|
76
76
|
}
|