stream-chat 9.50.3 → 10.0.0-rc.2

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 (242) hide show
  1. package/dist/cjs/index.browser.js +19798 -14472
  2. package/dist/cjs/index.browser.js.map +4 -4
  3. package/dist/cjs/index.node.js +19817 -14482
  4. package/dist/cjs/index.node.js.map +4 -4
  5. package/dist/esm/index.mjs +19795 -14469
  6. package/dist/esm/index.mjs.map +4 -4
  7. package/dist/types/ChannelManager.d.ts +221 -0
  8. package/dist/types/EventHandlerPipeline.d.ts +104 -0
  9. package/dist/types/api-client.d.ts +26 -0
  10. package/dist/types/campaign.d.ts +0 -44
  11. package/dist/types/channel.d.ts +454 -479
  12. package/dist/types/channel_batch_updater.d.ts +0 -93
  13. package/dist/types/channel_state.d.ts +53 -201
  14. package/dist/types/client.d.ts +451 -1920
  15. package/dist/types/client_state.d.ts +4 -4
  16. package/dist/types/configuration/InstanceConfigurationService.d.ts +26 -0
  17. package/dist/types/configuration/index.d.ts +1 -0
  18. package/dist/types/configuration/types.d.ts +53 -0
  19. package/dist/types/connection.d.ts +49 -41
  20. package/dist/types/connection_fallback.d.ts +6 -4
  21. package/dist/types/constants.d.ts +0 -4
  22. package/dist/types/custom_types.d.ts +2 -0
  23. package/dist/types/entityStore/EntityStore.d.ts +111 -0
  24. package/dist/types/entityStore/StoreBackedItemIndex.d.ts +60 -0
  25. package/dist/types/entityStore/applyReactionLocally.d.ts +26 -0
  26. package/dist/types/entityStore/index.d.ts +2 -0
  27. package/dist/types/errors.d.ts +10 -2
  28. package/dist/types/gen/chat/ChannelApi.d.ts +45 -0
  29. package/dist/types/gen/chat/ChatApi.d.ts +334 -0
  30. package/dist/types/gen/model-decoders/decoders.d.ts +3 -0
  31. package/dist/types/gen/model-decoders/event-decoder-mapping.d.ts +6 -0
  32. package/dist/types/gen/models/index.d.ts +8836 -0
  33. package/dist/types/gen/moderation/ModerationApi.d.ts +50 -0
  34. package/dist/types/gen-imports.d.ts +3 -0
  35. package/dist/types/index.d.ts +8 -7
  36. package/dist/types/insights.d.ts +9 -8
  37. package/dist/types/logger.d.ts +9 -0
  38. package/dist/types/messageComposer/LocationComposer.d.ts +7 -4
  39. package/dist/types/messageComposer/attachmentIdentity.d.ts +2 -2
  40. package/dist/types/messageComposer/attachmentManager.d.ts +8 -4
  41. package/dist/types/messageComposer/configuration/types.d.ts +11 -11
  42. package/dist/types/messageComposer/linkPreviewsManager.d.ts +25 -13
  43. package/dist/types/messageComposer/messageComposer.d.ts +10 -10
  44. package/dist/types/messageComposer/middleware/messageComposer/types.d.ts +3 -3
  45. package/dist/types/messageComposer/middleware/pollComposer/types.d.ts +4 -7
  46. package/dist/types/messageComposer/middleware/textComposer/commandUtils.d.ts +3 -3
  47. package/dist/types/messageComposer/middleware/textComposer/commands.d.ts +7 -7
  48. package/dist/types/messageComposer/middleware/textComposer/mentions.d.ts +218 -4
  49. package/dist/types/messageComposer/middleware/textComposer/types.d.ts +9 -4
  50. package/dist/types/messageComposer/pollComposer.d.ts +5 -4
  51. package/dist/types/messageComposer/textComposer.d.ts +8 -3
  52. package/dist/types/messageComposer/types.d.ts +6 -28
  53. package/dist/types/messageDelivery/MessageDeliveryReporter.d.ts +40 -25
  54. package/dist/types/messageDelivery/MessageReceiptsTracker.d.ts +53 -12
  55. package/dist/types/messageOperations/MessageOperationStatePolicy.d.ts +19 -0
  56. package/dist/types/messageOperations/MessageOperations.d.ts +17 -0
  57. package/dist/types/messageOperations/index.d.ts +3 -0
  58. package/dist/types/messageOperations/types.d.ts +49 -0
  59. package/dist/types/moderation.d.ts +21 -203
  60. package/dist/types/notifications/types.d.ts +8 -7
  61. package/dist/types/offline-support/offline_support_api.d.ts +111 -87
  62. package/dist/types/offline-support/offline_sync_manager.d.ts +0 -7
  63. package/dist/types/offline-support/types.d.ts +19 -23
  64. package/dist/types/offline-support/util.d.ts +4 -2
  65. package/dist/types/pagination/FilterBuilder.d.ts +1 -1
  66. package/dist/types/pagination/ItemIndex.d.ts +25 -0
  67. package/dist/types/pagination/cursorDerivation/createdAtAroundPaginationFlags.d.ts +9 -0
  68. package/dist/types/pagination/cursorDerivation/idAroundPaginationFlags.d.ts +6 -0
  69. package/dist/types/pagination/cursorDerivation/index.d.ts +1 -0
  70. package/dist/types/pagination/cursorDerivation/linearPaginationFlags.d.ts +6 -0
  71. package/dist/types/pagination/filterCompiler.d.ts +7 -0
  72. package/dist/types/pagination/index.d.ts +1 -3
  73. package/dist/types/pagination/paginators/BasePaginator.d.ts +848 -0
  74. package/dist/types/pagination/paginators/ChannelPaginator.d.ts +212 -0
  75. package/dist/types/pagination/paginators/MessageIntervalPaginator.d.ts +282 -0
  76. package/dist/types/pagination/paginators/MessagePaginator.d.ts +208 -0
  77. package/dist/types/pagination/paginators/PinnedMessagePaginator.d.ts +39 -0
  78. package/dist/types/pagination/paginators/ReminderPaginator.d.ts +19 -0
  79. package/dist/types/pagination/paginators/UserGroupPaginator.d.ts +24 -0
  80. package/dist/types/pagination/paginators/index.d.ts +7 -0
  81. package/dist/types/pagination/paginators/stateThrottling.d.ts +2 -0
  82. package/dist/types/pagination/sortCompiler.d.ts +49 -0
  83. package/dist/types/pagination/types.normalization.d.ts +11 -0
  84. package/dist/types/pagination/utility.normalization.d.ts +18 -0
  85. package/dist/types/pagination/utility.queryChannel.d.ts +26 -0
  86. package/dist/types/pagination/utility.search.d.ts +13 -0
  87. package/dist/types/permissions.d.ts +1 -1
  88. package/dist/types/poll.d.ts +29 -32
  89. package/dist/types/poll_manager.d.ts +2 -2
  90. package/dist/types/reminders/Reminder.d.ts +2 -5
  91. package/dist/types/reminders/ReminderManager.d.ts +2 -9
  92. package/dist/types/search/MessageSearchSource.d.ts +4 -4
  93. package/dist/types/search/UserSearchSource.d.ts +1 -1
  94. package/dist/types/search/types.d.ts +3 -3
  95. package/dist/types/segment.d.ts +0 -34
  96. package/dist/types/signing.d.ts +105 -65
  97. package/dist/types/store.d.ts +1 -0
  98. package/dist/types/thread.d.ts +70 -34
  99. package/dist/types/thread_manager.d.ts +3 -3
  100. package/dist/types/token_manager.d.ts +15 -10
  101. package/dist/types/types.d.ts +197 -3404
  102. package/dist/types/utils/FixedSizeQueueCache.d.ts +12 -6
  103. package/dist/types/utils/WithSubscriptions.d.ts +2 -1
  104. package/dist/types/utils/concurrency.d.ts +4 -4
  105. package/dist/types/utils/mergeWith/mergeWith.d.ts +3 -3
  106. package/dist/types/utils/mergeWith/mergeWithCore.d.ts +8 -3
  107. package/dist/types/utils/retryable.d.ts +34 -0
  108. package/dist/types/utils/throttling/throttle.d.ts +36 -0
  109. package/dist/types/utils.d.ts +100 -194
  110. package/package.json +7 -3
  111. package/src/ChannelManager.ts +724 -0
  112. package/src/CooldownTimer.ts +2 -2
  113. package/src/EventHandlerPipeline.ts +228 -0
  114. package/src/LiveLocationManager.ts +12 -11
  115. package/src/api-client.ts +275 -0
  116. package/src/campaign.ts +1 -77
  117. package/src/channel.ts +1344 -1282
  118. package/src/channel_batch_updater.ts +1 -211
  119. package/src/channel_state.ts +144 -1013
  120. package/src/client.ts +936 -3995
  121. package/src/client_state.ts +4 -4
  122. package/src/configuration/InstanceConfigurationService.ts +73 -0
  123. package/src/configuration/index.ts +1 -0
  124. package/src/configuration/types.ts +81 -0
  125. package/src/connection.ts +145 -94
  126. package/src/connection_fallback.ts +29 -27
  127. package/src/constants.ts +1 -5
  128. package/src/custom_types.ts +4 -1
  129. package/src/entityStore/EntityStore.ts +208 -0
  130. package/src/entityStore/StoreBackedItemIndex.ts +115 -0
  131. package/src/entityStore/applyReactionLocally.ts +113 -0
  132. package/src/entityStore/index.ts +2 -0
  133. package/src/errors.ts +14 -2
  134. package/src/gen/chat/ChannelApi.ts +275 -0
  135. package/src/gen/chat/ChatApi.ts +2554 -0
  136. package/src/gen/model-decoders/decoders.ts +2680 -0
  137. package/src/gen/model-decoders/event-decoder-mapping.ts +198 -0
  138. package/src/gen/models/index.ts +12673 -0
  139. package/src/gen/moderation/ModerationApi.ts +607 -0
  140. package/src/gen-imports.ts +3 -0
  141. package/src/index.ts +11 -12
  142. package/src/insights.ts +6 -5
  143. package/src/logger.ts +28 -0
  144. package/src/messageComposer/LocationComposer.ts +9 -5
  145. package/src/messageComposer/MessageComposerEffectHandlers.ts +1 -0
  146. package/src/messageComposer/attachmentIdentity.ts +16 -9
  147. package/src/messageComposer/attachmentManager.ts +13 -8
  148. package/src/messageComposer/configuration/types.ts +11 -11
  149. package/src/messageComposer/linkPreviewsManager.ts +9 -10
  150. package/src/messageComposer/messageComposer.ts +57 -53
  151. package/src/messageComposer/middleware/messageComposer/attachments.ts +1 -2
  152. package/src/messageComposer/middleware/messageComposer/cleanData.ts +1 -1
  153. package/src/messageComposer/middleware/messageComposer/compositionValidation.ts +2 -2
  154. package/src/messageComposer/middleware/messageComposer/messageComposerState.ts +2 -2
  155. package/src/messageComposer/middleware/messageComposer/sharedLocation.ts +4 -3
  156. package/src/messageComposer/middleware/messageComposer/textComposer.ts +2 -2
  157. package/src/messageComposer/middleware/messageComposer/types.ts +3 -4
  158. package/src/messageComposer/middleware/messageComposer/userDataInjection.ts +9 -5
  159. package/src/messageComposer/middleware/pollComposer/state.ts +0 -2
  160. package/src/messageComposer/middleware/pollComposer/types.ts +4 -7
  161. package/src/messageComposer/middleware/textComposer/TextComposerMiddlewareExecutor.ts +10 -1
  162. package/src/messageComposer/middleware/textComposer/commandEffects.ts +4 -4
  163. package/src/messageComposer/middleware/textComposer/commandUtils.ts +3 -6
  164. package/src/messageComposer/middleware/textComposer/commands.ts +3 -3
  165. package/src/messageComposer/middleware/textComposer/mentionUtils.ts +1 -1
  166. package/src/messageComposer/middleware/textComposer/mentions.ts +31 -15
  167. package/src/messageComposer/middleware/textComposer/types.ts +9 -4
  168. package/src/messageComposer/pollComposer.ts +6 -8
  169. package/src/messageComposer/textComposer.ts +27 -2
  170. package/src/messageComposer/types.ts +6 -28
  171. package/src/messageDelivery/MessageDeliveryReporter.ts +101 -45
  172. package/src/messageDelivery/MessageReceiptsTracker.ts +294 -21
  173. package/src/messageOperations/MessageOperationStatePolicy.ts +77 -0
  174. package/src/messageOperations/MessageOperations.ts +212 -0
  175. package/src/messageOperations/index.ts +10 -0
  176. package/src/messageOperations/types.ts +64 -0
  177. package/src/moderation.ts +38 -431
  178. package/src/notifications/types.ts +8 -7
  179. package/src/offline-support/offline_support_api.ts +200 -133
  180. package/src/offline-support/offline_sync_manager.ts +23 -44
  181. package/src/offline-support/types.ts +23 -26
  182. package/src/offline-support/util.ts +5 -4
  183. package/src/pagination/FilterBuilder.ts +4 -1
  184. package/src/pagination/ItemIndex.ts +25 -0
  185. package/src/pagination/cursorDerivation/createdAtAroundPaginationFlags.ts +73 -0
  186. package/src/pagination/cursorDerivation/idAroundPaginationFlags.ts +53 -0
  187. package/src/pagination/cursorDerivation/index.ts +1 -0
  188. package/src/pagination/cursorDerivation/linearPaginationFlags.ts +86 -0
  189. package/src/pagination/filterCompiler.ts +196 -0
  190. package/src/pagination/index.ts +1 -3
  191. package/src/pagination/paginators/BasePaginator.ts +2859 -0
  192. package/src/pagination/paginators/ChannelPaginator.ts +729 -0
  193. package/src/pagination/paginators/MessageIntervalPaginator.ts +1116 -0
  194. package/src/pagination/paginators/MessagePaginator.ts +508 -0
  195. package/src/pagination/paginators/PinnedMessagePaginator.ts +108 -0
  196. package/src/pagination/paginators/ReminderPaginator.ts +107 -0
  197. package/src/pagination/paginators/UserGroupPaginator.ts +127 -0
  198. package/src/pagination/paginators/index.ts +7 -0
  199. package/src/pagination/paginators/stateThrottling.ts +31 -0
  200. package/src/pagination/sortCompiler.ts +193 -0
  201. package/src/pagination/types.normalization.ts +14 -0
  202. package/src/pagination/utility.normalization.ts +106 -0
  203. package/src/pagination/utility.queryChannel.ts +84 -0
  204. package/src/pagination/utility.search.ts +80 -0
  205. package/src/permissions.ts +7 -8
  206. package/src/poll.ts +101 -110
  207. package/src/poll_manager.ts +14 -8
  208. package/src/reminders/Reminder.ts +2 -5
  209. package/src/reminders/ReminderManager.ts +19 -21
  210. package/src/search/BaseSearchSource.ts +0 -3
  211. package/src/search/ChannelMemberSearchSource.ts +7 -1
  212. package/src/search/ChannelSearchSource.ts +10 -3
  213. package/src/search/MessageSearchSource.ts +29 -30
  214. package/src/search/UserSearchSource.ts +12 -8
  215. package/src/search/types.ts +3 -4
  216. package/src/segment.ts +1 -95
  217. package/src/signing.ts +107 -67
  218. package/src/store.ts +2 -1
  219. package/src/thread.ts +562 -238
  220. package/src/thread_manager.ts +37 -16
  221. package/src/token_manager.ts +21 -10
  222. package/src/types.ts +364 -4533
  223. package/src/uploadManager.ts +8 -0
  224. package/src/utils/FixedSizeQueueCache.ts +12 -6
  225. package/src/utils/WithSubscriptions.ts +2 -1
  226. package/src/utils/concurrency.ts +4 -4
  227. package/src/utils/mergeWith/mergeWith.ts +3 -3
  228. package/src/utils/mergeWith/mergeWithCore.ts +140 -164
  229. package/src/utils/mergeWith/mergeWithDiff.ts +3 -3
  230. package/src/utils/retryable.ts +117 -0
  231. package/src/utils/throttling/throttle.ts +130 -0
  232. package/src/utils.ts +208 -754
  233. package/dist/types/channel_manager.d.ts +0 -122
  234. package/dist/types/events.d.ts +0 -72
  235. package/dist/types/pagination/BasePaginator.d.ts +0 -69
  236. package/dist/types/pagination/ReminderPaginator.d.ts +0 -16
  237. package/dist/types/pagination/UserGroupPaginator.d.ts +0 -21
  238. package/src/channel_manager.ts +0 -830
  239. package/src/events.ts +0 -75
  240. package/src/pagination/BasePaginator.ts +0 -184
  241. package/src/pagination/ReminderPaginator.ts +0 -56
  242. package/src/pagination/UserGroupPaginator.ts +0 -93
