From c518f315d8327ebacb32a7137e88efb1bb3b4516 Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Fri, 23 Feb 2024 12:16:20 +0100 Subject: [PATCH 01/16] Allow Apollo Client instance to intercept hook functionality --- .api-reports/api-report-react.md | 4 +- .api-reports/api-report-react_hooks.md | 4 +- .api-reports/api-report-react_internal.md | 412 +++++++++++++++++++++- .api-reports/api-report.md | 4 +- .size-limits.json | 2 +- src/react/hooks/internal/index.ts | 1 + src/react/hooks/internal/wrapHook.ts | 80 +++++ src/react/hooks/useBackgroundQuery.ts | 10 +- src/react/hooks/useFragment.ts | 14 +- src/react/hooks/useQuery.ts | 9 + src/react/hooks/useReadQuery.ts | 9 +- src/react/hooks/useSuspenseQuery.ts | 10 +- src/react/internal/index.ts | 1 + 13 files changed, 543 insertions(+), 17 deletions(-) create mode 100644 src/react/hooks/internal/wrapHook.ts diff --git a/.api-reports/api-report-react.md b/.api-reports/api-report-react.md index 0cdf3ce93bc..5197af6094e 100644 --- a/.api-reports/api-report-react.md +++ b/.api-reports/api-report-react.md @@ -2398,8 +2398,8 @@ interface WatchQueryOptions { version?: string; } +// Warning: (ae-forgotten-export) The symbol "ApolloClient" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +interface ApolloClientWithWrappers extends ApolloClient { + // (undocumented) + [wrapperSymbol]?: HookWrappers; +} + // @public (undocumented) class ApolloError extends Error { // Warning: (ae-forgotten-export) The symbol "ApolloErrorOptions" needs to be exported by the entry point index.d.ts @@ -308,6 +316,41 @@ type AsStoreObject; + +// Warning: (ae-forgotten-export) The symbol "QueryHookOptions" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +interface BackgroundQueryHookOptions extends Pick, "client" | "variables" | "errorPolicy" | "context" | "canonizeResults" | "returnPartialData" | "refetchWritePolicy"> { + // Warning: (ae-forgotten-export) The symbol "BackgroundQueryHookFetchPolicy" needs to be exported by the entry point index.d.ts + // + // (undocumented) + fetchPolicy?: BackgroundQueryHookFetchPolicy; + // (undocumented) + queryKey?: string | number | any[]; + // @deprecated + skip?: boolean; +} + +// Warning: (ae-forgotten-export) The symbol "BackgroundQueryHookOptions" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "NoInfer" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +type BackgroundQueryHookOptionsNoInfer = BackgroundQueryHookOptions, NoInfer>; + +// Warning: (ae-forgotten-export) The symbol "SharedWatchQueryOptions" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +interface BaseQueryOptions extends SharedWatchQueryOptions { + // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "@apollo/client" does not have an export "ApolloClient" + client?: ApolloClient; + context?: DefaultContext; + ssr?: boolean; +} + // @public (undocumented) namespace Cache_2 { // (undocumented) @@ -527,6 +570,40 @@ interface DataProxy { writeQuery(options: DataProxy.WriteQueryOptions): Reference | undefined; } +// Warning: (ae-forgotten-export) The symbol "DeepPartialPrimitive" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "DeepPartialMap" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "DeepPartialReadonlyMap" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "DeepPartialSet" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "DeepPartialReadonlySet" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "DeepPartialObject" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +type DeepPartial = T extends DeepPartialPrimitive ? T : T extends Map ? DeepPartialMap : T extends ReadonlyMap ? DeepPartialReadonlyMap : T extends Set ? DeepPartialSet : T extends ReadonlySet ? DeepPartialReadonlySet : T extends (...args: any[]) => unknown ? T | undefined : T extends object ? T extends (ReadonlyArray) ? TItem[] extends (T) ? readonly TItem[] extends T ? ReadonlyArray> : Array> : DeepPartialObject : DeepPartialObject : unknown; + +// Warning: (ae-forgotten-export) The symbol "DeepPartial" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +type DeepPartialMap = {} & Map, DeepPartial>; + +// @public (undocumented) +type DeepPartialObject = { + [K in keyof T]?: DeepPartial; +}; + +// Warning: (ae-forgotten-export) The symbol "Primitive" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +type DeepPartialPrimitive = Primitive | Date | RegExp; + +// @public (undocumented) +type DeepPartialReadonlyMap = {} & ReadonlyMap, DeepPartial>; + +// @public (undocumented) +type DeepPartialReadonlySet = {} & ReadonlySet>; + +// @public (undocumented) +type DeepPartialSet = {} & Set>; + // @public (undocumented) interface DefaultContext extends Record { } @@ -626,6 +703,16 @@ interface ExecutionPatchResultBase { hasNext?: boolean; } +// Warning: (ae-forgotten-export) The symbol "FetchMoreQueryOptions" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +type FetchMoreFunction = (fetchMoreOptions: FetchMoreQueryOptions & { + updateQuery?: (previousQueryResult: TData, options: { + fetchMoreResult: TData; + variables: TVariables; + }) => TData; +}) => Promise>; + // @public (undocumented) type FetchMoreOptions = Parameters["fetchMore"]>[0]; @@ -719,7 +806,6 @@ const getApolloClientMemoryInternals: (() => { }; }) | undefined; -// Warning: (ae-forgotten-export) The symbol "ApolloClient" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "SuspenseCache" needs to be exported by the entry point index.d.ts // // @public (undocumented) @@ -749,6 +835,13 @@ interface GraphQLRequest> { variables?: TVariables; } +// Warning: (ae-forgotten-export) The symbol "WrappableHooks" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export type HookWrappers = { + [K in keyof WrappableHooks]?: (originalHook: WrappableHooks[K]) => WrappableHooks[K]; +}; + // @public (undocumented) interface IgnoreModifier { // (undocumented) @@ -915,6 +1008,11 @@ type LocalStateOptions = { fragmentMatcher?: FragmentMatcher; }; +// Warning: (ae-forgotten-export) The symbol "ApolloClientWithWrappers" needs to be exported by the entry point index.d.ts +// +// @internal +export function makeHookWrappable(hookName: Name, useHook: WrappableHooks[Name], clientFromOptions: (...args: Parameters) => ApolloClientWithWrappers | undefined): WrappableHooks[Name]; + // @public (undocumented) type MaybeAsync = T | PromiseLike; @@ -1051,8 +1149,6 @@ enum NetworkStatus { // @public (undocumented) interface NextFetchPolicyContext { - // Warning: (ae-forgotten-export) The symbol "WatchQueryFetchPolicy" needs to be exported by the entry point index.d.ts - // // (undocumented) initialFetchPolicy: WatchQueryFetchPolicy; // (undocumented) @@ -1069,6 +1165,9 @@ type NextLink = (operation: Operation) => Observable; // @public (undocumented) type NextResultListener = (method: "next" | "error" | "complete", arg?: any) => any; +// @public +type NoInfer = [T][T extends any ? 0 : never]; + // @public (undocumented) class ObservableQuery extends Observable> { constructor({ queryManager, queryInfo, options, }: { @@ -1076,7 +1175,6 @@ class ObservableQuery; }); - // Warning: (ae-forgotten-export) The symbol "FetchMoreQueryOptions" needs to be exported by the entry point index.d.ts fetchMore(fetchMoreOptions: FetchMoreQueryOptions & { updateQuery?: (previousQueryResult: TData, options: { fetchMoreResult: TFetchData; @@ -1133,6 +1231,24 @@ class ObservableQuery { + fetchMore: (fetchMoreOptions: FetchMoreQueryOptions & { + updateQuery?: (previousQueryResult: TData, options: { + fetchMoreResult: TFetchData; + variables: TFetchVars; + }) => TData; + }) => Promise>; + refetch: (variables?: Partial) => Promise>; + // @internal (undocumented) + reobserve: (newOptions?: Partial>, newNetworkStatus?: NetworkStatus) => Promise>; + startPolling: (pollInterval: number) => void; + stopPolling: () => void; + subscribeToMore: (options: SubscribeToMoreOptions) => () => void; + updateQuery: (mapFn: (previousQueryResult: TData, options: Pick, "variables">) => TData) => void; + variables: TVariables | undefined; +} + // @public (undocumented) const OBSERVED_CHANGED_OPTIONS: readonly ["canonizeResults", "context", "errorPolicy", "fetchPolicy", "refetchWritePolicy", "returnPartialData"]; @@ -1172,6 +1288,9 @@ interface PendingPromise extends Promise { status: "pending"; } +// @public (undocumented) +type Primitive = null | undefined | string | number | boolean | symbol | bigint; + // @public (undocumented) const PROMISE_SYMBOL: unique symbol; @@ -1185,6 +1304,23 @@ type PromiseWithState = PendingPromise | FulfilledPromise extends BaseQueryOptions { + // @internal (undocumented) + defaultOptions?: Partial>; + onCompleted?: (data: TData) => void; + onError?: (error: ApolloError) => void; + skip?: boolean; +} + +// Warning: (ae-forgotten-export) The symbol "QueryFunctionOptions" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +interface QueryHookOptions extends QueryFunctionOptions { +} + // @public (undocumented) class QueryInfo { constructor(queryManager: QueryManager, queryId?: string); @@ -1411,6 +1547,20 @@ export interface QueryReference { // @public (undocumented) type QueryRefPromise = PromiseWithState>; +// Warning: (ae-forgotten-export) The symbol "ObservableQueryFields" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +interface QueryResult extends ObservableQueryFields { + called: boolean; + client: ApolloClient; + data: TData | undefined; + error?: ApolloError; + loading: boolean; + networkStatus: NetworkStatus; + observable: ObservableQuery; + previousData?: TData; +} + // @public (undocumented) type QueryStoreValue = Pick; @@ -1439,6 +1589,9 @@ interface Reference { readonly __ref: string; } +// @public (undocumented) +type RefetchFunction = ObservableQueryFields["refetch"]; + // @public (undocumented) type RefetchQueriesInclude = RefetchQueryDescriptor[] | RefetchQueriesIncludeShorthand; @@ -1550,6 +1703,12 @@ interface SingleExecutionResult, TContext = DefaultC data?: TData | null; } +// @public (undocumented) +type SkipToken = typeof skipToken; + +// @public (undocumented) +const skipToken: unique symbol; + // @public (undocumented) type Source = MaybeAsync>; @@ -1572,6 +1731,9 @@ type StoreObjectValueMaybeReference = StoreVal extends Array = ObservableQueryFields["subscribeToMore"]; + // @public (undocumented) type SubscribeToMoreOptions = { document: DocumentNode | TypedDocumentNode; @@ -1606,6 +1768,27 @@ export interface SuspenseCacheOptions { // @public (undocumented) const suspenseCacheSymbol: unique symbol; +// @public (undocumented) +type SuspenseQueryHookFetchPolicy = Extract; + +// @public (undocumented) +interface SuspenseQueryHookOptions { + // @deprecated + canonizeResults?: boolean; + // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "@apollo/client" does not have an export "ApolloClient" + client?: ApolloClient; + context?: DefaultContext; + errorPolicy?: ErrorPolicy; + // Warning: (ae-forgotten-export) The symbol "SuspenseQueryHookFetchPolicy" needs to be exported by the entry point index.d.ts + fetchPolicy?: SuspenseQueryHookFetchPolicy; + queryKey?: string | number | any[]; + refetchWritePolicy?: RefetchWritePolicy; + returnPartialData?: boolean; + // @deprecated + skip?: boolean; + variables?: TVariables; +} + // @public (undocumented) type ToReferenceFunction = (objOrIdOrRef: StoreObject | string | Reference, mergeIntoStore?: boolean) => Reference | undefined; @@ -1662,16 +1845,231 @@ interface UriFunction { (operation: Operation): string; } +// Warning: (ae-forgotten-export) The symbol "BackgroundQueryHookOptionsNoInfer" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "UseBackgroundQueryResult" needs to be exported by the entry point index.d.ts +// // @public (undocumented) -type WatchQueryFetchPolicy = FetchPolicy | "cache-and-network"; +function useBackgroundQuery, "variables">>(query: DocumentNode | TypedDocumentNode, options?: BackgroundQueryHookOptionsNoInfer & TOptions): [ +(QueryReference | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? DeepPartial : TData, TVariables> | (TOptions["skip"] extends boolean ? undefined : never)), +UseBackgroundQueryResult +]; -// Warning: (ae-forgotten-export) The symbol "SharedWatchQueryOptions" needs to be exported by the entry point index.d.ts +// @public (undocumented) +function useBackgroundQuery(query: DocumentNode | TypedDocumentNode, options: BackgroundQueryHookOptionsNoInfer & { + returnPartialData: true; + errorPolicy: "ignore" | "all"; +}): [ +QueryReference | undefined, TVariables>, +UseBackgroundQueryResult +]; + +// @public (undocumented) +function useBackgroundQuery(query: DocumentNode | TypedDocumentNode, options: BackgroundQueryHookOptionsNoInfer & { + errorPolicy: "ignore" | "all"; +}): [ +QueryReference, +UseBackgroundQueryResult +]; + +// @public (undocumented) +function useBackgroundQuery(query: DocumentNode | TypedDocumentNode, options: BackgroundQueryHookOptionsNoInfer & { + skip: boolean; + returnPartialData: true; +}): [ +QueryReference, TVariables> | undefined, +UseBackgroundQueryResult +]; + +// @public (undocumented) +function useBackgroundQuery(query: DocumentNode | TypedDocumentNode, options: BackgroundQueryHookOptionsNoInfer & { + returnPartialData: true; +}): [ +QueryReference, TVariables>, +UseBackgroundQueryResult +]; + +// @public (undocumented) +function useBackgroundQuery(query: DocumentNode | TypedDocumentNode, options: BackgroundQueryHookOptionsNoInfer & { + skip: boolean; +}): [ +QueryReference | undefined, +UseBackgroundQueryResult +]; + +// @public (undocumented) +function useBackgroundQuery(query: DocumentNode | TypedDocumentNode, options?: BackgroundQueryHookOptionsNoInfer): [ +QueryReference, +UseBackgroundQueryResult +]; + +// Warning: (ae-forgotten-export) The symbol "SkipToken" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +function useBackgroundQuery(query: DocumentNode | TypedDocumentNode, options: SkipToken): [undefined, UseBackgroundQueryResult]; + +// @public (undocumented) +function useBackgroundQuery(query: DocumentNode | TypedDocumentNode, options: SkipToken | (BackgroundQueryHookOptionsNoInfer & { + returnPartialData: true; +})): [ +QueryReference, TVariables> | undefined, +UseBackgroundQueryResult +]; + +// @public (undocumented) +function useBackgroundQuery(query: DocumentNode | TypedDocumentNode, options?: SkipToken | BackgroundQueryHookOptionsNoInfer): [ +QueryReference | undefined, +UseBackgroundQueryResult +]; + +// @public (undocumented) +type UseBackgroundQueryResult = { + fetchMore: FetchMoreFunction; + refetch: RefetchFunction; +}; + +// Warning: (ae-forgotten-export) The symbol "UseFragmentOptions" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "UseFragmentResult" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +function useFragment(options: UseFragmentOptions): UseFragmentResult; + +// @public (undocumented) +interface UseFragmentOptions extends Omit, NoInfer>, "id" | "query" | "optimistic" | "previousResult" | "returnPartialData">, Omit, "id" | "variables" | "returnPartialData"> { + // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "@apollo/client" does not have an export "ApolloClient" + client?: ApolloClient; + // (undocumented) + from: StoreObject | Reference | string; + // (undocumented) + optimistic?: boolean; +} + +// @public (undocumented) +type UseFragmentResult = { + data: TData; + complete: true; + missing?: never; +} | { + data: DeepPartial; + complete: false; + missing?: MissingTree; +}; + +// Warning: (ae-forgotten-export) The symbol "QueryResult" needs to be exported by the entry point index.d.ts +// +// @public +function useQuery(query: DocumentNode | TypedDocumentNode, options?: QueryHookOptions, NoInfer>): QueryResult; + +// Warning: (ae-forgotten-export) The symbol "UseReadQueryResult" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +function useReadQuery(queryRef: QueryReference): UseReadQueryResult; + +// @public (undocumented) +interface UseReadQueryResult { + data: TData; + error: ApolloError | undefined; + networkStatus: NetworkStatus; +} + +// Warning: (ae-forgotten-export) The symbol "SuspenseQueryHookOptions" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "UseSuspenseQueryResult" needs to be exported by the entry point index.d.ts // +// @public (undocumented) +function useSuspenseQuery, "variables">>(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer> & TOptions): UseSuspenseQueryResult | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? TOptions["skip"] extends boolean ? DeepPartial | undefined : DeepPartial : TOptions["skip"] extends boolean ? TData | undefined : TData, TVariables>; + +// @public (undocumented) +function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { + returnPartialData: true; + errorPolicy: "ignore" | "all"; +}): UseSuspenseQueryResult | undefined, TVariables>; + +// @public (undocumented) +function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { + errorPolicy: "ignore" | "all"; +}): UseSuspenseQueryResult; + +// @public (undocumented) +function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { + skip: boolean; + returnPartialData: true; +}): UseSuspenseQueryResult | undefined, TVariables>; + +// @public (undocumented) +function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { + returnPartialData: true; +}): UseSuspenseQueryResult, TVariables>; + +// @public (undocumented) +function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { + skip: boolean; +}): UseSuspenseQueryResult; + +// @public (undocumented) +function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer>): UseSuspenseQueryResult; + +// @public (undocumented) +function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SkipToken | (SuspenseQueryHookOptions, NoInfer> & { + returnPartialData: true; +})): UseSuspenseQueryResult | undefined, TVariables>; + +// @public (undocumented) +function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options?: SkipToken | SuspenseQueryHookOptions, NoInfer>): UseSuspenseQueryResult; + +// @public (undocumented) +interface UseSuspenseQueryResult { + // (undocumented) + client: ApolloClient; + // (undocumented) + data: TData; + // (undocumented) + error: ApolloError | undefined; + // (undocumented) + fetchMore: FetchMoreFunction; + // (undocumented) + networkStatus: NetworkStatus; + // (undocumented) + refetch: RefetchFunction; + // Warning: (ae-forgotten-export) The symbol "SubscribeToMoreFunction" needs to be exported by the entry point index.d.ts + // + // (undocumented) + subscribeToMore: SubscribeToMoreFunction; +} + +// @public (undocumented) +type WatchQueryFetchPolicy = FetchPolicy | "cache-and-network"; + // @public interface WatchQueryOptions extends SharedWatchQueryOptions { query: DocumentNode | TypedDocumentNode; } +// @public (undocumented) +interface WrappableHooks { + // Warning: (ae-forgotten-export) The symbol "useBackgroundQuery" needs to be exported by the entry point index.d.ts + // + // (undocumented) + useBackgroundQuery: typeof useBackgroundQuery; + // Warning: (ae-forgotten-export) The symbol "useFragment" needs to be exported by the entry point index.d.ts + // + // (undocumented) + useFragment: typeof useFragment; + // Warning: (ae-forgotten-export) The symbol "useQuery" needs to be exported by the entry point index.d.ts + // + // (undocumented) + useQuery: typeof useQuery; + // Warning: (ae-forgotten-export) The symbol "useReadQuery" needs to be exported by the entry point index.d.ts + // + // (undocumented) + useReadQuery: typeof useReadQuery; + // Warning: (ae-forgotten-export) The symbol "useSuspenseQuery" needs to be exported by the entry point index.d.ts + // + // (undocumented) + useSuspenseQuery: typeof useSuspenseQuery; +} + +// @public (undocumented) +const wrapperSymbol: unique symbol; + // @public (undocumented) export function wrapQueryRef(internalQueryRef: InternalQueryReference): QueryReference; @@ -1693,6 +2091,8 @@ export function wrapQueryRef(inter // src/core/types.ts:203:5 - (ae-forgotten-export) The symbol "Resolver" needs to be exported by the entry point index.d.ts // src/core/watchQueryOptions.ts:269:2 - (ae-forgotten-export) The symbol "IgnoreModifier" needs to be exported by the entry point index.d.ts // src/core/watchQueryOptions.ts:269:2 - (ae-forgotten-export) The symbol "UpdateQueryFn" needs to be exported by the entry point index.d.ts +// src/react/hooks/useBackgroundQuery.ts:30:3 - (ae-forgotten-export) The symbol "FetchMoreFunction" needs to be exported by the entry point index.d.ts +// src/react/hooks/useBackgroundQuery.ts:31:3 - (ae-forgotten-export) The symbol "RefetchFunction" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/.api-reports/api-report.md b/.api-reports/api-report.md index 1d09c28f12b..3a5d1aa7df9 100644 --- a/.api-reports/api-report.md +++ b/.api-reports/api-report.md @@ -3076,8 +3076,8 @@ interface WriteContext extends ReadMergeModifyContext { // src/core/watchQueryOptions.ts:269:2 - (ae-forgotten-export) The symbol "IgnoreModifier" needs to be exported by the entry point index.d.ts // src/core/watchQueryOptions.ts:269:2 - (ae-forgotten-export) The symbol "UpdateQueryFn" needs to be exported by the entry point index.d.ts // src/link/http/selectHttpOptionsAndBody.ts:128:32 - (ae-forgotten-export) The symbol "HttpQueryOptions" needs to be exported by the entry point index.d.ts -// src/react/hooks/useBackgroundQuery.ts:29:3 - (ae-forgotten-export) The symbol "FetchMoreFunction" needs to be exported by the entry point index.d.ts -// src/react/hooks/useBackgroundQuery.ts:30:3 - (ae-forgotten-export) The symbol "RefetchFunction" needs to be exported by the entry point index.d.ts +// src/react/hooks/useBackgroundQuery.ts:30:3 - (ae-forgotten-export) The symbol "FetchMoreFunction" needs to be exported by the entry point index.d.ts +// src/react/hooks/useBackgroundQuery.ts:31:3 - (ae-forgotten-export) The symbol "RefetchFunction" needs to be exported by the entry point index.d.ts // src/react/hooks/useLoadableQuery.ts:106:1 - (ae-forgotten-export) The symbol "ResetFunction" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/.size-limits.json b/.size-limits.json index 8d2f9f6c9d7..3fc5f41ba3b 100644 --- a/.size-limits.json +++ b/.size-limits.json @@ -1,4 +1,4 @@ { - "dist/apollo-client.min.cjs": 39075, + "dist/apollo-client.min.cjs": 39236, "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32584 } diff --git a/src/react/hooks/internal/index.ts b/src/react/hooks/internal/index.ts index 71cfbdc1799..0da2373ba70 100644 --- a/src/react/hooks/internal/index.ts +++ b/src/react/hooks/internal/index.ts @@ -4,3 +4,4 @@ export { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect.js"; export { useRenderGuard } from "./useRenderGuard.js"; export { useLazyRef } from "./useLazyRef.js"; export { __use } from "./__use.js"; +export { makeHookWrappable } from "./wrapHook.js"; diff --git a/src/react/hooks/internal/wrapHook.ts b/src/react/hooks/internal/wrapHook.ts new file mode 100644 index 00000000000..d8381905e10 --- /dev/null +++ b/src/react/hooks/internal/wrapHook.ts @@ -0,0 +1,80 @@ +import type { ApolloClient } from "../../../core/index.js"; +import { useApolloClient } from "../useApolloClient.js"; +import type { + useQuery, + useSuspenseQuery, + useBackgroundQuery, + useReadQuery, + useFragment, +} from "../index.js"; + +const wrapperSymbol = Symbol.for("apollo.hook.wrappers"); + +interface WrappableHooks { + useQuery: typeof useQuery; + useSuspenseQuery: typeof useSuspenseQuery; + useBackgroundQuery: typeof useBackgroundQuery; + useReadQuery: typeof useReadQuery; + useFragment: typeof useFragment; +} + +/** + * @internal + * Can be used to correctly type the [Symbol.for("apollo.hook.wrappers")] of + * a class that extends `ApolloClient`, to override/wrap hook functionality. + */ +export type HookWrappers = { + [K in keyof WrappableHooks]?: ( + originalHook: WrappableHooks[K] + ) => WrappableHooks[K]; +}; + +interface ApolloClientWithWrappers extends ApolloClient { + [wrapperSymbol]?: HookWrappers; +} + +/** + * @internal + * + * Makes an Apollo Client hook "wrappable". + * That means that the Apollo Client instance can expose a "wrapper" that will be + * used to wrap the original hook implementation with additional logic. + * @example + * ```tsx + * // this is already done in `@apollo/client` for all wrappable hooks (see `WrappableHooks`) + * const wrappedUseQuery = makeHookWrappable('useQuery', useQuery, (_, options) => options.client); + * + * // this is what a library like `@apollo/client-react-streaming` would do + * class ApolloClientWithStreaming extends ApolloClient { + * [Symbol.for("apollo.hook.wrappers")] = { + * useQuery: (original) => (query, options) => { + * console.log("useQuery was called with options", options); + * return original(query, options); + * } + * } + * } + * + * // this will now log the options and then call the original `useQuery` + * const client = new ApolloClientWithStreaming({ ... }); + * wrappedUseQuery(query, { client }); + * ``` + */ +export function makeHookWrappable( + hookName: Name, + useHook: WrappableHooks[Name], + clientFromOptions: ( + ...args: Parameters + ) => ApolloClientWithWrappers | undefined +): WrappableHooks[Name] { + return function (this: any) { + const args = arguments as unknown as Parameters; + const client: ApolloClientWithWrappers = useApolloClient( + clientFromOptions.apply(this, args) + ); + const wrappers = client[wrapperSymbol]; + const wrapper = wrappers && wrappers[hookName]; + const wrappedHook: WrappableHooks[Name] = + wrapper ? wrapper(useHook) : useHook; + return (wrappedHook as any).apply(this, args); + } as any; +} diff --git a/src/react/hooks/useBackgroundQuery.ts b/src/react/hooks/useBackgroundQuery.ts index ab9105d4243..e8fbf6ac572 100644 --- a/src/react/hooks/useBackgroundQuery.ts +++ b/src/react/hooks/useBackgroundQuery.ts @@ -15,7 +15,7 @@ import { } from "../internal/index.js"; import type { CacheKey, QueryReference } from "../internal/index.js"; import type { BackgroundQueryHookOptions, NoInfer } from "../types/types.js"; -import { __use } from "./internal/index.js"; +import { __use, makeHookWrappable } from "./internal/index.js"; import { useWatchQueryOptions } from "./useSuspenseQuery.js"; import type { FetchMoreFunction, RefetchFunction } from "./useSuspenseQuery.js"; import { canonicalStringify } from "../../cache/index.js"; @@ -246,3 +246,11 @@ export function useBackgroundQuery< { fetchMore, refetch }, ]; } + +const wrapped = /*#__PURE__*/ makeHookWrappable( + "useBackgroundQuery", + useBackgroundQuery, + (_, options) => (typeof options === "object" ? options.client : undefined) +); +// @ts-expect-error Cannot assign to 'useBackgroundQuery' because it is a function.ts(2630) +useBackgroundQuery = wrapped; diff --git a/src/react/hooks/useFragment.ts b/src/react/hooks/useFragment.ts index 80e59d0e49a..bacc92b735c 100644 --- a/src/react/hooks/useFragment.ts +++ b/src/react/hooks/useFragment.ts @@ -14,7 +14,11 @@ import { useApolloClient } from "./useApolloClient.js"; import { useSyncExternalStore } from "./useSyncExternalStore.js"; import type { ApolloClient, OperationVariables } from "../../core/index.js"; import type { NoInfer } from "../types/types.js"; -import { useDeepMemo, useLazyRef } from "./internal/index.js"; +import { + useDeepMemo, + useLazyRef, + makeHookWrappable, +} from "./internal/index.js"; export interface UseFragmentOptions extends Omit< @@ -112,6 +116,14 @@ export function useFragment( ); } +const wrapped = /*#__PURE__*/ makeHookWrappable( + "useFragment", + useFragment, + (options) => options.client +); +// @ts-expect-error Cannot assign to 'useFragment' because it is a function.ts(2630) +useFragment = wrapped; + function diffToResult( diff: Cache.DiffResult ): UseFragmentResult { diff --git a/src/react/hooks/useQuery.ts b/src/react/hooks/useQuery.ts index 4bc596ed371..ac963d03c39 100644 --- a/src/react/hooks/useQuery.ts +++ b/src/react/hooks/useQuery.ts @@ -36,6 +36,7 @@ import { isNonEmptyArray, maybeDeepFreeze, } from "../../utilities/index.js"; +import { makeHookWrappable } from "./internal/index.js"; const { prototype: { hasOwnProperty }, @@ -90,6 +91,14 @@ export function useQuery< ); } +const wrapped = /*#__PURE__*/ makeHookWrappable( + "useQuery", + useQuery, + (_, options) => options && options.client +); +// @ts-expect-error Cannot assign to 'useQuery' because it is a function.ts(2630) +useQuery = wrapped; + export function useInternalState( client: ApolloClient, query: DocumentNode | TypedDocumentNode diff --git a/src/react/hooks/useReadQuery.ts b/src/react/hooks/useReadQuery.ts index 3f110b26164..8fdfbd72a3e 100644 --- a/src/react/hooks/useReadQuery.ts +++ b/src/react/hooks/useReadQuery.ts @@ -5,7 +5,7 @@ import { updateWrappedQueryRef, } from "../internal/index.js"; import type { QueryReference } from "../internal/index.js"; -import { __use } from "./internal/index.js"; +import { __use, makeHookWrappable } from "./internal/index.js"; import { toApolloError } from "./useSuspenseQuery.js"; import { useSyncExternalStore } from "./useSyncExternalStore.js"; import type { ApolloError } from "../../errors/index.js"; @@ -80,3 +80,10 @@ export function useReadQuery( }; }, [result]); } +const wrapped = /*#__PURE__*/ makeHookWrappable( + "useReadQuery", + useReadQuery, + () => undefined +); +// @ts-expect-error Cannot assign to 'useReadQuery' because it is a function.ts(2630) +useReadQuery = wrapped; diff --git a/src/react/hooks/useSuspenseQuery.ts b/src/react/hooks/useSuspenseQuery.ts index 3a69e175ed5..ed042825eda 100644 --- a/src/react/hooks/useSuspenseQuery.ts +++ b/src/react/hooks/useSuspenseQuery.ts @@ -20,7 +20,7 @@ import type { ObservableQueryFields, NoInfer, } from "../types/types.js"; -import { __use, useDeepMemo } from "./internal/index.js"; +import { __use, useDeepMemo, makeHookWrappable } from "./internal/index.js"; import { getSuspenseCache } from "../internal/index.js"; import { canonicalStringify } from "../../cache/index.js"; import { skipToken } from "./constants.js"; @@ -281,6 +281,14 @@ export function useSuspenseQuery< }, [client, fetchMore, refetch, result, subscribeToMore]); } +const wrapped = /*#__PURE__*/ makeHookWrappable( + "useSuspenseQuery", + useSuspenseQuery, + (_, options) => (typeof options === "object" ? options.client : undefined) +); +// @ts-expect-error Cannot assign to 'useSuspenseQuery' because it is a function.ts(2630) +useSuspenseQuery = wrapped; + function validateOptions(options: WatchQueryOptions) { const { query, fetchPolicy, returnPartialData } = options; diff --git a/src/react/internal/index.ts b/src/react/internal/index.ts index cbcab8f0209..a453c6f802c 100644 --- a/src/react/internal/index.ts +++ b/src/react/internal/index.ts @@ -9,3 +9,4 @@ export { wrapQueryRef, } from "./cache/QueryReference.js"; export type { SuspenseCacheOptions } from "./cache/SuspenseCache.js"; +export type { HookWrappers } from "../hooks/internal/wrapHook.js"; From 661577898c3aff95d18a0c429ca3f3717b5604e2 Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Fri, 23 Feb 2024 12:29:17 +0100 Subject: [PATCH 02/16] update api extractor --- .api-reports/api-report-react.md | 4 ++-- .api-reports/api-report-react_hooks.md | 4 ++-- .api-reports/api-report-react_internal.md | 23 ++++------------------- .api-reports/api-report.md | 4 ++-- 4 files changed, 10 insertions(+), 25 deletions(-) diff --git a/.api-reports/api-report-react.md b/.api-reports/api-report-react.md index 5197af6094e..0cdf3ce93bc 100644 --- a/.api-reports/api-report-react.md +++ b/.api-reports/api-report-react.md @@ -2398,8 +2398,8 @@ interface WatchQueryOptions { version?: string; } -// Warning: (ae-forgotten-export) The symbol "ApolloClient" needs to be exported by the entry point index.d.ts -// -// @public (undocumented) -interface ApolloClientWithWrappers extends ApolloClient { - // (undocumented) - [wrapperSymbol]?: HookWrappers; -} - // @public (undocumented) class ApolloError extends Error { // Warning: (ae-forgotten-export) The symbol "ApolloErrorOptions" needs to be exported by the entry point index.d.ts @@ -345,6 +337,7 @@ type BackgroundQueryHookOptionsNoInfer extends SharedWatchQueryOptions { + // Warning: (ae-forgotten-export) The symbol "ApolloClient" needs to be exported by the entry point index.d.ts // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "@apollo/client" does not have an export "ApolloClient" client?: ApolloClient; context?: DefaultContext; @@ -837,7 +830,7 @@ interface GraphQLRequest> { // Warning: (ae-forgotten-export) The symbol "WrappableHooks" needs to be exported by the entry point index.d.ts // -// @public (undocumented) +// @internal export type HookWrappers = { [K in keyof WrappableHooks]?: (originalHook: WrappableHooks[K]) => WrappableHooks[K]; }; @@ -1008,11 +1001,6 @@ type LocalStateOptions = { fragmentMatcher?: FragmentMatcher; }; -// Warning: (ae-forgotten-export) The symbol "ApolloClientWithWrappers" needs to be exported by the entry point index.d.ts -// -// @internal -export function makeHookWrappable(hookName: Name, useHook: WrappableHooks[Name], clientFromOptions: (...args: Parameters) => ApolloClientWithWrappers | undefined): WrappableHooks[Name]; - // @public (undocumented) type MaybeAsync = T | PromiseLike; @@ -2067,9 +2055,6 @@ interface WrappableHooks { useSuspenseQuery: typeof useSuspenseQuery; } -// @public (undocumented) -const wrapperSymbol: unique symbol; - // @public (undocumented) export function wrapQueryRef(internalQueryRef: InternalQueryReference): QueryReference; @@ -2091,8 +2076,8 @@ export function wrapQueryRef(inter // src/core/types.ts:203:5 - (ae-forgotten-export) The symbol "Resolver" needs to be exported by the entry point index.d.ts // src/core/watchQueryOptions.ts:269:2 - (ae-forgotten-export) The symbol "IgnoreModifier" needs to be exported by the entry point index.d.ts // src/core/watchQueryOptions.ts:269:2 - (ae-forgotten-export) The symbol "UpdateQueryFn" needs to be exported by the entry point index.d.ts -// src/react/hooks/useBackgroundQuery.ts:30:3 - (ae-forgotten-export) The symbol "FetchMoreFunction" needs to be exported by the entry point index.d.ts -// src/react/hooks/useBackgroundQuery.ts:31:3 - (ae-forgotten-export) The symbol "RefetchFunction" needs to be exported by the entry point index.d.ts +// src/react/hooks/useBackgroundQuery.ts:29:3 - (ae-forgotten-export) The symbol "FetchMoreFunction" needs to be exported by the entry point index.d.ts +// src/react/hooks/useBackgroundQuery.ts:30:3 - (ae-forgotten-export) The symbol "RefetchFunction" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/.api-reports/api-report.md b/.api-reports/api-report.md index 3a5d1aa7df9..1d09c28f12b 100644 --- a/.api-reports/api-report.md +++ b/.api-reports/api-report.md @@ -3076,8 +3076,8 @@ interface WriteContext extends ReadMergeModifyContext { // src/core/watchQueryOptions.ts:269:2 - (ae-forgotten-export) The symbol "IgnoreModifier" needs to be exported by the entry point index.d.ts // src/core/watchQueryOptions.ts:269:2 - (ae-forgotten-export) The symbol "UpdateQueryFn" needs to be exported by the entry point index.d.ts // src/link/http/selectHttpOptionsAndBody.ts:128:32 - (ae-forgotten-export) The symbol "HttpQueryOptions" needs to be exported by the entry point index.d.ts -// src/react/hooks/useBackgroundQuery.ts:30:3 - (ae-forgotten-export) The symbol "FetchMoreFunction" needs to be exported by the entry point index.d.ts -// src/react/hooks/useBackgroundQuery.ts:31:3 - (ae-forgotten-export) The symbol "RefetchFunction" needs to be exported by the entry point index.d.ts +// src/react/hooks/useBackgroundQuery.ts:29:3 - (ae-forgotten-export) The symbol "FetchMoreFunction" needs to be exported by the entry point index.d.ts +// src/react/hooks/useBackgroundQuery.ts:30:3 - (ae-forgotten-export) The symbol "RefetchFunction" needs to be exported by the entry point index.d.ts // src/react/hooks/useLoadableQuery.ts:106:1 - (ae-forgotten-export) The symbol "ResetFunction" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) From 6d573c581eef07b53fd7547af12b0fe992f6c92a Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Fri, 23 Feb 2024 12:29:49 +0100 Subject: [PATCH 03/16] changeset --- .changeset/curvy-maps-give.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/curvy-maps-give.md diff --git a/.changeset/curvy-maps-give.md b/.changeset/curvy-maps-give.md new file mode 100644 index 00000000000..8217041c861 --- /dev/null +++ b/.changeset/curvy-maps-give.md @@ -0,0 +1,5 @@ +--- +"@apollo/client": patch +--- + +Allow Apollo Client instance to intercept hook functionality From 45fc3b3b85d6e07459f2d944df261f3199c2279a Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Fri, 23 Feb 2024 12:51:29 +0100 Subject: [PATCH 04/16] keep PURE comments when building cjs --- config/rollup.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/rollup.config.js b/config/rollup.config.js index 90dd8f56dec..d7be3528609 100644 --- a/config/rollup.config.js +++ b/config/rollup.config.js @@ -8,7 +8,7 @@ import cleanup from "rollup-plugin-cleanup"; const entryPoints = require("./entryPoints"); const distDir = "./dist"; -const removeComments = cleanup({}); +const removeComments = cleanup({ comments: ["some", /#__PURE__/] }); function isExternal(id, parentId, entryPointsAreExternal = true) { let posixId = toPosixPath(id); From d7934e097a0c08a0ec5f23bc639bf1c5379b1ed5 Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Fri, 23 Feb 2024 13:42:39 +0100 Subject: [PATCH 05/16] shave a few bytes --- src/react/hooks/useReadQuery.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/react/hooks/useReadQuery.ts b/src/react/hooks/useReadQuery.ts index 8fdfbd72a3e..beb43aedaf8 100644 --- a/src/react/hooks/useReadQuery.ts +++ b/src/react/hooks/useReadQuery.ts @@ -83,7 +83,7 @@ export function useReadQuery( const wrapped = /*#__PURE__*/ makeHookWrappable( "useReadQuery", useReadQuery, - () => undefined + (): undefined => {} ); // @ts-expect-error Cannot assign to 'useReadQuery' because it is a function.ts(2630) useReadQuery = wrapped; From ce8e6e785923319e9f9f649c262135acf70801be Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Fri, 23 Feb 2024 13:47:58 +0100 Subject: [PATCH 06/16] Workaround for `useReadableQuery` without wrapping `Provider`. --- src/react/hooks/internal/wrapHook.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/react/hooks/internal/wrapHook.ts b/src/react/hooks/internal/wrapHook.ts index d8381905e10..3b08447e25d 100644 --- a/src/react/hooks/internal/wrapHook.ts +++ b/src/react/hooks/internal/wrapHook.ts @@ -68,10 +68,17 @@ export function makeHookWrappable( ): WrappableHooks[Name] { return function (this: any) { const args = arguments as unknown as Parameters; - const client: ApolloClientWithWrappers = useApolloClient( - clientFromOptions.apply(this, args) - ); - const wrappers = client[wrapperSymbol]; + let client: ApolloClientWithWrappers | undefined; + try { + client = useApolloClient(clientFromOptions.apply(this, args)); + } catch { + /* + Not wrapped in a `Prodiver`. + This is valid for `useReadableQuery`. + Other hooks will error on their own. + */ + } + const wrappers = client && client[wrapperSymbol]; const wrapper = wrappers && wrappers[hookName]; const wrappedHook: WrappableHooks[Name] = wrapper ? wrapper(useHook) : useHook; From bb2c7184f10351afbf76bfb1d27d296cac6a317a Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Fri, 23 Feb 2024 13:51:31 +0100 Subject: [PATCH 07/16] update size-limits --- .size-limits.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.size-limits.json b/.size-limits.json index 3fc5f41ba3b..0315a805278 100644 --- a/.size-limits.json +++ b/.size-limits.json @@ -1,4 +1,4 @@ { - "dist/apollo-client.min.cjs": 39236, + "dist/apollo-client.min.cjs": 39246, "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32584 } From c153a4eea12f08195d9dcb72eb6169dbfe68d2a7 Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Fri, 23 Feb 2024 14:13:49 +0100 Subject: [PATCH 08/16] Update src/react/hooks/internal/wrapHook.ts --- src/react/hooks/internal/wrapHook.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/react/hooks/internal/wrapHook.ts b/src/react/hooks/internal/wrapHook.ts index 3b08447e25d..17794a6d3ac 100644 --- a/src/react/hooks/internal/wrapHook.ts +++ b/src/react/hooks/internal/wrapHook.ts @@ -73,7 +73,7 @@ export function makeHookWrappable( client = useApolloClient(clientFromOptions.apply(this, args)); } catch { /* - Not wrapped in a `Prodiver`. + Not wrapped in a `Provider`. This is valid for `useReadableQuery`. Other hooks will error on their own. */ From 7497c9c43ab1e9b062da57c20b669b9ae3ebab2d Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Fri, 23 Feb 2024 18:35:29 +0100 Subject: [PATCH 09/16] put wrappers on `QueryManager` instead --- .size-limits.json | 2 +- src/react/hooks/internal/wrapHook.ts | 30 +++++++++++++-------------- src/react/hooks/useBackgroundQuery.ts | 3 ++- src/react/hooks/useFragment.ts | 2 +- src/react/hooks/useQuery.ts | 2 +- src/react/hooks/useReadQuery.ts | 2 +- src/react/hooks/useSuspenseQuery.ts | 3 ++- 7 files changed, 22 insertions(+), 22 deletions(-) diff --git a/.size-limits.json b/.size-limits.json index 0315a805278..768bc9417b8 100644 --- a/.size-limits.json +++ b/.size-limits.json @@ -1,4 +1,4 @@ { - "dist/apollo-client.min.cjs": 39246, + "dist/apollo-client.min.cjs": 39260, "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32584 } diff --git a/src/react/hooks/internal/wrapHook.ts b/src/react/hooks/internal/wrapHook.ts index 17794a6d3ac..b089fe331ed 100644 --- a/src/react/hooks/internal/wrapHook.ts +++ b/src/react/hooks/internal/wrapHook.ts @@ -1,5 +1,3 @@ -import type { ApolloClient } from "../../../core/index.js"; -import { useApolloClient } from "../useApolloClient.js"; import type { useQuery, useSuspenseQuery, @@ -7,6 +5,9 @@ import type { useReadQuery, useFragment, } from "../index.js"; +import type { QueryManager } from "../../../core/QueryManager.js"; +import type { ApolloClient } from "../../../core/ApolloClient.js"; +import type { ObservableQuery } from "../../../core/ObservableQuery.js"; const wrapperSymbol = Symbol.for("apollo.hook.wrappers"); @@ -29,7 +30,7 @@ export type HookWrappers = { ) => WrappableHooks[K]; }; -interface ApolloClientWithWrappers extends ApolloClient { +interface QueryManagerWithWrappers extends QueryManager { [wrapperSymbol]?: HookWrappers; } @@ -62,23 +63,20 @@ interface ApolloClientWithWrappers extends ApolloClient { export function makeHookWrappable( hookName: Name, useHook: WrappableHooks[Name], - clientFromOptions: ( + getClientFromOptions: ( ...args: Parameters - ) => ApolloClientWithWrappers | undefined + ) => ObservableQuery | ApolloClient ): WrappableHooks[Name] { return function (this: any) { const args = arguments as unknown as Parameters; - let client: ApolloClientWithWrappers | undefined; - try { - client = useApolloClient(clientFromOptions.apply(this, args)); - } catch { - /* - Not wrapped in a `Provider`. - This is valid for `useReadableQuery`. - Other hooks will error on their own. - */ - } - const wrappers = client && client[wrapperSymbol]; + const queryManager = ( + getClientFromOptions.apply(this, args) as unknown as { + // both `ApolloClient` and `ObservableQuery` have a `queryManager` property + // but they're both `private`, so we have to cast around for a bit here. + queryManager: QueryManagerWithWrappers; + } + )["queryManager"]; + const wrappers = queryManager && queryManager[wrapperSymbol]; const wrapper = wrappers && wrappers[hookName]; const wrappedHook: WrappableHooks[Name] = wrapper ? wrapper(useHook) : useHook; diff --git a/src/react/hooks/useBackgroundQuery.ts b/src/react/hooks/useBackgroundQuery.ts index e8fbf6ac572..4a7e04dbb92 100644 --- a/src/react/hooks/useBackgroundQuery.ts +++ b/src/react/hooks/useBackgroundQuery.ts @@ -250,7 +250,8 @@ export function useBackgroundQuery< const wrapped = /*#__PURE__*/ makeHookWrappable( "useBackgroundQuery", useBackgroundQuery, - (_, options) => (typeof options === "object" ? options.client : undefined) + (_, options) => + useApolloClient(typeof options === "object" ? options.client : undefined) ); // @ts-expect-error Cannot assign to 'useBackgroundQuery' because it is a function.ts(2630) useBackgroundQuery = wrapped; diff --git a/src/react/hooks/useFragment.ts b/src/react/hooks/useFragment.ts index bacc92b735c..5ee91e79197 100644 --- a/src/react/hooks/useFragment.ts +++ b/src/react/hooks/useFragment.ts @@ -119,7 +119,7 @@ export function useFragment( const wrapped = /*#__PURE__*/ makeHookWrappable( "useFragment", useFragment, - (options) => options.client + (options) => useApolloClient(options.client) ); // @ts-expect-error Cannot assign to 'useFragment' because it is a function.ts(2630) useFragment = wrapped; diff --git a/src/react/hooks/useQuery.ts b/src/react/hooks/useQuery.ts index ac963d03c39..71f2ba26a16 100644 --- a/src/react/hooks/useQuery.ts +++ b/src/react/hooks/useQuery.ts @@ -94,7 +94,7 @@ export function useQuery< const wrapped = /*#__PURE__*/ makeHookWrappable( "useQuery", useQuery, - (_, options) => options && options.client + (_, options) => useApolloClient(options && options.client) ); // @ts-expect-error Cannot assign to 'useQuery' because it is a function.ts(2630) useQuery = wrapped; diff --git a/src/react/hooks/useReadQuery.ts b/src/react/hooks/useReadQuery.ts index beb43aedaf8..3c5635f9d3d 100644 --- a/src/react/hooks/useReadQuery.ts +++ b/src/react/hooks/useReadQuery.ts @@ -83,7 +83,7 @@ export function useReadQuery( const wrapped = /*#__PURE__*/ makeHookWrappable( "useReadQuery", useReadQuery, - (): undefined => {} + (ref) => unwrapQueryRef(ref)["observable"] ); // @ts-expect-error Cannot assign to 'useReadQuery' because it is a function.ts(2630) useReadQuery = wrapped; diff --git a/src/react/hooks/useSuspenseQuery.ts b/src/react/hooks/useSuspenseQuery.ts index ed042825eda..ed5c0293706 100644 --- a/src/react/hooks/useSuspenseQuery.ts +++ b/src/react/hooks/useSuspenseQuery.ts @@ -284,7 +284,8 @@ export function useSuspenseQuery< const wrapped = /*#__PURE__*/ makeHookWrappable( "useSuspenseQuery", useSuspenseQuery, - (_, options) => (typeof options === "object" ? options.client : undefined) + (_, options) => + useApolloClient(typeof options === "object" ? options.client : undefined) ); // @ts-expect-error Cannot assign to 'useSuspenseQuery' because it is a function.ts(2630) useSuspenseQuery = wrapped; From 94aa7473b4358d02bd23612aa3b026bdc69220a2 Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Mon, 26 Feb 2024 11:26:40 +0100 Subject: [PATCH 10/16] add `__NO_SIDE_EFFECTS__` annotation --- config/rollup.config.js | 4 +++- src/react/hooks/internal/wrapHook.ts | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/config/rollup.config.js b/config/rollup.config.js index d7be3528609..5ed93b35165 100644 --- a/config/rollup.config.js +++ b/config/rollup.config.js @@ -8,7 +8,9 @@ import cleanup from "rollup-plugin-cleanup"; const entryPoints = require("./entryPoints"); const distDir = "./dist"; -const removeComments = cleanup({ comments: ["some", /#__PURE__/] }); +const removeComments = cleanup({ + comments: ["some", /#__PURE__/, /#__NO_SIDE_EFFECTS__/], +}); function isExternal(id, parentId, entryPointsAreExternal = true) { let posixId = toPosixPath(id); diff --git a/src/react/hooks/internal/wrapHook.ts b/src/react/hooks/internal/wrapHook.ts index b089fe331ed..2c94e978bae 100644 --- a/src/react/hooks/internal/wrapHook.ts +++ b/src/react/hooks/internal/wrapHook.ts @@ -60,6 +60,7 @@ interface QueryManagerWithWrappers extends QueryManager { * wrappedUseQuery(query, { client }); * ``` */ +/*#__NO_SIDE_EFFECTS__*/ export function makeHookWrappable( hookName: Name, useHook: WrappableHooks[Name], From 5519a641b4f332785df611ad991180b0b4386b0c Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Mon, 26 Feb 2024 14:38:57 +0100 Subject: [PATCH 11/16] swap call order --- src/react/hooks/internal/wrapHook.ts | 4 ++-- src/react/hooks/useBackgroundQuery.ts | 4 ++-- src/react/hooks/useFragment.ts | 4 ++-- src/react/hooks/useQuery.ts | 4 ++-- src/react/hooks/useReadQuery.ts | 4 ++-- src/react/hooks/useSuspenseQuery.ts | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/react/hooks/internal/wrapHook.ts b/src/react/hooks/internal/wrapHook.ts index 2c94e978bae..fa31b3275e6 100644 --- a/src/react/hooks/internal/wrapHook.ts +++ b/src/react/hooks/internal/wrapHook.ts @@ -63,10 +63,10 @@ interface QueryManagerWithWrappers extends QueryManager { /*#__NO_SIDE_EFFECTS__*/ export function makeHookWrappable( hookName: Name, - useHook: WrappableHooks[Name], getClientFromOptions: ( ...args: Parameters - ) => ObservableQuery | ApolloClient + ) => ObservableQuery | ApolloClient, + useHook: WrappableHooks[Name] ): WrappableHooks[Name] { return function (this: any) { const args = arguments as unknown as Parameters; diff --git a/src/react/hooks/useBackgroundQuery.ts b/src/react/hooks/useBackgroundQuery.ts index 4a7e04dbb92..70c09bb5a5b 100644 --- a/src/react/hooks/useBackgroundQuery.ts +++ b/src/react/hooks/useBackgroundQuery.ts @@ -249,9 +249,9 @@ export function useBackgroundQuery< const wrapped = /*#__PURE__*/ makeHookWrappable( "useBackgroundQuery", - useBackgroundQuery, (_, options) => - useApolloClient(typeof options === "object" ? options.client : undefined) + useApolloClient(typeof options === "object" ? options.client : undefined), + useBackgroundQuery ); // @ts-expect-error Cannot assign to 'useBackgroundQuery' because it is a function.ts(2630) useBackgroundQuery = wrapped; diff --git a/src/react/hooks/useFragment.ts b/src/react/hooks/useFragment.ts index 5ee91e79197..9ec2a4b714e 100644 --- a/src/react/hooks/useFragment.ts +++ b/src/react/hooks/useFragment.ts @@ -118,8 +118,8 @@ export function useFragment( const wrapped = /*#__PURE__*/ makeHookWrappable( "useFragment", - useFragment, - (options) => useApolloClient(options.client) + (options) => useApolloClient(options.client), + useFragment ); // @ts-expect-error Cannot assign to 'useFragment' because it is a function.ts(2630) useFragment = wrapped; diff --git a/src/react/hooks/useQuery.ts b/src/react/hooks/useQuery.ts index 71f2ba26a16..c7054ac4cec 100644 --- a/src/react/hooks/useQuery.ts +++ b/src/react/hooks/useQuery.ts @@ -93,8 +93,8 @@ export function useQuery< const wrapped = /*#__PURE__*/ makeHookWrappable( "useQuery", - useQuery, - (_, options) => useApolloClient(options && options.client) + (_, options) => useApolloClient(options && options.client), + useQuery ); // @ts-expect-error Cannot assign to 'useQuery' because it is a function.ts(2630) useQuery = wrapped; diff --git a/src/react/hooks/useReadQuery.ts b/src/react/hooks/useReadQuery.ts index 3c5635f9d3d..400ad2c7a12 100644 --- a/src/react/hooks/useReadQuery.ts +++ b/src/react/hooks/useReadQuery.ts @@ -82,8 +82,8 @@ export function useReadQuery( } const wrapped = /*#__PURE__*/ makeHookWrappable( "useReadQuery", - useReadQuery, - (ref) => unwrapQueryRef(ref)["observable"] + (ref) => unwrapQueryRef(ref)["observable"], + useReadQuery ); // @ts-expect-error Cannot assign to 'useReadQuery' because it is a function.ts(2630) useReadQuery = wrapped; diff --git a/src/react/hooks/useSuspenseQuery.ts b/src/react/hooks/useSuspenseQuery.ts index ed5c0293706..2ad3c8346d2 100644 --- a/src/react/hooks/useSuspenseQuery.ts +++ b/src/react/hooks/useSuspenseQuery.ts @@ -283,9 +283,9 @@ export function useSuspenseQuery< const wrapped = /*#__PURE__*/ makeHookWrappable( "useSuspenseQuery", - useSuspenseQuery, (_, options) => - useApolloClient(typeof options === "object" ? options.client : undefined) + useApolloClient(typeof options === "object" ? options.client : undefined), + useSuspenseQuery ); // @ts-expect-error Cannot assign to 'useSuspenseQuery' because it is a function.ts(2630) useSuspenseQuery = wrapped; From c58802eb4cb77e19b79bb8ef9a6c93e783ab950f Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Mon, 26 Feb 2024 15:38:51 +0100 Subject: [PATCH 12/16] better tree-shaking approach --- .size-limits.json | 2 +- src/react/hooks/useBackgroundQuery.ts | 23 ++++++++++++---------- src/react/hooks/useFragment.ts | 23 ++++++++++++++-------- src/react/hooks/useQuery.ts | 28 ++++++++++++++++++--------- src/react/hooks/useReadQuery.ts | 21 +++++++++++++------- src/react/hooks/useSuspenseQuery.ts | 22 +++++++++++---------- 6 files changed, 74 insertions(+), 45 deletions(-) diff --git a/.size-limits.json b/.size-limits.json index 768bc9417b8..bb268e153b8 100644 --- a/.size-limits.json +++ b/.size-limits.json @@ -1,4 +1,4 @@ { - "dist/apollo-client.min.cjs": 39260, + "dist/apollo-client.min.cjs": 39283, "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32584 } diff --git a/src/react/hooks/useBackgroundQuery.ts b/src/react/hooks/useBackgroundQuery.ts index 70c09bb5a5b..7659d43f492 100644 --- a/src/react/hooks/useBackgroundQuery.ts +++ b/src/react/hooks/useBackgroundQuery.ts @@ -170,7 +170,19 @@ export function useBackgroundQuery< UseBackgroundQueryResult, ]; -export function useBackgroundQuery< +export function useBackgroundQuery() { + // @ts-expect-error Cannot assign to 'useBackgroundQuery' because it is a function.ts(2630) + useBackgroundQuery = makeHookWrappable( + "useBackgroundQuery", + (_, options) => + useApolloClient(typeof options === "object" ? options.client : undefined), + _useBackgroundQuery as any + ); + + return useBackgroundQuery.apply(null, arguments as any); +} + +function _useBackgroundQuery< TData = unknown, TVariables extends OperationVariables = OperationVariables, >( @@ -246,12 +258,3 @@ export function useBackgroundQuery< { fetchMore, refetch }, ]; } - -const wrapped = /*#__PURE__*/ makeHookWrappable( - "useBackgroundQuery", - (_, options) => - useApolloClient(typeof options === "object" ? options.client : undefined), - useBackgroundQuery -); -// @ts-expect-error Cannot assign to 'useBackgroundQuery' because it is a function.ts(2630) -useBackgroundQuery = wrapped; diff --git a/src/react/hooks/useFragment.ts b/src/react/hooks/useFragment.ts index 9ec2a4b714e..dae10a9a83e 100644 --- a/src/react/hooks/useFragment.ts +++ b/src/react/hooks/useFragment.ts @@ -57,6 +57,21 @@ export type UseFragmentResult = export function useFragment( options: UseFragmentOptions +): UseFragmentResult; + +export function useFragment() { + // @ts-expect-error Cannot assign to 'useFragment' because it is a function.ts(2630) + useFragment = makeHookWrappable( + "useFragment", + (options) => useApolloClient(options.client), + _useFragment + ); + + return useFragment.apply(null, arguments as any); +} + +function _useFragment( + options: UseFragmentOptions ): UseFragmentResult { const { cache } = useApolloClient(options.client); @@ -116,14 +131,6 @@ export function useFragment( ); } -const wrapped = /*#__PURE__*/ makeHookWrappable( - "useFragment", - (options) => useApolloClient(options.client), - useFragment -); -// @ts-expect-error Cannot assign to 'useFragment' because it is a function.ts(2630) -useFragment = wrapped; - function diffToResult( diff: Cache.DiffResult ): UseFragmentResult { diff --git a/src/react/hooks/useQuery.ts b/src/react/hooks/useQuery.ts index c7054ac4cec..896239f2346 100644 --- a/src/react/hooks/useQuery.ts +++ b/src/react/hooks/useQuery.ts @@ -79,26 +79,36 @@ const { export function useQuery< TData = any, TVariables extends OperationVariables = OperationVariables, +>( + query: DocumentNode | TypedDocumentNode, + options?: QueryHookOptions, NoInfer> +): QueryResult; + +export function useQuery() { + // @ts-expect-error Cannot assign to 'useQuery' because it is a function. ts(2630) + useQuery = makeHookWrappable( + "useQuery", + (_, options) => useApolloClient(options && options.client), + _useQuery + ); + return useQuery.apply(null, arguments as any); +} + +function _useQuery< + TData = any, + TVariables extends OperationVariables = OperationVariables, >( query: DocumentNode | TypedDocumentNode, options: QueryHookOptions< NoInfer, NoInfer > = Object.create(null) -): QueryResult { +) { return useInternalState(useApolloClient(options.client), query).useQuery( options ); } -const wrapped = /*#__PURE__*/ makeHookWrappable( - "useQuery", - (_, options) => useApolloClient(options && options.client), - useQuery -); -// @ts-expect-error Cannot assign to 'useQuery' because it is a function.ts(2630) -useQuery = wrapped; - export function useInternalState( client: ApolloClient, query: DocumentNode | TypedDocumentNode diff --git a/src/react/hooks/useReadQuery.ts b/src/react/hooks/useReadQuery.ts index 400ad2c7a12..b6479e26ea5 100644 --- a/src/react/hooks/useReadQuery.ts +++ b/src/react/hooks/useReadQuery.ts @@ -38,6 +38,20 @@ export interface UseReadQueryResult { export function useReadQuery( queryRef: QueryReference +): UseReadQueryResult; + +export function useReadQuery() { + // @ts-expect-error Cannot assign to 'useReadQuery' because it is a function.ts(2630) + useReadQuery = makeHookWrappable( + "useReadQuery", + (ref) => unwrapQueryRef(ref)["observable"], + _useReadQuery + ); + return useReadQuery.apply(null, arguments as any); +} + +function _useReadQuery( + queryRef: QueryReference ): UseReadQueryResult { const internalQueryRef = React.useMemo( () => unwrapQueryRef(queryRef), @@ -80,10 +94,3 @@ export function useReadQuery( }; }, [result]); } -const wrapped = /*#__PURE__*/ makeHookWrappable( - "useReadQuery", - (ref) => unwrapQueryRef(ref)["observable"], - useReadQuery -); -// @ts-expect-error Cannot assign to 'useReadQuery' because it is a function.ts(2630) -useReadQuery = wrapped; diff --git a/src/react/hooks/useSuspenseQuery.ts b/src/react/hooks/useSuspenseQuery.ts index 2ad3c8346d2..47da76ebfd2 100644 --- a/src/react/hooks/useSuspenseQuery.ts +++ b/src/react/hooks/useSuspenseQuery.ts @@ -166,7 +166,18 @@ export function useSuspenseQuery< | SuspenseQueryHookOptions, NoInfer> ): UseSuspenseQueryResult; -export function useSuspenseQuery< +export function useSuspenseQuery() { + // @ts-expect-error Cannot assign to 'useSuspenseQuery' because it is a function. ts(2630) + useSuspenseQuery = makeHookWrappable( + "useSuspenseQuery", + (_, options) => + useApolloClient(typeof options === "object" ? options.client : undefined), + _useSuspenseQuery + ); + return useSuspenseQuery.apply(null, arguments as any); +} + +function _useSuspenseQuery< TData = unknown, TVariables extends OperationVariables = OperationVariables, >( @@ -281,15 +292,6 @@ export function useSuspenseQuery< }, [client, fetchMore, refetch, result, subscribeToMore]); } -const wrapped = /*#__PURE__*/ makeHookWrappable( - "useSuspenseQuery", - (_, options) => - useApolloClient(typeof options === "object" ? options.client : undefined), - useSuspenseQuery -); -// @ts-expect-error Cannot assign to 'useSuspenseQuery' because it is a function.ts(2630) -useSuspenseQuery = wrapped; - function validateOptions(options: WatchQueryOptions) { const { query, fetchPolicy, returnPartialData } = options; From a114bb05ae33b5e2e55b28fbd6c0a72a404d7f4a Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Mon, 26 Feb 2024 15:47:17 +0100 Subject: [PATCH 13/16] adjust comment --- src/react/hooks/internal/wrapHook.ts | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/react/hooks/internal/wrapHook.ts b/src/react/hooks/internal/wrapHook.ts index fa31b3275e6..fcee397da89 100644 --- a/src/react/hooks/internal/wrapHook.ts +++ b/src/react/hooks/internal/wrapHook.ts @@ -45,12 +45,24 @@ interface QueryManagerWithWrappers extends QueryManager { * // this is already done in `@apollo/client` for all wrappable hooks (see `WrappableHooks`) * const wrappedUseQuery = makeHookWrappable('useQuery', useQuery, (_, options) => options.client); * + * // although for tree-shaking purposes, in reality it looks more like + * function useQuery() { + * useQuery = makeHookWrappable('useQuery', (_, options) => options.client, _useQuery); + * return useQuery.apply(null, arguments as any); + * } + * function _useQuery() { + * // original implementation + * } + * * // this is what a library like `@apollo/client-react-streaming` would do * class ApolloClientWithStreaming extends ApolloClient { - * [Symbol.for("apollo.hook.wrappers")] = { - * useQuery: (original) => (query, options) => { - * console.log("useQuery was called with options", options); - * return original(query, options); + * constructor(options) { + * super(options); + * this.queryManager[Symbol.for("apollo.hook.wrappers")] = { + * useQuery: (original) => (query, options) => { + * console.log("useQuery was called with options", options); + * return original(query, options); + * } * } * } * } From a274f964c2a4a67f66d63c4c6c399c62c16df796 Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Tue, 27 Feb 2024 11:23:10 +0100 Subject: [PATCH 14/16] simplify implementation by just calling `wrapHook` --- .size-limits.json | 2 +- src/react/hooks/internal/index.ts | 2 +- src/react/hooks/internal/wrapHook.ts | 42 +++++++++++---------------- src/react/hooks/useBackgroundQuery.ts | 30 ++++++++++++------- src/react/hooks/useFragment.ts | 21 ++++---------- src/react/hooks/useQuery.ts | 26 +++++++---------- src/react/hooks/useReadQuery.ts | 16 ++++------ src/react/hooks/useSuspenseQuery.ts | 25 +++++++++------- 8 files changed, 76 insertions(+), 88 deletions(-) diff --git a/.size-limits.json b/.size-limits.json index bb268e153b8..a4c53b7f485 100644 --- a/.size-limits.json +++ b/.size-limits.json @@ -1,4 +1,4 @@ { - "dist/apollo-client.min.cjs": 39283, + "dist/apollo-client.min.cjs": 39209, "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32584 } diff --git a/src/react/hooks/internal/index.ts b/src/react/hooks/internal/index.ts index 0da2373ba70..ce58b546f69 100644 --- a/src/react/hooks/internal/index.ts +++ b/src/react/hooks/internal/index.ts @@ -4,4 +4,4 @@ export { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect.js"; export { useRenderGuard } from "./useRenderGuard.js"; export { useLazyRef } from "./useLazyRef.js"; export { __use } from "./__use.js"; -export { makeHookWrappable } from "./wrapHook.js"; +export { wrapHook } from "./wrapHook.js"; diff --git a/src/react/hooks/internal/wrapHook.ts b/src/react/hooks/internal/wrapHook.ts index fcee397da89..2f4d0cc9fb1 100644 --- a/src/react/hooks/internal/wrapHook.ts +++ b/src/react/hooks/internal/wrapHook.ts @@ -47,10 +47,9 @@ interface QueryManagerWithWrappers extends QueryManager { * * // although for tree-shaking purposes, in reality it looks more like * function useQuery() { - * useQuery = makeHookWrappable('useQuery', (_, options) => options.client, _useQuery); - * return useQuery.apply(null, arguments as any); + * return wrapHook('useQuery', _useQuery, options.client)(query, options); * } - * function _useQuery() { + * function _useQuery(query, options) { * // original implementation * } * @@ -73,26 +72,19 @@ interface QueryManagerWithWrappers extends QueryManager { * ``` */ /*#__NO_SIDE_EFFECTS__*/ -export function makeHookWrappable( - hookName: Name, - getClientFromOptions: ( - ...args: Parameters - ) => ObservableQuery | ApolloClient, - useHook: WrappableHooks[Name] -): WrappableHooks[Name] { - return function (this: any) { - const args = arguments as unknown as Parameters; - const queryManager = ( - getClientFromOptions.apply(this, args) as unknown as { - // both `ApolloClient` and `ObservableQuery` have a `queryManager` property - // but they're both `private`, so we have to cast around for a bit here. - queryManager: QueryManagerWithWrappers; - } - )["queryManager"]; - const wrappers = queryManager && queryManager[wrapperSymbol]; - const wrapper = wrappers && wrappers[hookName]; - const wrappedHook: WrappableHooks[Name] = - wrapper ? wrapper(useHook) : useHook; - return (wrappedHook as any).apply(this, args); - } as any; +export function wrapHook any>( + hookName: keyof WrappableHooks, + useHook: Hook, + clientOrObsQuery: ObservableQuery | ApolloClient +): Hook { + const queryManager = ( + clientOrObsQuery as unknown as { + // both `ApolloClient` and `ObservableQuery` have a `queryManager` property + // but they're both `private`, so we have to cast around for a bit here. + queryManager: QueryManagerWithWrappers; + } + )["queryManager"]; + const wrappers = queryManager && queryManager[wrapperSymbol]; + const wrapper: (wrap: Hook) => Hook = wrappers && (wrappers[hookName] as any); + return wrapper ? wrapper(useHook) : useHook; } diff --git a/src/react/hooks/useBackgroundQuery.ts b/src/react/hooks/useBackgroundQuery.ts index 7659d43f492..8d83c50fc00 100644 --- a/src/react/hooks/useBackgroundQuery.ts +++ b/src/react/hooks/useBackgroundQuery.ts @@ -15,7 +15,7 @@ import { } from "../internal/index.js"; import type { CacheKey, QueryReference } from "../internal/index.js"; import type { BackgroundQueryHookOptions, NoInfer } from "../types/types.js"; -import { __use, makeHookWrappable } from "./internal/index.js"; +import { __use, wrapHook } from "./internal/index.js"; import { useWatchQueryOptions } from "./useSuspenseQuery.js"; import type { FetchMoreFunction, RefetchFunction } from "./useSuspenseQuery.js"; import { canonicalStringify } from "../../cache/index.js"; @@ -170,16 +170,24 @@ export function useBackgroundQuery< UseBackgroundQueryResult, ]; -export function useBackgroundQuery() { - // @ts-expect-error Cannot assign to 'useBackgroundQuery' because it is a function.ts(2630) - useBackgroundQuery = makeHookWrappable( +export function useBackgroundQuery< + TData = unknown, + TVariables extends OperationVariables = OperationVariables, +>( + query: DocumentNode | TypedDocumentNode, + options: + | (SkipToken & + Partial>) + | BackgroundQueryHookOptionsNoInfer = Object.create(null) +): [ + QueryReference | undefined, + UseBackgroundQueryResult, +] { + return wrapHook( "useBackgroundQuery", - (_, options) => - useApolloClient(typeof options === "object" ? options.client : undefined), - _useBackgroundQuery as any - ); - - return useBackgroundQuery.apply(null, arguments as any); + _useBackgroundQuery, + useApolloClient(typeof options === "object" ? options.client : undefined) + )(query, options); } function _useBackgroundQuery< @@ -190,7 +198,7 @@ function _useBackgroundQuery< options: | (SkipToken & Partial>) - | BackgroundQueryHookOptionsNoInfer = Object.create(null) + | BackgroundQueryHookOptionsNoInfer ): [ QueryReference | undefined, UseBackgroundQueryResult, diff --git a/src/react/hooks/useFragment.ts b/src/react/hooks/useFragment.ts index dae10a9a83e..96e2a1c014a 100644 --- a/src/react/hooks/useFragment.ts +++ b/src/react/hooks/useFragment.ts @@ -14,11 +14,7 @@ import { useApolloClient } from "./useApolloClient.js"; import { useSyncExternalStore } from "./useSyncExternalStore.js"; import type { ApolloClient, OperationVariables } from "../../core/index.js"; import type { NoInfer } from "../types/types.js"; -import { - useDeepMemo, - useLazyRef, - makeHookWrappable, -} from "./internal/index.js"; +import { useDeepMemo, useLazyRef, wrapHook } from "./internal/index.js"; export interface UseFragmentOptions extends Omit< @@ -57,17 +53,12 @@ export type UseFragmentResult = export function useFragment( options: UseFragmentOptions -): UseFragmentResult; - -export function useFragment() { - // @ts-expect-error Cannot assign to 'useFragment' because it is a function.ts(2630) - useFragment = makeHookWrappable( +): UseFragmentResult { + return wrapHook( "useFragment", - (options) => useApolloClient(options.client), - _useFragment - ); - - return useFragment.apply(null, arguments as any); + _useFragment, + useApolloClient(options.client) + )(options); } function _useFragment( diff --git a/src/react/hooks/useQuery.ts b/src/react/hooks/useQuery.ts index 896239f2346..225577521b4 100644 --- a/src/react/hooks/useQuery.ts +++ b/src/react/hooks/useQuery.ts @@ -36,7 +36,7 @@ import { isNonEmptyArray, maybeDeepFreeze, } from "../../utilities/index.js"; -import { makeHookWrappable } from "./internal/index.js"; +import { wrapHook } from "./internal/index.js"; const { prototype: { hasOwnProperty }, @@ -81,17 +81,16 @@ export function useQuery< TVariables extends OperationVariables = OperationVariables, >( query: DocumentNode | TypedDocumentNode, - options?: QueryHookOptions, NoInfer> -): QueryResult; - -export function useQuery() { - // @ts-expect-error Cannot assign to 'useQuery' because it is a function. ts(2630) - useQuery = makeHookWrappable( + options: QueryHookOptions< + NoInfer, + NoInfer + > = Object.create(null) +): QueryResult { + return wrapHook( "useQuery", - (_, options) => useApolloClient(options && options.client), - _useQuery - ); - return useQuery.apply(null, arguments as any); + _useQuery, + useApolloClient(options && options.client) + )(query, options); } function _useQuery< @@ -99,10 +98,7 @@ function _useQuery< TVariables extends OperationVariables = OperationVariables, >( query: DocumentNode | TypedDocumentNode, - options: QueryHookOptions< - NoInfer, - NoInfer - > = Object.create(null) + options: QueryHookOptions, NoInfer> ) { return useInternalState(useApolloClient(options.client), query).useQuery( options diff --git a/src/react/hooks/useReadQuery.ts b/src/react/hooks/useReadQuery.ts index b6479e26ea5..9e8e4621a83 100644 --- a/src/react/hooks/useReadQuery.ts +++ b/src/react/hooks/useReadQuery.ts @@ -5,7 +5,7 @@ import { updateWrappedQueryRef, } from "../internal/index.js"; import type { QueryReference } from "../internal/index.js"; -import { __use, makeHookWrappable } from "./internal/index.js"; +import { __use, wrapHook } from "./internal/index.js"; import { toApolloError } from "./useSuspenseQuery.js"; import { useSyncExternalStore } from "./useSyncExternalStore.js"; import type { ApolloError } from "../../errors/index.js"; @@ -38,16 +38,12 @@ export interface UseReadQueryResult { export function useReadQuery( queryRef: QueryReference -): UseReadQueryResult; - -export function useReadQuery() { - // @ts-expect-error Cannot assign to 'useReadQuery' because it is a function.ts(2630) - useReadQuery = makeHookWrappable( +): UseReadQueryResult { + return wrapHook( "useReadQuery", - (ref) => unwrapQueryRef(ref)["observable"], - _useReadQuery - ); - return useReadQuery.apply(null, arguments as any); + _useReadQuery, + unwrapQueryRef(queryRef)["observable"] + )(queryRef); } function _useReadQuery( diff --git a/src/react/hooks/useSuspenseQuery.ts b/src/react/hooks/useSuspenseQuery.ts index 47da76ebfd2..77159eb31f2 100644 --- a/src/react/hooks/useSuspenseQuery.ts +++ b/src/react/hooks/useSuspenseQuery.ts @@ -20,7 +20,7 @@ import type { ObservableQueryFields, NoInfer, } from "../types/types.js"; -import { __use, useDeepMemo, makeHookWrappable } from "./internal/index.js"; +import { __use, useDeepMemo, wrapHook } from "./internal/index.js"; import { getSuspenseCache } from "../internal/index.js"; import { canonicalStringify } from "../../cache/index.js"; import { skipToken } from "./constants.js"; @@ -166,15 +166,20 @@ export function useSuspenseQuery< | SuspenseQueryHookOptions, NoInfer> ): UseSuspenseQueryResult; -export function useSuspenseQuery() { - // @ts-expect-error Cannot assign to 'useSuspenseQuery' because it is a function. ts(2630) - useSuspenseQuery = makeHookWrappable( +export function useSuspenseQuery< + TData = unknown, + TVariables extends OperationVariables = OperationVariables, +>( + query: DocumentNode | TypedDocumentNode, + options: + | (SkipToken & Partial>) + | SuspenseQueryHookOptions = Object.create(null) +): UseSuspenseQueryResult { + return wrapHook( "useSuspenseQuery", - (_, options) => - useApolloClient(typeof options === "object" ? options.client : undefined), - _useSuspenseQuery - ); - return useSuspenseQuery.apply(null, arguments as any); + _useSuspenseQuery, + useApolloClient(typeof options === "object" ? options.client : undefined) + )(query, options); } function _useSuspenseQuery< @@ -184,7 +189,7 @@ function _useSuspenseQuery< query: DocumentNode | TypedDocumentNode, options: | (SkipToken & Partial>) - | SuspenseQueryHookOptions = Object.create(null) + | SuspenseQueryHookOptions ): UseSuspenseQueryResult { const client = useApolloClient(options.client); const suspenseCache = getSuspenseCache(client); From 57a6c75a2c816d1e60864ef77c77127d3aa1e1ff Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Tue, 27 Feb 2024 11:31:34 +0100 Subject: [PATCH 15/16] adjust comments --- src/react/hooks/internal/wrapHook.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/react/hooks/internal/wrapHook.ts b/src/react/hooks/internal/wrapHook.ts index 2f4d0cc9fb1..040333ab1e0 100644 --- a/src/react/hooks/internal/wrapHook.ts +++ b/src/react/hooks/internal/wrapHook.ts @@ -21,8 +21,8 @@ interface WrappableHooks { /** * @internal - * Can be used to correctly type the [Symbol.for("apollo.hook.wrappers")] of - * a class that extends `ApolloClient`, to override/wrap hook functionality. + * Can be used to correctly type the [Symbol.for("apollo.hook.wrappers")] property of + * `QueryManager`, to override/wrap hook functionality. */ export type HookWrappers = { [K in keyof WrappableHooks]?: ( @@ -43,9 +43,7 @@ interface QueryManagerWithWrappers extends QueryManager { * @example * ```tsx * // this is already done in `@apollo/client` for all wrappable hooks (see `WrappableHooks`) - * const wrappedUseQuery = makeHookWrappable('useQuery', useQuery, (_, options) => options.client); - * - * // although for tree-shaking purposes, in reality it looks more like + * // following this pattern * function useQuery() { * return wrapHook('useQuery', _useQuery, options.client)(query, options); * } @@ -68,10 +66,9 @@ interface QueryManagerWithWrappers extends QueryManager { * * // this will now log the options and then call the original `useQuery` * const client = new ApolloClientWithStreaming({ ... }); - * wrappedUseQuery(query, { client }); + * useQuery(query, { client }); * ``` */ -/*#__NO_SIDE_EFFECTS__*/ export function wrapHook any>( hookName: keyof WrappableHooks, useHook: Hook, From d0190582d992ce3ba75166bcca3bf7bad809c6ea Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Tue, 27 Feb 2024 11:32:28 +0100 Subject: [PATCH 16/16] slight type adjustment --- src/react/hooks/internal/wrapHook.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/react/hooks/internal/wrapHook.ts b/src/react/hooks/internal/wrapHook.ts index 040333ab1e0..abf9a49c035 100644 --- a/src/react/hooks/internal/wrapHook.ts +++ b/src/react/hooks/internal/wrapHook.ts @@ -82,6 +82,7 @@ export function wrapHook any>( } )["queryManager"]; const wrappers = queryManager && queryManager[wrapperSymbol]; - const wrapper: (wrap: Hook) => Hook = wrappers && (wrappers[hookName] as any); + const wrapper: undefined | ((wrap: Hook) => Hook) = + wrappers && (wrappers[hookName] as any); return wrapper ? wrapper(useHook) : useHook; }