검색/서버와 클라이언트가 함께 쓰는 API 스키마로 풀스택 TypeScript 앱 만들기

서버와 클라이언트가 함께 쓰는 API 스키마로 풀스택 TypeScript 앱 만들기

풀스택 TypeScript 앱을 만들거나 고칠 때 사용하세요. AI가 라우트와 클라이언트 훅에서 함께 쓸 API 요청과 응답을 한 번만 정의하고, 입력을 읽기 전에 인증을 확인하며, 의존성·캐시·컴포넌트 스타일을 일관되게 유지하며, 빠른 테스트를 작성합니다.

이 AI 규칙을 사용하려면
## 패키지 의존성을 한 방향으로 유지하고 상태마다 둘 곳을 정하세요 시스템이 성장해도 소유권, 의존성 방향, 상태 배치가 예측 가능하게 유지되도록 코드베이스를 구성하세요. ### 책임을 기준으로 구조화하세요 - 모든 컴포넌트, 훅, 스토어, 서비스, 패키지에 하나의 명확한 목적을 부여하고, 이미 복잡한 단위에 분기와 모드를 추가하기보다 작은 단위를 조합하세요. - 의존성 그래프에서 애플리케이션 코드를 재사용 가능한 패키지보다 위에 두세요. 하위 수준 패키지는 애플리케이션 모듈을 가져오면 안 됩니다. - 격리된 서비스에 관련 없는 UI 프레임워크, 레지스트리, 제공업체 SDK, 실행 엔진이 포함되지 않도록 하세요. - 애플리케이션마다 인증을 다시 구현하지 말고 호환되는 하나의 세션과 데이터 모델을 통해 인증을 공유하세요. ### 타입과 상태의 소유권을 보존하세요 - 컴포넌트 속성, 상태, 함수 입력, 반환 값에 명시적 인터페이스를 정의하세요. - `any`를 사용하지 마세요. 타입이 실제로 불확실할 때는 타입 가드와 함께 `unknown`을 사용하세요. - 서버 상태는 서버 상태 라이브러리에, 전역 클라이언트 상태는 스토어에, 로컬 표현 상태는 컴포넌트에 두세요. - 복잡한 스토어를 구현 모듈과 타입 모듈로 나누세요. 다시 불러온 뒤에도 유지되어야 하는 상태만 영속화하고 영속화할 필드를 명시적으로 선택하세요. ## 로컬에서 새로 만들지 말고 저장소의 공유 헬퍼와 도구를 쓰세요 로컬 대체물을 만들지 말고 하나의 표준 도구 체인과 공유 구현 패턴을 사용하세요. ### 일관된 코드 규칙을 적용하세요 - 저장소에서 선택한 패키지 관리자, 포매터, 린터, 타입 검사기, 감사 명령을 사용하세요. - 콘솔 출력 대신 구조화된 로깅을 사용하세요. 요청 범위 라우트 래퍼는 요청 식별자를 자동으로 첨부해야 합니다. - 일관된 이름 지정과 가져오기 순서를 따르세요. 장식적이거나 구조화되지 않은 주석 대신 타입 전용 가져오기, 상수 단언, 명시적 참조 요소 타입, TSDoc을 사용하세요. - 절대 경로 가져오기를 우선하세요. 폴더에 내보내기가 3개 이상 있을 때만 배럴 파일을 만들고, 배럴이 아닌 파일에서는 다시 내보내지 마세요. ### 검증된 헬퍼를 재사용하세요 - ID 생성, 지연, 오류 정규화, 메시지 추출, 깊은 복제, 객체 필터링, 잘라내기, 지터가 포함된 재시도 백오프에는 공유 헬퍼를 사용하세요. 로컬 패키지나 일회성 로직으로 다시 만들지 마세요. - 소비자 1곳에서 사용하는 헬퍼는 인라인으로 두고, 파일 2개 이상에서 필요할 때 유틸리티 모듈로 옮기세요. - 컴포넌트가 약 50줄을 넘거나 호출자 2곳 이상에서 사용되거나 자체 상태 또는 로직을 소유하면 별도로 추출하세요. 순수하게 표현만 담당하고 한 번만 사용되는 10줄 미만의 조각은 인라인으로 유지하세요. ## API 요청과 응답을 한 번만 정의하고 직접 fetch에는 이유를 적으세요 모든 HTTP 요청과 응답을 한 번만 정의하여 라우트와 클라이언트가 서로 호환되지 않는 전송 형식으로 어긋나지 않게 하세요. ### 계약을 경계의 단일 기준으로 삼으세요 - 리소스 계열마다 계약 모듈 하나를 유지하고, 경로 매개변수, 쿼리 값, 본문, 헤더, 응답에 각각 별도 스키마를 정의하여 하나의 라우트 계약으로 결합하세요. - 클라이언트용으로 이름이 있는 스키마와 이름이 있는 입력 및 출력 타입 별칭을 내보내고, 임시 전송 타입을 파생하거나 문자열 기반 식별자를 다시 정의하는 대신 공유 식별자 기본 타입을 재사용하세요. - 도메인 유효성 검사기는 스키마 라이브러리를 직접 사용할 수 있지만, 라우트 경계 스키마는 라우트 핸들러와 클라이언트 밖에 두세요. ### 예외를 명시적으로 통제하세요 - 예외적인 직접 `fetch`, 이중 타입 단언, 직접 JSON 읽기, 의도적으로 타입을 지정하지 않은 응답 바로 위에 근거를 설명하는 주석을 요구하세요. - 직접 `fetch`는 스트리밍, 바이너리 전송, 멀티파트 업로드, 서명된 URL, OAuth 리디렉션, 외부 오리진 같은 경우에만 허용하세요. - 직접 JSON 읽기는 표준 파서를 사용할 수 없는 봉투 구조나 관대한 파싱에만 허용하고, 타입 미지정 응답은 실제로 불투명한 데이터에만 허용하세요. - 적법한 파일 전체 예외는 검토된 허용 목록에서 관리하고 줄 단위 우회를 흩어 놓지 마세요. - 근거 누락, 라우트 로컬 스키마, 직접 읽기, 승인되지 않은 `fetch`, 안전하지 않은 이중 타입 단언이 있으면 엄격한 경계 감사가 실패하도록 하세요. ## 스키마부터 시작해 API 라우트와 클라이언트 훅을 추가하세요 인증, 유효성 검사, 응답 타입 지정, 캐싱이 처음부터 끝까지 일치하도록 경계 기능을 고정된 순서로 추가하세요. ### 구현 순서를 따르세요 1. 계약과 이름이 있는 입력 및 출력 타입을 작성하세요. 2. 라우트를 표준 요청 컨텍스트 및 오류 처리 래퍼로 감싸고, 라우트마다 다시 구현하지 말고 공유 인증, 요청률 제한, 접근 제어 미들웨어를 조합하세요. 3. 신뢰할 수 없는 입력을 파싱하기 전에 인증하고 권한을 확인한 다음, 계약을 통해 요청의 모든 부분을 파싱하세요. 4. 응답 스키마에서 선언한 JSON 구조를 정확히 반환하세요. 5. 동일한 계약을 통해 데이터를 전송하고 검증하는 타입 지정 클라이언트 훅을 추가하세요. 6. 컴포넌트에서 훅을 사용하고 타입이 지정된 오류 메시지를 표시하세요. ### 스키마를 마이그레이션처럼 검토하세요 - 생략과 `null`을 구분하세요. 선택 가능과 null 허용을 결합하면 세 번째 상태가 생기므로 의도적으로 사용해야 합니다. - 모든 응답 호출 지점을 선언된 응답 스키마와 비교하세요. - 문자열, 배열, 제한값, 크기에 명시적 범위를 설정하고 필드별로 설명적인 오류를 제공하세요. - 알 수 없는 스키마는 예외로 취급하고 페이로드가 불투명한 이유를 문서화하세요. 전송 형식에 판별자가 있으면 판별 유니온을 우선하세요. - 의미 품질은 사람이 검토하세요. 구조 감사만으로는 너무 느슨한 모든 제한이나 부정확한 응답 구조를 감지할 수 없습니다. ## 취소와 낙관적 업데이트 후에도 쿼리 캐시를 정확하게 유지하세요 서버 상태를 컴포넌트 상태에 숨기지 말고 취소, 캐싱, 무효화, 낙관적 업데이트를 명시적으로 드러내세요. ### 안정적인 훅과 쿼리 키를 만드세요 - 안정적인 콜백이 최신 입력을 읽어야 할 때 참조를 사용하고, 입력이 변경되면 그 참조를 업데이트하세요. - 모든 쿼리 함수에서 취소 신호를 전달하고 각 쿼리에 명시적인 최신성 유지 시간을 지정하세요. - 루트에서 목록과 상세 접두사를 거치는 계층형 키를 만들어 대상을 지정해 무효화하고, 이전 데이터는 정적 쿼리가 아니라 키가 변하는 쿼리에만 유지하세요. ### 변이를 안전하게 조정하세요 - 낙관적 업데이트 전에 영향을 받는 쿼리를 취소하고 이전 캐시 값을 저장하여 오류 발생 시 복원할 수 있게 하세요. - 성공한 경우에만 처리하지 말고 완료 시 조정하고 무효화하세요. 성공과 실패 모두 캐시 일관성이 필요합니다. - 전체 리소스 트리를 무효화하기보다 대상을 지정해 무효화하세요. - 안정적인 변이 메서드나 객체를 콜백 의존성에 불필요하게 추가하지 마세요. - 동일 오리진 JSON은 타입 지정 요청 헬퍼를 통해 전달하세요. 직접 `fetch` 예외는 문서화된 경계 정책을 따라야 합니다. ## 공유 컴포넌트의 모양은 컴포넌트 안에서 정하고 속성으로 조정하세요 공유 컴포넌트의 모양은 각 소비자가 아니라 컴포넌트가 소유하는 단일 기준으로 취급하세요. ### 표현을 로컬에서 조합 가능하게 유지하세요 - 로컬 기능을 위해 전역 스타일을 수정하지 말고 스타일 범위를 컴포넌트로 제한하세요. - 클래스 구성 로직을 중복하지 말고 프로젝트의 조건부 클래스 헬퍼와 치수 축약 표기법을 사용하세요. - 컴포넌트에 실제 변형이 2개 이상 있을 때만 변형 도구를 사용하고, 단일 상태 전환에는 간단한 조건부 클래스를 사용하세요. - 내부 하위 경로 대신 지원되는 공개 진입점을 통해 공유 컴포넌트를 가져오세요. - 정의되지 않은 CSS 변수는 의도하지 않은 색상으로 조용히 대체될 수 있으므로 참조한 변수가 존재하는지 확인하세요. ### 속성으로 의도를 전달하세요 - 오류, 장식 요소, 레이블, 힌트, 필수 상태, 동작 모드를 위한 속성을 노출하세요. 소비자 클래스 재정의는 표준 테두리, 채우기, 반경, 높이, 색상, 포커스 처리가 아니라 레이아웃과 크기 조정에만 사용하세요. - 거의 중복되는 컴포넌트를 유지하지 말고 모드에는 판별 유니온 속성을 사용하세요. - 모달 본문 안에서 표준 레이블 필드 래퍼를 사용하여 필드 여백이 헤더와 푸터에 맞춰지게 하세요. - 마이그레이션 후에는 레거시 변형을 제거하고 디자인 시스템 경로를 병렬로 남겨 두지 마세요. ## 공유 목 객체와 가짜 타이머로 빠른 Vitest 테스트를 작성하세요 저장소의 표준 테스트 러너, 전역 픽스처, 결정적 시간 패턴을 사용하세요. ### 동작을 격리하세요 - 각 테스트 파일을 소스 옆에 두고 저장소의 표준 테스트 접미사로 이름을 지정하세요. - 전역 목과 공유 팩토리를 재사용하고 테스트에 다른 동작이 필요할 때만 하나를 교체하세요. 각 테스트 전에 목 상태를 지우고 테스트별 반환 값을 명시적으로 정의하세요. - 테스트에서 사용하지 않는 무거운 레지스트리와 제공업체 의존성은 모킹하세요. - 브라우저 API가 필요하지 않다면 서버 런타임 환경을 사용하세요. - 실제 타이머를 피하고 가짜 타이머나 동작에 필요한 가장 짧은 결정적 지연을 사용하세요. ### 비용이 큰 모듈 재로딩을 피하세요 - Vitest에서는 호이스팅된 목과 정적 가져오기를 우선하세요. - 모듈 재설정, 뒤늦게 정의한 목, 동적 가져오기를 함께 사용하지 말고, 명시적 목으로 충분하다면 `importActual`을 통해 실제 구현을 가져오지 마세요. - 늦은 모킹을 숨기는 헬퍼를 호출하지 말고 인증 및 로깅 목을 명시적으로 유지하세요. - 검토 전에 관련 테스트와 함께 엄격한 API 경계 감사를 실행하세요. ## 변수가 해결되기 전에는 타입을 바꾸지 않고 통합을 추가하세요 의존성 순서에 따라 통합을 구축하고 해결되지 않은 참조는 실행 시점까지 보존하세요. ### 통합 순서를 따르세요 1. 안정적인 `snake_case` 식별자로 도구를 정의하고 등록하세요. 2. 소비 블록을 추가하고 표준 순서에 등록하세요. 3. 아이콘과 필수 메타데이터를 추가하세요. 4. 통합에 필요한 경우에만 트리거를 추가하세요. 5. 권위 있는 API 문서를 기준으로 서비스 동작을 검증하세요. ### 수명 주기 시점을 준수하세요 - 직렬화 시점의 구성과 실행 시점의 매개변수 해결을 구분하세요. - 해결되지 않은 값을 직렬화할 때 타입을 강제 변환하지 마세요. 그러면 동적 참조가 손상될 수 있습니다. - 숫자 및 기타 강제 변환은 실행 중 변수가 해결된 후에만 수행하세요. - 도구, 블록, 트리거, 인증, 파일 입력 메타데이터를 동기화된 상태로 유지하세요. - 린팅, 타입 검사, 테스트, 엄격한 경계 감사를 실행한 다음 최종 변경 내역에서 계약, 캐시, 스타일, 레지스트리의 일관성을 검토하세요.