@emeryld/rrroutes-client 2.4.1 → 2.4.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/dist/index.cjs CHANGED
@@ -95,10 +95,13 @@ var defaultFetcher = async (req) => {
95
95
  }
96
96
  };
97
97
 
98
- // src/routesV3.client.index.ts
99
- var import_rrroutes_contract = require("@emeryld/rrroutes-contract");
98
+ // src/routesV3.client.get.ts
99
+ var import_rrroutes_contract2 = require("@emeryld/rrroutes-contract");
100
100
  var import_react_query = require("@tanstack/react-query");
101
101
  var import_react = require("react");
102
+
103
+ // src/routesV3.client.shared.ts
104
+ var import_rrroutes_contract = require("@emeryld/rrroutes-contract");
102
105
  var toUpper = (m) => m.toUpperCase();
103
106
  function toSearchString(query) {
104
107
  if (!query) return "";
@@ -109,34 +112,895 @@ function toSearchString(query) {
109
112
  params.append(k, v);
110
113
  continue;
111
114
  }
112
- if (typeof v === "number" || typeof v === "boolean") {
113
- params.append(k, String(v));
114
- continue;
115
+ if (typeof v === "number" || typeof v === "boolean") {
116
+ params.append(k, String(v));
117
+ continue;
118
+ }
119
+ params.append(k, JSON.stringify(v));
120
+ }
121
+ const s = params.toString();
122
+ return s ? `?${s}` : "";
123
+ }
124
+ function stripKey(obj, key) {
125
+ if (!obj) return obj;
126
+ const { [key]: _omit, ...rest } = obj;
127
+ return rest;
128
+ }
129
+ var defaultGetNextCursor = (p) => {
130
+ if (!p || typeof p !== "object") return void 0;
131
+ const record = p;
132
+ if ("nextCursor" in record) {
133
+ return record.nextCursor;
134
+ }
135
+ if ("meta" in record) {
136
+ const meta = record.meta;
137
+ if (meta && typeof meta === "object" && "nextCursor" in meta) {
138
+ return meta.nextCursor;
139
+ }
140
+ }
141
+ return void 0;
142
+ };
143
+ function extractArgs(args) {
144
+ return args[0];
145
+ }
146
+ function toArgsTuple(args) {
147
+ return typeof args === "undefined" ? [] : [args];
148
+ }
149
+ function buildUrl(leaf, baseUrl, params, query) {
150
+ const normalizedParams = leaf.cfg.paramsSchema ? (0, import_rrroutes_contract.lowProfileParse)(leaf.cfg.paramsSchema, params) : {};
151
+ const normalizedQuery = leaf.cfg.querySchema ? (0, import_rrroutes_contract.lowProfileParse)(leaf.cfg.querySchema, query) : {};
152
+ const path = (0, import_rrroutes_contract.compilePath)(
153
+ leaf.path,
154
+ normalizedParams ?? {}
155
+ );
156
+ const url = `${baseUrl ?? ""}${path}${toSearchString(normalizedQuery)}`;
157
+ return { url, normalizedQuery, normalizedParams };
158
+ }
159
+ function toFormData(body) {
160
+ const fd = new FormData();
161
+ for (const [k, v] of Object.entries(body ?? {})) {
162
+ if (v == null) continue;
163
+ if (Array.isArray(v))
164
+ v.forEach((item, i) => fd.append(`${k}[${i}]`, item));
165
+ else fd.append(k, v);
166
+ }
167
+ return fd;
168
+ }
169
+ function getPathParamNames(path) {
170
+ const names = /* @__PURE__ */ new Set();
171
+ const re = /:([A-Za-z0-9_]+)/g;
172
+ let match;
173
+ while ((match = re.exec(path)) !== null) {
174
+ names.add(match[1]);
175
+ }
176
+ return names;
177
+ }
178
+ function normalizeFlatQuery(query) {
179
+ if (query == null) return void 0;
180
+ if (typeof query !== "object" || Array.isArray(query)) {
181
+ throw new Error("Query must be a plain object (Record<string, string>).");
182
+ }
183
+ const result = {};
184
+ for (const [k, v] of Object.entries(query)) {
185
+ if (v == null) continue;
186
+ if (typeof v !== "string") {
187
+ throw new Error(
188
+ `Query param "${k}" must be a string; received type "${typeof v}".`
189
+ );
190
+ }
191
+ result[k] = v;
192
+ }
193
+ return Object.keys(result).length > 0 ? result : void 0;
194
+ }
195
+ function compileRawPath(path, params) {
196
+ const placeholders = getPathParamNames(path);
197
+ if (!params || typeof params !== "object" || Array.isArray(params)) {
198
+ if (placeholders.size > 0) {
199
+ throw new Error(
200
+ `Missing path parameters for "${path}": ${[...placeholders].join(
201
+ ", "
202
+ )}`
203
+ );
204
+ }
205
+ return path;
206
+ }
207
+ const paramObj = params;
208
+ const providedNames = new Set(Object.keys(paramObj));
209
+ for (const name of providedNames) {
210
+ if (!placeholders.has(name)) {
211
+ throw new Error(
212
+ `Unexpected path parameter "${name}" for template "${path}".`
213
+ );
214
+ }
215
+ const value = paramObj[name];
216
+ if (value != null && (typeof value === "object" || Array.isArray(value))) {
217
+ throw new Error(
218
+ `Path parameter "${name}" must be a primitive; received "${typeof value}".`
219
+ );
220
+ }
221
+ }
222
+ for (const name of placeholders) {
223
+ if (!providedNames.has(name)) {
224
+ throw new Error(
225
+ `Missing value for path parameter "${name}" in template "${path}".`
226
+ );
227
+ }
228
+ }
229
+ if (placeholders.size === 0) {
230
+ return path;
231
+ }
232
+ return (0, import_rrroutes_contract.compilePath)(path, paramObj);
233
+ }
234
+
235
+ // src/routesV3.client.get.ts
236
+ function buildGetLeaf(leaf, rqOpts, env) {
237
+ const leafCfg = leaf.cfg;
238
+ const method = toUpper(leaf.method);
239
+ const expectsArgs = Boolean(leafCfg.paramsSchema || leafCfg.querySchema);
240
+ const {
241
+ baseUrl,
242
+ validateResponses,
243
+ fetcher,
244
+ queryClient,
245
+ emit,
246
+ decorateDebugEvent,
247
+ isVerboseDebug,
248
+ leafLabel
249
+ } = env;
250
+ emit({ type: "build", leaf: leafLabel });
251
+ const getQueryKeys = (...tuple) => {
252
+ const a = extractArgs(tuple);
253
+ const params = a?.params;
254
+ const query = a?.query;
255
+ return (0, import_rrroutes_contract2.buildCacheKey)({
256
+ leaf,
257
+ params,
258
+ query
259
+ });
260
+ };
261
+ const invalidateExact = async (...tuple) => {
262
+ const queryKey = getQueryKeys(...tuple);
263
+ await queryClient.invalidateQueries({ queryKey, exact: true });
264
+ emit({ type: "invalidate", key: queryKey, exact: true });
265
+ };
266
+ const setData = (...args) => {
267
+ const [updater, ...rest] = args;
268
+ const k = getQueryKeys(...rest);
269
+ const next = queryClient.setQueryData(
270
+ k,
271
+ (prev) => typeof updater === "function" ? updater(prev) : updater
272
+ );
273
+ emit({ type: "setData", key: k });
274
+ return next;
275
+ };
276
+ const buildOnReceive = rqOpts?.onReceive ?? void 0;
277
+ const fetchEndpoint = async (tuple, options) => {
278
+ const a = extractArgs(tuple);
279
+ const params = a?.params;
280
+ const query = options?.queryOverride ?? a?.query;
281
+ const { url, normalizedQuery, normalizedParams } = buildUrl(
282
+ { ...leaf, cfg: leafCfg },
283
+ baseUrl,
284
+ params,
285
+ query
286
+ );
287
+ let payload;
288
+ const acceptsBody = Boolean(leafCfg.bodySchema);
289
+ const requiresBody = options?.requireBody ?? (!acceptsBody ? false : true);
290
+ if (typeof options?.body !== "undefined") {
291
+ const normalizedBody = leafCfg.bodySchema ? (0, import_rrroutes_contract2.lowProfileParse)(leafCfg.bodySchema, options.body) : void 0;
292
+ const isMultipart = Array.isArray(leafCfg.bodyFiles) && leafCfg.bodyFiles.length > 0;
293
+ if (isMultipart && normalizedBody && typeof normalizedBody === "object") {
294
+ payload = toFormData(normalizedBody);
295
+ } else {
296
+ payload = normalizedBody;
297
+ }
298
+ } else if (requiresBody && acceptsBody) {
299
+ throw new Error("Body is required when invoking a mutation fetch.");
300
+ }
301
+ const startedAt = Date.now();
302
+ const detail = isVerboseDebug ? { params, normalizedParams, query, normalizedQuery, baseUrl } : void 0;
303
+ emit(
304
+ decorateDebugEvent(
305
+ {
306
+ type: "fetch",
307
+ stage: "start",
308
+ method,
309
+ url,
310
+ leaf: leafLabel,
311
+ ...payload !== void 0 ? { body: payload } : {}
312
+ },
313
+ detail
314
+ )
315
+ );
316
+ try {
317
+ const out = await fetcher(
318
+ payload === void 0 ? { url, method } : { url, method, body: payload }
319
+ );
320
+ emit(
321
+ decorateDebugEvent(
322
+ {
323
+ type: "fetch",
324
+ stage: "fetched",
325
+ method,
326
+ url,
327
+ leaf: leafLabel,
328
+ durationMs: Date.now() - startedAt
329
+ },
330
+ isVerboseDebug ? {
331
+ params: normalizedParams,
332
+ query: normalizedQuery,
333
+ output: out
334
+ } : void 0
335
+ )
336
+ );
337
+ if (validateResponses) {
338
+ if (!leafCfg.outputSchema) {
339
+ throw new Error(
340
+ `No output schema defined for leaf ${leafLabel}, cannot validate response.`
341
+ );
342
+ }
343
+ out.data = (0, import_rrroutes_contract2.lowProfileParse)(
344
+ leafCfg.outputSchema,
345
+ out.data
346
+ );
347
+ emit(
348
+ decorateDebugEvent(
349
+ {
350
+ type: "fetch",
351
+ stage: "parsed",
352
+ method,
353
+ url,
354
+ leaf: leafLabel,
355
+ durationMs: Date.now() - startedAt
356
+ },
357
+ isVerboseDebug ? {
358
+ params: normalizedParams,
359
+ query: normalizedQuery,
360
+ output: out
361
+ } : void 0
362
+ )
363
+ );
364
+ }
365
+ options?.onReceive?.(out.data);
366
+ return out.data;
367
+ } catch (error) {
368
+ emit(
369
+ decorateDebugEvent(
370
+ {
371
+ type: "fetch",
372
+ stage: "error",
373
+ method,
374
+ url,
375
+ leaf: leafLabel,
376
+ durationMs: Date.now() - startedAt,
377
+ ...payload !== void 0 ? { body: payload } : {},
378
+ error
379
+ },
380
+ detail
381
+ )
382
+ );
383
+ throw error;
384
+ }
385
+ };
386
+ const fetchGet = (...tupleWithBody) => {
387
+ const acceptsBody = Boolean(leafCfg.bodySchema);
388
+ const tupleLength = tupleWithBody.length;
389
+ const maybeBodyIndex = expectsArgs ? 1 : 0;
390
+ const hasBodyCandidate = acceptsBody && tupleLength > maybeBodyIndex;
391
+ const body = hasBodyCandidate ? tupleWithBody[tupleLength - 1] : void 0;
392
+ const tuple = hasBodyCandidate ? tupleWithBody.slice(0, tupleLength - 1) : tupleWithBody;
393
+ return fetchEndpoint(tuple, {
394
+ body,
395
+ onReceive: buildOnReceive,
396
+ requireBody: false
397
+ });
398
+ };
399
+ const useEndpoint = (...useArgs) => {
400
+ const args = useArgs[0];
401
+ const tuple = toArgsTuple(args);
402
+ const queryKeys = getQueryKeys(...tuple);
403
+ emit({
404
+ type: "useEndpoint",
405
+ leaf: leafLabel,
406
+ variant: "get",
407
+ keys: queryKeys
408
+ });
409
+ const buildOptions = rqOpts ?? {};
410
+ const listenersRef = (0, import_react.useRef)(/* @__PURE__ */ new Set());
411
+ const notifyOnReceive = (0, import_react.useCallback)((data) => {
412
+ buildOptions?.onReceive?.(data);
413
+ listenersRef.current.forEach((listener) => listener(data));
414
+ }, []);
415
+ const registerOnReceive = (0, import_react.useCallback)(
416
+ (listener) => {
417
+ listenersRef.current.add(listener);
418
+ return () => {
419
+ listenersRef.current.delete(listener);
420
+ };
421
+ },
422
+ []
423
+ );
424
+ const queryResult = (0, import_react_query.useQuery)(
425
+ {
426
+ ...buildOptions,
427
+ queryKey: getQueryKeys(...tuple),
428
+ placeholderData: import_react_query.keepPreviousData,
429
+ queryFn: () => fetchEndpoint(tuple, {
430
+ onReceive: notifyOnReceive
431
+ })
432
+ },
433
+ queryClient
434
+ );
435
+ return { ...queryResult, onReceive: registerOnReceive };
436
+ };
437
+ return {
438
+ getQueryKeys,
439
+ invalidate: invalidateExact,
440
+ setData,
441
+ useEndpoint,
442
+ fetch: fetchGet
443
+ };
444
+ }
445
+
446
+ // src/routesV3.client.infiniteGet.ts
447
+ var import_rrroutes_contract3 = require("@emeryld/rrroutes-contract");
448
+ var import_react_query2 = require("@tanstack/react-query");
449
+ var import_react2 = require("react");
450
+ function mergePageOutputs(prev, next) {
451
+ if (prev == null) return next;
452
+ if (next == null) return prev;
453
+ if (Array.isArray(prev) && Array.isArray(next)) {
454
+ return [...prev, ...next];
455
+ }
456
+ if (typeof prev !== "object" || typeof next !== "object") {
457
+ return next;
458
+ }
459
+ const merged = { ...prev };
460
+ for (const key of Object.keys(next)) {
461
+ const pv = prev[key];
462
+ const nv = next[key];
463
+ if (Array.isArray(pv) && Array.isArray(nv)) {
464
+ merged[key] = [...pv, ...nv];
465
+ continue;
466
+ }
467
+ if (pv && typeof pv === "object" && !Array.isArray(pv) && nv && typeof nv === "object" && !Array.isArray(nv)) {
468
+ if (key === "meta") {
469
+ merged[key] = mergePageOutputs(pv, nv);
470
+ } else {
471
+ merged[key] = nv;
472
+ }
473
+ continue;
474
+ }
475
+ merged[key] = nv;
476
+ }
477
+ return merged;
478
+ }
479
+ function buildInfiniteGetLeaf(leaf, rqOpts, env) {
480
+ const leafCfg = leaf.cfg;
481
+ const method = toUpper(leaf.method);
482
+ const expectsArgs = Boolean(leafCfg.paramsSchema || leafCfg.querySchema);
483
+ const {
484
+ baseUrl,
485
+ validateResponses,
486
+ fetcher,
487
+ queryClient,
488
+ emit,
489
+ decorateDebugEvent,
490
+ isVerboseDebug,
491
+ leafLabel
492
+ } = env;
493
+ emit({ type: "build", leaf: leafLabel });
494
+ const infiniteOptions = rqOpts ?? {};
495
+ const {
496
+ cursorParam,
497
+ getNextPageParam,
498
+ initialPageParam,
499
+ splitPageSize,
500
+ splitPageSizeParam,
501
+ ...passthroughOptions
502
+ } = infiniteOptions;
503
+ const feedCursorParam = cursorParam ?? "pagination_cursor";
504
+ const feedNextPageParam = getNextPageParam ?? ((lastPage) => defaultGetNextCursor(lastPage));
505
+ const cursorFromPage = (page) => feedNextPageParam(page);
506
+ const feedInitialPageParam = typeof initialPageParam === "undefined" ? void 0 : initialPageParam;
507
+ const feedQueryOptions = passthroughOptions;
508
+ const effectiveSplitPageSize = typeof splitPageSize === "number" && splitPageSize > 0 ? splitPageSize : void 0;
509
+ const effectiveSplitPageSizeParam = splitPageSizeParam ?? "pageSize";
510
+ const getQueryKeys = (...tuple) => {
511
+ const a = extractArgs(tuple);
512
+ const params = a?.params;
513
+ const query = a?.query;
514
+ const qForKey = stripKey(query, feedCursorParam);
515
+ return (0, import_rrroutes_contract3.buildCacheKey)({
516
+ leaf,
517
+ params,
518
+ query: qForKey
519
+ });
520
+ };
521
+ const invalidateExact = async (...tuple) => {
522
+ const queryKey = getQueryKeys(...tuple);
523
+ await queryClient.invalidateQueries({ queryKey, exact: true });
524
+ emit({ type: "invalidate", key: queryKey, exact: true });
525
+ };
526
+ const setData = (...args) => {
527
+ const [updater, ...rest] = args;
528
+ const k = getQueryKeys(...rest);
529
+ const next = queryClient.setQueryData(
530
+ k,
531
+ (prev) => typeof updater === "function" ? updater(prev) : updater
532
+ );
533
+ emit({ type: "setData", key: k });
534
+ return next;
535
+ };
536
+ const buildOnReceive = rqOpts?.onReceive ?? void 0;
537
+ const fetchEndpoint = async (tuple, options) => {
538
+ const a = extractArgs(tuple);
539
+ const params = a?.params;
540
+ const query = options?.queryOverride ?? a?.query;
541
+ const { url, normalizedQuery, normalizedParams } = buildUrl(
542
+ { ...leaf, cfg: leafCfg },
543
+ baseUrl,
544
+ params,
545
+ query
546
+ );
547
+ let payload;
548
+ const acceptsBody = Boolean(leafCfg.bodySchema);
549
+ const requiresBody = options?.requireBody ?? (!acceptsBody ? false : true);
550
+ if (typeof options?.body !== "undefined") {
551
+ const normalizedBody = leafCfg.bodySchema ? (0, import_rrroutes_contract3.lowProfileParse)(leafCfg.bodySchema, options.body) : void 0;
552
+ const isMultipart = Array.isArray(leafCfg.bodyFiles) && leafCfg.bodyFiles.length > 0;
553
+ if (isMultipart && normalizedBody && typeof normalizedBody === "object") {
554
+ payload = toFormData(normalizedBody);
555
+ } else {
556
+ payload = normalizedBody;
557
+ }
558
+ } else if (requiresBody && acceptsBody) {
559
+ throw new Error("Body is required when invoking a mutation fetch.");
560
+ }
561
+ const startedAt = Date.now();
562
+ const detail = isVerboseDebug ? { params, normalizedParams, query, normalizedQuery, baseUrl } : void 0;
563
+ emit(
564
+ decorateDebugEvent(
565
+ {
566
+ type: "fetch",
567
+ stage: "start",
568
+ method,
569
+ url,
570
+ leaf: leafLabel,
571
+ ...payload !== void 0 ? { body: payload } : {}
572
+ },
573
+ detail
574
+ )
575
+ );
576
+ try {
577
+ const out = await fetcher(
578
+ payload === void 0 ? { url, method } : { url, method, body: payload }
579
+ );
580
+ emit(
581
+ decorateDebugEvent(
582
+ {
583
+ type: "fetch",
584
+ stage: "fetched",
585
+ method,
586
+ url,
587
+ leaf: leafLabel,
588
+ durationMs: Date.now() - startedAt
589
+ },
590
+ isVerboseDebug ? {
591
+ params: normalizedParams,
592
+ query: normalizedQuery,
593
+ output: out
594
+ } : void 0
595
+ )
596
+ );
597
+ if (validateResponses) {
598
+ if (!leafCfg.outputSchema) {
599
+ throw new Error(
600
+ `No output schema defined for leaf ${leafLabel}, cannot validate response.`
601
+ );
602
+ }
603
+ out.data = (0, import_rrroutes_contract3.lowProfileParse)(
604
+ leafCfg.outputSchema,
605
+ out.data
606
+ );
607
+ emit(
608
+ decorateDebugEvent(
609
+ {
610
+ type: "fetch",
611
+ stage: "parsed",
612
+ method,
613
+ url,
614
+ leaf: leafLabel,
615
+ durationMs: Date.now() - startedAt
616
+ },
617
+ isVerboseDebug ? {
618
+ params: normalizedParams,
619
+ query: normalizedQuery,
620
+ output: out
621
+ } : void 0
622
+ )
623
+ );
624
+ }
625
+ options?.onReceive?.(out.data);
626
+ return out.data;
627
+ } catch (error) {
628
+ emit(
629
+ decorateDebugEvent(
630
+ {
631
+ type: "fetch",
632
+ stage: "error",
633
+ method,
634
+ url,
635
+ leaf: leafLabel,
636
+ durationMs: Date.now() - startedAt,
637
+ ...payload !== void 0 ? { body: payload } : {},
638
+ error
639
+ },
640
+ detail
641
+ )
642
+ );
643
+ throw error;
644
+ }
645
+ };
646
+ const fetchGet = (...tupleWithBody) => {
647
+ const acceptsBody = Boolean(leafCfg.bodySchema);
648
+ const tupleLength = tupleWithBody.length;
649
+ const maybeBodyIndex = expectsArgs ? 1 : 0;
650
+ const hasBodyCandidate = acceptsBody && tupleLength > maybeBodyIndex;
651
+ const body = hasBodyCandidate ? tupleWithBody[tupleLength - 1] : void 0;
652
+ const tuple = hasBodyCandidate ? tupleWithBody.slice(0, tupleLength - 1) : tupleWithBody;
653
+ return fetchEndpoint(tuple, {
654
+ body,
655
+ onReceive: buildOnReceive,
656
+ requireBody: false
657
+ });
658
+ };
659
+ const useEndpoint = (...useArgs) => {
660
+ const args = useArgs[0];
661
+ const tuple = toArgsTuple(args);
662
+ const queryKeys = getQueryKeys(...tuple);
663
+ emit({
664
+ type: "useEndpoint",
665
+ leaf: leafLabel,
666
+ variant: "infiniteGet",
667
+ keys: queryKeys
668
+ });
669
+ const params = args?.params;
670
+ const query = args?.query;
671
+ const buildOptions = feedQueryOptions ?? {};
672
+ const listenersRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
673
+ const notifyOnReceive = (0, import_react2.useCallback)((data) => {
674
+ buildOptions?.onReceive?.(data);
675
+ listenersRef.current.forEach((listener) => listener(data));
676
+ }, []);
677
+ const registerOnReceive = (0, import_react2.useCallback)(
678
+ (listener) => {
679
+ listenersRef.current.add(listener);
680
+ return () => {
681
+ listenersRef.current.delete(listener);
682
+ };
683
+ },
684
+ []
685
+ );
686
+ const { normalizedQuery, normalizedParams } = buildUrl(
687
+ { ...leaf, cfg: leafCfg },
688
+ baseUrl,
689
+ params,
690
+ query
691
+ );
692
+ const queryResult = (0, import_react_query2.useInfiniteQuery)(
693
+ {
694
+ ...buildOptions,
695
+ placeholderData: buildOptions.placeholderData ?? import_react_query2.keepPreviousData,
696
+ initialPageParam: feedInitialPageParam,
697
+ getNextPageParam: (lastPage) => cursorFromPage(lastPage),
698
+ queryKey: queryKeys,
699
+ queryFn: ({ pageParam }) => {
700
+ if (!effectiveSplitPageSize) {
701
+ const pageQuery = {
702
+ ...normalizedQuery,
703
+ ...pageParam ? { [feedCursorParam]: pageParam } : {}
704
+ };
705
+ return fetchEndpoint(tuple, {
706
+ queryOverride: pageQuery,
707
+ onReceive: notifyOnReceive
708
+ });
709
+ }
710
+ const basePageSizeRaw = normalizedQuery?.[effectiveSplitPageSizeParam];
711
+ const basePageSize = typeof basePageSizeRaw === "number" ? basePageSizeRaw : basePageSizeRaw != null ? Number(basePageSizeRaw) : void 0;
712
+ if (!basePageSize || !Number.isFinite(basePageSize) || basePageSize <= effectiveSplitPageSize) {
713
+ const pageQuery = {
714
+ ...normalizedQuery,
715
+ ...pageParam ? { [feedCursorParam]: pageParam } : {}
716
+ };
717
+ return fetchEndpoint(tuple, {
718
+ queryOverride: pageQuery,
719
+ onReceive: notifyOnReceive
720
+ });
721
+ }
722
+ const totalTarget = basePageSize;
723
+ const pageParamForThisPage = pageParam;
724
+ const runSplitFetch = async () => {
725
+ let remaining = totalTarget;
726
+ let currentCursor = pageParam;
727
+ let aggregated;
728
+ while (remaining > 0) {
729
+ const thisCallSize = Math.min(remaining, effectiveSplitPageSize);
730
+ const splitQuery = {
731
+ ...normalizedQuery,
732
+ ...currentCursor ? { [feedCursorParam]: currentCursor } : {},
733
+ [effectiveSplitPageSizeParam]: thisCallSize
734
+ };
735
+ const page = await fetchEndpoint(tuple, {
736
+ queryOverride: splitQuery,
737
+ onReceive: notifyOnReceive
738
+ });
739
+ aggregated = aggregated ? mergePageOutputs(aggregated, page) : page;
740
+ remaining -= thisCallSize;
741
+ const nextCursor = cursorFromPage(page);
742
+ currentCursor = nextCursor;
743
+ const k = queryKeys;
744
+ queryClient.setQueryData(k, (prev) => {
745
+ if (!aggregated) return prev;
746
+ if (!prev) {
747
+ return {
748
+ pages: [aggregated],
749
+ pageParams: [pageParamForThisPage]
750
+ };
751
+ }
752
+ const idx = prev.pageParams.findIndex(
753
+ (p) => p === pageParamForThisPage || p == null && pageParamForThisPage == null
754
+ );
755
+ if (idx === -1) {
756
+ return {
757
+ pages: [...prev.pages, aggregated],
758
+ pageParams: [...prev.pageParams, pageParamForThisPage]
759
+ };
760
+ }
761
+ const newPages = [...prev.pages];
762
+ newPages[idx] = aggregated;
763
+ return { ...prev, pages: newPages };
764
+ });
765
+ if (!nextCursor) {
766
+ break;
767
+ }
768
+ }
769
+ return aggregated;
770
+ };
771
+ return runSplitFetch();
772
+ }
773
+ },
774
+ queryClient
775
+ );
776
+ return { ...queryResult, onReceive: registerOnReceive };
777
+ };
778
+ return {
779
+ getQueryKeys,
780
+ invalidate: invalidateExact,
781
+ setData,
782
+ useEndpoint,
783
+ fetch: fetchGet
784
+ };
785
+ }
786
+
787
+ // src/routesV3.client.mutation.ts
788
+ var import_rrroutes_contract4 = require("@emeryld/rrroutes-contract");
789
+ var import_react_query3 = require("@tanstack/react-query");
790
+ var import_react3 = require("react");
791
+ function buildMutationLeaf(leaf, rqOpts, env) {
792
+ const leafCfg = leaf.cfg;
793
+ const method = toUpper(leaf.method);
794
+ const expectsArgs = Boolean(leafCfg.paramsSchema || leafCfg.querySchema);
795
+ const {
796
+ baseUrl,
797
+ validateResponses,
798
+ fetcher,
799
+ queryClient,
800
+ emit,
801
+ decorateDebugEvent,
802
+ isVerboseDebug,
803
+ leafLabel
804
+ } = env;
805
+ emit({ type: "build", leaf: leafLabel });
806
+ const getQueryKeys = (...tuple) => {
807
+ const a = extractArgs(tuple);
808
+ const params = a?.params;
809
+ const query = a?.query;
810
+ return (0, import_rrroutes_contract4.buildCacheKey)({
811
+ leaf,
812
+ params,
813
+ query
814
+ });
815
+ };
816
+ const invalidateExact = async (...tuple) => {
817
+ const queryKey = getQueryKeys(...tuple);
818
+ await queryClient.invalidateQueries({ queryKey, exact: true });
819
+ emit({ type: "invalidate", key: queryKey, exact: true });
820
+ };
821
+ const setData = (...args) => {
822
+ const [updater, ...rest] = args;
823
+ const k = getQueryKeys(...rest);
824
+ const next = queryClient.setQueryData(
825
+ k,
826
+ (prev) => typeof updater === "function" ? updater(prev) : updater
827
+ );
828
+ emit({ type: "setData", key: k });
829
+ return next;
830
+ };
831
+ const mutationBuildOptions = rqOpts ?? {};
832
+ const fetchEndpoint = async (tuple, options) => {
833
+ const a = extractArgs(tuple);
834
+ const params = a?.params;
835
+ const query = options?.queryOverride ?? a?.query;
836
+ const { url, normalizedQuery, normalizedParams } = buildUrl(
837
+ { ...leaf, cfg: leafCfg },
838
+ baseUrl,
839
+ params,
840
+ query
841
+ );
842
+ let payload;
843
+ const acceptsBody = Boolean(leafCfg.bodySchema);
844
+ const requiresBody = options?.requireBody ?? (!acceptsBody ? false : true);
845
+ if (typeof options?.body !== "undefined") {
846
+ const normalizedBody = leafCfg.bodySchema ? (0, import_rrroutes_contract4.lowProfileParse)(leafCfg.bodySchema, options.body) : void 0;
847
+ const isMultipart = Array.isArray(leafCfg.bodyFiles) && leafCfg.bodyFiles.length > 0;
848
+ if (isMultipart && normalizedBody && typeof normalizedBody === "object") {
849
+ payload = toFormData(normalizedBody);
850
+ } else {
851
+ payload = normalizedBody;
852
+ }
853
+ } else if (requiresBody && acceptsBody) {
854
+ throw new Error("Body is required when invoking a mutation fetch.");
855
+ }
856
+ const startedAt = Date.now();
857
+ const detail = isVerboseDebug ? { params, normalizedParams, query, normalizedQuery, baseUrl } : void 0;
858
+ emit(
859
+ decorateDebugEvent(
860
+ {
861
+ type: "fetch",
862
+ stage: "start",
863
+ method,
864
+ url,
865
+ leaf: leafLabel,
866
+ ...payload !== void 0 ? { body: payload } : {}
867
+ },
868
+ detail
869
+ )
870
+ );
871
+ try {
872
+ const out = await fetcher(
873
+ payload === void 0 ? { url, method } : { url, method, body: payload }
874
+ );
875
+ emit(
876
+ decorateDebugEvent(
877
+ {
878
+ type: "fetch",
879
+ stage: "fetched",
880
+ method,
881
+ url,
882
+ leaf: leafLabel,
883
+ durationMs: Date.now() - startedAt
884
+ },
885
+ isVerboseDebug ? {
886
+ params: normalizedParams,
887
+ query: normalizedQuery,
888
+ output: out
889
+ } : void 0
890
+ )
891
+ );
892
+ if (validateResponses) {
893
+ if (!leafCfg.outputSchema) {
894
+ throw new Error(
895
+ `No output schema defined for leaf ${leafLabel}, cannot validate response.`
896
+ );
897
+ }
898
+ out.data = (0, import_rrroutes_contract4.lowProfileParse)(
899
+ leafCfg.outputSchema,
900
+ out.data
901
+ );
902
+ emit(
903
+ decorateDebugEvent(
904
+ {
905
+ type: "fetch",
906
+ stage: "parsed",
907
+ method,
908
+ url,
909
+ leaf: leafLabel,
910
+ durationMs: Date.now() - startedAt
911
+ },
912
+ isVerboseDebug ? {
913
+ params: normalizedParams,
914
+ query: normalizedQuery,
915
+ output: out
916
+ } : void 0
917
+ )
918
+ );
919
+ }
920
+ options?.onReceive?.(out.data);
921
+ return out.data;
922
+ } catch (error) {
923
+ emit(
924
+ decorateDebugEvent(
925
+ {
926
+ type: "fetch",
927
+ stage: "error",
928
+ method,
929
+ url,
930
+ leaf: leafLabel,
931
+ durationMs: Date.now() - startedAt,
932
+ ...payload !== void 0 ? { body: payload } : {},
933
+ error
934
+ },
935
+ detail
936
+ )
937
+ );
938
+ throw error;
939
+ }
940
+ };
941
+ const fetchMutation = async (...tupleWithBody) => {
942
+ if (tupleWithBody.length === 0) {
943
+ throw new Error("Body is required when invoking a mutation fetch.");
115
944
  }
116
- params.append(k, JSON.stringify(v));
117
- }
118
- const s = params.toString();
119
- return s ? `?${s}` : "";
120
- }
121
- function stripKey(obj, key) {
122
- if (!obj) return obj;
123
- const { [key]: _omit, ...rest } = obj;
124
- return rest;
945
+ const bodyIndex = tupleWithBody.length - 1;
946
+ const tuple = tupleWithBody.slice(0, bodyIndex);
947
+ const body = tupleWithBody[bodyIndex];
948
+ const result = await fetchEndpoint(tuple, {
949
+ body,
950
+ onReceive: (data) => mutationBuildOptions?.onReceive?.(data),
951
+ requireBody: true
952
+ });
953
+ return result;
954
+ };
955
+ const useEndpoint = (...useArgs) => {
956
+ const args = useArgs[0];
957
+ const tuple = toArgsTuple(args);
958
+ const mutationKey = getQueryKeys(...tuple);
959
+ emit({
960
+ type: "useEndpoint",
961
+ leaf: leafLabel,
962
+ variant: "mutation",
963
+ keys: mutationKey
964
+ });
965
+ const listenersRef = (0, import_react3.useRef)(/* @__PURE__ */ new Set());
966
+ const notifyListeners = (0, import_react3.useCallback)((data) => {
967
+ listenersRef.current.forEach((listener) => listener(data));
968
+ }, []);
969
+ const registerOnReceive = (0, import_react3.useCallback)(
970
+ (listener) => {
971
+ listenersRef.current.add(listener);
972
+ return () => {
973
+ listenersRef.current.delete(listener);
974
+ };
975
+ },
976
+ []
977
+ );
978
+ const mutationResult = (0, import_react_query3.useMutation)(
979
+ {
980
+ ...mutationBuildOptions ?? {},
981
+ mutationKey,
982
+ mutationFn: async (body) => {
983
+ const result = await fetchMutation(
984
+ ...[...tuple, body]
985
+ );
986
+ notifyListeners(result);
987
+ return result;
988
+ }
989
+ },
990
+ queryClient
991
+ );
992
+ return { ...mutationResult, onReceive: registerOnReceive };
993
+ };
994
+ return {
995
+ getQueryKeys,
996
+ invalidate: invalidateExact,
997
+ setData,
998
+ useEndpoint,
999
+ fetch: fetchMutation
1000
+ };
125
1001
  }
