@stream-io/feeds-client 0.2.1 → 0.2.3

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 (92) hide show
  1. package/@react-bindings/hooks/feed-state-hooks/index.ts +4 -0
  2. package/CHANGELOG.md +16 -0
  3. package/dist/@react-bindings/contexts/StreamSearchContext.d.ts +1 -1
  4. package/dist/@react-bindings/contexts/StreamSearchResultsContext.d.ts +1 -1
  5. package/dist/@react-bindings/hooks/feed-state-hooks/index.d.ts +4 -0
  6. package/dist/@react-bindings/hooks/feed-state-hooks/useAggregatedActivities.d.ts +11 -0
  7. package/dist/@react-bindings/hooks/feed-state-hooks/useIsAggregatedActivityRead.d.ts +6 -0
  8. package/dist/@react-bindings/hooks/feed-state-hooks/useIsAggregatedActivitySeen.d.ts +6 -0
  9. package/dist/@react-bindings/hooks/feed-state-hooks/useNotificationStatus.d.ts +13 -0
  10. package/dist/@react-bindings/hooks/search-state-hooks/useSearchQuery.d.ts +1 -1
  11. package/dist/@react-bindings/hooks/search-state-hooks/useSearchResult.d.ts +1 -1
  12. package/dist/@react-bindings/hooks/search-state-hooks/useSearchSources.d.ts +2 -2
  13. package/dist/@react-bindings/wrappers/StreamFeed.d.ts +1 -1
  14. package/dist/@react-bindings/wrappers/StreamSearch.d.ts +1 -1
  15. package/dist/@react-bindings/wrappers/StreamSearchResults.d.ts +1 -1
  16. package/dist/index-react-bindings.browser.cjs +178 -35
  17. package/dist/index-react-bindings.browser.cjs.map +1 -1
  18. package/dist/index-react-bindings.browser.js +175 -36
  19. package/dist/index-react-bindings.browser.js.map +1 -1
  20. package/dist/index-react-bindings.node.cjs +178 -35
  21. package/dist/index-react-bindings.node.cjs.map +1 -1
  22. package/dist/index-react-bindings.node.js +175 -36
  23. package/dist/index-react-bindings.node.js.map +1 -1
  24. package/dist/index.browser.cjs +328 -180
  25. package/dist/index.browser.cjs.map +1 -1
  26. package/dist/index.browser.js +328 -181
  27. package/dist/index.browser.js.map +1 -1
  28. package/dist/index.d.ts +1 -5
  29. package/dist/index.node.cjs +328 -180
  30. package/dist/index.node.cjs.map +1 -1
  31. package/dist/index.node.js +328 -181
  32. package/dist/index.node.js.map +1 -1
  33. package/dist/src/common/{ActivitySearchSource.d.ts → search/ActivitySearchSource.d.ts} +3 -3
  34. package/dist/src/common/{BaseSearchSource.d.ts → search/BaseSearchSource.d.ts} +41 -35
  35. package/dist/src/common/{FeedSearchSource.d.ts → search/FeedSearchSource.d.ts} +3 -3
  36. package/dist/src/common/{SearchController.d.ts → search/SearchController.d.ts} +1 -3
  37. package/dist/src/common/{UserSearchSource.d.ts → search/UserSearchSource.d.ts} +4 -4
  38. package/dist/src/common/search/index.d.ts +6 -0
  39. package/dist/src/common/search/types.d.ts +22 -0
  40. package/dist/src/common/types.d.ts +1 -0
  41. package/dist/src/feed/event-handlers/activity/handle-activity-deleted.d.ts +5 -12
  42. package/dist/src/feed/event-handlers/activity/handle-activity-marked.d.ts +11 -0
  43. package/dist/src/feed/event-handlers/activity/index.d.ts +1 -0
  44. package/dist/src/feed/event-handlers/notification-feed/handle-notification-feed-updated.d.ts +8 -1
  45. package/dist/src/feed/feed.d.ts +2 -2
  46. package/dist/src/gen/models/index.d.ts +58 -26
  47. package/dist/tsconfig.tsbuildinfo +1 -1
  48. package/index.ts +1 -5
  49. package/package.json +1 -1
  50. package/src/common/{ActivitySearchSource.ts → search/ActivitySearchSource.ts} +3 -3
  51. package/src/common/{BaseSearchSource.ts → search/BaseSearchSource.ts} +137 -69
  52. package/src/common/{FeedSearchSource.ts → search/FeedSearchSource.ts} +3 -3
  53. package/src/common/{SearchController.ts → search/SearchController.ts} +2 -7
  54. package/src/common/{UserSearchSource.ts → search/UserSearchSource.ts} +3 -3
  55. package/src/common/search/index.ts +6 -0
  56. package/src/common/search/types.ts +21 -0
  57. package/src/common/types.ts +2 -0
  58. package/src/feed/event-handlers/activity/activity-marked-utils.test.ts +208 -0
  59. package/src/feed/event-handlers/activity/activity-utils.test.ts +2 -2
  60. package/src/feed/event-handlers/activity/handle-activity-added.test.ts +86 -0
  61. package/src/feed/event-handlers/activity/handle-activity-deleted.test.ts +117 -0
  62. package/src/feed/event-handlers/activity/handle-activity-deleted.ts +8 -4
  63. package/src/feed/event-handlers/activity/handle-activity-marked.ts +68 -0
  64. package/src/feed/event-handlers/activity/handle-activity-reaction-added.test.ts +15 -15
  65. package/src/feed/event-handlers/activity/handle-activity-reaction-deleted.test.ts +14 -14
  66. package/src/feed/event-handlers/activity/handle-activity-unpinned.test.ts +4 -3
  67. package/src/feed/event-handlers/activity/handle-activity-updated.test.ts +4 -4
  68. package/src/feed/event-handlers/activity/index.ts +2 -1
  69. package/src/feed/event-handlers/bookmark/handle-bookmark-added.test.ts +14 -14
  70. package/src/feed/event-handlers/bookmark/handle-bookmark-deleted.test.ts +14 -14
  71. package/src/feed/event-handlers/bookmark/handle-bookmark-updated.test.ts +16 -16
  72. package/src/feed/event-handlers/comment/handle-comment-added.test.ts +147 -0
  73. package/src/feed/event-handlers/comment/handle-comment-deleted.test.ts +133 -0
  74. package/src/feed/event-handlers/comment/handle-comment-deleted.ts +24 -10
  75. package/src/feed/event-handlers/comment/handle-comment-reaction.test.ts +315 -0
  76. package/src/feed/event-handlers/comment/handle-comment-updated.test.ts +131 -0
  77. package/src/feed/event-handlers/feed-member/handle-feed-member-added.test.ts +75 -0
  78. package/src/feed/event-handlers/feed-member/handle-feed-member-removed.test.ts +82 -0
  79. package/src/feed/event-handlers/feed-member/handle-feed-member-removed.ts +19 -9
  80. package/src/feed/event-handlers/feed-member/handle-feed-member-updated.test.ts +84 -0
  81. package/src/feed/event-handlers/follow/handle-follow-created.test.ts +7 -7
  82. package/src/feed/event-handlers/follow/handle-follow-deleted.test.ts +2 -2
  83. package/src/feed/event-handlers/follow/handle-follow-updated.test.ts +1 -1
  84. package/src/feed/event-handlers/notification-feed/handle-notification-feed-updated.test.ts +120 -0
  85. package/src/feed/event-handlers/notification-feed/handle-notification-feed-updated.ts +47 -3
  86. package/src/feed/feed.ts +4 -2
  87. package/src/gen/feeds/FeedsApi.ts +6 -0
  88. package/src/gen/model-decoders/decoders.ts +12 -0
  89. package/src/gen/models/index.ts +90 -34
  90. package/src/test-utils/response-generators.ts +230 -0
  91. package/dist/src/test-utils/index.d.ts +0 -1
  92. package/dist/src/test-utils/response-generators.d.ts +0 -54
