@emeryld/rrroutes-client 2.4.0 → 2.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -51,19 +51,16 @@ var defaultFetcher = async (req) => {
51
51
  }
52
52
  };
53
53
 
54
- // src/routesV3.client.index.ts
55
- import {
56
- buildCacheKey,
57
- compilePath,
58
- lowProfileParse
59
- } from "@emeryld/rrroutes-contract";
54
+ // src/routesV3.client.get.ts
55
+ import { buildCacheKey, lowProfileParse as lowProfileParse2 } from "@emeryld/rrroutes-contract";
60
56
  import {
61
57
  keepPreviousData,
62
- useInfiniteQuery,
63
- useMutation,
64
58
  useQuery
65
59
  } from "@tanstack/react-query";
66
60
  import { useCallback, useRef } from "react";
61
+
62
+ // src/routesV3.client.shared.ts
63
+ import { compilePath, lowProfileParse } from "@emeryld/rrroutes-contract";
67
64
  var toUpper = (m) => m.toUpperCase();
68
65
  function toSearchString(query) {
69
66
  if (!query) return "";
@@ -74,34 +71,900 @@ function toSearchString(query) {
74
71
  params.append(k, v);
75
72
  continue;
76
73
  }
77
- if (typeof v === "number" || typeof v === "boolean") {
78
- params.append(k, String(v));
79
- continue;
74
+ if (typeof v === "number" || typeof v === "boolean") {
75
+ params.append(k, String(v));
76
+ continue;
77
+ }
78
+ params.append(k, JSON.stringify(v));
79
+ }
80
+ const s = params.toString();
81
+ return s ? `?${s}` : "";
82
+ }
83
+ function stripKey(obj, key) {
84
+ if (!obj) return obj;
85
+ const { [key]: _omit, ...rest } = obj;
86
+ return rest;
87
+ }
88
+ var defaultGetNextCursor = (p) => {
89
+ if (!p || typeof p !== "object") return void 0;
90
+ const record = p;
91
+ if ("nextCursor" in record) {
92
+ return record.nextCursor;
93
+ }
94
+ if ("meta" in record) {
95
+ const meta = record.meta;
96
+ if (meta && typeof meta === "object" && "nextCursor" in meta) {
97
+ return meta.nextCursor;
98
+ }
99
+ }
100
+ return void 0;
101
+ };
102
+ function extractArgs(args) {
103
+ return args[0];
104
+ }
105
+ function toArgsTuple(args) {
106
+ return typeof args === "undefined" ? [] : [args];
107
+ }
108
+ function buildUrl(leaf, baseUrl, params, query) {
109
+ const normalizedParams = leaf.cfg.paramsSchema ? lowProfileParse(leaf.cfg.paramsSchema, params) : {};
110
+ const normalizedQuery = leaf.cfg.querySchema ? lowProfileParse(leaf.cfg.querySchema, query) : {};
111
+ const path = compilePath(
112
+ leaf.path,
113
+ normalizedParams ?? {}
114
+ );
115
+ const url = `${baseUrl ?? ""}${path}${toSearchString(normalizedQuery)}`;
116
+ return { url, normalizedQuery, normalizedParams };
117
+ }
118
+ function toFormData(body) {
119
+ const fd = new FormData();
120
+ for (const [k, v] of Object.entries(body ?? {})) {
121
+ if (v == null) continue;
122
+ if (Array.isArray(v))
123
+ v.forEach((item, i) => fd.append(`${k}[${i}]`, item));
124
+ else fd.append(k, v);
125
+ }
126
+ return fd;
127
+ }
128
+ function getPathParamNames(path) {
129
+ const names = /* @__PURE__ */ new Set();
130
+ const re = /:([A-Za-z0-9_]+)/g;
131
+ let match;
132
+ while ((match = re.exec(path)) !== null) {
133
+ names.add(match[1]);
134
+ }
135
+ return names;
136
+ }
137
+ function normalizeFlatQuery(query) {
138
+ if (query == null) return void 0;
139
+ if (typeof query !== "object" || Array.isArray(query)) {
140
+ throw new Error("Query must be a plain object (Record<string, string>).");
141
+ }
142
+ const result = {};
143
+ for (const [k, v] of Object.entries(query)) {
144
+ if (v == null) continue;
145
+ if (typeof v !== "string") {
146
+ throw new Error(
147
+ `Query param "${k}" must be a string; received type "${typeof v}".`
148
+ );
149
+ }
150
+ result[k] = v;
151
+ }
152
+ return Object.keys(result).length > 0 ? result : void 0;
153
+ }
154
+ function compileRawPath(path, params) {
155
+ const placeholders = getPathParamNames(path);
156
+ if (!params || typeof params !== "object" || Array.isArray(params)) {
157
+ if (placeholders.size > 0) {
158
+ throw new Error(
159
+ `Missing path parameters for "${path}": ${[...placeholders].join(
160
+ ", "
161
+ )}`
162
+ );
163
+ }
164
+ return path;
165
+ }
166
+ const paramObj = params;
167
+ const providedNames = new Set(Object.keys(paramObj));
168
+ for (const name of providedNames) {
169
+ if (!placeholders.has(name)) {
170
+ throw new Error(
171
+ `Unexpected path parameter "${name}" for template "${path}".`
172
+ );
173
+ }
174
+ const value = paramObj[name];
175
+ if (value != null && (typeof value === "object" || Array.isArray(value))) {
176
+ throw new Error(
177
+ `Path parameter "${name}" must be a primitive; received "${typeof value}".`
178
+ );
179
+ }
180
+ }
181
+ for (const name of placeholders) {
182
+ if (!providedNames.has(name)) {
183
+ throw new Error(
184
+ `Missing value for path parameter "${name}" in template "${path}".`
185
+ );
186
+ }
187
+ }
188
+ if (placeholders.size === 0) {
189
+ return path;
190
+ }
191
+ return compilePath(path, paramObj);
192
+ }
193
+
194
+ // src/routesV3.client.get.ts
195
+ function buildGetLeaf(leaf, rqOpts, env) {
196
+ const leafCfg = leaf.cfg;
197
+ const method = toUpper(leaf.method);
198
+ const expectsArgs = Boolean(leafCfg.paramsSchema || leafCfg.querySchema);
199
+ const {
200
+ baseUrl,
201
+ validateResponses,
202
+ fetcher,
203
+ queryClient,
204
+ emit,
205
+ decorateDebugEvent,
206
+ isVerboseDebug,
207
+ leafLabel
208
+ } = env;
209
+ emit({ type: "build", leaf: leafLabel });
210
+ const getQueryKeys = (...tuple) => {
211
+ const a = extractArgs(tuple);
212
+ const params = a?.params;
213
+ const query = a?.query;
214
+ return buildCacheKey({
215
+ leaf,
216
+ params,
217
+ query
218
+ });
219
+ };
220
+ const invalidateExact = async (...tuple) => {
221
+ const queryKey = getQueryKeys(...tuple);
222
+ await queryClient.invalidateQueries({ queryKey, exact: true });
223
+ emit({ type: "invalidate", key: queryKey, exact: true });
224
+ };
225
+ const setData = (...args) => {
226
+ const [updater, ...rest] = args;
227
+ const k = getQueryKeys(...rest);
228
+ const next = queryClient.setQueryData(
229
+ k,
230
+ (prev) => typeof updater === "function" ? updater(prev) : updater
231
+ );
232
+ emit({ type: "setData", key: k });
233
+ return next;
234
+ };
235
+ const buildOnReceive = rqOpts?.onReceive ?? void 0;
236
+ const fetchEndpoint = async (tuple, options) => {
237
+ const a = extractArgs(tuple);
238
+ const params = a?.params;
239
+ const query = options?.queryOverride ?? a?.query;
240
+ const { url, normalizedQuery, normalizedParams } = buildUrl(
241
+ { ...leaf, cfg: leafCfg },
242
+ baseUrl,
243
+ params,
244
+ query
245
+ );
246
+ let payload;
247
+ const acceptsBody = Boolean(leafCfg.bodySchema);
248
+ const requiresBody = options?.requireBody ?? (!acceptsBody ? false : true);
249
+ if (typeof options?.body !== "undefined") {
250
+ const normalizedBody = leafCfg.bodySchema ? lowProfileParse2(leafCfg.bodySchema, options.body) : void 0;
251
+ const isMultipart = Array.isArray(leafCfg.bodyFiles) && leafCfg.bodyFiles.length > 0;
252
+ if (isMultipart && normalizedBody && typeof normalizedBody === "object") {
253
+ payload = toFormData(normalizedBody);
254
+ } else {
255
+ payload = normalizedBody;
256
+ }
257
+ } else if (requiresBody && acceptsBody) {
258
+ throw new Error("Body is required when invoking a mutation fetch.");
259
+ }
260
+ const startedAt = Date.now();
261
+ const detail = isVerboseDebug ? { params: normalizedParams, query: normalizedQuery } : void 0;
262
+ emit(
263
+ decorateDebugEvent(
264
+ {
265
+ type: "fetch",
266
+ stage: "start",
267
+ method,
268
+ url,
269
+ leaf: leafLabel,
270
+ ...payload !== void 0 ? { body: payload } : {}
271
+ },
272
+ detail
273
+ )
274
+ );
275
+ try {
276
+ const out = await fetcher(
277
+ payload === void 0 ? { url, method } : { url, method, body: payload }
278
+ );
279
+ emit(
280
+ decorateDebugEvent(
281
+ {
282
+ type: "fetch",
283
+ stage: "fetched",
284
+ method,
285
+ url,
286
+ leaf: leafLabel,
287
+ durationMs: Date.now() - startedAt
288
+ },
289
+ isVerboseDebug ? {
290
+ params: normalizedParams,
291
+ query: normalizedQuery,
292
+ output: out
293
+ } : void 0
294
+ )
295
+ );
296
+ if (validateResponses) {
297
+ if (!leafCfg.outputSchema) {
298
+ throw new Error(
299
+ `No output schema defined for leaf ${leafLabel}, cannot validate response.`
300
+ );
301
+ }
302
+ out.data = lowProfileParse2(
303
+ leafCfg.outputSchema,
304
+ out.data
305
+ );
306
+ emit(
307
+ decorateDebugEvent(
308
+ {
309
+ type: "fetch",
310
+ stage: "parsed",
311
+ method,
312
+ url,
313
+ leaf: leafLabel,
314
+ durationMs: Date.now() - startedAt
315
+ },
316
+ isVerboseDebug ? {
317
+ params: normalizedParams,
318
+ query: normalizedQuery,
319
+ output: out
320
+ } : void 0
321
+ )
322
+ );
323
+ }
324
+ options?.onReceive?.(out.data);
325
+ return out.data;
326
+ } catch (error) {
327
+ emit(
328
+ decorateDebugEvent(
329
+ {
330
+ type: "fetch",
331
+ stage: "error",
332
+ method,
333
+ url,
334
+ leaf: leafLabel,
335
+ durationMs: Date.now() - startedAt,
336
+ ...payload !== void 0 ? { body: payload } : {},
337
+ error
338
+ },
339
+ detail
340
+ )
341
+ );
342
+ throw error;
343
+ }
344
+ };
345
+ const fetchGet = (...tupleWithBody) => {
346
+ const acceptsBody = Boolean(leafCfg.bodySchema);
347
+ const tupleLength = tupleWithBody.length;
348
+ const maybeBodyIndex = expectsArgs ? 1 : 0;
349
+ const hasBodyCandidate = acceptsBody && tupleLength > maybeBodyIndex;
350
+ const body = hasBodyCandidate ? tupleWithBody[tupleLength - 1] : void 0;
351
+ const tuple = hasBodyCandidate ? tupleWithBody.slice(0, tupleLength - 1) : tupleWithBody;
352
+ return fetchEndpoint(tuple, {
353
+ body,
354
+ onReceive: buildOnReceive,
355
+ requireBody: false
356
+ });
357
+ };
358
+ const useEndpoint = (...useArgs) => {
359
+ const args = useArgs[0];
360
+ const tuple = toArgsTuple(args);
361
+ const queryKeys = getQueryKeys(...tuple);
362
+ emit({
363
+ type: "useEndpoint",
364
+ leaf: leafLabel,
365
+ variant: "get",
366
+ keys: queryKeys
367
+ });
368
+ const buildOptions = rqOpts ?? {};
369
+ const listenersRef = useRef(/* @__PURE__ */ new Set());
370
+ const notifyOnReceive = useCallback((data) => {
371
+ buildOptions?.onReceive?.(data);
372
+ listenersRef.current.forEach((listener) => listener(data));
373
+ }, []);
374
+ const registerOnReceive = useCallback(
375
+ (listener) => {
376
+ listenersRef.current.add(listener);
377
+ return () => {
378
+ listenersRef.current.delete(listener);
379
+ };
380
+ },
381
+ []
382
+ );
383
+ const queryResult = useQuery(
384
+ {
385
+ ...buildOptions,
386
+ queryKey: getQueryKeys(...tuple),
387
+ placeholderData: keepPreviousData,
388
+ queryFn: () => fetchEndpoint(tuple, {
389
+ onReceive: notifyOnReceive
390
+ })
391
+ },
392
+ queryClient
393
+ );
394
+ return { ...queryResult, onReceive: registerOnReceive };
395
+ };
396
+ return {
397
+ getQueryKeys,
398
+ invalidate: invalidateExact,
399
+ setData,
400
+ useEndpoint,
401
+ fetch: fetchGet
402
+ };
403
+ }
404
+
405
+ // src/routesV3.client.infiniteGet.ts
406
+ import { buildCacheKey as buildCacheKey2, lowProfileParse as lowProfileParse3 } from "@emeryld/rrroutes-contract";
407
+ import {
408
+ keepPreviousData as keepPreviousData2,
409
+ useInfiniteQuery
410
+ } from "@tanstack/react-query";
411
+ import { useCallback as useCallback2, useRef as useRef2 } from "react";
412
+ function mergePageOutputs(prev, next) {
413
+ if (prev == null) return next;
414
+ if (next == null) return prev;
415
+ if (Array.isArray(prev) && Array.isArray(next)) {
416
+ return [...prev, ...next];
417
+ }
418
+ if (typeof prev !== "object" || typeof next !== "object") {
419
+ return next;
420
+ }
421
+ const merged = { ...prev };
422
+ for (const key of Object.keys(next)) {
423
+ const pv = prev[key];
424
+ const nv = next[key];
425
+ if (Array.isArray(pv) && Array.isArray(nv)) {
426
+ merged[key] = [...pv, ...nv];
427
+ continue;
428
+ }
429
+ if (pv && typeof pv === "object" && !Array.isArray(pv) && nv && typeof nv === "object" && !Array.isArray(nv)) {
430
+ if (key === "meta") {
431
+ merged[key] = mergePageOutputs(pv, nv);
432
+ } else {
433
+ merged[key] = nv;
434
+ }
435
+ continue;
436
+ }
437
+ merged[key] = nv;
438
+ }
439
+ return merged;
440
+ }
441
+ function buildInfiniteGetLeaf(leaf, rqOpts, env) {
442
+ const leafCfg = leaf.cfg;
443
+ const method = toUpper(leaf.method);
444
+ const expectsArgs = Boolean(leafCfg.paramsSchema || leafCfg.querySchema);
445
+ const {
446
+ baseUrl,
447
+ validateResponses,
448
+ fetcher,
449
+ queryClient,
450
+ emit,
451
+ decorateDebugEvent,
452
+ isVerboseDebug,
453
+ leafLabel
454
+ } = env;
455
+ emit({ type: "build", leaf: leafLabel });
456
+ const infiniteOptions = rqOpts ?? {};
457
+ const {
458
+ cursorParam,
459
+ getNextPageParam,
460
+ initialPageParam,
461
+ splitPageSize,
462
+ splitPageSizeParam,
463
+ ...passthroughOptions
464
+ } = infiniteOptions;
465
+ const feedCursorParam = cursorParam ?? "pagination_cursor";
466
+ const feedNextPageParam = getNextPageParam ?? ((lastPage) => defaultGetNextCursor(lastPage));
467
+ const cursorFromPage = (page) => feedNextPageParam(page);
468
+ const feedInitialPageParam = typeof initialPageParam === "undefined" ? void 0 : initialPageParam;
469
+ const feedQueryOptions = passthroughOptions;
470
+ const effectiveSplitPageSize = typeof splitPageSize === "number" && splitPageSize > 0 ? splitPageSize : void 0;
471
+ const effectiveSplitPageSizeParam = splitPageSizeParam ?? "pageSize";
472
+ const getQueryKeys = (...tuple) => {
473
+ const a = extractArgs(tuple);
474
+ const params = a?.params;
475
+ const query = a?.query;
476
+ const qForKey = stripKey(query, feedCursorParam);
477
+ return buildCacheKey2({
478
+ leaf,
479
+ params,
480
+ query: qForKey
481
+ });
482
+ };
483
+ const invalidateExact = async (...tuple) => {
484
+ const queryKey = getQueryKeys(...tuple);
485
+ await queryClient.invalidateQueries({ queryKey, exact: true });
486
+ emit({ type: "invalidate", key: queryKey, exact: true });
487
+ };
488
+ const setData = (...args) => {
489
+ const [updater, ...rest] = args;
490
+ const k = getQueryKeys(...rest);
491
+ const next = queryClient.setQueryData(
492
+ k,
493
+ (prev) => typeof updater === "function" ? updater(prev) : updater
494
+ );
495
+ emit({ type: "setData", key: k });
496
+ return next;
497
+ };
498
+ const buildOnReceive = rqOpts?.onReceive ?? void 0;
499
+ const fetchEndpoint = async (tuple, options) => {
500
+ const a = extractArgs(tuple);
501
+ const params = a?.params;
502
+ const query = options?.queryOverride ?? a?.query;
503
+ const { url, normalizedQuery, normalizedParams } = buildUrl(
504
+ { ...leaf, cfg: leafCfg },
505
+ baseUrl,
506
+ params,
507
+ query
508
+ );
509
+ let payload;
510
+ const acceptsBody = Boolean(leafCfg.bodySchema);
511
+ const requiresBody = options?.requireBody ?? (!acceptsBody ? false : true);
512
+ if (typeof options?.body !== "undefined") {
513
+ const normalizedBody = leafCfg.bodySchema ? lowProfileParse3(leafCfg.bodySchema, options.body) : void 0;
514
+ const isMultipart = Array.isArray(leafCfg.bodyFiles) && leafCfg.bodyFiles.length > 0;
515
+ if (isMultipart && normalizedBody && typeof normalizedBody === "object") {
516
+ payload = toFormData(normalizedBody);
517
+ } else {
518
+ payload = normalizedBody;
519
+ }
520
+ } else if (requiresBody && acceptsBody) {
521
+ throw new Error("Body is required when invoking a mutation fetch.");
522
+ }
523
+ const startedAt = Date.now();
524
+ const detail = isVerboseDebug ? { params: normalizedParams, query: normalizedQuery } : void 0;
525
+ emit(
526
+ decorateDebugEvent(
527
+ {
528
+ type: "fetch",
529
+ stage: "start",
530
+ method,
531
+ url,
532
+ leaf: leafLabel,
533
+ ...payload !== void 0 ? { body: payload } : {}
534
+ },
535
+ detail
536
+ )
537
+ );
538
+ try {
539
+ const out = await fetcher(
540
+ payload === void 0 ? { url, method } : { url, method, body: payload }
541
+ );
542
+ emit(
543
+ decorateDebugEvent(
544
+ {
545
+ type: "fetch",
546
+ stage: "fetched",
547
+ method,
548
+ url,
549
+ leaf: leafLabel,
550
+ durationMs: Date.now() - startedAt
551
+ },
552
+ isVerboseDebug ? {
553
+ params: normalizedParams,
554
+ query: normalizedQuery,
555
+ output: out
556
+ } : void 0
557
+ )
558
+ );
559
+ if (validateResponses) {
560
+ if (!leafCfg.outputSchema) {
561
+ throw new Error(
562
+ `No output schema defined for leaf ${leafLabel}, cannot validate response.`
563
+ );
564
+ }
565
+ out.data = lowProfileParse3(
566
+ leafCfg.outputSchema,
567
+ out.data
568
+ );
569
+ emit(
570
+ decorateDebugEvent(
571
+ {
572
+ type: "fetch",
573
+ stage: "parsed",
574
+ method,
575
+ url,
576
+ leaf: leafLabel,
577
+ durationMs: Date.now() - startedAt
578
+ },
579
+ isVerboseDebug ? {
580
+ params: normalizedParams,
581
+ query: normalizedQuery,
582
+ output: out
583
+ } : void 0
584
+ )
585
+ );
586
+ }
587
+ options?.onReceive?.(out.data);
588
+ return out.data;
589
+ } catch (error) {
590
+ emit(
591
+ decorateDebugEvent(
592
+ {
593
+ type: "fetch",
594
+ stage: "error",
595
+ method,
596
+ url,
597
+ leaf: leafLabel,
598
+ durationMs: Date.now() - startedAt,
599
+ ...payload !== void 0 ? { body: payload } : {},
600
+ error
601
+ },
602
+ detail
603
+ )
604
+ );
605
+ throw error;
606
+ }
607
+ };
608
+ const fetchGet = (...tupleWithBody) => {
609
+ const acceptsBody = Boolean(leafCfg.bodySchema);
610
+ const tupleLength = tupleWithBody.length;
611
+ const maybeBodyIndex = expectsArgs ? 1 : 0;
612
+ const hasBodyCandidate = acceptsBody && tupleLength > maybeBodyIndex;
613
+ const body = hasBodyCandidate ? tupleWithBody[tupleLength - 1] : void 0;
614
+ const tuple = hasBodyCandidate ? tupleWithBody.slice(0, tupleLength - 1) : tupleWithBody;
615
+ return fetchEndpoint(tuple, {
616
+ body,
617
+ onReceive: buildOnReceive,
618
+ requireBody: false
619
+ });
620
+ };
621
+ const useEndpoint = (...useArgs) => {
622
+ const args = useArgs[0];
623
+ const tuple = toArgsTuple(args);
624
+ const queryKeys = getQueryKeys(...tuple);
625
+ emit({
626
+ type: "useEndpoint",
627
+ leaf: leafLabel,
628
+ variant: "infiniteGet",
629
+ keys: queryKeys
630
+ });
631
+ const params = args?.params;
632
+ const query = args?.query;
633
+ const buildOptions = feedQueryOptions ?? {};
634
+ const listenersRef = useRef2(/* @__PURE__ */ new Set());
635
+ const notifyOnReceive = useCallback2((data) => {
636
+ buildOptions?.onReceive?.(data);
637
+ listenersRef.current.forEach((listener) => listener(data));
638
+ }, []);
639
+ const registerOnReceive = useCallback2(
640
+ (listener) => {
641
+ listenersRef.current.add(listener);
642
+ return () => {
643
+ listenersRef.current.delete(listener);
644
+ };
645
+ },
646
+ []
647
+ );
648
+ const { normalizedQuery, normalizedParams } = buildUrl(
649
+ { ...leaf, cfg: leafCfg },
650
+ baseUrl,
651
+ params,
652
+ query
653
+ );
654
+ const queryResult = useInfiniteQuery(
655
+ {
656
+ ...buildOptions,
657
+ placeholderData: buildOptions.placeholderData ?? keepPreviousData2,
658
+ initialPageParam: feedInitialPageParam,
659
+ getNextPageParam: (lastPage) => cursorFromPage(lastPage),
660
+ queryKey: queryKeys,
661
+ queryFn: ({ pageParam }) => {
662
+ if (!effectiveSplitPageSize) {
663
+ const pageQuery = {
664
+ ...normalizedQuery,
665
+ ...pageParam ? { [feedCursorParam]: pageParam } : {}
666
+ };
667
+ return fetchEndpoint(tuple, {
668
+ queryOverride: pageQuery,
669
+ onReceive: notifyOnReceive
670
+ });
671
+ }
672
+ const basePageSizeRaw = normalizedQuery?.[effectiveSplitPageSizeParam];
673
+ const basePageSize = typeof basePageSizeRaw === "number" ? basePageSizeRaw : basePageSizeRaw != null ? Number(basePageSizeRaw) : void 0;
674
+ if (!basePageSize || !Number.isFinite(basePageSize) || basePageSize <= effectiveSplitPageSize) {
675
+ const pageQuery = {
676
+ ...normalizedQuery,
677
+ ...pageParam ? { [feedCursorParam]: pageParam } : {}
678
+ };
679
+ return fetchEndpoint(tuple, {
680
+ queryOverride: pageQuery,
681
+ onReceive: notifyOnReceive
682
+ });
683
+ }
684
+ const totalTarget = basePageSize;
685
+ const pageParamForThisPage = pageParam;
686
+ const runSplitFetch = async () => {
687
+ let remaining = totalTarget;
688
+ let currentCursor = pageParam;
689
+ let aggregated;
690
+ while (remaining > 0) {
691
+ const thisCallSize = Math.min(remaining, effectiveSplitPageSize);
692
+ const splitQuery = {
693
+ ...normalizedQuery,
694
+ ...currentCursor ? { [feedCursorParam]: currentCursor } : {},
695
+ [effectiveSplitPageSizeParam]: thisCallSize
696
+ };
697
+ const page = await fetchEndpoint(tuple, {
698
+ queryOverride: splitQuery,
699
+ onReceive: notifyOnReceive
700
+ });
701
+ aggregated = aggregated ? mergePageOutputs(aggregated, page) : page;
702
+ remaining -= thisCallSize;
703
+ const nextCursor = cursorFromPage(page);
704
+ currentCursor = nextCursor;
705
+ const k = queryKeys;
706
+ queryClient.setQueryData(k, (prev) => {
707
+ if (!aggregated) return prev;
708
+ if (!prev) {
709
+ return {
710
+ pages: [aggregated],
711
+ pageParams: [pageParamForThisPage]
712
+ };
713
+ }
714
+ const idx = prev.pageParams.findIndex(
715
+ (p) => p === pageParamForThisPage || p == null && pageParamForThisPage == null
716
+ );
717
+ if (idx === -1) {
718
+ return {
719
+ pages: [...prev.pages, aggregated],
720
+ pageParams: [...prev.pageParams, pageParamForThisPage]
721
+ };
722
+ }
723
+ const newPages = [...prev.pages];
724
+ newPages[idx] = aggregated;
725
+ return { ...prev, pages: newPages };
726
+ });
727
+ if (!nextCursor) {
728
+ break;
729
+ }
730
+ }
731
+ return aggregated;
732
+ };
733
+ return runSplitFetch();
734
+ }
735
+ },
736
+ queryClient
737
+ );
738
+ return { ...queryResult, onReceive: registerOnReceive };
739
+ };
740
+ return {
741
+ getQueryKeys,
742
+ invalidate: invalidateExact,
743
+ setData,
744
+ useEndpoint,
745
+ fetch: fetchGet
746
+ };
747
+ }
748
+
749
+ // src/routesV3.client.mutation.ts
750
+ import { buildCacheKey as buildCacheKey3, lowProfileParse as lowProfileParse4 } from "@emeryld/rrroutes-contract";
751
+ import {
752
+ useMutation
753
+ } from "@tanstack/react-query";
754
+ import { useCallback as useCallback3, useRef as useRef3 } from "react";
755
+ function buildMutationLeaf(leaf, rqOpts, env) {
756
+ const leafCfg = leaf.cfg;
757
+ const method = toUpper(leaf.method);
758
+ const expectsArgs = Boolean(leafCfg.paramsSchema || leafCfg.querySchema);
759
+ const {
760
+ baseUrl,
761
+ validateResponses,
762
+ fetcher,
763
+ queryClient,
764
+ emit,
765
+ decorateDebugEvent,
766
+ isVerboseDebug,
767
+ leafLabel
768
+ } = env;
769
+ emit({ type: "build", leaf: leafLabel });
770
+ const getQueryKeys = (...tuple) => {
771
+ const a = extractArgs(tuple);
772
+ const params = a?.params;
773
+ const query = a?.query;
774
+ return buildCacheKey3({
775
+ leaf,
776
+ params,
777
+ query
778
+ });
779
+ };
780
+ const invalidateExact = async (...tuple) => {
781
+ const queryKey = getQueryKeys(...tuple);
782
+ await queryClient.invalidateQueries({ queryKey, exact: true });
783
+ emit({ type: "invalidate", key: queryKey, exact: true });
784
+ };
785
+ const setData = (...args) => {
786
+ const [updater, ...rest] = args;
787
+ const k = getQueryKeys(...rest);
788
+ const next = queryClient.setQueryData(
789
+ k,
790
+ (prev) => typeof updater === "function" ? updater(prev) : updater
791
+ );
792
+ emit({ type: "setData", key: k });
793
+ return next;
794
+ };
795
+ const mutationBuildOptions = rqOpts ?? {};
796
+ const fetchEndpoint = async (tuple, options) => {
797
+ const a = extractArgs(tuple);
798
+ const params = a?.params;
799
+ const query = options?.queryOverride ?? a?.query;
800
+ const { url, normalizedQuery, normalizedParams } = buildUrl(
801
+ { ...leaf, cfg: leafCfg },
802
+ baseUrl,
803
+ params,
804
+ query
805
+ );
806
+ let payload;
807
+ const acceptsBody = Boolean(leafCfg.bodySchema);
808
+ const requiresBody = options?.requireBody ?? (!acceptsBody ? false : true);
809
+ if (typeof options?.body !== "undefined") {
810
+ const normalizedBody = leafCfg.bodySchema ? lowProfileParse4(leafCfg.bodySchema, options.body) : void 0;
811
+ const isMultipart = Array.isArray(leafCfg.bodyFiles) && leafCfg.bodyFiles.length > 0;
812
+ if (isMultipart && normalizedBody && typeof normalizedBody === "object") {
813
+ payload = toFormData(normalizedBody);
814
+ } else {
815
+ payload = normalizedBody;
816
+ }
817
+ } else if (requiresBody && acceptsBody) {
818
+ throw new Error("Body is required when invoking a mutation fetch.");
819
+ }
820
+ const startedAt = Date.now();
821
+ const detail = isVerboseDebug ? { params: normalizedParams, query: normalizedQuery } : void 0;
822
+ emit(
823
+ decorateDebugEvent(
824
+ {
825
+ type: "fetch",
826
+ stage: "start",
827
+ method,
828
+ url,
829
+ leaf: leafLabel,
830
+ ...payload !== void 0 ? { body: payload } : {}
831
+ },
832
+ detail
833
+ )
834
+ );
835
+ try {
836
+ const out = await fetcher(
837
+ payload === void 0 ? { url, method } : { url, method, body: payload }
838
+ );
839
+ emit(
840
+ decorateDebugEvent(
841
+ {
842
+ type: "fetch",
843
+ stage: "fetched",
844
+ method,
845
+ url,
846
+ leaf: leafLabel,
847
+ durationMs: Date.now() - startedAt
848
+ },
849
+ isVerboseDebug ? {
850
+ params: normalizedParams,
851
+ query: normalizedQuery,
852
+ output: out
853
+ } : void 0
854
+ )
855
+ );
856
+ if (validateResponses) {
857
+ if (!leafCfg.outputSchema) {
858
+ throw new Error(
859
+ `No output schema defined for leaf ${leafLabel}, cannot validate response.`
860
+ );
861
+ }
862
+ out.data = lowProfileParse4(
863
+ leafCfg.outputSchema,
864
+ out.data
865
+ );
866
+ emit(
867
+ decorateDebugEvent(
868
+ {
869
+ type: "fetch",
870
+ stage: "parsed",
871
+ method,
872
+ url,
873
+ leaf: leafLabel,
874
+ durationMs: Date.now() - startedAt
875
+ },
876
+ isVerboseDebug ? {
877
+ params: normalizedParams,
878
+ query: normalizedQuery,
879
+ output: out
880
+ } : void 0
881
+ )
882
+ );
883
+ }
884
+ options?.onReceive?.(out.data);
885
+ return out.data;
886
+ } catch (error) {
887
+ emit(
888
+ decorateDebugEvent(
889
+ {
890
+ type: "fetch",
891
+ stage: "error",
892
+ method,
893
+ url,
894
+ leaf: leafLabel,
895
+ durationMs: Date.now() - startedAt,
896
+ ...payload !== void 0 ? { body: payload } : {},
897
+ error
898
+ },
899
+ detail
900
+ )
901
+ );
902
+ throw error;
903
+ }
904
+ };
905
+ const fetchMutation = async (...tupleWithBody) => {
906
+ if (tupleWithBody.length === 0) {
907
+ throw new Error("Body is required when invoking a mutation fetch.");
80
908
  }
81
- params.append(k, JSON.stringify(v));
82
- }
83
- const s = params.toString();
84
- return s ? `?${s}` : "";
85
- }
86
- function stripKey(obj, key) {
87
- if (!obj) return obj;
88
- const { [key]: _omit, ...rest } = obj;
89
- return rest;
909
+ const bodyIndex = tupleWithBody.length - 1;
910
+ const tuple = tupleWithBody.slice(0, bodyIndex);
911
+ const body = tupleWithBody[bodyIndex];
912
+ const result = await fetchEndpoint(tuple, {
913
+ body,
914
+ onReceive: (data) => mutationBuildOptions?.onReceive?.(data),
915
+ requireBody: true
916
+ });
917
+ return result;
918
+ };
919
+ const useEndpoint = (...useArgs) => {
920
+ const args = useArgs[0];
921
+ const tuple = toArgsTuple(args);
922
+ const mutationKey = getQueryKeys(...tuple);
923
+ emit({
924
+ type: "useEndpoint",
925
+ leaf: leafLabel,
926
+ variant: "mutation",
927
+ keys: mutationKey
928
+ });
929
+ const listenersRef = useRef3(/* @__PURE__ */ new Set());
930
+ const notifyListeners = useCallback3((data) => {
931
+ listenersRef.current.forEach((listener) => listener(data));
932
+ }, []);
933
+ const registerOnReceive = useCallback3(
934
+ (listener) => {
935
+ listenersRef.current.add(listener);
936
+ return () => {
937
+ listenersRef.current.delete(listener);
938
+ };
939
+ },
940
+ []
941
+ );
942
+ const mutationResult = useMutation(
943
+ {
944
+ ...mutationBuildOptions ?? {},
945
+ mutationKey,
946
+ mutationFn: async (body) => {
947
+ const result = await fetchMutation(
948
+ ...[...tuple, body]
949
+ );
950
+ notifyListeners(result);
951
+ return result;
952
+ }
953
+ },
954
+ queryClient
955
+ );
956
+ return { ...mutationResult, onReceive: registerOnReceive };
957
+ };
958
+ return {
959
+ getQueryKeys,
960
+ invalidate: invalidateExact,
961
+ setData,
962
+ useEndpoint,
963
+ fetch: fetchMutation
964
+ };
90
965
  }
