전체 흐름
데이터 소스 (비개발자가 에러 메시지 작성)
│
├─ Google Sheets (탭 = 언어별 분리 가능)
├─ Airtable
├─ Notion
├─ CSV (로컬 파일, 언어별 파일 분리 가능)
└─ XLSX (로컬 파일, 시트 = 언어별 분리 가능)
│
▼
@sanghyuk-2i/huh-cli ─── pull 명령어
│
├─ Adapter 패턴으로 데이터 소스별 fetch
├─ @sanghyuk-2i/huh-core의 parseSheetData로 파싱
├─ @sanghyuk-2i/huh-core의 validateConfig로 검증
├─ (i18n) @sanghyuk-2i/huh-core의 validateLocales로 cross-locale 검증
│
▼
huh.json 또는 huh/ 디렉토리 (빌드 타임 산출물)
│ ├─ ko.json, en.json (로케일별)
│ └─ index.ts (자동 생성 barrel)
▼
@sanghyuk-2i/huh-react, @sanghyuk-2i/huh-vue, @sanghyuk-2i/huh-svelte ─── HuhProvider
│
├─ source prop으로 JSON 데이터 주입 (단일 언어)
├─ locales prop으로 다국어 데이터 주입 (i18n)
├─ huh(code, variables)로 에러 트리거 (trackId 직접 또는 errorMap 경유)
├─ @sanghyuk-2i/huh-core의 resolveError로 조회 + 변수 치환
│
▼
사용자 제공 커스텀 렌더러 ─── TOAST / MODAL / PAGE / 커스텀 타입패키지 의존 관계
@sanghyuk-2i/huh-react ──depends──▶ @sanghyuk-2i/huh-core
@sanghyuk-2i/huh-vue ──depends──▶ @sanghyuk-2i/huh-core
@sanghyuk-2i/huh-svelte ──depends──▶ @sanghyuk-2i/huh-core
@sanghyuk-2i/huh-cli ──depends──▶ @sanghyuk-2i/huh-core
@sanghyuk-2i/huh-plugin-sentry ──depends──▶ @sanghyuk-2i/huh-core (+ @sentry/browser peer dep)
@sanghyuk-2i/huh-plugin-datadog ──depends──▶ @sanghyuk-2i/huh-core (+ @datadog/browser-logs peer dep)
@sanghyuk-2i/huh-core ── (zero dependencies)- core는 외부 의존성이 없어 어디서든 사용 가능합니다.
- react는 core에 의존하며, react/react-dom은 peer dependency입니다.
- vue는 core에 의존하며, vue >= 3.3은 peer dependency입니다.
- svelte는 core에 의존하며, svelte >= 5.0은 peer dependency입니다.
- cli는 core에 의존하며, commander/googleapis/picocolors/xlsx를 사용합니다.
- plugin-sentry는 core에 의존하며, @sentry/browser는 peer dependency입니다.
- plugin-datadog는 core에 의존하며, @datadog/browser-logs는 peer dependency입니다.
패키지별 역할
@sanghyuk-2i/huh-core
| 모듈 | 역할 |
|---|---|
schema.ts | 모든 타입 정의 (ErrorConfig, ErrorEntry, ERROR_TYPES, ACTION_TYPES, HuhPlugin 등) |
plugin.ts | 플러그인 실행 유틸리티 (runPluginHook) |
parser.ts | 시트 raw 데이터(2D 문자열 배열) → ErrorConfig 변환 |
template.ts | 플레이스홀더 치환 유틸리티 |
resolver.ts | trackId로 에러 조회 + 변수 치환 적용 |
validator.ts | ErrorConfig 유효성 검증 (errors + warnings) |
locale-validator.ts | 다국어 cross-locale 검증 (trackId 일관성, type/actionType 불일치) |
@sanghyuk-2i/huh-react
| 모듈 | 역할 |
|---|---|
ErrorContentProvider.tsx | Context Provider, 에러 상태 관리, 렌더러 호출 |
useErrorContent.ts | huh/clearError 훅 |
types.ts | ErrorRenderProps, RendererMap 타입 정의 |
@sanghyuk-2i/huh-vue
| 모듈 | 역할 |
|---|---|
ErrorContentProvider.ts | provide/inject 기반 Provider, 에러 상태 관리, render function으로 렌더러 호출 |
useErrorContent.ts | inject 기반 useHuh composable |
types.ts | ErrorRenderProps, RendererMap (Vue Component) 타입 정의 |
@sanghyuk-2i/huh-svelte
| 모듈 | 역할 |
|---|---|
HuhProvider.svelte | setContext 기반 Provider, $state로 에러 상태 관리, 동적 컴포넌트 렌더링 |
useErrorContent.ts | getContext 기반 useHuh |
context.ts | Symbol injection key 분리 |
types.ts | ErrorRenderProps, RendererMap (Svelte Component) 타입 정의 |
@sanghyuk-2i/huh-cli
| 모듈 | 역할 |
|---|---|
commands/init.ts | 설정 파일 템플릿 생성, 소스 타입 정의 |
commands/pull.ts | Adapter를 통해 데이터 fetch → parse → validate → JSON 파일 생성 |
commands/validate.ts | JSON 파일 유효성 검증 |
adapters/types.ts | SourceAdapter 인터페이스 정의 |
adapters/registry.ts | Adapter 등록/조회 레지스트리 |
adapters/google-sheets.ts | Google Sheets API v4 adapter |
adapters/airtable.ts | Airtable REST API adapter |
adapters/notion.ts | Notion API adapter |
adapters/csv.ts | 로컬 CSV 파일 adapter (RFC 4180 호환 파서) |
adapters/xlsx.ts | 로컬 XLSX 파일 adapter (SheetJS) |
adapters/index.ts | Barrel — import 시 모든 adapter 자동 등록 |
fetch-sheet.ts | Google Sheets API v4 데이터 fetch (adapter에서 사용) |
fetch-airtable.ts | Re-export shim → adapters/airtable |
fetch-notion.ts | Re-export shim → adapters/notion |
generate.ts | ErrorConfig → JSON 파일 쓰기, i18n 모드에서 로케일별 파일 + index.ts barrel 생성 |
JSON DSL 스키마
ErrorConfig (Record<trackId, ErrorEntry>)
│
├── trackId: string (고유 식별자)
│
└── ErrorEntry
├── type: ErrorType (대문자. 기본: 'TOAST' | 'MODAL' | 'PAGE' + 커스텀 타입)
├── message: string ({{변수}} 템플릿 지원)
├── title?: string
├── image?: string
├── severity?: Severity (대문자. 기본: 'INFO' | 'WARNING' | 'ERROR' | 'CRITICAL' + 커스텀 심각도)
└── action?: ErrorAction
├── label: string
├── type: ActionType (대문자. 기본: 'REDIRECT' | 'RETRY' | 'BACK' | 'DISMISS' + 커스텀 액션)
└── target?: string (REDIRECT 시 필수)타입 확장성
ErrorType과 ActionType은 열린(open-ended) 문자열 타입입니다:
- 기본 제공 타입:
ERROR_TYPES(TOAST,MODAL,PAGE),ACTION_TYPES(REDIRECT,RETRY,BACK,DISMISS),SEVERITY_LEVELS(INFO,WARNING,ERROR,CRITICAL) - 데이터 소스에서 커스텀 타입(예:
BANNER,SNACKBAR,OPEN_CHAT)을 자유롭게 추가 가능 - 파서가 자동으로 대문자로 변환 (소문자 입력 허용)
- 프레임워크별
RendererMap에 해당 타입의 렌더러를 등록하면 자동으로 동작 - 커스텀 액션 타입은
HuhProvider의onCustomAction콜백으로 처리
렌더링 전략
Provider는 기본 렌더러를 제공하지 않습니다. 이는 의도적인 설계입니다:
- 각 프로젝트의 디자인 시스템이 다르기 때문에 기본 UI를 제공해도 커스터마이징이 필요합니다.
- 사용자가
RendererMap을 통해 사용하는 에러 타입별 렌더러를 직접 구현합니다. 기본 제공 타입(TOAST,MODAL,PAGE) 외에도 커스텀 타입에 대한 렌더러를 추가할 수 있습니다. - 렌더러에는
ErrorRenderProps가 전달되며,onAction과onDismiss콜백이 자동 생성됩니다.
액션 처리 흐름
사용자가 액션 버튼 클릭 → onAction() 호출
│
├── REDIRECT → router 제공 시 router.push(target), 미제공 시 window.location.href = target
├── BACK → router 제공 시 router.back(), 미제공 시 window.history.back()
├── RETRY → clearError() + onRetry 콜백
├── DISMISS → clearError()
└── 커스텀 → clearError() + onCustomAction 콜백플러그인 시스템
플러그인은 에러 모니터링, 분석, 커스텀 연동을 위한 확장 포인트를 제공합니다. 각 프레임워크 Provider는 plugins prop을 지원합니다.
플러그인 라이프사이클
huh(code, variables)
│
├── resolveError → 활성 에러 설정
├── runPluginHook(plugins, 'onError', resolved, context) ◀── 플러그인 훅
│
▼
사용자가 액션 버튼 클릭 → onAction()
│
├── runPluginHook(plugins, 'onAction', error, action) ◀── 플러그인 훅
└── 액션 실행 (REDIRECT / RETRY / DISMISS / 등)에러 격리
플러그인 에러는 catch 후 경고로 로깅됩니다. 실패한 플러그인이 에러 렌더링 흐름을 깨뜨리지 않습니다.
공식 플러그인
| 패키지 | 설명 |
|---|---|
@sanghyuk-2i/huh-plugin-sentry | Sentry 연동 — 에러 시 captureMessage, 액션 시 addBreadcrumb |
@sanghyuk-2i/huh-plugin-datadog | Datadog 연동 — 에러 시 datadogLogs.logger, 액션 시 info 레벨 로깅 |
빌드 설정
모노레포는 다음 도구로 관리됩니다:
| 도구 | 역할 |
|---|---|
| pnpm workspace | 패키지 매니저 + 워크스페이스 |
| turborepo | 빌드/테스트 파이프라인 (캐싱, 의존 순서) |
| tsup | TypeScript 번들링 (CJS + ESM + IIFE + d.ts) |
| vitest | 테스트 러너 |
빌드 출력물
| 파일 | 포맷 | 용도 |
|---|---|---|
dist/index.js | CJS | require() |
dist/index.mjs | ESM | import |
dist/index.global.js | IIFE (minified) | CDN, <script> |
dist/index.d.ts | TypeScript 선언 | 타입 지원 |
빌드 순서
Turborepo가 의존 관계를 분석하여 자동으로 순서를 결정합니다:
1. @sanghyuk-2i/huh-core (의존성 없음, 먼저 빌드)
2. @sanghyuk-2i/huh-react + @sanghyuk-2i/huh-vue + @sanghyuk-2i/huh-svelte + @sanghyuk-2i/huh-cli (core 빌드 후 병렬 빌드)디렉토리 구조
/
├── package.json # 루트 (모노레포 스크립트)
├── pnpm-workspace.yaml
├── turbo.json
├── tsconfig.base.json
├── vitest.workspace.ts
├── docs/ # 문서
│ ├── getting-started.md
│ ├── google-sheet-guide.md
│ ├── airtable-guide.md
│ ├── notion-guide.md
│ ├── api-core.md
│ ├── api-react.md
│ ├── api-cli.md
│ └── architecture.md
└── packages/
├── core/
│ ├── package.json
│ ├── tsconfig.json
│ ├── tsup.config.ts
│ └── src/
│ ├── index.ts
│ ├── schema.ts
│ ├── parser.ts
│ ├── resolver.ts
│ ├── template.ts
│ ├── validator.ts
│ ├── locale-validator.ts
│ └── __tests__/
├── react/
│ ├── package.json
│ ├── tsup.config.ts
│ └── src/
│ ├── index.ts
│ ├── ErrorContentProvider.tsx
│ ├── useErrorContent.ts
│ ├── types.ts
│ └── __tests__/
├── vue/
│ ├── package.json
│ ├── tsup.config.ts
│ └── src/
│ ├── index.ts
│ ├── ErrorContentProvider.ts
│ ├── useErrorContent.ts
│ ├── types.ts
│ └── __tests__/
├── svelte/
│ ├── package.json
│ ├── svelte.config.js
│ └── src/
│ ├── index.ts
│ ├── HuhProvider.svelte
│ ├── useErrorContent.ts
│ ├── context.ts
│ ├── types.ts
│ └── __tests__/
└── cli/
├── package.json
├── tsconfig.json
├── tsup.config.ts
└── src/
├── index.ts
├── fetch-sheet.ts
├── fetch-airtable.ts # re-export → adapters/airtable
├── fetch-notion.ts # re-export → adapters/notion
├── generate.ts
├── adapters/
│ ├── index.ts # barrel (auto-registers all adapters)
│ ├── types.ts # SourceAdapter interface
│ ├── registry.ts # register/get/clear
│ ├── google-sheets.ts
│ ├── airtable.ts
│ ├── notion.ts
│ ├── csv.ts
│ └── xlsx.ts
├── commands/
│ ├── init.ts
│ ├── pull.ts
│ └── validate.ts
└── __tests__/