여러 브라우저에 흩어진 북마크를 한곳에서 실시간으로 통합·동기화하는 크로스 브라우저 북마크 관리 플랫폼
크롬·엣지·웨일·파이어폭스 등 서로 다른 브라우저에 각자 저장되던 북마크를, 브라우저 확장과 웹 대시보드로 하나의 계정에 모아 실시간으로 동기화합니다.
익스텐션이 각 브라우저의 네이티브 북마크 변경을 감지해 서버로 보고하고, 서버는 WebSocket으로 모든 기기에 즉시 전파합니다.
웹 대시보드에서는 북마크 활동을 통계·잔디 히트맵으로 시각화합니다.
Warning
현재 익스텐션 심사 중으로 아직 서비스를 이용할 수 없습니다.
브라우저마다 북마크가 따로 놀고, 기기를 옮기면 즐겨찾기가 사라지는 문제를 해결합니다.
- 크로스 브라우저 통합 — 크롬/엣지/웨일/파이어폭스의 네이티브 북마크를 하나의 Dookmark 계정으로 모읍니다.
- 실시간 양방향 동기화 — 한 브라우저에서 북마크를 추가·이동·삭제하면 WebSocket으로 다른 기기에 즉시 반영됩니다.
- 웹 · 확장 하이브리드 — 웹 대시보드(통계/관리)와 브라우저 확장(사이드패널/빠른 저장)이 동일한 세션을 공유합니다.
- 이중 백업 & 충돌 방지 — 휴지통(Trash)과 에코 루프 가드로 동기화 과정의 데이터 유실·중복을 막습니다.
# Node.js 버전 설정 (.nvmrc 기준)
nvm install && nvm use
# 사전 요구: Node ≥ 22.13, pnpm ≥ 10
pnpm install # 워크스페이스 전체 설치
docker compose up -d # 로컬 postgres + redis
pnpm dev # 모든 앱 병렬 실행
# 또는 앱별
pnpm --filter web dev # localhost:3000
pnpm --filter server dev # localhost:3001
pnpm --filter dookmark dev # WXT 개발 빌드 (확장 패키지명: dookmark)
pnpm lint # Biome (lint/format)유닛 테스트·타입 체크는 별도 준비 없이 바로 실행됩니다. 백엔드 E2E만 일회성 테스트 DB가 필요합니다.
# 유닛 테스트 (준비물 없음)
pnpm --filter web test # 웹 · Vitest(jsdom)
pnpm --filter dookmark test # 확장 · Vitest(jsdom)
pnpm --filter server test # 백엔드 · Jest(@nestjs/testing)
# 타입 체크
pnpm --filter dookmark compile # 확장 · tsc --noEmit
# 웹 E2E · Playwright (최초 1회 브라우저 설치)
pnpm --filter web exec playwright install
pnpm --filter web test:e2e # E2E용 API로 프로덕션 빌드 후 :3100 기동
# 커버리지
pnpm --filter web test:cov
pnpm --filter server test:cov백엔드 E2E — 목이 아닌 실제 PostgreSQL·마이그레이션 위에서 검증하므로 일회성 테스트 DB를 띄워야 합니다.
# 1. 테스트 Postgres 기동 (호스트 5433 — 개발용 5432와 분리)
docker compose -f apps/server/docker-compose.test.yml up -d
# 2. E2E 실행 (globalSetup이 prisma migrate deploy로 스키마 최신화)
pnpm --filter server test:e2e
# 3. 정리 (볼륨까지 삭제)
docker compose -f apps/server/docker-compose.test.yml down -v접속 정보는
apps/server/.env.test의DATABASE_URL(테스트 DB, 호스트 5433)을 사용합니다.
공통 — TypeScript · pnpm workspace(모노레포) · Biome(lint/format) · Vitest
| 레이어 | 스택 |
|---|---|
웹 (apps/web) |
Next.js 16 (App Router · RSC · cacheComponents) · React 19 · Tailwind CSS v4 · Chart.js · axios · Playwright(E2E) |
확장 (apps/extension) |
WXT · React 19 · react-router-dom · dnd-kit · socket.io-client · Tailwind CSS v4 |
서버 (apps/server) |
NestJS · Prisma(pg adapter) · PostgreSQL · Redis(ioredis) · Socket.IO(+Redis adapter) · Passport(JWT·Google OAuth) · JWT · bcrypt · nodemailer · Helmet · Throttler |
공유 패키지 (packages/*) |
api-client(axios 인터셉터) · extension-bridge(메시지/타입 계약) · analytics(대시보드 타입) · theme |
인프라 (infra/) |
AWS(VPC·EC2·ALB+ACM·RDS·ElastiCache·ECR·Lambda·CloudWatch) · Terraform(모듈형) · GitHub Actions(OIDC) · Docker |
| 영역 | 기능 |
|---|---|
| 인증 | 이메일 회원가입(인증코드 6자리) · 비밀번호 로그인 · Google OAuth · 세션 기반 refresh 로테이션 |
| 북마크 | 네이티브 북마크 실시간 감지/동기화 · 폴더 트리 · 드래그앤드롭 정렬 · 크로스 브라우저 이동 · 휴지통 |
| 확장 | 사이드패널 UI · 빠른 북마크 저장(quick-bookmark) · 웹 설치 감지 · 선별적 폴링 |
| 대시보드 | 통계 카드 · 활동 추이 라인차트 · 브라우저 분포 도넛차트 · GitHub 잔디형 활동 히트맵 · 기간 필터 |
| 시스템 | 다크/라이트 테마 · 반응형 레이아웃 · CloudWatch → Discord 알림 |
프론트엔드(웹)·브라우저 확장·백엔드·공유 라이브러리를 하나의 저장소에서 관리합니다. 세 클라이언트가 같은 API·같은 타입 계약을 공유하는 서비스라, 코드/타입을 한곳에서 관리하는 이점이 있습니다.
-
패키지 매니저: pnpm workspace (
pnpm-workspace.yaml→apps/*,packages/*) -
의존 방식: 앱은 공유 패키지를
"@dookmark/…": "workspace:*"프로토콜로 참조 → 심링크로 즉시 반영(퍼블리시 불필요) -
단일 잠금 파일: 루트
pnpm-lock.yaml하나로 전체 의존성 그래프를 고정 -
공유 계층 (
packages/*)패키지 소비하는 곳 역할 api-client웹 · 확장 axios 인스턴스 + silent-refresh 인터셉터 extension-bridge웹 · 확장 · 서버 메시지·이벤트·북마크 타입 계약(단일 진실 공급원) analytics웹 · 서버 대시보드/히트맵 응답 타입·상수 theme웹 · 확장 공유 테마 토큰 -
일관된 툴체인: 루트
biome.json(lint/format),tsconfig.base.json, Vitest를 전 패키지가 공유
폴더 트리는 플랫폼별 폴더 구조 참고.
- 타입 계약의 단일 진실 공급원 — 예를 들어
extension-bridge의 소켓 이벤트/북마크 타입을 바꾸면 웹·확장·서버가 컴파일 타임에 동시에 검증됩니다. 계약 불일치가 런타임이 아닌 빌드에서 걸립니다. - 원자적 크로스커팅 변경 — 프론트–백엔드에 걸친 API 변경을 PR 하나로 처리하고 함께 리뷰·롤백할 수 있습니다
- 코드 재사용 — 인증 인터셉터(
api-client)를 웹과 확장이 그대로 공유.tokenManager주입 여부로 쿠키/Bearer 환경 차이만 흡수. - 개발 경험 —
workspace:*심링크라 공유 패키지 수정이 별도 빌드·퍼블리시 없이 즉시 반영. 웹은transpilePackages로 소스 그대로 트랜스파일. - 일관성 — 하나의 lint/format/TS 설정으로 세 플랫폼의 코드 스타일·타입 규칙을 통일.
모노레포의 핵심 과제인 일부만 바뀌었는데 전체를 다 돌리는 문제를 경로 필터 + 스코프 실행으로 해결합니다.
- 경로 기반 선별 실행 — 워크플로마다
paths:필터로 관련 변경일 때만 파이프라인이 돕니다.각 워크플로가# web-test.yml paths: ["apps/web/**", "packages/**", "pnpm-lock.yaml", ".github/workflows/web-test.yml"]
packages/**를 포함하는 게 핵심입니다. 이를 통해 공유 패키지 변경은 이를 쓰는 앱 CI를 함께 트리거해 계약 파손을 놓치지 않습니다. (web-test·server-test·extension-test·deploy-server) - 스코프 빌드/테스트 — 설치는 워크스페이스 전체(
pnpm install --frozen-lockfile,cache: pnpm)로 하되, 실행은pnpm --filter <app>로 해당 앱만 대상.pnpm --filter web build && pnpm --filter web test pnpm --filter server exec prisma generate && pnpm --filter server test pnpm --filter dookmark build # 확장(패키지명: dookmark)
- 의존성 인지 Docker 빌드 — 서버 이미지는 빌드 컨텍스트가 저장소 루트(
context: .)이고,pnpm install --filter server...로 서버 + 그 워크스페이스 의존성만 설치합니다. Buildx GHA 캐시(cache-from/to: gha)로 레이어를 재사용하고:latest+:${SHA}로 태깅(롤백용). - 선별 배포 —
deploy-server도apps/server/**·packages/**변경일 때만 동작해, 확장/웹만 바뀌면 서버를 재배포하지 않습니다.
flowchart TB
subgraph Clients["클라이언트"]
WEB["웹 대시보드<br/>(Next.js)"]
EXT["브라우저 확장<br/>(WXT · 사이드패널)"]
NATIVE["네이티브 북마크<br/>(Chrome/Firefox…)"]
end
subgraph Delivery["배포 파이프라인"]
DEV["개발자 push / merge"]
GHA["GitHub Actions<br/>build · test<br/>(OIDC 인증)"]
ECR["ECR<br/>(Docker 이미지)"]
end
subgraph AWS["AWS VPC (ALB 뒤 EC2)"]
ALB["ALB + ACM<br/>(HTTPS)"]
SERVER["NestJS API<br/>REST + WebSocket<br/>(Docker on EC2)"]
PG[("PostgreSQL<br/>RDS")]
REDIS[("Redis<br/>ElastiCache")]
end
subgraph Ops["모니터링 · 알림"]
CW["CloudWatch<br/>경보"]
LAMBDA["Lambda<br/>릴레이"]
DISCORD["Discord<br/>Webhook"]
end
WEB -- "REST(쿠키)" --> ALB
EXT -- "REST(Bearer)" --> ALB
EXT <-- "Socket.IO" --> ALB
ALB --> SERVER
EXT <-- "감지/변경" --> NATIVE
WEB <-. "메시지 패싱<br/>(토큰 핸드오프)" .-> EXT
SERVER --> PG
SERVER --> REDIS
DEV --> GHA -- "이미지 push" --> ECR
ECR -- "SSM 재배포" --> SERVER
SERVER -. "지표/로그" .-> CW
CW -- "임계치 초과 시" --> LAMBDA --> DISCORD
- 웹은 httpOnly 쿠키, 확장은
Authorization: Bearer로 같은 API를 호출합니다(저장 매체만 다름). 모든 트래픽은 ALB(HTTPS)를 거칩니다. - 확장은 Socket.IO로 서버와 상시 연결되어 다른 기기의 북마크 변경을 실시간 수신합니다.
- Redis는 세션/캐시와 Socket.IO 멀티 인스턴스 어댑터 역할을 겸합니다.
- 배포 — 코드 push → GitHub Actions가 OIDC로 AWS에 인증(장기 키 없음)해 arm64 이미지를 빌드·ECR push하고, SSM Run Command로 EC2에서
docker pull → 컨테이너 교체 → prisma migrate deploy를 실행합니다. - 장애 알림 — 서버 지표/로그가 CloudWatch로 흘러가고, 경보가 임계치를 넘으면 Lambda가 이를 받아 Discord 웹훅으로 실시간 알림을 릴레이합니다. (아래 인프라 시스템에 상세)
확장의 background가 네이티브 북마크를 직접 변경하면서 동시에 그 변경을 감시하는 구조입니다. 이로 인해 자신의 변경을 다시 서버로 보고하는 에코 루프가 발생할 수 있습니다. 이를 두 개의 가드로 차단합니다.
sequenceDiagram
participant N as 네이티브 북마크
participant BG as 확장 background(감시자)
participant S as 서버
participant SP as 사이드패널(변경자)
Note over BG,SP: ① 사용자가 직접 북마크 생성
N->>BG: onCreated 이벤트
BG->>S: 변경 보고 (report)
S-->>SP: bookmark:updated 브로드캐스트
Note over S,N: ② 다른 기기발 변경 반영
S->>SP: 변경 명령
SP->>N: 네이티브 북마크 수정
N-->>BG: onCreated/onRemoved (에코!)
BG-->>BG: 가드로 억제 (재보고 차단)
- 삭제 에코 가드(id 기반) — 지울 id를 미리 알기에, 그 id의
onRemoved만 차단. - 생성 억제 윈도우(시간 기반) — 새 id는 나중에 알기에, 생성 직전부터 짧은 시간 동안 차단.
access_token(15분) + refresh_token(14일) JWT 2종과 DB Session 레코드로 관리합니다. 모든 토큰은 세션 ID(sid)로 하나의 세션에 묶입니다.
sequenceDiagram
participant C as 클라이언트
participant API as api-client 인터셉터
participant S as 서버
participant DB as Session(DB)
C->>API: API 요청
API->>S: Bearer/쿠키로 요청
S-->>API: 401 (access 만료)
Note over API: RefreshQueue<br/>(동시 401 → 갱신 1회만)
API->>S: POST /auth/refresh
S->>DB: sid 조회 · bcrypt.compare(refresh)
alt 일치
S->>DB: 새 토큰쌍 발급 + 해시 회전
S-->>API: 새 access/refresh
API->>S: 원 요청 재시도 (_retry)
else 옛 refresh 재사용 감지
S->>DB: 세션 강제 revoke (탈취 간주)
S-->>API: 401 → 로그아웃
end
- refresh 토큰은 1회용이라 사용 시마다 회전하며, 옛 토큰이 재등장하면 탈취로 간주해 세션을 폐기합니다(로테이션 + 재사용 감지).
Terraform 모듈로 구성한 단일 EC2 컴퓨트 + 관리형 데이터 계층. GitHub Actions가 OIDC로 AWS에 인증해 무자격증명 배포합니다.
flowchart TB
subgraph GH["GitHub Actions"]
CI["build & test → ECR push<br/>(OIDC 인증, 무자격증명)"]
end
subgraph VPC["AWS VPC"]
subgraph PUB["Public Subnet"]
ALB["ALB + ACM(HTTPS)"]
EC2["EC2 (Docker)<br/>NestJS 컨테이너"]
end
subgraph PRIV["Private Subnet"]
RDS[("RDS PostgreSQL")]
EC["ElastiCache Redis"]
end
end
ECR["ECR"]
LAMBDA["Lambda"]
DISCORD["Discord Webhook"]
CI --> ECR --> EC2
ALB --> EC2
EC2 --> RDS
EC2 --> EC
EC2 -. CloudWatch .-> LAMBDA --> DISCORD
- network — VPC · public/private 서브넷 · IGW · 보안그룹(ALB↔EC2 분리)
- compute — EC2 · ALB · Target Group · HTTPS 리스너 · ACM 인증서
- data — RDS(PostgreSQL) · ElastiCache(Redis) (private 서브넷)
- ecr / cicd — 이미지 레지스트리 · GitHub OIDC 역할 기반 CI/CD
- secrets / monitoring — 시크릿 관리 · CloudWatch 경보 → Lambda → Discord 알림 릴레이
저장소는 pnpm workspace 모노레포(apps/* + packages/*)이며, 아래는 플랫폼별 상세 구조입니다.
공유 패키지 (packages/*) — 웹·확장·서버가 workspace:*로 공유
packages/
├── api-client/ # axios 인스턴스 + silent-refresh 인터셉터 (웹/확장 공용)
│ └── src/
│ ├── index.ts # createApiClient · RefreshQueue · 401 재시도
│ └── types.ts # 활동/브라우저 등 공용 타입
├── extension-bridge/ # 웹·확장·서버 간 타입 계약(단일 진실 공급원)
│ └── src/
│ ├── bookmark/ # model · api · socket 이벤트 타입
│ ├── browser/ # browser-type 감지
│ └── messaging/ # messages · events (메시지 패싱 계약)
├── analytics/ # 대시보드/히트맵 응답 타입·상수
│ └── src/index.ts
└── theme/ # 공유 테마 토큰
└── theme.css
웹 (apps/web) — Next.js 16 대시보드
apps/web/
├── app/ # App Router
│ ├── dashboard/ # RSC 페치 + error/layout
│ ├── auth/ # 로그인·회원가입, auth/callback(OAuth)
│ ├── layout.tsx # FOUC 방지 인라인 테마 스크립트
│ └── providers.tsx # ThemeProvider · 확장 동기화
├── ui/ # 프레젠테이션 컴포넌트
│ ├── dashboard/ # widgets · period-filter
│ │ └── charts/ # trend · browser-distribution · heatmap
│ ├── auth/ # login/register form · feedback
│ └── shared/ # theme-context
├── domain/ # 도메인 로직(훅·API)
│ ├── auth/ # hooks(use-auth-*) · api(client·extension)
│ ├── analytics/ # client·server 페치
│ └── shared/hooks/ # use-timeout · use-countdown
├── proxy.ts # 미들웨어: 라우트 가드 + 서버사이드 silent refresh
└── test/e2e/ # Playwright E2E
확장 (apps/extension) — WXT 브라우저 확장
apps/extension/
├── entrypoints/ # 확장 실행 컨텍스트
│ ├── background/ # 네이티브 북마크 감시 · 서버 보고 · 토큰 저장
│ ├── content.ts # 웹↔확장 메시지 브릿지(폴백 경로)
│ ├── sidepanel/ # 메인 UI (pages · providers · ui · shared)
│ ├── quick-bookmark/ # 빠른 저장 팝업
│ └── welcome/ # 설치 온보딩
├── domain/
│ ├── auth/ # 토큰 매니저(storage.local)
│ ├── bookmarks/ # model · sync(에코 가드) · helper · utils
│ └── dnd/ # 드래그앤드롭 정렬
└── wxt.config.ts
백엔드 (apps/server) — NestJS API 서버
apps/server/
├── src/
│ ├── auth/ # JWT · Passport(Google OAuth) · guards · strategies · tasks
│ ├── bookmark/ # controllers · services · gateways(WebSocket) · handlers · repositories
│ ├── analytics/ # 대시보드/히트맵 집계 (activity repository)
│ ├── session/ # 세션 로테이션/폐기
│ ├── email/ # 인증코드 발송(nodemailer)
│ ├── common/ # redis · adapters(socket.io) · filters
│ ├── config/ # cors · env validation
│ ├── health/ # 헬스체크
│ └── prisma/ # Prisma 서비스
├── prisma/
│ └── schema.prisma # User·Session·Bookmark·ActivityEvent·VerificationCode…
└── Dockerfile
인프라·기타 (루트)
infra/terraform/ # 모듈형 IaC
├── modules/ # network · compute · data · ecr · cicd · secrets · monitoring
└── envs/prod/
.github/workflows/ # web/server/extension 테스트 · deploy-server(OIDC)
docker-compose.yml # 로컬 postgres + redis
pnpm-workspace.yaml
세 플랫폼이 각자의 런타임 특성에 맞는 도구로 테스트를 구성합니다. 유닛·통합·E2E를 합쳐 약 360개 케이스를 CI에서 검증합니다.
| 플랫폼 | 프레임워크 | 유형 | 파일 | 케이스 |
|---|---|---|---|---|
| 웹 | Vitest(jsdom) + Testing Library / Playwright | 유닛 · E2E | 11 + 4 | 45 + 22 |
| 확장 | Vitest(jsdom) + Testing Library | 유닛 (+tsc 타입체크) |
9 | 70 |
| 백엔드 | Jest + @nestjs/testing / supertest | 유닛 · E2E | 22 + 3 | 189 + 34 |
공유 패키지(
packages/*)는 이를 소비하는 웹·확장·서버 테스트와 타입 체크로 간접 검증됩니다.
- 유닛 (Vitest · jsdom · 45 cases) —
include: {domain,ui,app,test}/**,test/setup.ts로 jest-dom matcher 로드.- 커스텀 훅을
renderHook+ fake timers로 결정론적 검증 — 카운트다운/타임아웃 만료, 언마운트 cleanup, 인증 폼 다단계 전이, 서버 에러 메시지 표출, 쿨다운 초 파싱. - RSC 서버 페치(
authedGet)의 실패 모드 4종(쿠키 없음 / 401 / throw / 성공), 확장 동기화 폴백 경로.
- 커스텀 훅을
- E2E (Playwright · 22 cases · 4 spec) —
webServer가 E2E용 API URL로 프로덕션 빌드 후next start(:3100) 하여 dev 서버와 분리.mock-server픽스처로 백엔드를 스텁하고 쿠키 픽스처로 인증 상태를 주입.- 프로젝트: chromium · firefox · mobile-chromium(
@responsive태그만). 반응형 그리드 열 수까지 검증. - 커버: 랜딩, 인증(탭 전환·잘못된 자격증명·회원가입 1→2→3·OAuth 콜백 실패), 대시보드(가드·통계·canvas 차트 렌더·빈 상태·기간 필터·폴백 복구·세션 만료).
- 프로젝트: chromium · firefox · mobile-chromium(
pnpm --filter web test # 유닛
pnpm --filter web test:e2e # E2E (chromium/firefox/mobile)
pnpm --filter web test:cov # 커버리지- 유닛 (Vitest · jsdom · 70 cases · 9 파일) —
@/*·@extension/*alias를 vitest에서 재현. 테스트가 동기화 핵심 로직에 집중되어 있습니다.bookmarks/sync/— 에코 가드(native-sync-guard), 네이티브 리스너(local-bookmark-listeners), 네이티브 라이터(native-bookmark-writer), 대량 동기화(bulk-sync), 핸들러(sync-handlers).bookmarks/model/— 북마크 ID·정규화 유틸, 방문(visit) 계산, DnD 정렬 유틸.
- 타입 체크 게이트 —
compile(tsc --noEmit)을 별도 단계로 두어 WXT/브라우저 타입 계약을 CI에서 강제.
pnpm --filter dookmark test # 유닛 (확장 패키지명: dookmark)
pnpm --filter dookmark compile # 타입 체크- 유닛 (Jest · @nestjs/testing · 189 cases · 22 spec) —
testRegex: .spec.ts,testEnvironment: node. NestJS 테스트 모듈로 의존성을 주입/목킹해 계층별 검증.auth— 서비스·컨트롤러·전략(jwt·google)·인증코드 서비스,bookmark— 서비스·트리·휴지통·컨트롤러·게이트웨이(WebSocket)·이벤트 핸들러·리포지토리·트리 유틸,analytics·session·email·user·redis.
- E2E (Jest · supertest · 34 cases · 3 spec) —
app·auth·bookmark전 계층을 HTTP로 구동.- 실제 PostgreSQL(
docker-compose.test.yml) 기동 →globalSetup이prisma migrate deploy로 테스트 DB 스키마를 최신화 →--runInBand로 순차 실행. 목이 아닌 실제 DB/마이그레이션 위에서 인증·북마크 플로우를 검증합니다.
- 실제 PostgreSQL(
pnpm --filter server test # 유닛
pnpm --filter server test:e2e # E2E (실 DB 필요)경로 필터로 관련 변경일 때만 도는 워크플로가 플랫폼별 테스트를 수행합니다(CI/CD에서의 모노레포 참고).
| 워크플로 | 잡 | 실행 |
|---|---|---|
web-test.yml |
unit / e2e | pnpm --filter web build && test · Playwright(--with-deps) |
extension-test.yml |
build | wxt prepare → compile(타입) → build → test |
server-test.yml |
unit / e2e | prisma generate + test · postgres 16 + redis 7 서비스 컨테이너 위 test:e2e |
타입을 문서가 아니라 강제력 있는 계약으로 씁니다. 모노레포 특성상 여러 패키지에 흩어진 정의가 어긋나지 않도록, 런타임 값·라이브러리 타입에서 타입을 파생시켜 단일 진실 공급원을 유지합니다.
1. 컴파일 타임 enum 정합성 가드 — 별개로 선언된 두 타입(서버 Prisma enum ↔ extension-bridge union)이 어긋나면 빌드가 깨지게 만듭니다. 테스트가 아니라 타입 레벨에서 강제됩니다. (browser-type-sync.ts)
type BrowserTypeMismatch =
| Exclude<BridgeBrowserType, `${PrismaBrowserType}`> // 템플릿 리터럴로 enum→union 변환
| Exclude<`${PrismaBrowserType}`, BridgeBrowserType>;
// 일치하면 never → true. 어긋나면 불일치 멤버 union이 되어 `= true` 할당이 컴파일 실패.
export const BROWSER_TYPE_IN_SYNC:
BrowserTypeMismatch extends never ? true : BrowserTypeMismatch = true;2. 런타임 상수에서 타입 파생 (as const + 인덱스 접근) — 값과 타입의 단일 소스. 상수 배열을 고치면 유니온 타입이 자동으로 따라옵니다. (analytics/src/index.ts)
export const DASHBOARD_PERIODS = [7, 30, 90, 365] as const;
export type DashboardPeriod = (typeof DASHBOARD_PERIODS)[number]; // 7 | 30 | 90 | 3653. 매핑 타입 + 문자열 조작 intrinsic — 활동 종류(ActivityType)가 늘면 잔디 응답 타입의 키도 자동 파생됩니다. 손으로 키를 나열하지 않습니다. (analytics/src/index.ts)
// 'create' | 'edit' | 'delete' | 'visit' 키가 ActivityType에서 자동 도출
export type HeatmapDay = { date: string; total: number } & Record<Lowercase<ActivityType>, number>;4. 단일 모델에서 페이로드 파생 (Pick/Partial/Extract) — Bookmark 하나에서 소켓 페이로드·대시보드 응답 형태를 조립해 필드 표류를 막습니다. (bookmark/socket.ts, analytics)
type internalAction = 'remove' | 'create' | 'update' | 'delete' | 'restore';
export type CrossBrowserMoveAction = Extract<internalAction, 'remove' | 'create'>;
export type CrossBrowserMoveData = Partial<Pick<Bookmark, 'id' | 'parentId' | 'title' | 'url' | 'index' | 'isFolder'>>
& { children?: CrossBrowserMoveData[] }; // 재귀 타입: 서브트리
export type PopularBookmark = Pick<Bookmark, 'id' | 'title' | 'url'> & { visits: number };5. 판별 유니온(Discriminated Union) — type 판별자로 메시지·상태를 안전하게 좁힙니다. (messaging/messages.ts, dashboard-client-fallback.tsx)
export type ExtensionMessage = // 웹↔확장 메시지 계약
| SyncTokensMessage | NotifyLogoutMessage | ForceLogoutMessage | RequestSyncMessage | BookmarkSyncMessage;
type State = // 대시보드 폴백: 불가능한 상태 배제
| { status: 'loading' } | { status: 'error' }
| { status: 'ready'; dashboard: DashboardResponse; heatmap: HeatmapResponse };6. 라이브러리 타입 역파생 (Parameters/ReturnType) — 브라우저·훅·axios가 바뀌면 우리 타입도 따라 바뀝니다. 중복 선언 제거. (auth-messaging.ts, sidepanel-provider.tsx)
type MessageListener = Parameters<typeof browser.runtime.onMessage.addListener>[0];
type BookmarksApi = ReturnType<typeof useBookmarks>; // Context 타입을 훅에서 역산7. 의존성 주입형 제네릭 — createApiClient가 TokenManager 인터페이스를 주입받아 웹(쿠키)/확장(Bearer) 환경을 하나의 제네릭 클라이언트로 흡수하고, 호출부는 api.get<T>()로 응답 타입을 명시합니다. (api-client/src/index.ts)
![]() |
![]() |
![]() |
![]() |



