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