@emeryld/rrroutes-client 2.5.10 → 2.6.1

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.
package/dist/index.cjs CHANGED
@@ -1618,6 +1618,92 @@ function normalizeRooms(rooms) {
1618
1618
  }
1619
1619
  return normalized;
1620
1620
  }
1621
+ var objectReferenceIds = /* @__PURE__ */ new WeakMap();
1622
+ var objectReferenceCounter = 0;
1623
+ function describeObjectReference(value) {
1624
+ if (value == null) return null;
1625
+ const valueType = typeof value;
1626
+ if (valueType !== "object" && valueType !== "function") return null;
1627
+ const obj = value;
1628
+ let id = objectReferenceIds.get(obj);
1629
+ if (!id) {
1630
+ id = ++objectReferenceCounter;
1631
+ objectReferenceIds.set(obj, id);
1632
+ }
1633
+ return `ref#${id}`;
1634
+ }
1635
+ function safeJsonKey(value) {
1636
+ try {
1637
+ const serialized = JSON.stringify(value ?? null);
1638
+ return serialized ?? "null";
1639
+ } catch {
1640
+ return "[unserializable]";
1641
+ }
1642
+ }
1643
+ function arrayShallowEqual(a, b) {
1644
+ if (a.length !== b.length) return false;
1645
+ for (let i = 0; i < a.length; i += 1) {
1646
+ if (a[i] !== b[i]) return false;
1647
+ }
1648
+ return true;
1649
+ }
1650
+ function roomStateEqual(prev, next) {
1651
+ return arrayShallowEqual(prev.rooms, next.rooms) && safeJsonKey(prev.joinMeta) === safeJsonKey(next.joinMeta) && safeJsonKey(prev.leaveMeta) === safeJsonKey(next.leaveMeta);
1652
+ }
1653
+ function safeDescribeHookValue2(value) {
1654
+ if (value == null) return value;
1655
+ const valueType = typeof value;
1656
+ if (valueType === "string" || valueType === "number" || valueType === "boolean") {
1657
+ return value;
1658
+ }
1659
+ if (valueType === "bigint" || valueType === "symbol") return String(value);
1660
+ if (valueType === "function")
1661
+ return `[function ${value.name || "anonymous"}]`;
1662
+ if (Array.isArray(value)) return `[array length=${value.length}]`;
1663
+ const ctorName = value.constructor?.name ?? "object";
1664
+ const keys = Object.keys(value);
1665
+ const keyPreview = keys.slice(0, 4).join(",");
1666
+ const suffix = keys.length > 4 ? ",\u2026" : "";
1667
+ const ref = describeObjectReference(value);
1668
+ return `[${ctorName}${ref ? ` ${ref}` : ""} keys=${keyPreview}${suffix}]`;
1669
+ }
1670
+ function createHookDebugEvent2(prev, next, phase) {
1671
+ const reason = prev ? "change" : "init";
1672
+ const changed = Object.keys(next).reduce((acc, dependency) => {
1673
+ const prevValue = prev ? prev[dependency] : void 0;
1674
+ const nextValue = next[dependency];
1675
+ if (!prev || !Object.is(prevValue, nextValue)) {
1676
+ acc.push({
1677
+ dependency,
1678
+ previous: safeDescribeHookValue2(prevValue),
1679
+ next: safeDescribeHookValue2(nextValue)
1680
+ });
1681
+ }
1682
+ return acc;
1683
+ }, []);
1684
+ if (!changed.length) return null;
1685
+ return { type: "hook", phase, reason, changes: changed };
1686
+ }
1687
+ function dbg2(debug, event) {
1688
+ if (!debug?.logger) return;
1689
+ if (!debug[event.type]) return;
1690
+ debug.logger(event);
1691
+ }
1692
+ function trackHookTrigger2({
1693
+ ref,
1694
+ phase,
1695
+ debug,
1696
+ snapshot
1697
+ }) {
1698
+ const prev = ref.current;
1699
+ ref.current = snapshot;
1700
+ if (!debug?.logger || !debug?.hook) return;
1701
+ const event = createHookDebugEvent2(prev, snapshot, phase);
1702
+ if (event) dbg2(debug, event);
1703
+ }
1704
+ function isSocketClientUnavailableError(err) {
1705
+ return err instanceof Error && err.message.includes("SocketClient not found");
1706
+ }
1621
1707
  function mergeRoomState(prev, toRoomsResult) {
1622
1708
  const merged = new Set(prev.rooms);
1623
1709
  for (const r of normalizeRooms(toRoomsResult.rooms)) merged.add(r);
@@ -1656,64 +1742,194 @@ function roomsFromData(data, toRooms) {
1656
1742
  return state;
1657
1743
  }
1658
1744
  function buildSocketedRoute(options) {
1659
- const { built, toRooms, applySocket, useSocketClient: useSocketClient2 } = options;
1660
- return (...useArgs) => {
1661
- const client = useSocketClient2();
1662
- const endpointResult = built.useEndpoint(
1745
+ const { built, toRooms, applySocket, useSocketClient: useSocketClient2, debug } = options;
1746
+ const { useEndpoint: useInnerEndpoint, ...rest } = built;
1747
+ const useEndpoint = (...useArgs) => {
1748
+ let client = null;
1749
+ let socketClientError = null;
1750
+ try {
1751
+ client = useSocketClient2();
1752
+ } catch (err) {
1753
+ if (!isSocketClientUnavailableError(err)) throw err;
1754
+ socketClientError = err;
1755
+ }
1756
+ const endpointResult = useInnerEndpoint(
1663
1757
  ...useArgs
1664
1758
  );
1665
- const argsKey = (0, import_react4.useMemo)(() => JSON.stringify(useArgs[0] ?? null), [useArgs]);
1759
+ const argsKey = (0, import_react4.useMemo)(() => safeJsonKey(useArgs[0] ?? null), [useArgs]);
1666
1760
  const [roomState, setRoomState] = (0, import_react4.useState)(
1667
1761
  () => roomsFromData(endpointResult.data, toRooms)
1668
1762
  );
1763
+ const renderCountRef = (0, import_react4.useRef)(0);
1764
+ const clientReadyRef = (0, import_react4.useRef)(null);
1765
+ const onReceiveEffectDebugRef = (0, import_react4.useRef)(null);
1766
+ const deriveRoomsEffectDebugRef = (0, import_react4.useRef)(null);
1767
+ const joinRoomsEffectDebugRef = (0, import_react4.useRef)(null);
1768
+ const applySocketEffectDebugRef = (0, import_react4.useRef)(null);
1769
+ renderCountRef.current += 1;
1669
1770
  const roomsKey = (0, import_react4.useMemo)(() => roomState.rooms.join("|"), [roomState.rooms]);
1670
1771
  const joinMetaKey = (0, import_react4.useMemo)(
1671
- () => JSON.stringify(roomState.joinMeta ?? null),
1772
+ () => safeJsonKey(roomState.joinMeta ?? null),
1672
1773
  [roomState.joinMeta]
1673
1774
  );
1674
1775
  const leaveMetaKey = (0, import_react4.useMemo)(
1675
- () => JSON.stringify(roomState.leaveMeta ?? null),
1776
+ () => safeJsonKey(roomState.leaveMeta ?? null),
1676
1777
  [roomState.leaveMeta]
1677
1778
  );
1779
+ const hasClient = !!client;
1780
+ if (clientReadyRef.current !== hasClient) {
1781
+ clientReadyRef.current = hasClient;
1782
+ dbg2(debug, {
1783
+ type: "client",
1784
+ phase: hasClient ? "ready" : "missing",
1785
+ err: hasClient ? void 0 : String(socketClientError)
1786
+ });
1787
+ }
1788
+ dbg2(debug, {
1789
+ type: "render",
1790
+ renderCount: renderCountRef.current,
1791
+ hasClient,
1792
+ argsKey,
1793
+ rooms: roomState.rooms,
1794
+ roomsKey,
1795
+ joinMetaKey,
1796
+ leaveMetaKey
1797
+ });
1678
1798
  (0, import_react4.useEffect)(() => {
1799
+ trackHookTrigger2({
1800
+ ref: onReceiveEffectDebugRef,
1801
+ phase: "endpoint_on_receive_effect",
1802
+ debug,
1803
+ snapshot: {
1804
+ endpointResultRef: describeObjectReference(endpointResult),
1805
+ endpointDataRef: describeObjectReference(endpointResult.data),
1806
+ toRoomsRef: describeObjectReference(toRooms)
1807
+ }
1808
+ });
1679
1809
  const unsubscribe = endpointResult.onReceive((data) => {
1680
- setRoomState(
1681
- (prev) => mergeRoomState(prev, toRooms(data))
1682
- );
1810
+ setRoomState((prev) => {
1811
+ const next = mergeRoomState(prev, toRooms(data));
1812
+ return roomStateEqual(prev, next) ? prev : next;
1813
+ });
1683
1814
  });
1684
1815
  return unsubscribe;
1685
- }, [endpointResult, toRooms]);
1816
+ }, [endpointResult, toRooms, debug]);
1686
1817
  (0, import_react4.useEffect)(() => {
1687
- setRoomState(
1688
- roomsFromData(endpointResult.data, toRooms)
1818
+ trackHookTrigger2({
1819
+ ref: deriveRoomsEffectDebugRef,
1820
+ phase: "derive_rooms_effect",
1821
+ debug,
1822
+ snapshot: {
1823
+ endpointDataRef: describeObjectReference(endpointResult.data),
1824
+ toRoomsRef: describeObjectReference(toRooms)
1825
+ }
1826
+ });
1827
+ const next = roomsFromData(
1828
+ endpointResult.data,
1829
+ toRooms
1689
1830
  );
1690
- }, [endpointResult.data, toRooms]);
1831
+ setRoomState((prev) => roomStateEqual(prev, next) ? prev : next);
1832
+ }, [endpointResult.data, toRooms, debug]);
1691
1833
  (0, import_react4.useEffect)(() => {
1692
- if (roomState.rooms.length === 0) return;
1834
+ trackHookTrigger2({
1835
+ ref: joinRoomsEffectDebugRef,
1836
+ phase: "join_rooms_effect",
1837
+ debug,
1838
+ snapshot: {
1839
+ hasClient: !!client,
1840
+ clientRef: describeObjectReference(client),
1841
+ roomsKey,
1842
+ joinMetaKey,
1843
+ leaveMetaKey
1844
+ }
1845
+ });
1846
+ if (!client) {
1847
+ dbg2(debug, {
1848
+ type: "room",
1849
+ phase: "join_skip",
1850
+ rooms: roomState.rooms,
1851
+ reason: "socket_client_missing"
1852
+ });
1853
+ return;
1854
+ }
1855
+ if (roomState.rooms.length === 0) {
1856
+ dbg2(debug, {
1857
+ type: "room",
1858
+ phase: "join_skip",
1859
+ rooms: [],
1860
+ reason: "no_rooms"
1861
+ });
1862
+ return;
1863
+ }
1693
1864
  const { joinMeta, leaveMeta } = roomState;
1694
- if (!joinMeta || !leaveMeta) return;
1865
+ if (!joinMeta || !leaveMeta) {
1866
+ dbg2(debug, {
1867
+ type: "room",
1868
+ phase: "join_skip",
1869
+ rooms: roomState.rooms,
1870
+ reason: "missing_meta"
1871
+ });
1872
+ return;
1873
+ }
1695
1874
  let active = true;
1875
+ dbg2(debug, {
1876
+ type: "room",
1877
+ phase: "join_attempt",
1878
+ rooms: roomState.rooms
1879
+ });
1696
1880
  (async () => {
1697
1881
  try {
1698
1882
  await client.joinRooms(roomState.rooms, joinMeta);
1699
- } catch {
1883
+ } catch (err) {
1884
+ dbg2(debug, {
1885
+ type: "room",
1886
+ phase: "join_error",
1887
+ rooms: roomState.rooms,
1888
+ err: String(err)
1889
+ });
1700
1890
  }
1701
1891
  })();
1702
1892
  return () => {
1703
- if (!active || roomState.rooms.length === 0) return;
1893
+ if (!active) return;
1704
1894
  active = false;
1705
- void client.leaveRooms(roomState.rooms, leaveMeta).catch(() => {
1895
+ if (roomState.rooms.length === 0) {
1896
+ dbg2(debug, {
1897
+ type: "room",
1898
+ phase: "leave_skip",
1899
+ rooms: [],
1900
+ reason: "no_rooms"
1901
+ });
1902
+ return;
1903
+ }
1904
+ dbg2(debug, {
1905
+ type: "room",
1906
+ phase: "leave_attempt",
1907
+ rooms: roomState.rooms
1908
+ });
1909
+ void client.leaveRooms(roomState.rooms, leaveMeta).catch((err) => {
1910
+ dbg2(debug, {
1911
+ type: "room",
1912
+ phase: "leave_error",
1913
+ rooms: roomState.rooms,
1914
+ err: String(err)
1915
+ });
1706
1916
  });
1707
1917
  };
1708
- }, [
1709
- client,
1710
- roomsKey,
1711
- roomState.joinMeta,
1712
- roomState.leaveMeta,
1713
- joinMetaKey,
1714
- leaveMetaKey
1715
- ]);
1918
+ }, [client, roomsKey, joinMetaKey, leaveMetaKey, debug]);
1716
1919
  (0, import_react4.useEffect)(() => {
1920
+ trackHookTrigger2({
1921
+ ref: applySocketEffectDebugRef,
1922
+ phase: "apply_socket_effect",
1923
+ debug,
1924
+ snapshot: {
1925
+ hasClient: !!client,
1926
+ clientRef: describeObjectReference(client),
1927
+ applySocketKeys: Object.keys(applySocket).sort().join(","),
1928
+ argsKey,
1929
+ toRoomsRef: describeObjectReference(toRooms)
1930
+ }
1931
+ });
1932
+ if (!client) return;
1717
1933
  const entries = Object.entries(applySocket).filter(
1718
1934
  ([_event, fn]) => typeof fn === "function"
1719
1935
  );
@@ -1722,8 +1938,12 @@ function buildSocketedRoute(options) {
1722
1938
  built.setData(
1723
1939
  (prev) => {
1724
1940
  const next = fn(prev, payload, meta);
1941
+ const nextRoomState = roomsFromData(
1942
+ next,
1943
+ toRooms
1944
+ );
1725
1945
  setRoomState(
1726
- roomsFromData(next, toRooms)
1946
+ (prevRoomState) => roomStateEqual(prevRoomState, nextRoomState) ? prevRoomState : nextRoomState
1727
1947
  );
1728
1948
  return next;
1729
1949
  },
@@ -1732,9 +1952,13 @@ function buildSocketedRoute(options) {
1732
1952
  })
1733
1953
  );
1734
1954
  return () => unsubscribes.forEach((u) => u?.());
1735
- }, [client, applySocket, built, argsKey, toRooms]);
1955
+ }, [client, applySocket, built, argsKey, toRooms, debug]);
1736
1956
  return { ...endpointResult, rooms: roomState.rooms };
1737
1957
  };
1958
+ return {
1959
+ ...rest,
1960
+ useEndpoint
1961
+ };
1738
1962
  }
1739
1963
 
1740
1964
  // src/sockets/socket.client.index.ts