[FEAT] Swagger 기반 API 클라이언트 자동 생성 도입 (orval)#146
Conversation
- Storybook vite가 끌어온 esbuild 0.28.1 postinstall 충돌을 피하기 위해 pnpm.overrides.esbuild를 0.25.12로 고정했습니다 - apps/timo-web에 orval, @next/env devDependency와 gen:api 스크립트를 추가했습니다
- orval.config.ts에서 live 스펙(NEXT_PUBLIC_API_BASE_URL 기반)으로 react-query(axios) 클라이언트와 zod 검증 스키마를 함께 생성하도록 설정했습니다 - custom-instance.ts로 기존 axios 인스턴스(baseURL + 에러 파싱 인터셉터)를 orval mutator 시그니처에 연결했습니다 - fix-generated-escapes.js로 생성 코드의 불필요한 이스케이프 문자를 정리해 lint를 통과하게 했습니다 - 12개 도메인(AI/Auth/Focus/Home/Onboarding/Statistics/Tag/Terms/TimeBox/Timer/Todo/User)의 axios 함수, React Query 훅, Zod 스키마, 타입을 생성했습니다
- orval이 응답 타입을 BaseResponseXxx 형태로 직접 생성해 아무 곳에서도 참조하지 않던 제네릭 apiResponseSchema 래퍼 함수를 제거했습니다
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reached
Next review available in: 48 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
WalkthroughSwagger/OpenAPI 스펙 기반 Axios/React Query 및 Zod 클라이언트 생성 구성을 추가했습니다. Axios mutator와 후처리 도구를 연결하고, 요청별 로케일 설정 및 일부 훅의 서식을 조정했습니다. ChangesOpenAPI 클라이언트 자동 생성
요청 로케일 설정
Todo 훅 서식 조정
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant Environment
participant Orval
participant OpenAPISpec
participant GeneratedClient
participant Axios
Environment->>Orval: API base URL 제공
Orval->>OpenAPISpec: /v3/api-docs 요청
OpenAPISpec-->>Orval: OpenAPI 스펙 반환
Orval->>GeneratedClient: Axios/React Query 및 Zod 코드 생성
GeneratedClient->>Axios: customInstance로 API 요청
Axios-->>GeneratedClient: 응답 데이터 반환
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Timo Performance ReportBundle Size — timo-web
Lighthouse — timo-web
Image Optimization — timo-web
측정 커밋: |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/timo-web/api/client/custom-instance.ts`:
- Line 11: ErrorType<TError>를 실제 인터셉터가 reject하는 parseApiError() 결과인 ApiError 형태와
일치하도록 수정하세요. ApiError가 서버 에러 payload를 포함한다면 해당 필드를 명시적으로 연결하고, onError에서
response?.data에 의존하는 기존 타입 가정이 실제 런타임 구조와 맞도록 조정하세요.
In `@apps/timo-web/orval.config.ts`:
- Line 20: Add a TODO comment next to unsafeDisableValidation in the Orval
configuration documenting that it is a temporary workaround for the bearerAuth
issue and should be removed once the upstream Orval issue is resolved, allowing
spec validation to be re-enabled.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: f805e154-03d7-4e45-98b4-243b8610790f
⛔ Files ignored due to path filters (136)
apps/timo-web/api/generated/endpoints/ai/ai.tsis excluded by!**/generated/**apps/timo-web/api/generated/endpoints/ai/ai.zod.tsis excluded by!**/generated/**apps/timo-web/api/generated/endpoints/auth/auth.tsis excluded by!**/generated/**apps/timo-web/api/generated/endpoints/auth/auth.zod.tsis excluded by!**/generated/**apps/timo-web/api/generated/endpoints/focus/focus.tsis excluded by!**/generated/**apps/timo-web/api/generated/endpoints/focus/focus.zod.tsis excluded by!**/generated/**apps/timo-web/api/generated/endpoints/home/home.tsis excluded by!**/generated/**apps/timo-web/api/generated/endpoints/home/home.zod.tsis excluded by!**/generated/**apps/timo-web/api/generated/endpoints/onboarding/onboarding.tsis excluded by!**/generated/**apps/timo-web/api/generated/endpoints/onboarding/onboarding.zod.tsis excluded by!**/generated/**apps/timo-web/api/generated/endpoints/statistics/statistics.tsis excluded by!**/generated/**apps/timo-web/api/generated/endpoints/statistics/statistics.zod.tsis excluded by!**/generated/**apps/timo-web/api/generated/endpoints/tag/tag.tsis excluded by!**/generated/**apps/timo-web/api/generated/endpoints/tag/tag.zod.tsis excluded by!**/generated/**apps/timo-web/api/generated/endpoints/terms/terms.tsis excluded by!**/generated/**apps/timo-web/api/generated/endpoints/terms/terms.zod.tsis excluded by!**/generated/**apps/timo-web/api/generated/endpoints/time-box/time-box.tsis excluded by!**/generated/**apps/timo-web/api/generated/endpoints/time-box/time-box.zod.tsis excluded by!**/generated/**apps/timo-web/api/generated/endpoints/timer/timer.tsis excluded by!**/generated/**apps/timo-web/api/generated/endpoints/timer/timer.zod.tsis excluded by!**/generated/**apps/timo-web/api/generated/endpoints/todo/todo.tsis excluded by!**/generated/**apps/timo-web/api/generated/endpoints/todo/todo.zod.tsis excluded by!**/generated/**apps/timo-web/api/generated/endpoints/user/user.tsis excluded by!**/generated/**apps/timo-web/api/generated/endpoints/user/user.zod.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/authReissueResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/authTokenRequest.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/authTokenResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/baseResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/baseResponseAuthReissueResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/baseResponseAuthTokenResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/baseResponseFocusTodoResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/baseResponseHomeResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/baseResponseListTimeBoxResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/baseResponseObject.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/baseResponseRecommendDurationResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/baseResponseStatisticsCalendarResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/baseResponseStatisticsDailyResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/baseResponseStatisticsSummaryResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/baseResponseSubtaskStatusChangeResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/baseResponseTagCreateResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/baseResponseTagListResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/baseResponseTermsListResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/baseResponseTimerActiveResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/baseResponseTimerExtendResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/baseResponseTimerFinishResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/baseResponseTimerStartResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/baseResponseTimerStatusResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/baseResponseTodayResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/baseResponseTodoCreateResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/baseResponseTodoDetailResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/baseResponseTodoReorderResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/baseResponseTodoStatusChangeResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/baseResponseUpdateLanguageResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/baseResponseUpdateTimezoneResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/baseResponseUserProfileResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/baseResponseVoid.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/dailyTodoResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/dayCompletionResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/dayResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/dayResponseDayOfWeek.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/errorDto.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/focusTodoDetailResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/focusTodoResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/focusTodoResponseDayOfWeek.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/getCalendarParams.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/getDailyParams.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/getHomeParams.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/getSummaryParams.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/getTermsParams.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/getTimeBoxesParams.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/getTodoDetailParams.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/homeResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/homeResponseFilter.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/index.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/onboardingRequest.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/onboardingRequestLanguage.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/recommendDurationRequest.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/recommendDurationResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/repeatResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/repeatResponseWeekdaysItem.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/statisticsCalendarResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/statisticsDailyResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/statisticsSummaryResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/subtaskResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/subtaskStatusChangeResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/subtaskStatusUpdateRequest.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/tagCreateRequest.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/tagCreateResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/tagListResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/tagResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/termsListResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/termsResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/timeBoxResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/timeBoxResponseEndAction.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/timeBoxResponseStartAction.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/timerActionRequest.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/timerActionRequestAction.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/timerActiveResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/timerExtendRequest.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/timerExtendResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/timerFinishResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/timerStartResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/timerStatusResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/todayResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/todayResponseDayOfWeek.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/todayTodoResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/todayTodoResponseTimerStatus.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/todoCreateRequest.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/todoCreateRequestIcon.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/todoCreateRequestPriority.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/todoCreateRequestRepeatType.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/todoCreateRequestRepeatWeekdaysItem.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/todoCreateResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/todoDetailResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/todoDetailResponseTimerStatus.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/todoReorderRequest.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/todoReorderResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/todoResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/todoResponseTimerStatus.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/todoStatusChangeResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/todoStatusUpdateRequest.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/todoSubtaskUpdateRequest.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/todoUpdateRequest.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/todoUpdateRequestIcon.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/todoUpdateRequestPriority.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/todoUpdateRequestRepeatType.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/todoUpdateRequestRepeatWeekdaysItem.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/updateLanguageRequest.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/updateLanguageRequestLanguage.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/updateLanguageResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/updateLanguageResponseLanguage.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/updateTimezoneRequest.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/updateTimezoneResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/userInfo.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/userProfileResponse.tsis excluded by!**/generated/**pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (6)
apps/timo-web/api/client/custom-instance.tsapps/timo-web/api/schema/response.tsapps/timo-web/orval.config.tsapps/timo-web/package.jsonapps/timo-web/scripts/fix-generated-escapes.jspackage.json
💤 Files with no reviewable changes (1)
- apps/timo-web/api/schema/response.ts
- bearerAuth 검증 우회가 임시 조치임을 명시하고, upstream orval 이슈(#3203)가 해결되면 스펙 검증을 다시 켜야 한다는 TODO를 timo/timoZod 두 타깃 모두에 추가했습니다
- setRequestLocale(locale)을 호출하지 않아 next-intl이 매 요청마다 headers()를 읽으면서 전체 라우트가 강제로 dynamic 렌더링되던 문제를 고쳤습니다 - 이로써 9개 페이지가 자동으로 정적(SSG) 렌더링되어 Vercel 서버리스 함수 개수 제한 문제도 함께 해소됩니다
- en/ko 외 다른 locale 값에 대한 on-demand 렌더링 fallback을 막아서, SSG 페이지마다 Vercel이 여분으로 붙이던 Prerender Function을 제거했습니다
- dynamicParams = false가 실제 Vercel 함수 개수 문제를 해결하지 못했고, 로컬 vercel build 검증 중 /en/focus 같은 SSG 라우트의 lambda 매핑이 깨지는 버그까지 유발해서 되돌렸습니다
- settings/account, settings/policy 페이지는 아직 빈 스텁이라 기능 손실 없이 제거했습니다 - 추후 /settings?tab= 쿼리 파라미터 방식으로 통합할 예정이라 경로 기반 하위 라우트를 미리 정리했습니다 - 라우트 파일 수가 줄어 Vercel 서버리스 함수 개수 절감에도 도움이 됩니다
This reverts commit a5542da.
ehye1
left a comment
There was a problem hiding this comment.
우와 이거 한 번으로 엔드포인트랑 타입이랑 훅이랑 zod 검증 스키마까지 한 번에 뽑아주다니
수고하셨습니다~~
…to feat/web/145-orval-api-client-codegen
apiResponseSchema는 #145에서 미사용으로 제거됐으나 todo-schema.ts가 여전히 참조하고 있어 Build/Type Check가 실패했습니다. 어디서도 쓰이지 않는 createTodoResponseSchema/CreateTodoResponse도 함께 제거했습니다.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@apps/timo-web/app/`[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-tag-field.tsx:
- Around line 19-22: Rename the union type alias PresetTagName to
PresetTagNameTypes to match the project naming convention, and update all
references to the alias, including the TAG_ID_BY_NAME declaration and any
related usages.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 206a21d8-bbe6-48df-98b2-a2803c92479b
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (6)
apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-icon-field.tsapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-repeat-field.tsapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-tag-field.tsxapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-time-field.tsapps/timo-web/app/[locale]/layout.tsxapps/timo-web/package.json
There was a problem hiding this comment.
Caution
Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@apps/timo-web/app/`[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-tag-field.tsx:
- Around line 19-22: Rename the union type alias PresetTagName to
PresetTagNameTypes to match the project naming convention, and update all
references to the alias, including the TAG_ID_BY_NAME declaration and any
related usages.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 206a21d8-bbe6-48df-98b2-a2803c92479b
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (6)
apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-icon-field.tsapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-repeat-field.tsapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-tag-field.tsxapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-time-field.tsapps/timo-web/app/[locale]/layout.tsxapps/timo-web/package.json
🛑 Comments failed to post (1)
apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-tag-field.tsx (1)
19-22: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
타입 별칭 이름을 컨벤션에 맞춰 주세요.
PresetTagName은 유니언 타입 별칭이므로PresetTagNameTypes처럼Types접미사를 사용해야 합니다. 관련 참조도 함께 변경해 주세요. TypeScript 타입 별칭 공식 문서도 참고할 수 있습니다. (typescriptlang.org)수정 예시
-type PresetTagName = (typeof TAG_NAMES)[number]; +type PresetTagNameTypes = (typeof TAG_NAMES)[number]; -const TAG_ID_BY_NAME: Record<PresetTagName, number> = { +const TAG_ID_BY_NAME: Record<PresetTagNameTypes, number> = {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.type PresetTagNameTypes = (typeof TAG_NAMES)[number]; // 태그 관리 API가 아직 없어, 목데이터 용도로 태그 이름 순서에 고정 tagId를 매핑한다. const TAG_ID_BY_NAME: Record<PresetTagNameTypes, number> = {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/timo-web/app/`[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-tag-field.tsx around lines 19 - 22, Rename the union type alias PresetTagName to PresetTagNameTypes to match the project naming convention, and update all references to the alias, including the TAG_ID_BY_NAME declaration and any related usages.Source: Path instructions
jjangminii
left a comment
There was a problem hiding this comment.
To Reviewers에 리뷰 범위를 명확히 안내해주셔서 리뷰 시간이 많이 절약됐어요~ 저도 Zod로 검증하는 건 처음 봐서 사용 방법이랑 왜 이렇게 설계하셨는지 많이 배워갑니다 😊
하나의 OpenAPI 스펙에서 axios 함수 + React Query 훅(타입용) + Zod 스키마(런타임 검증용)를 한 번에 생성해서, "타입 따로 검증 로직 따로" 관리하던 이중 유지보수 문제를 구조적으로 없애신 게 인상 깊었어요. 기존 axios 인프라 재사용하신 점, 우회 조치에 근거 남겨주신 점, 죽은 코드 확인 후 제거하신 것까지 전반적으로 신중하게 처리하셨네요 👍
ISSUE 🔗
close #145
What is this PR? 🔍
Swagger(OpenAPI) 스펙에서 API 클라이언트(axios 함수 + React Query 훅)와 Zod 검증 스키마를 자동 생성하는 파이프라인(orval)을 도입했습니다.
배경
apps/timo-web/api/endpoint.ts에 문자열 상수로 손으로 하나씩 관리하고, 요청/응답 타입과 유효성 검증은 기능을 구현할 때마다 개별적으로 작성했습니다.코드 생성 파이프라인 (orval)
orval.config.ts에서 하나의 live 스펙으로 두 가지 산출물(axios+React Query 클라이언트, Zod 스키마)을 함께 생성하도록 설정했습니다.NEXT_PUBLIC_API_BASE_URL(기존 axios 인스턴스가 쓰는 것과 동일한 env) +/v3/api-docs로 스펙 URL을 만들고,pnpm run gen:api(orval --config ./orval.config.ts)를 실행하면api/generated/endpoints/<도메인>/*.ts(axios 함수 +useXxxReact Query 훅),*.zod.ts(Zod 스키마),api/generated/models/*.ts(타입)가 생성됩니다.output.override.mutator로 기존 axios 인스턴스(api/client/axios.ts, baseURL + 에러 파싱 인터셉터 포함)를 감싼customInstance를 연결해서 런타임 설정을 그대로 재사용합니다.hooks.afterAllFilesWrite에 prettier → eslint --fix를 걸어서 생성 직후 코드가 항상 프로젝트 lint 규칙을 통과하도록 했습니다.api/generated/**는 "Do not edit manually" 생성 코드이며, 스펙이 바뀌면 직접 고치지 말고pnpm run gen:api로 재생성해야 합니다. 백엔드 스펙의bearerAuth시큐리티 스킴에 스펙상 허용되지 않는name필드가 있어(springdoc-openapi 출력 특성)unsafeDisableValidation: true로 검증을 우회하고 있습니다(orval#3203).사용 예시
서버로 보내기 전에 값을 검증하고 싶다면 같은 스펙에서 생성된 Zod 스키마를 함께 씁니다.
GET 요청도 동일한 패턴입니다. 각 도메인 파일(
api/generated/endpoints/<도메인>/<도메인>.ts)에서use로 시작하는 export(useGetHome,useGetTodoDetail,useGetTags등)를 찾아 그대로 쓰면 됩니다.기대 효과
pnpm run gen:api한 번으로 타입 에러가 즉시 드러나 컴파일 타임에 발견됩니다(기존엔 런타임에야 발견).정리한 코드
api/endpoint.ts(엔드포인트 상수 모음)와, 아무 곳에서도 참조되지 않던apiResponseSchema제네릭 래퍼 함수를 제거했습니다.apiErrorSchema/ApiErrorResponse는 계속 사용 중이라 유지했습니다.To Reviewers
api/generated/**은 diff가 크지만 사람이 직접 작성한 코드가 아닙니다. 리뷰는orval.config.ts,api/client/custom-instance.ts,scripts/fix-generated-escapes.js위주로 봐주시면 충분합니다.unsafeDisableValidation: true는 백엔드 스펙의 사소한 오류(bearerAuth의 불필요한 name 필드)를 우회하기 위한 조치입니다. 백엔드에서 시큐리티 스킴을 고치면 이 옵션은 제거할 수 있습니다.NEXT_PUBLIC_API_BASE_URL은.env.local(gitignore 대상)에만 있어서, 다른 팀원이나 CI에서pnpm run gen:api를 돌리려면 각자 이 값을 설정해야 합니다. 현재 어떤 컴포넌트도 아직 생성된 훅을 실제로 사용하고 있지 않아, 실제 화면 동작 확인은 이 PR 범위 밖이며 후속 작업에서 진행됩니다.Screenshot 📷
Test Checklist ✔
pnpm lint통과 (apps/timo-web)pnpm check-types통과 (기존에 있던 무관한 아이콘 export 에러 2건 제외)pnpm run gen:api재실행 후 생성 결과가 lint/타입체크를 다시 통과하는지 확인pnpm build— 미실행: CI에서 확인 예정useRecommendDuration등) 호출 동작 확인 — 후속 작업 (현재 사용하는 컴포넌트 없음)