All files / use-signal/src store.ts

100% Statements 24/24
78.57% Branches 11/14
100% Functions 11/11
100% Lines 23/23

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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133                                  1x               191x 45x               191x                   51x 51x   51x 51x     51x 13x                     420x 420x                   140x         140x   140x 140x 140x     143x                 14x                 8x                 3x                 15x               42x    
/**
 * Internal Signal Store for cross-component communication
 * This module manages signal versions and subscribers for the useSignal hook.
 *
 * @internal This module is not exported publicly
 */
 
/** Signal data structure for each named signal */
interface SignalData {
  version: number;
  subscribers: Set<() => void>;
  emitCount: number;
  timestamp: number;
  data: unknown;
}
 
/** Map of signal name -> SignalData */
const signalStore = new Map<string, SignalData>();
 
/**
 * Get or create signal data for a given name
 * @param name - The signal name
 * @returns SignalData object
 */
function getOrCreateSignal(name: string): SignalData {
  if (!signalStore.has(name)) {
    signalStore.set(name, {
      version: 0,
      subscribers: new Set(),
      emitCount: 0,
      timestamp: 0,
      data: undefined,
    });
  }
  return signalStore.get(name)!;
}
 
/**
 * Subscribe a listener to changes for a specific signal name
 * @param name - The signal name to subscribe to
 * @param listener - Callback to invoke when the signal is emitted
 * @returns Unsubscribe function
 */
export function subscribe(name: string, listener: () => void): () => void {
  const signal = getOrCreateSignal(name);
  signal.subscribers.add(listener);
 
  return () => {
    signal.subscribers.delete(listener);
 
    // Cleanup: remove the signal entry if no more subscribers and never emitted
    if (signal.subscribers.size === 0 && signal.emitCount === 0) {
      signalStore.delete(name);
    }
  };
}
 
/**
 * Get the current version number for a signal
 * @param name - The signal name
 * @returns Current version number (0 if signal doesn't exist)
 */
export function getSnapshot(name: string): number {
  const signal = signalStore.get(name);
  return signal?.version ?? 0;
}
 
/**
 * Emit a signal - update data, increment version, update metadata, and notify all subscribers
 * Data is set BEFORE version increment to ensure useEffect callbacks see the latest data.
 * @param name - The signal name to emit
 * @param data - Optional data to pass with the signal
 */
export function emit(name: string, data?: unknown): void {
  const signal = getOrCreateSignal(name);
 
  // Set data FIRST before incrementing version
  // This ensures that when useEffect runs due to signal change,
  // info.data already contains the latest value
  signal.data = data;
 
  signal.version += 1;
  signal.emitCount += 1;
  signal.timestamp = Date.now();
 
  // Notify all subscribers
  signal.subscribers.forEach((listener) => listener());
}
 
/**
 * Get the current subscriber count for a signal
 * @param name - The signal name
 * @returns Number of active subscribers
 */
export function getSubscriberCount(name: string): number {
  return signalStore.get(name)?.subscribers.size ?? 0;
}
 
/**
 * Get the total emit count for a signal
 * @param name - The signal name
 * @returns Total number of times the signal has been emitted
 */
export function getEmitCount(name: string): number {
  return signalStore.get(name)?.emitCount ?? 0;
}
 
/**
 * Get the last emit timestamp for a signal
 * @param name - The signal name
 * @returns Timestamp of last emit (0 if never emitted)
 */
export function getTimestamp(name: string): number {
  return signalStore.get(name)?.timestamp ?? 0;
}
 
/**
 * Get the data passed with the last emit for a signal
 * @param name - The signal name
 * @returns Data from last emit (undefined if never emitted or no data)
 */
export function getData(name: string): unknown {
  return signalStore.get(name)?.data;
}
 
/**
 * Clear all signals (for testing purposes)
 * @internal
 */
export function clearAllSignals(): void {
  signalStore.clear();
}