91
- var defaultGetNextCursor = (p) => {
92
- if (!p || typeof p !== "object") return void 0;
93
- const record = p;
94
- if ("nextCursor" in record) {
95
- return record.nextCursor;
96
- }
97
- if ("meta" in record) {
98
- const meta = record.meta;
99
- if (meta && typeof meta === "object" && "nextCursor" in meta) {
100
- return meta.nextCursor;
101
- }
102
- }
103
- return void 0;
104
- };
966
+
967
+ // src/routesV3.client.ts
105
968
  var defaultDebugLogger = (event) => {
106
969
  if (typeof console === "undefined") return;
107
970
  const fn = console.debug ?? console.log;
@@ -163,22 +1026,6 @@ function createDebugEmitter(option, environment) {
163
1026
  }
164
1027
  return disabled;
165
1028
  }
166
- function extractArgs(args) {
167
- return args[0];
168
- }
169
- function toArgsTuple(args) {
170
- return typeof args === "undefined" ? [] : [args];
171
- }
172
- function buildUrl(leaf, baseUrl, params, query) {
173
- const normalizedParams = leaf.cfg.paramsSchema ? lowProfileParse(leaf.cfg.paramsSchema, params) : {};
174
- const normalizedQuery = leaf.cfg.querySchema ? lowProfileParse(leaf.cfg.querySchema, query) : {};
175
- const path = compilePath(
176
- leaf.path,
177
- normalizedParams ?? {}
178
- );
179
- const url = `${baseUrl ?? ""}${path}${toSearchString(normalizedQuery)}`;
180
- return { url, normalizedQuery, normalizedParams };
181
- }
182
1029
  function createRouteClient(opts) {
183
1030
  const queryClient = opts.queryClient;
184
1031
  const fetcher = opts.fetcher ?? defaultFetcher;
@@ -199,365 +1046,41 @@ function createRouteClient(opts) {
199
1046
  emitDebug({ type: "invalidate", key: queryKey, exact });
200
1047
  }
201
1048
  function buildInternal(leaf, rqOpts, meta) {
202
- const isGet = leaf.method === "get";
203
- const isFeed = !!leaf.cfg.feed;
204
- const leafCfg = leaf.cfg;
205
- const validateResponses = opts.validateResponses ?? true;
206
- const method = toUpper(leaf.method);
207
- const expectsArgs = Boolean(leafCfg.paramsSchema || leafCfg.querySchema);
208
1049
  const leafLabel = `${leaf.method.toUpperCase()} ${String(leaf.path)}`;
209
1050
  const debugName = meta?.name;
210
1051
  const emit = (event) => emitDebug(event, debugName);
211
- emit({ type: "build", leaf: leafLabel });
212
- let feedCursorParam = "pagination_cursor";
213
- let feedNextPageParam;
214
- let feedInitialPageParam;
215
- let feedQueryOptions;
1052
+ const isGet = leaf.method === "get";
1053
+ const isFeed = !!leaf.cfg.feed;
1054
+ const validateResponses = opts.validateResponses ?? true;
1055
+ const env = {
1056
+ baseUrl,
1057
+ validateResponses,
1058
+ fetcher,
1059
+ queryClient,
1060
+ emit,
1061
+ decorateDebugEvent,
1062
+ isVerboseDebug,
1063
+ leafLabel
1064
+ };
216
1065
  if (isGet && isFeed) {
217
- const infiniteOptions = rqOpts ?? {};
218
- const {
219
- cursorParam,
220
- getNextPageParam,
221
- initialPageParam,
222
- ...passthroughOptions
223
- } = infiniteOptions;
224
- feedCursorParam = cursorParam ?? "pagination_cursor";
225
- feedNextPageParam = getNextPageParam ?? ((lastPage) => defaultGetNextCursor(lastPage));
226
- feedInitialPageParam = typeof initialPageParam === "undefined" ? void 0 : initialPageParam;
227
- feedQueryOptions = passthroughOptions;
228
- }
229
- const getQueryKeys = (...tuple) => {
230
- const a = extractArgs(tuple);
231
- const params = a?.params;
232
- const query = a?.query;
233
- const qForKey = isGet && isFeed ? stripKey(query, feedCursorParam) : query;
234
- return buildCacheKey({
1066
+ return buildInfiniteGetLeaf(
235
1067
  leaf,
236
- params,
237
- query: qForKey
238
- });
239
- };
240
- const invalidateExact = async (...tuple) => {
241
- const queryKey = getQueryKeys(...tuple);
242
- await queryClient.invalidateQueries({ queryKey, exact: true });
243
- emit({ type: "invalidate", key: queryKey, exact: true });
244
- };
245
- const setData = (...args) => {
246
- const [updater, ...rest] = args;
247
- const k = getQueryKeys(...rest);
248
- let next;
249
- if (isGet && isFeed) {
250
- next = queryClient.setQueryData(
251
- k,
252
- (prev) => typeof updater === "function" ? updater(prev) : updater
253
- );
254
- } else {
255
- next = queryClient.setQueryData(
256
- k,
257
- (prev) => typeof updater === "function" ? updater(prev) : updater
258
- );
259
- }
260
- emit({ type: "setData", key: k });
261
- return next;
262
- };
263
- const buildOnReceive = rqOpts?.onReceive;
264
- const fetchEndpoint = async (tuple, options) => {
265
- const a = extractArgs(tuple);
266
- const params = a?.params;
267
- const query = options?.queryOverride ?? a?.query;
268
- const { url, normalizedQuery, normalizedParams } = buildUrl(
269
- { ...leaf, cfg: leafCfg },
270
- baseUrl,
271
- params,
272
- query
273
- );
274
- let payload;
275
- const acceptsBody = Boolean(leafCfg.bodySchema);
276
- const requiresBody = options?.requireBody ?? (!isGet && acceptsBody);
277
- if (typeof options?.body !== "undefined") {
278
- const normalizedBody = leafCfg.bodySchema ? lowProfileParse(leafCfg.bodySchema, options.body) : void 0;
279
- const isMultipart = Array.isArray(leafCfg.bodyFiles) && leafCfg.bodyFiles.length > 0;
280
- if (isMultipart && normalizedBody && typeof normalizedBody === "object") {
281
- payload = toFormData(normalizedBody);
282
- } else {
283
- payload = normalizedBody;
284
- }
285
- } else if (requiresBody) {
286
- throw new Error("Body is required when invoking a mutation fetch.");
287
- }
288
- const startedAt = Date.now();
289
- const detail = isVerboseDebug ? { params: normalizedParams, query: normalizedQuery } : void 0;
290
- emit(
291
- decorateDebugEvent(
292
- {
293
- type: "fetch",
294
- stage: "start",
295
- method,
296
- url,
297
- leaf: leafLabel,
298
- ...payload !== void 0 ? { body: payload } : {}
299
- },
300
- detail
301
- )
1068
+ rqOpts,
1069
+ env
302
1070
  );
303
- try {
304
- const out = await fetcher(
305
- payload === void 0 ? { url, method } : { url, method, body: payload }
306
- );
307
- emit(
308
- decorateDebugEvent(
309
- {
310
- type: "fetch",
311
- stage: "fetched",
312
- method,
313
- url,
314
- leaf: leafLabel,
315
- durationMs: Date.now() - startedAt
316
- },
317
- isVerboseDebug ? {
318
- params: normalizedParams,
319
- query: normalizedQuery,
320
- output: out
321
- } : void 0
322
- )
323
- );
324
- if (validateResponses) {
325
- if (!leafCfg.outputSchema) {
326
- throw new Error(
327
- `No output schema defined for leaf ${leafLabel}, cannot validate response.`
328
- );
329
- }
330
- out.data = lowProfileParse(
331
- leafCfg.outputSchema,
332
- out.data
333
- );
334
- emit(
335
- decorateDebugEvent(
336
- {
337
- type: "fetch",
338
- stage: "parsed",
339
- method,
340
- url,
341
- leaf: leafLabel,
342
- durationMs: Date.now() - startedAt
343
- },
344
- isVerboseDebug ? {
345
- params: normalizedParams,
346
- query: normalizedQuery,
347
- output: out
348
- } : void 0
349
- )
350
- );
351
- }
352
- options?.onReceive?.(out.data);
353
- return out.data;
354
- } catch (error) {
355
- emit(
356
- decorateDebugEvent(
357
- {
358
- type: "fetch",
359
- stage: "error",
360
- method,
361
- url,
362
- leaf: leafLabel,
363
- durationMs: Date.now() - startedAt,
364
- ...payload !== void 0 ? { body: payload } : {},
365
- error
366
- },
367
- detail
368
- )
369
- );
370
- throw error;
371
- }
372
- };
373
- const fetchGet = (...tupleWithBody) => {
374
- const acceptsBody = Boolean(leafCfg.bodySchema);
375
- const tupleLength = tupleWithBody.length;
376
- const maybeBodyIndex = expectsArgs ? 1 : 0;
377
- const hasBodyCandidate = acceptsBody && tupleLength > maybeBodyIndex;
378
- const body = hasBodyCandidate ? tupleWithBody[tupleLength - 1] : void 0;
379
- const tuple = hasBodyCandidate ? tupleWithBody.slice(0, tupleLength - 1) : tupleWithBody;
380
- return fetchEndpoint(tuple, {
381
- body,
382
- onReceive: buildOnReceive,
383
- requireBody: false
384
- });
385
- };
386
- if (isGet && isFeed) {
387
- const useEndpoint2 = (...useArgs) => {
388
- const args = useArgs[0];
389
- const tuple = toArgsTuple(args);
390
- const queryKeys = getQueryKeys(...tuple);
391
- emit({
392
- type: "useEndpoint",
393
- leaf: leafLabel,
394
- variant: "infiniteGet",
395
- keys: queryKeys
396
- });
397
- const params = args?.params;
398
- const query = args?.query;
399
- const buildOptions = feedQueryOptions ?? {};
400
- const listenersRef = useRef(/* @__PURE__ */ new Set());
401
- const notifyOnReceive = useCallback((data) => {
402
- buildOptions?.onReceive?.(data);
403
- listenersRef.current.forEach((listener) => listener(data));
404
- }, []);
405
- const registerOnReceive = useCallback(
406
- (listener) => {
407
- listenersRef.current.add(listener);
408
- return () => {
409
- listenersRef.current.delete(listener);
410
- };
411
- },
412
- []
413
- );
414
- const { normalizedQuery, normalizedParams } = buildUrl(
415
- { ...leaf, cfg: leafCfg },
416
- baseUrl,
417
- params,
418
- query
419
- );
420
- const queryResult = useInfiniteQuery(
421
- {
422
- ...buildOptions,
423
- placeholderData: buildOptions.placeholderData ?? keepPreviousData,
424
- initialPageParam: feedInitialPageParam,
425
- getNextPageParam: (lastPage) => (feedNextPageParam ?? defaultGetNextCursor)(lastPage),
426
- queryKey: queryKeys,
427
- queryFn: ({ pageParam }) => {
428
- const pageQuery = {
429
- ...normalizedQuery,
430
- ...pageParam ? { [feedCursorParam]: pageParam } : {}
431
- };
432
- return fetchEndpoint(tuple, {
433
- queryOverride: pageQuery,
434
- onReceive: notifyOnReceive
435
- });
436
- }
437
- // NOTE: TData is InfiniteData<T>, so we don't need a select here.
438
- },
439
- queryClient
440
- );
441
- return { ...queryResult, onReceive: registerOnReceive };
442
- };
443
- return {
444
- getQueryKeys,
445
- invalidate: invalidateExact,
446
- setData,
447
- useEndpoint: useEndpoint2,
448
- fetch: fetchGet
449
- };
450
1071
  }
451
1072
  if (isGet) {
452
- const useEndpoint2 = (...useArgs) => {
453
- const args = useArgs[0];
454
- const tuple = toArgsTuple(args);
455
- const queryKeys = getQueryKeys(...tuple);
456
- emit({
457
- type: "useEndpoint",
458
- leaf: leafLabel,
459
- variant: "get",
460
- keys: queryKeys
461
- });
462
- const params = args?.params;
463
- const query = args?.query;
464
- const buildOptions = rqOpts ?? {};
465
- const listenersRef = useRef(/* @__PURE__ */ new Set());
466
- const notifyOnReceive = useCallback((data) => {
467
- buildOptions?.onReceive?.(data);
468
- listenersRef.current.forEach((listener) => listener(data));
469
- }, []);
470
- const registerOnReceive = useCallback(
471
- (listener) => {
472
- listenersRef.current.add(listener);
473
- return () => {
474
- listenersRef.current.delete(listener);
475
- };
476
- },
477
- []
478
- );
479
- const queryResult = useQuery(
480
- {
481
- ...buildOptions,
482
- queryKey: getQueryKeys(...tuple),
483
- placeholderData: keepPreviousData,
484
- queryFn: () => fetchEndpoint(tuple, {
485
- onReceive: notifyOnReceive
486
- })
487
- },
488
- queryClient
489
- );
490
- return { ...queryResult, onReceive: registerOnReceive };
491
- };
492
- return {
493
- getQueryKeys,
494
- invalidate: invalidateExact,
495
- setData,
496
- useEndpoint: useEndpoint2,
497
- fetch: fetchGet
498
- };
499
- }
500
- const mutationBuildOptions = rqOpts ?? {};
501
- const fetchMutation = async (...tupleWithBody) => {
502
- if (tupleWithBody.length === 0) {
503
- throw new Error("Body is required when invoking a mutation fetch.");
504
- }
505
- const bodyIndex = tupleWithBody.length - 1;
506
- const tuple = tupleWithBody.slice(0, bodyIndex);
507
- const body = tupleWithBody[bodyIndex];
508
- const result = await fetchEndpoint(tuple, {
509
- body,
510
- onReceive: (data) => mutationBuildOptions?.onReceive?.(data),
511
- requireBody: true
512
- });
513
- return result;
514
- };
515
- const useEndpoint = (...useArgs) => {
516
- const args = useArgs[0];
517
- const tuple = toArgsTuple(args);
518
- const mutationKey = getQueryKeys(...tuple);
519
- emit({
520
- type: "useEndpoint",
521
- leaf: leafLabel,
522
- variant: "mutation",
523
- keys: mutationKey
524
- });
525
- const listenersRef = useRef(/* @__PURE__ */ new Set());
526
- const notifyListeners = useCallback((data) => {
527
- listenersRef.current.forEach((listener) => listener(data));
528
- }, []);
529
- const registerOnReceive = useCallback(
530
- (listener) => {
531
- listenersRef.current.add(listener);
532
- return () => {
533
- listenersRef.current.delete(listener);
534
- };
535
- },
536
- []
537
- );
538
- const mutationResult = useMutation(
539
- {
540
- ...mutationBuildOptions,
541
- mutationKey,
542
- mutationFn: async (body) => {
543
- const result = await fetchMutation(
544
- ...[...tuple, body]
545
- );
546
- notifyListeners(result);
547
- return result;
548
- }
549
- },
550
- queryClient
1073
+ return buildGetLeaf(
1074
+ leaf,
1075
+ rqOpts,
1076
+ env
551
1077
  );
552
- return { ...mutationResult, onReceive: registerOnReceive };
553
- };
554
- return {
555
- getQueryKeys,
556
- invalidate: invalidateExact,
557
- setData,
558
- useEndpoint,
559
- fetch: fetchMutation
560
- };
1078
+ }
1079
+ return buildMutationLeaf(
1080
+ leaf,
1081
+ rqOpts,
1082
+ env
1083
+ );
561
1084
  }
562
1085
  const fetchRaw = async (input) => {
563
1086
  const { path, method, query, body, params } = input;
@@ -641,81 +1164,6 @@ function buildRouter(routeClient, routes) {
641
1164
  meta
642
1165
  ));
643
1166
  }
644
- function toFormData(body) {
645
- const fd = new FormData();
646
- for (const [k, v] of Object.entries(body ?? {})) {
647
- if (v == null) continue;
648
- if (Array.isArray(v))
649
- v.forEach((item, i) => fd.append(`${k}[${i}]`, item));
650
- else fd.append(k, v);
651
- }
652
- return fd;
653
- }
654
- function getPathParamNames(path) {
655
- const names = /* @__PURE__ */ new Set();
656
- const re = /:([A-Za-z0-9_]+)/g;
657
- let match;
658
- while ((match = re.exec(path)) !== null) {
659
- names.add(match[1]);
660
- }
661
- return names;
662
- }
663
- function normalizeFlatQuery(query) {
664
- if (query == null) return void 0;
665
- if (typeof query !== "object" || Array.isArray(query)) {
666
- throw new Error("Query must be a plain object (Record<string, string>).");
667
- }
668
- const result = {};
669
- for (const [k, v] of Object.entries(query)) {
670
- if (v == null) continue;
671
- if (typeof v !== "string") {
672
- throw new Error(
673
- `Query param "${k}" must be a string; received type "${typeof v}".`
674
- );
675
- }
676
- result[k] = v;
677
- }
678
- return Object.keys(result).length > 0 ? result : void 0;
679
- }
680
- function compileRawPath(path, params) {
681
- const placeholders = getPathParamNames(path);
682
- if (!params || typeof params !== "object" || Array.isArray(params)) {
683
- if (placeholders.size > 0) {
684
- throw new Error(
685
- `Missing path parameters for "${path}": ${[...placeholders].join(
686
- ", "
687
- )}`
688
- );
689
- }
690
- return path;
691
- }
692
- const paramObj = params;
693
- const providedNames = new Set(Object.keys(paramObj));
694
- for (const name of providedNames) {
695
- if (!placeholders.has(name)) {
696
- throw new Error(
697
- `Unexpected path parameter "${name}" for template "${path}".`
698
- );
699
- }
700
- const value = paramObj[name];
701
- if (value != null && (typeof value === "object" || Array.isArray(value))) {
702
- throw new Error(
703
- `Path parameter "${name}" must be a primitive; received "${typeof value}".`
704
- );
705
- }
706
- }
707
- for (const name of placeholders) {
708
- if (!providedNames.has(name)) {
709
- throw new Error(
710
- `Missing value for path parameter "${name}" in template "${path}".`
711
- );
712
- }
713
- }
714
- if (placeholders.size === 0) {
715
- return path;
716
- }
717
- return compilePath(path, paramObj);
718
- }
719
1167
 
720
1168
  // src/sockets/socket.client.sys.ts
721
1169
  import { z } from "zod";