Skip to content

Nohgh/dookmark

Repository files navigation

Dookmark

여러 브라우저에 흩어진 북마크를 한곳에서 실시간으로 통합·동기화하는 크로스 브라우저 북마크 관리 플랫폼

크롬·엣지·웨일·파이어폭스 등 서로 다른 브라우저에 각자 저장되던 북마크를, 브라우저 확장과 웹 대시보드로 하나의 계정에 모아 실시간으로 동기화합니다.

익스텐션이 각 브라우저의 네이티브 북마크 변경을 감지해 서버로 보고하고, 서버는 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.testDATABASE_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.yamlapps/*, 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 설정으로 세 플랫폼의 코드 스타일·타입 규칙을 통일.

CI/CD에서의 모노레포

모노레포의 핵심 과제인 일부만 바뀌었는데 전체를 다 돌리는 문제경로 필터 + 스코프 실행으로 해결합니다.

  • 경로 기반 선별 실행 — 워크플로마다 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-serverapps/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
Loading
  • 웹은 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: 가드로 억제 (재보고 차단)
Loading
  • 삭제 에코 가드(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
Loading
  • 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
Loading
  • 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)webServerE2E용 API URL로 프로덕션 빌드 후 next start(:3100) 하여 dev 서버와 분리. mock-server 픽스처로 백엔드를 스텁하고 쿠키 픽스처로 인증 상태를 주입.
    • 프로젝트: chromium · firefox · mobile-chromium(@responsive 태그만). 반응형 그리드 열 수까지 검증.
    • 커버: 랜딩, 인증(탭 전환·잘못된 자격증명·회원가입 1→2→3·OAuth 콜백 실패), 대시보드(가드·통계·canvas 차트 렌더·빈 상태·기간 필터·폴백 복구·세션 만료).
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) 기동 → globalSetupprisma migrate deploy로 테스트 DB 스키마를 최신화 → --runInBand로 순차 실행. 목이 아닌 실제 DB/마이그레이션 위에서 인증·북마크 플로우를 검증합니다.
pnpm --filter server test       # 유닛
pnpm --filter server test:e2e   # E2E (실 DB 필요)

CI 연동

경로 필터로 관련 변경일 때만 도는 워크플로가 플랫폼별 테스트를 수행합니다(CI/CD에서의 모노레포 참고).

워크플로 실행
web-test.yml unit / e2e pnpm --filter web build && test · Playwright(--with-deps)
extension-test.yml build wxt preparecompile(타입) → buildtest
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 | 365

3. 매핑 타입 + 문자열 조작 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. 의존성 주입형 제네릭createApiClientTokenManager 인터페이스를 주입받아 웹(쿠키)/확장(Bearer) 환경을 하나의 제네릭 클라이언트로 흡수하고, 호출부는 api.get<T>()로 응답 타입을 명시합니다. (api-client/src/index.ts)


스크린샷

About

Cross-browser bookmark platform — unify and sync bookmarks across browsers in real time.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors