> ## Documentation Index
> Fetch the complete documentation index at: https://sonamu.cartanova.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Enums

> Enum 타입 정의하고 활용하기

Enum은 미리 정의된 값 목록 중 하나만 선택할 수 있는 열거형 타입입니다. 타입 안전성을 제공하고 사용자 인터페이스에서 선택 옵션을 쉽게 구성할 수 있습니다.

## Enum이란?

Enum은 제한된 값의 집합을 정의하여 다음을 제공합니다:

<CardGroup cols={2}>
  <Card title="타입 안전성" icon="shield-check">
    정의되지 않은 값 입력 방지

    TypeScript에서 Union 타입으로 자동 변환
  </Card>

  <Card title="UI 자동 생성" icon="list">
    선택 목록 자동 구성

    Select, ButtonSet 컴포넌트에 즉시 사용
  </Card>

  <Card title="레이블 관리" icon="tag">
    키와 표시명 분리

    다국어 지원 용이
  </Card>

  <Card title="유지보수성" icon="wrench">
    중앙 집중식 관리

    값 변경 시 한 곳만 수정
  </Card>
</CardGroup>

## Enum 정의하기

### entity.json에서 정의

Entity의 `enums` 섹션에 키-레이블 쌍으로 정의합니다.

```json title="user.entity.json" theme={null}
{
  "id": "User",
  "props": [
    {
      "name": "role",
      "type": "enum",
      "id": "UserRole",
      "desc": "사용자 역할"
    },
    {
      "name": "status",
      "type": "enum",
      "id": "UserStatus",
      "desc": "계정 상태"
    }
  ],
  "enums": {
    "UserRole": {
      "admin": "관리자",
      "normal": "일반 사용자",
      "guest": "게스트"
    },
    "UserStatus": {
      "active": "활성",
      "inactive": "비활성",
      "suspended": "정지",
      "deleted": "삭제됨"
    }
  }
}
```

**구조**:

* **Enum ID**: PascalCase (예: `UserRole`, `PostStatus`)
* **Key**: 소문자, 숫자, 언더스코어 (예: `admin`, `draft_saved`)
* **Label**: 표시용 텍스트, 한글 가능 (예: "관리자", "임시저장")

### Sonamu UI에서 정의

<Steps>
  <Step title="Entity 편집 페이지로 이동">
    Sonamu UI에서 Entity를 선택합니다.
  </Step>

  <Step title="Enums 섹션으로 스크롤">
    Props, Indexes, Subsets 아래에 Enums 섹션이 있습니다.
  </Step>

  <Step title="Add Enum 클릭">
    새 Enum을 추가합니다.

    **Enum ID 입력**:

    * PascalCase로 입력
    * Entity와 연관시키려면 `{Entity}` 패턴 사용
      * 예: `$ModelRole` → `UserRole` (자동 변환)
  </Step>

  <Step title="Enum 값 추가">
    각 Enum은 별도 탭으로 표시되며, **"Add Row"** 버튼으로 키-레이블 쌍을 추가합니다.

    * **Key**: 영문 소문자, 숫자, 언더스코어
    * **Label**: 한글 또는 표시명
  </Step>

  <Step title="저장">
    Entity를 저장하면 Enum이 함께 저장됩니다.
  </Step>
</Steps>

<Frame caption="📸 필요: Sonamu UI의 Enums 섹션 - 여러 Enum이 탭으로 구분되어 표시" />

## 자동 생성되는 코드

Enum을 정의하면 Sonamu가 자동으로 다음을 생성합니다:

### 1. Zod 스키마

```typescript title="sonamu.generated.ts" theme={null}
import { z } from "zod";

// Enum 스키마
export const UserRole = z.enum(["admin", "normal", "guest"]);

// Enum 레이블
export const UserRoleLabels = {
  admin: "관리자",
  normal: "일반 사용자",
  guest: "게스트",
} as const;
```

### 2. TypeScript 타입

```typescript title="user.types.ts" theme={null}
import { UserRole } from "./sonamu.generated";

// Union 타입으로 추론
export type UserRole = z.infer<typeof UserRole>;
// 결과: "admin" | "normal" | "guest"
```

### 3. 레이블 헬퍼 함수

```typescript title="sonamu.generated.ts" theme={null}
// 레이블 가져오기 함수
export function getUserRoleLabel(role: UserRole): string {
  return UserRoleLabels[role];
}
```

## Enum 사용하기

### Props에서 사용

```json theme={null}
{
  "props": [
    {
      "name": "role",
      "type": "enum",
      "id": "UserRole",
      "desc": "사용자 역할",
      "dbDefault": "normal"
    }
  ]
}
```

**데이터베이스**:

* 컬럼 타입: `text`
* 저장 값: Key 값 (예: `"admin"`)

**TypeScript**:

```typescript theme={null}
type User = {
  id: number;
  role: "admin" | "normal" | "guest"; // Union 타입
};
```

### API에서 사용

