@quark-fw/clisma 0.1.26 → 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.
Files changed (61) hide show
  1. package/dist/src/assert-replicated-schema.d.ts +2 -0
  2. package/dist/src/assert-replicated-schema.js +16 -0
  3. package/dist/src/build-count-query.d.ts +4 -0
  4. package/dist/src/build-count-query.js +13 -0
  5. package/dist/src/build-create-dictionary-query.d.ts +2 -0
  6. package/dist/src/build-create-dictionary-query.js +51 -0
  7. package/dist/src/build-delete-query.d.ts +4 -0
  8. package/dist/src/build-delete-query.js +11 -0
  9. package/dist/src/build-find-many-query.d.ts +3 -0
  10. package/dist/src/build-find-many-query.js +58 -0
  11. package/dist/src/build-index-sql.d.ts +2 -0
  12. package/dist/src/build-index-sql.js +11 -0
  13. package/dist/src/build-lightweight-delete-query.d.ts +4 -0
  14. package/dist/src/build-lightweight-delete-query.js +11 -0
  15. package/dist/src/build-update-query.d.ts +4 -0
  16. package/dist/src/build-update-query.js +13 -0
  17. package/dist/src/client.d.ts +36 -0
  18. package/dist/src/client.js +139 -0
  19. package/dist/src/column.d.ts +46 -0
  20. package/dist/src/column.js +79 -0
  21. package/dist/src/ddl-shared.d.ts +13 -0
  22. package/dist/src/ddl-shared.js +54 -0
  23. package/dist/src/ddl.d.ts +5 -0
  24. package/dist/src/ddl.js +52 -0
  25. package/dist/src/dictionary.d.ts +26 -0
  26. package/dist/src/dictionary.js +7 -0
  27. package/dist/src/engine.d.ts +16 -0
  28. package/dist/src/engine.js +26 -0
  29. package/dist/src/ensure-dictionaries-on-nodes.d.ts +3 -0
  30. package/dist/src/ensure-dictionaries-on-nodes.js +15 -0
  31. package/dist/src/ensure-tables-on-nodes.d.ts +11 -0
  32. package/dist/src/ensure-tables-on-nodes.js +39 -0
  33. package/dist/src/escape-id.d.ts +1 -0
  34. package/dist/src/escape-id.js +4 -0
  35. package/dist/src/expr.d.ts +5 -0
  36. package/dist/src/expr.js +4 -0
  37. package/dist/src/index.d.ts +17 -0
  38. package/dist/src/index.js +15 -0
  39. package/dist/src/migrate.d.ts +14 -0
  40. package/dist/src/migrate.js +91 -0
  41. package/dist/src/migration-error.d.ts +10 -0
  42. package/dist/src/migration-error.js +24 -0
  43. package/dist/src/query.d.ts +26 -0
  44. package/dist/src/query.js +60 -0
  45. package/dist/src/rows.d.ts +2 -0
  46. package/dist/src/rows.js +45 -0
  47. package/dist/src/schema.d.ts +8 -0
  48. package/dist/src/schema.js +4 -0
  49. package/dist/src/sql-shared.d.ts +33 -0
  50. package/dist/src/sql-shared.js +171 -0
  51. package/dist/src/sql.d.ts +6 -0
  52. package/dist/src/sql.js +9 -0
  53. package/dist/src/table-client.d.ts +55 -0
  54. package/dist/src/table-client.js +198 -0
  55. package/dist/src/table.d.ts +37 -0
  56. package/dist/src/table.js +30 -0
  57. package/dist/src/types.d.ts +4 -0
  58. package/dist/src/types.js +2 -0
  59. package/dist/src/where.d.ts +24 -0
  60. package/dist/src/where.js +2 -0
  61. package/package.json +3 -3
