@amityco/ts-sdk 6.12.2-d8fe0d5.0 → 6.12.2-e1d8270.0

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 (46) hide show
  1. package/dist/@types/core/live.d.ts +2 -0
  2. package/dist/@types/core/live.d.ts.map +1 -1
  3. package/dist/@types/core/paging.d.ts +8 -0
  4. package/dist/@types/core/paging.d.ts.map +1 -1
  5. package/dist/@types/domains/community.d.ts +1 -1
  6. package/dist/@types/domains/community.d.ts.map +1 -1
  7. package/dist/@types/domains/message.d.ts +1 -0
  8. package/dist/@types/domains/message.d.ts.map +1 -1
  9. package/dist/core/liveCollection/LiveCollectionController.d.ts +24 -0
  10. package/dist/core/liveCollection/LiveCollectionController.d.ts.map +1 -0
  11. package/dist/core/liveCollection/PaginationController.d.ts +15 -0
  12. package/dist/core/liveCollection/PaginationController.d.ts.map +1 -0
  13. package/dist/core/liveCollection/QueryStreamController.d.ts +8 -0
  14. package/dist/core/liveCollection/QueryStreamController.d.ts.map +1 -0
  15. package/dist/core/liveCollection/hash.d.ts +2 -0
  16. package/dist/core/liveCollection/hash.d.ts.map +1 -0
  17. package/dist/index.cjs.js +353 -163
  18. package/dist/index.esm.js +352 -163
  19. package/dist/index.umd.js +3 -3
  20. package/dist/messageRepository/observers/getMessages.d.ts.map +1 -1
  21. package/dist/messageRepository/observers/liveCollection/MessageLiveCollectionController.d.ts +14 -0
  22. package/dist/messageRepository/observers/liveCollection/MessageLiveCollectionController.d.ts.map +1 -0
  23. package/dist/messageRepository/observers/liveCollection/MessagePaginationController.d.ts +13 -0
  24. package/dist/messageRepository/observers/liveCollection/MessagePaginationController.d.ts.map +1 -0
  25. package/dist/messageRepository/observers/liveCollection/MessageQueryStreamController.d.ts +13 -0
  26. package/dist/messageRepository/observers/liveCollection/MessageQueryStreamController.d.ts.map +1 -0
  27. package/dist/messageRepository/utils/prepareMessagePayload.d.ts +1 -1
  28. package/dist/messageRepository/utils/prepareMessagePayload.d.ts.map +1 -1
  29. package/dist/utils/isEqual.d.ts.map +1 -1
  30. package/package.json +3 -1
  31. package/src/@types/core/live.ts +2 -0
  32. package/src/@types/core/paging.ts +8 -2
  33. package/src/@types/domains/community.ts +1 -1
  34. package/src/@types/domains/message.ts +1 -0
  35. package/src/communityRepository/communityMembership/observers/getMembers.ts +2 -2
  36. package/src/core/liveCollection/LiveCollectionController.ts +105 -0
  37. package/src/core/liveCollection/PaginationController.ts +62 -0
  38. package/src/core/liveCollection/QueryStreamController.ts +23 -0
  39. package/src/core/liveCollection/hash.ts +16 -0
  40. package/src/messageRepository/observers/getMessages.ts +7 -107
  41. package/src/messageRepository/observers/liveCollection/MessageLiveCollectionController.ts +162 -0
  42. package/src/messageRepository/observers/liveCollection/MessagePaginationController.ts +42 -0
  43. package/src/messageRepository/observers/liveCollection/MessageQueryStreamController.ts +129 -0
  44. package/src/messageRepository/observers/tests/getMessages.test.ts +35 -27
  45. package/src/messageRepository/utils/prepareMessagePayload.ts +3 -2
  46. package/src/utils/isEqual.ts +13 -0
