@emeryld/rrroutes-client 2.2.19 → 2.3.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/README.md CHANGED
@@ -61,10 +61,10 @@ export function Users() {
61
61
  ### 1) Configure the client
62
62
 
63
63
  ```ts
64
- import { QueryClient } from '@tanstack/react-query';
65
- import { createRouteClient, defaultFetcher } from '@emeryld/rrroutes-client';
64
+ import { QueryClient } from '@tanstack/react-query'
65
+ import { createRouteClient, defaultFetcher } from '@emeryld/rrroutes-client'
66
66
 
67
- const queryClient = new QueryClient();
67
+ const queryClient = new QueryClient()
68
68
 
69
69
  const client = createRouteClient({
70
70
  baseUrl: 'https://api.example.com',
@@ -74,7 +74,7 @@ const client = createRouteClient({
74
74
  return defaultFetcher({
75
75
  ...req,
76
76
  headers: { ...req.headers, Authorization: `Bearer ${getToken()}` },
77
- });
77
+ })
78
78
  },
79
79
  cursorParam: 'cursor', // optional; default is "cursor"
80
80
  getNextCursor: (page) => page?.links?.next ?? page?.nextCursor, // optional override
@@ -84,28 +84,28 @@ const client = createRouteClient({
84
84
  invalidate: true,
85
85
  verbose: true, // include params/query/output in debug events
86
86
  },
87
- });
87
+ })
88
88
  ```
89
89
 
90
90
  ### 2) Build endpoints from your registry
91
91
 
92
92
  ```ts
93
- import { registry } from '../routes';
93
+ import { registry } from '../routes'
94
94
 
95
95
  // Plain GET
96
96
  const getUser = client.build(registry.byKey['GET /v1/users/:userId'], {
97
97
  staleTime: 30_000,
98
- });
98
+ })
99
99
 
100
100
  // Infinite/feed GET (cfg.feed === true)
101
101
  const listFeed = client.build(registry.byKey['GET /v1/posts'], {
102
102
  getNextPageParam: (last) => last.nextCursor, // React Query option override
103
- });
103
+ })
104
104
 
105
105
  // Mutation
106
106
  const updateUser = client.build(registry.byKey['PATCH /v1/users/:userId'], {
107
107
  onSuccess: () => client.invalidate(['get', 'v1', 'users']), // prefix invalidate
108
- });
108
+ })
109
109
  ```
110
110
 
111
111
  ### 3) Use GET hooks (with params/query/body)
@@ -130,8 +130,8 @@ function Profile({ userId }: { userId: string }) {
130
130
  - For GET leaves that define a `bodySchema`, pass the body after the args tuple:
131
131
 
132
132
  ```ts
133
- const auditStatus = client.build(registry.byKey['GET /v1/audit']);
134
- await auditStatus.fetch({}, { includeExternal: true }); // body matches the leaf's bodySchema
133
+ const auditStatus = client.build(registry.byKey['GET /v1/audit'])
134
+ await auditStatus.fetch({}, { includeExternal: true }) // body matches the leaf's bodySchema
135
135
  ```
136
136
 
137
137
  ### 4) Use infinite feeds
@@ -193,13 +193,13 @@ export function RenameForm({ userId }: { userId: string }) {
193
193
  ### 6) Cache keys, invalidation, and manual cache writes
194
194
 
195
195
  ```ts
196
- const keys = getUser.getQueryKeys({ params: { userId: 'u_1' } }); // ['get','v1','users','u_1', {}]
197
- await getUser.invalidate({ params: { userId: 'u_1' } }); // invalidate exact detail
198
- await client.invalidate(['get', 'v1', 'users']); // invalidate any users endpoints
196
+ const keys = getUser.getQueryKeys({ params: { userId: 'u_1' } }) // ['get','v1','users','u_1', {}]
197
+ await getUser.invalidate({ params: { userId: 'u_1' } }) // invalidate exact detail
198
+ await client.invalidate(['get', 'v1', 'users']) // invalidate any users endpoints
199
199
 
200
200
  getUser.setData((prev) => (prev ? { ...prev, status: 'online' } : prev), {
201
201
  params: { userId: 'u_1' },
202
- });
202
+ })
203
203
  ```
204
204
 
205
205
  `setData` respects feeds (updates `InfiniteData` shape when `cfg.feed === true`).
@@ -207,18 +207,18 @@ getUser.setData((prev) => (prev ? { ...prev, status: 'online' } : prev), {
207
207
  ### 7) Router helper (build by name instead of leaf)
208
208
 
209
209
  ```ts
210
- import { buildRouter } from '@emeryld/rrroutes-client';
211
- import { registry } from '../routes';
210
+ import { buildRouter } from '@emeryld/rrroutes-client'
211
+ import { registry } from '../routes'
212
212
 
213
213
  const routes = {
214
214
  listUsers: registry.byKey['GET /v1/users'],
215
215
  updateUser: registry.byKey['PATCH /v1/users/:userId'],
216
- } as const;
216
+ } as const
217
217
 
218
- const buildRoute = buildRouter(client, routes);
218
+ const buildRoute = buildRouter(client, routes)
219
219
 
220
- const listUsers = buildRoute('listUsers'); // builds from routes.listUsers
221
- const updateUser = buildRoute('updateUser', {}, { name: 'profile' }); // debug name filtering
220
+ const listUsers = buildRoute('listUsers') // builds from routes.listUsers
221
+ const updateUser = buildRoute('updateUser', {}, { name: 'profile' }) // debug name filtering
222
222
  ```
223
223
 
224
224
  ### 8) File uploads (FormData)
@@ -226,12 +226,14 @@ const updateUser = buildRoute('updateUser', {}, { name: 'profile' }); // debug n
226
226
  If a leaf has `bodyFiles` set in its contract, the client automatically converts the body to `FormData`:
227
227
 
228
228
  ```ts
229
- const uploadAvatar = client.build(registry.byKey['PUT /v1/users/:userId/avatar']);
229
+ const uploadAvatar = client.build(
230
+ registry.byKey['PUT /v1/users/:userId/avatar'],
231
+ )
230
232
 
231
233
  await uploadAvatar.fetch(
232
234
  { params: { userId: 'u_1' } },
233
235
  { avatar: new File([blob], 'avatar.png', { type: 'image/png' }) },
234
- );
236
+ )
235
237
  ```
236
238
 
237
239
  ### 9) Debug logging
@@ -251,9 +253,13 @@ const client = createRouteClient({
251
253
  only: ['profile', 'feed'],
252
254
  logger: (e) => console.info('[rrroutes-client]', e),
253
255
  },
254
- });
256
+ })
255
257
 
256
- const profile = client.build(registry.byKey['GET /v1/me'], {}, { name: 'profile' });
258
+ const profile = client.build(
259
+ registry.byKey['GET /v1/me'],
260
+ {},
261
+ { name: 'profile' },
262
+ )
257
263
  ```
258
264
 
259
265
  Set `environment: 'production'` to silence all debug output regardless of the `debug` option.
@@ -267,33 +273,41 @@ The package also ships a typed Socket.IO client, React provider hooks, and a hel
267
273
  ### Define events + config
268
274
 
269
275
  ```ts
270
- import { z } from 'zod';
271
- import { defineSocketEvents } from '@emeryld/rrroutes-contract';
276
+ import { z } from 'zod'
277
+ import { defineSocketEvents } from '@emeryld/rrroutes-contract'
272
278
 
273
279
  const { events, config } = defineSocketEvents(
274
280
  {
275
281
  joinMetaMessage: z.object({ source: z.string().optional() }),
276
282
  leaveMetaMessage: z.object({ source: z.string().optional() }),
277
- pingPayload: z.object({ clientEcho: z.object({ sentAt: z.string() }).optional() }),
283
+ pingPayload: z.object({
284
+ clientEcho: z.object({ sentAt: z.string() }).optional(),
285
+ }),
278
286
  pongPayload: z.object({
279
287
  clientEcho: z.object({ sentAt: z.string() }).optional(),
280
288
  sinceMs: z.number().optional(),
281
289
  }),
282
290
  },
283
291
  {
284
- 'chat:message': { message: z.object({ roomId: z.string(), text: z.string(), userId: z.string() }) },
292
+ 'chat:message': {
293
+ message: z.object({
294
+ roomId: z.string(),
295
+ text: z.string(),
296
+ userId: z.string(),
297
+ }),
298
+ },
285
299
  },
286
- );
300
+ )
287
301
  ```
288
302
 
289
303
  ### Vanilla SocketClient
290
304
 
291
305
  ```ts
292
- import { io } from 'socket.io-client';
293
- import { SocketClient } from '@emeryld/rrroutes-client';
294
- import { events, config } from './socketContract';
306
+ import { io } from 'socket.io-client'
307
+ import { SocketClient } from '@emeryld/rrroutes-client'
308
+ import { events, config } from './socketContract'
295
309
 
296
- const socket = io('https://socket.example.com', { transports: ['websocket'] });
310
+ const socket = io('https://socket.example.com', { transports: ['websocket'] })
297
311
 
298
312
  const client = new SocketClient(events, {
299
313
  socket,
@@ -303,26 +317,30 @@ const client = new SocketClient(events, {
303
317
  clientEcho: { sentAt: new Date().toISOString() },
304
318
  }),
305
319
  'sys:pong': async ({ payload }) => {
306
- console.log('pong latency', payload.sinceMs);
320
+ console.log('pong latency', payload.sinceMs)
307
321
  },
308
322
  'sys:room_join': async ({ rooms }) => {
309
- console.log('joining rooms', rooms);
310
- return true; // allow join
323
+ console.log('joining rooms', rooms)
324
+ return true // allow join
311
325
  },
312
326
  'sys:room_leave': async ({ rooms }) => {
313
- console.log('leaving rooms', rooms);
314
- return true;
327
+ console.log('leaving rooms', rooms)
328
+ return true
315
329
  },
316
330
  },
317
331
  heartbeat: { intervalMs: 15_000, timeoutMs: 7_500 },
318
332
  debug: { receive: true, emit: true, verbose: true, logger: console.log },
319
- });
333
+ })
320
334
 
