$ docker run -d --name orm-clickhouse \
-e CLICKHOUSE_USER=orm_test \
-e CLICKHOUSE_PASSWORD=orm_test_pass \
-e CLICKHOUSE_DB=orm_test \
-p 8123:8123 -p 9000:9000 \
clickhouse/clickhouse-server:24.8-alpine8123 is the HTTP interface prorm uses; 9000 is the native protocol,clickhouse-client. The repository's compose file defines the same$ docker compose -f docker-compose.test.yml up -d --wait clickhouse$ docker compose up -d --wait # returns once the healthcheck passes
$ docker compose down -v # tear down, volumes included$ until curl -sf "http://127.0.0.1:8123/ping" >/dev/null; do sleep 0.5; done/ping returns Ok. once the server can serve queries.import { Prorm, DataTypes } from 'ts-prorm-orm';
const db = new Prorm({
dialect: 'clickhouse',
host: '127.0.0.1',
port: 8123,
username: 'orm_test',
password: 'orm_test_pass',
database: 'orm_test',
dialectOptions: {
protocol: 'http', // 'https' would use 8443
},
});
await db.connect();import { Prorm } from 'ts-prorm-orm';
const db = new Prorm({
dialect: 'clickhouse',
host: process.env.CLICKHOUSE_HOST, // xxx.clickhouse.cloud
port: 8443,
username: 'default',
password: process.env.CLICKHOUSE_PASSWORD,
database: 'default',
dialectOptions: { protocol: 'https' },
});
await db.connect();db is the instance from the Local tab above.import { DataTypes, Op } from 'ts-prorm-orm';
const Hit = db.define('Hit', {
id: { type: DataTypes.INTEGER(), primaryKey: true }, // a sorting key, not a constraint
path: { type: DataTypes.STRING(255) },
status: { type: DataTypes.INTEGER() },
timestamp: { type: DataTypes.DATE() },
});
await db.sync({ force: true }); // a throwaway database: recreate the tables
try {
// Insert in batches: ClickHouse is built for bulk appends, not row-at-a-time writes
await Hit.bulkCreate([
{ id: 1, path: '/', status: 200, timestamp: new Date() },
{ id: 2, path: '/docs', status: 200, timestamp: new Date() },
{ id: 3, path: '/missing', status: 404, timestamp: new Date() },
]);
const errors = await Hit.findAll({ where: { status: { [Op.gte]: 400 } }, order: [['timestamp', 'DESC']] });
console.log(errors.length, await Hit.count());
} finally {
await db.close();
}db.transaction() has nothing to map onto.UPDATE/DELETE are mutations — asynchronous, expensive, and notwhere on an index definition isMergeTree and friends) and the aggregate helpers.$ docker rm -f orm-clickhouse| Symptom | Cause |
|---|---|
Authentication failed | The env vars seed the server only on a fresh volume. |
Table already exists after sync({ force: true }) | Drops are asynchronous; give it a moment or use a distinct database per run. |
| Row missing right after insert | Inserts are visible quickly but not synchronously across replicas — single-node dev should be immediate; check you queried the same database. |
| Memory limit exceeded | A query exceeded max_memory_usage. Aggregate more aggressively or raise the limit. |