@@ -0,0 +1,162 @@
1
+ /* eslint-disable no-use-before-define */
2
+
3
+ import hash from 'object-hash';
4
+ import { pullFromCache, pushToCache } from '~/cache/api';
5
+ import { MessagePaginationController } from './MessagePaginationController';
6
+ import { MessageQueryStreamController } from './MessageQueryStreamController';
7
+ import { uuid } from '~/core/uuid';
8
+ import {
9
+ onMessageCreated,
10
+ onMessageDeleted,
11
+ onMessageFlagCleared,
12
+ onMessageFlagged,
13
+ onMessageReactionAdded,
14
+ onMessageReactionRemoved,
15
+ onMessageUnflagged,
16
+ onMessageUpdated,
17
+ } from '~/messageRepository/events';
18
+ import { convertEventPayload } from '~/utils/event';
19
+ import { LiveCollectionController } from '~/core/liveCollection/LiveCollectionController';
20
+ import { onMessageMarked, onMessageMarkerFetched } from '~/marker/events';
21
+ import { filterByPropEquality } from '~/core/query';
22
+
23
+ export class MessageLiveCollectionController extends LiveCollectionController<
24
+ MessagePaginationController,
25
+ Amity.LiveCollectionCallback<Amity.Message>,
26
+ Amity.Message
27
+ > {
28
+ private queryStreamController: MessageQueryStreamController;
29
+
30
+ private query: Amity.MessagesLiveCollection;
31
+
32
+ constructor(
33
+ query: Amity.MessagesLiveCollection,
34
+ callback: Amity.LiveCollectionCallback<Amity.Message>,
35
+ ) {
36
+ const queryStreamId = hash(query);
37
+ const cacheKey = ['message', 'collection', queryStreamId];
38
+
39
+ const paginationController = new MessagePaginationController(query, cacheKey);
40
+
41
+ super(paginationController, queryStreamId, cacheKey, callback);
42
+
43
+ this.query = query;
44
+ this.queryStreamController = new MessageQueryStreamController(
45
+ this.query,
46
+ this.cacheKey,
47
+ this.notifyChange.bind(this),
48
+ );
49
+
50
+ this.callback = callback.bind(this);
51
+ this.loadPage(true);
52
+ }
53
+
54
+ protected setup() {
55
+ const collection = pullFromCache<Amity.MessageLiveCollectionCache>(this.cacheKey)?.data;
56
+ if (!collection) {
57
+ pushToCache(this.cacheKey, {
58
+ data: [],
59
+ params: {},
60
+ });
61
+ }
62
+ }
63
+
64
+ protected async persistModel(queryPayload: Amity.MessagePayload & Amity.Pagination) {
65
+ await this.queryStreamController.saveToMainDB(queryPayload);
66
+ }
67
+
68
+ protected persistQueryStream(
69
+ queryPayload: Amity.MessagePayload & Amity.Pagination,
70
+ direction: Amity.LoadingDirection,
71
+ refresh?: boolean,
72
+ ) {
73
+ this.queryStreamController.appendToQueryStream(queryPayload, direction, refresh);
74
+ }
75
+
76
+ startSubscription() {
77
+ return this.queryStreamController.subscribeRTE([
78
+ { fn: onMessageCreated, action: 'onCreate' },
79
+ { fn: onMessageDeleted, action: 'onDelete' },
80
+ { fn: onMessageUpdated, action: 'onUpdate' },
81
+ { fn: onMessageFlagged, action: 'onFlagged' },
82
+ { fn: onMessageUnflagged, action: 'onUnflagged' },
83
+ { fn: onMessageFlagCleared, action: 'onFlagCleared' },
84
+ { fn: onMessageReactionAdded, action: 'onReactionAdded' },
85
+ { fn: onMessageReactionRemoved, action: 'onReactionRemoved' },
86
+ {
87
+ fn: convertEventPayload(onMessageMarkerFetched, 'contentId', 'message'),
88
+ action: 'onUpdate',
89
+ },
90
+ { fn: convertEventPayload(onMessageMarked, 'contentId', 'message'), action: 'onUpdate' },
91
+ ]);
92
+ }
93
+
94
+ notifyChange(loading: boolean, error?: any) {
95
+ const collection = pullFromCache<Amity.MessageLiveCollectionCache>(this.cacheKey)?.data;
96
+
97
+ if (!collection) return;
98
+
99
+ const data = this.applyFilter(
100
+ collection.data
101
+ .map(messageId => pullFromCache<Amity.Message>(['message', 'get', messageId])!)
102
+ .filter(Boolean)
103
+ .map(({ data }) => data) ?? [],
104
+ );
105
+
106
+ if (!loading && !error && !this.shouldNotify(loading, data)) return;
107
+
108
+ this.callback({
109
+ onNextPage: () => this.loadNextPage(),
110
+ onPrevPage: () => this.loadPrevPage(),
111
+ data,
112
+ hasNextPage: !!collection.params?.page?.after,
113
+ hasPrevPage: !!collection.params?.page?.before,
114
+ loading,
115
+ error,
116
+ });
117
+ }
118
+
119
+ applyFilter(data: Amity.Message[]) {
120
+ let messages = data;
121
+
122
+ messages = messages.filter(m => {
123
+ if (this.query.tags) {
124
+ return this.query.tags.find(value => {
125
+ if (!m.tags) return false;
126
+ return m.tags.includes(value);
127
+ });
128
+ }
129
+ return true;
130
+ });
131
+
132
+ messages = messages.filter(m => {
133
+ if (this.query.excludeTags) {
134
+ return (this.query.excludeTags || []).find(value => {
135
+ if (!m.tags) return true;
136
+ return !m.tags.includes(value);
137
+ });
138
+ }
139
+ return true;
140
+ });
141
+
142
+ /*
143
+ * for cases when message is deleted via RTE, this flag is used to get
144
+ * items from cache that are !deleted
145
+ */
146
+ if (!this.query.includeDeleted) {
147
+ messages = filterByPropEquality(messages, 'isDeleted', false);
148
+ }
149
+
150
+ messages = messages.sort((message1, message2) => {
151
+ if (this.query.sortBy === 'segmentAsc') {
152
+ return message1.channelSegment - message2.channelSegment;
153
+ }
154
+ if (this.query.sortBy === 'segmentDesc') {
155
+ return message2.channelSegment - message1.channelSegment;
156
+ }
157
+ return 0;
158
+ });
159
+
160
+ return messages;
161
+ }
162
+ }
@@ -0,0 +1,42 @@
1
+ /* eslint-disable no-use-before-define */
2
+
3
+ import { PaginationController } from '~/core/liveCollection/PaginationController';
4
+ import { toToken } from '~/core/query';
5
+ import { convertQueryParams } from '~/messageRepository/utils';
6
+ import { COLLECTION_DEFAULT_PAGINATION_LIMIT } from '~/utils/constants';
7
+
8
+ /**
9
+ * TODO: handle cache receive cache option, and cache policy
10
+ * TODO: check if querybyIds is supported
11
+ */
12
+ export class MessagePaginationController extends PaginationController<
13
+ Amity.MessagePayload & Amity.Pagination,
14
+ Amity.MessagesLiveCollection
15
+ > {
16
+ // eslint-disable-next-line no-useless-constructor
17
+ constructor(queryParams: Amity.MessagesLiveCollection, cacheKey: string[]) {
18
+ super(queryParams, cacheKey);
19
+ }
20
+
21
+ async getRequest(queryParams: Amity.MessagesLiveCollection, token: string | undefined) {
22
+ const { limit, aroundMessageId, ...params } = queryParams;
23
+ const processedQueryParams = convertQueryParams(params);
24
+ const { data: queryResponse } = await this.http.get<Amity.MessagePayload & Amity.Pagination>(
25
+ `/api/v5/messages`,
26
+ {
27
+ params: {
28
+ ...processedQueryParams,
29
+ options: token
30
+ ? {
31
+ token,
32
+ }
33
+ : {
34
+ limit,
35
+ around: aroundMessageId,
36
+ },
37
+ },
38
+ },
39
+ );
40
+ return queryResponse;
41
+ }
42
+ }
@@ -0,0 +1,129 @@
1
+ /* eslint-disable no-use-before-define */
2
+ import { QueryStreamController } from '../../../core/liveCollection/QueryStreamController';
3
+ import { pullFromCache, pushToCache } from '~/cache/api';
4
+ import { prepareMessagePayload } from '~/messageRepository/utils';
5
+ import { ingestInCache } from '~/cache/api/ingestInCache';
6
+ import { getResolver } from '~/core/model';
7
+ import { toPage } from '~/core/query';
8
+ import { getActiveClient } from '~/client';
9
+
10
+ export class MessageQueryStreamController extends QueryStreamController<
11
+ Amity.MessagePayload,
12
+ Amity.MessagesLiveCollection
13
+ > {
14
+ private notifyChange: (loading: boolean, error?: any) => void;
15
+
16
+ // private fetchMessageMarker: (response: Amity.MessagePayload) => Promise<void>;
17
+
18
+ constructor(
19
+ query: Amity.MessagesLiveCollection,
20
+ cacheKey: string[],
21
+ notifyChange: (loading: boolean, error?: any) => void,
22
+ ) {
23
+ super(query, cacheKey);
24
+ this.notifyChange = notifyChange;
25
+ }
26
+
27
+ // eslint-disable-next-line class-methods-use-this
28
+ async saveToMainDB(response: Amity.MessagePayload) {
29
+ const processedPayload = await prepareMessagePayload(response);
30
+
31
+ const client = getActiveClient();
32
+ const cachedAt = client.cache && Date.now();
33
+
34
+ if (client.cache) {
35
+ ingestInCache(processedPayload, { cachedAt });
36
+ }
37
+ }
38
+
39
+ appendToQueryStream(
40
+ response: Amity.MessagePayload & Amity.Pagination,
41
+ direction: Amity.LoadingDirection,
42
+ refresh = false,
43
+ ) {
44
+ if (refresh) {
45
+ pushToCache(this.cacheKey, {
46
+ data: response.messages.map(getResolver('message')),
47
+ params: {
48
+ page: {
49
+ after: toPage(response.paging?.next),
50
+ before: toPage(response.paging?.previous),
51
+ },
52
+ },
53
+ });
54
+ } else {
55
+ const collection = pullFromCache<Amity.MessageLiveCollectionCache>(this.cacheKey)?.data;
56
+
57
+ const messages = collection?.data ?? [];
58
+
59
+ pushToCache(this.cacheKey, {
60
+ ...collection,
61
+ data:
62
+ direction === 'next'
63
+ ? [...new Set([...messages, ...response.messages.map(getResolver('message'))])]
64
+ : [...new Set([...response.messages.map(getResolver('message')), ...messages])],
65
+ params: {
66
+ page: {
67
+ before: toPage(response.paging?.previous),
68
+ after: toPage(response.paging?.next),
69
+ },
70
+ },
71
+ });
72
+ }
73
+ }
74
+
75
+ reactor(action: string) {
76
+ return (payload: Amity.Message) => {
77
+ if (action === 'onCreate') {
78
+ const collection = pullFromCache<Amity.MessageLiveCollectionCache>(this.cacheKey)?.data;
79
+
80
+ if (!collection) return;
81
+ if (this.query.subChannelId !== payload?.subChannelId || !collection) return;
82
+
83
+ if (this.query.dataType && this.query.dataType !== payload.dataType) return;
84
+ if (
85
+ this.query.excludeTags &&
86
+ this.query.excludeTags?.some(value => payload.tags?.includes(value))
87
+ )
88
+ return;
89
+ if (!!this.query.hasFlags !== !!payload.flagCount) return;
90
+
91
+ if (this.query.parentId && this.query.parentId !== payload.parentId) return;
92
+
93
+ if (
94
+ this.query.hasOwnProperty('includeDeleted') &&
95
+ !this.query.includeDeleted &&
96
+ payload.isDeleted
97
+ )
98
+ return;
99
+
100
+ if (this.query.tags && !this.query.tags?.some(value => payload.tags?.includes(value)))
101
+ return;
102
+
103
+ if (
104
+ (!this.query.sortBy || this.query.sortBy === 'segmentDesc') &&
105
+ !collection.params.page?.before
106
+ ) {
107
+ collection.data = [...new Set([payload.messageId, ...collection.data])];
108
+ }
109
+
110
+ if (this.query.sortBy === 'segmentAsc' && !collection.params.page?.after) {
111
+ collection.data = [...new Set([...collection.data, payload.messageId])];
112
+ }
113
+
114
+ pushToCache(this.cacheKey, collection);
115
+ }
116
+
117
+ this.notifyChange(false);
118
+ };
119
+ }
120
+
121
+ subscribeRTE(
122
+ createSubscriber: {
123
+ fn: (reactor: Amity.Listener<Amity.Message>) => Amity.Unsubscriber;
124
+ action: string;
125
+ }[],
126
+ ) {
127
+ return createSubscriber.map(subscriber => subscriber.fn(this.reactor(subscriber.action)));
128
+ }
129
+ }
@@ -71,11 +71,12 @@ describe('getMessages', () => {
71
71
  });
