@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
@@ -2,10 +2,6 @@ import * as ops from './expressions';
2
2
  import * as cond from './modules/conditional';
3
3
  import { ClickHouseColumn, type RelationDefinition, type TableDefinition, type CleanSelect } from './core';
4
4
  import type { SQLExpression } from './expressions';
5
- /**
6
- * Internal map of SQL operators provided to relational query callbacks.
7
- * These match the standard HouseKit operators but are bundled for ergonomic access.
8
- */
9
5
  declare const operators: {
10
6
  eq: typeof ops.eq;
11
7
  ne: typeof ops.ne;
@@ -37,16 +33,7 @@ type RelationsOf<TTable> = TTable extends {
37
33
  } ? R : {};
38
34
  type NormalizedRelations<TTable> = RelationsOf<TTable> extends Record<string, RelationDefinition<any>> ? RelationsOf<TTable> : {};
39
35
  type RelationTarget<T> = T extends RelationDefinition<infer TTarget> ? TTarget : never;
40
- /**
41
- * Join strategy for relational queries.
42
- *
43
- * ClickHouse performance varies significantly depending on the join strategy used,
44
- * especially in distributed environments.
45
- */
46
36
  export type JoinStrategy = 'auto' | 'standard' | 'global' | 'any' | 'global_any';
47
- /**
48
- * Configuration options for the Relational Query API (`findMany`, `findFirst`).
49
- */
50
37
  export type RelationalWith<TTable> = [
51
38
  keyof NormalizedRelations<TTable>
52
39
  ] extends [never] ? Record<string, boolean | RelationalFindOptions<any>> : {
@@ -68,66 +55,17 @@ type WhereObject<TTable> = TTable extends TableDefinition<infer TCols> ? {
68
55
  [K in keyof TCols]?: TCols[K] extends ClickHouseColumn<infer T> ? T | SQLExpression : any;
69
56
  } : Record<string, any>;
70
57
  export type RelationalFindOptions<TTable = any> = {
71
- /**
72
- * Filter rows.
73
- *
74
- * @example
75
- * // Object syntax (simplest)
76
- * where: { email: 'a@b.com' }
77
- * where: { role: 'admin', active: true }
78
- *
79
- * // Direct expression
80
- * where: eq(users.active, true)
81
- *
82
- * // Callback for complex filters
83
- * where: (cols, { eq, and, gt }) => and(eq(cols.role, 'admin'), gt(cols.age, 18))
84
- */
85
58
  where?: WhereObject<TTable> | SQLExpression | ((columns: TTable extends TableDefinition<infer TCols> ? TCols : any, ops: typeof operators) => SQLExpression);
86
- /** Max rows to return */
87
59
  limit?: number;
88
- /**
89
- * Select specific columns. By default all columns are returned.
90
- *
91
- * @example
92
- * columns: { id: true, email: true }
93
- */
94
60
  columns?: TTable extends TableDefinition<infer TCols> ? {
95
61
  [K in keyof TCols]?: boolean;
96
62
  } : Record<string, boolean>;
97
- /** Rows to skip */
98
63
  offset?: number;
99
- /**
100
- * Sort results. Accepts direct value, array, or callback.
101
- *
102
- * @example
103
- * // Direct
104
- * orderBy: desc(users.createdAt)
105
- *
106
- * // Array
107
- * orderBy: [desc(users.createdAt), asc(users.name)]
108
- *
109
- * // Callback
110
- * orderBy: (cols, { desc }) => desc(cols.createdAt)
111
- */
112
64
  orderBy?: OrderByValue | OrderByValue[] | ((columns: TTable extends TableDefinition<infer TCols> ? TCols : any, fns: {
113
65
  asc: typeof ops.asc;
114
66
  desc: typeof ops.desc;
115
67
  }) => OrderByValue | OrderByValue[]);
116
- /**
117
- * Include related data. Use `true` for all columns or an object for filtering.
118
- *
119
- * @example
120
- * with: {
121
- * posts: true, // All posts
122
- * comments: { limit: 5 }, // Latest 5 comments
123
- * profile: { where: eq(profile.public, true) } // Only public profile
124
- * }
125
- */
126
68
  with?: RelationalWith<TTable>;
127
- /**
128
- * Join strategy for distributed clusters.
129
- * @default 'auto'
130
- */
131
69
  joinStrategy?: JoinStrategy;
132
70
  };
133
71
  type OrderByValue = {
@@ -136,30 +74,10 @@ type OrderByValue = {
136
74
  };
137
75
  export type RelationalAPI<TSchema extends Record<string, TableDefinition<any>>> = {
138
76
  [K in keyof TSchema]: {
139
- /** Find multiple records with optional filtering, pagination, and relations */
140
77
  findMany: <TOpts extends RelationalFindOptions<TSchema[K]> | undefined>(opts?: TOpts) => Promise<Array<RelationalResult<TSchema[K], TOpts extends RelationalFindOptions<TSchema[K]> ? TOpts['with'] : undefined>>>;
141
- /** Find a single record (first match) */
142
78
  findFirst: <TOpts extends RelationalFindOptions<TSchema[K]> | undefined>(opts?: TOpts) => Promise<RelationalResult<TSchema[K], TOpts extends RelationalFindOptions<TSchema[K]> ? TOpts['with'] : undefined> | null>;
143
- /**
144
- * Find a record by its primary key
145
- * @example
146
- * const user = await db.query.users.findById('uuid-here')
147
- * const user = await db.query.users.findById('uuid', { with: { posts: true } })
148
- */
149
79
  findById: <TOpts extends Omit<RelationalFindOptions<TSchema[K]>, 'where' | 'limit'> | undefined>(id: string | number, opts?: TOpts) => Promise<RelationalResult<TSchema[K], TOpts extends RelationalFindOptions<TSchema[K]> ? TOpts['with'] : undefined> | null>;
150
80
  };
151
81
  };
152
- /**
153
- * Build a modern relational API with ClickHouse-specific optimizations.
154
- *
155
- * Key architectural features:
156
- * - **groupUniqArray Optimization**: Instead of flat-joining which causes row explosion,
157
- * HouseKit uses ClickHouse aggregation to fetch nested relations as arrays of tuples.
158
- * - **Automatic Cluster Handling**: Detects sharded tables and applies GLOBAL JOINs.
159
- * - **Smart Deduplication**: Merges results in-memory when flat joins are unavoidable.
160
- *
161
- * @param client - The ClickHouse client instance.
162
- * @param schema - The shared schema definition containing all table and relation metadata.
163
- */
164
82
  export declare function buildRelationalAPI<TSchema extends Record<string, TableDefinition<any>>>(client: any, schema?: TSchema): RelationalAPI<TSchema> | undefined;
165
83
  export {};
@@ -0,0 +1,290 @@
1
+ import { ClickHouseQueryBuilder } from './builders/select';
2
+ import * as ops from './expressions';
3
+ import * as cond from './modules/conditional';
4
+ const operators = {
5
+ eq: ops.eq,
6
+ ne: ops.ne,
7
+ gt: ops.gt,
8
+ gte: ops.gte,
9
+ lt: ops.lt,
10
+ lte: ops.lte,
11
+ inArray: ops.inArray,
12
+ notInArray: ops.notInArray,
13
+ between: ops.between,
14
+ notBetween: ops.notBetween,
15
+ has: ops.has,
16
+ hasAll: ops.hasAll,
17
+ hasAny: ops.hasAny,
18
+ sql: ops.sql,
19
+ and: cond.and,
20
+ or: cond.or,
21
+ not: cond.not,
22
+ isNull: cond.isNull,
23
+ isNotNull: cond.isNotNull,
24
+ asc: ops.asc,
25
+ desc: ops.desc,
26
+ };
27
+ function buildJoinCondition(fields, references) {
28
+ if (!fields || !references || fields.length === 0 || references.length === 0)
29
+ return null;
30
+ const pairs = fields.map((f, i) => ops.sql `${f} = ${references[i]}`);
31
+ const filtered = pairs.filter((p) => Boolean(p));
32
+ if (filtered.length === 0)
33
+ return null;
34
+ if (filtered.length === 1)
35
+ return filtered[0];
36
+ return cond.and(...filtered) || null;
37
+ }
38
+ function isDistributedTable(tableDef) {
39
+ const options = tableDef.$options;
40
+ return !!(options?.onCluster || options?.shardKey);
41
+ }
42
+ export function buildRelationalAPI(client, schema) {
43
+ if (!schema)
44
+ return undefined;
45
+ const api = {};
46
+ for (const [tableKey, tableDef] of Object.entries(schema)) {
47
+ api[tableKey] = {
48
+ findMany: async (opts) => {
49
+ let builder = new ClickHouseQueryBuilder(client).from(tableDef);
50
+ const selectedColumns = opts?.columns;
51
+ const baseColumns = selectedColumns
52
+ ? Object.entries(tableDef.$columns).filter(([key]) => selectedColumns[key] === true)
53
+ : Object.entries(tableDef.$columns);
54
+ const relations = tableDef.$relations;
55
+ const requestedTopLevel = opts?.with ? Object.entries(opts.with).filter(([, v]) => v) : [];
56
+ const requestedRelations = requestedTopLevel
57
+ .map(([relName, val]) => {
58
+ const rel = relations?.[relName];
59
+ return { relName, rel, options: typeof val === 'object' ? val : {} };
60
+ })
61
+ .filter((r) => Boolean(r.rel));
62
+ const needsGrouping = requestedRelations.length > 0;
63
+ const allColumns = Object.entries(tableDef.$columns);
64
+ const groupByColumns = needsGrouping ? allColumns.map(([, col]) => col) : [];
65
+ const joinStrategy = opts?.joinStrategy || 'auto';
66
+ const isDistributed = isDistributedTable(tableDef);
67
+ const useGlobal = joinStrategy === 'global' || joinStrategy === 'global_any' || (joinStrategy === 'auto' && isDistributed);
68
+ const useAny = joinStrategy === 'any' || joinStrategy === 'global_any';
69
+ function buildNestedSelection(currentTableDef, currentWith, prefix = '', outerJoinStrategy, outerUseGlobal, outerUseAny, columnsFilter) {
70
+ let currentSelection = {};
71
+ let currentJoins = [];
72
+ Object.entries(currentTableDef.$columns).forEach(([key, col]) => {
73
+ if (!columnsFilter || columnsFilter[key] === true) {
74
+ currentSelection[`${prefix}${key}`] = col;
75
+ }
76
+ });
77
+ if (!currentWith)
78
+ return { selection: currentSelection, joins: currentJoins };
79
+ const currentRelations = currentTableDef.$relations;
80
+ const requestedNested = Object.entries(currentWith)
81
+ .map(([relName, val]) => {
82
+ const rel = currentRelations?.[relName];
83
+ return { relName, rel, options: typeof val === 'object' ? val : {} };
84
+ })
85
+ .filter((r) => Boolean(r.rel));
86
+ for (const { relName, rel, options } of requestedNested) {
87
+ const newPrefix = prefix ? `${prefix}_${relName}_` : `${relName}_`;
88
+ let relWhere = null;
89
+ if (options.where) {
90
+ if (typeof options.where === 'function') {
91
+ relWhere = options.where(rel.table.$columns, operators);
92
+ }
93
+ else if (typeof options.where === 'object' && !('toSQL' in options.where)) {
94
+ const conditions = Object.entries(options.where).map(([key, value]) => {
95
+ const col = rel.table.$columns[key];
96
+ if (!col)
97
+ throw new Error(`Column '${key}' not found in relation '${relName}'`);
98
+ return ops.eq(col, value);
99
+ });
100
+ relWhere = conditions.length === 1 ? conditions[0] : cond.and(...conditions);
101
+ }
102
+ else {
103
+ relWhere = options.where;
104
+ }
105
+ }
106
+ let joinCondition = buildJoinCondition(rel.fields, rel.references);
107
+ if (joinCondition) {
108
+ const joinType = (() => {
109
+ const relIsDistributed = isDistributedTable(rel.table);
110
+ const useRelGlobal = outerUseGlobal || (outerJoinStrategy === 'auto' && relIsDistributed);
111
+ if (useRelGlobal && outerUseAny)
112
+ return builder.globalAnyJoin;
113
+ if (useRelGlobal)
114
+ return builder.globalLeftJoin;
115
+ if (outerUseAny)
116
+ return builder.anyLeftJoin;
117
+ return builder.leftJoin;
118
+ })();
119
+ if (rel.relation === 'many' && !prefix) {
120
+ const relCols = Object.entries(rel.table.$columns);
121
+ const tupleArgs = relCols.map(([, col]) => ops.sql `${col}`);
122
+ if (relWhere) {
123
+ currentSelection[relName] = ops.sql `groupUniqArrayIf(tuple(${ops.sql.join(tupleArgs, ops.sql `, `)}), ${relWhere})`;
124
+ }
125
+ else {
126
+ currentSelection[relName] = ops.sql `groupUniqArray(tuple(${ops.sql.join(tupleArgs, ops.sql `, `)}))`;
127
+ }
128
+ }
129
+ else if (relWhere) {
130
+ joinCondition = cond.and(joinCondition, relWhere);
131
+ }
132
+ currentJoins.push({ type: joinType, table: rel.table, on: joinCondition });
133
+ }
134
+ const nestedResult = buildNestedSelection(rel.table, options.with, newPrefix, outerJoinStrategy, outerUseGlobal, outerUseAny, options.columns);
135
+ if (!(rel.relation === 'many' && !prefix)) {
136
+ Object.assign(currentSelection, nestedResult.selection);
137
+ }
138
+ currentJoins.push(...nestedResult.joins);
139
+ }
140
+ return { selection: currentSelection, joins: currentJoins };
141
+ }
142
+ const { selection: flatSelection, joins: allJoins } = buildNestedSelection(tableDef, opts?.with, '', joinStrategy, useGlobal, useAny, opts?.columns);
143
+ for (const joinDef of allJoins) {
144
+ builder = joinDef.type.call(builder, joinDef.table, joinDef.on);
145
+ }
146
+ builder = builder.select(flatSelection);
147
+ if (needsGrouping && groupByColumns.length > 0) {
148
+ builder = builder.groupBy(...groupByColumns);
149
+ }
150
+ if (opts?.where) {
151
+ let whereValue;
152
+ if (typeof opts.where === 'function') {
153
+ whereValue = opts.where(tableDef.$columns, operators);
154
+ }
155
+ else if (opts.where && typeof opts.where === 'object' && !('toSQL' in opts.where)) {
156
+ const conditions = Object.entries(opts.where).map(([key, value]) => {
157
+ const col = tableDef.$columns[key];
158
+ if (!col)
159
+ throw new Error(`Column '${key}' not found in table`);
160
+ return ops.eq(col, value);
161
+ });
162
+ whereValue = conditions.length === 1 ? conditions[0] : cond.and(...conditions);
163
+ }
164
+ else {
165
+ whereValue = opts.where;
166
+ }
167
+ builder = builder.where(whereValue);
168
+ }
169
+ if (opts?.orderBy) {
170
+ const orderByFns = { asc: ops.asc, desc: ops.desc };
171
+ const orderByValue = typeof opts.orderBy === 'function'
172
+ ? opts.orderBy(tableDef.$columns, orderByFns)
173
+ : opts.orderBy;
174
+ const orderByArray = Array.isArray(orderByValue) ? orderByValue : [orderByValue];
175
+ for (const ob of orderByArray) {
176
+ builder = builder.orderBy(ob.col, ob.dir);
177
+ }
178
+ }
179
+ if (opts?.limit)
180
+ builder = builder.limit(opts.limit);
181
+ if (opts?.offset)
182
+ builder = builder.offset(opts.offset);
183
+ const rows = await builder.then();
184
+ function reconstructNested(row, currentTableDef, currentWith, prefix = '') {
185
+ const result = {};
186
+ Object.entries(currentTableDef.$columns).forEach(([key]) => {
187
+ result[key] = row[`${prefix}${key}`];
188
+ });
189
+ if (!currentWith)
190
+ return result;
191
+ const currentRelations = currentTableDef.$relations;
192
+ Object.entries(currentWith).filter(([, v]) => v).forEach(([relName, val]) => {
193
+ const rel = currentRelations?.[relName];
194
+ if (!rel)
195
+ return;
196
+ const options = typeof val === 'object' ? val : {};
197
+ const newPrefix = prefix ? `${prefix}_${relName}_` : `${relName}_`;
198
+ if (rel.relation === 'one') {
199
+ const relatedData = reconstructNested(row, rel.table, options.with, newPrefix);
200
+ const allNull = Object.values(relatedData).every(v => v === null || v === undefined);
201
+ result[relName] = allNull ? null : relatedData;
202
+ }
203
+ else {
204
+ const rawVal = row[relName];
205
+ if (Array.isArray(rawVal)) {
206
+ const relCols = Object.keys(rel.table.$columns);
207
+ let items = rawVal.map((tuple) => {
208
+ const obj = {};
209
+ let allNull = true;
210
+ relCols.forEach((colKey, i) => {
211
+ const v = tuple[i];
212
+ if (v !== null && v !== undefined)
213
+ allNull = false;
214
+ obj[colKey] = v;
215
+ });
216
+ return allNull ? null : obj;
217
+ }).filter(Boolean);
218
+ if (options.offset)
219
+ items = items.slice(options.offset);
220
+ if (options.limit)
221
+ items = items.slice(0, options.limit);
222
+ result[relName] = items;
223
+ }
224
+ else {
225
+ const relatedData = reconstructNested(row, rel.table, options.with, newPrefix);
226
+ const allNull = Object.values(relatedData).every(v => v === null || v === undefined);
227
+ result[relName] = allNull ? [] : [relatedData];
228
+ }
229
+ }
230
+ });
231
+ return result;
232
+ }
233
+ const results = rows.map((row) => reconstructNested(row, tableDef, opts?.with));
234
+ if (needsGrouping) {
235
+ const grouped = new Map();
236
+ const pkCols = Array.isArray(tableDef.$options.primaryKey)
237
+ ? tableDef.$options.primaryKey
238
+ : [tableDef.$options.primaryKey || Object.keys(tableDef.$columns)[0]];
239
+ for (const item of results) {
240
+ const id = pkCols.map((col) => {
241
+ const val = item[col];
242
+ return val instanceof Date ? val.getTime() : String(val);
243
+ }).join('|');
244
+ if (!grouped.has(id)) {
245
+ grouped.set(id, item);
246
+ }
247
+ else {
248
+ const existing = grouped.get(id);
249
+ requestedRelations.forEach(({ relName, rel, options }) => {
250
+ if (rel.relation === 'many' && Array.isArray(item[relName])) {
251
+ for (const newItem of item[relName]) {
252
+ const isDuplicate = existing[relName].some((x) => JSON.stringify(x) === JSON.stringify(newItem));
253
+ if (!isDuplicate)
254
+ existing[relName].push(newItem);
255
+ }
256
+ if (options.limit)
257
+ existing[relName] = existing[relName].slice(0, options.limit);
258
+ }
259
+ });
260
+ }
261
+ }
262
+ return Array.from(grouped.values());
263
+ }
264
+ return results;
265
+ },
266
+ findFirst: async (opts) => {
267
+ const rows = await api[tableKey].findMany({ ...opts, limit: 1 });
268
+ return rows[0] ?? null;
269
+ },
270
+ findById: async (id, opts) => {
271
+ const pkOption = tableDef.$options?.primaryKey;
272
+ const pkCols = pkOption
273
+ ? (Array.isArray(pkOption) ? pkOption : [pkOption])
274
+ : [Object.keys(tableDef.$columns)[0]];
275
+ const pkColName = pkCols[0];
276
+ const pkColumn = tableDef.$columns[pkColName];
277
+ if (!pkColumn) {
278
+ throw new Error(`Primary key column '${pkColName}' not found in table '${tableKey}'`);
279
+ }
280
+ const rows = await api[tableKey].findMany({
281
+ ...opts,
282
+ where: ops.eq(pkColumn, id),
283
+ limit: 1
284
+ });
285
+ return rows[0] ?? null;
286
+ }
287
+ };
288
+ }
289
+ return api;
290
+ }
@@ -0,0 +1,21 @@
1
+ export function relations(table, callback) {
2
+ const helpers = {
3
+ one: (relTable, config) => ({
4
+ relation: 'one',
5
+ name: relTable.$table,
6
+ table: relTable,
7
+ fields: config.fields,
8
+ references: config.references
9
+ }),
10
+ many: (relTable, config = {}) => ({
11
+ relation: 'many',
12
+ name: relTable.$table,
13
+ table: relTable,
14
+ fields: config.fields,
15
+ references: config.references
16
+ })
17
+ };
18
+ const defs = callback(helpers);
19
+ table.$relations = defs;
20
+ return defs;
21
+ }
@@ -1,23 +1,3 @@
1
- /**
2
- * HouseKit Schema Builder - Fluent API for Table Definitions
3
- *
4
- * This module provides a modern fluent syntax for defining tables using
5
- * a builder function pattern instead of individual imports.
6
- *
7
- * @example
8
- * ```typescript
9
- * import { defineTable, t } from '@housekit/orm';
10
- *
11
- * // Using the builder function pattern
12
- * export const users = defineTable('users', (t) => ({
13
- * id: t.uuid('id').primaryKey(),
14
- * name: t.string('name'),
15
- * email: t.string('email'),
16
- * age: t.int32('age').nullable(),
17
- * createdAt: t.datetime('created_at').default('now()'),
18
- * }), { engine: Engine.MergeTree(), orderBy: 'createdAt' });
19
- * ```
20
- */
21
1
  import { ClickHouseColumn } from './column';
22
2
  import { chView, type TableOptions, type TableDefinition, type RelationDefinition, index, projection } from './table';
23
3
  import { chMaterializedView, detectMaterializedViewDrift, extractMVQuery, createMigrationBridge, generateBlueGreenMigration } from './materialized-views';
@@ -26,46 +6,17 @@ import { chDictionary } from './dictionary';
26
6
  import { chProjection } from './materialized-views';
27
7
  import { EngineConfiguration } from './engines';
28
8
  export { index };
29
- /**
30
- * Enhanced table options with column key references for orderBy, partitionBy, etc.
31
- */
32
9
  export type EnhancedTableOptions<TColKeys extends string = string> = Omit<TableOptions, 'orderBy' | 'partitionBy' | 'primaryKey' | 'sampleBy' | 'deduplicateBy' | 'versionColumn' | 'logicalPrimaryKey' | 'engine'> & {
33
10
  engine: EngineConfiguration;
34
11
  customEngine?: string;
35
- /**
36
- * Column(s) for ORDER BY. Can use column keys from your definition.
37
- * @example orderBy: 'timestamp' or orderBy: ['user_id', 'timestamp']
38
- */
39
12
  orderBy?: TColKeys | TColKeys[] | string | string[];
40
- /**
41
- * Column(s) for PARTITION BY. Can use column keys from your definition.
42
- */
43
13
  partitionBy?: TColKeys | TColKeys[] | string | string[];
44
- /**
45
- * Column(s) for PRIMARY KEY. Can use column keys from your definition.
46
- */
47
14
  primaryKey?: TColKeys | TColKeys[] | string | string[];
48
- /**
49
- * Column(s) for SAMPLE BY. Can use column keys from your definition.
50
- */
51
15
  sampleBy?: TColKeys | TColKeys[] | string | string[];
52
- /**
53
- * Column(s) for deduplication (ReplacingMergeTree).
54
- */
55
16
  deduplicateBy?: TColKeys | TColKeys[] | string | string[];
56
- /**
57
- * Version column for ReplacingMergeTree.
58
- */
59
17
  versionColumn?: TColKeys | string;
60
- /**
61
- * Logical primary key (for documentation, not enforced).
62
- */
63
18
  logicalPrimaryKey?: TColKeys | TColKeys[] | string | string[];
64
19
  };
65
- /**
66
- * ClickHouse data types builder.
67
- * All columns are NOT NULL by default, following ClickHouse philosophy.
68
- */
69
20
  declare function primaryUuid(): {
70
21
  id: ClickHouseColumn<string, true, true>;
71
22
  };
@@ -82,10 +33,6 @@ export declare const t: {
82
33
  int8: (name: string) => ClickHouseColumn<number, true, false>;
83
34
  int16: (name: string) => ClickHouseColumn<number, true, false>;
84
35
  integer: (name: string) => ClickHouseColumn<number, true, false>;
85
- /**
86
- * Int32 type. Signed 32-bit integer.
87
- * @range -2147483648 to 2147483647
88
- */
89
36
  int32: (name: string) => ClickHouseColumn<number, true, false>;
90
37
  int64: (name: string) => ClickHouseColumn<number, true, false>;
91
38
  int128: (name: string) => ClickHouseColumn<number, true, false>;
@@ -114,10 +61,6 @@ export declare const t: {
114
61
  date: (name: string) => ClickHouseColumn<string | Date, true, false>;
115
62
  date32: (name: string) => ClickHouseColumn<string | Date, true, false>;
116
63
  timestamp: (name: string, timezone?: string) => ClickHouseColumn<string | Date, true, false>;
117
- /**
118
- * DateTime type. Stores date and time.
119
- * @param timezone - Optional. Example: 'UTC', 'Europe/Madrid'
120
- */
121
64
  datetime: (name: string, timezone?: string) => ClickHouseColumn<string | Date, true, false>;
122
65
  datetime64: (name: string, precision?: number, timezone?: string) => ClickHouseColumn<string | Date, true, false>;
123
66
  boolean: (name: string) => ClickHouseColumn<boolean, true, false>;
@@ -131,11 +74,6 @@ export declare const t: {
131
74
  nested: (name: string, fields: Record<string, string>) => ClickHouseColumn<any, true, false>;
132
75
  json: <TSchema = Record<string, any>>(name: string) => ClickHouseColumn<TSchema, false, false>;
133
76
  dynamic: (name: string, maxTypes?: number) => ClickHouseColumn<any, true, false>;
134
- /**
135
- * LowCardinality type. Optimizes columns with few unique values
136
- * (typically < 10,000) for ultra-fast reading.
137
- * @see https://clickhouse.com/docs/en/sql-reference/data-types/lowcardinality
138
- */
139
77
  lowCardinality: <T, TNotNull extends boolean, TAutoGenerated extends boolean>(col: ClickHouseColumn<T, TNotNull, TAutoGenerated>) => ClickHouseColumn<T, TNotNull, TAutoGenerated>;
140
78
  aggregateFunction: (name: string, funcName: string, ...argTypes: string[]) => ClickHouseColumn<any, true, false>;
141
79
  simpleAggregateFunction: (name: string, funcName: string, argType: string) => ClickHouseColumn<any, true, false>;
@@ -144,44 +82,19 @@ export declare const t: {
144
82
  polygon: (name: string) => ClickHouseColumn<[number, number][][], true, false>;
145
83
  multiPolygon: (name: string) => ClickHouseColumn<[number, number][][][], true, false>;
146
84
  enum: (name: string, values: readonly string[]) => ClickHouseColumn<string, false, false>;
147
- /**
148
- * Adds 'created_at' and 'updated_at' columns with default now().
149
- */
150
85
  timestamps: () => {
151
86
  created_at: ClickHouseColumn<string | Date, true, true>;
152
87
  updated_at: ClickHouseColumn<string | Date, true, true>;
153
88
  };
154
- /**
155
- * Adds a standard UUID primary key column (default: 'id').
156
- */
157
89
  primaryUuid: typeof primaryUuid;
158
- /**
159
- * Adds a UUID v7 primary key column (default: 'id').
160
- */
161
90
  primaryUuidV7: typeof primaryUuidV7;
162
- /**
163
- * Adds 'is_deleted' and 'deleted_at' columns for soft deletes.
164
- */
165
91
  softDeletes: () => {
166
92
  is_deleted: ClickHouseColumn<boolean, true, true>;
167
93
  deleted_at: ClickHouseColumn<string | Date, false, false>;
168
94
  };
169
95
  };
170
96
  export type ColumnBuilder = typeof t;
171
- /**
172
- * Define a strongly-typed ClickHouse table.
173
- *
174
- * @param name - Physical table name in ClickHouse.
175
- * @param columns - Column definition object or callback using `t` builder.
176
- * @param options - Engine configuration, sorting keys, partitioning, etc.
177
- */
178
97
  export declare function defineTable<T extends Record<string, ClickHouseColumn<any, any, any>>>(tableName: string, columnsOrCallback: T | ((t: ColumnBuilder) => T), options: EnhancedTableOptions<keyof T & string>): TableDefinition<T, TableOptions>;
179
- /**
180
- * Aliases for modern API - providing both short and explicit naming.
181
- *
182
- * NOTE: defineTable is the preferred explicit naming for library consistency,
183
- * while 'table' is provided as a shorthand similar to other ORMs.
184
- */
185
98
  export declare const table: typeof defineTable;
186
99
  export declare const view: typeof chView;
187
100
  export declare const defineView: typeof chView;
@@ -191,9 +104,6 @@ export declare const dictionary: typeof chDictionary;
191
104
  export declare const defineDictionary: typeof chDictionary;
192
105
  export { projection };
193
106
  export { chProjection as defineProjection };
194
- /**
195
- * Define relations for a table using a callback pattern.
196
- */
197
107
  export declare function relations<TTable extends TableDefinition<any>, TRelations extends Record<string, RelationDefinition<any>>>(table: TTable, relationsBuilder: (helpers: {
198
108
  one: <TTarget extends TableDefinition<any>>(table: TTarget, config: {
199
109
  fields: ClickHouseColumn<any, any, any>[];