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