<CodeGroup>
  ```typescript title="Model에서 Enum 사용" theme={null}
  import { UserRole } from "./user.types";

  export class UserModel extends BaseModelClass {
    @api({ httpMethod: "POST" })
    async updateRole(userId: number, role: UserRole) {
      // role은 타입 안전함
      await this.puri()
        .where("id", userId)
        .update({ role });
        
      return { success: true };
    }
  }
  ```

  ```typescript title="Validation with Zod" theme={null}
  import { UserRole } from "./sonamu.generated";

  export const UserUpdateParams = z.object({
    id: z.number(),
    role: UserRole, // Enum 스키마로 자동 검증
  });

  export type UserUpdateParams = z.infer<typeof UserUpdateParams>;
  ```
</CodeGroup>

### 레이블 표시

<CodeGroup>
  ```typescript title="백엔드" theme={null}
  import { getUserRoleLabel } from "./sonamu.generated";

  const user = await UserModel.findById(1);
  const roleLabel = getUserRoleLabel(user.role);
  // "관리자"
  ```

  ```typescript title="프론트엔드" theme={null}
  import { UserRoleLabels } from "@/services/sonamu.generated";

  function UserBadge({ role }: { role: UserRole }) {
    return <span>{UserRoleLabels[role]}</span>;
    // "관리자"
  }
  ```
</CodeGroup>

## 자주 사용되는 Enum 패턴

### 1. OrderBy Enum

정렬 옵션을 정의합니다.

```json theme={null}
{
  "enums": {
    "UserOrderBy": {
      "id-desc": "ID 최신순",
      "id-asc": "ID 오래된순",
      "created_at-desc": "등록일 최신순",
      "created_at-asc": "등록일 오래된순",
      "username-asc": "이름순"
    }
  }
}
```

**활용**:

```typescript theme={null}
async findAll(orderBy: UserOrderBy = "id-desc") {
  const [field, direction] = orderBy.split("-");
  return this.puri()
    .orderBy(field, direction as "asc" | "desc")
    .many();
}
```

### 2. SearchField Enum

검색 가능한 필드를 정의합니다.

```json theme={null}
{
  "enums": {
    "UserSearchField": {
      "email": "이메일",
      "username": "이름",
      "phone": "전화번호"
    }
  }
}
```

**활용**:

```typescript theme={null}
async search(field: UserSearchField, keyword: string) {
  return this.puri()
    .whereLike(field, `%${keyword}%`)
    .many();
}
```

### 3. Status Enum

상태를 정의합니다.

```json theme={null}
{
  "enums": {
    "PostStatus": {
      "draft": "임시저장",
      "published": "발행됨",
      "archived": "보관됨",
      "deleted": "삭제됨"
    }
  }
}
```

**활용**:

```typescript theme={null}
async publish(postId: number) {
  await this.puri()
    .where("id", postId)
    .update({ status: "published" });
}
```

### 4. Type/Category Enum

분류를 정의합니다.

```json theme={null}
{
  "enums": {
    "NotificationType": {
      "email": "이메일",
      "sms": "문자메시지",
      "push": "푸시 알림",
      "in_app": "인앱 알림"
    }
  }
}
```

## Enum 배열 타입

여러 Enum 값을 배열로 저장할 수 있습니다.

```json theme={null}
{
  "props": [
    {
      "name": "permissions",
      "type": "enum[]",
      "id": "Permission",
      "desc": "권한 목록"
    }
  ],
  "enums": {
    "Permission": {
      "read": "읽기",
      "write": "쓰기",
      "delete": "삭제",
      "admin": "관리자"
    }
  }
}
```

**데이터베이스**:

* 컬럼 타입: `text[]`
* 저장 값: `["read", "write"]`

**TypeScript**:

```typescript theme={null}
type User = {
  id: number;
  permissions: ("read" | "write" | "delete" | "admin")[];
};
```

**활용**:

```typescript theme={null}
// 권한 체크
function hasPermission(user: User, permission: Permission): boolean {
  return user.permissions.includes(permission);
}

// 권한 추가
async grantPermission(userId: number, permission: Permission) {
  const user = await this.findById(userId);
  if (!user.permissions.includes(permission)) {
    await this.puri()
      .where("id", userId)
      .update({
        permissions: [...user.permissions, permission]
      });
  }
}
```

## 프론트엔드에서 Enum 사용

### Select 컴포넌트

<CodeGroup>
  ```tsx title="생성된 컴포넌트 사용" theme={null}
  import { UserRoleSelect } from "@/components/UserRoleSelect";

  function UserForm() {
    const [role, setRole] = useState<UserRole>("normal");
    
    return (
      <UserRoleSelect
        value={role}
        onChange={setRole}
      />
    );
  }
  ```

  ```tsx title="수동 구현" theme={null}
  import { UserRole, UserRoleLabels } from "@/services/sonamu.generated";

  function UserForm() {
    return (
      <select>
        {UserRole.options.map(key => (
          <option key={key} value={key}>
            {UserRoleLabels[key]}
          </option>
        ))}
      </select>
    );
  }
  ```
