@emeryld/rrroutes-client 2.2.14 → 2.2.16

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
@@ -69,22 +69,20 @@ var defaultFetcher = async (req) => {
69
69
  };
70
70
 
71
71
  // src/routesV3.client.index.ts
72
- var import_react = require("react");
72
+ var import_rrroutes_contract = require("@emeryld/rrroutes-contract");
73
73
  var import_react_query = require("@tanstack/react-query");
74
+ var import_react = require("react");
74
75
  var import_zod = require("zod");
75
- var import_rrroutes_contract = require("@emeryld/rrroutes-contract");
76
76
  var toUpper = (m) => m.toUpperCase();
77
- var defaultFeedQuerySchema = import_zod.z.object({
78
- cursor: import_zod.z.string().optional(),
79
- limit: import_zod.z.coerce.number().min(1).max(100).default(20)
80
- });
77
+ var paginationQueryShape = {
78
+ pagination_cursor: import_zod.z.string().optional(),
79
+ pagination_limit: import_zod.z.coerce.number().min(1).max(100).default(20)
80
+ };
81
+ var defaultFeedQuerySchema = import_zod.z.object(paginationQueryShape);
81
82
  var defaultFeedOutputSchema = import_zod.z.object({
82
83
  items: import_zod.z.array(import_zod.z.unknown()),
83
84
  nextCursor: import_zod.z.string().optional()
84
85
  });
