adapters.ts raw

   1  import { TFeedInfo, TFeedType } from '@/types'
   2  import { Pubkey } from '../shared/value-objects/Pubkey'
   3  import { RelayUrl } from '../shared/value-objects/RelayUrl'
   4  import { Feed, FeedState } from './Feed'
   5  import { FeedType } from './FeedType'
   6  
   7  /**
   8   * Adapters for converting between Feed domain model and legacy types
   9   *
  10   * These adapters provide backward compatibility during the migration
  11   * from raw state management to domain-driven design.
  12   */
  13  
  14  // ============================================================================
  15  // FeedType Adapters
  16  // ============================================================================
  17  
  18  /**
  19   * Convert legacy TFeedType to domain FeedType
  20   */
  21  export function toFeedType(feedType: TFeedType, id?: string): FeedType {
  22    switch (feedType) {
  23      case 'following':
  24        return FeedType.following()
  25      case 'pinned':
  26        return FeedType.pinned()
  27      case 'relays':
  28        if (!id) throw new Error('Relay set ID required for relays feed type')
  29        return FeedType.relays(id)
  30      case 'relay':
  31        if (!id) throw new Error('Relay URL required for relay feed type')
  32        return FeedType.relay(id)
  33      default:
  34        return FeedType.following()
  35    }
  36  }
  37  
  38  /**
  39   * Try to convert legacy TFeedType to domain FeedType
  40   * Returns null if conversion fails
  41   */
  42  export function tryToFeedType(feedType: TFeedType, id?: string): FeedType | null {
  43    try {
  44      return toFeedType(feedType, id)
  45    } catch {
  46      return null
  47    }
  48  }
  49  
  50  /**
  51   * Convert domain FeedType to legacy TFeedType
  52   */
  53  export function fromFeedType(feedType: FeedType): TFeedType {
  54    return feedType.value
  55  }
  56  
  57  // ============================================================================
  58  // FeedInfo Adapters
  59  // ============================================================================
  60  
  61  /**
  62   * Convert legacy TFeedInfo to domain Feed aggregate
  63   */
  64  export function toFeed(
  65    feedInfo: TFeedInfo,
  66    owner?: Pubkey,
  67    relayUrls?: RelayUrl[]
  68  ): Feed {
  69    if (!feedInfo) {
  70      return Feed.empty()
  71    }
  72  
  73    const feedType = tryToFeedType(feedInfo.feedType, feedInfo.id)
  74    if (!feedType) {
  75      return Feed.empty()
  76    }
  77  
  78    switch (feedInfo.feedType) {
  79      case 'following':
  80        return owner ? Feed.following(owner) : Feed.empty()
  81      case 'pinned':
  82        return owner ? Feed.pinned(owner) : Feed.empty()
  83      case 'relays':
  84        if (!owner || !feedInfo.id) return Feed.empty()
  85        return Feed.relays(owner, feedInfo.id, relayUrls ?? [])
  86      case 'relay':
  87        if (!feedInfo.id) return Feed.empty()
  88        const relayUrl = RelayUrl.tryCreate(feedInfo.id)
  89        if (!relayUrl) return Feed.empty()
  90        return Feed.singleRelay(relayUrl)
  91      default:
  92        return Feed.empty()
  93    }
  94  }
  95  
  96  /**
  97   * Convert domain Feed aggregate to legacy TFeedInfo
  98   */
  99  export function fromFeed(feed: Feed): TFeedInfo {
 100    const feedType = feed.type
 101  
 102    if (feedType.value === 'following' || feedType.value === 'pinned') {
 103      return { feedType: feedType.value }
 104    }
 105  
 106    if (feedType.value === 'relays' && feedType.relaySetId) {
 107      return { feedType: 'relays', id: feedType.relaySetId }
 108    }
 109  
 110    if (feedType.value === 'relay' && feedType.relayUrl) {
 111      return { feedType: 'relay', id: feedType.relayUrl }
 112    }
 113  
 114    return null
 115  }
 116  
 117  // ============================================================================
 118  // FeedState Adapters
 119  // ============================================================================
 120  
 121  /**
 122   * Convert legacy storage format to FeedState
 123   */
 124  export function toFeedState(feedInfo: TFeedInfo, relayUrls: string[] = []): FeedState | null {
 125    if (!feedInfo) return null
 126  
 127    return {
 128      feedType: feedInfo.feedType,
 129      relaySetId: feedInfo.feedType === 'relays' ? feedInfo.id : undefined,
 130      relayUrl: feedInfo.feedType === 'relay' ? feedInfo.id : undefined,
 131      relayUrls,
 132      contentFilter: {
 133        hideMutedUsers: true,
 134        hideContentMentioningMuted: true,
 135        hideUntrustedUsers: false,
 136        hideReplies: false,
 137        hideReposts: false,
 138        allowedKinds: [],
 139        nsfwPolicy: 'hide_content'
 140      },
 141      lastRefreshedAt: undefined
 142    }
 143  }
 144  
 145  /**
 146   * Convert FeedState to legacy storage format
 147   */
 148  export function fromFeedState(state: FeedState): { feedInfo: TFeedInfo; relayUrls: string[] } {
 149    let feedInfo: TFeedInfo = null
 150  
 151    if (state.feedType === 'following' || state.feedType === 'pinned') {
 152      feedInfo = { feedType: state.feedType as TFeedType }
 153    } else if (state.feedType === 'relays' && state.relaySetId) {
 154      feedInfo = { feedType: 'relays', id: state.relaySetId }
 155    } else if (state.feedType === 'relay' && state.relayUrl) {
 156      feedInfo = { feedType: 'relay', id: state.relayUrl }
 157    }
 158  
 159    return {
 160      feedInfo,
 161      relayUrls: state.relayUrls
 162    }
 163  }
 164  
 165  // ============================================================================
 166  // Relay URL Adapters
 167  // ============================================================================
 168  
 169  /**
 170   * Convert string URLs to RelayUrl value objects
 171   * Filters out invalid URLs
 172   */
 173  export function toRelayUrls(urls: string[]): RelayUrl[] {
 174    return urls
 175      .map((url) => RelayUrl.tryCreate(url))
 176      .filter((r): r is RelayUrl => r !== null)
 177  }
 178  
 179  /**
 180   * Convert RelayUrl value objects to strings
 181   */
 182  export function fromRelayUrls(relayUrls: readonly RelayUrl[]): string[] {
 183    return relayUrls.map((r) => r.value)
 184  }
 185  
 186  // ============================================================================
 187  // Comparison Utilities
 188  // ============================================================================
 189  
 190  /**
 191   * Check if two TFeedInfo objects represent the same feed
 192   */
 193  export function isSameFeedInfo(a: TFeedInfo, b: TFeedInfo): boolean {
 194    if (a === null && b === null) return true
 195    if (a === null || b === null) return false
 196    if (a.feedType !== b.feedType) return false
 197    return a.id === b.id
 198  }
 199  
 200  /**
 201   * Check if a Feed matches a TFeedInfo
 202   */
 203  export function feedMatchesInfo(feed: Feed, feedInfo: TFeedInfo): boolean {
 204    const converted = fromFeed(feed)
 205    return isSameFeedInfo(converted, feedInfo)
 206  }
 207