Skip to content

Schema DSL Reference

Schema files are TypeScript files that export definitions using functions from @chkit/core. All exported definitions are collected when chkit loads schema files matched by the schema glob in your configuration.

import { schema, table, view, materializedView } from '@chkit/core'

Groups definitions into a single array for export.

schema(...definitions: SchemaDefinition[]): SchemaDefinition[]
export default schema(users, events)

You can also export definitions individually — any exported value with a valid kind is discovered automatically.

Creates a table definition.

table(input: Omit<TableDefinition, 'kind'>): TableDefinition

Minimal example:

import { schema, table } from '@chkit/core'
const users = table({
database: 'app',
name: 'users',
columns: [
{ name: 'id', type: 'UInt64' },
{ name: 'email', type: 'String' },
],
engine: 'MergeTree()',
primaryKey: ['id'],
orderBy: ['id'],
})
export default schema(users)

Comprehensive example (all features):

const events = table({
database: 'analytics',
name: 'events',
columns: [
{ name: 'id', type: 'UInt64' },
{ name: 'org_id', type: 'String' },
{ name: 'source', type: 'LowCardinality(String)' },
{ name: 'payload', type: 'String', nullable: true },
{ name: 'received_at', type: 'DateTime64(3)', default: 'fn:now64(3)' },
{ name: 'status', type: 'String', default: 'pending', comment: 'Event processing status' },
],
engine: 'MergeTree()',
primaryKey: ['id'],
orderBy: ['org_id', 'received_at', 'id'],
partitionBy: 'toYYYYMM(received_at)',
ttl: 'received_at + INTERVAL 90 DAY',
settings: { index_granularity: 8192 },
indexes: [
{ name: 'idx_source', expression: 'source', type: 'set', typeArgs: '0', granularity: 1 },
],
projections: [
{ name: 'p_recent', query: 'SELECT id ORDER BY received_at DESC LIMIT 10' },
],
comment: 'Raw ingested events',
})
FieldTypeDescription
databasestringClickHouse database name
namestringTable name
columnsColumnDefinition[]Column definitions (see Columns)
enginestringEngine clause, e.g. 'MergeTree()', 'ReplacingMergeTree(ver)'
primaryKeystring[]Primary key columns
orderBystring[]ORDER BY columns
FieldTypeDescription
partitionBystringPartition expression, e.g. 'toYYYYMM(created_at)'
uniqueKeystring[]Unique key columns
ttlstringTTL expression, e.g. 'created_at + INTERVAL 90 DAY'
settingsRecord<string, string | number | boolean>Table-level settings
indexesSkipIndexDefinition[]Skip indexes (see Skip indexes)
projectionsProjectionDefinition[]Projections (see Projections)
commentstringTable comment
renamedFrom{ database?: string; name: string }Previous identity for rename tracking (see Rename support)
pluginsTablePluginsPer-table plugin configuration (see Plugin configuration)

Each entry in the columns array is a ColumnDefinition.

Column name.

Any ClickHouse type string. Parameterized types like DateTime64(3), Decimal(18, 4), Enum8('a' = 1, 'b' = 2), and FixedString(32) are supported.

Primitive types recognized by the DSL type system: String, UInt8, UInt16, UInt32, UInt64, UInt128, UInt256, Int8, Int16, Int32, Int64, Int128, Int256, Float32, Float64, Bool, Boolean, Date, DateTime, DateTime64.

When true, the column type is wrapped in Nullable(...) in the generated SQL.

{ name: 'payload', type: 'String', nullable: true }
// SQL: `payload` Nullable(String)

default (string | number | boolean, optional)

Section titled “default (string | number | boolean, optional)”

Default value for the column.

  • String values are single-quoted in SQL: default: 'pending' produces DEFAULT 'pending'
  • Number/boolean values are rendered literally: default: 0 produces DEFAULT 0
  • fn: prefix — for function-call defaults, prefix the string with fn: to emit a raw SQL expression:
{ name: 'received_at', type: 'DateTime64(3)', default: 'fn:now64(3)' }
// SQL: `received_at` DateTime64(3) DEFAULT now64(3)

Column-level comment rendered in SQL.

Previous column name for rename tracking. See Rename support.

Each entry in the indexes array is a SkipIndexDefinition.

FieldTypeDescription
namestringIndex name
expressionstringIndexed expression
type'minmax' | 'set' | 'bloom_filter' | 'tokenbf_v1' | 'ngrambf_v1'Index type
typeArgsstring, optionalArguments for parameterized index types. Required for set, tokenbf_v1, ngrambf_v1 (ClickHouse 26+)
granularitynumberIndex granularity
indexes: [
{ name: 'idx_source', expression: 'source', type: 'set', typeArgs: '0', granularity: 1 },
{ name: 'idx_ts', expression: 'received_at', type: 'minmax', granularity: 3 },
]

