@emeryld/rrroutes-client 2.7.2 → 2.7.4

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/README.md CHANGED
@@ -468,20 +468,21 @@ import { buildSocketedRoute } from '@emeryld/rrroutes-client';
468
468
  import { useSocketClient } from './socketProvider';
469
469
 
470
470
  const listRooms = client.build(registry.byKey['GET /v1/rooms'], { staleTime: 120_000 });
471
+ const useAppSocketContext = () => ({ workspaceId: 'acme' });
471
472
 
472
473
  const socketedRooms = buildSocketedRoute({
473
474
  built: listRooms,
474
- toRooms: (page) => ({
475
- rooms: page.items.map((r) => r.id), // derive rooms from data (feeds supported)
475
+ useContext: useAppSocketContext, // optional app-level context for hooks
476
+ toRooms: ({ data, meta }) => ({
477
+ rooms: data.items.map((r) => `${meta?.appContext?.workspaceId}:${r.id}`), // derive rooms from data (feeds supported)
476
478
  joinMeta: { source: 'rooms:list' },
477
479
  leaveMeta: { source: 'rooms:list' },
478
480
  }),
479
481
  useSocketClient,
480
482
  applySocket: {
481
- 'chat:message': (prev, payload, meta) => {
483
+ 'chat:message': ({ prev, payload, args, meta }) => {
482
484
  if (!prev) return null; // explicit no-op update
483
- const [args] = meta.args;
484
- console.debug('socket patch args', args);
485
+ console.debug('socket patch args', args, meta.appContext?.workspaceId, meta.ctx?.socketId);
485
486
  // Example: bump unread count in cache
486
487
  const apply = (items: any[]) =>
487
488
  items.map((room) =>
@@ -505,7 +506,11 @@ function RoomList() {
505
506
  }
506
507
  ```
507
508
 
508
- `buildSocketedRoute(...)` returns a built endpoint object with `useEndpoint()` plus the original built helpers. `applySocket` receives `{ envelope?, ctx?, args }` and can return `null` to skip cache updates.
509
+ `buildSocketedRoute(...)` returns a built endpoint object with `useEndpoint()` plus the original built helpers.
510
+
511
+ - `toRooms` receives `{ data, args, meta }` where `meta.appContext` comes from `useContext` (if provided).
512
+ - `applySocket` handlers receive `{ prev, payload, args, meta }`, where `meta` contains `envelope?`, `ctx?`, and `appContext?`.
513
+ - Return `null` from `applySocket` to skip cache updates.
509
514
 
510
515
  ---
511
516
 
package/dist/index.cjs CHANGED
@@ -300,6 +300,16 @@ function buildUrl(leaf, baseUrl, params, query) {
300
300
  const url = `${baseUrl ?? ""}${path}${toSearchString(normalizedQuery)}`;
301
301
  return { url, normalizedQuery, normalizedParams };
302
302
  }
303
+ function areEndpointArgsComplete(leaf, args) {
304
+ const params = args?.params;
305
+ const query = args?.query;
306
+ try {
307
+ buildUrl(leaf, "", params, query);
308
+ return true;
309
+ } catch {
310
+ return false;
311
+ }
312
+ }
303
313
  function isBlobLike(value) {
304
314
  return typeof Blob !== "undefined" && value instanceof Blob;
305
315
  }
@@ -643,6 +653,7 @@ function buildGetLeaf(leaf, rqOpts, env) {
643
653
  const useEndpoint = (...useArgs) => {
644
654
  const args = expectsArgs ? useArgs[0] : void 0;
645
655
  const useEndpointOptions = expectsArgs ? useArgs[1] : useArgs[0];
656
+ const hasCompleteArgs = !expectsArgs || areEndpointArgsComplete(leaf, args);
646
657
  const tuple = toArgsTuple(args);
647
658
  const queryKeys = getQueryKeys(...tuple);
648
659
  emit({
@@ -657,6 +668,8 @@ function buildGetLeaf(leaf, rqOpts, env) {
657
668
  ...buildQueryOptions,
658
669
  ...runtimeQueryOptions
659
670
  };
671
+ const mergedEnabled = mergedQueryOptions.enabled;
672
+ const guardedEnabled = hasCompleteArgs && (typeof mergedEnabled === "undefined" ? true : mergedEnabled);
660
673
  const listenersRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
661
674
  const notifyOnReceive = (0, import_react2.useCallback)((data) => {
662
675
  buildOnReceive2?.(data);
@@ -675,11 +688,17 @@ function buildGetLeaf(leaf, rqOpts, env) {
675
688
  const queryResult = (0, import_react_query2.useQuery)(
676
689
  {
677
690
  ...mergedQueryOptions,
691
+ enabled: guardedEnabled,
678
692
  queryKey: getQueryKeys(...tuple),
679
693
  placeholderData: mergedQueryOptions.placeholderData ?? import_react_query2.keepPreviousData,
680
- queryFn: () => fetchEndpoint(tuple, {
681
- onReceive: notifyOnReceive
682
- })
694
+ queryFn: () => {
695
+ if (!hasCompleteArgs) {
696
+ return Promise.resolve(void 0);
697
+ }
698
+ return fetchEndpoint(tuple, {
699
+ onReceive: notifyOnReceive
700
+ });
701
+ }
683
702
  },
684
703
  queryClient
685
704
  );
@@ -923,6 +942,7 @@ function buildInfiniteGetLeaf(leaf, rqOpts, env) {
923
942
  const useEndpoint = (...useArgs) => {
924
943
  const args = expectsArgs ? useArgs[0] : void 0;
925
944
  const useEndpointOptions = expectsArgs ? useArgs[1] : useArgs[0];
945
+ const hasCompleteArgs = !expectsArgs || areEndpointArgsComplete(leaf, args);
926
946
  const tuple = toArgsTuple(args);
927
947
  const queryKeys = getQueryKeys(...tuple);
928
948
  emit({
@@ -940,6 +960,8 @@ function buildInfiniteGetLeaf(leaf, rqOpts, env) {
940
960
  ...buildInfiniteQueryOptions,
941
961
  ...runtimeInfiniteQueryOptions
942
962
  };
963
+ const mergedEnabled = mergedInfiniteQueryOptions.enabled;
964
+ const guardedEnabled = hasCompleteArgs && (typeof mergedEnabled === "undefined" ? true : mergedEnabled);
943
965
  const listenersRef = (0, import_react3.useRef)(/* @__PURE__ */ new Set());
944
966
  const notifyOnReceive = (0, import_react3.useCallback)((data) => {
945
967
  buildOnReceive2?.(data);
@@ -955,20 +977,22 @@ function buildInfiniteGetLeaf(leaf, rqOpts, env) {
955
977
  },
956
978
  []
957
979
  );
958
- const { normalizedQuery, normalizedParams } = buildUrl(
959
- { ...leaf, cfg: leafCfg },
960
- baseUrl,
961
- params,
962
- query
963
- );
980
+ const { normalizedQuery, normalizedParams } = hasCompleteArgs ? buildUrl({ ...leaf, cfg: leafCfg }, baseUrl, params, query) : {
981
+ normalizedQuery: query ?? {},
982
+ normalizedParams: params ?? {}
983
+ };
964
984
  const queryResult = (0, import_react_query3.useInfiniteQuery)(
965
985
  {
966
986
  ...mergedInfiniteQueryOptions,
987
+ enabled: guardedEnabled,
967
988
  placeholderData: mergedInfiniteQueryOptions.placeholderData ?? import_react_query3.keepPreviousData,
968
989
  initialPageParam: feedInitialPageParam,
969
990
  getNextPageParam: (lastPage) => cursorFromPage(lastPage),
970
991
  queryKey: queryKeys,
971
992
  queryFn: ({ pageParam }) => {
993
+ if (!hasCompleteArgs) {
994
+ return Promise.resolve(void 0);
995
+ }
972
996
  if (!effectiveSplitPageSize) {
973
997
  const pageQuery = {
974
998
  ...normalizedQuery,
@@ -2752,12 +2776,15 @@ function mergeRoomState(prev, toRoomsResult) {
2752
2776
  leaveMeta: toRoomsResult.leaveMeta ?? prev.leaveMeta
2753
2777
  };
2754
2778
  }
2755
- function roomsFromData(data, args, toRooms) {
2779
+ function roomsFromData(data, args, toRooms, meta) {
2756
2780
  if (data == null) return { rooms: [] };
2757
2781
  let state = { rooms: [] };
2758
2782
  const add = (input) => {
2759
2783
  const mergeForValue = (value) => {
2760
- state = mergeRoomState(state, toRooms(value, args));
2784
+ state = mergeRoomState(
2785
+ state,
2786
+ toRooms({ data: value, args, meta })
2787
+ );
2761
2788
  };
2762
2789
  if (Array.isArray(input)) {
2763
2790
  input.forEach((entry) => mergeForValue(entry));
@@ -2792,8 +2819,21 @@ function parseUseEndpointArgs(useArgs) {
2792
2819
  const endpointArgsTuple = typeof endpointArgs === "undefined" ? [] : [endpointArgs];
2793
2820
  return { endpointArgs, endpointArgsTuple };
2794
2821
  }
2822
+ function useDefaultSocketedRouteContext() {
2823
+ return void 0;
2824
+ }
2825
+ function createToRoomsMeta(appContext) {
2826
+ return { appContext };
2827
+ }
2795
2828
  function buildSocketedRoute(options) {
2796
- const { built, toRooms, applySocket, useSocketClient: useSocketClient2, debug } = options;
2829
+ const {
2830
+ built,
2831
+ toRooms,
2832
+ applySocket,
2833
+ useSocketClient: useSocketClient2,
2834
+ useContext: useRouteContext = useDefaultSocketedRouteContext,
2835
+ debug
2836
+ } = options;
2797
2837
  const { useEndpoint: useInnerEndpoint, ...rest } = built;
2798
2838
  const useEndpoint = (...useArgs) => {
2799
2839
  const client = useSocketClient2();
@@ -2801,6 +2841,11 @@ function buildSocketedRoute(options) {
2801
2841
  const endpointResult = useInnerEndpoint(
2802
2842
  ...useArgs
2803
2843
  );
2844
+ const appContext = useRouteContext();
2845
+ const toRoomsMeta = (0, import_react5.useMemo)(
2846
+ () => createToRoomsMeta(appContext),
2847
+ [appContext]
2848
+ );
2804
2849
  const { endpointArgs, endpointArgsTuple } = parseUseEndpointArgs(useArgs);
2805
2850
  const argsKey = (0, import_react5.useMemo)(
2806
2851
  () => safeJsonKey(endpointArgs ?? null),
@@ -2810,7 +2855,8 @@ function buildSocketedRoute(options) {
2810
2855
  () => roomsFromData(
2811
2856
  endpointResult.data,
2812
2857
  endpointArgs,
2813
- toRooms
2858
+ toRooms,
2859
+ toRoomsMeta
2814
2860
  )
2815
2861
  );
2816
2862
  const renderCountRef = (0, import_react5.useRef)(0);
@@ -2819,6 +2865,7 @@ function buildSocketedRoute(options) {
2819
2865
  endpointResult.data
2820
2866
  );
2821
2867
  const toRoomsRef = (0, import_react5.useRef)(toRooms);
2868
+ const toRoomsMetaRef = (0, import_react5.useRef)(toRoomsMeta);
2822
2869
  const onReceiveEffectDebugRef = (0, import_react5.useRef)(null);
2823
2870
  const deriveRoomsEffectDebugRef = (0, import_react5.useRef)(null);
2824
2871
  const joinRoomsEffectDebugRef = (0, import_react5.useRef)(null);
@@ -2826,6 +2873,7 @@ function buildSocketedRoute(options) {
2826
2873
  renderCountRef.current += 1;
2827
2874
  endpointDataRef.current = endpointResult.data;
2828
2875
  toRoomsRef.current = toRooms;
2876
+ toRoomsMetaRef.current = toRoomsMeta;
2829
2877
  const roomsKey = (0, import_react5.useMemo)(() => roomState.rooms.join("|"), [roomState.rooms]);
2830
2878
  const joinMetaKey = (0, import_react5.useMemo)(
2831
2879
  () => safeJsonKey(roomState.joinMeta ?? null),
@@ -2867,7 +2915,11 @@ function buildSocketedRoute(options) {
2867
2915
  setRoomState((prev) => {
2868
2916
  const next = mergeRoomState(
2869
2917
  prev,
2870
- toRoomsRef.current(data, endpointArgs)
2918
+ toRoomsRef.current({
2919
+ data,
2920
+ args: endpointArgs,
2921
+ meta: toRoomsMetaRef.current
2922
+ })
2871
2923
  );
2872
2924
  return roomStateEqual(prev, next) ? prev : next;
2873
2925
  });
@@ -2885,9 +2937,14 @@ function buildSocketedRoute(options) {
2885
2937
  toRoomsRef: describeObjectReference(toRooms)
2886
2938
  }
2887
2939
  });
2888
- const next = roomsFromData(endpointDataRef.current, endpointArgs, toRooms);
2940
+ const next = roomsFromData(
2941
+ endpointDataRef.current,
2942
+ endpointArgs,
2943
+ toRooms,
2944
+ toRoomsMeta
2945
+ );
2889
2946
  setRoomState((prev) => roomStateEqual(prev, next) ? prev : next);
2890
- }, [argsKey, toRooms, debug]);
2947
+ }, [argsKey, toRooms, toRoomsMeta, debug]);
2891
2948
  (0, import_react5.useEffect)(() => {
2892
2949
  trackHookTrigger2({
2893
2950
  ref: joinRoomsEffectDebugRef,
@@ -3005,7 +3062,10 @@ function buildSocketedRoute(options) {
3005
3062
  prev,
3006
3063
  payload: nextUpdate.payload,
3007
3064
  args: endpointArgs,
3008
- meta: nextUpdate.meta ?? {}
3065
+ meta: {
3066
+ ...nextUpdate.meta ?? {},
3067
+ appContext: toRoomsMetaRef.current.appContext
3068
+ }
3009
3069
  });
3010
3070
  if (next === null) return prev;
3011
3071
  if (shouldWarnSocketMutationGuard() && isSameObjectReference(prev, next) && !sameRefWarnedEvents.has(nextUpdate.event)) {
@@ -3017,7 +3077,8 @@ function buildSocketedRoute(options) {
3017
3077
  const nextRoomState = roomsFromData(
3018
3078
  next,
3019
3079
  endpointArgs,
3020
- toRooms
3080
+ toRooms,
3081
+ toRoomsMetaRef.current
3021
3082
  );
3022
3083
  setRoomState(
3023
3084
  (prevRoomState) => roomStateEqual(prevRoomState, nextRoomState) ? prevRoomState : nextRoomState