@amityco/ts-sdk 0.0.1-beta.30 → 0.0.1-beta.31

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 (181) hide show
  1. package/dist/@types/core/cache.d.ts +3 -0
  2. package/dist/@types/core/cache.d.ts.map +1 -1
  3. package/dist/@types/core/errors.d.ts +2 -1
  4. package/dist/@types/core/errors.d.ts.map +1 -1
  5. package/dist/@types/core/events.d.ts +2 -0
  6. package/dist/@types/core/events.d.ts.map +1 -1
  7. package/dist/@types/core/live.d.ts +8 -4
  8. package/dist/@types/core/live.d.ts.map +1 -1
  9. package/dist/@types/core/query.d.ts +1 -1
  10. package/dist/@types/core/query.d.ts.map +1 -1
  11. package/dist/@types/domains/channel.d.ts +1 -0
  12. package/dist/@types/domains/channel.d.ts.map +1 -1
  13. package/dist/@types/domains/message.d.ts +13 -6
  14. package/dist/@types/domains/message.d.ts.map +1 -1
  15. package/dist/@types/domains/post.d.ts +10 -3
  16. package/dist/@types/domains/post.d.ts.map +1 -1
  17. package/dist/@types/domains/user.d.ts +1 -0
  18. package/dist/@types/domains/user.d.ts.map +1 -1
  19. package/dist/@types/index.d.ts +2 -2
  20. package/dist/@types/index.d.ts.map +1 -1
  21. package/dist/cache/api/isInTombstone.d.ts +18 -0
  22. package/dist/cache/api/isInTombstone.d.ts.map +1 -0
  23. package/dist/cache/api/pushToTombstone.d.ts +17 -0
  24. package/dist/cache/api/pushToTombstone.d.ts.map +1 -0
  25. package/dist/cache/utils/index.d.ts +17 -0
  26. package/dist/cache/utils/index.d.ts.map +1 -1
  27. package/dist/channel/api/queryChannelMembers.d.ts +1 -1
  28. package/dist/channel/api/queryChannelMembers.d.ts.map +1 -1
  29. package/dist/channel/api/queryChannels.d.ts +1 -1
  30. package/dist/channel/api/queryChannels.d.ts.map +1 -1
  31. package/dist/channel/api/tests/queryChannelMembers.test.d.ts +2 -0
  32. package/dist/channel/api/tests/queryChannelMembers.test.d.ts.map +1 -0
  33. package/dist/channel/api/tests/queryChannels.test.d.ts +2 -0
  34. package/dist/channel/api/tests/queryChannels.test.d.ts.map +1 -0
  35. package/dist/channel/events/index.d.ts +2 -0
  36. package/dist/channel/events/index.d.ts.map +1 -1
  37. package/dist/channel/events/onChannelMemberBanned.d.ts +17 -0
  38. package/dist/channel/events/onChannelMemberBanned.d.ts.map +1 -0
  39. package/dist/channel/events/onChannelMemberUnbanned.d.ts +17 -0
  40. package/dist/channel/events/onChannelMemberUnbanned.d.ts.map +1 -0
  41. package/dist/channel/events/tests/onChannelMemberBanned.test.d.ts +2 -0
  42. package/dist/channel/events/tests/onChannelMemberBanned.test.d.ts.map +1 -0
  43. package/dist/channel/events/tests/onChannelMemberUnbanned.test.d.ts +2 -0
  44. package/dist/channel/events/tests/onChannelMemberUnbanned.test.d.ts.map +1 -0
  45. package/dist/channel/observers/liveChannelMembers.d.ts.map +1 -1
  46. package/dist/channel/observers/liveChannels.d.ts.map +1 -1
  47. package/dist/channel/{tests/utils → utils/tests}/prepareChannelPayload.test.d.ts +0 -0
  48. package/dist/channel/utils/tests/prepareChannelPayload.test.d.ts.map +1 -0
  49. package/dist/core/events.d.ts +3 -3
  50. package/dist/core/events.d.ts.map +1 -1
  51. package/dist/core/query/filtering.d.ts +11 -0
  52. package/dist/core/query/filtering.d.ts.map +1 -1
  53. package/dist/core/query/query.d.ts.map +1 -1
  54. package/dist/index.cjs.js +963 -507
  55. package/dist/index.esm.js +946 -499
  56. package/dist/index.umd.js +4 -4
  57. package/dist/message/api/createMessage.d.ts +4 -1
  58. package/dist/message/api/createMessage.d.ts.map +1 -1
  59. package/dist/message/api/deleteMessage.d.ts.map +1 -1
  60. package/dist/message/api/getMessage.d.ts +1 -1
  61. package/dist/message/api/getMessage.d.ts.map +1 -1
  62. package/dist/message/api/getMessages.d.ts.map +1 -1
  63. package/dist/message/api/queryMessages.d.ts.map +1 -1
  64. package/dist/message/api/tests/createMessage.test.d.ts +2 -0
  65. package/dist/message/api/tests/createMessage.test.d.ts.map +1 -0
  66. package/dist/message/api/updateMessage.d.ts.map +1 -1
  67. package/dist/message/events/index.d.ts +1 -0
  68. package/dist/message/events/index.d.ts.map +1 -1
  69. package/dist/message/events/onMessageFetched.d.ts +17 -0
  70. package/dist/message/events/onMessageFetched.d.ts.map +1 -0
  71. package/dist/message/observers/index.d.ts +2 -1
  72. package/dist/message/observers/index.d.ts.map +1 -1
  73. package/dist/message/observers/liveMessage.d.ts +18 -0
  74. package/dist/message/observers/liveMessage.d.ts.map +1 -0
  75. package/dist/message/observers/liveMessages.d.ts.map +1 -1
  76. package/dist/message/observers/observeMessage.d.ts.map +1 -1
  77. package/dist/message/tests/api/getMessage.test.d.ts +2 -0
  78. package/dist/message/tests/api/getMessage.test.d.ts.map +1 -0
  79. package/dist/message/tests/observers/liveMessage.test.d.ts +2 -0
  80. package/dist/message/tests/observers/liveMessage.test.d.ts.map +1 -0
  81. package/dist/message/{observers/tests → tests/observers}/liveMessages.test.d.ts +0 -0
  82. package/dist/message/tests/observers/liveMessages.test.d.ts.map +1 -0
  83. package/dist/post/api/deletePost.d.ts.map +1 -1
  84. package/dist/post/api/getPost.d.ts.map +1 -1
  85. package/dist/post/api/updatePost.d.ts +6 -1
  86. package/dist/post/api/updatePost.d.ts.map +1 -1
  87. package/dist/user/api/getUser.d.ts +1 -1
  88. package/dist/user/api/getUser.d.ts.map +1 -1
  89. package/dist/user/api/getUsers.d.ts.map +1 -1
  90. package/dist/user/api/queryUsers.d.ts.map +1 -1
  91. package/dist/user/api/updateUser.d.ts.map +1 -1
  92. package/dist/user/events/index.d.ts +1 -0
  93. package/dist/user/events/index.d.ts.map +1 -1
  94. package/dist/user/events/onUserFetched.d.ts +17 -0
  95. package/dist/user/events/onUserFetched.d.ts.map +1 -0
  96. package/dist/user/observers/index.d.ts +1 -0
  97. package/dist/user/observers/index.d.ts.map +1 -1
  98. package/dist/user/observers/liveUser.d.ts +18 -0
  99. package/dist/user/observers/liveUser.d.ts.map +1 -0
  100. package/dist/user/observers/observeUser.d.ts.map +1 -1
  101. package/dist/user/tests/api/getUser.test.d.ts +2 -0
  102. package/dist/user/tests/api/getUser.test.d.ts.map +1 -0
  103. package/dist/user/tests/observers/liveUser.test.d.ts +2 -0
  104. package/dist/user/tests/observers/liveUser.test.d.ts.map +1 -0
  105. package/dist/utils/constants.d.ts +3 -0
  106. package/dist/utils/constants.d.ts.map +1 -1
  107. package/dist/utils/tests/dummy/channel.d.ts +15 -0
  108. package/dist/utils/tests/dummy/channel.d.ts.map +1 -1
  109. package/dist/utils/tests/dummy/message.d.ts +12 -1
  110. package/dist/utils/tests/dummy/message.d.ts.map +1 -1
  111. package/dist/utils/tests/dummy/user.d.ts +10 -0
  112. package/dist/utils/tests/dummy/user.d.ts.map +1 -1
  113. package/package.json +1 -1
  114. package/src/@types/core/cache.ts +4 -0
  115. package/src/@types/core/errors.ts +1 -0
  116. package/src/@types/core/events.ts +2 -0
  117. package/src/@types/core/live.ts +10 -4
  118. package/src/@types/core/query.ts +1 -1
  119. package/src/@types/domains/channel.ts +6 -0
  120. package/src/@types/domains/message.ts +13 -10
  121. package/src/@types/domains/post.ts +10 -3
  122. package/src/@types/domains/user.ts +8 -0
  123. package/src/@types/index.ts +2 -2
  124. package/src/cache/api/index.ts +2 -1
  125. package/src/cache/api/isInTombstone.ts +41 -0
  126. package/src/cache/api/pushToTombstone.ts +33 -0
  127. package/src/cache/utils/index.ts +22 -0
  128. package/src/channel/api/queryChannelMembers.ts +53 -5
  129. package/src/channel/api/queryChannels.ts +31 -4
  130. package/src/channel/api/tests/queryChannelMembers.test.ts +115 -0
  131. package/src/channel/api/tests/queryChannels.test.ts +101 -0
  132. package/src/channel/events/index.ts +5 -0
  133. package/src/channel/events/onChannelMemberBanned.ts +36 -0
  134. package/src/channel/events/onChannelMemberUnbanned.ts +36 -0
  135. package/src/channel/events/tests/onChannelMemberBanned.test.ts +58 -0
  136. package/src/channel/events/tests/onChannelMemberUnbanned.test.ts +58 -0
  137. package/src/channel/observers/liveChannelMembers.ts +28 -4
  138. package/src/channel/observers/liveChannels.ts +7 -1
  139. package/src/channel/observers/tests/liveChannelMembers.test.ts +53 -11
  140. package/src/channel/observers/tests/liveChannels.test.ts +50 -20
  141. package/src/channel/{tests/utils → utils/tests}/prepareChannelPayload.test.ts +1 -1
  142. package/src/core/events.ts +1 -0
  143. package/src/core/query/filtering.ts +49 -0
  144. package/src/core/query/query.ts +9 -6
  145. package/src/core/tests/query/query.test.ts +12 -2
  146. package/src/message/api/createMessage.ts +15 -9
  147. package/src/message/api/deleteMessage.ts +2 -1
  148. package/src/message/api/getMessage.ts +32 -4
  149. package/src/message/api/getMessages.ts +6 -1
  150. package/src/message/api/queryMessages.ts +3 -0
  151. package/src/message/api/tests/createMessage.test.ts +76 -0
  152. package/src/message/api/updateMessage.ts +2 -0
  153. package/src/message/events/index.ts +1 -0
  154. package/src/message/events/onMessageFetched.ts +30 -0
  155. package/src/message/observers/index.ts +2 -1
  156. package/src/message/observers/liveMessage.ts +100 -0
  157. package/src/message/observers/liveMessages.ts +3 -1
  158. package/src/message/observers/observeMessage.ts +4 -0
  159. package/src/message/tests/api/getMessage.test.ts +96 -0
  160. package/src/message/tests/observers/liveMessage.test.ts +248 -0
  161. package/src/message/{observers/tests → tests/observers}/liveMessages.test.ts +29 -3
  162. package/src/post/api/deletePost.ts +4 -3
  163. package/src/post/api/getPost.ts +8 -4
  164. package/src/post/api/updatePost.ts +6 -1
  165. package/src/user/api/getUser.ts +34 -6
  166. package/src/user/api/getUsers.ts +3 -0
  167. package/src/user/api/queryUsers.ts +3 -0
  168. package/src/user/api/updateUser.ts +3 -0
  169. package/src/user/events/index.ts +1 -0
  170. package/src/user/events/onUserFetched.ts +19 -0
  171. package/src/user/observers/index.ts +1 -0
  172. package/src/user/observers/liveUser.ts +89 -0
  173. package/src/user/observers/observeUser.ts +2 -0
  174. package/src/user/tests/api/getUser.test.ts +112 -0
  175. package/src/user/tests/observers/liveUser.test.ts +224 -0
  176. package/src/utils/constants.ts +5 -0
  177. package/src/utils/tests/dummy/channel.ts +43 -0
  178. package/src/utils/tests/dummy/message.ts +16 -2
  179. package/src/utils/tests/dummy/user.ts +11 -0
  180. package/dist/channel/tests/utils/prepareChannelPayload.test.d.ts.map +0 -1
  181. package/dist/message/observers/tests/liveMessages.test.d.ts.map +0 -1
