npm package: @sqnest/drizzle
Description: Drizzle ORM utilities for NestJS
Author: SQUAREKNOT
Latest version: 0.1.5

# Drizzle Module

이 모듈은 NestJS에서 Drizzle ORM을 사용하기 위한 동적 모듈입니다. 기본 Drizzle 기능과 더불어 CLS(Continuation Local Storage)와의 통합을 통한 고급 기능도 제공합니다.

## 설치

```bash
npm install drizzle-orm mysql2

# CLS 기능을 사용하려면 추가 패키지 설치
npm install nestjs-cls @nestjs-cls/transactional @nestjs-cls/transactional-adapter-drizzle-orm
```

## 기본 사용법

### 1. 동기적 설정 (forRoot)

```typescript
import { Module } from '@nestjs/common';
import { DrizzleModule } from '@rsnest/drizzle';

@Module({
  imports: [
    DrizzleModule.forRoot({
      host: 'localhost',
      port: 3306,
      user: 'root',
      password: 'password',
      database: 'mydb',
      connectTimeout: 30000,
    }),
  ],
})
export class AppModule {}
```

### 2. 비동기적 설정 (forRootAsync)

ConfigService를 사용하여 환경 변수에서 설정을 가져오는 경우:

```typescript
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { DrizzleModule } from '@rsnest/drizzle';

@Module({
  imports: [
    ConfigModule.forRoot(),
    DrizzleModule.forRootAsync({
      useFactory: (configService: ConfigService) => ({
        host: configService.get('DB_HOST'),
        port: configService.get('DB_PORT'),
        user: configService.get('DB_USER'),
        password: configService.get('DB_PASSWORD'),
        database: configService.get('DB_DATABASE'),
        connectTimeout: 30000,
      }),
      inject: [ConfigService],
    }),
  ],
})
export class AppModule {}
```

### 3. 서비스에서 사용

```typescript
import { Injectable } from '@nestjs/common';
import { DrizzleService } from '@rsnest/drizzle';

@Injectable()
export class UserService {
  constructor(private readonly drizzle: DrizzleService) {}

  async getUsers() {
    return this.drizzle.select().from(users);
  }
}
```

## CLS (Continuation Local Storage) 통합

CLS 기능을 사용하면 다음과 같은 고급 기능을 이용할 수 있습니다:

- **자동 트랜잭션 관리**: `@Transactional` 데코레이터를 통한 선언적 트랜잭션
- **요청 컨텍스트 관리**: 요청 ID, 사용자 ID, 테넌트 ID 등의 자동 추적
- **멀티테넌시 지원**: 테넌트별 데이터 분리
- **감사 로그**: 자동 사용자 추적 및 메타데이터 생성

### 1. CLS 모듈 설정

```typescript
import { Module } from '@nestjs/common';
import { DrizzleClsModule } from '@rsnest/drizzle';

@Module({
  imports: [
    DrizzleClsModule.forRoot({
      host: 'localhost',
      port: 3306,
      user: 'root',
      password: 'password',
      database: 'mydb',
      // CLS 특정 옵션
      mountMiddleware: true,        // 자동 미들웨어 마운트
      enableTransactions: true,     // 트랜잭션 기능 활성화
      global: true,                 // 글로벌 모듈로 설정
    }),
  ],
})
export class AppModule {}
```

### 2. 트랜잭션 사용

#### 데코레이터 방식

```typescript
import { Injectable } from '@nestjs/common';
import { Transactional, DrizzleClsService } from '@rsnest/drizzle';

@Injectable()
export class UserService {
  constructor(private readonly drizzleCls: DrizzleClsService) {}

  @Transactional()
  async createUserAndProfile(userData: any, profileData: any) {
    // 트랜잭션 내에서 실행됨
    const user = await this.drizzleCls.drizzle
      .insert(users)
      .values(userData)
      .returning();

    const profile = await this.drizzleCls.drizzle
      .insert(profiles)
      .values({ ...profileData, userId: user[0].id })
      .returning();

    // 에러가 발생하면 자동으로 롤백됨
    if (Math.random() > 0.5) {
      throw new Error('Random error for testing rollback');
    }

    return { user: user[0], profile: profile[0] };
  }
}
```

#### 프로그래매틱 방식

```typescript
import { Injectable } from '@nestjs/common';
import { DrizzleClsService } from '@rsnest/drizzle';

@Injectable()
export class UserService {
  constructor(private readonly drizzleCls: DrizzleClsService) {}

  async createUserAndProfile(userData: any, profileData: any) {
    return this.drizzleCls.withTransaction(async () => {
      const user = await this.drizzleCls.drizzle
        .insert(users)
        .values(userData)
        .returning();

      const profile = await this.drizzleCls.drizzle
        .insert(profiles)
        .values({ ...profileData, userId: user[0].id })
        .returning();

      return { user: user[0], profile: profile[0] };
    });
  }
}
```

### 3. 컨텍스트 정보 활용

