<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>chkit Docs | Blog</title><description>Define ClickHouse schemas, review migrations, and sync API data into your tables with chkit.</description><link>https://chkit.obsessiondb.com/</link><language>en</language><item><title>chkit is now available in Python</title><link>https://chkit.obsessiondb.com/blog/chkit-python/</link><guid isPermaLink="true">https://chkit.obsessiondb.com/blog/chkit-python/</guid><description>We closed our last post with a promise: a Python port in the coming weeks. It shipped. pip install chkit-py gets you the entire toolkit, at parity with the TypeScript original: same DSL semantics, same planner, same journal table, same CLI.

</description><pubDate>Tue, 11 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We closed &lt;a href=&quot;https://chkit.obsessiondb.com/blog/olap-deserves-better-tooling/&quot;&gt;our last post&lt;/a&gt; with a promise: if you do not use TypeScript but Python, a port was coming. It shipped.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;pip&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;chkit-py&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;chkit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--help&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That is the entire ClickHouse® schema-as-code toolkit, ported to Python at parity with the TypeScript original: the DSL, the diff engine, the migration planner, the drift detection, the CI gates, and the plugins. It is not a wrapper around a Node binary. A wrapper would still put a Node runtime in your Airflow images and give Python nothing importable. It is a full port, with Pydantic models, a library API, and a CLI that is just &lt;code dir=&quot;auto&quot;&gt;chkit&lt;/code&gt; on your PATH.&lt;/p&gt;
&lt;p&gt;This post covers why we built it, how the two implementations stay in sync, and what is different on the Python side.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-people-closest-to-clickhouse-write-python&quot;&gt;The people closest to ClickHouse write Python&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Look at who actually sits next to a ClickHouse cluster all day. Data engineers orchestrating pipelines in Airflow and Dagster. Analytics engineers living in notebooks. ML teams whose entire world is pandas, Polars, and PyArrow. The queries feeding your dashboards were probably prototyped in a Jupyter cell.&lt;/p&gt;
&lt;p&gt;For that whole population, a schema tool that only exists on npm is a tax. Adopting chkit meant adopting a Node toolchain (&lt;code dir=&quot;auto&quot;&gt;package.json&lt;/code&gt;, a runtime, a lockfile) in repositories that had none, maintained by people who did not choose JavaScript and should not have to. Most teams do the rational thing instead: they keep hand-writing DDL, which is exactly the failure mode chkit exists to eliminate.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;agents-default-to-python&quot;&gt;Agents default to Python&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Coding agents are the other reason. Ask an agent to script something against a database and it almost always writes Python, because that is the ecosystem it draws from.&lt;/p&gt;
&lt;p&gt;If your schema toolkit does not exist in Python, agents route around it: they hand-write the &lt;code dir=&quot;auto&quot;&gt;CREATE TABLE&lt;/code&gt;, and you are back to unreviewed DDL, the exact problem chkit exists to solve. If you want agents to keep schema changes inside the migration workflow, the tool has to exist in the language they actually use.&lt;/p&gt;
&lt;p&gt;The docs follow the same idea. Every page of &lt;a href=&quot;https://chkit.obsessiondb.com&quot;&gt;chkit.obsessiondb.com&lt;/a&gt; is available as raw Markdown (append &lt;code dir=&quot;auto&quot;&gt;.md&lt;/code&gt; to any URL), there is an &lt;a href=&quot;https://chkit.obsessiondb.com/llms.txt&quot;&gt;llms.txt&lt;/a&gt; index at the root, and every reference page now shows TypeScript and Python side by side with synced tabs: pick your language once, and the whole site follows. An agent (or a human) landing anywhere in the docs gets working code in the language it is actually going to run.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-same-schema-in-python&quot;&gt;The same schema, in Python&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Here is the table from our last post, in Python:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; chkit &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; schema, table&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;events &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;table&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;database&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;analytics&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;events&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;columns&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;UInt64&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;org_id&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;LowCardinality(String)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;payload&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;nullable&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;True&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;received_at&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;DateTime64(3)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;fn:now64(3)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;engine&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;MergeTree()&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;primary_key&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;order_by&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;org_id&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;received_at&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;partition_by&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;toYYYYMM(received_at)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;ttl&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;received_at + INTERVAL 90 DAY&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;settings&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;index_granularity&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;8192&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;indexes&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;idx_source&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;expression&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;maxRows&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;granularity&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;definitions &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;schema&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;events&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Definitions are Pydantic models: frozen, validated at construction, unknown fields rejected. A typo in a column option fails loudly at import time instead of silently shipping a table with no codec. The DSL accepts both snake_case and the TypeScript camelCase names (&lt;code dir=&quot;auto&quot;&gt;primary_key&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;primaryKey&lt;/code&gt;), so examples port between languages with their keys unchanged.&lt;/p&gt;
&lt;p&gt;The CLI is the same CLI. Same commands, same flags, same exit codes, same &lt;code dir=&quot;auto&quot;&gt;--json&lt;/code&gt; envelopes:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;chkit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;generate&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add_events_table&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;chkit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;migrate&lt;/span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;# shows the plan&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;chkit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;migrate&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--apply&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# applies, journals, verifies checksums&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;chkit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;drift&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;chkit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;check&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--strict&lt;/span&gt;&lt;span&gt;     &lt;/span&gt;&lt;span&gt;# your CI gate&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Everything from the last post carries over. The guardrails: risk-tagged plans, the structural-vs-alterable distinction, tracked renames, destructive-operation blocking with exit code 3, checksum-verified migration history. The schema surface: dictionaries, refreshable materialized views, projections, &lt;code dir=&quot;auto&quot;&gt;ON CLUSTER&lt;/code&gt; mode. The ecosystem: &lt;code dir=&quot;auto&quot;&gt;pull&lt;/code&gt; for adopting an existing database (a built-in command in Python, no plugin to install), the codegen plugin (which emits Pydantic models, one shape covering what TypeScript needed interfaces &lt;em&gt;and&lt;/em&gt; Zod for), the backfill engine, and the ObsessionDB integration.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;two-implementations-one-source-of-truth&quot;&gt;Two implementations, one source of truth&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A second implementation is only useful if the two never disagree. chkit-py and chkit share their artifacts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The snapshot&lt;/strong&gt; (&lt;code dir=&quot;auto&quot;&gt;chkit/meta/snapshot.json&lt;/code&gt;) serializes with identical JSON shapes. A snapshot written by the TypeScript CLI is read by the Python CLI, and vice versa.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The journal&lt;/strong&gt; is the same &lt;code dir=&quot;auto&quot;&gt;_chkit_migrations&lt;/code&gt; table in ClickHouse, same schema, same SHA-256 checksums.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The SQL&lt;/strong&gt;: both planners render the same DDL for the same definitions, statement for statement, down to how ClickHouse normalizes projection index parentheses.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Which means a TypeScript service and a Python pipeline can manage the &lt;em&gt;same database&lt;/em&gt; without stepping on each other. Your app team stays in TS, your data team stays in Python, and there is still exactly one source of truth with one migration history.&lt;/p&gt;
&lt;p&gt;Keeping that guarantee was most of the porting work, down to matching JavaScript’s number formatting so a plan written by one implementation runs unchanged under the other. It is verified by 1,100+ tests (most ported case-for-case from the TypeScript suites) that run in the same CI on every commit, plus an e2e suite per implementation against live ClickHouse. The deliberate differences between the two are documented in &lt;a href=&quot;https://github.com/obsessiondb/chkit/blob/main/chkit_python/DRIFT.md&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;DRIFT.md&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A few things intentionally did not cross over: the &lt;code dir=&quot;auto&quot;&gt;create chkit&lt;/code&gt; scaffolder (&lt;code dir=&quot;auto&quot;&gt;chkit init&lt;/code&gt; covers it), dependency auto-install (Python convention is an explicit &lt;code dir=&quot;auto&quot;&gt;pip install&lt;/code&gt;), the &lt;code dir=&quot;auto&quot;&gt;chkit skills&lt;/code&gt; proxy, and two codegen extras (ingest helpers and an embedded migration runner) that are deferred until Python users ask for them.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;start-from-where-you-already-are&quot;&gt;Start from where you already are&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Same story as the TypeScript side, minus the toolchain:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;pip&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;chkit-py&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;chkit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;init&lt;/span&gt;&lt;span&gt;                    &lt;/span&gt;&lt;span&gt;# writes clickhouse.config.py + a starter schema&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;chkit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;generate&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;init&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;chkit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;migrate&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--apply&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Already running ClickHouse? &lt;code dir=&quot;auto&quot;&gt;chkit pull&lt;/code&gt; introspects the live database and writes a deterministic Python schema file with everything in it: tables, views, materialized views, dictionaries, codecs, projections. Then &lt;code dir=&quot;auto&quot;&gt;chkit drift&lt;/code&gt; confirms the result matches before you change anything.&lt;/p&gt;
&lt;p&gt;Config is a plain Python module (&lt;code dir=&quot;auto&quot;&gt;clickhouse.config.py&lt;/code&gt;), reading credentials from environment variables, with the same options and defaults as the TypeScript config, including function-style configs for CI. The three first-party plugins ship inside the package; there is nothing else to install.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;one-toolkit-whatever-the-language&quot;&gt;One toolkit, whatever the language&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Last time we argued that ClickHouse deserves the migration tooling relational databases have had for years. That argument no longer depends on your language: if you run ClickHouse from Python, chkit now works the same way it does from TypeScript.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Install:&lt;/strong&gt; &lt;a href=&quot;https://pypi.org/project/chkit-py/&quot;&gt;https://pypi.org/project/chkit-py/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Docs:&lt;/strong&gt; &lt;a href=&quot;https://chkit.obsessiondb.com/python/overview/&quot;&gt;https://chkit.obsessiondb.com/python/overview/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href=&quot;https://github.com/obsessiondb/chkit&quot;&gt;https://github.com/obsessiondb/chkit&lt;/a&gt; (MIT, the Python port lives in &lt;code dir=&quot;auto&quot;&gt;chkit_python/&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Same disclosure as last time: we run &lt;a href=&quot;https://obsessiondb.com&quot;&gt;ObsessionDB&lt;/a&gt;, a managed ClickHouse offering, and chkit-py, like chkit, runs against ObsessionDB, ClickHouse Cloud, Altinity, or any self-hosted ClickHouse. No lock-in; the tooling is for the ecosystem.&lt;/p&gt;
&lt;p&gt;It is beta, like its TypeScript sibling: the CLI surface and the DSL are stable, but we may make small breaking changes before 1.0. If something is missing, or the Python ergonomics could be better, issues and PRs are welcome. Improvements land in both languages.&lt;/p&gt;
&lt;p&gt;Stay tuned!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;ClickHouse® is a registered trademark of ClickHouse, Inc.&lt;/em&gt;&lt;/p&gt;</content:encoded><category>announcement</category><category>clickhouse</category><category>python</category></item><item><title>The era of OLAP deserves the tooling relational has had for years</title><link>https://chkit.obsessiondb.com/blog/olap-deserves-better-tooling/</link><guid isPermaLink="true">https://chkit.obsessiondb.com/blog/olap-deserves-better-tooling/</guid><description>If you build on Postgres or MySQL, you have not hand-written a migration in years. ClickHouse should not be the exception. Here is why we built ch-kit, after running ClickHouse at near-petabyte scale.