@@ -1,8 +1,11 @@
1
1
  import type { StreamChat } from './client';
2
+ import { chatLoggerSystem } from './logger';
2
3
  import type { UploadRequestOptions } from './messageComposer/configuration/types';
3
4
  import { StateStore } from './store';
4
5
  import type { AttachmentManager } from '.';
5
6
 
7
+ const logger = chatLoggerSystem.getLogger('upload-manager');
8
+
6
9
  export type UploadRecord = {
7
10
  id: string;
8
11
  uploadProgress?: number;
@@ -150,6 +153,11 @@ export class UploadManager {
150
153
  );
151
154
  resolvePromise(response);
152
155
  } catch (error) {
156
+ if (!abortController.signal.aborted) {
157
+ logger
158
+ .withExtraTags('upload', channelCid)
159
+ .error(`Upload "${id}" failed.`, { error });
160
+ }
153
161
  rejectPromise(error);
154
162
  } finally {
155
163
  this.inFlightUploads.delete(id);
@@ -2,6 +2,7 @@ type Dispose<K, T> = (key: K, value: T) => void;
2
2
  /**
3
3
  * A cache that stores a fixed number of values in a queue.
4
4
  * The most recently added or retrieved value is kept at the front of the queue.
5
+ *
5
6
  * @template K - The type of the keys.
6
7
  * @template T - The type of the values.
7
8
  */
@@ -20,9 +21,10 @@ export class FixedSizeQueueCache<K, T> {
20
21
  }
21
22
 
22
23
  /**
23
- * Adds a new or moves the existing reference to the front of the queue
24
- * @param key
25
- * @param value
24
+ * Adds a new entry or moves the existing reference to the front of the queue.
25
+ *
26
+ * @param key - The cache key.
27
+ * @param value - The value to associate with `key`.
26
28
  */
27
29
  add(key: K, value: T) {
28
30
  const index = this.keys.indexOf(key);
@@ -48,8 +50,10 @@ export class FixedSizeQueueCache<K, T> {
48
50
  }
49
51
 
50
52
  /**
51
- * Retrieves the value by key.
52
- * @param key
53
+ * Retrieves the value by key without changing its position in the queue.
54
+ *
55
+ * @param key - The cache key.
56
+ * @returns The value, or `undefined` when the key is not cached.
53
57
  */
54
58
  peek(key: K) {
55
59
  const value = this.map.get(key);
@@ -59,7 +63,9 @@ export class FixedSizeQueueCache<K, T> {
59
63
 
60
64
  /**
61
65
  * Retrieves the value and moves it to the front of the queue.
62
- * @param key
66
+ *
67
+ * @param key - The cache key.
68
+ * @returns The value, or `undefined` when the key is not cached.
63
69
  */
64
70
  get(key: K) {
65
71
  const foundItem = this.peek(key);
@@ -1,8 +1,9 @@
1
1
  import type { Unsubscribe } from '../store';
2
2
 
3
3
  /**
4
- * @private
5
4
  * Class to use as a template for subscribable entities.
5
+ *
6
+ * @internal
6
7
  */
7
8
  export abstract class WithSubscriptions {
8
9
  private unsubscribeFunctions: Set<Unsubscribe> = new Set();
@@ -16,9 +16,9 @@ type AsyncWrapper<P extends unknown[], T> = (
16
16
  * should never run simultaneously: if marked with the same tag, functions
17
17
  * will run one after another.
18
18
  *
19
- * @param tag Async functions with the same tag will run serially. Async functions
19
+ * @param tag - Async functions with the same tag will run serially. Async functions
20
20
  * with different tags can run in parallel.
21
- * @param cb Async function to run.
21
+ * @param cb - Async function to run.
22
22
  * @returns Promise that resolves when async functions returns.
23
23
  */
24
24
  export const withoutConcurrency = createRunner(wrapWithContinuationTracking);
@@ -32,9 +32,9 @@ export const withoutConcurrency = createRunner(wrapWithContinuationTracking);
32
32
  * If an async function is already running and was canceled, it will be notified
33
33
  * via an abort signal passed as an argument.
34
34
  *
35
- * @param tag Async functions with the same tag will run serially and are canceled
35
+ * @param tag - Async functions with the same tag will run serially and are canceled
36
36
  * when a new action with the same tag is scheduled.
37
- * @param cb Async function to run. Receives AbortSignal as the only argument.
37
+ * @param cb - Async function to run. Receives AbortSignal as the only argument.
38
38
  * @returns Promise that resolves when async functions returns. If the function didn't
39
39
  * start and was canceled, will resolve with 'canceled'. If the function started to run,
40
40
  * it's up to the function to decide how to react to cancelation.
@@ -6,9 +6,9 @@
6
6
  * (objValue, srcValue, key, object, source, stack).
7
7
  *
8
8
  * @category Object
9
- * @param object The destination object.
10
- * @param source A single source object or an array of objects to be merged into the .
11
- * @param customizer The function to customize assigned values.
9
+ * @param object - The destination object.
10
+ * @param source - A single source object or an array of objects to be merged into the .
11
+ * @param customizer - The function to customize assigned values.
12
12
  * @returns Returns `object`.
13
13
  * @example
14
14
  *
@@ -44,177 +44,162 @@ export const isClassInstance = (value: unknown): boolean => {
44
44
  return value.constructor && value.constructor !== Object;
45
45
  };
46
46
 
47
+ type PairMemo = WeakMap<object, WeakSet<object>>;
48
+
49
+ function memoHasOrAdd(memo: PairMemo, a: object, b: object): boolean {
50
+ const set = memo.get(a);
51
+ if (set && set.has(b)) return true;
52
+ if (set) set.add(b);
53
+ else memo.set(a, new WeakSet([b]));
54
+ return false;
55
+ }
56
+
47
57
  /**
48
- * Performs a deep comparison between two values to determine if they are equivalent.
49
- * This is similar to Lodash's isEqual implementation but simplified.
58
+ * Deep semantic equality with cycle safety and symbol-key support.
59
+ * Keeps your existing semantics:
60
+ * - Dates/RegExps compared by value
61
+ * - "Class instances" are treated atomically (unequal unless ===)
62
+ * - NaN equals NaN; -0 equals 0 (same as ===)
50
63
  */
51
64
  export const isEqual = (
52
65
  value1: unknown,
53
66
  value2: unknown,
54
- compareStack = new Set<[unknown, unknown]>(),
55
- objectStack1 = new WeakSet<object>(),
56
- objectStack2 = new WeakSet<object>(),
67
+ pairMemo: PairMemo = new WeakMap(),
57
68
  ): boolean => {
58
- // Handle simple equality cases first
59
- if (value1 === value2) return true;
60
-
61
- // If either is null/undefined, they're not equal (already checked ===)
69
+ if (value1 === value2) return true; // includes -0 === 0
62
70
  if (value1 == null || value2 == null) return false;
63
71
 
64
- // Get the type of both values
65
- const type1 = typeof value1;
66
- const type2 = typeof value2;
72
+ const t1 = typeof value1;
73
+ const t2 = typeof value2;
74
+ if (t1 !== t2) return false;
67
75
 
68
- // Different types mean they're not equal
69
- if (type1 !== type2) return false;
70
-
71
- // Handle non-object types that need special comparison
72
- if (type1 !== 'object') {
73
- // Special case for NaN
76
+ if (t1 !== 'object') {
77
+ // NaN handling
74
78
  // eslint-disable-next-line no-self-compare
75
- if (value1 !== value1 && value2 !== value2) return true;
76
- return value1 === value2;
79
+ return value1 !== value1 && value2 !== value2 ? true : value1 === value2;
77
80
  }
78
81
 
79
- // At this point, both values are objects
80
- const obj1 = value1 as object;
81
- const obj2 = value2 as object;
82
-
83
- // Check for circular references in each object
84
- if (objectStack1.has(obj1) || objectStack2.has(obj2)) {
85
- // If either object has been seen before, consider them equal
86
- // if they're both in a circular reference
87
- return objectStack1.has(obj1) && objectStack2.has(obj2);
88
- }
82
+ // Objects
83
+ const o1 = value1 as object;
84
+ const o2 = value2 as object;
89
85
 
90
- // Add objects to their respective stacks
91
- objectStack1.add(obj1);
92
- objectStack2.add(obj2);
86
+ // Fast path for tag mismatch
87
+ const tag1 = Object.prototype.toString.call(o1);
88
+ const tag2 = Object.prototype.toString.call(o2);
89
+ if (tag1 !== tag2) return false;
93
90
 
94
- // Handle Date objects - needs to be before the class instance check
95
- if (value1 instanceof Date && value2 instanceof Date) {
96
- objectStack1.delete(obj1);
97
- objectStack2.delete(obj2);
98
- return value1.getTime() === value2.getTime();
91
+ // Special cases before instance test
92
+ if (o1 instanceof Date && o2 instanceof Date) {
93
+ return (o1 as Date).getTime() === (o2 as Date).getTime();
99
94
  }
100
-
101
- // Handle RegExp objects - needs to be before the class instance check
102
- if (value1 instanceof RegExp && value2 instanceof RegExp) {
103
- objectStack1.delete(obj1);
104
- objectStack2.delete(obj2);
105
- return value1.toString() === value2.toString();
106
- }
107
-
108
- // If either is a class instance, use reference equality (already checked above)
109
- if (isClassInstance(value1) || isClassInstance(value2)) {
110
- // Clean up before returning
111
- objectStack1.delete(obj1);
112
- objectStack2.delete(obj2);
113
- return false;
95
+ if (o1 instanceof RegExp && o2 instanceof RegExp) {
96
+ const r1 = o1 as RegExp,
97
+ r2 = o2 as RegExp;
98
+ return r1.source === r2.source && r1.flags === r2.flags;
114
99
  }
115
100
 
116
- // Handle arrays
117
- const isArray1 = Array.isArray(value1);
118
- const isArray2 = Array.isArray(value2);
101
+ // Handle Set comparison
102
+ // Two sets are equal if they have the same size and
103
+ // every value in one has an equivalent value in the
104
+ // other (using deep equality).
105
+ // Cannot use the same item for multiple matches in another set.
106
+ if (value1 instanceof Set && value2 instanceof Set) {
107
+ if (value1.size !== value2.size) return false;
108
+ if (memoHasOrAdd(pairMemo, value1, value2)) return true;
109
+
110
+ const unmatched = new Set(value2);
111
+
112
+ for (const v1 of value1) {
113
+ let matched = false;
114
+ for (const v2 of unmatched) {
115
+ if (isEqual(v1, v2, pairMemo)) {
116
+ unmatched.delete(v2); // consume the match
117
+ matched = true;
118
+ break;
119
+ }
120
+ }
121
+ if (!matched) return false;
122
+ }
119
123
 
120
- if (isArray1 !== isArray2) {
121
- // Clean up before returning
122
- objectStack1.delete(obj1);
123
- objectStack2.delete(obj2);
124
- return false;
124
+ return unmatched.size === 0;
125
125
  }
126
126
 
127
- if (isArray1 && isArray2) {
128
- const arr1 = value1 as unknown[];
129
- const arr2 = value2 as unknown[];
127
+ // Handle Map comparison
128
+ if (value1 instanceof Map && value2 instanceof Map) {
129
+ if (value1.size !== value2.size) return false;
130
130
 
131
- if (arr1.length !== arr2.length) {
132
- // Clean up before returning
133
- objectStack1.delete(obj1);
134
- objectStack2.delete(obj2);
135
- return false;
136
- }
131
+ if (memoHasOrAdd(pairMemo, value1, value2)) return true;
137
132
 
138
- // Check for circular references in the comparison context
139
- const pairKey: [unknown, unknown] = [value1, value2];
140
- if (compareStack.has(pairKey)) {
141
- // Clean up before returning
142
- objectStack1.delete(obj1);
143
- objectStack2.delete(obj2);
144
- return true;
145
- }
146
- compareStack.add(pairKey);
147
-
148
- // Compare each element
149
- for (let i = 0; i < arr1.length; i++) {
150
- if (!isEqual(arr1[i], arr2[i], compareStack, objectStack1, objectStack2)) {
151
- compareStack.delete(pairKey);
152
- // Clean up before returning
153
- objectStack1.delete(obj1);
154
- objectStack2.delete(obj2);
155
- return false;
156
- }
157
- }
133
+ const unmatched = new Set(value2); // tracks entries in map2 not yet matched
158
134
 
159
- compareStack.delete(pairKey);
160
- objectStack1.delete(obj1);
161
- objectStack2.delete(obj2);
162
- return true;
163
- }
135
+ for (const [k1, v1] of value1) {
136
+ let matchedEntry: [unknown, unknown] | null = null;
164
137
 
165
- // Handle plain objects
166
- const plainObj1 = value1 as Record<string, unknown>;
167
- const plainObj2 = value2 as Record<string, unknown>;
138
+ for (const entry of unmatched) {
139
+ const [k2, v2] = entry as [unknown, unknown];
140
+ if (isEqual(k1, k2, pairMemo) && isEqual(v1, v2, pairMemo)) {
141
+ matchedEntry = entry;
142
+ break;
143
+ }
144
+ }
168
145
 
169
- const keys1 = Object.keys(plainObj1);
170
- const keys2 = Object.keys(plainObj2);
146
+ if (!matchedEntry) return false; // nothing matched this entry
147
+ unmatched.delete(matchedEntry); // consume it
148
+ }
171
149
 
172
- // If key counts differ, objects aren't equal
173
- if (keys1.length !== keys2.length) {
174
- // Clean up before returning
175
- objectStack1.delete(obj1);
176
- objectStack2.delete(obj2);
177
- return false;
150
+ return unmatched.size === 0;
178
151
  }
179
152
 
180
- // Verify all keys in obj2 are in obj1 (we already checked counts, so this
181
- // also ensures all keys in obj1 are in obj2)
182
- for (const key of keys2) {
183
- if (!Object.prototype.hasOwnProperty.call(plainObj1, key)) {
184
- // Clean up before returning
185
- objectStack1.delete(obj1);
186
- objectStack2.delete(obj2);
187
- return false;
153
+ // Treat non-plain instances atomically (your current rule)
154
+ if (isClassInstance(o1) || isClassInstance(o2)) return false;
155
+
156
+ // Cycle guard (pairwise)
157
+ if (memoHasOrAdd(pairMemo, o1, o2)) return true;
158
+
159
+ // Arrays (respect holes vs undefined)
160
+ if (Array.isArray(o1)) {
161
+ const a1 = value1 as unknown[],
162
+ a2 = value2 as unknown[];
163
+ if (a1.length !== a2.length) return false;
164
+ for (let i = 0; i < a1.length; i++) {
165
+ const has1 = i in a1,
166
+ has2 = i in a2;
167
+ if (has1 !== has2) return false;
168
+ if (has1 && !isEqual(a1[i], a2[i], pairMemo)) return false;
188
169
  }
189
- }
190
-
191
- // Check for circular references in the comparison context
192
- const pairKey: [unknown, unknown] = [value1, value2];
193
- if (compareStack.has(pairKey)) {
194
- // Clean up before returning
195
- objectStack1.delete(obj1);
196
- objectStack2.delete(obj2);
197
- return true;
198
- }
199
- compareStack.add(pairKey);
200
-
201
- // Compare each property's value
202
- for (const key of keys1) {
203
- if (
204
- !isEqual(plainObj1[key], plainObj2[key], compareStack, objectStack1, objectStack2)
205
- ) {
206
- compareStack.delete(pairKey);
207
- // Clean up before returning
208
- objectStack1.delete(obj1);
209
- objectStack2.delete(obj2);
210
- return false;
170
+ // Compare enumerable non-index props as well (to align with objects)
171
+ const extraKeys1 = Reflect.ownKeys(o1)
172
+ .filter((k) => typeof k !== 'string' || isNaN(+k))
173
+ .filter((k) => Object.prototype.propertyIsEnumerable.call(o1, k));
174
+ const extraKeys2 = Reflect.ownKeys(o2)
175
+ .filter((k) => typeof k !== 'string' || isNaN(+k))
176
+ .filter((k) => Object.prototype.propertyIsEnumerable.call(o2, k));
177
+ if (extraKeys1.length !== extraKeys2.length) return false;
178
+ for (const k of extraKeys1) {
179
+ if (!Object.prototype.hasOwnProperty.call(o2, k)) return false;
180
+ // @ts-expect-error index signature
181
+ if (!isEqual(o1[k], o2[k], pairMemo)) return false;
211
182
  }
183
+ return true;
212
184
  }
213
185
 
214
- compareStack.delete(pairKey);
215
- // Clean up before returning successful comparison
216
- objectStack1.delete(obj1);
217
- objectStack2.delete(obj2);
186
+ // Plain objects (string + symbol enumerable own keys)
187
+ const keys1 = Reflect.ownKeys(o1).filter((k) =>
188
+ Object.prototype.propertyIsEnumerable.call(o1, k),
189
+ );
190
+ const keys2 = Reflect.ownKeys(o2).filter((k) =>
191
+ Object.prototype.propertyIsEnumerable.call(o2, k),
192
+ );
193
+ if (keys1.length !== keys2.length) return false;
194
+
195
+ // enforce same prototype to avoid {} == Object.create(null, ...)
196
+ if (Object.getPrototypeOf(o1) !== Object.getPrototypeOf(o2)) return false;
197
+
198
+ for (const k of keys1) {
199
+ if (!Object.prototype.hasOwnProperty.call(o2, k)) return false;
200
+ // @ts-expect-error index signature
201
+ if (!isEqual(o1[k], o2[k], pairMemo)) return false;
202
+ }
218
203
  return true;
219
204
  };
220
205
 
@@ -241,13 +226,7 @@ function compareAndBuildDiff(
241
226
  modified: unknown,
242
227
  parentDiffNode: DiffNode,
243
228
  key?: string | symbol,
244
- /**
245
- * Tracks pairs of objects being compared
246
- * - It stores pairs of values that are being compared `[original, modified]`
247
- * - This helps detect when we're comparing the same pair of objects again
248
- * - It prevents infinite recursion when comparing complex object structures
249
- */
250
- compareStack = new Set<[unknown, unknown]>(),
229
+ pairMemo: PairMemo = new WeakMap(),
251
230
  /**
252
231
  * Tracks individual objects that are being processed in the current traversal path
253
232
  * - It's used to detect when we encounter the same object multiple times in a single traversal path
@@ -257,9 +236,7 @@ function compareAndBuildDiff(
257
236
  objectStack = new Set<unknown>(),
258
237
  ): void {
259
238
  // If values are equal, no diff to record
260
- if (isEqual(original, modified, new Set(compareStack))) {
261
- return;
262
- }
239
+ if (isEqual(original, modified)) return;
263
240
 
264
241
  // Handle additions (value in modified but not in original)
265
242
  if (original === undefined || original === null) {
@@ -335,16 +312,20 @@ function compareAndBuildDiff(
335
312
  parentDiffNode.children[String(key)] = currentDiffNode;
336
313
  }
337
314
 
338
- // Check for circular references in comparison
339
- const pairKey: [unknown, unknown] = [original, modified];
340
- if (compareStack.has(pairKey)) {
341
- // Remove from object stack before returning
342
- if (typeof original === 'object' && original !== null) {
343
- objectStack.delete(original);
315
+ // Pairwise cycle check (prevents infinite recursion across the *pair*)
316
+ if (
317
+ typeof original === 'object' &&
318
+ original !== null &&
319
+ typeof modified === 'object' &&
320
+ modified !== null
321
+ ) {
322
+ if (memoHasOrAdd(pairMemo, original as object, modified as object)) {
323
+ // already visited this exact pair in this diff traversal
324
+ // (prevents infinite recursion), so stop here
325
+ if (typeof original === 'object') objectStack.delete(original);
326
+ return;
344
327
  }
345
- return;
346
328
  }
347
- compareStack.add(pairKey);
348
329
 
349
330
  // Process all keys from both objects
350
331
  const allKeys = new Set([
@@ -380,17 +361,12 @@ function compareAndBuildDiff(
380
361
  modifiedValue,
381
362
  currentDiffNode,
382
363
  childKey,
383
- compareStack,
364
+ pairMemo,
384
365
  objectStack,
385
366
  );
386
367
  }
387
368
 
388
- compareStack.delete(pairKey);
389
-
390
- // Remove from object stack before returning
391
- if (typeof original === 'object' && original !== null) {
392
- objectStack.delete(original);
393
- }
369
+ if (typeof original === 'object' && original !== null) objectStack.delete(original);
394
370
  }
395
371
 
396
372
  export function createMergeCore<T extends object>(options: { trackDiff?: boolean } = {}) {
@@ -3,9 +3,9 @@
3
3
  * which keys have been added or updated during the merge operation.
4
4
  *
5
5
  * @category Object
6
- * @param object The destination object.
7
- * @param source A single source object or an array of objects to be merged into the object.
8
- * @param customizer The function to customize assigned values.
6
+ * @param object - The destination object.
7
+ * @param source - A single source object or an array of objects to be merged into the object.
8
+ * @param customizer - The function to customize assigned values.
9
9
  * @returns Returns an object containing the merged result and a hierarchical diff object.
10
10
  * @example
11
11
  *
@@ -0,0 +1,117 @@
1
+ type FunctionToRetry<T = any> = (...functionArguments: any[]) => PromiseLike<T>;
2
+
3
+ enum RetryErrorType {
4
+ ABORT,
5
+ ATTEMPT_LIMIT_REACHED,
6
+ }
7
+
8
+ export class RetryError extends Error {
9
+ public name = 'RetryError';
10
+
11
+ private static errorMap = {
12
+ [RetryErrorType.ABORT]: 'Value changed, retry handler aborted',
13
+ [RetryErrorType.ATTEMPT_LIMIT_REACHED]: 'Reached maximum amount of retry attempts',
14
+ } satisfies Record<RetryErrorType, string>;
15
+
16
+ constructor({ type }: { type: RetryErrorType }) {
17
+ super(RetryError.errorMap[type]);
18
+ }
19
+ }
20
+
21
+ export const sleep = (duration: number) =>
22
+ new Promise((resolve) => setTimeout(resolve, duration));
23
+
24
+ // export const handleFalsePositiveResponse = <
25
+ // T extends (...v: any[]) => PromiseLike<SuppliesResponse>,
26
+ // >(
27
+ // f: T,
28
+ // ) =>
29
+ // async function falsePositiveHandler(...functionArguments: Parameters<T>) {
30
+ // // await or throw if "f" fails
31
+ // const data = (await f(...functionArguments)) as Awaited<ReturnType<T>>;
32
+ // // check for error data, throw if exist
33
+ // if (data.errors !== null) {
34
+ // const stringifiedErrors = data.errors
35
+ // .map((error, index) => `E${index + 1}(${error.errorCode}): ${error.message}`)
36
+ // .join('\n');
37
+
38
+ // throw new Error(stringifiedErrors);
39
+ // }
40
+
41
+ // return data;
42
+ // };
43
+
44
+ export type RunWithRetryOptions<T extends FunctionToRetry> = {
45
+ retryAttempts?: number;
46
+ delayBetweenRetries?: number | ((attempt: number) => number);
47
+ isRetryable?: (error: unknown) => boolean;
48
+ didValueChange?: (...functionArguments: Parameters<T>) => Promise<boolean> | boolean;
49
+ };
50
+
51
+ /**
52
+ * Function which wraps asynchronous functions with retry mechanism which'll keep executing said
53
+ * function pre-defined number of times until it resolves, rejects or the retry mechanism runs out of available attempts.
54
+ *
55
+ * #### Available options:
56
+ * - `didValueChange` - check function with initial argument value to check against new values, return `true` to abort next attempt
57
+ *
58
+ * - `isRetryable` - error evaluation function which determines whether to retry the execution
59
+ *
60
+ * - `delayBetweenRetries` - accepts either fixed numeric value or function which provides retry attempt as the argument, expects number as return value
61
+ *
62
+ * - `retryAttempts` - number of attempts to try out before rejecting the promise
63
+ */
64
+ export const runWithRetry = <T extends FunctionToRetry>(
65
+ f: T,
66
+ {
67
+ retryAttempts = 3,
68
+ delayBetweenRetries,
69
+ isRetryable,
70
+ didValueChange,
71
+ }: RunWithRetryOptions<T> = {},
72
+ ) =>
73
+ async function retryable(...functionArguments: Parameters<T>) {
74
+ // starting with -1 as first attempt is not considered a retry
75
+ let retryAttempt = -1;
76
+
77
+ do {
78
+ let data: Awaited<ReturnType<T>> | null = null;
79
+ let error: unknown = null;
80
+
81
+ if (await didValueChange?.(...functionArguments)) {
82
+ throw new RetryError({ type: RetryErrorType.ABORT });
83
+ }
84
+
85
+ try {
86
+ data = await f(...functionArguments);
87
+ } catch (e) {
88
+ error = e;
89
+ }
90
+
91
+ // disable value change check after successfull server call
92
+ // throwing error at this point could lead to stale local states
93
+
94
+ // if (await didValueChange?.(...functionArguments)) {
95
+ // throw new RetryError({ type: 'abort' });
96
+ // }
97
+
98
+ if (data) return data;
99
+
100
+ const runRetry = isRetryable?.(error) ?? true;
101
+ if (!runRetry) throw error;
102
+
103
+ retryAttempt++;
104
+
105
+ const isLastAttempt = retryAttempt === retryAttempts;
106
+
107
+ if (delayBetweenRetries && !isLastAttempt) {
108
+ await sleep(
109
+ typeof delayBetweenRetries === 'function'
110
+ ? delayBetweenRetries(retryAttempt)
111
+ : delayBetweenRetries,
112
+ );
113
+ }
114
+ } while (retryAttempt < retryAttempts);
115
+
116
+ throw new RetryError({ type: RetryErrorType.ATTEMPT_LIMIT_REACHED });
117
+ };