diff --git a/packages/pinia-colada/src/meta.test.ts b/packages/pinia-colada/src/meta.test.ts index 141b9fa23..17d2dbbe9 100644 --- a/packages/pinia-colada/src/meta.test.ts +++ b/packages/pinia-colada/src/meta.test.ts @@ -145,6 +145,36 @@ describe('contractOptionsUtilsPlugin', () => { expect(client.planet.find).toHaveBeenCalledTimes(1) }) + it('lets object utils options beat defaults of a function meta modifier', () => { + const contract = { + planet: { + find: oc.meta(piniaColada({ + queryOptions: (options: any) => ({ staleTime: 1000, gcTime: 1, ...options }), + })), + }, + } + + const client = { planet: { find: vi.fn(async () => '__found__') } } + + const utils = createRouterUtils(client as any, { + scoped: { + planet: { + find: { + queryOptions: { gcTime: 2 }, + }, + }, + }, + plugins: [new ContractOptionsUtilsPlugin(contract)], + }) as any + + const options = utils.planet.find.queryOptions({ input: { id: 1 } }) + + expect(options.staleTime).toBe(1000) + expect(options.gcTime).toBe(2) + + expect(utils.planet.find.queryOptions({ input: { id: 1 }, gcTime: 5 }).gcTime).toBe(5) + }) + it('keeps contract meta interceptors when utils define none', async () => { const metaInterceptor = vi.fn(({ next }: any) => next()) const contract = { planet: { find: oc.meta(piniaColada({ queryInterceptors: [metaInterceptor] })) } } diff --git a/packages/pinia-colada/src/procedure-utils.test.ts b/packages/pinia-colada/src/procedure-utils.test.ts index 21297ddba..1088749e7 100644 --- a/packages/pinia-colada/src/procedure-utils.test.ts +++ b/packages/pinia-colada/src/procedure-utils.test.ts @@ -1004,4 +1004,14 @@ describe('mergeProcedureUtilsOptions', () => { expect((functionFirst.queryOptions as any)({ enabled: true })).toEqual({ enabled: true, staleTime: 1000, gcTime: 2 }) }) + + it('lets an object override beat defaults of a function base, below per-call options', () => { + const merged = mergeProcedureUtilsOptions( + { queryOptions: (options: any) => ({ staleTime: 1000, ...options }) }, + { queryOptions: { staleTime: 0 } }, + ) + + expect((merged.queryOptions as any)({})).toEqual({ staleTime: 0 }) + expect((merged.queryOptions as any)({ staleTime: 5 })).toEqual({ staleTime: 5 }) + }) }) diff --git a/packages/pinia-colada/src/procedure-utils.ts b/packages/pinia-colada/src/procedure-utils.ts index ff01b4e8f..27f8701d0 100644 --- a/packages/pinia-colada/src/procedure-utils.ts +++ b/packages/pinia-colada/src/procedure-utils.ts @@ -215,22 +215,25 @@ function mergeProcedureUtilsModifier( base: ProcedureUtilsModifier, override: ProcedureUtilsModifier, ): ProcedureUtilsModifier { - if (typeof base !== 'function' && typeof override !== 'function') { - return { ...base, ...override } as Partial + if (typeof override !== 'function') { + if (typeof base !== 'function') { + return { ...base, ...override } as Partial + } + + return options => base({ ...override, ...options }) } const applyBase = typeof base === 'function' ? base : (options: T) => ({ ...base, ...options }) - const applyOverride = typeof override === 'function' ? override : (options: T) => ({ ...override, ...options }) - return options => applyOverride(applyBase(options)) + return options => override(applyBase(options)) } /** * Merge two procedure utils options where `override` takes priority. * A key explicitly set to `undefined` in `override` resets the base value. - * When both sides define a key: interceptors are concatenated (base ones run first), - * modifiers are spread-merged when both are plain objects and composed (base applied - * first) otherwise, with plain objects applied as regular spread merges. + * When both sides define a key, interceptors are concatenated (base ones run first) and + * modifiers are combined so that, for object values, per-call options beat `override`, + * which beats `base`. A function modifier receives the merged options and its result wins. */ export function mergeProcedureUtilsOptions( base: ProcedureUtilsOptions, diff --git a/packages/tanstack-query/src/meta.test.ts b/packages/tanstack-query/src/meta.test.ts index 1f244dbbb..8aa362601 100644 --- a/packages/tanstack-query/src/meta.test.ts +++ b/packages/tanstack-query/src/meta.test.ts @@ -145,6 +145,36 @@ describe('contractMetaPlugin', () => { expect(client.planet.find).toHaveBeenCalledTimes(1) }) + it('lets object utils options beat defaults of a function meta modifier', () => { + const contract = { + planet: { + find: oc.meta(tanstackQuery({ + queryOptions: (options: any) => ({ staleTime: 1000, retry: 1, ...options }), + })), + }, + } + + const client = { planet: { find: vi.fn(async () => '__found__') } } + + const utils = createRouterUtils(client as any, { + scoped: { + planet: { + find: { + queryOptions: { retry: 2 }, + }, + }, + }, + plugins: [new ContractOptionsUtilsPlugin(contract)], + }) as any + + const options = utils.planet.find.queryOptions({ input: { id: 1 } }) + + expect(options.staleTime).toBe(1000) + expect(options.retry).toBe(2) + + expect(utils.planet.find.queryOptions({ input: { id: 1 }, retry: 5 }).retry).toBe(5) + }) + it('keeps contract meta interceptors when utils define none', async () => { const metaInterceptor = vi.fn(({ next }: any) => next()) const contract = { planet: { find: oc.meta(tanstackQuery({ queryInterceptors: [metaInterceptor] })) } } diff --git a/packages/tanstack-query/src/procedure-utils.test.ts b/packages/tanstack-query/src/procedure-utils.test.ts index d8fa0fa1a..730b0904c 100644 --- a/packages/tanstack-query/src/procedure-utils.test.ts +++ b/packages/tanstack-query/src/procedure-utils.test.ts @@ -1253,4 +1253,14 @@ describe('mergeProcedureUtilsOptions', () => { expect((functionFirst.queryOptions as any)({ enabled: true })).toEqual({ enabled: true, staleTime: 1000, retry: 2 }) }) + + it('lets an object override beat defaults of a function base, below per-call options', () => { + const merged = mergeProcedureUtilsOptions( + { queryOptions: (options: any) => ({ staleTime: 1000, ...options }) }, + { queryOptions: { staleTime: 0 } }, + ) + + expect((merged.queryOptions as any)({})).toEqual({ staleTime: 0 }) + expect((merged.queryOptions as any)({ staleTime: 5 })).toEqual({ staleTime: 5 }) + }) }) diff --git a/packages/tanstack-query/src/procedure-utils.ts b/packages/tanstack-query/src/procedure-utils.ts index d817a1b33..d396574d4 100644 --- a/packages/tanstack-query/src/procedure-utils.ts +++ b/packages/tanstack-query/src/procedure-utils.ts @@ -227,22 +227,25 @@ function mergeProcedureUtilsModifier( base: ProcedureUtilsModifier, override: ProcedureUtilsModifier, ): ProcedureUtilsModifier { - if (typeof base !== 'function' && typeof override !== 'function') { - return { ...base, ...override } as Partial + if (typeof override !== 'function') { + if (typeof base !== 'function') { + return { ...base, ...override } as Partial + } + + return options => base({ ...override, ...options }) } const applyBase = typeof base === 'function' ? base : (options: T) => ({ ...base, ...options }) - const applyOverride = typeof override === 'function' ? override : (options: T) => ({ ...override, ...options }) - return options => applyOverride(applyBase(options)) + return options => override(applyBase(options)) } /** * Merge two procedure utils options where `override` takes priority. * A key explicitly set to `undefined` in `override` resets the base value. - * When both sides define a key: interceptors are concatenated (base ones run first), - * modifiers are spread-merged when both are plain objects and composed (base applied - * first) otherwise, with plain objects applied as regular spread merges. + * When both sides define a key, interceptors are concatenated (base ones run first) and + * modifiers are combined so that, for object values, per-call options beat `override`, + * which beats `base`. A function modifier receives the merged options and its result wins. */ export function mergeProcedureUtilsOptions( base: ProcedureUtilsOptions,