@emeryld/rrroutes-client 2.6.0 → 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,65 +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;
1745
+ const { built, toRooms, applySocket, useSocketClient: useSocketClient2, debug } = options;
1660
1746
  const { useEndpoint: useInnerEndpoint, ...rest } = built;
1661
1747
  const useEndpoint = (...useArgs) => {
1662
- const client = useSocketClient2();
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
+ }
1663
1756
  const endpointResult = useInnerEndpoint(
1664
1757
  ...useArgs
1665
1758
  );
1666
- const argsKey = (0, import_react4.useMemo)(() => JSON.stringify(useArgs[0] ?? null), [useArgs]);
1759
+ const argsKey = (0, import_react4.useMemo)(() => safeJsonKey(useArgs[0] ?? null), [useArgs]);
1667
1760
  const [roomState, setRoomState] = (0, import_react4.useState)(
1668
1761
  () => roomsFromData(endpointResult.data, toRooms)
1669
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;
1670
1770
  const roomsKey = (0, import_react4.useMemo)(() => roomState.rooms.join("|"), [roomState.rooms]);
1671
1771
  const joinMetaKey = (0, import_react4.useMemo)(
1672
- () => JSON.stringify(roomState.joinMeta ?? null),
1772
+ () => safeJsonKey(roomState.joinMeta ?? null),
1673
1773
  [roomState.joinMeta]
1674
1774
  );
1675
1775
  const leaveMetaKey = (0, import_react4.useMemo)(
1676
- () => JSON.stringify(roomState.leaveMeta ?? null),
1776
+ () => safeJsonKey(roomState.leaveMeta ?? null),
1677
1777
  [roomState.leaveMeta]
1678
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
+ });
1679
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
+ });
1680
1809
  const unsubscribe = endpointResult.onReceive((data) => {
1681
- setRoomState(
1682
- (prev) => mergeRoomState(prev, toRooms(data))
1683
- );
1810
+ setRoomState((prev) => {
1811
+ const next = mergeRoomState(prev, toRooms(data));
1812
+ return roomStateEqual(prev, next) ? prev : next;
1813
+ });
1684
1814
  });
1685
1815
  return unsubscribe;
1686
- }, [endpointResult, toRooms]);
1816
+ }, [endpointResult, toRooms, debug]);
1687
1817
  (0, import_react4.useEffect)(() => {
1688
- setRoomState(
1689
- 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
1690
1830
  );
1691
- }, [endpointResult.data, toRooms]);
1831
+ setRoomState((prev) => roomStateEqual(prev, next) ? prev : next);
1832
+ }, [endpointResult.data, toRooms, debug]);
1692
1833
  (0, import_react4.useEffect)(() => {
1693
- 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
+ }
1694
1864
  const { joinMeta, leaveMeta } = roomState;
1695
- 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
+ }
1696
1874
  let active = true;
1875
+ dbg2(debug, {
1876
+ type: "room",
1877
+ phase: "join_attempt",
1878
+ rooms: roomState.rooms
1879
+ });
1697
1880
  (async () => {
1698
1881
  try {
1699
1882
  await client.joinRooms(roomState.rooms, joinMeta);
1700
- } catch {
1883
+ } catch (err) {
1884
+ dbg2(debug, {
1885
+ type: "room",
1886
+ phase: "join_error",
1887
+ rooms: roomState.rooms,
1888
+ err: String(err)
1889
+ });
1701
1890
  }
1702
1891
  })();
1703
1892
  return () => {
1704
- if (!active || roomState.rooms.length === 0) return;
1893
+ if (!active) return;
1705
1894
  active = false;
1706
- 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
+ });
1707
1916
  });
1708
1917
  };
1709
- }, [
1710
- client,
1711
- roomsKey,
1712
- roomState.joinMeta,
1713
- roomState.leaveMeta,
1714
- joinMetaKey,
1715
- leaveMetaKey
1716
- ]);
1918
+ }, [client, roomsKey, joinMetaKey, leaveMetaKey, debug]);
1717
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;
1718
1933
  const entries = Object.entries(applySocket).filter(
1719
1934
  ([_event, fn]) => typeof fn === "function"
1720
1935
  );
@@ -1723,8 +1938,12 @@ function buildSocketedRoute(options) {
1723
1938
  built.setData(
1724
1939
  (prev) => {
1725
1940
  const next = fn(prev, payload, meta);
1941
+ const nextRoomState = roomsFromData(
1942
+ next,
1943
+ toRooms
1944
+ );
1726
1945
  setRoomState(
1727
- roomsFromData(next, toRooms)
1946
+ (prevRoomState) => roomStateEqual(prevRoomState, nextRoomState) ? prevRoomState : nextRoomState
1728
1947
  );
1729
1948
  return next;
1730
1949
  },
@@ -1733,7 +1952,7 @@ function buildSocketedRoute(options) {
1733
1952
  })
1734
1953
  );
1735
1954
  return () => unsubscribes.forEach((u) => u?.());
1736
- }, [client, applySocket, built, argsKey, toRooms]);
1955
+ }, [client, applySocket, built, argsKey, toRooms, debug]);
1737
1956
  return { ...endpointResult, rooms: roomState.rooms };
1738
1957
  };
1739
1958
  return {