@amityco/ts-sdk 0.0.1-beta.37 → 0.0.1-beta.39

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 (251) hide show
  1. package/.eslintrc.json +3 -5
  2. package/dist/@types/core/events.d.ts +1 -0
  3. package/dist/@types/core/events.d.ts.map +1 -1
  4. package/dist/@types/core/model.d.ts +2 -0
  5. package/dist/@types/core/model.d.ts.map +1 -1
  6. package/dist/@types/domains/category.d.ts +7 -0
  7. package/dist/@types/domains/category.d.ts.map +1 -1
  8. package/dist/@types/domains/channel.d.ts +2 -2
  9. package/dist/@types/domains/channel.d.ts.map +1 -1
  10. package/dist/@types/domains/community.d.ts +12 -0
  11. package/dist/@types/domains/community.d.ts.map +1 -1
  12. package/dist/@types/domains/feed.d.ts +1 -2
  13. package/dist/@types/domains/feed.d.ts.map +1 -1
  14. package/dist/@types/domains/message.d.ts +1 -1
  15. package/dist/@types/domains/message.d.ts.map +1 -1
  16. package/dist/@types/domains/post.d.ts +1 -1
  17. package/dist/@types/domains/post.d.ts.map +1 -1
  18. package/dist/@types/domains/user.d.ts +8 -0
  19. package/dist/@types/domains/user.d.ts.map +1 -1
  20. package/dist/@types/global.d.ts +5 -0
  21. package/dist/@types/global.d.ts.map +1 -1
  22. package/dist/category/api/queryCategories.d.ts +1 -5
  23. package/dist/category/api/queryCategories.d.ts.map +1 -1
  24. package/dist/category/observers/index.d.ts +2 -0
  25. package/dist/category/observers/index.d.ts.map +1 -0
  26. package/dist/category/observers/liveCategories.d.ts +18 -0
  27. package/dist/category/observers/liveCategories.d.ts.map +1 -0
  28. package/dist/category/observers/tests/liveCategories.test.d.ts +2 -0
  29. package/dist/category/observers/tests/liveCategories.test.d.ts.map +1 -0
  30. package/dist/channel/api/tests/getChannels.test.d.ts +2 -0
  31. package/dist/channel/api/tests/getChannels.test.d.ts.map +1 -0
  32. package/dist/channel/api/tests/leaveChannel.test.d.ts +2 -0
  33. package/dist/channel/api/tests/leaveChannel.test.d.ts.map +1 -0
  34. package/dist/channel/observers/liveChannel.d.ts.map +1 -1
  35. package/dist/channel/observers/liveChannels.d.ts.map +1 -1
  36. package/dist/client/api/accessTokenExpiryWatcher.d.ts +4 -1
  37. package/dist/client/api/accessTokenExpiryWatcher.d.ts.map +1 -1
  38. package/dist/client/api/connectClient.d.ts.map +1 -1
  39. package/dist/client/api/getToken.d.ts +3 -1
  40. package/dist/client/api/getToken.d.ts.map +1 -1
  41. package/dist/client/utils/setClientToken.d.ts +3 -3
  42. package/dist/comment/api/deleteComment.d.ts.map +1 -1
  43. package/dist/comment/api/getComment.d.ts.map +1 -1
  44. package/dist/comment/observers/index.d.ts +1 -0
  45. package/dist/comment/observers/index.d.ts.map +1 -1
  46. package/dist/comment/observers/liveComment.d.ts +21 -0
  47. package/dist/comment/observers/liveComment.d.ts.map +1 -0
  48. package/dist/comment/observers/tests/liveComment.test.d.ts +2 -0
  49. package/dist/comment/observers/tests/liveComment.test.d.ts.map +1 -0
  50. package/dist/community/api/queryCommunities.d.ts +1 -10
  51. package/dist/community/api/queryCommunities.d.ts.map +1 -1
  52. package/dist/community/events/index.d.ts +1 -0
  53. package/dist/community/events/index.d.ts.map +1 -1
  54. package/dist/community/events/onCommunityUserChanged.d.ts +17 -0
  55. package/dist/community/events/onCommunityUserChanged.d.ts.map +1 -0
  56. package/dist/community/events/utils.d.ts +1 -0
  57. package/dist/community/events/utils.d.ts.map +1 -1
  58. package/dist/community/observers/index.d.ts +1 -0
  59. package/dist/community/observers/index.d.ts.map +1 -1
  60. package/dist/community/observers/liveCommunities.d.ts +20 -0
  61. package/dist/community/observers/liveCommunities.d.ts.map +1 -0
  62. package/dist/community/observers/tests/liveCommunities.test.d.ts +2 -0
  63. package/dist/community/observers/tests/liveCommunities.test.d.ts.map +1 -0
  64. package/dist/core/events.d.ts +3 -3
  65. package/dist/core/events.d.ts.map +1 -1
  66. package/dist/core/model/idResolvers.d.ts.map +1 -1
  67. package/dist/core/model/identifyModel.d.ts.map +1 -1
  68. package/dist/core/model/index.d.ts.map +1 -1
  69. package/dist/core/query/filtering.d.ts +10 -0
  70. package/dist/core/query/filtering.d.ts.map +1 -1
  71. package/dist/core/query/paging.d.ts +2 -2
  72. package/dist/core/query/paging.d.ts.map +1 -1
  73. package/dist/core/query/sorting.d.ts +6 -0
  74. package/dist/core/query/sorting.d.ts.map +1 -1
  75. package/dist/core/query/tests/paging.test.d.ts +2 -0
  76. package/dist/core/query/tests/paging.test.d.ts.map +1 -0
  77. package/dist/core/transports/http.d.ts +0 -5
  78. package/dist/core/transports/http.d.ts.map +1 -1
  79. package/dist/file/api/test/createImage.test.d.ts +2 -0
  80. package/dist/file/api/test/createImage.test.d.ts.map +1 -0
  81. package/dist/file/api/test/createVideo.test.d.ts +2 -0
  82. package/dist/file/api/test/createVideo.test.d.ts.map +1 -0
  83. package/dist/index.cjs.js +607 -252
  84. package/dist/index.d.ts +1 -0
  85. package/dist/index.d.ts.map +1 -1
  86. package/dist/index.esm.js +599 -251
  87. package/dist/index.umd.js +4 -4
  88. package/dist/message/api/queryMessages.d.ts +2 -2
  89. package/dist/message/api/queryMessages.d.ts.map +1 -1
  90. package/dist/message/observers/liveMessage.d.ts.map +1 -1
  91. package/dist/post/observers/index.d.ts +1 -0
  92. package/dist/post/observers/index.d.ts.map +1 -1
  93. package/dist/post/observers/livePost.d.ts +21 -0
  94. package/dist/post/observers/livePost.d.ts.map +1 -0
  95. package/dist/post/observers/livePosts.d.ts +2 -2
  96. package/dist/post/observers/livePosts.d.ts.map +1 -1
  97. package/dist/post/observers/tests/livePost.test.d.ts +2 -0
  98. package/dist/post/observers/tests/livePost.test.d.ts.map +1 -0
  99. package/dist/report/api/createReport.d.ts.map +1 -1
  100. package/dist/report/api/deleteReport.d.ts.map +1 -1
  101. package/dist/report/api/tests/createReport.test.d.ts +2 -0
  102. package/dist/report/api/tests/createReport.test.d.ts.map +1 -0
  103. package/dist/report/api/tests/deleteReport.test.d.ts +2 -0
  104. package/dist/report/api/tests/deleteReport.test.d.ts.map +1 -0
  105. package/dist/subChannel/api/tests/deleteSubChannel.test.d.ts +2 -0
  106. package/dist/subChannel/api/tests/deleteSubChannel.test.d.ts.map +1 -0
  107. package/dist/subChannel/api/tests/getSubChannel.test.d.ts +2 -0
  108. package/dist/subChannel/api/tests/getSubChannel.test.d.ts.map +1 -0
  109. package/dist/subChannel/api/tests/getSubChannels.test.d.ts +2 -0
  110. package/dist/subChannel/api/tests/getSubChannels.test.d.ts.map +1 -0
  111. package/dist/subChannel/api/tests/updateSubChannel.test.d.ts +2 -0
  112. package/dist/subChannel/api/tests/updateSubChannel.test.d.ts.map +1 -0
  113. package/dist/subChannel/observers/liveSubChannel.d.ts.map +1 -1
  114. package/dist/user/api/queryUsers.d.ts +1 -6
  115. package/dist/user/api/queryUsers.d.ts.map +1 -1
  116. package/dist/user/api/tests/updateUser.test.d.ts +2 -0
  117. package/dist/user/api/tests/updateUser.test.d.ts.map +1 -0
  118. package/dist/user/observers/index.d.ts +1 -0
  119. package/dist/user/observers/index.d.ts.map +1 -1
  120. package/dist/user/observers/liveUser.d.ts.map +1 -1
  121. package/dist/user/observers/liveUsers.d.ts +18 -0
  122. package/dist/user/observers/liveUsers.d.ts.map +1 -0
  123. package/dist/user/observers/tests/liveUsers.test.d.ts +2 -0
  124. package/dist/user/observers/tests/liveUsers.test.d.ts.map +1 -0
  125. package/dist/utils/liveObject.d.ts +2 -0
  126. package/dist/utils/liveObject.d.ts.map +1 -0
  127. package/dist/utils/tests/dummy/category.d.ts +64 -0
  128. package/dist/utils/tests/dummy/category.d.ts.map +1 -0
  129. package/dist/utils/tests/dummy/channel.d.ts +50 -0
  130. package/dist/utils/tests/dummy/channel.d.ts.map +1 -1
  131. package/dist/utils/tests/dummy/community.d.ts +132 -0
  132. package/dist/utils/tests/dummy/community.d.ts.map +1 -1
  133. package/dist/utils/tests/dummy/feed.d.ts +21 -0
  134. package/dist/utils/tests/dummy/feed.d.ts.map +1 -0
  135. package/dist/utils/tests/dummy/file.d.ts +3 -0
  136. package/dist/utils/tests/dummy/file.d.ts.map +1 -1
  137. package/dist/utils/tests/dummy/index.d.ts +2 -0
  138. package/dist/utils/tests/dummy/index.d.ts.map +1 -1
  139. package/dist/utils/tests/dummy/post.d.ts +11 -0
  140. package/dist/utils/tests/dummy/post.d.ts.map +1 -1
  141. package/dist/utils/tests/dummy/subChannel.d.ts +0 -20
  142. package/dist/utils/tests/dummy/subChannel.d.ts.map +1 -1
  143. package/dist/utils/tests/dummy/user.d.ts +81 -0
  144. package/dist/utils/tests/dummy/user.d.ts.map +1 -1
  145. package/dist/utils/tests/liveObject.test.d.ts +2 -0
  146. package/dist/utils/tests/liveObject.test.d.ts.map +1 -0
  147. package/package.json +4 -4
  148. package/src/@types/core/events.ts +1 -0
  149. package/src/@types/core/model.ts +4 -0
  150. package/src/@types/domains/category.ts +13 -0
  151. package/src/@types/domains/channel.ts +2 -2
  152. package/src/@types/domains/community.ts +25 -0
  153. package/src/@types/domains/feed.ts +1 -3
  154. package/src/@types/domains/message.ts +4 -1
  155. package/src/@types/domains/post.ts +1 -1
  156. package/src/@types/domains/user.ts +14 -0
  157. package/src/@types/global.ts +5 -0
  158. package/src/cache/api/tests/ingestInCache.test.ts +2 -0
  159. package/src/category/api/queryCategories.ts +3 -5
  160. package/src/category/observers/index.ts +1 -0
  161. package/src/category/observers/liveCategories.ts +141 -0
  162. package/src/category/observers/tests/liveCategories.test.ts +147 -0
  163. package/src/channel/api/tests/createChannel.test.ts +30 -6
  164. package/src/channel/api/tests/getChannel.test.ts +3 -1
  165. package/src/channel/api/tests/getChannels.test.ts +97 -0
  166. package/src/channel/api/tests/joinChannel.test.ts +62 -3
  167. package/src/channel/api/tests/leaveChannel.test.ts +90 -0
  168. package/src/channel/api/tests/queryChannels.test.ts +38 -1
  169. package/src/channel/observers/liveChannel.ts +8 -80
  170. package/src/channel/observers/liveChannels.ts +29 -24
  171. package/src/client/api/accessTokenExpiryWatcher.ts +7 -5
  172. package/src/client/api/connectClient.ts +0 -2
  173. package/src/client/api/tests/accessTokenExpiryWatcher.test.ts +8 -5
  174. package/src/comment/api/deleteComment.ts +5 -5
  175. package/src/comment/api/getComment.ts +8 -3
  176. package/src/comment/observers/index.ts +1 -0
  177. package/src/comment/observers/liveComment.ts +43 -0
  178. package/src/comment/observers/tests/liveComment.test.ts +95 -0
  179. package/src/community/api/queryCommunities.ts +3 -10
  180. package/src/community/api/test/addCommunityMember.test.ts +14 -2
  181. package/src/community/api/test/joinCommunity.test.ts +14 -2
  182. package/src/community/api/test/leaveCommunity.test.ts +11 -1
  183. package/src/community/api/test/removeCommunityMembers.test.ts +11 -1
  184. package/src/community/events/index.ts +1 -0
  185. package/src/community/events/onCommunityUserChanged.ts +20 -0
  186. package/src/community/events/test/onCommunityJoined.test.ts +1 -1
  187. package/src/community/events/test/onCommunityUserAdded.test.ts +1 -1
  188. package/src/community/events/test/onCommunityUserBanned.test.ts +1 -1
  189. package/src/community/events/test/onCommunityUserUnbaned.test.ts +1 -1
  190. package/src/community/events/test/utils.test.ts +52 -3
  191. package/src/community/events/utils.ts +49 -0
  192. package/src/community/observers/index.ts +1 -0
  193. package/src/community/observers/liveCommunities.ts +188 -0
  194. package/src/community/observers/tests/liveCommunities.test.ts +284 -0
  195. package/src/core/model/idResolvers.ts +2 -0
  196. package/src/core/model/identifyModel.ts +1 -0
  197. package/src/core/model/index.ts +2 -0
  198. package/src/core/query/filtering.ts +33 -3
  199. package/src/core/query/paging.ts +5 -4
  200. package/src/core/query/sorting.ts +8 -0
  201. package/src/core/query/tests/paging.test.ts +66 -0
  202. package/src/core/transports/http.ts +17 -13
  203. package/src/core/transports/test/http.test.ts +49 -60
  204. package/src/file/api/createFile.ts +1 -1
  205. package/src/file/api/createImage.ts +1 -1
  206. package/src/file/api/createVideo.ts +1 -1
  207. package/src/file/api/test/createImage.test.ts +69 -0
  208. package/src/file/api/test/createVideo.test.ts +81 -0
  209. package/src/index.ts +1 -0
  210. package/src/message/api/queryMessages.ts +7 -7
  211. package/src/message/api/tests/queryMessages.test.ts +6 -6
  212. package/src/message/observers/liveMessage.ts +11 -83
  213. package/src/message/tests/observers/liveMessage.test.ts +1 -143
  214. package/src/message/tests/observers/liveMessages.test.ts +4 -4
  215. package/src/post/observers/index.ts +1 -0
  216. package/src/post/observers/livePost.ts +47 -0
  217. package/src/post/observers/livePosts.ts +14 -22
  218. package/src/post/observers/tests/livePost.test.ts +97 -0
  219. package/src/post/observers/tests/livePosts.test.ts +65 -8
  220. package/src/report/api/createReport.ts +1 -6
  221. package/src/report/api/deleteReport.ts +2 -6
  222. package/src/report/api/tests/createReport.test.ts +93 -0
  223. package/src/report/api/tests/deleteReport.test.ts +93 -0
  224. package/src/stream/api/tests/createStream.test.ts +3 -11
  225. package/src/stream/api/tests/deleteStream.test.ts +5 -3
  226. package/src/subChannel/api/tests/createSubChannel.test.ts +21 -14
  227. package/src/subChannel/api/tests/deleteSubChannel.test.ts +112 -0
  228. package/src/subChannel/api/tests/getSubChannel.test.ts +97 -0
  229. package/src/subChannel/api/tests/getSubChannels.test.ts +97 -0
  230. package/src/subChannel/api/tests/updateSubChannel.test.ts +86 -0
  231. package/src/subChannel/observers/liveSubChannel.ts +6 -56
  232. package/src/subChannel/observers/tests/liveSubChannel.test.ts +0 -82
  233. package/src/user/api/queryUsers.ts +1 -6
  234. package/src/user/api/tests/updateUser.test.ts +60 -0
  235. package/src/user/observers/index.ts +1 -0
  236. package/src/user/observers/liveUser.ts +9 -61
  237. package/src/user/observers/liveUsers.ts +162 -0
  238. package/src/user/observers/tests/liveUsers.test.ts +202 -0
  239. package/src/user/tests/observers/liveUser.test.ts +1 -154
  240. package/src/utils/liveObject.ts +86 -0
  241. package/src/utils/tests/dummy/category.ts +57 -0
  242. package/src/utils/tests/dummy/channel.ts +52 -1
  243. package/src/utils/tests/dummy/community.ts +94 -0
  244. package/src/utils/tests/dummy/feed.ts +13 -0
  245. package/src/utils/tests/dummy/file.ts +32 -0
  246. package/src/utils/tests/dummy/index.ts +3 -0
  247. package/src/utils/tests/dummy/post.ts +62 -47
  248. package/src/utils/tests/dummy/stream.ts +73 -73
  249. package/src/utils/tests/dummy/subChannel.ts +0 -22
  250. package/src/utils/tests/dummy/user.ts +57 -0
  251. package/src/utils/tests/liveObject.test.ts +172 -0
