@emeryld/rrroutes-client 2.6.0 → 2.6.2

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
@@ -1557,7 +1557,9 @@ function useSocketConnection(args) {
1557
1557
  React.useEffect(() => {
1558
1558
  if (autoJoin && normalizedRooms.length > 0)
1559
1559
  client.joinRooms(normalizedRooms, args.joinMeta);
1560
- const unsubscribe = client.on(event, onMessage);
1560
+ const unsubscribe = client.on(event, (payload, meta) => {
1561
+ onMessage(payload, meta);
1562
+ });
1561
1563
  return () => {
1562
1564
  unsubscribe();
1563
1565
  if (autoLeave && normalizedRooms.length > 0)
@@ -1568,7 +1570,7 @@ function useSocketConnection(args) {
1568
1570
  }
1569
1571
 
1570
1572
  // src/sockets/socketedRoute/socket.client.helper.ts
1571
- import { useEffect as useEffect2, useMemo as useMemo2, useState as useState2 } from "react";
1573
+ import { useEffect as useEffect2, useMemo as useMemo2, useRef as useRef5, useState as useState2 } from "react";
1572
1574
  function normalizeRooms(rooms) {
1573
1575
  if (rooms == null) return [];
1574
1576
  const list = Array.isArray(rooms) ? rooms : [rooms];
@@ -1582,6 +1584,92 @@ function normalizeRooms(rooms) {
1582
1584
  }
1583
1585
  return normalized;
1584
1586
  }
1587
+ var objectReferenceIds = /* @__PURE__ */ new WeakMap();
1588
+ var objectReferenceCounter = 0;
1589
+ function describeObjectReference(value) {
1590
+ if (value == null) return null;
1591
+ const valueType = typeof value;
1592
+ if (valueType !== "object" && valueType !== "function") return null;
1593
+ const obj = value;
1594
+ let id = objectReferenceIds.get(obj);
1595
+ if (!id) {
1596
+ id = ++objectReferenceCounter;
1597
+ objectReferenceIds.set(obj, id);
1598
+ }
1599
+ return `ref#${id}`;
1600
+ }
1601
+ function safeJsonKey(value) {
1602
+ try {
1603
+ const serialized = JSON.stringify(value ?? null);
1604
+ return serialized ?? "null";
1605
+ } catch {
1606
+ return "[unserializable]";
1607
+ }
1608
+ }
1609
+ function arrayShallowEqual(a, b) {
1610
+ if (a.length !== b.length) return false;
1611
+ for (let i = 0; i < a.length; i += 1) {
1612
+ if (a[i] !== b[i]) return false;
1613
+ }
1614
+ return true;
1615
+ }
1616
+ function roomStateEqual(prev, next) {
1617
+ return arrayShallowEqual(prev.rooms, next.rooms) && safeJsonKey(prev.joinMeta) === safeJsonKey(next.joinMeta) && safeJsonKey(prev.leaveMeta) === safeJsonKey(next.leaveMeta);
1618
+ }
1619
+ function safeDescribeHookValue2(value) {
1620
+ if (value == null) return value;
1621
+ const valueType = typeof value;
1622
+ if (valueType === "string" || valueType === "number" || valueType === "boolean") {
1623
+ return value;
1624
+ }
1625
+ if (valueType === "bigint" || valueType === "symbol") return String(value);
1626
+ if (valueType === "function")
1627
+ return `[function ${value.name || "anonymous"}]`;
1628
+ if (Array.isArray(value)) return `[array length=${value.length}]`;
1629
+ const ctorName = value.constructor?.name ?? "object";
1630
+ const keys = Object.keys(value);
1631
+ const keyPreview = keys.slice(0, 4).join(",");
1632
+ const suffix = keys.length > 4 ? ",\u2026" : "";
1633
+ const ref = describeObjectReference(value);
1634
+ return `[${ctorName}${ref ? ` ${ref}` : ""} keys=${keyPreview}${suffix}]`;
1635
+ }
1636
+ function createHookDebugEvent2(prev, next, phase) {
1637
+ const reason = prev ? "change" : "init";
1638
+ const changed = Object.keys(next).reduce((acc, dependency) => {
1639
+ const prevValue = prev ? prev[dependency] : void 0;
1640
+ const nextValue = next[dependency];
1641
+ if (!prev || !Object.is(prevValue, nextValue)) {
1642
+ acc.push({
1643
+ dependency,
1644
+ previous: safeDescribeHookValue2(prevValue),
1645
+ next: safeDescribeHookValue2(nextValue)
1646
+ });
1647
+ }
1648
+ return acc;
1649
+ }, []);
1650
+ if (!changed.length) return null;
1651
+ return { type: "hook", phase, reason, changes: changed };
1652
+ }
1653
+ function dbg2(debug, event) {
1654
+ if (!debug?.logger) return;
1655
+ if (!debug[event.type]) return;
1656
+ debug.logger(event);
1657
+ }
1658
+ function trackHookTrigger2({
1659
+ ref,
1660
+ phase,
1661
+ debug,
1662
+ snapshot
1663
+ }) {
1664
+ const prev = ref.current;
1665
+ ref.current = snapshot;
1666
+ if (!debug?.logger || !debug?.hook) return;
1667
+ const event = createHookDebugEvent2(prev, snapshot, phase);
1668
+ if (event) dbg2(debug, event);
1669
+ }
1670
+ function isSocketClientUnavailableError(err) {
1671
+ return err instanceof Error && err.message.includes("SocketClient not found");
1672
+ }
1585
1673
  function mergeRoomState(prev, toRoomsResult) {
1586
1674
  const merged = new Set(prev.rooms);
1587
1675
  for (const r of normalizeRooms(toRoomsResult.rooms)) merged.add(r);
@@ -1620,84 +1708,247 @@ function roomsFromData(data, toRooms) {
1620
1708
  return state;
1621
1709
  }
1622
1710
  function buildSocketedRoute(options) {
1623
- const { built, toRooms, applySocket, useSocketClient: useSocketClient2 } = options;
1711
+ const { built, toRooms, applySocket, useSocketClient: useSocketClient2, debug } = options;
1624
1712
  const { useEndpoint: useInnerEndpoint, ...rest } = built;
1625
1713
  const useEndpoint = (...useArgs) => {
1626
- const client = useSocketClient2();
1714
+ let client = null;
1715
+ let socketClientError = null;
1716
+ try {
1717
+ client = useSocketClient2();
1718
+ } catch (err) {
1719
+ if (!isSocketClientUnavailableError(err)) throw err;
1720
+ socketClientError = err;
1721
+ }
1627
1722
  const endpointResult = useInnerEndpoint(
1628
1723
  ...useArgs
1629
1724
  );
1630
- const argsKey = useMemo2(() => JSON.stringify(useArgs[0] ?? null), [useArgs]);
1725
+ const argsKey = useMemo2(() => safeJsonKey(useArgs[0] ?? null), [useArgs]);
1631
1726
  const [roomState, setRoomState] = useState2(
1632
1727
  () => roomsFromData(endpointResult.data, toRooms)
1633
1728
  );
1729
+ const renderCountRef = useRef5(0);
1730
+ const clientReadyRef = useRef5(null);
1731
+ const onReceiveEffectDebugRef = useRef5(null);
1732
+ const deriveRoomsEffectDebugRef = useRef5(null);
1733
+ const joinRoomsEffectDebugRef = useRef5(null);
1734
+ const applySocketEffectDebugRef = useRef5(null);
1735
+ renderCountRef.current += 1;
1634
1736
  const roomsKey = useMemo2(() => roomState.rooms.join("|"), [roomState.rooms]);
1635
1737
  const joinMetaKey = useMemo2(
1636
- () => JSON.stringify(roomState.joinMeta ?? null),
1738
+ () => safeJsonKey(roomState.joinMeta ?? null),
1637
1739
  [roomState.joinMeta]
1638
1740
  );
1639
1741
  const leaveMetaKey = useMemo2(
1640
- () => JSON.stringify(roomState.leaveMeta ?? null),
1742
+ () => safeJsonKey(roomState.leaveMeta ?? null),
1641
1743
  [roomState.leaveMeta]
1642
1744
  );
1745
+ const hasClient = !!client;
1746
+ if (clientReadyRef.current !== hasClient) {
1747
+ clientReadyRef.current = hasClient;
1748
+ dbg2(debug, {
1749
+ type: "client",
1750
+ phase: hasClient ? "ready" : "missing",
1751
+ err: hasClient ? void 0 : String(socketClientError)
1752
+ });
1753
+ }
1754
+ dbg2(debug, {
1755
+ type: "render",
1756
+ renderCount: renderCountRef.current,
1757
+ hasClient,
1758
+ argsKey,
1759
+ rooms: roomState.rooms,
1760
+ roomsKey,
1761
+ joinMetaKey,
1762
+ leaveMetaKey
1763
+ });
1643
1764
  useEffect2(() => {
1765
+ trackHookTrigger2({
1766
+ ref: onReceiveEffectDebugRef,
1767
+ phase: "endpoint_on_receive_effect",
1768
+ debug,
1769
+ snapshot: {
1770
+ endpointResultRef: describeObjectReference(endpointResult),
1771
+ endpointDataRef: describeObjectReference(endpointResult.data),
1772
+ toRoomsRef: describeObjectReference(toRooms)
1773
+ }
1774
+ });
1644
1775
  const unsubscribe = endpointResult.onReceive((data) => {
1645
- setRoomState(
1646
- (prev) => mergeRoomState(prev, toRooms(data))
1647
- );
1776
+ setRoomState((prev) => {
1777
+ const next = mergeRoomState(prev, toRooms(data));
1778
+ return roomStateEqual(prev, next) ? prev : next;
1779
+ });
1648
1780
  });
1649
1781
  return unsubscribe;
1650
- }, [endpointResult, toRooms]);
1782
+ }, [endpointResult, toRooms, debug]);
1651
1783
  useEffect2(() => {
1652
- setRoomState(
1653
- roomsFromData(endpointResult.data, toRooms)
1784
+ trackHookTrigger2({
1785
+ ref: deriveRoomsEffectDebugRef,
1786
+ phase: "derive_rooms_effect",
1787
+ debug,
1788
+ snapshot: {
1789
+ endpointDataRef: describeObjectReference(endpointResult.data),
1790
+ toRoomsRef: describeObjectReference(toRooms)
1791
+ }
1792
+ });
1793
+ const next = roomsFromData(
1794
+ endpointResult.data,
1795
+ toRooms
1654
1796
  );
1655
- }, [endpointResult.data, toRooms]);
1797
+ setRoomState((prev) => roomStateEqual(prev, next) ? prev : next);
1798
+ }, [endpointResult.data, toRooms, debug]);
1656
1799
  useEffect2(() => {
1657
- if (roomState.rooms.length === 0) return;
1800
+ trackHookTrigger2({
1801
+ ref: joinRoomsEffectDebugRef,
1802
+ phase: "join_rooms_effect",
1803
+ debug,
1804
+ snapshot: {
1805
+ hasClient: !!client,
1806
+ clientRef: describeObjectReference(client),
1807
+ roomsKey,
1808
+ joinMetaKey,
1809
+ leaveMetaKey
1810
+ }
1811
+ });
1812
+ if (!client) {
1813
+ dbg2(debug, {
1814
+ type: "room",
1815
+ phase: "join_skip",
1816
+ rooms: roomState.rooms,
1817
+ reason: "socket_client_missing"
1818
+ });
1819
+ return;
1820
+ }
1821
+ if (roomState.rooms.length === 0) {
1822
+ dbg2(debug, {
1823
+ type: "room",
1824
+ phase: "join_skip",
1825
+ rooms: [],
1826
+ reason: "no_rooms"
1827
+ });
1828
+ return;
1829
+ }
1658
1830
  const { joinMeta, leaveMeta } = roomState;
1659
- if (!joinMeta || !leaveMeta) return;
1831
+ if (!joinMeta || !leaveMeta) {
1832
+ dbg2(debug, {
1833
+ type: "room",
1834
+ phase: "join_skip",
1835
+ rooms: roomState.rooms,
1836
+ reason: "missing_meta"
1837
+ });
1838
+ return;
1839
+ }
1660
1840
  let active = true;
1841
+ dbg2(debug, {
1842
+ type: "room",
1843
+ phase: "join_attempt",
1844
+ rooms: roomState.rooms
1845
+ });
1661
1846
  (async () => {
1662
1847
  try {
1663
1848
  await client.joinRooms(roomState.rooms, joinMeta);
1664
- } catch {
1849
+ } catch (err) {
1850
+ dbg2(debug, {
1851
+ type: "room",
1852
+ phase: "join_error",
1853
+ rooms: roomState.rooms,
1854
+ err: String(err)
1855
+ });
1665
1856
  }
1666
1857
  })();
1667
1858
  return () => {
1668
- if (!active || roomState.rooms.length === 0) return;
1859
+ if (!active) return;
1669
1860
  active = false;
1670
- void client.leaveRooms(roomState.rooms, leaveMeta).catch(() => {
1861
+ if (roomState.rooms.length === 0) {
1862
+ dbg2(debug, {
1863
+ type: "room",
1864
+ phase: "leave_skip",
1865
+ rooms: [],
1866
+ reason: "no_rooms"
1867
+ });
1868
+ return;
1869
+ }
1870
+ dbg2(debug, {
1871
+ type: "room",
1872
+ phase: "leave_attempt",
1873
+ rooms: roomState.rooms
1874
+ });
1875
+ void client.leaveRooms(roomState.rooms, leaveMeta).catch((err) => {
1876
+ dbg2(debug, {
1877
+ type: "room",
1878
+ phase: "leave_error",
1879
+ rooms: roomState.rooms,
1880
+ err: String(err)
1881
+ });
1671
1882
  });
1672
1883
  };
1673
- }, [
1674
- client,
1675
- roomsKey,
1676
- roomState.joinMeta,
1677
- roomState.leaveMeta,
1678
- joinMetaKey,
1679
- leaveMetaKey
1680
- ]);
1884
+ }, [client, roomsKey, joinMetaKey, leaveMetaKey, debug]);
1681
1885
  useEffect2(() => {
1886
+ trackHookTrigger2({
1887
+ ref: applySocketEffectDebugRef,
1888
+ phase: "apply_socket_effect",
1889
+ debug,
1890
+ snapshot: {
1891
+ hasClient: !!client,
1892
+ clientRef: describeObjectReference(client),
1893
+ applySocketKeys: Object.keys(applySocket).sort().join(","),
1894
+ argsKey,
1895
+ toRoomsRef: describeObjectReference(toRooms)
1896
+ }
1897
+ });
1898
+ if (!client) return;
1899
+ const queue = [];
1900
+ let draining = false;
1901
+ let active = true;
1902
+ const drainQueue = () => {
1903
+ if (!active || draining) return;
1904
+ draining = true;
1905
+ try {
1906
+ while (active && queue.length > 0) {
1907
+ const nextUpdate = queue.shift();
1908
+ if (!nextUpdate) continue;
1909
+ built.setData(
1910
+ (prev) => {
1911
+ const next = nextUpdate.fn(
1912
+ prev,
1913
+ nextUpdate.payload,
1914
+ nextUpdate.meta ? { ...nextUpdate.meta, args: useArgs } : { args: useArgs }
1915
+ );
1916
+ const nextRoomState = roomsFromData(
1917
+ next,
1918
+ toRooms
1919
+ );
1920
+ setRoomState(
1921
+ (prevRoomState) => roomStateEqual(prevRoomState, nextRoomState) ? prevRoomState : nextRoomState
1922
+ );
1923
+ return next;
1924
+ },
1925
+ ...useArgs
1926
+ );
1927
+ }
1928
+ } finally {
1929
+ draining = false;
1930
+ if (active && queue.length > 0) drainQueue();
1931
+ }
1932
+ };
1682
1933
  const entries = Object.entries(applySocket).filter(
1683
1934
  ([_event, fn]) => typeof fn === "function"
1684
1935
  );
1685
1936
  const unsubscribes = entries.map(
1686
1937
  ([ev, fn]) => client.on(ev, (payload, meta) => {
1687
- built.setData(
1688
- (prev) => {
1689
- const next = fn(prev, payload, meta);
1690
- setRoomState(
1691
- roomsFromData(next, toRooms)
1692
- );
1693
- return next;
1694
- },
1695
- ...useArgs
1696
- );
1938
+ queue.push({
1939
+ fn,
1940
+ payload,
1941
+ ...meta ? { meta } : {}
1942
+ });
1943
+ drainQueue();
1697
1944
  })
1698
1945
  );
1699
- return () => unsubscribes.forEach((u) => u?.());
1700
- }, [client, applySocket, built, argsKey, toRooms]);
1946
+ return () => {
1947
+ active = false;
1948
+ queue.length = 0;
1949
+ unsubscribes.forEach((u) => u?.());
1950
+ };
1951
+ }, [client, applySocket, built, argsKey, toRooms, debug]);
1701
1952
  return { ...endpointResult, rooms: roomState.rooms };
1702
1953
  };
1703
1954
  return {
@@ -2209,9 +2460,9 @@ var SocketClient = class {
2209
2460
  };
2210
2461
  }
2211
2462
  const socket = this.socket;
2212
- const wrapped = (envelopeOrRaw) => {
2213
- const maybeEnvelope = envelopeOrRaw;
2214
- const rawData = maybeEnvelope?.data ?? maybeEnvelope;
2463
+ const toStringList = (value) => Array.isArray(value) ? value.filter((entry2) => typeof entry2 === "string") : [];
2464
+ const wrappedEnv = (envelope) => {
2465
+ const rawData = envelope.data;
2215
2466
  const parsed = schema.safeParse(rawData);
2216
2467
  if (!parsed.success) {
2217
2468
  this.dbg({
@@ -2222,15 +2473,17 @@ var SocketClient = class {
2222
2473
  });
2223
2474
  return;
2224
2475
  }
2476
+ const data = parsed.data;
2225
2477
  const receivedAt = /* @__PURE__ */ new Date();
2226
- const sentAt = maybeEnvelope?.sentAt ? new Date(maybeEnvelope.sentAt) : void 0;
2478
+ const sentAt = envelope?.sentAt ? new Date(envelope.sentAt) : void 0;
2227
2479
  const meta = {
2228
2480
  envelope: {
2229
- eventName: maybeEnvelope?.eventName ?? event,
2230
- sentAt: maybeEnvelope?.sentAt ?? receivedAt.toISOString(),
2231
- sentTo: maybeEnvelope?.sentTo ?? [],
2232
- data: void 0,
2233
- metadata: maybeEnvelope?.metadata
2481
+ eventName: typeof envelope?.eventName === "string" ? envelope.eventName : event,
2482
+ sentAt: envelope?.sentAt ?? receivedAt.toISOString(),
2483
+ sentTo: toStringList(envelope?.sentTo),
2484
+ data,
2485
+ metadata: envelope?.metadata,
2486
+ rooms: toStringList(envelope?.rooms)
2234
2487
  },
2235
2488
  ctx: {
2236
2489
  receivedAt,
@@ -2250,22 +2503,38 @@ var SocketClient = class {
2250
2503
  metadata: meta.envelope.metadata
2251
2504
  } : void 0
2252
2505
  });
2253
- handler(parsed.data, meta);
2506
+ handler(data, meta);
2507
+ };
2508
+ const wrappedDispatcher = (envelopeOrRaw) => {
2509
+ if (typeof envelopeOrRaw === "object" && envelopeOrRaw !== null && "eventName" in envelopeOrRaw && "sentAt" in envelopeOrRaw && "sentTo" in envelopeOrRaw && "data" in envelopeOrRaw) {
2510
+ wrappedEnv(envelopeOrRaw);
2511
+ } else {
2512
+ this.dbg({
2513
+ type: "receive",
2514
+ event,
2515
+ envelope: void 0
2516
+ });
2517
+ handler(envelopeOrRaw, void 0);
2518
+ }
2254
2519
  };
2255
2520
  const errorWrapped = (e) => {
2256
2521
  this.dbg({ type: "receive", event, err: String(e) });
2257
2522
  };
2258
- socket.on(String(event), wrapped);
2523
+ socket.on(String(event), wrappedDispatcher);
2259
2524
  socket.on(`${String(event)}:error`, errorWrapped);
2260
2525
  let set = this.handlerMap.get(String(event));
2261
2526
  if (!set) {
2262
2527
  set = /* @__PURE__ */ new Set();
2263
2528
  this.handlerMap.set(String(event), set);
2264
2529
  }
2265
- const entry = { orig: handler, wrapped, errorWrapped };
2530
+ const entry = {
2531
+ orig: handler,
2532
+ wrapped: wrappedDispatcher,
2533
+ errorWrapped
2534
+ };
2266
2535
  set.add(entry);
2267
2536
  return () => {
2268
- socket.off(String(event), wrapped);
2537
+ socket.off(String(event), wrappedDispatcher);
2269
2538
  socket.off(`${String(event)}:error`, errorWrapped);
2270
2539
  const s = this.handlerMap.get(String(event));
2271
2540
  if (s) {