@cossistant/react 0.0.32 → 0.1.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 (145) hide show
  1. package/README.md +3 -3
  2. package/hooks/index.d.ts +2 -2
  3. package/hooks/index.js +2 -2
  4. package/hooks/private/store/use-store-selector.d.ts.map +1 -1
  5. package/hooks/private/store/use-store-selector.js +8 -4
  6. package/hooks/private/store/use-store-selector.js.map +1 -1
  7. package/hooks/private/use-client-query.js +5 -2
  8. package/hooks/private/use-client-query.js.map +1 -1
  9. package/hooks/private/use-grouped-messages.d.ts +27 -2
  10. package/hooks/private/use-grouped-messages.d.ts.map +1 -1
  11. package/hooks/private/use-grouped-messages.js +154 -106
  12. package/hooks/private/use-grouped-messages.js.map +1 -1
  13. package/hooks/private/use-rest-client.js +2 -1
  14. package/hooks/private/use-rest-client.js.map +1 -1
  15. package/hooks/use-conversation-auto-seen.d.ts.map +1 -1
  16. package/hooks/use-conversation-auto-seen.js +9 -3
  17. package/hooks/use-conversation-auto-seen.js.map +1 -1
  18. package/hooks/use-conversation-page.d.ts.map +1 -1
  19. package/hooks/use-conversation-page.js +13 -3
  20. package/hooks/use-conversation-page.js.map +1 -1
  21. package/hooks/use-new-message-sound.d.ts.map +1 -1
  22. package/hooks/use-new-message-sound.js +2 -2
  23. package/hooks/use-new-message-sound.js.map +1 -1
  24. package/hooks/use-typing-sound.d.ts.map +1 -1
  25. package/hooks/use-typing-sound.js +2 -2
  26. package/hooks/use-typing-sound.js.map +1 -1
  27. package/index.d.ts +3 -3
  28. package/index.js +3 -3
  29. package/package.json +6 -12
  30. package/packages/tiny-markdown/src/context/index.d.ts +1 -0
  31. package/packages/tiny-markdown/src/context/tiny-markdown-context.d.ts +3 -0
  32. package/packages/tiny-markdown/src/hooks/index.d.ts +4 -0
  33. package/packages/tiny-markdown/src/hooks/use-caret-position.d.ts +1 -0
  34. package/packages/tiny-markdown/src/hooks/use-tiny-markdown.d.ts +1 -0
  35. package/packages/tiny-markdown/src/hooks/use-tiny-mention.d.ts +1 -0
  36. package/packages/tiny-markdown/src/hooks/use-tiny-shortcuts.d.ts +1 -0
  37. package/packages/tiny-markdown/src/index.d.ts +4 -0
  38. package/packages/tiny-markdown/src/types.d.ts +75 -0
  39. package/packages/tiny-markdown/src/types.d.ts.map +1 -0
  40. package/packages/tiny-markdown/src/utils/index.d.ts +3 -0
  41. package/packages/tiny-markdown/src/utils/markdown-parser.d.ts +1 -0
  42. package/packages/tiny-markdown/src/utils/mention-parser.d.ts +1 -0
  43. package/packages/tiny-markdown/src/utils/merge-refs.d.ts +1 -0
  44. package/packages/types/src/api/conversation.d.ts +300 -0
  45. package/packages/types/src/api/conversation.d.ts.map +1 -1
  46. package/packages/types/src/api/timeline-item.d.ts +225 -0
  47. package/packages/types/src/api/timeline-item.d.ts.map +1 -1
  48. package/packages/types/src/realtime-events.d.ts +228 -3
  49. package/packages/types/src/realtime-events.d.ts.map +1 -1
  50. package/packages/types/src/schemas.d.ts +75 -0
  51. package/packages/types/src/schemas.d.ts.map +1 -1
  52. package/primitives/avatar/image.d.ts +1 -1
  53. package/primitives/command-block-utils.d.ts +26 -0
  54. package/primitives/command-block-utils.d.ts.map +1 -0
  55. package/primitives/command-block-utils.js +310 -0
  56. package/primitives/command-block-utils.js.map +1 -0
  57. package/primitives/index.d.ts +7 -3
  58. package/primitives/index.js +11 -2
  59. package/primitives/index.parts.d.ts +6 -2
  60. package/primitives/index.parts.js +5 -1
  61. package/primitives/timeline-code-block.d.ts +32 -0
  62. package/primitives/timeline-code-block.d.ts.map +1 -0
  63. package/primitives/timeline-code-block.js +66 -0
  64. package/primitives/timeline-code-block.js.map +1 -0
  65. package/primitives/timeline-command-block.d.ts +29 -0
  66. package/primitives/timeline-command-block.d.ts.map +1 -0
  67. package/primitives/timeline-command-block.js +97 -0
  68. package/primitives/timeline-command-block.js.map +1 -0
  69. package/primitives/timeline-item-group.d.ts.map +1 -1
  70. package/primitives/timeline-item-group.js +5 -15
  71. package/primitives/timeline-item-group.js.map +1 -1
  72. package/primitives/timeline-item.d.ts +21 -1
  73. package/primitives/timeline-item.d.ts.map +1 -1
  74. package/primitives/timeline-item.js +148 -83
  75. package/primitives/timeline-item.js.map +1 -1
  76. package/primitives/timeline-message-layout.d.ts +9 -0
  77. package/primitives/timeline-message-layout.d.ts.map +1 -0
  78. package/primitives/timeline-message-layout.js +20 -0
  79. package/primitives/timeline-message-layout.js.map +1 -0
  80. package/provider.d.ts.map +1 -1
  81. package/provider.js +1 -7
  82. package/provider.js.map +1 -1
  83. package/realtime/event-filter.js +4 -3
  84. package/realtime/event-filter.js.map +1 -1
  85. package/realtime/provider.d.ts +0 -1
  86. package/realtime/provider.d.ts.map +1 -1
  87. package/realtime/provider.js +29 -34
  88. package/realtime/provider.js.map +1 -1
  89. package/sounds/sound-data.d.ts +6 -0
  90. package/sounds/sound-data.d.ts.map +1 -0
  91. package/sounds/sound-data.js +7 -0
  92. package/sounds/sound-data.js.map +1 -0
  93. package/styles.css +2 -0
  94. package/support/components/avatar.js +3 -3
  95. package/support/components/avatar.js.map +1 -1
  96. package/support/components/button.d.ts +2 -2
  97. package/support/components/button.d.ts.map +1 -1
  98. package/support/components/button.js +1 -0
  99. package/support/components/button.js.map +1 -1
  100. package/support/components/conversation-event.d.ts +3 -0
  101. package/support/components/conversation-event.d.ts.map +1 -1
  102. package/support/components/conversation-event.js +47 -16
  103. package/support/components/conversation-event.js.map +1 -1
  104. package/support/components/conversation-timeline.d.ts.map +1 -1
  105. package/support/components/conversation-timeline.js +12 -0
  106. package/support/components/conversation-timeline.js.map +1 -1
  107. package/support/components/index.d.ts +2 -1
  108. package/support/components/index.js +2 -1
  109. package/support/components/timeline-activity-group.d.ts +25 -0
  110. package/support/components/timeline-activity-group.d.ts.map +1 -0
  111. package/support/components/timeline-activity-group.js +104 -0
  112. package/support/components/timeline-activity-group.js.map +1 -0
  113. package/support/components/timeline-code-block.d.ts +14 -0
  114. package/support/components/timeline-code-block.d.ts.map +1 -0
  115. package/support/components/timeline-code-block.js +44 -0
  116. package/support/components/timeline-code-block.js.map +1 -0
  117. package/support/components/timeline-command-block.d.ts +12 -0
  118. package/support/components/timeline-command-block.d.ts.map +1 -0
  119. package/support/components/timeline-command-block.js +42 -0
  120. package/support/components/timeline-command-block.js.map +1 -0
  121. package/support/components/timeline-message-item.d.ts +2 -1
  122. package/support/components/timeline-message-item.d.ts.map +1 -1
  123. package/support/components/timeline-message-item.js +23 -3
  124. package/support/components/timeline-message-item.js.map +1 -1
  125. package/support/pages/home.js +1 -1
  126. package/support/pages/home.js.map +1 -1
  127. package/support/store/support-store.d.ts.map +1 -1
  128. package/support/store/support-store.js +4 -4
  129. package/support/store/support-store.js.map +1 -1
  130. package/support/{support-DmViRaga.css → support-Dc5L__HC.css} +15 -15
  131. package/support/{support-DmViRaga.css.map → support-Dc5L__HC.css.map} +1 -1
  132. package/{tailwind.css → support/support.css} +14 -14
  133. package/support-config.d.ts +31 -4
  134. package/support-config.d.ts.map +1 -1
  135. package/support-config.js +52 -4
  136. package/support-config.js.map +1 -1
  137. package/support.css +1 -2
  138. package/utils/metadata-hash.d.ts +1 -1
  139. package/utils/metadata-hash.js +9 -4
  140. package/utils/metadata-hash.js.map +1 -1
  141. package/utils/timeline-item-sender.d.ts +17 -0
  142. package/utils/timeline-item-sender.d.ts.map +1 -0
  143. package/utils/timeline-item-sender.js +43 -0
  144. package/utils/timeline-item-sender.js.map +1 -0
  145. package/utils/use-render-element.d.ts.map +1 -1