126
- var defaultGetNextCursor = (p) => {
127
- if (!p || typeof p !== "object") return void 0;
128
- const record = p;
129
- if ("nextCursor" in record) {
130
- return record.nextCursor;
131
- }
132
- if ("meta" in record) {
133
- const meta = record.meta;
134
- if (meta && typeof meta === "object" && "nextCursor" in meta) {
135
- return meta.nextCursor;
136
- }
137
- }
138
- return void 0;
139
- };
1002
+
1003
+ // src/routesV3.client.ts
140
1004
  var defaultDebugLogger = (event) => {
141
1005
  if (typeof console === "undefined") return;
142
1006
  const fn = console.debug ?? console.log;
@@ -198,22 +1062,6 @@ function createDebugEmitter(option, environment) {
198
1062
  }
199
1063
  return disabled;
200
1064
  }
201
- function extractArgs(args) {
202
- return args[0];
203
- }
204
- function toArgsTuple(args) {
205
- return typeof args === "undefined" ? [] : [args];
206
- }
207
- function buildUrl(leaf, baseUrl, params, query) {
208
- const normalizedParams = leaf.cfg.paramsSchema ? (0, import_rrroutes_contract.lowProfileParse)(leaf.cfg.paramsSchema, params) : {};
209
- const normalizedQuery = leaf.cfg.querySchema ? (0, import_rrroutes_contract.lowProfileParse)(leaf.cfg.querySchema, query) : {};
210
- const path = (0, import_rrroutes_contract.compilePath)(
211
- leaf.path,
212
- normalizedParams ?? {}
213
- );
214
- const url = `${baseUrl ?? ""}${path}${toSearchString(normalizedQuery)}`;
215
- return { url, normalizedQuery, normalizedParams };
216
- }
217
1065
  function createRouteClient(opts) {
218
1066
  const queryClient = opts.queryClient;
219
1067
  const fetcher = opts.fetcher ?? defaultFetcher;
@@ -234,365 +1082,41 @@ function createRouteClient(opts) {
234
1082
  emitDebug({ type: "invalidate", key: queryKey, exact });
235
1083
  }
236
1084
  function buildInternal(leaf, rqOpts, meta) {
237
- const isGet = leaf.method === "get";
238
- const isFeed = !!leaf.cfg.feed;
239
- const leafCfg = leaf.cfg;
240
- const validateResponses = opts.validateResponses ?? true;
241
- const method = toUpper(leaf.method);
242
- const expectsArgs = Boolean(leafCfg.paramsSchema || leafCfg.querySchema);
243
1085
  const leafLabel = `${leaf.method.toUpperCase()} ${String(leaf.path)}`;
244
1086
  const debugName = meta?.name;
245
1087
  const emit = (event) => emitDebug(event, debugName);
246
- emit({ type: "build", leaf: leafLabel });
247
- let feedCursorParam = "pagination_cursor";
248
- let feedNextPageParam;
249
- let feedInitialPageParam;
250
- let feedQueryOptions;
1088
+ const isGet = leaf.method === "get";
1089
+ const isFeed = !!leaf.cfg.feed;
1090
+ const validateResponses = opts.validateResponses ?? true;
1091
+ const env = {
1092
+ baseUrl,
1093
+ validateResponses,
1094
+ fetcher,
1095
+ queryClient,
1096
+ emit,
1097
+ decorateDebugEvent,
1098
+ isVerboseDebug,
1099
+ leafLabel
1100
+ };
251
1101
  if (isGet && isFeed) {
252
- const infiniteOptions = rqOpts ?? {};
253
- const {
254
- cursorParam,
255
- getNextPageParam,
256
- initialPageParam,
257
- ...passthroughOptions
258
- } = infiniteOptions;
259
- feedCursorParam = cursorParam ?? "pagination_cursor";
260
- feedNextPageParam = getNextPageParam ?? ((lastPage) => defaultGetNextCursor(lastPage));
261
- feedInitialPageParam = typeof initialPageParam === "undefined" ? void 0 : initialPageParam;
262
- feedQueryOptions = passthroughOptions;
263
- }
264
- const getQueryKeys = (...tuple) => {
265
- const a = extractArgs(tuple);
266
- const params = a?.params;
267
- const query = a?.query;
268
- const qForKey = isGet && isFeed ? stripKey(query, feedCursorParam) : query;
269
- return (0, import_rrroutes_contract.buildCacheKey)({
1102
+ return buildInfiniteGetLeaf(
270
1103
  leaf,
271
- params,
272
- query: qForKey
273
- });
274
- };
275
- const invalidateExact = async (...tuple) => {
276
- const queryKey = getQueryKeys(...tuple);
277
- await queryClient.invalidateQueries({ queryKey, exact: true });
278
- emit({ type: "invalidate", key: queryKey, exact: true });
279
- };
280
- const setData = (...args) => {
281
- const [updater, ...rest] = args;
282
- const k = getQueryKeys(...rest);
283
- let next;
284
- if (isGet && isFeed) {
285
- next = queryClient.setQueryData(
286
- k,
287
- (prev) => typeof updater === "function" ? updater(prev) : updater
288
- );
289
- } else {
290
- next = queryClient.setQueryData(
291
- k,
292
- (prev) => typeof updater === "function" ? updater(prev) : updater
293
- );
294
- }
295
- emit({ type: "setData", key: k });
296
- return next;
297
- };
298
- const buildOnReceive = rqOpts?.onReceive;
299
- const fetchEndpoint = async (tuple, options) => {
300
- const a = extractArgs(tuple);
301
- const params = a?.params;
302
- const query = options?.queryOverride ?? a?.query;
303
- const { url, normalizedQuery, normalizedParams } = buildUrl(
304
- { ...leaf, cfg: leafCfg },
305
- baseUrl,
306
- params,
307
- query
308
- );
309
- let payload;
310
- const acceptsBody = Boolean(leafCfg.bodySchema);
311
- const requiresBody = options?.requireBody ?? (!isGet && acceptsBody);
312
- if (typeof options?.body !== "undefined") {
313
- const normalizedBody = leafCfg.bodySchema ? (0, import_rrroutes_contract.lowProfileParse)(leafCfg.bodySchema, options.body) : void 0;
314
- const isMultipart = Array.isArray(leafCfg.bodyFiles) && leafCfg.bodyFiles.length > 0;
315
- if (isMultipart && normalizedBody && typeof normalizedBody === "object") {
316
- payload = toFormData(normalizedBody);
317
- } else {
318
- payload = normalizedBody;
319
- }
320
- } else if (requiresBody) {
321
- throw new Error("Body is required when invoking a mutation fetch.");
322
- }
323
- const startedAt = Date.now();
324
- const detail = isVerboseDebug ? { params: normalizedParams, query: normalizedQuery } : void 0;
325
- emit(
326
- decorateDebugEvent(
327
- {
328
- type: "fetch",
329
- stage: "start",
330
- method,
331
- url,
332
- leaf: leafLabel,
333
- ...payload !== void 0 ? { body: payload } : {}
334
- },
335
- detail
336
- )
1104
+ rqOpts,
1105
+ env
337
1106
  );
338
- try {
339
- const out = await fetcher(
340
- payload === void 0 ? { url, method } : { url, method, body: payload }
341
- );
342
- emit(
343
- decorateDebugEvent(
344
- {
345
- type: "fetch",
346
- stage: "fetched",
347
- method,
348
- url,
349
- leaf: leafLabel,
350
- durationMs: Date.now() - startedAt
351
- },
352
- isVerboseDebug ? {
353
- params: normalizedParams,
354
- query: normalizedQuery,
355
- output: out
356
- } : void 0
357
- )
358
- );
359
- if (validateResponses) {
360
- if (!leafCfg.outputSchema) {
361
- throw new Error(
362
- `No output schema defined for leaf ${leafLabel}, cannot validate response.`
363
- );
364
- }
365
- out.data = (0, import_rrroutes_contract.lowProfileParse)(
366
- leafCfg.outputSchema,
367
- out.data
368
- );
369
- emit(
370
- decorateDebugEvent(
371
- {
372
- type: "fetch",
373
- stage: "parsed",
374
- method,
375
- url,
376
- leaf: leafLabel,
377
- durationMs: Date.now() - startedAt
378
- },
379
- isVerboseDebug ? {
380
- params: normalizedParams,
381
- query: normalizedQuery,
382
- output: out
383
- } : void 0
384
- )
385
- );
386
- }
387
- options?.onReceive?.(out.data);
388
- return out.data;
389
- } catch (error) {
390
- emit(
391
- decorateDebugEvent(
392
- {
393
- type: "fetch",
394
- stage: "error",
395
- method,
396
- url,
397
- leaf: leafLabel,
398
- durationMs: Date.now() - startedAt,
399
- ...payload !== void 0 ? { body: payload } : {},
400
- error
401
- },
402
- detail
403
- )
404
- );
405
- throw error;
406
- }
407
- };
408
- const fetchGet = (...tupleWithBody) => {
409
- const acceptsBody = Boolean(leafCfg.bodySchema);
410
- const tupleLength = tupleWithBody.length;
411
- const maybeBodyIndex = expectsArgs ? 1 : 0;
412
- const hasBodyCandidate = acceptsBody && tupleLength > maybeBodyIndex;
413
- const body = hasBodyCandidate ? tupleWithBody[tupleLength - 1] : void 0;
414
- const tuple = hasBodyCandidate ? tupleWithBody.slice(0, tupleLength - 1) : tupleWithBody;
415
- return fetchEndpoint(tuple, {
416
- body,
417
- onReceive: buildOnReceive,
418
- requireBody: false
419
- });
420
- };
421
- if (isGet && isFeed) {
422
- const useEndpoint2 = (...useArgs) => {
423
- const args = useArgs[0];
424
- const tuple = toArgsTuple(args);
425
- const queryKeys = getQueryKeys(...tuple);
426
- emit({
427
- type: "useEndpoint",
428
- leaf: leafLabel,
429
- variant: "infiniteGet",
430
- keys: queryKeys
431
- });
432
- const params = args?.params;
433
- const query = args?.query;
434
- const buildOptions = feedQueryOptions ?? {};
435
- const listenersRef = (0, import_react.useRef)(/* @__PURE__ */ new Set());
436
- const notifyOnReceive = (0, import_react.useCallback)((data) => {
437
- buildOptions?.onReceive?.(data);
438
- listenersRef.current.forEach((listener) => listener(data));
439
- }, []);
440
- const registerOnReceive = (0, import_react.useCallback)(
441
- (listener) => {
442
- listenersRef.current.add(listener);
443
- return () => {
444
- listenersRef.current.delete(listener);
445
- };
446
- },
447
- []
448
- );
449
- const { normalizedQuery, normalizedParams } = buildUrl(
450
- { ...leaf, cfg: leafCfg },
451
- baseUrl,
452
- params,
453
- query
454
- );
455
- const queryResult = (0, import_react_query.useInfiniteQuery)(
456
- {
457
- ...buildOptions,
458
- placeholderData: buildOptions.placeholderData ?? import_react_query.keepPreviousData,
459
- initialPageParam: feedInitialPageParam,
460
- getNextPageParam: (lastPage) => (feedNextPageParam ?? defaultGetNextCursor)(lastPage),
461
- queryKey: queryKeys,
462
- queryFn: ({ pageParam }) => {
463
- const pageQuery = {
464
- ...normalizedQuery,
465
- ...pageParam ? { [feedCursorParam]: pageParam } : {}
466
- };
467
- return fetchEndpoint(tuple, {
468
- queryOverride: pageQuery,
469
- onReceive: notifyOnReceive
470
- });
471
- }
472
- // NOTE: TData is InfiniteData<T>, so we don't need a select here.
473
- },
474
- queryClient
475
- );
476
- return { ...queryResult, onReceive: registerOnReceive };
477
- };
478
- return {
479
- getQueryKeys,
480
- invalidate: invalidateExact,
481
- setData,
482
- useEndpoint: useEndpoint2,
483
- fetch: fetchGet
484
- };
485
1107
  }
486
1108
  if (isGet) {
487
- const useEndpoint2 = (...useArgs) => {
488
- const args = useArgs[0];
489
- const tuple = toArgsTuple(args);
490
- const queryKeys = getQueryKeys(...tuple);
491
- emit({
492
- type: "useEndpoint",
493
- leaf: leafLabel,
494
- variant: "get",
495
- keys: queryKeys
496
- });
497
- const params = args?.params;
498
- const query = args?.query;
499
- const buildOptions = rqOpts ?? {};
500
- const listenersRef = (0, import_react.useRef)(/* @__PURE__ */ new Set());
501
- const notifyOnReceive = (0, import_react.useCallback)((data) => {
502
- buildOptions?.onReceive?.(data);
503
- listenersRef.current.forEach((listener) => listener(data));
504
- }, []);
505
- const registerOnReceive = (0, import_react.useCallback)(
506
- (listener) => {
507
- listenersRef.current.add(listener);
508
- return () => {
509
- listenersRef.current.delete(listener);
510
- };
511
- },
512
- []
513
- );
514
- const queryResult = (0, import_react_query.useQuery)(
515
- {
516
- ...buildOptions,
517
- queryKey: getQueryKeys(...tuple),
518
- placeholderData: import_react_query.keepPreviousData,
519
- queryFn: () => fetchEndpoint(tuple, {
520
- onReceive: notifyOnReceive
521
- })
522
- },
523
- queryClient
524
- );
525
- return { ...queryResult, onReceive: registerOnReceive };
526
- };
527
- return {
528
- getQueryKeys,
529
- invalidate: invalidateExact,
530
- setData,
531
- useEndpoint: useEndpoint2,
532
- fetch: fetchGet
533
- };
534
- }
535
- const mutationBuildOptions = rqOpts ?? {};
536
- const fetchMutation = async (...tupleWithBody) => {
537
- if (tupleWithBody.length === 0) {
538
- throw new Error("Body is required when invoking a mutation fetch.");
539
- }
540
- const bodyIndex = tupleWithBody.length - 1;
541
- const tuple = tupleWithBody.slice(0, bodyIndex);
542
- const body = tupleWithBody[bodyIndex];
543
- const result = await fetchEndpoint(tuple, {
544
- body,
545
- onReceive: (data) => mutationBuildOptions?.onReceive?.(data),
546
- requireBody: true
547
- });
548
- return result;
549
- };
550
- const useEndpoint = (...useArgs) => {
551
- const args = useArgs[0];
552
- const tuple = toArgsTuple(args);
553
- const mutationKey = getQueryKeys(...tuple);
554
- emit({
555
- type: "useEndpoint",
556
- leaf: leafLabel,
557
- variant: "mutation",
558
- keys: mutationKey
559
- });
560
- const listenersRef = (0, import_react.useRef)(/* @__PURE__ */ new Set());
561
- const notifyListeners = (0, import_react.useCallback)((data) => {
562
- listenersRef.current.forEach((listener) => listener(data));
563
- }, []);
564
- const registerOnReceive = (0, import_react.useCallback)(
565
- (listener) => {
566
- listenersRef.current.add(listener);
567
- return () => {
568
- listenersRef.current.delete(listener);
569
- };
570
- },
571
- []
572
- );
573
- const mutationResult = (0, import_react_query.useMutation)(
574
- {
575
- ...mutationBuildOptions,
576
- mutationKey,
577
- mutationFn: async (body) => {
578
- const result = await fetchMutation(
579
- ...[...tuple, body]
580
- );
581
- notifyListeners(result);
582
- return result;
583
- }
584
- },
585
- queryClient
1109
+ return buildGetLeaf(
1110
+ leaf,
1111
+ rqOpts,
1112
+ env
586
1113
  );
587
- return { ...mutationResult, onReceive: registerOnReceive };
588
- };
589
- return {
590
- getQueryKeys,
591
- invalidate: invalidateExact,
592
- setData,
593
- useEndpoint,
594
- fetch: fetchMutation
595
- };
1114
+ }
1115
+ return buildMutationLeaf(
1116
+ leaf,
1117
+ rqOpts,
1118
+ env
1119
+ );
596
1120
  }
597
1121
  const fetchRaw = async (input) => {
598
1122
  const { path, method, query, body, params } = input;
@@ -610,7 +1134,7 @@ function createRouteClient(opts) {
610
1134
  const url = `${baseUrl ?? ""}${compiledPath}${search}`;
611
1135
  const leafLabel = `${methodUpper} ${path}`;
612
1136
  const startedAt = Date.now();
613
- const detail = isVerboseDebug ? { params, query: flatQuery } : void 0;
1137
+ const detail = isVerboseDebug ? { params, flatQuery, query, baseUrl } : void 0;
614
1138
  emitDebug(
615
1139
  decorateDebugEvent(
616
1140
  {
@@ -676,81 +1200,6 @@ function buildRouter(routeClient, routes) {
676
1200
  meta
677
1201
  ));
678
1202
  }
679
- function toFormData(body) {
680
- const fd = new FormData();
681
- for (const [k, v] of Object.entries(body ?? {})) {
682
- if (v == null) continue;
683
- if (Array.isArray(v))
684
- v.forEach((item, i) => fd.append(`${k}[${i}]`, item));
685
- else fd.append(k, v);
686
- }
687
- return fd;
688
- }
689
- function getPathParamNames(path) {
690
- const names = /* @__PURE__ */ new Set();
691
- const re = /:([A-Za-z0-9_]+)/g;
692
- let match;
693
- while ((match = re.exec(path)) !== null) {
694
- names.add(match[1]);
695
- }
696
- return names;
697
- }
698
- function normalizeFlatQuery(query) {
699
- if (query == null) return void 0;
700
- if (typeof query !== "object" || Array.isArray(query)) {
701
- throw new Error("Query must be a plain object (Record<string, string>).");
702
- }
703
- const result = {};
704
- for (const [k, v] of Object.entries(query)) {
705
- if (v == null) continue;
706
- if (typeof v !== "string") {
707
- throw new Error(
708
- `Query param "${k}" must be a string; received type "${typeof v}".`
709
- );
710
- }
711
- result[k] = v;
712
- }
713
- return Object.keys(result).length > 0 ? result : void 0;
714
- }
715
- function compileRawPath(path, params) {
716
- const placeholders = getPathParamNames(path);
717
- if (!params || typeof params !== "object" || Array.isArray(params)) {
718
- if (placeholders.size > 0) {
719
- throw new Error(
720
- `Missing path parameters for "${path}": ${[...placeholders].join(
721
- ", "
722
- )}`
723
- );
724
- }
725
- return path;
726
- }
727
- const paramObj = params;
728
- const providedNames = new Set(Object.keys(paramObj));
729
- for (const name of providedNames) {
730
- if (!placeholders.has(name)) {
731
- throw new Error(
732
- `Unexpected path parameter "${name}" for template "${path}".`
733
- );
734
- }
735
- const value = paramObj[name];
736
- if (value != null && (typeof value === "object" || Array.isArray(value))) {
737
- throw new Error(
738
- `Path parameter "${name}" must be a primitive; received "${typeof value}".`
739
- );
740
- }
741
- }
742
- for (const name of placeholders) {
743
- if (!providedNames.has(name)) {
744
- throw new Error(
745
- `Missing value for path parameter "${name}" in template "${path}".`
746
- );
747
- }
748
- }
749
- if (placeholders.size === 0) {
750
- return path;
751
- }
752
- return (0, import_rrroutes_contract.compilePath)(path, paramObj);
753
- }
754
1203
 
755
1204
  // src/sockets/socket.client.sys.ts
756
1205
  var import_zod = require("zod");
@@ -1025,7 +1474,7 @@ function useSocketConnection(args) {
1025
1474
  }
1026
1475
 
1027
1476
  // src/sockets/socketedRoute/socket.client.helper.ts
1028
- var import_react2 = require("react");
1477
+ var import_react4 = require("react");
1029
1478
  function normalizeRooms(rooms) {
1030
1479
  if (rooms == null) return [];
1031
1480
  const list = Array.isArray(rooms) ? rooms : [rooms];
@@ -1083,20 +1532,20 @@ function buildSocketedRoute(options) {
1083
1532
  const endpointResult = built.useEndpoint(
1084
1533
  ...useArgs
1085
1534
  );
1086
- const argsKey = (0, import_react2.useMemo)(() => JSON.stringify(useArgs[0] ?? null), [useArgs]);
1087
- const [roomState, setRoomState] = (0, import_react2.useState)(
1535
+ const argsKey = (0, import_react4.useMemo)(() => JSON.stringify(useArgs[0] ?? null), [useArgs]);
1536
+ const [roomState, setRoomState] = (0, import_react4.useState)(
1088
1537
  () => roomsFromData(endpointResult.data, toRooms)
1089
1538
  );
1090
- const roomsKey = (0, import_react2.useMemo)(() => roomState.rooms.join("|"), [roomState.rooms]);
1091
- const joinMetaKey = (0, import_react2.useMemo)(
1539
+ const roomsKey = (0, import_react4.useMemo)(() => roomState.rooms.join("|"), [roomState.rooms]);
1540
+ const joinMetaKey = (0, import_react4.useMemo)(
1092
1541
  () => JSON.stringify(roomState.joinMeta ?? null),
1093
1542
  [roomState.joinMeta]
1094
1543
  );
1095
- const leaveMetaKey = (0, import_react2.useMemo)(
1544
+ const leaveMetaKey = (0, import_react4.useMemo)(
1096
1545
  () => JSON.stringify(roomState.leaveMeta ?? null),
1097
1546
  [roomState.leaveMeta]
1098
1547
  );
1099
- (0, import_react2.useEffect)(() => {
1548
+ (0, import_react4.useEffect)(() => {
1100
1549
  const unsubscribe = endpointResult.onReceive((data) => {
1101
1550
  setRoomState(
1102
1551
  (prev) => mergeRoomState(prev, toRooms(data))
@@ -1104,12 +1553,12 @@ function buildSocketedRoute(options) {
1104
1553
  });
1105
1554
  return unsubscribe;
1106
1555
  }, [endpointResult, toRooms]);
1107
- (0, import_react2.useEffect)(() => {
1556
+ (0, import_react4.useEffect)(() => {
1108
1557
  setRoomState(
1109
1558
  roomsFromData(endpointResult.data, toRooms)
1110
1559
  );
1111
1560
  }, [endpointResult.data, toRooms]);
1112
- (0, import_react2.useEffect)(() => {
1561
+ (0, import_react4.useEffect)(() => {
1113
1562
  if (roomState.rooms.length === 0) return;
1114
1563
  const { joinMeta, leaveMeta } = roomState;
1115
1564
  if (!joinMeta || !leaveMeta) return;
@@ -1134,7 +1583,7 @@ function buildSocketedRoute(options) {
1134
1583
  joinMetaKey,
1135
1584
  leaveMetaKey
1136
1585
  ]);
1137
- (0, import_react2.useEffect)(() => {
1586
+ (0, import_react4.useEffect)(() => {
1138
1587
  const entries = Object.entries(applySocket).filter(
1139
1588
  ([_event, fn]) => typeof fn === "function"
1140
1589
  );