๋ฉ”์ธ ์ฝ˜ํ…์ธ ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ
Enum์€ ๋ฏธ๋ฆฌ ์ •์˜๋œ ๊ฐ’ ๋ชฉ๋ก ์ค‘ ํ•˜๋‚˜๋งŒ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋Š” ์—ด๊ฑฐํ˜• ํƒ€์ž…์ž…๋‹ˆ๋‹ค. ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ์ œ๊ณตํ•˜๊ณ  ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค์—์„œ ์„ ํƒ ์˜ต์…˜์„ ์‰ฝ๊ฒŒ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Enum์ด๋ž€?

Enum์€ ์ œํ•œ๋œ ๊ฐ’์˜ ์ง‘ํ•ฉ์„ ์ •์˜ํ•˜์—ฌ ๋‹ค์Œ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค:

ํƒ€์ž… ์•ˆ์ „์„ฑ

์ •์˜๋˜์ง€ ์•Š์€ ๊ฐ’ ์ž…๋ ฅ ๋ฐฉ์ง€TypeScript์—์„œ Union ํƒ€์ž…์œผ๋กœ ์ž๋™ ๋ณ€ํ™˜

UI ์ž๋™ ์ƒ์„ฑ

์„ ํƒ ๋ชฉ๋ก ์ž๋™ ๊ตฌ์„ฑSelect, ButtonSet ์ปดํฌ๋„ŒํŠธ์— ์ฆ‰์‹œ ์‚ฌ์šฉ

๋ ˆ์ด๋ธ” ๊ด€๋ฆฌ

ํ‚ค์™€ ํ‘œ์‹œ๋ช… ๋ถ„๋ฆฌ๋‹ค๊ตญ์–ด ์ง€์› ์šฉ์ด

์œ ์ง€๋ณด์ˆ˜์„ฑ

์ค‘์•™ ์ง‘์ค‘์‹ ๊ด€๋ฆฌ๊ฐ’ ๋ณ€๊ฒฝ ์‹œ ํ•œ ๊ณณ๋งŒ ์ˆ˜์ •

Enum ์ •์˜ํ•˜๊ธฐ

entity.json์—์„œ ์ •์˜