@@ -73,30 +73,30 @@ describe(handleBookmarkUpdated.name, () => {
73
73
  expect(
74
74
  stateBefore.pinned_activities![0].activity.own_bookmarks,
75
75
  ).toHaveLength(1);
76
- expect(stateBefore.activities![0].own_bookmarks[0].updated_at).not.toEqual(
76
+ expect(stateBefore.activities![0].own_bookmarks[0].updated_at).not.toBe(
77
77
  event.bookmark.updated_at,
78
78
  );
79
79
  expect(
80
80
  stateBefore.pinned_activities![0].activity.own_bookmarks[0].updated_at,
81
- ).not.toEqual(event.bookmark.updated_at);
81
+ ).not.toBe(event.bookmark.updated_at);
82
82
 
83
83
  handleBookmarkUpdated.call(feed, event);
84
84
 
85
85
  const stateAfter = feed.currentState;
86
86
  expect(stateAfter.activities![0].own_bookmarks).toHaveLength(1);
87
87
  expect(stateAfter.pinned_activities![0].activity.own_bookmarks).toHaveLength(1);
88
- expect(stateAfter.activities![0].own_bookmarks[0]).toEqual(event.bookmark);
89
- expect(stateAfter.pinned_activities![0].activity.own_bookmarks[0]).toEqual(
88
+ expect(stateAfter.activities![0].own_bookmarks[0]).toBe(event.bookmark);
89
+ expect(stateAfter.pinned_activities![0].activity.own_bookmarks[0]).toBe(
90
90
  event.bookmark,
91
91
  );
92
- expect(stateAfter.activities![0].own_reactions).toEqual(
92
+ expect(stateAfter.activities![0].own_reactions).toBe(
93
93
  stateBefore.activities![0].own_reactions,
94
94
  );
95
- expect(stateAfter.pinned_activities![0].activity.own_reactions).toEqual(
95
+ expect(stateAfter.pinned_activities![0].activity.own_reactions).toBe(
96
96
  stateBefore.pinned_activities![0].activity.own_reactions,
97
97
  );
98
- expect(stateAfter.activities![0].bookmark_count).toEqual(1);
99
- expect(stateAfter.pinned_activities![0].activity.bookmark_count).toEqual(1);
98
+ expect(stateAfter.activities![0].bookmark_count).toBe(1);
99
+ expect(stateAfter.pinned_activities![0].activity.bookmark_count).toBe(1);
100
100
  });
101
101
 
102
102
  it('does not update own_bookmarks if bookmark is from another user but still updates activity', () => {
@@ -135,28 +135,28 @@ describe(handleBookmarkUpdated.name, () => {
135
135
  expect(
136
136
  stateBefore.pinned_activities![0].activity.own_bookmarks,
137
137
  ).toHaveLength(1);
138
- expect(stateBefore.activities![0].bookmark_count).toEqual(1);
139
- expect(stateBefore.pinned_activities![0].activity.bookmark_count).toEqual(
138
+ expect(stateBefore.activities![0].bookmark_count).toBe(1);
139
+ expect(stateBefore.pinned_activities![0].activity.bookmark_count).toBe(
140
140
  1,
141
141
  );
142
142
 
143
143
  handleBookmarkUpdated.call(feed, event);
144
144
 
145
145
  const stateAfter = feed.currentState;
146
- expect(stateAfter.activities![0].own_bookmarks).toEqual(
146
+ expect(stateAfter.activities![0].own_bookmarks).toBe(
147
147
  stateBefore.activities![0].own_bookmarks,
148
148
  );
149
- expect(stateAfter.pinned_activities![0].activity.own_bookmarks).toEqual(
149
+ expect(stateAfter.pinned_activities![0].activity.own_bookmarks).toBe(
150
150
  stateBefore.pinned_activities![0].activity.own_bookmarks,
151
151
  );
152
- expect(stateAfter.activities![0].own_reactions).toEqual(
152
+ expect(stateAfter.activities![0].own_reactions).toBe(
153
153
  stateBefore.activities![0].own_reactions,
154
154
  );
155
- expect(stateAfter.pinned_activities![0].activity.own_reactions).toEqual(
155
+ expect(stateAfter.pinned_activities![0].activity.own_reactions).toBe(
156
156
  stateBefore.pinned_activities![0].activity.own_reactions,
157
157
  );
158
- expect(stateAfter.activities![0].bookmark_count).toEqual(2);
159
- expect(stateAfter.pinned_activities![0].activity.bookmark_count).toEqual(2);
158
+ expect(stateAfter.activities![0].bookmark_count).toBe(2);
159
+ expect(stateAfter.pinned_activities![0].activity.bookmark_count).toBe(2);
160
160
  });
161
161
 
162
162
  it('does nothing if activity is not found', () => {
@@ -0,0 +1,147 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { Feed } from '../../../feed';
3
+ import { FeedsClient } from '../../../feeds-client';
4
+ import { handleCommentAdded } from './handle-comment-added';
5
+ import {
6
+ generateFeedResponse,
7
+ generateOwnUser,
8
+ getHumanId,
9
+ generateCommentAddedEvent,
10
+ generateCommentResponse,
11
+ } from '../../../test-utils/response-generators';
12
+
13
+ describe(handleCommentAdded.name, () => {
14
+ let feed: Feed;
15
+ let client: FeedsClient;
16
+ let currentUserId: string;
17
+ let activityId: string;
18
+
19
+ beforeEach(() => {
20
+ client = new FeedsClient('mock-api-key');
21
+ currentUserId = getHumanId();
22
+ client.state.partialNext({
23
+ connected_user: generateOwnUser({ id: currentUserId }),
24
+ });
25
+ const feedResponse = generateFeedResponse({
26
+ id: 'main',
27
+ group_id: 'user',
28
+ created_by: { id: currentUserId },
29
+ });
30
+ feed = new Feed(
31
+ client,
32
+ feedResponse.group_id,
33
+ feedResponse.id,
34
+ feedResponse,
35
+ );
36
+ activityId = `activity-${getHumanId()}`;
37
+ });
38
+
39
+ it('appends a new comment when pagination.sort is not "last"', () => {
40
+ const existingComment = generateCommentResponse({
41
+ object_id: activityId,
42
+ });
43
+
44
+ feed.state.partialNext({
45
+ comments_by_entity_id: {
46
+ [activityId]: {
47
+ comments: [existingComment],
48
+ pagination: { sort: 'first' },
49
+ },
50
+ },
51
+ });
52
+
53
+ const event = generateCommentAddedEvent({
54
+ comment: { object_id: activityId },
55
+ });
56
+
57
+ const stateBefore = feed.currentState;
58
+ handleCommentAdded.call(feed, event);
59
+ const stateAfter = feed.currentState;
60
+
61
+ expect(stateAfter.comments_by_entity_id[activityId]!.comments).not.toBe(
62
+ stateBefore.comments_by_entity_id[activityId]!.comments,
63
+ );
64
+ expect(stateAfter.comments_by_entity_id[activityId]!.comments).toHaveLength(
65
+ 2,
66
+ );
67
+ expect(
68
+ stateAfter.comments_by_entity_id[activityId]!.comments!.at(-1),
69
+ ).toBe(event.comment);
70
+ });
71
+
72
+ it('prepends a new comment when pagination.sort is "last"', () => {
73
+ const existingComment = generateCommentResponse({
74
+ object_id: activityId,
75
+ });
76
+
77
+ feed.state.partialNext({
78
+ comments_by_entity_id: {
79
+ [activityId]: {
80
+ comments: [existingComment],
81
+ pagination: { sort: 'last' },
82
+ },
83
+ },
84
+ });
85
+
86
+ const event = generateCommentAddedEvent({
87
+ comment: { object_id: activityId },
88
+ });
89
+
90
+ const stateBefore = feed.currentState;
91
+ handleCommentAdded.call(feed, event);
92
+ const stateAfter = feed.currentState;
93
+
94
+ expect(stateAfter.comments_by_entity_id[activityId]!.comments).not.toBe(
95
+ stateBefore.comments_by_entity_id[activityId]!.comments,
96
+ );
97
+ expect(stateAfter.comments_by_entity_id[activityId]!.comments).toHaveLength(
98
+ 2,
99
+ );
100
+ expect(
101
+ stateAfter.comments_by_entity_id[activityId]!.comments!.at(0),
102
+ ).toBe(event.comment);
103
+ });
104
+
105
+ it('stores the comment in the correct parent entity state (prefers parent_id)', () => {
106
+ const parentId = `comment-${getHumanId()}`;
107
+ const existingComment = generateCommentResponse({
108
+ object_id: activityId,
109
+ parent_id: parentId,
110
+ });
111
+
112
+ feed.state.partialNext({
113
+ comments_by_entity_id: {
114
+ [parentId]: {
115
+ comments: [existingComment],
116
+ pagination: { sort: 'best' },
117
+ },
118
+ },
119
+ });
120
+
121
+ const event = generateCommentAddedEvent({
122
+ comment: { parent_id: parentId, object_id: activityId },
123
+ });
124
+
125
+ const prevState = feed.currentState;
126
+ handleCommentAdded.call(feed, event);
127
+ const nextState = feed.currentState;
128
+
129
+ expect(nextState.comments_by_entity_id[parentId]!.comments).not.toBe(
130
+ prevState.comments_by_entity_id[parentId]!.comments,
131
+ );
132
+ expect(nextState.comments_by_entity_id[parentId]!.comments).toHaveLength(2);
133
+ expect(nextState.comments_by_entity_id[parentId]!.comments!.at(-1)).toBe(
134
+ event.comment,
135
+ );
136
+ });
137
+
138
+ it('does nothing if entity state does not exist (comments have not been loaded yet)', () => {
139
+ const event = generateCommentAddedEvent({
140
+ comment: { object_id: activityId },
141
+ });
142
+ const stateBefore = feed.currentState;
143
+ handleCommentAdded.call(feed, event);
144
+ const stateAfter = feed.currentState;
145
+ expect(stateAfter).toBe(stateBefore);
146
+ });
147
+ });
@@ -0,0 +1,133 @@
1
+ import { beforeEach, describe, expect, it } from 'vitest';
2
+ import { Feed } from '../../../feed';
3
+ import { FeedsClient } from '../../../feeds-client';
4
+ import { handleCommentDeleted } from './handle-comment-deleted';
5
+ import {
6
+ generateCommentDeletedEvent,
7
+ generateCommentResponse,
8
+ generateFeedResponse,
9
+ generateOwnUser,
10
+ getHumanId,
11
+ } from '../../../test-utils/response-generators';
12
+
13
+ describe(handleCommentDeleted.name, () => {
14
+ let feed: Feed;
15
+ let client: FeedsClient;
16
+ let currentUserId: string;
17
+ let activityId: string;
18
+
19
+ beforeEach(() => {
20
+ client = new FeedsClient('mock-api-key');
21
+ currentUserId = getHumanId();
22
+ client.state.partialNext({
23
+ connected_user: generateOwnUser({ id: currentUserId }),
24
+ });
25
+ const feedResponse = generateFeedResponse({
26
+ id: 'main',
27
+ group_id: 'user',
28
+ created_by: { id: currentUserId },
29
+ });
30
+ feed = new Feed(
31
+ client,
32
+ feedResponse.group_id,
33
+ feedResponse.id,
34
+ feedResponse,
35
+ );
36
+ activityId = `activity-${getHumanId()}`;
37
+ });
38
+
39
+ it('removes the comment from the entity state (activity level)', () => {
40
+ const comment = generateCommentResponse({ object_id: activityId });
41
+
42
+ feed.state.partialNext({
43
+ comments_by_entity_id: {
44
+ [activityId]: {
45
+ comments: [comment],
46
+ pagination: { sort: 'first' },
47
+ },
48
+ // should be removed by id cleanup
49
+ [comment.id]: {
50
+ comments: [],
51
+ pagination: { sort: 'first' },
52
+ },
53
+ },
54
+ });
55
+
56
+ const event = generateCommentDeletedEvent({
57
+ comment: { id: comment.id, object_id: activityId },
58
+ });
59
+
60
+ const stateBefore = feed.currentState;
61
+
62
+ handleCommentDeleted.call(feed, event);
63
+
64
+ const stateAfter = feed.currentState;
65
+
66
+ expect(stateAfter.comments_by_entity_id[activityId]?.comments).not.toBe(
67
+ stateBefore.comments_by_entity_id[activityId]?.comments,
68
+ );
69
+ expect(stateAfter.comments_by_entity_id[activityId]?.comments).toHaveLength(
70
+ 0,
71
+ );
72
+ expect(stateAfter.comments_by_entity_id).not.toHaveProperty(comment.id);
73
+ });
74
+
75
+ it('removes the comment from the correct parent entity (comment reply)', () => {
76
+ const parentComment = generateCommentResponse({ object_id: activityId });
77
+ const reply = generateCommentResponse({
78
+ object_id: activityId,
79
+ parent_id: parentComment.id,
80
+ });
81
+
82
+ feed.state.partialNext({
83
+ comments_by_entity_id: {
84
+ [activityId]: {
85
+ comments: [parentComment],
86
+ pagination: { sort: 'first' },
87
+ },
88
+ [parentComment.id]: {
89
+ comments: [reply],
90
+ pagination: { sort: 'first' },
91
+ },
92
+ [reply.id]: { comments: [], pagination: { sort: 'first' } },
93
+ },
94
+ });
95
+
96
+ const event = generateCommentDeletedEvent({
97
+ comment: {
98
+ id: reply.id,
99
+ object_id: activityId,
100
+ parent_id: parentComment.id,
101
+ },
102
+ });
103
+
104
+ const stateBefore = feed.currentState;
105
+
106
+ handleCommentDeleted.call(feed, event);
107
+
108
+ const stateAfter = feed.currentState;
109
+ expect(stateAfter).not.toBe(stateBefore);
110
+ expect(
111
+ stateAfter.comments_by_entity_id[parentComment.id]?.comments,
112
+ ).not.toBe(
113
+ stateBefore.comments_by_entity_id[parentComment.id]?.comments,
114
+ );
115
+ expect(
116
+ stateAfter.comments_by_entity_id[parentComment.id]?.comments,
117
+ ).toHaveLength(0);
118
+ expect(stateAfter.comments_by_entity_id).not.toHaveProperty(reply.id);
119
+ expect(stateAfter.comments_by_entity_id).toHaveProperty(activityId);
120
+ });
121
+
122
+ it('does not change the state if the deleted comment is not in state', () => {
123
+ const event = generateCommentDeletedEvent({
124
+ comment: { object_id: activityId },
125
+ });
126
+
127
+ const stateBefore = feed.currentState;
128
+ handleCommentDeleted.call(feed, event);
129
+ const stateAfter = feed.currentState;
130
+
131
+ expect(stateAfter).toBe(stateBefore);
132
+ });
133
+ });
@@ -8,24 +8,38 @@ export function handleCommentDeleted(
8
8
  const entityId = comment.parent_id ?? comment.object_id;
9
9
 
10
10
  this.state.next((currentState) => {
11
- const newCommentsByEntityId = {
12
- ...currentState.comments_by_entity_id,
13
- [entityId]: {
14
- ...currentState.comments_by_entity_id[entityId],
15
- },
16
- };
11
+ let newCommentsByEntityId:
12
+ | typeof currentState.comments_by_entity_id
13
+ | undefined;
17
14
 
18
15
  const index = this.getCommentIndex(comment, currentState);
19
16
 
20
- if (newCommentsByEntityId?.[entityId]?.comments?.length && index !== -1) {
21
- newCommentsByEntityId[entityId].comments = [
22
- ...newCommentsByEntityId[entityId].comments,
17
+ if (index !== -1) {
18
+ newCommentsByEntityId ??= {
19
+ ...currentState.comments_by_entity_id,
20
+ [entityId]: {
21
+ ...currentState.comments_by_entity_id[entityId],
22
+ },
23
+ };
24
+
25
+ newCommentsByEntityId[entityId]!.comments = [
26
+ ...newCommentsByEntityId[entityId]!.comments!,
23
27
  ];
24
28
 
25
29
  newCommentsByEntityId[entityId]?.comments?.splice(index, 1);
26
30
  }
27
31
 
28
- delete newCommentsByEntityId[comment.id];
32
+ if (typeof currentState.comments_by_entity_id[comment.id] !== 'undefined') {
33
+ newCommentsByEntityId ??= {
34
+ ...currentState.comments_by_entity_id,
35
+ };
36
+
37
+ delete newCommentsByEntityId[comment.id];
38
+ }
39
+
40
+ if (!newCommentsByEntityId) {
41
+ return currentState;
42
+ }
29
43
 
30
44
  return {
31
45
  ...currentState,