@@ -0,0 +1,188 @@
1
+ /* eslint-disable no-use-before-define */
2
+ import { getResolver } from '~/core/model';
3
+ import { pullFromCache, pushToCache } from '~/cache/api';
4
+ import { getActiveClient } from '~/client/api';
5
+ import {
6
+ createQuery,
7
+ runQuery,
8
+ queryOptions,
9
+ filterByPropEquality,
10
+ sortByLastCreated,
11
+ sortByDisplayName,
12
+ sortByFirstCreated,
13
+ } from '~/core/query';
14
+ import {
15
+ COLLECTION_DEFAULT_CACHING_POLICY,
16
+ COLLECTION_DEFAULT_PAGINATION_LIMIT,
17
+ ENABLE_CACHE_MESSAGE,
18
+ } from '~/utils/constants';
19
+ import {
20
+ onCommunityCreated,
21
+ onCommunityDeleted,
22
+ onCommunityUpdated,
23
+ onCommunityJoined,
24
+ onCommunityLeft,
25
+ onCommunityUserChanged,
26
+ } from '../events';
27
+ import { queryCommunities } from '../api';
28
+
29
+ /**
30
+ * ```js
31
+ * import { liveCommunities } from '@amityco/ts-sdk'
32
+ *
33
+ * let communities = []
34
+ * const unsub = liveCommunities({
35
+ * displayName: Amity.Community['displayName'],
36
+ * }, response => merge(communities, response.data))
37
+ * ```
38
+ *
39
+ * Observe all mutations on a list of {@link Amity.Community}s
40
+ *
41
+ * @param params for querying communities
42
+ * @param callback the function to call when new data are available
43
+ * @returns An {@link Amity.Unsubscriber} function to run when willing to stop observing the communities
44
+ *
45
+ * @category Community Live Collection
46
+ */
47
+ export const liveCommunities = (
48
+ params: Amity.CommunityLiveCollection,
49
+ callback: Amity.LiveCollectionCallback<Amity.Community>,
50
+ config?: Amity.LiveCollectionConfig,
51
+ ) => {
52
+ const { log, cache } = getActiveClient();
53
+
54
+ if (!cache) {
55
+ console.log(ENABLE_CACHE_MESSAGE);
56
+ }
57
+
58
+ const timestamp = Date.now();
59
+ log(`liveCommunities(tmpid: ${timestamp}) > listen`);
60
+
61
+ const { limit: queryLimit, ...queryParams } = params;
62
+
63
+ const limit = queryLimit ?? COLLECTION_DEFAULT_PAGINATION_LIMIT;
64
+ const { policy = COLLECTION_DEFAULT_CACHING_POLICY } = config ?? {};
65
+
66
+ const disposers: Amity.Unsubscriber[] = [];
67
+ const cacheKey = ['community', 'collection', {}];
68
+
69
+ const responder = (data: Amity.CommunityLiveCollectionCache) => {
70
+ let communities: Amity.Community[] =
71
+ data.data
72
+ .map(communityId => pullFromCache<Amity.Community>(['community', 'get', communityId])!)
73
+ .filter(Boolean)
74
+ .map(({ data }) => data) ?? [];
75
+
76
+ communities = filterByPropEquality(communities, 'isDeleted', params.isDeleted);
77
+ communities = filterByPropEquality(communities, 'displayName', params.displayName);
78
+
79
+ if (params.categoryId) {
80
+ communities = communities.filter(c => c.categoryIds?.includes(params.categoryId!));
81
+ }
82
+
83
+ if (params.tags) {
84
+ communities = communities.filter(c => c.tags?.some(t => params.tags?.includes(t)));
85
+ }
86
+
87
+ if (params.membership === 'member') {
88
+ communities = filterByPropEquality(communities, 'isJoined', true);
89
+ }
90
+
91
+ if (params.membership === 'notMember') {
92
+ communities = filterByPropEquality(communities, 'isJoined', false);
93
+ }
94
+
95
+ const sortBy = params.sortBy || 'lastCreated';
96
+
97
+ if (sortBy === 'lastCreated' || sortBy === 'firstCreated') {
98
+ communities = communities.sort(
99
+ sortBy === 'lastCreated' ? sortByLastCreated : sortByFirstCreated,
100
+ );
101
+ }
102
+
103
+ /*
104
+ * The server returns communities with empty | null displayName's first before
105
+ * returning sorted list of communities with displayNames
106
+ *
107
+ * This section needs to be updated as displayNames can be null as well
108
+ */
109
+ if (sortBy === 'displayName') {
110
+ communities = communities
111
+ // this needs to be aligned with the backend data type
112
+ .map(c => (c.displayName ? c : { ...c, displayName: '' }))
113
+ // @ts-ignore
114
+ .sort(sortByDisplayName);
115
+ }
116
+
117
+ callback({
118
+ onNextPage: onFetch,
119
+ data: communities,
120
+ hasNextPage: !!data.params?.page,
121
+ loading: data.loading,
122
+ error: data.error,
123
+ });
124
+ };
125
+
126
+ const realtimeRouter = (_: Amity.CommunityActionType) => (community: Amity.Community) => {
127
+ const collection = pullFromCache<Amity.CommunityLiveCollectionCache>(cacheKey)?.data;
128
+ if (!collection) return;
129
+
130
+ /*
131
+ * Simply update collection and let responder decide what to do with data
132
+ */
133
+ collection.data = [...new Set([community.communityId, ...collection.data])];
134
+
135
+ pushToCache(cacheKey, collection);
136
+ responder(collection);
137
+ };
138
+
139
+ const onFetch = () => {
140
+ const collection = pullFromCache<Amity.CommunityLiveCollectionCache>(cacheKey)?.data;
141
+
142
+ const communities = collection?.data ?? [];
143
+
144
+ if (communities.length > 0 && !collection?.params.page) return;
145
+
146
+ const query = createQuery(queryCommunities, {
147
+ ...queryParams,
148
+ page: collection?.params.page ?? { limit },
149
+ });
150
+
151
+ runQuery(
152
+ query,
153
+ ({ data: result, error, loading, nextPage: page }) => {
154
+ const data = {
155
+ loading,
156
+ error,
157
+ params: { page },
158
+ data: communities,
159
+ };
160
+
161
+ if (result) {
162
+ data.data = [...new Set([...communities, ...result.map(getResolver('community'))])];
163
+ }
164
+
165
+ pushToCache(cacheKey, data);
166
+
167
+ responder(data);
168
+ },
169
+ queryOptions(policy),
170
+ );
171
+ };
172
+
173
+ disposers.push(
174
+ onCommunityCreated(realtimeRouter('onCreate')),
175
+ onCommunityDeleted(realtimeRouter('onDelete')),
176
+ onCommunityUpdated(realtimeRouter('onUpdate')),
177
+ onCommunityJoined(realtimeRouter('onJoin')),
178
+ onCommunityLeft(realtimeRouter('onLeft')),
179
+ onCommunityUserChanged(realtimeRouter('onMemberCountChanged')),
180
+ );
181
+
182
+ onFetch();
183
+
184
+ return () => {
185
+ log(`liveCommunities(tmpid: ${timestamp}) > dispose`);
186
+ disposers.forEach(fn => fn());
187
+ };
188
+ };
@@ -0,0 +1,284 @@
1
+ import { disableCache, enableCache } from '~/cache/api';
2
+ import {
3
+ communityQueryResponse,
4
+ communityQueryResponsePage2,
5
+ client,
6
+ connectClient,
7
+ disconnectClient,
8
+ pause,
9
+ communityUser11,
10
+ } from '~/utils/tests';
11
+
12
+ import { liveCommunities } from '../liveCommunities';
13
+
14
+ import { unpackCommunityPayload } from '../../utils';
15
+
16
+ const getSnapshot = (params?: Record<string, any>) => {
17
+ return {
18
+ data: [] as Amity.Community[],
19
+ loading: true,
20
+ error: undefined as any,
21
+ ...params,
22
+ };
23
+ };
24
+
25
+ const { paging, ...payload } = communityQueryResponse.data;
26
+ const { paging: paging2, ...payload2 } = communityQueryResponsePage2.data;
27
+
28
+ const community1 = unpackCommunityPayload(payload).communities[0];
29
+ const community2 = unpackCommunityPayload(payload).communities[1];
30
+
31
+ describe('liveCommunities', () => {
32
+ beforeAll(connectClient);
33
+ afterAll(disconnectClient);
34
+
35
+ beforeEach(enableCache);
36
+
37
+ afterEach(disableCache);
38
+
39
+ test('it should return community collection', async () => {
40
+ const callback = jest.fn();
41
+ client.http.get = jest.fn().mockResolvedValue(communityQueryResponse);
42
+
43
+ liveCommunities({}, callback);
44
+ await pause();
45
+
46
+ expect(callback).toHaveBeenCalledTimes(2);
47
+ expect(callback).toHaveBeenNthCalledWith(1, expect.objectContaining(getSnapshot()));
48
+ expect(callback).toHaveBeenNthCalledWith(
49
+ 2,
50
+ expect.objectContaining(
51
+ getSnapshot({
52
+ data: unpackCommunityPayload(payload).communities,
53
+ loading: false,
54
+ }),
55
+ ),
56
+ );
57
+ });
58
+
59
+ test('it should return data from cache', async () => {
60
+ const callback = jest.fn();
61
+ client.http.get = jest.fn().mockResolvedValue(communityQueryResponse);
62
+
63
+ liveCommunities({}, () => undefined);
64
+ await pause();
65
+ liveCommunities({}, callback);
66
+ await pause();
67
+
68
+ expect(callback).toHaveBeenCalledTimes(2);
69
+ expect(callback).toHaveBeenNthCalledWith(
70
+ 1,
71
+ expect.objectContaining(
72
+ getSnapshot({
73
+ data: unpackCommunityPayload(payload).communities,
74
+ }),
75
+ ),
76
+ );
77
+ expect(callback).toHaveBeenNthCalledWith(
78
+ 2,
79
+ expect.objectContaining(
80
+ getSnapshot({
81
+ data: unpackCommunityPayload(payload).communities,
82
+ loading: false,
83
+ }),
84
+ ),
85
+ );
86
+ });
87
+
88
+ const filters: [string, Amity.CommunityLiveCollection, Amity.Community[]][] = [
89
+ ['deleted', { isDeleted: true }, [community1]],
90
+ ['categoryId', { categoryId: 'test-category-id' }, [community1]],
91
+ ['displayName', { displayName: 'community 1' }, [community1]],
92
+ ['tagged', { tags: ['test-community-tag'] }, [community1]],
93
+ ['member', { membership: 'member' }, [community1]],
94
+ ['not member', { membership: 'notMember' }, [community2]],
95
+ ['all membership', { membership: 'all' }, [community1, community2]],
96
+ ];
97
+
98
+ test.each(filters)('it should filter by %s communities', async (filter, params, expected) => {
99
+ const callback = jest.fn();
100
+ client.http.get = jest.fn().mockResolvedValue(communityQueryResponse);
101
+
102
+ liveCommunities(params, callback);
103
+
104
+ expect(callback).toHaveBeenCalledTimes(1);
105
+ // check if cache data returned (should be empty)
106
+ expect(callback).toHaveBeenCalledWith(expect.objectContaining(getSnapshot()));
107
+
108
+ await pause();
109
+
110
+ expect(callback).toHaveBeenCalledTimes(2);
111
+ expect(callback).toHaveBeenCalledWith(
112
+ expect.objectContaining(
113
+ getSnapshot({
114
+ loading: false,
115
+ data: expected,
116
+ }),
117
+ ),
118
+ );
119
+ });
120
+
121
+ test('it should return method to fetch next page', async () => {
122
+ const callback = jest.fn();
123
+ client.http.get = jest
124
+ .fn()
125
+ .mockResolvedValue(communityQueryResponse)
126
+ .mockResolvedValueOnce(communityQueryResponsePage2);
127
+
128
+ liveCommunities({}, callback);
129
+ await pause();
130
+
131
+ expect(callback).toHaveBeenCalled();
132
+ expect(callback.mock.lastCall).toHaveLength(1);
133
+
134
+ const { onNextPage, hasNextPage } = callback.mock.lastCall[0];
135
+
136
+ expect(hasNextPage).toBe(true);
137
+ expect(onNextPage).toBeTruthy();
138
+
139
+ onNextPage();
140
+ await pause();
141
+
142
+ // 4 -> because 1 local & server call each per call (2)
143
+ expect(callback).toHaveBeenCalledTimes(4);
144
+ expect(callback).toHaveBeenNthCalledWith(
145
+ 4,
146
+ expect.objectContaining(
147
+ getSnapshot({
148
+ loading: false,
149
+ data: unpackCommunityPayload(payload).communities,
150
+ }),
151
+ ),
152
+ );
153
+ });
154
+
155
+ describe('events', () => {
156
+ const newCommunityPayload = {
157
+ ...communityQueryResponse.data.communities[0],
158
+ communityId: 'new-community',
159
+ isDeleted: false,
160
+ };
161
+ const newCommunity = unpackCommunityPayload({ communities: [newCommunityPayload] })
162
+ .communities[0];
163
+
164
+ const communityPayload = {
165
+ communities: [communityQueryResponse.data.communities[0]],
166
+ communityUsers: [communityUser11],
167
+ };
168
+
169
+ const cases: [string, keyof Amity.Events, Amity.CommunityPayload, Amity.Community[]][] = [
170
+ [
171
+ 'it should add new community to collection onCreate',
172
+ 'community.created',
173
+ { communities: [newCommunityPayload] },
174
+ [newCommunity, community2],
175
+ ],
176
+ [
177
+ 'it should update community in collection onUpdate',
178
+ 'community.updated',
179
+ { communities: [{ ...newCommunityPayload, communityId: community1.communityId }] },
180
+ [{ ...newCommunity, communityId: community1.communityId }, community2],
181
+ ],
182
+ [
183
+ 'it should remove community from collection onDelete',
184
+ 'community.deleted',
185
+ communityPayload,
186
+ [community2],
187
+ ],
188
+ ];
189
+
190
+ test.each(cases)('%s', async (test, event, communityPayload, expected) => {
191
+ const callback = jest.fn();
192
+ client.http.get = jest.fn().mockResolvedValue(communityQueryResponse);
193
+
194
+ liveCommunities({ isDeleted: false }, callback);
195
+ await pause();
196
+
197
+ client.emitter.emit(event, communityPayload);
198
+ await pause();
199
+
200
+ expect(callback).toHaveBeenNthCalledWith(
201
+ 3,
202
+ expect.objectContaining(
203
+ getSnapshot({
204
+ data: expected,
205
+ loading: false,
206
+ }),
207
+ ),
208
+ );
209
+ });
210
+
211
+ const joinCommunityPayload = {
212
+ ...communityQueryResponse.data.communities[0],
213
+ communityId: 'joining-community',
214
+ isDeleted: false,
215
+ };
216
+ const joinedCommunity = unpackCommunityPayload({ communities: [joinCommunityPayload] })
217
+ .communities[0];
218
+
219
+ const joiningUser: Amity.MembershipRaw<'community'> = {
220
+ ...communityUser11,
221
+ userId: 'test',
222
+ communityId: 'joining-community',
223
+ communityMembership: 'member',
224
+ };
225
+
226
+ const memberEvents: [
227
+ string,
228
+ Amity.CommunityLiveCollection,
229
+ keyof Amity.Events,
230
+ Amity.CommunityPayload,
231
+ Amity.Community[],
232
+ ][] = [
233
+ [
234
+ 'it should add new community to collection onJoin',
235
+ { membership: 'member' },
236
+ 'community.joined',
237
+ { communities: [joinCommunityPayload], communityUsers: [joiningUser] },
238
+ [joinedCommunity, community1],
239
+ ],
240
+ [
241
+ 'it should not add new member to community collection onJoin if membership:not member',
242
+ { membership: 'notMember' },
243
+ 'community.joined',
244
+ { communities: [joinCommunityPayload], communityUsers: [joiningUser] },
245
+ [],
246
+ ],
247
+ [
248
+ 'it should update community in collection onLeft',
249
+ { membership: 'member' },
250
+ 'community.left',
251
+ { ...communityPayload, communities: [{ ...community1, isJoined: false }] },
252
+ [],
253
+ ],
254
+ [
255
+ 'it should add new community in collection onLeft and if membership:not member',
256
+ { membership: 'notMember' },
257
+ 'community.left',
258
+ { ...communityPayload, communities: [{ ...community1, isJoined: false }] },
259
+ [{ ...community1, isJoined: false }],
260
+ ],
261
+ ];
262
+
263
+ test.each(memberEvents)('%s', async (test, params, event, communityPayload, expected) => {
264
+ const callback = jest.fn();
265
+ client.http.get = jest.fn().mockResolvedValue(communityQueryResponsePage2);
266
+
267
+ liveCommunities(params, callback);
268
+ await pause();
269
+
270
+ client.emitter.emit(event, communityPayload);
271
+ await pause();
272
+
273
+ expect(callback).toHaveBeenNthCalledWith(
274
+ 3,
275
+ expect.objectContaining(
276
+ getSnapshot({
277
+ data: expected,
278
+ loading: false,
279
+ }),
280
+ ),
281
+ );
282
+ });
283
+ });
284
+ });
@@ -31,6 +31,8 @@ const idResolvers: Resolvers = {
31
31
 
32
32
  follow: ({ from, to }) => `${from}#${to}`,
33
33
  followCount: ({ userId }) => userId,
34
+
35
+ feed: ({ targetId, feedId }) => `${targetId}#${feedId}`,
34
36
  };
