@stream-io/feeds-client 0.1.9 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/@react-bindings/hooks/search-state-hooks/index.ts +3 -0
  2. package/@react-bindings/index.ts +5 -0
  3. package/CHANGELOG.md +20 -0
  4. package/dist/@react-bindings/contexts/StreamFeedContext.d.ts +1 -1
  5. package/dist/@react-bindings/contexts/StreamFeedsContext.d.ts +1 -1
  6. package/dist/@react-bindings/contexts/StreamSearchContext.d.ts +12 -0
  7. package/dist/@react-bindings/contexts/StreamSearchResultsContext.d.ts +12 -0
  8. package/dist/@react-bindings/hooks/feed-state-hooks/useComments.d.ts +1 -1
  9. package/dist/@react-bindings/hooks/feed-state-hooks/useFeedActivities.d.ts +1 -1
  10. package/dist/@react-bindings/hooks/feed-state-hooks/useFeedMetadata.d.ts +1 -1
  11. package/dist/@react-bindings/hooks/feed-state-hooks/useFollowers.d.ts +1 -1
  12. package/dist/@react-bindings/hooks/feed-state-hooks/useFollowing.d.ts +1 -1
  13. package/dist/@react-bindings/hooks/feed-state-hooks/useOwnCapabilities.d.ts +1 -1
  14. package/dist/@react-bindings/hooks/feed-state-hooks/useOwnFollows.d.ts +1 -1
  15. package/dist/@react-bindings/hooks/search-state-hooks/index.d.ts +3 -0
  16. package/dist/@react-bindings/hooks/search-state-hooks/useSearchQuery.d.ts +4 -0
  17. package/dist/@react-bindings/hooks/search-state-hooks/useSearchResult.d.ts +8 -0
  18. package/dist/@react-bindings/hooks/search-state-hooks/useSearchSources.d.ts +4 -0
  19. package/dist/@react-bindings/hooks/useCreateFeedsClient.d.ts +1 -1
  20. package/dist/@react-bindings/index.d.ts +5 -0
  21. package/dist/@react-bindings/wrappers/StreamFeed.d.ts +1 -1
  22. package/dist/@react-bindings/wrappers/StreamSearch.d.ts +12 -0
  23. package/dist/@react-bindings/wrappers/StreamSearchResults.d.ts +12 -0
  24. package/dist/index-react-bindings.browser.cjs +1669 -1529
  25. package/dist/index-react-bindings.browser.cjs.map +1 -1
  26. package/dist/index-react-bindings.browser.js +1661 -1530
  27. package/dist/index-react-bindings.browser.js.map +1 -1
  28. package/dist/index-react-bindings.node.cjs +1669 -1529
  29. package/dist/index-react-bindings.node.cjs.map +1 -1
  30. package/dist/index-react-bindings.node.js +1661 -1530
  31. package/dist/index-react-bindings.node.js.map +1 -1
  32. package/dist/index.browser.cjs +1615 -1640
  33. package/dist/index.browser.cjs.map +1 -1
  34. package/dist/index.browser.js +1613 -1641
  35. package/dist/index.browser.js.map +1 -1
  36. package/dist/index.d.ts +2 -2
  37. package/dist/index.node.cjs +1615 -1640
  38. package/dist/index.node.cjs.map +1 -1
  39. package/dist/index.node.js +1613 -1641
  40. package/dist/index.node.js.map +1 -1
  41. package/dist/src/common/ActivitySearchSource.d.ts +1 -1
  42. package/dist/src/common/BaseSearchSource.d.ts +3 -1
  43. package/dist/src/common/FeedSearchSource.d.ts +7 -3
  44. package/dist/src/common/Poll.d.ts +1 -1
  45. package/dist/src/common/SearchController.d.ts +2 -0
  46. package/dist/src/common/UserSearchSource.d.ts +1 -1
  47. package/dist/src/common/real-time/StableWSConnection.d.ts +3 -3
  48. package/dist/src/feed/event-handlers/activity/handle-activity-added.d.ts +7 -0
  49. package/dist/src/feed/event-handlers/activity/handle-activity-deleted.d.ts +8 -0
  50. package/dist/src/feed/event-handlers/activity/handle-activity-reaction-added.d.ts +8 -0
  51. package/dist/src/feed/event-handlers/activity/handle-activity-reaction-deleted.d.ts +8 -0
  52. package/dist/src/feed/event-handlers/activity/handle-activity-removed-from-feed.d.ts +3 -0
  53. package/dist/src/feed/event-handlers/activity/handle-activity-updated.d.ts +8 -0
  54. package/dist/src/feed/event-handlers/activity/index.d.ts +6 -0
  55. package/dist/src/feed/event-handlers/bookmark/handle-bookmark-added.d.ts +8 -0
  56. package/dist/src/feed/event-handlers/bookmark/handle-bookmark-deleted.d.ts +9 -0
  57. package/dist/src/feed/event-handlers/bookmark/handle-bookmark-updated.d.ts +8 -0
  58. package/dist/src/feed/event-handlers/bookmark/index.d.ts +3 -0
  59. package/dist/src/feed/event-handlers/comment/handle-comment-added.d.ts +3 -0
  60. package/dist/src/feed/event-handlers/comment/handle-comment-deleted.d.ts +3 -0
  61. package/dist/src/feed/event-handlers/comment/handle-comment-reaction.d.ts +3 -0
  62. package/dist/src/feed/event-handlers/comment/handle-comment-updated.d.ts +3 -0
  63. package/dist/src/feed/event-handlers/comment/index.d.ts +4 -0
  64. package/dist/src/feed/event-handlers/feed/handle-feed-updated.d.ts +3 -0
  65. package/dist/src/feed/event-handlers/feed/index.d.ts +1 -0
  66. package/dist/src/feed/event-handlers/feed-member/handle-feed-member-added.d.ts +3 -0
  67. package/dist/src/feed/event-handlers/feed-member/handle-feed-member-removed.d.ts +3 -0
  68. package/dist/src/feed/event-handlers/feed-member/handle-feed-member-updated.d.ts +3 -0
  69. package/dist/src/feed/event-handlers/feed-member/index.d.ts +3 -0
  70. package/dist/src/feed/event-handlers/follow/handle-follow-created.d.ts +7 -0
  71. package/dist/src/feed/event-handlers/follow/handle-follow-deleted.d.ts +7 -0
  72. package/dist/src/feed/event-handlers/follow/handle-follow-updated.d.ts +3 -0
  73. package/dist/src/feed/event-handlers/follow/index.d.ts +3 -0
  74. package/dist/src/feed/event-handlers/index.d.ts +7 -0
  75. package/dist/src/feed/event-handlers/notification-feed/handle-notification-feed-updated.d.ts +3 -0
  76. package/dist/src/feed/event-handlers/notification-feed/index.d.ts +1 -0
  77. package/dist/src/{Feed.d.ts → feed/feed.d.ts} +15 -34
  78. package/dist/src/feed/index.d.ts +2 -0
  79. package/dist/src/{FeedsClient.d.ts → feeds-client.d.ts} +6 -5
  80. package/dist/src/gen/models/index.d.ts +5 -0
  81. package/dist/src/gen-imports.d.ts +1 -1
  82. package/dist/src/test-utils/index.d.ts +1 -0
  83. package/dist/src/test-utils/response-generators.d.ts +9 -0
  84. package/dist/src/types-internal.d.ts +7 -0
  85. package/dist/src/types.d.ts +1 -1
  86. package/dist/src/utils/check-has-another-page.d.ts +1 -0
  87. package/dist/src/utils/constants.d.ts +3 -0
  88. package/dist/src/utils/index.d.ts +5 -0
  89. package/dist/src/utils/state-update-queue.d.ts +6 -0
  90. package/dist/src/utils/type-assertions.d.ts +7 -0
  91. package/dist/src/utils/unique-array-merge.d.ts +1 -0
  92. package/dist/tsconfig.tsbuildinfo +1 -1
  93. package/index.ts +2 -2
  94. package/package.json +2 -1
  95. package/src/common/ActivitySearchSource.ts +6 -16
  96. package/src/common/BaseSearchSource.ts +9 -9
  97. package/src/common/FeedSearchSource.ts +22 -67
  98. package/src/common/Poll.ts +1 -1
  99. package/src/common/SearchController.ts +2 -0
  100. package/src/common/UserSearchSource.ts +10 -62
  101. package/src/{state-updates → feed/event-handlers/activity}/activity-reaction-utils.test.ts +12 -2
  102. package/src/{state-updates → feed/event-handlers/activity}/activity-utils.test.ts +3 -2
  103. package/src/{state-updates/activity-utils.ts → feed/event-handlers/activity/handle-activity-added.ts} +16 -36
  104. package/src/feed/event-handlers/activity/handle-activity-deleted.ts +30 -0
  105. package/src/feed/event-handlers/activity/handle-activity-reaction-added.ts +67 -0
  106. package/src/feed/event-handlers/activity/handle-activity-reaction-deleted.ts +75 -0
  107. package/src/feed/event-handlers/activity/handle-activity-removed-from-feed.ts +16 -0
  108. package/src/feed/event-handlers/activity/handle-activity-updated.ts +47 -0
  109. package/src/feed/event-handlers/activity/index.ts +6 -0
  110. package/src/{state-updates → feed/event-handlers/bookmark}/bookmark-utils.test.ts +2 -2
  111. package/src/feed/event-handlers/bookmark/handle-bookmark-added.ts +63 -0
  112. package/src/feed/event-handlers/bookmark/handle-bookmark-deleted.ts +84 -0
  113. package/src/feed/event-handlers/bookmark/handle-bookmark-updated.ts +76 -0
  114. package/src/feed/event-handlers/bookmark/index.ts +3 -0
  115. package/src/feed/event-handlers/comment/handle-comment-added.ts +38 -0
  116. package/src/feed/event-handlers/comment/handle-comment-deleted.ts +35 -0
  117. package/src/feed/event-handlers/comment/handle-comment-reaction.ts +61 -0
  118. package/src/feed/event-handlers/comment/handle-comment-updated.ts +35 -0
  119. package/src/feed/event-handlers/comment/index.ts +4 -0
  120. package/src/feed/event-handlers/feed/handle-feed-updated.ts +9 -0
  121. package/src/feed/event-handlers/feed/index.ts +1 -0
  122. package/src/feed/event-handlers/feed-member/handle-feed-member-added.ts +31 -0
  123. package/src/feed/event-handlers/feed-member/handle-feed-member-removed.ts +24 -0
  124. package/src/feed/event-handlers/feed-member/handle-feed-member-updated.ts +40 -0
  125. package/src/feed/event-handlers/feed-member/index.ts +3 -0
  126. package/src/feed/event-handlers/follow/handle-follow-created.test.ts +246 -0
  127. package/src/feed/event-handlers/follow/handle-follow-created.ts +93 -0
  128. package/src/feed/event-handlers/follow/handle-follow-deleted.test.ts +264 -0
  129. package/src/feed/event-handlers/follow/handle-follow-deleted.ts +95 -0
  130. package/src/feed/event-handlers/follow/handle-follow-updated.test.ts +174 -0
  131. package/src/feed/event-handlers/follow/handle-follow-updated.ts +88 -0
  132. package/src/feed/event-handlers/follow/index.ts +3 -0
  133. package/src/feed/event-handlers/index.ts +7 -0
  134. package/src/feed/event-handlers/notification-feed/handle-notification-feed-updated.ts +10 -0
  135. package/src/feed/event-handlers/notification-feed/index.ts +1 -0
  136. package/src/{Feed.ts → feed/feed.ts} +72 -483
  137. package/src/feed/index.ts +2 -0
  138. package/src/{FeedsClient.ts → feeds-client.ts} +26 -8
  139. package/src/gen/model-decoders/decoders.ts +7 -0
  140. package/src/gen/models/index.ts +10 -0
  141. package/src/gen-imports.ts +1 -1
  142. package/src/test-utils/index.ts +1 -0
  143. package/src/test-utils/response-generators.ts +102 -0
  144. package/src/types-internal.ts +11 -0
  145. package/src/types.ts +1 -1
  146. package/src/utils/check-has-another-page.ts +6 -0
  147. package/src/utils/constants.ts +3 -0
  148. package/src/utils/index.ts +5 -0
  149. package/src/{state-updates → utils}/state-update-queue.test.ts +6 -6
  150. package/src/utils/state-update-queue.ts +42 -0
  151. package/src/utils/type-assertions.ts +22 -0
  152. package/src/{utils.test.ts → utils/unique-array-merge.test.ts} +7 -3
  153. package/src/utils/unique-array-merge.ts +19 -0
  154. package/dist/src/state-updates/activity-reaction-utils.d.ts +0 -10
  155. package/dist/src/state-updates/activity-utils.d.ts +0 -13
  156. package/dist/src/state-updates/bookmark-utils.d.ts +0 -14
  157. package/dist/src/state-updates/follow-utils.d.ts +0 -19
  158. package/dist/src/state-updates/state-update-queue.d.ts +0 -15
  159. package/dist/src/utils.d.ts +0 -10
  160. package/src/state-updates/activity-reaction-utils.ts +0 -107
  161. package/src/state-updates/bookmark-utils.ts +0 -167
  162. package/src/state-updates/follow-utils.test.ts +0 -552
  163. package/src/state-updates/follow-utils.ts +0 -126
  164. package/src/state-updates/state-update-queue.ts +0 -35
  165. package/src/utils.ts +0 -48
  166. /package/dist/src/{ModerationClient.d.ts → moderation-client.d.ts} +0 -0
  167. /package/src/{ModerationClient.ts → moderation-client.ts} +0 -0