72
72
 
73
73
  getMessages({ subChannelId }, callback);
74
+ await pause();
74
75
 
75
- expect(callback).toHaveBeenCalledTimes(1);
76
+ expect(callback).toHaveBeenCalledTimes(2);
76
77
 
77
- // check if cache data returned (should be empty)
78
- expect(callback).toHaveBeenCalledWith(
78
+ expect(callback).toHaveBeenNthCalledWith(
79
+ 1,
79
80
  expect.objectContaining({
80
81
  data: [],
81
82
  error: undefined,
@@ -83,11 +84,8 @@ describe('getMessages', () => {
83
84
  }),
84
85
  );
85
86
 
86
- await expect(queryMessages({ subChannelId })).resolves.toBeTruthy();
87
-
88
- expect(callback).toHaveBeenCalledTimes(2);
89
-
90
- expect(callback).toHaveBeenCalledWith(
87
+ expect(callback).toHaveBeenNthCalledWith(
88
+ 2,
91
89
  expect.objectContaining({
92
90
  data: returnValue.map(convertRawMessage),
93
91
  error: undefined,
@@ -108,8 +106,9 @@ describe('getMessages', () => {
108
106
  client.http.get = jest.fn().mockRejectedValue(error);
109
107
 
110
108
  getMessages({ subChannelId }, callback);
109
+ await pause();
111
110
 
112
- expect(callback).toHaveBeenCalledTimes(1);
111
+ expect(callback).toHaveBeenCalledTimes(2);
113
112
 
114
113
  // check if cache data returned (should be empty)
115
114
  expect(callback).toHaveBeenCalledWith(
@@ -150,11 +149,16 @@ describe('getMessages', () => {
150
149
  });
151
150
 
152
151
  getMessages({ subChannelId, includeDeleted: false }, callback);
152
+ await pause();
153
153
 
154
- expect(callback).toHaveBeenCalledTimes(1);
154
+ // not use queryMessages anymore
155
+ // await expect(queryMessages({ subChannelId })).resolves.toBeTruthy();
156
+
157
+ expect(callback).toHaveBeenCalledTimes(2);
155
158
 
156
159
  // check if cache data returned (should be empty)
157
- expect(callback).toHaveBeenCalledWith(
160
+ expect(callback).toHaveBeenNthCalledWith(
161
+ 1,
158
162
  expect.objectContaining({
159
163
  data: [],
160
164
  error: undefined,
@@ -162,11 +166,8 @@ describe('getMessages', () => {
162
166
  }),
163
167
  );
164
168
 
165
- await expect(queryMessages({ subChannelId })).resolves.toBeTruthy();
166
-
167
- expect(callback).toHaveBeenCalledTimes(2);
168
-
169
- expect(callback).toHaveBeenCalledWith(
169
+ expect(callback).toHaveBeenNthCalledWith(
170
+ 2,
170
171
  expect.objectContaining({
171
172
  // @ts-ignore
172
173
  data: [returnValue[1]].map(convertRawMessage),
@@ -202,10 +203,11 @@ describe('getMessages', () => {
202
203
  });
203
204
 
204
205
  getMessages({ subChannelId, includeDeleted: true }, callback);
206
+ await pause();
205
207
 
206
208
  expect(callback).toHaveBeenCalled();
207
209
 
208
- await expect(queryMessages({ subChannelId })).resolves.toBeTruthy();
210
+ // await expect(queryMessages({ subChannelId })).resolves.toBeTruthy();
209
211
 
210
212
  expect(callback.mock.lastCall).toHaveLength(1);
211
213
 
@@ -215,8 +217,9 @@ describe('getMessages', () => {
215
217
  expect(onNextPage).toBeTruthy();
216
218
 
217
219
  onNextPage();
220
+ await pause();
218
221
 
219
- await expect(queryMessages({ subChannelId })).resolves.toBeTruthy();
222
+ // await expect(queryMessages({ subChannelId })).resolves.toBeTruthy();
220
223
 
221
224
  // 4 -> becuase 1 local & server call each per call (2)
222
225
  expect(callback).toHaveBeenCalledTimes(4);
@@ -232,7 +235,8 @@ describe('getMessages', () => {
232
235
  );
233
236
  });
234
237
 
235
- test('it should add new message to collection onFetch', async () => {
238
+ // skip this test for now because onFecth should not add a new message to the collection
239
+ test.skip('it should add new message to collection onFetch', async () => {
236
240
  const callback = jest.fn();
237
241
  client.http.get = jest.fn().mockResolvedValue({
238
242
  data: {
@@ -242,8 +246,9 @@ describe('getMessages', () => {
242
246
  });
243
247
 
244
248
  getMessages({ subChannelId }, callback);
249
+ await pause();
245
250
 
246
- await expect(queryMessages({ subChannelId })).resolves.toBeTruthy();
251
+ // await expect(queryMessages({ subChannelId })).resolves.toBeTruthy();
247
252
  expect(callback).toHaveBeenCalledTimes(2);
248
253
 
249
254
  client.emitter.emit('local.message.fetched', { messages: [message] });
@@ -275,8 +280,9 @@ describe('getMessages', () => {
275
280
  });
276
281
 
277
282
  getMessages({ subChannelId }, callback);
283
+ await pause();
278
284
 
279
- await expect(queryMessages({ subChannelId })).resolves.toBeTruthy();
285
+ // await expect(queryMessages({ subChannelId })).resolves.toBeTruthy();
280
286
  expect(callback).toHaveBeenCalledTimes(2);
281
287
 
282
288
  // fire new message creation event
@@ -312,8 +318,9 @@ describe('getMessages', () => {
312
318
  });
313
319
 
314
320
  getMessages({ subChannelId }, callback);
321
+ await pause();
315
322
 
316
- await expect(queryMessages({ subChannelId })).resolves.toBeTruthy();
323
+ // await expect(queryMessages({ subChannelId })).resolves.toBeTruthy();
317
324
  expect(callback).toHaveBeenCalledTimes(2);
318
325
 
319
326
  // fire new message creation event
@@ -345,14 +352,15 @@ describe('getMessages', () => {
345
352
  },
346
353
  });
347
354
 
348
- getMessages({ subChannelId }, callback);
355
+ getMessages({ subChannelId, includeDeleted: true }, callback);
356
+ await pause();
349
357
 
350
- await expect(queryMessages({ subChannelId })).resolves.toBeTruthy();
358
+ // await expect(queryMessages({ subChannelId })).resolves.toBeTruthy();
351
359
  expect(callback).toHaveBeenCalledTimes(2);
352
360
 
353
- // fire new message creation event
361
+ // fire new message deletion event
354
362
  client.emitter.emit('message.deleted', {
355
- messages: [rawMessage],
363
+ messages: [{ ...rawMessage, isDeleted: true }],
356
364
  messageFeeds: [],
357
365
  users: [],
358
366
  files: [],
@@ -364,7 +372,7 @@ describe('getMessages', () => {
364
372
  expect(callback).toHaveBeenNthCalledWith(
365
373
  3,
366
374
  expect.objectContaining({
367
- data: [message],
375
+ data: [{ ...message, isDeleted: true }],
368
376
  error: undefined,
369
377
  loading: false,
370
378
  }),
@@ -137,7 +137,7 @@ type RawQueryMessages = Omit<
137
137
  /*
138
138
  * include deleted is for internal sdk use only. The server expects isDeleted
139
139
  */
140
- 'page' | 'sortBy' | 'subChannelId' | 'tags' | 'includeDeleted'
140
+ 'page' | 'sortBy' | 'subChannelId' | 'tags' | 'includeDeleted' | 'aroundMessageId'
141
141
  > & {
142
142
  includeTags?: Amity.QueryMessages['tags'];
143
143
  isDeleted?: Amity.Message['isDeleted'];
@@ -178,6 +178,7 @@ export function convertQueryParams({
178
178
  includeDeleted,
179
179
  ...rest
180
180
  }: Amity.QueryMessages): RawQueryMessages {
181
+ console.log('convertQueryParams', page);
181
182
  const out: RawQueryMessages = {
182
183
  ...rest,
183
184
  messageFeedId: subChannelId,
@@ -192,6 +193,6 @@ export function convertQueryParams({
192
193
  if (tags) {
193
194
  out.includeTags = tags;
194
195
  }
195
-
196
+ console.log('out', out);
196
197
  return out;
197
198
  }
@@ -41,6 +41,19 @@ export function isEqual(x: any, y: any): boolean {
41
41
  return false;
42
42
  }
43
43
 
44
+ // check each element of the array for equality
45
+ if (Array.isArray(x) && Array.isArray(y)) {
46
+ if (x.length !== y.length) return false;
47
+
48
+ // eslint-disable-next-line no-plusplus
49
+ for (let i = 0; i < x.length; i++) {
50
+ if (!isEqual(x[i], y[i])) return false;
51
+ }
52
+
53
+ // if all elements are equal, the arrays are equal
54
+ return true;
55
+ }
56
+
44
57
  // if they are dates, they must had equal valueOf
45
58
  if (x instanceof Date) {
46
59
  return false;