43 lines
2.2 KiB
Markdown
43 lines
2.2 KiB
Markdown
# Contract: 프론트 데이터 인터페이스 (`snippets.api.ts`)
|
|
|
|
feature의 유일한 데이터 진입점. 화면·hook은 이 인터페이스만 알고, 뒤가 브릿지(SQLite)인지 나중에 백엔드인지는 모른다. (spec FR-022 / plan 편차1: HTTP 클라이언트 미경유)
|
|
|
|
```ts
|
|
import type { Snippet, SnippetInput } from "../types"
|
|
|
|
export interface SnippetsApi {
|
|
list(): Promise<Snippet[]> // usage 병합, 정렬은 프론트가
|
|
create(input: SnippetInput): Promise<Snippet> // 중복 name → reject(Error)
|
|
update(input: SnippetInput): Promise<Snippet> // name=키
|
|
remove(name: string): Promise<void>
|
|
recordUse(name: string): Promise<{ name: string; usageCount: number; lastUsed: number }> // 붙여넣기 성공 후, 갱신된 usage 반환(caller는 무시 가능)
|
|
}
|
|
```
|
|
|
|
- v1 구현체 `snippetBridge`(브릿지 왕복). 나중에 백엔드 전환 시 이 인터페이스 뒤 구현만 교체.
|
|
- 반환 타입은 순수 데이터(브릿지/HTTP 세부 노출 안 함). 실패는 `Error` reject.
|
|
|
|
## react-query 계약 (`hooks/useSnippets.ts`) — memos 패턴 준용
|
|
|
|
```ts
|
|
export const SNIPPETS_KEY = "snippets"
|
|
|
|
useSnippets() // useQuery([SNIPPETS_KEY]) → Snippet[]
|
|
useCreateSnippet() // mutation → invalidate([SNIPPETS_KEY]) + sonner "스니펫 생성됨"
|
|
useUpdateSnippet() // → invalidate + "저장됨"
|
|
useDeleteSnippet() // → invalidate + "삭제됨"
|
|
useRecordUse() // → invalidate([SNIPPETS_KEY]) (랭킹 갱신). 토스트 없음(조용히)
|
|
```
|
|
|
|
- 에러: 브릿지 실패는 `ApiError`(HTTP 전용)가 아니라 rejected promise의 순수 `Error`임 → `err instanceof Error ? err.message : "<한글 폴백>"` 형태(memos 톤은 같되 타입만 Error).
|
|
- `useSnippets`가 준 목록에 `core/search`+`core/ranking`을 화면에서 적용(서버 정렬 아님 — 로컬 계산).
|
|
|
|
## 붙여넣기 (기존 재사용, 신규 아님)
|
|
|
|
```ts
|
|
import { pasteToApp } from "@/lib/bridge/webviewBridge"
|
|
// Enter / Alt+숫자 →
|
|
pasteToApp(snippet.body) // 기존 paste.code — C# PasteService가 앞 창에 Ctrl+V + 창 숨김
|
|
await recordUse(snippet.name)
|
|
```
|