All files / src/hooks useCommentsRealtime.ts

100% Statements 32/32
100% Branches 8/8
100% Functions 1/1
100% Lines 32/32

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 511x 1x     1x                           1x 9x 9x 9x 9x 9x 9x   9x 8x   6x 6x 6x 6x 2x 2x 3x 2x 2x 2x 6x 6x 6x 6x 6x   6x 6x 6x 9x 9x  
import { useEffect, useRef } from 'react';
import { Effect, Stream, Fiber } from 'effect';
 
// Services
import { subscribeService } from '@/services/subscription';
 
// Types
import { MenuSocketUpdate } from '@/types/meal';
 
/**
 * Subscribe to the lunch-menu socket and call `onUpdate` for each update
 * (reaction / sentiment / comment-change).
 *
 * `onUpdate` is held in a ref so it can change every render without re-opening
 * the WebSocket — only `enabled` / `origin` trigger (re)subscription.
 *
 * Best-effort: stream errors are swallowed; the dashboard re-syncs via its REST refetch.
 */
export const useCommentsRealtime = (
  onUpdate: (update: MenuSocketUpdate) => void,
  enabled: boolean,
  origin?: string,
) => {
  const callbackRef = useRef(onUpdate);
  callbackRef.current = onUpdate;
 
  useEffect(() => {
    if (!enabled) return;
 
    const fiber = Effect.runFork(
      Effect.scoped(
        subscribeService(origin).pipe(
          Effect.flatMap((stream) =>
            stream.pipe(
              Stream.tap((update) =>
                Effect.sync(() => callbackRef.current(update)),
              ),
              Stream.runDrain,
            ),
          ),
          Effect.catchAll(() => Effect.void),
        ),
      ),
    );
 
    return () => {
      Effect.runFork(Fiber.interrupt(fiber));
    };
  }, [enabled, origin]);
};