@@ -1,8 +1,9 @@
1
1
  import { getActiveClient } from '~/client/api';
2
2
 
3
- import { getCommunity } from '~/community/api';
4
- import { dropFromCache, upsertInCache } from '~/cache/api';
5
3
  import { fireEvent } from '~/core/events';
4
+ import { upsertInCache } from '~/cache/api';
5
+ import { getCommunity } from '~/community/api';
6
+ import { pushToTombstone } from '~/cache/api/pushToTombstone';
6
7
 
7
8
  import { getPost } from './getPost';
8
9
 
@@ -53,7 +54,7 @@ export const deletePost = async (
53
54
 
54
55
  if (permanent) {
55
56
  setTimeout(() => {
56
- dropFromCache(['post', 'get', postId], true);
57
+ pushToTombstone('post', postId);
57
58
  }, 0);
58
59
  } else {
59
60
  upsertInCache(['post', 'get', postId], { isDeleted: true });
@@ -1,7 +1,10 @@
1
1
  import { getActiveClient } from '~/client/api';
2
2
 
3
- import { pullFromCache, dropFromCache } from '~/cache/api';
3
+ import { pullFromCache } from '~/cache/api';
4
4
  import { ingestInCache } from '~/cache/api/ingestInCache';
5
+ import { isInTombstone } from '~/cache/api/isInTombstone';
6
+ import { checkIfShouldGoesToTombstone } from '~/cache/utils';
7
+ import { pushToTombstone } from '~/cache/api/pushToTombstone';
5
8
 
6
9
  /**
7
10
  * ```js
@@ -21,6 +24,8 @@ export const getPost = async (postId: Amity.Post['postId']): Promise<Amity.Cache
21
24
  const client = getActiveClient();
22
25
  client.log('post/getPost', postId);
23
26
 
27
+ isInTombstone('post', postId);
28
+
24
29
  let data: Amity.PostPayload;
25
30
 
26
31
  try {
@@ -28,11 +33,10 @@ export const getPost = async (postId: Amity.Post['postId']): Promise<Amity.Cache
28
33
  const response = await client.http.get<Amity.PostPayload>(
29
34
  `/api/v3/posts/${encodeURIComponent(postId)}`,
30
35
  );
31
-
32
36
  data = response.data;
33
37
  } catch (error) {
34
- if ((error as Amity.ErrorResponse)?.code === Amity.ServerError.ITEM_NOT_FOUND) {
35
- dropFromCache(['post', 'get', postId], true);
38
+ if (checkIfShouldGoesToTombstone((error as Amity.ErrorResponse)?.code)) {
39
+ pushToTombstone('post', postId);
36
40
  }
37
41
 
38
42
  throw error;
@@ -22,7 +22,12 @@ import { fireEvent } from '~/core/events';
22
22
  */
23
23
  export const updatePost = async (
24
24
  postId: Amity.Post['postId'],
25
- patch: Patch<Amity.Post, 'data' | 'metadata' | 'mentionees' | 'tags'>,
25
+ patch: Patch<Amity.Post, 'data' | 'metadata' | 'mentionees' | 'tags'> & {
26
+ attachments?: {
27
+ type: 'image' | 'file' | 'video';
28
+ fileId: Amity.File['fileId'];
29
+ }[];
30
+ },
26
31
  ): Promise<Amity.Cached<Amity.Post>> => {
27
32
  const client = getActiveClient();
28
33
  client.log('user/updatePost', patch);
@@ -1,7 +1,12 @@
1
1
  import { getActiveClient } from '~/client/api';
2
2
 
3
- import { pullFromCache } from '~/cache/api';
3
+ import { pullFromCache, dropFromCache } from '~/cache/api';
4
4
  import { ingestInCache } from '~/cache/api/ingestInCache';
5
+ import { fireEvent } from '~/core/events';
6
+
7
+ import { isInTombstone } from '~/cache/api/isInTombstone';
8
+ import { checkIfShouldGoesToTombstone } from '~/cache/utils';
9
+ import { pushToTombstone } from '~/cache/api/pushToTombstone';
5
10
 
6
11
  /**
7
12
  * ```js
@@ -17,20 +22,43 @@ import { ingestInCache } from '~/cache/api/ingestInCache';
17
22
  * @category User API
18
23
  * @async
19
24
  */
20
- export const getUser = async (userId: Amity.User['userId']): Promise<Amity.Cached<Amity.User>> => {
25
+ export const getUser = async (
26
+ userId: Amity.User['userId'],
27
+ isLive = false,
28
+ ): Promise<Amity.Cached<Amity.User>> => {
21
29
  const client = getActiveClient();
22
30
  client.log('user/getUser', userId);
23
31
 
24
- // API-FIX: endpoint should not be /list, parameters should be querystring.
25
- const { data } = await client.http.get<Amity.UserPayload>(
26
- `/api/v3/users/${encodeURIComponent(userId)}`,
27
- );
32
+ if (!isLive) {
33
+ console.log('getUser will be deprecated on 25th November 2022, please use liveUser instead.');
34
+ }
35
+
36
+ isInTombstone('user', userId);
37
+
38
+ let data: Amity.UserPayload;
39
+
40
+ try {
41
+ // API-FIX: endpoint should not be /list, parameters should be querystring.
42
+ const response = await client.http.get<Amity.UserPayload>(
43
+ `/api/v3/users/${encodeURIComponent(userId)}`,
44
+ );
45
+
46
+ data = response.data;
47
+ } catch (error) {
48
+ if (checkIfShouldGoesToTombstone((error as Amity.ErrorResponse)?.code)) {
49
+ pushToTombstone('user', userId);
50
+ }
51
+
52
+ throw error;
53
+ }
28
54
 
29
55
  const cachedAt = client.cache && Date.now();
30
56
  if (client.cache) ingestInCache(data, { cachedAt });
31
57
 
32
58
  const { users } = data;
33
59
 
60
+ fireEvent('user.fetched', data);
61
+
34
62
  return {
35
63
  data: users.find(user => user.userId === userId)!,
36
64
  cachedAt,
@@ -2,6 +2,7 @@ import { getActiveClient } from '~/client/api';
2
2
 
3
3
  import { pullFromCache } from '~/cache/api';
4
4
  import { ingestInCache } from '~/cache/api/ingestInCache';
5
+ import { fireEvent } from '~/core/events';
5
6
 
6
7
  /**
7
8
  * ```js
@@ -33,6 +34,8 @@ export const getUsers = async (
33
34
  const cachedAt = client.cache && Date.now();
34
35
  if (client.cache) ingestInCache(data, { cachedAt });
35
36
 
37
+ fireEvent('user.fetched', data);
38
+
36
39
  return {
37
40
  data: data.users,
38
41
  cachedAt,
@@ -5,6 +5,7 @@ import { toPage, toToken } from '~/core/query';
5
5
  import { pullFromCache, pushToCache } from '~/cache/api';
6
6
  import { ingestInCache } from '~/cache/api/ingestInCache';
7
7
  import { getResolver } from '~/core/model';
8
+ import { fireEvent } from '~/core/events';
8
9
 
9
10
  /**
10
11
  * ```js
@@ -60,6 +61,8 @@ export const queryUsers = async (
60
61
  pushToCache(cacheKey, { users: users.map(getResolver('user')), paging });
61
62
  }
62
63
 
64
+ fireEvent('user.fetched', data);
65
+
63
66
  const prevPage = toPage(paging.previous);
64
67
  const nextPage = toPage(paging.next);
65
68
 
@@ -1,5 +1,6 @@
1
1
  import { getActiveClient } from '~/client/api';
2
2
 
3
+ import { fireEvent } from '~/core/events';
3
4
  import { ingestInCache } from '~/cache/api/ingestInCache';
4
5
 
5
6
  /**
@@ -45,6 +46,8 @@ export const updateUser = async (
45
46
 
46
47
  const { users } = data;
47
48
 
49
+ fireEvent('user.updated', data);
50
+
48
51
  return {
49
52
  data: users.find(user => user.userId === userId)!,
50
53
  cachedAt,
@@ -1,3 +1,4 @@
1
+ export * from './onUserFetched';
1
2
  export * from './onUserUpdated';
2
3
  export * from './onUserDeleted';
3
4
 
@@ -0,0 +1,19 @@
1
+ import { createUserEventSubscriber } from './utils';
2
+
3
+ /**
4
+ * ```js
5
+ * import { onUserFetched } from '@amityco/ts-sdk'
6
+ * const dispose = onUserFetched(user => {
7
+ * // ...
8
+ * })
9
+ * ```
10
+ *
11
+ * Fired when a {@link Amity.User} has been fetched
12
+ *
13
+ * @param callback The function to call when the event was fired
14
+ * @returns an {@link Amity.Unsubscriber} function to stop listening
15
+ *
16
+ * @category User Events
17
+ */
18
+ export const onUserFetched = (callback: Amity.Listener<Amity.User>) =>
19
+ createUserEventSubscriber('user.fetched', callback);
@@ -1 +1,2 @@
1
+ export { liveUser } from './liveUser';
1
2
  export { observeUser } from './observeUser';
@@ -0,0 +1,89 @@
1
+ import { getActiveClient } from '~/client/api';
2
+ import { createQuery, runQuery } from '~/core/query';
3
+
4
+ import { LIVE_OBJECT_ENABLE_CACHE_MESSAGE } from '~/utils/constants';
5
+
6
+ import {
7
+ onUserFetched,
8
+ onUserDeleted,
9
+ onUserUpdated,
10
+ onUserFlagCleared,
11
+ onUserFlagged,
12
+ onUserUnflagged,
13
+ } from '../events';
14
+ import { getUser } from '../api';
15
+
16
+ /**
17
+ * ```js
18
+ * import { liveUser } from '@amityco/ts-sdk'
19
+ *
20
+ * let users = []
21
+ * const unsub = liveUser(userId, response => merge(users, response.data))
22
+ * ```
23
+ *
24
+ * Observe all mutation on a given {@link Amity.User}
25
+ *
26
+ * @param userId the ID of the user to observe
27
+ * @param callback the function to call when new data are available
28
+ * @returns An {@link Amity.Unsubscriber} function to run when willing to stop observing the users
29
+ *
30
+ * @category Message Live Object
31
+ */
32
+ export const liveUser = (
33
+ userId: Amity.User['userId'],
34
+ callback: Amity.LiveObjectCallback<Amity.User>,
35
+ ): Amity.Unsubscriber => {
36
+ const { log, cache } = getActiveClient();
37
+ let user: Amity.User;
38
+
39
+ if (!cache) {
40
+ console.log(LIVE_OBJECT_ENABLE_CACHE_MESSAGE);
41
+ }
42
+
43
+ const timestamp = Date.now();
44
+ log(`liveUser(tmpid: ${timestamp}) > listen`);
45
+
46
+ const disposers: Amity.Unsubscriber[] = [];
47
+
48
+ const dispatcher = (data: Amity.LiveObject<Amity.User>) => {
49
+ // we check updatedAt to see if the current snapshot is new so
50
+ // we won't send old data back, only fresh new data
51
+ if (user && data.data?.updatedAt === user?.updatedAt && data.origin !== 'server') return;
52
+
53
+ callback(data);
54
+ user = data.data;
55
+ };
56
+
57
+ const realtimeRouter = (user: Amity.User, action: Amity.UserActionType) => {
58
+ dispatcher({ loading: false, data: user, origin: 'event' });
59
+ };
60
+
61
+ const onFetch = () => {
62
+ const query = createQuery(getUser, userId, true);
63
+
64
+ runQuery(query, ({ loading, data, error, origin }) => {
65
+ dispatcher({ loading, data, origin, error });
66
+
67
+ if (error) {
68
+ // Terminate observe functionality on error
69
+ disposers.forEach(fn => fn());
70
+ }
71
+ });
72
+ };
73
+
74
+ disposers.push(
75
+ onUserFetched(user => realtimeRouter(user, 'onFetch')),
76
+ onUserUpdated(user => realtimeRouter(user, 'onUpdate')),
77
+ onUserDeleted(user => realtimeRouter(user, 'onDelete')),
78
+ onUserFlagged(user => realtimeRouter(user, 'onFlagged')),
79
+ onUserUnflagged(user => realtimeRouter(user, 'onUnflagged')),
80
+ onUserFlagCleared(user => realtimeRouter(user, 'onFlagCleared')),
81
+ );
82
+
83
+ onFetch();
84
+
85
+ return () => {
86
+ log(`liveUser(tmpid: ${timestamp}) > dispose`);
87
+ disposers.forEach(fn => fn());
88
+ };
89
+ };
@@ -37,6 +37,8 @@ export const observeUser = <
37
37
  const timestamp = Date.now();
38
38
  log(`observeUser(tmpid: ${timestamp}) > listen`);
39
39
 
40
+ console.log('observeUser will be deprecated on 25th November 2022, please use liveUser instead.');
41
+
40
42
  // wrapper function to make sure
41
43
  const router = (result: Amity.Snapshot<Amity.User>, action: Events[number]) => {
42
44
  // filter function
@@ -0,0 +1,112 @@
1
+ import { ASCApiError } from '~/core/errors';
2
+ import { disableCache, enableCache, pushToCache } from '~/cache/api';
3
+ import { client, user11 } from '~/utils/tests';
4
+
5
+ import { pushToTombstone } from '~/cache/api/pushToTombstone';
6
+ import { isInTombstone } from '~/cache/api/isInTombstone';
7
+
8
+ import { getUser } from '../../api/getUser';
9
+
10
+ const user = user11;
11
+ const getResolvedUserValue = () => ({
12
+ data: {
13
+ users: [user],
14
+ files: [],
15
+ },
16
+ });
17
+
18
+ describe('getUser', () => {
19
+ test('should return user data', async () => {
20
+ client.http.get = jest.fn().mockResolvedValue(getResolvedUserValue());
21
+
22
+ const { data } = await getUser(user.userId);
23
+
24
+ expect(data).toStrictEqual(user);
25
+ });
26
+
27
+ test('it should update cache after fetch from server', async () => {
28
+ enableCache();
29
+ client.http.get = jest.fn().mockResolvedValue(getResolvedUserValue());
30
+ await getUser(user.userId);
31
+
32
+ expect(getUser.locally(user.userId)).toBeDefined();
33
+
34
+ disableCache();
35
+ });
36
+
37
+ test('should return an error', async () => {
38
+ client.http.get = jest.fn().mockRejectedValueOnce(new Error('error'));
39
+
40
+ await expect(getUser(user.userId)).rejects.toThrow('error');
41
+ });
42
+
43
+ test('should return an error if user in cache but api throws not found', async () => {
44
+ enableCache();
45
+ pushToCache(['user', 'get', user.userId], user, { cachedAt: Date.now() });
46
+
47
+ client.http.get = jest
48
+
49
+ .fn()
50
+ .mockRejectedValueOnce(
51
+ new ASCApiError('not found!', Amity.ServerError.ITEM_NOT_FOUND, Amity.ErrorLevel.ERROR),
52
+ );
53
+
54
+ await expect(getUser(user.userId)).rejects.toThrow();
55
+
56
+ expect(getUser.locally(user.userId)).toBeUndefined();
57
+ disableCache();
58
+ });
59
+ });
60
+
61
+ describe('getUser locally', () => {
62
+ test('should return data from cache', () => {
63
+ enableCache();
64
+
65
+ const cachedAt = Date.now();
66
+
67
+ pushToCache(['user', 'get', user.userId], user, { cachedAt });
68
+
69
+ const data = getUser.locally(user.userId);
70
+
71
+ expect(data).toBeDefined();
72
+ expect(data?.data).toEqual(user);
73
+ expect(data?.cachedAt).toEqual(cachedAt);
74
+
75
+ disableCache();
76
+ });
77
+
78
+ test('should return undefined if user not in cache', () => {
79
+ expect(getUser.locally('non-existent-user')).toBeUndefined();
80
+ });
81
+
82
+ test('should return undefined if cache not enabled', () => {
83
+ expect(getUser.locally(user.userId)).toBeUndefined();
84
+ });
85
+ });
86
+
87
+ describe('getUser tombostone', () => {
88
+ test('it should add user to tombostone if 404', async () => {
89
+ enableCache();
90
+ client.http.get = jest
91
+ .fn()
92
+ .mockRejectedValueOnce(
93
+ new ASCApiError('not found!', Amity.ServerError.ITEM_NOT_FOUND, Amity.ErrorLevel.ERROR),
94
+ );
95
+
96
+ await expect(getUser(user.userId)).rejects.toThrow();
97
+ expect(() => {
98
+ isInTombstone('user', user.userId);
99
+ }).toThrow();
100
+
101
+ disableCache();
102
+ });
103
+
104
+ test('it should throw error if user not found in tombostone', async () => {
105
+ enableCache();
106
+
107
+ pushToTombstone('user', user.userId);
108
+ await expect(getUser(user.userId)).rejects.toThrow();
109
+
110
+ disableCache();
111
+ });
112
+ });
@@ -0,0 +1,224 @@
1
+ import { ASCApiError } from '~/core/errors';
2
+ import { disableCache, enableCache } from '~/cache/api';
3
+ import { LIVE_OBJECT_ENABLE_CACHE_MESSAGE } from '~/utils/constants';
4
+
5
+ import { liveUser } from '../../observers/liveUser';
6
+
7
+ import {
8
+ pause,
9
+ client,
10
+ connectClient,
11
+ disconnectClient,
12
+ userQueryResponse,
13
+ user11 as defaultUser,
14
+ } from '~/utils/tests';
15
+
16
+ const getSnapshot = () => {
17
+ return {
18
+ loading: true,
19
+ error: undefined as any,
20
+ data: undefined as undefined | Amity.User,
21
+ };
22
+ };
23
+
24
+ describe('liveUser', () => {
25
+ beforeAll(async () => {
26
+ await connectClient();
27
+ });
28
+
29
+ afterAll(async () => {
30
+ await disconnectClient();
31
+ });
32
+
33
+ beforeEach(async () => {
34
+ enableCache();
35
+ });
36
+
37
+ afterEach(disableCache);
38
+
39
+ test('it should show user if cache not enabled', () => {
40
+ /*
41
+ * NOTE: at the time of writing this test cache is diabled by default, but
42
+ * there is a proposal to enable cache by default. So I'm disabling cache
43
+ * here to ensure cache is disabled
44
+ */
45
+ disableCache();
46
+
47
+ const callback = jest.fn();
48
+ jest.spyOn(global.console, 'log');
49
+ client.http.get = jest.fn();
50
+
51
+ // call liveUser check if mocked console get's called with the correct
52
+ // user
53
+ client.use();
54
+ liveUser(defaultUser.userId, callback);
55
+
56
+ expect(console.log).toBeCalledWith(LIVE_OBJECT_ENABLE_CACHE_MESSAGE);
57
+ });
58
+
59
+ test('it should return user live object', async () => {
60
+ const snapshot = getSnapshot();
61
+
62
+ // mock response of queryUsers and validate
63
+ const callback = jest.fn();
64
+ client.http.get = jest.fn().mockResolvedValue(userQueryResponse);
65
+
66
+ liveUser(defaultUser.userId, callback);
67
+
68
+ expect(callback).toHaveBeenCalledTimes(1);
69
+
70
+ // check if cache data returned (should be empty)
71
+ expect(callback).toHaveBeenCalledWith(expect.objectContaining(snapshot));
72
+
73
+ await pause();
74
+
75
+ expect(callback).toHaveBeenCalledTimes(2);
76
+
77
+ snapshot.loading = false;
78
+ snapshot.data = defaultUser;
79
+
80
+ expect(callback).toHaveBeenCalledWith(expect.objectContaining(snapshot));
81
+ });
82
+
83
+ test('it should return error on api failure', async () => {
84
+ const snapshot = getSnapshot();
85
+ const error = new ASCApiError(
86
+ 'server error!',
87
+ Amity.ServerError.ITEM_NOT_FOUND,
88
+ Amity.ErrorLevel.ERROR,
89
+ );
90
+
91
+ // mock response of queryUsers and validate
92
+ const callback = jest.fn();
93
+ client.http.get = jest.fn().mockRejectedValue(error);
94
+
95
+ liveUser(defaultUser.userId, callback);
96
+
97
+ expect(callback).toHaveBeenCalledTimes(1);
98
+
99
+ // check if cache data returned (should be empty)
100
+ expect(callback).toHaveBeenCalledWith(expect.objectContaining(snapshot));
101
+
102
+ await pause();
103
+
104
+ expect(callback).toHaveBeenCalledTimes(2);
105
+
106
+ snapshot.error = error;
107
+ snapshot.loading = false;
108
+ expect(callback).toHaveBeenLastCalledWith(expect.objectContaining(snapshot));
109
+ });
110
+
111
+ test('it should terminate updates on api failure', async () => {
112
+ const snapshot = getSnapshot();
113
+ const error = new ASCApiError(
114
+ 'server error!',
115
+ Amity.ServerError.ITEM_NOT_FOUND,
116
+ Amity.ErrorLevel.ERROR,
117
+ );
118
+
119
+ // mock response of queryUsers and validate
120
+ const callback = jest.fn();
121
+ client.http.get = jest.fn().mockRejectedValue(error);
122
+
123
+ liveUser(defaultUser.userId, callback);
124
+
125
+ expect(callback).toHaveBeenCalledTimes(1);
126
+
127
+ // check if cache data returned (should be empty)
128
+ expect(callback).toHaveBeenCalledWith(expect.objectContaining(snapshot));
129
+
130
+ await pause();
131
+
132
+ expect(callback).toHaveBeenCalledTimes(2);
133
+
134
+ snapshot.error = error;
135
+ snapshot.loading = false;
136
+ expect(callback).toHaveBeenLastCalledWith(expect.objectContaining(snapshot));
137
+
138
+ const updatedAt = new Date(defaultUser?.updatedAt ?? new Date());
139
+ updatedAt.setDate(updatedAt.getDate() + 7);
140
+
141
+ const updatedUser = {
142
+ ...defaultUser,
143
+ updatedAt: updatedAt.toISOString(),
144
+ };
145
+ client.emitter.emit('user.updated', { users: [updatedUser], files: [] });
146
+
147
+ delete snapshot.error;
148
+ snapshot.loading = false;
149
+ snapshot.data = updatedUser;
150
+ expect(callback).not.toHaveBeenNthCalledWith(3, expect.objectContaining(snapshot));
151
+ });
152
+
153
+ test('it should not return updates on obtain once!', async () => {
154
+ const snapshot = getSnapshot();
155
+
156
+ // mock response of queryUsers and validate
157
+ const callback = jest.fn();
158
+ client.http.get = jest.fn().mockResolvedValue(userQueryResponse);
159
+
160
+ const unsub = liveUser(defaultUser.userId, callback);
161
+
162
+ expect(callback).toHaveBeenCalledTimes(1);
163
+
164
+ // check if cache data returned (should be empty)
165
+ expect(callback).toHaveBeenCalledWith(expect.objectContaining(snapshot));
166
+
167
+ await pause();
168
+
169
+ expect(callback).toHaveBeenCalledTimes(2);
170
+
171
+ unsub();
172
+
173
+ const updatedAt = new Date(defaultUser?.updatedAt ?? new Date());
174
+ updatedAt.setDate(updatedAt.getDate() + 7);
175
+
176
+ const updatedUser = {
177
+ ...defaultUser,
178
+ updatedAt: updatedAt.toISOString(),
179
+ };
180
+ client.emitter.emit('user.updated', { users: [updatedUser], files: [] });
181
+
182
+ delete snapshot.error;
183
+ snapshot.loading = false;
184
+ snapshot.data = updatedUser;
185
+ expect(callback).not.toHaveBeenNthCalledWith(3, expect.objectContaining(snapshot));
186
+ });
187
+
188
+ const events: [string, keyof Amity.Events][] = [
189
+ ['it should return updated user on onFetch', 'user.fetched'],
190
+ ['it should return updated user on onUpdate', 'user.updated'],
191
+ ['it should return updated user on onDelete', 'user.deleted'],
192
+ ['it should return updated user on onFlagged', 'user.flagged'],
193
+ ['it should return updated user on onUnflagged', 'user.unflagged'],
194
+ ['it should return updated user on onFlagCleared', 'user.flagCleared'],
195
+ ];
196
+
197
+ test.each(events)('%s', async (test, event) => {
198
+ const snapshot = getSnapshot();
199
+
200
+ // mock response of queryUsers and validate
201
+ const callback = jest.fn();
202
+ client.http.get = jest.fn().mockResolvedValue(userQueryResponse);
203
+
204
+ liveUser(defaultUser.userId, callback);
205
+
206
+ await pause();
207
+
208
+ expect(callback).toHaveBeenCalledTimes(2);
209
+
210
+ const updatedAt = new Date(defaultUser?.updatedAt ?? new Date());
211
+ updatedAt.setDate(updatedAt.getDate() + 7);
212
+
213
+ const updatedUser = {
214
+ ...defaultUser,
215
+ updatedAt: updatedAt.toISOString(),
216
+ };
217
+ client.emitter.emit(event, { users: [updatedUser], files: [] });
218
+
219
+ delete snapshot.error;
220
+ snapshot.loading = false;
221
+ snapshot.data = updatedUser;
222
+ expect(callback).toHaveBeenNthCalledWith(3, expect.objectContaining(snapshot));
223
+ });
224
+ });
@@ -1,3 +1,8 @@
1
1
  export const COLLECTION_DEFAULT_PAGINATION_LIMIT = 5;
2
2
  export const COLLECTION_DEFAULT_CACHING_POLICY: Amity.QueryPolicy = 'cache_then_server';
3
3
  export const ENABLE_CACHE_MESSAGE = 'For using Live Collection feature you need to enable Cache!';
4
+ export const LIVE_OBJECT_ENABLE_CACHE_MESSAGE =
5
+ 'For using Live Object feature you need to enable Cache!';
6
+ export const UNSYNCED_OBJECT_CACHED_AT_MESSAGE =
7
+ 'Observing unsynced object is not supported by Live Object.';
8
+ export const UNSYNCED_OBJECT_CACHED_AT_VALUE = -5;