Each entry in the projections array is a ProjectionDefinition.

FieldTypeDescription
namestringProjection name
querystringProjection SELECT query
projections: [
{ name: 'p_recent', query: 'SELECT id ORDER BY received_at DESC LIMIT 10' },
]

Creates a view definition.

view(input: Omit<ViewDefinition, 'kind'>): ViewDefinition
FieldTypeRequiredDescription
databasestringyesDatabase name
namestringyesView name
asstringyesSELECT query
commentstringnoView comment
import { view } from '@chkit/core'
const activeUsers = view({
database: 'app',
name: 'active_users',
as: 'SELECT id, email FROM app.users WHERE active = 1',
})

Creates a materialized view definition.

materializedView(input: Omit<MaterializedViewDefinition, 'kind'>): MaterializedViewDefinition
FieldTypeRequiredDescription
databasestringyesDatabase name
namestringyesMaterialized view name
to{ database: string; name: string }yesTarget table for the view
asstringyesSELECT query
commentstringnoView comment
import { materializedView } from '@chkit/core'
const eventCounts = materializedView({
database: 'analytics',
name: 'event_counts_mv',
to: { database: 'analytics', name: 'event_counts' },
as: 'SELECT org_id, count() AS total FROM analytics.events GROUP BY org_id',
})

The codegen plugin maps ClickHouse types to TypeScript types using these rules:

CategoryClickHouse TypesTypeScript Type
String-likeString, FixedString, Date, Date32, DateTime, DateTime64, UUID, IPv4, IPv6, Enum8, Enum16, Decimal*string
NumberInt8, Int16, Int32, UInt8, UInt16, UInt32, Float32, Float64, BFloat16number
Large integersInt64, Int128, Int256, UInt64, UInt128, UInt256string (default) or bigint
BooleanBool, Booleanboolean
WrappersNullable(T)T | null
WrappersLowCardinality(T)same as T
CompositeArray(T)T[]
CompositeMap(K, V)Record<K, V>
CompositeTuple(T1, T2, ...)[T1, T2, ...]
AggregateSimpleAggregateFunction(fn, T)same as T
JSONJSONRecord<string, unknown>

Parameterized types like DateTime('UTC'), Decimal(18, 4), and Enum8('a' = 1) are supported. The bigintMode option in the codegen plugin controls whether large integers map to string or bigint.

chkit tracks renames to avoid destructive drop-and-recreate operations.

Set renamedFrom on a table definition to rename a table:

const users = table({
database: 'app',
name: 'accounts', // new name
renamedFrom: { name: 'users' }, // old name
// ...
})

The database field in renamedFrom is optional and defaults to the table’s current database.

Set renamedFrom on a column definition to rename a column:

columns: [
{ name: 'user_email', type: 'String', renamedFrom: 'email' },
]

Both table and column renames can be overridden by CLI flags --rename-table and --rename-column.

The plugins field on a table definition provides per-table configuration for plugins. Each plugin that supports table-level config augments the TablePlugins interface via TypeScript declaration merging, so the available keys and their types depend on which plugin packages are imported.

import { table } from '@chkit/core'
const events = table({
database: 'app',
name: 'events',
columns: [
{ name: 'event_time', type: 'DateTime' },
{ name: 'id', type: 'UInt64' },
],
engine: 'MergeTree',
orderBy: ['event_time', 'id'],
primaryKey: ['event_time', 'id'],
plugins: {
backfill: { timeColumn: 'event_time' },
},
})

Currently supported plugin keys:

KeyPluginFieldsDescription
backfill@chkit/plugin-backfilltimeColumn?: stringTime column for backfill WHERE clauses

The plugins field is ignored by the diff engine — it does not affect migration planning or SQL generation.

chkit validates schema definitions and throws a ChxValidationError if any issues are found:

  • Duplicate object names — two definitions with the same kind, database, and name
  • Duplicate column names — repeated column name within a table
  • Duplicate index names — repeated index name within a table
  • Duplicate projection names — repeated projection name within a table
  • Primary key references missing columnprimaryKey includes a column not in columns
  • Order by references missing columnorderBy includes a column not in columns

When a property changes, chkit determines whether the table can be altered in place or must be dropped and recreated.

Structural (drop + recreate): engine, primaryKey, orderBy, partitionBy, uniqueKey

Alterable (ALTER in place): columns, indexes, projections, settings, TTL, comment

Views and materialized views always use drop + recreate.