Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.

fix(vue): fix variables typing (#3734)

+5
.changeset/short-crews-retire.md
···
···
+
---
+
'@urql/vue': minor
+
---
+
+
Fix regression breaking `variables` typing
+7 -4
packages/vue-urql/src/useMutation.ts
···
} from '@urql/core';
import { useClient } from './useClient';
-
import type { MaybeRef } from './utils';
-
import { createRequestWithArgs, useRequestState } from './utils';
/** State of the last mutation executed by {@link useMutation}.
*
···
}
export function callUseMutation<T = any, V extends AnyVariables = AnyVariables>(
-
query: MaybeRef<DocumentInput<T, V>>,
client: Ref<Client> = useClient()
): UseMutationResponse<T, V> {
const data: Ref<T | undefined> = shallowRef();
···
return pipe(
client.value.executeMutation<T, V>(
-
createRequestWithArgs({ query, variables }),
context || {}
),
onPush(result => {
···
} from '@urql/core';
import { useClient } from './useClient';
+
import {
+
createRequestWithArgs,
+
type MaybeRefOrGetter,
+
useRequestState,
+
} from './utils';
/** State of the last mutation executed by {@link useMutation}.
*
···
}
export function callUseMutation<T = any, V extends AnyVariables = AnyVariables>(
+
query: MaybeRefOrGetter<DocumentInput<T, V>>,
client: Ref<Client> = useClient()
): UseMutationResponse<T, V> {
const data: Ref<T | undefined> = shallowRef();
···
return pipe(
client.value.executeMutation<T, V>(
+
createRequestWithArgs<T, V>({ query, variables }),
context || {}
),
onPush(result => {
+39 -99
packages/vue-urql/src/useQuery.test.ts
···
);
});
-
it('runs a query with different variables', async () => {
-
const simpleVariables = {
-
null: null,
-
NaN: NaN,
-
empty: '',
-
bool: false,
-
int: 1,
-
float: 1.1,
-
string: 'string',
-
blob: new Blob(),
-
date: new Date(),
-
};
-
-
const variablesSet = {
-
func: () => 'func',
-
ref: ref('ref'),
-
computed: computed(() => 'computed'),
-
...simpleVariables,
-
};
-
-
const variablesSetUnwrapped = {
-
func: 'func',
-
ref: 'ref',
-
computed: 'computed',
-
...simpleVariables,
-
};
-
-
const { query$ } = createQuery({
-
query: ref('{ test }'),
-
variables: {
-
...variablesSet,
-
nested: variablesSet,
-
array: [variablesSet],
-
},
-
});
-
-
await query$;
-
-
expect(query$.operation.value?.variables).toStrictEqual({
-
...variablesSetUnwrapped,
-
nested: variablesSetUnwrapped,
-
array: [variablesSetUnwrapped],
-
});
-
});
-
it('reacts to ref variables changing', async () => {
const variables = ref({ prop: 1 });
···
expect(query$.operation.value).toHaveProperty('variables.prop', 3);
});
-
it('reacts to nested ref variables changing', async () => {
-
const prop = ref(1);
const { executeQuery, query$ } = createQuery({
query: ref('{ test }'),
-
variables: { prop },
});
await query$;
expect(executeQuery).toHaveBeenCalledTimes(1);
expect(query$.operation.value).toHaveProperty('variables.prop', 1);
-
prop.value++;
await query$;
expect(executeQuery).toHaveBeenCalledTimes(2);
expect(query$.operation.value).toHaveProperty('variables.prop', 2);
});
-
it('reacts to deep nested ref variables changing', async () => {
-
const prop = ref(1);
const { executeQuery, query$ } = createQuery({
query: ref('{ test }'),
-
variables: { deep: { nested: { prop } } },
});
await query$;
expect(executeQuery).toHaveBeenCalledTimes(1);
-
expect(query$.operation.value).toHaveProperty(
-
'variables.deep.nested.prop',
-
1
-
);
-
prop.value++;
await query$;
expect(executeQuery).toHaveBeenCalledTimes(2);
-
expect(query$.operation.value).toHaveProperty(
-
'variables.deep.nested.prop',
-
2
-
);
});
it('reacts to reactive variables changing', async () => {
···
});
it('reacts to computed variables changing', async () => {
-
const prop = ref(1);
-
const prop2 = ref(1);
const variables = computed(() => ({
-
prop: prop.value,
-
deep: { nested: { prop2 } },
}));
const { executeQuery, query$ } = createQuery({
···
expect(executeQuery).toHaveBeenCalledTimes(1);
expect(query$.operation.value).toHaveProperty('variables.prop', 1);
-
prop.value++;
await query$;
expect(executeQuery).toHaveBeenCalledTimes(2);
expect(query$.operation.value).toHaveProperty('variables.prop', 2);
-
prop2.value++;
await query$;
expect(executeQuery).toHaveBeenCalledTimes(3);
expect(query$.operation.value).toHaveProperty(
-
'variables.deep.nested.prop2',
-
2
-
);
-
});
-
-
it('reacts to callback variables changing', async () => {
-
const prop = ref(1);
-
const prop2 = ref(1);
-
const variables = () => ({
-
prop: prop.value,
-
deep: { nested: { prop2 } },
-
});
-
-
const { executeQuery, query$ } = createQuery({
-
query: ref('{ test }'),
-
variables,
-
});
-
-
await query$;
-
expect(executeQuery).toHaveBeenCalledTimes(1);
-
expect(query$.operation.value).toHaveProperty('variables.prop', 1);
-
-
prop.value++;
-
await query$;
-
expect(executeQuery).toHaveBeenCalledTimes(2);
-
expect(query$.operation.value).toHaveProperty('variables.prop', 2);
-
-
prop2.value++;
-
await query$;
-
expect(executeQuery).toHaveBeenCalledTimes(3);
-
expect(query$.operation.value).toHaveProperty(
-
'variables.deep.nested.prop2',
2
);
});
···
);
});
it('reacts to ref variables changing', async () => {
const variables = ref({ prop: 1 });
···
expect(query$.operation.value).toHaveProperty('variables.prop', 3);
});
+
it('reacts to ref variables changing', async () => {
+
const foo = ref(1);
+
const bar = ref(1);
const { executeQuery, query$ } = createQuery({
query: ref('{ test }'),
+
variables: ref({
+
prop: foo,
+
nested: {
+
prop: bar,
+
},
+
}),
});
await query$;
expect(executeQuery).toHaveBeenCalledTimes(1);
expect(query$.operation.value).toHaveProperty('variables.prop', 1);
+
foo.value++;
await query$;
expect(executeQuery).toHaveBeenCalledTimes(2);
expect(query$.operation.value).toHaveProperty('variables.prop', 2);
+
+
bar.value++;
+
await query$;
+
expect(executeQuery).toHaveBeenCalledTimes(3);
+
expect(query$.operation.value).toHaveProperty('variables.nested.prop', 2);
});
+
it('reacts to getter variables changing', async () => {
+
const foo = ref(1);
+
const bar = ref(1);
const { executeQuery, query$ } = createQuery({
query: ref('{ test }'),
+
variables: () => ({
+
prop: foo.value,
+
nested: {
+
prop: bar.value,
+
},
+
}),
});
await query$;
expect(executeQuery).toHaveBeenCalledTimes(1);
+
expect(query$.operation.value).toHaveProperty('variables.prop', 1);
+
foo.value++;
await query$;
expect(executeQuery).toHaveBeenCalledTimes(2);
+
expect(query$.operation.value).toHaveProperty('variables.prop', 2);
+
+
bar.value++;
+
await query$;
+
expect(executeQuery).toHaveBeenCalledTimes(3);
+
expect(query$.operation.value).toHaveProperty('variables.nested.prop', 2);
});
it('reacts to reactive variables changing', async () => {
···
});
it('reacts to computed variables changing', async () => {
+
const foo = ref(1);
+
const bar = ref(1);
const variables = computed(() => ({
+
prop: foo.value,
+
deep: { nested: { prop: bar.value } },
}));
const { executeQuery, query$ } = createQuery({
···
expect(executeQuery).toHaveBeenCalledTimes(1);
expect(query$.operation.value).toHaveProperty('variables.prop', 1);
+
foo.value++;
await query$;
expect(executeQuery).toHaveBeenCalledTimes(2);
expect(query$.operation.value).toHaveProperty('variables.prop', 2);
+
bar.value++;
await query$;
expect(executeQuery).toHaveBeenCalledTimes(3);
expect(query$.operation.value).toHaveProperty(
+
'variables.deep.nested.prop',
2
);
});
+9 -10
packages/vue-urql/src/useQuery.ts
···
import { useClient } from './useClient';
-
import type { MaybeRef, MaybeRefObj } from './utils';
import { useRequestState, useClientState } from './utils';
/** Input arguments for the {@link useQuery} function.
···
*
* @see {@link OperationContext.requestPolicy} for where this value is set.
*/
-
requestPolicy?: MaybeRef<RequestPolicy>;
/** Updates the {@link OperationContext} for the executed GraphQL query operation.
*
* @remarks
···
* });
* ```
*/
-
context?: MaybeRef<Partial<OperationContext>>;
/** Prevents {@link useQuery} from automatically executing GraphQL query operations.
*
* @remarks
···
* @see {@link https://urql.dev/goto/docs/basics/vue#pausing-usequery} for
* documentation on the `pause` option.
*/
-
pause?: MaybeRef<boolean>;
-
} & MaybeRefObj<GraphQLRequestParams<Data, MaybeRefObj<Variables>>>;
/** State of the current query, your {@link useQuery} function is executing.
*
···
const { fetching, operation, extensions, stale, error, hasNext } =
useRequestState<T, V>();
-
const { isPaused, source, pause, resume, execute, teardown } = useClientState(
-
args,
-
client,
-
'executeQuery'
-
);
const teardownQuery = watchEffect(
onInvalidate => {
···
import { useClient } from './useClient';
+
import type { MaybeRefOrGetter, MaybeRefOrGetterObj } from './utils';
import { useRequestState, useClientState } from './utils';
/** Input arguments for the {@link useQuery} function.
···
*
* @see {@link OperationContext.requestPolicy} for where this value is set.
*/
+
requestPolicy?: MaybeRefOrGetter<RequestPolicy>;
/** Updates the {@link OperationContext} for the executed GraphQL query operation.
*
* @remarks
···
* });
* ```
*/
+
context?: MaybeRefOrGetter<Partial<OperationContext>>;
/** Prevents {@link useQuery} from automatically executing GraphQL query operations.
*
* @remarks
···
* @see {@link https://urql.dev/goto/docs/basics/vue#pausing-usequery} for
* documentation on the `pause` option.
*/
+
pause?: MaybeRefOrGetter<boolean>;
+
} & MaybeRefOrGetterObj<GraphQLRequestParams<Data, Variables>>;
/** State of the current query, your {@link useQuery} function is executing.
*
···
const { fetching, operation, extensions, stale, error, hasNext } =
useRequestState<T, V>();
+
const { isPaused, source, pause, resume, execute, teardown } = useClientState<
+
T,
+
V
+
>(args, client, 'executeQuery');
const teardownQuery = watchEffect(
onInvalidate => {
+8 -9
packages/vue-urql/src/useSubscription.ts
···
import { useClient } from './useClient';
-
import type { MaybeRef, MaybeRefObj } from './utils';
import { useRequestState, useClientState } from './utils';
/** Input arguments for the {@link useSubscription} function.
···
* {@link UseSubscriptionResponse.resume} is called, or, if `pause` is a reactive
* ref of a boolean, until this ref changes to `true`.
*/
-
pause?: MaybeRef<boolean>;
/** Updates the {@link OperationContext} for the executed GraphQL subscription operation.
*
* @remarks
···
* });
* ```
*/
-
context?: MaybeRef<Partial<OperationContext>>;
-
} & MaybeRefObj<GraphQLRequestParams<Data, MaybeRefObj<Variables>>>;
/** Combines previous data with an incoming subscription result’s data.
*
···
V
>();
-
const { isPaused, source, pause, resume, execute, teardown } = useClientState(
-
args,
-
client,
-
'executeSubscription'
-
);
const teardownSubscription = watchEffect(onInvalidate => {
if (source.value) {
···
import { useClient } from './useClient';
+
import type { MaybeRefOrGetter, MaybeRefOrGetterObj } from './utils';
import { useRequestState, useClientState } from './utils';
/** Input arguments for the {@link useSubscription} function.
···
* {@link UseSubscriptionResponse.resume} is called, or, if `pause` is a reactive
* ref of a boolean, until this ref changes to `true`.
*/
+
pause?: MaybeRefOrGetter<boolean>;
/** Updates the {@link OperationContext} for the executed GraphQL subscription operation.
*
* @remarks
···
* });
* ```
*/
+
context?: MaybeRefOrGetter<Partial<OperationContext>>;
+
} & MaybeRefOrGetterObj<GraphQLRequestParams<Data, Variables>>;
/** Combines previous data with an incoming subscription result’s data.
*
···
V
>();
+
const { isPaused, source, pause, resume, execute, teardown } = useClientState<
+
T,
+
V
+
>(args, client, 'executeSubscription');
const teardownSubscription = watchEffect(onInvalidate => {
if (source.value) {
+20 -63
packages/vue-urql/src/utils.ts
···
Client,
CombinedError,
DocumentInput,
Operation,
OperationContext,
OperationResult,
OperationResultSource,
} from '@urql/core';
import { createRequest } from '@urql/core';
-
import type { Ref } from 'vue';
import { watchEffect, isReadonly, computed, ref, shallowRef, isRef } from 'vue';
import type { UseSubscriptionArgs } from './useSubscription';
import type { UseQueryArgs } from './useQuery';
-
export type MaybeRef<T> = T | (() => T) | Ref<T>;
-
export type MaybeRefObj<T> = T extends {}
-
? { [K in keyof T]: MaybeRef<T[K]> }
-
: T;
-
-
const unwrap = <T>(maybeRef: MaybeRef<T>): T =>
-
typeof maybeRef === 'function'
-
? (maybeRef as () => T)()
-
: maybeRef != null && isRef(maybeRef)
-
? maybeRef.value
-
: maybeRef;
-
-
const isPlainObject = (value: any): boolean => {
-
if (typeof value !== 'object' || value === null) return false;
-
return (
-
value.constructor &&
-
Object.getPrototypeOf(value).constructor === Object.prototype.constructor
-
);
-
};
-
export const isArray = Array.isArray;
-
const unwrapDeeply = <T>(input: T): T => {
-
input = isRef(input) ? (input.value as T) : input;
-
if (typeof input === 'function') {
-
return unwrapDeeply(input()) as T;
-
}
-
-
if (input && typeof input === 'object') {
-
if (isArray(input)) {
-
const length = input.length;
-
const out = new Array(length) as T;
-
let i = 0;
-
for (; i < length; i++) {
-
out[i] = unwrapDeeply(input[i]);
-
}
-
-
return out;
-
} else if (isPlainObject(input)) {
-
const keys = Object.keys(input);
-
const length = keys.length;
-
let i = 0;
-
let key: string;
-
const out = {} as T;
-
-
for (; i < length; i++) {
-
key = keys[i];
-
out[key] = unwrapDeeply(input[key]);
-
}
-
-
return out;
-
}
-
}
-
-
return input;
-
};
export const createRequestWithArgs = <
T = any,
···
args:
| UseQueryArgs<T, V>
| UseSubscriptionArgs<T, V>
-
| { query: MaybeRef<DocumentInput<T, V>>; variables: V }
-
) => {
return createRequest<T, V>(
-
unwrap(args.query),
-
unwrapDeeply(args.variables) as V
);
};
···
? computed(args.pause)
: ref(!!args.pause);
-
const request = computed(() => createRequestWithArgs(args));
const requestOptions = computed(() => {
return 'requestPolicy' in args
? {
-
requestPolicy: unwrap(args.requestPolicy),
-
...unwrap(args.context),
}
: {
-
...unwrap(args.context),
};
});
···
Client,
CombinedError,
DocumentInput,
+
GraphQLRequest,
Operation,
OperationContext,
OperationResult,
OperationResultSource,
} from '@urql/core';
import { createRequest } from '@urql/core';
+
import { type Ref, unref } from 'vue';
import { watchEffect, isReadonly, computed, ref, shallowRef, isRef } from 'vue';
import type { UseSubscriptionArgs } from './useSubscription';
import type { UseQueryArgs } from './useQuery';
+
export type MaybeRefOrGetter<T> = T | (() => T) | Ref<T>;
+
export type MaybeRefOrGetterObj<T extends {}> =
+
T extends Record<string, never>
+
? T
+
: { [K in keyof T]: MaybeRefOrGetter<T[K]> };
+
const isFunction = <T>(val: MaybeRefOrGetter<T>): val is () => T =>
+
typeof val === 'function';
+
const toValue = <T>(source: MaybeRefOrGetter<T>): T =>
+
isFunction(source) ? source() : unref(source);
export const createRequestWithArgs = <
T = any,
···
args:
| UseQueryArgs<T, V>
| UseSubscriptionArgs<T, V>
+
| { query: MaybeRefOrGetter<DocumentInput<T, V>>; variables: V }
+
): GraphQLRequest<T, V> => {
+
const _args = toValue(args);
return createRequest<T, V>(
+
toValue(_args.query),
+
toValue(_args.variables as MaybeRefOrGetter<V>)
);
};
···
? computed(args.pause)
: ref(!!args.pause);
+
const request = computed(() => createRequestWithArgs<T, V>(args));
const requestOptions = computed(() => {
return 'requestPolicy' in args
? {
+
requestPolicy: toValue(args.requestPolicy),
+
...toValue(args.context),
}
: {
+
...toValue(args.context),
};
});