npm package: @makebelieve21213-packages/clickhouse-client
Description: ClickHouse database client module for NestJS with migration support for OLAP analytics
Author: Skryabin Aleksey
Latest version: 1.0.1

# @makebelieve21213-packages/clickhouse-client

ClickHouse database client для NestJS с поддержкой TypeScript и полной типобезопасностью.

## 📋 Содержание

- [Возможности](#-возможности)
- [Требования](#-требования)
- [Установка](#-установка)
- [Структура пакета](#-структура-пакета)
- [Быстрый старт](#-быстрый-старт)
- [API Reference](#-api-reference)
- [Примеры использования](#-примеры-использования)
- [Миграции](#-миграции)
- [Troubleshooting](#-troubleshooting)
- [Тестирование](#-тестирование)

## 🚀 Возможности

- ✅ **NestJS интеграция** - глобальный модуль с forRootAsync для простой интеграции
- ✅ **Type-safe API** - полная типобезопасность TypeScript с экспортируемыми типами
- ✅ **ClickHouse клиент** - использование официального @clickhouse/client
- ✅ **Конфигурация** - поддержка конфигурации через ConfigModule
- ✅ **Миграции** - поддержка миграций через clickhouse-migrations
- ✅ **Singleton паттерн** - эффективное использование ресурсов
- ✅ **Graceful shutdown** - корректное закрытие соединения при завершении приложения
- ✅ **Обработка ошибок** - детальная обработка ошибок API с логированием
- ✅ **Параметризованные запросы** - безопасность через параметризацию
- ✅ **100% покрытие тестами** - надежность и качество кода

## 📋 Требования

- **Node.js**: >= 22.11.0
- **NestJS**: >= 11.0.0
- **ClickHouse Server**: рекомендуется версия 23.x или выше

## 📦 Установка

```bash
npm install @makebelieve21213-packages/clickhouse-client
```

### Зависимости

```json
{
  "@nestjs/common": "^11.0.0",
  "@nestjs/config": "^4.0.0",
  "@clickhouse/client": "^1.12.0",
  "@packages/logger": "^1.0.0",
  "@packages/types": "^1.0.0",
  "reflect-metadata": "^0.1.13 || ^0.2.0"
}
```

## 📁 Структура пакета

```
src/
├── main/                    # NestJS модуль
├── types/                   # TypeScript типы
├── migrations/              # SQL миграции
├── utils/                   # Утилиты
└── index.ts                 # Экспорты
```

## 🏗️ Архитектура

Пакет предоставляет NestJS глобальный модуль `ClickhouseClientModule` для работы с ClickHouse базой данных через официальный клиент @clickhouse/client.

**Основные компоненты:**
- `ClickhouseClientModule` - NestJS глобальный модуль
- `ClickhouseClientService` - сервис для работы с базой данных
- `ClickhouseConfig` - конфигурация клиента

## 🔧 Быстрый старт

### Шаг 1: Настройка переменных окружения

```env
CH_URL=http://localhost:8123
CH_USERNAME=default
CH_PASSWORD=your-password-here
CH_DATABASE=NA_KOLENKE_CHAIN
```

### Шаг 2: Создание конфигурации

Создайте файл `clickhouse.config.ts` в вашем сервисе:

```typescript
import { registerAs } from "@nestjs/config";
import type { ClickhouseConfig } from "@makebelieve21213-packages/clickhouse-client";
import { CONFIG_SYMBOLS } from "@packages/types";

const clickhouseConfig = registerAs<ClickhouseConfig>(
  CONFIG_SYMBOLS.CLICKHOUSE,
  (): ClickhouseConfig => ({
    url: process.env.CH_URL!,
    username: process.env.CH_USERNAME!,
    password: process.env.CH_PASSWORD!,
    database: process.env.CH_DATABASE!,
  }),
);

export default clickhouseConfig;
```

### Шаг 3: Регистрация модуля

```typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { ClickhouseClientModule, type ClickhouseConfig } from '@makebelieve21213-packages/clickhouse-client';
import { CONFIG_SYMBOLS } from '@packages/types';
import clickhouseConfig from 'src/configs/clickhouse.config';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      load: [clickhouseConfig],
    }),
    ClickhouseClientModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: (...args: unknown[]) => {
        const configService = args[0] as ConfigService;
        return configService.get<ClickhouseConfig>(CONFIG_SYMBOLS.CLICKHOUSE)!;
      },
      inject: [ConfigService],
    }),
  ],
})
export class AppModule {}
```

### Шаг 4: Использование сервиса

```typescript
// analytics.service.ts
import { Injectable } from '@nestjs/common';
import { ClickhouseClientService } from '@makebelieve21213-packages/clickhouse-client';
import type { Transfer } from './types';

@Injectable()
export class AnalyticsService {
  constructor(private readonly clickhouse: ClickhouseClientService) {}

  async getTransfers(blockNumber: number): Promise<Transfer[]> {
    return await this.clickhouse.query<Transfer>(
      'SELECT * FROM transfers WHERE block_number > {blockNumber:UInt64}',
      { blockNumber }
    );
  }
}
```

## 📚 API Reference

### ClickhouseClientModule

**forRootAsync(options):**

```typescript
ClickhouseClientModule.forRootAsync({
  imports: [ConfigModule],
  useFactory: (...args: unknown[]) => {
    const configService = args[0] as ConfigService;
    return configService.get<ClickhouseConfig>(CONFIG_SYMBOLS.CLICKHOUSE)!;
  },
  inject: [ConfigService],
})
```

**Экспортирует:** `ClickhouseClientService` (глобально)

### ClickhouseClientService

**Конфигурация:**
- `url: string` - URL сервера ClickHouse
- `username: string` - имя пользователя
- `password: string` - пароль
- `database: string` - имя базы данных

**Методы:**

#### `exec(sql, params?)`

Выполняет SQL команды (CREATE, INSERT, UPDATE, DELETE, ALTER). Поддерживает выполнение нескольких команд, разделенных точкой с запятой.

```typescript
async exec(sql: string, params?: Record<string, unknown>): Promise<void>
```

**Пример:**
```typescript
// Создание таблицы
await clickhouse.exec(`
  CREATE TABLE IF NOT EXISTS test_table (
    id UInt64,
    name String,
    created_at DateTime64(3, 'UTC')
  ) ENGINE = MergeTree() ORDER BY id
`);

// Выполнение с параметрами
await clickhouse.exec(
  'ALTER TABLE test_table DELETE WHERE id = {id:UInt64}',
  { id: 123 }
);

// Множественные команды
await clickhouse.exec(`
  CREATE TABLE IF NOT EXISTS table1 (id UInt64) ENGINE = Memory;
  CREATE TABLE IF NOT EXISTS table2 (id UInt64) ENGINE = Memory;
`);
```

#### `query<T>(sql, params?)`

Выполняет SELECT запросы и возвращает результат в формате JSON.

```typescript
async query<T>(sql: string, params?: Record<string, unknown>): Promise<T[]>
```

**Пример:**
```typescript
interface Transfer {
  contract_address: string;
  block_number: number;
  tx_hash: string;
  from: string;
  to: string;
  value: number;
}

// Простой запрос
const transfers = await clickhouse.query<Transfer>(
  'SELECT * FROM transfers LIMIT 10'
);

// Запрос с параметрами
const recentTransfers = await clickhouse.query<Transfer>(
  'SELECT * FROM transfers WHERE block_number > {blockNumber:UInt64} AND timestamp > {date:DateTime64}',
  { 
    blockNumber: 18000000,
    date: new Date().toISOString()
  }
);

// Агрегация данных
const stats = await clickhouse.query<{ count: number; total_value: number }>(
  'SELECT count() as count, sum(value) as total_value FROM transfers WHERE block_number > {blockNumber:UInt64}',
  { blockNumber: 18000000 }
);
```

#### `insert<T>(tableName, rows)`

Вставляет массив объектов в указанную таблицу. Автоматически фильтрует `null` и `undefined` значения.

```typescript
async insert<T>(tableName: string, rows: T[]): Promise<void>
```

**Пример:**
```typescript
const transfersData = [
  {
    contract_address: '0x123...',
    transfer_type: 'ERC-20',
    block_number: 18000001,
    timestamp: new Date().toISOString(),
    tx_hash: '0xabc...',
    from: '0x456...',
    to: '0x789...',
    value: 100.5,
    symbol: 'ETH',
    gas_used: 21000,
    gas_price: 20000000000,
    fee_eth: 0.00042
  },
  // ... больше данных
];

// Вставка в основную таблицу
await clickhouse.insert('transfers', transfersData);

// Вставка в буферную таблицу (рекомендуется для массовых операций)
await clickhouse.insert('transfers_buffer', transfersData);
```

#### `get client()`

Прямой доступ к клиенту ClickHouse для расширенных операций.

```typescript
get client(): ClickHouseClient | undefined
```

**Пример:**
```typescript
// Получение статистики сервера
const stats = await clickhouse.client?.query({
  query: 'SELECT * FROM system.metrics',
  format: 'JSON'
});
```

## 🧪 Примеры использования

### Выполнение команд

```typescript
await clickhouse.exec('CREATE TABLE test (id UInt64) ENGINE = Memory');
```

### Типизированные запросы

```typescript
const transfers = await clickhouse.query<Transfer>(
  'SELECT * FROM transfers WHERE block_number > {blockNumber:UInt64}',
  { blockNumber: 18000000 }
);
```

### Массовая вставка

```typescript
await clickhouse.insert('transfers_buffer', transfersData);
```

## 📝 Миграции

Пакет включает систему миграций для управления схемой базы данных через `clickhouse-migrations`.

### Структура миграций

Миграции находятся в папке `src/migrations/`:
- `1_create_main_database.sql` - Создание основной базы данных `NA_KOLENKE_CHAIN`
- `2_create_analytics_table.sql` - Создание таблиц `transfers` и `transfers_buffer`

### Выполнение миграций

```bash
pnpm run migrate:dev
```

### Создание новой миграции

1. Создайте новый файл в `src/migrations/` с номером и описательным именем:
   ```
   3_add_user_table.sql
   ```

2. Добавьте SQL команды:
   ```sql
   CREATE TABLE IF NOT EXISTS NA_KOLENKE_CHAIN.users (
       id UInt64,
       wallet_address String,
       created_at DateTime64(3, 'UTC')
   ) ENGINE = MergeTree() ORDER BY id;
   ```

3. Выполните миграцию:
   ```bash
   pnpm run migrate:dev
   ```

## 🚨 Troubleshooting

### Connection timeout

**Решение:** Проверить доступность сервера ClickHouse, проверить переменные окружения CH_URL, CH_USERNAME, CH_PASSWORD.

### Table not found

**Решение:** Выполнить миграции через `pnpm run migrate:dev`, проверить правильность имени базы данных.

### Query execution failed

**Решение:** Проверить синтаксис SQL запроса, проверить типы параметров в параметризованных запросах.

### Insert failed

**Решение:** Проверить соответствие полей объектов названиям колонок таблицы, проверить типы данных.

## 🧪 Тестирование

Пакет имеет **100% покрытие тестами**.

```bash
pnpm test                # Все тесты
pnpm test:coverage       # С покрытием
pnpm test:watch          # Watch режим
```

## 🔧 Конфигурация

```typescript
interface ClickhouseConfig {
  url: string;        // URL сервера ClickHouse
  username: string;   // Имя пользователя
  password: string;   // Пароль
  database: string;   // Имя базы данных
}
```

**Примечание:** Конфигурация должна создаваться в сервисе, который использует пакет.

## 📦 Зависимости

- `@nestjs/common` - NestJS core
- `@nestjs/config` - NestJS config
- `@clickhouse/client` - официальный клиент ClickHouse
- `@packages/logger` - логирование
- `@packages/types` - типы для конфигурации
- `clickhouse-migrations` - утилита для миграций
- `reflect-metadata` - TypeScript decorators

## 📄 Лицензия

MIT

## 👥 Автор

Skryabin Aleksey