85
- function zParse(value, schema) {
86
- return schema ? schema.parse(value) : value;
87
- }
88
86
  function toSearchString(query) {
89
87
  if (!query) return "";
90
88
  const params = new URLSearchParams();
@@ -183,41 +181,53 @@ function extractArgs(args) {
183
181
  function toArgsTuple(args) {
184
182
  return typeof args === "undefined" ? [] : [args];
185
183
  }
184
+ function getZodShape(schema) {
185
+ const shapeOrGetter = schema.shape ? schema.shape : schema._def?.shape?.();
186
+ if (!shapeOrGetter) return {};
187
+ return typeof shapeOrGetter === "function" ? shapeOrGetter.call(schema) : shapeOrGetter;
188
+ }
186
189
  function augmentFeedQuerySchema(schema) {
187
190
  if (!schema) return defaultFeedQuerySchema;
188
- if (schema instanceof import_zod.z.ZodObject) {
189
- const shape = schema.shape ? schema.shape : schema._def?.shape?.();
190
- return schema.extend({
191
- ...shape ?? {},
192
- cursor: defaultFeedQuerySchema.shape.cursor,
193
- limit: defaultFeedQuerySchema.shape.limit
194
- });
191
+ if (!(schema instanceof import_zod.z.ZodObject)) {
192
+ console.warn(
193
+ "Feed queries must be a ZodObject; default pagination applied."
194
+ );
195
+ return defaultFeedQuerySchema;
195
196
  }
196
- return import_zod.z.intersection(schema, defaultFeedQuerySchema);
197
+ return schema.extend(paginationQueryShape);
197
198
  }
198
199
  function augmentFeedOutputSchema(schema) {
199
200
  if (!schema) return defaultFeedOutputSchema;
200
- if (schema instanceof import_zod.z.ZodObject) {
201
- const shape = schema.shape ? schema.shape : schema._def?.shape?.();
202
- const hasItems = Boolean(shape?.items);
203
- if (hasItems) return schema.extend({ nextCursor: import_zod.z.string().optional() });
201
+ if (schema instanceof import_zod.z.ZodArray) {
204
202
  return import_zod.z.object({
205
- items: import_zod.z.array(schema),
203
+ items: schema,
206
204
  nextCursor: import_zod.z.string().optional()
207
205
  });
208
206
  }
209
- if (schema instanceof import_zod.z.ZodArray) {
207
+ if (schema instanceof import_zod.z.ZodObject) {
208
+ const shape = getZodShape(schema);
209
+ if (shape?.items) {
210
+ return schema.extend({
211
+ nextCursor: import_zod.z.string().optional()
212
+ });
213
+ }
210
214
  return import_zod.z.object({
211
- items: schema,
215
+ items: import_zod.z.array(schema),
212
216
  nextCursor: import_zod.z.string().optional()
213
217
  });
214
218
  }
215
- return defaultFeedOutputSchema;
219
+ return import_zod.z.object({
220
+ items: import_zod.z.array(schema),
221
+ nextCursor: import_zod.z.string().optional()
222
+ });
216
223
  }
217
224
  function buildUrl(leaf, baseUrl, params, query) {
218
- const normalizedParams = zParse(params, leaf.cfg.paramsSchema);
219
- const normalizedQuery = zParse(query, leaf.cfg.querySchema);
220
- const path = (0, import_rrroutes_contract.compilePath)(leaf.path, normalizedParams ?? {});
225
+ const normalizedParams = leaf.cfg.paramsSchema ? (0, import_rrroutes_contract.lowProfileParse)(leaf.cfg.paramsSchema, params) : {};
226
+ const normalizedQuery = leaf.cfg.querySchema ? (0, import_rrroutes_contract.lowProfileParse)(leaf.cfg.querySchema, query) : {};
227
+ const path = (0, import_rrroutes_contract.compilePath)(
228
+ leaf.path,
229
+ normalizedParams ?? {}
230
+ );
221
231
  const url = `${baseUrl ?? ""}${path}${toSearchString(normalizedQuery)}`;
222
232
  return { url, normalizedQuery, normalizedParams };
223
233
  }
@@ -225,10 +235,13 @@ function createRouteClient(opts) {
225
235
  const queryClient = opts.queryClient;
226
236
  const fetcher = opts.fetcher ?? defaultFetcher;
227
237
  const baseUrl = opts.baseUrl;
228
- const cursorParam = opts.cursorParam ?? "cursor";
238
+ const cursorParam = opts.cursorParam ?? "pagination_cursor";
229
239
  const getNextCursor = opts.getNextCursor ?? defaultGetNextCursor;
230
240
  const environment = opts.environment ?? void 0;
231
- const { emit: emitDebug, mode: debugMode } = createDebugEmitter(opts.debug, environment);
241
+ const { emit: emitDebug, mode: debugMode } = createDebugEmitter(
242
+ opts.debug,
243
+ environment
244
+ );
232
245
  const isVerboseDebug = debugMode === "complete";
233
246
  const decorateDebugEvent = (event, details) => {
234
247
  if (!isVerboseDebug || !details) return event;
@@ -242,11 +255,12 @@ function createRouteClient(opts) {
242
255
  function buildInternal(leaf, rqOpts, meta) {
243
256
  const isGet = leaf.method === "get";
244
257
  const isFeed = !!leaf.cfg.feed;
258
+ const rawLeafCfg = leaf.cfg;
245
259
  const leafCfg = isFeed ? {
246
- ...leaf.cfg,
247
- querySchema: augmentFeedQuerySchema(leaf.cfg.querySchema),
248
- outputSchema: augmentFeedOutputSchema(leaf.cfg.outputSchema)
249
- } : leaf.cfg;
260
+ ...rawLeafCfg,
261
+ querySchema: augmentFeedQuerySchema(rawLeafCfg.querySchema),
262
+ outputSchema: augmentFeedOutputSchema(rawLeafCfg.outputSchema)
263
+ } : rawLeafCfg;
250
264
  const method = toUpper(leaf.method);
251
265
  const expectsArgs = Boolean(leafCfg.paramsSchema || leafCfg.querySchema);
252
266
  const leafLabel = `${leaf.method.toUpperCase()} ${String(leaf.path)}`;
@@ -302,9 +316,13 @@ function createRouteClient(opts) {
302
316
  const acceptsBody = Boolean(leafCfg.bodySchema);
303
317
  const requiresBody = options?.requireBody ?? (!isGet && acceptsBody);
304
318
  if (typeof options?.body !== "undefined") {
305
- const normalizedBody = zParse(options.body, leafCfg.bodySchema);
319
+ const normalizedBody = leafCfg.bodySchema ? (0, import_rrroutes_contract.lowProfileParse)(leafCfg.bodySchema, options.body) : void 0;
306
320
  const isMultipart = Array.isArray(leafCfg.bodyFiles) && leafCfg.bodyFiles.length > 0;
307
- payload = isMultipart ? toFormData(normalizedBody) : normalizedBody;
321
+ if (isMultipart && normalizedBody && typeof normalizedBody === "object") {
322
+ payload = toFormData(normalizedBody);
323
+ } else {
324
+ payload = normalizedBody;
325
+ }
308
326
  } else if (requiresBody) {
309
327
  throw new Error("Body is required when invoking a mutation fetch.");
310
328
  }
@@ -327,7 +345,7 @@ function createRouteClient(opts) {
327
345
  const out = await fetcher(
328
346
  payload === void 0 ? { url, method } : { url, method, body: payload }
329
347
  );
330
- const parsed = zParse(out, leafCfg.outputSchema);
348
+ const parsed = leafCfg.outputSchema ? (0, import_rrroutes_contract.lowProfileParse)(leafCfg.outputSchema, out) : void 0;
331
349
  emit(
332
350
  decorateDebugEvent(
333
351
  {
@@ -338,7 +356,11 @@ function createRouteClient(opts) {
338
356
  leaf: leafLabel,
339
357
  durationMs: Date.now() - startedAt
340
358
  },
341
- isVerboseDebug ? { params: normalizedParams, query: normalizedQuery, output: parsed } : void 0
359
+ isVerboseDebug ? {
360
+ params: normalizedParams,
361
+ query: normalizedQuery,
362
+ output: parsed
363
+ } : void 0
342
364
  )
343
365
  );
344
366
  options?.onReceive?.(parsed);
@@ -369,7 +391,11 @@ function createRouteClient(opts) {
369
391
  const hasBodyCandidate = acceptsBody && tupleLength > maybeBodyIndex;
370
392
  const body = hasBodyCandidate ? tupleWithBody[tupleLength - 1] : void 0;
371
393
  const tuple = hasBodyCandidate ? tupleWithBody.slice(0, tupleLength - 1) : tupleWithBody;
372
- return fetchEndpoint(tuple, { body, onReceive: buildOnReceive, requireBody: false });
394
+ return fetchEndpoint(tuple, {
395
+ body,
396
+ onReceive: buildOnReceive,
397
+ requireBody: false
398
+ });
373
399
  };
374
400
  if (isGet && isFeed) {
375
401
  const useEndpoint2 = (...useArgs) => {
@@ -380,13 +406,10 @@ function createRouteClient(opts) {
380
406
  const query = args?.query;
381
407
  const buildOptions = rqOpts ?? {};
382
408
  const listenersRef = (0, import_react.useRef)(/* @__PURE__ */ new Set());
383
- const notifyOnReceive = (0, import_react.useCallback)(
384
- (data) => {
385
- buildOptions?.onReceive?.(data);
386
- listenersRef.current.forEach((listener) => listener(data));
387
- },
388
- []
389
- );
409
+ const notifyOnReceive = (0, import_react.useCallback)((data) => {
410
+ buildOptions?.onReceive?.(data);
411
+ listenersRef.current.forEach((listener) => listener(data));
412
+ }, []);
390
413
  const registerOnReceive = (0, import_react.useCallback)(
391
414
  (listener) => {
392
415
  listenersRef.current.add(listener);
@@ -402,24 +425,27 @@ function createRouteClient(opts) {
402
425
  params,
403
426
  query
404
427
  );
405
- const queryResult = (0, import_react_query.useInfiniteQuery)({
406
- ...buildOptions,
407
- queryKey: getQueryKeys(...tuple),
408
- initialPageParam: void 0,
409
- getNextPageParam: (lastPage) => getNextCursor(lastPage),
410
- placeholderData: import_react_query.keepPreviousData,
411
- queryFn: ({ pageParam }) => {
412
- const pageQuery = {
413
- ...normalizedQuery,
414
- ...pageParam ? { [cursorParam]: pageParam } : {}
415
- };
416
- return fetchEndpoint(tuple, {
417
- queryOverride: pageQuery,
418
- onReceive: notifyOnReceive
419
- });
420
- }
421
- // NOTE: TData is InfiniteData<T>, so we don't need a select here.
422
- }, queryClient);
428
+ const queryResult = (0, import_react_query.useInfiniteQuery)(
429
+ {
430
+ ...buildOptions,
431
+ queryKey: getQueryKeys(...tuple),
432
+ initialPageParam: void 0,
433
+ getNextPageParam: (lastPage) => getNextCursor(lastPage),
434
+ placeholderData: import_react_query.keepPreviousData,
435
+ queryFn: ({ pageParam }) => {
436
+ const pageQuery = {
437
+ ...normalizedQuery,
438
+ ...pageParam ? { [cursorParam]: pageParam } : {}
439
+ };
440
+ return fetchEndpoint(tuple, {
441
+ queryOverride: pageQuery,
442
+ onReceive: notifyOnReceive
443
+ });
444
+ }
445
+ // NOTE: TData is InfiniteData<T>, so we don't need a select here.
446
+ },
447
+ queryClient
448
+ );
423
449
  return { ...queryResult, onReceive: registerOnReceive };
424
450
  };
425
451
  return {
@@ -439,13 +465,10 @@ function createRouteClient(opts) {
439
465
  const query = args?.query;
440
466
  const buildOptions = rqOpts ?? {};
441
467
  const listenersRef = (0, import_react.useRef)(/* @__PURE__ */ new Set());
442
- const notifyOnReceive = (0, import_react.useCallback)(
443
- (data) => {
444
- buildOptions?.onReceive?.(data);
445
- listenersRef.current.forEach((listener) => listener(data));
446
- },
447
- []
448
- );
468
+ const notifyOnReceive = (0, import_react.useCallback)((data) => {
469
+ buildOptions?.onReceive?.(data);
470
+ listenersRef.current.forEach((listener) => listener(data));
471
+ }, []);
449
472
  const registerOnReceive = (0, import_react.useCallback)(
450
473
  (listener) => {
451
474
  listenersRef.current.add(listener);
@@ -456,14 +479,17 @@ function createRouteClient(opts) {
456
479
  []
457
480
  );
458
481
  buildUrl({ ...leaf, cfg: leafCfg }, baseUrl, params, query);
459
- const queryResult = (0, import_react_query.useQuery)({
460
- ...buildOptions,
461
- queryKey: getQueryKeys(...tuple),
462
- placeholderData: import_react_query.keepPreviousData,
463
- queryFn: () => fetchEndpoint(tuple, {
464
- onReceive: notifyOnReceive
465
- })
466
- }, queryClient);
482
+ const queryResult = (0, import_react_query.useQuery)(
483
+ {
484
+ ...buildOptions,
485
+ queryKey: getQueryKeys(...tuple),
486
+ placeholderData: import_react_query.keepPreviousData,
487
+ queryFn: () => fetchEndpoint(tuple, {
488
+ onReceive: notifyOnReceive
489
+ })
490
+ },
491
+ queryClient
492
+ );
467
493
  return { ...queryResult, onReceive: registerOnReceive };
468
494
  };
469
495
  return {
@@ -497,21 +523,29 @@ function createRouteClient(opts) {
497
523
  const notifyListeners = (0, import_react.useCallback)((data) => {
498
524
  listenersRef.current.forEach((listener) => listener(data));
499
525
  }, []);
500
- const registerOnReceive = (0, import_react.useCallback)((listener) => {
501
- listenersRef.current.add(listener);
502
- return () => {
503
- listenersRef.current.delete(listener);
504
- };
505
- }, []);
506
- const mutationResult = (0, import_react_query.useMutation)({
507
- ...mutationBuildOptions,
508
- mutationKey: getQueryKeys(...tuple),
509
- mutationFn: async (body) => {
510
- const result = await fetchMutation(...[...tuple, body]);
511
- notifyListeners(result);
512
- return result;
513
- }
514
- }, queryClient);
526
+ const registerOnReceive = (0, import_react.useCallback)(
527
+ (listener) => {
528
+ listenersRef.current.add(listener);
529
+ return () => {
530
+ listenersRef.current.delete(listener);
531
+ };
532
+ },
533
+ []
534
+ );
535
+ const mutationResult = (0, import_react_query.useMutation)(
536
+ {
537
+ ...mutationBuildOptions,
538
+ mutationKey: getQueryKeys(...tuple),
539
+ mutationFn: async (body) => {
540
+ const result = await fetchMutation(
541
+ ...[...tuple, body]
542
+ );
543
+ notifyListeners(result);
544
+ return result;
545
+ }
546
+ },
547
+ queryClient
548
+ );
515
549
  return { ...mutationResult, onReceive: registerOnReceive };
516
550
  };
517
551
  return {
@@ -522,25 +556,163 @@ function createRouteClient(opts) {
522
556
  fetch: fetchMutation
523
557
  };
524
558
  }
559
+ const fetchRaw = async (input) => {
560
+ const { path, method, query, body, params } = input;
561
+ if (!path || typeof path !== "string") {
562
+ throw new Error("fetch(path, ...) requires a non-empty string path.");
563
+ }
564
+ if (!method) {
565
+ throw new Error("fetch(path, method, ...) requires an HTTP method.");
566
+ }
567
+ const methodLower = String(method).toLowerCase();
568
+ const methodUpper = toUpper(methodLower);
569
+ const flatQuery = normalizeFlatQuery(query);
570
+ const search = toSearchString(flatQuery);
571
+ const compiledPath = compileRawPath(path, params);
572
+ const url = `${baseUrl ?? ""}${compiledPath}${search}`;
573
+ const leafLabel = `${methodUpper} ${path}`;
574
+ const startedAt = Date.now();
575
+ const detail = isVerboseDebug ? { params, query: flatQuery } : void 0;
576
+ emitDebug(
577
+ decorateDebugEvent(
578
+ {
579
+ type: "fetch",
580
+ stage: "start",
581
+ method: methodUpper,
582
+ url,
583
+ leaf: leafLabel,
584
+ ...body !== void 0 ? { body } : {}
585
+ },
586
+ detail
587
+ )
588
+ );
589
+ try {
590
+ const out = await fetcher(
591
+ body === void 0 ? { url, method: methodUpper } : { url, method: methodUpper, body }
592
+ );
593
+ emitDebug(
594
+ decorateDebugEvent(
595
+ {
596
+ type: "fetch",
597
+ stage: "success",
598
+ method: methodUpper,
599
+ url,
600
+ leaf: leafLabel,
601
+ durationMs: Date.now() - startedAt
602
+ },
603
+ isVerboseDebug ? { params, query: flatQuery, output: out } : void 0
604
+ )
605
+ );
606
+ return out;
607
+ } catch (error) {
608
+ emitDebug(
609
+ decorateDebugEvent(
610
+ {
611
+ type: "fetch",
612
+ stage: "error",
613
+ method: methodUpper,
614
+ url,
615
+ leaf: leafLabel,
616
+ durationMs: Date.now() - startedAt,
617
+ ...body !== void 0 ? { body } : {},
618
+ error
619
+ },
620
+ detail
621
+ )
622
+ );
623
+ throw error;
624
+ }
625
+ };
525
626
  return {
526
627
  queryClient,
527
628
  invalidate,
629
+ fetch: fetchRaw,
528
630
  build: buildInternal
529
631
  };
530
632
  }
531
633
  function buildRouter(routeClient, routes) {
532
634
  const buildLeaf = routeClient.build;
533
- return ((key, opts, meta) => buildLeaf(routes[key], opts, meta));
635
+ return ((key, opts, meta) => buildLeaf(
636
+ routes[key],
637
+ opts,
638
+ meta
639
+ ));
534
640
  }
535
641
  function toFormData(body) {
536
642
  const fd = new FormData();
537
643
  for (const [k, v] of Object.entries(body ?? {})) {
538
644
  if (v == null) continue;
539
- if (Array.isArray(v)) v.forEach((item, i) => fd.append(`${k}[${i}]`, item));
645
+ if (Array.isArray(v))
646
+ v.forEach((item, i) => fd.append(`${k}[${i}]`, item));
540
647
  else fd.append(k, v);
541
648
  }
542
649
  return fd;
543
650
  }
651
+ function getPathParamNames(path) {
652
+ const names = /* @__PURE__ */ new Set();
653
+ const re = /:([A-Za-z0-9_]+)/g;
654
+ let match;
655
+ while ((match = re.exec(path)) !== null) {
656
+ names.add(match[1]);
657
+ }
658
+ return names;
659
+ }
660
+ function normalizeFlatQuery(query) {
661
+ if (query == null) return void 0;
662
+ if (typeof query !== "object" || Array.isArray(query)) {
663
+ throw new Error("Query must be a plain object (Record<string, string>).");
664
+ }
665
+ const result = {};
666
+ for (const [k, v] of Object.entries(query)) {
667
+ if (v == null) continue;
668
+ if (typeof v !== "string") {
669
+ throw new Error(
670
+ `Query param "${k}" must be a string; received type "${typeof v}".`
671
+ );
672
+ }
673
+ result[k] = v;
674
+ }
675
+ return Object.keys(result).length > 0 ? result : void 0;
676
+ }
677
+ function compileRawPath(path, params) {
678
+ const placeholders = getPathParamNames(path);
679
+ if (!params || typeof params !== "object" || Array.isArray(params)) {
680
+ if (placeholders.size > 0) {
681
+ throw new Error(
682
+ `Missing path parameters for "${path}": ${[...placeholders].join(
683
+ ", "
684
+ )}`
685
+ );
686
+ }
687
+ return path;
688
+ }
689
+ const paramObj = params;
690
+ const providedNames = new Set(Object.keys(paramObj));
691
+ for (const name of providedNames) {
692
+ if (!placeholders.has(name)) {
693
+ throw new Error(
694
+ `Unexpected path parameter "${name}" for template "${path}".`
695
+ );
696
+ }
697
+ const value = paramObj[name];
698
+ if (value != null && (typeof value === "object" || Array.isArray(value))) {
699
+ throw new Error(
700
+ `Path parameter "${name}" must be a primitive; received "${typeof value}".`
701
+ );
702
+ }
703
+ }
704
+ for (const name of placeholders) {
705
+ if (!providedNames.has(name)) {
706
+ throw new Error(
707
+ `Missing value for path parameter "${name}" in template "${path}".`
708
+ );
709
+ }
710
+ }
711
+ if (placeholders.size === 0) {
712
+ return path;
713
+ }
714
+ return (0, import_rrroutes_contract.compilePath)(path, paramObj);
715
+ }
544
716
 
545
717
  // src/sockets/socket.client.sys.ts
546
718
  var import_zod2 = require("zod");
@@ -799,7 +971,21 @@ function roomsFromData(data, toRooms) {
799
971
  if (data == null) return { rooms: [] };
800
972
  let state = { rooms: [] };
801
973
  const add = (input) => {
802
- state = mergeRoomState(state, toRooms(input));
974
+ const mergeForValue = (value) => {
975
+ state = mergeRoomState(state, toRooms(value));
976
+ };
977
+ if (Array.isArray(input)) {
978
+ input.forEach((entry) => mergeForValue(entry));
979
+ return;
980
+ }
981
+ if (input && typeof input === "object") {
982
+ const maybeItems = input.items;
983
+ if (Array.isArray(maybeItems)) {
984
+ maybeItems.forEach((entry) => mergeForValue(entry));
985
+ return;
986
+ }
987
+ }
988
+ mergeForValue(input);
803
989
  };
804
990
  const maybePages = data?.pages;
805
991
  if (Array.isArray(maybePages)) {
@@ -813,22 +999,34 @@ function buildSocketedRoute(options) {
813
999
  const { built, toRooms, applySocket, useSocketClient: useSocketClient2 } = options;
814
1000
  return (...useArgs) => {
815
1001
  const client = useSocketClient2();
816
- const endpointResult = built.useEndpoint(...useArgs);
1002
+ const endpointResult = built.useEndpoint(
1003
+ ...useArgs
1004
+ );
817
1005
  const argsKey = (0, import_react2.useMemo)(() => JSON.stringify(useArgs[0] ?? null), [useArgs]);
818
1006
  const [roomState, setRoomState] = (0, import_react2.useState)(
819
1007
  () => roomsFromData(endpointResult.data, toRooms)
820
1008
  );
821
1009
  const roomsKey = (0, import_react2.useMemo)(() => roomState.rooms.join("|"), [roomState.rooms]);
822
- const joinMetaKey = (0, import_react2.useMemo)(() => JSON.stringify(roomState.joinMeta ?? null), [roomState.joinMeta]);
823
- const leaveMetaKey = (0, import_react2.useMemo)(() => JSON.stringify(roomState.leaveMeta ?? null), [roomState.leaveMeta]);
1010
+ const joinMetaKey = (0, import_react2.useMemo)(
1011
+ () => JSON.stringify(roomState.joinMeta ?? null),
1012
+ [roomState.joinMeta]
1013
+ );
1014
+ const leaveMetaKey = (0, import_react2.useMemo)(
1015
+ () => JSON.stringify(roomState.leaveMeta ?? null),
1016
+ [roomState.leaveMeta]
1017
+ );
824
1018
  (0, import_react2.useEffect)(() => {
825
1019
  const unsubscribe = endpointResult.onReceive((data) => {
826
- setRoomState((prev) => mergeRoomState(prev, toRooms(data)));
1020
+ setRoomState(
1021
+ (prev) => mergeRoomState(prev, toRooms(data))
1022
+ );
827
1023
  });
828
1024
  return unsubscribe;
829
1025
  }, [endpointResult, toRooms]);
830
1026
  (0, import_react2.useEffect)(() => {
831
- setRoomState(roomsFromData(endpointResult.data, toRooms));
1027
+ setRoomState(
1028
+ roomsFromData(endpointResult.data, toRooms)
1029
+ );
832
1030
  }, [endpointResult.data, toRooms]);
833
1031
  (0, import_react2.useEffect)(() => {
834
1032
  if (roomState.rooms.length === 0) return;
@@ -847,7 +1045,14 @@ function buildSocketedRoute(options) {
847
1045
  void client.leaveRooms(roomState.rooms, leaveMeta).catch(() => {
848
1046
  });
849
1047
  };
850
- }, [client, roomsKey, roomState.joinMeta, roomState.leaveMeta, joinMetaKey, leaveMetaKey]);
1048
+ }, [
1049
+ client,
1050
+ roomsKey,
1051
+ roomState.joinMeta,
1052
+ roomState.leaveMeta,
1053
+ joinMetaKey,
1054
+ leaveMetaKey
1055
+ ]);
851
1056
  (0, import_react2.useEffect)(() => {
852
1057
  const entries = Object.entries(applySocket).filter(
853
1058
  ([_event, fn]) => typeof fn === "function"
@@ -856,7 +1061,9 @@ function buildSocketedRoute(options) {
856
1061
  ([ev, fn]) => client.on(ev, (payload, meta) => {
857
1062
  built.setData((prev) => {
858
1063
  const next = fn(prev, payload, meta);
859
- setRoomState(roomsFromData(next, toRooms));
1064
+ setRoomState(
1065
+ roomsFromData(next, toRooms)
1066
+ );
860
1067
  return next;
861
1068
  }, ...useArgs);
862
1069
  })
@@ -905,7 +1112,11 @@ var SocketClient = class {
905
1112
  }
906
1113
  this.onConnect = async () => {
907
1114
  if (!this.socket) {
908
- this.dbg({ type: "connection", phase: "connect_event", err: "Socket is null" });
1115
+ this.dbg({
1116
+ type: "connection",
1117
+ phase: "connect_event",
1118
+ err: "Socket is null"
1119
+ });
909
1120
  throw new Error("Socket is null in onConnect handler");
910
1121
  }
911
1122
  this.dbg({
@@ -924,7 +1135,11 @@ var SocketClient = class {
924
1135
  };
925
1136
  this.onReconnect = async (attempt) => {
926
1137
  if (!this.socket) {
927
- this.dbg({ type: "connection", phase: "reconnect_event", err: "Socket is null" });
1138
+ this.dbg({
1139
+ type: "connection",
1140
+ phase: "reconnect_event",
1141
+ err: "Socket is null"
1142
+ });
928
1143
  throw new Error("Socket is null in onReconnect handler");
929
1144
  }
930
1145
  this.dbg({
@@ -945,7 +1160,11 @@ var SocketClient = class {
945
1160
  };
946
1161
  this.onDisconnect = async (reason) => {
947
1162
  if (!this.socket) {
948
- this.dbg({ type: "connection", phase: "disconnect_event", err: "Socket is null" });
1163
+ this.dbg({
1164
+ type: "connection",
1165
+ phase: "disconnect_event",
1166
+ err: "Socket is null"
1167
+ });
949
1168
  throw new Error("Socket is null in onDisconnect handler");
950
1169
  }
951
1170
  this.dbg({
@@ -965,7 +1184,11 @@ var SocketClient = class {
965
1184
  };
966
1185
  this.onConnectError = async (err) => {
967
1186
  if (!this.socket) {
968
- this.dbg({ type: "connection", phase: "connect_error_event", err: "Socket is null" });
1187
+ this.dbg({
1188
+ type: "connection",
1189
+ phase: "connect_error_event",
1190
+ err: "Socket is null"
1191
+ });
969
1192
  throw new Error("Socket is null in onConnectError handler");
970
1193
  }
971
1194
  this.dbg({
@@ -983,7 +1206,11 @@ var SocketClient = class {
983
1206
  };
984
1207
  this.onPong = async (raw) => {
985
1208
  if (!this.socket) {
986
- this.dbg({ type: "heartbeat", phase: "pong_recv", err: "Socket is null" });
1209
+ this.dbg({
1210
+ type: "heartbeat",
1211
+ phase: "pong_recv",
1212
+ err: "Socket is null"
1213
+ });
987
1214
  throw new Error("Socket is null in onPong handler");
988
1215
  }
989
1216
  const parsed = this.config.pongPayload.safeParse(raw);
@@ -1083,11 +1310,15 @@ var SocketClient = class {
1083
1310
  }
1084
1311
  /** internal stats snapshot */
1085
1312
  stats() {
1086
- const rooms = Array.from(this.roomCounts.entries()).map(([room, count]) => ({ room, count }));
1087
- const handlers = Array.from(this.handlerMap.entries()).map(([event, set]) => ({
1088
- event,
1089
- handlers: set.size
1090
- }));
1313
+ const rooms = Array.from(this.roomCounts.entries()).map(
1314
+ ([room, count]) => ({ room, count })
1315
+ );
1316
+ const handlers = Array.from(this.handlerMap.entries()).map(
1317
+ ([event, set]) => ({
1318
+ event,
1319
+ handlers: set.size
1320
+ })
1321
+ );
1091
1322
  return {
1092
1323
  roomsCount: rooms.length,
1093
1324
  totalHandlers: handlers.reduce((a, b) => a + b.handlers, 0),
@@ -1159,7 +1390,10 @@ var SocketClient = class {
1159
1390
  details: this.getValidationDetails(check.error)
1160
1391
  });
1161
1392
  if (this.environment === "development") {
1162
- console.warn("[socket] ping schema validation failed", check.error.issues);
1393
+ console.warn(
1394
+ "[socket] ping schema validation failed",
1395
+ check.error.issues
1396
+ );
1163
1397
  }
1164
1398
  return;
1165
1399
  }
@@ -1218,7 +1452,12 @@ var SocketClient = class {
1218
1452
  }
1219
1453
  async joinRooms(rooms, meta) {
1220
1454
  if (!this.socket) {
1221
- this.dbg({ type: "room", phase: "join", rooms: this.toArray(rooms), err: "Socket is null" });
1455
+ this.dbg({
1456
+ type: "room",
1457
+ phase: "join",
1458
+ rooms: this.toArray(rooms),
1459
+ err: "Socket is null"
1460
+ });
1222
1461
  throw new Error("Socket is null in joinRooms method");
1223
1462
  }
1224
1463
  if (!await this.getSysEvent("sys:room_join")({
@@ -1271,7 +1510,12 @@ var SocketClient = class {
1271
1510
  }
1272
1511
  async leaveRooms(rooms, meta) {
1273
1512
  if (!this.socket) {
1274
- this.dbg({ type: "room", phase: "leave", rooms: this.toArray(rooms), err: "Socket is null" });
1513
+ this.dbg({
1514
+ type: "room",
1515
+ phase: "leave",
1516
+ rooms: this.toArray(rooms),
1517
+ err: "Socket is null"
1518
+ });
1275
1519
  throw new Error("Socket is null in leaveRooms method");
1276
1520
  }
1277
1521
  if (!await this.getSysEvent("sys:room_leave")({
@@ -1323,7 +1567,12 @@ var SocketClient = class {
1323
1567
  const schema = this.events[event].message;
1324
1568
  this.dbg({ type: "register", phase: "register", event });
1325
1569
  if (!this.socket) {
1326
- this.dbg({ type: "register", phase: "register", event, err: "Socket is null" });
1570
+ this.dbg({
1571
+ type: "register",
1572
+ phase: "register",
1573
+ event,
1574
+ err: "Socket is null"
1575
+ });
1327
1576
  return () => {
1328
1577
  };
1329
1578
  }