# Migration to v2.0.0 — Presence/Typing 단일화

> 작성일: 2026-04-27
> 대상 버전: 1.12.x → 2.0.0 (1.13 deprecation 단계 생략, 즉시 제거)
> 영향: `cb.database.setPresence`, `cb.database.subscribePresence`, `DatabasePresenceState`, WebSocket URL

본 문서는 v1.12.x 이전에서 v2.0.0 으로 업그레이드할 때 필요한 변경 사항을 안내합니다.
v2.0.0 에서 데이터베이스 측 presence/typing API 가 코드에서 완전히 제거되었습니다
(호출 시 `TypeError`). presence/typing 의 단일 SoT 는 `cb.realtime.*` 입니다.

## 1. 책임 경계 변경

| 기능 | v1.12.x | v2.0.0 |
|------|---------|--------|
| presence 상태 설정/조회/구독 | `cb.database` / `cb.realtime` 양쪽 | `cb.realtime` 만 |
| typing 상태 | `cb.database` | `cb.realtime` 만 |
| DB 변경 구독 | `cb.database.connectRealtime` | (변경 없음) |
| WebSocket URL (DB) | `/v1/realtime/ws` | `/v1/database/realtime/ws` |

## 2. setPresence 마이그레이션

### Before (v1.12.x)

```ts
cb.database.setPresence('online', 'web', { nickname: '홍길동' })
```

### After (v1.13+ / v2)

```ts
await cb.realtime.setPresence('online', {
  device: 'web',
  metadata: { nickname: '홍길동' }
})
```

**차이점**

- `cb.realtime.setPresence` 는 `Promise<void>` 를 반환합니다 (기존은 동기 void).
- `device` 와 `metadata` 가 두 번째 객체 파라미터의 옵션 필드입니다.
- 상태값에 `'busy'` 가 추가되어 있습니다 (`'online' | 'away' | 'busy' | 'offline'`).

## 3. subscribePresence 마이그레이션

### Before (v1.12.x)

```ts
cb.database.subscribePresence(['user1', 'user2'], (states) => {
  console.log(states['user1']?.last_seen) // string ISO
})
```

### After (옵션 1 — 단일 사용자별 구독)

```ts
const unsub1 = await cb.realtime.subscribePresence('user1', (info) => {
  console.log(info.lastSeen) // number (epoch ms)
})
const unsub2 = await cb.realtime.subscribePresence('user2', (info) => {
  console.log(info.userId, info.status, info.eventType) // join/leave/update
})

// 정리
unsub1()
unsub2()
```

### After (옵션 2 — 앱 전체 변경 일괄 수신)

```ts
const unsub = cb.realtime.onPresenceChange((info) => {
  // 모든 사용자의 변경 이벤트가 한 콜백으로 전달됨
  if (['user1', 'user2'].includes(info.userId)) {
    console.log(info.userId, info.status)
  }
})
```

**차이점**

| 필드 | v1.12 (`DatabasePresenceState`) | v1.13+ (`PresenceInfo`) |
|------|-------------------------------|-----------------------|
| ID | `user_id` (snake) | `userId` (camel) |
| 마지막 활동 | `last_seen: string` (ISO) | `lastSeen: number` (epoch ms) |
| 상태 | `'online' \| 'away' \| 'offline'` | `'online' \| 'away' \| 'busy' \| 'offline'` |
| 이벤트 타입 | (없음) | `eventType?: 'join' \| 'leave' \| 'update'` |

### 변환 헬퍼 (마이그레이션 보조)

기존 `DatabasePresenceState` 모양으로 데이터를 가공하던 코드를 점진적으로 옮길 때 도움이
되도록, v1 호환 형태(snake_case · ISO 문자열) 로 변환하는 헬퍼 예제. v2.0.0 에서는
`DatabasePresenceState` type 이 제거되었으므로 inline 타입으로 정의한다.

```ts
import type { PresenceInfo } from 'connectbase-client'

interface LegacyPresenceState {
  user_id: string
  status: 'online' | 'away' | 'offline'
  last_seen: string
  device?: string
  metadata?: Record<string, unknown>
}

function fromPresenceInfo(info: PresenceInfo): LegacyPresenceState {
  return {
    user_id: info.userId,
    status: info.status === 'busy' ? 'away' : info.status,
    last_seen: new Date(info.lastSeen).toISOString(),
    device: info.device,
    metadata: info.metadata,
  }
}
```

## 4. typing 마이그레이션 (있는 경우)

`cb.database` 에는 공개된 typing 메서드가 없으므로 추가 마이그레이션은 불필요합니다.
`cb.realtime.startTyping(roomId)` / `cb.realtime.stopTyping(roomId)` /
`cb.realtime.onTypingChange(roomId, handler)` 를 그대로 사용하세요.

## 5. WebSocket URL 변경

```ts
await cb.database.connectRealtime({
  accessToken: 'xxx',
  dataServerUrl: 'https://data.connectbase.world',
})
// SDK 는 자동으로 /v1/database/realtime/ws 로 연결 (v1.13+).
// 사용자가 dataServerUrl 의 host:port 만 지정하면 path 는 SDK 가 결정.
```

옛 경로(`/v1/realtime/ws`) 는 v2.0.0 과 함께 서버에서도 제거되었습니다. 사용자 측
firewall/proxy 에 `/v1/database/realtime/` 경로 허용 규칙이 있는지 확인하세요.

## 6. 런타임 동작 차이

- **v1.12.x**: 두 API 모두 동작.
- **v2.0.0**: `cb.database.setPresence` / `subscribePresence` 호출 시 `TypeError` (메서드 미존재).

## 7. 자동 마이그레이션 (codemod)

향후 별도 codemod 스크립트가 제공될 예정입니다. 우선은 위 가이드에 따라 수동 변경을
권장합니다 (필드 네이밍/시그니처 차이가 있어 수동 검토가 안전).

## 8. 문의

- 이슈: GitHub Issues
- 디스코드: 공식 채널의 `#sdk` 룸