@@ -0,0 +1,3 @@
1
+ export * from './handle-feed-member-added';
2
+ export * from './handle-feed-member-updated';
3
+ export * from './handle-feed-member-removed';
@@ -0,0 +1,246 @@
1
+ import { FeedResponse, FollowResponse, UserResponse } from '../../../gen/models';
2
+ import { generateFollowResponse } from '../../../test-utils';
3
+ import { updateStateFollowCreated } from './handle-follow-created';
4
+
5
+ import { describe, it, expect, beforeEach } from 'vitest';
6
+
7
+ describe('handle-follow-created', () => {
8
+ describe(updateStateFollowCreated.name, () => {
9
+ let mockFollow: FollowResponse;
10
+ let mockFeed: FeedResponse;
11
+ let mockUser: UserResponse;
12
+
13
+ beforeEach(() => {
14
+ mockFollow = generateFollowResponse();
15
+ mockFeed = mockFollow.source_feed;
16
+ mockUser = mockFeed.created_by;
17
+ });
18
+
19
+ it('should return unchanged state for non-accepted follows', () => {
20
+ const follow: FollowResponse = {
21
+ ...mockFollow,
22
+ status: 'pending',
23
+ };
24
+
25
+ // @ts-expect-error - we're not testing the full state here
26
+ const currentState: FeedState = {
27
+ followers: [],
28
+ following: [],
29
+ };
30
+
31
+ const result = updateStateFollowCreated(
32
+ follow,
33
+ currentState,
34
+ 'user:feed-1',
35
+ 'user-1',
36
+ );
37
+
38
+ expect(result.changed).toBe(false);
39
+ });
40
+
41
+ it('should handle when this feed follows someone', () => {
42
+ const follow: FollowResponse = {
43
+ ...mockFollow,
44
+ source_feed: {
45
+ ...mockFeed,
46
+ id: 'feed-x',
47
+ fid: 'user:feed-x',
48
+ created_by: {
49
+ ...mockUser,
50
+ id: 'user-x',
51
+ },
52
+ following_count: 1,
53
+ },
54
+ target_feed: {
55
+ ...mockFeed,
56
+ id: 'other-feed',
57
+ fid: 'user:other-feed',
58
+ created_by: mockUser,
59
+ },
60
+ };
61
+
62
+ // @ts-expect-error - we're not testing the full state here
63
+ const currentState: FeedState = {
64
+ following: [],
65
+ following_count: 0,
66
+ };
67
+
68
+ const result = updateStateFollowCreated(
69
+ follow,
70
+ currentState,
71
+ 'user:feed-x',
72
+ 'user-1',
73
+ );
74
+
75
+ expect(result.changed).toBe(true);
76
+ expect(result.data.following).toHaveLength(1);
77
+ expect(result.data.following?.[0]).toEqual(follow);
78
+ expect(result.data).toMatchObject(follow.source_feed);
79
+ expect(result.data.own_follows).toBeUndefined();
80
+ expect(result.data.following_count).toEqual(1);
81
+ });
82
+
83
+ it('should handle when someone follows this feed', () => {
84
+ const follow: FollowResponse = {
85
+ ...mockFollow,
86
+ source_feed: {
87
+ ...mockFeed,
88
+ id: 'other-feed',
89
+ fid: 'user:other-feed',
90
+ created_by: {
91
+ ...mockUser,
92
+ id: 'other-user',
93
+ },
94
+ },
95
+ target_feed: {
96
+ ...mockFeed,
97
+ id: 'feed-1',
98
+ fid: 'user:feed-1',
99
+ created_by: mockUser,
100
+ follower_count: 1,
101
+ },
102
+ };
103
+
104
+ // @ts-expect-error - we're not testing the full state here
105
+ const currentState: FeedState = {
106
+ followers: [],
107
+ follower_count: 0,
108
+ };
109
+
110
+ const result = updateStateFollowCreated(
111
+ follow,
112
+ currentState,
113
+ 'user:feed-1',
114
+ 'user-1',
115
+ );
116
+
117
+ expect(result.changed).toBe(true);
118
+ expect(result.data.followers).toHaveLength(1);
119
+ expect(result.data.followers?.[0]).toEqual(follow);
120
+ expect(result.data).toMatchObject(follow.target_feed);
121
+ expect(result.data.own_follows).toBeUndefined();
122
+ expect(result.data.follower_count).toEqual(1);
123
+ });
124
+
125
+ it('should add to own_follows when connected user is the source', () => {
126
+ const follow: FollowResponse = {
127
+ ...mockFollow,
128
+ source_feed: {
129
+ ...mockFeed,
130
+ id: 'feed-1',
131
+ fid: 'user:feed-1',
132
+ created_by: { ...mockUser, id: 'user-1' },
133
+ },
134
+ target_feed: {
135
+ ...mockFeed,
136
+ id: 'feed-x',
137
+ fid: 'user:feed-x',
138
+ created_by: {
139
+ ...mockUser,
140
+ id: 'user-x',
141
+ },
142
+ },
143
+ };
144
+
145
+ // @ts-expect-error - we're not testing the full state here
146
+ const currentState: FeedState = {
147
+ followers: [],
148
+ own_follows: [],
149
+ };
150
+
151
+ const result = updateStateFollowCreated(
152
+ follow,
153
+ currentState,
154
+ 'user:feed-x',
155
+ 'user-1',
156
+ );
157
+
158
+ expect(result.changed).toBe(true);
159
+ expect(result.data.own_follows).toHaveLength(1);
160
+ expect(result.data.own_follows?.[0]).toEqual(follow);
161
+ });
162
+
163
+ it('should not update followers/following when they are undefined', () => {
164
+ const follow: FollowResponse = {
165
+ ...mockFollow,
166
+ source_feed: {
167
+ ...mockFeed,
168
+ id: 'other-feed',
169
+ fid: 'user:other-feed',
170
+ created_by: mockUser,
171
+ },
172
+ target_feed: {
173
+ ...mockFeed,
174
+ id: 'feed-1',
175
+ fid: 'user:feed-1',
176
+ created_by: mockUser,
177
+ },
178
+ };
179
+
180
+ // @ts-expect-error - we're not testing the full state here
181
+ const currentState: FeedState = {
182
+ followers: undefined,
183
+ following: undefined,
184
+ own_follows: undefined,
185
+ };
186
+
187
+ const result = updateStateFollowCreated(
188
+ follow,
189
+ currentState,
190
+ 'user:feed-1',
191
+ 'user-1',
192
+ );
193
+
194
+ expect(result.changed).toBe(true);
195
+ expect(result.data.followers).toBeUndefined();
196
+ expect(result.data).toMatchObject(follow.target_feed);
197
+ });
198
+
199
+ it('should add new followers to the top of existing arrays', () => {
200
+ const existingFollow: FollowResponse = {
201
+ ...mockFollow,
202
+ source_feed: {
203
+ ...mockFeed,
204
+ id: 'existing-feed',
205
+ fid: 'user:existing-feed',
206
+ created_by: mockUser,
207
+ },
208
+ };
209
+
210
+ const follow: FollowResponse = {
211
+ ...mockFollow,
212
+ source_feed: {
213
+ ...mockFeed,
214
+ id: 'other-feed',
215
+ fid: 'user:other-feed',
216
+ created_by: mockUser,
217
+ },
218
+ target_feed: {
219
+ ...mockFeed,
220
+ id: 'feed-1',
221
+ fid: 'user:feed-1',
222
+ created_by: mockUser,
223
+ },
224
+ };
225
+
226
+ // @ts-expect-error - we're not testing the full state here
227
+ const currentState: FeedState = {
228
+ followers: [existingFollow],
229
+ following: undefined,
230
+ own_follows: undefined,
231
+ };
232
+
233
+ const result = updateStateFollowCreated(
234
+ follow,
235
+ currentState,
236
+ 'user:feed-1',
237
+ 'user-1',
238
+ );
239
+
240
+ expect(result.changed).toBe(true);
241
+ expect(result.data.followers).toHaveLength(2);
242
+ expect(result.data.followers?.[0]).toEqual(follow);
243
+ expect(result.data.followers?.[1]).toEqual(existingFollow);
244
+ });
245
+ });
246
+ });
@@ -0,0 +1,93 @@
1
+ import type { Feed, FeedState } from '../../../feed';
2
+ import type { FollowResponse } from '../../../gen/models';
3
+ import type {
4
+ EventPayload,
5
+ PartializeAllBut,
6
+ UpdateStateResult,
7
+ } from '../../../types-internal';
8
+ import {
9
+ getStateUpdateQueueId,
10
+ shouldUpdateState,
11
+ } from '../../../utils';
12
+
13
+ export const updateStateFollowCreated = (
14
+ follow: FollowResponse,
15
+ currentState: FeedState,
16
+ currentFeedId: string,
17
+ connectedUserId?: string,
18
+ ): UpdateStateResult<{ data: FeedState }> => {
19
+ // filter non-accepted follows (the way getOrCreate does by default)
20
+ if (follow.status !== 'accepted') {
21
+ return { changed: false, data: currentState };
22
+ }
23
+
24
+ let newState: FeedState = { ...currentState };
25
+
26
+ // this feed followed someone
27
+ if (follow.source_feed.fid === currentFeedId) {
28
+ newState = {
29
+ ...newState,
30
+ // Update FeedResponse fields, that has the new follower/following count
31
+ ...follow.source_feed,
32
+ };
33
+
34
+ // Only update if following array already exists
35
+ if (currentState.following !== undefined) {
36
+ newState.following = [follow, ...currentState.following];
37
+ }
38
+ } else if (
39
+ // someone followed this feed
40
+ follow.target_feed.fid === currentFeedId
41
+ ) {
42
+ const source = follow.source_feed;
43
+
44
+ newState = {
45
+ ...newState,
46
+ // Update FeedResponse fields, that has the new follower/following count
47
+ ...follow.target_feed,
48
+ };
49
+
50
+ if (source.created_by.id === connectedUserId) {
51
+ newState.own_follows = currentState.own_follows
52
+ ? currentState.own_follows.concat(follow)
53
+ : [follow];
54
+ }
55
+
56
+ // Only update if followers array already exists
57
+ if (currentState.followers !== undefined) {
58
+ newState.followers = [follow, ...currentState.followers];
59
+ }
60
+ }
61
+
62
+ return { changed: true, data: newState };
63
+ };
64
+
65
+ export function handleFollowCreated(
66
+ this: Feed,
67
+ eventOrResponse: PartializeAllBut<
68
+ EventPayload<'feeds.follow.created'>,
69
+ 'follow'
70
+ >,
71
+ ) {
72
+ const follow = eventOrResponse.follow;
73
+
74
+ if (
75
+ !shouldUpdateState({
76
+ stateUpdateQueueId: getStateUpdateQueueId(follow, 'created'),
77
+ stateUpdateQueue: this.stateUpdateQueue,
78
+ watch: this.currentState.watch,
79
+ })
80
+ ) {
81
+ return;
82
+ }
83
+ const connectedUser = this.client.state.getLatestValue().connected_user;
84
+ const result = updateStateFollowCreated(
85
+ follow,
86
+ this.currentState,
87
+ this.fid,
88
+ connectedUser?.id,
89
+ );
90
+ if (result.changed) {
91
+ this.state.next(result.data);
92
+ }
93
+ }
@@ -0,0 +1,264 @@
1
+ import { FollowResponse, FeedResponse, UserResponse } from '../../../gen/models';
2
+ import { generateFollowResponse } from '../../../test-utils';
3
+ import { updateStateFollowDeleted } from './handle-follow-deleted';
4
+
5
+ import { describe, it, expect, beforeEach } from 'vitest';
6
+
7
+ describe('handle-follow-deleted', () => {
8
+ describe(updateStateFollowDeleted.name, () => {
9
+ let mockFollow: FollowResponse;
10
+ let mockFeed: FeedResponse;
11
+ let mockUser: UserResponse;
12
+
13
+ beforeEach(() => {
14
+ mockFollow = generateFollowResponse();
15
+ mockFeed = mockFollow.source_feed;
16
+ mockUser = mockFeed.created_by;
17
+ });
18
+
19
+ it('should handle when this feed unfollows someone', () => {
20
+ const existingFollow: FollowResponse = {
21
+ ...mockFollow,
22
+ source_feed: {
23
+ ...mockFeed,
24
+ id: 'feed-1',
25
+ fid: 'user:feed-1',
26
+ created_by: mockUser,
27
+ },
28
+ target_feed: {
29
+ ...mockFeed,
30
+ id: 'other-feed',
31
+ fid: 'user:other-feed',
32
+ created_by: mockUser,
33
+ },
34
+ };
35
+
36
+ const follow: FollowResponse = existingFollow;
37
+
38
+ // @ts-expect-error - we're not testing the full state here
39
+ const currentState: FeedState = {
40
+ following: [existingFollow],
41
+ following_count: 1,
42
+ };
43
+
44
+ const result = updateStateFollowDeleted(
45
+ follow,
46
+ currentState,
47
+ 'user:feed-1',
48
+ 'user-1',
49
+ );
50
+
51
+ expect(result.changed).toBe(true);
52
+ expect(result.data.following).toHaveLength(0);
53
+ expect(result.data).toMatchObject(follow.source_feed);
54
+ });
55
+
56
+ it('should handle when someone unfollows this feed', () => {
57
+ const existingFollow: FollowResponse = {
58
+ ...mockFollow,
59
+ source_feed: {
60
+ ...mockFeed,
61
+ id: 'other-feed',
62
+ fid: 'user:other-feed',
63
+ created_by: {
64
+ ...mockUser,
65
+ id: 'other-user',
66
+ },
67
+ },
68
+ target_feed: {
69
+ ...mockFeed,
70
+ id: 'feed-1',
71
+ fid: 'user:feed-1',
72
+ created_by: mockUser,
73
+ },
74
+ };
75
+
76
+ const follow: FollowResponse = existingFollow;
77
+
78
+ // @ts-expect-error - we're not testing the full state here
79
+ const currentState: FeedState = {
80
+ followers: [existingFollow],
81
+ own_follows: [existingFollow],
82
+ following_count: 1,
83
+ };
84
+
85
+ const result = updateStateFollowDeleted(
86
+ follow,
87
+ currentState,
88
+ 'user:feed-1',
89
+ 'user-1',
90
+ );
91
+
92
+ expect(result.changed).toBe(true);
93
+ expect(result.data.followers).toHaveLength(0);
94
+ expect(result.data.own_follows).toEqual(currentState.own_follows);
95
+ expect(result.data).toMatchObject(follow.target_feed);
96
+ });
97
+
98
+ it('should only remove own_follows when connected user is the source', () => {
99
+ const existingFollow: FollowResponse = {
100
+ ...mockFollow,
101
+ source_feed: {
102
+ ...mockFeed,
103
+ id: 'other-feed',
104
+ fid: 'user:other-feed',
105
+ created_by: { ...mockUser, id: 'user-1' },
106
+ },
107
+ target_feed: {
108
+ ...mockFeed,
109
+ id: 'feed-1',
110
+ fid: 'user:feed-1',
111
+ created_by: mockUser,
112
+ },
113
+ };
114
+
115
+ const follow: FollowResponse = existingFollow;
116
+
117
+ // @ts-expect-error - we're not testing the full state here
118
+ const currentState: FeedState = {
119
+ followers: [existingFollow],
120
+ own_follows: [existingFollow],
121
+ following_count: 1,
122
+ };
123
+
124
+ const result = updateStateFollowDeleted(
125
+ follow,
126
+ currentState,
127
+ 'user:feed-1',
128
+ 'user-1',
129
+ );
130
+
131
+ expect(result.changed).toBe(true);
132
+ expect(result.data.followers).toHaveLength(0);
133
+ expect(result.data.own_follows).toHaveLength(0);
134
+ });
135
+
136
+ it('should not remove own_follows when connected user is not the source', () => {
137
+ const existingFollow: FollowResponse = {
138
+ ...mockFollow,
139
+ source_feed: {
140
+ ...mockFeed,
141
+ id: 'other-feed',
142
+ fid: 'user:other-feed',
143
+ created_by: { ...mockUser, id: 'other-user' },
144
+ },
145
+ target_feed: {
146
+ ...mockFeed,
147
+ id: 'feed-1',
148
+ fid: 'user:feed-1',
149
+ created_by: mockUser,
150
+ },
151
+ };
152
+
153
+ const follow: FollowResponse = existingFollow;
154
+
155
+ // @ts-expect-error - we're not testing the full state here
156
+ const currentState: FeedState = {
157
+ followers: [existingFollow],
158
+ own_follows: [existingFollow],
159
+ };
160
+
161
+ const result = updateStateFollowDeleted(
162
+ follow,
163
+ currentState,
164
+ 'user:feed-1',
165
+ 'user-1',
166
+ );
167
+
168
+ expect(result.changed).toBe(true);
169
+ expect(result.data.followers).toHaveLength(0);
170
+ expect(result.data.own_follows).toHaveLength(1); // Should remain unchanged
171
+ });
172
+
173
+ it('should not update followers/following when they are undefined in delete', () => {
174
+ const existingFollow: FollowResponse = {
175
+ ...mockFollow,
176
+ source_feed: {
177
+ ...mockFeed,
178
+ id: 'other-feed',
179
+ fid: 'user:other-feed',
180
+ created_by: mockUser,
181
+ },
182
+ target_feed: {
183
+ ...mockFeed,
184
+ id: 'feed-1',
185
+ fid: 'user:feed-1',
186
+ created_by: mockUser,
187
+ },
188
+ };
189
+
190
+ const follow: FollowResponse = existingFollow;
191
+
192
+ // @ts-expect-error - we're not testing the full state here
193
+ const currentState: FeedState = {
194
+ followers: undefined,
195
+ own_follows: undefined,
196
+ };
197
+
198
+ const result = updateStateFollowDeleted(
199
+ follow,
200
+ currentState,
201
+ 'user:feed-1',
202
+ 'user-1',
203
+ );
204
+
205
+ expect(result.changed).toBe(true);
206
+ expect(result.data.followers).toBeUndefined();
207
+ expect(result.data.own_follows).toBeUndefined();
208
+ expect(result.data).toMatchObject(follow.target_feed);
209
+ });
210
+
211
+ it('should filter out the correct follow by target feed id', () => {
212
+ const followToRemove: FollowResponse = {
213
+ ...mockFollow,
214
+ source_feed: {
215
+ ...mockFeed,
216
+ id: 'feed-1',
217
+ fid: 'user:feed-1',
218
+ created_by: mockUser,
219
+ },
220
+ target_feed: {
221
+ ...mockFeed,
222
+ id: 'target-to-remove',
223
+ fid: 'user:target-to-remove',
224
+ created_by: mockUser,
225
+ },
226
+ };
227
+
228
+ const followToKeep: FollowResponse = {
229
+ ...mockFollow,
230
+ source_feed: {
231
+ ...mockFeed,
232
+ id: 'feed-1',
233
+ fid: 'user:feed-1',
234
+ created_by: mockUser,
235
+ },
236
+ target_feed: {
237
+ ...mockFeed,
238
+ id: 'target-to-keep',
239
+ fid: 'user:target-to-keep',
240
+ created_by: mockUser,
241
+ },
242
+ };
243
+
244
+ const follow: FollowResponse = followToRemove;
245
+
246
+ // @ts-expect-error - we're not testing the full state here
247
+ const currentState: FeedState = {
248
+ following: [followToRemove, followToKeep],
249
+ following_count: 2,
250
+ };
251
+
252
+ const result = updateStateFollowDeleted(
253
+ follow,
254
+ currentState,
255
+ 'user:feed-1',
256
+ 'user-1',
257
+ );
258
+
259
+ expect(result.changed).toBe(true);
260
+ expect(result.data.following).toHaveLength(1);
261
+ expect(result.data.following?.[0]).toEqual(followToKeep);
262
+ });
263
+ });
264
+ });
@@ -0,0 +1,95 @@
1
+ import type { Feed, FeedState } from '../../../feed';
2
+ import type { FollowResponse } from '../../../gen/models';
3
+ import type {
4
+ EventPayload,
5
+ PartializeAllBut,
6
+ UpdateStateResult,
7
+ } from '../../../types-internal';
8
+
9
+ import { getStateUpdateQueueId, shouldUpdateState } from '../../../utils';
10
+
11
+ export const updateStateFollowDeleted = (
12
+ follow: FollowResponse,
13
+ currentState: FeedState,
14
+ currentFeedId: string,
15
+ connectedUserId?: string,
16
+ ): UpdateStateResult<{ data: FeedState }> => {
17
+ let newState: FeedState = { ...currentState };
18
+
19
+ // this feed unfollowed someone
20
+ if (follow.source_feed.fid === currentFeedId) {
21
+ newState = {
22
+ ...newState,
23
+ // Update FeedResponse fields, that has the new follower/following count
24
+ ...follow.source_feed,
25
+ };
26
+
27
+ // Only update if following array already exists
28
+ if (currentState.following !== undefined) {
29
+ newState.following = currentState.following.filter(
30
+ (followItem) => followItem.target_feed.fid !== follow.target_feed.fid,
31
+ );
32
+ }
33
+ } else if (
34
+ // someone unfollowed this feed
35
+ follow.target_feed.fid === currentFeedId
36
+ ) {
37
+ const source = follow.source_feed;
38
+
39
+ newState = {
40
+ ...newState,
41
+ // Update FeedResponse fields, that has the new follower/following count
42
+ ...follow.target_feed,
43
+ };
44
+
45
+ if (
46
+ source.created_by.id === connectedUserId &&
47
+ currentState.own_follows !== undefined
48
+ ) {
49
+ newState.own_follows = currentState.own_follows.filter(
50
+ (followItem) => followItem.source_feed.fid !== follow.source_feed.fid,
51
+ );
52
+ }
53
+
54
+ // Only update if followers array already exists
55
+ if (currentState.followers !== undefined) {
56
+ newState.followers = currentState.followers.filter(
57
+ (followItem) => followItem.source_feed.fid !== follow.source_feed.fid,
58
+ );
59
+ }
60
+ }
61
+
62
+ return { changed: true, data: newState };
63
+ };
64
+
65
+ export function handleFollowDeleted(
66
+ this: Feed,
67
+ eventOrResponse: PartializeAllBut<
68
+ EventPayload<'feeds.follow.deleted'>,
69
+ 'follow'
70
+ >,
71
+ ) {
72
+ const follow = eventOrResponse.follow;
73
+
74
+ if (
75
+ !shouldUpdateState({
76
+ stateUpdateQueueId: getStateUpdateQueueId(follow, 'deleted'),
77
+ stateUpdateQueue: this.stateUpdateQueue,
78
+ watch: this.currentState.watch,
79
+ })
80
+ ) {
81
+ return;
82
+ }
83
+
84
+ const connectedUser = this.client.state.getLatestValue().connected_user;
85
+ const result = updateStateFollowDeleted(
86
+ follow,
87
+ this.currentState,
88
+ this.fid,
89
+ connectedUser?.id,
90
+ );
91
+
92
+ if (result.changed) {
93
+ this.state.next(result.data);
94
+ }
95
+ }