</CodeGroup>

### ButtonSet 컴포넌트

```tsx theme={null}
import { UserStatusButtonSet } from "@/components/UserStatusButtonSet";

function StatusFilter() {
  const [status, setStatus] = useState<UserStatus | null>(null);
  
  return (
    <UserStatusButtonSet
      value={status}
      onChange={setStatus}
      allowNull // 선택 해제 가능
    />
  );
}
```

## Enum 검증

Zod를 사용하여 자동으로 Enum 값을 검증합니다.

<CodeGroup>
  ```typescript title="API 파라미터 검증" theme={null}
  import { UserRole } from "./sonamu.generated";

  export const UpdateRoleParams = z.object({
    userId: z.number(),
    role: UserRole, // 자동 검증
  });

  // 잘못된 값 입력 시
  UpdateRoleParams.parse({
    userId: 1,
    role: "invalid_role" // Error!
  });
  ```

  ```typescript title="커스텀 검증" theme={null}
  import { UserRole } from "./sonamu.generated";

  // 특정 역할만 허용
  export const AdminOnlyParams = z.object({
    role: UserRole.refine(
      (val) => val === "admin" || val === "superadmin",
      { message: "관리자 권한이 필요합니다" }
    ),
  });
  ```
</CodeGroup>

## Enum 네이밍 규칙

### 권장 패턴

| 용도        | 패턴                    | 예시                                |
| --------- | --------------------- | --------------------------------- |
| Entity 상태 | `{Entity}Status`      | `PostStatus`, `OrderStatus`       |
| Entity 타입 | `{Entity}Type`        | `NotificationType`, `PaymentType` |
| Entity 역할 | `{Entity}Role`        | `UserRole`, `MemberRole`          |
| 정렬 옵션     | `{Entity}OrderBy`     | `UserOrderBy`, `PostOrderBy`      |
| 검색 필드     | `{Entity}SearchField` | `UserSearchField`                 |
| 카테고리      | `{Entity}Category`    | `ProductCategory`                 |
| 권한        | `{Entity}Permission`  | `UserPermission`                  |

### \$Model 패턴

Entity 이름을 동적으로 포함하려면 `$Model`을 사용합니다:

```json theme={null}
{
  "enums": {
    "$ModelRole": {
      "admin": "관리자",
      "normal": "일반"
    },
    "$ModelStatus": {
      "active": "활성",
      "inactive": "비활성"
    }
  }
}
```

**변환 결과**:

* `User` Entity → `UserRole`, `UserStatus`
* `Post` Entity → `PostRole`, `PostStatus`

## 주의사항

<Warning>
  **Enum 값 변경 시 주의**

  이미 데이터베이스에 저장된 Enum 키를 변경하면 기존 데이터와 불일치가 발생합니다.

  **변경 방법**:

  1. 새 키 추가
  2. 데이터 마이그레이션 (기존 키 → 새 키)
  3. 구 키 삭제
</Warning>

<Warning>
  **Key 네이밍 제한**

  Enum Key는 다음만 사용 가능합니다:

  * 소문자 (a-z)
  * 숫자 (0-9)
  * 언더스코어 (\_)

  하이픈(-)은 사용 불가합니다.
</Warning>

<Tip>
  **레이블 변경은 자유롭게**

  Label은 표시용이므로 언제든지 변경 가능합니다. 데이터베이스에는 Key만 저장됩니다.
</Tip>

## Enum vs 별도 테이블

| 기준    | Enum          | 별도 테이블        |
| ----- | ------------- | ------------- |
| 값 개수  | 적음 (\< 20개)   | 많음 (> 20개)    |
| 변경 빈도 | 거의 없음         | 자주 변경         |
| 추가 정보 | 불필요           | 필요 (설명, 순서 등) |
| 성능    | 빠름 (JOIN 불필요) | 느림 (JOIN 필요)  |
| 권장 사용 | 상태, 역할, 타입    | 카테고리, 코드 테이블  |

**Enum 사용 예시**:

* 사용자 역할 (admin, user, guest)
* 게시글 상태 (draft, published, deleted)
* 알림 타입 (email, sms, push)

**별도 테이블 사용 예시**:

* 지역 목록 (서울, 부산, ...)
* 상품 카테고리 (계층 구조)
* 국가 코드 (추가 정보 많음)

## 다음 단계

<CardGroup cols={2}>
  <Card title="Field Types" icon="list" href="/ko/core-concepts/entity/field-types">
    다른 필드 타입 알아보기
  </Card>

  <Card title="Validation" icon="shield-check" href="/ko/api-development/validation/zod-schemas">
    Zod로 데이터 검증하기
  </Card>

  <Card title="Frontend Components" icon="browser" href="/ko/frontend-integration/generated-services/using-services">
    생성된 컴포넌트 사용하기
  </Card>

  <Card title="API Development" icon="code" href="/ko/api-development/creating-apis/basic-crud">
    Enum을 사용한 API 개발
  </Card>
</CardGroup>