</description><pubDate>Thu, 25 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you build on Postgres or MySQL, you have not hand written a migration in years. You change a TypeScript schema, a tool diffs it, generates the SQL, applies it, and fails your CI if production has drifted. Drizzle, Prisma, and Flyway made that the default. It is boring, and boring is exactly what you want from the thing that changes your database.&lt;/p&gt;
&lt;p&gt;Now switch to ClickHouse®. Suddenly you are back in 2010: hand written DDL, diffs done by eye, a &lt;code dir=&quot;auto&quot;&gt;migrations/&lt;/code&gt; folder that may or may not match what is actually running, and a sinking feeling every time someone runs an &lt;code dir=&quot;auto&quot;&gt;ALTER&lt;/code&gt; in production. OLAP is becoming the default for analytics, but the tooling around it never caught up to what the relational world treats as table stakes.&lt;/p&gt;
&lt;p&gt;We felt this acutely. In our previous venture, Numia, we ran ClickHouse at near-petabyte scale for real-time blockchain analytics. We hit two walls. The first was scaling the database like serious infrastructure. The second was the complete absence of tooling to manage the schema as code. We built &lt;a href=&quot;https://obsessiondb.com&quot;&gt;ObsessionDB&lt;/a&gt;, managed ClickHouse with decoupled storage and compute, to solve the first. &lt;strong&gt;ch-kit&lt;/strong&gt; is how we are solving the second, and it is open source.&lt;/p&gt;
&lt;p&gt;This post is about the second wall, and what schema-as-code for ClickHouse actually looks like once you have it.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-problem-with-hand-written-ddl&quot;&gt;The problem with hand written DDL&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A ClickHouse schema is not just a list of columns. A single &lt;code dir=&quot;auto&quot;&gt;MergeTree&lt;/code&gt; table carries an engine, a sorting key, a partitioning expression, TTL rules, skip indexes, projections, codecs, and table settings. Materialized views point at target tables. Some of those properties can be altered in place. Some cannot, and changing them means a drop and recreate, which on a large table is not a thing you discover by accident.&lt;/p&gt;
&lt;p&gt;When all of that lives in raw SQL files, three failures show up over and over:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Drift.&lt;/strong&gt; Someone runs a manual &lt;code dir=&quot;auto&quot;&gt;ALTER&lt;/code&gt; to fix an incident at 3am. The fix never makes it back into a migration file. Now your repository and your production database disagree, and nothing tells you.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unsafe changes.&lt;/strong&gt; A migration drops a column. The reviewer did not notice. It runs in CI on a Friday. The data is gone.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No source of truth.&lt;/strong&gt; The schema is whatever the sum of every migration file happens to produce. To know the current shape of a table, you read the database, not your code.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Relational tooling solved all three. ch-kit solves them for ClickHouse.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;define-your-schemas-in-typescript&quot;&gt;Define your schemas in TypeScript&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;In ch-kit, tables, views, and materialized views are TypeScript values. Here is a real table definition, with the features you actually use in production:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { schema, table } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@chkit/core&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;events&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;table&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;database: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;analytics&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;events&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;columns:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ name: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, type: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;UInt64&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ name: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;org_id&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, type: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ name: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, type: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;LowCardinality(String)&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ name: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;payload&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, type: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, nullable: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ name: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;received_at&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, type: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;DateTime64(3)&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, default: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;fn:now64(3)&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;engine: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;MergeTree()&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;primaryKey:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;orderBy:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;org_id&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;received_at&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;partitionBy: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;toYYYYMM(received_at)&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ttl: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;received_at + INTERVAL 90 DAY&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;settings: { index_granularity: &lt;/span&gt;&lt;span&gt;8192&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;indexes:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ name: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;idx_source&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, expression: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, type: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, maxRows: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, granularity: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;schema&lt;/span&gt;&lt;span&gt;(events)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This is the desired state of your database, expressed as code, type checked, reviewable in a pull request, and split across as many files as you want. Materialized views and refreshable views are first class, including the &lt;code dir=&quot;auto&quot;&gt;to&lt;/code&gt; target table and refresh schedules. Compression codecs are typed too, so a column codec chain like &lt;code dir=&quot;auto&quot;&gt;[{ kind: &apos;DoubleDelta&apos; }, { kind: &apos;ZSTD&apos;, level: 3 }]&lt;/code&gt; is validated rather than stringly typed.&lt;/p&gt;
&lt;p&gt;You do not write SQL to define this. You write SQL for your queries, because ch-kit is not an ORM and has no opinion about how you read your data. It only owns the schema, the migrations, and the guardrails.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;migrations-are-diffs-not-artifacts-you-maintain&quot;&gt;Migrations are diffs, not artifacts you maintain&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;As we’ve mentioned already multiple times, with ch-kit you just change the TypeScript, and the rest is handled for you. Migrations are part of it.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;chkit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;generate&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add_events_table&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;With &lt;code dir=&quot;auto&quot;&gt;generate&lt;/code&gt;, ch-kit loads your TS definitions, compares them against the previous snapshot, computes an ordered plan, and writes a migration SQL file plus an updated snapshot. If nothing changed, no file is created. Every operation in the plan is tagged with a risk level:&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Risk&lt;/th&gt;&lt;th&gt;Meaning&lt;/th&gt;&lt;th&gt;Examples&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;safe&lt;/code&gt;&lt;/td&gt;&lt;td&gt;No data loss&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;CREATE TABLE&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;ADD COLUMN&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;caution&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Review recommended&lt;/td&gt;&lt;td&gt;settings changes&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;danger&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Destructive&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;DROP TABLE&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;DROP COLUMN&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Here is a detail that matters and that most ad hoc setups get wrong. ch-kit knows which schema properties are structural and which are alterable. Changing columns, indexes, projections, settings, or TTL is an &lt;code dir=&quot;auto&quot;&gt;ALTER&lt;/code&gt; in place. Changing the engine, &lt;code dir=&quot;auto&quot;&gt;primaryKey&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;orderBy&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;partitionBy&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;uniqueKey&lt;/code&gt; is structural, which means a drop and recreate. ch-kit makes that distinction explicit in the plan instead of letting you find out in production that your “small” change rewrites the whole table.&lt;/p&gt;
&lt;p&gt;Renames are tracked, not guessed. Mark a column or table with &lt;code dir=&quot;auto&quot;&gt;renamedFrom&lt;/code&gt; (or pass &lt;code dir=&quot;auto&quot;&gt;--rename-table&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;--rename-column&lt;/code&gt;) and ch-kit emits a real rename instead of a destructive drop and add:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;columns: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ name: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;user_email&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, type: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, renamedFrom: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;email&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;applying-changes-without-losing-data&quot;&gt;Applying changes without losing data&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Once you have your migrations defined with &lt;code dir=&quot;auto&quot;&gt;generate&lt;/code&gt;, you can preview the impact by running &lt;code dir=&quot;auto&quot;&gt;chkit migrate&lt;/code&gt;. This will dry run and show you the plan. If you like what you see, just add &lt;code dir=&quot;auto&quot;&gt;--apply&lt;/code&gt; at the end for the actual run:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;chkit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;migrate&lt;/span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;# shows the plan&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;chkit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;migrate&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--apply&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# applies pending migrations&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Three guardrails run on every apply:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Destructive operations are blocked.&lt;/strong&gt; Any migration containing a &lt;code dir=&quot;auto&quot;&gt;risk=danger&lt;/code&gt; operation, a &lt;code dir=&quot;auto&quot;&gt;DROP TABLE&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;DROP COLUMN&lt;/code&gt;, requires explicit approval. In an interactive terminal you get a prompt. In CI, the command exits with code 3 and refuses to run unless you pass &lt;code dir=&quot;auto&quot;&gt;--allow-destructive&lt;/code&gt; or set &lt;code dir=&quot;auto&quot;&gt;safety.allowDestructive&lt;/code&gt; in config. The error payload tells you exactly which migration and which operation, with an impact description and a recommendation. Your data does not disappear because a reviewer was tired.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Checksums are verified.&lt;/strong&gt; Every applied migration is recorded in a &lt;code dir=&quot;auto&quot;&gt;_chkit_migrations&lt;/code&gt; journal table in ClickHouse along with a SHA-256 hash of the file content. If a migration file is edited after it was applied, the next run aborts before touching anything. Migration history is immutable, and ch-kit enforces it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It is built for CI.&lt;/strong&gt; The CLI detects non-interactive environments (&lt;code dir=&quot;auto&quot;&gt;CI=true&lt;/code&gt;, no TTY) and never blocks on a prompt. Every command supports &lt;code dir=&quot;auto&quot;&gt;--json&lt;/code&gt; with a stable output envelope, so you can parse results with &lt;code dir=&quot;auto&quot;&gt;jq&lt;/code&gt; and build approval gates.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;dont-let-your-project-drift&quot;&gt;Don’t let your project drift!&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;ch-kit also implements a &lt;code dir=&quot;auto&quot;&gt;drift&lt;/code&gt; command that introspects the live database and compares it, object by object and column by column, against your snapshot:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;chkit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;drift&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;It reports missing objects, extra objects, changed columns, and mismatches on settings, TTL, indexes, &lt;code dir=&quot;auto&quot;&gt;ORDER BY&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;PARTITION BY&lt;/code&gt;, primary keys, and projections. It is detailed enough that the 3am manual &lt;code dir=&quot;auto&quot;&gt;ALTER&lt;/code&gt; shows up the next morning as a concrete, named difference instead of a vague feeling that something is off.&lt;/p&gt;
&lt;p&gt;In CI you turn that signal into a gate with &lt;code dir=&quot;auto&quot;&gt;chkit check&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;chkit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;check&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--strict&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;check&lt;/code&gt; evaluates three policies, all on by default: fail on pending migrations, fail on checksum mismatch, fail on drift. The &lt;code dir=&quot;auto&quot;&gt;--strict&lt;/code&gt; flag forces them on regardless of config, so a permissive local setting never leaks into your pipeline. A full pull request gate is a few lines:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Check schema consistency&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;npx chkit check --strict --json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Verify generated types&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;npx chkit codegen --check --json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That &lt;code dir=&quot;auto&quot;&gt;codegen --check&lt;/code&gt; step is the other half. ch-kit generates TypeScript row types (and optional Zod schemas) from the same definitions, so your application types and your database schema cannot quietly diverge either.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;works-with-any-clickhouse&quot;&gt;Works with any ClickHouse&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;ch-kit is not a lock-in play. We run ObsessionDB, a managed ClickHouse offering that competes with ClickHouse Cloud, Altinity, and the rest. But we love ClickHouse, and we believe the tooling around it should be available to everyone using the technology, because that is what strengthens the ecosystem. So ch-kit runs against ObsessionDB, ClickHouse Cloud, Altinity, or any other managed or self-hosted ClickHouse.&lt;/p&gt;
&lt;p&gt;The only managed-specific touch is that &lt;code dir=&quot;auto&quot;&gt;Shared*&lt;/code&gt; engine prefixes (&lt;code dir=&quot;auto&quot;&gt;SharedMergeTree&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;SharedReplacingMergeTree&lt;/code&gt;, and so on) are normalized away during comparison, so managed engines do not show up as false drift. If you run ObsessionDB, the first-party plugin keeps your schema on the right &lt;code dir=&quot;auto&quot;&gt;Shared*&lt;/code&gt; engines automatically, from the exact same TypeScript you use locally. If you do not, those engine variants are stripped for your target. Same schema, different backends.&lt;/p&gt;
&lt;p&gt;It is MIT licensed, written in TypeScript, runs on Node 20+ or Bun, and needs ClickHouse 24.x or newer. It is beta: the CLI surface and the schema DSL are stable and run our own production workloads today, and we may still make small breaking changes before 1.0.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;start-from-where-you-already-are&quot;&gt;Start from where you already are&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Most teams adopting ch-kit are not starting from an empty database. They already have ClickHouse in production, with tables that matter and data they cannot lose. Either way, the first step is the same: get a project scaffolded.&lt;/p&gt;
&lt;p&gt;If you are starting from scratch, &lt;code dir=&quot;auto&quot;&gt;npm create chkit@latest&lt;/code&gt; gives you a working project from a curated example:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;chkit@latest&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If you are adding ch-kit to an existing TypeScript project, &lt;code dir=&quot;auto&quot;&gt;chkit init&lt;/code&gt; writes the project config (&lt;code dir=&quot;auto&quot;&gt;clickhouse.config.ts&lt;/code&gt;, with the ClickHouse connection block reading from environment variables) and a starter schema file. It is idempotent, so running it on an existing project leaves your files untouched.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;my-app/&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# before: your existing TypeScript project&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# ├── package.json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# ├── tsconfig.json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# └── src/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#     └── index.ts&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;chkit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;init&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Created&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;clickhouse.config.ts&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Created&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;src/db/schema/example.ts&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# after: chkit added exactly two files, nothing else touched&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# my-app/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# ├── clickhouse.config.ts          ← config: ClickHouse connection + plugins&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# ├── package.json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# ├── tsconfig.json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# └── src/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#     ├── index.ts&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#     └── db/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#         └── schema/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#             └── example.ts        ← starter schema: an `events` table&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If you are starting fresh, edit the generated example schema to match the tables you want, then move on to &lt;code dir=&quot;auto&quot;&gt;generate&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you already run ClickHouse, the next command is &lt;code dir=&quot;auto&quot;&gt;chkit pull&lt;/code&gt;. It introspects your live database and writes a deterministic TypeScript schema file from what is actually there:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 1) install the pull plugin&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-D&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@chkit/plugin-pull&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 2) register it in clickhouse.config.ts:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#      import { pull } from &apos;@chkit/plugin-pull&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#      plugins: [pull({ outFile: &apos;./src/db/schema/pulled.ts&apos; })]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 3) introspect your live database into TypeScript&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;chkit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pull&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--out-file&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./src/db/schema/pulled.ts&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This is the step that turns an existing production database into version-controlled code. You do not transcribe table definitions by hand and hope you got the &lt;code dir=&quot;auto&quot;&gt;ORDER BY&lt;/code&gt; and the codecs right. ch-kit reads them. The output is deterministic, so it produces clean, reviewable diffs instead of churn, and you can scope it to specific databases with &lt;code dir=&quot;auto&quot;&gt;--database&lt;/code&gt; or preview it with &lt;code dir=&quot;auto&quot;&gt;--dryrun&lt;/code&gt; before writing anything.&lt;/p&gt;
&lt;p&gt;Once you have pulled, prove the result immediately:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;chkit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;drift&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If your code matches production, drift reports nothing. That zero-drift result is the moment your database stops being the source of truth and your repository takes over. From here, every future change starts in TypeScript:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;chkit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;generate&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;init&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;chkit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;migrate&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--apply&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;chkit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;drift&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;want-more-check-the-rest-of-the-features&quot;&gt;Want more? Check the rest of the features!&lt;/h2&gt;&lt;/div&gt;
&lt;aside aria-label=&quot;Tip&quot;&gt;&lt;p aria-hidden=&quot;true&quot;&gt;Tip&lt;/p&gt;&lt;div&gt;&lt;p&gt;We have only covered the initial bits of what ch-kit can do here. The &lt;a href=&quot;https://chkit.obsessiondb.com&quot;&gt;docs&lt;/a&gt; go into more detail on getting started no matter your case, and on everything else the tooling can do.&lt;/p&gt;&lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;The relational world stopped hand writing migrations a long time ago. OLAP should not be the exception. If you run ClickHouse and you are still managing schema by hand, we built ch-kit so you do not have to.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href=&quot;https://github.com/obsessiondb/chkit&quot;&gt;https://github.com/obsessiondb/chkit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Docs:&lt;/strong&gt; &lt;a href=&quot;https://chkit.obsessiondb.com/getting-started/&quot;&gt;https://chkit.obsessiondb.com/getting-started/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Install:&lt;/strong&gt; &lt;a href=&quot;https://www.npmjs.com/package/chkit&quot;&gt;https://www.npmjs.com/package/chkit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you do not use TypeScript but Python, do not worry: we will publish a Python port in the coming weeks. And since ch-kit is open source, if you feel something is missing, you are more than welcome to open an issue or a PR.&lt;/p&gt;
&lt;p&gt;Stay tuned!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;ClickHouse® is a registered trademark of ClickHouse, Inc.&lt;/em&gt;&lt;/p&gt;</content:encoded><category>announcement</category><category>clickhouse</category></item></channel></rss>