@emeryld/rrroutes-client 1.2.4 → 1.2.7
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 +130 -87
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +130 -87
- package/dist/index.mjs.map +1 -1
- package/dist/routesV3.client.index.d.ts +1 -1
- package/dist/routesV3.client.types.d.ts +46 -16
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -89,34 +89,66 @@ function stripKey(obj, key) {
|
|
|
89
89
|
return rest;
|
|
90
90
|
}
|
|
91
91
|
var defaultGetNextCursor = (p) => p && typeof p === "object" && "nextCursor" in p ? p.nextCursor : void 0;
|
|
92
|
-
var noopDebugLogger = () => {
|
|
93
|
-
};
|
|
94
92
|
var defaultDebugLogger = (event) => {
|
|
95
93
|
if (typeof console === "undefined") return;
|
|
96
94
|
const fn = console.debug ?? console.log;
|
|
97
95
|
fn?.call(console, "[rrroutes-client]", event);
|
|
98
96
|
};
|
|
99
|
-
|
|
100
|
-
|
|
97
|
+
var debugEventTypes = [
|
|
98
|
+
"fetch",
|
|
99
|
+
"invalidate",
|
|
100
|
+
"setData",
|
|
101
|
+
"build",
|
|
102
|
+
"useEndpoint"
|
|
103
|
+
];
|
|
104
|
+
var noopEmit = () => {
|
|
105
|
+
};
|
|
106
|
+
function createDebugEmitter(option, environment) {
|
|
107
|
+
const disabled = { emit: noopEmit, mode: "minimal" };
|
|
108
|
+
if (environment && environment.toLowerCase() === "production") {
|
|
109
|
+
return disabled;
|
|
110
|
+
}
|
|
101
111
|
if (!option) {
|
|
102
112
|
return disabled;
|
|
103
113
|
}
|
|
104
114
|
if (option === true || option === "minimal") {
|
|
105
|
-
return {
|
|
115
|
+
return {
|
|
116
|
+
emit: (event, name) => defaultDebugLogger(name ? { ...event, name } : event),
|
|
117
|
+
mode: "minimal"
|
|
118
|
+
};
|
|
106
119
|
}
|
|
107
120
|
if (option === "complete") {
|
|
108
|
-
return {
|
|
121
|
+
return {
|
|
122
|
+
emit: (event, name) => defaultDebugLogger(name ? { ...event, name } : event),
|
|
123
|
+
mode: "complete"
|
|
124
|
+
};
|
|
109
125
|
}
|
|
110
126
|
if (typeof option === "function") {
|
|
111
|
-
return {
|
|
127
|
+
return {
|
|
128
|
+
emit: (event, name) => option(name ? { ...event, name } : event),
|
|
129
|
+
mode: "minimal"
|
|
130
|
+
};
|
|
112
131
|
}
|
|
113
|
-
if (option
|
|
114
|
-
|
|
132
|
+
if (typeof option === "object") {
|
|
133
|
+
const toggles = option;
|
|
134
|
+
const verbose = Boolean(toggles.verbose);
|
|
135
|
+
const enabledTypes = debugEventTypes.filter((type) => toggles[type]);
|
|
136
|
+
if (enabledTypes.length === 0) {
|
|
137
|
+
return { emit: noopEmit, mode: verbose ? "complete" : "minimal" };
|
|
138
|
+
}
|
|
139
|
+
const whitelist = new Set(enabledTypes);
|
|
140
|
+
const onlySet = toggles.only && toggles.only.length > 0 ? new Set(toggles.only) : void 0;
|
|
141
|
+
const logger = toggles.logger ?? defaultDebugLogger;
|
|
142
|
+
const emit = (event, name) => {
|
|
143
|
+
if (!whitelist.has(event.type)) return;
|
|
144
|
+
if (onlySet) {
|
|
145
|
+
if (!name || !onlySet.has(name)) return;
|
|
146
|
+
}
|
|
147
|
+
logger(name ? { ...event, name } : event);
|
|
148
|
+
};
|
|
149
|
+
return { emit, mode: verbose ? "complete" : "minimal" };
|
|
115
150
|
}
|
|
116
|
-
return
|
|
117
|
-
emit: option.logger ?? defaultDebugLogger,
|
|
118
|
-
mode: option.mode ?? "minimal"
|
|
119
|
-
};
|
|
151
|
+
return disabled;
|
|
120
152
|
}
|
|
121
153
|
function extractArgs(args) {
|
|
122
154
|
return args[0];
|
|
@@ -134,7 +166,8 @@ function createRouteClient(opts) {
|
|
|
134
166
|
const baseUrl = opts.baseUrl;
|
|
135
167
|
const cursorParam = opts.cursorParam ?? "cursor";
|
|
136
168
|
const getNextCursor = opts.getNextCursor ?? defaultGetNextCursor;
|
|
137
|
-
const
|
|
169
|
+
const environment = opts.environment ?? (typeof process !== "undefined" && process.env ? process.env.NODE_ENV : void 0);
|
|
170
|
+
const { emit: emitDebug, mode: debugMode } = createDebugEmitter(opts.debug, environment);
|
|
138
171
|
const isVerboseDebug = debugMode === "complete";
|
|
139
172
|
const decorateDebugEvent = (event, details) => {
|
|
140
173
|
if (!isVerboseDebug || !details) return event;
|
|
@@ -145,12 +178,14 @@ function createRouteClient(opts) {
|
|
|
145
178
|
await queryClient.invalidateQueries({ queryKey, exact });
|
|
146
179
|
emitDebug({ type: "invalidate", key: queryKey, exact });
|
|
147
180
|
}
|
|
148
|
-
function buildInternal(leaf, rqOpts) {
|
|
181
|
+
function buildInternal(leaf, rqOpts, meta) {
|
|
149
182
|
const isGet = leaf.method === "get";
|
|
150
183
|
const isFeed = !!leaf.cfg.feed;
|
|
151
184
|
const method = toUpper(leaf.method);
|
|
152
185
|
const leafLabel = `${leaf.method.toUpperCase()} ${String(leaf.path)}`;
|
|
153
|
-
|
|
186
|
+
const debugName = meta?.name;
|
|
187
|
+
const emit = (event) => emitDebug(event, debugName);
|
|
188
|
+
emit({ type: "build", leaf: leafLabel });
|
|
154
189
|
const key = (...tuple) => {
|
|
155
190
|
const a = extractArgs(tuple);
|
|
156
191
|
const params = a?.params;
|
|
@@ -161,7 +196,7 @@ function createRouteClient(opts) {
|
|
|
161
196
|
const invalidateExact = async (...tuple) => {
|
|
162
197
|
const queryKey = key(...tuple);
|
|
163
198
|
await queryClient.invalidateQueries({ queryKey, exact: true });
|
|
164
|
-
|
|
199
|
+
emit({ type: "invalidate", key: queryKey, exact: true });
|
|
165
200
|
};
|
|
166
201
|
const setData = (...args) => {
|
|
167
202
|
const [updater, ...rest] = args;
|
|
@@ -177,11 +212,11 @@ function createRouteClient(opts) {
|
|
|
177
212
|
(prev) => typeof updater === "function" ? updater(prev) : updater
|
|
178
213
|
);
|
|
179
214
|
}
|
|
180
|
-
|
|
215
|
+
emit({ type: "setData", key: k });
|
|
181
216
|
};
|
|
182
217
|
if (isGet && isFeed) {
|
|
183
218
|
const useEndpoint2 = (...tuple) => {
|
|
184
|
-
|
|
219
|
+
emit({ type: "useEndpoint", leaf: leafLabel, variant: "infiniteGet" });
|
|
185
220
|
const a = extractArgs(tuple);
|
|
186
221
|
const params = a?.params;
|
|
187
222
|
const query = a?.query;
|
|
@@ -200,7 +235,7 @@ function createRouteClient(opts) {
|
|
|
200
235
|
const { url } = buildUrl(leaf, baseUrl, params, pageQuery);
|
|
201
236
|
const startedAt = Date.now();
|
|
202
237
|
const detail = isVerboseDebug ? { params: normalizedParams, query: pageQuery } : void 0;
|
|
203
|
-
|
|
238
|
+
emit(
|
|
204
239
|
decorateDebugEvent(
|
|
205
240
|
{ type: "fetch", stage: "start", method, url, leaf: leafLabel },
|
|
206
241
|
detail
|
|
@@ -209,7 +244,7 @@ function createRouteClient(opts) {
|
|
|
209
244
|
try {
|
|
210
245
|
const out = await fetcher({ url, method });
|
|
211
246
|
const parsed = zParse(out, leaf.cfg.outputSchema);
|
|
212
|
-
|
|
247
|
+
emit(
|
|
213
248
|
decorateDebugEvent(
|
|
214
249
|
{
|
|
215
250
|
type: "fetch",
|
|
@@ -224,7 +259,7 @@ function createRouteClient(opts) {
|
|
|
224
259
|
);
|
|
225
260
|
return parsed;
|
|
226
261
|
} catch (error) {
|
|
227
|
-
|
|
262
|
+
emit(
|
|
228
263
|
decorateDebugEvent(
|
|
229
264
|
{
|
|
230
265
|
type: "fetch",
|
|
@@ -253,7 +288,7 @@ function createRouteClient(opts) {
|
|
|
253
288
|
}
|
|
254
289
|
if (isGet) {
|
|
255
290
|
const useEndpoint2 = (...tuple) => {
|
|
256
|
-
|
|
291
|
+
emit({ type: "useEndpoint", leaf: leafLabel, variant: "get" });
|
|
257
292
|
const a = extractArgs(tuple);
|
|
258
293
|
const params = a?.params;
|
|
259
294
|
const query = a?.query;
|
|
@@ -265,7 +300,7 @@ function createRouteClient(opts) {
|
|
|
265
300
|
queryFn: async () => {
|
|
266
301
|
const startedAt = Date.now();
|
|
267
302
|
const detail = isVerboseDebug ? { params: normalizedParams, query: normalizedQuery } : void 0;
|
|
268
|
-
|
|
303
|
+
emit(
|
|
269
304
|
decorateDebugEvent(
|
|
270
305
|
{ type: "fetch", stage: "start", method, url, leaf: leafLabel },
|
|
271
306
|
detail
|
|
@@ -274,7 +309,7 @@ function createRouteClient(opts) {
|
|
|
274
309
|
try {
|
|
275
310
|
const out = await fetcher({ url, method });
|
|
276
311
|
const parsed = zParse(out, leaf.cfg.outputSchema);
|
|
277
|
-
|
|
312
|
+
emit(
|
|
278
313
|
decorateDebugEvent(
|
|
279
314
|
{
|
|
280
315
|
type: "fetch",
|
|
@@ -289,7 +324,7 @@ function createRouteClient(opts) {
|
|
|
289
324
|
);
|
|
290
325
|
return parsed;
|
|
291
326
|
} catch (error) {
|
|
292
|
-
|
|
327
|
+
emit(
|
|
293
328
|
decorateDebugEvent(
|
|
294
329
|
{
|
|
295
330
|
type: "fetch",
|
|
@@ -315,77 +350,85 @@ function createRouteClient(opts) {
|
|
|
315
350
|
useEndpoint: useEndpoint2
|
|
316
351
|
};
|
|
317
352
|
}
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
const
|
|
353
|
+
const fetchEndpoint = async (...tupleWithBody) => {
|
|
354
|
+
if (tupleWithBody.length === 0) {
|
|
355
|
+
throw new Error("Body is required when invoking a mutation fetch.");
|
|
356
|
+
}
|
|
357
|
+
const bodyIndex = tupleWithBody.length - 1;
|
|
358
|
+
const tuple = tupleWithBody.slice(0, bodyIndex);
|
|
359
|
+
const body = tupleWithBody[bodyIndex];
|
|
360
|
+
const args = extractArgs(tuple);
|
|
361
|
+
const params = args?.params;
|
|
362
|
+
const query = args?.query;
|
|
323
363
|
const { url, normalizedQuery, normalizedParams } = buildUrl(leaf, baseUrl, params, query);
|
|
364
|
+
const normalizedBody = zParse(body, leaf.cfg.bodySchema);
|
|
365
|
+
const isMultipart = Array.isArray(leaf.cfg.bodyFiles) && leaf.cfg.bodyFiles.length > 0;
|
|
366
|
+
const payload = isMultipart ? toFormData(normalizedBody) : normalizedBody;
|
|
367
|
+
const startedAt = Date.now();
|
|
368
|
+
const detail = isVerboseDebug ? { params: normalizedParams, query: normalizedQuery } : void 0;
|
|
369
|
+
emit(
|
|
370
|
+
decorateDebugEvent(
|
|
371
|
+
{
|
|
372
|
+
type: "fetch",
|
|
373
|
+
stage: "start",
|
|
374
|
+
method,
|
|
375
|
+
url,
|
|
376
|
+
leaf: leafLabel,
|
|
377
|
+
body: payload
|
|
378
|
+
},
|
|
379
|
+
detail
|
|
380
|
+
)
|
|
381
|
+
);
|
|
382
|
+
try {
|
|
383
|
+
const out = await fetcher({ url, method, body: payload });
|
|
384
|
+
const parsed = zParse(out, leaf.cfg.outputSchema);
|
|
385
|
+
emit(
|
|
386
|
+
decorateDebugEvent(
|
|
387
|
+
{
|
|
388
|
+
type: "fetch",
|
|
389
|
+
stage: "success",
|
|
390
|
+
method,
|
|
391
|
+
url,
|
|
392
|
+
leaf: leafLabel,
|
|
393
|
+
durationMs: Date.now() - startedAt
|
|
394
|
+
},
|
|
395
|
+
isVerboseDebug ? { params: normalizedParams, query: normalizedQuery, output: parsed } : void 0
|
|
396
|
+
)
|
|
397
|
+
);
|
|
398
|
+
return parsed;
|
|
399
|
+
} catch (error) {
|
|
400
|
+
emit(
|
|
401
|
+
decorateDebugEvent(
|
|
402
|
+
{
|
|
403
|
+
type: "fetch",
|
|
404
|
+
stage: "error",
|
|
405
|
+
method,
|
|
406
|
+
url,
|
|
407
|
+
leaf: leafLabel,
|
|
408
|
+
durationMs: Date.now() - startedAt,
|
|
409
|
+
body: payload,
|
|
410
|
+
error
|
|
411
|
+
},
|
|
412
|
+
detail
|
|
413
|
+
)
|
|
414
|
+
);
|
|
415
|
+
throw error;
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
const useEndpoint = (...tuple) => {
|
|
419
|
+
emit({ type: "useEndpoint", leaf: leafLabel, variant: "mutation" });
|
|
324
420
|
return (0, import_react_query.useMutation)({
|
|
325
421
|
...rqOpts,
|
|
326
422
|
mutationKey: key(...tuple),
|
|
327
|
-
mutationFn:
|
|
328
|
-
const normalizedBody = zParse(body, leaf.cfg.bodySchema);
|
|
329
|
-
const isMultipart = Array.isArray(leaf.cfg.bodyFiles) && leaf.cfg.bodyFiles.length > 0;
|
|
330
|
-
const payload = isMultipart ? toFormData(normalizedBody) : normalizedBody;
|
|
331
|
-
const startedAt = Date.now();
|
|
332
|
-
const detail = isVerboseDebug ? { params: normalizedParams, query: normalizedQuery } : void 0;
|
|
333
|
-
emitDebug(
|
|
334
|
-
decorateDebugEvent(
|
|
335
|
-
{
|
|
336
|
-
type: "fetch",
|
|
337
|
-
stage: "start",
|
|
338
|
-
method,
|
|
339
|
-
url,
|
|
340
|
-
leaf: leafLabel,
|
|
341
|
-
body: payload
|
|
342
|
-
},
|
|
343
|
-
detail
|
|
344
|
-
)
|
|
345
|
-
);
|
|
346
|
-
try {
|
|
347
|
-
const out = await fetcher({ url, method, body: payload });
|
|
348
|
-
const parsed = zParse(out, leaf.cfg.outputSchema);
|
|
349
|
-
emitDebug(
|
|
350
|
-
decorateDebugEvent(
|
|
351
|
-
{
|
|
352
|
-
type: "fetch",
|
|
353
|
-
stage: "success",
|
|
354
|
-
method,
|
|
355
|
-
url,
|
|
356
|
-
leaf: leafLabel,
|
|
357
|
-
durationMs: Date.now() - startedAt
|
|
358
|
-
},
|
|
359
|
-
isVerboseDebug ? { params: normalizedParams, query: normalizedQuery, output: parsed } : void 0
|
|
360
|
-
)
|
|
361
|
-
);
|
|
362
|
-
return parsed;
|
|
363
|
-
} catch (error) {
|
|
364
|
-
emitDebug(
|
|
365
|
-
decorateDebugEvent(
|
|
366
|
-
{
|
|
367
|
-
type: "fetch",
|
|
368
|
-
stage: "error",
|
|
369
|
-
method,
|
|
370
|
-
url,
|
|
371
|
-
leaf: leafLabel,
|
|
372
|
-
durationMs: Date.now() - startedAt,
|
|
373
|
-
body: payload,
|
|
374
|
-
error
|
|
375
|
-
},
|
|
376
|
-
detail
|
|
377
|
-
)
|
|
378
|
-
);
|
|
379
|
-
throw error;
|
|
380
|
-
}
|
|
381
|
-
}
|
|
423
|
+
mutationFn: (body) => fetchEndpoint(...[...tuple, body])
|
|
382
424
|
}, queryClient);
|
|
383
425
|
};
|
|
384
426
|
return {
|
|
385
427
|
key,
|
|
386
428
|
invalidate: invalidateExact,
|
|
387
429
|
setData,
|
|
388
|
-
useEndpoint
|
|
430
|
+
useEndpoint,
|
|
431
|
+
fetch: fetchEndpoint
|
|
389
432
|
};
|
|
390
433
|
}
|
|
391
434
|
return {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/routesV3.client.fetch.ts","../src/routesV3.client.index.ts"],"sourcesContent":["/**\n * This package exports React Query hooks, so we mark it as client-only to keep Next.js happy.\n */\n'use client';\n\nexport * from './routesV3.client.types';\nexport * from './routesV3.client.fetch';\nexport * from './routesV3.client.index';\n","// routesV3.client.fetch.ts\n\nimport { Fetcher, FetchInput } from './routesV3.client.types';\n\n/**\n * Default fetch implementation used by the route client helper.\n * @param req Normalized request information (URL, method, body, headers).\n * @returns Parsed JSON (or text fallback) from the server response.\n */\nexport const defaultFetcher: Fetcher = async <T>(req: FetchInput): Promise<T> => {\n const headers: Record<string, string> = { ...(req.headers ?? {}) };\n const isFormData = typeof FormData !== 'undefined' && req.body instanceof FormData;\n if (!isFormData) {\n headers['Content-Type'] ||= 'application/json';\n headers['Accept'] ||= 'application/json';\n }\n\n const res = await fetch(req.url, {\n method: req.method,\n headers,\n body: isFormData ? (req.body as any) : req.body == null ? undefined : JSON.stringify(req.body),\n });\n\n const text = await res.text();\n if (!res.ok) {\n const snippet = text.slice(0, 400);\n throw new Error(`[${res.status}] ${res.statusText} — ${snippet}`);\n }\n\n try {\n return JSON.parse(text) as T;\n } catch {\n return text as unknown as T;\n }\n};\n","// routesV3.client.ts\nimport {\n keepPreviousData,\n useInfiniteQuery,\n useMutation,\n useQuery,\n type InfiniteData,\n type QueryKey,\n} from '@tanstack/react-query';\nimport type { ZodType } from 'zod';\nimport {\n HttpMethod,\n buildCacheKey,\n compilePath,\n} from '@emeryld/rrroutes-contract';\nimport type {\n AnyLeaf,\n InferBody,\n InferOutput,\n InferParams,\n InferQuery,\n} from '@emeryld/rrroutes-contract';\nimport { defaultFetcher } from './routesV3.client.fetch';\nimport type {\n ArgsFor,\n ArgsTuple,\n BuiltForLeaf,\n BuiltInfinite,\n BuiltMutation,\n BuiltQuery,\n Cursor,\n DataShape,\n InfiniteBuildOptionsFor,\n MutationBuildOptionsFor,\n QueryBuildOptionsFor,\n RouteClient,\n RouteClientOptions,\n RouteClientDebugEvent,\n RouteClientDebugLogger,\n RouteClientDebugMode,\n RouteClientDebugOptions,\n Updater,\n} from './routesV3.client.types';\n\n// -------------------------------------------------------------------------------------\n// Tiny helpers\n// -------------------------------------------------------------------------------------\n/**\n * Convert an HTTP method to uppercase (as expected by fetch).\n * @param m Lowercase HTTP method.\n * @returns Uppercase method string.\n */\nconst toUpper = (m: HttpMethod): Uppercase<HttpMethod> => m.toUpperCase() as Uppercase<HttpMethod>;\n\n/**\n * Parse the given value with the supplied schema (if present).\n * @param value Raw value to validate.\n * @param schema Optional Zod schema used for validation/coercion.\n * @returns The validated or original value.\n */\nfunction zParse<T>(value: unknown, schema?: ZodType): T {\n return schema ? (schema.parse(value) as T) : (value as T);\n}\n\n/**\n * Serialize a query object into a search string.\n * @param query Query params object (possibly undefined).\n * @returns Query string prefixed with `?`, or empty string.\n */\nfunction toSearchString(query: Record<string, unknown> | undefined) {\n if (!query) return '';\n const params = new URLSearchParams();\n for (const [k, v] of Object.entries(query)) {\n if (v == null) continue;\n if (Array.isArray(v)) {\n v.forEach((x) => {\n if (x == null) return;\n if (typeof x === 'object') {\n params.append(k, JSON.stringify(x));\n } else {\n params.append(k, String(x));\n }\n });\n continue;\n }\n if (typeof v === 'object') {\n params.set(k, JSON.stringify(v));\n continue;\n }\n params.set(k, String(v));\n }\n const s = params.toString();\n return s ? `?${s}` : '';\n}\n\n/**\n * Remove the given key from an object (used to drop cursors from cache keys).\n * @param obj Source object.\n * @param key Property name to omit.\n * @returns Copy of the object without the specified key.\n */\nfunction stripKey<Q extends Record<string, unknown> | undefined>(obj: Q, key: string): Q {\n if (!obj) return obj;\n const { [key]: _omit, ...rest } = obj as any;\n return rest as Q;\n}\n\n/**\n * Default cursor extractor used by infinite queries.\n * @param p Page result returned from the server.\n * @returns Next cursor string, if present.\n */\nconst defaultGetNextCursor = (p: unknown): Cursor =>\n p && typeof p === 'object' && 'nextCursor' in p ? (p as any).nextCursor : undefined;\n\n// Debug logging --------------------------------------------------------------\nconst noopDebugLogger: RouteClientDebugLogger = () => {};\n\nconst defaultDebugLogger: RouteClientDebugLogger = (event: RouteClientDebugEvent) => {\n if (typeof console === 'undefined') return;\n const fn = console.debug ?? console.log;\n fn?.call(console, '[rrroutes-client]', event);\n};\n\ntype DebugEmitter = {\n emit: RouteClientDebugLogger;\n mode: RouteClientDebugMode;\n};\n\nfunction createDebugEmitter(option?: RouteClientDebugOptions): DebugEmitter {\n const disabled: DebugEmitter = { emit: noopDebugLogger, mode: 'minimal' };\n\n if (!option) {\n return disabled;\n }\n if (option === true || option === 'minimal') {\n return { emit: defaultDebugLogger, mode: 'minimal' };\n }\n if (option === 'complete') {\n return { emit: defaultDebugLogger, mode: 'complete' };\n }\n if (typeof option === 'function') {\n return { emit: option, mode: 'minimal' };\n }\n if (option.enabled === false) {\n return { emit: noopDebugLogger, mode: option.mode ?? 'minimal' };\n }\n return {\n emit: option.logger ?? defaultDebugLogger,\n mode: option.mode ?? 'minimal',\n };\n}\n\n// Split the variadic tuple at runtime\n/**\n * Extract the optional argument object from a variadic tuple.\n * @param args Tuple passed to a built endpoint helper.\n * @returns The argument object if present.\n */\nfunction extractArgs<L extends AnyLeaf>(args: ArgsTuple<L>): ArgsFor<L> | undefined {\n // At runtime ArgsTuple<L> is either [] or [obj]; we just pick the first if present.\n return (args as unknown as any[])[0] as any;\n}\n\n/**\n * Normalize params and query values, then construct a request URL for the given leaf.\n * @param leaf Leaf describing the endpoint.\n * @param baseUrl Optional base URL prepended to the path.\n * @param params Route parameters supplied by the caller.\n * @param query Query parameters supplied by the caller.\n * @returns Object containing the composed URL plus normalized params/query payloads.\n */\nfunction buildUrl<L extends AnyLeaf>(\n leaf: L,\n baseUrl: string,\n params: InferParams<L> | undefined,\n query: InferQuery<L> | undefined,\n) {\n const normalizedParams = zParse<InferParams<L>>(params, leaf.cfg.paramsSchema);\n const normalizedQuery = zParse<InferQuery<L>>(query, leaf.cfg.querySchema);\n const path = compilePath<L['path']>(leaf.path, (normalizedParams ?? {}) as any);\n const url = `${baseUrl ?? ''}${path}${toSearchString(normalizedQuery as any)}`;\n return { url, normalizedQuery, normalizedParams };\n}\n\n// -------------------------------------------------------------------------------------\n// Client factory\n// -------------------------------------------------------------------------------------\n/**\n * Construct typed React Query helpers backed by a routes-v3 registry leaf.\n * @param opts Route client configuration (query client, fetcher overrides, etc).\n * @returns Object that can build endpoint hooks/mutations from leaves.\n */\nexport function createRouteClient(opts: RouteClientOptions): RouteClient {\n const queryClient = opts.queryClient;\n const fetcher = opts.fetcher ?? defaultFetcher;\n const baseUrl = opts.baseUrl;\n const cursorParam = opts.cursorParam ?? 'cursor';\n const getNextCursor = opts.getNextCursor ?? defaultGetNextCursor;\n const { emit: emitDebug, mode: debugMode } = createDebugEmitter(opts.debug);\n const isVerboseDebug = debugMode === 'complete';\n const decorateDebugEvent = <T extends RouteClientDebugEvent>(\n event: T,\n details?: Partial<RouteClientDebugEvent>,\n ): RouteClientDebugEvent => {\n if (!isVerboseDebug || !details) return event;\n return { ...event, ...details } as RouteClientDebugEvent;\n };\n\n /**\n * Invalidate a set of queries sharing the given prefix.\n * @param prefix Key parts shared by matching endpoints.\n * @param exact When true, invalidate only exact key matches.\n */\n async function invalidate(prefix: string[], exact = false) {\n const queryKey = prefix as unknown as QueryKey;\n await queryClient.invalidateQueries({ queryKey, exact });\n emitDebug({ type: 'invalidate', key: queryKey, exact });\n }\n\n /**\n * Build the client surface for a single leaf (query/mutation/infinite query).\n * @param leaf Leaf describing the endpoint.\n * @param rqOpts Optional React Query configuration.\n * @returns Helper object exposing key/invalidate/setData/useEndpoint.\n */\n function buildInternal<L extends AnyLeaf>(\n leaf: L,\n rqOpts?: QueryBuildOptionsFor<L> | InfiniteBuildOptionsFor<L> | MutationBuildOptionsFor<L>,\n ): BuiltForLeaf<L> {\n const isGet = leaf.method === 'get';\n const isFeed = !!leaf.cfg.feed;\n const method = toUpper(leaf.method);\n const leafLabel = `${leaf.method.toUpperCase()} ${String(leaf.path)}`;\n emitDebug({ type: 'build', leaf: leafLabel });\n\n // --- key/invalidate/setData shared helpers ---\n const key = (...tuple: ArgsTuple<L>): QueryKey => {\n const a = extractArgs<L>(tuple);\n const params = (a as any)?.params as InferParams<L> | undefined;\n const query = (a as any)?.query as InferQuery<L> | undefined;\n const qForKey = isGet && isFeed ? stripKey(query as any, cursorParam) : (query as any);\n return buildCacheKey({ leaf, params: params as any, query: qForKey }) as unknown as QueryKey;\n };\n\n /**\n * Invalidate the React Query cache for this exact leaf invocation.\n * @param tuple Optional params/query tuple.\n */\n const invalidateExact = async (...tuple: ArgsTuple<L>) => {\n const queryKey = key(...tuple);\n await queryClient.invalidateQueries({ queryKey, exact: true });\n emitDebug({ type: 'invalidate', key: queryKey, exact: true });\n };\n\n /**\n * Update the cache entries for this leaf.\n * @param args Tuple whose first entry is the updater and optional params/query follow.\n */\n const setData = (...args: [Updater<DataShape<L>>, ...rest: ArgsTuple<L>]) => {\n const [updater, ...rest] = args;\n const k = key(...(rest as ArgsTuple<L>));\n if (isGet && isFeed) {\n queryClient.setQueryData<InfiniteData<InferOutput<L>> | undefined>(k, (prev) =>\n typeof updater === 'function' ? (updater as any)(prev) : (updater as any),\n );\n } else {\n queryClient.setQueryData<InferOutput<L> | undefined>(k, (prev) =>\n typeof updater === 'function' ? (updater as any)(prev) : (updater as any),\n );\n }\n emitDebug({ type: 'setData', key: k });\n };\n\n // --- Infinite GET ---\n if (isGet && isFeed) {\n const useEndpoint: BuiltInfinite<L>['useEndpoint'] = (...tuple) => {\n emitDebug({ type: 'useInfiniteGet', leaf: leafLabel });\n const a = extractArgs<L>(tuple);\n const params = (a as any)?.params as InferParams<L> | undefined;\n const query = (a as any)?.query as InferQuery<L> | undefined;\n\n // Normalize once; we’ll inject the cursor per page below.\n const { normalizedQuery, normalizedParams } = buildUrl(leaf, baseUrl, params, query);\n return useInfiniteQuery<\n InferOutput<L>, // TQueryFnData (per page)\n unknown, // TError\n InfiniteData<InferOutput<L>>, // TData (returned by the hook)\n QueryKey,\n Cursor\n >({\n ...(rqOpts as InfiniteBuildOptionsFor<L>),\n queryKey: key(...tuple),\n initialPageParam: undefined,\n getNextPageParam: (lastPage) => getNextCursor(lastPage),\n placeholderData: keepPreviousData,\n queryFn: async ({ pageParam }) => {\n const pageQuery = {\n ...(normalizedQuery as any),\n ...(pageParam ? { [cursorParam]: pageParam } : {}),\n };\n const { url } = buildUrl(leaf, baseUrl, params, pageQuery);\n const startedAt = Date.now();\n const detail = isVerboseDebug ? { params: normalizedParams, query: pageQuery } : undefined;\n emitDebug(\n decorateDebugEvent(\n { type: 'fetch', stage: 'start', method, url, leaf: leafLabel },\n detail,\n ),\n );\n try {\n const out = await fetcher<unknown>({ url, method });\n const parsed = zParse<InferOutput<L>>(out, leaf.cfg.outputSchema);\n emitDebug(\n decorateDebugEvent(\n {\n type: 'fetch',\n stage: 'success',\n method,\n url,\n leaf: leafLabel,\n durationMs: Date.now() - startedAt,\n },\n isVerboseDebug ? { params: normalizedParams, query: pageQuery, output: parsed } : undefined,\n ),\n );\n return parsed;\n } catch (error) {\n emitDebug(\n decorateDebugEvent(\n {\n type: 'fetch',\n stage: 'error',\n method,\n url,\n leaf: leafLabel,\n durationMs: Date.now() - startedAt,\n error,\n },\n detail,\n ),\n );\n throw error;\n }\n },\n // NOTE: TData is InfiniteData<T>, so we don't need a select here.\n }, queryClient);\n };\n\n return {\n key,\n invalidate: invalidateExact,\n setData: setData as any,\n useEndpoint,\n } as BuiltForLeaf<L>;\n }\n // --- Plain GET ---\n if (isGet) {\n const useEndpoint: BuiltQuery<L>['useEndpoint'] = (...tuple) => {\n emitDebug({ type: 'useGet', leaf: leafLabel });\n const a = extractArgs<L>(tuple);\n const params = (a as any)?.params as InferParams<L> | undefined;\n const query = (a as any)?.query as InferQuery<L> | undefined;\n\n const { url, normalizedQuery, normalizedParams } = buildUrl(leaf, baseUrl, params, query);\n return useQuery<InferOutput<L>, unknown, InferOutput<L>, QueryKey>({\n ...(rqOpts as QueryBuildOptionsFor<L>),\n queryKey: key(...tuple),\n placeholderData: keepPreviousData,\n queryFn: async () => {\n const startedAt = Date.now();\n const detail = isVerboseDebug\n ? { params: normalizedParams, query: normalizedQuery }\n : undefined;\n emitDebug(\n decorateDebugEvent(\n { type: 'fetch', stage: 'start', method, url, leaf: leafLabel },\n detail,\n ),\n );\n try {\n const out = await fetcher<unknown>({ url, method });\n const parsed = zParse<InferOutput<L>>(out, leaf.cfg.outputSchema);\n emitDebug(\n decorateDebugEvent(\n {\n type: 'fetch',\n stage: 'success',\n method,\n url,\n leaf: leafLabel,\n durationMs: Date.now() - startedAt,\n },\n isVerboseDebug\n ? { params: normalizedParams, query: normalizedQuery, output: parsed }\n : undefined,\n ),\n );\n return parsed;\n } catch (error) {\n emitDebug(\n decorateDebugEvent(\n {\n type: 'fetch',\n stage: 'error',\n method,\n url,\n leaf: leafLabel,\n durationMs: Date.now() - startedAt,\n error,\n },\n detail,\n ),\n );\n throw error;\n }\n },\n }, queryClient);\n };\n\n return {\n key,\n invalidate: invalidateExact,\n setData: setData as any,\n useEndpoint,\n } as BuiltForLeaf<L>;\n }\n\n // --- Mutation (POST/PUT/PATCH/DELETE) ---\n const useEndpoint: BuiltMutation<L>['useEndpoint'] = (...tuple) => {\n emitDebug({ type: 'useMutation', leaf: leafLabel });\n const a = extractArgs<L>(tuple);\n const params = (a as any)?.params as InferParams<L> | undefined;\n const query = (a as any)?.query as InferQuery<L> | undefined;\n\n const { url, normalizedQuery, normalizedParams } = buildUrl(leaf, baseUrl, params, query);\n return useMutation<InferOutput<L>, unknown, InferBody<L>, unknown>({\n ...(rqOpts as MutationBuildOptionsFor<L>),\n mutationKey: key(...tuple),\n mutationFn: async (body: InferBody<L>) => {\n const normalizedBody = zParse<InferBody<L>>(body, leaf.cfg.bodySchema);\n\n // Optional: switch to FormData if your method declares bodyFiles\n const isMultipart = Array.isArray(leaf.cfg.bodyFiles) && leaf.cfg.bodyFiles.length > 0;\n const payload = isMultipart ? toFormData(normalizedBody as any) : normalizedBody;\n\n const startedAt = Date.now();\n const detail = isVerboseDebug\n ? { params: normalizedParams, query: normalizedQuery }\n : undefined;\n emitDebug(\n decorateDebugEvent(\n {\n type: 'fetch',\n stage: 'start',\n method,\n url,\n leaf: leafLabel,\n body: payload,\n },\n detail,\n ),\n );\n try {\n const out = await fetcher<unknown>({ url, method, body: payload });\n const parsed = zParse<InferOutput<L>>(out, leaf.cfg.outputSchema);\n emitDebug(\n decorateDebugEvent(\n {\n type: 'fetch',\n stage: 'success',\n method,\n url,\n leaf: leafLabel,\n durationMs: Date.now() - startedAt,\n },\n isVerboseDebug\n ? { params: normalizedParams, query: normalizedQuery, output: parsed }\n : undefined,\n ),\n );\n return parsed;\n } catch (error) {\n emitDebug(\n decorateDebugEvent(\n {\n type: 'fetch',\n stage: 'error',\n method,\n url,\n leaf: leafLabel,\n durationMs: Date.now() - startedAt,\n body: payload,\n error,\n },\n detail,\n ),\n );\n throw error;\n }\n },\n }, queryClient);\n };\n\n return {\n key,\n invalidate: invalidateExact,\n setData: setData as any,\n useEndpoint,\n } as BuiltForLeaf<L>;\n }\n\n return {\n queryClient,\n invalidate,\n build: buildInternal as RouteClient['build'],\n };\n}\n\n// -------------------------------------------------------------------------------------\n// Multipart helper\n// -------------------------------------------------------------------------------------\nfunction toFormData(body: Record<string, any>): FormData {\n const fd = new FormData();\n for (const [k, v] of Object.entries(body ?? {})) {\n if (v == null) continue;\n if (Array.isArray(v)) v.forEach((item, i) => fd.append(`${k}[${i}]`, item as any));\n else fd.append(k, v as any);\n }\n return fd;\n}\n"],"mappings":"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","names":["useEndpoint"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/routesV3.client.fetch.ts","../src/routesV3.client.index.ts"],"sourcesContent":["/**\n * This package exports React Query hooks, so we mark it as client-only to keep Next.js happy.\n */\n'use client';\n\nexport * from './routesV3.client.types';\nexport * from './routesV3.client.fetch';\nexport * from './routesV3.client.index';\n","// routesV3.client.fetch.ts\n\nimport { Fetcher, FetchInput } from './routesV3.client.types';\n\n/**\n * Default fetch implementation used by the route client helper.\n * @param req Normalized request information (URL, method, body, headers).\n * @returns Parsed JSON (or text fallback) from the server response.\n */\nexport const defaultFetcher: Fetcher = async <T>(req: FetchInput): Promise<T> => {\n const headers: Record<string, string> = { ...(req.headers ?? {}) };\n const isFormData = typeof FormData !== 'undefined' && req.body instanceof FormData;\n if (!isFormData) {\n headers['Content-Type'] ||= 'application/json';\n headers['Accept'] ||= 'application/json';\n }\n\n const res = await fetch(req.url, {\n method: req.method,\n headers,\n body: isFormData ? (req.body as any) : req.body == null ? undefined : JSON.stringify(req.body),\n });\n\n const text = await res.text();\n if (!res.ok) {\n const snippet = text.slice(0, 400);\n throw new Error(`[${res.status}] ${res.statusText} — ${snippet}`);\n }\n\n try {\n return JSON.parse(text) as T;\n } catch {\n return text as unknown as T;\n }\n};\n","// routesV3.client.ts\nimport {\n keepPreviousData,\n useInfiniteQuery,\n useMutation,\n useQuery,\n type InfiniteData,\n type QueryKey,\n} from '@tanstack/react-query';\nimport type { ZodType } from 'zod';\nimport {\n HttpMethod,\n buildCacheKey,\n compilePath,\n} from '@emeryld/rrroutes-contract';\nimport type {\n AnyLeaf,\n InferBody,\n InferOutput,\n InferParams,\n InferQuery,\n} from '@emeryld/rrroutes-contract';\nimport { defaultFetcher } from './routesV3.client.fetch';\nimport type {\n ArgsFor,\n ArgsTuple,\n BuildMeta,\n BuiltForLeaf,\n BuiltInfinite,\n BuiltMutation,\n BuiltQuery,\n Cursor,\n DataShape,\n InfiniteBuildOptionsFor,\n MutationBuildOptionsFor,\n QueryBuildOptionsFor,\n RouteClient,\n RouteClientOptions,\n RouteClientDebugEvent,\n RouteClientDebugLogger,\n RouteClientDebugMode,\n RouteClientDebugOptions,\n RouteClientDebugToggleOptions,\n Updater,\n} from './routesV3.client.types';\n\n// -------------------------------------------------------------------------------------\n// Tiny helpers\n// -------------------------------------------------------------------------------------\n/**\n * Convert an HTTP method to uppercase (as expected by fetch).\n * @param m Lowercase HTTP method.\n * @returns Uppercase method string.\n */\nconst toUpper = (m: HttpMethod): Uppercase<HttpMethod> => m.toUpperCase() as Uppercase<HttpMethod>;\n\n/**\n * Parse the given value with the supplied schema (if present).\n * @param value Raw value to validate.\n * @param schema Optional Zod schema used for validation/coercion.\n * @returns The validated or original value.\n */\nfunction zParse<T>(value: unknown, schema?: ZodType): T {\n return schema ? (schema.parse(value) as T) : (value as T);\n}\n\n/**\n * Serialize a query object into a search string.\n * @param query Query params object (possibly undefined).\n * @returns Query string prefixed with `?`, or empty string.\n */\nfunction toSearchString(query: Record<string, unknown> | undefined) {\n if (!query) return '';\n const params = new URLSearchParams();\n for (const [k, v] of Object.entries(query)) {\n if (v == null) continue;\n if (Array.isArray(v)) {\n v.forEach((x) => {\n if (x == null) return;\n if (typeof x === 'object') {\n params.append(k, JSON.stringify(x));\n } else {\n params.append(k, String(x));\n }\n });\n continue;\n }\n if (typeof v === 'object') {\n params.set(k, JSON.stringify(v));\n continue;\n }\n params.set(k, String(v));\n }\n const s = params.toString();\n return s ? `?${s}` : '';\n}\n\n/**\n * Remove the given key from an object (used to drop cursors from cache keys).\n * @param obj Source object.\n * @param key Property name to omit.\n * @returns Copy of the object without the specified key.\n */\nfunction stripKey<Q extends Record<string, unknown> | undefined>(obj: Q, key: string): Q {\n if (!obj) return obj;\n const { [key]: _omit, ...rest } = obj as any;\n return rest as Q;\n}\n\n/**\n * Default cursor extractor used by infinite queries.\n * @param p Page result returned from the server.\n * @returns Next cursor string, if present.\n */\nconst defaultGetNextCursor = (p: unknown): Cursor =>\n p && typeof p === 'object' && 'nextCursor' in p ? (p as any).nextCursor : undefined;\n\n// Debug logging --------------------------------------------------------------\nconst noopDebugLogger: RouteClientDebugLogger = () => {};\n\nconst defaultDebugLogger: RouteClientDebugLogger = (event: RouteClientDebugEvent) => {\n if (typeof console === 'undefined') return;\n const fn = console.debug ?? console.log;\n fn?.call(console, '[rrroutes-client]', event);\n};\n\nconst debugEventTypes: RouteClientDebugEvent['type'][] = [\n 'fetch',\n 'invalidate',\n 'setData',\n 'build',\n 'useEndpoint',\n];\n\ntype DebugEmitter<Names extends string> = {\n emit: (event: RouteClientDebugEvent, name?: Names) => void;\n mode: RouteClientDebugMode;\n};\n\nconst noopEmit = () => {};\n\nfunction createDebugEmitter<Names extends string>(\n option?: RouteClientDebugOptions<Names>,\n environment?: string,\n): DebugEmitter<Names> {\n const disabled: DebugEmitter<Names> = { emit: noopEmit, mode: 'minimal' };\n\n if (environment && environment.toLowerCase() === 'production') {\n return disabled;\n }\n\n if (!option) {\n return disabled;\n }\n if (option === true || option === 'minimal') {\n return {\n emit: (event, name) => defaultDebugLogger(name ? { ...event, name } : event),\n mode: 'minimal',\n };\n }\n if (option === 'complete') {\n return {\n emit: (event, name) => defaultDebugLogger(name ? { ...event, name } : event),\n mode: 'complete',\n };\n }\n if (typeof option === 'function') {\n return {\n emit: (event, name) => option(name ? { ...event, name } : event),\n mode: 'minimal',\n };\n }\n if (typeof option === 'object') {\n const toggles = option as RouteClientDebugToggleOptions<Names>;\n const verbose = Boolean(toggles.verbose);\n const enabledTypes = debugEventTypes.filter((type) => toggles[type]);\n if (enabledTypes.length === 0) {\n return { emit: noopEmit, mode: verbose ? 'complete' : 'minimal' };\n }\n const whitelist = new Set<RouteClientDebugEvent['type']>(enabledTypes);\n const onlySet =\n toggles.only && toggles.only.length > 0 ? new Set<Names>(toggles.only) : undefined;\n const logger = toggles.logger ?? defaultDebugLogger;\n const emit: DebugEmitter<Names>['emit'] = (event, name) => {\n if (!whitelist.has(event.type)) return;\n if (onlySet) {\n if (!name || !onlySet.has(name)) return;\n }\n logger(name ? { ...event, name } : event);\n };\n return { emit, mode: verbose ? 'complete' : 'minimal' };\n }\n\n return disabled;\n}\n\n// Split the variadic tuple at runtime\n/**\n * Extract the optional argument object from a variadic tuple.\n * @param args Tuple passed to a built endpoint helper.\n * @returns The argument object if present.\n */\nfunction extractArgs<L extends AnyLeaf>(args: ArgsTuple<L>): ArgsFor<L> | undefined {\n // At runtime ArgsTuple<L> is either [] or [obj]; we just pick the first if present.\n return (args as unknown as any[])[0] as any;\n}\n\n/**\n * Normalize params and query values, then construct a request URL for the given leaf.\n * @param leaf Leaf describing the endpoint.\n * @param baseUrl Optional base URL prepended to the path.\n * @param params Route parameters supplied by the caller.\n * @param query Query parameters supplied by the caller.\n * @returns Object containing the composed URL plus normalized params/query payloads.\n */\nfunction buildUrl<L extends AnyLeaf>(\n leaf: L,\n baseUrl: string,\n params: InferParams<L> | undefined,\n query: InferQuery<L> | undefined,\n) {\n const normalizedParams = zParse<InferParams<L>>(params, leaf.cfg.paramsSchema);\n const normalizedQuery = zParse<InferQuery<L>>(query, leaf.cfg.querySchema);\n const path = compilePath<L['path']>(leaf.path, (normalizedParams ?? {}) as any);\n const url = `${baseUrl ?? ''}${path}${toSearchString(normalizedQuery as any)}`;\n return { url, normalizedQuery, normalizedParams };\n}\n\n// -------------------------------------------------------------------------------------\n// Client factory\n// -------------------------------------------------------------------------------------\n/**\n * Construct typed React Query helpers backed by a routes-v3 registry leaf.\n * @param opts Route client configuration (query client, fetcher overrides, etc).\n * @returns Object that can build endpoint hooks/mutations from leaves.\n */\nexport function createRouteClient<Names extends string = string>(\n opts: RouteClientOptions<Names>,\n): RouteClient<Names> {\n const queryClient = opts.queryClient;\n const fetcher = opts.fetcher ?? defaultFetcher;\n const baseUrl = opts.baseUrl;\n const cursorParam = opts.cursorParam ?? 'cursor';\n const getNextCursor = opts.getNextCursor ?? defaultGetNextCursor;\n const environment =\n opts.environment ??\n (typeof process !== 'undefined' && process.env ? process.env.NODE_ENV : undefined);\n const { emit: emitDebug, mode: debugMode } = createDebugEmitter<Names>(opts.debug, environment);\n const isVerboseDebug = debugMode === 'complete';\n const decorateDebugEvent = <T extends RouteClientDebugEvent>(\n event: T,\n details?: Partial<RouteClientDebugEvent>,\n ): RouteClientDebugEvent => {\n if (!isVerboseDebug || !details) return event;\n return { ...event, ...details } as RouteClientDebugEvent;\n };\n\n /**\n * Invalidate a set of queries sharing the given prefix.\n * @param prefix Key parts shared by matching endpoints.\n * @param exact When true, invalidate only exact key matches.\n */\n async function invalidate(prefix: string[], exact = false) {\n const queryKey = prefix as unknown as QueryKey;\n await queryClient.invalidateQueries({ queryKey, exact });\n emitDebug({ type: 'invalidate', key: queryKey, exact });\n }\n\n /**\n * Build the client surface for a single leaf (query/mutation/infinite query).\n * @param leaf Leaf describing the endpoint.\n * @param rqOpts Optional React Query configuration.\n * @returns Helper object exposing key/invalidate/setData/useEndpoint.\n */\n function buildInternal<L extends AnyLeaf>(\n leaf: L,\n rqOpts?: QueryBuildOptionsFor<L> | InfiniteBuildOptionsFor<L> | MutationBuildOptionsFor<L>,\n meta?: BuildMeta<Names>,\n ): BuiltForLeaf<L> {\n const isGet = leaf.method === 'get';\n const isFeed = !!leaf.cfg.feed;\n const method = toUpper(leaf.method);\n const leafLabel = `${leaf.method.toUpperCase()} ${String(leaf.path)}`;\n const debugName = meta?.name;\n const emit = (event: RouteClientDebugEvent) => emitDebug(event, debugName);\n emit({ type: 'build', leaf: leafLabel });\n\n // --- key/invalidate/setData shared helpers ---\n const key = (...tuple: ArgsTuple<L>): QueryKey => {\n const a = extractArgs<L>(tuple);\n const params = (a as any)?.params as InferParams<L> | undefined;\n const query = (a as any)?.query as InferQuery<L> | undefined;\n const qForKey = isGet && isFeed ? stripKey(query as any, cursorParam) : (query as any);\n return buildCacheKey({ leaf, params: params as any, query: qForKey }) as unknown as QueryKey;\n };\n\n /**\n * Invalidate the React Query cache for this exact leaf invocation.\n * @param tuple Optional params/query tuple.\n */\n const invalidateExact = async (...tuple: ArgsTuple<L>) => {\n const queryKey = key(...tuple);\n await queryClient.invalidateQueries({ queryKey, exact: true });\n emit({ type: 'invalidate', key: queryKey, exact: true });\n };\n\n /**\n * Update the cache entries for this leaf.\n * @param args Tuple whose first entry is the updater and optional params/query follow.\n */\n const setData = (...args: [Updater<DataShape<L>>, ...rest: ArgsTuple<L>]) => {\n const [updater, ...rest] = args;\n const k = key(...(rest as ArgsTuple<L>));\n if (isGet && isFeed) {\n queryClient.setQueryData<InfiniteData<InferOutput<L>> | undefined>(k, (prev) =>\n typeof updater === 'function' ? (updater as any)(prev) : (updater as any),\n );\n } else {\n queryClient.setQueryData<InferOutput<L> | undefined>(k, (prev) =>\n typeof updater === 'function' ? (updater as any)(prev) : (updater as any),\n );\n }\n emit({ type: 'setData', key: k });\n };\n\n // --- Infinite GET ---\n if (isGet && isFeed) {\n const useEndpoint: BuiltInfinite<L>['useEndpoint'] = (...tuple) => {\n emit({ type: 'useEndpoint', leaf: leafLabel, variant: 'infiniteGet' });\n const a = extractArgs<L>(tuple);\n const params = (a as any)?.params as InferParams<L> | undefined;\n const query = (a as any)?.query as InferQuery<L> | undefined;\n\n // Normalize once; we’ll inject the cursor per page below.\n const { normalizedQuery, normalizedParams } = buildUrl(leaf, baseUrl, params, query);\n return useInfiniteQuery<\n InferOutput<L>, // TQueryFnData (per page)\n unknown, // TError\n InfiniteData<InferOutput<L>>, // TData (returned by the hook)\n QueryKey,\n Cursor\n >({\n ...(rqOpts as InfiniteBuildOptionsFor<L>),\n queryKey: key(...tuple),\n initialPageParam: undefined,\n getNextPageParam: (lastPage) => getNextCursor(lastPage),\n placeholderData: keepPreviousData,\n queryFn: async ({ pageParam }) => {\n const pageQuery = {\n ...(normalizedQuery as any),\n ...(pageParam ? { [cursorParam]: pageParam } : {}),\n };\n const { url } = buildUrl(leaf, baseUrl, params, pageQuery);\n const startedAt = Date.now();\n const detail = isVerboseDebug ? { params: normalizedParams, query: pageQuery } : undefined;\n emit(\n decorateDebugEvent(\n { type: 'fetch', stage: 'start', method, url, leaf: leafLabel },\n detail,\n ),\n );\n try {\n const out = await fetcher<unknown>({ url, method });\n const parsed = zParse<InferOutput<L>>(out, leaf.cfg.outputSchema);\n emit(\n decorateDebugEvent(\n {\n type: 'fetch',\n stage: 'success',\n method,\n url,\n leaf: leafLabel,\n durationMs: Date.now() - startedAt,\n },\n isVerboseDebug ? { params: normalizedParams, query: pageQuery, output: parsed } : undefined,\n ),\n );\n return parsed;\n } catch (error) {\n emit(\n decorateDebugEvent(\n {\n type: 'fetch',\n stage: 'error',\n method,\n url,\n leaf: leafLabel,\n durationMs: Date.now() - startedAt,\n error,\n },\n detail,\n ),\n );\n throw error;\n }\n },\n // NOTE: TData is InfiniteData<T>, so we don't need a select here.\n }, queryClient);\n };\n\n return {\n key,\n invalidate: invalidateExact,\n setData: setData as any,\n useEndpoint,\n } as BuiltForLeaf<L>;\n }\n // --- Plain GET ---\n if (isGet) {\n const useEndpoint: BuiltQuery<L>['useEndpoint'] = (...tuple) => {\n emit({ type: 'useEndpoint', leaf: leafLabel, variant: 'get' });\n const a = extractArgs<L>(tuple);\n const params = (a as any)?.params as InferParams<L> | undefined;\n const query = (a as any)?.query as InferQuery<L> | undefined;\n\n const { url, normalizedQuery, normalizedParams } = buildUrl(leaf, baseUrl, params, query);\n return useQuery<InferOutput<L>, unknown, InferOutput<L>, QueryKey>({\n ...(rqOpts as QueryBuildOptionsFor<L>),\n queryKey: key(...tuple),\n placeholderData: keepPreviousData,\n queryFn: async () => {\n const startedAt = Date.now();\n const detail = isVerboseDebug\n ? { params: normalizedParams, query: normalizedQuery }\n : undefined;\n emit(\n decorateDebugEvent(\n { type: 'fetch', stage: 'start', method, url, leaf: leafLabel },\n detail,\n ),\n );\n try {\n const out = await fetcher<unknown>({ url, method });\n const parsed = zParse<InferOutput<L>>(out, leaf.cfg.outputSchema);\n emit(\n decorateDebugEvent(\n {\n type: 'fetch',\n stage: 'success',\n method,\n url,\n leaf: leafLabel,\n durationMs: Date.now() - startedAt,\n },\n isVerboseDebug\n ? { params: normalizedParams, query: normalizedQuery, output: parsed }\n : undefined,\n ),\n );\n return parsed;\n } catch (error) {\n emit(\n decorateDebugEvent(\n {\n type: 'fetch',\n stage: 'error',\n method,\n url,\n leaf: leafLabel,\n durationMs: Date.now() - startedAt,\n error,\n },\n detail,\n ),\n );\n throw error;\n }\n },\n }, queryClient);\n };\n\n return {\n key,\n invalidate: invalidateExact,\n setData: setData as any,\n useEndpoint,\n } as BuiltForLeaf<L>;\n }\n\n // --- Mutation (POST/PUT/PATCH/DELETE) ---\n const fetchEndpoint: BuiltMutation<L>['fetch'] = async (\n ...tupleWithBody: [...ArgsTuple<L>, InferBody<L>]\n ) => {\n if (tupleWithBody.length === 0) {\n throw new Error('Body is required when invoking a mutation fetch.');\n }\n const bodyIndex = tupleWithBody.length - 1;\n const tuple = tupleWithBody.slice(0, bodyIndex) as ArgsTuple<L>;\n const body = tupleWithBody[bodyIndex] as InferBody<L>;\n const args = extractArgs<L>(tuple);\n const params = (args as any)?.params as InferParams<L> | undefined;\n const query = (args as any)?.query as InferQuery<L> | undefined;\n\n const { url, normalizedQuery, normalizedParams } = buildUrl(leaf, baseUrl, params, query);\n const normalizedBody = zParse<InferBody<L>>(body, leaf.cfg.bodySchema);\n\n // Optional: switch to FormData if your method declares bodyFiles\n const isMultipart = Array.isArray(leaf.cfg.bodyFiles) && leaf.cfg.bodyFiles.length > 0;\n const payload = isMultipart ? toFormData(normalizedBody as any) : normalizedBody;\n\n const startedAt = Date.now();\n const detail = isVerboseDebug\n ? { params: normalizedParams, query: normalizedQuery }\n : undefined;\n emit(\n decorateDebugEvent(\n {\n type: 'fetch',\n stage: 'start',\n method,\n url,\n leaf: leafLabel,\n body: payload,\n },\n detail,\n ),\n );\n try {\n const out = await fetcher<unknown>({ url, method, body: payload });\n const parsed = zParse<InferOutput<L>>(out, leaf.cfg.outputSchema);\n emit(\n decorateDebugEvent(\n {\n type: 'fetch',\n stage: 'success',\n method,\n url,\n leaf: leafLabel,\n durationMs: Date.now() - startedAt,\n },\n isVerboseDebug\n ? { params: normalizedParams, query: normalizedQuery, output: parsed }\n : undefined,\n ),\n );\n return parsed;\n } catch (error) {\n emit(\n decorateDebugEvent(\n {\n type: 'fetch',\n stage: 'error',\n method,\n url,\n leaf: leafLabel,\n durationMs: Date.now() - startedAt,\n body: payload,\n error,\n },\n detail,\n ),\n );\n throw error;\n }\n };\n\n const useEndpoint: BuiltMutation<L>['useEndpoint'] = (...tuple) => {\n emit({ type: 'useEndpoint', leaf: leafLabel, variant: 'mutation' });\n return useMutation<InferOutput<L>, unknown, InferBody<L>, unknown>({\n ...(rqOpts as MutationBuildOptionsFor<L>),\n mutationKey: key(...tuple),\n mutationFn: (body: InferBody<L>) =>\n fetchEndpoint(...([...tuple, body] as [...ArgsTuple<L>, InferBody<L>])),\n }, queryClient);\n };\n\n return {\n key,\n invalidate: invalidateExact,\n setData: setData as any,\n useEndpoint,\n fetch: fetchEndpoint,\n } as BuiltForLeaf<L>;\n }\n\n return {\n queryClient,\n invalidate,\n build: buildInternal as RouteClient<Names>['build'],\n };\n}\n\n// -------------------------------------------------------------------------------------\n// Multipart helper\n// -------------------------------------------------------------------------------------\nfunction toFormData(body: Record<string, any>): FormData {\n const fd = new FormData();\n for (const [k, v] of Object.entries(body ?? {})) {\n if (v == null) continue;\n if (Array.isArray(v)) v.forEach((item, i) => fd.append(`${k}[${i}]`, item as any));\n else fd.append(k, v as any);\n }\n return fd;\n}\n"],"mappings":"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","names":["useEndpoint"]}
|
package/dist/index.mjs
CHANGED
|
@@ -71,34 +71,66 @@ function stripKey(obj, key) {
|
|
|
71
71
|
return rest;
|
|
72
72
|
}
|
|
73
73
|
var defaultGetNextCursor = (p) => p && typeof p === "object" && "nextCursor" in p ? p.nextCursor : void 0;
|
|
74
|
-
var noopDebugLogger = () => {
|
|
75
|
-
};
|
|
76
74
|
var defaultDebugLogger = (event) => {
|
|
77
75
|
if (typeof console === "undefined") return;
|
|
78
76
|
const fn = console.debug ?? console.log;
|
|
79
77
|
fn?.call(console, "[rrroutes-client]", event);
|
|
80
78
|
};
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
var debugEventTypes = [
|
|
80
|
+
"fetch",
|
|
81
|
+
"invalidate",
|
|
82
|
+
"setData",
|
|
83
|
+
"build",
|
|
84
|
+
"useEndpoint"
|
|
85
|
+
];
|
|
86
|
+
var noopEmit = () => {
|
|
87
|
+
};
|
|
88
|
+
function createDebugEmitter(option, environment) {
|
|
89
|
+
const disabled = { emit: noopEmit, mode: "minimal" };
|
|
90
|
+
if (environment && environment.toLowerCase() === "production") {
|
|
91
|
+
return disabled;
|
|
92
|
+
}
|
|
83
93
|
if (!option) {
|
|
84
94
|
return disabled;
|
|
85
95
|
}
|
|
86
96
|
if (option === true || option === "minimal") {
|
|
87
|
-
return {
|
|
97
|
+
return {
|
|
98
|
+
emit: (event, name) => defaultDebugLogger(name ? { ...event, name } : event),
|
|
99
|
+
mode: "minimal"
|
|
100
|
+
};
|
|
88
101
|
}
|
|
89
102
|
if (option === "complete") {
|
|
90
|
-
return {
|
|
103
|
+
return {
|
|
104
|
+
emit: (event, name) => defaultDebugLogger(name ? { ...event, name } : event),
|
|
105
|
+
mode: "complete"
|
|
106
|
+
};
|
|
91
107
|
}
|
|
92
108
|
if (typeof option === "function") {
|
|
93
|
-
return {
|
|
109
|
+
return {
|
|
110
|
+
emit: (event, name) => option(name ? { ...event, name } : event),
|
|
111
|
+
mode: "minimal"
|
|
112
|
+
};
|
|
94
113
|
}
|
|
95
|
-
if (option
|
|
96
|
-
|
|
114
|
+
if (typeof option === "object") {
|
|
115
|
+
const toggles = option;
|
|
116
|
+
const verbose = Boolean(toggles.verbose);
|
|
117
|
+
const enabledTypes = debugEventTypes.filter((type) => toggles[type]);
|
|
118
|
+
if (enabledTypes.length === 0) {
|
|
119
|
+
return { emit: noopEmit, mode: verbose ? "complete" : "minimal" };
|
|
120
|
+
}
|
|
121
|
+
const whitelist = new Set(enabledTypes);
|
|
122
|
+
const onlySet = toggles.only && toggles.only.length > 0 ? new Set(toggles.only) : void 0;
|
|
123
|
+
const logger = toggles.logger ?? defaultDebugLogger;
|
|
124
|
+
const emit = (event, name) => {
|
|
125
|
+
if (!whitelist.has(event.type)) return;
|
|
126
|
+
if (onlySet) {
|
|
127
|
+
if (!name || !onlySet.has(name)) return;
|
|
128
|
+
}
|
|
129
|
+
logger(name ? { ...event, name } : event);
|
|
130
|
+
};
|
|
131
|
+
return { emit, mode: verbose ? "complete" : "minimal" };
|
|
97
132
|
}
|
|
98
|
-
return
|
|
99
|
-
emit: option.logger ?? defaultDebugLogger,
|
|
100
|
-
mode: option.mode ?? "minimal"
|
|
101
|
-
};
|
|
133
|
+
return disabled;
|
|
102
134
|
}
|
|
103
135
|
function extractArgs(args) {
|
|
104
136
|
return args[0];
|
|
@@ -116,7 +148,8 @@ function createRouteClient(opts) {
|
|
|
116
148
|
const baseUrl = opts.baseUrl;
|
|
117
149
|
const cursorParam = opts.cursorParam ?? "cursor";
|
|
118
150
|
const getNextCursor = opts.getNextCursor ?? defaultGetNextCursor;
|
|
119
|
-
const
|
|
151
|
+
const environment = opts.environment ?? (typeof process !== "undefined" && process.env ? process.env.NODE_ENV : void 0);
|
|
152
|
+
const { emit: emitDebug, mode: debugMode } = createDebugEmitter(opts.debug, environment);
|
|
120
153
|
const isVerboseDebug = debugMode === "complete";
|
|
121
154
|
const decorateDebugEvent = (event, details) => {
|
|
122
155
|
if (!isVerboseDebug || !details) return event;
|
|
@@ -127,12 +160,14 @@ function createRouteClient(opts) {
|
|
|
127
160
|
await queryClient.invalidateQueries({ queryKey, exact });
|
|
128
161
|
emitDebug({ type: "invalidate", key: queryKey, exact });
|
|
129
162
|
}
|
|
130
|
-
function buildInternal(leaf, rqOpts) {
|
|
163
|
+
function buildInternal(leaf, rqOpts, meta) {
|
|
131
164
|
const isGet = leaf.method === "get";
|
|
132
165
|
const isFeed = !!leaf.cfg.feed;
|
|
133
166
|
const method = toUpper(leaf.method);
|
|
134
167
|
const leafLabel = `${leaf.method.toUpperCase()} ${String(leaf.path)}`;
|
|
135
|
-
|
|
168
|
+
const debugName = meta?.name;
|
|
169
|
+
const emit = (event) => emitDebug(event, debugName);
|
|
170
|
+
emit({ type: "build", leaf: leafLabel });
|
|
136
171
|
const key = (...tuple) => {
|
|
137
172
|
const a = extractArgs(tuple);
|
|
138
173
|
const params = a?.params;
|
|
@@ -143,7 +178,7 @@ function createRouteClient(opts) {
|
|
|
143
178
|
const invalidateExact = async (...tuple) => {
|
|
144
179
|
const queryKey = key(...tuple);
|
|
145
180
|
await queryClient.invalidateQueries({ queryKey, exact: true });
|
|
146
|
-
|
|
181
|
+
emit({ type: "invalidate", key: queryKey, exact: true });
|
|
147
182
|
};
|
|
148
183
|
const setData = (...args) => {
|
|
149
184
|
const [updater, ...rest] = args;
|
|
@@ -159,11 +194,11 @@ function createRouteClient(opts) {
|
|
|
159
194
|
(prev) => typeof updater === "function" ? updater(prev) : updater
|
|
160
195
|
);
|
|
161
196
|
}
|
|
162
|
-
|
|
197
|
+
emit({ type: "setData", key: k });
|
|
163
198
|
};
|
|
164
199
|
if (isGet && isFeed) {
|
|
165
200
|
const useEndpoint2 = (...tuple) => {
|
|
166
|
-
|
|
201
|
+
emit({ type: "useEndpoint", leaf: leafLabel, variant: "infiniteGet" });
|
|
167
202
|
const a = extractArgs(tuple);
|
|
168
203
|
const params = a?.params;
|
|
169
204
|
const query = a?.query;
|
|
@@ -182,7 +217,7 @@ function createRouteClient(opts) {
|
|
|
182
217
|
const { url } = buildUrl(leaf, baseUrl, params, pageQuery);
|
|
183
218
|
const startedAt = Date.now();
|
|
184
219
|
const detail = isVerboseDebug ? { params: normalizedParams, query: pageQuery } : void 0;
|
|
185
|
-
|
|
220
|
+
emit(
|
|
186
221
|
decorateDebugEvent(
|
|
187
222
|
{ type: "fetch", stage: "start", method, url, leaf: leafLabel },
|
|
188
223
|
detail
|
|
@@ -191,7 +226,7 @@ function createRouteClient(opts) {
|
|
|
191
226
|
try {
|
|
192
227
|
const out = await fetcher({ url, method });
|
|
193
228
|
const parsed = zParse(out, leaf.cfg.outputSchema);
|
|
194
|
-
|
|
229
|
+
emit(
|
|
195
230
|
decorateDebugEvent(
|
|
196
231
|
{
|
|
197
232
|
type: "fetch",
|
|
@@ -206,7 +241,7 @@ function createRouteClient(opts) {
|
|
|
206
241
|
);
|
|
207
242
|
return parsed;
|
|
208
243
|
} catch (error) {
|
|
209
|
-
|
|
244
|
+
emit(
|
|
210
245
|
decorateDebugEvent(
|
|
211
246
|
{
|
|
212
247
|
type: "fetch",
|
|
@@ -235,7 +270,7 @@ function createRouteClient(opts) {
|
|
|
235
270
|
}
|
|
236
271
|
if (isGet) {
|
|
237
272
|
const useEndpoint2 = (...tuple) => {
|
|
238
|
-
|
|
273
|
+
emit({ type: "useEndpoint", leaf: leafLabel, variant: "get" });
|
|
239
274
|
const a = extractArgs(tuple);
|
|
240
275
|
const params = a?.params;
|
|
241
276
|
const query = a?.query;
|
|
@@ -247,7 +282,7 @@ function createRouteClient(opts) {
|
|
|
247
282
|
queryFn: async () => {
|
|
248
283
|
const startedAt = Date.now();
|
|
249
284
|
const detail = isVerboseDebug ? { params: normalizedParams, query: normalizedQuery } : void 0;
|
|
250
|
-
|
|
285
|
+
emit(
|
|
251
286
|
decorateDebugEvent(
|
|
252
287
|
{ type: "fetch", stage: "start", method, url, leaf: leafLabel },
|
|
253
288
|
detail
|
|
@@ -256,7 +291,7 @@ function createRouteClient(opts) {
|
|
|
256
291
|
try {
|
|
257
292
|
const out = await fetcher({ url, method });
|
|
258
293
|
const parsed = zParse(out, leaf.cfg.outputSchema);
|
|
259
|
-
|
|
294
|
+
emit(
|
|
260
295
|
decorateDebugEvent(
|
|
261
296
|
{
|
|
262
297
|
type: "fetch",
|
|
@@ -271,7 +306,7 @@ function createRouteClient(opts) {
|
|
|
271
306
|
);
|
|
272
307
|
return parsed;
|
|
273
308
|
} catch (error) {
|
|
274
|
-
|
|
309
|
+
emit(
|
|
275
310
|
decorateDebugEvent(
|
|
276
311
|
{
|
|
277
312
|
type: "fetch",
|
|
@@ -297,77 +332,85 @@ function createRouteClient(opts) {
|
|
|
297
332
|
useEndpoint: useEndpoint2
|
|
298
333
|
};
|
|
299
334
|
}
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
const
|
|
335
|
+
const fetchEndpoint = async (...tupleWithBody) => {
|
|
336
|
+
if (tupleWithBody.length === 0) {
|
|
337
|
+
throw new Error("Body is required when invoking a mutation fetch.");
|
|
338
|
+
}
|
|
339
|
+
const bodyIndex = tupleWithBody.length - 1;
|
|
340
|
+
const tuple = tupleWithBody.slice(0, bodyIndex);
|
|
341
|
+
const body = tupleWithBody[bodyIndex];
|
|
342
|
+
const args = extractArgs(tuple);
|
|
343
|
+
const params = args?.params;
|
|
344
|
+
const query = args?.query;
|
|
305
345
|
const { url, normalizedQuery, normalizedParams } = buildUrl(leaf, baseUrl, params, query);
|
|
346
|
+
const normalizedBody = zParse(body, leaf.cfg.bodySchema);
|
|
347
|
+
const isMultipart = Array.isArray(leaf.cfg.bodyFiles) && leaf.cfg.bodyFiles.length > 0;
|
|
348
|
+
const payload = isMultipart ? toFormData(normalizedBody) : normalizedBody;
|
|
349
|
+
const startedAt = Date.now();
|
|
350
|
+
const detail = isVerboseDebug ? { params: normalizedParams, query: normalizedQuery } : void 0;
|
|
351
|
+
emit(
|
|
352
|
+
decorateDebugEvent(
|
|
353
|
+
{
|
|
354
|
+
type: "fetch",
|
|
355
|
+
stage: "start",
|
|
356
|
+
method,
|
|
357
|
+
url,
|
|
358
|
+
leaf: leafLabel,
|
|
359
|
+
body: payload
|
|
360
|
+
},
|
|
361
|
+
detail
|
|
362
|
+
)
|
|
363
|
+
);
|
|
364
|
+
try {
|
|
365
|
+
const out = await fetcher({ url, method, body: payload });
|
|
366
|
+
const parsed = zParse(out, leaf.cfg.outputSchema);
|
|
367
|
+
emit(
|
|
368
|
+
decorateDebugEvent(
|
|
369
|
+
{
|
|
370
|
+
type: "fetch",
|
|
371
|
+
stage: "success",
|
|
372
|
+
method,
|
|
373
|
+
url,
|
|
374
|
+
leaf: leafLabel,
|
|
375
|
+
durationMs: Date.now() - startedAt
|
|
376
|
+
},
|
|
377
|
+
isVerboseDebug ? { params: normalizedParams, query: normalizedQuery, output: parsed } : void 0
|
|
378
|
+
)
|
|
379
|
+
);
|
|
380
|
+
return parsed;
|
|
381
|
+
} catch (error) {
|
|
382
|
+
emit(
|
|
383
|
+
decorateDebugEvent(
|
|
384
|
+
{
|
|
385
|
+
type: "fetch",
|
|
386
|
+
stage: "error",
|
|
387
|
+
method,
|
|
388
|
+
url,
|
|
389
|
+
leaf: leafLabel,
|
|
390
|
+
durationMs: Date.now() - startedAt,
|
|
391
|
+
body: payload,
|
|
392
|
+
error
|
|
393
|
+
},
|
|
394
|
+
detail
|
|
395
|
+
)
|
|
396
|
+
);
|
|
397
|
+
throw error;
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
const useEndpoint = (...tuple) => {
|
|
401
|
+
emit({ type: "useEndpoint", leaf: leafLabel, variant: "mutation" });
|
|
306
402
|
return useMutation({
|
|
307
403
|
...rqOpts,
|
|
308
404
|
mutationKey: key(...tuple),
|
|
309
|
-
mutationFn:
|
|
310
|
-
const normalizedBody = zParse(body, leaf.cfg.bodySchema);
|
|
311
|
-
const isMultipart = Array.isArray(leaf.cfg.bodyFiles) && leaf.cfg.bodyFiles.length > 0;
|
|
312
|
-
const payload = isMultipart ? toFormData(normalizedBody) : normalizedBody;
|
|
313
|
-
const startedAt = Date.now();
|
|
314
|
-
const detail = isVerboseDebug ? { params: normalizedParams, query: normalizedQuery } : void 0;
|
|
315
|
-
emitDebug(
|
|
316
|
-
decorateDebugEvent(
|
|
317
|
-
{
|
|
318
|
-
type: "fetch",
|
|
319
|
-
stage: "start",
|
|
320
|
-
method,
|
|
321
|
-
url,
|
|
322
|
-
leaf: leafLabel,
|
|
323
|
-
body: payload
|
|
324
|
-
},
|
|
325
|
-
detail
|
|
326
|
-
)
|
|
327
|
-
);
|
|
328
|
-
try {
|
|
329
|
-
const out = await fetcher({ url, method, body: payload });
|
|
330
|
-
const parsed = zParse(out, leaf.cfg.outputSchema);
|
|
331
|
-
emitDebug(
|
|
332
|
-
decorateDebugEvent(
|
|
333
|
-
{
|
|
334
|
-
type: "fetch",
|
|
335
|
-
stage: "success",
|
|
336
|
-
method,
|
|
337
|
-
url,
|
|
338
|
-
leaf: leafLabel,
|
|
339
|
-
durationMs: Date.now() - startedAt
|
|
340
|
-
},
|
|
341
|
-
isVerboseDebug ? { params: normalizedParams, query: normalizedQuery, output: parsed } : void 0
|
|
342
|
-
)
|
|
343
|
-
);
|
|
344
|
-
return parsed;
|
|
345
|
-
} catch (error) {
|
|
346
|
-
emitDebug(
|
|
347
|
-
decorateDebugEvent(
|
|
348
|
-
{
|
|
349
|
-
type: "fetch",
|
|
350
|
-
stage: "error",
|
|
351
|
-
method,
|
|
352
|
-
url,
|
|
353
|
-
leaf: leafLabel,
|
|
354
|
-
durationMs: Date.now() - startedAt,
|
|
355
|
-
body: payload,
|
|
356
|
-
error
|
|
357
|
-
},
|
|
358
|
-
detail
|
|
359
|
-
)
|
|
360
|
-
);
|
|
361
|
-
throw error;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
405
|
+
mutationFn: (body) => fetchEndpoint(...[...tuple, body])
|
|
364
406
|
}, queryClient);
|
|
365
407
|
};
|
|
366
408
|
return {
|
|
367
409
|
key,
|
|
368
410
|
invalidate: invalidateExact,
|
|
369
411
|
setData,
|
|
370
|
-
useEndpoint
|
|
412
|
+
useEndpoint,
|
|
413
|
+
fetch: fetchEndpoint
|
|
371
414
|
};
|
|
372
415
|
}
|
|
373
416
|
return {
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/routesV3.client.fetch.ts","../src/routesV3.client.index.ts"],"sourcesContent":["// routesV3.client.fetch.ts\n\nimport { Fetcher, FetchInput } from './routesV3.client.types';\n\n/**\n * Default fetch implementation used by the route client helper.\n * @param req Normalized request information (URL, method, body, headers).\n * @returns Parsed JSON (or text fallback) from the server response.\n */\nexport const defaultFetcher: Fetcher = async <T>(req: FetchInput): Promise<T> => {\n const headers: Record<string, string> = { ...(req.headers ?? {}) };\n const isFormData = typeof FormData !== 'undefined' && req.body instanceof FormData;\n if (!isFormData) {\n headers['Content-Type'] ||= 'application/json';\n headers['Accept'] ||= 'application/json';\n }\n\n const res = await fetch(req.url, {\n method: req.method,\n headers,\n body: isFormData ? (req.body as any) : req.body == null ? undefined : JSON.stringify(req.body),\n });\n\n const text = await res.text();\n if (!res.ok) {\n const snippet = text.slice(0, 400);\n throw new Error(`[${res.status}] ${res.statusText} — ${snippet}`);\n }\n\n try {\n return JSON.parse(text) as T;\n } catch {\n return text as unknown as T;\n }\n};\n","// routesV3.client.ts\nimport {\n keepPreviousData,\n useInfiniteQuery,\n useMutation,\n useQuery,\n type InfiniteData,\n type QueryKey,\n} from '@tanstack/react-query';\nimport type { ZodType } from 'zod';\nimport {\n HttpMethod,\n buildCacheKey,\n compilePath,\n} from '@emeryld/rrroutes-contract';\nimport type {\n AnyLeaf,\n InferBody,\n InferOutput,\n InferParams,\n InferQuery,\n} from '@emeryld/rrroutes-contract';\nimport { defaultFetcher } from './routesV3.client.fetch';\nimport type {\n ArgsFor,\n ArgsTuple,\n BuiltForLeaf,\n BuiltInfinite,\n BuiltMutation,\n BuiltQuery,\n Cursor,\n DataShape,\n InfiniteBuildOptionsFor,\n MutationBuildOptionsFor,\n QueryBuildOptionsFor,\n RouteClient,\n RouteClientOptions,\n RouteClientDebugEvent,\n RouteClientDebugLogger,\n RouteClientDebugMode,\n RouteClientDebugOptions,\n Updater,\n} from './routesV3.client.types';\n\n// -------------------------------------------------------------------------------------\n// Tiny helpers\n// -------------------------------------------------------------------------------------\n/**\n * Convert an HTTP method to uppercase (as expected by fetch).\n * @param m Lowercase HTTP method.\n * @returns Uppercase method string.\n */\nconst toUpper = (m: HttpMethod): Uppercase<HttpMethod> => m.toUpperCase() as Uppercase<HttpMethod>;\n\n/**\n * Parse the given value with the supplied schema (if present).\n * @param value Raw value to validate.\n * @param schema Optional Zod schema used for validation/coercion.\n * @returns The validated or original value.\n */\nfunction zParse<T>(value: unknown, schema?: ZodType): T {\n return schema ? (schema.parse(value) as T) : (value as T);\n}\n\n/**\n * Serialize a query object into a search string.\n * @param query Query params object (possibly undefined).\n * @returns Query string prefixed with `?`, or empty string.\n */\nfunction toSearchString(query: Record<string, unknown> | undefined) {\n if (!query) return '';\n const params = new URLSearchParams();\n for (const [k, v] of Object.entries(query)) {\n if (v == null) continue;\n if (Array.isArray(v)) {\n v.forEach((x) => {\n if (x == null) return;\n if (typeof x === 'object') {\n params.append(k, JSON.stringify(x));\n } else {\n params.append(k, String(x));\n }\n });\n continue;\n }\n if (typeof v === 'object') {\n params.set(k, JSON.stringify(v));\n continue;\n }\n params.set(k, String(v));\n }\n const s = params.toString();\n return s ? `?${s}` : '';\n}\n\n/**\n * Remove the given key from an object (used to drop cursors from cache keys).\n * @param obj Source object.\n * @param key Property name to omit.\n * @returns Copy of the object without the specified key.\n */\nfunction stripKey<Q extends Record<string, unknown> | undefined>(obj: Q, key: string): Q {\n if (!obj) return obj;\n const { [key]: _omit, ...rest } = obj as any;\n return rest as Q;\n}\n\n/**\n * Default cursor extractor used by infinite queries.\n * @param p Page result returned from the server.\n * @returns Next cursor string, if present.\n */\nconst defaultGetNextCursor = (p: unknown): Cursor =>\n p && typeof p === 'object' && 'nextCursor' in p ? (p as any).nextCursor : undefined;\n\n// Debug logging --------------------------------------------------------------\nconst noopDebugLogger: RouteClientDebugLogger = () => {};\n\nconst defaultDebugLogger: RouteClientDebugLogger = (event: RouteClientDebugEvent) => {\n if (typeof console === 'undefined') return;\n const fn = console.debug ?? console.log;\n fn?.call(console, '[rrroutes-client]', event);\n};\n\ntype DebugEmitter = {\n emit: RouteClientDebugLogger;\n mode: RouteClientDebugMode;\n};\n\nfunction createDebugEmitter(option?: RouteClientDebugOptions): DebugEmitter {\n const disabled: DebugEmitter = { emit: noopDebugLogger, mode: 'minimal' };\n\n if (!option) {\n return disabled;\n }\n if (option === true || option === 'minimal') {\n return { emit: defaultDebugLogger, mode: 'minimal' };\n }\n if (option === 'complete') {\n return { emit: defaultDebugLogger, mode: 'complete' };\n }\n if (typeof option === 'function') {\n return { emit: option, mode: 'minimal' };\n }\n if (option.enabled === false) {\n return { emit: noopDebugLogger, mode: option.mode ?? 'minimal' };\n }\n return {\n emit: option.logger ?? defaultDebugLogger,\n mode: option.mode ?? 'minimal',\n };\n}\n\n// Split the variadic tuple at runtime\n/**\n * Extract the optional argument object from a variadic tuple.\n * @param args Tuple passed to a built endpoint helper.\n * @returns The argument object if present.\n */\nfunction extractArgs<L extends AnyLeaf>(args: ArgsTuple<L>): ArgsFor<L> | undefined {\n // At runtime ArgsTuple<L> is either [] or [obj]; we just pick the first if present.\n return (args as unknown as any[])[0] as any;\n}\n\n/**\n * Normalize params and query values, then construct a request URL for the given leaf.\n * @param leaf Leaf describing the endpoint.\n * @param baseUrl Optional base URL prepended to the path.\n * @param params Route parameters supplied by the caller.\n * @param query Query parameters supplied by the caller.\n * @returns Object containing the composed URL plus normalized params/query payloads.\n */\nfunction buildUrl<L extends AnyLeaf>(\n leaf: L,\n baseUrl: string,\n params: InferParams<L> | undefined,\n query: InferQuery<L> | undefined,\n) {\n const normalizedParams = zParse<InferParams<L>>(params, leaf.cfg.paramsSchema);\n const normalizedQuery = zParse<InferQuery<L>>(query, leaf.cfg.querySchema);\n const path = compilePath<L['path']>(leaf.path, (normalizedParams ?? {}) as any);\n const url = `${baseUrl ?? ''}${path}${toSearchString(normalizedQuery as any)}`;\n return { url, normalizedQuery, normalizedParams };\n}\n\n// -------------------------------------------------------------------------------------\n// Client factory\n// -------------------------------------------------------------------------------------\n/**\n * Construct typed React Query helpers backed by a routes-v3 registry leaf.\n * @param opts Route client configuration (query client, fetcher overrides, etc).\n * @returns Object that can build endpoint hooks/mutations from leaves.\n */\nexport function createRouteClient(opts: RouteClientOptions): RouteClient {\n const queryClient = opts.queryClient;\n const fetcher = opts.fetcher ?? defaultFetcher;\n const baseUrl = opts.baseUrl;\n const cursorParam = opts.cursorParam ?? 'cursor';\n const getNextCursor = opts.getNextCursor ?? defaultGetNextCursor;\n const { emit: emitDebug, mode: debugMode } = createDebugEmitter(opts.debug);\n const isVerboseDebug = debugMode === 'complete';\n const decorateDebugEvent = <T extends RouteClientDebugEvent>(\n event: T,\n details?: Partial<RouteClientDebugEvent>,\n ): RouteClientDebugEvent => {\n if (!isVerboseDebug || !details) return event;\n return { ...event, ...details } as RouteClientDebugEvent;\n };\n\n /**\n * Invalidate a set of queries sharing the given prefix.\n * @param prefix Key parts shared by matching endpoints.\n * @param exact When true, invalidate only exact key matches.\n */\n async function invalidate(prefix: string[], exact = false) {\n const queryKey = prefix as unknown as QueryKey;\n await queryClient.invalidateQueries({ queryKey, exact });\n emitDebug({ type: 'invalidate', key: queryKey, exact });\n }\n\n /**\n * Build the client surface for a single leaf (query/mutation/infinite query).\n * @param leaf Leaf describing the endpoint.\n * @param rqOpts Optional React Query configuration.\n * @returns Helper object exposing key/invalidate/setData/useEndpoint.\n */\n function buildInternal<L extends AnyLeaf>(\n leaf: L,\n rqOpts?: QueryBuildOptionsFor<L> | InfiniteBuildOptionsFor<L> | MutationBuildOptionsFor<L>,\n ): BuiltForLeaf<L> {\n const isGet = leaf.method === 'get';\n const isFeed = !!leaf.cfg.feed;\n const method = toUpper(leaf.method);\n const leafLabel = `${leaf.method.toUpperCase()} ${String(leaf.path)}`;\n emitDebug({ type: 'build', leaf: leafLabel });\n\n // --- key/invalidate/setData shared helpers ---\n const key = (...tuple: ArgsTuple<L>): QueryKey => {\n const a = extractArgs<L>(tuple);\n const params = (a as any)?.params as InferParams<L> | undefined;\n const query = (a as any)?.query as InferQuery<L> | undefined;\n const qForKey = isGet && isFeed ? stripKey(query as any, cursorParam) : (query as any);\n return buildCacheKey({ leaf, params: params as any, query: qForKey }) as unknown as QueryKey;\n };\n\n /**\n * Invalidate the React Query cache for this exact leaf invocation.\n * @param tuple Optional params/query tuple.\n */\n const invalidateExact = async (...tuple: ArgsTuple<L>) => {\n const queryKey = key(...tuple);\n await queryClient.invalidateQueries({ queryKey, exact: true });\n emitDebug({ type: 'invalidate', key: queryKey, exact: true });\n };\n\n /**\n * Update the cache entries for this leaf.\n * @param args Tuple whose first entry is the updater and optional params/query follow.\n */\n const setData = (...args: [Updater<DataShape<L>>, ...rest: ArgsTuple<L>]) => {\n const [updater, ...rest] = args;\n const k = key(...(rest as ArgsTuple<L>));\n if (isGet && isFeed) {\n queryClient.setQueryData<InfiniteData<InferOutput<L>> | undefined>(k, (prev) =>\n typeof updater === 'function' ? (updater as any)(prev) : (updater as any),\n );\n } else {\n queryClient.setQueryData<InferOutput<L> | undefined>(k, (prev) =>\n typeof updater === 'function' ? (updater as any)(prev) : (updater as any),\n );\n }\n emitDebug({ type: 'setData', key: k });\n };\n\n // --- Infinite GET ---\n if (isGet && isFeed) {\n const useEndpoint: BuiltInfinite<L>['useEndpoint'] = (...tuple) => {\n emitDebug({ type: 'useInfiniteGet', leaf: leafLabel });\n const a = extractArgs<L>(tuple);\n const params = (a as any)?.params as InferParams<L> | undefined;\n const query = (a as any)?.query as InferQuery<L> | undefined;\n\n // Normalize once; we’ll inject the cursor per page below.\n const { normalizedQuery, normalizedParams } = buildUrl(leaf, baseUrl, params, query);\n return useInfiniteQuery<\n InferOutput<L>, // TQueryFnData (per page)\n unknown, // TError\n InfiniteData<InferOutput<L>>, // TData (returned by the hook)\n QueryKey,\n Cursor\n >({\n ...(rqOpts as InfiniteBuildOptionsFor<L>),\n queryKey: key(...tuple),\n initialPageParam: undefined,\n getNextPageParam: (lastPage) => getNextCursor(lastPage),\n placeholderData: keepPreviousData,\n queryFn: async ({ pageParam }) => {\n const pageQuery = {\n ...(normalizedQuery as any),\n ...(pageParam ? { [cursorParam]: pageParam } : {}),\n };\n const { url } = buildUrl(leaf, baseUrl, params, pageQuery);\n const startedAt = Date.now();\n const detail = isVerboseDebug ? { params: normalizedParams, query: pageQuery } : undefined;\n emitDebug(\n decorateDebugEvent(\n { type: 'fetch', stage: 'start', method, url, leaf: leafLabel },\n detail,\n ),\n );\n try {\n const out = await fetcher<unknown>({ url, method });\n const parsed = zParse<InferOutput<L>>(out, leaf.cfg.outputSchema);\n emitDebug(\n decorateDebugEvent(\n {\n type: 'fetch',\n stage: 'success',\n method,\n url,\n leaf: leafLabel,\n durationMs: Date.now() - startedAt,\n },\n isVerboseDebug ? { params: normalizedParams, query: pageQuery, output: parsed } : undefined,\n ),\n );\n return parsed;\n } catch (error) {\n emitDebug(\n decorateDebugEvent(\n {\n type: 'fetch',\n stage: 'error',\n method,\n url,\n leaf: leafLabel,\n durationMs: Date.now() - startedAt,\n error,\n },\n detail,\n ),\n );\n throw error;\n }\n },\n // NOTE: TData is InfiniteData<T>, so we don't need a select here.\n }, queryClient);\n };\n\n return {\n key,\n invalidate: invalidateExact,\n setData: setData as any,\n useEndpoint,\n } as BuiltForLeaf<L>;\n }\n // --- Plain GET ---\n if (isGet) {\n const useEndpoint: BuiltQuery<L>['useEndpoint'] = (...tuple) => {\n emitDebug({ type: 'useGet', leaf: leafLabel });\n const a = extractArgs<L>(tuple);\n const params = (a as any)?.params as InferParams<L> | undefined;\n const query = (a as any)?.query as InferQuery<L> | undefined;\n\n const { url, normalizedQuery, normalizedParams } = buildUrl(leaf, baseUrl, params, query);\n return useQuery<InferOutput<L>, unknown, InferOutput<L>, QueryKey>({\n ...(rqOpts as QueryBuildOptionsFor<L>),\n queryKey: key(...tuple),\n placeholderData: keepPreviousData,\n queryFn: async () => {\n const startedAt = Date.now();\n const detail = isVerboseDebug\n ? { params: normalizedParams, query: normalizedQuery }\n : undefined;\n emitDebug(\n decorateDebugEvent(\n { type: 'fetch', stage: 'start', method, url, leaf: leafLabel },\n detail,\n ),\n );\n try {\n const out = await fetcher<unknown>({ url, method });\n const parsed = zParse<InferOutput<L>>(out, leaf.cfg.outputSchema);\n emitDebug(\n decorateDebugEvent(\n {\n type: 'fetch',\n stage: 'success',\n method,\n url,\n leaf: leafLabel,\n durationMs: Date.now() - startedAt,\n },\n isVerboseDebug\n ? { params: normalizedParams, query: normalizedQuery, output: parsed }\n : undefined,\n ),\n );\n return parsed;\n } catch (error) {\n emitDebug(\n decorateDebugEvent(\n {\n type: 'fetch',\n stage: 'error',\n method,\n url,\n leaf: leafLabel,\n durationMs: Date.now() - startedAt,\n error,\n },\n detail,\n ),\n );\n throw error;\n }\n },\n }, queryClient);\n };\n\n return {\n key,\n invalidate: invalidateExact,\n setData: setData as any,\n useEndpoint,\n } as BuiltForLeaf<L>;\n }\n\n // --- Mutation (POST/PUT/PATCH/DELETE) ---\n const useEndpoint: BuiltMutation<L>['useEndpoint'] = (...tuple) => {\n emitDebug({ type: 'useMutation', leaf: leafLabel });\n const a = extractArgs<L>(tuple);\n const params = (a as any)?.params as InferParams<L> | undefined;\n const query = (a as any)?.query as InferQuery<L> | undefined;\n\n const { url, normalizedQuery, normalizedParams } = buildUrl(leaf, baseUrl, params, query);\n return useMutation<InferOutput<L>, unknown, InferBody<L>, unknown>({\n ...(rqOpts as MutationBuildOptionsFor<L>),\n mutationKey: key(...tuple),\n mutationFn: async (body: InferBody<L>) => {\n const normalizedBody = zParse<InferBody<L>>(body, leaf.cfg.bodySchema);\n\n // Optional: switch to FormData if your method declares bodyFiles\n const isMultipart = Array.isArray(leaf.cfg.bodyFiles) && leaf.cfg.bodyFiles.length > 0;\n const payload = isMultipart ? toFormData(normalizedBody as any) : normalizedBody;\n\n const startedAt = Date.now();\n const detail = isVerboseDebug\n ? { params: normalizedParams, query: normalizedQuery }\n : undefined;\n emitDebug(\n decorateDebugEvent(\n {\n type: 'fetch',\n stage: 'start',\n method,\n url,\n leaf: leafLabel,\n body: payload,\n },\n detail,\n ),\n );\n try {\n const out = await fetcher<unknown>({ url, method, body: payload });\n const parsed = zParse<InferOutput<L>>(out, leaf.cfg.outputSchema);\n emitDebug(\n decorateDebugEvent(\n {\n type: 'fetch',\n stage: 'success',\n method,\n url,\n leaf: leafLabel,\n durationMs: Date.now() - startedAt,\n },\n isVerboseDebug\n ? { params: normalizedParams, query: normalizedQuery, output: parsed }\n : undefined,\n ),\n );\n return parsed;\n } catch (error) {\n emitDebug(\n decorateDebugEvent(\n {\n type: 'fetch',\n stage: 'error',\n method,\n url,\n leaf: leafLabel,\n durationMs: Date.now() - startedAt,\n body: payload,\n error,\n },\n detail,\n ),\n );\n throw error;\n }\n },\n }, queryClient);\n };\n\n return {\n key,\n invalidate: invalidateExact,\n setData: setData as any,\n useEndpoint,\n } as BuiltForLeaf<L>;\n }\n\n return {\n queryClient,\n invalidate,\n build: buildInternal as RouteClient['build'],\n };\n}\n\n// -------------------------------------------------------------------------------------\n// Multipart helper\n// -------------------------------------------------------------------------------------\nfunction toFormData(body: Record<string, any>): FormData {\n const fd = new FormData();\n for (const [k, v] of Object.entries(body ?? {})) {\n if (v == null) continue;\n if (Array.isArray(v)) v.forEach((item, i) => fd.append(`${k}[${i}]`, item as any));\n else fd.append(k, v as any);\n }\n return fd;\n}\n"],"mappings":"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","names":["useEndpoint"]}
|
|
1
|
+
{"version":3,"sources":["../src/routesV3.client.fetch.ts","../src/routesV3.client.index.ts"],"sourcesContent":["// routesV3.client.fetch.ts\n\nimport { Fetcher, FetchInput } from './routesV3.client.types';\n\n/**\n * Default fetch implementation used by the route client helper.\n * @param req Normalized request information (URL, method, body, headers).\n * @returns Parsed JSON (or text fallback) from the server response.\n */\nexport const defaultFetcher: Fetcher = async <T>(req: FetchInput): Promise<T> => {\n const headers: Record<string, string> = { ...(req.headers ?? {}) };\n const isFormData = typeof FormData !== 'undefined' && req.body instanceof FormData;\n if (!isFormData) {\n headers['Content-Type'] ||= 'application/json';\n headers['Accept'] ||= 'application/json';\n }\n\n const res = await fetch(req.url, {\n method: req.method,\n headers,\n body: isFormData ? (req.body as any) : req.body == null ? undefined : JSON.stringify(req.body),\n });\n\n const text = await res.text();\n if (!res.ok) {\n const snippet = text.slice(0, 400);\n throw new Error(`[${res.status}] ${res.statusText} — ${snippet}`);\n }\n\n try {\n return JSON.parse(text) as T;\n } catch {\n return text as unknown as T;\n }\n};\n","// routesV3.client.ts\nimport {\n keepPreviousData,\n useInfiniteQuery,\n useMutation,\n useQuery,\n type InfiniteData,\n type QueryKey,\n} from '@tanstack/react-query';\nimport type { ZodType } from 'zod';\nimport {\n HttpMethod,\n buildCacheKey,\n compilePath,\n} from '@emeryld/rrroutes-contract';\nimport type {\n AnyLeaf,\n InferBody,\n InferOutput,\n InferParams,\n InferQuery,\n} from '@emeryld/rrroutes-contract';\nimport { defaultFetcher } from './routesV3.client.fetch';\nimport type {\n ArgsFor,\n ArgsTuple,\n BuildMeta,\n BuiltForLeaf,\n BuiltInfinite,\n BuiltMutation,\n BuiltQuery,\n Cursor,\n DataShape,\n InfiniteBuildOptionsFor,\n MutationBuildOptionsFor,\n QueryBuildOptionsFor,\n RouteClient,\n RouteClientOptions,\n RouteClientDebugEvent,\n RouteClientDebugLogger,\n RouteClientDebugMode,\n RouteClientDebugOptions,\n RouteClientDebugToggleOptions,\n Updater,\n} from './routesV3.client.types';\n\n// -------------------------------------------------------------------------------------\n// Tiny helpers\n// -------------------------------------------------------------------------------------\n/**\n * Convert an HTTP method to uppercase (as expected by fetch).\n * @param m Lowercase HTTP method.\n * @returns Uppercase method string.\n */\nconst toUpper = (m: HttpMethod): Uppercase<HttpMethod> => m.toUpperCase() as Uppercase<HttpMethod>;\n\n/**\n * Parse the given value with the supplied schema (if present).\n * @param value Raw value to validate.\n * @param schema Optional Zod schema used for validation/coercion.\n * @returns The validated or original value.\n */\nfunction zParse<T>(value: unknown, schema?: ZodType): T {\n return schema ? (schema.parse(value) as T) : (value as T);\n}\n\n/**\n * Serialize a query object into a search string.\n * @param query Query params object (possibly undefined).\n * @returns Query string prefixed with `?`, or empty string.\n */\nfunction toSearchString(query: Record<string, unknown> | undefined) {\n if (!query) return '';\n const params = new URLSearchParams();\n for (const [k, v] of Object.entries(query)) {\n if (v == null) continue;\n if (Array.isArray(v)) {\n v.forEach((x) => {\n if (x == null) return;\n if (typeof x === 'object') {\n params.append(k, JSON.stringify(x));\n } else {\n params.append(k, String(x));\n }\n });\n continue;\n }\n if (typeof v === 'object') {\n params.set(k, JSON.stringify(v));\n continue;\n }\n params.set(k, String(v));\n }\n const s = params.toString();\n return s ? `?${s}` : '';\n}\n\n/**\n * Remove the given key from an object (used to drop cursors from cache keys).\n * @param obj Source object.\n * @param key Property name to omit.\n * @returns Copy of the object without the specified key.\n */\nfunction stripKey<Q extends Record<string, unknown> | undefined>(obj: Q, key: string): Q {\n if (!obj) return obj;\n const { [key]: _omit, ...rest } = obj as any;\n return rest as Q;\n}\n\n/**\n * Default cursor extractor used by infinite queries.\n * @param p Page result returned from the server.\n * @returns Next cursor string, if present.\n */\nconst defaultGetNextCursor = (p: unknown): Cursor =>\n p && typeof p === 'object' && 'nextCursor' in p ? (p as any).nextCursor : undefined;\n\n// Debug logging --------------------------------------------------------------\nconst noopDebugLogger: RouteClientDebugLogger = () => {};\n\nconst defaultDebugLogger: RouteClientDebugLogger = (event: RouteClientDebugEvent) => {\n if (typeof console === 'undefined') return;\n const fn = console.debug ?? console.log;\n fn?.call(console, '[rrroutes-client]', event);\n};\n\nconst debugEventTypes: RouteClientDebugEvent['type'][] = [\n 'fetch',\n 'invalidate',\n 'setData',\n 'build',\n 'useEndpoint',\n];\n\ntype DebugEmitter<Names extends string> = {\n emit: (event: RouteClientDebugEvent, name?: Names) => void;\n mode: RouteClientDebugMode;\n};\n\nconst noopEmit = () => {};\n\nfunction createDebugEmitter<Names extends string>(\n option?: RouteClientDebugOptions<Names>,\n environment?: string,\n): DebugEmitter<Names> {\n const disabled: DebugEmitter<Names> = { emit: noopEmit, mode: 'minimal' };\n\n if (environment && environment.toLowerCase() === 'production') {\n return disabled;\n }\n\n if (!option) {\n return disabled;\n }\n if (option === true || option === 'minimal') {\n return {\n emit: (event, name) => defaultDebugLogger(name ? { ...event, name } : event),\n mode: 'minimal',\n };\n }\n if (option === 'complete') {\n return {\n emit: (event, name) => defaultDebugLogger(name ? { ...event, name } : event),\n mode: 'complete',\n };\n }\n if (typeof option === 'function') {\n return {\n emit: (event, name) => option(name ? { ...event, name } : event),\n mode: 'minimal',\n };\n }\n if (typeof option === 'object') {\n const toggles = option as RouteClientDebugToggleOptions<Names>;\n const verbose = Boolean(toggles.verbose);\n const enabledTypes = debugEventTypes.filter((type) => toggles[type]);\n if (enabledTypes.length === 0) {\n return { emit: noopEmit, mode: verbose ? 'complete' : 'minimal' };\n }\n const whitelist = new Set<RouteClientDebugEvent['type']>(enabledTypes);\n const onlySet =\n toggles.only && toggles.only.length > 0 ? new Set<Names>(toggles.only) : undefined;\n const logger = toggles.logger ?? defaultDebugLogger;\n const emit: DebugEmitter<Names>['emit'] = (event, name) => {\n if (!whitelist.has(event.type)) return;\n if (onlySet) {\n if (!name || !onlySet.has(name)) return;\n }\n logger(name ? { ...event, name } : event);\n };\n return { emit, mode: verbose ? 'complete' : 'minimal' };\n }\n\n return disabled;\n}\n\n// Split the variadic tuple at runtime\n/**\n * Extract the optional argument object from a variadic tuple.\n * @param args Tuple passed to a built endpoint helper.\n * @returns The argument object if present.\n */\nfunction extractArgs<L extends AnyLeaf>(args: ArgsTuple<L>): ArgsFor<L> | undefined {\n // At runtime ArgsTuple<L> is either [] or [obj]; we just pick the first if present.\n return (args as unknown as any[])[0] as any;\n}\n\n/**\n * Normalize params and query values, then construct a request URL for the given leaf.\n * @param leaf Leaf describing the endpoint.\n * @param baseUrl Optional base URL prepended to the path.\n * @param params Route parameters supplied by the caller.\n * @param query Query parameters supplied by the caller.\n * @returns Object containing the composed URL plus normalized params/query payloads.\n */\nfunction buildUrl<L extends AnyLeaf>(\n leaf: L,\n baseUrl: string,\n params: InferParams<L> | undefined,\n query: InferQuery<L> | undefined,\n) {\n const normalizedParams = zParse<InferParams<L>>(params, leaf.cfg.paramsSchema);\n const normalizedQuery = zParse<InferQuery<L>>(query, leaf.cfg.querySchema);\n const path = compilePath<L['path']>(leaf.path, (normalizedParams ?? {}) as any);\n const url = `${baseUrl ?? ''}${path}${toSearchString(normalizedQuery as any)}`;\n return { url, normalizedQuery, normalizedParams };\n}\n\n// -------------------------------------------------------------------------------------\n// Client factory\n// -------------------------------------------------------------------------------------\n/**\n * Construct typed React Query helpers backed by a routes-v3 registry leaf.\n * @param opts Route client configuration (query client, fetcher overrides, etc).\n * @returns Object that can build endpoint hooks/mutations from leaves.\n */\nexport function createRouteClient<Names extends string = string>(\n opts: RouteClientOptions<Names>,\n): RouteClient<Names> {\n const queryClient = opts.queryClient;\n const fetcher = opts.fetcher ?? defaultFetcher;\n const baseUrl = opts.baseUrl;\n const cursorParam = opts.cursorParam ?? 'cursor';\n const getNextCursor = opts.getNextCursor ?? defaultGetNextCursor;\n const environment =\n opts.environment ??\n (typeof process !== 'undefined' && process.env ? process.env.NODE_ENV : undefined);\n const { emit: emitDebug, mode: debugMode } = createDebugEmitter<Names>(opts.debug, environment);\n const isVerboseDebug = debugMode === 'complete';\n const decorateDebugEvent = <T extends RouteClientDebugEvent>(\n event: T,\n details?: Partial<RouteClientDebugEvent>,\n ): RouteClientDebugEvent => {\n if (!isVerboseDebug || !details) return event;\n return { ...event, ...details } as RouteClientDebugEvent;\n };\n\n /**\n * Invalidate a set of queries sharing the given prefix.\n * @param prefix Key parts shared by matching endpoints.\n * @param exact When true, invalidate only exact key matches.\n */\n async function invalidate(prefix: string[], exact = false) {\n const queryKey = prefix as unknown as QueryKey;\n await queryClient.invalidateQueries({ queryKey, exact });\n emitDebug({ type: 'invalidate', key: queryKey, exact });\n }\n\n /**\n * Build the client surface for a single leaf (query/mutation/infinite query).\n * @param leaf Leaf describing the endpoint.\n * @param rqOpts Optional React Query configuration.\n * @returns Helper object exposing key/invalidate/setData/useEndpoint.\n */\n function buildInternal<L extends AnyLeaf>(\n leaf: L,\n rqOpts?: QueryBuildOptionsFor<L> | InfiniteBuildOptionsFor<L> | MutationBuildOptionsFor<L>,\n meta?: BuildMeta<Names>,\n ): BuiltForLeaf<L> {\n const isGet = leaf.method === 'get';\n const isFeed = !!leaf.cfg.feed;\n const method = toUpper(leaf.method);\n const leafLabel = `${leaf.method.toUpperCase()} ${String(leaf.path)}`;\n const debugName = meta?.name;\n const emit = (event: RouteClientDebugEvent) => emitDebug(event, debugName);\n emit({ type: 'build', leaf: leafLabel });\n\n // --- key/invalidate/setData shared helpers ---\n const key = (...tuple: ArgsTuple<L>): QueryKey => {\n const a = extractArgs<L>(tuple);\n const params = (a as any)?.params as InferParams<L> | undefined;\n const query = (a as any)?.query as InferQuery<L> | undefined;\n const qForKey = isGet && isFeed ? stripKey(query as any, cursorParam) : (query as any);\n return buildCacheKey({ leaf, params: params as any, query: qForKey }) as unknown as QueryKey;\n };\n\n /**\n * Invalidate the React Query cache for this exact leaf invocation.\n * @param tuple Optional params/query tuple.\n */\n const invalidateExact = async (...tuple: ArgsTuple<L>) => {\n const queryKey = key(...tuple);\n await queryClient.invalidateQueries({ queryKey, exact: true });\n emit({ type: 'invalidate', key: queryKey, exact: true });\n };\n\n /**\n * Update the cache entries for this leaf.\n * @param args Tuple whose first entry is the updater and optional params/query follow.\n */\n const setData = (...args: [Updater<DataShape<L>>, ...rest: ArgsTuple<L>]) => {\n const [updater, ...rest] = args;\n const k = key(...(rest as ArgsTuple<L>));\n if (isGet && isFeed) {\n queryClient.setQueryData<InfiniteData<InferOutput<L>> | undefined>(k, (prev) =>\n typeof updater === 'function' ? (updater as any)(prev) : (updater as any),\n );\n } else {\n queryClient.setQueryData<InferOutput<L> | undefined>(k, (prev) =>\n typeof updater === 'function' ? (updater as any)(prev) : (updater as any),\n );\n }\n emit({ type: 'setData', key: k });\n };\n\n // --- Infinite GET ---\n if (isGet && isFeed) {\n const useEndpoint: BuiltInfinite<L>['useEndpoint'] = (...tuple) => {\n emit({ type: 'useEndpoint', leaf: leafLabel, variant: 'infiniteGet' });\n const a = extractArgs<L>(tuple);\n const params = (a as any)?.params as InferParams<L> | undefined;\n const query = (a as any)?.query as InferQuery<L> | undefined;\n\n // Normalize once; we’ll inject the cursor per page below.\n const { normalizedQuery, normalizedParams } = buildUrl(leaf, baseUrl, params, query);\n return useInfiniteQuery<\n InferOutput<L>, // TQueryFnData (per page)\n unknown, // TError\n InfiniteData<InferOutput<L>>, // TData (returned by the hook)\n QueryKey,\n Cursor\n >({\n ...(rqOpts as InfiniteBuildOptionsFor<L>),\n queryKey: key(...tuple),\n initialPageParam: undefined,\n getNextPageParam: (lastPage) => getNextCursor(lastPage),\n placeholderData: keepPreviousData,\n queryFn: async ({ pageParam }) => {\n const pageQuery = {\n ...(normalizedQuery as any),\n ...(pageParam ? { [cursorParam]: pageParam } : {}),\n };\n const { url } = buildUrl(leaf, baseUrl, params, pageQuery);\n const startedAt = Date.now();\n const detail = isVerboseDebug ? { params: normalizedParams, query: pageQuery } : undefined;\n emit(\n decorateDebugEvent(\n { type: 'fetch', stage: 'start', method, url, leaf: leafLabel },\n detail,\n ),\n );\n try {\n const out = await fetcher<unknown>({ url, method });\n const parsed = zParse<InferOutput<L>>(out, leaf.cfg.outputSchema);\n emit(\n decorateDebugEvent(\n {\n type: 'fetch',\n stage: 'success',\n method,\n url,\n leaf: leafLabel,\n durationMs: Date.now() - startedAt,\n },\n isVerboseDebug ? { params: normalizedParams, query: pageQuery, output: parsed } : undefined,\n ),\n );\n return parsed;\n } catch (error) {\n emit(\n decorateDebugEvent(\n {\n type: 'fetch',\n stage: 'error',\n method,\n url,\n leaf: leafLabel,\n durationMs: Date.now() - startedAt,\n error,\n },\n detail,\n ),\n );\n throw error;\n }\n },\n // NOTE: TData is InfiniteData<T>, so we don't need a select here.\n }, queryClient);\n };\n\n return {\n key,\n invalidate: invalidateExact,\n setData: setData as any,\n useEndpoint,\n } as BuiltForLeaf<L>;\n }\n // --- Plain GET ---\n if (isGet) {\n const useEndpoint: BuiltQuery<L>['useEndpoint'] = (...tuple) => {\n emit({ type: 'useEndpoint', leaf: leafLabel, variant: 'get' });\n const a = extractArgs<L>(tuple);\n const params = (a as any)?.params as InferParams<L> | undefined;\n const query = (a as any)?.query as InferQuery<L> | undefined;\n\n const { url, normalizedQuery, normalizedParams } = buildUrl(leaf, baseUrl, params, query);\n return useQuery<InferOutput<L>, unknown, InferOutput<L>, QueryKey>({\n ...(rqOpts as QueryBuildOptionsFor<L>),\n queryKey: key(...tuple),\n placeholderData: keepPreviousData,\n queryFn: async () => {\n const startedAt = Date.now();\n const detail = isVerboseDebug\n ? { params: normalizedParams, query: normalizedQuery }\n : undefined;\n emit(\n decorateDebugEvent(\n { type: 'fetch', stage: 'start', method, url, leaf: leafLabel },\n detail,\n ),\n );\n try {\n const out = await fetcher<unknown>({ url, method });\n const parsed = zParse<InferOutput<L>>(out, leaf.cfg.outputSchema);\n emit(\n decorateDebugEvent(\n {\n type: 'fetch',\n stage: 'success',\n method,\n url,\n leaf: leafLabel,\n durationMs: Date.now() - startedAt,\n },\n isVerboseDebug\n ? { params: normalizedParams, query: normalizedQuery, output: parsed }\n : undefined,\n ),\n );\n return parsed;\n } catch (error) {\n emit(\n decorateDebugEvent(\n {\n type: 'fetch',\n stage: 'error',\n method,\n url,\n leaf: leafLabel,\n durationMs: Date.now() - startedAt,\n error,\n },\n detail,\n ),\n );\n throw error;\n }\n },\n }, queryClient);\n };\n\n return {\n key,\n invalidate: invalidateExact,\n setData: setData as any,\n useEndpoint,\n } as BuiltForLeaf<L>;\n }\n\n // --- Mutation (POST/PUT/PATCH/DELETE) ---\n const fetchEndpoint: BuiltMutation<L>['fetch'] = async (\n ...tupleWithBody: [...ArgsTuple<L>, InferBody<L>]\n ) => {\n if (tupleWithBody.length === 0) {\n throw new Error('Body is required when invoking a mutation fetch.');\n }\n const bodyIndex = tupleWithBody.length - 1;\n const tuple = tupleWithBody.slice(0, bodyIndex) as ArgsTuple<L>;\n const body = tupleWithBody[bodyIndex] as InferBody<L>;\n const args = extractArgs<L>(tuple);\n const params = (args as any)?.params as InferParams<L> | undefined;\n const query = (args as any)?.query as InferQuery<L> | undefined;\n\n const { url, normalizedQuery, normalizedParams } = buildUrl(leaf, baseUrl, params, query);\n const normalizedBody = zParse<InferBody<L>>(body, leaf.cfg.bodySchema);\n\n // Optional: switch to FormData if your method declares bodyFiles\n const isMultipart = Array.isArray(leaf.cfg.bodyFiles) && leaf.cfg.bodyFiles.length > 0;\n const payload = isMultipart ? toFormData(normalizedBody as any) : normalizedBody;\n\n const startedAt = Date.now();\n const detail = isVerboseDebug\n ? { params: normalizedParams, query: normalizedQuery }\n : undefined;\n emit(\n decorateDebugEvent(\n {\n type: 'fetch',\n stage: 'start',\n method,\n url,\n leaf: leafLabel,\n body: payload,\n },\n detail,\n ),\n );\n try {\n const out = await fetcher<unknown>({ url, method, body: payload });\n const parsed = zParse<InferOutput<L>>(out, leaf.cfg.outputSchema);\n emit(\n decorateDebugEvent(\n {\n type: 'fetch',\n stage: 'success',\n method,\n url,\n leaf: leafLabel,\n durationMs: Date.now() - startedAt,\n },\n isVerboseDebug\n ? { params: normalizedParams, query: normalizedQuery, output: parsed }\n : undefined,\n ),\n );\n return parsed;\n } catch (error) {\n emit(\n decorateDebugEvent(\n {\n type: 'fetch',\n stage: 'error',\n method,\n url,\n leaf: leafLabel,\n durationMs: Date.now() - startedAt,\n body: payload,\n error,\n },\n detail,\n ),\n );\n throw error;\n }\n };\n\n const useEndpoint: BuiltMutation<L>['useEndpoint'] = (...tuple) => {\n emit({ type: 'useEndpoint', leaf: leafLabel, variant: 'mutation' });\n return useMutation<InferOutput<L>, unknown, InferBody<L>, unknown>({\n ...(rqOpts as MutationBuildOptionsFor<L>),\n mutationKey: key(...tuple),\n mutationFn: (body: InferBody<L>) =>\n fetchEndpoint(...([...tuple, body] as [...ArgsTuple<L>, InferBody<L>])),\n }, queryClient);\n };\n\n return {\n key,\n invalidate: invalidateExact,\n setData: setData as any,\n useEndpoint,\n fetch: fetchEndpoint,\n } as BuiltForLeaf<L>;\n }\n\n return {\n queryClient,\n invalidate,\n build: buildInternal as RouteClient<Names>['build'],\n };\n}\n\n// -------------------------------------------------------------------------------------\n// Multipart helper\n// -------------------------------------------------------------------------------------\nfunction toFormData(body: Record<string, any>): FormData {\n const fd = new FormData();\n for (const [k, v] of Object.entries(body ?? {})) {\n if (v == null) continue;\n if (Array.isArray(v)) v.forEach((item, i) => fd.append(`${k}[${i}]`, item as any));\n else fd.append(k, v as any);\n }\n return fd;\n}\n"],"mappings":";;;AASO,IAAM,iBAA0B,OAAU,QAAgC;AAC/E,QAAM,UAAkC,EAAE,GAAI,IAAI,WAAW,CAAC,EAAG;AACjE,QAAM,aAAa,OAAO,aAAa,eAAe,IAAI,gBAAgB;AAC1E,MAAI,CAAC,YAAY;AACf,0DAA4B;AAC5B,8CAAsB;AAAA,EACxB;AAEA,QAAM,MAAM,MAAM,MAAM,IAAI,KAAK;AAAA,IAC/B,QAAQ,IAAI;AAAA,IACZ;AAAA,IACA,MAAM,aAAc,IAAI,OAAe,IAAI,QAAQ,OAAO,SAAY,KAAK,UAAU,IAAI,IAAI;AAAA,EAC/F,CAAC;AAED,QAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,UAAU,KAAK,MAAM,GAAG,GAAG;AACjC,UAAM,IAAI,MAAM,IAAI,IAAI,MAAM,KAAK,IAAI,UAAU,WAAM,OAAO,EAAE;AAAA,EAClE;AAEA,MAAI;AACF,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ACjCA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAEP;AAAA,EAEE;AAAA,EACA;AAAA,OACK;AAwCP,IAAM,UAAU,CAAC,MAAyC,EAAE,YAAY;AAQxE,SAAS,OAAU,OAAgB,QAAqB;AACtD,SAAO,SAAU,OAAO,MAAM,KAAK,IAAW;AAChD;AAOA,SAAS,eAAe,OAA4C;AAClE,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,SAAS,IAAI,gBAAgB;AACnC,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,KAAK,GAAG;AAC1C,QAAI,KAAK,KAAM;AACf,QAAI,MAAM,QAAQ,CAAC,GAAG;AACpB,QAAE,QAAQ,CAAC,MAAM;AACf,YAAI,KAAK,KAAM;AACf,YAAI,OAAO,MAAM,UAAU;AACzB,iBAAO,OAAO,GAAG,KAAK,UAAU,CAAC,CAAC;AAAA,QACpC,OAAO;AACL,iBAAO,OAAO,GAAG,OAAO,CAAC,CAAC;AAAA,QAC5B;AAAA,MACF,CAAC;AACD;AAAA,IACF;AACA,QAAI,OAAO,MAAM,UAAU;AACzB,aAAO,IAAI,GAAG,KAAK,UAAU,CAAC,CAAC;AAC/B;AAAA,IACF;AACA,WAAO,IAAI,GAAG,OAAO,CAAC,CAAC;AAAA,EACzB;AACA,QAAM,IAAI,OAAO,SAAS;AAC1B,SAAO,IAAI,IAAI,CAAC,KAAK;AACvB;AAQA,SAAS,SAAwD,KAAQ,KAAgB;AACvF,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,EAAE,CAAC,GAAG,GAAG,OAAO,GAAG,KAAK,IAAI;AAClC,SAAO;AACT;AAOA,IAAM,uBAAuB,CAAC,MAC5B,KAAK,OAAO,MAAM,YAAY,gBAAgB,IAAK,EAAU,aAAa;AAK5E,IAAM,qBAA6C,CAAC,UAAiC;AACnF,MAAI,OAAO,YAAY,YAAa;AACpC,QAAM,KAAK,QAAQ,SAAS,QAAQ;AACpC,MAAI,KAAK,SAAS,qBAAqB,KAAK;AAC9C;AAEA,IAAM,kBAAmD;AAAA,EACvD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAOA,IAAM,WAAW,MAAM;AAAC;AAExB,SAAS,mBACP,QACA,aACqB;AACrB,QAAM,WAAgC,EAAE,MAAM,UAAU,MAAM,UAAU;AAExE,MAAI,eAAe,YAAY,YAAY,MAAM,cAAc;AAC7D,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,QAAQ;AACX,WAAO;AAAA,EACT;AACA,MAAI,WAAW,QAAQ,WAAW,WAAW;AAC3C,WAAO;AAAA,MACL,MAAM,CAAC,OAAO,SAAS,mBAAmB,OAAO,EAAE,GAAG,OAAO,KAAK,IAAI,KAAK;AAAA,MAC3E,MAAM;AAAA,IACR;AAAA,EACF;AACA,MAAI,WAAW,YAAY;AACzB,WAAO;AAAA,MACL,MAAM,CAAC,OAAO,SAAS,mBAAmB,OAAO,EAAE,GAAG,OAAO,KAAK,IAAI,KAAK;AAAA,MAC3E,MAAM;AAAA,IACR;AAAA,EACF;AACA,MAAI,OAAO,WAAW,YAAY;AAChC,WAAO;AAAA,MACL,MAAM,CAAC,OAAO,SAAS,OAAO,OAAO,EAAE,GAAG,OAAO,KAAK,IAAI,KAAK;AAAA,MAC/D,MAAM;AAAA,IACR;AAAA,EACF;AACA,MAAI,OAAO,WAAW,UAAU;AAC9B,UAAM,UAAU;AAChB,UAAM,UAAU,QAAQ,QAAQ,OAAO;AACvC,UAAM,eAAe,gBAAgB,OAAO,CAAC,SAAS,QAAQ,IAAI,CAAC;AACnE,QAAI,aAAa,WAAW,GAAG;AAC7B,aAAO,EAAE,MAAM,UAAU,MAAM,UAAU,aAAa,UAAU;AAAA,IAClE;AACA,UAAM,YAAY,IAAI,IAAmC,YAAY;AACrE,UAAM,UACJ,QAAQ,QAAQ,QAAQ,KAAK,SAAS,IAAI,IAAI,IAAW,QAAQ,IAAI,IAAI;AAC3E,UAAM,SAAS,QAAQ,UAAU;AACjC,UAAM,OAAoC,CAAC,OAAO,SAAS;AACzD,UAAI,CAAC,UAAU,IAAI,MAAM,IAAI,EAAG;AAChC,UAAI,SAAS;AACX,YAAI,CAAC,QAAQ,CAAC,QAAQ,IAAI,IAAI,EAAG;AAAA,MACnC;AACA,aAAO,OAAO,EAAE,GAAG,OAAO,KAAK,IAAI,KAAK;AAAA,IAC1C;AACA,WAAO,EAAE,MAAM,MAAM,UAAU,aAAa,UAAU;AAAA,EACxD;AAEA,SAAO;AACT;AAQA,SAAS,YAA+B,MAA4C;AAElF,SAAQ,KAA0B,CAAC;AACrC;AAUA,SAAS,SACP,MACA,SACA,QACA,OACA;AACA,QAAM,mBAAmB,OAAuB,QAAQ,KAAK,IAAI,YAAY;AAC7E,QAAM,kBAAkB,OAAsB,OAAO,KAAK,IAAI,WAAW;AACzE,QAAM,OAAO,YAAuB,KAAK,MAAO,oBAAoB,CAAC,CAAS;AAC9E,QAAM,MAAM,GAAG,WAAW,EAAE,GAAG,IAAI,GAAG,eAAe,eAAsB,CAAC;AAC5E,SAAO,EAAE,KAAK,iBAAiB,iBAAiB;AAClD;AAUO,SAAS,kBACd,MACoB;AACpB,QAAM,cAAc,KAAK;AACzB,QAAM,UAAU,KAAK,WAAW;AAChC,QAAM,UAAU,KAAK;AACrB,QAAM,cAAc,KAAK,eAAe;AACxC,QAAM,gBAAgB,KAAK,iBAAiB;AAC5C,QAAM,cACJ,KAAK,gBACJ,OAAO,YAAY,eAAe,QAAQ,MAAM,QAAQ,IAAI,WAAW;AAC1E,QAAM,EAAE,MAAM,WAAW,MAAM,UAAU,IAAI,mBAA0B,KAAK,OAAO,WAAW;AAC9F,QAAM,iBAAiB,cAAc;AACrC,QAAM,qBAAqB,CACzB,OACA,YAC0B;AAC1B,QAAI,CAAC,kBAAkB,CAAC,QAAS,QAAO;AACxC,WAAO,EAAE,GAAG,OAAO,GAAG,QAAQ;AAAA,EAChC;AAOA,iBAAe,WAAW,QAAkB,QAAQ,OAAO;AACzD,UAAM,WAAW;AACjB,UAAM,YAAY,kBAAkB,EAAE,UAAU,MAAM,CAAC;AACvD,cAAU,EAAE,MAAM,cAAc,KAAK,UAAU,MAAM,CAAC;AAAA,EACxD;AAQA,WAAS,cACP,MACA,QACA,MACiB;AACjB,UAAM,QAAQ,KAAK,WAAW;AAC9B,UAAM,SAAS,CAAC,CAAC,KAAK,IAAI;AAC1B,UAAM,SAAS,QAAQ,KAAK,MAAM;AAClC,UAAM,YAAY,GAAG,KAAK,OAAO,YAAY,CAAC,IAAI,OAAO,KAAK,IAAI,CAAC;AACnE,UAAM,YAAY,MAAM;AACxB,UAAM,OAAO,CAAC,UAAiC,UAAU,OAAO,SAAS;AACzE,SAAK,EAAE,MAAM,SAAS,MAAM,UAAU,CAAC;AAGvC,UAAM,MAAM,IAAI,UAAkC;AAChD,YAAM,IAAI,YAAe,KAAK;AAC9B,YAAM,SAAU,GAAW;AAC3B,YAAM,QAAS,GAAW;AAC1B,YAAM,UAAU,SAAS,SAAS,SAAS,OAAc,WAAW,IAAK;AACzE,aAAO,cAAc,EAAE,MAAM,QAAuB,OAAO,QAAQ,CAAC;AAAA,IACtE;AAMA,UAAM,kBAAkB,UAAU,UAAwB;AACxD,YAAM,WAAW,IAAI,GAAG,KAAK;AAC7B,YAAM,YAAY,kBAAkB,EAAE,UAAU,OAAO,KAAK,CAAC;AAC7D,WAAK,EAAE,MAAM,cAAc,KAAK,UAAU,OAAO,KAAK,CAAC;AAAA,IACzD;AAMA,UAAM,UAAU,IAAI,SAAyD;AAC3E,YAAM,CAAC,SAAS,GAAG,IAAI,IAAI;AAC3B,YAAM,IAAI,IAAI,GAAI,IAAqB;AACvC,UAAI,SAAS,QAAQ;AACnB,oBAAY;AAAA,UAAuD;AAAA,UAAG,CAAC,SACrE,OAAO,YAAY,aAAc,QAAgB,IAAI,IAAK;AAAA,QAC5D;AAAA,MACF,OAAO;AACL,oBAAY;AAAA,UAAyC;AAAA,UAAG,CAAC,SACvD,OAAO,YAAY,aAAc,QAAgB,IAAI,IAAK;AAAA,QAC5D;AAAA,MACF;AACA,WAAK,EAAE,MAAM,WAAW,KAAK,EAAE,CAAC;AAAA,IAClC;AAGA,QAAI,SAAS,QAAQ;AACnB,YAAMA,eAA+C,IAAI,UAAU;AACjE,aAAK,EAAE,MAAM,eAAe,MAAM,WAAW,SAAS,cAAc,CAAC;AACrE,cAAM,IAAI,YAAe,KAAK;AAC9B,cAAM,SAAU,GAAW;AAC3B,cAAM,QAAS,GAAW;AAG1B,cAAM,EAAE,iBAAiB,iBAAiB,IAAI,SAAS,MAAM,SAAS,QAAQ,KAAK;AACnF,eAAO,iBAML;AAAA,UACA,GAAI;AAAA,UACJ,UAAU,IAAI,GAAG,KAAK;AAAA,UACtB,kBAAkB;AAAA,UAClB,kBAAkB,CAAC,aAAa,cAAc,QAAQ;AAAA,UACtD,iBAAiB;AAAA,UACjB,SAAS,OAAO,EAAE,UAAU,MAAM;AAChC,kBAAM,YAAY;AAAA,cAChB,GAAI;AAAA,cACJ,GAAI,YAAY,EAAE,CAAC,WAAW,GAAG,UAAU,IAAI,CAAC;AAAA,YAClD;AACA,kBAAM,EAAE,IAAI,IAAI,SAAS,MAAM,SAAS,QAAQ,SAAS;AACzD,kBAAM,YAAY,KAAK,IAAI;AAC3B,kBAAM,SAAS,iBAAiB,EAAE,QAAQ,kBAAkB,OAAO,UAAU,IAAI;AACjF;AAAA,cACE;AAAA,gBACE,EAAE,MAAM,SAAS,OAAO,SAAS,QAAQ,KAAK,MAAM,UAAU;AAAA,gBAC9D;AAAA,cACF;AAAA,YACF;AACA,gBAAI;AACF,oBAAM,MAAM,MAAM,QAAiB,EAAE,KAAK,OAAO,CAAC;AAClD,oBAAM,SAAS,OAAuB,KAAK,KAAK,IAAI,YAAY;AAChE;AAAA,gBACE;AAAA,kBACE;AAAA,oBACE,MAAM;AAAA,oBACN,OAAO;AAAA,oBACP;AAAA,oBACA;AAAA,oBACA,MAAM;AAAA,oBACN,YAAY,KAAK,IAAI,IAAI;AAAA,kBAC3B;AAAA,kBACA,iBAAiB,EAAE,QAAQ,kBAAkB,OAAO,WAAW,QAAQ,OAAO,IAAI;AAAA,gBACpF;AAAA,cACF;AACA,qBAAO;AAAA,YACT,SAAS,OAAO;AACd;AAAA,gBACE;AAAA,kBACE;AAAA,oBACE,MAAM;AAAA,oBACN,OAAO;AAAA,oBACP;AAAA,oBACA;AAAA,oBACA,MAAM;AAAA,oBACN,YAAY,KAAK,IAAI,IAAI;AAAA,oBACzB;AAAA,kBACF;AAAA,kBACA;AAAA,gBACF;AAAA,cACF;AACA,oBAAM;AAAA,YACR;AAAA,UACF;AAAA;AAAA,QAEF,GAAG,WAAW;AAAA,MAChB;AAEA,aAAO;AAAA,QACL;AAAA,QACA,YAAY;AAAA,QACZ;AAAA,QACA,aAAAA;AAAA,MACF;AAAA,IACF;AAEA,QAAI,OAAO;AACT,YAAMA,eAA4C,IAAI,UAAU;AAC9D,aAAK,EAAE,MAAM,eAAe,MAAM,WAAW,SAAS,MAAM,CAAC;AAC7D,cAAM,IAAI,YAAe,KAAK;AAC9B,cAAM,SAAU,GAAW;AAC3B,cAAM,QAAS,GAAW;AAE1B,cAAM,EAAE,KAAK,iBAAiB,iBAAiB,IAAI,SAAS,MAAM,SAAS,QAAQ,KAAK;AACxF,eAAO,SAA4D;AAAA,UACjE,GAAI;AAAA,UACJ,UAAU,IAAI,GAAG,KAAK;AAAA,UACtB,iBAAiB;AAAA,UACjB,SAAS,YAAY;AACnB,kBAAM,YAAY,KAAK,IAAI;AAC3B,kBAAM,SAAS,iBACX,EAAE,QAAQ,kBAAkB,OAAO,gBAAgB,IACnD;AACJ;AAAA,cACE;AAAA,gBACE,EAAE,MAAM,SAAS,OAAO,SAAS,QAAQ,KAAK,MAAM,UAAU;AAAA,gBAC9D;AAAA,cACF;AAAA,YACF;AACA,gBAAI;AACF,oBAAM,MAAM,MAAM,QAAiB,EAAE,KAAK,OAAO,CAAC;AAClD,oBAAM,SAAS,OAAuB,KAAK,KAAK,IAAI,YAAY;AAChE;AAAA,gBACE;AAAA,kBACE;AAAA,oBACE,MAAM;AAAA,oBACN,OAAO;AAAA,oBACP;AAAA,oBACA;AAAA,oBACA,MAAM;AAAA,oBACN,YAAY,KAAK,IAAI,IAAI;AAAA,kBAC3B;AAAA,kBACA,iBACI,EAAE,QAAQ,kBAAkB,OAAO,iBAAiB,QAAQ,OAAO,IACnE;AAAA,gBACN;AAAA,cACF;AACA,qBAAO;AAAA,YACT,SAAS,OAAO;AACd;AAAA,gBACE;AAAA,kBACE;AAAA,oBACE,MAAM;AAAA,oBACN,OAAO;AAAA,oBACP;AAAA,oBACA;AAAA,oBACA,MAAM;AAAA,oBACN,YAAY,KAAK,IAAI,IAAI;AAAA,oBACzB;AAAA,kBACF;AAAA,kBACA;AAAA,gBACF;AAAA,cACF;AACA,oBAAM;AAAA,YACR;AAAA,UACF;AAAA,QACF,GAAG,WAAW;AAAA,MAChB;AAEA,aAAO;AAAA,QACL;AAAA,QACA,YAAY;AAAA,QACZ;AAAA,QACA,aAAAA;AAAA,MACF;AAAA,IACF;AAGA,UAAM,gBAA2C,UAC5C,kBACA;AACH,UAAI,cAAc,WAAW,GAAG;AAC9B,cAAM,IAAI,MAAM,kDAAkD;AAAA,MACpE;AACA,YAAM,YAAY,cAAc,SAAS;AACzC,YAAM,QAAQ,cAAc,MAAM,GAAG,SAAS;AAC9C,YAAM,OAAO,cAAc,SAAS;AACpC,YAAM,OAAO,YAAe,KAAK;AACjC,YAAM,SAAU,MAAc;AAC9B,YAAM,QAAS,MAAc;AAE7B,YAAM,EAAE,KAAK,iBAAiB,iBAAiB,IAAI,SAAS,MAAM,SAAS,QAAQ,KAAK;AACxF,YAAM,iBAAiB,OAAqB,MAAM,KAAK,IAAI,UAAU;AAGrE,YAAM,cAAc,MAAM,QAAQ,KAAK,IAAI,SAAS,KAAK,KAAK,IAAI,UAAU,SAAS;AACrF,YAAM,UAAU,cAAc,WAAW,cAAqB,IAAI;AAElE,YAAM,YAAY,KAAK,IAAI;AAC3B,YAAM,SAAS,iBACX,EAAE,QAAQ,kBAAkB,OAAO,gBAAgB,IACnD;AACJ;AAAA,QACE;AAAA,UACE;AAAA,YACE,MAAM;AAAA,YACN,OAAO;AAAA,YACP;AAAA,YACA;AAAA,YACA,MAAM;AAAA,YACN,MAAM;AAAA,UACR;AAAA,UACA;AAAA,QACF;AAAA,MACF;AACA,UAAI;AACF,cAAM,MAAM,MAAM,QAAiB,EAAE,KAAK,QAAQ,MAAM,QAAQ,CAAC;AACjE,cAAM,SAAS,OAAuB,KAAK,KAAK,IAAI,YAAY;AAChE;AAAA,UACE;AAAA,YACE;AAAA,cACE,MAAM;AAAA,cACN,OAAO;AAAA,cACP;AAAA,cACA;AAAA,cACA,MAAM;AAAA,cACN,YAAY,KAAK,IAAI,IAAI;AAAA,YAC3B;AAAA,YACA,iBACI,EAAE,QAAQ,kBAAkB,OAAO,iBAAiB,QAAQ,OAAO,IACnE;AAAA,UACN;AAAA,QACF;AACA,eAAO;AAAA,MACT,SAAS,OAAO;AACd;AAAA,UACE;AAAA,YACE;AAAA,cACE,MAAM;AAAA,cACN,OAAO;AAAA,cACP;AAAA,cACA;AAAA,cACA,MAAM;AAAA,cACN,YAAY,KAAK,IAAI,IAAI;AAAA,cACzB,MAAM;AAAA,cACN;AAAA,YACF;AAAA,YACA;AAAA,UACF;AAAA,QACF;AACA,cAAM;AAAA,MACR;AAAA,IACF;AAEA,UAAM,cAA+C,IAAI,UAAU;AACjE,WAAK,EAAE,MAAM,eAAe,MAAM,WAAW,SAAS,WAAW,CAAC;AAClE,aAAO,YAA4D;AAAA,QACjE,GAAI;AAAA,QACJ,aAAa,IAAI,GAAG,KAAK;AAAA,QACzB,YAAY,CAAC,SACX,cAAc,GAAI,CAAC,GAAG,OAAO,IAAI,CAAqC;AAAA,MAC1E,GAAG,WAAW;AAAA,IAChB;AAEA,WAAO;AAAA,MACL;AAAA,MACA,YAAY;AAAA,MACZ;AAAA,MACA;AAAA,MACA,OAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,OAAO;AAAA,EACT;AACF;AAKA,SAAS,WAAW,MAAqC;AACvD,QAAM,KAAK,IAAI,SAAS;AACxB,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,QAAQ,CAAC,CAAC,GAAG;AAC/C,QAAI,KAAK,KAAM;AACf,QAAI,MAAM,QAAQ,CAAC,EAAG,GAAE,QAAQ,CAAC,MAAM,MAAM,GAAG,OAAO,GAAG,CAAC,IAAI,CAAC,KAAK,IAAW,CAAC;AAAA,QAC5E,IAAG,OAAO,GAAG,CAAQ;AAAA,EAC5B;AACA,SAAO;AACT;","names":["useEndpoint"]}
|
|
@@ -4,4 +4,4 @@ import type { RouteClient, RouteClientOptions } from './routesV3.client.types';
|
|
|
4
4
|
* @param opts Route client configuration (query client, fetcher overrides, etc).
|
|
5
5
|
* @returns Object that can build endpoint hooks/mutations from leaves.
|
|
6
6
|
*/
|
|
7
|
-
export declare function createRouteClient(opts: RouteClientOptions): RouteClient
|
|
7
|
+
export declare function createRouteClient<Names extends string = string>(opts: RouteClientOptions<Names>): RouteClient<Names>;
|
|
@@ -19,8 +19,12 @@ export type FetchInput = {
|
|
|
19
19
|
export type Fetcher = <T>(req: FetchInput) => Promise<T>;
|
|
20
20
|
/** Debug verbosity levels supported by the client. */
|
|
21
21
|
export type RouteClientDebugMode = 'minimal' | 'complete';
|
|
22
|
+
type RouteClientDebugEventBase = {
|
|
23
|
+
/** Optional logical name supplied when building this endpoint. */
|
|
24
|
+
name?: string;
|
|
25
|
+
};
|
|
22
26
|
/** Discrete debug events emitted by the client internals. */
|
|
23
|
-
export type RouteClientDebugEvent = {
|
|
27
|
+
export type RouteClientDebugEvent = (RouteClientDebugEventBase & {
|
|
24
28
|
type: 'fetch';
|
|
25
29
|
stage: 'start' | 'success' | 'error';
|
|
26
30
|
method: Uppercase<HttpMethod>;
|
|
@@ -32,27 +36,38 @@ export type RouteClientDebugEvent = {
|
|
|
32
36
|
query?: unknown;
|
|
33
37
|
output?: unknown;
|
|
34
38
|
error?: unknown;
|
|
35
|
-
} | {
|
|
39
|
+
}) | (RouteClientDebugEventBase & {
|
|
36
40
|
type: 'invalidate';
|
|
37
41
|
key: QueryKey;
|
|
38
42
|
exact: boolean;
|
|
39
|
-
} | {
|
|
43
|
+
}) | (RouteClientDebugEventBase & {
|
|
40
44
|
type: 'setData';
|
|
41
45
|
key: QueryKey;
|
|
42
|
-
} | {
|
|
43
|
-
type: 'build'
|
|
46
|
+
}) | (RouteClientDebugEventBase & {
|
|
47
|
+
type: 'build';
|
|
44
48
|
leaf: string;
|
|
45
|
-
}
|
|
49
|
+
}) | (RouteClientDebugEventBase & {
|
|
50
|
+
type: 'useEndpoint';
|
|
51
|
+
leaf: string;
|
|
52
|
+
variant: 'get' | 'infiniteGet' | 'mutation';
|
|
53
|
+
});
|
|
46
54
|
/** Logger signature invoked when debug logging is enabled. */
|
|
47
55
|
export type RouteClientDebugLogger = (event: RouteClientDebugEvent) => void;
|
|
48
|
-
/** Toggle
|
|
49
|
-
export type
|
|
50
|
-
|
|
56
|
+
/** Toggle-specific configuration controlling which events log and whether verbose details are included. */
|
|
57
|
+
export type RouteClientDebugToggleOptions<Names extends string = string> = Partial<Record<RouteClientDebugEvent['type'], boolean>> & {
|
|
58
|
+
/** When true, include verbose metadata (same as `mode === 'complete'`). */
|
|
59
|
+
verbose?: boolean;
|
|
60
|
+
/** Optional logger override used instead of the default console logger. */
|
|
51
61
|
logger?: RouteClientDebugLogger;
|
|
52
|
-
|
|
62
|
+
/** Limit logs to endpoints whose debug name is contained in this allow-list. */
|
|
63
|
+
only?: Names[];
|
|
53
64
|
};
|
|
65
|
+
/** Toggle or customize debug logging produced by the client. */
|
|
66
|
+
export type RouteClientDebugOptions<Names extends string = string> = boolean | RouteClientDebugLogger | RouteClientDebugMode | RouteClientDebugToggleOptions<Names>;
|
|
67
|
+
/** Optional runtime environment; when set to 'production' logging is disabled. */
|
|
68
|
+
export type RouteClientEnvironment = 'development' | 'production' | (string & {});
|
|
54
69
|
/** Configuration passed to `createRouteClient`. */
|
|
55
|
-
export type RouteClientOptions = {
|
|
70
|
+
export type RouteClientOptions<Names extends string = string> = {
|
|
56
71
|
/** Base URL prepended to every route (optional). */
|
|
57
72
|
baseUrl: string;
|
|
58
73
|
/** Custom fetch implementation; defaults to `defaultFetcher`. */
|
|
@@ -64,12 +79,22 @@ export type RouteClientOptions = {
|
|
|
64
79
|
/** Function that extracts the next cursor from an infinite query page. */
|
|
65
80
|
getNextCursor?: (page: unknown) => Cursor;
|
|
66
81
|
/** Optional debug logger for verbose insights (supports minimal/complete modes). */
|
|
67
|
-
debug?: RouteClientDebugOptions
|
|
82
|
+
debug?: RouteClientDebugOptions<Names>;
|
|
83
|
+
/** Optional environment hint used to silence logging in production. */
|
|
84
|
+
environment?: RouteClientEnvironment;
|
|
68
85
|
};
|
|
69
86
|
type OutputOf<L extends AnyLeaf> = InferOutput<L>;
|
|
70
87
|
type ParamsOf<L extends AnyLeaf> = InferParams<L>;
|
|
71
88
|
type QueryOf<L extends AnyLeaf> = InferQuery<L>;
|
|
72
89
|
type BodyOf<L extends AnyLeaf> = InferBody<L>;
|
|
90
|
+
/** Variadic args consumed by the direct fetch helper: optional args + body. */
|
|
91
|
+
export type MutationFetchArgs<L extends AnyLeaf> = [...ArgsTuple<L>, BodyOf<L>];
|
|
92
|
+
/** Signature for the fetch helper returned by mutation builds. */
|
|
93
|
+
export type MutationFetcher<L extends AnyLeaf> = (...args: MutationFetchArgs<L>) => Promise<OutputOf<L>>;
|
|
94
|
+
/** Optional metadata provided when building a helper (used for debug filtering). */
|
|
95
|
+
export type BuildMeta<Names extends string = string> = {
|
|
96
|
+
name?: Names;
|
|
97
|
+
};
|
|
73
98
|
/** Object shape the user passes to hooks and helpers (params/query are optional). */
|
|
74
99
|
export type ArgsFor<L extends AnyLeaf> = (ParamsOf<L> extends never ? {} : {
|
|
75
100
|
params: ParamsOf<L>;
|
|
@@ -129,11 +154,16 @@ export type BuiltMutation<L extends AnyLeaf> = BuiltCommon<L> & {
|
|
|
129
154
|
* @param args Optional params/query tuple for the leaf.
|
|
130
155
|
*/
|
|
131
156
|
useEndpoint: (...args: ArgsTuple<L>) => UseMutationResult<OutputOf<L>, unknown, BodyOf<L>, unknown>;
|
|
157
|
+
/**
|
|
158
|
+
* Direct fetch helper that bypasses React Query, useful for server actions or scripts.
|
|
159
|
+
* Pass params/query first (if required), followed by the body payload.
|
|
160
|
+
*/
|
|
161
|
+
fetch: MutationFetcher<L>;
|
|
132
162
|
};
|
|
133
163
|
/** Type-safe union that resolves to the correct built helper for a leaf. */
|
|
134
164
|
export type BuiltForLeaf<L extends AnyLeaf> = L['method'] extends 'get' ? L['cfg']['feed'] extends true ? BuiltInfinite<L> : BuiltQuery<L> : BuiltMutation<L>;
|
|
135
165
|
/** Public surface returned by `createRouteClient`. */
|
|
136
|
-
export type RouteClient = {
|
|
166
|
+
export type RouteClient<Names extends string = string> = {
|
|
137
167
|
/**
|
|
138
168
|
* Invalidate by a key prefix (e.g., ['get','api','users']).
|
|
139
169
|
* @param prefix Key parts shared by matching endpoints.
|
|
@@ -147,16 +177,16 @@ export type RouteClient = {
|
|
|
147
177
|
cfg: {
|
|
148
178
|
feed: true;
|
|
149
179
|
};
|
|
150
|
-
}>(leaf: L, opts?: InfiniteBuildOptionsFor<L>): BuiltInfinite<L>;
|
|
180
|
+
}>(leaf: L, opts?: InfiniteBuildOptionsFor<L>, meta?: BuildMeta<Names>): BuiltInfinite<L>;
|
|
151
181
|
<L extends AnyLeaf & {
|
|
152
182
|
method: 'get';
|
|
153
183
|
cfg: {
|
|
154
184
|
feed?: false | undefined;
|
|
155
185
|
};
|
|
156
|
-
}>(leaf: L, opts?: QueryBuildOptionsFor<L>): BuiltQuery<L>;
|
|
186
|
+
}>(leaf: L, opts?: QueryBuildOptionsFor<L>, meta?: BuildMeta<Names>): BuiltQuery<L>;
|
|
157
187
|
<L extends AnyLeaf & {
|
|
158
188
|
method: Exclude<HttpMethod, 'get'>;
|
|
159
|
-
}>(leaf: L, opts?: MutationBuildOptionsFor<L>): BuiltMutation<L>;
|
|
189
|
+
}>(leaf: L, opts?: MutationBuildOptionsFor<L>, meta?: BuildMeta<Names>): BuiltMutation<L>;
|
|
160
190
|
};
|
|
161
191
|
/** Underlying React Query client (exposed for advanced scenarios). */
|
|
162
192
|
queryClient: QueryClient;
|