35
37
 
36
38
  /**
@@ -37,6 +37,7 @@ const CRITERIAS: Criterias = {
37
37
  stream: ['streamId'],
38
38
  follow: ['from', 'to'],
39
39
  followCount: ['userId', 'followerCount'],
40
+ feed: ['feedId', 'feedType'],
40
41
  };
41
42
 
42
43
  /** @hidden */
@@ -27,6 +27,8 @@ export const PAYLOAD2MODEL: Record<string, Amity.Domain> = {
27
27
 
28
28
  follows: 'follow',
29
29
  followCounts: 'followCount',
30
+
31
+ feeds: 'feed',
30
32
  };
31
33
 
32
34
  /** hidden */
@@ -58,10 +58,40 @@ export const filterByChannelMembership = (
58
58
  channelUserCacheKey,
59
59
  ])?.data;
60
60
 
61
- if (membership === 'member' && channelUser && channelUser.membership !== 'none') {
62
- return true;
61
+ if (membership === 'member') {
62
+ return channelUser && channelUser.membership !== 'none';
63
63
  }
64
64
 
65
- return membership === 'notMember' && (!channelUser || channelUser.membership === 'none');
65
+ // only membership value remainging is 'notMember'
66
+ return !channelUser || channelUser.membership === 'none';
67
+ });
68
+ };
69
+
70
+ /**
71
+ * Filter a channel collection by membership of the userId
72
+ *
73
+ * @param collection the channel collection to filter
74
+ * @param feedType to be filtered by
75
+ * @returns a filtered collection with items only matching the criteria
76
+ *
77
+ * @hidden
78
+ */
79
+ export const filterByFeedType = <T extends Amity.Post>(
80
+ collection: T[],
81
+ feedType: Amity.Feed['feedType'],
82
+ ): T[] => {
83
+ /*
84
+ * It is possible that the targetId & feedId are the same for most of the posts
85
+ * But since cache is in-memory, i've avoided memoizing, to avoid premature
86
+ * optimization. Can be revisited if performance issues arise
87
+ */
88
+ return collection.filter(({ targetId, feedId }) => {
89
+ const feed = pullFromCache<Amity.Feed>([
90
+ 'feed',
91
+ 'get',
92
+ getResolver('feed')({ targetId, feedId }),
93
+ ])?.data!;
94
+
95
+ return feed && feed.feedType === feedType;
66
96
  });
67
97
  };