321
335
  client.on('chat:message', (payload, { ctx }) => {
322
- console.log('socket message', payload.text, 'latency', ctx.latencyMs);
323
- });
324
-
325
- void client.emit('chat:message', { roomId: 'general', text: 'hi', userId: 'u_1' });
336
+ console.log('socket message', payload.text, 'latency', ctx.latencyMs)
337
+ })
338
+
339
+ void client.emit('chat:message', {
340
+ roomId: 'general',
341
+ text: 'hi',
342
+ userId: 'u_1',
343
+ })
326
344
  ```
327
345
 
328
346
  Key methods: `emit`, `on`, `joinRooms` / `leaveRooms`, `startHeartbeat` / `stopHeartbeat`, `connect` / `disconnect`, `stats()`, `destroy()`.
package/dist/index.cjs CHANGED
@@ -58,7 +58,6 @@ var HttpError = class extends Error {
58
58
  this.url = url;
59
59
  this.body = { message };
60
60
  this.headers = res.headers ? Object.fromEntries(res.headers.entries()) : {};
61
- console.log("Created HttpError:", this);
62
61
  }
63
62
  };
64
63
  var defaultFetcher = async (req) => {
@@ -780,7 +779,8 @@ function safeDescribeHookValue(value) {
780
779
  return value;
781
780
  }
782
781
  if (valueType === "bigint" || valueType === "symbol") return String(value);
783
- if (valueType === "function") return `[function ${value.name || "anonymous"}]`;
782
+ if (valueType === "function")
783
+ return `[function ${value.name || "anonymous"}]`;
784
784
  if (Array.isArray(value)) return `[array length=${value.length}]`;
785
785
  if (isProbablySocket(value)) {
786
786
  return describeSocketLike(value);
@@ -792,7 +792,8 @@ function safeDescribeHookValue(value) {
792
792
  return `[${ctorName} keys=${keyPreview}${suffix}]`;
793
793
  }
794
794
  function summarizeEvents(events) {
795
- if (!events || typeof events !== "object") return safeDescribeHookValue(events);
795
+ if (!events || typeof events !== "object")
796
+ return safeDescribeHookValue(events);
796
797
  const keys = Object.keys(events);
797
798
  if (!keys.length) return "[events empty]";
798
799
  const preview = keys.slice(0, 4).join(",");
@@ -800,7 +801,8 @@ function summarizeEvents(events) {
800
801
  return `[events count=${keys.length} keys=${preview}${suffix}]`;
801
802
  }
802
803
  function summarizeBaseOptions(options) {
803
- if (!options || typeof options !== "object") return safeDescribeHookValue(options);
804
+ if (!options || typeof options !== "object")
805
+ return safeDescribeHookValue(options);
804
806
  const obj = options;
805
807
  const keys = Object.keys(obj);
806
808
  if (!keys.length) return "[baseOptions empty]";
@@ -864,8 +866,17 @@ function buildSocketProvider(args) {
864
866
  };
865
867
  }
866
868
  function SocketProvider(props) {
867
- const { events, baseOptions, children, fallback, providerDebug, destroyLeaveMeta } = props;
868
- const [resolvedSocket, setResolvedSocket] = React.useState(null);
869
+ const {
870
+ events,
871
+ baseOptions,
872
+ children,
873
+ fallback,
874
+ providerDebug,
875
+ destroyLeaveMeta
876
+ } = props;
877
+ const [resolvedSocket, setResolvedSocket] = React.useState(
878
+ null
879
+ );
869
880
  const socket = "socket" in props ? props.socket ?? null : resolvedSocket;
870
881
  const providerDebugRef = React.useRef();
871
882
  providerDebugRef.current = providerDebug;
@@ -887,18 +898,28 @@ function SocketProvider(props) {
887
898
  if (!resolvedSocket) {
888
899
  Promise.resolve(props.getSocket()).then((s) => {
889
900
  if (cancelled) {
890
- dbg(providerDebugRef.current, { type: "resolve", phase: "cancelled" });
901
+ dbg(providerDebugRef.current, {
902
+ type: "resolve",
903
+ phase: "cancelled"
904
+ });
891
905
  return;
892
906
  }
893
907
  if (!s) {
894
- dbg(providerDebugRef.current, { type: "resolve", phase: "socketMissing" });
908
+ dbg(providerDebugRef.current, {
909
+ type: "resolve",
910
+ phase: "socketMissing"
911
+ });
895
912
  return;
896
913
  }
897
914
  setResolvedSocket(s);
898
915
  dbg(providerDebugRef.current, { type: "resolve", phase: "ok" });
899
916
  }).catch((err) => {
900
917
  if (cancelled) return;
901
- dbg(providerDebugRef.current, { type: "resolve", phase: "error", err: String(err) });
918
+ dbg(providerDebugRef.current, {
919
+ type: "resolve",
920
+ phase: "error",
921
+ err: String(err)
922
+ });
902
923
  });
903
924
  }
904
925
  return () => {
@@ -917,11 +938,19 @@ function SocketProvider(props) {
917
938
  });
918
939
  const client = React.useMemo(() => {
919
940
  if (!socket) {
920
- dbg(providerDebugRef.current, { type: "client", phase: "init", missing: true });
941
+ dbg(providerDebugRef.current, {
942
+ type: "client",
943
+ phase: "init",
944
+ missing: true
945
+ });
921
946
  return null;
922
947
  }
923
948
  const c = new SocketClient(events, { ...baseOptions, socket });
924
- dbg(providerDebugRef.current, { type: "client", phase: "init", missing: false });
949
+ dbg(providerDebugRef.current, {
950
+ type: "client",
951
+ phase: "init",
952
+ missing: false
953
+ });
925
954
  return c;
926
955
  }, [events, baseOptions, socket]);
927
956
  const destroyLeaveMetaRef = React.useRef(destroyLeaveMeta);
@@ -935,7 +964,10 @@ function SocketProvider(props) {
935
964
  providerDebug: providerDebugRef.current,
936
965
  snapshot: {
937
966
  hasClient: !!client,
938
- destroyLeaveMeta: summarizeMeta(destroyLeaveMetaRef.current, "destroyLeaveMeta")
967
+ destroyLeaveMeta: summarizeMeta(
968
+ destroyLeaveMetaRef.current,
969
+ "destroyLeaveMeta"
970
+ )
939
971
  }
940
972
  });
941
973
  return () => {
@@ -950,22 +982,32 @@ function SocketProvider(props) {
950
982
  }
951
983
  function useSocketClient() {
952
984
  const ctx = React.useContext(SocketCtx);
953
- if (!ctx) throw new Error("SocketClient not found. Wrap with <SocketProvider>.");
985
+ if (!ctx)
986
+ throw new Error("SocketClient not found. Wrap with <SocketProvider>.");
954
987
  return ctx;
955
988
  }
956
989
  function useSocketConnection(args) {
957
- const { event, rooms, onMessage, onCleanup, autoJoin = true, autoLeave = true } = args;
990
+ const {
991
+ event,
992
+ rooms,
993
+ onMessage,
994
+ onCleanup,
995
+ autoJoin = true,
996
+ autoLeave = true
997
+ } = args;
958
998
  const client = useSocketClient();
959
999
  const normalizedRooms = React.useMemo(
960
1000
  () => rooms == null ? [] : Array.isArray(rooms) ? rooms : [rooms],
961
1001
  [rooms]
962
1002
  );
963
1003
  React.useEffect(() => {
964
- if (autoJoin && normalizedRooms.length > 0) client.joinRooms(normalizedRooms, args.joinMeta);
1004
+ if (autoJoin && normalizedRooms.length > 0)
1005
+ client.joinRooms(normalizedRooms, args.joinMeta);
965
1006
  const unsubscribe = client.on(event, onMessage);
966
1007
  return () => {
967
1008
  unsubscribe();
968
- if (autoLeave && normalizedRooms.length > 0) client.leaveRooms(normalizedRooms, args.leaveMeta);
1009
+ if (autoLeave && normalizedRooms.length > 0)
1010
+ client.leaveRooms(normalizedRooms, args.leaveMeta);
969
1011
  if (onCleanup) onCleanup();
970
1012
  };
971
1013
  }, [client, event, onMessage, autoJoin, autoLeave, ...normalizedRooms]);
@@ -1087,13 +1129,16 @@ function buildSocketedRoute(options) {
1087
1129
  );
1088
1130
  const unsubscribes = entries.map(
1089
1131
  ([ev, fn]) => client.on(ev, (payload, meta) => {
1090
- built.setData((prev) => {
1091
- const next = fn(prev, payload, meta);
1092
- setRoomState(
1093
- roomsFromData(next, toRooms)
1094
- );
1095
- return next;
1096
- }, ...useArgs);
1132
+ built.setData(
1133
+ (prev) => {
1134
+ const next = fn(prev, payload, meta);
1135
+ setRoomState(
1136
+ roomsFromData(next, toRooms)
1137
+ );
1138
+ return next;
1139
+ },
1140
+ ...useArgs
1141
+ );
1097
1142
  })
1098
1143
  );
1099
1144
  return () => unsubscribes.forEach((u) => u?.());