@orpc/tanstack-query 1.14.9 → 1.14.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,35 +1,55 @@
1
- import { stringifyJSON, isAsyncIteratorObject, toArray, get } from '@orpc/shared';
1
+ import { resolveBasePathMeta, getRouterContract, ProcedureContract } from '@orpc/contract';
2
+ import { sortPlugins, stringifyJSON, resolveMaybeOptionalOptions, intercept, toArray, isAsyncIteratorObject, isTypescriptObject, bindMethods, getOrBind, get } from '@orpc/shared';
3
+ import { RECURSIVE_CLIENT_UNWRAP_KEYS } from '@orpc/client';
2
4
  import { skipToken } from '@tanstack/query-core';
3
5
 
4
- function generateOperationKey(path, state = {}) {
5
- return [path, {
6
- ...state.input !== void 0 ? { input: state.input } : {},
7
- ...state.type !== void 0 ? { type: state.type } : {},
8
- ...state.fnOptions !== void 0 ? { fnOptions: state.fnOptions } : {}
9
- }];
6
+ class CompositeRouterUtilsPlugin {
7
+ constructor(plugins = []) {
8
+ this.plugins = plugins;
9
+ this.plugins = sortPlugins(plugins);
10
+ }
11
+ name = "~composite";
12
+ init(options) {
13
+ for (const plugin of this.plugins) {
14
+ if (plugin.init) {
15
+ options = plugin.init(options);
16
+ }
17
+ }
18
+ return options;
19
+ }
20
+ initProcedureOptions(path, options) {
21
+ for (const plugin of this.plugins) {
22
+ if (plugin.initProcedureOptions) {
23
+ options = plugin.initProcedureOptions(path, options);
24
+ }
25
+ }
26
+ return options;
27
+ }
10
28
  }
11
29
 
12
- function createGeneralUtils(path) {
13
- return {
14
- key(options) {
15
- return generateOperationKey(path, options);
30
+ function generateOperationKey(path, options = {}) {
31
+ return [
32
+ ...options.prefix !== void 0 ? [options.prefix] : [],
33
+ path,
34
+ {
35
+ ...options.input !== void 0 ? { input: options.input } : {},
36
+ ...options.type !== void 0 ? { type: options.type } : {},
37
+ ...options.fnOptions !== void 0 ? { fnOptions: options.fnOptions } : {}
16
38
  }
17
- };
39
+ ];
18
40
  }
19
41
 
20
- function experimental_liveQuery(queryFn) {
42
+ function liveQuery(queryFn) {
21
43
  return async (context) => {
22
44
  const stream = await queryFn(context);
23
45
  let last;
24
46
  for await (const chunk of stream) {
25
- if (context.signal.aborted) {
26
- throw context.signal.reason;
27
- }
47
+ context.signal?.throwIfAborted();
28
48
  last = { chunk };
29
49
  context.client.setQueryData(context.queryKey, chunk);
30
50
  }
31
51
  if (!last) {
32
- throw new Error(
52
+ throw new TypeError(
33
53
  `Live query for ${stringifyJSON(context.queryKey)} did not yield any data. Ensure the query function returns an AsyncIterable with at least one chunk.`
34
54
  );
35
55
  }
@@ -37,7 +57,20 @@ function experimental_liveQuery(queryFn) {
37
57
  };
38
58
  }
39
59
 
40
- function experimental_serializableStreamedQuery(queryFn, { refetchMode = "reset", maxChunks = Number.POSITIVE_INFINITY } = {}) {
60
+ class SharedUtils {
61
+ constructor(path, options) {
62
+ this.path = path;
63
+ this.options = options;
64
+ }
65
+ /**
66
+ * Generate a **partial matching** key for actions like revalidating queries, checking mutation status, etc.
67
+ */
68
+ key(options = {}) {
69
+ return generateOperationKey(this.path, { ...options, prefix: this.options.prefix });
70
+ }
71
+ }
72
+
73
+ function serializableStreamedQuery(queryFn, { refetchMode = "reset", maxChunks = Number.POSITIVE_INFINITY } = {}) {
41
74
  return async (context) => {
42
75
  const query = context.client.getQueryCache().find({ queryKey: context.queryKey, exact: true });
43
76
  const hasPreviousData = !!query && query.state.data !== void 0;
@@ -93,194 +126,430 @@ function limitArraySize(items, maxSize) {
93
126
  return items.slice(items.length - maxSize);
94
127
  }
95
128
 
96
- const OPERATION_CONTEXT_SYMBOL = Symbol("ORPC_OPERATION_CONTEXT");
129
+ const OPERATION_CONTEXT_SYMBOL = Symbol.for("ORPC_TANSTACK_QUERY_OPERATION_CONTEXT");
97
130
 
98
- function createProcedureUtils(client, options) {
99
- const utils = {
100
- call: client,
101
- queryKey(...[optionsIn = {}]) {
102
- optionsIn = { ...options.experimental_defaults?.queryKey, ...optionsIn };
103
- const queryKey = optionsIn.queryKey ?? generateOperationKey(options.path, { type: "query", input: optionsIn.input });
104
- return queryKey;
105
- },
106
- queryOptions(...[optionsIn = {}]) {
107
- optionsIn = { ...options.experimental_defaults?.queryOptions, ...optionsIn };
108
- const queryKey = utils.queryKey(optionsIn);
109
- return {
110
- queryFn: ({ signal }) => {
111
- if (optionsIn.input === skipToken) {
112
- throw new Error("queryFn should not be called with skipToken used as input");
113
- }
114
- return client(optionsIn.input, {
115
- signal,
131
+ const PROCEDURE_UTILS_INTERCEPTOR_KEYS = [
132
+ "queryInterceptors",
133
+ "streamedInterceptors",
134
+ "liveInterceptors",
135
+ "infiniteInterceptors",
136
+ "mutationInterceptors"
137
+ ];
138
+ const PROCEDURE_UTILS_MODIFIER_KEYS = [
139
+ "queryKey",
140
+ "queryOptions",
141
+ "streamedKey",
142
+ "streamedOptions",
143
+ "liveKey",
144
+ "liveOptions",
145
+ "infiniteKey",
146
+ "infiniteOptions",
147
+ "mutationKey",
148
+ "mutationOptions"
149
+ ];
150
+ function isProcedureUtilsOptions(value) {
151
+ if (!isTypescriptObject(value)) {
152
+ return false;
153
+ }
154
+ for (const key in value) {
155
+ if (value[key] === void 0) {
156
+ continue;
157
+ }
158
+ if (key === "prefix") {
159
+ if (typeof value[key] !== "string") {
160
+ return false;
161
+ }
162
+ } else if (PROCEDURE_UTILS_INTERCEPTOR_KEYS.includes(key)) {
163
+ if (!Array.isArray(value[key]) || value[key].some((i) => typeof i !== "function")) {
164
+ return false;
165
+ }
166
+ } else if (PROCEDURE_UTILS_MODIFIER_KEYS.includes(key)) {
167
+ if (!isTypescriptObject(value[key])) {
168
+ return false;
169
+ }
170
+ }
171
+ }
172
+ return true;
173
+ }
174
+ function mergeProcedureUtilsModifier(base, override) {
175
+ if (typeof base !== "function" && typeof override !== "function") {
176
+ return { ...base, ...override };
177
+ }
178
+ const applyBase = typeof base === "function" ? base : (options) => ({ ...base, ...options });
179
+ const applyOverride = typeof override === "function" ? override : (options) => ({ ...override, ...options });
180
+ return (options) => applyOverride(applyBase(options));
181
+ }
182
+ function mergeProcedureUtilsOptions(base, override) {
183
+ const merged = { ...base, ...override };
184
+ for (const key of PROCEDURE_UTILS_INTERCEPTOR_KEYS) {
185
+ const baseValue = base[key];
186
+ const overrideValue = override[key];
187
+ if (baseValue && overrideValue) {
188
+ merged[key] = [...baseValue, ...overrideValue];
189
+ }
190
+ }
191
+ for (const key of PROCEDURE_UTILS_MODIFIER_KEYS) {
192
+ const baseValue = base[key];
193
+ const overrideValue = override[key];
194
+ if (baseValue && overrideValue) {
195
+ merged[key] = mergeProcedureUtilsModifier(baseValue, overrideValue);
196
+ }
197
+ }
198
+ return merged;
199
+ }
200
+ class ProcedureUtils extends SharedUtils {
201
+ /**
202
+ * Calling corresponding procedure client
203
+ */
204
+ call;
205
+ constructor(path, client, options = {}) {
206
+ super(path, options);
207
+ this.call = client;
208
+ }
209
+ /**
210
+ * Generate a **full matching** key for [Query Options](https://orpc.dev/docs/integrations/tanstack-query#query-options).
211
+ */
212
+ queryKey(...rest) {
213
+ let optionsIn = resolveMaybeOptionalOptions(rest);
214
+ if (typeof this.options.queryKey === "function") {
215
+ optionsIn = this.options.queryKey(optionsIn);
216
+ } else if (this.options.queryKey) {
217
+ optionsIn = { ...this.options.queryKey, ...optionsIn };
218
+ }
219
+ const queryKey = optionsIn.queryKey ?? generateOperationKey(this.path, { prefix: this.options.prefix, type: "query", input: optionsIn.input });
220
+ return queryKey;
221
+ }
222
+ /**
223
+ * Generate options used for useQuery/useSuspenseQuery/prefetchQuery/...
224
+ */
225
+ queryOptions(...rest) {
226
+ let optionsIn = resolveMaybeOptionalOptions(rest);
227
+ if (typeof this.options.queryOptions === "function") {
228
+ optionsIn = this.options.queryOptions(optionsIn);
229
+ } else if (this.options.queryOptions) {
230
+ optionsIn = { ...this.options.queryOptions, ...optionsIn };
231
+ }
232
+ const queryKey = this.queryKey(optionsIn);
233
+ return {
234
+ ...optionsIn,
235
+ queryKey,
236
+ queryFn: (fnContext) => {
237
+ return intercept(
238
+ this.options.queryInterceptors,
239
+ {
240
+ path: this.path,
116
241
  context: {
117
242
  [OPERATION_CONTEXT_SYMBOL]: {
118
243
  key: queryKey,
119
244
  type: "query"
120
245
  },
121
246
  ...optionsIn.context
247
+ },
248
+ input: optionsIn.input,
249
+ fnContext
250
+ },
251
+ ({ context, input, fnContext: fnContext2 }) => {
252
+ if (input === skipToken || optionsIn.queryFn === skipToken) {
253
+ throw new Error("queryFn should not be called when skipToken used for skipping");
122
254
  }
123
- });
124
- },
125
- ...optionsIn.input === skipToken ? { enabled: false } : {},
126
- ...optionsIn,
127
- queryKey
128
- };
129
- },
130
- experimental_streamedKey(...[optionsIn = {}]) {
131
- optionsIn = { ...options.experimental_defaults?.experimental_streamedKey, ...optionsIn };
132
- const queryKey = optionsIn.queryKey ?? generateOperationKey(options.path, { type: "streamed", input: optionsIn.input, fnOptions: optionsIn.queryFnOptions });
133
- return queryKey;
134
- },
135
- experimental_streamedOptions(...[optionsIn = {}]) {
136
- optionsIn = { ...options.experimental_defaults?.experimental_streamedOptions, ...optionsIn };
137
- const queryKey = utils.experimental_streamedKey(optionsIn);
138
- return {
139
- queryFn: experimental_serializableStreamedQuery(
140
- async ({ signal }) => {
141
- if (optionsIn.input === skipToken) {
142
- throw new Error("queryFn should not be called with skipToken used as input");
143
- }
144
- const output = await client(optionsIn.input, {
145
- signal,
146
- context: {
147
- [OPERATION_CONTEXT_SYMBOL]: {
148
- key: queryKey,
149
- type: "streamed"
150
- },
151
- ...optionsIn.context
152
- }
153
- });
154
- if (!isAsyncIteratorObject(output)) {
155
- throw new Error("streamedQuery requires an event iterator output");
255
+ if (optionsIn.queryFn) {
256
+ return optionsIn.queryFn(fnContext2);
156
257
  }
157
- return output;
258
+ return this.call(input, { signal: fnContext2.signal, context });
259
+ }
260
+ );
261
+ },
262
+ ...optionsIn.input === skipToken || optionsIn.queryFn === skipToken ? { enabled: false } : {}
263
+ };
264
+ }
265
+ /**
266
+ * Generate a **full matching** key for [Streamed Query Options](https://orpc.dev/docs/integrations/tanstack-query#streamed-query-options).
267
+ */
268
+ streamedKey(...rest) {
269
+ let optionsIn = resolveMaybeOptionalOptions(rest);
270
+ if (typeof this.options.streamedKey === "function") {
271
+ optionsIn = this.options.streamedKey(optionsIn);
272
+ } else if (this.options.streamedKey) {
273
+ optionsIn = { ...this.options.streamedKey, ...optionsIn };
274
+ }
275
+ const queryKey = optionsIn.queryKey ?? generateOperationKey(this.path, { prefix: this.options.prefix, type: "streamed", input: optionsIn.input, fnOptions: optionsIn.queryFnOptions });
276
+ return queryKey;
277
+ }
278
+ /**
279
+ * Configure queries for [AsyncIteratorObject](https://orpc.dev/docs/async-iterator-object).
280
+ * This is built on [TanStack Query streamedQuery](https://tanstack.com/query/latest/docs/reference/streamedQuery)
281
+ * and works with hooks like `useQuery`, `useSuspenseQuery`, or `prefetchQuery`.
282
+ */
283
+ streamedOptions(...rest) {
284
+ let optionsIn = resolveMaybeOptionalOptions(rest);
285
+ if (typeof this.options.streamedOptions === "function") {
286
+ optionsIn = this.options.streamedOptions(optionsIn);
287
+ } else if (this.options.streamedOptions) {
288
+ optionsIn = { ...this.options.streamedOptions, ...optionsIn };
289
+ }
290
+ const queryKey = this.streamedKey(optionsIn);
291
+ return {
292
+ ...optionsIn,
293
+ queryKey,
294
+ queryFn: (fnContext) => {
295
+ return intercept(
296
+ toArray(this.options.streamedInterceptors),
297
+ {
298
+ path: this.path,
299
+ context: {
300
+ [OPERATION_CONTEXT_SYMBOL]: {
301
+ key: queryKey,
302
+ type: "streamed"
303
+ },
304
+ ...optionsIn.context
305
+ },
306
+ input: optionsIn.input,
307
+ fnContext
158
308
  },
159
- optionsIn.queryFnOptions
160
- ),
161
- ...optionsIn.input === skipToken ? { enabled: false } : {},
162
- ...optionsIn,
163
- queryKey
164
- };
165
- },
166
- experimental_liveKey(...[optionsIn = {}]) {
167
- optionsIn = { ...options.experimental_defaults?.experimental_liveKey, ...optionsIn };
168
- const queryKey = optionsIn.queryKey ?? generateOperationKey(options.path, { type: "live", input: optionsIn.input });
169
- return queryKey;
170
- },
171
- experimental_liveOptions(...[optionsIn = {}]) {
172
- optionsIn = { ...options.experimental_defaults?.experimental_liveOptions, ...optionsIn };
173
- const queryKey = utils.experimental_liveKey(optionsIn);
174
- return {
175
- queryFn: experimental_liveQuery(async ({ signal }) => {
176
- if (optionsIn.input === skipToken) {
177
- throw new Error("queryFn should not be called with skipToken used as input");
309
+ ({ context, fnContext: fnContext2, input }) => {
310
+ if (input === skipToken || optionsIn.queryFn === skipToken) {
311
+ throw new Error("queryFn should not be called when skipToken used for skipping");
312
+ }
313
+ if (optionsIn.queryFn) {
314
+ return optionsIn.queryFn(fnContext2);
315
+ }
316
+ return serializableStreamedQuery(
317
+ async (queryContext) => {
318
+ const output = await this.call(input, { signal: queryContext.signal, context });
319
+ if (!isAsyncIteratorObject(output)) {
320
+ throw new Error("streamedQuery requires an AsyncIteratorObject output");
321
+ }
322
+ return output;
323
+ },
324
+ optionsIn.queryFnOptions
325
+ )(fnContext2);
178
326
  }
179
- const output = await client(optionsIn.input, {
180
- signal,
327
+ );
328
+ },
329
+ ...optionsIn.input === skipToken || optionsIn.queryFn === skipToken ? { enabled: false } : {}
330
+ };
331
+ }
332
+ /**
333
+ * Generate a **full matching** key for [Live Query Options](https://orpc.dev/docs/integrations/tanstack-query#live-query-options).
334
+ */
335
+ liveKey(...rest) {
336
+ let optionsIn = resolveMaybeOptionalOptions(rest);
337
+ if (typeof this.options.liveKey === "function") {
338
+ optionsIn = this.options.liveKey(optionsIn);
339
+ } else if (this.options.liveKey) {
340
+ optionsIn = { ...this.options.liveKey, ...optionsIn };
341
+ }
342
+ const queryKey = optionsIn.queryKey ?? generateOperationKey(this.path, { prefix: this.options.prefix, type: "live", input: optionsIn.input });
343
+ return queryKey;
344
+ }
345
+ /**
346
+ * Configure live queries for [AsyncIteratorObject](https://orpc.dev/docs/async-iterator-object).
347
+ * Unlike `.streamedOptions` which accumulates chunks, live queries replace the entire result with each new chunk received.
348
+ * Works with hooks like `useQuery`, `useSuspenseQuery`, or `prefetchQuery`.
349
+ */
350
+ liveOptions(...rest) {
351
+ let optionsIn = resolveMaybeOptionalOptions(rest);
352
+ if (typeof this.options.liveOptions === "function") {
353
+ optionsIn = this.options.liveOptions(optionsIn);
354
+ } else if (this.options.liveOptions) {
355
+ optionsIn = { ...this.options.liveOptions, ...optionsIn };
356
+ }
357
+ const queryKey = this.liveKey(optionsIn);
358
+ return {
359
+ ...optionsIn,
360
+ queryKey,
361
+ queryFn: (fnContext) => {
362
+ return intercept(
363
+ toArray(this.options.liveInterceptors),
364
+ {
365
+ path: this.path,
181
366
  context: {
182
367
  [OPERATION_CONTEXT_SYMBOL]: {
183
368
  key: queryKey,
184
369
  type: "live"
185
370
  },
186
371
  ...optionsIn.context
372
+ },
373
+ input: optionsIn.input,
374
+ fnContext
375
+ },
376
+ ({ fnContext: fnContext2, input, context }) => {
377
+ if (input === skipToken || optionsIn.queryFn === skipToken) {
378
+ throw new Error("queryFn should not be called when skipToken used for skipping");
187
379
  }
188
- });
189
- if (!isAsyncIteratorObject(output)) {
190
- throw new Error("liveQuery requires an event iterator output");
191
- }
192
- return output;
193
- }),
194
- ...optionsIn.input === skipToken ? { enabled: false } : {},
195
- ...optionsIn,
196
- queryKey
197
- };
198
- },
199
- infiniteKey(optionsIn) {
200
- optionsIn = { ...options.experimental_defaults?.infiniteKey, ...optionsIn };
201
- const queryKey = optionsIn.queryKey ?? generateOperationKey(options.path, {
202
- type: "infinite",
203
- input: optionsIn.input === skipToken ? skipToken : optionsIn.input(optionsIn.initialPageParam)
204
- });
205
- return queryKey;
206
- },
207
- infiniteOptions(optionsIn) {
208
- optionsIn = { ...options.experimental_defaults?.infiniteOptions, ...optionsIn };
209
- const queryKey = utils.infiniteKey(optionsIn);
210
- return {
211
- queryFn: ({ pageParam, signal }) => {
212
- if (optionsIn.input === skipToken) {
213
- throw new Error("queryFn should not be called with skipToken used as input");
380
+ if (optionsIn.queryFn) {
381
+ return optionsIn.queryFn(fnContext2);
382
+ }
383
+ return liveQuery(async (queryContext) => {
384
+ const output = await this.call(input, {
385
+ signal: queryContext.signal,
386
+ context
387
+ });
388
+ if (!isAsyncIteratorObject(output)) {
389
+ throw new Error("liveQuery requires an AsyncIteratorObject output");
390
+ }
391
+ return output;
392
+ })(fnContext2);
214
393
  }
215
- return client(optionsIn.input(pageParam), {
216
- signal,
394
+ );
395
+ },
396
+ ...optionsIn.input === skipToken || optionsIn.queryFn === skipToken ? { enabled: false } : {}
397
+ };
398
+ }
399
+ /**
400
+ * Generate a **full matching** key for [Infinite Query Options](https://orpc.dev/docs/integrations/tanstack-query#infinite-query-options).
401
+ */
402
+ infiniteKey(optionsIn) {
403
+ if (typeof this.options.infiniteKey === "function") {
404
+ optionsIn = this.options.infiniteKey(optionsIn);
405
+ } else if (this.options.infiniteKey) {
406
+ optionsIn = { ...this.options.infiniteKey, ...optionsIn };
407
+ }
408
+ const queryKey = optionsIn.queryKey ?? generateOperationKey(this.path, {
409
+ prefix: this.options.prefix,
410
+ type: "infinite",
411
+ input: optionsIn.input === skipToken ? skipToken : optionsIn.input(optionsIn.initialPageParam)
412
+ });
413
+ return queryKey;
414
+ }
415
+ /**
416
+ * Generate options used for useInfiniteQuery/useSuspenseInfiniteQuery/prefetchInfiniteQuery/...
417
+ */
418
+ infiniteOptions(optionsIn) {
419
+ if (typeof this.options.infiniteOptions === "function") {
420
+ optionsIn = this.options.infiniteOptions(optionsIn);
421
+ } else if (this.options.infiniteOptions) {
422
+ optionsIn = { ...this.options.infiniteOptions, ...optionsIn };
423
+ }
424
+ const queryKey = this.infiniteKey(optionsIn);
425
+ return {
426
+ ...optionsIn,
427
+ queryKey,
428
+ queryFn: (fnContext) => {
429
+ return intercept(
430
+ toArray(this.options.infiniteInterceptors),
431
+ {
432
+ path: this.path,
217
433
  context: {
218
434
  [OPERATION_CONTEXT_SYMBOL]: {
219
435
  key: queryKey,
220
436
  type: "infinite"
221
437
  },
222
438
  ...optionsIn.context
223
- }
224
- });
225
- },
226
- ...optionsIn.input === skipToken ? { enabled: false } : {},
227
- ...optionsIn,
228
- queryKey
229
- };
230
- },
231
- mutationKey(...[optionsIn = {}]) {
232
- optionsIn = { ...options.experimental_defaults?.mutationKey, ...optionsIn };
233
- const mutationKey = optionsIn.mutationKey ?? generateOperationKey(options.path, { type: "mutation" });
234
- return mutationKey;
235
- },
236
- mutationOptions(...[optionsIn = {}]) {
237
- optionsIn = { ...options.experimental_defaults?.mutationOptions, ...optionsIn };
238
- const mutationKey = utils.mutationKey(optionsIn);
239
- return {
240
- mutationFn: (input) => client(input, {
241
- context: {
242
- [OPERATION_CONTEXT_SYMBOL]: {
243
- key: mutationKey,
244
- type: "mutation"
245
439
  },
246
- ...optionsIn.context
440
+ input: optionsIn.input === skipToken ? skipToken : optionsIn.input(fnContext.pageParam),
441
+ fnContext
442
+ },
443
+ ({ context, input, fnContext: fnContext2 }) => {
444
+ if (input === skipToken || optionsIn.queryFn === skipToken) {
445
+ throw new Error("queryFn should not be called when skipToken used for skipping");
446
+ }
447
+ if (optionsIn.queryFn) {
448
+ return optionsIn.queryFn(fnContext2);
449
+ }
450
+ return this.call(input, { signal: fnContext2.signal, context });
247
451
  }
248
- }),
249
- ...optionsIn,
250
- mutationKey
251
- };
452
+ );
453
+ },
454
+ ...optionsIn.input === skipToken || optionsIn.queryFn === skipToken ? { enabled: false } : {}
455
+ };
456
+ }
457
+ /**
458
+ * Generate a **full matching** key for [Mutation Options](https://orpc.dev/docs/integrations/tanstack-query#mutation-options).
459
+ */
460
+ mutationKey(optionsIn = {}) {
461
+ if (typeof this.options.mutationKey === "function") {
462
+ optionsIn = this.options.mutationKey(optionsIn);
463
+ } else if (this.options.mutationKey) {
464
+ optionsIn = { ...this.options.mutationKey, ...optionsIn };
252
465
  }
253
- };
254
- return utils;
466
+ const mutationKey = optionsIn.mutationKey ?? generateOperationKey(this.path, { prefix: this.options.prefix, type: "mutation" });
467
+ return mutationKey;
468
+ }
469
+ /**
470
+ * Generate options used for useMutation/...
471
+ */
472
+ mutationOptions(...rest) {
473
+ let optionsIn = resolveMaybeOptionalOptions(rest);
474
+ if (typeof this.options.mutationOptions === "function") {
475
+ optionsIn = this.options.mutationOptions(optionsIn);
476
+ } else if (this.options.mutationOptions) {
477
+ optionsIn = { ...this.options.mutationOptions, ...optionsIn };
478
+ }
479
+ const mutationKey = this.mutationKey(optionsIn);
480
+ return {
481
+ ...optionsIn,
482
+ mutationKey,
483
+ mutationFn: (input, fnContext) => {
484
+ return intercept(
485
+ toArray(this.options.mutationInterceptors),
486
+ {
487
+ path: this.path,
488
+ context: {
489
+ [OPERATION_CONTEXT_SYMBOL]: {
490
+ key: mutationKey,
491
+ type: "mutation"
492
+ },
493
+ ...optionsIn.context
494
+ },
495
+ fnContext,
496
+ input
497
+ },
498
+ ({ input: input2, fnContext: fnContext2, context }) => {
499
+ if (optionsIn.mutationFn) {
500
+ return optionsIn.mutationFn(input2, fnContext2);
501
+ }
502
+ return this.call(input2, { context });
503
+ }
504
+ );
505
+ }
506
+ };
507
+ }
255
508
  }
256
509
 
257
510
  function createRouterUtils(client, options = {}) {
511
+ const plugin = new CompositeRouterUtilsPlugin(options.plugins);
512
+ options = plugin.init(options);
513
+ return createRouterUtilsInternal(client, options, plugin);
514
+ }
515
+ function createRouterUtilsInternal(client, options, plugin) {
258
516
  const path = toArray(options.path);
259
- const generalUtils = createGeneralUtils(path);
260
- const procedureUtils = createProcedureUtils(client, {
517
+ const utils = typeof client === "function" && (options.scoped === void 0 || isProcedureUtilsOptions(options.scoped)) ? bindMethods(new ProcedureUtils(
261
518
  path,
262
- experimental_defaults: options.experimental_defaults
263
- });
264
- const recursive = new Proxy({
265
- ...generalUtils,
266
- ...procedureUtils
267
- }, {
519
+ client,
520
+ plugin.initProcedureOptions(path, mergeProcedureUtilsOptions(
521
+ {
522
+ prefix: options.prefix,
523
+ queryInterceptors: options.queryInterceptors,
524
+ streamedInterceptors: options.streamedInterceptors,
525
+ liveInterceptors: options.liveInterceptors,
526
+ infiniteInterceptors: options.infiniteInterceptors,
527
+ mutationInterceptors: options.mutationInterceptors
528
+ },
529
+ options.scoped ?? {}
530
+ ))
531
+ )) : bindMethods(new SharedUtils(path, options));
532
+ const recursive = new Proxy(utils, {
268
533
  get(target, prop) {
269
- const value = Reflect.get(target, prop);
270
- if (typeof prop !== "string") {
534
+ const value = getOrBind(target, prop);
535
+ const nextClient = client[prop];
536
+ if (typeof prop !== "string" || RECURSIVE_CLIENT_UNWRAP_KEYS.has(prop) || !isTypescriptObject(nextClient)) {
271
537
  return value;
272
538
  }
273
- const nextUtils = createRouterUtils(client[prop], {
539
+ const nextUtils = createRouterUtilsInternal(nextClient, {
274
540
  ...options,
275
541
  path: [...path, prop],
276
- experimental_defaults: get(options.experimental_defaults, [prop])
277
- });
542
+ scoped: get(options.scoped, [prop])
543
+ }, plugin);
278
544
  if (typeof value !== "function") {
279
545
  return nextUtils;
280
546
  }
281
547
  return new Proxy(value, {
282
- get(_, prop2) {
283
- return Reflect.get(nextUtils, prop2);
548
+ get(target2, prop2) {
549
+ if (typeof prop2 !== "string" || RECURSIVE_CLIENT_UNWRAP_KEYS.has(prop2)) {
550
+ return getOrBind(target2, prop2);
551
+ }
552
+ return getOrBind(nextUtils, prop2);
284
553
  }
285
554
  });
286
555
  }
@@ -288,4 +557,54 @@ function createRouterUtils(client, options = {}) {
288
557
  return recursive;
289
558
  }
290
559
 
291
- export { OPERATION_CONTEXT_SYMBOL, OPERATION_CONTEXT_SYMBOL as TANSTACK_QUERY_OPERATION_CONTEXT_SYMBOL, createGeneralUtils, createProcedureUtils, createRouterUtils, createRouterUtils as createTanstackQueryUtils, experimental_serializableStreamedQuery, generateOperationKey };
560
+ function createContractUtilsFactory(clientFactory, options) {
561
+ const factory = (contract) => {
562
+ const client = clientFactory(contract);
563
+ const path = resolveBasePathMeta(contract);
564
+ if (path === void 0) {
565
+ throw new TypeError(
566
+ "ContractUtilsFactory: procedure contract must define `meta.path` that matches its path in the root router contract."
567
+ );
568
+ }
569
+ return createRouterUtils(client, { ...options, path, scoped: get(options.scoped, path) });
570
+ };
571
+ return factory;
572
+ }
573
+ function createContractJsonifiedUtilsFactory(clientFactory, options) {
574
+ return createContractUtilsFactory(clientFactory, options);
575
+ }
576
+
577
+ function tanstackQuery(options) {
578
+ return {
579
+ name: "~tanstack-query",
580
+ init(meta) {
581
+ const current = meta["~tanstack-query"];
582
+ return {
583
+ ...meta,
584
+ "~tanstack-query": current ? mergeProcedureUtilsOptions(current, options) : options
585
+ };
586
+ }
587
+ };
588
+ }
589
+ function getTanstackQueryMeta(procedureOrLazy) {
590
+ return procedureOrLazy["~orpc"].meta["~tanstack-query"];
591
+ }
592
+ class ContractOptionsUtilsPlugin {
593
+ constructor(contract) {
594
+ this.contract = contract;
595
+ }
596
+ name = "~contract-options";
597
+ initProcedureOptions(path, options) {
598
+ const procedure = getRouterContract(this.contract, path);
599
+ if (!(procedure instanceof ProcedureContract)) {
600
+ return options;
601
+ }
602
+ const base = getTanstackQueryMeta(procedure);
603
+ if (!base) {
604
+ return options;
605
+ }
606
+ return mergeProcedureUtilsOptions(base, options);
607
+ }
608
+ }
609
+
610
+ export { CompositeRouterUtilsPlugin, ContractOptionsUtilsPlugin, OPERATION_CONTEXT_SYMBOL, ProcedureUtils, SharedUtils, OPERATION_CONTEXT_SYMBOL as TANSTACK_QUERY_OPERATION_CONTEXT_SYMBOL, createContractJsonifiedUtilsFactory, createContractUtilsFactory, createRouterUtils, createRouterUtils as createTanstackQueryUtils, generateOperationKey, getTanstackQueryMeta, isProcedureUtilsOptions, mergeProcedureUtilsOptions, serializableStreamedQuery, tanstackQuery };