@housekit/orm 0.1.21 → 0.1.23
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 +48 -67
- package/dist/builders/delete.d.ts +4 -2
- package/dist/builders/select.d.ts +2 -2
- package/dist/builders/update.d.ts +4 -2
- package/dist/index.js +41 -9
- package/dist/table.d.ts +4 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,8 +13,8 @@ HouseKit ORM is a modern database toolkit designed specifically for ClickHouse.
|
|
|
13
13
|
|
|
14
14
|
## 🚀 Key Features
|
|
15
15
|
|
|
16
|
-
- **🛡️ First-Class TypeScript**: Full type inference for every query.
|
|
17
|
-
- **🏎️ Automatic Turbo Mode**: Native `RowBinary` serialization
|
|
16
|
+
- **🛡️ First-Class TypeScript**: Full type inference for every query. Schema definition acts as the single source of truth.
|
|
17
|
+
- **🏎️ Automatic Turbo Mode**: Native `RowBinary` serialization is used automatically when possible. Bypasses the overhead of JSON parsing for **5-10x faster inserts**.
|
|
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
20
|
- **🤝 Smart Relational API**: Query relations using `groupArray` internally, preventing row duplication and keeping data transfer lean.
|
|
@@ -35,10 +35,11 @@ bun add @housekit/orm @clickhouse/client
|
|
|
35
35
|
|
|
36
36
|
## ⚡️ Quick Start
|
|
37
37
|
|
|
38
|
-
### 1. Define your Table
|
|
39
|
-
Use the fluent `defineTable` API.
|
|
38
|
+
### 1. Define your Table & Export Types
|
|
39
|
+
Use the fluent `defineTable` API. You can export the inferred types directly from the schema definition thanks to **Phantom Types**.
|
|
40
40
|
|
|
41
41
|
```typescript
|
|
42
|
+
// schema.ts
|
|
42
43
|
import { defineTable, t, Engine } from '@housekit/orm';
|
|
43
44
|
|
|
44
45
|
export const webEvents = defineTable('web_events', {
|
|
@@ -47,7 +48,7 @@ export const webEvents = defineTable('web_events', {
|
|
|
47
48
|
url: t.string('url'),
|
|
48
49
|
revenue: t.decimal('revenue', 18, 4).default(0),
|
|
49
50
|
tags: t.array(t.string('tag')),
|
|
50
|
-
metadata: t.json('metadata'),
|
|
51
|
+
metadata: t.json('metadata'),
|
|
51
52
|
at: t.datetime('at').default('now()'),
|
|
52
53
|
}, {
|
|
53
54
|
engine: Engine.MergeTree(),
|
|
@@ -55,27 +56,34 @@ export const webEvents = defineTable('web_events', {
|
|
|
55
56
|
partitionBy: 'toYYYYMM(at)',
|
|
56
57
|
ttl: 'at + INTERVAL 1 MONTH'
|
|
57
58
|
});
|
|
59
|
+
|
|
60
|
+
// ✨ Export inferred types directly
|
|
61
|
+
export type WebEvent = typeof webEvents.$inferSelect;
|
|
62
|
+
export type NewWebEvent = typeof webEvents.$inferInsert;
|
|
58
63
|
```
|
|
59
64
|
|
|
60
65
|
### 2. Connect and Query
|
|
61
66
|
HouseKit automatically picks up configuration from your environment or `housekit.config.ts`.
|
|
62
67
|
|
|
63
68
|
```typescript
|
|
64
|
-
import { createClient,
|
|
69
|
+
import { createClient, sql } from '@housekit/orm';
|
|
70
|
+
import { webEvents } from './schema';
|
|
65
71
|
|
|
66
72
|
const db = await createClient();
|
|
67
73
|
|
|
68
|
-
// Fully typed result inference
|
|
74
|
+
// Fully typed result inference.
|
|
75
|
+
// No need to call .then() or .execute(), just await the builder!
|
|
69
76
|
const results = await db.select({
|
|
70
77
|
id: webEvents.id,
|
|
71
78
|
path: webEvents.url,
|
|
72
79
|
total: sql<number>`sum(${webEvents.revenue})`
|
|
73
80
|
})
|
|
74
81
|
.from(webEvents)
|
|
75
|
-
.where(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
82
|
+
.where({
|
|
83
|
+
// Object syntax implicitly uses AND operator
|
|
84
|
+
eventType: 'sale',
|
|
85
|
+
url: '/checkout'
|
|
86
|
+
})
|
|
79
87
|
.groupBy(webEvents.id, webEvents.url)
|
|
80
88
|
.limit(10);
|
|
81
89
|
```
|
|
@@ -106,7 +114,6 @@ export const users = defineTable('users', {
|
|
|
106
114
|
engine: Engine.ReplacingMergeTree('version'),
|
|
107
115
|
|
|
108
116
|
// Portability: '{cluster}' references the server-side macro.
|
|
109
|
-
// This allows your schema to be environment-agnostic.
|
|
110
117
|
onCluster: '{cluster}',
|
|
111
118
|
|
|
112
119
|
orderBy: 'id'
|
|
@@ -134,9 +141,10 @@ export const userCache = defineDictionary('user_dict', {
|
|
|
134
141
|
## 🚀 High-Performance Data Ingestion
|
|
135
142
|
|
|
136
143
|
### Automatic Turbo Mode (RowBinary)
|
|
137
|
-
When you call `db.insert()`, HouseKit analyzes your schema. If
|
|
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.
|
|
138
145
|
|
|
139
146
|
```typescript
|
|
147
|
+
// Clean syntax: No .execute() needed. Just await.
|
|
140
148
|
await db.insert(webEvents).values([
|
|
141
149
|
{ id: '...', eventType: 'click', revenue: 0, metadata: { browser: 'chrome' } },
|
|
142
150
|
{ id: '...', eventType: 'purchase', revenue: 99.90, metadata: { browser: 'safari' } },
|
|
@@ -155,60 +163,29 @@ const builder = db.insert(webEvents)
|
|
|
155
163
|
});
|
|
156
164
|
|
|
157
165
|
// Add rows to the background queue.
|
|
158
|
-
//
|
|
166
|
+
// Processing and flushing happen automatically.
|
|
167
|
+
// Returns immediately (Fire-and-forget).
|
|
159
168
|
await builder.append(row1);
|
|
160
169
|
await builder.append(row2);
|
|
161
170
|
```
|
|
162
171
|
|
|
163
172
|
---
|
|
164
173
|
|
|
165
|
-
## 🛠️
|
|
166
|
-
|
|
167
|
-
### Simple Repository Pattern
|
|
168
|
-
|
|
169
|
-
```typescript
|
|
170
|
-
import type { InferInsert } from '@housekit/orm';
|
|
171
|
-
|
|
172
|
-
async insertEvents(events: InferInsert<typeof auditEvents>[]) {
|
|
173
|
-
return await db.insert(auditEvents).values(events);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Usage
|
|
177
|
-
await repository.insertEvents([
|
|
178
|
-
{ venueId: 'venue-1', ingredientId: 'ing-1', type: 'restock', quantity: 100, at: new Date() },
|
|
179
|
-
{ venueId: 'venue-2', ingredientId: 'ing-2', type: 'sale', quantity: -50, at: new Date(), referenceId: null }
|
|
180
|
-
]);
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
### Type Helpers
|
|
174
|
+
## 🛠️ Zero-Config Type Safety
|
|
184
175
|
|
|
185
|
-
|
|
186
|
-
import type { TableInsertArray, InferInsert } from '@housekit/orm';
|
|
187
|
-
|
|
188
|
-
// Using the direct helper
|
|
189
|
-
async insertEvents(events: InferInsert<typeof salesEvents>[]) {
|
|
190
|
-
return await db.insert(salesEvents).values(events);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Using explicit type helper
|
|
194
|
-
async insertEvents(events: TableInsertArray<typeof salesEvents>) {
|
|
195
|
-
return await db.insert(salesEvents).values(events);
|
|
196
|
-
}
|
|
197
|
-
```
|
|
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.
|
|
198
177
|
|
|
199
|
-
###
|
|
178
|
+
### In Repository Functions
|
|
200
179
|
|
|
201
180
|
```typescript
|
|
202
|
-
import {
|
|
203
|
-
import type { Infer } from '@housekit/orm';
|
|
181
|
+
import { webEvents, type NewWebEvent } from './schema';
|
|
204
182
|
|
|
205
|
-
function
|
|
206
|
-
|
|
183
|
+
async function logEvents(events: NewWebEvent[]) {
|
|
184
|
+
// Types match automatically
|
|
185
|
+
return await db.insert(webEvents).values(events);
|
|
207
186
|
}
|
|
208
187
|
```
|
|
209
188
|
|
|
210
|
-
**Note**: Autocomplete shows clean data types by default without exposing internal types.
|
|
211
|
-
|
|
212
189
|
---
|
|
213
190
|
|
|
214
191
|
## 🤝 Smart Relational API
|
|
@@ -216,27 +193,29 @@ function UserCard({ user }: { user: Infer<typeof users> }) {
|
|
|
216
193
|
Traditional ORMs produce "Flat Joins" that duplicate data (the Cartesian Product problem). HouseKit's Relational API uses ClickHouse's `groupArray` internally to fetch related data as nested arrays in a single, efficient query.
|
|
217
194
|
|
|
218
195
|
```typescript
|
|
196
|
+
// Define relations in your schema first
|
|
197
|
+
import { relations } from '@housekit/orm';
|
|
198
|
+
|
|
199
|
+
relations(users, ({ many }) => ({
|
|
200
|
+
posts: many(posts, { fields: [users.id], references: [posts.userId] })
|
|
201
|
+
}));
|
|
202
|
+
|
|
203
|
+
// Query with nested data
|
|
219
204
|
const usersWithData = await db.query.users.findMany({
|
|
205
|
+
where: { country: 'US' }, // Object syntax
|
|
220
206
|
with: {
|
|
221
207
|
posts: {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
225
|
-
profile: true
|
|
208
|
+
limit: 5,
|
|
209
|
+
orderBy: { col: posts.createdAt, dir: 'DESC' }
|
|
210
|
+
}
|
|
226
211
|
},
|
|
227
212
|
limit: 10
|
|
228
213
|
});
|
|
229
214
|
|
|
230
215
|
// Result structure:
|
|
231
|
-
// [{ id: 1, name: 'Alice', posts: [{ title: '...', ... }]
|
|
216
|
+
// [{ id: 1, name: 'Alice', posts: [{ title: '...', ... }] }]
|
|
232
217
|
```
|
|
233
218
|
|
|
234
|
-
### Advanced Relational Engine
|
|
235
|
-
HouseKit's relational API is optimized for ClickHouse:
|
|
236
|
-
- **Filtered Relations**: Where clauses in `with` blocks are executed server-side using `groupUniqArrayIf`.
|
|
237
|
-
- **Nested Pagination**: Control the size of related collections with `limit` and `offset` directly in the relation config.
|
|
238
|
-
- **Smart Deduplication**: Merges results in-memory to handle row multiplication from complex joins.
|
|
239
|
-
|
|
240
219
|
---
|
|
241
220
|
|
|
242
221
|
## 🛠 SQL Utilities
|
|
@@ -250,7 +229,7 @@ const conditions = [
|
|
|
250
229
|
gte(users.age, 18)
|
|
251
230
|
];
|
|
252
231
|
|
|
253
|
-
const query = db.select()
|
|
232
|
+
const query = await db.select()
|
|
254
233
|
.from(users)
|
|
255
234
|
.where(sql.join(conditions, sql` AND `));
|
|
256
235
|
```
|
|
@@ -273,7 +252,9 @@ const matched = await db.select()
|
|
|
273
252
|
Essential for distributed setups to avoid local-data-only results on sharded clusters.
|
|
274
253
|
|
|
275
254
|
```typescript
|
|
276
|
-
db.select()
|
|
255
|
+
await db.select()
|
|
256
|
+
.from(distributedTable)
|
|
257
|
+
.globalJoin(rightTable, condition);
|
|
277
258
|
```
|
|
278
259
|
|
|
279
260
|
---
|
|
@@ -297,4 +278,4 @@ const db = await createClient({
|
|
|
297
278
|
|
|
298
279
|
## License
|
|
299
280
|
|
|
300
|
-
MIT © [Pablo Fernandez Ruiz](https://github.com/pablofdezr)
|
|
281
|
+
MIT © [Pablo Fernandez Ruiz](https://github.com/pablofdezr)
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import type { ClickHouseClient } from '@clickhouse/client';
|
|
2
2
|
import type { SQLExpression } from '../expressions';
|
|
3
|
-
import type { TableRuntime } from '../core';
|
|
3
|
+
import type { TableRuntime, InferSelectModel } from '../core';
|
|
4
4
|
export declare class ClickHouseDeleteBuilder<TTable extends TableRuntime<any, any>> {
|
|
5
5
|
private client;
|
|
6
6
|
private table;
|
|
7
7
|
private _where;
|
|
8
8
|
private _lastMutationId;
|
|
9
9
|
constructor(client: ClickHouseClient, table: TTable);
|
|
10
|
-
where(expression: SQLExpression
|
|
10
|
+
where(expression: SQLExpression | Partial<InferSelectModel<{
|
|
11
|
+
$columns: TTable['$columns'];
|
|
12
|
+
}>> | undefined | null): this;
|
|
11
13
|
toSQL(): {
|
|
12
14
|
query: string;
|
|
13
15
|
params: Record<string, unknown>;
|
|
@@ -219,10 +219,10 @@ export declare class ClickHouseQueryBuilder<TTable extends TableDefinition<any>
|
|
|
219
219
|
kind: "cte";
|
|
220
220
|
query?: string;
|
|
221
221
|
}>;
|
|
222
|
-
$inferSelect: InferSelectModel<{
|
|
222
|
+
readonly $inferSelect: InferSelectModel<{
|
|
223
223
|
$columns: TSelection extends SelectionShape ? { [K in keyof (TSelection extends infer T_1 ? T_1 extends TSelection ? T_1 extends SelectionShape ? SelectResult<T_1> : Record<string, any> : never : never)]: ClickHouseColumn<(TSelection extends infer T_2 ? T_2 extends TSelection ? T_2 extends SelectionShape ? SelectResult<T_2> : Record<string, any> : never : never)[K], true, false>; } : Record<string, ClickHouseColumn<any, true, false>>;
|
|
224
224
|
}>;
|
|
225
|
-
$inferInsert: import("..").TableInsert<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>>>;
|
|
225
|
+
readonly $inferInsert: import("..").TableInsert<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>>>;
|
|
226
226
|
} & (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>>);
|
|
227
227
|
register: (mainQuery: ClickHouseQueryBuilder<any, any, any>) => ClickHouseQueryBuilder<any, any, any>;
|
|
228
228
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ClickHouseClient } from '@clickhouse/client';
|
|
2
2
|
import type { SQLExpression } from '../expressions';
|
|
3
|
-
import { type TableRuntime } from '../core';
|
|
3
|
+
import { type TableRuntime, type InferSelectModel } from '../core';
|
|
4
4
|
export declare class ClickHouseUpdateBuilder<TTable extends TableRuntime<any, any>> {
|
|
5
5
|
private client;
|
|
6
6
|
private table;
|
|
@@ -9,7 +9,9 @@ export declare class ClickHouseUpdateBuilder<TTable extends TableRuntime<any, an
|
|
|
9
9
|
private _lastMutationId;
|
|
10
10
|
constructor(client: ClickHouseClient, table: TTable);
|
|
11
11
|
set(values: Record<string, any>): this;
|
|
12
|
-
where(expression: SQLExpression
|
|
12
|
+
where(expression: SQLExpression | Partial<InferSelectModel<{
|
|
13
|
+
$columns: TTable['$columns'];
|
|
14
|
+
}>> | undefined | null): this;
|
|
13
15
|
toSQL(): {
|
|
14
16
|
query: string;
|
|
15
17
|
params: Record<string, unknown>;
|
package/dist/index.js
CHANGED
|
@@ -1005,8 +1005,6 @@ function chTable(tableName, columns, options = {}) {
|
|
|
1005
1005
|
$table: tableName,
|
|
1006
1006
|
$columns: columns,
|
|
1007
1007
|
$options: finalOptions,
|
|
1008
|
-
$inferSelect: undefined,
|
|
1009
|
-
$inferInsert: undefined,
|
|
1010
1008
|
toSQL: () => {
|
|
1011
1009
|
if (finalOptions.externallyManaged) {
|
|
1012
1010
|
return "";
|
|
@@ -2949,7 +2947,7 @@ class ClickHouseQueryBuilder {
|
|
|
2949
2947
|
const columns = table.$columns || table;
|
|
2950
2948
|
for (const [key, value] of Object.entries(expression)) {
|
|
2951
2949
|
const column = table[key] || columns?.[key];
|
|
2952
|
-
if (column) {
|
|
2950
|
+
if (column && value !== undefined) {
|
|
2953
2951
|
chunks.push(eq(column, value));
|
|
2954
2952
|
}
|
|
2955
2953
|
}
|
|
@@ -4567,10 +4565,6 @@ class ClickHouseInsertBuilder {
|
|
|
4567
4565
|
this.table = table;
|
|
4568
4566
|
if (table.$options?.asyncInsert !== undefined) {
|
|
4569
4567
|
this._async = table.$options.asyncInsert;
|
|
4570
|
-
} else if (table.$options?.appendOnly) {
|
|
4571
|
-
this._async = true;
|
|
4572
|
-
} else {
|
|
4573
|
-
this._async = false;
|
|
4574
4568
|
}
|
|
4575
4569
|
}
|
|
4576
4570
|
values(value) {
|
|
@@ -4867,7 +4861,26 @@ class ClickHouseDeleteBuilder {
|
|
|
4867
4861
|
this.table = table;
|
|
4868
4862
|
}
|
|
4869
4863
|
where(expression) {
|
|
4870
|
-
|
|
4864
|
+
if (!expression)
|
|
4865
|
+
return this;
|
|
4866
|
+
if (typeof expression === "object" && "toSQL" in expression) {
|
|
4867
|
+
this._where = expression;
|
|
4868
|
+
return this;
|
|
4869
|
+
}
|
|
4870
|
+
if (typeof expression === "object") {
|
|
4871
|
+
const chunks = [];
|
|
4872
|
+
for (const [key, value] of Object.entries(expression)) {
|
|
4873
|
+
const column = this.table.$columns[key];
|
|
4874
|
+
if (column && value !== undefined) {
|
|
4875
|
+
chunks.push(eq(column, value));
|
|
4876
|
+
}
|
|
4877
|
+
}
|
|
4878
|
+
if (chunks.length > 0) {
|
|
4879
|
+
const combined = chunks.length === 1 ? chunks[0] : and(...chunks);
|
|
4880
|
+
if (combined)
|
|
4881
|
+
this._where = combined;
|
|
4882
|
+
}
|
|
4883
|
+
}
|
|
4871
4884
|
return this;
|
|
4872
4885
|
}
|
|
4873
4886
|
toSQL() {
|
|
@@ -4964,7 +4977,26 @@ class ClickHouseUpdateBuilder {
|
|
|
4964
4977
|
return this;
|
|
4965
4978
|
}
|
|
4966
4979
|
where(expression) {
|
|
4967
|
-
|
|
4980
|
+
if (!expression)
|
|
4981
|
+
return this;
|
|
4982
|
+
if (typeof expression === "object" && "toSQL" in expression) {
|
|
4983
|
+
this._where = expression;
|
|
4984
|
+
return this;
|
|
4985
|
+
}
|
|
4986
|
+
if (typeof expression === "object") {
|
|
4987
|
+
const chunks = [];
|
|
4988
|
+
for (const [key, value] of Object.entries(expression)) {
|
|
4989
|
+
const column = this.table.$columns[key];
|
|
4990
|
+
if (column && value !== undefined) {
|
|
4991
|
+
chunks.push(eq(column, value));
|
|
4992
|
+
}
|
|
4993
|
+
}
|
|
4994
|
+
if (chunks.length > 0) {
|
|
4995
|
+
const combined = chunks.length === 1 ? chunks[0] : and(...chunks);
|
|
4996
|
+
if (combined)
|
|
4997
|
+
this._where = combined;
|
|
4998
|
+
}
|
|
4999
|
+
}
|
|
4968
5000
|
return this;
|
|
4969
5001
|
}
|
|
4970
5002
|
toSQL() {
|
package/dist/table.d.ts
CHANGED
|
@@ -112,10 +112,10 @@ export type TableDefinition<TCols extends TableColumns, TOptions = TableOptions>
|
|
|
112
112
|
toSQL(): string;
|
|
113
113
|
toSQLs?(): string[];
|
|
114
114
|
as(alias: string): TableDefinition<TCols, TOptions>;
|
|
115
|
-
$inferSelect: InferSelectModel<{
|
|
115
|
+
readonly $inferSelect: InferSelectModel<{
|
|
116
116
|
$columns: TCols;
|
|
117
117
|
}>;
|
|
118
|
-
$inferInsert: InferInsertModel<{
|
|
118
|
+
readonly $inferInsert: InferInsertModel<{
|
|
119
119
|
$columns: TCols;
|
|
120
120
|
}>;
|
|
121
121
|
} & TCols;
|
|
@@ -127,8 +127,8 @@ export type TableRuntime<TInsert = any, TSelect = any, TOptions = TableOptions>
|
|
|
127
127
|
toSQL(): string;
|
|
128
128
|
toSQLs?(): string[];
|
|
129
129
|
as(alias: string): TableRuntime<TInsert, TSelect, TOptions>;
|
|
130
|
-
$inferSelect: TSelect;
|
|
131
|
-
$inferInsert: TInsert;
|
|
130
|
+
readonly $inferSelect: TSelect;
|
|
131
|
+
readonly $inferInsert: TInsert;
|
|
132
132
|
};
|
|
133
133
|
export interface VersionedMeta {
|
|
134
134
|
baseName: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@housekit/orm",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.23",
|
|
4
4
|
"description": "Type-safe ClickHouse ORM with modern DX and ClickHouse-specific optimizations. Features Turbo Mode (RowBinary), full engine support, and advanced query capabilities.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|