Manages chunk buffering, deduplication, and ordered replay of NATS message chunks during dialog catchup, ensuring historical and live-streamed chunks are merged without gaps or duplicates. ## Key Components ### Internal Helpers - **`makeSeqKey(messageType, chunkType, sequenceId)`** — Generates a composite key for sequence-level deduplication across message types. - **`makeBatchDedupKey(item)`** — Generates a broader deduplication key for batch processing, incorporating text, tool, and approval fields. - **`getChatTypeMessageType(chatType)`** — Maps a `ChatType` to its corresponding `NatsMessageType` (`admin-message` or `message`). ### Hook: `useChunkCatchup` **Parameters** (`UseChunkCatchupOptions`): | Parameter | Description | |---|---| | `dialogId` | Active dialog identifier | | `onChunkReceived` | Callback fired for each processed chunk | | `chatTypes` | Array of chat types to fetch (default: `[CHAT_TYPE.CLIENT]`) | | `fetchChunks` | Async function to retrieve historical chunks from the API | **Internal Refs:** | Ref | Purpose | |---|---| | `processedSequenceKeys` | Tracks already-processed sequence keys to prevent duplicates | | `lastSequenceIdByType` | Per-`NatsMessageType` resume checkpoint (prevents cross-stream offset contamination) | | `fetchingInProgress` | Guards against concurrent fetches | | `pendingCatchupRef` | Queues a second catchup requested during an in-flight fetch | | `chunkBuffer` | Buffers live NATS chunks arriving during catchup | **Returned Callbacks** (`UseChunkCatchupReturn`): - **`processChunk(chunk, messageType, forceProcess?)`** — Routes an incoming chunk to the buffer or directly to `onChunkReceived`, recording its sequence key. - **`flushBufferedRealtimeChunks()`** — Drains the buffer after catchup, sorting chunks so id-less live chunks appear after sequenced history. - **`catchUpChunks(fromSequenceId?)`** — Orchestrates the full catchup flow: fetches historical chunks per chat type using per-type checkpoints, merges with buffered live chunks, sorts, deduplicates, and processes in order up to the last complete message boundary. ## Usage Example ```typescript const { processChunk, catchUpChunks } = useChunkCatchup({ dialogId: 'dialog-abc123', chatTypes: [CHAT_TYPE.CLIENT, CHAT_TYPE.ADMIN], fetchChunks: async (dialogId, chatType, fromSequenceId) => { const res = await api.getChunks({ dialogId, chatType, fromSequenceId }) return res.chunks }, onChunkReceived: (chunk, messageType) => { appendChunkToTranscript(chunk, messageType) }, }) // Called when a live NATS message arrives natsSubscription.on('message', (chunk, messageType) => { processChunk(chunk, messageType) }) // Called on dialog load or reconnect await catchUpChunks() ``` ## Notes - Per-type sequence tracking (`lastSequenceIdByType`) prevents a faster stream's high sequence ID from skipping chunks in a slower, independently-numbered stream (legacy Redis transport behaviour). - Pending catchup requests are queued rather than dropped to avoid permanent transcript gaps on double-reconnect. - Chunks without a `sequenceId` are treated as the newest live events and sorted after all sequenced history. - Deduplication only applies to chunks carrying a `sequenceId`; id-less streaming deltas are always preserved to avoid silent content loss. **Source:** [`use-chunk-catchup.ts`](https://github.com/flamingo-stack/openframe-oss-lib/blob/main/use-chunk-catchup.ts)