@@ -0,0 +1,171 @@
1
+ // src/sql-shared.ts
2
+ // Shared helpers and types for the SQL builders (build-*.ts).
3
+ import { escapeId } from "./escape-id.js";
4
+ import { collapsingOf, keyColumns } from "./ddl-shared.js";
5
+ // escapeId живёт отдельным файлом: он нужен и рендеру DDL, и билдерам
6
+ // запросов, а взаимный импорт между ними создал бы цикл модулей
7
+ export { escapeId } from "./escape-id.js";
8
+ // UInt64 max — used to emulate a bare OFFSET (skip without take)
9
+ export const NO_LIMIT = "18446744073709551615";
10
+ // DateTime is second-precision; the client would send Date with
11
+ // a fractional part, which ClickHouse rejects — send unix seconds.
12
+ function serialize(value) {
13
+ if (value instanceof Date) {
14
+ return Math.floor(value.getTime() / 1000);
15
+ }
16
+ if (Array.isArray(value)) {
17
+ return value.map(serialize);
18
+ }
19
+ return value;
20
+ }
21
+ export function createBuildContext(tableName, columns) {
22
+ const params = {};
23
+ let paramIndex = 0;
24
+ function colRef(key) {
25
+ if (!columns[key]) {
26
+ throw new Error(`Unknown column "${key}" in table "${tableName}"`);
27
+ }
28
+ return escapeId(key);
29
+ }
30
+ function baseType(key) {
31
+ const chType = columns[key].clickhouseType;
32
+ const m = /^Nullable\((.+)\)$/.exec(chType);
33
+ return m ? m[1] : chType;
34
+ }
35
+ function addParam(value, type) {
36
+ const name = `p${paramIndex++}`;
37
+ params[name] = serialize(value);
38
+ return `{${name}:${type}}`;
39
+ }
40
+ function buildFieldFilter(key, filter) {
41
+ const col = colRef(key);
42
+ const conditions = [];
43
+ for (const op in filter) {
44
+ const value = filter[op];
45
+ if (value === undefined)
46
+ continue;
47
+ switch (op) {
48
+ case "equals":
49
+ conditions.push(value === null
50
+ ? `${col} IS NULL`
51
+ : `${col} = ${addParam(value, baseType(key))}`);
52
+ break;
53
+ case "not":
54
+ conditions.push(value === null
55
+ ? `${col} IS NOT NULL`
56
+ : `${col} != ${addParam(value, baseType(key))}`);
57
+ break;
58
+ case "in":
59
+ conditions.push(value.length === 0
60
+ ? "0"
61
+ : `${col} IN ${addParam(value, `Array(${baseType(key)})`)}`);
62
+ break;
63
+ case "notIn":
64
+ if (value.length > 0) {
65
+ conditions.push(`${col} NOT IN ${addParam(value, `Array(${baseType(key)})`)}`);
66
+ }
67
+ break;
68
+ case "lt":
69
+ case "lte":
70
+ case "gt":
71
+ case "gte": {
72
+ const cmp = { lt: "<", lte: "<=", gt: ">", gte: ">=" }[op];
73
+ conditions.push(`${col} ${cmp} ${addParam(value, baseType(key))}`);
74
+ break;
75
+ }
76
+ case "contains":
77
+ conditions.push(`position(${col}, ${addParam(value, "String")}) > 0`);
78
+ break;
79
+ case "startsWith":
80
+ conditions.push(`startsWith(${col}, ${addParam(value, "String")})`);
81
+ break;
82
+ case "endsWith":
83
+ conditions.push(`endsWith(${col}, ${addParam(value, "String")})`);
84
+ break;
85
+ default:
86
+ throw new Error(`Unknown operator "${op}" for column "${key}"`);
87
+ }
88
+ }
89
+ return conditions;
90
+ }
91
+ function isFilterObject(value) {
92
+ return (value !== null &&
93
+ typeof value === "object" &&
94
+ !(value instanceof Date) &&
95
+ !Array.isArray(value));
96
+ }
97
+ function whereSQL(where) {
98
+ const parts = [];
99
+ for (const key in where) {
100
+ const value = where[key];
101
+ if (value === undefined)
102
+ continue;
103
+ if (key === "AND") {
104
+ const list = (Array.isArray(value) ? value : [value]);
105
+ for (const w of list) {
106
+ const c = whereSQL(w);
107
+ if (c)
108
+ parts.push(`(${c})`);
109
+ }
110
+ }
111
+ else if (key === "OR") {
112
+ const conds = value
113
+ .map((w) => whereSQL(w))
114
+ .filter(Boolean)
115
+ .map((c) => `(${c})`);
116
+ if (conds.length)
117
+ parts.push(`(${conds.join(" OR ")})`);
118
+ }
119
+ else if (key === "NOT") {
120
+ const list = (Array.isArray(value) ? value : [value]);
121
+ for (const w of list) {
122
+ const c = whereSQL(w);
123
+ if (c)
124
+ parts.push(`NOT (${c})`);
125
+ }
126
+ }
127
+ else if (value === null) {
128
+ parts.push(`${colRef(key)} IS NULL`);
129
+ }
130
+ else if (isFilterObject(value)) {
131
+ parts.push(...buildFieldFilter(key, value));
132
+ }
133
+ else {
134
+ parts.push(`${colRef(key)} = ${addParam(value, baseType(key))}`);
135
+ }
136
+ }
137
+ return parts.join(" AND ");
138
+ }
139
+ return { params, colRef, baseType, addParam, whereSQL };
140
+ }
141
+ export function tableRef(database, tableName, table, final = false) {
142
+ const ref = `${escapeId(database)}.${escapeId(tableName)}`;
143
+ // replacing-таблицы обязаны читать последние версии строк; для остальных
144
+ // FINAL включается явно (например, чтобы прочитать коллапсирующую таблицу)
145
+ return table.mutations === "replacing" || final ? `${ref} FINAL` : ref;
146
+ }
147
+ // у коллапсирующих таблиц строка «существует», только если её знак не
148
+ // погашен парой -1 — count() посчитал бы обе строки пары
149
+ export function countExpr(table) {
150
+ const collapsing = collapsingOf(table.engine);
151
+ return collapsing ? `sum(${escapeId(collapsing.sign)})` : "count()";
152
+ }
153
+ export function checkPagination(name, value) {
154
+ if (!Number.isInteger(value) || value < 0) {
155
+ throw new Error(`"${name}" must be a non-negative integer, got: ${value}`);
156
+ }
157
+ }
158
+ // shared by buildUpdateQuery and the replacing-strategy update path
159
+ export function validateUpdateData(table, tableName, data) {
160
+ const keys = Object.keys(data).filter((k) => data[k] !== undefined);
161
+ if (keys.length === 0) {
162
+ throw new Error(`updateMany requires non-empty data`);
163
+ }
164
+ for (const key of keys) {
165
+ if (keyColumns(table.orderBy).includes(key)) {
166
+ throw new Error(`Cannot update ORDER BY key column "${key}" ` +
167
+ `of table "${tableName}"`);
168
+ }
169
+ }
170
+ return keys;
171
+ }
@@ -0,0 +1,6 @@
1
+ export { escapeId, validateUpdateData, type FindManyArgs, type OrderByInput, type SQLBuildResult, } from "./sql-shared.js";
2
+ export { buildFindManyQuery } from "./build-find-many-query.js";
3
+ export { buildCountQuery } from "./build-count-query.js";
4
+ export { buildUpdateQuery } from "./build-update-query.js";
5
+ export { buildDeleteQuery } from "./build-delete-query.js";
6
+ export { buildLightweightDeleteQuery } from "./build-lightweight-delete-query.js";
@@ -0,0 +1,9 @@
1
+ // src/sql.ts
2
+ // Public surface of the SQL builders; implementation is split by function
3
+ // (one function per file), shared helpers live in sql-shared.ts.
4
+ export { escapeId, validateUpdateData, } from "./sql-shared.js";
5
+ export { buildFindManyQuery } from "./build-find-many-query.js";
6
+ export { buildCountQuery } from "./build-count-query.js";
7
+ export { buildUpdateQuery } from "./build-update-query.js";
8
+ export { buildDeleteQuery } from "./build-delete-query.js";
9
+ export { buildLightweightDeleteQuery } from "./build-lightweight-delete-query.js";
@@ -0,0 +1,55 @@
1
+ import type { ClickHouseClient } from "@clickhouse/client";
2
+ import { AnyTable } from "./table.js";
3
+ import { Simplify } from "./types.js";
4
+ import { WhereInput } from "./where.js";
5
+ import { FindManyArgs } from "./sql.js";
6
+ export type FindManyResult<T, A> = A extends {
7
+ select: infer S;
8
+ } ? Simplify<{
9
+ [K in keyof S & keyof T as S[K] extends true ? K : never]: T[K];
10
+ }> : T;
11
+ type NullableKeys<T> = {
12
+ [K in keyof T]-?: null extends T[K] ? K : never;
13
+ }[keyof T];
14
+ type OptionalCreateKeys<T> = NullableKeys<T> | ("id" & keyof T);
15
+ export type CreateData<T> = Simplify<Omit<T, OptionalCreateKeys<T>> & {
16
+ [K in OptionalCreateKeys<T>]?: T[K];
17
+ }>;
18
+ export declare class TableClient<T extends Record<string, unknown>> {
19
+ private database;
20
+ private tableName;
21
+ private table;
22
+ private client;
23
+ constructor(database: string, tableName: string, table: AnyTable, client: ClickHouseClient);
24
+ private get tableRef();
25
+ private runQuery;
26
+ findMany<A extends FindManyArgs<T>>(args?: A): Promise<FindManyResult<T, A>[]>;
27
+ findFirst<A extends Omit<FindManyArgs<T>, "take">>(args?: A): Promise<FindManyResult<T, A> | null>;
28
+ count(args?: {
29
+ where?: WhereInput<T>;
30
+ }): Promise<number>;
31
+ private prepareRows;
32
+ private insertWire;
33
+ insert(rows: Partial<T> | Partial<T>[]): Promise<void>;
34
+ create(args: {
35
+ data: CreateData<T>;
36
+ }): Promise<T>;
37
+ private get strategy();
38
+ updateMany(args: {
39
+ where?: WhereInput<T>;
40
+ data: Partial<T>;
41
+ }): Promise<{
42
+ count: number;
43
+ }>;
44
+ deleteMany(args?: {
45
+ where?: WhereInput<T>;
46
+ }): Promise<{
47
+ count: number;
48
+ }>;
49
+ createMany(args: {
50
+ data: CreateData<T>[];
51
+ }): Promise<{
52
+ count: number;
53
+ }>;
54
+ }
55
+ export {};
@@ -0,0 +1,198 @@
1
+ // src/table-client.ts
2
+ import { buildFindManyQuery, buildCountQuery, buildUpdateQuery, buildDeleteQuery, buildLightweightDeleteQuery, validateUpdateData, escapeId, } from "./sql.js";
3
+ import { deserializeRows } from "./rows.js";
4
+ import { collapsingOf } from "./ddl-shared.js";
5
+ // process-unique id: ms timestamp in the high bits + 20-bit counter
6
+ let idCounter = 0n;
7
+ function generateId() {
8
+ return (BigInt(Date.now()) << 20n) | (idCounter++ & 0xfffffn);
9
+ }
10
+ export class TableClient {
11
+ database;
12
+ tableName;
13
+ table;
14
+ client;
15
+ constructor(database, tableName, table, client) {
16
+ this.database = database;
17
+ this.tableName = tableName;
18
+ this.table = table;
19
+ this.client = client;
20
+ }
21
+ get tableRef() {
22
+ return `${escapeId(this.database)}.${escapeId(this.tableName)}`;
23
+ }
24
+ async runQuery(sql, params, settings = {}) {
25
+ const result = await this.client.query({
26
+ query: sql,
27
+ query_params: params,
28
+ format: "JSONEachRow",
29
+ clickhouse_settings: {
30
+ ...settings,
31
+ // ISO with Z: the default DateTime rendering is in server
32
+ // timezone with no offset, which is unparseable correctly
33
+ date_time_output_format: "iso",
34
+ // force 64-bit ints to arrive as strings so BigInt() is
35
+ // lossless even if the server default was changed
36
+ output_format_json_quote_64bit_integers: 1,
37
+ },
38
+ });
39
+ return result.json();
40
+ }
41
+ async findMany(args) {
42
+ // сырые строки коллапсирующей таблицы содержат и погашенные версии —
43
+ // без FINAL такой результат вводит в заблуждение
44
+ if (collapsingOf(this.table.engine) && !args?.final) {
45
+ throw new Error(`Table "${this.tableName}" uses a collapsing engine: pass final: true to findMany`);
46
+ }
47
+ const { sql, params } = buildFindManyQuery(this.database, this.tableName, this.table, args);
48
+ const rows = await this.runQuery(sql, params, args?.settings);
49
+ return deserializeRows(rows, this.table.columns);
50
+ }
51
+ async findFirst(args) {
52
+ const rows = await this.findMany({
53
+ ...args,
54
+ take: 1,
55
+ });
56
+ return rows[0] ?? null;
57
+ }
58
+ async count(args) {
59
+ const { sql, params } = buildCountQuery(this.database, this.tableName, this.table, args?.where);
60
+ const rows = await this.runQuery(sql, params);
61
+ return Number(rows[0].count);
62
+ }
63
+ // wire: values serialized for JSONEachRow; completed: JS values
64
+ // including the generated id (what create() returns)
65
+ prepareRows(arr) {
66
+ const hasIdColumn = "id" in this.table.columns;
67
+ const wire = [];
68
+ const completed = [];
69
+ for (const r of arr) {
70
+ const full = { ...r };
71
+ if (hasIdColumn && full.id === undefined) {
72
+ full.id = generateId();
73
+ }
74
+ // the returned row must match both its type and what a
75
+ // subsequent read returns: omitted nullable columns are null,
76
+ // DateTime values are stored with second precision
77
+ for (const colName in this.table.columns) {
78
+ if (full[colName] === undefined &&
79
+ this.table.columns[colName].nullable) {
80
+ full[colName] = null;
81
+ }
82
+ else if (full[colName] instanceof Date) {
83
+ full[colName] = new Date(Math.floor(full[colName].getTime() / 1000) *
84
+ 1000);
85
+ }
86
+ }
87
+ const obj = {};
88
+ for (const k in full) {
89
+ const v = full[k];
90
+ if (v === undefined)
91
+ continue;
92
+ if (typeof v === "bigint") {
93
+ obj[k] = v.toString();
94
+ }
95
+ else if (v instanceof Date) {
96
+ // unix seconds: ISO strings are rejected by DateTime
97
+ // columns in the default (basic) input format
98
+ obj[k] = Math.floor(v.getTime() / 1000);
99
+ }
100
+ else {
101
+ obj[k] = v;
102
+ }
103
+ }
104
+ wire.push(obj);
105
+ completed.push(full);
106
+ }
107
+ return { wire, completed };
108
+ }
109
+ async insertWire(wire) {
110
+ await this.client.insert({
111
+ table: this.tableRef,
112
+ values: wire,
113
+ format: "JSONEachRow",
114
+ clickhouse_settings: {
115
+ // with wait_for_async_insert=0 on the server, inserts are
116
+ // acked before flush: errors are swallowed and rows are not
117
+ // immediately readable — force read-your-writes semantics
118
+ wait_for_async_insert: 1,
119
+ },
120
+ });
121
+ }
122
+ async insert(rows) {
123
+ const arr = Array.isArray(rows) ? rows : [rows];
124
+ if (arr.length === 0)
125
+ return;
126
+ const { wire } = this.prepareRows(arr);
127
+ await this.insertWire(wire);
128
+ }
129
+ async create(args) {
130
+ const { wire, completed } = this.prepareRows([args.data]);
131
+ await this.insertWire(wire);
132
+ return completed[0];
133
+ }
134
+ get strategy() {
135
+ return this.table.mutations ?? "alter";
136
+ }
137
+ async updateMany(args) {
138
+ if (this.strategy === "replacing") {
139
+ const keys = validateUpdateData(this.table, this.tableName, args.data);
140
+ // re-insert new versions of the matching rows;
141
+ // FINAL on the read keeps this idempotent
142
+ const rows = await this.findMany({ where: args.where });
143
+ if (rows.length === 0)
144
+ return { count: 0 };
145
+ const patch = {};
146
+ for (const k of keys)
147
+ patch[k] = args.data[k];
148
+ const { wire } = this.prepareRows(rows.map((r) => ({ ...r, ...patch })));
149
+ await this.insertWire(wire);
150
+ return { count: rows.length };
151
+ }
152
+ // "alter" and "lightweight" (ClickHouse has no stable lightweight
153
+ // UPDATE — lightweight tables fall back to a heavy ALTER mutation)
154
+ const { sql, params } = buildUpdateQuery(this.database, this.tableName, this.table, args.where, args.data);
155
+ const count = await this.count({ where: args.where });
156
+ if (count === 0)
157
+ return { count: 0 };
158
+ await this.client.command({
159
+ query: sql,
160
+ query_params: params,
161
+ clickhouse_settings: {
162
+ // wait for the mutation so subsequent reads see the change
163
+ mutations_sync: "1",
164
+ },
165
+ });
166
+ return { count };
167
+ }
168
+ async deleteMany(args) {
169
+ const count = await this.count({ where: args?.where });
170
+ if (count === 0)
171
+ return { count: 0 };
172
+ if (this.strategy === "alter") {
173
+ const { sql, params } = buildDeleteQuery(this.database, this.tableName, this.table, args?.where);
174
+ await this.client.command({
175
+ query: sql,
176
+ query_params: params,
177
+ clickhouse_settings: { mutations_sync: "1" },
178
+ });
179
+ }
180
+ else {
181
+ // "lightweight" and "replacing": fast tombstone-based delete
182
+ const { sql, params } = buildLightweightDeleteQuery(this.database, this.tableName, this.table, args?.where);
183
+ await this.client.command({
184
+ query: sql,
185
+ query_params: params,
186
+ clickhouse_settings: { lightweight_deletes_sync: "2" },
187
+ });
188
+ }
189
+ return { count };
190
+ }
191
+ async createMany(args) {
192
+ if (args.data.length === 0)
193
+ return { count: 0 };
194
+ const { wire } = this.prepareRows(args.data);
195
+ await this.insertWire(wire);
196
+ return { count: args.data.length };
197
+ }
198
+ }
@@ -0,0 +1,37 @@
1
+ import { Column } from "./column.js";
2
+ import type { EngineSpec } from "./engine.js";
3
+ import type { KeyPart } from "./expr.js";
4
+ import { Simplify } from "./types.js";
5
+ export type MutationStrategy = "alter" | "lightweight" | "replacing";
6
+ export type IndexType = "bloom_filter" | "set" | "minmax" | "tokenbf_v1" | "ngrambf_v1";
7
+ export type IndexDefinition = {
8
+ name: string;
9
+ expr: KeyPart;
10
+ type: IndexType;
11
+ args?: (number | string)[];
12
+ granularity?: number;
13
+ };
14
+ export type TableDefinition<TColumns extends Record<string, Column<unknown>>> = {
15
+ engine: string | EngineSpec;
16
+ replication?: {
17
+ path: string;
18
+ replica: string;
19
+ };
20
+ orderBy: KeyPart[];
21
+ primaryKey?: KeyPart[];
22
+ partitionBy?: KeyPart;
23
+ sampleBy?: KeyPart;
24
+ indexes?: IndexDefinition[];
25
+ ttl?: string;
26
+ settings?: Record<string, string | number>;
27
+ comment?: string;
28
+ mutations?: MutationStrategy;
29
+ columns: TColumns;
30
+ };
31
+ export type AnyColumns = Record<string, Column<unknown>>;
32
+ export type AnyTable = TableDefinition<AnyColumns>;
33
+ export declare function table<TColumns extends Record<string, Column<unknown>>>(def: TableDefinition<TColumns>): TableDefinition<TColumns>;
34
+ export type InferColumns<TColumns extends Record<string, Column<unknown>>> = Simplify<{
35
+ [K in keyof TColumns]: TColumns[K] extends Column<infer T> ? T : never;
36
+ }>;
37
+ export type InferRow<TTable> = TTable extends TableDefinition<infer TColumns> ? InferColumns<TColumns> : never;
@@ -0,0 +1,30 @@
1
+ // src/table.ts
2
+ import { collapsingOf, isReplacingEngine, keyColumns } from "./ddl-shared.js";
3
+ const INTEGER_TYPE = /^(U?Int(8|16|32|64))$/;
4
+ export function table(def) {
5
+ if (def.mutations === "replacing" && !isReplacingEngine(def.engine)) {
6
+ throw new Error(`mutations: "replacing" requires a ReplacingMergeTree engine ` +
7
+ `(got "${typeof def.engine === "string" ? def.engine : def.engine.family}")`);
8
+ }
9
+ // коллапс работает только с целочисленным знаком и версией — ошибка
10
+ // схемы должна всплыть при объявлении, а не при первом INSERT
11
+ const collapsing = collapsingOf(def.engine);
12
+ if (collapsing) {
13
+ const sign = def.columns[collapsing.sign];
14
+ if (!sign || sign.clickhouseType !== "Int8") {
15
+ throw new Error(`Collapsing engine requires sign column "${collapsing.sign}" of type Int8`);
16
+ }
17
+ if (collapsing.version) {
18
+ const version = def.columns[collapsing.version];
19
+ if (!version || !INTEGER_TYPE.test(version.clickhouseType)) {
20
+ throw new Error(`VersionedCollapsingMergeTree requires integer version column "${collapsing.version}"`);
21
+ }
22
+ }
23
+ }
24
+ for (const key of keyColumns([...def.orderBy, ...(def.primaryKey ?? [])])) {
25
+ if (!def.columns[key]) {
26
+ throw new Error(`Key column "${key}" is not declared in columns`);
27
+ }
28
+ }
29
+ return def;
30
+ }
@@ -0,0 +1,4 @@
1
+ export type Primitive = string | number | bigint | boolean | Date | null;
2
+ export type Simplify<T> = {
3
+ [K in keyof T]: T[K];
4
+ } & {};
@@ -0,0 +1,2 @@
1
+ // src/types.ts
2
+ export {};
@@ -0,0 +1,24 @@
1
+ export type ComparableFilter<T> = {
2
+ lt?: T;
3
+ lte?: T;
4
+ gt?: T;
5
+ gte?: T;
6
+ };
7
+ export type StringFilter = {
8
+ contains?: string;
9
+ startsWith?: string;
10
+ endsWith?: string;
11
+ };
12
+ export type ScalarFilter<T> = {
13
+ equals?: T | null;
14
+ not?: T | null;
15
+ in?: T[];
16
+ notIn?: T[];
17
+ } & (T extends number | bigint | Date | string ? ComparableFilter<T> : unknown) & (T extends string ? StringFilter : unknown);
18
+ export type WhereInput<T> = {
19
+ AND?: WhereInput<T> | WhereInput<T>[];
20
+ OR?: WhereInput<T>[];
21
+ NOT?: WhereInput<T> | WhereInput<T>[];
22
+ } & {
23
+ [K in keyof T]?: T[K] extends readonly unknown[] ? never : T[K] | ScalarFilter<NonNullable<T[K]>> | null;
24
+ };
@@ -0,0 +1,2 @@
1
+ // src/where.ts
2
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quark-fw/clisma",
3
- "version": "0.1.26",
3
+ "version": "0.1.27",
4
4
  "description": "Prisma-like typed client and schema migrator for ClickHouse",
5
5
  "keywords": [
6
6
  "clickhouse",
@@ -29,8 +29,8 @@
29
29
  "node": ">=20"
30
30
  },
31
31
  "scripts": {
32
- "build": "tsc -p tsconfig.build.json",
33
- "typecheck": "tsc --noEmit",
32
+ "build": "node ../../node_modules/tsgo/bin/tsc -p tsconfig.build.json",
33
+ "typecheck": "node ../../node_modules/tsgo/bin/tsc --noEmit",
34
34
  "test:integration": "tsx --env-file=./src/test/.env ./src/test/index.ts",
35
35
  "test": "tsx --test src/test/unit.test.ts src/test/ddl.test.ts src/test/migrate-nodes.test.ts",
36
36
  "prepublishOnly": "npm run typecheck && npm test && npm run build"