diff --git a/.changeset/lucky-months-fail.md b/.changeset/lucky-months-fail.md new file mode 100644 index 0000000000..fa8b5f0851 --- /dev/null +++ b/.changeset/lucky-months-fail.md @@ -0,0 +1,5 @@ +--- +'@urql/core': patch +--- + +Add missing type exports of SSR-related types (`SerializedResult`, `SSRExchangeParams`, `SSRExchange`, and `SSRData`) to `@urql/core`'s type exports. diff --git a/.changeset/strong-shirts-hear.md b/.changeset/strong-shirts-hear.md new file mode 100644 index 0000000000..c1f011e4d5 --- /dev/null +++ b/.changeset/strong-shirts-hear.md @@ -0,0 +1,7 @@ +--- +'@urql/introspection': patch +'@urql/storage-rn': patch +'next-urql': patch +--- + +Add TSDocs to `@urql/*` packages. diff --git a/.changeset/tricky-cherries-glow.md b/.changeset/tricky-cherries-glow.md new file mode 100644 index 0000000000..02e1b3f3dc --- /dev/null +++ b/.changeset/tricky-cherries-glow.md @@ -0,0 +1,8 @@ +--- +'@urql/preact': patch +'@urql/svelte': patch +'urql': patch +'@urql/vue': patch +--- + +Add TSDocs to all `urql` bindings packages. diff --git a/packages/core/src/exchanges/index.ts b/packages/core/src/exchanges/index.ts index 3434aa7668..5c96d31292 100644 --- a/packages/core/src/exchanges/index.ts +++ b/packages/core/src/exchanges/index.ts @@ -6,6 +6,13 @@ export { dedupExchange } from './dedup'; export { fetchExchange } from './fetch'; export { composeExchanges } from './compose'; +export type { + SerializedResult, + SSRExchangeParams, + SSRExchange, + SSRData, +} from './ssr'; + export type { SubscriptionOperation, SubscriptionForwarder, diff --git a/packages/introspection/src/getIntrospectedSchema.ts b/packages/introspection/src/getIntrospectedSchema.ts index aae8cdc362..094c9ac9c2 100644 --- a/packages/introspection/src/getIntrospectedSchema.ts +++ b/packages/introspection/src/getIntrospectedSchema.ts @@ -7,6 +7,21 @@ import { getIntrospectionQuery, } from 'graphql'; +/** Returns an {@link IntrospectionQuery} result for a given GraphQL schema. + * + * @param input - A GraphQL schema, either as an SDL string, or a {@link GraphQLSchema} object. + * @returns an {@link IntrospectionQuery} result. + * + * @remarks + * `getIntrospectedSchema` can be used to get a Schema Introspection result from + * a given GraphQL schema. The schema can be passed as an SDL string or a + * {@link GraphQLSchema} object. If an {@link IntrospectionQuery} object is + * passed, it'll be passed through. + * + * @throws + * If `input` cannot be parsed or converted into a {@link GraphQLSchema} then + * a {@link TypeError} will be thrown. + */ export const getIntrospectedSchema = ( input: string | IntrospectionQuery | GraphQLSchema ): IntrospectionQuery => { diff --git a/packages/introspection/src/minifyIntrospectionQuery.ts b/packages/introspection/src/minifyIntrospectionQuery.ts index 0bf77bdf66..c2b72dbc82 100644 --- a/packages/introspection/src/minifyIntrospectionQuery.ts +++ b/packages/introspection/src/minifyIntrospectionQuery.ts @@ -156,18 +156,52 @@ const minifyIntrospectionType = ( } }; +/** Input parameters for the {@link minifyIntrospectionQuery} function. */ export interface MinifySchemaOptions { - /** Includes scalar names (instead of an `Any` replacement) in the output when enabled. */ + /** Includes scalars instead of removing them. + * + * @remarks + * By default, all scalars will be replaced by a single scalar called `Any` + * in the output, unless this option is set to `true`. + */ includeScalars?: boolean; - /** Includes enums (instead of an `Any` replacement) in the output when enabled. */ + /** Includes enums instead of removing them. + * + * @remarks + * By default, all enums will be replaced by a single scalar called `Any` + * in the output, unless this option is set to `true`. + */ includeEnums?: boolean; - /** Includes all input objects (instead of an `Any` replacement) in the output when enabled. */ + /** Includes inputs instead of removing them. + * + * @remarks + * By default, all inputs will be replaced by a single scalar called `Any` + * in the output, unless this option is set to `true`. + */ includeInputs?: boolean; - /** Includes all directives in the output when enabled. */ + /** Includes directives instead of removing them. */ includeDirectives?: boolean; } -/** Removes extraneous information from introspected schema data to minify it and prepare it for use on the client-side. */ +/** Minifies an {@link IntrospectionQuery} for use with Graphcache or the `populateExchange`. + * + * @param schema - An {@link IntrospectionQuery} object to be minified. + * @param opts - An optional {@link MinifySchemaOptions} configuration object. + * @returns the minified {@link IntrospectionQuery} object. + * + * @remarks + * `minifyIntrospectionQuery` reduces the size of an {@link IntrospectionQuery} by + * removing data and information that a client-side consumer, like Graphcache or the + * `populateExchange`, may not require. + * + * At the very least, it will remove system types, descriptions, depreactions, + * and source locations. Unless disabled via the options passed, it will also + * by default remove all scalars, enums, inputs, and directives. + * + * @throws + * If `schema` receives an object that isn’t an {@link IntrospectionQuery}, a + * {@link TypeError} will be thrown. + */ export const minifyIntrospectionQuery = ( schema: IntrospectionQuery, opts: MinifySchemaOptions = {} diff --git a/packages/next-urql/src/init-urql-client.ts b/packages/next-urql/src/init-urql-client.ts index 7cff1fd2c3..786ef664fc 100644 --- a/packages/next-urql/src/init-urql-client.ts +++ b/packages/next-urql/src/init-urql-client.ts @@ -2,14 +2,38 @@ import { Client, ClientOptions, createClient } from '@urql/core'; let urqlClient: Client | null = null; +/** Resets the `Client` that {@link initUrqlClient} returns. + * + * @remarks + * `resetClient` will force {@link initUrqlClient} to create a new + * {@link Client}, rather than reusing the same `Client` it already + * created on the client-side. + * + * This may be used to force the cache and any state in the `Client` + * to be cleared and reset. + */ export function resetClient() { urqlClient = null; } +/** Creates a {@link Client} the given options. + * + * @param clientOptions - {@link ClientOptions} to create the `Client` with. + * @param canEnableSuspense - Enables React Suspense on the server-side for `react-ssr-prepass`. + * @returns the created {@link Client} + * + * @remarks + * `initUrqlClient` creates a {@link Client} with the given options, + * like {@link createClient} does, but reuses the same client when + * run on the client-side. + * + * As long as `canEnableSuspense` is set to `true`, it enables React Suspense + * mode on the server-side for `react-ssr-prepass`. + */ export function initUrqlClient( clientOptions: ClientOptions, canEnableSuspense: boolean -): Client | null { +): Client { // Create a new Client for every server-side rendered request. // This ensures we reset the state for each rendered page. // If there is an exising client instance on the client-side, use it. diff --git a/packages/next-urql/src/types.ts b/packages/next-urql/src/types.ts index 5a3795cb0a..06044954c4 100644 --- a/packages/next-urql/src/types.ts +++ b/packages/next-urql/src/types.ts @@ -1,58 +1,102 @@ -import { GraphQLError } from 'graphql'; -import { ClientOptions, Exchange, Client } from 'urql'; -import { NextPageContext } from 'next'; -import { AppContext } from 'next/app'; +import type { ClientOptions, Client, SSRExchange, SSRData } from '@urql/core'; +import type { NextPageContext } from 'next'; +import type { AppContext } from 'next/app'; +/** The Next.js {@link NextPageContext}, as modified by `next-urql`. */ export interface NextUrqlPageContext extends NextPageContext { urqlClient: Client; } +/** The Next.js {@link AppContext}, as modified by `next-urql`. */ export interface NextUrqlAppContext extends AppContext { urqlClient: Client; } export type NextUrqlContext = NextUrqlPageContext | NextUrqlAppContext; +/** Passed to {@link withUrqlClient} returning the options a {@link Client} is created with. + * + * @param ssrExchange - the `ssrExchange` you must use in your `exchanges` array. + * @param ctx - Passed when `getInitialProps` is used and set to Next.js’ {@link NextPageContext}. + * @returns a {@link ClientOptions} configuration object to create a {@link Client} with. + * + * @remarks + * You must define a `getClientConfig` function and pass it to {@link withUrqlClient}. + * + * This function defines the options passed to {@link initUrqlClient}. + * It passes you an `ssrExchange` that you must use in your `exchanges` array. + * + * @example + * ```ts + * import { cacheExchange, fetchExchange } from '@urql/core'; + * import { withUrqlClient } from 'next-urql'; + * + * const WrappedPage = withUrqlClient( + * (ssrExchange) => ({ + * url: 'https://YOUR_API', + * exchanges: [cacheExchange, ssrExchange, fetchExchange], + * }) + * )(Page); + * ``` + */ export type NextUrqlClientConfig = ( ssrExchange: SSRExchange, ctx?: NextPageContext ) => ClientOptions; -export interface WithUrqlState { - urqlState?: SSRData; -} - -export interface WithUrqlClient { +/** Props that {@link withUrqlClient} components pass on to your component. */ +export interface WithUrqlProps { + /** The {@link Client} that {@link withUrqlClient} created for your component. */ urqlClient?: Client; -} - -export interface WithUrqlProps extends WithUrqlClient, WithUrqlState { - resetUrqlClient?: () => void; + /** Next.js’ `pageProps` prop, as passed to it by Next.js. */ pageProps: any; + /** The SSR data that {@link withUrqlClient} created for your component. */ + urqlState?: SSRData; + /** Resets the `Client` that on the client-side. + * + * @remarks + * `resetUrqlClient` will force a new {@link Client} to be created + * on the client-side, rather than the same `Client` with the same + * server-side data to be reused. + * + * This may be used to force the cache and any state in the `Client` + * to be cleared and reset. + */ + resetUrqlClient?(): void; [key: string]: any; } -export interface SerializedResult { - data?: any; - error?: { - graphQLErrors: Array | string>; - networkError?: string; - }; -} - -export interface SSRData { - [key: string]: SerializedResult; -} - -export interface SSRExchange extends Exchange { - /** Rehydrates cached data */ - restoreData(data: SSRData): void; - /** Extracts cached data */ - extractData(): SSRData; -} - +/** Options that may be passed to the {@link withUrqlClient} wrapper function. */ export interface WithUrqlClientOptions { + /** Enables automatic server-side rendering mode. + * + * @remarks + * When enabled, {@link withUrqlClient} will add a `getInitialProps` + * function to the resulting component, even if you haven't defined + * one. + * + * This function will automatically capture `urql`'s SSR state on the + * server-side and rehydrate it on the client-side, unless + * {@link WithUrqlClientOptions.neverSuspend} is `true`. + */ ssr?: boolean; + /** Disables automatic server-side rendering, even if a `getInitialProps` function is defined. + * + * @remarks + * When enabled, {@link withUrqlClient} will never execute queries + * on the server-side automatically, and will instead rely on you + * to do so manually. + */ neverSuspend?: boolean; + /** Enables reexecuting operations on the client-side after rehydration. + * + * @remarks + * When enabled, `staleWhileRevalidate` will reexecute GraphQL queries on + * the client-side, if they’ve been rehydrated from SSR state. + * + * This is useful if you, for instance, cache your server-side rendered + * pages, or if you use `getStaticProps` and wish to get this data + * updated. + */ staleWhileRevalidate?: boolean; } diff --git a/packages/next-urql/src/with-urql-client.ts b/packages/next-urql/src/with-urql-client.ts index cd78c4376b..1b3ab6085e 100644 --- a/packages/next-urql/src/with-urql-client.ts +++ b/packages/next-urql/src/with-urql-client.ts @@ -6,18 +6,19 @@ import { ReactNode, ReactElement, } from 'react'; -import ssrPrepass from 'react-ssr-prepass'; -import { NextComponentType, NextPage, NextPageContext } from 'next'; -import NextApp, { AppContext } from 'next/app'; import { Provider, + SSRExchange, ssrExchange, - dedupExchange, cacheExchange, fetchExchange, } from 'urql'; +import ssrPrepass from 'react-ssr-prepass'; +import { NextComponentType, NextPage, NextPageContext } from 'next'; +import NextApp, { AppContext } from 'next/app'; + import { initUrqlClient, resetClient } from './init-urql-client'; import { @@ -25,7 +26,6 @@ import { NextUrqlContext, WithUrqlProps, WithUrqlClientOptions, - SSRExchange, } from './types'; let ssr: SSRExchange; @@ -33,6 +33,39 @@ type NextPageWithLayout = NextPage & { getLayout?: (page: ReactElement) => ReactNode; }; +/** Creates a wrapper for Next.js Page, App, or Document components that rehydrates `urql` state. + * + * @param getClientConfig - function used to create the {@link Client} + * @param options - optional {@link WithUrqlClientOptions} configuration options + * @returns a higher-order component function, which you can pass a Next.js page or app component. + * + * @remarks + * Used to wrap a Next.js page or app component. It will create a {@link Client} and passes + * it on to the child component and adds a React Provider for it. + * + * It will restore any page’s `pageProps.urqlState` with the {@link SSRExchange} and also + * supports doing this automatically when the {@link WithUrqlClientOptions.ssr} option + * is enabled. + * + * If you don’t define the above option, you will have to write `getServerSideProps` or + * `getStaticProps` methods on your component manually. + * + * @see {@link https://urql.dev/goto/docs/advanced/server-side-rendering/#nextjs} for more documentation. + * + * @example + * ```ts + * import { cacheExchange, fetchExchange } from '@urql/core'; + * import { withUrqlClient } from 'next-urql'; + * + * const WrappedPage = withUrqlClient( + * (ssrExchange) => ({ + * url: 'https://YOUR_API', + * exchanges: [cacheExchange, ssrExchange, fetchExchange], + * }), + * { ssr: true }, + * )(Page); + * ``` + */ export function withUrqlClient( getClientConfig: NextUrqlClientConfig, options?: WithUrqlClientOptions @@ -77,12 +110,7 @@ export function withUrqlClient( const clientConfig = getClientConfig(ssr); if (!clientConfig.exchanges) { // When the user does not provide exchanges we make the default assumption. - clientConfig.exchanges = [ - dedupExchange, - cacheExchange, - ssr, - fetchExchange, - ]; + clientConfig.exchanges = [cacheExchange, ssr, fetchExchange]; } return initUrqlClient(clientConfig, shouldEnableSuspense)!; @@ -129,12 +157,7 @@ export function withUrqlClient( const clientConfig = getClientConfig(ssrCache, ctx); if (!clientConfig.exchanges) { // When the user does not provide exchanges we make the default assumption. - clientConfig.exchanges = [ - dedupExchange, - cacheExchange, - ssrCache, - fetchExchange, - ]; + clientConfig.exchanges = [cacheExchange, ssrCache, fetchExchange]; } const urqlClient = initUrqlClient(clientConfig, !options!.neverSuspend); diff --git a/packages/preact-urql/src/components/Mutation.ts b/packages/preact-urql/src/components/Mutation.ts index f12af22003..09b3fc5dbe 100644 --- a/packages/preact-urql/src/components/Mutation.ts +++ b/packages/preact-urql/src/components/Mutation.ts @@ -1,31 +1,49 @@ import { VNode } from 'preact'; import { DocumentNode } from 'graphql'; -import { - AnyVariables, - TypedDocumentNode, - OperationResult, - OperationContext, -} from '@urql/core'; -import { useMutation, UseMutationState } from '../hooks'; +import { AnyVariables, TypedDocumentNode } from '@urql/core'; +import { useMutation, UseMutationState, UseMutationExecute } from '../hooks'; + +/** Props accepted by {@link Mutation}. + * + * @remarks + * `MutationProps` are the props accepted by the {@link Mutation} component. + * + * The result, the {@link MutationState} object, will be passed to + * a {@link MutationProps.children} function, passed as children + * to the `Mutation` component. + */ export interface MutationProps< Data = any, Variables extends AnyVariables = AnyVariables > { + /* The GraphQL mutation document that {@link useMutation} will execute. */ query: DocumentNode | TypedDocumentNode | string; - children: (arg: MutationState) => VNode; + children(arg: MutationState): VNode; } +/** Object that {@link MutationProps.children} is called with. + * + * @remarks + * This is an extented {@link UseMutationstate} with an added + * {@link MutationState.executeMutation} method, which is usually + * part of a tuple returned by {@link useMutation}. + */ export interface MutationState< Data = any, Variables extends AnyVariables = AnyVariables > extends UseMutationState { - executeMutation: ( - variables: Variables, - context?: Partial - ) => Promise>; + /** Alias to {@link useMutation}’s `executeMutation` function. */ + executeMutation: UseMutationExecute; } +/** Component Wrapper around {@link useMutation} to run a GraphQL query. + * + * @remarks + * `Mutation` is a component wrapper around the {@link useMutation} hook + * that calls the {@link MutationProps.children} prop, as a function, + * with the {@link MutationState} object. + */ export function Mutation< Data = any, Variables extends AnyVariables = AnyVariables diff --git a/packages/preact-urql/src/components/Query.ts b/packages/preact-urql/src/components/Query.ts index 15489fc38c..421a3dd67a 100644 --- a/packages/preact-urql/src/components/Query.ts +++ b/packages/preact-urql/src/components/Query.ts @@ -1,21 +1,52 @@ import { VNode } from 'preact'; -import { AnyVariables, OperationContext } from '@urql/core'; -import { useQuery, UseQueryArgs, UseQueryState } from '../hooks'; +import { AnyVariables } from '@urql/core'; +import { + useQuery, + UseQueryArgs, + UseQueryState, + UseQueryExecute, +} from '../hooks'; + +/** Props accepted by {@link Query}. + * + * @remarks + * `QueryProps` are the props accepted by the {@link Query} component, + * which is identical to {@link UseQueryArgs}. + * + * The result, the {@link QueryState} object, will be passed to + * a {@link QueryProps.children} function, passed as children + * to the `Query` component. + */ export type QueryProps< Data = any, Variables extends AnyVariables = AnyVariables > = UseQueryArgs & { - children: (arg: QueryState) => VNode; + children(arg: QueryState): VNode; }; +/** Object that {@link QueryProps.children} is called with. + * + * @remarks + * This is an extented {@link UseQueryState} with an added + * {@link QueryState.executeQuery} method, which is usually + * part of a tuple returned by {@link useQuery}. + */ export interface QueryState< Data = any, Variables extends AnyVariables = AnyVariables > extends UseQueryState { - executeQuery: (opts?: Partial) => void; + /** Alias to {@link useQuery}’s `executeQuery` function. */ + executeQuery: UseQueryExecute; } +/** Component Wrapper around {@link useQuery} to run a GraphQL query. + * + * @remarks + * `Query` is a component wrapper around the {@link useQuery} hook + * that calls the {@link QueryProps.children} prop, as a function, + * with the {@link QueryState} object. + */ export function Query< Data = any, Variables extends AnyVariables = AnyVariables diff --git a/packages/preact-urql/src/components/Subscription.ts b/packages/preact-urql/src/components/Subscription.ts index 20bb947640..e8d77294cb 100644 --- a/packages/preact-urql/src/components/Subscription.ts +++ b/packages/preact-urql/src/components/Subscription.ts @@ -1,29 +1,58 @@ import { VNode } from 'preact'; -import { AnyVariables, OperationContext } from '@urql/core'; +import { AnyVariables } from '@urql/core'; import { useSubscription, UseSubscriptionArgs, UseSubscriptionState, + UseSubscriptionExecute, SubscriptionHandler, } from '../hooks'; +/** Props accepted by {@link Subscription}. + * + * @remarks + * `SubscriptionProps` are the props accepted by the {@link Subscription} component, + * which is identical to {@link UseSubscriptionArgs} with an added + * {@link SubscriptionProps.handler} prop, which {@link useSubscription} usually + * accepts as an additional argument. + * + * The result, the {@link SubscriptionState} object, will be passed to + * a {@link SubscriptionProps.children} function, passed as children + * to the `Subscription` component. + */ export type SubscriptionProps< Data = any, Result = Data, Variables extends AnyVariables = AnyVariables > = UseSubscriptionArgs & { + /** Accepts the {@link SubscriptionHandler} as a prop. */ handler?: SubscriptionHandler; - children: (arg: SubscriptionState) => VNode; + children(arg: SubscriptionState): VNode; }; +/** Object that {@link SubscriptionProps.children} is called with. + * + * @remarks + * This is an extented {@link UseSubscriptionState} with an added + * {@link SubscriptionState.executeSubscription} method, which is usually + * part of a tuple returned by {@link useSubscription}. + */ export interface SubscriptionState< Data = any, Variables extends AnyVariables = AnyVariables > extends UseSubscriptionState { - executeSubscription: (opts?: Partial) => void; + /** Alias to {@link useSubscription}’s `executeMutation` function. */ + executeSubscription: UseSubscriptionExecute; } +/** Component Wrapper around {@link useSubscription} to run a GraphQL subscription. + * + * @remarks + * `Subscription` is a component wrapper around the {@link useSubscription} hook + * that calls the {@link SubscriptionProps.children} prop, as a function, + * with the {@link SubscriptionState} object. + */ export function Subscription< Data = any, Result = Data, diff --git a/packages/preact-urql/src/context.ts b/packages/preact-urql/src/context.ts index 37a8c18ebd..b2428aa723 100644 --- a/packages/preact-urql/src/context.ts +++ b/packages/preact-urql/src/context.ts @@ -3,15 +3,69 @@ import { useContext } from 'preact/hooks'; import { Client } from '@urql/core'; const OBJ = {}; + +/** `@urql/preact`'s Preact Context. + * + * @remarks + * The Preact Context that `urql`’s {@link Client} will be provided with. + * You may use the reexported {@link Provider} to provide a `Client` as well. + */ export const Context: import('preact').Context = createContext(OBJ); + +/** Provider for `urql`’s {@link Client} to GraphQL hooks. + * + * @remarks + * `Provider` accepts a {@link Client} and provides it to all GraphQL hooks, + * and {@link useClient}. + * + * You should make sure to create a {@link Client} and provide it with the + * `Provider` to parts of your component tree that use GraphQL hooks. + * + * @example + * ```tsx + * import { Provider } from '@urql/preact'; + * // All of `@urql/core` is also re-exported by `@urql/preact`: + * import { Client, cacheExchange, fetchExchange } from '@urql/core'; + * + * const client = new Client({ + * url: 'https://API', + * exchanges: [cacheExchange, fetchExchange], + * }); + * + * const App = () => ( + * + * + * + * ); + * ``` + */ + export const Provider: import('preact').Provider = Context.Provider; + +/** Preact Consumer component, providing the {@link Client} provided on a parent component. + * @remarks + * This is an alias for {@link Context.Consumer}. + */ export const Consumer: import('preact').Consumer = Context.Consumer; Context.displayName = 'UrqlContext'; +/** Hook returning a {@link Client} from {@link Context}. + * + * @remarks + * `useClient` is a convenience hook, which accesses `@urql/preact`'s {@link Context} + * and returns the {@link Client} defined on it. + * + * This will be the {@link Client} you passed to a {@link Provider} + * you wrapped your elements containing this hook with. + * + * @throws + * In development, if the component you call `useClient()` in is + * not wrapped in a {@link Provider}, an error is thrown. + */ export const useClient = (): Client => { const client = useContext(Context); diff --git a/packages/preact-urql/src/hooks/useMutation.ts b/packages/preact-urql/src/hooks/useMutation.ts index 2576ac6fdd..0727d97f30 100644 --- a/packages/preact-urql/src/hooks/useMutation.ts +++ b/packages/preact-urql/src/hooks/useMutation.ts @@ -15,29 +15,132 @@ import { import { useClient } from '../context'; import { initialState } from './constants'; +/** State of the last mutation executed by your {@link useMutation} hook. + * + * @remarks + * `UseMutationState` is returned (in a tuple) by {@link useMutation} and + * gives you the {@link OperationResult} of the last mutation executed + * with {@link UseMutationExecute}. + * + * Even if the mutation document passed to {@link useMutation} changes, + * the state isn’t reset, so you can keep displaying the previous result. + */ export interface UseMutationState< Data = any, Variables extends AnyVariables = AnyVariables > { + /** Indicates whether `useMutation` is currently executing a mutation. */ fetching: boolean; + /** Indicates that the mutation result is not fresh. + * + * @remarks + * The `stale` flag is set to `true` when a new result for the mutation + * is expected. + * This is mostly unused for mutations and will rarely affect you, and + * is more relevant for queries. + * + * @see {@link OperationResult.stale} for the source of this value. + */ stale: boolean; + /** The {@link OperationResult.data} for the executed mutation. */ data?: Data; + /** The {@link OperationResult.error} for the executed mutation. */ error?: CombinedError; + /** The {@link OperationResult.extensions} for the executed mutation. */ extensions?: Record; + /** The {@link Operation} that the current state is for. + * + * @remarks + * This is the mutation {@link Operation} that has last been executed. + * When {@link UseQueryState.fetching} is `true`, this is the + * last `Operation` that the current state was for. + */ operation?: Operation; } +/** Triggers {@link useMutation} to execute its GraphQL mutation operation. + * + * @param variables - variables using which the mutation will be executed. + * @param context - optionally, context options that will be merged with the hook's + * {@link UseQueryArgs.context} options and the `Client`’s options. + * @returns the {@link OperationResult} of the mutation. + * + * @remarks + * When called, {@link useMutation} will start the GraphQL mutation + * it currently holds and use the `variables` passed to it. + * + * Once the mutation response comes back from the API, its + * returned promise will resolve to the mutation’s {@link OperationResult} + * and the {@link UseMutationState} will be updated with the result. + * + * @example + * ```ts + * const [result, executeMutation] = useMutation(UpdateTodo); + * const start = async ({ id, title }) => { + * const result = await executeMutation({ id, title }); + * }; + */ +export type UseMutationExecute< + Data = any, + Variables extends AnyVariables = AnyVariables +> = ( + variables: Variables, + context?: Partial +) => Promise>; + +/** Result tuple returned by the {@link useMutation} hook. + * + * @remarks + * Similarly to a `useState` hook’s return value, + * the first element is the {@link useMutation}’s state, updated + * as mutations are executed with the second value, which is + * used to start mutations and is a {@link UseMutationExecute} + * function. + */ export type UseMutationResponse< Data = any, Variables extends AnyVariables = AnyVariables -> = [ - UseMutationState, - ( - variables: Variables, - context?: Partial - ) => Promise> -]; +> = [UseMutationState, UseMutationExecute]; +/** Hook to create a GraphQL mutation, run by passing variables to the returned execute function. + * + * @param query - a GraphQL mutation document which `useMutation` will execute. + * @returns a {@link UseMutationResponse} tuple of a {@link UseMutationState} result, + * and an execute function to start the mutation. + * + * @remarks + * `useMutation` allows GraphQL mutations to be defined and keeps its state + * after the mutation is started with the returned execute function. + * + * Given a GraphQL mutation document it returns state to keep track of the + * mutation state and a {@link UseMutationExecute} function, which accepts + * variables for the mutation to be executed. + * Once called, the mutation executes and the state will be updated with + * the mutation’s result. + * + * @see {@link https://urql.dev/goto/docs/basics/react-preact/#mutations} for `useMutation` docs. + * + * @example + * ```ts + * import { gql, useMutation } from '@urql/preact'; + * + * const UpdateTodo = gql` + * mutation ($id: ID!, $title: String!) { + * updateTodo(id: $id, title: $title) { + * id, title + * } + * } + * `; + * + * const UpdateTodo = () => { + * const [result, executeMutation] = useMutation(UpdateTodo); + * const start = async ({ id, title }) => { + * const result = await executeMutation({ id, title }); + * }; + * // ... + * }; + * ``` + */ export function useMutation< Data = any, Variables extends AnyVariables = AnyVariables diff --git a/packages/preact-urql/src/hooks/useQuery.ts b/packages/preact-urql/src/hooks/useQuery.ts index 6b15e2c2fc..bf5a115999 100644 --- a/packages/preact-urql/src/hooks/useQuery.ts +++ b/packages/preact-urql/src/hooks/useQuery.ts @@ -28,36 +28,154 @@ import { useSource } from './useSource'; import { useRequest } from './useRequest'; import { initialState } from './constants'; +/** Input arguments for the {@link useQuery} hook. + * + * @param query - The GraphQL query that `useQuery` executes. + * @param variables - The variables for the GraphQL query that `useQuery` executes. + */ export type UseQueryArgs< Variables extends AnyVariables = AnyVariables, Data = any > = { + /** Updates the {@link RequestPolicy} for the executed GraphQL query operation. + * + * @remarks + * `requestPolicy` modifies the {@link RequestPolicy} of the GraphQL query operation + * that `useQuery` executes, and indicates a caching strategy for cache exchanges. + * + * For example, when set to `'cache-and-network'`, {@link useQuery} will + * receive a cached result with `stale: true` and an API request will be + * sent in the background. + * + * @see {@link OperationContext.requestPolicy} for where this value is set. + */ requestPolicy?: RequestPolicy; + /** Updates the {@link OperationContext} for the executed GraphQL query operation. + * + * @remarks + * `context` may be passed to {@link useQuery}, to update the {@link OperationContext} + * of a query operation. This may be used to update the `context` that exchanges + * will receive for a single hook. + * + * Hint: This should be wrapped in a `useMemo` hook, to make sure that your + * component doesn’t infinitely update. + * + * @example + * ```ts + * const [result, reexecute] = useQuery({ + * query, + * context: useMemo(() => ({ + * additionalTypenames: ['Item'], + * }), []) + * }); + * ``` + */ context?: Partial; + /** Prevents {@link useQuery} from automatically executing GraphQL query operations. + * + * @remarks + * `pause` may be set to `true` to stop {@link useQuery} from executing + * automatically. The hook will stop receiving updates from the {@link Client} + * and won’t execute the query operation, until either it’s set to `false` + * or the {@link UseQueryExecute} function is called. + * + * @see {@link https://urql.dev/goto/docs/basics/react-preact/#pausing-usequery} for + * documentation on the `pause` option. + */ pause?: boolean; } & GraphQLRequestParams; +/** State of the current query, your {@link useQuery} hook is executing. + * + * @remarks + * `UseQueryState` is returned (in a tuple) by {@link useQuery} and + * gives you the updating {@link OperationResult} of GraphQL queries. + * + * Even when the query and variables passed to {@link useQuery} change, + * this state preserves the prior state and sets the `fetching` flag to + * `true`. + * This allows you to display the previous state, while implementing + * a separate loading indicator separately. + */ export interface UseQueryState< Data = any, Variables extends AnyVariables = AnyVariables > { + /** Indicates whether `useQuery` is waiting for a new result. + * + * @remarks + * When `useQuery` is passed a new query and/or variables, it will + * start executing the new query operation and `fetching` is set to + * `true` until a result arrives. + * + * Hint: This is subtly different than whether the query is actually + * fetching, and doesn’t indicate whether a query is being re-executed + * in the background. For this, see {@link UseQueryState.stale}. + */ fetching: boolean; + /** Indicates that the state is not fresh and a new result will follow. + * + * @remarks + * The `stale` flag is set to `true` when a new result for the query + * is expected and `useQuery` is waiting for it. This may indicate that + * a new request is being requested in the background. + * + * @see {@link OperationResult.stale} for the source of this value. + */ stale: boolean; + /** The {@link OperationResult.data} for the executed query. */ data?: Data; + /** The {@link OperationResult.error} for the executed query. */ error?: CombinedError; + /** The {@link OperationResult.extensions} for the executed query. */ extensions?: Record; + /** The {@link Operation} that the current state is for. + * + * @remarks + * This is the {@link Operation} that is currently being executed. + * When {@link UseQueryState.fetching} is `true`, this is the + * last `Operation` that the current state was for. + */ operation?: Operation; } +/** Triggers {@link useQuery} to execute a new GraphQL query operation. + * + * @remarks + * When called, {@link useQuery} will re-execute the GraphQL query operation + * it currently holds, even if {@link UseQueryArgs.pause} is set to `true`. + * + * This is useful for executing a paused query or re-executing a query + * and get a new network result, by passing a new request policy. + * + * ```ts + * const [result, reexecuteQuery] = useQuery({ query }); + * + * const refresh = () => { + * // Re-execute the query with a network-only policy, skipping the cache + * reexecuteQuery({ requestPolicy: 'network-only' }); + * }; + * ``` + */ +export type UseQueryExecute = (opts?: Partial) => void; + +/** Result tuple returned by the {@link useQuery} hook. + * + * @remarks + * Similarly to a `useState` hook’s return value, + * the first element is the {@link useQuery}’s result and state, + * a {@link UseQueryState} object, + * and the second is used to imperatively re-execute the query + * via a {@link UseQueryExecute} function. + */ export type UseQueryResponse< Data = any, Variables extends AnyVariables = AnyVariables -> = [ - UseQueryState, - (opts?: Partial) => void -]; +> = [UseQueryState, UseQueryExecute]; -/** Convert the Source to a React Suspense source on demand */ +/** Convert the Source to a React Suspense source on demand + * @internal + */ function toSuspenseSource(source: Source): Source { const shared = share(source); let cache: T | void; @@ -98,6 +216,42 @@ const isSuspense = (client: Client, context?: Partial) => const sources = new Map>(); +/** Hook to run a GraphQL query and get updated GraphQL results. + * + * @param args - a {@link UseQueryArgs} object, to pass a `query`, `variables`, and options. + * @returns a {@link UseQueryResponse} tuple of a {@link UseQueryState} result, and re-execute function. + * + * @remarks + * `useQuery` allows GraphQL queries to be defined and executed. + * Given {@link UseQueryArgs.query}, it executes the GraphQL query with the + * context’s {@link Client}. + * + * The returned result updates when the `Client` has new results + * for the query, and changes when your input `args` change. + * + * Additionally, if the `suspense` option is enabled on the `Client`, + * the `useQuery` hook will suspend instead of indicating that it’s + * waiting for a result via {@link UseQueryState.fetching}. + * + * @see {@link https://urql.dev/goto/docs/basics/react-preact/#queries} for `useQuery` docs. + * + * @example + * ```ts + * import { gql, useQuery } from '@urql/preact'; + * + * const TodosQuery = gql` + * query { todos { id, title } } + * `; + * + * const Todos = () => { + * const [result, reexecuteQuery] = useQuery({ + * query: TodosQuery, + * variables: {}, + * }); + * // ... + * }; + * ``` + */ export function useQuery< Data = any, Variables extends AnyVariables = AnyVariables diff --git a/packages/preact-urql/src/hooks/useRequest.ts b/packages/preact-urql/src/hooks/useRequest.ts index 480a8a0658..72cf9d98df 100644 --- a/packages/preact-urql/src/hooks/useRequest.ts +++ b/packages/preact-urql/src/hooks/useRequest.ts @@ -7,7 +7,9 @@ import { createRequest, } from '@urql/core'; -/** Creates a request from a query and variables but preserves reference equality if the key isn't changing */ +/** Creates a request from a query and variables but preserves reference equality if the key isn't changing + * @internal + */ export function useRequest< Data = any, Variables extends AnyVariables = AnyVariables diff --git a/packages/preact-urql/src/hooks/useSubscription.ts b/packages/preact-urql/src/hooks/useSubscription.ts index f8f80dcbd8..17e2576815 100644 --- a/packages/preact-urql/src/hooks/useSubscription.ts +++ b/packages/preact-urql/src/hooks/useSubscription.ts @@ -14,36 +14,207 @@ import { useSource } from './useSource'; import { useRequest } from './useRequest'; import { initialState } from './constants'; +/** Input arguments for the {@link useSubscription} hook. + * + * @param query - The GraphQL subscription document that `useSubscription` executes. + * @param variables - The variables for the GraphQL subscription that `useSubscription` executes. + */ export type UseSubscriptionArgs< Variables extends AnyVariables = AnyVariables, Data = any > = { + /** Prevents {@link useSubscription} from automatically starting GraphQL subscriptions. + * + * @remarks + * `pause` may be set to `true` to stop {@link useSubscription} from starting its subscription + * automatically. The hook will stop receiving updates from the {@link Client} + * and won’t start the subscription operation, until either it’s set to `false` + * or the {@link UseSubscriptionExecute} function is called. + */ pause?: boolean; + /** Updates the {@link OperationContext} for the executed GraphQL subscription operation. + * + * @remarks + * `context` may be passed to {@link useSubscription}, to update the {@link OperationContext} + * of a subscription operation. This may be used to update the `context` that exchanges + * will receive for a single hook. + * + * Hint: This should be wrapped in a `useMemo` hook, to make sure that your + * component doesn’t infinitely update. + * + * @example + * ```ts + * const [result, reexecute] = useSubscription({ + * query, + * context: useMemo(() => ({ + * additionalTypenames: ['Item'], + * }), []) + * }); + * ``` + */ context?: Partial; } & GraphQLRequestParams; +/** Combines previous data with an incoming subscription result’s data. + * + * @remarks + * A `SubscriptionHandler` may be passed to {@link useSubscription} to + * aggregate subscription results into a combined {@link UseSubscriptionState.data} + * value. + * + * This is useful when a subscription event delivers a single item, while + * you’d like to display a list of events. + * + * @example + * ```ts + * const NotificationsSubscription = gql` + * subscription { newNotification { id, text } } + * `; + * + * const combineNotifications = (notifications = [], data) => { + * return [...notifications, data.newNotification]; + * }; + * + * const [result, executeSubscription] = useSubscription( + * { query: NotificationsSubscription }, + * combineNotifications, + * ); + * ``` + */ export type SubscriptionHandler = (prev: R | undefined, data: T) => R; +/** State of the current subscription, your {@link useSubscription} hook is executing. + * + * @remarks + * `UseSubscriptionState` is returned (in a tuple) by {@link useSubscription} and + * gives you the updating {@link OperationResult} of GraphQL subscriptions. + * + * If a {@link SubscriptionHandler} has been passed to `useSubscription` then + * {@link UseSubscriptionState.data} is instead the updated data as returned + * by the handler, otherwise it’s the latest result’s data. + * + * Hint: Even when the query and variables passed to {@link useSubscription} change, + * this state preserves the prior state. + */ export interface UseSubscriptionState< Data = any, Variables extends AnyVariables = AnyVariables > { + /** Indicates whether `useSubscription`’s subscription is active. + * + * @remarks + * When `useSubscription` starts a subscription, the `fetching` flag + * is set to `true` and will remain `true` until the subscription + * completes on the API, or the {@link UseSubscriptionArgs.pause} + * flag is set to `true`. + */ fetching: boolean; + /** Indicates that the subscription result is not fresh. + * + * @remarks + * This is mostly unused for subscriptions and will rarely affect you, and + * is more relevant for queries. + * + * @see {@link OperationResult.stale} for the source of this value. + */ stale: boolean; + /** The {@link OperationResult.data} for the executed subscription, or data returned by a handler. + * + * @remarks + * `data` will be set to the last {@link OperationResult.data} value + * received for the subscription. + * + * It will instead be set to the values that {@link SubscriptionHandler} + * returned, if a handler has been passed to {@link useSubscription}. + */ data?: Data; + /** The {@link OperationResult.error} for the executed subscription. */ error?: CombinedError; + /** The {@link OperationResult.extensions} for the executed mutation. */ extensions?: Record; + /** The {@link Operation} that the current state is for. + * + * @remarks + * This is the subscription {@link Operation} that is currently active. + * When {@link UseSubscriptionState.fetching} is `true`, this is the + * last `Operation` that the current state was for. + */ operation?: Operation; } +/** Triggers {@link useSubscription} to reexecute a GraphQL subscription operation. + * + * @param opts - optionally, context options that will be merged with the hook's + * {@link UseSubscriptionArgs.context} options and the `Client`’s options. + * + * @remarks + * When called, {@link useSubscription} will restart the GraphQL subscription + * operation it currently holds. If {@link UseSubscriptionArgs.pause} is set + * to `true`, it will start executing the subscription. + * + * ```ts + * const [result, executeSubscription] = useSubscription({ + * query, + * pause: true, + * }); + * + * const start = () => { + * executeSubscription(); + * }; + * ``` + */ +export type UseSubscriptionExecute = (opts?: Partial) => void; + +/** Result tuple returned by the {@link useSubscription} hook. + * + * @remarks + * Similarly to a `useState` hook’s return value, + * the first element is the {@link useSubscription}’s state, + * a {@link UseSubscriptionState} object, + * and the second is used to imperatively re-execute or start the subscription + * via a {@link UseMutationExecute} function. + */ export type UseSubscriptionResponse< Data = any, Variables extends AnyVariables = AnyVariables -> = [ - UseSubscriptionState, - (opts?: Partial) => void -]; +> = [UseSubscriptionState, UseSubscriptionExecute]; +/** Hook to run a GraphQL subscription and get updated GraphQL results. + * + * @param args - a {@link UseSubscriptionArgs} object, to pass a `query`, `variables`, and options. + * @param handler - optionally, a {@link SubscriptionHandler} function to combine multiple subscription results. + * @returns a {@link UseSubscriptionResponse} tuple of a {@link UseSubscriptionState} result, and an execute function. + * + * @remarks + * `useSubscription` allows GraphQL subscriptions to be defined and executed. + * Given {@link UseSubscriptionArgs.query}, it executes the GraphQL subscription with the + * context’s {@link Client}. + * + * The returned result updates when the `Client` has new results + * for the subscription, and `data` is updated with the result’s data + * or with the `data` that a `handler` returns. + * + * @example + * ```ts + * import { gql, useSubscription } from '@urql/preact'; + * + * const NotificationsSubscription = gql` + * subscription { newNotification { id, text } } + * `; + * + * const combineNotifications = (notifications = [], data) => { + * return [...notifications, data.newNotification]; + * }; + * + * const Notifications = () => { + * const [result, executeSubscription] = useSubscription( + * { query: NotificationsSubscription }, + * combineNotifications, + * ); + * // ... + * }; + * ``` + */ export function useSubscription< Data = any, Result = Data, diff --git a/packages/react-urql/src/components/Mutation.ts b/packages/react-urql/src/components/Mutation.ts index dcbc8c6243..43bde23c46 100644 --- a/packages/react-urql/src/components/Mutation.ts +++ b/packages/react-urql/src/components/Mutation.ts @@ -1,31 +1,49 @@ import { DocumentNode } from 'graphql'; import { ReactElement } from 'react'; -import { - AnyVariables, - TypedDocumentNode, - OperationResult, - OperationContext, -} from '@urql/core'; -import { useMutation, UseMutationState } from '../hooks'; +import { AnyVariables, TypedDocumentNode } from '@urql/core'; +import { useMutation, UseMutationState, UseMutationExecute } from '../hooks'; + +/** Props accepted by {@link Mutation}. + * + * @remarks + * `MutationProps` are the props accepted by the {@link Mutation} component. + * + * The result, the {@link MutationState} object, will be passed to + * a {@link MutationProps.children} function, passed as children + * to the `Mutation` component. + */ export interface MutationProps< Data = any, Variables extends AnyVariables = AnyVariables > { + /* The GraphQL mutation document that {@link useMutation} will execute. */ query: DocumentNode | TypedDocumentNode | string; - children: (arg: MutationState) => ReactElement; + children(arg: MutationState): ReactElement; } +/** Object that {@link MutationProps.children} is called with. + * + * @remarks + * This is an extented {@link UseMutationstate} with an added + * {@link MutationState.executeMutation} method, which is usually + * part of a tuple returned by {@link useMutation}. + */ export interface MutationState< Data = any, Variables extends AnyVariables = AnyVariables > extends UseMutationState { - executeMutation: ( - variables: Variables, - context?: Partial - ) => Promise>; + /** Alias to {@link useMutation}’s `executeMutation` function. */ + executeMutation: UseMutationExecute; } +/** Component Wrapper around {@link useMutation} to run a GraphQL query. + * + * @remarks + * `Mutation` is a component wrapper around the {@link useMutation} hook + * that calls the {@link MutationProps.children} prop, as a function, + * with the {@link MutationState} object. + */ export function Mutation< Data = any, Variables extends AnyVariables = AnyVariables diff --git a/packages/react-urql/src/components/Query.ts b/packages/react-urql/src/components/Query.ts index 154614a17a..77de601e2e 100644 --- a/packages/react-urql/src/components/Query.ts +++ b/packages/react-urql/src/components/Query.ts @@ -1,21 +1,52 @@ import { ReactElement } from 'react'; -import { AnyVariables, OperationContext } from '@urql/core'; -import { useQuery, UseQueryArgs, UseQueryState } from '../hooks'; +import { AnyVariables } from '@urql/core'; +import { + useQuery, + UseQueryArgs, + UseQueryState, + UseQueryExecute, +} from '../hooks'; + +/** Props accepted by {@link Query}. + * + * @remarks + * `QueryProps` are the props accepted by the {@link Query} component, + * which is identical to {@link UseQueryArgs}. + * + * The result, the {@link QueryState} object, will be passed to + * a {@link QueryProps.children} function, passed as children + * to the `Query` component. + */ export type QueryProps< Data = any, Variables extends AnyVariables = AnyVariables > = UseQueryArgs & { - children: (arg: QueryState) => ReactElement; + children(arg: QueryState): ReactElement; }; +/** Object that {@link QueryProps.children} is called with. + * + * @remarks + * This is an extented {@link UseQueryState} with an added + * {@link QueryState.executeQuery} method, which is usually + * part of a tuple returned by {@link useQuery}. + */ export interface QueryState< Data = any, Variables extends AnyVariables = AnyVariables > extends UseQueryState { - executeQuery: (opts?: Partial) => void; + /** Alias to {@link useQuery}’s `executeQuery` function. */ + executeQuery: UseQueryExecute; } +/** Component Wrapper around {@link useQuery} to run a GraphQL query. + * + * @remarks + * `Query` is a component wrapper around the {@link useQuery} hook + * that calls the {@link QueryProps.children} prop, as a function, + * with the {@link QueryState} object. + */ export function Query< Data = any, Variables extends AnyVariables = AnyVariables diff --git a/packages/react-urql/src/components/Subscription.ts b/packages/react-urql/src/components/Subscription.ts index 95521d204a..fd561f6d58 100644 --- a/packages/react-urql/src/components/Subscription.ts +++ b/packages/react-urql/src/components/Subscription.ts @@ -1,29 +1,57 @@ import { ReactElement } from 'react'; -import { AnyVariables, OperationContext } from '@urql/core'; +import { AnyVariables } from '@urql/core'; import { useSubscription, UseSubscriptionArgs, UseSubscriptionState, + UseSubscriptionExecute, SubscriptionHandler, } from '../hooks'; +/** Props accepted by {@link Subscription}. + * + * @remarks + * `SubscriptionProps` are the props accepted by the {@link Subscription} component, + * which is identical to {@link UseSubscriptionArgs} with an added + * {@link SubscriptionProps.handler} prop, which {@link useSubscription} usually + * accepts as an additional argument. + * + * The result, the {@link SubscriptionState} object, will be passed to + * a {@link SubscriptionProps.children} function, passed as children + * to the `Subscription` component. + */ export type SubscriptionProps< Data = any, Result = Data, Variables extends AnyVariables = AnyVariables > = UseSubscriptionArgs & { handler?: SubscriptionHandler; - children: (arg: SubscriptionState) => ReactElement; + children(arg: SubscriptionState): ReactElement; }; +/** Object that {@link SubscriptionProps.children} is called with. + * + * @remarks + * This is an extented {@link UseSubscriptionState} with an added + * {@link SubscriptionState.executeSubscription} method, which is usually + * part of a tuple returned by {@link useSubscription}. + */ export interface SubscriptionState< Data = any, Variables extends AnyVariables = AnyVariables > extends UseSubscriptionState { - executeSubscription: (opts?: Partial) => void; + /** Alias to {@link useSubscription}’s `executeMutation` function. */ + executeSubscription: UseSubscriptionExecute; } +/** Component Wrapper around {@link useSubscription} to run a GraphQL subscription. + * + * @remarks + * `Subscription` is a component wrapper around the {@link useSubscription} hook + * that calls the {@link SubscriptionProps.children} prop, as a function, + * with the {@link SubscriptionState} object. + */ export function Subscription< Data = any, Result = Data, diff --git a/packages/react-urql/src/context.ts b/packages/react-urql/src/context.ts index 90b2e575d7..c0c8162f5e 100644 --- a/packages/react-urql/src/context.ts +++ b/packages/react-urql/src/context.ts @@ -2,15 +2,68 @@ import { createContext, useContext } from 'react'; import { Client } from '@urql/core'; const OBJ = {}; + +/** `urql`'s React Context. + * + * @remarks + * The React Context that `urql`’s {@link Client} will be provided with. + * You may use the reexported {@link Provider} to provide a `Client` as well. + */ export const Context: import('react').Context = createContext(OBJ); + +/** Provider for `urql`'s {@link Client} to GraphQL hooks. + * + * @remarks + * `Provider` accepts a {@link Client} and provides it to all GraphQL hooks, + * and {@link useClient}. + * + * You should make sure to create a {@link Client} and provide it with the + * `Provider` to parts of your component tree that use GraphQL hooks. + * + * @example + * ```tsx + * import { Provider } from 'urql'; + * // All of `@urql/core` is also re-exported by `urql`: + * import { Client, cacheExchange, fetchExchange } from '@urql/core'; + * + * const client = new Client({ + * url: 'https://API', + * exchanges: [cacheExchange, fetchExchange], + * }); + * + * const App = () => ( + * + * + * + * ); + * ``` + */ export const Provider: import('react').Provider = Context.Provider; + +/** React Consumer component, providing the {@link Client} provided on a parent component. + * @remarks + * This is an alias for {@link Context.Consumer}. + */ export const Consumer: import('react').Consumer = Context.Consumer; Context.displayName = 'UrqlContext'; +/** Hook returning a {@link Client} from {@link Context}. + * + * @remarks + * `useClient` is a convenience hook, which accesses `urql`'s {@link Context} + * and returns the {@link Client} defined on it. + * + * This will be the {@link Client} you passed to a {@link Provider} + * you wrapped your elements containing this hook with. + * + * @throws + * In development, if the component you call `useClient()` in is + * not wrapped in a {@link Provider}, an error is thrown. + */ export const useClient = (): Client => { const client = useContext(Context); diff --git a/packages/react-urql/src/hooks/useMutation.ts b/packages/react-urql/src/hooks/useMutation.ts index 1927c5d8db..3e9e4ad72b 100644 --- a/packages/react-urql/src/hooks/useMutation.ts +++ b/packages/react-urql/src/hooks/useMutation.ts @@ -15,29 +15,132 @@ import { import { useClient } from '../context'; import { initialState } from './state'; +/** State of the last mutation executed by your {@link useMutation} hook. + * + * @remarks + * `UseMutationState` is returned (in a tuple) by {@link useMutation} and + * gives you the {@link OperationResult} of the last mutation executed + * with {@link UseMutationExecute}. + * + * Even if the mutation document passed to {@link useMutation} changes, + * the state isn’t reset, so you can keep displaying the previous result. + */ export interface UseMutationState< Data = any, Variables extends AnyVariables = AnyVariables > { + /** Indicates whether `useMutation` is currently executing a mutation. */ fetching: boolean; + /** Indicates that the mutation result is not fresh. + * + * @remarks + * The `stale` flag is set to `true` when a new result for the mutation + * is expected. + * This is mostly unused for mutations and will rarely affect you, and + * is more relevant for queries. + * + * @see {@link OperationResult.stale} for the source of this value. + */ stale: boolean; + /** The {@link OperationResult.data} for the executed mutation. */ data?: Data; + /** The {@link OperationResult.error} for the executed mutation. */ error?: CombinedError; + /** The {@link OperationResult.extensions} for the executed mutation. */ extensions?: Record; + /** The {@link Operation} that the current state is for. + * + * @remarks + * This is the mutation {@link Operation} that has last been executed. + * When {@link UseQueryState.fetching} is `true`, this is the + * last `Operation` that the current state was for. + */ operation?: Operation; } +/** Triggers {@link useMutation} to execute its GraphQL mutation operation. + * + * @param variables - variables using which the mutation will be executed. + * @param context - optionally, context options that will be merged with the hook's + * {@link UseQueryArgs.context} options and the `Client`’s options. + * @returns the {@link OperationResult} of the mutation. + * + * @remarks + * When called, {@link useMutation} will start the GraphQL mutation + * it currently holds and use the `variables` passed to it. + * + * Once the mutation response comes back from the API, its + * returned promise will resolve to the mutation’s {@link OperationResult} + * and the {@link UseMutationState} will be updated with the result. + * + * @example + * ```ts + * const [result, executeMutation] = useMutation(UpdateTodo); + * const start = async ({ id, title }) => { + * const result = await executeMutation({ id, title }); + * }; + */ +export type UseMutationExecute< + Data = any, + Variables extends AnyVariables = AnyVariables +> = ( + variables: Variables, + context?: Partial +) => Promise>; + +/** Result tuple returned by the {@link useMutation} hook. + * + * @remarks + * Similarly to a `useState` hook’s return value, + * the first element is the {@link useMutation}’s state, updated + * as mutations are executed with the second value, which is + * used to start mutations and is a {@link UseMutationExecute} + * function. + */ export type UseMutationResponse< Data = any, Variables extends AnyVariables = AnyVariables -> = [ - UseMutationState, - ( - variables: Variables, - context?: Partial - ) => Promise> -]; +> = [UseMutationState, UseMutationExecute]; +/** Hook to create a GraphQL mutation, run by passing variables to the returned execute function. + * + * @param query - a GraphQL mutation document which `useMutation` will execute. + * @returns a {@link UseMutationResponse} tuple of a {@link UseMutationState} result, + * and an execute function to start the mutation. + * + * @remarks + * `useMutation` allows GraphQL mutations to be defined and keeps its state + * after the mutation is started with the returned execute function. + * + * Given a GraphQL mutation document it returns state to keep track of the + * mutation state and a {@link UseMutationExecute} function, which accepts + * variables for the mutation to be executed. + * Once called, the mutation executes and the state will be updated with + * the mutation’s result. + * + * @see {@link https://urql.dev/goto/urql/docs/basics/react-preact/#mutations} for `useMutation` docs. + * + * @example + * ```ts + * import { gql, useMutation } from 'urql'; + * + * const UpdateTodo = gql` + * mutation ($id: ID!, $title: String!) { + * updateTodo(id: $id, title: $title) { + * id, title + * } + * } + * `; + * + * const UpdateTodo = () => { + * const [result, executeMutation] = useMutation(UpdateTodo); + * const start = async ({ id, title }) => { + * const result = await executeMutation({ id, title }); + * }; + * // ... + * }; + * ``` + */ export function useMutation< Data = any, Variables extends AnyVariables = AnyVariables diff --git a/packages/react-urql/src/hooks/useQuery.ts b/packages/react-urql/src/hooks/useQuery.ts index 1137d853a7..554ed5b740 100644 --- a/packages/react-urql/src/hooks/useQuery.ts +++ b/packages/react-urql/src/hooks/useQuery.ts @@ -19,38 +19,193 @@ import { useRequest } from './useRequest'; import { getCacheForClient } from './cache'; import { initialState, computeNextState, hasDepsChanged } from './state'; +/** Input arguments for the {@link useQuery} hook. + * + * @param query - The GraphQL query that `useQuery` executes. + * @param variables - The variables for the GraphQL query that `useQuery` executes. + */ export type UseQueryArgs< Variables extends AnyVariables = AnyVariables, Data = any > = { + /** Updates the {@link RequestPolicy} for the executed GraphQL query operation. + * + * @remarks + * `requestPolicy` modifies the {@link RequestPolicy} of the GraphQL query operation + * that `useQuery` executes, and indicates a caching strategy for cache exchanges. + * + * For example, when set to `'cache-and-network'`, {@link useQuery} will + * receive a cached result with `stale: true` and an API request will be + * sent in the background. + * + * @see {@link OperationContext.requestPolicy} for where this value is set. + */ requestPolicy?: RequestPolicy; + /** Updates the {@link OperationContext} for the executed GraphQL query operation. + * + * @remarks + * `context` may be passed to {@link useQuery}, to update the {@link OperationContext} + * of a query operation. This may be used to update the `context` that exchanges + * will receive for a single hook. + * + * Hint: This should be wrapped in a `useMemo` hook, to make sure that your + * component doesn’t infinitely update. + * + * @example + * ```ts + * const [result, reexecute] = useQuery({ + * query, + * context: useMemo(() => ({ + * additionalTypenames: ['Item'], + * }), []) + * }); + * ``` + */ context?: Partial; + /** Prevents {@link useQuery} from automatically executing GraphQL query operations. + * + * @remarks + * `pause` may be set to `true` to stop {@link useQuery} from executing + * automatically. The hook will stop receiving updates from the {@link Client} + * and won’t execute the query operation, until either it’s set to `false` + * or the {@link UseQueryExecute} function is called. + * + * @see {@link https://urql.dev/goto/docs/basics/react-preact/#pausing-usequery} for + * documentation on the `pause` option. + */ pause?: boolean; } & GraphQLRequestParams; +/** State of the current query, your {@link useQuery} hook is executing. + * + * @remarks + * `UseQueryState` is returned (in a tuple) by {@link useQuery} and + * gives you the updating {@link OperationResult} of GraphQL queries. + * + * Even when the query and variables passed to {@link useQuery} change, + * this state preserves the prior state and sets the `fetching` flag to + * `true`. + * This allows you to display the previous state, while implementing + * a separate loading indicator separately. + */ export interface UseQueryState< Data = any, Variables extends AnyVariables = AnyVariables > { + /** Indicates whether `useQuery` is waiting for a new result. + * + * @remarks + * When `useQuery` is passed a new query and/or variables, it will + * start executing the new query operation and `fetching` is set to + * `true` until a result arrives. + * + * Hint: This is subtly different than whether the query is actually + * fetching, and doesn’t indicate whether a query is being re-executed + * in the background. For this, see {@link UseQueryState.stale}. + */ fetching: boolean; + /** Indicates that the state is not fresh and a new result will follow. + * + * @remarks + * The `stale` flag is set to `true` when a new result for the query + * is expected and `useQuery` is waiting for it. This may indicate that + * a new request is being requested in the background. + * + * @see {@link OperationResult.stale} for the source of this value. + */ stale: boolean; + /** The {@link OperationResult.data} for the executed query. */ data?: Data; + /** The {@link OperationResult.error} for the executed query. */ error?: CombinedError; + /** The {@link OperationResult.extensions} for the executed query. */ extensions?: Record; + /** The {@link Operation} that the current state is for. + * + * @remarks + * This is the {@link Operation} that is currently being executed. + * When {@link UseQueryState.fetching} is `true`, this is the + * last `Operation` that the current state was for. + */ operation?: Operation; } +/** Triggers {@link useQuery} to execute a new GraphQL query operation. + * + * @param opts - optionally, context options that will be merged with the hook's + * {@link UseQueryArgs.context} options and the `Client`’s options. + * + * @remarks + * When called, {@link useQuery} will re-execute the GraphQL query operation + * it currently holds, even if {@link UseQueryArgs.pause} is set to `true`. + * + * This is useful for executing a paused query or re-executing a query + * and get a new network result, by passing a new request policy. + * + * ```ts + * const [result, reexecuteQuery] = useQuery({ query }); + * + * const refresh = () => { + * // Re-execute the query with a network-only policy, skipping the cache + * reexecuteQuery({ requestPolicy: 'network-only' }); + * }; + * ``` + */ +export type UseQueryExecute = (opts?: Partial) => void; + +/** Result tuple returned by the {@link useQuery} hook. + * + * @remarks + * Similarly to a `useState` hook’s return value, + * the first element is the {@link useQuery}’s result and state, + * a {@link UseQueryState} object, + * and the second is used to imperatively re-execute the query + * via a {@link UseQueryExecute} function. + */ export type UseQueryResponse< Data = any, Variables extends AnyVariables = AnyVariables -> = [ - UseQueryState, - (opts?: Partial) => void -]; +> = [UseQueryState, UseQueryExecute]; const isSuspense = (client: Client, context?: Partial) => client.suspense && (!context || context.suspense !== false); +/** Hook to run a GraphQL query and get updated GraphQL results. + * + * @param args - a {@link UseQueryArgs} object, to pass a `query`, `variables`, and options. + * @returns a {@link UseQueryResponse} tuple of a {@link UseQueryState} result, and re-execute function. + * + * @remarks + * `useQuery` allows GraphQL queries to be defined and executed. + * Given {@link UseQueryArgs.query}, it executes the GraphQL query with the + * context’s {@link Client}. + * + * The returned result updates when the `Client` has new results + * for the query, and changes when your input `args` change. + * + * Additionally, if the `suspense` option is enabled on the `Client`, + * the `useQuery` hook will suspend instead of indicating that it’s + * waiting for a result via {@link UseQueryState.fetching}. + * + * @see {@link https://urql.dev/goto/urql/docs/basics/react-preact/#queries} for `useQuery` docs. + * + * @example + * ```ts + * import { gql, useQuery } from 'urql'; + * + * const TodosQuery = gql` + * query { todos { id, title } } + * `; + * + * const Todos = () => { + * const [result, reexecuteQuery] = useQuery({ + * query: TodosQuery, + * variables: {}, + * }); + * // ... + * }; + * ``` + */ export function useQuery< Data = any, Variables extends AnyVariables = AnyVariables diff --git a/packages/react-urql/src/hooks/useRequest.ts b/packages/react-urql/src/hooks/useRequest.ts index ef17103866..a909b95f00 100644 --- a/packages/react-urql/src/hooks/useRequest.ts +++ b/packages/react-urql/src/hooks/useRequest.ts @@ -7,7 +7,9 @@ import { createRequest, } from '@urql/core'; -/** Creates a request from a query and variables but preserves reference equality if the key isn't changing */ +/** Creates a request from a query and variables but preserves reference equality if the key isn't changing + * @internal + */ export function useRequest< Data = any, Variables extends AnyVariables = AnyVariables diff --git a/packages/react-urql/src/hooks/useSubscription.ts b/packages/react-urql/src/hooks/useSubscription.ts index 1f3f3b3708..33eadbcde0 100644 --- a/packages/react-urql/src/hooks/useSubscription.ts +++ b/packages/react-urql/src/hooks/useSubscription.ts @@ -15,36 +15,207 @@ import { useClient } from '../context'; import { useRequest } from './useRequest'; import { initialState, computeNextState, hasDepsChanged } from './state'; +/** Input arguments for the {@link useSubscription} hook. + * + * @param query - The GraphQL subscription document that `useSubscription` executes. + * @param variables - The variables for the GraphQL subscription that `useSubscription` executes. + */ export type UseSubscriptionArgs< Variables extends AnyVariables = AnyVariables, Data = any > = { + /** Prevents {@link useSubscription} from automatically starting GraphQL subscriptions. + * + * @remarks + * `pause` may be set to `true` to stop {@link useSubscription} from starting its subscription + * automatically. The hook will stop receiving updates from the {@link Client} + * and won’t start the subscription operation, until either it’s set to `false` + * or the {@link UseSubscriptionExecute} function is called. + */ pause?: boolean; + /** Updates the {@link OperationContext} for the executed GraphQL subscription operation. + * + * @remarks + * `context` may be passed to {@link useSubscription}, to update the {@link OperationContext} + * of a subscription operation. This may be used to update the `context` that exchanges + * will receive for a single hook. + * + * Hint: This should be wrapped in a `useMemo` hook, to make sure that your + * component doesn’t infinitely update. + * + * @example + * ```ts + * const [result, reexecute] = useSubscription({ + * query, + * context: useMemo(() => ({ + * additionalTypenames: ['Item'], + * }), []) + * }); + * ``` + */ context?: Partial; } & GraphQLRequestParams; +/** Combines previous data with an incoming subscription result’s data. + * + * @remarks + * A `SubscriptionHandler` may be passed to {@link useSubscription} to + * aggregate subscription results into a combined {@link UseSubscriptionState.data} + * value. + * + * This is useful when a subscription event delivers a single item, while + * you’d like to display a list of events. + * + * @example + * ```ts + * const NotificationsSubscription = gql` + * subscription { newNotification { id, text } } + * `; + * + * const combineNotifications = (notifications = [], data) => { + * return [...notifications, data.newNotification]; + * }; + * + * const [result, executeSubscription] = useSubscription( + * { query: NotificationsSubscription }, + * combineNotifications, + * ); + * ``` + */ export type SubscriptionHandler = (prev: R | undefined, data: T) => R; +/** State of the current subscription, your {@link useSubscription} hook is executing. + * + * @remarks + * `UseSubscriptionState` is returned (in a tuple) by {@link useSubscription} and + * gives you the updating {@link OperationResult} of GraphQL subscriptions. + * + * If a {@link SubscriptionHandler} has been passed to `useSubscription` then + * {@link UseSubscriptionState.data} is instead the updated data as returned + * by the handler, otherwise it’s the latest result’s data. + * + * Hint: Even when the query and variables passed to {@link useSubscription} change, + * this state preserves the prior state. + */ export interface UseSubscriptionState< Data = any, Variables extends AnyVariables = AnyVariables > { + /** Indicates whether `useSubscription`’s subscription is active. + * + * @remarks + * When `useSubscription` starts a subscription, the `fetching` flag + * is set to `true` and will remain `true` until the subscription + * completes on the API, or the {@link UseSubscriptionArgs.pause} + * flag is set to `true`. + */ fetching: boolean; + /** Indicates that the subscription result is not fresh. + * + * @remarks + * This is mostly unused for subscriptions and will rarely affect you, and + * is more relevant for queries. + * + * @see {@link OperationResult.stale} for the source of this value. + */ stale: boolean; + /** The {@link OperationResult.data} for the executed subscription, or data returned by a handler. + * + * @remarks + * `data` will be set to the last {@link OperationResult.data} value + * received for the subscription. + * + * It will instead be set to the values that {@link SubscriptionHandler} + * returned, if a handler has been passed to {@link useSubscription}. + */ data?: Data; + /** The {@link OperationResult.error} for the executed subscription. */ error?: CombinedError; + /** The {@link OperationResult.extensions} for the executed mutation. */ extensions?: Record; + /** The {@link Operation} that the current state is for. + * + * @remarks + * This is the subscription {@link Operation} that is currently active. + * When {@link UseSubscriptionState.fetching} is `true`, this is the + * last `Operation` that the current state was for. + */ operation?: Operation; } +/** Triggers {@link useSubscription} to reexecute a GraphQL subscription operation. + * + * @param opts - optionally, context options that will be merged with the hook's + * {@link UseSubscriptionArgs.context} options and the `Client`’s options. + * + * @remarks + * When called, {@link useSubscription} will restart the GraphQL subscription + * operation it currently holds. If {@link UseSubscriptionArgs.pause} is set + * to `true`, it will start executing the subscription. + * + * ```ts + * const [result, executeSubscription] = useSubscription({ + * query, + * pause: true, + * }); + * + * const start = () => { + * executeSubscription(); + * }; + * ``` + */ +export type UseSubscriptionExecute = (opts?: Partial) => void; + +/** Result tuple returned by the {@link useSubscription} hook. + * + * @remarks + * Similarly to a `useState` hook’s return value, + * the first element is the {@link useSubscription}’s state, + * a {@link UseSubscriptionState} object, + * and the second is used to imperatively re-execute or start the subscription + * via a {@link UseMutationExecute} function. + */ export type UseSubscriptionResponse< Data = any, Variables extends AnyVariables = AnyVariables -> = [ - UseSubscriptionState, - (opts?: Partial) => void -]; +> = [UseSubscriptionState, UseSubscriptionExecute]; +/** Hook to run a GraphQL subscription and get updated GraphQL results. + * + * @param args - a {@link UseSubscriptionArgs} object, to pass a `query`, `variables`, and options. + * @param handler - optionally, a {@link SubscriptionHandler} function to combine multiple subscription results. + * @returns a {@link UseSubscriptionResponse} tuple of a {@link UseSubscriptionState} result, and an execute function. + * + * @remarks + * `useSubscription` allows GraphQL subscriptions to be defined and executed. + * Given {@link UseSubscriptionArgs.query}, it executes the GraphQL subscription with the + * context’s {@link Client}. + * + * The returned result updates when the `Client` has new results + * for the subscription, and `data` is updated with the result’s data + * or with the `data` that a `handler` returns. + * + * @example + * ```ts + * import { gql, useSubscription } from 'urql'; + * + * const NotificationsSubscription = gql` + * subscription { newNotification { id, text } } + * `; + * + * const combineNotifications = (notifications = [], data) => { + * return [...notifications, data.newNotification]; + * }; + * + * const Notifications = () => { + * const [result, executeSubscription] = useSubscription( + * { query: NotificationsSubscription }, + * combineNotifications, + * ); + * // ... + * }; + * ``` + */ export function useSubscription< Data = any, Result = Data, diff --git a/packages/storage-rn/src/makeAsyncStorage.ts b/packages/storage-rn/src/makeAsyncStorage.ts index 27ccc39aa2..7acb7ecdd9 100644 --- a/packages/storage-rn/src/makeAsyncStorage.ts +++ b/packages/storage-rn/src/makeAsyncStorage.ts @@ -2,11 +2,20 @@ import { StorageAdapter } from '@urql/exchange-graphcache'; import AsyncStorage from '@react-native-async-storage/async-storage'; import NetInfo from '@react-native-community/netinfo'; -export type StorageOptions = { +export interface StorageOptions { + /** Name of the `AsyncStorage` key that’s used for persisted data. + * @defaultValue `'graphcache-data'` + */ dataKey?: string; + /** Name of the `AsyncStorage` key that’s used for persisted metadata. + * @defaultValue `'graphcache-metadata'` + */ metadataKey?: string; - maxAge?: number; // Number of days -}; + /** Maximum age of cache entries (in days) after which data is discarded. + * @defaultValue `7` days + */ + maxAge?: number; +} const parseData = (persistedData: any, fallback: any) => { try { @@ -20,10 +29,25 @@ const parseData = (persistedData: any, fallback: any) => { let disconnect; +/** React Native storage adapter persisting to `AsyncStorage`. */ export interface DefaultAsyncStorage extends StorageAdapter { + /** Clears the entire `AsyncStorage`. */ clear(): Promise; } +/** Creates a {@link StorageAdapter} which uses React Native’s `AsyncStorage`. + * + * @param opts - A {@link StorageOptions} configuration object. + * @returns the created {@link DefaultAsyncStorage} adapter. + * + * @remarks + * `makeAsyncStorage` creates a storage adapter for React Native, + * which persisted to `AsyncStorage` via the `@react-native-async-storage/async-storage` + * package. + * + * Note: We have no data on stability of this storage and our Offline Support + * for large APIs or longterm use. Proceed with caution. + */ export const makeAsyncStorage: ( ops?: StorageOptions ) => DefaultAsyncStorage = ({ diff --git a/packages/svelte-urql/src/common.ts b/packages/svelte-urql/src/common.ts index 23ac6648c4..b4ee1a9ba2 100644 --- a/packages/svelte-urql/src/common.ts +++ b/packages/svelte-urql/src/common.ts @@ -2,19 +2,38 @@ import type { Readable, Writable } from 'svelte/store'; import type { AnyVariables, OperationResult } from '@urql/core'; import { Source, make } from 'wonka'; +/** An {@link OperationResult} with an added {@link OperationResultState.fetching} flag. + * + * @remarks + * Stores will contain a readable state based on {@link OperationResult | OperationResults} + * they received. + */ export interface OperationResultState< Data = any, Variables extends AnyVariables = AnyVariables > extends OperationResult { + /** Indicates whether the store is waiting for a new {@link OperationResult}. + * + * @remarks + * When a store starts executing a GraphQL operation, `fetching` is + * set to `true` until a result arrives. + * + * Hint: This is subtly different than whether the operation is actually + * fetching, and doesn’t indicate whether an operation is being re-executed + * in the background. For this, see {@link OperationResult.stale}. + */ fetching: boolean; } -/** A Readable containing an `OperationResult` with a fetching flag. */ +/** A Readable store of {@link OperationResultState}. */ export type OperationResultStore< Data = any, Variables extends AnyVariables = AnyVariables > = Readable>; +/** Consumes a {@link Readable} as a {@link Source}. + * @internal + */ export const fromStore = (store$: Readable): Source => make(observer => store$.subscribe(observer.next)); @@ -28,12 +47,56 @@ export const initialResult = { stale: false, }; +/** A pausable Svelte store. + * + * @remarks + * The {@link queryStore} and {@link useSubscription} store allow + * you to pause execution and resume it later on, which is managed + * by a `pause` option passed to them. + * + * A `Pauseable` allows execution of GraphQL operations to be paused, + * which means a {@link OperationResultStore} won’t update with new + * results or execute new operations, and to be resumed later on. + */ export interface Pausable { + /** Indicates whether a store is currently paused. + * + * @remarks + * When a {@link OperationResultStore} has been paused, it will stop + * receiving updates from the {@link Client} and won’t execute GraphQL + * operations, until this writable becomes `true` or + * {@link Pausable.resume} is called. + * + * @see {@link https://urql.dev/goto/docs/basics/svelte#pausing-queries} for + * documentation on the `Pausable`. + */ isPaused$: Writable; + /** Pauses a GraphQL operation to stop it from executing. + * + * @remarks + * Pauses an {@link OperationResultStore}’s GraphQL operation, which + * stops it from receiving updates from the {@link Client} and to stop + * an ongoing operation. + * + * @see {@link https://urql.dev/goto/docs/basics/svelte#pausing-queries} for + * documentation on the `Pausable`. + */ pause(): void; + /** Resumes a paused GraphQL operation if it’s currently paused. + * + * @remarks + * Resumes or starts {@link OperationResultStore}’s GraphQL operation, + * if it’s currently paused. + * + * @see {@link https://urql.dev/goto/docs/basics/svelte#pausing-queries} for + * documentation on the `Pausable`. + */ resume(): void; } +/** Creates a {@link Pausable}. + * @internal + */ export const createPausable = (isPaused$: Writable): Pausable => ({ isPaused$, pause() { diff --git a/packages/svelte-urql/src/context.ts b/packages/svelte-urql/src/context.ts index 43889c7702..d01cac57e7 100644 --- a/packages/svelte-urql/src/context.ts +++ b/packages/svelte-urql/src/context.ts @@ -3,7 +3,20 @@ import { Client, ClientOptions } from '@urql/core'; const _contextKey = '$$_urql'; -/** Retrieves a Client from Svelte's context */ +/** Returns a provided {@link Client}. + * + * @remarks + * `getContextClient` returns the {@link Client} that’s previously + * been provided on Svelte’s context with {@link setContextClient}. + * + * This is useful to create a `Client` on Svelte’s context once, and + * then pass it to all GraphQL store functions without importing it + * from a singleton export. + * + * @throws + * In development, if `getContextClient` can’t get a {@link Client} + * from Svelte’s context, an error will be thrown. + */ export const getContextClient = (): Client => { const client = getContext(_contextKey); if (process.env.NODE_ENV !== 'production' && !client) { @@ -15,12 +28,28 @@ export const getContextClient = (): Client => { return client as Client; }; -/** Sets a Client on Svelte's context */ +/** Provides a {@link Client} to a component’s children. + * + * @remarks + * `setContextClient` updates the Svelte context to provide + * a {@link Client} to be later retrieved using the + * {@link getContextClient} function. + */ export const setContextClient = (client: Client): void => { setContext(_contextKey, client); }; -/** Creates Client and adds it to Svelte's context */ +/** Creates a {@link Client} and provides it to a component’s children. + * + * @param args - a {@link ClientOptions} object to create a `Client` with. + * @returns the created {@link Client}. + * + * @remarks + * `initContextClient` is a convenience wrapper around + * `setContextClient` that accepts {@link ClientOptions}, + * creates a {@link Client} and provides it to be later + * retrieved using the {@link getContextClient} function. + */ export const initContextClient = (args: ClientOptions): Client => { const client = new Client(args); setContextClient(client); diff --git a/packages/svelte-urql/src/index.ts b/packages/svelte-urql/src/index.ts index e259cff7e2..776559b3d9 100644 --- a/packages/svelte-urql/src/index.ts +++ b/packages/svelte-urql/src/index.ts @@ -1,4 +1,11 @@ export * from '@urql/core'; + +export type { + Pausable, + OperationResultStore, + OperationResultState, +} from './common'; + export * from './queryStore'; export * from './mutationStore'; export * from './subscriptionStore'; diff --git a/packages/svelte-urql/src/mutationStore.ts b/packages/svelte-urql/src/mutationStore.ts index fe8915d01b..c475fc600e 100644 --- a/packages/svelte-urql/src/mutationStore.ts +++ b/packages/svelte-urql/src/mutationStore.ts @@ -15,14 +15,81 @@ import { initialResult, } from './common'; +/** Input arguments for the {@link mutationStore} function. + * + * @param query - The GraphQL mutation that the `mutationStore` executes. + * @param variables - The variables for the GraphQL mutation that `mutationStore` executes. + */ export type MutationArgs< Data = any, Variables extends AnyVariables = AnyVariables > = { + /** The {@link Client} using which the subscription will be started. + * + * @remarks + * If you’ve previously provided a {@link Client} on Svelte’s context + * this can be set to {@link getContextClient}’s return value. + */ client: Client; + /** Updates the {@link OperationContext} for the GraphQL mutation operation. + * + * @remarks + * `context` may be passed to {@link mutationStore}, to update the + * {@link OperationContext} of a mutation operation. This may be used to update + * the `context` that exchanges will receive for a single hook. + * + * @example + * ```ts + * mutationStore({ + * query, + * context: { + * additionalTypenames: ['Item'], + * }, + * }); + * ``` + */ context?: Partial; } & GraphQLRequestParams; +/** Function to create a `mutationStore` that runs a GraphQL mutation and updates with a GraphQL result. + * + * @param args - a {@link MutationArgs} object, to pass a `query`, `variables`, and options. + * @returns a {@link OperationResultStore} of the mutation’s result. + * + * @remarks + * `mutationStore` allows a GraphQL mutation to be defined as a Svelte store. + * Given {@link MutationArgs.query}, it executes the GraphQL mutation on the + * {@link MutationArgs.client}. + * + * The returned store updates with an {@link OperationResult} when + * the `Client` returns a result for the mutation. + * + * Hint: It’s often easier to use {@link Client.mutation} if you’re + * creating a mutation imperatively and don’t need a store. + * + * @see {@link https://urql.dev/goto/docs/basics/svelte#mutations} for + * `mutationStore` docs. + * + * @example + * ```ts + * import { mutationStore, gql, getContextClient } from '@urql/svelte'; + * + * const client = getContextClient(); + * + * let result; + * function updateTodo({ id, title }) { + * result = queryStore({ + * client, + * query: gql` + * mutation($id: ID!, $title: String!) { + * updateTodo(id: $id, title: $title) { id, title } + * } + * `, + * variables: { id, title }, + * }); + * } + * ``` + */ export function mutationStore< Data = any, Variables extends AnyVariables = AnyVariables diff --git a/packages/svelte-urql/src/queryStore.ts b/packages/svelte-urql/src/queryStore.ts index 49e0393bb3..cc1cb7c167 100644 --- a/packages/svelte-urql/src/queryStore.ts +++ b/packages/svelte-urql/src/queryStore.ts @@ -30,16 +30,92 @@ import { fromStore, } from './common'; +/** Input arguments for the {@link queryStore} function. + * + * @param query - The GraphQL query that the `queryStore` executes. + * @param variables - The variables for the GraphQL query that `queryStore` executes. + */ export type QueryArgs< Data = any, Variables extends AnyVariables = AnyVariables > = { + /** The {@link Client} using which the query will be executed. + * + * @remarks + * If you’ve previously provided a {@link Client} on Svelte’s context + * this can be set to {@link getContextClient}’s return value. + */ client: Client; + /** Updates the {@link OperationContext} for the executed GraphQL query operation. + * + * @remarks + * `context` may be passed to {@link queryStore}, to update the {@link OperationContext} + * of a query operation. This may be used to update the `context` that exchanges + * will receive for a single hook. + * + * @example + * ```ts + * queryStore({ + * query, + * context: { + * additionalTypenames: ['Item'], + * }, + * }); + * ``` + */ context?: Partial; + /** Sets the {@link RequestPolicy} for the executed GraphQL query operation. + * + * @remarks + * `requestPolicy` modifies the {@link RequestPolicy} of the GraphQL query operation + * that the {@link queryStore} executes, and indicates a caching strategy for cache exchanges. + * + * For example, when set to `'cache-and-network'`, the `queryStore` will + * receive a cached result with `stale: true` and an API request will be + * sent in the background. + * + * @see {@link OperationContext.requestPolicy} for where this value is set. + */ requestPolicy?: RequestPolicy; + /** Prevents the {@link queryStore} from automatically executing GraphQL query operations. + * + * @remarks + * `pause` may be set to `true` to stop the {@link queryStore} from executing + * automatically. The store will stop receiving updates from the {@link Client} + * and won’t execute the query operation, until either it’s set to `false` + * or {@link Pausable.resume} is called. + * + * @see {@link https://urql.dev/goto/docs/basics/svelte#pausing-queries} for + * documentation on the `pause` option. + */ pause?: boolean; } & GraphQLRequestParams; +/** Function to create a `queryStore` that runs a GraphQL query and updates with GraphQL results. + * + * @param args - a {@link QueryArgs} object, to pass a `query`, `variables`, and options. + * @returns a {@link OperationResultStore} of query results, which implements {@link Pausable}. + * + * @remarks + * `queryStore` allows GraphQL queries to be defined as Svelte stores. + * Given {@link QueryArgs.query}, it executes the GraphQL query on the + * {@link QueryArgs.client}. + * + * The returned store updates with {@link OperationResult} values when + * the `Client` has new results for the query. + * + * @see {@link https://urql.dev/goto/docs/basics/svelte#queries} for `queryStore` docs. + * + * @example + * ```ts + * import { queryStore, gql, getContextClient } from '@urql/svelte'; + * + * const todos = queryStore({ + * client: getContextClient(), + * query: gql`{ todos { id, title } }`, + * }); + * ``` + */ export function queryStore< Data = any, Variables extends AnyVariables = AnyVariables diff --git a/packages/svelte-urql/src/subscriptionStore.ts b/packages/svelte-urql/src/subscriptionStore.ts index 11de995cd4..8bb15d7229 100644 --- a/packages/svelte-urql/src/subscriptionStore.ts +++ b/packages/svelte-urql/src/subscriptionStore.ts @@ -29,17 +29,110 @@ import { fromStore, } from './common'; +/** Combines previous data with an incoming subscription result’s data. + * + * @remarks + * A `SubscriptionHandler` may be passed to {@link subscriptionStore} to + * aggregate subscription results into a combined `data` value on the + * {@link OperationResultStore}. + * + * This is useful when a subscription event delivers a single item, while + * you’d like to display a list of events. + * + * @example + * ```ts + * const NotificationsSubscription = gql` + * subscription { newNotification { id, text } } + * `; + * + * subscriptionStore( + * { query: NotificationsSubscription }, + * function combineNotifications(notifications = [], data) { + * return [...notifications, data.newNotification]; + * }, + * ); + * ``` + */ export type SubscriptionHandler = (prev: R | undefined, data: T) => R; +/** Input arguments for the {@link subscriptionStore} function. + * + * @param query - The GraphQL subscription that the `subscriptionStore` executes. + * @param variables - The variables for the GraphQL subscription that `subscriptionStore` executes. + */ export type SubscriptionArgs< Data = any, Variables extends AnyVariables = AnyVariables > = { + /** The {@link Client} using which the subscription will be started. + * + * @remarks + * If you’ve previously provided a {@link Client} on Svelte’s context + * this can be set to {@link getContextClient}’s return value. + */ client: Client; + /** Updates the {@link OperationContext} for the GraphQL subscription operation. + * + * @remarks + * `context` may be passed to {@link subscriptionStore}, to update the + * {@link OperationContext} of a subscription operation. This may be used to update + * the `context` that exchanges will receive for a single hook. + * + * @example + * ```ts + * subscriptionStore({ + * query, + * context: { + * additionalTypenames: ['Item'], + * }, + * }); + * ``` + */ context?: Partial; + /** Prevents the {@link subscriptionStore} from automatically starting the GraphQL subscription. + * + * @remarks + * `pause` may be set to `true` to stop the {@link subscriptionStore} from starting + * its subscription automatically. The store won't execute the subscription operation, + * until either it’s set to `false` or {@link Pausable.resume} is called. + */ pause?: boolean; } & GraphQLRequestParams; +/** Function to create a `subscriptionStore` that starts a GraphQL subscription. + * + * @param args - a {@link QueryArgs} object, to pass a `query`, `variables`, and options. + * @param handler - optionally, a {@link SubscriptionHandler} function to combine multiple subscription results. + * @returns a {@link OperationResultStore} of subscription results, which implements {@link Pausable}. + * + * @remarks + * `subscriptionStore` allows GraphQL subscriptions to be defined as Svelte stores. + * Given {@link SubscriptionArgs.query}, it executes the GraphQL subsription on the + * {@link SubscriptionArgs.client}. + * + * The returned store updates with {@link OperationResult} values when + * the `Client` has new results for the subscription. + * + * @see {@link https://urql.dev/goto/docs/advanced/subscriptions#svelte} for + * `subscriptionStore` docs. + * + * @example + * ```ts + * import { subscriptionStore, gql, getContextClient } from '@urql/svelte'; + * + * const todos = subscriptionStore({ + * client: getContextClient(), + * query: gql` + * subscription { + * newNotification { id, text } + * } + * `, + * function combineNotifications(notifications = [], data) { + * return [...notifications, data.newNotification]; + * }, + * }); + * ``` + */ export function subscriptionStore< Data, Result = Data, diff --git a/packages/vue-urql/src/index.ts b/packages/vue-urql/src/index.ts index c0c76e6501..e217121ff2 100644 --- a/packages/vue-urql/src/index.ts +++ b/packages/vue-urql/src/index.ts @@ -12,14 +12,13 @@ export { useSubscription } from './useSubscription'; export type { UseSubscriptionArgs, UseSubscriptionResponse, - UseSubscriptionState, SubscriptionHandlerArg, SubscriptionHandler, } from './useSubscription'; export { useMutation } from './useMutation'; -export type { UseMutationResponse, UseMutationState } from './useMutation'; +export type { UseMutationResponse } from './useMutation'; import { install } from './useClient'; diff --git a/packages/vue-urql/src/useClient.ts b/packages/vue-urql/src/useClient.ts index 38d1f5a370..32c3d53cd8 100644 --- a/packages/vue-urql/src/useClient.ts +++ b/packages/vue-urql/src/useClient.ts @@ -3,6 +3,34 @@ import { Client, ClientOptions } from '@urql/core'; const clientsPerInstance = new WeakMap<{}, Ref>(); +/** Provides a {@link Client} to a component’s children. + * + * @param opts - {@link ClientOptions}, a {@link Client}, or a reactive ref object of a `Client`. + * + * @remarks + * `provideClient` provides a {@link Client} to `@urql/vue`’s GraphQL + * functions in children components. + * + * Hint: GraphQL functions and {@link useClient} will see the + * provided `Client`, even if `provideClient` has been called + * in the same component’s `setup` function. + * + * @example + * ```ts + * import { provideClient } from '@urql/vue'; + * // All of `@urql/core` is also re-exported by `@urql/vue`: + * import { Client, cacheExchange, fetchExchange } from '@urql/core'; + * + * export default { + * setup() { + * provideClient(new Client({ + * url: 'https://API', + * exchanges: [cacheExchange, fetchExchange], + * })); + * }, + * }; + * ``` + */ export function provideClient(opts: ClientOptions | Client | Ref) { let client: Ref; if (!isRef(opts)) { @@ -20,6 +48,31 @@ export function provideClient(opts: ClientOptions | Client | Ref) { return client.value; } +/** Provides a {@link Client} to a Vue app. + * + * @param app - the Vue {@link App} + * @param opts - {@link ClientOptions}, a {@link Client}, or a reactive ref object of a `Client`. + * + * @remarks + * `install` provides a {@link Client} to `@urql/vue`’s GraphQL + * functions in a Vue app. + * + * @example + * ```ts + * import * as urql from '@urql/vue'; + * // All of `@urql/core` is also re-exported by `@urql/vue`: + * import { cacheExchange, fetchExchange } from '@urql/core'; + * + * import { createApp } from 'vue'; + * import Root from './App.vue'; + * + * const app = createApp(Root); + * app.use(urql, { + * url: 'http://localhost:3000/graphql', + * exchanges: [cacheExchange, fetchExchange], + * }); + * ``` + */ export function install(app: App, opts: ClientOptions | Client | Ref) { let client: Ref; if (!isRef(opts)) { @@ -30,6 +83,18 @@ export function install(app: App, opts: ClientOptions | Client | Ref) { app.provide('$urql', client); } +/** Returns a provided reactive ref object of a {@link Client}. + * + * @remarks + * `useClient` may be called in Vue `setup` functions to retrieve a + * reactive rev object of a {@link Client} that’s previously been + * provided with {@link provideClient} in the current or a parent’s + * `setup` function. + * + * @throws + * In development, if `useClient` is called outside of a Vue `setup` + * function or no {@link Client} was provided, an error will be thrown. + */ export function useClient(): Ref { const instance = getCurrentInstance(); if (process.env.NODE_ENV !== 'production' && !instance) { diff --git a/packages/vue-urql/src/useClientHandle.ts b/packages/vue-urql/src/useClientHandle.ts index dfe1c30baa..4cdca9f9fa 100644 --- a/packages/vue-urql/src/useClientHandle.ts +++ b/packages/vue-urql/src/useClientHandle.ts @@ -20,23 +20,115 @@ import { UseSubscriptionResponse, } from './useSubscription'; +/** Handle to create GraphQL operations outside of Vue’s `setup` functions. + * + * @remarks + * The `ClientHandle` object is created inside a Vue `setup` function but + * allows its methods to be called outside of `setup` functions, delaying + * the creation of GraphQL operations, as an alternative to pausing queries + * or subscriptions. + * + * This is also important when chaining multiple functions inside an + * `async setup()` function. + * + * Hint: If you only need a single, non-updating result and want to execute + * queries programmatically, it may be easier to call the {@link Client.query} + * method. + */ export interface ClientHandle { + /** The {@link Client} that’ll be used to execute GraphQL operations. */ client: Client; + /** Calls {@link useQuery} outside of a synchronous Vue `setup` function. + * + * @param args - a {@link UseQueryArgs} object, to pass a `query`, `variables`, and options. + * @returns a {@link UseQueryResponse} object. + * + * @remarks + * Creates a {@link UseQueryResponse} outside of a synchronous Vue `setup` + * function or when chained in an `async setup()` function. + */ useQuery( args: UseQueryArgs ): UseQueryResponse; + /** Calls {@link useSubscription} outside of a synchronous Vue `setup` function. + * + * @param args - a {@link UseSubscriptionArgs} object, to pass a `query`, `variables`, and options. + * @param handler - optionally, a {@link SubscriptionHandler} function to combine multiple subscription results. + * @returns a {@link UseSubscriptionResponse} object. + * + * @remarks + * Creates a {@link UseSubscriptionResponse} outside of a synchronous Vue `setup` + * function or when chained in an `async setup()` function. + */ useSubscription( args: UseSubscriptionArgs, handler?: SubscriptionHandlerArg ): UseSubscriptionResponse; + /** Calls {@link useMutation} outside of a synchronous Vue `setup` function. + * + * @param query - a GraphQL mutation document which `useMutation` will execute. + * @returns a {@link UseMutationResponse} object. + * + * @remarks + * Creates a {@link UseMutationResponse} outside of a synchronous Vue `setup` + * function or when chained in an `async setup()` function. + */ useMutation( query: TypedDocumentNode | DocumentNode | string ): UseMutationResponse; } +/** Creates a {@link ClientHandle} inside a Vue `setup` function. + * + * @remarks + * `useClientHandle` creates and returns a {@link ClientHandle} + * when called in a Vue `setup` function, which allows queries, + * mutations, and subscriptions to be created _outside_ of + * `setup` functions. + * + * This is also important when chaining multiple functions inside an + * `async setup()` function. + * + * {@link useQuery} and other GraphQL functions must usually + * be created in Vue `setup` functions so they can stop GraphQL + * operations when your component unmounts. However, while they + * queries and subscriptions can be paused, sometimes it’s easier + * to delay the creation of their response objects. + * + * + * @example + * ```ts + * import { ref, computed } from 'vue'; + * import { gql, useClientHandle } from '@urql/vue'; + * + * export default { + * async setup() { + * const handle = useClientHandle(); + * + * const pokemons = await handle.useQuery({ + * query: gql`{ pokemons(limit: 10) { id, name } }`, + * }); + * + * const index = ref(0); + * + * // The `handle` allows another `useQuery` call to now be setup again + * const pokemon = await handle.useQuery({ + * query: gql` + * query ($id: ID!) { + * pokemon(id: $id) { id, name } + * } + * `, + * variables: computed(() => ({ + * id: pokemons.data.value.pokemons[index.value].id, + * }), + * }); + * } + * }; + * ``` + */ export function useClientHandle(): ClientHandle { const client = useClient(); const stops: WatchStopHandle[] = []; diff --git a/packages/vue-urql/src/useMutation.ts b/packages/vue-urql/src/useMutation.ts index 5968b0fe76..6ebb776a3b 100644 --- a/packages/vue-urql/src/useMutation.ts +++ b/packages/vue-urql/src/useMutation.ts @@ -18,24 +18,111 @@ import { import { useClient } from './useClient'; import { unwrapPossibleProxy } from './utils'; -export interface UseMutationState { +/** State of the last mutation executed by {@link useMutation}. + * + * @remarks + * `UseMutationResponse` is returned by {@link useMutation} and + * gives you the {@link OperationResult} of the last executed mutation, + * and a {@link UseMutationResponse.executeMutation} method to + * start mutations. + * + * Even if the mutation document passed to {@link useMutation} changes, + * the state isn’t reset, so you can keep displaying the previous result. + */ +export interface UseMutationResponse { + /** Indicates whether `useMutation` is currently executing a mutation. */ fetching: Ref; + /** Indicates that the mutation result is not fresh. + * + * @remarks + * The `stale` flag is set to `true` when a new result for the mutation + * is expected. + * This is mostly unused for mutations and will rarely affect you, and + * is more relevant for queries. + * + * @see {@link OperationResult.stale} for the source of this value. + */ stale: Ref; + /** Reactive {@link OperationResult.data} for the executed mutation. */ data: Ref; + /** Reactive {@link OperationResult.error} for the executed mutation. */ error: Ref; + /** Reactive {@link OperationResult.extensions} for the executed mutation. */ extensions: Ref | undefined>; + /** Reactive {@link Operation} that the current state is for. + * + * @remarks + * This is the mutation {@link Operation} that has last been executed. + * When {@link UseQueryState.fetching} is `true`, this is the + * last `Operation` that the current state was for. + */ operation: Ref | undefined>; - executeMutation: ( + /** Triggers {@link useMutation} to execute its GraphQL mutation operation. + * + * @param variables - variables using which the mutation will be executed. + * @param context - optionally, context options that will be merged with + * {@link UseMutationArgs.context} and the `Client`’s options. + * @returns the {@link OperationResult} of the mutation. + * + * @remarks + * When called, {@link useMutation} will start the GraphQL mutation + * it currently holds and use the `variables` passed to it. + * + * Once the mutation response comes back from the API, its + * returned promise will resolve to the mutation’s {@link OperationResult} + * and the {@link UseMutationResponse} will be updated with the result. + * + * @example + * ```ts + * const result = useMutation(UpdateTodo); + * const start = async ({ id, title }) => { + * const result = await result.executeMutation({ id, title }); + * }; + */ + executeMutation( variables: V, context?: Partial - ) => Promise>; + ): Promise>; } -export type UseMutationResponse< - T, - V extends AnyVariables = AnyVariables -> = UseMutationState; - +/** Function to create a GraphQL mutation, run by passing variables to {@link UseMutationResponse.executeMutation} + * + * @param query - a GraphQL mutation document which `useMutation` will execute. + * @returns a {@link UseMutationResponse} object. + * + * @remarks + * `useMutation` allows GraphQL mutations to be defined inside Vue `setup` functions, + * and keeps its state after the mutation is started. Mutations can be started by calling + * {@link UseMutationResponse.executeMutation} with variables. + * + * The returned result updates when a mutation is executed and keeps + * track of the last mutation result. + * + * @see {@link https://urql.dev/goto/docs/basics/vue#mutations} for `useMutation` docs. + * + * @example + * ```ts + * import { gql, useMutation } from '@urql/vue'; + * + * const UpdateTodo = gql` + * mutation ($id: ID!, $title: String!) { + * updateTodo(id: $id, title: $title) { + * id, title + * } + * } + * `; + * + * export default { + * setup() { + * const result = useMutation(UpdateTodo); + * const start = async ({ id, title }) => { + * const result = await result.executeMutation({ id, title }); + * }; + * // ... + * }, + * }; + * ``` + */ export function useMutation( query: TypedDocumentNode | DocumentNode | string ): UseMutationResponse { diff --git a/packages/vue-urql/src/useQuery.ts b/packages/vue-urql/src/useQuery.ts index cc22fd5e62..6233944214 100644 --- a/packages/vue-urql/src/useQuery.ts +++ b/packages/vue-urql/src/useQuery.ts @@ -23,33 +23,173 @@ import { unwrapPossibleProxy } from './utils'; type MaybeRef = T | Ref; type MaybeRefObj = { [K in keyof T]: MaybeRef }; +/** Input arguments for the {@link useQuery} function. + * + * @param query - The GraphQL query that `useQuery` executes. + * @param variables - The variables for the GraphQL query that `useQuery` executes. + */ export type UseQueryArgs< Data = any, Variables extends AnyVariables = AnyVariables > = { + /** Updates the {@link RequestPolicy} for the executed GraphQL query operation. + * + * @remarks + * `requestPolicy` modifies the {@link RequestPolicy} of the GraphQL query operation + * that `useQuery` executes, and indicates a caching strategy for cache exchanges. + * + * For example, when set to `'cache-and-network'`, {@link useQuery} will + * receive a cached result with `stale: true` and an API request will be + * sent in the background. + * + * @see {@link OperationContext.requestPolicy} for where this value is set. + */ requestPolicy?: MaybeRef; + /** Updates the {@link OperationContext} for the executed GraphQL query operation. + * + * @remarks + * `context` may be passed to {@link useQuery}, to update the {@link OperationContext} + * of a query operation. This may be used to update the `context` that exchanges + * will receive for a single hook. + * + * @example + * ```ts + * const result = useQuery({ + * query, + * context: { + * additionalTypenames: ['Item'], + * }, + * }); + * ``` + */ context?: MaybeRef>; + /** Prevents {@link useQuery} from automatically executing GraphQL query operations. + * + * @remarks + * `pause` may be set to `true` to stop {@link useQuery} from executing + * automatically. This will pause the query until {@link UseQueryState.resume} + * is called, or, if `pause` is a reactive ref of a boolean, until this + * ref changes to `true`. + * + * @see {@link https://urql.dev/goto/docs/basics/vue#pausing-usequery} for + * documentation on the `pause` option. + */ pause?: MaybeRef; } & MaybeRefObj>; -export type QueryPartialState< - T = any, - V extends AnyVariables = AnyVariables -> = Partial> & { fetching?: boolean }; - +/** State of the current query, your {@link useQuery} function is executing. + * + * @remarks + * `UseQueryState` is returned by {@link useQuery} and + * gives you the updating {@link OperationResult} of + * GraphQL queries. + * + * Each value that is part of the result is wrapped in a reactive ref + * and updates as results come in. + * + * Hint: Even when the query and variables update, the previous state of + * the last result is preserved, which allows you to display the + * previous state, while implementing a loading indicator separately. + */ export interface UseQueryState { + /** Indicates whether `useQuery` is waiting for a new result. + * + * @remarks + * When `useQuery` receives a new query and/or variables, it will + * start executing the new query operation and `fetching` is set to + * `true` until a result arrives. + * + * Hint: This is subtly different than whether the query is actually + * fetching, and doesn’t indicate whether a query is being re-executed + * in the background. For this, see {@link UseQueryState.stale}. + */ fetching: Ref; + /** Indicates that the state is not fresh and a new result will follow. + * + * @remarks + * The `stale` flag is set to `true` when a new result for the query + * is expected and `useQuery` is waiting for it. This may indicate that + * a new request is being requested in the background. + * + * @see {@link OperationResult.stale} for the source of this value. + */ stale: Ref; + /** Reactive {@link OperationResult.data} for the executed query. */ data: Ref; + /** Reactive {@link OperationResult.error} for the executed query. */ error: Ref; + /** Reactive {@link OperationResult.extensions} for the executed query. */ extensions: Ref | undefined>; + /** Reactive {@link Operation} that the current state is for. + * + * @remarks + * This is the {@link Operation} that is currently being executed. + * When {@link UseQueryState.fetching} is `true`, this is the + * last `Operation` that the current state was for. + */ operation: Ref | undefined>; + /** Indicates whether {@link useQuery} is currently paused. + * + * @remarks + * When `useQuery` has been paused, it will stop receiving updates + * from the {@link Client} and won’t execute query operations, until + * {@link UseQueryArgs.pause} becomes `true` or {@link UseQueryState.resume} + * is called. + * + * @see {@link https://urql.dev/goto/docs/basics/vue#pausing-usequery} for + * documentation on the `pause` option. + */ isPaused: Ref; + /** Resumes {@link useQuery} if it’s currently paused. + * + * @remarks + * Resumes or starts {@link useQuery}’s query, if it’s currently paused. + * + * @see {@link https://urql.dev/goto/docs/basics/vue#pausing-usequery} for + * documentation on the `pause` option. + */ resume(): void; + /** Pauses {@link useQuery} to stop it from executing the query. + * + * @remarks + * Pauses {@link useQuery}’s query, which stops it from receiving updates + * from the {@link Client} and to stop the ongoing query operation. + * + * @see {@link https://urql.dev/goto/docs/basics/vue#pausing-usequery} for + * documentation on the `pause` option. + */ pause(): void; + /** Triggers {@link useQuery} to execute a new GraphQL query operation. + * + * @param opts - optionally, context options that will be merged with + * {@link UseQueryArgs.context} and the `Client`’s options. + * + * @remarks + * When called, {@link useQuery} will re-execute the GraphQL query operation + * it currently holds, unless it’s currently paused. + * + * This is useful for re-executing a query and get a new network result, + * by passing a new request policy. + * + * ```ts + * const result = useQuery({ query }); + * + * const refresh = () => { + * // Re-execute the query with a network-only policy, skipping the cache + * result.executeQuery({ requestPolicy: 'network-only' }); + * }; + * ``` + */ executeQuery(opts?: Partial): UseQueryResponse; } +/** Return value of {@link useQuery}, which is an awaitable {@link UseQueryState}. + * + * @remarks + * {@link useQuery} returns a {@link UseQueryState} but may also be + * awaited inside a Vue `async setup()` function. If it’s awaited + * the query is executed before resolving. + */ export type UseQueryResponse< T, V extends AnyVariables = AnyVariables @@ -59,6 +199,41 @@ const watchOptions = { flush: 'pre' as const, }; +/** Function to run a GraphQL query and get reactive GraphQL results. + * + * @param args - a {@link UseQueryArgs} object, to pass a `query`, `variables`, and options. + * @returns a {@link UseQueryResponse} object. + * + * @remarks + * `useQuery` allows GraphQL queries to be defined and executed inside + * Vue `setup` functions. + * Given {@link UseQueryArgs.query}, it executes the GraphQL query with the + * provided {@link Client}. + * + * The returned result’s reactive values update when the `Client` has + * new results for the query, and changes when your input `args` change. + * + * Additionally, `useQuery` may also be awaited inside an `async setup()` + * function to use Vue’s Suspense feature. + * + * @see {@link https://urql.dev/goto/docs/basics/vue#queries} for `useQuery` docs. + * + * @example + * ```ts + * import { gql, useQuery } from '@urql/vue'; + * + * const TodosQuery = gql` + * query { todos { id, title } } + * `; + * + * export default { + * setup() { + * const result = useQuery({ query: TodosQuery }); + * return { data: result.data }; + * }, + * }; + * ``` + */ export function useQuery( args: UseQueryArgs ): UseQueryResponse { diff --git a/packages/vue-urql/src/useSubscription.ts b/packages/vue-urql/src/useSubscription.ts index 0da7c4b7f9..33078eb31a 100644 --- a/packages/vue-urql/src/useSubscription.ts +++ b/packages/vue-urql/src/useSubscription.ts @@ -22,51 +22,214 @@ import { unwrapPossibleProxy } from './utils'; type MaybeRef = Exclude | Ref>; type MaybeRefObj = { [K in keyof T]: MaybeRef }; +/** Input arguments for the {@link useSubscription} function. + * + * @param query - The GraphQL subscription document that `useSubscription` executes. + * @param variables - The variables for the GraphQL subscription that `useSubscription` executes. + */ export type UseSubscriptionArgs< Data = any, Variables extends AnyVariables = AnyVariables > = { + /** Prevents {@link useSubscription} from automatically executing GraphQL subscription operations. + * + * @remarks + * `pause` may be set to `true` to stop {@link useSubscription} from starting + * its subscription automatically. This will pause the subscription until + * {@link UseSubscriptonState.resume} is called, or, if `pause` is a reactive + * ref of a boolean, until this ref changes to `true`. + */ pause?: MaybeRef; + /** Updates the {@link OperationContext} for the executed GraphQL subscription operation. + * + * @remarks + * `context` may be passed to {@link useSubscription}, to update the {@link OperationContext} + * of a subscription operation. This may be used to update the `context` that exchanges + * will receive for a single hook. + * + * @example + * ```ts + * const result = useQuery({ + * query, + * context: { + * additionalTypenames: ['Item'], + * }, + * }); + * ``` + */ context?: MaybeRef>; } & MaybeRefObj>; +/** Combines previous data with an incoming subscription result’s data. + * + * @remarks + * A `SubscriptionHandler` may be passed to {@link useSubscription} to + * aggregate subscription results into a combined {@link UseSubscriptionResponse.data} + * value. + * + * This is useful when a subscription event delivers a single item, while + * you’d like to display a list of events. + * + * @example + * ```ts + * const NotificationsSubscription = gql` + * subscription { newNotification { id, text } } + * `; + * + * const combineNotifications = (notifications = [], data) => { + * return [...notifications, data.newNotification]; + * }; + * + * const result = useSubscription( + * { query: NotificationsSubscription }, + * combineNotifications, + * ); + * ``` + */ export type SubscriptionHandler = (prev: R | undefined, data: T) => R; + +/** A {@link SubscriptionHandler} or a reactive ref of one. */ export type SubscriptionHandlerArg = MaybeRef>; -export interface UseSubscriptionState< +/** State of the current query, your {@link useSubscription} function is executing. + * + * @remarks + * `UseSubscriptionResponse` is returned by {@link useSubscription} and + * gives you the updating {@link OperationResult} of GraphQL subscriptions. + * + * Each value that is part of the result is wrapped in a reactive ref + * and updates as results come in. + * + * Hint: Even when the query and variables update, the prior state of + * the last result is preserved. + */ +export interface UseSubscriptionResponse< T = any, R = T, V extends AnyVariables = AnyVariables > { + /** Indicates whether `useSubscription`’s subscription is active. + * + * @remarks + * When `useSubscription` starts a subscription, the `fetching` flag + * is set to `true` and will remain `true` until the subscription + * completes on the API, or `useSubscription` is paused. + */ fetching: Ref; + /** Indicates that the subscription result is not fresh. + * + * @remarks + * This is mostly unused for subscriptions and will rarely affect you, and + * is more relevant for queries. + * + * @see {@link OperationResult.stale} for the source of this value. + */ stale: Ref; + /** Reactive {@link OperationResult.data} for the executed subscription, or data returned by the handler. + * + * @remarks + * `data` will be set to the last {@link OperationResult.data} value + * received for the subscription. + * + * It will instead be set to the values that {@link SubscriptionHandler} + * returned, if a handler has been passed to {@link useSubscription}. + */ data: Ref; + /** Reactive {@link OperationResult.error} for the executed subscription. */ error: Ref; + /** Reactive {@link OperationResult.extensions} for the executed mutation. */ extensions: Ref | undefined>; + /** Reactive {@link Operation} that the current state is for. + * + * @remarks + * This is the subscription {@link Operation} that is currently active. + * When {@link UseQueryState.fetching} is `true`, this is the + * last `Operation` that the current state was for. + */ operation: Ref | undefined>; + /** Indicates whether {@link useSubscription} is currently paused. + * + * @remarks + * When `useSubscription` has been paused, it will stop receiving updates + * from the {@link Client} and won’t execute the subscription, until + * {@link UseSubscriptionArgs.pause} becomes true or + * {@link UseSubscriptionResponse.resume} is called. + */ isPaused: Ref; + /** Resumes {@link useSubscription} if it’s currently paused. + * + * @remarks + * Resumes or starts {@link useSubscription}’s subscription, if it’s currently paused. + */ resume(): void; + /** Pauses {@link useSubscription} to stop the subscription. + * + * @remarks + * Pauses {@link useSubscription}’s subscription, which stops it + * from receiving updates from the {@link Client} and to stop executing + * the subscription operation. + */ pause(): void; + /** Triggers {@link useQuery} to reexecute a GraphQL subscription operation. + * + * @param opts - optionally, context options that will be merged with + * {@link UseQueryArgs.context} and the `Client`’s options. + * + * @remarks + * When called, {@link useSubscription} will re-execute the GraphQL subscription + * operation it currently holds, unless it’s currently paused. + */ executeSubscription(opts?: Partial): void; } -export type UseSubscriptionResponse< - T = any, - R = T, - V extends AnyVariables = AnyVariables -> = UseSubscriptionState; - const watchOptions = { flush: 'pre' as const, }; +/** Function to run a GraphQL subscription and get reactive GraphQL results. + * + * @param args - a {@link UseSubscriptionArgs} object, to pass a `query`, `variables`, and options. + * @param handler - optionally, a {@link SubscriptionHandler} function to combine multiple subscription results. + * @returns a {@link UseSubscriptionResponse} object. + * + * @remarks + * `useSubscription` allows GraphQL subscriptions to be defined and executed inside + * Vue `setup` functions. + * Given {@link UseSubscriptionArgs.query}, it executes the GraphQL subscription with the + * provided {@link Client}. + * + * The returned result updates when the `Client` has new results + * for the subscription, and `data` is updated with the result’s data + * or with the `data` that a `handler` returns. + * + * @example + * ```ts + * import { gql, useSubscription } from '@urql/vue'; + * + * const NotificationsSubscription = gql` + * subscription { newNotification { id, text } } + * `; + * + * export default { + * setup() { + * const result = useSubscription( + * { query: NotificationsSubscription }, + * function combineNotifications(notifications = [], data) { + * return [...notifications, data.newNotification]; + * }, + * ); + * // ... + * }, + * }; + * ``` + */ export function useSubscription< T = any, R = T, V extends AnyVariables = AnyVariables >( args: UseSubscriptionArgs, - handler?: SubscriptionHandlerArg + handler?: MaybeRef> ): UseSubscriptionResponse { return callUseSubscription(args, handler); } @@ -77,7 +240,7 @@ export function callUseSubscription< V extends AnyVariables = AnyVariables >( _args: UseSubscriptionArgs, - handler?: SubscriptionHandlerArg, + handler?: MaybeRef>, client: Ref = useClient(), stops: WatchStopHandle[] = [] ): UseSubscriptionResponse { @@ -159,7 +322,7 @@ export function callUseSubscription< }, watchOptions) ); - const state: UseSubscriptionState = { + const state: UseSubscriptionResponse = { data, stale, error, @@ -169,7 +332,7 @@ export function callUseSubscription< isPaused, executeSubscription( opts?: Partial - ): UseSubscriptionState { + ): UseSubscriptionResponse { source.value = client.value.executeSubscription(request.value, { ...unwrapPossibleProxy(args.context), ...opts,