Entity์˜ enums ์„น์…˜์— ํ‚ค-๋ ˆ์ด๋ธ” ์Œ์œผ๋กœ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
user.entity.json
{
  "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์—์„œ ์ •์˜

1

Entity ํŽธ์ง‘ ํŽ˜์ด์ง€๋กœ ์ด๋™

Sonamu UI์—์„œ Entity๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.
2

Enums ์„น์…˜์œผ๋กœ ์Šคํฌ๋กค

Props, Indexes, Subsets ์•„๋ž˜์— Enums ์„น์…˜์ด ์žˆ์Šต๋‹ˆ๋‹ค.
3

Add Enum ํด๋ฆญ

์ƒˆ Enum์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.Enum ID ์ž…๋ ฅ:
  • PascalCase๋กœ ์ž…๋ ฅ
  • Entity์™€ ์—ฐ๊ด€์‹œํ‚ค๋ ค๋ฉด {Entity} ํŒจํ„ด ์‚ฌ์šฉ
    • ์˜ˆ: $ModelRole โ†’ UserRole (์ž๋™ ๋ณ€ํ™˜)
4

Enum ๊ฐ’ ์ถ”๊ฐ€

๊ฐ Enum์€ ๋ณ„๋„ ํƒญ์œผ๋กœ ํ‘œ์‹œ๋˜๋ฉฐ, โ€œAdd Rowโ€ ๋ฒ„ํŠผ์œผ๋กœ ํ‚ค-๋ ˆ์ด๋ธ” ์Œ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
  • Key: ์˜๋ฌธ ์†Œ๋ฌธ์ž, ์ˆซ์ž, ์–ธ๋”์Šค์ฝ”์–ด
  • Label: ํ•œ๊ธ€ ๋˜๋Š” ํ‘œ์‹œ๋ช…
5

์ €์žฅ

Entity๋ฅผ ์ €์žฅํ•˜๋ฉด Enum์ด ํ•จ๊ป˜ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.

๐Ÿ“ธ ํ•„์š”: Sonamu UI์˜ Enums ์„น์…˜ - ์—ฌ๋Ÿฌ Enum์ด ํƒญ์œผ๋กœ ๊ตฌ๋ถ„๋˜์–ด ํ‘œ์‹œ

์ž๋™ ์ƒ์„ฑ๋˜๋Š” ์ฝ”๋“œ

Enum์„ ์ •์˜ํ•˜๋ฉด Sonamu๊ฐ€ ์ž๋™์œผ๋กœ ๋‹ค์Œ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค:

1. Zod ์Šคํ‚ค๋งˆ

sonamu.generated.ts
import { z } from "zod";

// Enum ์Šคํ‚ค๋งˆ
export const UserRole = z.enum(["admin", "normal", "guest"]);

// Enum ๋ ˆ์ด๋ธ”
export const UserRoleLabels = {
  admin: "๊ด€๋ฆฌ์ž",
  normal: "์ผ๋ฐ˜ ์‚ฌ์šฉ์ž",
  guest: "๊ฒŒ์ŠคํŠธ",
} as const;

2. TypeScript ํƒ€์ž…

user.types.ts
import { UserRole } from "./sonamu.generated";

// Union ํƒ€์ž…์œผ๋กœ ์ถ”๋ก 
export type UserRole = z.infer<typeof UserRole>;
// ๊ฒฐ๊ณผ: "admin" | "normal" | "guest"

3. ๋ ˆ์ด๋ธ” ํ—ฌํผ ํ•จ์ˆ˜

sonamu.generated.ts
// ๋ ˆ์ด๋ธ” ๊ฐ€์ ธ์˜ค๊ธฐ ํ•จ์ˆ˜
export function getUserRoleLabel(role: UserRole): string {
  return UserRoleLabels[role];
}

Enum ์‚ฌ์šฉํ•˜๊ธฐ

Props์—์„œ ์‚ฌ์šฉ

{
  "props": [
    {
      "name": "role",
      "type": "enum",
      "id": "UserRole",
      "desc": "์‚ฌ์šฉ์ž ์—ญํ• ",
      "dbDefault": "normal"
    }
  ]
}
๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค:
  • ์ปฌ๋Ÿผ ํƒ€์ž…: text
  • ์ €์žฅ ๊ฐ’: Key ๊ฐ’ (์˜ˆ: "admin")
TypeScript:
type User = {
  id: number;
  role: "admin" | "normal" | "guest"; // Union ํƒ€์ž…
};

API์—์„œ ์‚ฌ์šฉ

import { UserRole } from "./user.types";

export class UserModel extends BaseModel {
  @api({ httpMethod: "POST" })
  async updateRole(userId: number, role: UserRole) {
    // role์€ ํƒ€์ž… ์•ˆ์ „ํ•จ
    await this.puri()
      .where("id", userId)
      .update({ role });
      
    return { success: true };
  }
}

๋ ˆ์ด๋ธ” ํ‘œ์‹œ

import { getUserRoleLabel } from "./sonamu.generated";

const user = await UserModel.findById(1);
const roleLabel = getUserRoleLabel(user.role);
// "๊ด€๋ฆฌ์ž"

์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” Enum ํŒจํ„ด

1. OrderBy Enum

์ •๋ ฌ ์˜ต์…˜์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
{
  "enums": {
    "UserOrderBy": {
      "id-desc": "ID ์ตœ์‹ ์ˆœ",
      "id-asc": "ID ์˜ค๋ž˜๋œ์ˆœ",
      "created_at-desc": "๋“ฑ๋ก์ผ ์ตœ์‹ ์ˆœ",
      "created_at-asc": "๋“ฑ๋ก์ผ ์˜ค๋ž˜๋œ์ˆœ",
      "username-asc": "์ด๋ฆ„์ˆœ"
    }
  }
}
ํ™œ์šฉ:
async findAll(orderBy: UserOrderBy = "id-desc") {
  const [field, direction] = orderBy.split("-");
  return this.puri()
    .orderBy(field, direction as "asc" | "desc")
    .many();
}

2. SearchField Enum

๊ฒ€์ƒ‰ ๊ฐ€๋Šฅํ•œ ํ•„๋“œ๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
{
  "enums": {
    "UserSearchField": {
      "email": "์ด๋ฉ”์ผ",
      "username": "์ด๋ฆ„",
      "phone": "์ „ํ™”๋ฒˆํ˜ธ"
    }
  }
}
ํ™œ์šฉ:
async search(field: UserSearchField, keyword: string) {
  return this.puri()
    .whereLike(field, `%${keyword}%`)
    .many();
}

3. Status Enum

์ƒํƒœ๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
{
  "enums": {
    "PostStatus": {
      "draft": "์ž„์‹œ์ €์žฅ",
      "published": "๋ฐœํ–‰๋จ",
      "archived": "๋ณด๊ด€๋จ",
      "deleted": "์‚ญ์ œ๋จ"
    }
  }
}
ํ™œ์šฉ:
async publish(postId: number) {
  await this.puri()
    .where("id", postId)
    .update({ status: "published" });
}

4. Type/Category Enum

๋ถ„๋ฅ˜๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
{
  "enums": {
    "NotificationType": {
      "email": "์ด๋ฉ”์ผ",
      "sms": "๋ฌธ์ž๋ฉ”์‹œ์ง€",
      "push": "ํ‘ธ์‹œ ์•Œ๋ฆผ",
      "in_app": "์ธ์•ฑ ์•Œ๋ฆผ"
    }
  }
}

Enum ๋ฐฐ์—ด ํƒ€์ž…

์—ฌ๋Ÿฌ Enum ๊ฐ’์„ ๋ฐฐ์—ด๋กœ ์ €์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
{
  "props": [
    {
      "name": "permissions",
      "type": "enum[]",
      "id": "Permission",
      "desc": "๊ถŒํ•œ ๋ชฉ๋ก"
    }
  ],
  "enums": {
    "Permission": {
      "read": "์ฝ๊ธฐ",
      "write": "์“ฐ๊ธฐ",
      "delete": "์‚ญ์ œ",
      "admin": "๊ด€๋ฆฌ์ž"
    }
  }
}
๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค:
  • ์ปฌ๋Ÿผ ํƒ€์ž…: text[]
  • ์ €์žฅ ๊ฐ’: ["read", "write"]
TypeScript:
type User = {
  id: number;
  permissions: ("read" | "write" | "delete" | "admin")[];
};
ํ™œ์šฉ:
// ๊ถŒํ•œ ์ฒดํฌ
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 ์ปดํฌ๋„ŒํŠธ

import { UserRoleSelect } from "@/components/UserRoleSelect";

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

ButtonSet ์ปดํฌ๋„ŒํŠธ

import { UserStatusButtonSet } from "@/components/UserStatusButtonSet";

function StatusFilter() {
  const [status, setStatus] = useState<UserStatus | null>(null);
  
  return (
    <UserStatusButtonSet
      value={status}
      onChange={setStatus}
      allowNull // ์„ ํƒ ํ•ด์ œ ๊ฐ€๋Šฅ
    />
  );
}

Enum ๊ฒ€์ฆ

Zod๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ž๋™์œผ๋กœ Enum ๊ฐ’์„ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.
import { UserRole } from "./sonamu.generated";

export const UpdateRoleParams = z.object({
  userId: z.number(),
  role: UserRole, // ์ž๋™ ๊ฒ€์ฆ
});

// ์ž˜๋ชป๋œ ๊ฐ’ ์ž…๋ ฅ ์‹œ
UpdateRoleParams.parse({
  userId: 1,
  role: "invalid_role" // Error!
});

Enum ๋„ค์ด๋ฐ ๊ทœ์น™

๊ถŒ์žฅ ํŒจํ„ด

์šฉ๋„ํŒจํ„ด์˜ˆ์‹œ
Entity ์ƒํƒœ{Entity}StatusPostStatus, OrderStatus
Entity ํƒ€์ž…{Entity}TypeNotificationType, PaymentType
Entity ์—ญํ• {Entity}RoleUserRole, MemberRole
์ •๋ ฌ ์˜ต์…˜{Entity}OrderByUserOrderBy, PostOrderBy
๊ฒ€์ƒ‰ ํ•„๋“œ{Entity}SearchFieldUserSearchField
์นดํ…Œ๊ณ ๋ฆฌ{Entity}CategoryProductCategory
๊ถŒํ•œ{Entity}PermissionUserPermission

$Model ํŒจํ„ด

Entity ์ด๋ฆ„์„ ๋™์ ์œผ๋กœ ํฌํ•จํ•˜๋ ค๋ฉด $Model์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค:
{
  "enums": {
    "$ModelRole": {
      "admin": "๊ด€๋ฆฌ์ž",
      "normal": "์ผ๋ฐ˜"
    },
    "$ModelStatus": {
      "active": "ํ™œ์„ฑ",
      "inactive": "๋น„ํ™œ์„ฑ"
    }
  }
}
๋ณ€ํ™˜ ๊ฒฐ๊ณผ:
  • User Entity โ†’ UserRole, UserStatus
  • Post Entity โ†’ PostRole, PostStatus

์ฃผ์˜์‚ฌํ•ญ

Enum ๊ฐ’ ๋ณ€๊ฒฝ ์‹œ ์ฃผ์˜์ด๋ฏธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ๋œ Enum ํ‚ค๋ฅผ ๋ณ€๊ฒฝํ•˜๋ฉด ๊ธฐ์กด ๋ฐ์ดํ„ฐ์™€ ๋ถˆ์ผ์น˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.๋ณ€๊ฒฝ ๋ฐฉ๋ฒ•:
  1. ์ƒˆ ํ‚ค ์ถ”๊ฐ€
  2. ๋ฐ์ดํ„ฐ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ (๊ธฐ์กด ํ‚ค โ†’ ์ƒˆ ํ‚ค)
  3. ๊ตฌ ํ‚ค ์‚ญ์ œ
Key ๋„ค์ด๋ฐ ์ œํ•œEnum Key๋Š” ๋‹ค์Œ๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค:
  • ์†Œ๋ฌธ์ž (a-z)
  • ์ˆซ์ž (0-9)
  • ์–ธ๋”์Šค์ฝ”์–ด (_)
ํ•˜์ดํ”ˆ(-)์€ ์‚ฌ์šฉ ๋ถˆ๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
๋ ˆ์ด๋ธ” ๋ณ€๊ฒฝ์€ ์ž์œ ๋กญ๊ฒŒLabel์€ ํ‘œ์‹œ์šฉ์ด๋ฏ€๋กœ ์–ธ์ œ๋“ ์ง€ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—๋Š” Key๋งŒ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.

Enum vs ๋ณ„๋„ ํ…Œ์ด๋ธ”

๊ธฐ์ค€Enum๋ณ„๋„ ํ…Œ์ด๋ธ”
๊ฐ’ ๊ฐœ์ˆ˜์ ์Œ (< 20๊ฐœ)๋งŽ์Œ (> 20๊ฐœ)
๋ณ€๊ฒฝ ๋นˆ๋„๊ฑฐ์˜ ์—†์Œ์ž์ฃผ ๋ณ€๊ฒฝ
์ถ”๊ฐ€ ์ •๋ณด๋ถˆํ•„์š”ํ•„์š” (์„ค๋ช…, ์ˆœ์„œ ๋“ฑ)
์„ฑ๋Šฅ๋น ๋ฆ„ (JOIN ๋ถˆํ•„์š”)๋А๋ฆผ (JOIN ํ•„์š”)
๊ถŒ์žฅ ์‚ฌ์šฉ์ƒํƒœ, ์—ญํ• , ํƒ€์ž…์นดํ…Œ๊ณ ๋ฆฌ, ์ฝ”๋“œ ํ…Œ์ด๋ธ”
Enum ์‚ฌ์šฉ ์˜ˆ์‹œ:
  • ์‚ฌ์šฉ์ž ์—ญํ•  (admin, user, guest)
  • ๊ฒŒ์‹œ๊ธ€ ์ƒํƒœ (draft, published, deleted)
  • ์•Œ๋ฆผ ํƒ€์ž… (email, sms, push)
๋ณ„๋„ ํ…Œ์ด๋ธ” ์‚ฌ์šฉ ์˜ˆ์‹œ:
  • ์ง€์—ญ ๋ชฉ๋ก (์„œ์šธ, ๋ถ€์‚ฐ, โ€ฆ)
  • ์ƒํ’ˆ ์นดํ…Œ๊ณ ๋ฆฌ (๊ณ„์ธต ๊ตฌ์กฐ)
  • ๊ตญ๊ฐ€ ์ฝ”๋“œ (์ถ”๊ฐ€ ์ •๋ณด ๋งŽ์Œ)

๋‹ค์Œ ๋‹จ๊ณ„