```typescript
import { Injectable } from '@nestjs/common';
import { DrizzleClsService } from '@rsnest/drizzle';

@Injectable()
export class AuditService {
  constructor(private readonly drizzleCls: DrizzleClsService) {}

  async createAuditLog(action: string, entityId: string) {
    const metadata = this.drizzleCls.getAuditMetadata();
    
    return this.drizzleCls.drizzle
      .insert(auditLogs)
      .values({
        action,
        entityId,
        userId: metadata.userId,
        requestId: metadata.requestId,
        timestamp: metadata.timestamp,
      });
  }

  async getUserSpecificData() {
    const userId = this.drizzleCls.getUserId();
    const tenantId = this.drizzleCls.getTenantId();
    
    // 현재 사용자의 데이터만 가져오기
    return this.drizzleCls.drizzle
      .select()
      .from(userDocuments)
      .where(and(
        eq(userDocuments.userId, userId),
        eq(userDocuments.tenantId, tenantId)
      ));
  }
}
```

### 4. 미들웨어를 통한 헤더 기반 컨텍스트 설정

CLS 모듈은 자동으로 다음 HTTP 헤더들을 CLS 컨텍스트에 저장합니다:

- `x-request-id`: 요청 ID (없으면 자동 생성)
- `x-user-id`: 사용자 ID
- `x-tenant-id`: 테넌트 ID

```bash
# API 호출 예시
curl -H "x-user-id: user-123" \
     -H "x-tenant-id: tenant-456" \
     -H "x-request-id: req-789" \
     http://localhost:3000/api/users
```

### 5. 멀티테넌시 지원

```typescript
import { Injectable } from '@nestjs/common';
import { DrizzleClsService } from '@rsnest/drizzle';

@Injectable()
export class MultiTenantService {
  constructor(private readonly drizzleCls: DrizzleClsService) {}

  async getTenantData() {
    const schemaPrefix = this.drizzleCls.getSchemaPrefix();
    const tenantId = this.drizzleCls.getTenantId();
    
    // 테넌트별 데이터 조회
    return this.drizzleCls.drizzle
      .select()
      .from(tenantData)
      .where(eq(tenantData.tenantId, tenantId));
  }

  async getLogContext() {
    // 로깅을 위한 전체 컨텍스트 정보
    return this.drizzleCls.getLogContext();
    // 결과: { requestId: '...', userId: '...', tenantId: '...', transactionActive: true }
  }
}
```

## API 레퍼런스

### DrizzleOptions

| 속성 | 타입 | 필수 | 기본값 | 설명 |
|------|------|------|--------|------|
| host | string | ✓ | - | 데이터베이스 호스트 |
| port | number | ✗ | 3306 | 데이터베이스 포트 |
| user | string | ✓ | - | 데이터베이스 사용자명 |
| password | string | ✓ | - | 데이터베이스 비밀번호 |
| database | string | ✓ | - | 데이터베이스 이름 |
| connectTimeout | number | ✗ | 30000 | 연결 타임아웃 (밀리초) |
| global | boolean | ✗ | false | 글로벌 모듈 여부 |

### DrizzleClsOptions

`DrizzleOptions`의 모든 속성과 추가로:

| 속성 | 타입 | 필수 | 기본값 | 설명 |
|------|------|------|--------|------|
| mountMiddleware | boolean | ✗ | true | CLS 미들웨어 자동 마운트 |
| enableTransactions | boolean | ✗ | true | 트랜잭션 기능 활성화 |
| namespace | string | ✗ | 'drizzle' | CLS 네임스페이스 |

### DrizzleClsService 메서드

#### 컨텍스트 관리
- `getRequestId()`: 현재 요청 ID 조회
- `getUserId()`: 현재 사용자 ID 조회  
- `getTenantId()`: 현재 테넌트 ID 조회
- `get<T>(key: string)`: 임의의 CLS 값 조회
- `set<T>(key: string, value: T)`: 임의의 CLS 값 설정

#### 트랜잭션 관리
- `isTransactionActive()`: 트랜잭션 활성 상태 확인
- `withTransaction<T>(callback)`: 트랜잭션 내에서 콜백 실행
- `withoutTransaction<T>(callback)`: 트랜잭션 없이 콜백 실행
- `drizzle`: 현재 Drizzle 인스턴스 (트랜잭션 지원)

#### 유틸리티
- `getLogContext()`: 로깅용 컨텍스트 정보
- `getSchemaPrefix()`: 멀티테넌시용 스키마 접두사
- `getAuditMetadata()`: 감사 로그용 메타데이터

## 주의사항

1. **CLS 기능 사용 시**: CLS 관련 패키지들이 설치되어 있어야 합니다.
2. **트랜잭션 사용 시**: `@Transactional` 데코레이터는 메서드에만 사용 가능합니다.
3. **컨텍스트 접근**: CLS 컨텍스트는 요청 생명주기 내에서만 유효합니다.
4. **멀티테넌시**: 테넌트 ID는 HTTP 헤더 또는 수동으로 설정해야 합니다.

## License

MIT