@therobot/codegen-vue-apollo
v0.1.1
Published
Generates vue-apollo operations using composition-api, smart-query, or computed mixins
Downloads
6
Readme
Vue Apollo Codegen
# src/allPosts.gql
query allPosts($id: ID!) {
allPosts(categoryId: $id) {
id
title
}
}
# src/components/Post/addComment.gql
mutation addComment($postId: ID!, $content: String!) {
addComment(postId: $postId)
}
Usage
config:
federation: true
noSchemaStitching: true
hooks:
afterAllFileWrite:
- 'npx prettier --write "./src/generated/**/*" # ignore args'
schema:
- ../packages/*-server/**/*.gql # server schema
generates:
./src/generated/index.ts:
schema:
- ./src/apollo/clientSchema.gql
documents: './src/**/*.gql'
config:
mixinComponents: false # default: true
smartQueryDecorators: false # default: true
useComposable: true # default: false
partialVariablesOnComposableMutations: true # default: true
# default: false - compatibility
# should use typescript-graphql-files-modules
noDeclareOperationModules: true
noFragmentUtils: true # default: false
noQueryUtils: true # default: false
# from ClientSideBasePluginConfig
noGraphQLTag: true # default: false
plugins:
- add: "import { DocumentNode } from 'graphql'"
- time
- add: '/* eslint-disable */'
- typescript
- typescript-operations
- '@therobot/codegen-vue-apollo'
./src/generated/types.d.ts:
documents: './src/**/*.gql'
plugins:
- typescript-graphql-files-modules
in case of
Cannot use GraphQLScalarType "ID" from another module or realm
runenv NODE_ENV=production graphql-codegen
Composable Partial
TypeScript will scream at you when using useMutation
without proper variables.
Since mutations, not called instantly, it should be possible to pass variables on mutate
call. And this is where TypeScript should be concerned.
mutation learn($questionId: ID!, $answerId: ID) {
learn(answerId: $answerId, questionId: $questionId) @client {
id
score
previousScore
}
}
const { mutate } = useLearnMutation({
// variables, not required
variables: {
questionId, // also not required
},
})
mutate({
// '{}' is not assignable
// 'questionId' required in type 'LearnMutationVariables'.
})
Mixins and Decorators
This was my starting point for this plugin. As soon as composable got released, i stopped thinking about this. Was writing while learning typescript, on my first vue-typescript project.
Mixins should be usable in non-ts non-class based components.
import { AllPostsSmartQuery, AllPostsQuery, AddCommentMutationMixin } from '@generated'
import { Component, Mixins } from 'vue-property-decorator'
@Component({ name: 'PostList' })
export default class PostList extends Mixins(AddCommentMutationMixin) {
// if you need proper 'this' inside hooks (result, update, skip)
// add 'this type' - PostList
@AllPostsSmartQuery<PostList>({
variables() {
return { id: 'DEADBEEF' }
},
})
public allPosts: AllPostsQuery['allPosts']
public addComment() {
this.addCommentMutation({
variables: {
content: 'Hello World',
},
})
}
}
Templates
Copied from this readme, using comments as markers.
// vue-apollo-query:begin
export const $NAME$SmartQuery = <Component = any>(
options?: Omit<VueApolloQueryDefinitionPatched<Component, $RESULT_TYPE$, $VARIABLES_TYPE$>, 'query'>
) =>
SmartQuery<Component, $RESULT_TYPE$, $VARIABLES_TYPE$>({
query: $DOCUMENT$,
...options,
})
// vue-apollo-query:end
// query-utils:begin
export const useCache$NAME$$OPERATION_TYPE$ = (
options: Omit<ProxyQuery<$RESULT_TYPE$, $VARIABLES_TYPE$>, 'query'>
) => proxyQuery<$RESULT_TYPE$, $VARIABLES_TYPE$>({ query: $DOCUMENT$, ...options })
// query-utils:end
// vue-apollo-mutation-mixin:begin
@Component({ name: '$NAME$MutationMixin' })
export class $NAME$MutationMixin extends Vue {
public $OPERATION_NAME$Mutation(
options: Omit<VueApolloMutationOptions<$RESULT_TYPE$, $VARIABLES_TYPE$>, 'mutation'>
): Promise<FetchResult<$RESULT_TYPE$>> {
return this.$apollo.mutate<$RESULT_TYPE$, $VARIABLES_TYPE$>({
...options,
mutation: $DOCUMENT$,
})
}
}
// vue-apollo-mutation-mixin:end
// fragment-utils:begin
export const fragment$NAME$ = (
options: Omit<FragmentResolver<$TYPE$>, 'type' | 'fragment' | 'fragmentName'>
) =>
fragmentResolver<$TYPE$>({
...options,
fragment: $DOCUMENT$,
type: '$ON_TYPE$',
fragmentName: '$NAME$',
})
// fragment-utils:end
// vue-apollo-query-mixin:begin
@Component({ name: '$NAME$QueryMixin' })
export class $NAME$QueryMixin extends Vue {
public $OPERATION_NAME$Query(
options: Omit<VueQueryOptions<$VARIABLES_TYPE$>, 'query'>
): Promise<FetchResult<$RESULT_TYPE$>> {
return this.$apollo.query<$RESULT_TYPE$, $VARIABLES_TYPE$>({
...options,
query: $DOCUMENT$,
})
}
}
// vue-apollo-query-mixin:end
Vue-Apollo v4 composable
// vue-apollo-query-composable:begin
export const use$NAME$$OPERATION_TYPE$ = (
variables: $VARIABLES_TYPE$ | Ref<$VARIABLES_TYPE$> | ReactiveFunction<$VARIABLES_TYPE$> = {},
options:
| UseQueryOptions<$RESULT_TYPE$, $VARIABLES_TYPE$>
| Ref<UseQueryOptions<$RESULT_TYPE$, $VARIABLES_TYPE$>>
| ReactiveFunction<UseQueryOptions<$RESULT_TYPE$, $VARIABLES_TYPE$>> = {}
) => useQuery<$RESULT_TYPE$, $VARIABLES_TYPE$>($DOCUMENT$, variables, options)
// vue-apollo-query-composable:end
// vue-apollo-query-composable-require-variables:begin
export const use$NAME$$OPERATION_TYPE$ = (
variables: $VARIABLES_TYPE$ | Ref<$VARIABLES_TYPE$> | ReactiveFunction<$VARIABLES_TYPE$>,
options:
| UseQueryOptions<$RESULT_TYPE$, $VARIABLES_TYPE$>
| Ref<UseQueryOptions<$RESULT_TYPE$, $VARIABLES_TYPE$>>
| ReactiveFunction<UseQueryOptions<$RESULT_TYPE$, $VARIABLES_TYPE$>> = {}
) => useQuery<$RESULT_TYPE$, $VARIABLES_TYPE$>($DOCUMENT$, variables, options)
// vue-apollo-query-composable-require-variables:end
// composable-mutation-partial-variables:begin
export const use$NAME$$OPERATION_TYPE$ = (
options:
| UseMutationOptions<$RESULT_TYPE$, Partial<$VARIABLES_TYPE$>>
| ReactiveFunction<UseMutationOptions<$RESULT_TYPE$, Partial<$VARIABLES_TYPE$>>> = { variables: {} }
): UseMutationReturn<$RESULT_TYPE$, $VARIABLES_TYPE$> =>
useMutation<$RESULT_TYPE$, Partial<$VARIABLES_TYPE$>>($DOCUMENT$, { variables: {}, ...options })
// composable-mutation-partial-variables:end
// vue-apollo-mutation-composable:begin
export const use$NAME$$OPERATION_TYPE$ = (
options:
| UseMutationOptions<$RESULT_TYPE$, $VARIABLES_TYPE$>
| ReactiveFunction<UseMutationOptions<$RESULT_TYPE$, $VARIABLES_TYPE$>> = {}
) => useMutation<$RESULT_TYPE$, $VARIABLES_TYPE$>($DOCUMENT$, options)
// vue-apollo-mutation-composable:end
Bunch of common definitions
// vue-apollo-common-composable:begin
import { Ref, PropType } from '@vue/composition-api'
// Maybe is generated by graphql-codegen, and can be overridden in codegen.yml
export type MaybeProp<T> = PropType<Maybe<T>>
// prettier-ignore
import { useQuery, useMutation, UseQueryOptions, UseMutationReturn, UseMutationOptions } from '@vue/apollo-composable'
type ReactiveFunction<TParam> = () => TParam
// vue-apollo-common-composable:end
// vue-apollo-common-class:begin
import Vue from 'vue'
import { MutationOptions, QueryOptions } from 'apollo-client'
import { VueApolloQueryDefinition, VueApolloSubscribeToMoreOptions } from 'vue-apollo/types/options'
import Component, { createDecorator, VueDecorator } from 'vue-class-component'
interface ClientOptions {
client?: string
}
type VueApolloMutationOptions<R, V> = MutationOptions<R, V> & ClientOptions
function SmartQuery<C = any, R = any, V = any>(
options: VueApolloQueryDefinitionPatched<C, R, V>
): VueDecorator {
return createDecorator((componentOptions: any, k: string) => {
componentOptions.apollo = componentOptions.apollo || {}
componentOptions.apollo[k] = options
})
}
type VueQueryOptions<V> = QueryOptions<V> & ClientOptions
type OverrideThis<F, T> = F extends (...args: infer A) => infer B ? (this: T, ...args: A) => B : F
type OverrideAllThis<O, T> = {
[key in keyof O]: OverrideThis<O[key], T>
}
type VueApolloQueryDefinitionWithoutVariablesAndSubscribeToMore<R = any, V = any> = Omit<
VueApolloQueryDefinition<R, V>,
'subscribeToMore' | 'variables'
>
type SubscribeToMoreOptionsPatched<C, R, V> = OverrideAllThis<
Omit<VueApolloSubscribeToMoreOptions<R, V>, 'updateQuery' | 'variables'>,
C
> & {
variables?: (this: C) => any
updateQuery?: UpdateQueryFn<C, R, any, any>
}
type UpdateQueryFn<C = any, R = any, TSubscriptionVariables = any, TSubscriptionData = any> = (
this: C,
previousQueryResult: R,
options: {
subscriptionData: { data: TSubscriptionData }
variables?: TSubscriptionVariables
}
) => R
export interface VueApolloQueryDefinitionPatched<C = any, R = any, V = any>
extends OverrideAllThis<VueApolloQueryDefinitionWithoutVariablesAndSubscribeToMore<R, V>, C> {
variables?: (this: C) => V | V
subscribeToMore?: SubscribeToMoreOptionsPatched<C, R, V> | Array<SubscribeToMoreOptionsPatched<C, R, V>>
}
// vue-apollo-common-class:end
// vue-apollo-common:begin
import { DataProxy } from 'apollo-cache'
import { FetchResult } from 'apollo-link'
// vue-apollo-common:end
// query-utils-common:begin
interface ProxyQuery<R = any, V = any> {
cache: DataProxy
query: DocumentNode
variables?: V
reducer?: (source: R) => R
patch?: Partial<R>
}
export const proxyQuery = <R, V>(o: ProxyQuery<R, V>): R | null => {
const old = o.cache.readQuery<R, V>({
query: o.query,
variables: o.variables,
})
const result = o.patch && old ? { ...old, ...o.patch } : old
const data = o.reducer && result ? o.reducer(result) : result
o.cache.writeQuery({
data,
query: o.query,
variables: o.variables,
})
return data
}
// query-utils-common:end
// fragment-utils-common:begin
interface FragmentResolver<T> {
fragment: DocumentNode
fragmentName?: string
/** convenience alias for {source.__typename} */
type?: string
source: Partial<T>
patch?: Partial<T>
reducer?: (source: Partial<T> | T) => T
context: {
getCacheKey?: (result: any) => string | null
cache: DataProxy
}
onError?: (error: Error) => void
}
export const fragmentResolver = <T extends { id?: string; _id?: string; __typename?: string }>({
type,
source,
patch,
fragment,
fragmentName,
reducer,
context: { getCacheKey, cache },
onError,
}: FragmentResolver<T>): Partial<T> | null => {
const fragmentSource = { __typename: type, ...source } as Partial<T>
const id = getCacheKey
? getCacheKey(fragmentSource)
: `${fragmentSource.__typename}:${fragmentSource.id || fragmentSource._id}`
if (id) {
let oldData = null
try {
oldData = cache.readFragment<T>({ id, fragment, fragmentName })
} catch (e) {
if (onError) {
onError(e)
}
}
const patched =
oldData && patch
? { ...oldData, ...patch }
: patch
? { ...fragmentSource, ...patch }
: oldData || fragmentSource
const data = reducer ? reducer(patched) : patched
if (data !== null) {
cache.writeFragment<Partial<T>>({ fragment, fragmentName, data, id })
}
return data && type ? { __typename: type, ...data } : data
}
return null
}
// fragment-utils-common:end
MAYBE: Mutation Decorator
export function Mutate<C = any, R = any, V = any>(options: VueApolloMutationOptionsPatched<C, R, V>) {
// tslint:disable-next-line
return function(_target: Vue, _key: string, descriptor: any) {
const original = descriptor.value
descriptor.value = function apolloMutator(variables?: any) {
const mutationOptions = !variables
? options
: typeof options.variables === 'function'
? {
...options,
variables: options.variables.apply(this, [variables]),
}
: { ...options, variables }
return this.$apollo
.mutate(mutationOptions)
.then((result: FetchResult<R>) => original.apply(this, [variables, result]))
.catch((networkError: Error) => original.apply(this, [variables, { networkError }]))
}
}
}
type VueApolloMutationOptionsWithoutVariables<R = any, V = any> = Omit<
VueApolloMutationOptions<R, V>,
'variables'
>
interface VueApolloMutationOptionsPatched<C = any, R = any, V = any>
extends OverrideAllThis<VueApolloMutationOptionsWithoutVariables<R, V>, C> {
variables?: (this: C, ...args: any) => V | V
}