@@ -1,15 +1,15 @@
1
1
  import { encode, decode } from 'js-base64';
2
2
 
3
3
  /**
4
- * Type guard to find if the a given decoded backend token uses "skip/limit" pagination style
4
+ * Type guard to find if the a given decoded backend token uses "skip" pagination style
5
5
  *
6
6
  * @param json any json object as extracted from the backend
7
7
  * @returns success boolean if the token has either "skip" or "limit" props
8
8
  *
9
9
  * @hidden
10
10
  */
11
- export const isSkipLimit = (json: Record<string, unknown>): json is Amity.SkipLimitPagination =>
12
- ['skip', 'limit'].some(prop => prop in json);
11
+ export const isSkip = (json: Record<string, unknown>): json is Amity.SkipLimitPagination =>
12
+ ['skip'].some(prop => prop in json);
13
13
 
14
14
  /**
15
15
  * Type guard to find if the a given decoded backend token uses "after/before" pagination style
@@ -120,7 +120,7 @@ export const toPage = (token?: Amity.Token): Amity.Page | undefined => {
120
120
  // don't try catch, let it throw
121
121
  const json = JSON.parse(decode(token));
122
122
 
123
- if (isSkipLimit(json)) {
123
+ if (isSkip(json)) {
124
124
  return {
125
125
  after: json.skip,
126
126
  limit: json.limit!,
@@ -133,6 +133,7 @@ export const toPage = (token?: Amity.Token): Amity.Page | undefined => {
133
133
  limit: json.last,
134
134
  } as Amity.PrevPage<number>;
135
135
  }
136
+
136
137
  if ('after' in json) {
137
138
  return {
138
139
  after: json.after,
@@ -15,6 +15,14 @@ export const sortByDisplayName: SortingFunc<{ displayName: string }> = (
15
15
  return a < b ? -1 : 1;
16
16
  };
17
17
 
18
+ /**
19
+ * Alphabetic sorting of objects having a name
20
+ */
21
+ export const sortByName: SortingFunc<{ name: string }> = ({ name: a }, { name: b }) => {
22
+ if (a === b) return 0;
23
+ return a < b ? -1 : 1;
24
+ };
25
+
18
26
  /**
19
27
  * Sorting a collection by their apparition order (oldest first)
20
28
  */
