diff --git a/.changeset/chatty-cameras-remain.md b/.changeset/chatty-cameras-remain.md new file mode 100644 index 0000000000000..f1a41c09e153b --- /dev/null +++ b/.changeset/chatty-cameras-remain.md @@ -0,0 +1,11 @@ +--- +"@refinedev/core": patch +--- + +fix(core): keep query context signal lazy when merging meta + +`prepareQueryContext` was previously spread into new `meta` objects inside core data hooks. Because `signal` is exposed as a getter, object spread eagerly accessed it during merge and marked the query as abortable earlier than intended. + +This keeps `signal` lazy by merging `meta` inside `prepareQueryContext` and preserving the getter on the returned object. The fix is applied across the affected hooks in `@refinedev/core`, and a regression test was added for the lazy `signal` behavior. + +Resolves #7132 diff --git a/packages/core/src/definitions/helpers/prepare-query-context/index.spec.ts b/packages/core/src/definitions/helpers/prepare-query-context/index.spec.ts new file mode 100644 index 0000000000000..f0afe1b97d3c2 --- /dev/null +++ b/packages/core/src/definitions/helpers/prepare-query-context/index.spec.ts @@ -0,0 +1,23 @@ +import { prepareQueryContext } from "."; + +describe("prepareQueryContext", () => { + it("should keep signal lazy when merging it into meta", () => { + const signal = new AbortController().signal; + const signalGetter = vi.fn(() => signal); + const context = { + queryKey: ["posts", "list"], + get signal() { + return signalGetter(); + }, + } as any; + + const meta = prepareQueryContext(context, { foo: "bar" }); + + expect(meta.foo).toBe("bar"); + expect(meta.queryKey).toEqual(["posts", "list"]); + expect(signalGetter).not.toHaveBeenCalled(); + + expect(meta.signal).toBe(signal); + expect(signalGetter).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/core/src/definitions/helpers/prepare-query-context/index.ts b/packages/core/src/definitions/helpers/prepare-query-context/index.ts index 571c8fffe3dbc..1282a0cd13deb 100644 --- a/packages/core/src/definitions/helpers/prepare-query-context/index.ts +++ b/packages/core/src/definitions/helpers/prepare-query-context/index.ts @@ -4,13 +4,18 @@ type Context = | QueryFunctionContext | QueryFunctionContext; +type QueryContextMeta | undefined> = + (TMeta extends Record ? TMeta : Record) & + Pick; + export const prepareQueryContext = ( context: Context, -): Pick => { - const queryContext: Pick = { + meta?: Record, +): QueryContextMeta => { + const queryContext = { + ...(meta ?? {}), queryKey: context.queryKey, - signal: undefined as any, - }; + } as QueryContextMeta; Object.defineProperty(queryContext, "signal", { enumerable: true, diff --git a/packages/core/src/hooks/data/useCustom.ts b/packages/core/src/hooks/data/useCustom.ts index 1c0299bbe38fc..1f39c25de84d5 100644 --- a/packages/core/src/hooks/data/useCustom.ts +++ b/packages/core/src/hooks/data/useCustom.ts @@ -173,10 +173,7 @@ export const useCustom = < url, method, ...config, - meta: { - ...combinedMeta, - ...prepareQueryContext(context as any), - }, + meta: prepareQueryContext(context as any, combinedMeta), }), ...queryOptions, meta: { diff --git a/packages/core/src/hooks/data/useInfiniteList.ts b/packages/core/src/hooks/data/useInfiniteList.ts index 744688f1c1ec8..ccf8922550b61 100644 --- a/packages/core/src/hooks/data/useInfiniteList.ts +++ b/packages/core/src/hooks/data/useInfiniteList.ts @@ -254,10 +254,7 @@ export const useInfiniteList = < (context.pageParam as number) ?? prefferedPagination.currentPage, }; - const meta = { - ...combinedMeta, - ...prepareQueryContext(context), - }; + const meta = prepareQueryContext(context, combinedMeta); return getList({ resource: resource?.name || "", diff --git a/packages/core/src/hooks/data/useList.ts b/packages/core/src/hooks/data/useList.ts index a7a8399c53bca..747a8d904b709 100644 --- a/packages/core/src/hooks/data/useList.ts +++ b/packages/core/src/hooks/data/useList.ts @@ -255,10 +255,7 @@ export const useList = < }) .get(), queryFn: (context) => { - const meta = { - ...combinedMeta, - ...prepareQueryContext(context), - }; + const meta = prepareQueryContext(context, combinedMeta); return getList({ resource: resource?.name ?? "", pagination: prefferedPagination, diff --git a/packages/core/src/hooks/data/useMany.ts b/packages/core/src/hooks/data/useMany.ts index ed67473f4a08b..f25a1e4304e77 100644 --- a/packages/core/src/hooks/data/useMany.ts +++ b/packages/core/src/hooks/data/useMany.ts @@ -188,10 +188,7 @@ export const useMany = < }) .get(), queryFn: (context) => { - const meta = { - ...combinedMeta, - ...prepareQueryContext(context as any), - }; + const meta = prepareQueryContext(context as any, combinedMeta); if (getMany) { return getMany({ diff --git a/packages/core/src/hooks/data/useOne.ts b/packages/core/src/hooks/data/useOne.ts index a22bf8ca451b9..30071130950e7 100644 --- a/packages/core/src/hooks/data/useOne.ts +++ b/packages/core/src/hooks/data/useOne.ts @@ -185,10 +185,7 @@ export const useOne = < getOne({ resource: resource?.name ?? "", id: id!, - meta: { - ...combinedMeta, - ...prepareQueryContext(context as any), - }, + meta: prepareQueryContext(context as any, combinedMeta), }), ...queryOptions, enabled: isEnabled,