package/README.md CHANGED
@@ -25,7 +25,7 @@ The SDK provides two CSS entrypoints to fit your setup:
25
25
  If you're using Tailwind CSS v4, import the source file to enable full theme customization:
26
26
 
27
27
  ```tsx
28
- import "@cossistant/react/tailwind.css";
28
+ import "@cossistant/react/support.css";
29
29
  ```
30
30
 
31
31
  ### Option 2: Plain CSS
@@ -33,7 +33,7 @@ import "@cossistant/react/tailwind.css";
33
33
  Import the pre-compiled CSS with no Tailwind dependency:
34
34
 
35
35
  ```tsx
36
- import "@cossistant/react/support.css";
36
+ import "@cossistant/react/styles.css";
37
37
  ```
38
38
 
39
39
  This file contains all the compiled styles and works in any React application without requiring Tailwind CSS.
@@ -44,7 +44,7 @@ This file contains all the compiled styles and works in any React application wi
44
44
 
45
45
  ```tsx
46
46
  import { SupportProvider, Support } from "@cossistant/react";
47
- import "@cossistant/react/support.css";
47
+ import "@cossistant/react/styles.css";
48
48
 
49
49
  export function App() {
50
50
  return (
package/hooks/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { useClientQuery } from "./private/use-client-query.js";
2
2
  import { useDefaultMessages } from "./private/use-default-messages.js";
3
- import { ConversationItem, DaySeparatorItem, GroupedMessage, TimelineEventItem, TimelineToolItem, UseGroupedMessagesOptions, UseGroupedMessagesProps, useGroupedMessages } from "./private/use-grouped-messages.js";
3
+ import { ConversationItem, DaySeparatorItem, GroupedActivity, GroupedMessage, PreparedTimelineItems, TIMELINE_GROUP_WINDOW_MS, TimelineEventItem, TimelineToolItem, UseGroupedMessagesOptions, UseGroupedMessagesProps, buildTimelineReadReceiptData, groupTimelineItems, prepareTimelineItems, useGroupedMessages } from "./private/use-grouped-messages.js";
4
4
  import { UseMultimodalInputOptions, UseMultimodalInputReturn, useMultimodalInput } from "./private/use-multimodal-input.js";
5
5
  import { ConfigurationError, UseClientResult, useClient } from "./private/use-rest-client.js";
6
6
  import { UseComposerRefocusOptions, UseComposerRefocusReturn, useComposerRefocus } from "./use-composer-refocus.js";
@@ -27,4 +27,4 @@ import { UseSoundEffectOptions, UseSoundEffectReturn, useSoundEffect } from "./u
27
27
  import { useTypingSound } from "./use-typing-sound.js";
28
28
  import { UseVisitorReturn, useVisitor } from "./use-visitor.js";
29
29
  import { WindowVisibilityFocusState, useWindowVisibilityFocus } from "./use-window-visibility-focus.js";
30
- export { CONVERSATION_AUTO_SEEN_DELAY_MS, ConfigurationError, ConversationItem, ConversationLifecycleState, ConversationPreviewAssignedAgent, ConversationPreviewLastMessage, ConversationPreviewTypingParticipant, ConversationPreviewTypingState, ConversationTimelineTypingParticipant, ConversationTypingParticipant, CreateConversationVariables, DaySeparatorItem, FileUploadPart, GroupedMessage, SendMessageOptions, SendMessageResult, TimelineEventItem, TimelineToolItem, UseClientResult, UseComposerRefocusOptions, UseComposerRefocusReturn, UseConversationAutoSeenOptions, UseConversationHistoryPageOptions, UseConversationHistoryPageReturn, UseConversationLifecycleOptions, UseConversationLifecycleReturn, UseConversationOptions, UseConversationPageOptions, UseConversationPageReturn, UseConversationPreviewOptions, UseConversationPreviewReturn, UseConversationResult, UseConversationTimelineItemsOptions, UseConversationTimelineItemsResult, UseConversationTimelineOptions, UseConversationTimelineReturn, UseConversationsOptions, UseConversationsResult, UseCreateConversationOptions, UseCreateConversationResult, UseFileUploadOptions, UseFileUploadReturn, UseGroupedMessagesOptions, UseGroupedMessagesProps, UseHomePageOptions, UseHomePageReturn, UseMessageComposerOptions, UseMessageComposerReturn, UseMultimodalInputOptions, UseMultimodalInputReturn, UseRealtimeSupportOptions, UseRealtimeSupportResult, UseScrollMaskOptions, UseScrollMaskReturn, UseSendMessageOptions, UseSendMessageResult, UseSoundEffectOptions, UseSoundEffectReturn, UseVisitorReturn, WindowVisibilityFocusState, useClient, useClientQuery, useComposerRefocus, useConversation, useConversationAutoSeen, useConversationHistoryPage, useConversationLifecycle, useConversationPage, useConversationPreview, useConversationSeen, useConversationTimeline, useConversationTimelineItems, useConversationTyping, useConversations, useCreateConversation, useDebouncedConversationSeen, useDefaultMessages, useFileUpload, useGroupedMessages, useHomePage, useMessageComposer, useMultimodalInput, useNewMessageSound, useRealtimeSupport, useScrollMask, useSendMessage, useSoundEffect, useTypingSound, useVisitor, useWindowVisibilityFocus };
30
+ export { CONVERSATION_AUTO_SEEN_DELAY_MS, ConfigurationError, ConversationItem, ConversationLifecycleState, ConversationPreviewAssignedAgent, ConversationPreviewLastMessage, ConversationPreviewTypingParticipant, ConversationPreviewTypingState, ConversationTimelineTypingParticipant, ConversationTypingParticipant, CreateConversationVariables, DaySeparatorItem, FileUploadPart, GroupedActivity, GroupedMessage, PreparedTimelineItems, SendMessageOptions, SendMessageResult, TIMELINE_GROUP_WINDOW_MS, TimelineEventItem, TimelineToolItem, UseClientResult, UseComposerRefocusOptions, UseComposerRefocusReturn, UseConversationAutoSeenOptions, UseConversationHistoryPageOptions, UseConversationHistoryPageReturn, UseConversationLifecycleOptions, UseConversationLifecycleReturn, UseConversationOptions, UseConversationPageOptions, UseConversationPageReturn, UseConversationPreviewOptions, UseConversationPreviewReturn, UseConversationResult, UseConversationTimelineItemsOptions, UseConversationTimelineItemsResult, UseConversationTimelineOptions, UseConversationTimelineReturn, UseConversationsOptions, UseConversationsResult, UseCreateConversationOptions, UseCreateConversationResult, UseFileUploadOptions, UseFileUploadReturn, UseGroupedMessagesOptions, UseGroupedMessagesProps, UseHomePageOptions, UseHomePageReturn, UseMessageComposerOptions, UseMessageComposerReturn, UseMultimodalInputOptions, UseMultimodalInputReturn, UseRealtimeSupportOptions, UseRealtimeSupportResult, UseScrollMaskOptions, UseScrollMaskReturn, UseSendMessageOptions, UseSendMessageResult, UseSoundEffectOptions, UseSoundEffectReturn, UseVisitorReturn, WindowVisibilityFocusState, buildTimelineReadReceiptData, groupTimelineItems, prepareTimelineItems, useClient, useClientQuery, useComposerRefocus, useConversation, useConversationAutoSeen, useConversationHistoryPage, useConversationLifecycle, useConversationPage, useConversationPreview, useConversationSeen, useConversationTimeline, useConversationTimelineItems, useConversationTyping, useConversations, useCreateConversation, useDebouncedConversationSeen, useDefaultMessages, useFileUpload, useGroupedMessages, useHomePage, useMessageComposer, useMultimodalInput, useNewMessageSound, useRealtimeSupport, useScrollMask, useSendMessage, useSoundEffect, useTypingSound, useVisitor, useWindowVisibilityFocus };
package/hooks/index.js CHANGED
@@ -12,7 +12,7 @@ import { useMultimodalInput } from "./private/use-multimodal-input.js";
12
12
  import { useSendMessage } from "./use-send-message.js";
13
13
  import { useMessageComposer } from "./use-message-composer.js";
14
14
  import { useConversationPage } from "./use-conversation-page.js";
15
- import { useGroupedMessages } from "./private/use-grouped-messages.js";
15
+ import { TIMELINE_GROUP_WINDOW_MS, buildTimelineReadReceiptData, groupTimelineItems, prepareTimelineItems, useGroupedMessages } from "./private/use-grouped-messages.js";
16
16
  import { useConversationSeen, useDebouncedConversationSeen } from "./use-conversation-seen.js";
17
17
  import { useConversationTyping } from "./use-conversation-typing.js";
18
18
  import { useConversationTimeline } from "./use-conversation-timeline.js";
@@ -28,4 +28,4 @@ import { useCreateConversation } from "./use-create-conversation.js";
28
28
  import { useFileUpload } from "./use-file-upload.js";
29
29
  import { useRealtimeSupport } from "./use-realtime-support.js";
30
30
 
31
- export { CONVERSATION_AUTO_SEEN_DELAY_MS, useClient, useClientQuery, useComposerRefocus, useConversation, useConversationAutoSeen, useConversationHistoryPage, useConversationLifecycle, useConversationPage, useConversationPreview, useConversationSeen, useConversationTimeline, useConversationTimelineItems, useConversationTyping, useConversations, useCreateConversation, useDebouncedConversationSeen, useDefaultMessages, useFileUpload, useGroupedMessages, useHomePage, useMessageComposer, useMultimodalInput, useNewMessageSound, useRealtimeSupport, useScrollMask, useSendMessage, useSoundEffect, useTypingSound, useVisitor, useWindowVisibilityFocus };
31
+ export { CONVERSATION_AUTO_SEEN_DELAY_MS, TIMELINE_GROUP_WINDOW_MS, buildTimelineReadReceiptData, groupTimelineItems, prepareTimelineItems, useClient, useClientQuery, useComposerRefocus, useConversation, useConversationAutoSeen, useConversationHistoryPage, useConversationLifecycle, useConversationPage, useConversationPreview, useConversationSeen, useConversationTimeline, useConversationTimelineItems, useConversationTyping, useConversations, useCreateConversation, useDebouncedConversationSeen, useDefaultMessages, useFileUpload, useGroupedMessages, useHomePage, useMessageComposer, useMultimodalInput, useNewMessageSound, useRealtimeSupport, useScrollMask, useSendMessage, useSoundEffect, useTypingSound, useVisitor, useWindowVisibilityFocus };
@@ -1 +1 @@
1
- {"version":3,"file":"use-store-selector.d.ts","names":[],"sources":["../../../../src/hooks/private/store/use-store-selector.ts"],"sourcesContent":[],"mappings":";KAEK,+BAA+B;KAE/B,UAFA,CAAA,MAAY,CAAA,GAAA;EAEZ,QAAA,EAAA,EACQ,MADE;EACF,SAAA,CAAA,QAAA,EACQ,YADR,CACqB,MADrB,CAAA,CAAA,EAAA,GAAA,GAAA,IAAA;CACqB;;;AAYlC;;;;AAE8B,iBAFd,gBAEc,CAAA,MAAA,EAAA,SAAA,CAAA,CAAA,KAAA,EADtB,UACsB,CADX,MACW,CAAA,EAAA,QAAA,EAAA,CAAA,KAAA,EAAX,MAAW,EAAA,GAAA,SAAA,EAAA,OAAA,CAAA,EAAA,CAAA,QAAA,EACR,SADQ,EAAA,IAAA,EACS,SADT,EAAA,GAAA,OAAA,CAAA,EAE3B,SAF2B;AACR,iBAGN,gBAHM,CAAA,MAAA,EAAA,SAAA,CAAA,CAAA,KAAA,EAId,UAJc,CAIH,MAJG,CAAA,GAAA,IAAA,EAAA,QAAA,EAAA,CAAA,KAAA,EAKH,MALG,GAAA,IAAA,EAAA,GAKe,SALf,EAAA,OAAA,CAAA,EAAA,CAAA,QAAA,EAMA,SANA,EAAA,IAAA,EAMiB,SANjB,EAAA,GAAA,OAAA,CAAA,EAOnB,SAPmB"}
1
+ {"version":3,"file":"use-store-selector.d.ts","names":[],"sources":["../../../../src/hooks/private/store/use-store-selector.ts"],"sourcesContent":[],"mappings":";KAEK,+BAA+B;KAE/B,UAFA,CAAA,MAAY,CAAA,GAAA;EAEZ,QAAA,EAAA,EACQ,MADE;EACF,SAAA,CAAA,QAAA,EACQ,YADR,CACqB,MADrB,CAAA,CAAA,EAAA,GAAA,GAAA,IAAA;CACqB;;;AAalC;;;;AAE8B,iBAFd,gBAEc,CAAA,MAAA,EAAA,SAAA,CAAA,CAAA,KAAA,EADtB,UACsB,CADX,MACW,CAAA,EAAA,QAAA,EAAA,CAAA,KAAA,EAAX,MAAW,EAAA,GAAA,SAAA,EAAA,OAAA,CAAA,EAAA,CAAA,QAAA,EACR,SADQ,EAAA,IAAA,EACS,SADT,EAAA,GAAA,OAAA,CAAA,EAE3B,SAF2B;AACR,iBAGN,gBAHM,CAAA,MAAA,EAAA,SAAA,CAAA,CAAA,KAAA,EAId,UAJc,CAIH,MAJG,CAAA,GAAA,IAAA,EAAA,QAAA,EAAA,CAAA,KAAA,EAKH,MALG,GAAA,IAAA,EAAA,GAKe,SALf,EAAA,OAAA,CAAA,EAAA,CAAA,QAAA,EAMA,SANA,EAAA,IAAA,EAMiB,SANjB,EAAA,GAAA,OAAA,CAAA,EAOnB,SAPmB"}
@@ -1,12 +1,16 @@
1
- import { useRef, useSyncExternalStore } from "react";
1
+ import { useCallback, useRef, useSyncExternalStore } from "react";
2
2
 
3
3
  //#region src/hooks/private/store/use-store-selector.ts
4
4
  const noopSubscribe = () => () => {};
5
+ const getNull = () => null;
5
6
  function useStoreSelector(store, selector, isEqual = Object.is) {
6
7
  const selectionRef = useRef(void 0);
7
- const subscribe = store ? (onStoreChange) => store.subscribe(() => onStoreChange()) : noopSubscribe;
8
- const getSnapshot = store ? () => store.getState() : () => null;
9
- const selected = selector(useSyncExternalStore(subscribe, getSnapshot, getSnapshot));
8
+ const subscribe = useCallback((onStoreChange) => {
9
+ if (!store) return noopSubscribe();
10
+ return store.subscribe(() => onStoreChange());
11
+ }, [store]);
12
+ const getSnapshot = useCallback(() => store ? store.getState() : null, [store]);
13
+ const selected = selector(useSyncExternalStore(store ? subscribe : noopSubscribe, store ? getSnapshot : getNull, store ? getSnapshot : getNull));
10
14
  if (selectionRef.current === void 0 || !isEqual(selectionRef.current, selected)) selectionRef.current = selected;
11
15
  return selectionRef.current;
12
16
  }
@@ -1 +1 @@
1
- {"version":3,"file":"use-store-selector.js","names":[],"sources":["../../../../src/hooks/private/store/use-store-selector.ts"],"sourcesContent":["import { useRef, useSyncExternalStore } from \"react\";\n\ntype Subscription<TState> = (state: TState) => void;\n\ntype BasicStore<TState> = {\n\tgetState(): TState;\n\tsubscribe(listener: Subscription<TState>): () => void;\n};\n\n// No-op subscribe function for null store case\nconst noopSubscribe = () => () => {};\n\n/**\n * React hook that bridges Zustand-like stores with React components by\n * memoizing selector results and resubscribing when dependencies change.\n *\n * Overloaded to support both nullable and non-nullable stores.\n */\nexport function useStoreSelector<TState, TSelected>(\n\tstore: BasicStore<TState>,\n\tselector: (state: TState) => TSelected,\n\tisEqual?: (previous: TSelected, next: TSelected) => boolean\n): TSelected;\n\nexport function useStoreSelector<TState, TSelected>(\n\tstore: BasicStore<TState> | null,\n\tselector: (state: TState | null) => TSelected,\n\tisEqual?: (previous: TSelected, next: TSelected) => boolean\n): TSelected;\n\nexport function useStoreSelector<TState, TSelected>(\n\tstore: BasicStore<TState> | null,\n\tselector: (state: TState | null) => TSelected,\n\tisEqual: (previous: TSelected, next: TSelected) => boolean = Object.is\n): TSelected {\n\tconst selectionRef = useRef<TSelected>(undefined);\n\n\t// Create stable subscribe function\n\tconst subscribe = store\n\t\t? (onStoreChange: () => void) => store.subscribe(() => onStoreChange())\n\t\t: noopSubscribe;\n\n\t// Create stable getSnapshot function\n\tconst getSnapshot = store ? () => store.getState() : () => null;\n\n\t// Always call useSyncExternalStore unconditionally\n\tconst snapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n\tconst selected = selector(snapshot);\n\n\tif (\n\t\tselectionRef.current === undefined ||\n\t\t!isEqual(selectionRef.current, selected)\n\t) {\n\t\tselectionRef.current = selected;\n\t}\n\n\treturn selectionRef.current as TSelected;\n}\n"],"mappings":";;;AAUA,MAAM,4BAA4B;AAoBlC,SAAgB,iBACf,OACA,UACA,UAA6D,OAAO,IACxD;CACZ,MAAM,eAAe,OAAkB,OAAU;CAGjD,MAAM,YAAY,SACd,kBAA8B,MAAM,gBAAgB,eAAe,CAAC,GACrE;CAGH,MAAM,cAAc,cAAc,MAAM,UAAU,SAAS;CAK3D,MAAM,WAAW,SAFA,qBAAqB,WAAW,aAAa,YAAY,CAEvC;AAEnC,KACC,aAAa,YAAY,UACzB,CAAC,QAAQ,aAAa,SAAS,SAAS,CAExC,cAAa,UAAU;AAGxB,QAAO,aAAa"}
1
+ {"version":3,"file":"use-store-selector.js","names":[],"sources":["../../../../src/hooks/private/store/use-store-selector.ts"],"sourcesContent":["import { useCallback, useRef, useSyncExternalStore } from \"react\";\n\ntype Subscription<TState> = (state: TState) => void;\n\ntype BasicStore<TState> = {\n\tgetState(): TState;\n\tsubscribe(listener: Subscription<TState>): () => void;\n};\n\n// No-op subscribe function for null store case\nconst noopSubscribe = () => () => {};\nconst getNull = () => null;\n\n/**\n * React hook that bridges Zustand-like stores with React components by\n * memoizing selector results and resubscribing when dependencies change.\n *\n * Overloaded to support both nullable and non-nullable stores.\n */\nexport function useStoreSelector<TState, TSelected>(\n\tstore: BasicStore<TState>,\n\tselector: (state: TState) => TSelected,\n\tisEqual?: (previous: TSelected, next: TSelected) => boolean\n): TSelected;\n\nexport function useStoreSelector<TState, TSelected>(\n\tstore: BasicStore<TState> | null,\n\tselector: (state: TState | null) => TSelected,\n\tisEqual?: (previous: TSelected, next: TSelected) => boolean\n): TSelected;\n\nexport function useStoreSelector<TState, TSelected>(\n\tstore: BasicStore<TState> | null,\n\tselector: (state: TState | null) => TSelected,\n\tisEqual: (previous: TSelected, next: TSelected) => boolean = Object.is\n): TSelected {\n\tconst selectionRef = useRef<TSelected>(undefined);\n\n\t// Stable subscribe function — only recreated when store identity changes\n\tconst subscribe = useCallback(\n\t\t(onStoreChange: () => void) => {\n\t\t\tif (!store) {\n\t\t\t\treturn noopSubscribe();\n\t\t\t}\n\t\t\treturn store.subscribe(() => onStoreChange());\n\t\t},\n\t\t[store]\n\t);\n\n\t// Stable getSnapshot function — only recreated when store identity changes\n\tconst getSnapshot = useCallback(\n\t\t() => (store ? store.getState() : null),\n\t\t[store]\n\t);\n\n\t// Always call useSyncExternalStore unconditionally\n\tconst snapshot = useSyncExternalStore(\n\t\tstore ? subscribe : noopSubscribe,\n\t\tstore ? getSnapshot : getNull,\n\t\tstore ? getSnapshot : getNull\n\t);\n\n\tconst selected = selector(snapshot);\n\n\tif (\n\t\tselectionRef.current === undefined ||\n\t\t!isEqual(selectionRef.current, selected)\n\t) {\n\t\tselectionRef.current = selected;\n\t}\n\n\treturn selectionRef.current as TSelected;\n}\n"],"mappings":";;;AAUA,MAAM,4BAA4B;AAClC,MAAM,gBAAgB;AAoBtB,SAAgB,iBACf,OACA,UACA,UAA6D,OAAO,IACxD;CACZ,MAAM,eAAe,OAAkB,OAAU;CAGjD,MAAM,YAAY,aAChB,kBAA8B;AAC9B,MAAI,CAAC,MACJ,QAAO,eAAe;AAEvB,SAAO,MAAM,gBAAgB,eAAe,CAAC;IAE9C,CAAC,MAAM,CACP;CAGD,MAAM,cAAc,kBACZ,QAAQ,MAAM,UAAU,GAAG,MAClC,CAAC,MAAM,CACP;CASD,MAAM,WAAW,SANA,qBAChB,QAAQ,YAAY,eACpB,QAAQ,cAAc,SACtB,QAAQ,cAAc,QACtB,CAEkC;AAEnC,KACC,aAAa,YAAY,UACzB,CAAC,QAAQ,aAAa,SAAS,SAAS,CAExC,cAAa,UAAU;AAGxB,QAAO,aAAa"}
@@ -44,8 +44,11 @@ function useClientQuery(options) {
44
44
  const isMountedRef = useRef(true);
45
45
  const queryFnRef = useRef(queryFn);
46
46
  queryFnRef.current = queryFn;
47
- useEffect(() => () => {
48
- isMountedRef.current = false;
47
+ useEffect(() => {
48
+ isMountedRef.current = true;
49
+ return () => {
50
+ isMountedRef.current = false;
51
+ };
49
52
  }, []);
50
53
  useEffect(() => {
51
54
  argsRef.current = initialArgs;
@@ -1 +1 @@
1
- {"version":3,"file":"use-client-query.js","names":["EMPTY_DEPENDENCIES: readonly unknown[]","raw: unknown"],"sources":["../../../src/hooks/private/use-client-query.ts"],"sourcesContent":["import type { CossistantClient } from \"@cossistant/core\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\n\ntype QueryFn<TData, TArgs> = (\n\tclient: CossistantClient,\n\targs?: TArgs | undefined\n) => Promise<TData>;\n\ntype UseClientQueryOptions<TData, TArgs> = {\n\tclient: CossistantClient | null;\n\tqueryFn: QueryFn<TData, TArgs>;\n\t/**\n\t * Unique key to identify this query for deduplication.\n\t * When provided, concurrent requests with the same key will share a single\n\t * in-flight promise instead of making duplicate API calls.\n\t */\n\tqueryKey?: string;\n\tenabled?: boolean;\n\trefetchInterval?: number | false;\n\trefetchOnWindowFocus?: boolean;\n\trefetchOnMount?: boolean;\n\tinitialData?: TData;\n\tinitialArgs?: TArgs;\n\tdependencies?: readonly unknown[];\n};\n\ntype UseClientQueryResult<TData, TArgs> = {\n\tdata: TData | undefined;\n\terror: Error | null;\n\tisLoading: boolean;\n\trefetch: (args?: TArgs) => Promise<TData | undefined>;\n};\n\nfunction toError(error: unknown): Error {\n\tif (error instanceof Error) {\n\t\treturn error;\n\t}\n\n\treturn new Error(typeof error === \"string\" ? error : \"Unknown error\");\n}\n\nconst EMPTY_DEPENDENCIES: readonly unknown[] = [];\n\n/**\n * Module-level cache for in-flight requests.\n * Maps query keys to their pending promises for deduplication.\n */\nconst inFlightRequests = new Map<string, Promise<unknown>>();\n\n/**\n * Execute a query with deduplication support.\n * If a query with the same key is already in flight, returns the existing promise.\n */\nfunction executeWithDeduplication<TData>(\n\tqueryKey: string | undefined,\n\tqueryFn: () => Promise<TData>\n): Promise<TData> {\n\t// No deduplication if no key provided\n\tif (!queryKey) {\n\t\treturn queryFn();\n\t}\n\n\t// Check for existing in-flight request\n\tconst existing = inFlightRequests.get(queryKey);\n\tif (existing) {\n\t\treturn existing as Promise<TData>;\n\t}\n\n\t// Create new request and track it\n\tconst promise = queryFn().finally(() => {\n\t\t// Clean up after request completes (success or error)\n\t\tinFlightRequests.delete(queryKey);\n\t});\n\n\tinFlightRequests.set(queryKey, promise);\n\treturn promise;\n}\n\n/**\n * Lightweight data-fetching abstraction that plugs into the SDK client instead\n * of React Query. It tracks loading/error state, supports polling, window\n * focus refetching and exposes a typed refetch helper.\n */\nexport function useClientQuery<TData, TArgs = void>(\n\toptions: UseClientQueryOptions<TData, TArgs>\n): UseClientQueryResult<TData, TArgs> {\n\tconst {\n\t\tclient,\n\t\tqueryFn,\n\t\tqueryKey,\n\t\tenabled = true,\n\t\trefetchInterval = false,\n\t\trefetchOnWindowFocus = false,\n\t\trefetchOnMount = true,\n\t\tinitialData,\n\t\tinitialArgs,\n\t\tdependencies = EMPTY_DEPENDENCIES,\n\t} = options;\n\n\tconst [data, setData] = useState<TData | undefined>(initialData);\n\tconst [error, setError] = useState<Error | null>(null);\n\tconst [isLoading, setIsLoading] = useState(\n\t\tinitialData === undefined && Boolean(enabled)\n\t);\n\n\tconst dataRef = useRef(data);\n\tdataRef.current = data;\n\n\tconst argsRef = useRef<TArgs | undefined>(initialArgs);\n\tconst fetchIdRef = useRef(0);\n\tconst hasMountedRef = useRef(false);\n\tconst hasFetchedRef = useRef(initialData !== undefined);\n\tconst isMountedRef = useRef(true);\n\tconst queryFnRef = useRef(queryFn);\n\n\tqueryFnRef.current = queryFn;\n\n\tuseEffect(\n\t\t() => () => {\n\t\t\tisMountedRef.current = false;\n\t\t},\n\t\t[]\n\t);\n\n\tuseEffect(() => {\n\t\targsRef.current = initialArgs;\n\t}, [initialArgs]);\n\n\tconst execute = useCallback(\n\t\tasync (args?: TArgs, ignoreEnabled = false): Promise<TData | undefined> => {\n\t\t\t// Handle null client (configuration error case)\n\t\t\tif (!client) {\n\t\t\t\treturn dataRef.current;\n\t\t\t}\n\n\t\t\tif (!(enabled || ignoreEnabled)) {\n\t\t\t\treturn dataRef.current;\n\t\t\t}\n\n\t\t\tconst nextArgs = args ?? argsRef.current;\n\t\t\targsRef.current = nextArgs;\n\n\t\t\tconst fetchId = fetchIdRef.current + 1;\n\t\t\tfetchIdRef.current = fetchId;\n\n\t\t\tsetIsLoading(true);\n\t\t\tsetError(null);\n\n\t\t\ttry {\n\t\t\t\t// Use deduplication to share in-flight requests with the same key\n\t\t\t\tconst result = await executeWithDeduplication(queryKey, () =>\n\t\t\t\t\tqueryFnRef.current(client, nextArgs)\n\t\t\t\t);\n\n\t\t\t\tif (!isMountedRef.current || fetchId !== fetchIdRef.current) {\n\t\t\t\t\treturn dataRef.current;\n\t\t\t\t}\n\n\t\t\t\tdataRef.current = result;\n\t\t\t\tsetData(result);\n\t\t\t\tsetError(null);\n\t\t\t\tsetIsLoading(false);\n\t\t\t\thasFetchedRef.current = true;\n\n\t\t\t\treturn result;\n\t\t\t} catch (raw: unknown) {\n\t\t\t\tif (!isMountedRef.current || fetchId !== fetchIdRef.current) {\n\t\t\t\t\treturn dataRef.current;\n\t\t\t\t}\n\n\t\t\t\tconst normalized = toError(raw);\n\t\t\t\tsetError(normalized);\n\t\t\t\tsetIsLoading(false);\n\n\t\t\t\tthrow normalized;\n\t\t\t}\n\t\t},\n\t\t[client, enabled, queryKey]\n\t);\n\n\tuseEffect(() => {\n\t\tif (!enabled) {\n\t\t\tsetIsLoading(false);\n\t\t\treturn;\n\t\t}\n\n\t\tconst shouldFetchInitially = hasMountedRef.current\n\t\t\t? true\n\t\t\t: refetchOnMount || !hasFetchedRef.current;\n\n\t\thasMountedRef.current = true;\n\n\t\tif (!shouldFetchInitially) {\n\t\t\treturn;\n\t\t}\n\n\t\tvoid execute(argsRef.current);\n\t}, [enabled, execute, refetchOnMount, ...dependencies]);\n\n\tuseEffect(() => {\n\t\tif (!enabled) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (\n\t\t\trefetchInterval === false ||\n\t\t\trefetchInterval === null ||\n\t\t\trefetchInterval <= 0 ||\n\t\t\ttypeof window === \"undefined\"\n\t\t) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst timer = window.setInterval(() => {\n\t\t\tvoid execute(argsRef.current);\n\t\t}, refetchInterval);\n\n\t\treturn () => {\n\t\t\twindow.clearInterval(timer);\n\t\t};\n\t}, [enabled, execute, refetchInterval]);\n\n\tuseEffect(() => {\n\t\tif (\n\t\t\t!refetchOnWindowFocus ||\n\t\t\ttypeof window === \"undefined\" ||\n\t\t\ttypeof document === \"undefined\"\n\t\t) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst handleRefetch = () => {\n\t\t\tif (!enabled) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvoid execute(argsRef.current);\n\t\t};\n\n\t\tconst onFocus = () => {\n\t\t\tvoid handleRefetch();\n\t\t};\n\n\t\tconst onVisibilityChange = () => {\n\t\t\tif (document.visibilityState === \"visible\") {\n\t\t\t\tvoid handleRefetch();\n\t\t\t}\n\t\t};\n\n\t\twindow.addEventListener(\"focus\", onFocus);\n\t\tdocument.addEventListener(\"visibilitychange\", onVisibilityChange);\n\n\t\treturn () => {\n\t\t\twindow.removeEventListener(\"focus\", onFocus);\n\t\t\tdocument.removeEventListener(\"visibilitychange\", onVisibilityChange);\n\t\t};\n\t}, [enabled, execute, refetchOnWindowFocus]);\n\n\tconst refetch = useCallback(\n\t\tasync (args?: TArgs) => execute(args, true),\n\t\t[execute]\n\t);\n\n\treturn useMemo(\n\t\t() => ({\n\t\t\tdata,\n\t\t\terror,\n\t\t\tisLoading,\n\t\t\trefetch,\n\t\t}),\n\t\t[data, error, isLoading, refetch]\n\t);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"use-client-query.js","names":["EMPTY_DEPENDENCIES: readonly unknown[]","raw: unknown"],"sources":["../../../src/hooks/private/use-client-query.ts"],"sourcesContent":["import type { CossistantClient } from \"@cossistant/core\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\n\ntype QueryFn<TData, TArgs> = (\n\tclient: CossistantClient,\n\targs?: TArgs | undefined\n) => Promise<TData>;\n\ntype UseClientQueryOptions<TData, TArgs> = {\n\tclient: CossistantClient | null;\n\tqueryFn: QueryFn<TData, TArgs>;\n\t/**\n\t * Unique key to identify this query for deduplication.\n\t * When provided, concurrent requests with the same key will share a single\n\t * in-flight promise instead of making duplicate API calls.\n\t */\n\tqueryKey?: string;\n\tenabled?: boolean;\n\trefetchInterval?: number | false;\n\trefetchOnWindowFocus?: boolean;\n\trefetchOnMount?: boolean;\n\tinitialData?: TData;\n\tinitialArgs?: TArgs;\n\tdependencies?: readonly unknown[];\n};\n\ntype UseClientQueryResult<TData, TArgs> = {\n\tdata: TData | undefined;\n\terror: Error | null;\n\tisLoading: boolean;\n\trefetch: (args?: TArgs) => Promise<TData | undefined>;\n};\n\nfunction toError(error: unknown): Error {\n\tif (error instanceof Error) {\n\t\treturn error;\n\t}\n\n\treturn new Error(typeof error === \"string\" ? error : \"Unknown error\");\n}\n\nconst EMPTY_DEPENDENCIES: readonly unknown[] = [];\n\n/**\n * Module-level cache for in-flight requests.\n * Maps query keys to their pending promises for deduplication.\n */\nconst inFlightRequests = new Map<string, Promise<unknown>>();\n\n/**\n * Execute a query with deduplication support.\n * If a query with the same key is already in flight, returns the existing promise.\n */\nfunction executeWithDeduplication<TData>(\n\tqueryKey: string | undefined,\n\tqueryFn: () => Promise<TData>\n): Promise<TData> {\n\t// No deduplication if no key provided\n\tif (!queryKey) {\n\t\treturn queryFn();\n\t}\n\n\t// Check for existing in-flight request\n\tconst existing = inFlightRequests.get(queryKey);\n\tif (existing) {\n\t\treturn existing as Promise<TData>;\n\t}\n\n\t// Create new request and track it\n\tconst promise = queryFn().finally(() => {\n\t\t// Clean up after request completes (success or error)\n\t\tinFlightRequests.delete(queryKey);\n\t});\n\n\tinFlightRequests.set(queryKey, promise);\n\treturn promise;\n}\n\n/**\n * Lightweight data-fetching abstraction that plugs into the SDK client instead\n * of React Query. It tracks loading/error state, supports polling, window\n * focus refetching and exposes a typed refetch helper.\n */\nexport function useClientQuery<TData, TArgs = void>(\n\toptions: UseClientQueryOptions<TData, TArgs>\n): UseClientQueryResult<TData, TArgs> {\n\tconst {\n\t\tclient,\n\t\tqueryFn,\n\t\tqueryKey,\n\t\tenabled = true,\n\t\trefetchInterval = false,\n\t\trefetchOnWindowFocus = false,\n\t\trefetchOnMount = true,\n\t\tinitialData,\n\t\tinitialArgs,\n\t\tdependencies = EMPTY_DEPENDENCIES,\n\t} = options;\n\n\tconst [data, setData] = useState<TData | undefined>(initialData);\n\tconst [error, setError] = useState<Error | null>(null);\n\tconst [isLoading, setIsLoading] = useState(\n\t\tinitialData === undefined && Boolean(enabled)\n\t);\n\n\tconst dataRef = useRef(data);\n\tdataRef.current = data;\n\n\tconst argsRef = useRef<TArgs | undefined>(initialArgs);\n\tconst fetchIdRef = useRef(0);\n\tconst hasMountedRef = useRef(false);\n\tconst hasFetchedRef = useRef(initialData !== undefined);\n\tconst isMountedRef = useRef(true);\n\tconst queryFnRef = useRef(queryFn);\n\n\tqueryFnRef.current = queryFn;\n\n\tuseEffect(() => {\n\t\tisMountedRef.current = true;\n\t\treturn () => {\n\t\t\tisMountedRef.current = false;\n\t\t};\n\t}, []);\n\n\tuseEffect(() => {\n\t\targsRef.current = initialArgs;\n\t}, [initialArgs]);\n\n\tconst execute = useCallback(\n\t\tasync (args?: TArgs, ignoreEnabled = false): Promise<TData | undefined> => {\n\t\t\t// Handle null client (configuration error case)\n\t\t\tif (!client) {\n\t\t\t\treturn dataRef.current;\n\t\t\t}\n\n\t\t\tif (!(enabled || ignoreEnabled)) {\n\t\t\t\treturn dataRef.current;\n\t\t\t}\n\n\t\t\tconst nextArgs = args ?? argsRef.current;\n\t\t\targsRef.current = nextArgs;\n\n\t\t\tconst fetchId = fetchIdRef.current + 1;\n\t\t\tfetchIdRef.current = fetchId;\n\n\t\t\tsetIsLoading(true);\n\t\t\tsetError(null);\n\n\t\t\ttry {\n\t\t\t\t// Use deduplication to share in-flight requests with the same key\n\t\t\t\tconst result = await executeWithDeduplication(queryKey, () =>\n\t\t\t\t\tqueryFnRef.current(client, nextArgs)\n\t\t\t\t);\n\n\t\t\t\tif (!isMountedRef.current || fetchId !== fetchIdRef.current) {\n\t\t\t\t\treturn dataRef.current;\n\t\t\t\t}\n\n\t\t\t\tdataRef.current = result;\n\t\t\t\tsetData(result);\n\t\t\t\tsetError(null);\n\t\t\t\tsetIsLoading(false);\n\t\t\t\thasFetchedRef.current = true;\n\n\t\t\t\treturn result;\n\t\t\t} catch (raw: unknown) {\n\t\t\t\tif (!isMountedRef.current || fetchId !== fetchIdRef.current) {\n\t\t\t\t\treturn dataRef.current;\n\t\t\t\t}\n\n\t\t\t\tconst normalized = toError(raw);\n\t\t\t\tsetError(normalized);\n\t\t\t\tsetIsLoading(false);\n\n\t\t\t\tthrow normalized;\n\t\t\t}\n\t\t},\n\t\t[client, enabled, queryKey]\n\t);\n\n\tuseEffect(() => {\n\t\tif (!enabled) {\n\t\t\tsetIsLoading(false);\n\t\t\treturn;\n\t\t}\n\n\t\tconst shouldFetchInitially = hasMountedRef.current\n\t\t\t? true\n\t\t\t: refetchOnMount || !hasFetchedRef.current;\n\n\t\thasMountedRef.current = true;\n\n\t\tif (!shouldFetchInitially) {\n\t\t\treturn;\n\t\t}\n\n\t\tvoid execute(argsRef.current);\n\t}, [enabled, execute, refetchOnMount, ...dependencies]);\n\n\tuseEffect(() => {\n\t\tif (!enabled) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (\n\t\t\trefetchInterval === false ||\n\t\t\trefetchInterval === null ||\n\t\t\trefetchInterval <= 0 ||\n\t\t\ttypeof window === \"undefined\"\n\t\t) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst timer = window.setInterval(() => {\n\t\t\tvoid execute(argsRef.current);\n\t\t}, refetchInterval);\n\n\t\treturn () => {\n\t\t\twindow.clearInterval(timer);\n\t\t};\n\t}, [enabled, execute, refetchInterval]);\n\n\tuseEffect(() => {\n\t\tif (\n\t\t\t!refetchOnWindowFocus ||\n\t\t\ttypeof window === \"undefined\" ||\n\t\t\ttypeof document === \"undefined\"\n\t\t) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst handleRefetch = () => {\n\t\t\tif (!enabled) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvoid execute(argsRef.current);\n\t\t};\n\n\t\tconst onFocus = () => {\n\t\t\tvoid handleRefetch();\n\t\t};\n\n\t\tconst onVisibilityChange = () => {\n\t\t\tif (document.visibilityState === \"visible\") {\n\t\t\t\tvoid handleRefetch();\n\t\t\t}\n\t\t};\n\n\t\twindow.addEventListener(\"focus\", onFocus);\n\t\tdocument.addEventListener(\"visibilitychange\", onVisibilityChange);\n\n\t\treturn () => {\n\t\t\twindow.removeEventListener(\"focus\", onFocus);\n\t\t\tdocument.removeEventListener(\"visibilitychange\", onVisibilityChange);\n\t\t};\n\t}, [enabled, execute, refetchOnWindowFocus]);\n\n\tconst refetch = useCallback(\n\t\tasync (args?: TArgs) => execute(args, true),\n\t\t[execute]\n\t);\n\n\treturn useMemo(\n\t\t() => ({\n\t\t\tdata,\n\t\t\terror,\n\t\t\tisLoading,\n\t\t\trefetch,\n\t\t}),\n\t\t[data, error, isLoading, refetch]\n\t);\n}\n"],"mappings":"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"}
@@ -13,6 +13,18 @@ type GroupedMessage = {
13
13
  firstMessageTime: Date;
14
14
  lastMessageTime: Date;
15
15
  };
16
+ type GroupedActivity = {
17
+ type: "activity_group";
18
+ senderId: string;
19
+ senderType: SenderType;
20
+ items: TimelineItem[];
21
+ firstItemId: string;
22
+ lastItemId: string;
23
+ firstItemTime: Date;
24
+ lastItemTime: Date;
25
+ hasEvent: boolean;
26
+ hasTool: boolean;
27
+ };
16
28
  type TimelineEventItem = {
17
29
  type: "timeline_event";
18
30
  item: TimelineItem;
@@ -29,13 +41,26 @@ type DaySeparatorItem = {
29
41
  date: Date;
30
42
  dateString: string;
31
43
  };
32
- type ConversationItem = GroupedMessage | TimelineEventItem | TimelineToolItem | DaySeparatorItem;
44
+ type ConversationItem = GroupedMessage | GroupedActivity | TimelineEventItem | TimelineToolItem | DaySeparatorItem;
33
45
  type UseGroupedMessagesOptions = {
34
46
  items: TimelineItem[];
35
47
  seenData?: ConversationSeen[];
36
48
  currentViewerId?: string;
37
49
  };
38
50
  type UseGroupedMessagesProps = UseGroupedMessagesOptions;
51
+ type PreparedTimelineItems = {
52
+ items: TimelineItem[];
53
+ times: number[];
54
+ didSort: boolean;
55
+ };
56
+ declare const TIMELINE_GROUP_WINDOW_MS: number;
57
+ declare const prepareTimelineItems: (items: TimelineItem[]) => PreparedTimelineItems;
58
+ declare const groupTimelineItems: (items: TimelineItem[], itemTimes: number[]) => ConversationItem[];
59
+ declare const buildTimelineReadReceiptData: (seenData: ConversationSeen[], sortedMessageItems: TimelineItem[], sortedMessageTimes: number[]) => {
60
+ seenByMap: Map<string, Set<string>>;
61
+ lastReadMessageMap: Map<string, string>;
62
+ unreadCountMap: Map<string, number>;
63
+ };
39
64
  /**
40
65
  * Batches sequential timeline items from the same sender into groups and enriches
41
66
  * them with read-receipt helpers so UIs can render conversation timelines with
@@ -59,5 +84,5 @@ declare const useGroupedMessages: ({
59
84
  hasUnreadAfter: (messageId: string, userId: string) => boolean;
60
85
  };
61
86
  //#endregion
62
- export { ConversationItem, DaySeparatorItem, GroupedMessage, TimelineEventItem, TimelineToolItem, UseGroupedMessagesOptions, UseGroupedMessagesProps, useGroupedMessages };
87
+ export { ConversationItem, DaySeparatorItem, GroupedActivity, GroupedMessage, PreparedTimelineItems, TIMELINE_GROUP_WINDOW_MS, TimelineEventItem, TimelineToolItem, UseGroupedMessagesOptions, UseGroupedMessagesProps, buildTimelineReadReceiptData, groupTimelineItems, prepareTimelineItems, useGroupedMessages };
63
88
  //# sourceMappingURL=use-grouped-messages.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-grouped-messages.d.ts","names":[],"sources":["../../../src/hooks/private/use-grouped-messages.ts"],"sourcesContent":[],"mappings":";;;;;KAKY,cAAA;;EAAA,QAAA,EAAA,MAAA;EAGC,UAAA,EAAA,UAAA;EACL,KAAA,EAAA,YAAA,EAAA;EAGW,cAAA,EAAA,MAAA;EACD,aAAA,EAAA,MAAA;EAAI,gBAAA,EADH,IACG;EAGV,eAAA,EAHM,IAGW;AAM7B,CAAA;AAOY,KAbA,iBAAA,GAagB;EAMhB,IAAA,EAAA,gBAAgB;EACzB,IAAA,EAlBI,YAkBJ;EACA,SAAA,EAlBS,IAkBT;CACA;AACA,KAjBS,gBAAA,GAiBT;EAAgB,IAAA,EAAA,eAAA;EAEP,IAAA,EAjBL,YAiBK;EAMA,IAAA,EAAA,MAAA,GAAA,IAAA;EAmQC,SAAA,EAxRD,IAwRC;CAAsB;AAAA,KArRvB,gBAAA,GAqRuB;EAAA,IAAA,EAAA,eAAA;EAIhC,IAAA,EAvRI,IAuRJ;;;KAnRS,gBAAA,GACT,iBACA,oBACA,mBACA;KAES,yBAAA;SACJ;aACI;;;KAIA,uBAAA,GAA0B;;;;;;;cAmQzB;;;;GAIV"}
1
+ {"version":3,"file":"use-grouped-messages.d.ts","names":[],"sources":["../../../src/hooks/private/use-grouped-messages.ts"],"sourcesContent":[],"mappings":";;;;;KAMY,cAAA;;EAAA,QAAA,EAAA,MAAA;EAGC,UAAA,EAAA,UAAA;EACL,KAAA,EAAA,YAAA,EAAA;EAGW,cAAA,EAAA,MAAA;EACD,aAAA,EAAA,MAAA;EAAI,gBAAA,EADH,IACG;EAGV,eAAA,EAHM,IAGS;CAGd;AACL,KAJI,eAAA,GAIJ;EAGQ,IAAA,EAAA,gBAAA;EACD,QAAA,EAAA,MAAA;EAAI,UAAA,EALN,UAKM;EAKP,KAAA,EATJ,YASI,EAAiB;EAMjB,WAAA,EAAA,MAAgB;EAOhB,UAAA,EAAA,MAAA;EAMA,aAAA,EAzBI,IAyBY;EACzB,YAAA,EAzBY,IAyBZ;EACA,QAAA,EAAA,OAAA;EACA,OAAA,EAAA,OAAA;CACA;AACA,KAxBS,iBAAA,GAwBT;EAAgB,IAAA,EAAA,gBAAA;EAEP,IAAA,EAxBL,YAwBK;EAMA,SAAA,EA7BA,IA6BA;AAEZ,CAAA;AAMa,KAlCD,gBAAA,GAkCyC;EA2ExC,IAAA,EAAA,eAAA;EAmEA,IAAA,EA9KN,YA8KM;EAiKA,IAAA,EAAA,MAAA,GAAA,IAAA;EACF,SAAA,EA9UC,IA8UD;CACU;KA5UT,gBAAA;;QAEL;;;AA0YM,KAtYD,gBAAA,GACT,cAyeF,GAxeE,eAweF,GAveE,iBAueF,GAteE,gBAseF,GAreE,gBAqeF;AApGkC,KA/XvB,yBAAA,GA+XuB;EAAA,KAAA,EA9X3B,YA8X2B,EAAA;EAAA,QAAA,CAAA,EA7XvB,gBA6XuB,EAAA;EAIhC,eAAA,CAAA,EAAA,MAAA;;KA7XS,uBAAA,GAA0B;KAE1B,qBAAA;SACJ;;;;cAKK;cA2EA,8BACL,mBACL;cAiEU,4BACL,wCAEL;cA8JU,yCACF,wCACU;;;;;;;;;;;cAgER;;;;GAIV"}
@@ -1,17 +1,13 @@
1
+ import { getTimelineItemSender } from "../../utils/timeline-item-sender.js";
1
2
  import { useMemo } from "react";
2
- import { SenderType } from "@cossistant/types";
3
3
 
4
4
  //#region src/hooks/private/use-grouped-messages.ts
5
+ const TIMELINE_GROUP_WINDOW_MS = 300 * 1e3;
5
6
  const getTimestamp = (date) => {
6
7
  if (!date) return 0;
7
8
  if (typeof date === "string") return new Date(date).getTime();
8
9
  return date.getTime();
9
10
  };
10
- const toDate = (date) => {
11
- if (!date) return typeof window !== "undefined" ? /* @__PURE__ */ new Date() : /* @__PURE__ */ new Date(0);
12
- if (typeof date === "string") return new Date(date);
13
- return date;
14
- };
15
11
  const getDateString = (date) => {
16
12
  return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
17
13
  };
@@ -19,69 +15,144 @@ const createDayDate = (dateString) => {
19
15
  const [year, month, day] = dateString.split("-").map(Number);
20
16
  return new Date(year ?? 0, (month ?? 1) - 1, day ?? 1, 0, 0, 0, 0);
21
17
  };
22
- const getSenderIdAndTypeFromTimelineItem = (item) => {
23
- if (item.visitorId) return {
24
- senderId: item.visitorId,
25
- senderType: SenderType.VISITOR
26
- };
27
- if (item.aiAgentId) return {
28
- senderId: item.aiAgentId,
29
- senderType: SenderType.AI
30
- };
31
- if (item.userId) return {
32
- senderId: item.userId,
33
- senderType: SenderType.TEAM_MEMBER
34
- };
35
- return {
36
- senderId: item.id || "default-sender",
37
- senderType: SenderType.TEAM_MEMBER
38
- };
39
- };
40
18
  const getToolNameFromTimelineItem = (item) => {
41
19
  if (item.tool) return item.tool;
42
20
  for (const part of item.parts) if (typeof part === "object" && part !== null && "type" in part && "toolName" in part && typeof part.type === "string" && part.type.startsWith("tool-") && typeof part.toolName === "string") return part.toolName;
43
21
  return null;
44
22
  };
45
23
  const EMPTY_STRING_ARRAY = Object.freeze([]);
46
- const groupTimelineItems = (items) => {
24
+ function getGroupableTimelineItemType(item) {
25
+ if (item.type === "message") return "message";
26
+ if (item.type === "event" || item.type === "tool") return "activity";
27
+ if (item.type === "identification") return "standalone_tool";
28
+ return "standalone_event";
29
+ }
30
+ const prepareTimelineItems = (items) => {
31
+ if (items.length <= 1) return {
32
+ items,
33
+ times: items.map((item) => getTimestamp(item.createdAt)),
34
+ didSort: false
35
+ };
36
+ const times = new Array(items.length);
37
+ let isSorted = true;
38
+ for (let index = 0; index < items.length; index++) {
39
+ const item = items[index];
40
+ const time = getTimestamp(item?.createdAt);
41
+ times[index] = time;
42
+ if (index === 0) continue;
43
+ const previousTime = times[index - 1];
44
+ if (previousTime !== void 0 && time !== void 0 && time < previousTime) isSorted = false;
45
+ }
46
+ if (isSorted) return {
47
+ items,
48
+ times,
49
+ didSort: false
50
+ };
51
+ const entries = items.map((item, index) => ({
52
+ item,
53
+ time: times[index] ?? 0,
54
+ index
55
+ }));
56
+ entries.sort((a, b) => {
57
+ if (a.time === b.time) return a.index - b.index;
58
+ return a.time - b.time;
59
+ });
60
+ return {
61
+ items: entries.map((entry) => entry.item),
62
+ times: entries.map((entry) => entry.time),
63
+ didSort: true
64
+ };
65
+ };
66
+ const isWithinGroupingWindow = (previousTimestamp, currentTimestamp) => currentTimestamp - previousTimestamp <= TIMELINE_GROUP_WINDOW_MS;
67
+ const groupTimelineItems = (items, itemTimes) => {
47
68
  const result = [];
48
- let currentGroup = null;
69
+ let currentMessageGroup = null;
70
+ let currentActivityGroup = null;
49
71
  let currentDayString = null;
72
+ const flushMessageGroup = () => {
73
+ if (!currentMessageGroup) return;
74
+ result.push(currentMessageGroup);
75
+ currentMessageGroup = null;
76
+ };
77
+ const flushActivityGroup = () => {
78
+ if (!currentActivityGroup) return;
79
+ result.push(currentActivityGroup);
80
+ currentActivityGroup = null;
81
+ };
82
+ const flushAllGroups = () => {
83
+ flushMessageGroup();
84
+ flushActivityGroup();
85
+ };
50
86
  const maybeInsertDaySeparator = (itemDate) => {
51
87
  const itemDayString = getDateString(itemDate);
52
- if (currentDayString !== itemDayString) {
53
- if (currentGroup) {
54
- result.push(currentGroup);
55
- currentGroup = null;
56
- }
57
- result.push({
58
- type: "day_separator",
59
- date: createDayDate(itemDayString),
60
- dateString: itemDayString
61
- });
62
- currentDayString = itemDayString;
63
- }
88
+ if (currentDayString === itemDayString) return;
89
+ flushAllGroups();
90
+ result.push({
91
+ type: "day_separator",
92
+ date: createDayDate(itemDayString),
93
+ dateString: itemDayString
94
+ });
95
+ currentDayString = itemDayString;
64
96
  };
65
- for (const item of items) {
66
- const itemDate = toDate(item.createdAt);
97
+ for (let index = 0; index < items.length; index++) {
98
+ const item = items[index];
99
+ if (!item) continue;
100
+ const itemTimestamp = itemTimes[index] ?? getTimestamp(item.createdAt);
101
+ const itemDate = new Date(itemTimestamp);
67
102
  maybeInsertDaySeparator(itemDate);
68
- if (item.type === "event") {
69
- if (currentGroup) {
70
- result.push(currentGroup);
71
- currentGroup = null;
103
+ const groupableType = getGroupableTimelineItemType(item);
104
+ if (groupableType === "message") {
105
+ flushActivityGroup();
106
+ const { senderId, senderType } = getTimelineItemSender(item);
107
+ const previousTimestamp = currentMessageGroup?.lastMessageTime.getTime();
108
+ if (Boolean(currentMessageGroup && currentMessageGroup.senderId === senderId && previousTimestamp !== void 0 && isWithinGroupingWindow(previousTimestamp, itemTimestamp)) && currentMessageGroup) {
109
+ currentMessageGroup.items.push(item);
110
+ currentMessageGroup.lastMessageId = item.id || currentMessageGroup.lastMessageId;
111
+ currentMessageGroup.lastMessageTime = itemDate;
112
+ continue;
72
113
  }
73
- result.push({
74
- type: "timeline_event",
75
- item,
76
- timestamp: itemDate
77
- });
114
+ flushMessageGroup();
115
+ currentMessageGroup = {
116
+ type: "message_group",
117
+ senderId,
118
+ senderType,
119
+ items: [item],
120
+ firstMessageId: item.id || "",
121
+ lastMessageId: item.id || "",
122
+ firstMessageTime: itemDate,
123
+ lastMessageTime: itemDate
124
+ };
78
125
  continue;
79
126
  }
80
- if (item.type === "identification" || item.type === "tool") {
81
- if (currentGroup) {
82
- result.push(currentGroup);
83
- currentGroup = null;
127
+ if (groupableType === "activity") {
128
+ flushMessageGroup();
129
+ const { senderId, senderType } = getTimelineItemSender(item);
130
+ const previousTimestamp = currentActivityGroup?.lastItemTime.getTime();
131
+ if (Boolean(currentActivityGroup && currentActivityGroup.senderId === senderId && previousTimestamp !== void 0 && isWithinGroupingWindow(previousTimestamp, itemTimestamp)) && currentActivityGroup) {
132
+ currentActivityGroup.items.push(item);
133
+ currentActivityGroup.lastItemId = item.id || currentActivityGroup.lastItemId;
134
+ currentActivityGroup.lastItemTime = itemDate;
135
+ currentActivityGroup.hasEvent = currentActivityGroup.hasEvent || item.type === "event";
136
+ currentActivityGroup.hasTool = currentActivityGroup.hasTool || item.type === "tool";
137
+ continue;
84
138
  }
139
+ flushActivityGroup();
140
+ currentActivityGroup = {
141
+ type: "activity_group",
142
+ senderId,
143
+ senderType,
144
+ items: [item],
145
+ firstItemId: item.id || "",
146
+ lastItemId: item.id || "",
147
+ firstItemTime: itemDate,
148
+ lastItemTime: itemDate,
149
+ hasEvent: item.type === "event",
150
+ hasTool: item.type === "tool"
151
+ };
152
+ continue;
153
+ }
154
+ flushAllGroups();
155
+ if (groupableType === "standalone_tool") {
85
156
  result.push({
86
157
  type: "timeline_tool",
87
158
  item,
@@ -90,33 +161,25 @@ const groupTimelineItems = (items) => {
90
161
  });
91
162
  continue;
92
163
  }
93
- const { senderId, senderType } = getSenderIdAndTypeFromTimelineItem(item);
94
- if (currentGroup && currentGroup.senderId === senderId) {
95
- currentGroup.items.push(item);
96
- currentGroup.lastMessageId = item.id || currentGroup.lastMessageId;
97
- currentGroup.lastMessageTime = itemDate;
98
- } else {
99
- if (currentGroup) result.push(currentGroup);
100
- currentGroup = {
101
- type: "message_group",
102
- senderId,
103
- senderType,
104
- items: [item],
105
- firstMessageId: item.id || "",
106
- lastMessageId: item.id || "",
107
- firstMessageTime: itemDate,
108
- lastMessageTime: itemDate
109
- };
110
- }
164
+ result.push({
165
+ type: "timeline_event",
166
+ item,
167
+ timestamp: itemDate
168
+ });
111
169
  }
112
- if (currentGroup) result.push(currentGroup);
170
+ flushAllGroups();
113
171
  return result;
114
172
  };
115
- const buildTimelineReadReceiptData = (seenData, items, sortedMessageItems, sortedMessageTimes) => {
173
+ const buildTimelineReadReceiptData = (seenData, sortedMessageItems, sortedMessageTimes) => {
116
174
  const seenByMap = /* @__PURE__ */ new Map();
117
175
  const lastReadMessageMap = /* @__PURE__ */ new Map();
118
176
  const unreadCountMap = /* @__PURE__ */ new Map();
119
- for (const item of items) if (item.type === "message" && item.id) seenByMap.set(item.id, /* @__PURE__ */ new Set());
177
+ for (const item of sortedMessageItems) if (item.id) seenByMap.set(item.id, /* @__PURE__ */ new Set());
178
+ if (seenData.length === 0 || sortedMessageItems.length === 0) return {
179
+ seenByMap,
180
+ lastReadMessageMap,
181
+ unreadCountMap
182
+ };
120
183
  for (const seen of seenData) {
121
184
  const seenTime = getTimestamp(seen.lastSeenAt);
122
185
  const viewerId = seen.userId || seen.visitorId || seen.aiAgentId;
@@ -127,12 +190,11 @@ const buildTimelineReadReceiptData = (seenData, items, sortedMessageItems, sorte
127
190
  const item = sortedMessageItems[index];
128
191
  if (!item) continue;
129
192
  if ((sortedMessageTimes[index] ?? getTimestamp(item.createdAt)) <= seenTime) {
130
- if (item.id) {
131
- const seenBy = seenByMap.get(item.id);
132
- if (seenBy) seenBy.add(viewerId);
133
- }
193
+ if (item.id) seenByMap.get(item.id)?.add(viewerId);
134
194
  lastReadItem = item;
135
- } else unreadCount++;
195
+ continue;
196
+ }
197
+ unreadCount++;
136
198
  }
137
199
  if (lastReadItem?.id) lastReadMessageMap.set(viewerId, lastReadItem.id);
138
200
  unreadCountMap.set(viewerId, unreadCount);
@@ -151,34 +213,20 @@ const buildTimelineReadReceiptData = (seenData, items, sortedMessageItems, sorte
151
213
  */
152
214
  const useGroupedMessages = ({ items, seenData = [], currentViewerId }) => {
153
215
  return useMemo(() => {
154
- const groupedItems = groupTimelineItems(items);
155
- const messageItems = items.filter((item) => item.type === "message");
156
- let sortedMessageItems = messageItems;
157
- let sortedMessageTimes = messageItems.map((item) => getTimestamp(item.createdAt));
158
- let isSorted = true;
159
- for (let index = 1; index < sortedMessageTimes.length; index++) {
160
- const currentTime = sortedMessageTimes[index];
161
- const previousTime = sortedMessageTimes[index - 1];
162
- if (currentTime !== void 0 && previousTime !== void 0 && currentTime < previousTime) {
163
- isSorted = false;
164
- break;
165
- }
166
- }
167
- if (!isSorted) {
168
- const itemsWithTimes = messageItems.map((item, index) => ({
169
- item,
170
- time: sortedMessageTimes[index] ?? 0
171
- }));
172
- itemsWithTimes.sort((a, b) => a.time - b.time);
173
- sortedMessageItems = itemsWithTimes.map((entry) => entry.item);
174
- sortedMessageTimes = itemsWithTimes.map((entry) => entry.time);
175
- }
216
+ const preparedItems = prepareTimelineItems(items);
217
+ const groupedItems = groupTimelineItems(preparedItems.items, preparedItems.times);
218
+ const sortedMessageItems = [];
219
+ const sortedMessageTimes = [];
176
220
  const messageIndexMap = /* @__PURE__ */ new Map();
177
- for (let i = 0; i < sortedMessageItems.length; i++) {
178
- const item = sortedMessageItems[i];
179
- if (item?.id) messageIndexMap.set(item.id, i);
221
+ for (let index = 0; index < preparedItems.items.length; index++) {
222
+ const item = preparedItems.items[index];
223
+ if (item?.type !== "message") continue;
224
+ const messageIndex = sortedMessageItems.length;
225
+ sortedMessageItems.push(item);
226
+ sortedMessageTimes.push(preparedItems.times[index] ?? getTimestamp(item.createdAt));
227
+ if (item.id) messageIndexMap.set(item.id, messageIndex);
180
228
  }
181
- const { seenByMap, lastReadMessageMap, unreadCountMap } = buildTimelineReadReceiptData(seenData, items, sortedMessageItems, sortedMessageTimes);
229
+ const { seenByMap, lastReadMessageMap, unreadCountMap } = buildTimelineReadReceiptData(seenData, sortedMessageItems, sortedMessageTimes);
182
230
  const seenByArrayCache = /* @__PURE__ */ new Map();
183
231
  return {
184
232
  items: groupedItems,
@@ -221,5 +269,5 @@ const useGroupedMessages = ({ items, seenData = [], currentViewerId }) => {
221
269
  };
222
270
 
223
271
  //#endregion
224
- export { useGroupedMessages };
272
+ export { TIMELINE_GROUP_WINDOW_MS, buildTimelineReadReceiptData, groupTimelineItems, prepareTimelineItems, useGroupedMessages };
225
273
  //# sourceMappingURL=use-grouped-messages.js.map