@@ -0,0 +1,66 @@
1
+ import { toPage, isSkip, isAfterBefore } from '../paging';
2
+
3
+ describe('paging', () => {
4
+ const skipToken = 'eyJza2lwIjoiMTAiLCJsaW1pdCI6IjEwIn0=';
5
+ const afterToken = 'eyJhZnRlciI6InRlc3QtdG9rZW4iLCJsaW1pdCI6Mn0=';
6
+ const beforeToken =
7
+ 'eyJiZWZvcmUiOiI2M2Y0NzFjZmI2MTQzOTExZjFkMWUwNmYiLCJsaW1pdCI6Miwic2VnbWVudCI6Nn0=';
8
+
9
+ describe('toPage', () => {
10
+ test('it should return expected response for skip paging token', () => {
11
+ const expected = { after: '10', limit: '10' };
12
+
13
+ expect(toPage(skipToken)).toStrictEqual(expected);
14
+ });
15
+
16
+ test('it should return appropriate response for "before" paging token', () => {
17
+ const expected = {
18
+ before: '63f471cfb6143911f1d1e06f',
19
+ limit: undefined,
20
+ };
21
+
22
+ expect(toPage(beforeToken)).toStrictEqual(expected);
23
+ });
24
+
25
+ test('it should return appropriate response for "after" paging token', () => {
26
+ const expected = {
27
+ after: 'test-token',
28
+ limit: undefined,
29
+ };
30
+
31
+ expect(toPage(afterToken)).toStrictEqual(expected);
32
+ });
33
+ });
34
+
35
+ describe('isSkip', () => {
36
+ test('it should return true if skip prop in object', () => {
37
+ expect(isSkip({ skip: 10 })).toBe(true);
38
+ });
39
+
40
+ test('it should return true if skip prop not in object', () => {
41
+ expect(isSkip({ limit: 10 })).toBe(false);
42
+ });
43
+ });
44
+
45
+ describe('isAfterBefore', () => {
46
+ test('it should return true if after prop in object', () => {
47
+ expect(isAfterBefore({ after: '' })).toBe(true);
48
+ });
49
+
50
+ test('it should return true if before prop in object', () => {
51
+ expect(isAfterBefore({ before: '' })).toBe(true);
52
+ });
53
+
54
+ test('it should return true if first prop in object', () => {
55
+ expect(isAfterBefore({ first: '' })).toBe(true);
56
+ });
57
+
58
+ test('it should return true if last prop in object', () => {
59
+ expect(isAfterBefore({ last: '' })).toBe(true);
60
+ });
61
+
62
+ test('it should return false if skip prop in object', () => {
63
+ expect(isAfterBefore({ skip: '' })).toBe(false);
64
+ });
65
+ });
66
+ });